contrat,booking,global logestic

This commit is contained in:
Marshal
2026-06-26 23:24:48 +00:00
parent f931342f31
commit 01d53c218c
105 changed files with 19573 additions and 909 deletions

View File

@@ -0,0 +1,65 @@
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];
/**
* 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: 'sort_order', type: 'smallint', default: 0 })
sortOrder!: number;
}

View File

@@ -0,0 +1,46 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Contract } from './contract.entity';
export const CONTRACT_APPROVAL_STEP_STATUSES = [
'PENDING',
'APPROVED',
'REJECTED',
'SKIPPED',
] as const;
export type ContractApprovalStepStatus =
(typeof CONTRACT_APPROVAL_STEP_STATUSES)[number];
@Entity({ schema: 'freight', name: 'contract_approval_steps' })
@Index(['contractId'])
@Index(['status'])
@Index(['contractId', 'stepOrder'])
export class ContractApprovalStep extends BaseEntity {
@Column({ name: 'contract_id', type: 'uuid' })
contractId!: string;
@ManyToOne(() => Contract, (c) => c.approvalSteps, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'contract_id' })
contract?: Contract;
@Column({ name: 'step_order', type: 'smallint', default: 0 })
stepOrder!: number;
@Column({ name: 'required_role', type: 'varchar', length: 40 })
requiredRole!: string;
@Column({ name: 'blocks_role', type: 'varchar', length: 40, nullable: true })
blocksRole?: string | null;
@Column({ name: 'status', type: 'varchar', length: 20, default: 'PENDING' })
status!: ContractApprovalStepStatus;
@Column({ name: 'acted_by_staff_id', type: 'uuid', nullable: true })
actedByStaffId?: string | null;
@Column({ name: 'acted_at', type: 'timestamptz', nullable: true })
actedAt?: Date | null;
@Column({ name: 'note', type: 'text', nullable: true })
note?: string | null;
}

View File

@@ -0,0 +1,34 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { CargoType } from '../../rule-engine/entities/cargo-type.entity';
import { Contract } from './contract.entity';
/**
* What cargo sizes/commodities are in scope for a contract — NO quantities.
* Container: one row per enabled size (20ft/40ft). Bulk: one row with a cargo
* type. See §5.4.
*/
@Entity({ schema: 'freight', name: 'contract_cargo_scope' })
@Index(['contractId'])
export class ContractCargoScope extends BaseEntity {
@Column({ name: 'contract_id', type: 'uuid' })
contractId!: string;
@ManyToOne(() => Contract, (c) => c.cargoScope, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'contract_id' })
contract?: Contract;
/** '20ft' | '40ft'; null for bulk. */
@Column({ name: 'container_size', type: 'varchar', length: 10, nullable: true })
containerSize?: string | null;
@Column({ name: 'cargo_type_id', type: 'uuid', nullable: true })
cargoTypeId?: string | null;
@ManyToOne(() => CargoType, { nullable: true })
@JoinColumn({ name: 'cargo_type_id' })
cargoType?: CargoType | null;
@Column({ name: 'cargo_free_text', type: 'varchar', length: 200, nullable: true })
cargoFreeText?: string | null;
}

View File

@@ -0,0 +1,37 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Contract } from './contract.entity';
/**
* One pre-booking clearance round on a contract (Path B). GENERAL contracts run
* many cycles; ONE_TIME uses cycle_number = 1. The booking GL creates is linked
* back via bookingId. See §5.16.
*/
@Entity({ schema: 'freight', name: 'contract_clearance_cycles' })
@Index(['contractId'])
export class ContractClearanceCycle extends BaseEntity {
@Column({ name: 'contract_id', type: 'uuid' })
contractId!: string;
@ManyToOne(() => Contract, (c) => c.clearanceCycles, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'contract_id' })
contract?: Contract;
@Column({ name: 'cycle_number', type: 'int' })
cycleNumber!: number;
@Column({ name: 'status', type: 'varchar', length: 40, default: 'AWAITING_DOCUMENTS' })
status!: string;
@Column({ name: 'booking_id', type: 'uuid', nullable: true })
bookingId?: string | null;
@Column({ name: 'started_at', type: 'timestamptz', default: () => 'now()' })
startedAt!: Date;
@Column({ name: 'clearance_ready_at', type: 'timestamptz', nullable: true })
clearanceReadyAt?: Date | null;
@Column({ name: 'completed_at', type: 'timestamptz', nullable: true })
completedAt?: Date | null;
}

View File

@@ -0,0 +1,55 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Contract } from './contract.entity';
export const CONTRACT_DOC_REVIEW_STATUSES = ['PENDING', 'APPROVED', 'QUERIED'] as const;
export type ContractDocReviewStatus =
(typeof CONTRACT_DOC_REVIEW_STATUSES)[number];
export const CONTRACT_DOC_UPLOADER_ROLES = ['CUSTOMER', 'GL_ET', 'GL_DJ'] as const;
export type ContractDocUploaderRole =
(typeof CONTRACT_DOC_UPLOADER_ROLES)[number];
/**
* Per-document pre-booking clearance review on a contract (Path B). Mirrors
* BookingDocumentReview but keyed on contract_id (+ optional clearance cycle).
* See §5.16.
*/
@Entity({ schema: 'freight', name: 'contract_document_review' })
@Index(['contractId'])
@Index(['status'])
export class ContractDocumentReview extends BaseEntity {
@Column({ name: 'contract_id', type: 'uuid' })
contractId!: string;
@ManyToOne(() => Contract, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'contract_id' })
contract?: Contract;
@Column({ name: 'clearance_cycle_id', type: 'uuid', nullable: true })
clearanceCycleId?: string | null;
@Column({ name: 'setting_code', type: 'varchar', length: 128 })
settingCode!: string;
@Column({ name: 'file_key', type: 'varchar', length: 128 })
fileKey!: string;
@Column({ name: 'file_record_id', type: 'uuid', nullable: true })
fileRecordId?: string | null;
@Column({ name: 'status', type: 'varchar', length: 20, default: 'PENDING' })
status!: ContractDocReviewStatus;
@Column({ name: 'note', type: 'text', nullable: true })
note?: string | null;
@Column({ name: 'uploaded_by_role', type: 'varchar', length: 20, default: 'CUSTOMER' })
uploadedByRole!: ContractDocUploaderRole;
@Column({ name: 'reviewed_by_staff_id', type: 'uuid', nullable: true })
reviewedByStaffId?: string | null;
@Column({ name: 'reviewed_at', type: 'timestamptz', nullable: true })
reviewedAt?: Date | null;
}

View File

@@ -0,0 +1,47 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Contract } from './contract.entity';
/**
* Frozen UNIT rates at contract submit time — one row per rate line. The booking
* computes totals from these × entered quantities. See §5.7.
*/
@Entity({ schema: 'freight', name: 'contract_rate_snapshots' })
@Index(['contractId'])
export class ContractRateSnapshot extends BaseEntity {
@Column({ name: 'contract_id', type: 'uuid' })
contractId!: string;
@ManyToOne(() => Contract, (c) => c.rateSnapshots, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'contract_id' })
contract?: Contract;
@Column({ name: 'rate_id', type: 'uuid', nullable: true })
rateId?: string | null;
@Column({ name: 'rate_code', type: 'varchar', length: 64 })
rateCode!: string;
@Column({ name: 'description', type: 'varchar', length: 255, nullable: true })
description?: string | null;
@Column({ name: 'unit_price', type: 'numeric', precision: 14, scale: 2 })
unitPrice!: number;
/** per_container | per_ton | per_item | per_km | flat */
@Column({ name: 'unit_of_measure', type: 'varchar', length: 32 })
unitOfMeasure!: string;
@Column({ name: 'currency', type: 'varchar', length: 5 })
currency!: string;
@Column({ name: 'container_size', type: 'varchar', length: 10, nullable: true })
containerSize?: string | null;
@Column({ name: 'is_surcharge', type: 'boolean', default: false })
isSurcharge!: boolean;
/** is_hazardous | is_reefer when this is a conditional surcharge. */
@Column({ name: 'conditional_on', type: 'varchar', length: 32, nullable: true })
conditionalOn?: string | null;
}

View File

@@ -0,0 +1,36 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Contract } from './contract.entity';
export const CONTRACT_REVIEW_NOTE_TYPES = [
'CHANGES_REQUESTED',
'REJECTION',
'STAFF_NOTE',
'CUSTOMER_NOTE',
'AMENDMENT',
] as const;
export type ContractReviewNoteType =
(typeof CONTRACT_REVIEW_NOTE_TYPES)[number];
@Entity({ schema: 'freight', name: 'contract_review_notes' })
@Index(['contractId'])
export class ContractReviewNote extends BaseEntity {
@Column({ name: 'contract_id', type: 'uuid' })
contractId!: string;
@ManyToOne(() => Contract, (c) => c.reviewNotes, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'contract_id' })
contract?: Contract;
@Column({ name: 'note_type', type: 'varchar', length: 40 })
noteType!: ContractReviewNoteType;
@Column({ name: 'body', type: 'text' })
body!: string;
@Column({ name: 'author_role', type: 'varchar', length: 20, nullable: true })
authorRole?: string | null;
@Column({ name: 'author_user_id', type: 'uuid', nullable: true })
authorUserId?: string | null;
}

View File

@@ -0,0 +1,40 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Yard } from '../../rule-engine/entities/yard.entity';
import { Contract } from './contract.entity';
/**
* An allowed origin→destination lane of a contract. Routes carry NO quantity —
* the contract scope just lists which lanes shipments may use. See §5.3.
*/
@Entity({ schema: 'freight', name: 'contract_routes' })
@Index(['contractId'])
export class ContractRoute extends BaseEntity {
@Column({ name: 'contract_id', type: 'uuid' })
contractId!: string;
@ManyToOne(() => Contract, (c) => c.routes, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'contract_id' })
contract?: Contract;
@Column({ name: 'origin_yard_id', type: 'uuid' })
originYardId!: string;
@ManyToOne(() => Yard)
@JoinColumn({ name: 'origin_yard_id' })
originYard?: Yard;
@Column({ name: 'destination_yard_id', type: 'uuid' })
destinationYardId!: string;
@ManyToOne(() => Yard)
@JoinColumn({ name: 'destination_yard_id' })
destinationYard?: Yard;
/** Road billing distance; null for rail-only. */
@Column({ name: 'km', type: 'numeric', precision: 10, scale: 2, nullable: true })
km?: number | null;
@Column({ name: 'sort_order', type: 'smallint', default: 0 })
sortOrder!: number;
}

View File

@@ -0,0 +1,37 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { FileRecord } from '../../files/entities/file.entity';
import { Contract } from './contract.entity';
export const CONTRACT_SIGNER_ROLES = ['CUSTOMER', 'STAFF', 'DIRECTOR', 'CEO'] as const;
export type ContractSignerRole = (typeof CONTRACT_SIGNER_ROLES)[number];
@Entity({ schema: 'freight', name: 'contract_signatures' })
@Index(['contractId'])
export class ContractSignature extends BaseEntity {
@Column({ name: 'contract_id', type: 'uuid' })
contractId!: string;
@ManyToOne(() => Contract, (c) => c.signatures, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'contract_id' })
contract?: Contract;
@Column({ name: 'role', type: 'varchar', length: 20 })
role!: ContractSignerRole;
@Column({ name: 'signer_display_name', type: 'varchar', length: 255 })
signerDisplayName!: string;
@Column({ name: 'signature_file_id', type: 'uuid', nullable: true })
signatureFileId?: string | null;
@ManyToOne(() => FileRecord, { nullable: true })
@JoinColumn({ name: 'signature_file_id' })
signatureFile?: FileRecord | null;
@Column({ name: 'consent_text', type: 'text', nullable: true })
consentText?: string | null;
@Column({ name: 'signed_at', type: 'timestamptz', default: () => 'now()' })
signedAt!: Date;
}

View File

@@ -0,0 +1,256 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
import { Company } from '../../companies/entities/company.entity';
import { CompanyProfile } from '../../companies/entities/company-profile.entity';
import { ServiceType } from '../../rule-engine/entities/service-type.entity';
import { FileRecord } from '../../files/entities/file.entity';
import { ContractRoute } from './contract-route.entity';
import { ContractCargoScope } from './contract-cargo-scope.entity';
import { ContractRateSnapshot } from './contract-rate-snapshot.entity';
import { ContractSignature } from './contract-signature.entity';
import { ContractApprovalStep } from './contract-approval-step.entity';
import { ContractReviewNote } from './contract-review-note.entity';
import { ContractClearanceCycle } from './contract-clearance-cycle.entity';
export const CONTRACT_STATUSES = [
'DRAFT',
'SUBMITTED',
'PRICE_CHANGED_PENDING_CONFIRM',
'CHANGES_REQUESTED',
'PENDING_APPROVAL',
'APPROVED',
'APPROVED_PENDING_SIGNATURE',
'CONTRACT_READY',
'SIGNED_CUSTOMER',
'FULLY_EXECUTED',
'CONTRACT_ACTIVE',
'AWAITING_CLEARANCE_DOCUMENTS',
'CLEARANCE_UNDER_REVIEW',
'CLEARANCE_READY_FOR_BOOKING',
'ACTIVE_SHIPMENT_IN_PROGRESS',
'CONTRACT_CLOSED',
'EXPIRED',
'REJECTED',
'CANCELLED',
'RENEWAL_DRAFT',
'RENEWAL_SUBMITTED',
'RENEWAL_PENDING_APPROVAL',
'AMENDMENTS_PROPOSED',
'ARCHIVED',
] as const;
export type ContractStatus = (typeof CONTRACT_STATUSES)[number];
export const CONTRACT_KINDS = ['ONE_TIME', 'GENERAL'] as const;
export type ContractKindValue = (typeof CONTRACT_KINDS)[number];
export const CONTRACT_CLEARANCE_STATUSES = [
'NOT_APPLICABLE',
'AWAITING_DOCUMENTS',
'DOCUMENTS_UNDER_REVIEW',
'CLEARANCE_READY_FOR_BOOKING',
'ACTIVE_SHIPMENT_IN_PROGRESS',
] as const;
export type ContractClearanceStatusValue =
(typeof CONTRACT_CLEARANCE_STATUSES)[number];
/** Statuses where the customer may still edit contract fields. */
export const CONTRACT_CUSTOMER_EDITABLE_STATUSES: ContractStatus[] = [
'DRAFT',
'CHANGES_REQUESTED',
];
/**
* The legal/commercial agreement. Defines what cargo sizes/commodities, routes,
* and flags are in scope plus the frozen unit rates — but NO quantities. Spawns
* shipment {@link Booking} rows via bookings.contract_id. See docs/new-doc.md §5.2.
*/
@Entity({ schema: 'freight', name: 'contracts' })
@Index(['companyId'])
@Index(['status'])
@Index(['contractKind'])
export class Contract extends BaseEntity {
@Column({ name: 'reference', type: 'varchar', length: 64, unique: true })
reference!: string;
@Column({ name: 'company_id', type: 'uuid', nullable: true })
companyId?: string | null;
@ManyToOne(() => Company, { nullable: true })
@JoinColumn({ name: 'company_id' })
company?: Company | null;
@Column({ name: 'company_profile_id', type: 'uuid', nullable: true })
companyProfileId?: string | null;
@ManyToOne(() => CompanyProfile, { nullable: true })
@JoinColumn({ name: 'company_profile_id' })
companyProfile?: CompanyProfile | null;
@Column({ name: 'is_government', type: 'boolean', default: false })
isGovernment!: boolean;
@Column({ name: 'government_institution', type: 'varchar', length: 255, nullable: true })
governmentInstitution?: string | null;
@Column({ name: 'contract_kind', type: 'varchar', length: 20 })
contractKind!: ContractKindValue;
@Column({ name: 'renewal_of_id', type: 'uuid', nullable: true })
renewalOfId?: string | null;
@ManyToOne(() => Contract, { nullable: true })
@JoinColumn({ name: 'renewal_of_id' })
renewalOf?: Contract | null;
@Column({ name: 'trade_direction', type: 'varchar', length: 10 })
tradeDirection!: string;
@Column({ name: 'freight_type', type: 'varchar', length: 20 })
freightType!: string;
@Column({ name: 'service_type_id', type: 'uuid' })
serviceTypeId!: string;
@ManyToOne(() => ServiceType)
@JoinColumn({ name: 'service_type_id' })
serviceType?: ServiceType;
@Column({ name: 'payment_currency', type: 'varchar', length: 5 })
paymentCurrency!: string;
@Column({ name: 'customs_clearing_enabled', type: 'boolean', default: false })
customsClearingEnabled!: boolean;
@Column({ name: 'customs_clearing_agent', type: 'varchar', length: 200, nullable: true })
customsClearingAgent?: string | null;
@Column({ name: 'equipment_return', type: 'varchar', length: 20, nullable: true })
equipmentReturn?: string | null;
@Column({ name: 'first_mile_pickup_address', type: 'text', nullable: true })
firstMilePickupAddress?: string | null;
@Column({ name: 'first_mile_pickup_lat', type: 'numeric', precision: 10, scale: 7, nullable: true })
firstMilePickupLat?: number | null;
@Column({ name: 'first_mile_pickup_lng', type: 'numeric', precision: 10, scale: 7, nullable: true })
firstMilePickupLng?: number | null;
@Column({ name: 'last_mile_delivery_address', type: 'text', nullable: true })
lastMileDeliveryAddress?: string | null;
@Column({ name: 'last_mile_delivery_lat', type: 'numeric', precision: 10, scale: 7, nullable: true })
lastMileDeliveryLat?: number | null;
@Column({ name: 'last_mile_delivery_lng', type: 'numeric', precision: 10, scale: 7, nullable: true })
lastMileDeliveryLng?: number | null;
@Column({ name: 'is_hazardous', type: 'boolean', default: false })
isHazardous!: boolean;
@Column({ name: 'is_reefer', type: 'boolean', default: false })
isReefer!: boolean;
@Column({ name: 'estimated_shipment_date', type: 'timestamptz', nullable: true })
estimatedShipmentDate?: Date | null;
@Column({ name: 'contract_validity_days', type: 'int', nullable: true })
contractValidityDays?: number | null;
@Column({ name: 'contract_valid_from', type: 'timestamptz', nullable: true })
contractValidFrom?: Date | null;
@Column({ name: 'contract_valid_until', type: 'timestamptz', nullable: true })
contractValidUntil?: Date | null;
@Column({ name: 'expires_at', type: 'timestamptz', nullable: true })
expiresAt?: Date | null;
@Column({ name: 'status', type: 'varchar', length: 40, default: 'DRAFT' })
status!: string;
@Column({ name: 'clearance_status', type: 'varchar', length: 40, default: 'NOT_APPLICABLE' })
clearanceStatus!: string;
@Column({ name: 'clearance_cycle_number', type: 'int', default: 0 })
clearanceCycleNumber!: number;
@Column({ name: 'pricing_breakdown', type: 'jsonb', nullable: true })
pricingBreakdown?: Record<string, unknown> | null;
@Column({ name: 'pricing_display_mode', type: 'varchar', length: 20, default: 'UNIT_RATES', nullable: true })
pricingDisplayMode?: string | null;
@Column({ name: 'contract_type', type: 'varchar', length: 20, nullable: true })
contractType?: string | null;
@Column({ name: 'contract_template_key', type: 'varchar', length: 128, nullable: true })
contractTemplateKey?: string | null;
@Column({ name: 'contract_generated_at', type: 'timestamptz', nullable: true })
contractGeneratedAt?: Date | null;
@Column({ name: 'contract_summary', type: 'text', nullable: true })
contractSummary?: string | null;
@Column({ name: 'version_number', type: 'int', default: 1 })
versionNumber!: number;
@Column({ name: 'financial_terms', type: 'jsonb', nullable: true })
financialTerms?: Record<string, unknown> | null;
@Column({ name: 'approved_by_staff_id', type: 'uuid', nullable: true })
approvedByStaffId?: string | null;
@Column({ name: 'approved_by_staff_at', type: 'timestamptz', nullable: true })
approvedByStaffAt?: Date | null;
@Column({ name: 'signed_by_director_id', type: 'uuid', nullable: true })
signedByDirectorId?: string | null;
@Column({ name: 'signed_by_director_at', type: 'timestamptz', nullable: true })
signedByDirectorAt?: Date | null;
@Column({ name: 'signed_by_ceo_id', type: 'uuid', nullable: true })
signedByCeoId?: string | null;
@Column({ name: 'signed_by_ceo_at', type: 'timestamptz', nullable: true })
signedByCeoAt?: Date | null;
@Column({ name: 'customer_signed_at', type: 'timestamptz', nullable: true })
customerSignedAt?: Date | null;
@Column({ name: 'fully_executed_at', type: 'timestamptz', nullable: true })
fullyExecutedAt?: Date | null;
@Column({ name: 'locked_at', type: 'timestamptz', nullable: true })
lockedAt?: Date | null;
@OneToMany(() => ContractRoute, (r) => r.contract)
routes?: ContractRoute[];
@OneToMany(() => ContractCargoScope, (c) => c.contract)
cargoScope?: ContractCargoScope[];
@OneToMany(() => ContractRateSnapshot, (s) => s.contract)
rateSnapshots?: ContractRateSnapshot[];
@OneToMany(() => ContractSignature, (s) => s.contract)
signatures?: ContractSignature[];
@OneToMany(() => ContractApprovalStep, (s) => s.contract)
approvalSteps?: ContractApprovalStep[];
@OneToMany(() => ContractReviewNote, (n) => n.contract)
reviewNotes?: ContractReviewNote[];
@OneToMany(() => ContractClearanceCycle, (c) => c.contract)
clearanceCycles?: ContractClearanceCycle[];
@OneToMany(() => FileRecord, (file) => file.resourceId, {
createForeignKeyConstraints: false,
})
files?: FileRecord[];
}