diff --git a/apps/edr-freight-api/src/migrations/1821000000002-CreateInvoices.ts b/apps/edr-freight-api/src/migrations/1821000000002-CreateInvoices.ts
new file mode 100644
index 000000000..5c42cad65
--- /dev/null
+++ b/apps/edr-freight-api/src/migrations/1821000000002-CreateInvoices.ts
@@ -0,0 +1,101 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+/**
+ * Freight billing — `invoices` + `invoice_lines` tables.
+ *
+ * Matches:
+ * - billing/entities/invoice.entity.ts
+ * - billing/entities/invoice-line.entity.ts
+ *
+ * The status enum mirrors `Freight.InvoiceStatus` and uses TypeORM's default
+ * enum-type name (`
__enum`) so the entity's `type: "enum"`
+ * column resolves to it without an explicit `enumName`.
+ */
+export class CreateInvoices1821000000002 implements MigrationInterface {
+ name = "CreateInvoices1821000000002";
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`
+ CREATE TYPE freight.invoices_status_enum AS ENUM (
+ 'DRAFT',
+ 'PENDING',
+ 'PAID',
+ 'OVERDUE',
+ 'CANCELLED',
+ 'REFUNDED'
+ );
+ `);
+
+ await queryRunner.query(`
+ CREATE TABLE freight.invoices (
+ id uuid NOT NULL DEFAULT uuid_generate_v4(),
+ invoice_number varchar(64) NOT NULL,
+ company_id uuid NOT NULL,
+ company_profile_id uuid NOT NULL,
+ total_amount numeric(14, 2) NOT NULL,
+ currency varchar(8) NOT NULL DEFAULT 'ETB',
+ status freight.invoices_status_enum NOT NULL DEFAULT 'DRAFT',
+ source varchar(255) NOT NULL,
+ source_id varchar(255) NOT NULL,
+ type varchar(255) NOT NULL,
+ issued_at timestamptz,
+ payment_id uuid,
+ due_at timestamptz NOT NULL,
+ created_at timestamptz NOT NULL DEFAULT now(),
+ updated_at timestamptz NOT NULL DEFAULT now(),
+ deleted_at timestamptz,
+ CONSTRAINT pk_invoices PRIMARY KEY (id),
+ CONSTRAINT uq_invoices_invoice_number UNIQUE (invoice_number),
+ CONSTRAINT fk_invoices_company FOREIGN KEY (company_id)
+ REFERENCES freight.companies (id) ON DELETE RESTRICT,
+ CONSTRAINT fk_invoices_company_profile FOREIGN KEY (company_profile_id)
+ REFERENCES freight.company_profiles (id) ON DELETE RESTRICT,
+ CONSTRAINT fk_invoices_payment FOREIGN KEY (payment_id)
+ REFERENCES freight.payments (id) ON DELETE SET NULL
+ );
+ `);
+
+ await queryRunner.query(
+ `CREATE INDEX idx_invoices_company ON freight.invoices (company_id);`,
+ );
+ await queryRunner.query(
+ `CREATE INDEX idx_invoices_company_profile ON freight.invoices (company_profile_id);`,
+ );
+ await queryRunner.query(
+ `CREATE INDEX idx_invoices_source ON freight.invoices (source, source_id);`,
+ );
+ await queryRunner.query(
+ `CREATE INDEX idx_invoices_status ON freight.invoices (status);`,
+ );
+
+ await queryRunner.query(`
+ CREATE TABLE freight.invoice_lines (
+ id uuid NOT NULL DEFAULT uuid_generate_v4(),
+ invoice_id uuid NOT NULL,
+ charge_type varchar NOT NULL,
+ description varchar(255),
+ quantity numeric(12, 2) NOT NULL DEFAULT 1,
+ unit_rate numeric(14, 2) NOT NULL DEFAULT 0,
+ amount numeric(14, 2) NOT NULL,
+ currency varchar(8) NOT NULL DEFAULT 'ETB',
+ metadata jsonb,
+ created_at timestamptz NOT NULL DEFAULT now(),
+ updated_at timestamptz NOT NULL DEFAULT now(),
+ deleted_at timestamptz,
+ CONSTRAINT pk_invoice_lines PRIMARY KEY (id),
+ CONSTRAINT fk_invoice_lines_invoice FOREIGN KEY (invoice_id)
+ REFERENCES freight.invoices (id) ON DELETE CASCADE
+ );
+ `);
+
+ await queryRunner.query(
+ `CREATE INDEX idx_invoice_lines_invoice ON freight.invoice_lines (invoice_id);`,
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`DROP TABLE IF EXISTS freight.invoice_lines;`);
+ await queryRunner.query(`DROP TABLE IF EXISTS freight.invoices;`);
+ await queryRunner.query(`DROP TYPE IF EXISTS freight.invoices_status_enum;`);
+ }
+}