Refactorización Issued invoice / Proformas
This commit is contained in:
parent
8d6b53e431
commit
97544b012d
@ -1,4 +1,4 @@
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import type { Collection, Result } from "@repo/rdx-utils";
|
||||
|
||||
/**
|
||||
* Tipo para los parámetros que reciben los métodos de los mappers
|
||||
|
||||
@ -3,15 +3,11 @@ import type { IAggregateRootRepository, UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import type { FindOptions, ModelDefined, Sequelize, Transaction } from "sequelize";
|
||||
|
||||
import type { IMapperRegistry } from "../mappers";
|
||||
|
||||
export abstract class SequelizeRepository<T> implements IAggregateRootRepository<T> {
|
||||
protected readonly _database!: Sequelize;
|
||||
protected readonly _registry!: IMapperRegistry;
|
||||
protected readonly database!: Sequelize;
|
||||
|
||||
constructor(params: { mapperRegistry: IMapperRegistry; database: Sequelize }) {
|
||||
this._registry = params.mapperRegistry;
|
||||
this._database = params.database;
|
||||
constructor(params: { database: Sequelize }) {
|
||||
this.database = params.database;
|
||||
}
|
||||
|
||||
protected convertCriteria(criteria: Criteria): FindOptions {
|
||||
|
||||
@ -8,10 +8,10 @@ import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { CreateCustomerInvoiceRequestDTO } from "../../../common";
|
||||
import {
|
||||
CustomerInvoiceItem,
|
||||
CustomerInvoiceItemDescription,
|
||||
type CustomerInvoiceItemProps,
|
||||
IssuedInvoiceItem,
|
||||
type IssuedInvoiceItemProps,
|
||||
ItemAmount,
|
||||
ItemDescription,
|
||||
ItemDiscount,
|
||||
ItemQuantity,
|
||||
} from "../../domain";
|
||||
@ -20,17 +20,15 @@ import { hasNoUndefinedFields } from "./has-no-undefined-fields";
|
||||
|
||||
export function mapDTOToCustomerInvoiceItemsProps(
|
||||
dtoItems: Pick<CreateCustomerInvoiceRequestDTO, "items">["items"]
|
||||
): Result<CustomerInvoiceItem[], ValidationErrorCollection> {
|
||||
): Result<IssuedInvoiceItem[], ValidationErrorCollection> {
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
const items: CustomerInvoiceItem[] = [];
|
||||
const items: IssuedInvoiceItem[] = [];
|
||||
|
||||
dtoItems.forEach((item, index) => {
|
||||
const path = (field: string) => `items[${index}].${field}`;
|
||||
|
||||
const description = extractOrPushError(
|
||||
maybeFromNullableVO(item.description, (value) =>
|
||||
CustomerInvoiceItemDescription.create(value)
|
||||
),
|
||||
maybeFromNullableVO(item.description, (value) => ItemDescription.create(value)),
|
||||
path("description"),
|
||||
errors
|
||||
);
|
||||
@ -54,7 +52,7 @@ export function mapDTOToCustomerInvoiceItemsProps(
|
||||
);
|
||||
|
||||
if (errors.length === 0) {
|
||||
const itemProps: CustomerInvoiceItemProps = {
|
||||
const itemProps: IssuedInvoiceItemProps = {
|
||||
description: description,
|
||||
quantity: quantity,
|
||||
unitAmount: unitAmount,
|
||||
@ -66,7 +64,7 @@ export function mapDTOToCustomerInvoiceItemsProps(
|
||||
|
||||
if (hasNoUndefinedFields(itemProps)) {
|
||||
// Validar y crear el item de factura
|
||||
const itemOrError = CustomerInvoiceItem.create(itemProps);
|
||||
const itemOrError = IssuedInvoiceItem.create(itemProps);
|
||||
|
||||
if (itemOrError.isSuccess) {
|
||||
items.push(itemOrError.data);
|
||||
|
||||
@ -10,12 +10,7 @@ import {
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { CreateCustomerInvoiceRequestDTO } from "../../../common";
|
||||
import {
|
||||
CustomerInvoiceNumber,
|
||||
type CustomerInvoiceProps,
|
||||
CustomerInvoiceSerie,
|
||||
CustomerInvoiceStatus,
|
||||
} from "../../domain";
|
||||
import { type IProformaProps, InvoiceNumber, InvoiceSerie, InvoiceStatus } from "../../domain";
|
||||
|
||||
import { mapDTOToCustomerInvoiceItemsProps } from "./map-dto-to-customer-invoice-items-props";
|
||||
|
||||
@ -35,12 +30,12 @@ export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceRequestDT
|
||||
const invoiceId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
||||
|
||||
const invoiceNumber = extractOrPushError(
|
||||
maybeFromNullableVO(dto.invoice_number, (value) => CustomerInvoiceNumber.create(value)),
|
||||
maybeFromNullableVO(dto.invoice_number, (value) => InvoiceNumber.create(value)),
|
||||
"invoice_number",
|
||||
errors
|
||||
);
|
||||
const invoiceSeries = extractOrPushError(
|
||||
maybeFromNullableVO(dto.series, (value) => CustomerInvoiceSerie.create(value)),
|
||||
maybeFromNullableVO(dto.series, (value) => InvoiceSerie.create(value)),
|
||||
"invoice_series",
|
||||
errors
|
||||
);
|
||||
@ -71,12 +66,12 @@ export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceRequestDT
|
||||
return Result.fail(new ValidationErrorCollection("Invoice dto mapping failed", errors));
|
||||
}
|
||||
|
||||
const invoiceProps: CustomerInvoiceProps = {
|
||||
const invoiceProps: IProformaProps = {
|
||||
invoiceNumber: invoiceNumber!,
|
||||
series: invoiceSeries!,
|
||||
invoiceDate: invoiceDate!,
|
||||
operationDate: operationDate!,
|
||||
status: CustomerInvoiceStatus.createDraft(),
|
||||
status: InvoiceStatus.createDraft(),
|
||||
currencyCode: currencyCode!,
|
||||
};
|
||||
|
||||
|
||||
@ -1,5 +1,2 @@
|
||||
//export * from "./services";
|
||||
//export * from "./snapshot-builders";
|
||||
|
||||
export * from "./issued-invoices";
|
||||
export * from "./use-cases";
|
||||
export * from "./proformas";
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
export * from "./issued-invoice-document.model";
|
||||
export * from "./report-cache-key";
|
||||
export * from "./snapshots";
|
||||
@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Documento legal generado para una factura emitida.
|
||||
*
|
||||
*/
|
||||
|
||||
export interface IIssuedInvoiceDocument {
|
||||
payload: Buffer;
|
||||
mimeType: string;
|
||||
filename: string;
|
||||
}
|
||||
|
||||
export class IssuedInvoiceDocument implements IIssuedInvoiceDocument {
|
||||
public readonly payload: Buffer;
|
||||
public readonly mimeType: string;
|
||||
public readonly filename: string;
|
||||
|
||||
constructor(params: {
|
||||
payload: Buffer;
|
||||
filename: string;
|
||||
}) {
|
||||
if (!params.payload || params.payload.length === 0) {
|
||||
throw new Error("IssuedInvoiceDocument payload cannot be empty");
|
||||
}
|
||||
|
||||
if (!params.filename.toLowerCase().endsWith(".pdf")) {
|
||||
throw new Error("IssuedInvoiceDocument filename must end with .pdf");
|
||||
}
|
||||
|
||||
this.payload = params.payload;
|
||||
this.mimeType = "application/pdf";
|
||||
this.filename = params.filename;
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
|
||||
/**
|
||||
* Clave determinista que identifica de forma única
|
||||
* un documento legal generado.
|
||||
*
|
||||
* Encapsula la regla de idempotencia del caso de uso.
|
||||
*/
|
||||
export class ReportCacheKey {
|
||||
private readonly value: string;
|
||||
|
||||
private constructor(value: string) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
static forIssuedInvoice(params: {
|
||||
companyId: UniqueID;
|
||||
invoiceId: UniqueID;
|
||||
language: string;
|
||||
canonicalModelHash: string;
|
||||
templateChecksum: string;
|
||||
rendererVersion: string;
|
||||
signingProviderVersion: string;
|
||||
}): ReportCacheKey {
|
||||
const {
|
||||
companyId,
|
||||
invoiceId,
|
||||
language,
|
||||
canonicalModelHash,
|
||||
templateChecksum,
|
||||
rendererVersion,
|
||||
signingProviderVersion,
|
||||
} = params;
|
||||
|
||||
// Fail-fast: campos obligatorios
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (!value || String(value).trim() === "") {
|
||||
throw new Error(`ReportCacheKey missing field: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
return new ReportCacheKey(
|
||||
[
|
||||
"issued-invoice",
|
||||
companyId,
|
||||
invoiceId,
|
||||
language,
|
||||
canonicalModelHash,
|
||||
templateChecksum,
|
||||
rendererVersion,
|
||||
signingProviderVersion,
|
||||
].join("__")
|
||||
);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
export * from "./issued-invoice-full-snapshot";
|
||||
export * from "./issued-invoice-item-full-snapshot";
|
||||
export * from "./issued-invoice-recipient-full-snapshot";
|
||||
export * from "./issued-invoice-verifactu-full-snapshot";
|
||||
@ -1 +0,0 @@
|
||||
export * from "./issued-invoice-list-item-snapshot";
|
||||
@ -1,3 +0,0 @@
|
||||
export * from "./issued-invoice-report-item-snapshot";
|
||||
export * from "./issued-invoice-report-snapshot";
|
||||
export * from "./issued-invoice-report-tax-snapshot";
|
||||
@ -1,3 +1,3 @@
|
||||
export * from "./finder.di";
|
||||
export * from "./snapshot-builders.di";
|
||||
export * from "./use-cases.di";
|
||||
export * from "./issued-invoice-finder.di";
|
||||
export * from "./issued-invoice-snapshot-builders.di";
|
||||
export * from "./issued-invoice-use-cases.di";
|
||||
|
||||
@ -1 +1 @@
|
||||
export * from "./sign-document-command";
|
||||
export * from "./issued-invoice-list.dto";
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
import type { CurrencyCode, LanguageCode, Percentage, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||
import type { Maybe } from "@repo/rdx-utils";
|
||||
|
||||
import type {
|
||||
InvoiceAmount,
|
||||
InvoiceNumber,
|
||||
InvoiceRecipient,
|
||||
InvoiceSerie,
|
||||
InvoiceStatus,
|
||||
VerifactuRecord,
|
||||
} from "../../../domain";
|
||||
|
||||
export type IssuedInvoiceListDTO = {
|
||||
id: UniqueID;
|
||||
companyId: UniqueID;
|
||||
|
||||
isProforma: boolean;
|
||||
invoiceNumber: InvoiceNumber;
|
||||
status: InvoiceStatus;
|
||||
series: Maybe<InvoiceSerie>;
|
||||
|
||||
invoiceDate: UtcDate;
|
||||
operationDate: Maybe<UtcDate>;
|
||||
|
||||
reference: Maybe<string>;
|
||||
description: Maybe<string>;
|
||||
|
||||
customerId: UniqueID;
|
||||
recipient: InvoiceRecipient;
|
||||
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
|
||||
discountPercentage: Percentage;
|
||||
|
||||
subtotalAmount: InvoiceAmount;
|
||||
discountAmount: InvoiceAmount;
|
||||
taxableAmount: InvoiceAmount;
|
||||
taxesAmount: InvoiceAmount;
|
||||
totalAmount: InvoiceAmount;
|
||||
|
||||
verifactu: Maybe<VerifactuRecord>;
|
||||
};
|
||||
@ -1,15 +0,0 @@
|
||||
/**
|
||||
* DTO de Application para solicitar la firma de un documento.
|
||||
*
|
||||
* Se utiliza exclusivamente para intercambiar datos con la capa
|
||||
* de infraestructura (DocumentSigningService).
|
||||
*
|
||||
* No contiene lógica ni validaciones de negocio.
|
||||
*/
|
||||
export interface SignDocumentCommand {
|
||||
/** PDF sin firmar */
|
||||
readonly file: Buffer;
|
||||
|
||||
/** Identificador estable de la empresa */
|
||||
readonly companyId: string;
|
||||
}
|
||||
@ -1,5 +1,8 @@
|
||||
export * from "./application-models";
|
||||
export * from "./di";
|
||||
export * from "./dtos";
|
||||
export * from "./mappers";
|
||||
export * from "./repositories";
|
||||
export * from "./services";
|
||||
export * from "./snapshot-builders";
|
||||
export * from "./use-cases";
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./issued-invoice-domain-mapper.interface";
|
||||
export * from "./issued-invoice-list-mapper.interface";
|
||||
@ -0,0 +1,9 @@
|
||||
import type { MapperParamsType } from "@erp/core/api";
|
||||
import type { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { IssuedInvoice } from "../../../domain";
|
||||
|
||||
export interface IIssuedInvoiceDomainMapper {
|
||||
mapToPersistence(invoice: IssuedInvoice, params?: MapperParamsType): Result<unknown, Error>;
|
||||
mapToDomain(raw: unknown, params?: MapperParamsType): Result<IssuedInvoice, Error>;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
import type { MapperParamsType } from "@erp/core/api";
|
||||
import type { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { IssuedInvoiceListDTO } from "../dtos";
|
||||
|
||||
export interface IIssuedInvoiceListMapper {
|
||||
mapToDTO(raw: unknown, params?: MapperParamsType): Result<IssuedInvoiceListDTO, Error>;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./issued-invoice-repository.interface";
|
||||
@ -0,0 +1,22 @@
|
||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import type { Collection, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { IssuedInvoice } from "../../../domain";
|
||||
import type { IssuedInvoiceListDTO } from "../dtos";
|
||||
|
||||
export interface IIssuedInvoiceRepository {
|
||||
create(invoice: IssuedInvoice, transaction?: unknown): Promise<Result<void, Error>>;
|
||||
|
||||
getByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction: unknown
|
||||
): Promise<Result<IssuedInvoice, Error>>;
|
||||
|
||||
findByCriteriaInCompany(
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
transaction: unknown
|
||||
): Promise<Result<Collection<IssuedInvoiceListDTO>, Error>>;
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
export * from "./issued-invoice-document-generator.interface";
|
||||
export * from "./issued-invoice-document-metadata-factory";
|
||||
export * from "./issued-invoice-document-properties-factory";
|
||||
export * from "./issued-invoice-document-renderer.interface";
|
||||
export * from "./issued-invoice-finder";
|
||||
export * from "./proforma-to-issued-invoice-materializer";
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
import type { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { IIssuedInvoiceDocument, IssuedInvoiceReportSnapshot } from "../application-models";
|
||||
|
||||
export interface IIssuedInvoiceDocumentRenderer {
|
||||
render(input: {
|
||||
snapshot: IssuedInvoiceReportSnapshot;
|
||||
documentId: string;
|
||||
}): Promise<Result<IIssuedInvoiceDocument, Error>>;
|
||||
}
|
||||
@ -4,14 +4,14 @@ import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import type { Collection, Result } from "@repo/rdx-utils";
|
||||
import type { Transaction } from "sequelize";
|
||||
|
||||
import type { CustomerInvoice, ICustomerInvoiceRepository } from "../../../domain";
|
||||
import type { ICustomerInvoiceRepository, Proforma } from "../../../domain";
|
||||
|
||||
export interface IIssuedInvoiceFinder {
|
||||
findIssuedInvoiceById(
|
||||
companyId: UniqueID,
|
||||
invoiceId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<CustomerInvoice, Error>>;
|
||||
): Promise<Result<Proforma, Error>>;
|
||||
|
||||
issuedInvoiceExists(
|
||||
companyId: UniqueID,
|
||||
@ -33,18 +33,10 @@ export class IssuedInvoiceFinder implements IIssuedInvoiceFinder {
|
||||
companyId: UniqueID,
|
||||
invoiceId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<CustomerInvoice, Error>> {
|
||||
): Promise<Result<Proforma, Error>> {
|
||||
return this.repository.getIssuedInvoiceByIdInCompany(companyId, invoiceId, transaction, {});
|
||||
}
|
||||
|
||||
async findProformaById(
|
||||
companyId: UniqueID,
|
||||
proformaId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<CustomerInvoice, Error>> {
|
||||
return this.repository.getProformaByIdInCompany(companyId, proformaId, transaction, {});
|
||||
}
|
||||
|
||||
async issuedInvoiceExists(
|
||||
companyId: UniqueID,
|
||||
invoiceId: UniqueID,
|
||||
@ -55,16 +47,6 @@ export class IssuedInvoiceFinder implements IIssuedInvoiceFinder {
|
||||
});
|
||||
}
|
||||
|
||||
async proformaExists(
|
||||
companyId: UniqueID,
|
||||
proformaId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<boolean, Error>> {
|
||||
return this.repository.existsByIdInCompany(companyId, proformaId, transaction, {
|
||||
is_proforma: true,
|
||||
});
|
||||
}
|
||||
|
||||
async findIssuedInvoicesByCriteria(
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
@ -77,12 +59,4 @@ export class IssuedInvoiceFinder implements IIssuedInvoiceFinder {
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
async findProformasByCriteria(
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>> {
|
||||
return this.repository.findProformasByCriteriaInCompany(companyId, criteria, transaction, {});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import type { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type {
|
||||
CustomerInvoiceNumber,
|
||||
CustomerInvoiceSerie,
|
||||
ICustomerInvoiceNumberGenerator,
|
||||
} from "../../../domain";
|
||||
import type { ICustomerInvoiceNumberGenerator, InvoiceNumber, InvoiceSerie } from "../../../domain";
|
||||
|
||||
export interface IIssuedInvoiceNumberService {
|
||||
/**
|
||||
@ -13,9 +9,9 @@ export interface IIssuedInvoiceNumberService {
|
||||
*/
|
||||
nextIssuedInvoiceNumber(
|
||||
companyId: UniqueID,
|
||||
series: Maybe<CustomerInvoiceSerie>,
|
||||
series: Maybe<InvoiceSerie>,
|
||||
transaction: unknown
|
||||
): Promise<Result<CustomerInvoiceNumber, Error>>;
|
||||
): Promise<Result<InvoiceNumber, Error>>;
|
||||
}
|
||||
|
||||
export class IssuedInvoiceNumberService implements IIssuedInvoiceNumberService {
|
||||
@ -23,9 +19,9 @@ export class IssuedInvoiceNumberService implements IIssuedInvoiceNumberService {
|
||||
|
||||
async nextIssuedInvoiceNumber(
|
||||
companyId: UniqueID,
|
||||
series: Maybe<CustomerInvoiceSerie>,
|
||||
series: Maybe<InvoiceSerie>,
|
||||
transaction: unknown
|
||||
): Promise<Result<CustomerInvoiceNumber, Error>> {
|
||||
): Promise<Result<InvoiceNumber, Error>> {
|
||||
return this.numberGenerator.nextForCompany(companyId, series, transaction);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { IssuedInvoiceProps, Proforma } from "../../../domain";
|
||||
|
||||
export interface IProformaToIssuedInvoiceMaterializer {
|
||||
materialize(proforma: Proforma, issuedInvoiceId: UniqueID): Result<IssuedInvoiceProps, Error>;
|
||||
}
|
||||
|
||||
export class ProformaToIssuedInvoiceMaterializer implements IProformaToIssuedInvoiceMaterializer {
|
||||
public materialize(
|
||||
proforma: Proforma,
|
||||
issuedInvoiceId: UniqueID
|
||||
): Result<IssuedInvoiceProps, Error> {
|
||||
const amounts = proforma.calculateAllAmounts();
|
||||
const taxGroups = proforma.getTaxes();
|
||||
|
||||
const issuedItems = proforma.items.map((item) => ({
|
||||
description: item.description,
|
||||
quantity: item.quantity,
|
||||
unitPrice: item.unitAmount,
|
||||
taxableAmount: item.getTaxableAmount(),
|
||||
taxesAmount: item.getTaxesAmount(),
|
||||
totalAmount: item.getTotalAmount(),
|
||||
}));
|
||||
|
||||
const issuedTaxes = taxGroups.map((group) => ({
|
||||
ivaCode: group.iva.code,
|
||||
ivaPercentage: group.iva.percentage,
|
||||
ivaAmount: group.calculateAmounts().ivaAmount,
|
||||
recCode: group.rec?.code,
|
||||
recPercentage: group.rec?.percentage,
|
||||
recAmount: group.calculateAmounts().recAmount,
|
||||
retentionCode: group.retention?.code,
|
||||
retentionPercentage: group.retention?.percentage,
|
||||
retentionAmount: group.calculateAmounts().retentionAmount,
|
||||
}));
|
||||
|
||||
return Result.ok({
|
||||
companyId: proforma.companyId,
|
||||
invoiceNumber: proforma.invoiceNumber,
|
||||
invoiceDate: proforma.invoiceDate,
|
||||
customerId: proforma.customerId,
|
||||
languageCode: proforma.languageCode,
|
||||
currencyCode: proforma.currencyCode,
|
||||
paymentMethod: proforma.paymentMethod,
|
||||
discountPercentage: proforma.discountPercentage,
|
||||
|
||||
items: new Collection(issuedItems),
|
||||
taxes: new Collection(issuedTaxes),
|
||||
subtotalAmount: amounts.subtotalAmount,
|
||||
taxableAmount: amounts.taxableAmount,
|
||||
totalAmount: amounts.totalAmount,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,8 @@
|
||||
export * from "./issued-invoice-full-snapshot.interface";
|
||||
export * from "./issued-invoice-full-snapshot-builder";
|
||||
export * from "./issued-invoice-item-full-snapshot.interface";
|
||||
export * from "./issued-invoice-items-full-snapshot-builder";
|
||||
export * from "./issued-invoice-recipient-full-snapshot.interfce";
|
||||
export * from "./issued-invoice-recipient-full-snapshot-builder";
|
||||
export * from "./issued-invoice-verifactu-full-snapshot.interface";
|
||||
export * from "./issued-invoice-verifactu-full-snapshot-builder";
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
|
||||
import { type CustomerInvoice, InvoiceAmount } from "../../../../domain";
|
||||
import type { IssuedInvoiceFullSnapshot } from "../../application-models";
|
||||
import { InvoiceAmount, type Proforma } from "../../../../domain";
|
||||
|
||||
import type { IssuedInvoiceFullSnapshot } from "./issued-invoice-full-snapshot.interface";
|
||||
import type { IIssuedInvoiceItemsFullSnapshotBuilder } from "./issued-invoice-items-full-snapshot-builder";
|
||||
import type { IIssuedInvoiceRecipientFullSnapshotBuilder } from "./issued-invoice-recipient-full-snapshot-builder";
|
||||
import type { IIssuedInvoiceVerifactuFullSnapshotBuilder } from "./issued-invoice-verifactu-full-snapshot-builder";
|
||||
|
||||
export interface IIssuedInvoiceFullSnapshotBuilder
|
||||
extends ISnapshotBuilder<CustomerInvoice, IssuedInvoiceFullSnapshot> {}
|
||||
extends ISnapshotBuilder<Proforma, IssuedInvoiceFullSnapshot> {}
|
||||
|
||||
export class IssuedInvoiceFullSnapshotBuilder implements IIssuedInvoiceFullSnapshotBuilder {
|
||||
constructor(
|
||||
@ -18,7 +18,7 @@ export class IssuedInvoiceFullSnapshotBuilder implements IIssuedInvoiceFullSnaps
|
||||
private readonly verifactuBuilder: IIssuedInvoiceVerifactuFullSnapshotBuilder
|
||||
) {}
|
||||
|
||||
toOutput(invoice: CustomerInvoice): IssuedInvoiceFullSnapshot {
|
||||
toOutput(invoice: Proforma): IssuedInvoiceFullSnapshot {
|
||||
const items = this.itemsBuilder.toOutput(invoice.items);
|
||||
const recipient = this.recipientBuilder.toOutput(invoice);
|
||||
const verifactu = this.verifactuBuilder.toOutput(invoice);
|
||||
@ -129,7 +129,6 @@ export class IssuedInvoiceFullSnapshotBuilder implements IIssuedInvoiceFullSnaps
|
||||
|
||||
metadata: {
|
||||
entity: "issued-invoices",
|
||||
link: "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { IssuedInvoiceItemFullSnapshot } from "./issued-invoice-item-full-snapshot";
|
||||
import type { IssuedInvoiceRecipientFullSnapshot } from "./issued-invoice-recipient-full-snapshot";
|
||||
import type { IssuedInvoiceVerifactuFullSnapshot } from "./issued-invoice-verifactu-full-snapshot";
|
||||
import type { IssuedInvoiceItemFullSnapshot } from "./issued-invoice-item-full-snapshot.interface";
|
||||
import type { IssuedInvoiceRecipientFullSnapshot } from "./issued-invoice-recipient-full-snapshot.interfce";
|
||||
import type { IssuedInvoiceVerifactuFullSnapshot } from "./issued-invoice-verifactu-full-snapshot.interface";
|
||||
|
||||
export interface IssuedInvoiceFullSnapshot {
|
||||
id: string;
|
||||
@ -1,7 +1,7 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
|
||||
import type { CustomerInvoiceItem, CustomerInvoiceItems } from "../../../../domain";
|
||||
import type { CustomerInvoiceItems, IssuedInvoiceItem } from "../../../../domain";
|
||||
import type { IssuedInvoiceItemFullSnapshot } from "../../application-models";
|
||||
|
||||
export interface IIssuedInvoiceItemsFullSnapshotBuilder
|
||||
@ -10,7 +10,7 @@ export interface IIssuedInvoiceItemsFullSnapshotBuilder
|
||||
export class IssuedInvoiceItemsFullSnapshotBuilder
|
||||
implements IIssuedInvoiceItemsFullSnapshotBuilder
|
||||
{
|
||||
private mapItem(invoiceItem: CustomerInvoiceItem, index: number): IssuedInvoiceItemFullSnapshot {
|
||||
private mapItem(invoiceItem: IssuedInvoiceItem, index: number): IssuedInvoiceItemFullSnapshot {
|
||||
const allAmounts = invoiceItem.calculateAllAmounts();
|
||||
|
||||
return {
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import { DomainValidationError, toEmptyString } from "@repo/rdx-ddd";
|
||||
|
||||
import type { CustomerInvoice, InvoiceRecipient } from "../../../../domain";
|
||||
import type { InvoiceRecipient, Proforma } from "../../../../domain";
|
||||
import type { IssuedInvoiceRecipientFullSnapshot } from "../../application-models";
|
||||
|
||||
export interface IIssuedInvoiceRecipientFullSnapshotBuilder
|
||||
extends ISnapshotBuilder<CustomerInvoice, IssuedInvoiceRecipientFullSnapshot> {}
|
||||
extends ISnapshotBuilder<Proforma, IssuedInvoiceRecipientFullSnapshot> {}
|
||||
|
||||
export class IssuedInvoiceRecipientFullSnapshotBuilder
|
||||
implements IIssuedInvoiceRecipientFullSnapshotBuilder
|
||||
{
|
||||
toOutput(invoice: CustomerInvoice): IssuedInvoiceRecipientFullSnapshot {
|
||||
toOutput(invoice: Proforma): IssuedInvoiceRecipientFullSnapshot {
|
||||
if (!invoice.recipient) {
|
||||
throw DomainValidationError.requiredValue("recipient", {
|
||||
cause: invoice,
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import { DomainValidationError } from "@repo/rdx-ddd";
|
||||
|
||||
import type { CustomerInvoice } from "../../../../domain";
|
||||
import type { Proforma } from "../../../../domain";
|
||||
import type { IssuedInvoiceVerifactuFullSnapshot } from "../../application-models";
|
||||
|
||||
export interface IIssuedInvoiceVerifactuFullSnapshotBuilder
|
||||
extends ISnapshotBuilder<CustomerInvoice, IssuedInvoiceVerifactuFullSnapshot> {}
|
||||
extends ISnapshotBuilder<Proforma, IssuedInvoiceVerifactuFullSnapshot> {}
|
||||
|
||||
export class IssuedInvoiceVerifactuFullSnapshotBuilder
|
||||
implements IIssuedInvoiceVerifactuFullSnapshotBuilder
|
||||
{
|
||||
toOutput(invoice: CustomerInvoice): IssuedInvoiceVerifactuFullSnapshot {
|
||||
toOutput(invoice: Proforma): IssuedInvoiceVerifactuFullSnapshot {
|
||||
if (!invoice.verifactu) {
|
||||
throw DomainValidationError.requiredValue("verifactu", {
|
||||
cause: invoice,
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export * from "./issued-invoice-list-item-snapshot.interface";
|
||||
export * from "./issued-invoice-list-item-snapshot-builder";
|
||||
|
||||
@ -2,7 +2,7 @@ import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
|
||||
import type { CustomerInvoiceListDTO } from "../../../../infrastructure";
|
||||
import type { IssuedInvoiceListItemSnapshot } from "../../application-models/snapshots/list";
|
||||
import type { IssuedInvoiceListItemSnapshot } from "../../application-models";
|
||||
|
||||
export interface IIssuedInvoiceListItemSnapshotBuilder
|
||||
extends ISnapshotBuilder<CustomerInvoiceListDTO, IssuedInvoiceListItemSnapshot> {}
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
export * from "./issued-invoice-items-report-snapshot-builder";
|
||||
export * from "./issued-invoice-report-item-snapshot.interface";
|
||||
export * from "./issued-invoice-report-snapshot.interface";
|
||||
export * from "./issued-invoice-report-snapshot-builder";
|
||||
export * from "./issued-invoice-report-tax-snapshot.interface";
|
||||
export * from "./issued-invoice-tax-report-snapshot-builder";
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { IssuedInvoiceReportItemSnapshot } from "./issued-invoice-report-item-snapshot";
|
||||
import type { IssuedInvoiceReportTaxSnapshot } from "./issued-invoice-report-tax-snapshot";
|
||||
import type { IssuedInvoiceReportItemSnapshot } from "./issued-invoice-report-item-snapshot.interface";
|
||||
import type { IssuedInvoiceReportTaxSnapshot } from "./issued-invoice-report-tax-snapshot.interface";
|
||||
|
||||
export interface IssuedInvoiceReportSnapshot {
|
||||
id: string;
|
||||
@ -1,3 +1,3 @@
|
||||
export * from "./get-issued-invoice-by-id.use-case";
|
||||
export * from "./list-issued-invoices.use-case";
|
||||
export * from "./report-issued-invoices";
|
||||
export * from "./report-issued-invoice.use-case";
|
||||
|
||||
@ -15,7 +15,7 @@ type ListIssuedInvoicesUseCaseInput = {
|
||||
export class ListIssuedInvoicesUseCase {
|
||||
constructor(
|
||||
private readonly finder: IIssuedInvoiceFinder,
|
||||
private readonly itemSnapshotBuilder: IIssuedInvoiceListItemSnapshotBuilder,
|
||||
private readonly listItemSnapshotBuilder: IIssuedInvoiceListItemSnapshotBuilder,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
@ -37,9 +37,8 @@ export class ListIssuedInvoicesUseCase {
|
||||
const invoices = result.data;
|
||||
const totalInvoices = invoices.total();
|
||||
|
||||
const items = invoices.map((item) => this.itemSnapshotBuilder.toOutput(item));
|
||||
const items = invoices.map((item) => this.listItemSnapshotBuilder.toOutput(item));
|
||||
|
||||
// ?????
|
||||
const snapshot = {
|
||||
page: criteria.pageNumber,
|
||||
per_page: criteria.pageSize,
|
||||
|
||||
@ -2,9 +2,9 @@ import type { ITransactionManager, RendererFormat } from "@erp/core/api";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { IIssuedInvoiceFinder, IssuedInvoiceDocumentGeneratorService } from "../../services";
|
||||
import type { IIssuedInvoiceFullSnapshotBuilder } from "../../snapshot-builders";
|
||||
import type { IIssuedInvoiceReportSnapshotBuilder } from "../../snapshot-builders/report";
|
||||
import type { IIssuedInvoiceFinder, IssuedInvoiceDocumentGeneratorService } from "../services";
|
||||
import type { IIssuedInvoiceFullSnapshotBuilder } from "../snapshot-builders";
|
||||
import type { IIssuedInvoiceReportSnapshotBuilder } from "../snapshot-builders/report";
|
||||
|
||||
type ReportIssuedInvoiceUseCaseInput = {
|
||||
companyId: UniqueID;
|
||||
@ -23,7 +23,7 @@ export class ReportIssuedInvoiceUseCase {
|
||||
) {}
|
||||
|
||||
public async execute(params: ReportIssuedInvoiceUseCaseInput) {
|
||||
const { invoice_id, companyId, companySlug, format } = params;
|
||||
const { invoice_id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(invoice_id);
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
//export * from "./issued-invoice-document";
|
||||
export * from "./report-issued-invoice.use-case";
|
||||
@ -0,0 +1,4 @@
|
||||
export * from "./proforma-creator.di";
|
||||
export * from "./proforma-finder.di";
|
||||
export * from "./proforma-snapshot-builders.di";
|
||||
export * from "./proforma-use-cases.di";
|
||||
@ -0,0 +1,14 @@
|
||||
import type { ICustomerInvoiceRepository } from "../../../domain/repositories";
|
||||
import { ProformaFactory } from "../factories";
|
||||
import { type IProformaCreator, type IProformaNumberGenerator, ProformaCreator } from "../services";
|
||||
|
||||
export function buildProformaCreator(
|
||||
numberService: IProformaNumberGenerator,
|
||||
repository: ICustomerInvoiceRepository
|
||||
): IProformaCreator {
|
||||
return new ProformaCreator({
|
||||
numberService,
|
||||
factory: new ProformaFactory(),
|
||||
repository,
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
import type { ICustomerInvoiceRepository } from "../../../domain";
|
||||
import { type IProformaFinder, ProformaFinder } from "../services";
|
||||
|
||||
export function buildProformaFinder(repository: ICustomerInvoiceRepository): IProformaFinder {
|
||||
return new ProformaFinder(repository);
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
// application/issued-invoices/di/snapshot-builders.di.ts
|
||||
|
||||
import {
|
||||
ProformaFullSnapshotBuilder,
|
||||
ProformaItemReportSnapshotBuilder,
|
||||
ProformaItemsFullSnapshotBuilder,
|
||||
ProformaListItemSnapshotBuilder,
|
||||
ProformaRecipientFullSnapshotBuilder,
|
||||
ProformaReportSnapshotBuilder,
|
||||
ProformaTaxReportSnapshotBuilder,
|
||||
} from "../snapshot-builders";
|
||||
|
||||
export function buildProformaSnapshotBuilders() {
|
||||
const itemsBuilder = new ProformaItemsFullSnapshotBuilder();
|
||||
|
||||
const recipientBuilder = new ProformaRecipientFullSnapshotBuilder();
|
||||
|
||||
const fullSnapshotBuilder = new ProformaFullSnapshotBuilder(itemsBuilder, recipientBuilder);
|
||||
|
||||
const listSnapshotBuilder = new ProformaListItemSnapshotBuilder();
|
||||
|
||||
const itemsReportBuilder = new ProformaItemReportSnapshotBuilder();
|
||||
const taxesReportBuilder = new ProformaTaxReportSnapshotBuilder();
|
||||
const reportSnapshotBuilder = new ProformaReportSnapshotBuilder(
|
||||
itemsReportBuilder,
|
||||
taxesReportBuilder
|
||||
);
|
||||
|
||||
return {
|
||||
full: fullSnapshotBuilder,
|
||||
list: listSnapshotBuilder,
|
||||
report: reportSnapshotBuilder,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
import type { ITransactionManager } from "@erp/core/api";
|
||||
|
||||
import type { IProformaFinder, ProformaDocumentGeneratorService } from "../services";
|
||||
import type {
|
||||
IProformaListItemSnapshotBuilder,
|
||||
IProformaReportSnapshotBuilder,
|
||||
} from "../snapshot-builders";
|
||||
import type { IProformaFullSnapshotBuilder } from "../snapshot-builders/full";
|
||||
import { GetProformaByIdUseCase, ListProformasUseCase, ReportProformaUseCase } from "../use-cases";
|
||||
|
||||
export function buildGetProformaByIdUseCase(deps: {
|
||||
finder: IProformaFinder;
|
||||
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||
transactionManager: ITransactionManager;
|
||||
}) {
|
||||
return new GetProformaByIdUseCase(deps.finder, deps.fullSnapshotBuilder, deps.transactionManager);
|
||||
}
|
||||
|
||||
export function buildListProformasUseCase(deps: {
|
||||
finder: IProformaFinder;
|
||||
itemSnapshotBuilder: IProformaListItemSnapshotBuilder;
|
||||
transactionManager: ITransactionManager;
|
||||
}) {
|
||||
return new ListProformasUseCase(deps.finder, deps.itemSnapshotBuilder, deps.transactionManager);
|
||||
}
|
||||
|
||||
export function buildReportProformaUseCase(deps: {
|
||||
finder: IProformaFinder;
|
||||
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||
reportSnapshotBuilder: IProformaReportSnapshotBuilder;
|
||||
documentService: ProformaDocumentGeneratorService;
|
||||
transactionManager: ITransactionManager;
|
||||
}) {
|
||||
return new ReportProformaUseCase(
|
||||
deps.finder,
|
||||
deps.fullSnapshotBuilder,
|
||||
deps.reportSnapshotBuilder,
|
||||
deps.documentService,
|
||||
deps.transactionManager
|
||||
);
|
||||
}
|
||||
|
||||
/*export function buildCreateProformaUseCase(deps: {
|
||||
creator: IProformaCreator;
|
||||
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||
transactionManager: ITransactionManager;
|
||||
}) {
|
||||
return new CreateProformaUseCase({
|
||||
mapper: new CreateProformaPropsMapper(),
|
||||
creator: deps.creator,
|
||||
fullSnapshotBuilder: deps.fullSnapshotBuilder,
|
||||
transactionManager: deps.transactionManager,
|
||||
});
|
||||
}*/
|
||||
|
||||
/*export function buildUpdateProformaUseCase(deps: {
|
||||
finder: IProformaFinder;
|
||||
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||
}) {
|
||||
return new UpdateProformaUseCase(deps.finder, deps.fullSnapshotBuilder);
|
||||
}
|
||||
|
||||
export function buildDeleteProformaUseCase(deps: { finder: IProformaFinder }) {
|
||||
return new DeleteProformaUseCase(deps.finder);
|
||||
}
|
||||
|
||||
export function buildIssueProformaUseCase(deps: { finder: IProformaFinder }) {
|
||||
return new IssueProformaUseCase(deps.finder);
|
||||
}
|
||||
|
||||
export function buildChangeStatusProformaUseCase(deps: {
|
||||
finder: IProformaFinder;
|
||||
transactionManager: ITransactionManager;
|
||||
}) {
|
||||
return new ChangeStatusProformaUseCase(deps.finder, deps.transactionManager);
|
||||
}*/
|
||||
@ -0,0 +1 @@
|
||||
export * from "./proforma-list.dto";
|
||||
@ -0,0 +1,40 @@
|
||||
import type { CurrencyCode, LanguageCode, Percentage, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||
import type { Maybe } from "@repo/rdx-utils";
|
||||
|
||||
import type {
|
||||
InvoiceAmount,
|
||||
InvoiceNumber,
|
||||
InvoiceRecipient,
|
||||
InvoiceSerie,
|
||||
InvoiceStatus,
|
||||
} from "../../../domain";
|
||||
|
||||
export type ProformaListDTO = {
|
||||
id: UniqueID;
|
||||
companyId: UniqueID;
|
||||
|
||||
isProforma: boolean;
|
||||
invoiceNumber: InvoiceNumber;
|
||||
status: InvoiceStatus;
|
||||
series: Maybe<InvoiceSerie>;
|
||||
|
||||
invoiceDate: UtcDate;
|
||||
operationDate: Maybe<UtcDate>;
|
||||
|
||||
reference: Maybe<string>;
|
||||
description: Maybe<string>;
|
||||
|
||||
customerId: UniqueID;
|
||||
recipient: InvoiceRecipient;
|
||||
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
|
||||
discountPercentage: Percentage;
|
||||
|
||||
subtotalAmount: InvoiceAmount;
|
||||
discountAmount: InvoiceAmount;
|
||||
taxableAmount: InvoiceAmount;
|
||||
taxesAmount: InvoiceAmount;
|
||||
totalAmount: InvoiceAmount;
|
||||
};
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./proforma-factory";
|
||||
export * from "./proforma-factory.interface";
|
||||
@ -0,0 +1,17 @@
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import type { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { IProformaProps, Proforma } from "../../../domain";
|
||||
|
||||
export interface IProformaFactory {
|
||||
/**
|
||||
* Crea una proforma válida para una empresa a partir de props ya validadas.
|
||||
*
|
||||
* No persiste el agregado.
|
||||
*/
|
||||
createProforma(
|
||||
companyId: UniqueID,
|
||||
props: Omit<IProformaProps, "companyId">,
|
||||
proformaId?: UniqueID
|
||||
): Result<Proforma, Error>;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import type { Result } from "@repo/rdx-utils";
|
||||
|
||||
import { type IProformaProps, Proforma } from "../../../domain";
|
||||
|
||||
import type { IProformaFactory } from "./proforma-factory.interface";
|
||||
|
||||
export class ProformaFactory implements IProformaFactory {
|
||||
createProforma(
|
||||
companyId: UniqueID,
|
||||
props: Omit<IProformaProps, "companyId">,
|
||||
proformaId?: UniqueID
|
||||
): Result<Proforma, Error> {
|
||||
return Proforma.create({ ...props, companyId }, proformaId);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
export * from "./application-models";
|
||||
export * from "./di";
|
||||
export * from "./dtos";
|
||||
export * from "./mappers";
|
||||
export * from "./repositories";
|
||||
export * from "./services";
|
||||
export * from "./snapshot-builders";
|
||||
export * from "./use-cases";
|
||||
@ -15,24 +15,25 @@ import {
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { CreateProformaItemRequestDTO, CreateProformaRequestDTO } from "../../../../../common";
|
||||
import type { CreateProformaItemRequestDTO, CreateProformaRequestDTO } from "../../../../common";
|
||||
import {
|
||||
CustomerInvoiceItem,
|
||||
CustomerInvoiceItemDescription,
|
||||
type CustomerInvoiceItemProps,
|
||||
CustomerInvoiceItems,
|
||||
CustomerInvoiceNumber,
|
||||
type CustomerInvoiceProps,
|
||||
CustomerInvoiceSerie,
|
||||
CustomerInvoiceStatus,
|
||||
type IProformaProps,
|
||||
InvoiceNumber,
|
||||
InvoicePaymentMethod,
|
||||
type InvoiceRecipient,
|
||||
InvoiceSerie,
|
||||
InvoiceStatus,
|
||||
IssuedInvoiceItem,
|
||||
type IssuedInvoiceItemProps,
|
||||
ItemAmount,
|
||||
ItemDescription,
|
||||
ItemDiscount,
|
||||
ItemQuantity,
|
||||
} from "../../../../domain";
|
||||
} from "../../../domain";
|
||||
|
||||
/**
|
||||
* CreateProformaPropsMapper
|
||||
* Convierte el DTO a las props validadas (CustomerProps).
|
||||
* No construye directamente el agregado.
|
||||
*
|
||||
@ -42,7 +43,7 @@ import {
|
||||
*
|
||||
*/
|
||||
|
||||
export class CreateCustomerInvoicePropsMapper {
|
||||
export class CreateProformaPropsMapper {
|
||||
private readonly taxCatalog: JsonTaxCatalogProvider;
|
||||
private errors: ValidationErrorDetail[] = [];
|
||||
private languageCode?: LanguageCode;
|
||||
@ -57,7 +58,7 @@ export class CreateCustomerInvoicePropsMapper {
|
||||
try {
|
||||
this.errors = [];
|
||||
|
||||
const defaultStatus = CustomerInvoiceStatus.createDraft();
|
||||
const defaultStatus = InvoiceStatus.createDraft();
|
||||
|
||||
const proformaId = extractOrPushError(UniqueID.create(dto.id), "id", this.errors);
|
||||
|
||||
@ -72,13 +73,13 @@ export class CreateCustomerInvoicePropsMapper {
|
||||
const recipient = Maybe.none<InvoiceRecipient>();
|
||||
|
||||
const proformaNumber = extractOrPushError(
|
||||
CustomerInvoiceNumber.create(dto.invoice_number),
|
||||
InvoiceNumber.create(dto.invoice_number),
|
||||
"invoice_number",
|
||||
this.errors
|
||||
);
|
||||
|
||||
const series = extractOrPushError(
|
||||
maybeFromNullableVO(dto.series, (value) => CustomerInvoiceSerie.create(value)),
|
||||
maybeFromNullableVO(dto.series, (value) => InvoiceSerie.create(value)),
|
||||
"series",
|
||||
this.errors
|
||||
);
|
||||
@ -150,7 +151,7 @@ export class CreateCustomerInvoicePropsMapper {
|
||||
);
|
||||
}
|
||||
|
||||
const proformaProps: CustomerInvoiceProps = {
|
||||
const proformaProps: IProformaProps = {
|
||||
companyId,
|
||||
isProforma,
|
||||
proformaId: Maybe.none(),
|
||||
@ -194,9 +195,7 @@ export class CreateCustomerInvoicePropsMapper {
|
||||
|
||||
items.forEach((item, index) => {
|
||||
const description = extractOrPushError(
|
||||
maybeFromNullableVO(item.description, (value) =>
|
||||
CustomerInvoiceItemDescription.create(value)
|
||||
),
|
||||
maybeFromNullableVO(item.description, (value) => ItemDescription.create(value)),
|
||||
"description",
|
||||
this.errors
|
||||
);
|
||||
@ -221,7 +220,7 @@ export class CreateCustomerInvoicePropsMapper {
|
||||
|
||||
const taxes = this.mapTaxes(item, index);
|
||||
|
||||
const itemProps: CustomerInvoiceItemProps = {
|
||||
const itemProps: IssuedInvoiceItemProps = {
|
||||
currencyCode: this.currencyCode!,
|
||||
languageCode: this.languageCode!,
|
||||
description: description!,
|
||||
@ -231,7 +230,7 @@ export class CreateCustomerInvoicePropsMapper {
|
||||
taxes: taxes,
|
||||
};
|
||||
|
||||
const itemResult = CustomerInvoiceItem.create(itemProps);
|
||||
const itemResult = IssuedInvoiceItem.create(itemProps);
|
||||
if (itemResult.isSuccess) {
|
||||
invoiceItems.add(itemResult.data);
|
||||
} else {
|
||||
@ -0,0 +1,4 @@
|
||||
export * from "./create-proforma-props.mapper";
|
||||
export * from "./proforma-domain-mapper.interface";
|
||||
export * from "./proforma-list-mapper.interface";
|
||||
export * from "./update-proforma-props.mapper";
|
||||
@ -0,0 +1,9 @@
|
||||
import type { MapperParamsType } from "@erp/core/api";
|
||||
import type { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { Proforma } from "../../../domain";
|
||||
|
||||
export interface IProformaDomainMapper {
|
||||
mapToPersistence(proforma: Proforma, params?: MapperParamsType): Result<unknown, Error>;
|
||||
mapToDomain(raw: unknown, params?: MapperParamsType): Result<Proforma, Error>;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
import type { MapperParamsType } from "@erp/core/api";
|
||||
import type { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { ProformaListDTO } from "../dtos";
|
||||
|
||||
export interface IProformaListMapper {
|
||||
mapToDTO(raw: unknown, params?: MapperParamsType): Result<ProformaListDTO, Error>;
|
||||
}
|
||||
@ -16,7 +16,7 @@ import type { UpdateProformaByIdRequestDTO } from "../../../../../common/dto";
|
||||
import { type CustomerInvoicePatchProps, CustomerInvoiceSerie } from "../../../../domain";
|
||||
|
||||
/**
|
||||
* mapDTOToUpdateCustomerInvoicePatchProps
|
||||
* UpdateProformaPropsMapper
|
||||
* Convierte el DTO a las props validadas (CustomerInvoiceProps).
|
||||
* No construye directamente el agregado.
|
||||
* Tri-estado:
|
||||
@ -29,7 +29,7 @@ import { type CustomerInvoicePatchProps, CustomerInvoiceSerie } from "../../../.
|
||||
*
|
||||
*/
|
||||
|
||||
export function mapDTOToUpdateCustomerInvoicePatchProps(dto: UpdateProformaByIdRequestDTO) {
|
||||
export function UpdateProformaPropsMapper(dto: UpdateProformaByIdRequestDTO) {
|
||||
try {
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
const props: CustomerInvoicePatchProps = {};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./proforma-repository.interface";
|
||||
@ -0,0 +1,43 @@
|
||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import type { Collection, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { InvoiceStatus, Proforma } from "../../../domain";
|
||||
import type { ProformaListDTO } from "../dtos";
|
||||
|
||||
export interface IProformaRepository {
|
||||
create(proforma: Proforma, transaction?: unknown): Promise<Result<void, Error>>;
|
||||
|
||||
update(proforma: Proforma, transaction?: unknown): Promise<Result<void, Error>>;
|
||||
|
||||
existsByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
tx: unknown
|
||||
): Promise<Result<boolean, Error>>;
|
||||
|
||||
getByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
tx: unknown
|
||||
): Promise<Result<Proforma, Error>>;
|
||||
|
||||
findByCriteriaInCompany(
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
tx: unknown
|
||||
): Promise<Result<Collection<ProformaListDTO>, Error>>;
|
||||
|
||||
deleteByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
tx: unknown
|
||||
): Promise<Result<boolean, Error>>;
|
||||
|
||||
updateStatusByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
newStatus: InvoiceStatus,
|
||||
tx: unknown
|
||||
): Promise<Result<boolean, Error>>;
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
export * from "./proforma-creator";
|
||||
export * from "./proforma-document-generator.interface";
|
||||
export * from "./proforma-document-metadata-factory";
|
||||
export * from "./proforma-document-properties-factory";
|
||||
export * from "./proforma-finder";
|
||||
export * from "./proforma-issuer";
|
||||
export * from "./proforma-number-generator.interface";
|
||||
@ -0,0 +1,78 @@
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import type { Transaction } from "sequelize";
|
||||
|
||||
import type { ICustomerInvoiceRepository } from "../../../domain";
|
||||
import type { CustomerInvoice, CustomerInvoiceProps } from "../../../domain/aggregates";
|
||||
import type { IProformaFactory } from "../factories";
|
||||
|
||||
import type { IProformaNumberGenerator } from "./proforma-number-generator.interface";
|
||||
|
||||
export interface IProformaCreator {
|
||||
create(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
props: CustomerInvoiceProps,
|
||||
transaction: Transaction
|
||||
): Promise<Result<CustomerInvoice, Error>>;
|
||||
}
|
||||
|
||||
type ProformaCreatorDeps = {
|
||||
numberService: IProformaNumberGenerator;
|
||||
factory: IProformaFactory;
|
||||
repository: ICustomerInvoiceRepository;
|
||||
};
|
||||
|
||||
export class ProformaCreator implements IProformaCreator {
|
||||
private readonly numberService: IProformaNumberGenerator;
|
||||
private readonly factory: IProformaFactory;
|
||||
private readonly repository: ICustomerInvoiceRepository;
|
||||
|
||||
constructor(deps: ProformaCreatorDeps) {
|
||||
this.numberService = deps.numberService;
|
||||
this.factory = deps.factory;
|
||||
this.repository = deps.repository;
|
||||
}
|
||||
|
||||
async create(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
props: CustomerInvoiceProps,
|
||||
transaction: Transaction
|
||||
): Promise<Result<CustomerInvoice, Error>> {
|
||||
// 1. Obtener siguiente número
|
||||
const { series } = props;
|
||||
const numberResult = await this.numberService.getNextForCompany(companyId, series, transaction);
|
||||
|
||||
if (numberResult.isFailure) {
|
||||
return Result.fail(numberResult.error);
|
||||
}
|
||||
|
||||
const invoiceNumber = numberResult.data;
|
||||
|
||||
// 2. Crear agregado
|
||||
const buildResult = this.factory.createProforma(
|
||||
companyId,
|
||||
{
|
||||
...props,
|
||||
invoiceNumber,
|
||||
},
|
||||
id
|
||||
);
|
||||
|
||||
if (buildResult.isFailure) {
|
||||
return Result.fail(buildResult.error);
|
||||
}
|
||||
|
||||
const proforma = buildResult.data;
|
||||
|
||||
// 3. Persistir
|
||||
const saveResult = await this.repository.create(proforma, transaction);
|
||||
|
||||
if (saveResult.isFailure) {
|
||||
return Result.fail(saveResult.error);
|
||||
}
|
||||
|
||||
return Result.ok(proforma);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
import type { DocumentGenerationService } from "@erp/core/api";
|
||||
|
||||
import type { ProformaReportSnapshot } from "../application-models";
|
||||
|
||||
export interface ProformaDocumentGeneratorService
|
||||
extends DocumentGenerationService<ProformaReportSnapshot> {}
|
||||
@ -0,0 +1,47 @@
|
||||
import type { IDocumentMetadata, IDocumentMetadataFactory } from "@erp/core/api";
|
||||
|
||||
import type { ProformaReportSnapshot } from "../application-models";
|
||||
|
||||
/**
|
||||
* Construye los metadatos del documento PDF de una factura emitida.
|
||||
*
|
||||
* - Application-level
|
||||
* - Determinista
|
||||
* - Sin IO
|
||||
*/
|
||||
export class ProformaDocumentMetadataFactory
|
||||
implements IDocumentMetadataFactory<ProformaReportSnapshot>
|
||||
{
|
||||
build(snapshot: ProformaReportSnapshot): IDocumentMetadata {
|
||||
if (!snapshot.id) {
|
||||
throw new Error("ProformaReportSnapshot.id is required");
|
||||
}
|
||||
|
||||
if (!snapshot.company_id) {
|
||||
throw new Error("ProformaReportSnapshot.companyId is required");
|
||||
}
|
||||
|
||||
return {
|
||||
documentType: "proforma",
|
||||
documentId: snapshot.id,
|
||||
companyId: snapshot.company_id,
|
||||
companySlug: snapshot.company_slug,
|
||||
format: "PDF",
|
||||
languageCode: snapshot.language_code ?? "es",
|
||||
filename: this.buildFilename(snapshot),
|
||||
storageKey: this.buildCacheKey(snapshot),
|
||||
};
|
||||
}
|
||||
|
||||
private buildFilename(snapshot: ProformaReportSnapshot): string {
|
||||
// Ejemplo: factura-F2024-000123-FULANITO.pdf
|
||||
return `factura-${snapshot.series}${snapshot.invoice_number}-${snapshot.recipient.name}.pdf`;
|
||||
}
|
||||
|
||||
private buildCacheKey(snapshot: ProformaReportSnapshot): string {
|
||||
// Versionado explícito para invalidaciones futuras
|
||||
return ["proforma", snapshot.company_id, snapshot.series, snapshot.invoice_number, "v1"].join(
|
||||
":"
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import type { IDocumentProperties, IDocumentPropertiesFactory } from "@erp/core/api";
|
||||
|
||||
import type { ProformaReportSnapshot } from "../application-models";
|
||||
|
||||
/**
|
||||
* Construye los metadatos del documento PDF de una factura emitida.
|
||||
*
|
||||
* - Application-level
|
||||
* - Determinista
|
||||
* - Sin IO
|
||||
*/
|
||||
export class ProformaDocumentPropertiesFactory
|
||||
implements IDocumentPropertiesFactory<ProformaReportSnapshot>
|
||||
{
|
||||
build(snapshot: ProformaReportSnapshot): IDocumentProperties {
|
||||
return {
|
||||
title: snapshot.reference,
|
||||
subject: "proforma",
|
||||
author: snapshot.company_slug,
|
||||
creator: "FactuGES ERP",
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
import type { CustomerInvoiceListDTO } from "@erp/customer-invoices/api/infrastructure";
|
||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import type { Collection, Result } from "@repo/rdx-utils";
|
||||
import type { Transaction } from "sequelize";
|
||||
|
||||
import type { ICustomerInvoiceRepository, Proforma } from "../../../domain";
|
||||
|
||||
export interface IProformaFinder {
|
||||
findProformaById(
|
||||
companyId: UniqueID,
|
||||
invoiceId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Proforma, Error>>;
|
||||
|
||||
proformaExists(
|
||||
companyId: UniqueID,
|
||||
invoiceId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<boolean, Error>>;
|
||||
|
||||
findProformasByCriteria(
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>>;
|
||||
}
|
||||
|
||||
export class ProformaFinder implements IProformaFinder {
|
||||
constructor(private readonly repository: ICustomerInvoiceRepository) {}
|
||||
|
||||
async findProformaById(
|
||||
companyId: UniqueID,
|
||||
proformaId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Proforma, Error>> {
|
||||
return this.repository.getProformaByIdInCompany(companyId, proformaId, transaction, {});
|
||||
}
|
||||
|
||||
async proformaExists(
|
||||
companyId: UniqueID,
|
||||
proformaId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<boolean, Error>> {
|
||||
return this.repository.existsByIdInCompany(companyId, proformaId, transaction, {
|
||||
is_proforma: true,
|
||||
});
|
||||
}
|
||||
|
||||
async findProformasByCriteria(
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>> {
|
||||
return this.repository.findProformasByCriteriaInCompany(companyId, criteria, transaction, {});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import type { Transaction } from "sequelize";
|
||||
|
||||
import type { IProformaToIssuedInvoiceMaterializer } from "../../issued-invoices";
|
||||
|
||||
export class ProformaIssuer implements IProformaIssuer {
|
||||
private readonly proformaRepository: IProformaRepository;
|
||||
private readonly issuedInvoiceFactory: IIssuedInvoiceFactory;
|
||||
private readonly issuedInvoiceRepository: IIssuedInvoiceRepository;
|
||||
private readonly materializer: IProformaToIssuedInvoiceMaterializer;
|
||||
|
||||
constructor(deps: ProformaIssuerDeps) {
|
||||
this.proformaRepository = deps.proformaRepository;
|
||||
this.issuedInvoiceFactory = deps.issuedInvoiceFactory;
|
||||
this.issuedInvoiceRepository = deps.issuedInvoiceRepository;
|
||||
this.materializer = deps.materializer;
|
||||
}
|
||||
|
||||
public async issue(
|
||||
proforma: Proforma,
|
||||
issuedInvoiceId: UniqueID,
|
||||
transaction: Transaction
|
||||
): Promise<Result<Proforma, Error>> {
|
||||
const issueResult = proforma.issue();
|
||||
if (issueResult.isFailure) return Result.fail(issueResult.error);
|
||||
|
||||
const propsResult = this.materializer.materialize(proforma, issuedInvoiceId);
|
||||
|
||||
if (propsResult.isFailure) return Result.fail(propsResult.error);
|
||||
|
||||
const invoiceResult = this.issuedInvoiceFactory.create(propsResult.data, issuedInvoiceId);
|
||||
|
||||
if (invoiceResult.isFailure) {
|
||||
return Result.fail(invoiceResult.error);
|
||||
}
|
||||
|
||||
await this.issuedInvoiceRepository.save(proforma.companyId, invoiceResult.data, transaction);
|
||||
|
||||
await this.proformaRepository.save(proforma.companyId, proforma, transaction);
|
||||
|
||||
return Result.ok(proforma);
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,11 @@
|
||||
import type { InvoiceNumber, InvoiceSerie } from "@erp/customer-invoices/api/domain";
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import type { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { CustomerInvoiceNumber, CustomerInvoiceSerie } from "../value-objects";
|
||||
|
||||
/**
|
||||
* Servicio de dominio que define cómo se genera el siguiente número de factura.
|
||||
*/
|
||||
export interface ICustomerInvoiceNumberGenerator {
|
||||
export interface IProformaNumberGenerator {
|
||||
/**
|
||||
* Devuelve el siguiente número de factura disponible para una empresa dentro de una "serie" de factura.
|
||||
*
|
||||
@ -14,9 +13,9 @@ export interface ICustomerInvoiceNumberGenerator {
|
||||
* @param serie - Serie por la que buscar la última factura
|
||||
* @param transaction - Transacción activa
|
||||
*/
|
||||
nextForCompany(
|
||||
getNextForCompany(
|
||||
companyId: UniqueID,
|
||||
series: Maybe<CustomerInvoiceSerie>,
|
||||
series: Maybe<InvoiceSerie>,
|
||||
transaction: any
|
||||
): Promise<Result<CustomerInvoiceNumber, Error>>;
|
||||
): Promise<Result<InvoiceNumber, Error>>;
|
||||
}
|
||||
@ -4,18 +4,17 @@ import type { Transaction } from "sequelize";
|
||||
|
||||
import {
|
||||
CustomerInvoiceIsProformaSpecification,
|
||||
type CustomerInvoiceStatus,
|
||||
type InvoiceStatus,
|
||||
ProformaCannotBeDeletedError,
|
||||
StatusInvoiceIsDraftSpecification,
|
||||
} from "../../domain";
|
||||
} from "../../../domain";
|
||||
import type {
|
||||
CustomerInvoice,
|
||||
CustomerInvoicePatchProps,
|
||||
CustomerInvoiceProps,
|
||||
} from "../../domain/aggregates";
|
||||
import type { ICustomerInvoiceRepository } from "../../domain/repositories";
|
||||
|
||||
import type { IProformaFactory } from "./proforma-factory";
|
||||
} from "../../../domain/aggregates";
|
||||
import type { ICustomerInvoiceRepository } from "../../../domain/repositories";
|
||||
import type { IProformaFactory } from "../../services/proforma-factory";
|
||||
|
||||
export type IIssuedInvoiceWriteService = {};
|
||||
|
||||
@ -122,7 +121,7 @@ export class IssuedInvoiceWriteService implements IIssuedInvoiceWriteService {
|
||||
async updateProformaStatus(
|
||||
companyId: UniqueID,
|
||||
proformaId: UniqueID,
|
||||
newStatus: CustomerInvoiceStatus,
|
||||
newStatus: InvoiceStatus,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<boolean, Error>> {
|
||||
return this.repository.updateProformaStatusByIdInCompany(
|
||||
@ -0,0 +1,6 @@
|
||||
export * from "./proforma-full-snapshot.interface";
|
||||
export * from "./proforma-full-snapshot-builder";
|
||||
export * from "./proforma-item-full-snapshot.interface";
|
||||
export * from "./proforma-items-full-snapshot-builder";
|
||||
export * from "./proforma-recipient-full-snapshot.interface";
|
||||
export * from "./proforma-recipient-full-snapshot-builder";
|
||||
@ -0,0 +1,130 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
|
||||
import { InvoiceAmount, type Proforma } from "../../../../domain";
|
||||
|
||||
import type { IProformaFullSnapshot } from "./proforma-full-snapshot.interface";
|
||||
import type { IProformaItemsFullSnapshotBuilder } from "./proforma-items-full-snapshot-builder";
|
||||
import type { IProformaRecipientFullSnapshotBuilder } from "./proforma-recipient-full-snapshot-builder";
|
||||
|
||||
export interface IProformaFullSnapshotBuilder
|
||||
extends ISnapshotBuilder<Proforma, IProformaFullSnapshot> {}
|
||||
|
||||
export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder {
|
||||
constructor(
|
||||
private readonly itemsBuilder: IProformaItemsFullSnapshotBuilder,
|
||||
private readonly recipientBuilder: IProformaRecipientFullSnapshotBuilder
|
||||
) {}
|
||||
|
||||
toOutput(invoice: Proforma): IProformaFullSnapshot {
|
||||
const items = this.itemsBuilder.toOutput(invoice.items);
|
||||
const recipient = this.recipientBuilder.toOutput(invoice);
|
||||
|
||||
const allAmounts = invoice.calculateAllAmounts();
|
||||
|
||||
const payment = invoice.paymentMethod.match(
|
||||
(payment) => {
|
||||
const { id, payment_description } = payment.toObjectString();
|
||||
return {
|
||||
payment_id: id,
|
||||
payment_description,
|
||||
};
|
||||
},
|
||||
() => undefined
|
||||
);
|
||||
|
||||
let totalIvaAmount = InvoiceAmount.zero(invoice.currencyCode.code);
|
||||
let totalRecAmount = InvoiceAmount.zero(invoice.currencyCode.code);
|
||||
let totalRetentionAmount = InvoiceAmount.zero(invoice.currencyCode.code);
|
||||
|
||||
const invoiceTaxes = invoice.getTaxes().map((taxGroup) => {
|
||||
const { ivaAmount, recAmount, retentionAmount, totalAmount } = taxGroup.calculateAmounts();
|
||||
|
||||
totalIvaAmount = totalIvaAmount.add(ivaAmount);
|
||||
totalRecAmount = totalRecAmount.add(recAmount);
|
||||
totalRetentionAmount = totalRetentionAmount.add(retentionAmount);
|
||||
|
||||
return {
|
||||
taxable_amount: taxGroup.taxableAmount.toObjectString(),
|
||||
|
||||
iva_code: taxGroup.iva.code,
|
||||
iva_percentage: taxGroup.iva.percentage.toObjectString(),
|
||||
iva_amount: ivaAmount.toObjectString(),
|
||||
|
||||
rec_code: taxGroup.rec.match(
|
||||
(rec) => rec.code,
|
||||
() => ""
|
||||
),
|
||||
|
||||
rec_percentage: taxGroup.rec.match(
|
||||
(rec) => rec.percentage.toObjectString(),
|
||||
() => ({ value: "", scale: "" })
|
||||
),
|
||||
|
||||
rec_amount: recAmount.toObjectString(),
|
||||
|
||||
retention_code: taxGroup.retention.match(
|
||||
(retention) => retention.code,
|
||||
() => ""
|
||||
),
|
||||
|
||||
retention_percentage: taxGroup.retention.match(
|
||||
(retention) => retention.percentage.toObjectString(),
|
||||
() => ({ value: "", scale: "" })
|
||||
),
|
||||
|
||||
retention_amount: retentionAmount.toObjectString(),
|
||||
|
||||
taxes_amount: totalAmount.toObjectString(),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
id: invoice.id.toString(),
|
||||
company_id: invoice.companyId.toString(),
|
||||
|
||||
is_proforma: invoice.isProforma ? "true" : "false",
|
||||
invoice_number: invoice.invoiceNumber.toString(),
|
||||
status: invoice.status.toPrimitive(),
|
||||
series: toEmptyString(invoice.series, (value) => value.toString()),
|
||||
|
||||
invoice_date: invoice.invoiceDate.toDateString(),
|
||||
operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()),
|
||||
|
||||
reference: toEmptyString(invoice.reference, (value) => value.toString()),
|
||||
description: toEmptyString(invoice.description, (value) => value.toString()),
|
||||
notes: toEmptyString(invoice.notes, (value) => value.toString()),
|
||||
|
||||
language_code: invoice.languageCode.toString(),
|
||||
currency_code: invoice.currencyCode.toString(),
|
||||
|
||||
customer_id: invoice.customerId.toString(),
|
||||
recipient,
|
||||
|
||||
payment_method: payment,
|
||||
|
||||
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
||||
items_discount_amount: allAmounts.itemDiscountAmount.toObjectString(),
|
||||
|
||||
discount_percentage: invoice.discountPercentage.toObjectString(),
|
||||
discount_amount: allAmounts.globalDiscountAmount.toObjectString(),
|
||||
|
||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||
|
||||
iva_amount: totalIvaAmount.toObjectString(),
|
||||
rec_amount: totalRecAmount.toObjectString(),
|
||||
retention_amount: totalRetentionAmount.toObjectString(),
|
||||
|
||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||
|
||||
taxes: invoiceTaxes,
|
||||
|
||||
items,
|
||||
|
||||
metadata: {
|
||||
entity: "proformas",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
import type { IProformaItemFullSnapshot } from "./proforma-item-full-snapshot.interface";
|
||||
import type { IProformaRecipientFullSnapshot } from "./proforma-recipient-full-snapshot.interface";
|
||||
|
||||
export interface IProformaFullSnapshot {
|
||||
id: string;
|
||||
company_id: string;
|
||||
|
||||
is_proforma: "true" | "false";
|
||||
invoice_number: string;
|
||||
status: string;
|
||||
series: string;
|
||||
|
||||
invoice_date: string;
|
||||
operation_date: string;
|
||||
|
||||
reference: string;
|
||||
description: string;
|
||||
notes: string;
|
||||
|
||||
language_code: string;
|
||||
currency_code: string;
|
||||
|
||||
customer_id: string;
|
||||
recipient: IProformaRecipientFullSnapshot;
|
||||
|
||||
payment_method?: {
|
||||
payment_id: string;
|
||||
payment_description: string;
|
||||
};
|
||||
|
||||
subtotal_amount: { value: string; scale: string; currency_code: string };
|
||||
items_discount_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
discount_percentage: { value: string; scale: string };
|
||||
discount_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
taxable_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
iva_amount: { value: string; scale: string; currency_code: string };
|
||||
rec_amount: { value: string; scale: string; currency_code: string };
|
||||
retention_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
taxes_amount: { value: string; scale: string; currency_code: string };
|
||||
total_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
taxes: Array<{
|
||||
taxable_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
iva_code: string;
|
||||
iva_percentage: { value: string; scale: string };
|
||||
iva_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
rec_code: string;
|
||||
rec_percentage: { value: string; scale: string };
|
||||
rec_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
retention_code: string;
|
||||
retention_percentage: { value: string; scale: string };
|
||||
retention_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
taxes_amount: { value: string; scale: string; currency_code: string };
|
||||
}>;
|
||||
|
||||
items: IProformaItemFullSnapshot[];
|
||||
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
export interface IProformaItemFullSnapshot {
|
||||
id: string;
|
||||
is_valued: string;
|
||||
position: string;
|
||||
description: string;
|
||||
|
||||
quantity: { value: string; scale: string };
|
||||
unit_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
subtotal_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
discount_percentage: { value: string; scale: string };
|
||||
discount_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
global_discount_percentage: { value: string; scale: string };
|
||||
global_discount_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
taxable_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
iva_code: string;
|
||||
iva_percentage: { value: string; scale: string };
|
||||
iva_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
rec_code: string;
|
||||
rec_percentage: { value: string; scale: string };
|
||||
rec_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
retention_code: string;
|
||||
retention_percentage: { value: string; scale: string };
|
||||
retention_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
taxes_amount: { value: string; scale: string; currency_code: string };
|
||||
total_amount: { value: string; scale: string; currency_code: string };
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
|
||||
import type { CustomerInvoiceItems, IssuedInvoiceItem } from "../../../../domain";
|
||||
|
||||
import type { IProformaItemFullSnapshot } from "./proforma-item-full-snapshot.interface";
|
||||
|
||||
export interface IProformaItemsFullSnapshotBuilder
|
||||
extends ISnapshotBuilder<CustomerInvoiceItems, IProformaItemFullSnapshot[]> {}
|
||||
|
||||
export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnapshotBuilder {
|
||||
private mapItem(invoiceItem: IssuedInvoiceItem, index: number): IProformaItemFullSnapshot {
|
||||
const allAmounts = invoiceItem.calculateAllAmounts();
|
||||
|
||||
return {
|
||||
id: invoiceItem.id.toPrimitive(),
|
||||
is_valued: String(invoiceItem.isValued),
|
||||
position: String(index),
|
||||
description: toEmptyString(invoiceItem.description, (value) => value.toPrimitive()),
|
||||
|
||||
quantity: invoiceItem.quantity.match(
|
||||
(quantity) => quantity.toObjectString(),
|
||||
() => ({ value: "", scale: "" })
|
||||
),
|
||||
|
||||
unit_amount: invoiceItem.unitAmount.match(
|
||||
(unitAmount) => unitAmount.toObjectString(),
|
||||
() => ({ value: "", scale: "", currency_code: "" })
|
||||
),
|
||||
|
||||
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
||||
|
||||
discount_percentage: invoiceItem.itemDiscountPercentage.match(
|
||||
(discountPercentage) => discountPercentage.toObjectString(),
|
||||
() => ({ value: "", scale: "" })
|
||||
),
|
||||
|
||||
discount_amount: allAmounts.itemDiscountAmount.toObjectString(),
|
||||
|
||||
global_discount_percentage: invoiceItem.globalDiscountPercentage.match(
|
||||
(discountPercentage) => discountPercentage.toObjectString(),
|
||||
() => ({ value: "", scale: "" })
|
||||
),
|
||||
|
||||
global_discount_amount: allAmounts.globalDiscountAmount.toObjectString(),
|
||||
|
||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||
|
||||
iva_code: invoiceItem.taxes.iva.match(
|
||||
(iva) => iva.code,
|
||||
() => ""
|
||||
),
|
||||
|
||||
iva_percentage: invoiceItem.taxes.iva.match(
|
||||
(iva) => iva.percentage.toObjectString(),
|
||||
() => ({ value: "", scale: "" })
|
||||
),
|
||||
|
||||
iva_amount: allAmounts.ivaAmount.toObjectString(),
|
||||
|
||||
rec_code: invoiceItem.taxes.rec.match(
|
||||
(rec) => rec.code,
|
||||
() => ""
|
||||
),
|
||||
|
||||
rec_percentage: invoiceItem.taxes.rec.match(
|
||||
(rec) => rec.percentage.toObjectString(),
|
||||
() => ({ value: "", scale: "" })
|
||||
),
|
||||
|
||||
rec_amount: allAmounts.recAmount.toObjectString(),
|
||||
|
||||
retention_code: invoiceItem.taxes.retention.match(
|
||||
(retention) => retention.code,
|
||||
() => ""
|
||||
),
|
||||
|
||||
retention_percentage: invoiceItem.taxes.retention.match(
|
||||
(retention) => retention.percentage.toObjectString(),
|
||||
() => ({ value: "", scale: "" })
|
||||
),
|
||||
|
||||
retention_amount: allAmounts.retentionAmount.toObjectString(),
|
||||
|
||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||
|
||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||
};
|
||||
}
|
||||
|
||||
toOutput(invoiceItems: CustomerInvoiceItems): IProformaItemFullSnapshot[] {
|
||||
return invoiceItems.map((item, index) => this.mapItem(item, index));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import { DomainValidationError, toEmptyString } from "@repo/rdx-ddd";
|
||||
|
||||
import type { InvoiceRecipient, Proforma } from "../../../../domain";
|
||||
import type { ProformaRecipientFullSnapshot } from "../../application-models";
|
||||
|
||||
export interface IProformaRecipientFullSnapshotBuilder
|
||||
extends ISnapshotBuilder<Proforma, ProformaRecipientFullSnapshot> {}
|
||||
|
||||
export class ProformaRecipientFullSnapshotBuilder implements IProformaRecipientFullSnapshotBuilder {
|
||||
toOutput(invoice: Proforma): ProformaRecipientFullSnapshot {
|
||||
if (!invoice.recipient) {
|
||||
throw DomainValidationError.requiredValue("recipient", {
|
||||
cause: invoice,
|
||||
});
|
||||
}
|
||||
|
||||
return invoice.recipient.match(
|
||||
(recipient: InvoiceRecipient) => ({
|
||||
id: invoice.customerId.toString(),
|
||||
name: recipient.name.toString(),
|
||||
tin: recipient.tin.toString(),
|
||||
street: toEmptyString(recipient.street, (v) => v.toString()),
|
||||
street2: toEmptyString(recipient.street2, (v) => v.toString()),
|
||||
city: toEmptyString(recipient.city, (v) => v.toString()),
|
||||
province: toEmptyString(recipient.province, (v) => v.toString()),
|
||||
postal_code: toEmptyString(recipient.postalCode, (v) => v.toString()),
|
||||
country: toEmptyString(recipient.country, (v) => v.toString()),
|
||||
}),
|
||||
() => ({
|
||||
id: "",
|
||||
name: "",
|
||||
tin: "",
|
||||
street: "",
|
||||
street2: "",
|
||||
city: "",
|
||||
province: "",
|
||||
postal_code: "",
|
||||
country: "",
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
export interface IProformaRecipientFullSnapshot {
|
||||
id: string;
|
||||
name: string;
|
||||
tin: string;
|
||||
street: string;
|
||||
street2: string;
|
||||
city: string;
|
||||
province: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./proforma-list-item-snapshot.interface";
|
||||
export * from "./proforma-list-item-snapshot-builder";
|
||||
@ -0,0 +1,46 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
|
||||
import type { CustomerInvoiceListDTO } from "../../../../infrastructure";
|
||||
import type { ProformaListItemSnapshot } from "../../application-models";
|
||||
|
||||
export interface IProformaListItemSnapshotBuilder
|
||||
extends ISnapshotBuilder<CustomerInvoiceListDTO, ProformaListItemSnapshot> {}
|
||||
|
||||
export class ProformaListItemSnapshotBuilder implements IProformaListItemSnapshotBuilder {
|
||||
toOutput(proforma: CustomerInvoiceListDTO): ProformaListItemSnapshot {
|
||||
const recipient = proforma.recipient.toObjectString();
|
||||
|
||||
return {
|
||||
id: proforma.id.toString(),
|
||||
company_id: proforma.companyId.toString(),
|
||||
is_proforma: proforma.isProforma,
|
||||
customer_id: proforma.customerId.toString(),
|
||||
|
||||
invoice_number: proforma.invoiceNumber.toString(),
|
||||
status: proforma.status.toPrimitive(),
|
||||
series: toEmptyString(proforma.series, (value) => value.toString()),
|
||||
|
||||
invoice_date: proforma.invoiceDate.toDateString(),
|
||||
operation_date: toEmptyString(proforma.operationDate, (value) => value.toDateString()),
|
||||
reference: toEmptyString(proforma.reference, (value) => value.toString()),
|
||||
description: toEmptyString(proforma.description, (value) => value.toString()),
|
||||
|
||||
recipient,
|
||||
|
||||
language_code: proforma.languageCode.code,
|
||||
currency_code: proforma.currencyCode.code,
|
||||
|
||||
subtotal_amount: proforma.subtotalAmount.toObjectString(),
|
||||
discount_percentage: proforma.discountPercentage.toObjectString(),
|
||||
discount_amount: proforma.discountAmount.toObjectString(),
|
||||
taxable_amount: proforma.taxableAmount.toObjectString(),
|
||||
taxes_amount: proforma.taxesAmount.toObjectString(),
|
||||
total_amount: proforma.totalAmount.toObjectString(),
|
||||
|
||||
metadata: {
|
||||
entity: "proforma",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
export interface ProformaListItemSnapshot {
|
||||
id: string;
|
||||
company_id: string;
|
||||
is_proforma: boolean;
|
||||
|
||||
customer_id: string;
|
||||
|
||||
invoice_number: string;
|
||||
status: string;
|
||||
series: string;
|
||||
|
||||
invoice_date: string;
|
||||
operation_date: string;
|
||||
|
||||
language_code: string;
|
||||
currency_code: string;
|
||||
|
||||
reference: string;
|
||||
description: string;
|
||||
|
||||
recipient: {
|
||||
tin: string;
|
||||
name: string;
|
||||
street: string;
|
||||
street2: string;
|
||||
city: string;
|
||||
postal_code: string;
|
||||
province: string;
|
||||
country: string;
|
||||
};
|
||||
|
||||
subtotal_amount: { value: string; scale: string; currency_code: string };
|
||||
discount_percentage: { value: string; scale: string };
|
||||
discount_amount: { value: string; scale: string; currency_code: string };
|
||||
taxable_amount: { value: string; scale: string; currency_code: string };
|
||||
taxes_amount: { value: string; scale: string; currency_code: string };
|
||||
total_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
export * from "./proforma-items-report-snapshot-builder";
|
||||
export * from "./proforma-report-item-snapshot.interface";
|
||||
export * from "./proforma-report-snapshot.interface";
|
||||
export * from "./proforma-report-snapshot-builder";
|
||||
export * from "./proforma-report-tax-snapshot.interface";
|
||||
export * from "./proforma-tax-report-snapshot-builder";
|
||||
@ -0,0 +1,35 @@
|
||||
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
|
||||
import type { ISnapshotBuilder, ISnapshotBuilderParams } from "@erp/core/api";
|
||||
|
||||
import type { ProformaFullSnapshot, ProformaReportItemSnapshot } from "../../application-models";
|
||||
|
||||
export interface IProformaItemReportSnapshotBuilder
|
||||
extends ISnapshotBuilder<ProformaFullSnapshot["items"], ProformaReportItemSnapshot[]> {}
|
||||
|
||||
export class ProformaItemReportSnapshotBuilder implements IProformaItemReportSnapshotBuilder {
|
||||
toOutput(
|
||||
items: ProformaFullSnapshot["items"],
|
||||
params?: ISnapshotBuilderParams
|
||||
): ProformaReportItemSnapshot[] {
|
||||
const locale = params?.locale as string;
|
||||
|
||||
const moneyOptions = {
|
||||
hideZeros: true,
|
||||
minimumFractionDigits: 2,
|
||||
};
|
||||
|
||||
return items.map((item) => ({
|
||||
description: item.description,
|
||||
quantity: QuantityDTOHelper.format(item.quantity, locale, { minimumFractionDigits: 0 }),
|
||||
unit_amount: MoneyDTOHelper.format(item.unit_amount, locale, moneyOptions),
|
||||
subtotal_amount: MoneyDTOHelper.format(item.subtotal_amount, locale, moneyOptions),
|
||||
discount_percentage: PercentageDTOHelper.format(item.discount_percentage, locale, {
|
||||
minimumFractionDigits: 0,
|
||||
}),
|
||||
discount_amount: MoneyDTOHelper.format(item.discount_amount, locale, moneyOptions),
|
||||
taxable_amount: MoneyDTOHelper.format(item.taxable_amount, locale, moneyOptions),
|
||||
taxes_amount: MoneyDTOHelper.format(item.taxes_amount, locale, moneyOptions),
|
||||
total_amount: MoneyDTOHelper.format(item.total_amount, locale, moneyOptions),
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
export interface ProformaReportItemSnapshot {
|
||||
description: string;
|
||||
quantity: string;
|
||||
unit_amount: string;
|
||||
subtotal_amount: string;
|
||||
discount_percentage: string;
|
||||
discount_amount: string;
|
||||
taxable_amount: string;
|
||||
taxes_amount: string;
|
||||
total_amount: string;
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
import { DateHelper, MoneyDTOHelper, PercentageDTOHelper } from "@erp/core";
|
||||
import type { ISnapshotBuilder, ISnapshotBuilderParams } from "@erp/core/api";
|
||||
|
||||
import type {
|
||||
ProformaFullSnapshot,
|
||||
ProformaReportItemSnapshot,
|
||||
ProformaReportSnapshot,
|
||||
ProformaReportTaxSnapshot,
|
||||
} from "../../application-models";
|
||||
|
||||
export interface IProformaReportSnapshotBuilder
|
||||
extends ISnapshotBuilder<ProformaFullSnapshot, ProformaReportSnapshot> {}
|
||||
|
||||
export class ProformaReportSnapshotBuilder implements IProformaReportSnapshotBuilder {
|
||||
constructor(
|
||||
private readonly itemsBuilder: ISnapshotBuilder<
|
||||
ProformaFullSnapshot["items"],
|
||||
ProformaReportItemSnapshot[]
|
||||
>,
|
||||
private readonly taxesBuilder: ISnapshotBuilder<
|
||||
ProformaFullSnapshot["taxes"],
|
||||
ProformaReportTaxSnapshot[]
|
||||
>
|
||||
) {}
|
||||
|
||||
toOutput(
|
||||
snapshot: ProformaFullSnapshot,
|
||||
params?: ISnapshotBuilderParams
|
||||
): ProformaReportSnapshot {
|
||||
const locale = params?.locale as string;
|
||||
|
||||
const moneyOptions = {
|
||||
hideZeros: true,
|
||||
minimumFractionDigits: 2,
|
||||
};
|
||||
|
||||
return {
|
||||
id: snapshot.id,
|
||||
company_id: snapshot.company_id,
|
||||
company_slug: "rodax",
|
||||
invoice_number: snapshot.invoice_number,
|
||||
series: snapshot.series,
|
||||
status: snapshot.status,
|
||||
reference: snapshot.reference,
|
||||
|
||||
language_code: snapshot.language_code,
|
||||
currency_code: snapshot.currency_code,
|
||||
|
||||
invoice_date: DateHelper.format(snapshot.invoice_date, locale),
|
||||
|
||||
payment_method: snapshot.payment_method?.payment_description ?? "",
|
||||
notes: snapshot.notes,
|
||||
|
||||
recipient: {
|
||||
name: snapshot.recipient.name,
|
||||
tin: snapshot.recipient.tin,
|
||||
format_address: this.formatAddress(snapshot.recipient),
|
||||
},
|
||||
|
||||
items: this.itemsBuilder.toOutput(snapshot.items, { locale }),
|
||||
taxes: this.taxesBuilder.toOutput(snapshot.taxes, { locale }),
|
||||
|
||||
subtotal_amount: MoneyDTOHelper.format(snapshot.subtotal_amount, locale, moneyOptions),
|
||||
discount_percentage: PercentageDTOHelper.format(snapshot.discount_percentage, locale, {
|
||||
hideZeros: true,
|
||||
}),
|
||||
discount_amount: MoneyDTOHelper.format(snapshot.discount_amount, locale, moneyOptions),
|
||||
taxable_amount: MoneyDTOHelper.format(snapshot.taxable_amount, locale, moneyOptions),
|
||||
taxes_amount: MoneyDTOHelper.format(snapshot.taxes_amount, locale, moneyOptions),
|
||||
total_amount: MoneyDTOHelper.format(snapshot.total_amount, locale, moneyOptions),
|
||||
};
|
||||
}
|
||||
|
||||
private formatAddress(recipient: ProformaFullSnapshot["recipient"]): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
if (recipient.street) lines.push(recipient.street);
|
||||
if (recipient.street2) lines.push(recipient.street2);
|
||||
|
||||
const cityLine = [recipient.postal_code, recipient.city].filter(Boolean).join(" ");
|
||||
|
||||
if (cityLine) lines.push(cityLine);
|
||||
if (recipient.province && recipient.province !== recipient.city) {
|
||||
lines.push(recipient.province);
|
||||
}
|
||||
if (recipient.country && recipient.country !== "es") {
|
||||
lines.push(recipient.country);
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
import type { ProformaReportItemSnapshot } from "./proforma-report-item-snapshot.interface";
|
||||
import type { ProformaReportTaxSnapshot } from "./proforma-report-tax-snapshot.interface";
|
||||
|
||||
export interface ProformaReportSnapshot {
|
||||
id: string;
|
||||
company_id: string;
|
||||
company_slug: string;
|
||||
invoice_number: string;
|
||||
series: string;
|
||||
status: string;
|
||||
reference: string;
|
||||
|
||||
language_code: string;
|
||||
currency_code: string;
|
||||
|
||||
invoice_date: string;
|
||||
payment_method: string;
|
||||
notes: string;
|
||||
|
||||
recipient: {
|
||||
name: string;
|
||||
tin: string;
|
||||
format_address: string;
|
||||
};
|
||||
|
||||
items: ProformaReportItemSnapshot[];
|
||||
taxes: ProformaReportTaxSnapshot[];
|
||||
|
||||
subtotal_amount: string;
|
||||
discount_percentage: string;
|
||||
discount_amount: string;
|
||||
taxable_amount: string;
|
||||
taxes_amount: string;
|
||||
total_amount: string;
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
export interface ProformaReportTaxSnapshot {
|
||||
taxable_amount: string;
|
||||
|
||||
iva_code: string;
|
||||
iva_percentage: string;
|
||||
iva_amount: string;
|
||||
|
||||
rec_code: string;
|
||||
rec_percentage: string;
|
||||
rec_amount: string;
|
||||
|
||||
retention_code: string;
|
||||
retention_percentage: string;
|
||||
retention_amount: string;
|
||||
|
||||
taxes_amount: string;
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import { MoneyDTOHelper, PercentageDTOHelper } from "@erp/core";
|
||||
import type { ISnapshotBuilder, ISnapshotBuilderParams } from "@erp/core/api";
|
||||
|
||||
import type { ProformaFullSnapshot, ProformaReportTaxSnapshot } from "../../application-models";
|
||||
|
||||
export interface IProformaTaxReportSnapshotBuilder
|
||||
extends ISnapshotBuilder<ProformaFullSnapshot["taxes"], ProformaReportTaxSnapshot[]> {}
|
||||
|
||||
export class ProformaTaxReportSnapshotBuilder implements IProformaTaxReportSnapshotBuilder {
|
||||
toOutput(
|
||||
taxes: ProformaFullSnapshot["taxes"],
|
||||
params?: ISnapshotBuilderParams
|
||||
): ProformaReportTaxSnapshot[] {
|
||||
const locale = params?.locale as string;
|
||||
|
||||
const moneyOptions = {
|
||||
hideZeros: true,
|
||||
minimumFractionDigits: 2,
|
||||
};
|
||||
|
||||
return taxes.map((tax) => ({
|
||||
taxable_amount: MoneyDTOHelper.format(tax.taxable_amount, locale, moneyOptions),
|
||||
|
||||
iva_code: tax.iva_code,
|
||||
iva_percentage: PercentageDTOHelper.format(tax.iva_percentage, locale),
|
||||
iva_amount: MoneyDTOHelper.format(tax.iva_amount, locale, moneyOptions),
|
||||
|
||||
rec_code: tax.rec_code,
|
||||
rec_percentage: PercentageDTOHelper.format(tax.rec_percentage, locale),
|
||||
rec_amount: MoneyDTOHelper.format(tax.rec_amount, locale, moneyOptions),
|
||||
|
||||
retention_code: tax.retention_code,
|
||||
retention_percentage: PercentageDTOHelper.format(tax.retention_percentage, locale),
|
||||
retention_amount: MoneyDTOHelper.format(tax.retention_amount, locale, moneyOptions),
|
||||
|
||||
taxes_amount: MoneyDTOHelper.format(tax.taxes_amount, locale, moneyOptions),
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
import type { ITransactionManager } from "@erp/core/api";
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { CreateProformaRequestDTO } from "../../../../../common";
|
||||
import type { CreateProformaPropsMapper } from "../../mappers";
|
||||
import type { IProformaCreator } from "../../services";
|
||||
import type { IProformaFullSnapshotBuilder } from "../../snapshot-builders";
|
||||
|
||||
type CreateProformaUseCaseInput = {
|
||||
companyId: UniqueID;
|
||||
dto: CreateProformaRequestDTO;
|
||||
};
|
||||
|
||||
type CreateProformaUseCaseDeps = {
|
||||
mapper: CreateProformaPropsMapper;
|
||||
creator: IProformaCreator;
|
||||
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||
transactionManager: ITransactionManager;
|
||||
};
|
||||
|
||||
export class CreateProformaUseCase {
|
||||
private readonly mapper: CreateProformaPropsMapper;
|
||||
private readonly creator: IProformaCreator;
|
||||
private readonly fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||
private readonly transactionManager: ITransactionManager;
|
||||
|
||||
constructor(deps: CreateProformaUseCaseDeps) {
|
||||
this.mapper = deps.mapper;
|
||||
this.creator = deps.creator;
|
||||
this.fullSnapshotBuilder = deps.fullSnapshotBuilder;
|
||||
this.transactionManager = deps.transactionManager;
|
||||
}
|
||||
|
||||
public async execute(params: CreateProformaUseCaseInput) {
|
||||
const { dto, companyId } = params;
|
||||
|
||||
// 1) Mapear DTO → props de dominio
|
||||
const mappedResult = this.mapper.map(dto, companyId);
|
||||
if (mappedResult.isFailure) {
|
||||
return Result.fail(mappedResult.error);
|
||||
}
|
||||
|
||||
const { props, id } = mappedResult.data;
|
||||
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
const createResult = await this.creator.create(companyId, id, props, transaction);
|
||||
|
||||
if (createResult.isFailure) {
|
||||
return Result.fail(createResult.error);
|
||||
}
|
||||
|
||||
const snapshot = this.fullSnapshotBuilder.toOutput(createResult.data);
|
||||
|
||||
return Result.ok(snapshot);
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
import type { ITransactionManager } from "@erp/core/api";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { IProformaFinder } from "../services";
|
||||
import type { IProformaFullSnapshotBuilder } from "../snapshot-builders";
|
||||
|
||||
type GetProformaUseCaseInput = {
|
||||
companyId: UniqueID;
|
||||
proforma_id: string;
|
||||
};
|
||||
|
||||
export class GetProformaByIdUseCase {
|
||||
constructor(
|
||||
private readonly finder: IProformaFinder,
|
||||
private readonly fullSnapshotBuilder: IProformaFullSnapshotBuilder,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(params: GetProformaUseCaseInput) {
|
||||
const { proforma_id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(proforma_id);
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const proformaId = idOrError.data;
|
||||
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
const proformaResult = await this.finder.findProformaById(
|
||||
companyId,
|
||||
proformaId,
|
||||
transaction
|
||||
);
|
||||
if (proformaResult.isFailure) {
|
||||
return Result.fail(proformaResult.error);
|
||||
}
|
||||
|
||||
const fullSnapshot = this.fullSnapshotBuilder.toOutput(proformaResult.data);
|
||||
|
||||
return Result.ok(fullSnapshot);
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user