mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
87 lines
3.1 KiB
TypeScript
87 lines
3.1 KiB
TypeScript
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 (18–23 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;
|
||
}
|