implement contract cancellation feature and update contract statuses

- Added functionality to cancel contracts, allowing users to provide a reason for cancellation.
- Updated contract statuses to include SUSPENDED and changed CLOSED to COMPLETED.
- Enhanced the UI to reflect the new cancellation option and updated messaging for contract statuses.
- Refactored contract booking actions to accommodate changes in booking logic for ONE_TIME and GENERAL contracts.
- Removed clearance document management from the contract detail page, as it is now handled per booking.
- Introduced a SQL script to reset bookings and train schedules for development purposes.
This commit is contained in:
Marshal
2026-07-28 05:02:58 +00:00
parent 481878e553
commit 2429f6b629
83 changed files with 3083 additions and 3729 deletions

View File

@@ -13,6 +13,12 @@ export const CONTRACT_REVIEW_NOTE_TYPES = [
* correct it. One row per round — the advice/dispute loop can repeat.
*/
'DUTY_DISPUTE',
/** Backoffice froze the contract; body is the reason shown to the customer. */
'SUSPENSION',
/** Backoffice lifted a suspension; body is the optional lift note. */
'SUSPENSION_LIFTED',
/** Customer cancelled their own contract; body is their reason. */
'CANCELLATION',
] as const;
export type ContractReviewNoteType =
(typeof CONTRACT_REVIEW_NOTE_TYPES)[number];

View File

@@ -29,6 +29,8 @@ export const CONTRACT_STATUSES = [
'CLEARANCE_UNDER_REVIEW',
'CLEARANCE_READY_FOR_BOOKING',
'ACTIVE_SHIPMENT_IN_PROGRESS',
// Reversible backoffice freeze — see statusBeforeSuspension.
'SUSPENDED',
'CONTRACT_CLOSED',
'EXPIRED',
'REJECTED',
@@ -217,6 +219,14 @@ export class Contract extends BaseEntity {
@Column({ name: 'status', type: 'varchar', length: 40, default: 'DRAFT' })
status!: string;
/**
* Status the contract held when the backoffice suspended it, restored when
* the suspension is lifted. Null unless the contract is (or once was)
* SUSPENDED. A suspension without this would just be a cancellation.
*/
@Column({ name: 'status_before_suspension', type: 'varchar', length: 40, nullable: true })
statusBeforeSuspension?: string | null;
@Column({ name: 'clearance_status', type: 'varchar', length: 40, default: 'NOT_APPLICABLE' })
clearanceStatus!: string;
@@ -343,4 +353,18 @@ export class Contract extends BaseEntity {
* contract_review_notes, not a column here.
*/
latestSendBackNote?: string | null;
/**
* Body of the most recent SUSPENSION review note, attached by
* ContractsService.findById while the contract is SUSPENDED so both sides see
* why it was frozen. Lives in contract_review_notes, not a column here.
*/
latestSuspensionNote?: string | null;
/**
* Count of this contract's non-terminal bookings, attached by
* ContractsService.findById. The portal disables customer cancellation while
* it is > 0 (the API enforces the same). Not a column.
*/
activeBookingCount?: number;
}