- Added SequelizeProformaV2RecipientDomainMapper for mapping recipient data to domain model. - Introduced SequelizeProformaV2TaxesDomainMapper for handling tax data mapping. - Created SequelizeProformaV2RecipientSummaryMapper for summarizing recipient information. - Developed SequelizeProformaV2SummaryMapper for mapping Proforma summary data. - Implemented SequelizeProformaRepositoryV2 for managing Proforma data persistence. - Added SequelizeProformaV2NumberGenerator for generating Proforma numbers.
18 KiB
18 KiB
Split Proformas / Issued Invoices
Estado: diseno tecnico / fases 1A-1D
Estado del backend: persistencia V2 activada; SQL de desarrollo preparado; sin ejecucion confirmada de migracion DB
Objetivo
Preparar el esquema fisico para separar:
customer_invoices->proformascustomer_invoices->issued_invoicescustomer_invoice_items->proforma_itemscustomer_invoice_items->issued_invoice_itemscustomer_invoice_taxes->proforma_taxescustomer_invoice_taxes->issued_invoice_taxesverifactu_records.invoice_id->verifactu_records.issued_invoice_id
Decisiones de fase 1A
proforma_referencees el identificador visible canonico de proforma.target_invoice_series_codesustituye la semantica legacy deseriesen proformas.issued_invoicesusainvoice_series_code,invoice_number,invoice_dateysource_proforma_id.proformasno guardaissued_invoice_id; la relacion se resuelve conissued_invoices.source_proforma_id.- Los impuestos del esquema actual son por documento, no por linea:
proforma_taxesyissued_invoice_taxes. payment_methodypayment_termenissued_invoicesson snapshot historico, no estado vivo de cobro.- No hay doble escritura.
- Legacy queda congelado solo para validacion y rollback cuando se haga el cutover.
Estado de fase 1B
- Existen mappers Sequelize V2 sobre
proformas,proforma_items,proforma_taxes. - Existen mappers Sequelize V2 sobre
issued_invoices,issued_invoice_items,issued_invoice_taxes. - Existen repositorios Sequelize V2 listos para convivir con los repositorios legacy.
- El wiring activo legacy no se ha sustituido todavia.
- El dominio/application sigue arrastrando naming legacy en algunos contratos (
series,invoiceNumber,linkedProformaId); la traduccion queda encapsulada en Infrastructure V2. payment_termsigue sin soporte real en el dominio actual de customer invoices; en persistencia V2 queda preparado pero no conectado funcionalmente.
Estado de fase 1C
- El wiring activo del modulo usa persistencia V2 en los composition roots principales.
buildV2ProformaPersistence(...)queda activo en:modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.tsmodules/customer-invoices/src/api/infrastructure/proformas/di/proforma-public-services.ts
buildV2IssuedInvoicePersistence(...)queda activo en:modules/customer-invoices/src/api/infrastructure/issued-invoices/di/issued-invoices.di.tsmodules/customer-invoices/src/api/infrastructure/issued-invoices/di/issued-invoice-public-services.ts
- La persistencia legacy no se borra, pero deja de ser la ruta operativa principal.
- La emision
Proforma -> IssuedInvoiceahora encadena sobre V2:- lee
proformas - crea
issued_invoices - crea
issued_invoice_items - crea
issued_invoice_taxes - marca la proforma como
issued - usa
issued_invoices.source_proforma_id
- lee
- No hay doble escritura: cada composition root sigue instanciando una sola familia de repositorios.
VerifactuRecordqueda preparado para la ruta V2 medianteissued_invoice_idcomo relacion principal.- La migracion/backfill real sigue pendiente. Si la BD real no tiene las tablas nuevas, el backend no podra arrancar correctamente con este wiring.
Estado de fase 1D
- Existen scripts SQL separados para desarrollo:
docs/customer-invoices/sql/dev-split-proformas-issued-invoices.sqldocs/customer-invoices/sql/validate-split-proformas-issued-invoices.sql
- El SQL de desarrollo es idempotente y conservador:
- crea tablas V2 con
CREATE TABLE IF NOT EXISTS - hace backfill con
NOT EXISTS - no usa
DROP,TRUNCATEniDELETE - no sobrescribe filas ya migradas
- no toca datos legacy salvo la columna transicional
verifactu_records.issued_invoice_id
- crea tablas V2 con
- Se documenta el orden de ejecucion en
docs/customer-invoices/sql/README.md. - En este workspace no se encontro una configuracion de BD de desarrollo inequivoca (
.env*no presente en raiz del repo), por lo que no se ejecuto SQL real. - Tampoco se hizo validacion funcional contra runtime real porque falta una BD de desarrollo confirmada y migrada para esta fase.
Dependencias compartidas y exclusivas
Compartidas entre legacy y V2
- contratos de Application (
IProformaRepository,IIssuedInvoiceRepository) - use cases, servicios de dominio y assemblers
TransactionManagerInvoiceSeriesNumberAssignerparaIssuedInvoice- resolvers de catalogos
- servicios de documentos PDF/preview
Exclusivas de legacy
CustomerInvoiceModelcomo storage real de Proforma e IssuedInvoice- numerador de proformas sobre
customer_invoices.invoice_numberconis_proforma = true - repositorios Sequelize legacy de
customer_invoices
Exclusivas de V2
- tablas
proformas,proforma_items,proforma_taxes - tablas
issued_invoices,issued_invoice_items,issued_invoice_taxes - numerador V2 de proformas sobre
proformas.proforma_reference - mappers y repositorios Sequelize V2
Tablas nuevas previstas
proformas
idcompany_idstatusproforma_referenceproforma_dateoperation_datetarget_invoice_series_codereferencedescriptionnoteslanguage_codecurrency_codecustomer_id- snapshot cliente
payment_method_idpayment_term_idtax_regime_code- totales monetarios actuales
created_atupdated_atdeleted_at
issued_invoices
idcompany_idstatussource_proforma_idinvoice_series_codeinvoice_numberinvoice_dateoperation_datereferencedescriptionnoteslanguage_codecurrency_codecustomer_id- snapshot cliente
payment_method_idpayment_method_namepayment_method_descriptionpayment_term_idpayment_term_namepayment_term_descriptionpayment_term_snapshot_jsontax_regime_codetax_regime_description- totales monetarios actuales
created_atupdated_atdeleted_at
Items y taxes
proforma_items.proforma_idissued_invoice_items.issued_invoice_idproforma_taxes.proforma_idissued_invoice_taxes.issued_invoice_id
Verifactu
verifactu_records.issued_invoice_idnuevoverifactu_records.invoice_idlegacy temporal durante la transicion de fase 1A
SQL orientativo de backfill
Proformas
INSERT INTO proformas (
id,
company_id,
status,
proforma_reference,
proforma_date,
operation_date,
target_invoice_series_code,
reference,
description,
notes,
language_code,
currency_code,
customer_id,
customer_tin,
customer_name,
customer_street,
customer_street2,
customer_city,
customer_province,
customer_postal_code,
customer_country,
payment_method_id,
payment_term_id,
tax_regime_code,
subtotal_amount_value,
subtotal_amount_scale,
items_discount_amount_value,
items_discount_amount_scale,
global_discount_percentage_value,
global_discount_percentage_scale,
global_discount_amount_value,
global_discount_amount_scale,
total_discount_amount_value,
total_discount_amount_scale,
taxable_amount_value,
taxable_amount_scale,
iva_amount_value,
iva_amount_scale,
rec_amount_value,
rec_amount_scale,
retention_amount_value,
retention_amount_scale,
taxes_amount_value,
taxes_amount_scale,
total_amount_value,
total_amount_scale,
created_at,
updated_at,
deleted_at
)
SELECT
id,
company_id,
status,
CASE
WHEN series IS NULL OR series = '' THEN invoice_number
ELSE CONCAT(series, '-', invoice_number)
END AS proforma_reference,
invoice_date,
operation_date,
series,
reference,
description,
notes,
language_code,
currency_code,
customer_id,
customer_tin,
customer_name,
customer_street,
customer_street2,
customer_city,
customer_province,
customer_postal_code,
customer_country,
payment_method_id,
NULL AS payment_term_id,
tax_regime_code,
subtotal_amount_value,
subtotal_amount_scale,
items_discount_amount_value,
items_discount_amount_scale,
global_discount_percentage_value,
global_discount_percentage_scale,
global_discount_amount_value,
global_discount_amount_scale,
total_discount_amount_value,
total_discount_amount_scale,
taxable_amount_value,
taxable_amount_scale,
iva_amount_value,
iva_amount_scale,
rec_amount_value,
rec_amount_scale,
retention_amount_value,
retention_amount_scale,
taxes_amount_value,
taxes_amount_scale,
total_amount_value,
total_amount_scale,
created_at,
updated_at,
deleted_at
FROM customer_invoices
WHERE is_proforma = 1;
Issued invoices
INSERT INTO issued_invoices (
id,
company_id,
status,
source_proforma_id,
invoice_series_code,
invoice_number,
invoice_date,
operation_date,
reference,
description,
notes,
language_code,
currency_code,
customer_id,
customer_tin,
customer_name,
customer_street,
customer_street2,
customer_city,
customer_province,
customer_postal_code,
customer_country,
payment_method_id,
payment_method_name,
payment_method_description,
payment_term_id,
payment_term_name,
payment_term_description,
payment_term_snapshot_json,
tax_regime_code,
tax_regime_description,
subtotal_amount_value,
subtotal_amount_scale,
items_discount_amount_value,
items_discount_amount_scale,
global_discount_percentage_value,
global_discount_percentage_scale,
global_discount_amount_value,
global_discount_amount_scale,
total_discount_amount_value,
total_discount_amount_scale,
taxable_amount_value,
taxable_amount_scale,
iva_amount_value,
iva_amount_scale,
rec_amount_value,
rec_amount_scale,
retention_amount_value,
retention_amount_scale,
taxes_amount_value,
taxes_amount_scale,
total_amount_value,
total_amount_scale,
created_at,
updated_at,
deleted_at
)
SELECT
id,
company_id,
status,
proforma_id,
series,
invoice_number,
invoice_date,
operation_date,
reference,
description,
notes,
language_code,
currency_code,
customer_id,
customer_tin,
customer_name,
customer_street,
customer_street2,
customer_city,
customer_province,
customer_postal_code,
customer_country,
payment_method_id,
payment_method_description,
payment_method_description,
NULL AS payment_term_id,
NULL AS payment_term_name,
NULL AS payment_term_description,
NULL AS payment_term_snapshot_json,
tax_regime_code,
COALESCE(tax_regime_description, tax_regime_code),
subtotal_amount_value,
subtotal_amount_scale,
items_discount_amount_value,
items_discount_amount_scale,
global_discount_percentage_value,
global_discount_percentage_scale,
global_discount_amount_value,
global_discount_amount_scale,
total_discount_amount_value,
total_discount_amount_scale,
taxable_amount_value,
taxable_amount_scale,
iva_amount_value,
iva_amount_scale,
rec_amount_value,
rec_amount_scale,
retention_amount_value,
retention_amount_scale,
taxes_amount_value,
taxes_amount_scale,
total_amount_value,
total_amount_scale,
created_at,
updated_at,
deleted_at
FROM customer_invoices
WHERE is_proforma = 0;
Items
INSERT INTO proforma_items (
item_id,
proforma_id,
position,
description,
quantity_value,
quantity_scale,
unit_amount_value,
unit_amount_scale,
subtotal_amount_value,
subtotal_amount_scale,
item_discount_percentage_value,
item_discount_percentage_scale,
item_discount_amount_value,
item_discount_amount_scale,
global_discount_percentage_value,
global_discount_percentage_scale,
global_discount_amount_value,
global_discount_amount_scale,
total_discount_amount_value,
total_discount_amount_scale,
taxable_amount_value,
taxable_amount_scale,
iva_code,
iva_percentage_value,
iva_percentage_scale,
iva_amount_value,
iva_amount_scale,
rec_code,
rec_percentage_value,
rec_percentage_scale,
rec_amount_value,
rec_amount_scale,
retention_code,
retention_percentage_value,
retention_percentage_scale,
retention_amount_value,
retention_amount_scale,
taxes_amount_value,
taxes_amount_scale,
total_amount_value,
total_amount_scale
)
SELECT
i.item_id,
i.invoice_id,
i.position,
i.description,
i.quantity_value,
i.quantity_scale,
i.unit_amount_value,
i.unit_amount_scale,
i.subtotal_amount_value,
i.subtotal_amount_scale,
i.item_discount_percentage_value,
i.item_discount_percentage_scale,
i.item_discount_amount_value,
i.item_discount_amount_scale,
i.global_discount_percentage_value,
i.global_discount_percentage_scale,
i.global_discount_amount_value,
i.global_discount_amount_scale,
i.total_discount_amount_value,
i.total_discount_amount_scale,
i.taxable_amount_value,
i.taxable_amount_scale,
i.iva_code,
i.iva_percentage_value,
i.iva_percentage_scale,
i.iva_amount_value,
i.iva_amount_scale,
i.rec_code,
i.rec_percentage_value,
i.rec_percentage_scale,
i.rec_amount_value,
i.rec_amount_scale,
i.retention_code,
i.retention_percentage_value,
i.retention_percentage_scale,
i.retention_amount_value,
i.retention_amount_scale,
i.taxes_amount_value,
i.taxes_amount_scale,
i.total_amount_value,
i.total_amount_scale
FROM customer_invoice_items i
JOIN customer_invoices ci ON ci.id = i.invoice_id
WHERE ci.is_proforma = 1;
INSERT INTO issued_invoice_items (...)
SELECT ...
FROM customer_invoice_items i
JOIN customer_invoices ci ON ci.id = i.invoice_id
WHERE ci.is_proforma = 0;
Taxes
INSERT INTO proforma_taxes (
tax_id,
proforma_id,
taxable_amount_value,
taxable_amount_scale,
iva_code,
iva_percentage_value,
iva_percentage_scale,
iva_amount_value,
iva_amount_scale,
rec_code,
rec_percentage_value,
rec_percentage_scale,
rec_amount_value,
rec_amount_scale,
retention_code,
retention_percentage_value,
retention_percentage_scale,
retention_amount_value,
retention_amount_scale,
taxes_amount_value,
taxes_amount_scale
)
SELECT
t.tax_id,
t.invoice_id,
t.taxable_amount_value,
t.taxable_amount_scale,
t.iva_code,
t.iva_percentage_value,
t.iva_percentage_scale,
t.iva_amount_value,
t.iva_amount_scale,
t.rec_code,
t.rec_percentage_value,
t.rec_percentage_scale,
t.rec_amount_value,
t.rec_amount_scale,
t.retention_code,
t.retention_percentage_value,
t.retention_percentage_scale,
t.retention_amount_value,
t.retention_amount_scale,
t.taxes_amount_value,
t.taxes_amount_scale
FROM customer_invoice_taxes t
JOIN customer_invoices ci ON ci.id = t.invoice_id
WHERE ci.is_proforma = 1;
INSERT INTO issued_invoice_taxes (...)
SELECT ...
FROM customer_invoice_taxes t
JOIN customer_invoices ci ON ci.id = t.invoice_id
WHERE ci.is_proforma = 0;
Verifactu
UPDATE verifactu_records vr
JOIN customer_invoices ci ON ci.id = vr.invoice_id
SET vr.issued_invoice_id = vr.invoice_id
WHERE ci.is_proforma = 0;
Validaciones SQL
Conteos
SELECT COUNT(*) FROM customer_invoices WHERE is_proforma = 1;
SELECT COUNT(*) FROM proformas;
SELECT COUNT(*) FROM customer_invoices WHERE is_proforma = 0;
SELECT COUNT(*) FROM issued_invoices;
SELECT COUNT(*)
FROM customer_invoice_items i
JOIN customer_invoices ci ON ci.id = i.invoice_id
WHERE ci.is_proforma = 1;
SELECT COUNT(*) FROM proforma_items;
SELECT COUNT(*)
FROM customer_invoice_taxes t
JOIN customer_invoices ci ON ci.id = t.invoice_id
WHERE ci.is_proforma = 0;
SELECT COUNT(*) FROM issued_invoice_taxes;
Duplicados fiscales
SELECT company_id, invoice_series_code, invoice_number, COUNT(*)
FROM issued_invoices
GROUP BY company_id, invoice_series_code, invoice_number
HAVING COUNT(*) > 1;
Huerfanos
SELECT COUNT(*)
FROM issued_invoices ii
LEFT JOIN proformas p ON p.id = ii.source_proforma_id
WHERE ii.source_proforma_id IS NOT NULL
AND p.id IS NULL;
SELECT COUNT(*)
FROM verifactu_records vr
LEFT JOIN issued_invoices ii ON ii.id = vr.issued_invoice_id
WHERE vr.issued_invoice_id IS NOT NULL
AND ii.id IS NULL;
Totales
SELECT SUM(total_amount_value) FROM customer_invoices WHERE is_proforma = 1;
SELECT SUM(total_amount_value) FROM proformas;
SELECT SUM(total_amount_value) FROM customer_invoices WHERE is_proforma = 0;
SELECT SUM(total_amount_value) FROM issued_invoices;
No incluido en fase 1A
- no cutover del backend
- no borrado de tablas legacy
- no doble escritura
- no ejecucion de migraciones en una BD real
- no integracion Verifactu externa
- no dominio de cobros, vencimientos vivos o conciliacion
No incluido en fase 1C
- no borrado de legacy
- no backfill real
- no ejecucion de migraciones
- no doble escritura
- no integracion Verifactu funcional nueva
- no cambios en controllers, rutas o DTOs publicos
No incluido en fase 1D
- no ejecucion en produccion
- no borrado de tablas legacy
- no eliminacion de modelos/repositorios legacy
- no integracion Verifacti funcional
- no dominio de cobros
- no doble escritura
Resultado de validacion tecnica fase 1D
tscmodulocustomer-invoices: esperado ejecutar en esta fasebiome lintsobresrc/api/infrastructureydocs/customer-invoices: esperado ejecutar en esta fasetscraiz: esperado ejecutar en esta fase- Validacion SQL real: no ejecutada por falta de entorno de desarrollo inequivoco
- Validacion runtime real contra BD migrada: no ejecutada por la misma razon
Siguiente fase recomendada
- confirmar
.env/credenciales de una BD de desarrollo segura - ejecutar
dev-split-proformas-issued-invoices.sql - ejecutar
validate-split-proformas-issued-invoices.sql - levantar el backend contra esa BD y validar
create,update,get,listeissue proforma - validar numeracion de proformas V2 y asignacion de serie/numero de factura emitida
- revisar la ruta
VerifactuRecord -> issued_invoice_idya con persistencia V2 activa