import { BaseEntity } from '@edr/api-common'; import { Column, Entity, Index } from 'typeorm'; export const WAREHOUSE_INVOICE_TYPES = ['DEMURRAGE', 'STORAGE_FEE', 'MIXED_WAREHOUSE_FEES'] as const; export type WarehouseInvoiceType = (typeof WAREHOUSE_INVOICE_TYPES)[number]; export const WAREHOUSE_INVOICE_STATUSES = [ 'DRAFT', 'ISSUED', 'PARTIALLY_PAID', 'PAID', 'CANCELLED', ] as const; export type WarehouseInvoiceStatus = (typeof WAREHOUSE_INVOICE_STATUSES)[number]; /** A single recorded payment against a warehouse fee invoice (history). */ export interface WarehouseInvoicePayment { amount: number; method?: string | null; reference?: string | null; paidAt: string; } /** * Batch 6 — invoice generated from Batch 5 demurrage/storage fee calculation. * Owns warehouse fees; links to booking/customer/inventory/location so it can * connect to the existing payment module without duplicating it. */ @Entity({ schema: 'freight', name: 'warehouse_fee_invoices' }) @Index(['invoiceNumber'], { unique: true }) @Index(['bookingId']) @Index(['inventoryId']) @Index(['status']) export class WarehouseFeeInvoice extends BaseEntity { @Column({ name: 'invoice_number', type: 'varchar', length: 40, unique: true }) invoiceNumber!: string; @Column({ name: 'booking_id', type: 'uuid', nullable: true }) bookingId?: string | null; @Column({ name: 'customer_id', type: 'uuid', nullable: true }) customerId?: string | null; @Column({ name: 'inventory_id', type: 'uuid' }) inventoryId!: string; @Column({ name: 'facility_id', type: 'uuid', nullable: true }) facilityId?: string | null; @Column({ name: 'warehouse_id', type: 'uuid', nullable: true }) warehouseId?: string | null; @Column({ name: 'yard_id', type: 'uuid', nullable: true }) yardId?: string | null; @Column({ name: 'zone_id', type: 'uuid', nullable: true }) zoneId?: string | null; @Column({ name: 'invoice_type', type: 'varchar', length: 32, default: 'MIXED_WAREHOUSE_FEES' }) invoiceType!: WarehouseInvoiceType; @Column({ name: 'status', type: 'varchar', length: 20, default: 'DRAFT' }) status!: WarehouseInvoiceStatus; @Column({ name: 'subtotal_amount', type: 'numeric', precision: 14, scale: 2, default: 0 }) subtotalAmount!: number; @Column({ name: 'tax_amount', type: 'numeric', precision: 14, scale: 2, default: 0 }) taxAmount!: number; @Column({ name: 'total_amount', type: 'numeric', precision: 14, scale: 2, default: 0 }) totalAmount!: number; @Column({ name: 'paid_amount', type: 'numeric', precision: 14, scale: 2, default: 0 }) paidAmount!: number; @Column({ name: 'balance_amount', type: 'numeric', precision: 14, scale: 2, default: 0 }) balanceAmount!: number; @Column({ name: 'currency', type: 'varchar', length: 8, default: 'USD' }) currency!: string; /** Charge window covered by this invoice — used to allow a later invoice for a new period. */ @Column({ name: 'period_start', type: 'timestamptz', nullable: true }) periodStart?: Date | null; @Column({ name: 'period_end', type: 'timestamptz', nullable: true }) periodEnd?: Date | null; @Column({ name: 'issued_at', type: 'timestamptz', nullable: true }) issuedAt?: Date | null; @Column({ name: 'due_date', type: 'timestamptz', nullable: true }) dueDate?: Date | null; @Column({ name: 'paid_at', type: 'timestamptz', nullable: true }) paidAt?: Date | null; @Column({ name: 'cancelled_at', type: 'timestamptz', nullable: true }) cancelledAt?: Date | null; @Column({ name: 'payments', type: 'jsonb', default: () => "'[]'" }) payments!: WarehouseInvoicePayment[]; @Column({ name: 'notes', type: 'text', nullable: true }) notes?: string | null; }