Característica: Mejora de los modelos de facturas proforma y emitidas con atributos de serie de documentos

- Se han añadido `document_series_id`, `invoice_series_code` e `invoice_reference` a los modelos y instantáneas de facturas emitidas.
- Se han introducido `proforma_series_code` y `proforma_number`, y se han actualizado las referencias en los modelos y instantáneas de las facturas proforma.
- Se han actualizado los esquemas de la base de datos y los scripts SQL para dar cabida a los nuevos campos y garantizar la integridad de los datos.
- Se ha elaborado la documentación correspondiente a las nuevas series de documentos «contrato de instantánea» y «contrato de serie de facturas emitidas».
- Se han implementado scripts SQL de rellenado para completar los registros existentes con los nuevos campos.
This commit is contained in:
David Arranz 2026-08-04 11:05:02 +02:00
parent 1f5bd18665
commit ee040b8335
51 changed files with 456 additions and 43 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,31 @@
# Document Series Snapshot Contract
## Regla base
- `document-series` asigna numero y referencia.
- `proformas` e `issued_invoices` persisten un snapshot inmutable del resultado.
- las referencias visibles no se recalculan en lecturas normales.
## Snapshot persistido
### Proformas
- `document_series_id`
- `proforma_series_code`
- `proforma_number`
- `proforma_reference`
### Issued invoices
- `document_series_id`
- `invoice_series_code`
- `invoice_number`
- `invoice_reference`
## Motivo
- `document_series_id` conserva trazabilidad tecnica exacta.
- `series_code` conserva un snapshot legible e historico.
- `number` conserva el valor estructurado y consultable.
- `reference` conserva el valor visible/legal final.
- una factura emitida es historica e inalterable aunque luego cambie la configuracion viva de `document_series`.

View File

@ -0,0 +1,42 @@
# Issued Invoice Series Contract
## Objetivo
Definir el contrato backend para la numeracion documental de `issued_invoices`.
## Snapshot persistido
Una factura emitida debe persistir estos cuatro datos de numeracion:
- `document_series_id`
- `invoice_series_code`
- `invoice_number`
- `invoice_reference`
## Regla operativa
- `document-series` es la unica fuente de verdad para asignar numeracion de `document_type = issued_invoice`.
- `customer-invoices` no recompone manualmente la referencia final si `document-series` ya la devuelve.
- `invoice_reference` se calcula en el momento de emision.
- `invoice_reference` se persiste como snapshot documental inmutable.
- `invoice_reference` no se recalcula despues aunque cambie la configuracion de `document_series`.
## Emision desde proforma
- si `target_invoice_series_code` tiene valor, se usa como `seriesCode` de `issued_invoice`
- si `target_invoice_series_code` es `NULL`, `document-series` resuelve la serie default activa
- la respuesta de `assignNextNumber(...)` debe aportar `series_id`, `series_code`, `number` y `reference`
- `issued_invoices` debe persistir `document_series_id`, `invoice_series_code`, `invoice_number` e `invoice_reference`
## Semantica de campos
- `document_series_id`: id tecnico de la serie exacta usada al emitir para trazabilidad y backoffice
- `invoice_number`: numero secuencial dentro de la serie
- `invoice_reference`: referencia final visible/legal compuesta por la serie documental
- `reference`: referencia comercial libre del documento, si aplica; no sustituye a `invoice_reference`
## Regla de no contaminacion
- `proforma_reference` nunca se reutiliza como `invoice_reference`
- `target_invoice_series_code` solo selecciona la serie futura y no es en si mismo la referencia final
- no se usa `MAX(invoice_number)` para recomponer numeracion

View File

@ -9,6 +9,7 @@ Normalizar el contrato publico de proformas para separar definitivamente la seri
En proformas: En proformas:
- `document_series_id` identifica la serie documental propia de la proforma - `document_series_id` identifica la serie documental propia de la proforma
- `proforma_series_code` guarda el codigo snapshot de la serie propia de la proforma
- `proforma_number` guarda el numero propio de la proforma - `proforma_number` guarda el numero propio de la proforma
- `proforma_reference` guarda la referencia visible de la proforma, por ejemplo `PF-0139` - `proforma_reference` guarda la referencia visible de la proforma, por ejemplo `PF-0139`
- `proforma_series_code` es el codigo funcional que se usa solo en create para elegir una serie de `document_type = proforma` - `proforma_series_code` es el codigo funcional que se usa solo en create para elegir una serie de `document_type = proforma`
@ -92,7 +93,7 @@ Si el request informa `proforma_series_code`:
- debe corresponder a una serie activa de `document_type = proforma` - debe corresponder a una serie activa de `document_type = proforma`
- solo se usa para numerar la proforma en create - solo se usa para numerar la proforma en create
- despues queda congelado en `document_series_id`, `proforma_number` y `proforma_reference` - despues queda congelado en `document_series_id`, `proforma_series_code`, `proforma_number` y `proforma_reference`
## Retirada futura de `series` ## Retirada futura de `series`

View File

@ -32,7 +32,7 @@ Preparar el esquema fisico para separar:
- `target_invoice_series_code` es opcional y solo puede apuntar a una serie de `issued_invoice`. - `target_invoice_series_code` es opcional y solo puede apuntar a una serie de `issued_invoice`.
- una serie `PF` o cualquier otra serie de `document_type = proforma` nunca debe persistirse en `target_invoice_series_code`. - una serie `PF` o cualquier otra serie de `document_type = proforma` nunca debe persistirse en `target_invoice_series_code`.
- si `target_invoice_series_code` queda `NULL`, la emision de la factura usara la serie default activa de `issued_invoice`. - si `target_invoice_series_code` queda `NULL`, la emision de la factura usara la serie default activa de `issued_invoice`.
- `issued_invoices` usa `invoice_series_code`, `invoice_number`, `invoice_date` y `source_proforma_id`. - `issued_invoices` usa `invoice_series_code`, `invoice_number`, `invoice_reference`, `invoice_date` y `source_proforma_id`.
- `proformas` no guarda `issued_invoice_id`; la relacion se resuelve con `issued_invoices.source_proforma_id`. - `proformas` no guarda `issued_invoice_id`; la relacion se resuelve con `issued_invoices.source_proforma_id`.
- Los impuestos del esquema actual son por documento, no por linea: `proforma_taxes` y `issued_invoice_taxes`. - Los impuestos del esquema actual son por documento, no por linea: `proforma_taxes` y `issued_invoice_taxes`.
- `payment_method` y `payment_term` en `issued_invoices` son snapshot historico, no estado vivo de cobro. - `payment_method` y `payment_term` en `issued_invoices` son snapshot historico, no estado vivo de cobro.
@ -119,6 +119,8 @@ Preparar el esquema fisico para separar:
- al emitir una proforma, la factura definitiva se numera con `document_type = issued_invoice` - al emitir una proforma, la factura definitiva se numera con `document_type = issued_invoice`
- si la proforma trae `target_invoice_series_code`, se usa como `seriesCode` - si la proforma trae `target_invoice_series_code`, se usa como `seriesCode`
- si `target_invoice_series_code` es `NULL`, `document-series` resuelve la serie default activa de `issued_invoice` - si `target_invoice_series_code` es `NULL`, `document-series` resuelve la serie default activa de `issued_invoice`
- la referencia final devuelta por `document-series` se persiste en `issued_invoices.invoice_reference`
- `invoice_reference` es snapshot documental y no se recalcula despues
- `proforma_series_code` es un input de create para `document_type = proforma` y no vuelve a ser editable despues - `proforma_series_code` es un input de create para `document_type = proforma` y no vuelve a ser editable despues
## Estado Fase 2B ## Estado Fase 2B
@ -218,6 +220,7 @@ Preparar el esquema fisico para separar:
- `source_proforma_id` - `source_proforma_id`
- `invoice_series_code` - `invoice_series_code`
- `invoice_number` - `invoice_number`
- `invoice_reference`
- `invoice_date` - `invoice_date`
- `operation_date` - `operation_date`
- `reference` - `reference`

View File

@ -8,6 +8,8 @@ Este directorio mezcla SQL vigentes de soporte con SQL historicos/transicionales
- `add-proforma-document-series-columns.sql` - `add-proforma-document-series-columns.sql`
- `add-proforma-tax-config-columns.sql` - `add-proforma-tax-config-columns.sql`
- `add-issued-invoice-reference.sql`
- `normalize-document-series-snapshots.sql`
- `fix-proforma-series-semantics.sql` - `fix-proforma-series-semantics.sql`
- `validate-proforma-tax-config.sql` - `validate-proforma-tax-config.sql`
- `validate-proforma-soft-delete.sql` - `validate-proforma-soft-delete.sql`
@ -24,8 +26,9 @@ Estos archivos no deben tratarse como el camino normal de entornos nuevos salvo
1. Verificar que la BD objetivo es inequívocamente de desarrollo. 1. Verificar que la BD objetivo es inequívocamente de desarrollo.
2. Hacer backup o confirmar que los datos son descartables. 2. Hacer backup o confirmar que los datos son descartables.
3. Ejecutar el DDL vigente que falte para `proformas`. 3. Ejecutar el DDL vigente que falte para `proformas`.
4. Ejecutar las validaciones SQL vigentes. 4. Ejecutar `normalize-document-series-snapshots.sql` para completar snapshots y validaciones de seriado.
5. Levantar backend V2 y validar flujos funcionales. 5. Ejecutar las validaciones SQL vigentes.
6. Levantar backend V2 y validar flujos funcionales.
## Propiedades de los scripts históricos de split ## Propiedades de los scripts históricos de split

View File

@ -0,0 +1,53 @@
-- Estado: vigente / manual.
-- Objetivo:
-- 1. Anadir `issued_invoices.invoice_reference` si no existe.
-- 2. Backfill conservador para facturas emitidas existentes.
-- 3. Validar que no quedan filas sin snapshot documental.
-- 4. Endurecer a NOT NULL cuando la validacion sea correcta.
--
-- No ejecutar automaticamente desde backend.
-- Revisar antes el nombre real de la tabla `document_series` y sus columnas
-- `prefix`, `padding`, `suffix` en el entorno objetivo.
START TRANSACTION;
ALTER TABLE issued_invoices
ADD COLUMN IF NOT EXISTS invoice_reference VARCHAR(64) NULL AFTER invoice_number;
UPDATE issued_invoices ii
JOIN document_series ds
ON ds.company_id = ii.company_id
AND ds.code = ii.invoice_series_code
AND ds.document_type = 'issued_invoice'
SET ii.invoice_reference = CONCAT(
COALESCE(ds.prefix, ''),
LPAD(CAST(ii.invoice_number AS CHAR), ds.padding, '0'),
COALESCE(ds.suffix, '')
)
WHERE ii.invoice_reference IS NULL;
SELECT id, source_proforma_id, invoice_series_code, invoice_number, invoice_reference
FROM issued_invoices
WHERE invoice_reference IS NULL;
SELECT
ii.id,
ii.invoice_series_code,
ii.invoice_number,
ii.invoice_reference,
ds.prefix,
ds.padding,
ds.suffix
FROM issued_invoices ii
JOIN document_series ds
ON ds.company_id = ii.company_id
AND ds.code = ii.invoice_series_code
AND ds.document_type = 'issued_invoice'
ORDER BY ii.created_at DESC
LIMIT 20;
-- Ejecutar solo cuando la consulta anterior no devuelva filas.
ALTER TABLE issued_invoices
MODIFY COLUMN invoice_reference VARCHAR(64) NOT NULL;
COMMIT;

View File

@ -170,6 +170,7 @@ CREATE TABLE IF NOT EXISTS issued_invoices (
source_proforma_id CHAR(36) NULL, source_proforma_id CHAR(36) NULL,
invoice_series_code VARCHAR(10) NOT NULL, invoice_series_code VARCHAR(10) NOT NULL,
invoice_number VARCHAR(12) NOT NULL, invoice_number VARCHAR(12) NOT NULL,
invoice_reference VARCHAR(64) NOT NULL,
invoice_date DATE NOT NULL, invoice_date DATE NOT NULL,
operation_date DATE NULL, operation_date DATE NULL,
reference VARCHAR(255) NULL, reference VARCHAR(255) NULL,

View File

@ -0,0 +1,85 @@
-- Normaliza snapshots documentales en `proformas` e `issued_invoices`.
-- Fases:
-- 1. DDL conservadora.
-- 2. Backfill de snapshots.
-- 3. Diagnostico/validacion.
-- 4. Endurecimiento opcional cuando no queden nulos.
ALTER TABLE proformas
ADD COLUMN IF NOT EXISTS proforma_series_code VARCHAR(10) NULL AFTER document_series_id;
ALTER TABLE issued_invoices
ADD COLUMN IF NOT EXISTS document_series_id CHAR(36) NULL AFTER source_proforma_id,
ADD COLUMN IF NOT EXISTS invoice_reference VARCHAR(64) NULL AFTER invoice_number;
UPDATE proformas p
JOIN document_series ds ON ds.id = p.document_series_id
SET p.proforma_series_code = ds.code
WHERE p.proforma_series_code IS NULL;
SELECT id, document_series_id, proforma_series_code, proforma_number, proforma_reference
FROM proformas
WHERE proforma_series_code IS NULL;
SELECT
ds.company_id,
ds.document_type,
ds.code,
COUNT(*) AS series_count
FROM document_series ds
WHERE ds.document_type = 'issued_invoice'
GROUP BY ds.company_id, ds.document_type, ds.code
HAVING COUNT(*) > 1;
UPDATE issued_invoices ii
JOIN document_series ds
ON ds.company_id = ii.company_id
AND ds.document_type = 'issued_invoice'
AND ds.code = ii.invoice_series_code
SET ii.document_series_id = ds.id
WHERE ii.document_series_id IS NULL;
SELECT id, invoice_series_code, invoice_number
FROM issued_invoices
ORDER BY created_at DESC
LIMIT 20;
UPDATE issued_invoices ii
JOIN document_series ds ON ds.id = ii.document_series_id
SET ii.invoice_reference = CONCAT(
COALESCE(ds.prefix, ''),
LPAD(CAST(ii.invoice_number AS UNSIGNED), ds.padding, '0'),
COALESCE(ds.suffix, '')
)
WHERE ii.invoice_reference IS NULL;
SELECT id, source_proforma_id, document_series_id, invoice_series_code, invoice_number, invoice_reference
FROM issued_invoices
WHERE document_series_id IS NULL
OR invoice_reference IS NULL;
SELECT company_id, proforma_series_code, proforma_number, COUNT(*) AS c
FROM proformas
WHERE deleted_at IS NULL
GROUP BY company_id, proforma_series_code, proforma_number
HAVING c > 1;
SELECT company_id, invoice_series_code, invoice_number, COUNT(*) AS c
FROM issued_invoices
WHERE deleted_at IS NULL
GROUP BY company_id, invoice_series_code, invoice_number
HAVING c > 1;
CREATE INDEX idx_proformas_document_series_id
ON proformas (document_series_id);
CREATE INDEX idx_issued_invoices_document_series_id
ON issued_invoices (document_series_id);
-- Ejecutar solo cuando las validaciones anteriores devuelvan 0 filas pendientes.
-- ALTER TABLE proformas
-- MODIFY proforma_series_code VARCHAR(10) NOT NULL;
--
-- ALTER TABLE issued_invoices
-- MODIFY document_series_id CHAR(36) NOT NULL,
-- MODIFY invoice_reference VARCHAR(64) NOT NULL;

View File

@ -52,8 +52,11 @@ La asignación usa transacción obligatoria y bloqueo de fila con `LOCK.UPDATE`
En V1: En V1:
- `issued-invoices` delega la asignacion de numero en `document-series` - `issued-invoices` delega la asignacion de numero en `document-series`
- `document-series.assignNextNumber(...)` devuelve `series_id`, `series_code`, `number` y `reference`
- `proformas` delega la asignacion de referencia en `document-series` - `proformas` delega la asignacion de referencia en `document-series`
- `proformas` persiste `document_series_id` y `proforma_number` como snapshot tecnico adicional a `proforma_reference` - `proformas` persiste `document_series_id` y `proforma_number` como snapshot tecnico adicional a `proforma_reference`
- `issued-invoices` persiste `invoice_reference` como snapshot documental inmutable adicional a `invoice_number`
- `issued-invoices.invoice_reference` no se recalcula si luego cambia `prefix`, `suffix` o `padding` de la serie
- `proformas.document_series_id` identifica la serie documental usada para numerar la proforma - `proformas.document_series_id` identifica la serie documental usada para numerar la proforma
- `proformas.proforma_number` guarda el numero propio de la proforma - `proformas.proforma_number` guarda el numero propio de la proforma
- `proformas.proforma_reference` guarda la referencia visible de la proforma - `proformas.proforma_reference` guarda la referencia visible de la proforma

View File

@ -5,7 +5,7 @@ import type { InvoiceNumber, InvoiceSerie } from "../../../domain";
export interface InvoiceSeriesAssignment { export interface InvoiceSeriesAssignment {
invoiceNumber: InvoiceNumber; invoiceNumber: InvoiceNumber;
formattedNumber: string; invoiceReference: string;
invoiceSeriesId: string; invoiceSeriesId: string;
invoiceSeriesCode: InvoiceSerie; invoiceSeriesCode: InvoiceSerie;
} }

View File

@ -14,7 +14,10 @@ export type IssuedInvoiceSummary = {
id: UniqueID; id: UniqueID;
companyId: UniqueID; companyId: UniqueID;
documentSeriesId: Maybe<string>;
invoiceSeriesCode: Maybe<InvoiceSerie>;
invoiceNumber: InvoiceNumber; invoiceNumber: InvoiceNumber;
invoiceReference: string;
status: InvoiceStatus; status: InvoiceStatus;
series: Maybe<InvoiceSerie>; series: Maybe<InvoiceSerie>;

View File

@ -1,5 +1,5 @@
import type { UniqueID } from "@repo/rdx-ddd"; import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Maybe, Result } from "@repo/rdx-utils";
import { IssuedInvoice } from "../../../domain"; import { IssuedInvoice } from "../../../domain";
import type { IInvoiceSeriesNumberAssigner } from "../../invoice-series"; import type { IInvoiceSeriesNumberAssigner } from "../../invoice-series";
@ -45,13 +45,13 @@ export class IssuedInvoiceCreator implements IIssuedInvoiceCreator {
return Result.fail(numberResult.error); return Result.fail(numberResult.error);
} }
const invoiceNumber = numberResult.data.invoiceNumber;
const invoiceResult = IssuedInvoice.create( const invoiceResult = IssuedInvoice.create(
{ {
...props, ...props,
documentSeriesId: Maybe.some(numberResult.data.invoiceSeriesId),
series: numberResult.data.invoiceSeriesCode, series: numberResult.data.invoiceSeriesCode,
invoiceNumber, invoiceNumber: numberResult.data.invoiceNumber,
invoiceReference: numberResult.data.invoiceReference,
companyId, companyId,
}, },
id id

View File

@ -34,8 +34,7 @@ export class IssuedInvoiceDocumentMetadataFactory
} }
private buildFilename(snapshot: IIssuedInvoiceReportSnapshot): string { private buildFilename(snapshot: IIssuedInvoiceReportSnapshot): string {
// Ejemplo: factura-F2024-000123-FULANITO.pdf return `factura-${snapshot.invoice_reference}-${snapshot.recipient.name}.pdf`;
return `factura-${snapshot.series}${snapshot.invoice_number}-${snapshot.recipient.name}.pdf`;
} }
private buildCacheKey(snapshot: IIssuedInvoiceReportSnapshot): string { private buildCacheKey(snapshot: IIssuedInvoiceReportSnapshot): string {
@ -43,8 +42,7 @@ export class IssuedInvoiceDocumentMetadataFactory
return [ return [
"issued-invoice", "issued-invoice",
snapshot.company_id, snapshot.company_id,
snapshot.series, snapshot.invoice_reference,
snapshot.invoice_number,
"v1", "v1",
].join(":"); ].join(":");
} }

View File

@ -1,6 +1,6 @@
import type { IDocumentProperties, IDocumentPropertiesFactory } from "@erp/core/api"; import type { IDocumentProperties, IDocumentPropertiesFactory } from "@erp/core/api";
import type { IssuedInvoiceReportSnapshot } from "../models"; import type { IIssuedInvoiceReportSnapshot } from "../snapshot-builders";
/** /**
* Construye los metadatos del documento PDF de una factura emitida. * Construye los metadatos del documento PDF de una factura emitida.
@ -10,11 +10,11 @@ import type { IssuedInvoiceReportSnapshot } from "../models";
* - Sin IO * - Sin IO
*/ */
export class IssuedInvoiceDocumentPropertiesFactory export class IssuedInvoiceDocumentPropertiesFactory
implements IDocumentPropertiesFactory<IssuedInvoiceReportSnapshot> implements IDocumentPropertiesFactory<IIssuedInvoiceReportSnapshot>
{ {
build(snapshot: IssuedInvoiceReportSnapshot): IDocumentProperties { build(snapshot: IIssuedInvoiceReportSnapshot): IDocumentProperties {
return { return {
title: snapshot.reference, title: snapshot.invoice_reference,
subject: "issued-invoice", subject: "issued-invoice",
author: snapshot.company_slug, author: snapshot.company_slug,
creator: "FactuGES ERP", creator: "FactuGES ERP",

View File

@ -19,7 +19,7 @@ import type { ProformaIssueReadModel } from "../models";
export type ProformaToIssuedInvoiceCreateProps = Omit< export type ProformaToIssuedInvoiceCreateProps = Omit<
IIssuedInvoiceCreateProps, IIssuedInvoiceCreateProps,
"invoiceNumber" | "series" "documentSeriesId" | "invoiceNumber" | "invoiceReference" | "series"
> & { > & {
series: Maybe<InvoiceSerie>; series: Maybe<InvoiceSerie>;
}; };

View File

@ -1,5 +1,6 @@
import type { ISnapshotBuilder } from "@erp/core/api"; import type { ISnapshotBuilder } from "@erp/core/api";
import { maybeToNullable } from "@repo/rdx-ddd"; import { maybeToNullable } from "@repo/rdx-ddd";
import { Maybe } from "@repo/rdx-utils";
import type { IssuedInvoice } from "../../../../domain"; import type { IssuedInvoice } from "../../../../domain";
@ -41,7 +42,10 @@ export class IssuedInvoiceFullSnapshotBuilder implements IIssuedInvoiceFullSnaps
company_id: invoice.companyId.toString(), company_id: invoice.companyId.toString(),
is_proforma: false, is_proforma: false,
document_series_id: maybeToNullable(invoice.documentSeriesId, (value) => value),
invoice_series_code: maybeToNullable(Maybe.some(invoice.series), (value) => value.toString()),
invoice_number: invoice.invoiceNumber.toString(), invoice_number: invoice.invoiceNumber.toString(),
invoice_reference: invoice.invoiceReference,
status: invoice.status.toPrimitive(), status: invoice.status.toPrimitive(),
series: invoice.series.toString(), series: invoice.series.toString(),

View File

@ -19,7 +19,10 @@ export interface IIssuedInvoiceFullSnapshot {
company_id: string; company_id: string;
is_proforma: boolean; is_proforma: boolean;
document_series_id: string | null;
invoice_series_code: string | null;
invoice_number: string; invoice_number: string;
invoice_reference: string;
status: string; status: string;
series: string; series: string;

View File

@ -15,7 +15,7 @@ export class IssuedInvoiceReportItemSnapshotBuilder
items: IIssuedInvoiceItemFullSnapshot[], items: IIssuedInvoiceItemFullSnapshot[],
params?: ISnapshotBuilderParams params?: ISnapshotBuilderParams
): IIssuedInvoiceReportItemSnapshot[] { ): IIssuedInvoiceReportItemSnapshot[] {
const locale = params?.locale as string; const locale = (params as { locale?: string } | undefined)?.locale ?? "es";
const moneyOptions = { const moneyOptions = {
hideZeros: true, hideZeros: true,
@ -23,7 +23,7 @@ export class IssuedInvoiceReportItemSnapshotBuilder
}; };
return items.map((item) => ({ return items.map((item) => ({
description: item.description, description: item.description ?? "",
quantity: QuantityDTOHelper.format(item.quantity, locale, { minimumFractionDigits: 0 }), quantity: QuantityDTOHelper.format(item.quantity, locale, { minimumFractionDigits: 0 }),
unit_amount: MoneyDTOHelper.format(item.unit_amount, locale, moneyOptions), unit_amount: MoneyDTOHelper.format(item.unit_amount, locale, moneyOptions),
subtotal_amount: MoneyDTOHelper.format(item.subtotal_amount, locale, moneyOptions), subtotal_amount: MoneyDTOHelper.format(item.subtotal_amount, locale, moneyOptions),

View File

@ -21,7 +21,7 @@ export class IssuedInvoiceReportSnapshotBuilder implements IIssuedInvoiceReportS
snapshot: IIssuedInvoiceFullSnapshot, snapshot: IIssuedInvoiceFullSnapshot,
params?: ISnapshotBuilderParams params?: ISnapshotBuilderParams
): IIssuedInvoiceReportSnapshot { ): IIssuedInvoiceReportSnapshot {
const locale = params?.locale as string; const locale = (params as { locale?: string } | undefined)?.locale ?? "es";
const moneyOptions = { const moneyOptions = {
hideZeros: true, hideZeros: true,
@ -33,6 +33,7 @@ export class IssuedInvoiceReportSnapshotBuilder implements IIssuedInvoiceReportS
company_id: snapshot.company_id, company_id: snapshot.company_id,
company_slug: "rodax", company_slug: "rodax",
invoice_number: snapshot.invoice_number, invoice_number: snapshot.invoice_number,
invoice_reference: snapshot.invoice_reference,
series: snapshot.series, series: snapshot.series,
status: snapshot.status, status: snapshot.status,
reference: snapshot.reference, reference: snapshot.reference,
@ -43,7 +44,7 @@ export class IssuedInvoiceReportSnapshotBuilder implements IIssuedInvoiceReportS
invoice_date: DateHelper.format(snapshot.invoice_date, locale), invoice_date: DateHelper.format(snapshot.invoice_date, locale),
payment_method: snapshot.payment_method?.payment_description ?? "", payment_method: snapshot.payment_method?.payment_description ?? "",
notes: snapshot.notes, notes: snapshot.notes ?? "",
recipient: { recipient: {
name: snapshot.recipient.name, name: snapshot.recipient.name,
@ -65,7 +66,8 @@ export class IssuedInvoiceReportSnapshotBuilder implements IIssuedInvoiceReportS
verifactu: { verifactu: {
...snapshot.verifactu, ...snapshot.verifactu,
qr_code: snapshot.verifactu.qr_code.replace("data:image/png;base64,", ""), url: snapshot.verifactu.url ?? "",
qr_code: (snapshot.verifactu.qr_code ?? "").replace("data:image/png;base64,", ""),
}, },
}; };
} }

View File

@ -6,9 +6,10 @@ export interface IIssuedInvoiceReportSnapshot {
company_id: string; company_id: string;
company_slug: string; company_slug: string;
invoice_number: string; invoice_number: string;
invoice_reference: string;
series: string; series: string;
status: string; status: string;
reference: string; reference: string | null;
language_code: string; language_code: string;
currency_code: string; currency_code: string;

View File

@ -15,7 +15,7 @@ export class IssuedInvoiceReportTaxSnapshotBuilder
taxes: IIssuedInvoiceTaxFullSnapshot[], taxes: IIssuedInvoiceTaxFullSnapshot[],
params?: ISnapshotBuilderParams params?: ISnapshotBuilderParams
): IIssuedInvoiceReportTaxSnapshot[] { ): IIssuedInvoiceReportTaxSnapshot[] {
const locale = params?.locale as string; const locale = (params as { locale?: string } | undefined)?.locale ?? "es";
const moneyOptions = { const moneyOptions = {
hideZeros: true, hideZeros: true,
@ -29,11 +29,11 @@ export class IssuedInvoiceReportTaxSnapshotBuilder
iva_percentage: PercentageDTOHelper.format(tax.iva_percentage, locale), iva_percentage: PercentageDTOHelper.format(tax.iva_percentage, locale),
iva_amount: MoneyDTOHelper.format(tax.iva_amount, locale, moneyOptions), iva_amount: MoneyDTOHelper.format(tax.iva_amount, locale, moneyOptions),
rec_code: tax.rec_code, rec_code: tax.rec_code ?? "",
rec_percentage: PercentageDTOHelper.format(tax.rec_percentage, locale), rec_percentage: PercentageDTOHelper.format(tax.rec_percentage, locale),
rec_amount: MoneyDTOHelper.format(tax.rec_amount, locale, moneyOptions), rec_amount: MoneyDTOHelper.format(tax.rec_amount, locale, moneyOptions),
retention_code: tax.retention_code, retention_code: tax.retention_code ?? "",
retention_percentage: PercentageDTOHelper.format(tax.retention_percentage, locale), retention_percentage: PercentageDTOHelper.format(tax.retention_percentage, locale),
retention_amount: MoneyDTOHelper.format(tax.retention_amount, locale, moneyOptions), retention_amount: MoneyDTOHelper.format(tax.retention_amount, locale, moneyOptions),

View File

@ -32,7 +32,10 @@ export class IssuedInvoiceSummarySnapshotBuilder implements IIssuedInvoiceSummar
id: invoice.id.toString(), id: invoice.id.toString(),
company_id: invoice.companyId.toString(), company_id: invoice.companyId.toString(),
document_series_id: maybeToNullable(invoice.documentSeriesId, (value) => value),
invoice_series_code: maybeToNullable(invoice.invoiceSeriesCode, (value) => value.toString()),
invoice_number: invoice.invoiceNumber.toString(), invoice_number: invoice.invoiceNumber.toString(),
invoice_reference: invoice.invoiceReference,
status: invoice.status.toPrimitive(), status: invoice.status.toPrimitive(),
series: maybeToEmptyString(invoice.series, (v) => v.toString()), series: maybeToEmptyString(invoice.series, (v) => v.toString()),

View File

@ -5,7 +5,10 @@ export interface IIssuedInvoiceSummarySnapshot {
id: string; id: string;
company_id: string; company_id: string;
document_series_id: string | null;
invoice_series_code: string | null;
invoice_number: string; invoice_number: string;
invoice_reference: string;
status: string; status: string;
series: string; series: string;

View File

@ -13,6 +13,9 @@ export type ProformaSummary = {
id: UniqueID; id: UniqueID;
companyId: UniqueID; companyId: UniqueID;
documentSeriesId: Maybe<string>;
proformaSeriesCode: Maybe<InvoiceSerie>;
proformaNumber: Maybe<InvoiceNumber>;
proformaReference: InvoiceNumber; proformaReference: InvoiceNumber;
status: InvoiceStatus; status: InvoiceStatus;
archivedAt: Maybe<UtcDate>; archivedAt: Maybe<UtcDate>;

View File

@ -6,6 +6,7 @@ import {
type IProformaCreateProps, type IProformaCreateProps,
type IProformaItemCreateProps, type IProformaItemCreateProps,
InvoiceNumber, InvoiceNumber,
InvoiceSerie,
Proforma, Proforma,
ProformaTaxConfig, ProformaTaxConfig,
} from "../../../domain"; } from "../../../domain";
@ -98,6 +99,12 @@ export class ProformaCreator implements IProformaCreator {
return Result.fail(proformaNumberResult.error); return Result.fail(proformaNumberResult.error);
} }
const proformaSeriesCodeResult = InvoiceSerie.create(numberResult.data.documentSeriesCode);
if (proformaSeriesCodeResult.isFailure) {
return Result.fail(proformaSeriesCodeResult.error);
}
const proformaReference = proformaReferenceResult.data; const proformaReference = proformaReferenceResult.data;
// 2. Crear agregado // 2. Crear agregado
@ -107,6 +114,7 @@ export class ProformaCreator implements IProformaCreator {
companyId, companyId,
archivedAt: Maybe.none(), archivedAt: Maybe.none(),
documentSeriesId: Maybe.some(numberResult.data.documentSeriesId), documentSeriesId: Maybe.some(numberResult.data.documentSeriesId),
proformaSeriesCode: Maybe.some(proformaSeriesCodeResult.data),
proformaNumber: Maybe.some(proformaNumberResult.data), proformaNumber: Maybe.some(proformaNumberResult.data),
proformaReference, proformaReference,
targetInvoiceSeriesCode: resolvedPropsResult.data.targetInvoiceSeriesCode, targetInvoiceSeriesCode: resolvedPropsResult.data.targetInvoiceSeriesCode,
@ -142,7 +150,10 @@ export class ProformaCreator implements IProformaCreator {
transaction?: unknown transaction?: unknown
): Promise< ): Promise<
Result< Result<
Omit<IProformaCreateProps, "proformaReference" | "proformaNumber" | "documentSeriesId">, Omit<
IProformaCreateProps,
"proformaReference" | "proformaNumber" | "documentSeriesId" | "proformaSeriesCode"
>,
Error Error
> >
> { > {

View File

@ -14,7 +14,7 @@ export class ProformaDocumentPropertiesFactory
{ {
build(snapshot: ProformaReportSnapshot): IDocumentProperties { build(snapshot: ProformaReportSnapshot): IDocumentProperties {
return { return {
title: snapshot.reference ?? `Proforma ${snapshot.series}${snapshot.invoice_number}`, title: snapshot.reference ?? snapshot.proforma_reference,
subject: "proforma", subject: "proforma",
author: snapshot.company.legalName, author: snapshot.company.legalName,
creator: "FactuGES ERP", creator: "FactuGES ERP",

View File

@ -40,6 +40,11 @@ export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder
id: proforma.id.toString(), id: proforma.id.toString(),
company_id: proforma.companyId.toString(), company_id: proforma.companyId.toString(),
document_series_id: maybeToNullable(proforma.documentSeriesId, (value) => value),
proforma_series_code: maybeToNullable(proforma.proformaSeriesCode, (value) =>
value.toString()
),
proforma_number: maybeToNullable(proforma.proformaNumber, (value) => value.toString()),
proforma_reference: proforma.proformaReference.toString(), proforma_reference: proforma.proformaReference.toString(),
status: proforma.status.toPrimitive() as ProformaFullSnapshot["status"], status: proforma.status.toPrimitive() as ProformaFullSnapshot["status"],
archived_at: maybeToNullable(proforma.archivedAt, (value) => value.toISOString()), archived_at: maybeToNullable(proforma.archivedAt, (value) => value.toISOString()),

View File

@ -12,8 +12,11 @@ export class IssueProformaResponseSnapshotBuilder implements IIssueProformaRespo
issued_invoice: { issued_invoice: {
id: invoice.id.toString(), id: invoice.id.toString(),
proforma_id: invoice.linkedProformaId.toString(), proforma_id: invoice.linkedProformaId.toString(),
document_series_id: invoice.documentSeriesId.getOrUndefined() ?? null,
invoice_series_code: invoice.series.toString(),
series: invoice.series.toString(), series: invoice.series.toString(),
invoice_number: invoice.invoiceNumber.toString(), invoice_number: invoice.invoiceNumber.toString(),
invoice_reference: invoice.invoiceReference,
invoice_date: invoice.invoiceDate.toDateString(), invoice_date: invoice.invoiceDate.toDateString(),
status: "issued", status: "issued",
recipient: { recipient: {

View File

@ -53,6 +53,7 @@ export class ProformaReportSnapshotBuilder implements IProformaReportSnapshotBui
return { return {
id: snapshot.id, id: snapshot.id,
company_id: snapshot.company_id, company_id: snapshot.company_id,
proforma_reference: snapshot.proforma_reference,
invoice_number: snapshot.invoice_number, invoice_number: snapshot.invoice_number,
series: snapshot.series, series: snapshot.series,
status: snapshot.status, status: snapshot.status,

View File

@ -5,6 +5,7 @@ import type { ProformaReportTaxSnapshot } from "./proforma-report-tax-snapshot.i
export interface ProformaReportSnapshot { export interface ProformaReportSnapshot {
id: string; id: string;
company_id: string; company_id: string;
proforma_reference: string;
invoice_number: string; invoice_number: string;
series: string | null; series: string | null;
status: string; status: string;

View File

@ -15,11 +15,18 @@ export class ProformaSummarySnapshotBuilder implements IProformaSummarySnapshotB
id: proforma.id.toString(), id: proforma.id.toString(),
company_id: proforma.companyId.toString(), company_id: proforma.companyId.toString(),
document_series_id: maybeToNullable(proforma.documentSeriesId, (value) => value),
proforma_series_code: maybeToNullable(proforma.proformaSeriesCode, (value) =>
value.toString()
),
proforma_number: maybeToNullable(proforma.proformaNumber, (value) => value.toString()),
proforma_reference: proforma.proformaReference.toString(), proforma_reference: proforma.proformaReference.toString(),
status: proforma.status.toPrimitive() as ProformaSummaryDTO["status"], status: proforma.status.toPrimitive() as ProformaSummaryDTO["status"],
archived_at: maybeToNullable(proforma.archivedAt, (value) => value.toISOString()), archived_at: maybeToNullable(proforma.archivedAt, (value) => value.toISOString()),
target_invoice_series_code: maybeToNullable(proforma.series, (value) => value.toString()), target_invoice_series_code: maybeToNullable(proforma.targetInvoiceSeriesCode, (value) =>
series: maybeToNullable(proforma.series, (value) => value.toString()), value.toString()
),
series: maybeToNullable(proforma.targetInvoiceSeriesCode, (value) => value.toString()),
proforma_date: proforma.proformaDate.toDateString(), proforma_date: proforma.proformaDate.toDateString(),
operation_date: maybeToNullable(proforma.operationDate, (value) => value.toDateString()), operation_date: maybeToNullable(proforma.operationDate, (value) => value.toDateString()),

View File

@ -35,8 +35,10 @@ export interface IIssuedInvoiceCreateProps {
linkedProformaId: UniqueID; // <- id de la proforma padre en caso de issue linkedProformaId: UniqueID; // <- id de la proforma padre en caso de issue
documentSeriesId: Maybe<string>;
series: InvoiceSerie; series: InvoiceSerie;
invoiceNumber: InvoiceNumber; invoiceNumber: InvoiceNumber;
invoiceReference: string;
invoiceDate: UtcDate; invoiceDate: UtcDate;
operationDate: Maybe<UtcDate>; operationDate: Maybe<UtcDate>;
@ -151,6 +153,16 @@ export class IssuedInvoice
); );
} }
if (!props.invoiceReference.trim()) {
return Result.fail(
new DomainValidationError(
"MISSING_INVOICE_REFERENCE",
"invoiceReference",
"Issued invoice requires invoice reference"
)
);
}
return Result.ok(); return Result.ok();
} }
@ -194,6 +206,10 @@ export class IssuedInvoice
return this.props.linkedProformaId; return this.props.linkedProformaId;
} }
public get documentSeriesId(): Maybe<string> {
return this.props.documentSeriesId;
}
public get status(): InvoiceStatus { public get status(): InvoiceStatus {
return this.props.status; return this.props.status;
} }
@ -206,6 +222,10 @@ export class IssuedInvoice
return this.props.invoiceNumber; return this.props.invoiceNumber;
} }
public get invoiceReference(): string {
return this.props.invoiceReference;
}
public get invoiceDate(): UtcDate { public get invoiceDate(): UtcDate {
return this.props.invoiceDate; return this.props.invoiceDate;
} }

View File

@ -51,6 +51,7 @@ export interface IProformaCreateProps {
archivedAt: Maybe<UtcDate>; archivedAt: Maybe<UtcDate>;
documentSeriesId: Maybe<string>; documentSeriesId: Maybe<string>;
proformaSeriesCode: Maybe<InvoiceSerie>;
proformaNumber: Maybe<InvoiceNumber>; proformaNumber: Maybe<InvoiceNumber>;
proformaReference: InvoiceNumber; proformaReference: InvoiceNumber;
targetInvoiceSeriesCode: Maybe<InvoiceSerie>; targetInvoiceSeriesCode: Maybe<InvoiceSerie>;
@ -105,6 +106,7 @@ export interface IProforma {
archivedAt: Maybe<UtcDate>; archivedAt: Maybe<UtcDate>;
documentSeriesId: Maybe<string>; documentSeriesId: Maybe<string>;
proformaSeriesCode: Maybe<InvoiceSerie>;
proformaNumber: Maybe<InvoiceNumber>; proformaNumber: Maybe<InvoiceNumber>;
targetInvoiceSeriesCode: Maybe<InvoiceSerie>; targetInvoiceSeriesCode: Maybe<InvoiceSerie>;
proformaReference: InvoiceNumber; proformaReference: InvoiceNumber;
@ -266,6 +268,10 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
return this.props.documentSeriesId; return this.props.documentSeriesId;
} }
public get proformaSeriesCode(): Maybe<InvoiceSerie> {
return this.props.proformaSeriesCode;
}
public get proformaNumber(): Maybe<InvoiceNumber> { public get proformaNumber(): Maybe<InvoiceNumber> {
return this.props.proformaNumber; return this.props.proformaNumber;
} }

View File

@ -44,8 +44,10 @@ export class IssuedInvoiceModel extends Model<
declare status: string; declare status: string;
declare source_proforma_id: CreationOptional<string | null>; declare source_proforma_id: CreationOptional<string | null>;
declare document_series_id: CreationOptional<string | null>;
declare invoice_series_code: string; declare invoice_series_code: string;
declare invoice_number: string; declare invoice_number: string;
declare invoice_reference: string;
declare invoice_date: string; declare invoice_date: string;
declare operation_date: CreationOptional<string | null>; declare operation_date: CreationOptional<string | null>;
@ -231,6 +233,13 @@ export default (database: Sequelize) => {
charset: "utf8mb4", charset: "utf8mb4",
collate: "utf8mb4_bin", collate: "utf8mb4_bin",
} as any, } as any,
document_series_id: {
type: DataTypes.UUID,
allowNull: true,
defaultValue: null,
charset: "utf8mb4",
collate: "utf8mb4_bin",
} as any,
invoice_series_code: { invoice_series_code: {
type: new DataTypes.STRING(10), type: new DataTypes.STRING(10),
@ -240,6 +249,10 @@ export default (database: Sequelize) => {
type: new DataTypes.STRING(12), type: new DataTypes.STRING(12),
allowNull: false, allowNull: false,
}, },
invoice_reference: {
type: new DataTypes.STRING(64),
allowNull: false,
},
invoice_date: { invoice_date: {
type: new DataTypes.DATEONLY(), type: new DataTypes.DATEONLY(),
allowNull: false, allowNull: false,
@ -502,6 +515,10 @@ export default (database: Sequelize) => {
name: "idx_issued_invoices_company", name: "idx_issued_invoices_company",
fields: ["company_id"], fields: ["company_id"],
}, },
{
name: "idx_issued_invoices_document_series_id",
fields: ["document_series_id"],
},
{ {
name: "uq_issued_invoice_company_series_number", name: "uq_issued_invoice_company_series_number",
fields: ["company_id", "invoice_series_code", "invoice_number"], fields: ["company_id", "invoice_series_code", "invoice_number"],

View File

@ -37,6 +37,7 @@ export class ProformaModel extends Model<
declare archived_at: CreationOptional<string | null>; declare archived_at: CreationOptional<string | null>;
declare document_series_id: CreationOptional<string | null>; declare document_series_id: CreationOptional<string | null>;
declare proforma_series_code: CreationOptional<string | null>;
declare proforma_number: CreationOptional<string | null>; declare proforma_number: CreationOptional<string | null>;
declare proforma_reference: string; declare proforma_reference: string;
declare proforma_date: string; declare proforma_date: string;
@ -192,6 +193,11 @@ export default (database: Sequelize) => {
charset: "utf8mb4", charset: "utf8mb4",
collate: "utf8mb4_bin", collate: "utf8mb4_bin",
} as any, } as any,
proforma_series_code: {
type: new DataTypes.STRING(10),
allowNull: true,
defaultValue: null,
},
proforma_number: { proforma_number: {
type: new DataTypes.STRING(32), type: new DataTypes.STRING(32),
allowNull: true, allowNull: true,
@ -479,6 +485,15 @@ export default (database: Sequelize) => {
name: "idx_proformas_company_document_series", name: "idx_proformas_company_document_series",
fields: ["company_id", "document_series_id"], fields: ["company_id", "document_series_id"],
}, },
{
name: "idx_proformas_document_series_id",
fields: ["document_series_id"],
},
{
name: "uq_proformas_company_series_number",
fields: ["company_id", "proforma_series_code", "proforma_number"],
unique: true,
},
{ {
name: "uq_proformas_company_reference", name: "uq_proformas_company_reference",
fields: ["company_id", "proforma_reference"], fields: ["company_id", "proforma_reference"],

View File

@ -60,7 +60,7 @@ export function buildIssuedInvoicePublicServices(
return Result.ok({ return Result.ok({
invoiceNumber: invoiceNumberResult.data, invoiceNumber: invoiceNumberResult.data,
formattedNumber: assignResult.data.reference, invoiceReference: assignResult.data.reference,
invoiceSeriesId: assignResult.data.series_id, invoiceSeriesId: assignResult.data.series_id,
invoiceSeriesCode: assignedSeriesResult.data, invoiceSeriesCode: assignedSeriesResult.data,
}); });

View File

@ -83,6 +83,11 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
"invoice_number", "invoice_number",
errors errors
); );
const invoiceReference = extractOrPushError(
Result.ok(`${String(raw.series ?? "")}${String(raw.invoice_number ?? "")}`),
"invoice_reference",
errors
);
// Fechas // Fechas
const invoiceDate = extractOrPushError( const invoiceDate = extractOrPushError(
@ -281,6 +286,7 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
status, status,
series, series,
invoiceNumber, invoiceNumber,
invoiceReference,
invoiceDate, invoiceDate,
operationDate, operationDate,
reference, reference,
@ -375,6 +381,7 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
status: attributes.status!, status: attributes.status!,
series: attributes.series!, series: attributes.series!,
invoiceNumber: attributes.invoiceNumber!, invoiceNumber: attributes.invoiceNumber!,
invoiceReference: attributes.invoiceReference!,
invoiceDate: attributes.invoiceDate!, invoiceDate: attributes.invoiceDate!,
operationDate: attributes.operationDate!, operationDate: attributes.operationDate!,

View File

@ -62,6 +62,11 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
"source_proforma_id", "source_proforma_id",
errors errors
); );
const documentSeriesId = extractOrPushError(
maybeFromNullableResult(raw.document_series_id, (value) => Result.ok(String(value))),
"document_series_id",
errors
);
const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors); const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors);
const series = extractOrPushError( const series = extractOrPushError(
InvoiceSerie.create(raw.invoice_series_code ?? ""), InvoiceSerie.create(raw.invoice_series_code ?? ""),
@ -73,6 +78,11 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
"invoice_number", "invoice_number",
errors errors
); );
const invoiceReference = extractOrPushError(
Result.ok(String(raw.invoice_reference ?? "")),
"invoice_reference",
errors
);
const invoiceDate = extractOrPushError( const invoiceDate = extractOrPushError(
UtcDate.createFromISO(raw.invoice_date), UtcDate.createFromISO(raw.invoice_date),
"invoice_date", "invoice_date",
@ -217,9 +227,11 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
companyId, companyId,
customerId, customerId,
linkedProformaId, linkedProformaId,
documentSeriesId,
status, status,
series, series,
invoiceNumber, invoiceNumber,
invoiceReference,
invoiceDate, invoiceDate,
operationDate, operationDate,
reference, reference,
@ -294,8 +306,10 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
const invoiceProps: InternalIssuedInvoiceProps = { const invoiceProps: InternalIssuedInvoiceProps = {
companyId: attributes.companyId!, companyId: attributes.companyId!,
status: attributes.status!, status: attributes.status!,
documentSeriesId: attributes.documentSeriesId!,
series: attributes.series!, series: attributes.series!,
invoiceNumber: attributes.invoiceNumber!, invoiceNumber: attributes.invoiceNumber!,
invoiceReference: attributes.invoiceReference!,
invoiceDate: attributes.invoiceDate!, invoiceDate: attributes.invoiceDate!,
operationDate: attributes.operationDate!, operationDate: attributes.operationDate!,
customerId: attributes.customerId!, customerId: attributes.customerId!,
@ -372,8 +386,10 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
company_id: source.companyId.toPrimitive(), company_id: source.companyId.toPrimitive(),
status: source.status.toPrimitive(), status: source.status.toPrimitive(),
source_proforma_id: source.linkedProformaId.toPrimitive(), source_proforma_id: source.linkedProformaId.toPrimitive(),
document_series_id: maybeToNullable(source.documentSeriesId, (value) => value),
invoice_series_code: source.series.toPrimitive(), invoice_series_code: source.series.toPrimitive(),
invoice_number: source.invoiceNumber.toPrimitive(), invoice_number: source.invoiceNumber.toPrimitive(),
invoice_reference: source.invoiceReference,
invoice_date: source.invoiceDate.toPrimitive(), invoice_date: source.invoiceDate.toPrimitive(),
operation_date: maybeToNullable(source.operationDate, (v) => v.toPrimitive()), operation_date: maybeToNullable(source.operationDate, (v) => v.toPrimitive()),
language_code: source.languageCode.toPrimitive(), language_code: source.languageCode.toPrimitive(),

View File

@ -92,6 +92,7 @@ export class SequelizeIssuedInvoiceSummaryMapper extends SequelizeQueryMapper<
status: attributes.status!, status: attributes.status!,
series: attributes.series!, series: attributes.series!,
invoiceNumber: attributes.invoiceNumber!, invoiceNumber: attributes.invoiceNumber!,
invoiceReference: attributes.invoiceReference!,
invoiceDate: attributes.invoiceDate!, invoiceDate: attributes.invoiceDate!,
operationDate: attributes.operationDate!, operationDate: attributes.operationDate!,
@ -141,6 +142,11 @@ export class SequelizeIssuedInvoiceSummaryMapper extends SequelizeQueryMapper<
"invoice_number", "invoice_number",
errors errors
); );
const invoiceReference = extractOrPushError(
Result.ok(`${String(raw.series ?? "")}${String(raw.invoice_number ?? "")}`),
"invoice_reference",
errors
);
const invoiceDate = extractOrPushError( const invoiceDate = extractOrPushError(
UtcDate.createFromISO(raw.invoice_date), UtcDate.createFromISO(raw.invoice_date),
@ -237,6 +243,7 @@ export class SequelizeIssuedInvoiceSummaryMapper extends SequelizeQueryMapper<
status, status,
series, series,
invoiceNumber, invoiceNumber,
invoiceReference,
invoiceDate, invoiceDate,
operationDate, operationDate,
reference, reference,

View File

@ -75,9 +75,12 @@ export class SequelizeIssuedInvoiceV2SummaryMapper extends SequelizeQueryMapper<
id: attributes.invoiceId!, id: attributes.invoiceId!,
companyId: attributes.companyId!, companyId: attributes.companyId!,
isProforma: false, isProforma: false,
documentSeriesId: attributes.documentSeriesId!,
invoiceSeriesCode: attributes.series!,
status: attributes.status!, status: attributes.status!,
series: attributes.series!, series: attributes.series!,
invoiceNumber: attributes.invoiceNumber!, invoiceNumber: attributes.invoiceNumber!,
invoiceReference: attributes.invoiceReference!,
invoiceDate: attributes.invoiceDate!, invoiceDate: attributes.invoiceDate!,
operationDate: attributes.operationDate!, operationDate: attributes.operationDate!,
description: attributes.description!, description: attributes.description!,
@ -102,6 +105,11 @@ export class SequelizeIssuedInvoiceV2SummaryMapper extends SequelizeQueryMapper<
const invoiceId = extractOrPushError(UniqueID.create(raw.id), "id", errors); const invoiceId = extractOrPushError(UniqueID.create(raw.id), "id", errors);
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors); const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors); const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
const documentSeriesId = extractOrPushError(
maybeFromNullableResult(raw.document_series_id, (value) => Result.ok(String(value))),
"document_series_id",
errors
);
const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors); const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors);
const series = extractOrPushError( const series = extractOrPushError(
maybeFromNullableResult(raw.invoice_series_code, (value) => InvoiceSerie.create(value)), maybeFromNullableResult(raw.invoice_series_code, (value) => InvoiceSerie.create(value)),
@ -113,6 +121,11 @@ export class SequelizeIssuedInvoiceV2SummaryMapper extends SequelizeQueryMapper<
"invoice_number", "invoice_number",
errors errors
); );
const invoiceReference = extractOrPushError(
Result.ok(String(raw.invoice_reference ?? "")),
"invoice_reference",
errors
);
const invoiceDate = extractOrPushError( const invoiceDate = extractOrPushError(
UtcDate.createFromISO(raw.invoice_date), UtcDate.createFromISO(raw.invoice_date),
"invoice_date", "invoice_date",
@ -182,9 +195,11 @@ export class SequelizeIssuedInvoiceV2SummaryMapper extends SequelizeQueryMapper<
invoiceId, invoiceId,
companyId, companyId,
customerId, customerId,
documentSeriesId,
status, status,
series, series,
invoiceNumber, invoiceNumber,
invoiceReference,
invoiceDate, invoiceDate,
operationDate, operationDate,
reference, reference,

View File

@ -175,10 +175,11 @@ export class SequelizeIssuedInvoiceRepositoryV2
try { try {
const criteriaConverter = new CriteriaToSequelizeConverter(); const criteriaConverter = new CriteriaToSequelizeConverter();
const criteriaQuery = criteriaConverter.convert(criteria, { const criteriaQuery = criteriaConverter.convert(criteria, {
searchableFields: ["invoice_number", "reference", "description"], searchableFields: ["invoice_number", "invoice_reference", "reference", "description"],
mappings: { mappings: {
invoice_date: { type: "root", column: "invoice_date" }, invoice_date: { type: "root", column: "invoice_date" },
invoice_number: { type: "root", column: "invoice_number" }, invoice_number: { type: "root", column: "invoice_number" },
invoice_reference: { type: "root", column: "invoice_reference" },
reference: { type: "root", column: "reference" }, reference: { type: "root", column: "reference" },
description: { type: "root", column: "description" }, description: { type: "root", column: "description" },
recipient_name: { type: "root", column: "customer_name" }, recipient_name: { type: "root", column: "customer_name" },

View File

@ -13,7 +13,7 @@ export class ProformaHtmlPreviewDocumentMetadataFactory
companySlug: snapshot.company.slug, companySlug: snapshot.company.slug,
format: "HTML", format: "HTML",
languageCode: snapshot.localization.languageCode, languageCode: snapshot.localization.languageCode,
filename: `proforma-${snapshot.series}${snapshot.invoice_number}.html`, filename: `proforma-${snapshot.proforma_reference}.html`,
}; };
} }
} }

View File

@ -13,7 +13,7 @@ export class ProformaPdfDocumentMetadataFactory
companySlug: snapshot.company.slug, companySlug: snapshot.company.slug,
format: "PDF", format: "PDF",
languageCode: snapshot.localization.languageCode, languageCode: snapshot.localization.languageCode,
filename: `proforma-${snapshot.series}${snapshot.invoice_number}.pdf`, filename: `proforma-${snapshot.proforma_reference}.pdf`,
storageKey: this.buildStorageKey(snapshot), storageKey: this.buildStorageKey(snapshot),
}; };
} }

View File

@ -61,6 +61,11 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
"document_series_id", "document_series_id",
errors errors
); );
const proformaSeriesCode = extractOrPushError(
maybeFromNullableResult(raw.proforma_series_code, (value) => InvoiceSerie.create(value)),
"proforma_series_code",
errors
);
const targetInvoiceSeriesCode = extractOrPushError( const targetInvoiceSeriesCode = extractOrPushError(
maybeFromNullableResult(raw.target_invoice_series_code, (value) => maybeFromNullableResult(raw.target_invoice_series_code, (value) =>
InvoiceSerie.create(value) InvoiceSerie.create(value)
@ -167,6 +172,7 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
status, status,
archivedAt, archivedAt,
documentSeriesId, documentSeriesId,
proformaSeriesCode,
proformaNumber, proformaNumber,
targetInvoiceSeriesCode, targetInvoiceSeriesCode,
proformaReference, proformaReference,
@ -220,6 +226,7 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
status: attributes.status!, status: attributes.status!,
archivedAt: attributes.archivedAt!, archivedAt: attributes.archivedAt!,
documentSeriesId: attributes.documentSeriesId!, documentSeriesId: attributes.documentSeriesId!,
proformaSeriesCode: attributes.proformaSeriesCode!,
proformaNumber: attributes.proformaNumber!, proformaNumber: attributes.proformaNumber!,
targetInvoiceSeriesCode: attributes.targetInvoiceSeriesCode!, targetInvoiceSeriesCode: attributes.targetInvoiceSeriesCode!,
proformaReference: attributes.proformaReference!, proformaReference: attributes.proformaReference!,
@ -286,6 +293,9 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
status: source.status.toPrimitive(), status: source.status.toPrimitive(),
archived_at: maybeToNullable(source.archivedAt, (value) => value.toPrimitive()), archived_at: maybeToNullable(source.archivedAt, (value) => value.toPrimitive()),
document_series_id: maybeToNullable(source.documentSeriesId, (value) => value), document_series_id: maybeToNullable(source.documentSeriesId, (value) => value),
proforma_series_code: maybeToNullable(source.proformaSeriesCode, (value) =>
value.toPrimitive()
),
proforma_number: maybeToNullable(source.proformaNumber, (value) => value.toPrimitive()), proforma_number: maybeToNullable(source.proformaNumber, (value) => value.toPrimitive()),
proforma_reference: source.proformaReference.toPrimitive(), proforma_reference: source.proformaReference.toPrimitive(),
proforma_date: source.proformaDate.toPrimitive(), proforma_date: source.proformaDate.toPrimitive(),

View File

@ -59,6 +59,9 @@ export class SequelizeProformaV2SummaryMapper extends SequelizeQueryMapper<
return Result.ok({ return Result.ok({
id: attributes.invoiceId!, id: attributes.invoiceId!,
companyId: attributes.companyId!, companyId: attributes.companyId!,
documentSeriesId: attributes.documentSeriesId!,
proformaSeriesCode: attributes.proformaSeriesCode!,
proformaNumber: attributes.proformaNumber!,
status: attributes.status!, status: attributes.status!,
archivedAt: attributes.archivedAt!, archivedAt: attributes.archivedAt!,
targetInvoiceSeriesCode: attributes.targetInvoiceSeriesCode!, targetInvoiceSeriesCode: attributes.targetInvoiceSeriesCode!,
@ -92,6 +95,21 @@ export class SequelizeProformaV2SummaryMapper extends SequelizeQueryMapper<
"archived_at", "archived_at",
errors errors
); );
const documentSeriesId = extractOrPushError(
maybeFromNullableResult(raw.document_series_id, (value) => Result.ok(String(value))),
"document_series_id",
errors
);
const proformaSeriesCode = extractOrPushError(
maybeFromNullableResult(raw.proforma_series_code, (value) => InvoiceSerie.create(value)),
"proforma_series_code",
errors
);
const proformaNumber = extractOrPushError(
maybeFromNullableResult(raw.proforma_number, (value) => InvoiceNumber.create(value)),
"proforma_number",
errors
);
const targetInvoiceSeriesCode = extractOrPushError( const targetInvoiceSeriesCode = extractOrPushError(
maybeFromNullableResult(raw.target_invoice_series_code, (value) => maybeFromNullableResult(raw.target_invoice_series_code, (value) =>
InvoiceSerie.create(value) InvoiceSerie.create(value)
@ -172,6 +190,9 @@ export class SequelizeProformaV2SummaryMapper extends SequelizeQueryMapper<
invoiceId, invoiceId,
companyId, companyId,
customerId, customerId,
documentSeriesId,
proformaSeriesCode,
proformaNumber,
status, status,
archivedAt, archivedAt,
targetInvoiceSeriesCode, targetInvoiceSeriesCode,

View File

@ -22,7 +22,10 @@ export const GetIssuedInvoiceByIdResponseSchema = z.object({
id: z.uuid(), id: z.uuid(),
company_id: z.uuid(), company_id: z.uuid(),
document_series_id: z.uuid().nullable(),
invoice_series_code: z.string().nullable(),
invoice_number: z.string(), invoice_number: z.string(),
invoice_reference: z.string(),
status: IssuedInvoiceStatusSchema, status: IssuedInvoiceStatusSchema,
series: z.string(), series: z.string(),

View File

@ -25,6 +25,9 @@ export const GetProformaByIdResponseSchema = z.object({
id: z.uuid(), id: z.uuid(),
company_id: z.uuid(), company_id: z.uuid(),
document_series_id: z.uuid().nullable(),
proforma_series_code: z.string().nullable(),
proforma_number: z.string().nullable(),
proforma_reference: z.string(), proforma_reference: z.string(),
status: ProformaStatusSchema, status: ProformaStatusSchema,
archived_at: z.iso.datetime().nullable(), archived_at: z.iso.datetime().nullable(),

View File

@ -19,8 +19,11 @@ export const IssueProformaIssuedInvoiceTotalsSchema = z.object({
export const IssueProformaIssuedInvoiceSchema = z.object({ export const IssueProformaIssuedInvoiceSchema = z.object({
id: z.uuid(), id: z.uuid(),
proforma_id: z.uuid().nullable(), proforma_id: z.uuid().nullable(),
document_series_id: z.uuid().nullable(),
invoice_series_code: z.string(),
series: z.string(), series: z.string(),
invoice_number: z.string(), invoice_number: z.string(),
invoice_reference: z.string(),
invoice_date: IsoDateSchema, invoice_date: IsoDateSchema,
status: IssuedInvoiceStatusSchema, status: IssuedInvoiceStatusSchema,
recipient: IssueProformaIssuedInvoiceRecipientSchema, recipient: IssueProformaIssuedInvoiceRecipientSchema,

View File

@ -9,7 +9,10 @@ export const IssuedInvoiceSummarySchema = z.object({
id: z.uuid(), id: z.uuid(),
company_id: z.uuid(), company_id: z.uuid(),
document_series_id: z.uuid().nullable(),
invoice_series_code: z.string().nullable(),
invoice_number: z.string(), invoice_number: z.string(),
invoice_reference: z.string(),
status: IssuedInvoiceStatusSchema, status: IssuedInvoiceStatusSchema,
series: z.string(), series: z.string(),

View File

@ -8,6 +8,9 @@ export const ProformaSummarySchema = z.object({
id: z.uuid(), id: z.uuid(),
company_id: z.uuid(), company_id: z.uuid(),
document_series_id: z.uuid().nullable(),
proforma_series_code: z.string().nullable(),
proforma_number: z.string().nullable(),
proforma_reference: z.string(), proforma_reference: z.string(),
status: ProformaStatusSchema, status: ProformaStatusSchema,
archived_at: z.iso.datetime().nullable(), archived_at: z.iso.datetime().nullable(),