import { BaseEntity } from "@edr/api-common"; import { Freight } from "@edr/types"; import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; import { PaymentEntity } from "../../payment/entities/payment.entity"; import { Company } from "../../companies/entities/company.entity"; import { CompanyProfile } from "../../companies/entities/company-profile.entity"; /** A single recorded settlement against an invoice (payment ledger entry). */ export interface InvoicePayment { amount: number; method?: string | null; reference?: string | null; /** ISO timestamp of when the settlement was recorded. */ paidAt: string; metadata?: Record | null; } @Entity({ schema: "freight", name: "invoices" }) @Index(["companyId"]) @Index(["companyProfileId"]) export class Invoice extends BaseEntity { @Column({ name: "invoice_number", type: "varchar", length: 64, unique: true }) invoiceNumber!: string; /** The customer (company) this invoice is billed to. */ @Column({ name: "company_id", type: "uuid" }) companyId!: string; @ManyToOne(() => Company) @JoinColumn({ name: "company_id" }) company?: Company; /** The specific company profile (importer/exporter/forwarder/...) billed. */ @Column({ name: "company_profile_id", type: "uuid" }) companyProfileId!: string; @ManyToOne(() => CompanyProfile) @JoinColumn({ name: "company_profile_id" }) companyProfile?: CompanyProfile; /** Sum of line amounts before tax; defaults to `totalAmount` for tax-free invoices. */ @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 }) totalAmount!: number; /** Cumulative amount settled so far (supports partial payment). */ @Column({ name: "paid_amount", type: "numeric", precision: 14, scale: 2, default: 0 }) paidAmount!: number; /** Outstanding balance = `totalAmount - paidAmount` (0 once fully paid). */ @Column({ name: "balance_amount", type: "numeric", precision: 14, scale: 2, default: 0 }) balanceAmount!: number; @Column({ name: "currency", type: "varchar", length: 8, default: "ETB" }) currency!: string; @Column({ name: "status", type: "enum", enum: Freight.InvoiceStatus, default: Freight.InvoiceStatus.Draft, }) status!: Freight.InvoiceStatus; /** The source of the payment (e.g. booking, customer, etc.). */ @Column({ name: "source", type: "varchar", length: 255, nullable: false }) source!: string; /** The ID of the source (e.g. booking ID, customer ID, etc.). */ @Column({ name: "source_id", type: "varchar", length: 255, nullable: false }) sourceId!: string; /** The type of Invoice (e.g. prepaid, credit, etc.). it suppose to answer the question "what is the invoice for?" */ @Column({ type: "varchar", length: 255, nullable: false, }) type!: string; /** Set when the invoice is actually issued (DRAFT invoices leave this null). */ @Column({ name: "issued_at", type: "timestamptz", nullable: true }) issuedAt?: Date | null; /** Set when the invoice is fully settled. */ @Column({ name: "paid_at", type: "timestamptz", nullable: true }) paidAt?: Date | null; /** Ledger of individual settlements (manual or gateway), newest last. */ @Column({ name: "payments", type: "jsonb", default: () => "'[]'" }) payments!: InvoicePayment[]; /** The ID of the payment that generated this invoice. */ @Column({ name: "payment_id", type: "uuid", nullable: true }) paymentId?: string | null; @ManyToOne(() => PaymentEntity) @JoinColumn({ name: "payment_id" }) payment?: PaymentEntity; @Column({ name: "due_at", type: "timestamptz" }) dueAt!: Date; }