import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, Index, OneToMany, } from 'typeorm'; import { InvoiceItem } from './invoice-item.entity'; /** * Unified Invoice Entity * * Combines fields from commercial invoices and SaaS billing invoices. * Schema: billing * * Context discriminator: * - 'commercial': Sales/purchase invoices (salesOrderId, purchaseOrderId, partnerId) * - 'saas': SaaS subscription invoices (subscriptionId, periodStart, periodEnd) */ export type InvoiceType = 'sale' | 'purchase' | 'credit_note' | 'debit_note'; export type InvoiceStatus = 'draft' | 'validated' | 'sent' | 'partial' | 'paid' | 'overdue' | 'void' | 'refunded' | 'cancelled' | 'voided'; export type InvoiceContext = 'commercial' | 'saas'; @Entity({ name: 'invoices', schema: 'billing' }) export class Invoice { @PrimaryGeneratedColumn('uuid') id: string; @Index() @Column({ name: 'tenant_id', type: 'uuid' }) tenantId: string; // ============================================ // IDENTIFICATION // ============================================ @Index({ unique: true }) @Column({ name: 'invoice_number', type: 'varchar', length: 30 }) invoiceNumber: string; @Index() @Column({ name: 'invoice_type', type: 'varchar', length: 20, default: 'sale' }) invoiceType: InvoiceType; @Index() @Column({ name: 'invoice_context', type: 'varchar', length: 20, default: 'commercial' }) invoiceContext: InvoiceContext; // ============================================ // COMMERCIAL INVOICE FIELDS // ============================================ @Column({ name: 'sales_order_id', type: 'uuid', nullable: true }) salesOrderId: string | null; @Column({ name: 'purchase_order_id', type: 'uuid', nullable: true }) purchaseOrderId: string | null; @Index() @Column({ name: 'partner_id', type: 'uuid', nullable: true }) partnerId: string | null; @Column({ name: 'partner_name', type: 'varchar', length: 200, nullable: true }) partnerName: string | null; @Column({ name: 'partner_tax_id', type: 'varchar', length: 50, nullable: true }) partnerTaxId: string | null; // ============================================ // SAAS BILLING FIELDS // ============================================ @Index() @Column({ name: 'subscription_id', type: 'uuid', nullable: true }) subscriptionId: string | null; @Column({ name: 'period_start', type: 'date', nullable: true }) periodStart: Date | null; @Column({ name: 'period_end', type: 'date', nullable: true }) periodEnd: Date | null; // ============================================ // BILLING INFORMATION // ============================================ @Column({ name: 'billing_name', type: 'varchar', length: 200, nullable: true }) billingName: string | null; @Column({ name: 'billing_email', type: 'varchar', length: 255, nullable: true }) billingEmail: string | null; @Column({ name: 'billing_address', type: 'jsonb', nullable: true }) billingAddress: Record | null; @Column({ name: 'tax_id', type: 'varchar', length: 50, nullable: true }) taxId: string | null; // ============================================ // DATES // ============================================ @Index() @Column({ name: 'invoice_date', type: 'date', default: () => 'CURRENT_DATE' }) invoiceDate: Date; @Column({ name: 'due_date', type: 'date', nullable: true }) dueDate: Date | null; @Column({ name: 'payment_date', type: 'date', nullable: true }) paymentDate: Date | null; @Column({ name: 'paid_at', type: 'timestamptz', nullable: true }) paidAt: Date | null; // ============================================ // AMOUNTS // ============================================ @Column({ type: 'varchar', length: 3, default: 'MXN' }) currency: string; @Column({ name: 'exchange_rate', type: 'decimal', precision: 10, scale: 6, default: 1 }) exchangeRate: number; @Column({ type: 'decimal', precision: 15, scale: 2, default: 0 }) subtotal: number; @Column({ name: 'tax_amount', type: 'decimal', precision: 15, scale: 2, default: 0 }) taxAmount: number; @Column({ name: 'withholding_tax', type: 'decimal', precision: 15, scale: 2, default: 0 }) withholdingTax: number; @Column({ name: 'discount_amount', type: 'decimal', precision: 15, scale: 2, default: 0 }) discountAmount: number; @Column({ type: 'decimal', precision: 15, scale: 2, default: 0 }) total: number; @Column({ name: 'amount_paid', type: 'decimal', precision: 15, scale: 2, default: 0 }) amountPaid: number; // Computed or handled by DB trigger @Column({ name: 'amount_due', type: 'decimal', precision: 15, scale: 2, insert: false, update: false, nullable: true }) amountDue: number | null; // Alias for SaaS compatibility @Column({ name: 'paid_amount', type: 'decimal', precision: 15, scale: 2, default: 0 }) paidAmount: number; // ============================================ // PAYMENT DETAILS // ============================================ @Column({ name: 'payment_term_days', type: 'int', default: 0 }) paymentTermDays: number; @Column({ name: 'payment_method', type: 'varchar', length: 50, nullable: true }) paymentMethod: string | null; @Column({ name: 'payment_reference', type: 'varchar', length: 100, nullable: true }) paymentReference: string | null; // ============================================ // STATUS // ============================================ @Index() @Column({ type: 'varchar', length: 20, default: 'draft' }) status: InvoiceStatus; // ============================================ // CFDI (Mexico Electronic Invoice) // ============================================ @Index() @Column({ name: 'cfdi_uuid', type: 'varchar', length: 40, nullable: true }) cfdiUuid: string | null; @Column({ name: 'cfdi_status', type: 'varchar', length: 20, nullable: true }) cfdiStatus: string | null; @Column({ name: 'cfdi_xml', type: 'text', nullable: true }) cfdiXml: string | null; @Column({ name: 'cfdi_pdf_url', type: 'varchar', length: 500, nullable: true }) cfdiPdfUrl: string | null; // ============================================ // NOTES // ============================================ @Column({ type: 'text', nullable: true }) notes: string | null; @Column({ name: 'internal_notes', type: 'text', nullable: true }) internalNotes: string | null; // ============================================ // AUDIT // ============================================ @CreateDateColumn({ name: 'created_at', type: 'timestamptz' }) createdAt: Date; @Column({ name: 'created_by', type: 'uuid', nullable: true }) createdBy: string | null; @UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' }) updatedAt: Date; @Column({ name: 'updated_by', type: 'uuid', nullable: true }) updatedBy: string | null; @DeleteDateColumn({ name: 'deleted_at', type: 'timestamptz', nullable: true }) deletedAt: Date | null; // ============================================ // RELATIONS // ============================================ @OneToMany(() => InvoiceItem, (item) => item.invoice, { cascade: true }) items: InvoiceItem[]; }