Files
edr-platform/apps/edr-freight-api/src/modules/contracts/entities/clearance-milestone.entity.ts
2026-07-02 17:55:59 +00:00

87 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Booking } from '../../bookings/entities/booking.entity';
import { Contract } from './contract.entity';
export const MILESTONE_STATUSES = ['PENDING', 'COMPLETED', 'SKIPPED'] as const;
export type MilestoneStatus = (typeof MILESTONE_STATUSES)[number];
export const MILESTONE_OWNER_REGIONS = ['ET', 'DJ', 'OPS', 'CUST'] as const;
export type MilestoneOwnerRegion = (typeof MILESTONE_OWNER_REGIONS)[number];
export const CUSTOMS_RISK_LEVELS = ['GREEN', 'YELLOW', 'RED'] as const;
export type CustomsRiskLevel = (typeof CUSTOMS_RISK_LEVELS)[number];
/**
* Structured payload some milestones carry beyond a plain note (doc §11.3):
* - RISK_ASSIGNED → `riskLevel`
* - DUTY_TAXES_ADVISED → `dutyAmount`, `dutyCurrency`, `declarationSerial`
* Stored on the milestone so the timeline can render the value inline.
*/
export interface MilestoneMetadata {
riskLevel?: CustomsRiskLevel;
dutyAmount?: number;
dutyCurrency?: string;
declarationSerial?: string;
/** When the gate pass was physically granted (GL DJ captures the time). */
gatepassAt?: string;
}
/**
* A GL clearance milestone (1823 per direction). Pre-booking milestones attach
* to contract_id + clearance_cycle_id; post-booking milestones to booking_id.
* See §5.12, §5.16, §11.3.
*/
@Entity({ schema: 'freight', name: 'clearance_milestones' })
@Index(['bookingId'])
@Index(['contractId'])
@Index(['ownerRegion', 'status'])
export class ClearanceMilestone extends BaseEntity {
@Column({ name: 'booking_id', type: 'uuid', nullable: true })
bookingId?: string | null;
@ManyToOne(() => Booking, { nullable: true, onDelete: 'CASCADE' })
@JoinColumn({ name: 'booking_id' })
booking?: Booking | null;
@Column({ name: 'contract_id', type: 'uuid', nullable: true })
contractId?: string | null;
@ManyToOne(() => Contract, { nullable: true, onDelete: 'CASCADE' })
@JoinColumn({ name: 'contract_id' })
contract?: Contract | null;
@Column({ name: 'clearance_cycle_id', type: 'uuid', nullable: true })
clearanceCycleId?: string | null;
@Column({ name: 'milestone_code', type: 'varchar', length: 64 })
milestoneCode!: string;
@Column({ name: 'milestone_label', type: 'varchar', length: 255 })
milestoneLabel!: string;
@Column({ name: 'status', type: 'varchar', length: 20, default: 'PENDING' })
status!: MilestoneStatus;
@Column({ name: 'owner_region', type: 'varchar', length: 5, nullable: true })
ownerRegion?: MilestoneOwnerRegion | null;
@Column({ name: 'triggered_by_doc', type: 'boolean', default: false })
triggeredByDoc!: boolean;
@Column({ name: 'triggered_at', type: 'timestamptz', nullable: true })
triggeredAt?: Date | null;
@Column({ name: 'triggered_by_user_id', type: 'uuid', nullable: true })
triggeredByUserId?: string | null;
@Column({ name: 'note', type: 'text', nullable: true })
note?: string | null;
@Column({ name: 'metadata', type: 'jsonb', nullable: true })
metadata?: MilestoneMetadata | null;
@Column({ name: 'sort_order', type: 'smallint', default: 0 })
sortOrder!: number;
}