diff --git a/src/modules/partners/entities/partner.entity.ts b/src/modules/partners/entities/partner.entity.ts index 3173892..9118000 100644 --- a/src/modules/partners/entities/partner.entity.ts +++ b/src/modules/partners/entities/partner.entity.ts @@ -6,11 +6,17 @@ import { UpdateDateColumn, DeleteDateColumn, Index, - ManyToOne, - OneToMany, - JoinColumn, } from 'typeorm'; +/** + * Partner Entity + * + * Synchronized with DDL: database/ddl/16-partners.sql + * Table: partners.partners + * + * Represents customers, suppliers, and other business partners. + * Extended fiscal information is stored in partners.partner_tax_info (separate entity). + */ @Entity({ name: 'partners', schema: 'partners' }) export class Partner { @PrimaryGeneratedColumn('uuid') @@ -20,34 +26,34 @@ export class Partner { @Column({ name: 'tenant_id', type: 'uuid' }) tenantId: string; - // Identificacion + // Identification @Index() - @Column({ type: 'varchar', length: 20, unique: true }) + @Column({ type: 'varchar', length: 30 }) code: string; - @Column({ name: 'display_name', type: 'varchar', length: 200 }) + @Column({ type: 'varchar', length: 200 }) + name: string; + + @Column({ name: 'display_name', type: 'varchar', length: 200, nullable: true }) displayName: string; + // Partner type + @Index() + @Column({ name: 'partner_type', type: 'varchar', length: 20, default: 'customer' }) + partnerType: 'customer' | 'supplier' | 'both' | 'contact'; + + // Fiscal data + @Index() + @Column({ name: 'tax_id', type: 'varchar', length: 50, nullable: true }) + taxId: string; + + @Column({ name: 'tax_id_type', type: 'varchar', length: 20, nullable: true }) + taxIdType: string; + @Column({ name: 'legal_name', type: 'varchar', length: 200, nullable: true }) legalName: string; - // Tipo de partner - @Index() - @Column({ name: 'partner_type', type: 'varchar', length: 20, default: 'customer' }) - partnerType: 'customer' | 'supplier' | 'both'; - - // Fiscal - @Index() - @Column({ name: 'tax_id', type: 'varchar', length: 20, nullable: true }) - taxId: string; - - @Column({ name: 'tax_regime', type: 'varchar', length: 100, nullable: true }) - taxRegime: string; - - @Column({ name: 'cfdi_use', type: 'varchar', length: 10, nullable: true }) - cfdiUse: string; - - // Contacto principal + // Primary contact @Column({ type: 'varchar', length: 255, nullable: true }) email: string; @@ -57,48 +63,43 @@ export class Partner { @Column({ type: 'varchar', length: 30, nullable: true }) mobile: string; - @Column({ type: 'varchar', length: 500, nullable: true }) + @Column({ type: 'varchar', length: 255, nullable: true }) website: string; - // Terminos de pago - @Column({ name: 'payment_term_days', type: 'int', default: 0 }) - paymentTermDays: number; - + // Credit and payments @Column({ name: 'credit_limit', type: 'decimal', precision: 15, scale: 2, default: 0 }) creditLimit: number; - @Column({ name: 'current_balance', type: 'decimal', precision: 15, scale: 2, default: 0 }) - currentBalance: number; + @Column({ name: 'payment_term_days', type: 'int', default: 0 }) + paymentTermDays: number; - // Lista de precios - @Column({ name: 'price_list_id', type: 'uuid', nullable: true }) - priceListId: string; + @Column({ name: 'payment_method', type: 'varchar', length: 50, nullable: true }) + paymentMethod: string; - // Descuentos - @Column({ name: 'discount_percent', type: 'decimal', precision: 5, scale: 2, default: 0 }) - discountPercent: number; - - // Categoria + // Classification @Column({ type: 'varchar', length: 50, nullable: true }) category: string; @Column({ type: 'text', array: true, default: '{}' }) tags: string[]; - // Notas - @Column({ type: 'text', nullable: true }) - notes: string; - - // Estado + // Status @Column({ name: 'is_active', type: 'boolean', default: true }) isActive: boolean; @Column({ name: 'is_verified', type: 'boolean', default: false }) isVerified: boolean; - // Vendedor asignado - @Column({ name: 'sales_rep_id', type: 'uuid', nullable: true }) - salesRepId: string; + @Column({ name: 'verified_at', type: 'timestamptz', nullable: true }) + verifiedAt: Date | null; + + // Settings (flexible JSONB) + @Column({ type: 'jsonb', default: '{}' }) + settings: Record; + + // Notes + @Column({ type: 'text', nullable: true }) + notes: string; // Metadata @CreateDateColumn({ name: 'created_at', type: 'timestamptz' }) diff --git a/src/modules/products/entities/product.entity.ts b/src/modules/products/entities/product.entity.ts index d665b2c..3b47080 100644 --- a/src/modules/products/entities/product.entity.ts +++ b/src/modules/products/entities/product.entity.ts @@ -12,22 +12,21 @@ import { import { ProductCategory } from './product-category.entity'; /** - * Commerce Product Entity (schema: products.products) + * Commerce Product Entity + * + * Synchronized with DDL: database/ddl/17-products.sql + * Table: products.products * * NOTE: This is NOT a duplicate of inventory/entities/product.entity.ts * * Key differences: * - This entity: products.products - Commerce/retail focused - * - Has: SAT codes, tax rates, detailed dimensions, min/max stock, reorder points + * - Has: SAT codes, tax rates, pricing, attributes * - Used by: Sales, purchases, invoicing, POS * * - Inventory Product: inventory.products - Warehouse/stock management focused (Odoo-style) * - Has: valuationMethod, tracking (lot/serial), isStorable, StockQuant/Lot relations * - Used by: Inventory module for stock tracking, valuation, picking operations - * - * These are intentionally separate by domain. This commerce product entity handles - * pricing, tax compliance (SAT/CFDI), and business rules. For physical stock tracking, - * use the inventory module's product entity. */ @Entity({ name: 'products', schema: 'products' }) export class Product { @@ -46,19 +45,7 @@ export class Product { @JoinColumn({ name: 'category_id' }) category: ProductCategory; - /** - * Optional link to inventory.products for unified stock management. - * This allows the commerce product to be linked to its inventory counterpart - * for stock tracking, valuation (FIFO/AVERAGE), and warehouse operations. - * - * The inventory product handles: stock levels, lot/serial tracking, valuation layers - * This commerce product handles: pricing, taxes, SAT compliance, commercial data - */ - @Index() - @Column({ name: 'inventory_product_id', type: 'uuid', nullable: true }) - inventoryProductId: string | null; - - // Identificacion + // Identification @Index() @Column({ type: 'varchar', length: 50 }) sku: string; @@ -70,55 +57,48 @@ export class Product { @Column({ type: 'varchar', length: 200 }) name: string; - @Column({ name: 'short_name', type: 'varchar', length: 50, nullable: true }) - shortName: string; - @Column({ type: 'text', nullable: true }) description: string; - // Tipo + @Column({ name: 'short_description', type: 'varchar', length: 500, nullable: true }) + shortDescription: string; + + // Type @Index() @Column({ name: 'product_type', type: 'varchar', length: 20, default: 'product' }) productType: 'product' | 'service' | 'consumable' | 'kit'; - // Precios - @Column({ name: 'sale_price', type: 'decimal', precision: 15, scale: 4, default: 0 }) - salePrice: number; + // Pricing + @Column({ type: 'decimal', precision: 15, scale: 4, default: 0 }) + price: number; - @Column({ name: 'cost_price', type: 'decimal', precision: 15, scale: 4, default: 0 }) - costPrice: number; - - @Column({ name: 'min_sale_price', type: 'decimal', precision: 15, scale: 4, nullable: true }) - minSalePrice: number; + @Column({ type: 'decimal', precision: 15, scale: 4, default: 0 }) + cost: number; @Column({ type: 'varchar', length: 3, default: 'MXN' }) currency: string; - // Impuestos + @Column({ name: 'tax_included', type: 'boolean', default: true }) + taxIncluded: boolean; + + // Taxes @Column({ name: 'tax_rate', type: 'decimal', precision: 5, scale: 2, default: 16 }) taxRate: number; - @Column({ name: 'tax_included', type: 'boolean', default: false }) - taxIncluded: boolean; + @Column({ name: 'tax_code', type: 'varchar', length: 20, nullable: true }) + taxCode: string; - // SAT (Mexico) - @Column({ name: 'sat_product_code', type: 'varchar', length: 20, nullable: true }) - satProductCode: string; - - @Column({ name: 'sat_unit_code', type: 'varchar', length: 10, nullable: true }) - satUnitCode: string; - - // Unidad de medida + // Unit of measure @Column({ type: 'varchar', length: 20, default: 'PZA' }) uom: string; @Column({ name: 'uom_purchase', type: 'varchar', length: 20, nullable: true }) uomPurchase: string; - @Column({ name: 'conversion_factor', type: 'decimal', precision: 10, scale: 4, default: 1 }) - conversionFactor: number; + @Column({ name: 'uom_conversion', type: 'decimal', precision: 10, scale: 4, default: 1 }) + uomConversion: number; - // Inventario + // Inventory @Column({ name: 'track_inventory', type: 'boolean', default: true }) trackInventory: boolean; @@ -131,26 +111,13 @@ export class Product { @Column({ name: 'reorder_point', type: 'decimal', precision: 15, scale: 4, nullable: true }) reorderPoint: number; - @Column({ name: 'reorder_quantity', type: 'decimal', precision: 15, scale: 4, nullable: true }) - reorderQuantity: number; + @Column({ name: 'lead_time_days', type: 'int', default: 0 }) + leadTimeDays: number; - // Lotes y series - @Column({ name: 'track_lots', type: 'boolean', default: false }) - trackLots: boolean; - - @Column({ name: 'track_serials', type: 'boolean', default: false }) - trackSerials: boolean; - - @Column({ name: 'track_expiry', type: 'boolean', default: false }) - trackExpiry: boolean; - - // Dimensiones + // Physical characteristics @Column({ type: 'decimal', precision: 10, scale: 4, nullable: true }) weight: number; - @Column({ name: 'weight_unit', type: 'varchar', length: 10, default: 'kg' }) - weightUnit: string; - @Column({ type: 'decimal', precision: 10, scale: 4, nullable: true }) length: number; @@ -160,25 +127,21 @@ export class Product { @Column({ type: 'decimal', precision: 10, scale: 4, nullable: true }) height: number; - @Column({ name: 'dimension_unit', type: 'varchar', length: 10, default: 'cm' }) - dimensionUnit: string; + @Column({ type: 'decimal', precision: 10, scale: 4, nullable: true }) + volume: number; - // Imagenes + // Images @Column({ name: 'image_url', type: 'varchar', length: 500, nullable: true }) imageUrl: string; - @Column({ type: 'text', array: true, default: '{}' }) + @Column({ type: 'jsonb', default: '[]' }) images: string[]; - // Tags - @Column({ type: 'text', array: true, default: '{}' }) - tags: string[]; + // Attributes (flexible JSONB for custom attributes like color, size, material) + @Column({ type: 'jsonb', default: '{}' }) + attributes: Record; - // Notas - @Column({ type: 'text', nullable: true }) - notes: string; - - // Estado + // Status @Column({ name: 'is_active', type: 'boolean', default: true }) isActive: boolean; diff --git a/src/modules/sales/entities/sales-order.entity.ts b/src/modules/sales/entities/sales-order.entity.ts index f23829b..829168c 100644 --- a/src/modules/sales/entities/sales-order.entity.ts +++ b/src/modules/sales/entities/sales-order.entity.ts @@ -1,15 +1,22 @@ -import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, Index, ManyToOne, JoinColumn } from 'typeorm'; -import { PaymentTerm } from '../../core/entities/payment-term.entity.js'; +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, + Index, + ManyToOne, + JoinColumn, +} from 'typeorm'; /** * Sales Order Entity * - * Aligned with SQL schema used by orders.service.ts - * Supports full Order-to-Cash flow with: - * - PaymentTerms integration - * - Automatic picking creation - * - Stock reservation - * - Invoice and delivery status tracking + * Synchronized with DDL: database/ddl/22-sales.sql + * Table: sales.sales_orders + * + * Represents confirmed sales orders with full order-to-delivery tracking. */ @Entity({ name: 'sales_orders', schema: 'sales' }) export class SalesOrder { @@ -20,110 +27,104 @@ export class SalesOrder { @Column({ name: 'tenant_id', type: 'uuid' }) tenantId: string; - @Index() - @Column({ name: 'company_id', type: 'uuid' }) - companyId: string; - // Order identification @Index() - @Column({ type: 'varchar', length: 30 }) - name: string; // Order number (e.g., SO-000001) - - @Column({ name: 'client_order_ref', type: 'varchar', length: 100, nullable: true }) - clientOrderRef: string | null; // Customer's reference number + @Column({ name: 'order_number', type: 'varchar', length: 30 }) + orderNumber: string; + // Origin (from quotation) @Column({ name: 'quotation_id', type: 'uuid', nullable: true }) quotationId: string | null; - // Partner/Customer + // Customer @Index() @Column({ name: 'partner_id', type: 'uuid' }) partnerId: string; + @Column({ name: 'partner_name', type: 'varchar', length: 200, nullable: true }) + partnerName: string | null; + + @Column({ name: 'partner_email', type: 'varchar', length: 255, nullable: true }) + partnerEmail: string | null; + + // Addresses (JSONB) + @Column({ name: 'billing_address', type: 'jsonb', nullable: true }) + billingAddress: Record | null; + + @Column({ name: 'shipping_address', type: 'jsonb', nullable: true }) + shippingAddress: Record | null; + // Dates @Column({ name: 'order_date', type: 'date', default: () => 'CURRENT_DATE' }) orderDate: Date; - @Column({ name: 'validity_date', type: 'date', nullable: true }) - validityDate: Date | null; + @Column({ name: 'requested_date', type: 'date', nullable: true }) + requestedDate: Date | null; - @Column({ name: 'commitment_date', type: 'date', nullable: true }) - commitmentDate: Date | null; // Promised delivery date + @Column({ name: 'promised_date', type: 'date', nullable: true }) + promisedDate: Date | null; - // Currency and pricing - @Index() - @Column({ name: 'currency_id', type: 'uuid' }) - currencyId: string; + @Column({ name: 'shipped_date', type: 'date', nullable: true }) + shippedDate: Date | null; - @Column({ name: 'pricelist_id', type: 'uuid', nullable: true }) - pricelistId: string | null; + @Column({ name: 'delivered_date', type: 'date', nullable: true }) + deliveredDate: Date | null; - // Payment terms integration (TASK-003-01) - @Index() - @Column({ name: 'payment_term_id', type: 'uuid', nullable: true }) - paymentTermId: string | null; + // Sales rep + @Column({ name: 'sales_rep_id', type: 'uuid', nullable: true }) + salesRepId: string | null; - @ManyToOne(() => PaymentTerm) - @JoinColumn({ name: 'payment_term_id' }) - paymentTerm: PaymentTerm; + // Warehouse + @Column({ name: 'warehouse_id', type: 'uuid', nullable: true }) + warehouseId: string | null; - // Sales team - @Column({ name: 'user_id', type: 'uuid', nullable: true }) - userId: string | null; // Sales representative + // Totals + @Column({ type: 'varchar', length: 3, default: 'MXN' }) + currency: string; - @Column({ name: 'sales_team_id', type: 'uuid', nullable: true }) - salesTeamId: string | null; + @Column({ type: 'decimal', precision: 15, scale: 2, default: 0 }) + subtotal: number; - // Amounts - @Column({ name: 'amount_untaxed', type: 'decimal', precision: 15, scale: 2, default: 0 }) - amountUntaxed: number; + @Column({ name: 'tax_amount', type: 'decimal', precision: 15, scale: 2, default: 0 }) + taxAmount: number; - @Column({ name: 'amount_tax', type: 'decimal', precision: 15, scale: 2, default: 0 }) - amountTax: number; + @Column({ name: 'discount_amount', type: 'decimal', precision: 15, scale: 2, default: 0 }) + discountAmount: number; - @Column({ name: 'amount_total', type: 'decimal', precision: 15, scale: 2, default: 0 }) - amountTotal: number; + @Column({ name: 'shipping_amount', type: 'decimal', precision: 15, scale: 2, default: 0 }) + shippingAmount: number; - // Status fields (Order-to-Cash tracking) + @Column({ type: 'decimal', precision: 15, scale: 2, default: 0 }) + total: number; + + // Payment terms + @Column({ name: 'payment_term_days', type: 'int', default: 0 }) + paymentTermDays: number; + + @Column({ name: 'payment_method', type: 'varchar', length: 50, nullable: true }) + paymentMethod: string | null; + + // Status @Index() @Column({ type: 'varchar', length: 20, default: 'draft' }) - status: 'draft' | 'sent' | 'sale' | 'done' | 'cancelled'; + status: 'draft' | 'confirmed' | 'processing' | 'shipped' | 'delivered' | 'cancelled'; - @Index() - @Column({ name: 'invoice_status', type: 'varchar', length: 20, default: 'pending' }) - invoiceStatus: 'pending' | 'partial' | 'invoiced'; + // Shipping + @Column({ name: 'shipping_method', type: 'varchar', length: 50, nullable: true }) + shippingMethod: string | null; - @Index() - @Column({ name: 'delivery_status', type: 'varchar', length: 20, default: 'pending' }) - deliveryStatus: 'pending' | 'partial' | 'delivered'; + @Column({ name: 'tracking_number', type: 'varchar', length: 100, nullable: true }) + trackingNumber: string | null; - @Column({ name: 'invoice_policy', type: 'varchar', length: 20, default: 'order' }) - invoicePolicy: 'order' | 'delivery'; - - // Delivery/Picking integration (TASK-003-03) - @Column({ name: 'picking_id', type: 'uuid', nullable: true }) - pickingId: string | null; + @Column({ type: 'varchar', length: 100, nullable: true }) + carrier: string | null; // Notes @Column({ type: 'text', nullable: true }) notes: string | null; - @Column({ name: 'terms_conditions', type: 'text', nullable: true }) - termsConditions: string | null; - - // Confirmation tracking - @Column({ name: 'confirmed_at', type: 'timestamptz', nullable: true }) - confirmedAt: Date | null; - - @Column({ name: 'confirmed_by', type: 'uuid', nullable: true }) - confirmedBy: string | null; - - // Cancellation tracking - @Column({ name: 'cancelled_at', type: 'timestamptz', nullable: true }) - cancelledAt: Date | null; - - @Column({ name: 'cancelled_by', type: 'uuid', nullable: true }) - cancelledBy: string | null; + @Column({ name: 'internal_notes', type: 'text', nullable: true }) + internalNotes: string | null; // Audit fields @CreateDateColumn({ name: 'created_at', type: 'timestamptz' })