add GL operations for customs risk assignment, duty advising, and incident reporting

This commit is contained in:
Marshal
2026-06-28 16:09:45 +00:00
parent 7c744352d1
commit e1d54746c2
31 changed files with 1879 additions and 29 deletions

View File

@@ -0,0 +1,49 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Booking } from '../../bookings/entities/booking.entity';
/**
* Standard anomaly taxonomy for cargo exception reporting at a border/port
* station (GL Import US-07 AC1.2). GL Djibouti logs one of these against a
* shipment with a free-text narrative and photo evidence; a high-priority alert
* is then surfaced to GL Ethiopia.
*/
export const INCIDENT_TYPES = [
'SEAL_BROKEN',
'CONTAINER_OPENED',
'CONTAINER_DAMAGED',
'FLUID_LEAKING',
] as const;
export type IncidentType = (typeof INCIDENT_TYPES)[number];
/**
* A cargo exception/damage report raised during loading or handover. Attaches to
* a booking (post-booking phase) and carries one or more photo file references
* for verification. See docs/new-doc.md §14 gap #18, GL Import US-07.
*/
@Entity({ schema: 'freight', name: 'clearance_incidents' })
@Index(['bookingId'])
export class ClearanceIncident extends BaseEntity {
@Column({ name: 'booking_id', type: 'uuid' })
bookingId!: string;
@ManyToOne(() => Booking, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'booking_id' })
booking?: Booking;
@Column({ name: 'incident_type', type: 'varchar', length: 32 })
incidentType!: IncidentType;
@Column({ name: 'description', type: 'text' })
description!: string;
/** MinIO file ids of the uploaded photo evidence (.jpg). */
@Column({ name: 'photo_file_ids', type: 'jsonb', default: () => "'[]'" })
photoFileIds!: string[];
@Column({ name: 'reported_by_user_id', type: 'uuid', nullable: true })
reportedByUserId?: string | null;
@Column({ name: 'reported_at', type: 'timestamptz', default: () => 'NOW()' })
reportedAt!: Date;
}

View File

@@ -9,6 +9,22 @@ 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;
}
/**
* A GL clearance milestone (1823 per direction). Pre-booking milestones attach
* to contract_id + clearance_cycle_id; post-booking milestones to booking_id.
@@ -60,6 +76,9 @@ export class ClearanceMilestone extends BaseEntity {
@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;
}