Files
edr-platform/apps/edr-freight-api/src/migrations/1828000000000-ExtendInvoicesForPartialPayment.ts

72 lines
2.9 KiB
TypeScript

import { MigrationInterface, QueryRunner } from "typeorm";
/**
* Extend `freight.invoices` into the billing record of record for every source
* (booking, demurrage, warehouse fees, …) so warehouse fee invoices can be
* centralized onto it instead of the parallel `warehouse_fee_invoices` table.
*
* Adds money tracking that supports partial payment (`subtotal/tax/paid/balance`),
* a `paid_at` stamp, a `payments` jsonb ledger, and the `ISSUED` / `PARTIALLY_PAID`
* statuses the warehouse flow uses.
*
* Matches billing/entities/invoice.entity.ts. All columns are additive with
* defaults, so existing booking/demurrage rows are unaffected.
*/
export class ExtendInvoicesForPartialPayment1828000000000
implements MigrationInterface
{
name = "ExtendInvoicesForPartialPayment1828000000000";
public async up(queryRunner: QueryRunner): Promise<void> {
// New statuses. ADD VALUE is non-transactional-value-safe on PG 12+ as long
// as the value is not referenced in the same transaction (it is not here).
await queryRunner.query(
`ALTER TYPE freight.invoices_status_enum ADD VALUE IF NOT EXISTS 'ISSUED' BEFORE 'PENDING';`,
);
await queryRunner.query(
`ALTER TYPE freight.invoices_status_enum ADD VALUE IF NOT EXISTS 'PARTIALLY_PAID' BEFORE 'PAID';`,
);
await queryRunner.query(`
ALTER TABLE freight.invoices
ADD COLUMN IF NOT EXISTS subtotal_amount numeric(14, 2) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS tax_amount numeric(14, 2) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS paid_amount numeric(14, 2) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS balance_amount numeric(14, 2) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS paid_at timestamptz,
ADD COLUMN IF NOT EXISTS payments jsonb NOT NULL DEFAULT '[]';
`);
// Backfill existing rows: subtotal mirrors the total (no tax was modeled),
// the outstanding balance is the full total for unpaid invoices.
await queryRunner.query(`
UPDATE freight.invoices
SET subtotal_amount = total_amount,
balance_amount = total_amount;
`);
// Already-settled invoices: fully paid, zero balance, stamped from updated_at.
await queryRunner.query(`
UPDATE freight.invoices
SET paid_amount = total_amount,
balance_amount = 0,
paid_at = updated_at
WHERE status = 'PAID';
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.invoices
DROP COLUMN IF EXISTS payments,
DROP COLUMN IF EXISTS paid_at,
DROP COLUMN IF EXISTS balance_amount,
DROP COLUMN IF EXISTS paid_amount,
DROP COLUMN IF EXISTS tax_amount,
DROP COLUMN IF EXISTS subtotal_amount;
`);
// Postgres cannot drop individual enum values; ISSUED / PARTIALLY_PAID are
// left on freight.invoices_status_enum (harmless, unused after down).
}
}