mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 12:58:13 +00:00
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:
@@ -0,0 +1,47 @@
|
||||
import { ForbiddenException } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
assertCanApproveContractStep,
|
||||
canEditContractStep,
|
||||
} from './freight-permission.util';
|
||||
import { FREIGHT_PERMS } from '../seed/freight-permissions.registry';
|
||||
|
||||
const userWith = (...keys: string[]) => ({
|
||||
permissions: keys.map((key) => ({ key })),
|
||||
});
|
||||
|
||||
describe('hazardous contract approval steps', () => {
|
||||
it('rejects an approver who only holds ordinary contract-approve permissions', () => {
|
||||
// The blanket "any contract approve permission" fallback must NOT reach
|
||||
// dangerous goods — that is the whole point of the dedicated desks.
|
||||
const lineStaff = userWith(FREIGHT_PERMS.contracts.approveLineStaff);
|
||||
|
||||
expect(() =>
|
||||
assertCanApproveContractStep(lineStaff, 'HAZARDOUS_APPROVAL_ONE'),
|
||||
).toThrow(ForbiddenException);
|
||||
expect(canEditContractStep(lineStaff, 'HAZARDOUS_APPROVAL_ONE')).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts only the matching hazardous permission', () => {
|
||||
const first = userWith(FREIGHT_PERMS.contracts.hazardousApprovalOne);
|
||||
|
||||
expect(() =>
|
||||
assertCanApproveContractStep(first, 'HAZARDOUS_APPROVAL_ONE'),
|
||||
).not.toThrow();
|
||||
// Holding step one does not confer step two.
|
||||
expect(() =>
|
||||
assertCanApproveContractStep(first, 'HAZARDOUS_APPROVAL_TWO'),
|
||||
).toThrow(ForbiddenException);
|
||||
});
|
||||
|
||||
it('does not let a hazardous approver stand in for the commercial chain', () => {
|
||||
const hazardOnly = userWith(
|
||||
FREIGHT_PERMS.contracts.hazardousApprovalOne,
|
||||
FREIGHT_PERMS.contracts.hazardousApprovalTwo,
|
||||
);
|
||||
|
||||
expect(() => assertCanApproveContractStep(hazardOnly, 'CEO')).toThrow(
|
||||
ForbiddenException,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -151,6 +151,24 @@ const APPROVE_ROLE_PERMISSION: Record<string, string> = {
|
||||
CEO: FREIGHT_PERMS.bookings.approveCeo,
|
||||
};
|
||||
|
||||
/**
|
||||
* Approval-chain roles synthesized for hazardous contracts (see
|
||||
* `instantiateApprovalSteps`). Unlike the legacy roles below they are NOT
|
||||
* position types — they authorize purely on their own dedicated permission, and
|
||||
* they deliberately opt out of the blanket "holds any contract-approve
|
||||
* permission" fallback so a normal approver cannot sign off dangerous goods.
|
||||
*/
|
||||
export const HAZARDOUS_APPROVAL_ROLE_PERMISSION: Record<string, string> = {
|
||||
HAZARDOUS_APPROVAL_ONE: FREIGHT_PERMS.contracts.hazardousApprovalOne,
|
||||
HAZARDOUS_APPROVAL_TWO: FREIGHT_PERMS.contracts.hazardousApprovalTwo,
|
||||
};
|
||||
|
||||
/** The two hazardous steps, in the order they are prepended to the chain. */
|
||||
export const HAZARDOUS_APPROVAL_ROLES = [
|
||||
'HAZARDOUS_APPROVAL_ONE',
|
||||
'HAZARDOUS_APPROVAL_TWO',
|
||||
] as const;
|
||||
|
||||
const CONTRACT_APPROVE_ROLE_PERMISSION: Record<string, string> = {
|
||||
LINE_STAFF: FREIGHT_PERMS.contracts.approveLineStaff,
|
||||
DIRECTOR: FREIGHT_PERMS.contracts.approveDirector,
|
||||
@@ -183,6 +201,16 @@ export function assertCanApproveContractStep(
|
||||
): void {
|
||||
if (isFreightApprovalAdmin(user)) return;
|
||||
|
||||
// Hazardous steps are permission-only and strict — no legacy alias, no
|
||||
// blanket approve fallback.
|
||||
const hazardousPermission = HAZARDOUS_APPROVAL_ROLE_PERMISSION[requiredRole];
|
||||
if (hazardousPermission) {
|
||||
if (hasFreightPermission(user, hazardousPermission)) return;
|
||||
throw new ForbiddenException(
|
||||
`Missing permission: ${hazardousPermission}`,
|
||||
);
|
||||
}
|
||||
|
||||
const positionTypes = collectPositionTypeKeys(user);
|
||||
if (positionTypes.includes(requiredRole)) return;
|
||||
|
||||
@@ -219,6 +247,11 @@ export function canEditContractStep(
|
||||
): boolean {
|
||||
if (isFreightApprovalAdmin(user)) return true;
|
||||
|
||||
const hazardousPermission = HAZARDOUS_APPROVAL_ROLE_PERMISSION[requiredRole];
|
||||
if (hazardousPermission) {
|
||||
return hasFreightPermission(user, hazardousPermission);
|
||||
}
|
||||
|
||||
const positionTypes = collectPositionTypeKeys(user);
|
||||
if (positionTypes.includes(requiredRole)) return true;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user