feat: add hazardous goods declaration feature

- Introduced HazardDeclarationPanel component to display dangerous goods declaration details.
- Updated URL constants to include CLEARANCE_PROCEED endpoint for re-requesting operations.
- Enhanced permissions to include hazardous approval roles for contract approvals.
- Integrated HazardDeclarationPanel into ContractRequestDetailPage and ContractClearanceDetailPage.
- Added proceedToOperation method in bookings service for handling operation re-requests.
- Updated contract forms and schemas to include hazard class and UN number fields.
- Implemented validation for hazardous contracts in the contract creation flow.
- Added expiry notice functionality for contracts nearing validity end.
- Created tests for expiry notice calculations and labels.
- Updated UI components to reflect hazardous cargo information and validation errors.
This commit is contained in:
Marshal
2026-07-25 21:10:09 +00:00
parent fde5e6de4b
commit 9a1c8e5603
41 changed files with 1663 additions and 99 deletions

View File

@@ -107,6 +107,30 @@ export class ContractsRepository extends BaseRepository<Contract> {
return result.affected ?? 0;
}
/**
* Live contracts whose validity ends between `days` and `days + 1` days from
* now — the slice the daily expiry-reminder cron warns about. The window is
* rolling and exactly 24h wide, so consecutive daily runs tile it without
* gaps or overlaps: each contract is picked up by exactly one run and the
* customer is notified once, with no "already reminded" flag to store.
*/
async findExpiringInDays(days: number): Promise<Contract[]> {
const now = Date.now();
return this.repository
.createQueryBuilder('contract')
.where('contract.deleted_at IS NULL')
.andWhere('contract.status NOT IN (:...terminal)', {
terminal: TERMINAL_CONTRACT_STATUSES,
})
.andWhere('contract.contract_valid_until >= :from', {
from: new Date(now + days * 86_400_000),
})
.andWhere('contract.contract_valid_until < :to', {
to: new Date(now + (days + 1) * 86_400_000),
})
.getMany();
}
/** Find a contract by ID with all child collections, service type, company and files. */
async findByIdWithRelations(id: string): Promise<Contract | null> {
if (!id) return null;