Files
edr-platform/apps/edr-freight-api/src/modules/bookings/entities/booking-clearance-charge.entity.ts

87 lines
3.2 KiB
TypeScript

import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Booking } from './booking.entity';
export const CLEARANCE_CHARGE_TYPES = ['PORT_CHARGES', 'MISCELLANEOUS'] as const;
export type ClearanceChargeType = (typeof CLEARANCE_CHARGE_TYPES)[number];
export const CLEARANCE_CHARGE_STATUSES = [
'DOC_UPLOADED',
'BILLED',
'SENT',
'REJECTED',
'ACCEPTED',
'PAID',
] as const;
export type ClearanceChargeStatus = (typeof CLEARANCE_CHARGE_STATUSES)[number];
/**
* Clearance charge billed to the customer. One PORT_CHARGES row per booking
* (enforced by a partial unique index) and any number of MISCELLANEOUS rows.
* GL Djibouti uploads the port-charges document (DOC_UPLOADED); GL Ethiopia
* sets amount + currency + description (BILLED) and proposes it to the
* customer (SENT). The customer either REJECTS with a note (GL revises and
* re-sends) or ACCEPTS, which issues the invoice and locks the charge; the
* billing `clearance_charge.invoice.paid` event marks it PAID. The two levels
* are independent — either may be raised first.
*/
@Entity({ schema: 'freight', name: 'booking_clearance_charge' })
@Index(['bookingId'])
export class BookingClearanceCharge extends BaseEntity {
@Column({ name: 'booking_id', type: 'uuid' })
bookingId!: string;
@ManyToOne(() => Booking, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'booking_id' })
booking?: Booking;
@Column({ name: 'type', type: 'varchar', length: 20 })
type!: ClearanceChargeType;
@Column({ name: 'status', type: 'varchar', length: 20, default: 'DOC_UPLOADED' })
status!: ClearanceChargeStatus;
/** The supporting charge document (FileRecord). */
@Column({ name: 'file_record_id', type: 'uuid', nullable: true })
fileRecordId?: string | null;
@Column({ name: 'amount', type: 'numeric', precision: 14, scale: 2, nullable: true })
amount?: string | null;
@Column({ name: 'currency', type: 'varchar', length: 8, nullable: true })
currency?: string | null;
/** What the price is for, written by GL. */
@Column({ name: 'description', type: 'text', nullable: true })
description?: string | null;
/** Customer's reason when REJECTED; cleared when GL revises. */
@Column({ name: 'customer_note', type: 'text', nullable: true })
customerNote?: string | null;
@Column({ name: 'customer_decided_at', type: 'timestamptz', nullable: true })
customerDecidedAt?: Date | null;
@Column({ name: 'customer_decided_by', type: 'uuid', nullable: true })
customerDecidedBy?: string | null;
/** The payable invoice issued for this charge (null until SENT). */
@Column({ name: 'invoice_id', type: 'uuid', nullable: true })
invoiceId?: string | null;
@Column({ name: 'uploaded_by_staff_id', type: 'uuid', nullable: true })
uploadedByStaffId?: string | null;
@Column({ name: 'uploaded_at', type: 'timestamptz', nullable: true })
uploadedAt?: Date | null;
@Column({ name: 'billed_by_staff_id', type: 'uuid', nullable: true })
billedByStaffId?: string | null;
@Column({ name: 'billed_at', type: 'timestamptz', nullable: true })
billedAt?: Date | null;
@Column({ name: 'paid_at', type: 'timestamptz', nullable: true })
paidAt?: Date | null;
}