diff --git a/modules/core/src/common/catalogs/taxes/index.ts b/modules/core/src/common/catalogs/taxes/index.ts index 9b0eefda..a4c9e8e9 100644 --- a/modules/core/src/common/catalogs/taxes/index.ts +++ b/modules/core/src/common/catalogs/taxes/index.ts @@ -1,3 +1,5 @@ -export * from "./spain-tax-catalog-provider"; -export * from "./tax-catalog-provider"; +export * from "./json-tax-catalog.provider"; +export * from "./spain-tax-catalog.provider"; export * from "./tax-catalog-types"; +export * from "./tax-catalog.provider"; + diff --git a/modules/core/src/common/catalogs/taxes/json-tax-catalog-provider.ts b/modules/core/src/common/catalogs/taxes/json-tax-catalog.provider.ts similarity index 96% rename from modules/core/src/common/catalogs/taxes/json-tax-catalog-provider.ts rename to modules/core/src/common/catalogs/taxes/json-tax-catalog.provider.ts index e7a18311..460315cd 100644 --- a/modules/core/src/common/catalogs/taxes/json-tax-catalog-provider.ts +++ b/modules/core/src/common/catalogs/taxes/json-tax-catalog.provider.ts @@ -1,8 +1,8 @@ // --- Adaptador que carga el catálogo JSON en memoria e indexa por code --- import { Maybe } from "@repo/rdx-utils"; -import { TaxCatalogProvider } from "./tax-catalog-provider"; import { TaxCatalogType, TaxItemType } from "./tax-catalog-types"; +import { TaxCatalogProvider } from "./tax-catalog.provider"; // Si quieres habilitar la carga desde fichero en Node: // import * as fs from "node:fs"; diff --git a/modules/core/src/common/catalogs/taxes/spain-tax-catalog-provider.ts b/modules/core/src/common/catalogs/taxes/spain-tax-catalog.provider.ts similarity index 69% rename from modules/core/src/common/catalogs/taxes/spain-tax-catalog-provider.ts rename to modules/core/src/common/catalogs/taxes/spain-tax-catalog.provider.ts index 182d5558..91ad4b37 100644 --- a/modules/core/src/common/catalogs/taxes/spain-tax-catalog-provider.ts +++ b/modules/core/src/common/catalogs/taxes/spain-tax-catalog.provider.ts @@ -1,4 +1,4 @@ -import { JsonTaxCatalogProvider } from "./json-tax-catalog-provider"; +import { JsonTaxCatalogProvider } from "./json-tax-catalog.provider"; import spainTaxCatalog from "./spain-tax-catalog.json"; export const spainTaxCatalogProvider = new JsonTaxCatalogProvider(spainTaxCatalog); diff --git a/modules/core/src/common/catalogs/taxes/tax-catalog-provider.ts b/modules/core/src/common/catalogs/taxes/tax-catalog.provider.ts similarity index 100% rename from modules/core/src/common/catalogs/taxes/tax-catalog-provider.ts rename to modules/core/src/common/catalogs/taxes/tax-catalog.provider.ts diff --git a/modules/customer-invoices/src/api/application/create-customer-invoice/create-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/create-customer-invoice/create-customer-invoice.use-case.ts index fa80212b..a070d5ba 100644 --- a/modules/customer-invoices/src/api/application/create-customer-invoice/create-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/create-customer-invoice/create-customer-invoice.use-case.ts @@ -1,3 +1,4 @@ +import { JsonTaxCatalogProvider } from "@erp/core"; import { DuplicateEntityError, ITransactionManager } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; @@ -5,7 +6,7 @@ import { Transaction } from "sequelize"; import { CreateCustomerInvoiceRequestDTO } from "../../../common/dto"; import { CustomerInvoiceService } from "../../domain"; import { CreateCustomerInvoiceAssembler } from "./assembler"; -import { mapDTOToCreateCustomerInvoiceProps } from "./map-dto-to-create-customer-invoice-props"; +import { CreateCustomerInvoicePropsMapper } from "./map-dto-to-create-customer-invoice-props"; type CreateCustomerInvoiceUseCaseInput = { companyId: UniqueID; @@ -16,14 +17,17 @@ export class CreateCustomerInvoiceUseCase { constructor( private readonly service: CustomerInvoiceService, private readonly transactionManager: ITransactionManager, - private readonly assembler: CreateCustomerInvoiceAssembler + private readonly assembler: CreateCustomerInvoiceAssembler, + private readonly taxCatalog: JsonTaxCatalogProvider, + ) {} public execute(params: CreateCustomerInvoiceUseCaseInput) { const { dto, companyId } = params; + const dtoMapper = new CreateCustomerInvoicePropsMapper({ taxCatalog: this.taxCatalog }); // 1) Mapear DTO → props de dominio - const dtoResult = mapDTOToCreateCustomerInvoiceProps(dto); + const dtoResult = dtoMapper.map(dto); if (dtoResult.isFailure) { return Result.fail(dtoResult.error); } diff --git a/modules/customer-invoices/src/api/application/create-customer-invoice/map-dto-to-create-customer-invoice-props.ts b/modules/customer-invoices/src/api/application/create-customer-invoice/map-dto-to-create-customer-invoice-props.ts index 3f32b5ed..33a8e223 100644 --- a/modules/customer-invoices/src/api/application/create-customer-invoice/map-dto-to-create-customer-invoice-props.ts +++ b/modules/customer-invoices/src/api/application/create-customer-invoice/map-dto-to-create-customer-invoice-props.ts @@ -1,4 +1,4 @@ -import { spainTaxCatalogProvider } from "@erp/core"; +import { JsonTaxCatalogProvider } from "@erp/core"; import { DomainError, Tax, @@ -45,181 +45,189 @@ import { * */ -export function mapDTOToCreateCustomerInvoiceProps(dto: CreateCustomerInvoiceRequestDTO) { - try { - const errors: ValidationErrorDetail[] = []; +export class CreateCustomerInvoicePropsMapper { + private readonly taxCatalog: JsonTaxCatalogProvider; + private errors: ValidationErrorDetail[] = []; + private languageCode?: LanguageCode; + private currencyCode?: CurrencyCode; - const customerId = extractOrPushError(UniqueID.create(dto.id), "id", errors); - const companyId = extractOrPushError(UniqueID.create(dto.company_id), "company_id", errors); + constructor(params: {taxCatalog: JsonTaxCatalogProvider}) { + this.taxCatalog = params.taxCatalog; + this.errors = []; + } - const invoiceNumber = extractOrPushError( - CustomerInvoiceNumber.create(dto.invoice_number), - "invoice_number", - errors - ); - const status = extractOrPushError(CustomerInvoiceStatus.create(dto.status), "status", errors); + public map(dto: CreateCustomerInvoiceRequestDTO) { + try { + this.errors = []; - const series = extractOrPushError( - maybeFromNullableVO(dto.series, (value) => CustomerInvoiceSerie.create(value)), - "series", - errors - ); + const defaultStatus = CustomerInvoiceStatus.createDraft(); - const invoiceDate = extractOrPushError( - UtcDate.createFromISO(dto.invoice_date), - "invoice_date", - errors - ); + const invoiceId = extractOrPushError(UniqueID.create(dto.id), "id", this.errors); + const companyId = extractOrPushError(UniqueID.create(dto.company_id), "company_id", this.errors); + const customerId = extractOrPushError(UniqueID.create(dto.customer_id), "customer_id", this.errors); - const operationDate = extractOrPushError( - maybeFromNullableVO(dto.operation_date, (value) => UtcDate.createFromISO(value)), - "operation_date", - errors - ); - - const notes = extractOrPushError( - maybeFromNullableVO(dto.notes, (value) => TextValue.create(value)), - "notes", - errors - ); - - const languageCode = extractOrPushError( - LanguageCode.create(dto.language_code), - "language_code", - errors - ); - - const currencyCode = extractOrPushError( - CurrencyCode.create(dto.currency_code), - "currency_code", - errors - ); - - const discountPercentage = extractOrPushError( - Percentage.create({ - value: Number(dto.discount_percentage.value), - scale: Number(dto.discount_percentage.scale), - }), - "discount_percentage", - errors - ); - - const items = _mapDTOtoInvoiceItems(dto.items, languageCode!, currencyCode!, errors); - - if (errors.length > 0) { - return Result.fail( - new ValidationErrorCollection("Customer invoice props mapping failed", errors) + const invoiceNumber = extractOrPushError( + CustomerInvoiceNumber.create(dto.invoice_number), + "invoice_number", + this.errors ); + + const series = extractOrPushError( + maybeFromNullableVO(dto.series, (value) => CustomerInvoiceSerie.create(value)), + "series", + this.errors + ); + + const invoiceDate = extractOrPushError( + UtcDate.createFromISO(dto.invoice_date), + "invoice_date", + this.errors + ); + + const operationDate = extractOrPushError( + maybeFromNullableVO(dto.operation_date, (value) => UtcDate.createFromISO(value)), + "operation_date", + this.errors + ); + + const notes = extractOrPushError( + maybeFromNullableVO(dto.notes, (value) => TextValue.create(value)), + "notes", + this.errors + ); + + this.languageCode = extractOrPushError( + LanguageCode.create(dto.language_code), + "language_code", + this.errors + ); + + this.currencyCode = extractOrPushError( + CurrencyCode.create(dto.currency_code), + "currency_code", + this.errors + ); + + const discountPercentage = extractOrPushError( + Percentage.create({ + value: Number(dto.discount_percentage.value), + scale: Number(dto.discount_percentage.scale), + }), + "discount_percentage", + this.errors + ); + + const items = this.mapItems(dto.items); + + if (this.errors.length > 0) { + return Result.fail( + new ValidationErrorCollection("Customer invoice props mapping failed", this.errors) + ); + } + + const invoiceProps: CustomerInvoiceProps = { + companyId: companyId!, + status: defaultStatus!, + + invoiceNumber: invoiceNumber!, + invoiceDate: invoiceDate!, + + operationDate: operationDate!, + series: series!, + + notes: notes!, + + customerId: customerId!, + + languageCode: this.languageCode!, + currencyCode: this.currencyCode!, + + discountPercentage: discountPercentage!, + + taxes: Taxes.create({ items: [] }), + items: items, + }; + + return Result.ok({ id: invoiceId!, props: invoiceProps }); + } catch (err: unknown) { + return Result.fail(new DomainError("Customer invoice props mapping failed", { cause: err })); } + } - const invoiceProps: CustomerInvoiceProps = { - companyId: companyId!, - status: status!, + private mapItems(items: CreateCustomerInvoiceItemRequestDTO[]) { + const invoiceItems = CustomerInvoiceItems.create({ + currencyCode: this.currencyCode!, + languageCode: this.languageCode!, + items: [], + }); - invoiceNumber: invoiceNumber!, - invoiceDate: invoiceDate!, + items.forEach((item, index) => { + const description = extractOrPushError( + maybeFromNullableVO(item.description, (value) => + CustomerInvoiceItemDescription.create(value) + ), + "description", + this.errors + ); - operationDate: operationDate!, - series: series!, + const quantity = extractOrPushError( + maybeFromNullableVO(item.quantity, (value) => ItemQuantity.create(value)), + "quantity", + this.errors + ); - notes: notes!, + const unitAmount = extractOrPushError( + maybeFromNullableVO(item.unit_amount, (value) => ItemAmount.create(value)), + "unit_amount", + this.errors + ); - languageCode: languageCode!, - currencyCode: currencyCode!, + const discountPercentage = extractOrPushError( + maybeFromNullableVO(item.discount_percentage, (value) => ItemDiscount.create(value)), + "discount_percentage", + this.errors + ); - discountPercentage: discountPercentage!, + const taxes = this.mapTaxes(item, index); - taxes: Taxes.create({ items: [] }), - items: items, - }; + const itemProps: CustomerInvoiceItemProps = { + currencyCode: this.currencyCode!, + languageCode: this.languageCode!, + description: description!, + quantity: quantity!, + unitAmount: unitAmount!, + discountPercentage: discountPercentage!, + taxes, + }; - return Result.ok({ id: customerId!, props: invoiceProps }); - } catch (err: unknown) { - return Result.fail(new DomainError("Customer invoice props mapping failed", { cause: err })); + const itemResult = CustomerInvoiceItem.create(itemProps); + if (itemResult.isSuccess) { + invoiceItems.add(itemResult.data); + } else { + this.errors.push({ + path: `items[${index}]`, + message: itemResult.error.message, + }); + } + }); + return invoiceItems; + } + + private mapTaxes(item: CreateCustomerInvoiceItemRequestDTO, itemIndex: number) { + const taxes = Taxes.create({ items: [] }); + + item.taxes.split(",").every((tax_code, taxIndex) => { + const taxResult = Tax.createFromCode(tax_code, this.taxCatalog); + if (taxResult.isSuccess) { + taxes.add(taxResult.data); + } else { + this.errors.push({ + path: `items[${itemIndex}].taxes[${taxIndex}]`, + message: taxResult.error.message, + }); + } + }); + return taxes; } } -function _mapDTOtoInvoiceItems( - items: CreateCustomerInvoiceItemRequestDTO[], - languageCode: LanguageCode, - currencyCode: CurrencyCode, - errors: ValidationErrorDetail[] -) { - const invoiceItems = CustomerInvoiceItems.create({ - currencyCode, - languageCode, - items: [], - }); - - items.forEach((item, index) => { - const description = extractOrPushError( - maybeFromNullableVO(item.description, (value) => - CustomerInvoiceItemDescription.create(value) - ), - "description", - errors - ); - - const quantity = extractOrPushError( - maybeFromNullableVO(item.quantity, (value) => ItemQuantity.create(value)), - "quantity", - errors - ); - - const unitAmount = extractOrPushError( - maybeFromNullableVO(item.unit_amount, (value) => ItemAmount.create(value)), - "unit_amount", - errors - ); - - const discountPercentage = extractOrPushError( - maybeFromNullableVO(item.discount_percentage, (value) => ItemDiscount.create(value)), - "discount_percentage", - errors - ); - - const taxes = _mapDTOtoTaxes(item, index, errors); - - const itemProps: CustomerInvoiceItemProps = { - currencyCode: currencyCode!, - languageCode: languageCode!, - description: description!, - quantity: quantity!, - unitAmount: unitAmount!, - discountPercentage: discountPercentage!, - taxes, - }; - - const itemResult = CustomerInvoiceItem.create(itemProps); - if (itemResult.isSuccess) { - invoiceItems.add(itemResult.data); - } else { - errors.push({ - path: `items[${index}]`, - message: itemResult.error.message, - }); - } - }); - return invoiceItems; -} - -function _mapDTOtoTaxes( - item: CreateCustomerInvoiceItemRequestDTO, - itemIndex: number, - errors: ValidationErrorDetail[] -) { - const taxes = Taxes.create({ items: [] }); - - item.taxes.split(",").every((tax_code, taxIndex) => { - const taxResult = Tax.createFromCode(tax_code, spainTaxCatalogProvider); - if (taxResult.isSuccess) { - taxes.add(taxResult.data); - } else { - errors.push({ - path: `items[${itemIndex}].taxes[${taxIndex}]`, - message: taxResult.error.message, - }); - } - }); - return taxes; -} diff --git a/modules/customer-invoices/src/api/application/get-customer-invoice/get-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/get-customer-invoice/get-customer-invoice.use-case.ts index 3888aea2..5d27136e 100644 --- a/modules/customer-invoices/src/api/application/get-customer-invoice/get-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/get-customer-invoice/get-customer-invoice.use-case.ts @@ -13,7 +13,7 @@ export class GetCustomerInvoiceUseCase { constructor( private readonly service: CustomerInvoiceService, private readonly transactionManager: ITransactionManager, - private readonly assembler: GetCustomerInvoiceAssembler + private readonly assembler: GetCustomerInvoiceAssembler, ) {} public execute(params: GetCustomerInvoiceUseCaseInput) { diff --git a/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts b/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts index fa2234a3..43192456 100644 --- a/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts +++ b/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts @@ -1,4 +1,4 @@ -import { Taxes } from "@erp/core/api"; +import { DomainValidationError, Taxes } from "@erp/core/api"; import { AggregateRoot, CurrencyCode, @@ -10,7 +10,7 @@ import { UtcDate, } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; -import { CustomerInvoiceItems } from "../entities"; +import { CustomerInvoiceItems, InvoiceRecipient } from "../entities"; import { CustomerInvoiceNumber, CustomerInvoiceSerie, @@ -27,6 +27,10 @@ export interface CustomerInvoiceProps { invoiceDate: UtcDate; operationDate: Maybe; + customerId: UniqueID; + + recipient: Maybe; + notes: Maybe; //dueDate: UtcDate; // ? --> depende de la forma de pago @@ -52,7 +56,7 @@ export interface CustomerInvoiceProps { //totalAmount: MoneyValue; - //customer?: CustomerInvoiceCustomer; + items: CustomerInvoiceItems; } @@ -75,9 +79,17 @@ export class CustomerInvoice extends AggregateRoot { const customerInvoice = new CustomerInvoice(props, id); // Reglas de negocio / validaciones - // ... - // ... + + if (!customerInvoice.isDraft() && !customerInvoice.hasRecipient()) { + return Result.fail( + new DomainValidationError( + "MISSING_CUSTOMER_DATA", + "customerData", + "Customer data must be provided for non-draft invoices" + ) + ); + } // 🔹 Disparar evento de dominio "CustomerInvoiceAuthenticatedEvent" //const { customerInvoice } = props; //user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerInvoice.toString())); @@ -93,6 +105,10 @@ export class CustomerInvoice extends AggregateRoot { return this.props.companyId; } + public get customerId(): UniqueID { + return this.props.customerId; + } + public get status(): CustomerInvoiceStatus { return this.props.status; } @@ -117,6 +133,10 @@ export class CustomerInvoice extends AggregateRoot { return this.props.notes; } + public get recipient(): Maybe { + return this.props.recipient; + } + public get languageCode(): LanguageCode { return this.props.languageCode; } @@ -154,6 +174,14 @@ export class CustomerInvoice extends AggregateRoot { return this._items; } + public isDraft() { + return this.status.isDraft(); + } + + public hasRecipient() { + return this.recipient.isSome() + } + /*get senderId(): UniqueID { return this.props.senderId; }*/ diff --git a/modules/customer-invoices/src/api/domain/entities/index.ts b/modules/customer-invoices/src/api/domain/entities/index.ts index 1751fa67..ea23a845 100644 --- a/modules/customer-invoices/src/api/domain/entities/index.ts +++ b/modules/customer-invoices/src/api/domain/entities/index.ts @@ -1,2 +1,4 @@ export * from "./customer-invoice-items"; export * from "./invoice-customer"; +export * from "./invoice-recipient"; + diff --git a/modules/customer-invoices/src/api/domain/entities/invoice-recipient/index.ts b/modules/customer-invoices/src/api/domain/entities/invoice-recipient/index.ts new file mode 100644 index 00000000..5ca076cd --- /dev/null +++ b/modules/customer-invoices/src/api/domain/entities/invoice-recipient/index.ts @@ -0,0 +1,3 @@ +export * from "./invoice-recipient"; + + diff --git a/modules/customer-invoices/src/api/domain/entities/invoice-recipient/invoice-recipient.ts b/modules/customer-invoices/src/api/domain/entities/invoice-recipient/invoice-recipient.ts new file mode 100644 index 00000000..324198a0 --- /dev/null +++ b/modules/customer-invoices/src/api/domain/entities/invoice-recipient/invoice-recipient.ts @@ -0,0 +1,103 @@ +import { + City, + Country, + Name, + PostalCode, + Province, + Street, + TINNumber, + ValueObject +} from "@repo/rdx-ddd"; +import { Maybe, Result } from "@repo/rdx-utils"; +import * as z from "zod/v4"; + +export interface InvoiceRecipientProps { + name: Name; + tin: TINNumber; + street: Maybe; + street2: Maybe; + city: Maybe; + postalCode: Maybe; + province: Maybe; + country: Maybe; +} + +export class InvoiceRecipient extends ValueObject { + protected static validate(values: InvoiceRecipientProps) { + const schema = z.object({ + name: z.string(), + tin: z.string(), + street: z.string().optional(), + street2: z.string().optional(), + city: z.string().optional(), + postalCode: z.string().optional(), + province: z.string().optional(), + country: z.string().optional(), + }) + + return schema.safeParse(values); + } + + static create(values: InvoiceRecipientProps): Result { + const valueIsValid = InvoiceRecipient.validate(values) + + if (!valueIsValid.success) { + return Result.fail(new Error(valueIsValid.error.issues[0].message)); + } + + return Result.ok(new InvoiceRecipient(values)); + } + + public update(partial: Partial): Result { + const updatedProps = { + ...this.props, + ...partial, + } as InvoiceRecipientProps; + + return InvoiceRecipient.create(updatedProps); + } + + + public get name(): Name { + return this.props.name; + } + + public get tin(): TINNumber { + return this.props.tin; + } + + get street(): Maybe { + return this.props.street; + } + + get street2(): Maybe { + return this.props.street2; + } + + get city(): Maybe { + return this.props.city; + } + + get postalCode(): Maybe { + return this.props.postalCode; + } + + get province(): Maybe { + return this.props.province; + } + + get country(): Maybe { + return this.props.country; + } + + getProps(): InvoiceRecipientProps { + return this.props; + } + + toPrimitive() { + return this.getProps(); + } + +} + + diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-status.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-status.ts index 92ecd299..c49eac32 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-status.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-status.ts @@ -71,6 +71,10 @@ export class CustomerInvoiceStatus extends ValueObject new ListCustomerInvoicesUseCase(_service!, transactionManager!, _assemblers!.list), get: () => new GetCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.get), create: () => - new CreateCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.create), + new CreateCustomerInvoiceUseCase( + _service!, + transactionManager!, + _assemblers!.create, + _catalogs!.taxes + ), update: () => new UpdateCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.update), delete: () => new DeleteCustomerInvoiceUseCase(_service!, transactionManager!), diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice.mapper.ts index 8b944777..0858e36b 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice.mapper.ts @@ -7,9 +7,16 @@ import { extractOrPushError, } from "@erp/core/api"; import { + City, + Country, CurrencyCode, LanguageCode, + Name, Percentage, + PostalCode, + Province, + Street, + TINNumber, TextValue, UniqueID, UtcDate, @@ -117,11 +124,60 @@ export class CustomerInvoiceMapper errors ); - if (errors.length > 0) { - return Result.fail( - new ValidationErrorCollection("Customer invoice props mapping failed", errors) - ); - } + // Customer + const customerId = extractOrPushError( + UniqueID.create(source.customer_id), + "customer_id", + errors + ); + + const customerName = extractOrPushError( + maybeFromNullableVO(source.customer_name, (value) => Name.create(value)), + "customer_name", + errors + ); + + const customerTin = extractOrPushError( + maybeFromNullableVO(source.customer_tin, (value) => TINNumber.create(value)), + "customer_tin", + errors + ); + + const customerStreet = extractOrPushError( + maybeFromNullableVO(source.customer_street, (value) => Street.create(value)), + "customer_street", + errors + ); + + const customerStreet2 = extractOrPushError( + maybeFromNullableVO(source.customer_street2, (value) => Street.create(value)), + "customer_street2", + errors + ); + + const customerCity = extractOrPushError( + maybeFromNullableVO(source.customer_city, (value) => City.create(value)), + "customer_city", + errors + ); + + const customerProvince = extractOrPushError( + maybeFromNullableVO(source.customer_province, (value) => Province.create(value)), + "customer_province", + errors + ); + + const customerPostalCode = extractOrPushError( + maybeFromNullableVO(source.customer_postal_code, (value) => PostalCode.create(value)), + "customer_postal_code", + errors + ); + + const customerCountry = extractOrPushError( + maybeFromNullableVO(source.customer_country, (value) => Country.create(value)), + "customer_country", + errors + ); // Mapear los items de la factura const items = this._itemsMapper.mapArrayToDomain(source.items, { @@ -130,6 +186,8 @@ export class CustomerInvoiceMapper ...params, }); + // Mapear los impuestos + if (errors.length > 0) { return Result.fail( new ValidationErrorCollection("Customer invoice item props mapping failed", errors) @@ -144,6 +202,8 @@ export class CustomerInvoiceMapper invoiceDate: invoiceDate!, operationDate: operationDate!, + customerId: customerId!, + notes: notes!, languageCode: languageCode!, @@ -167,11 +227,30 @@ export class CustomerInvoiceMapper source: CustomerInvoice, params?: MapperParamsType ): CustomerInvoiceCreationAttributes { - //const subtotal = source.calculateSubtotal(); - //const total = source.calculateTotal(); - const items = this._itemsMapper.mapCollectionToPersistence(source.items, params); + const customer = source.recipient.match((recipient) => ({ + customer_id: recipient.id.toPrimitive(), + customer_tin: recipient.tin.toPrimitive(), + customer_name: recipient.name.toPrimitive(), + customer_street: toNullable(recipient.address.street, (street) => street.toPrimitive()), + customer_street2: toNullable(recipient.address.street2, (street2) => street2.toPrimitive()), + customer_city: toNullable(recipient.address.city, (city) => city.toPrimitive()), + customer_province: toNullable(recipient.address.province, (province) => province.toPrimitive()), + customer_postal_code: toNullable(recipient.address.postalCode, (postalCode) => postalCode.toPrimitive()), + customer_country: toNullable(recipient.address.country, (country) => country.toPrimitive()), + }) as any, () => ({ + customer_id: source.customerId.toPrimitive(), + customer_tin: null, + customer_name: null, + customer_street: null, + customer_street2: null, + customer_city: null, + customer_province: null, + customer_postal_code: null, + customer_country: null, + })) as any + return { id: source.id.toPrimitive(), company_id: source.companyId.toPrimitive(), @@ -194,16 +273,23 @@ export class CustomerInvoiceMapper subtotal_amount_value: 0, //subtotal.amount, subtotal_amount_scale: 2, //subtotal.scale, - /*discount_percentage_value: source.discountPercentage.value, + discount_percentage_value: source.discountPercentage.value, discount_percentage_scale: source.discountPercentage.scale, discount_amount_value: source.discountAmount.value, - discount_amount_scale: source.discountAmount.scale,*/ + discount_amount_scale: source.discountAmount.scale, + + taxable_amount_value: source.taxableAmount.value, + taxable_amount_scale: source.taxableAmount.value, + tax_amount_value: source.taxAmount.value, + tax_amount_scale: source.taxAmount.value, total_amount_value: 0, //total.amount, total_amount_scale: 2, //total.scale, items, + ...customer, + }; } } diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.model.ts index 6ef239d6..dba2cd72 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.model.ts @@ -59,6 +59,17 @@ export class CustomerInvoiceModel extends Model< declare total_amount_value: number; declare total_amount_scale: number; + // Customer + declare customer_id: string; + declare customer_tin: string; + declare customer_name: string; + declare customer_street: string; + declare customer_street2: string; + declare customer_city: string; + declare customer_province: string; + declare customer_postal_code: string; + declare customer_country: string; + // Relaciones declare items: NonAttribute; //declare customer: NonAttribute; @@ -216,6 +227,51 @@ export default (database: Sequelize) => { allowNull: false, defaultValue: 2, }, + + customer_id: { + type: DataTypes.UUID, + allowNull: false, + }, + customer_tin: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + }, + customer_name: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + }, + customer_street: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + }, + customer_street2: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + }, + customer_city: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + }, + customer_province: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + }, + customer_postal_code: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + }, + customer_country: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + }, }, { sequelize: database, diff --git a/modules/customer-invoices/src/common/dto/request/create-customer-invoice.request.dto.ts b/modules/customer-invoices/src/common/dto/request/create-customer-invoice.request.dto.ts index c07c7316..1f183ac1 100644 --- a/modules/customer-invoices/src/common/dto/request/create-customer-invoice.request.dto.ts +++ b/modules/customer-invoices/src/common/dto/request/create-customer-invoice.request.dto.ts @@ -16,12 +16,13 @@ export const CreateCustomerInvoiceRequestSchema = z.object({ company_id: z.uuid(), invoice_number: z.string(), - status: z.string().default("draft"), series: z.string().default(""), invoice_date: z.string(), operation_date: z.string().default(""), + customer_id: z.uuid(), + notes: z.string().default(""), language_code: z.string().toLowerCase().default("es"),