diff --git a/modules/customer-invoices/src/api/application/create-customer-invoice/assembler/create-customer-invoice-items.assembler.ts b/modules/customer-invoices/src/api/application/create-customer-invoice/assembler/create-customer-invoice-items.assembler.ts new file mode 100644 index 00000000..980ac9d5 --- /dev/null +++ b/modules/customer-invoices/src/api/application/create-customer-invoice/assembler/create-customer-invoice-items.assembler.ts @@ -0,0 +1,45 @@ +import { toEmptyString } from "@repo/rdx-ddd"; +import { CreateCustomerInvoiceResponseDTO } from "../../../../common/dto"; +import { CustomerInvoice } from "../../../domain"; + +type CreateCustomerInvoiceItemsByInvoiceIdResponseDTO = CreateCustomerInvoiceResponseDTO["items"]; + +export class CreateCustomerInvoiceItemsAssembler { + toDTO(invoice: CustomerInvoice): CreateCustomerInvoiceItemsByInvoiceIdResponseDTO { + const { items } = invoice; + return items.map((item, index) => ({ + id: item.id.toString(), + position: String(index), + description: toEmptyString(item.description, (value) => value.toPrimitive()), + + quantity: item.quantity.match( + (quantity) => { + const { value, scale } = quantity.toPrimitive(); + return { value: value.toString(), scale: scale.toString() }; + }, + () => ({ value: "", scale: "" }) + ), + + unit_amount: item.unitAmount.match( + (unitAmount) => { + const { value, scale } = unitAmount.toPrimitive(); + return { value: value.toString(), scale: scale.toString() }; + }, + () => ({ value: "", scale: "" }) + ), + + discount_percentage: item.discountPercentage.match( + (discountPercentage) => { + const { value, scale } = discountPercentage.toPrimitive(); + return { value: value.toString(), scale: scale.toString() }; + }, + () => ({ value: "", scale: "" }) + ), + + total_amount: { + value: item.totalAmount.toPrimitive().value.toString(), + scale: item.totalAmount.toPrimitive().scale.toString(), + }, + })); + } +} diff --git a/modules/customer-invoices/src/api/application/create-customer-invoice/assembler/create-customer-invoices.assembler.ts b/modules/customer-invoices/src/api/application/create-customer-invoice/assembler/create-customer-invoices.assembler.ts index 6e34ab7d..380fba18 100644 --- a/modules/customer-invoices/src/api/application/create-customer-invoice/assembler/create-customer-invoices.assembler.ts +++ b/modules/customer-invoices/src/api/application/create-customer-invoice/assembler/create-customer-invoices.assembler.ts @@ -1,26 +1,68 @@ -import { CustomerInvoice } from "@erp/customer-invoices/api/domain"; -import { CustomerInvoicesCreationResponseDTO } from "@erp/customer-invoices/common/dto"; +import { UpdateCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto"; +import { toEmptyString } from "@repo/rdx-ddd"; +import { CustomerInvoice } from "../../../domain"; +import { CreateCustomerInvoiceItemsAssembler } from "./create-customer-invoice-items.assembler"; + +export class CreateCustomerInvoiceAssembler { + private _itemsAssembler!: CreateCustomerInvoiceItemsAssembler; + + constructor() { + this._itemsAssembler = new CreateCustomerInvoiceItemsAssembler(); + } + + public toDTO(invoice: CustomerInvoice): UpdateCustomerInvoiceByIdResponseDTO { + const items = this._itemsAssembler.toDTO(invoice); -export class CreateCustomerInvoicesAssembler { - public toDTO(invoice: CustomerInvoice): CustomerInvoicesCreationResponseDTO { return { id: invoice.id.toPrimitive(), + company_id: invoice.companyId.toPrimitive(), - invoice_status: invoice.status.toString(), invoice_number: invoice.invoiceNumber.toString(), - invoice_series: invoice.invoiceSeries.toString(), - issue_date: invoice.issueDate.toISOString(), - operation_date: invoice.operationDate.toISOString(), - language_code: "ES", - currency: "EUR", + status: invoice.status.toPrimitive(), + series: invoice.series.toString(), - //subtotal_price: invoice.calculateSubtotal().toPrimitive(), - //total_price: invoice.calculateTotal().toPrimitive(), + invoice_date: invoice.invoiceDate.toDateString(), + operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()), - //recipient: CustomerInvoiceParticipantAssembler(customerInvoice.recipient), + notes: toEmptyString(invoice.notes, (value) => value.toPrimitive()), + + language_code: invoice.languageCode.toPrimitive(), + currency_code: invoice.currencyCode.toPrimitive(), + + subtotal_amount: { + value: invoice.subtotalAmount.value.toString(), + scale: invoice.subtotalAmount.scale.toString(), + }, + + discount_percentage: { + value: invoice.discountPercentage.value.toString(), + scale: invoice.discountPercentage.scale.toString(), + }, + + discount_amount: { + value: invoice.discountAmount.value.toString(), + scale: invoice.discountAmount.scale.toString(), + }, + + taxable_amount: { + value: invoice.taxableAmount.value.toString(), + scale: invoice.taxableAmount.scale.toString(), + }, + + tax_amount: { + value: invoice.taxAmount.value.toString(), + scale: invoice.taxAmount.scale.toString(), + }, + + total_amount: { + value: invoice.totalAmount.value.toString(), + scale: invoice.totalAmount.scale.toString(), + }, + + items, metadata: { - entity: "customer-invoice", + entity: "customer-invoices", }, }; } 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 c45b5d1d..fa80212b 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,62 +1,78 @@ import { DuplicateEntityError, ITransactionManager } from "@erp/core/api"; -import { CreateCustomerInvoiceRequestDTO } from "@erp/customer-invoices/common/dto"; +import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { Transaction } from "sequelize"; -import { ICustomerInvoiceService } from "../../domain"; -import { mapDTOToCustomerInvoiceProps } from "../helpers"; -import { CreateCustomerInvoicesAssembler } from "./assembler"; +import { CreateCustomerInvoiceRequestDTO } from "../../../common/dto"; +import { CustomerInvoiceService } from "../../domain"; +import { CreateCustomerInvoiceAssembler } from "./assembler"; +import { mapDTOToCreateCustomerInvoiceProps } from "./map-dto-to-create-customer-invoice-props"; type CreateCustomerInvoiceUseCaseInput = { - tenantId: string; + companyId: UniqueID; dto: CreateCustomerInvoiceRequestDTO; }; export class CreateCustomerInvoiceUseCase { constructor( - private readonly service: ICustomerInvoiceService, + private readonly service: CustomerInvoiceService, private readonly transactionManager: ITransactionManager, - private readonly assembler: CreateCustomerInvoicesAssembler + private readonly assembler: CreateCustomerInvoiceAssembler ) {} public execute(params: CreateCustomerInvoiceUseCaseInput) { - const { dto, tenantId } = params; - const invoicePropsOrError = mapDTOToCustomerInvoiceProps(dto); + const { dto, companyId } = params; - if (invoicePropsOrError.isFailure) { - return Result.fail(invoicePropsOrError.error); + // 1) Mapear DTO → props de dominio + const dtoResult = mapDTOToCreateCustomerInvoiceProps(dto); + if (dtoResult.isFailure) { + return Result.fail(dtoResult.error); } - const { props, id } = invoicePropsOrError.data; - const invoiceOrError = this.service.build(props, id); + const { props, id } = dtoResult.data; - if (invoiceOrError.isFailure) { - return Result.fail(invoiceOrError.error); + // 2) Construir entidad de dominio + const buildResult = this.service.buildInvoiceInCompany(companyId, props, id); + if (buildResult.isFailure) { + return Result.fail(buildResult.error); } - const newInvoice = invoiceOrError.data; + const newInvoice = buildResult.data; + // 3) Ejecutar bajo transacción: verificar duplicado → persistir → ensamblar vista return this.transactionManager.complete(async (transaction: Transaction) => { - try { - const duplicateCheck = await this.service.existsById(id, transaction); - - if (duplicateCheck.isFailure) { - return Result.fail(duplicateCheck.error); - } - - if (duplicateCheck.data) { - return Result.fail(new DuplicateEntityError("CustomerInvoice", id.toString())); - } - - const result = await this.service.save(newInvoice, transaction); - if (result.isFailure) { - return Result.fail(result.error); - } - - const viewDTO = this.assembler.toDTO(newInvoice); - return Result.ok(viewDTO); - } catch (error: unknown) { - return Result.fail(error as Error); + const existsGuard = await this.ensureNotExists(companyId, id, transaction); + if (existsGuard.isFailure) { + return Result.fail(existsGuard.error); } + + const saveResult = await this.service.saveInvoice(newInvoice, transaction); + if (saveResult.isFailure) { + return Result.fail(saveResult.error); + } + + const viewDTO = this.assembler.toDTO(saveResult.data); + + return Result.ok(viewDTO); }); } + + /** + Verifica que no exista uana factura con el mismo id en la companyId. + */ + private async ensureNotExists( + companyId: UniqueID, + id: UniqueID, + transaction: Transaction + ): Promise> { + const existsResult = await this.service.existsByIdInCompany(companyId, id, transaction); + if (existsResult.isFailure) { + return Result.fail(existsResult.error); + } + + if (existsResult.data) { + return Result.fail(new DuplicateEntityError("Customer invoice", "id", String(id))); + } + + return Result.ok(undefined); + } } 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 new file mode 100644 index 00000000..d0ca73c7 --- /dev/null +++ b/modules/customer-invoices/src/api/application/create-customer-invoice/map-dto-to-create-customer-invoice-props.ts @@ -0,0 +1,190 @@ +import { + DomainError, + ValidationErrorCollection, + ValidationErrorDetail, + extractOrPushError, +} from "@erp/core/api"; +import { + CurrencyCode, + LanguageCode, + Percentage, + TextValue, + UniqueID, + UtcDate, + maybeFromNullableVO, +} from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; +import { CreateCustomerInvoiceRequestDTO } from "../../../common/dto"; +import { + CustomerInvoiceItemDescription, + CustomerInvoiceItemDiscount, + CustomerInvoiceItemProps, + CustomerInvoiceItemQuantity, + CustomerInvoiceItemUnitAmount, + CustomerInvoiceNumber, + CustomerInvoiceProps, + CustomerInvoiceSerie, + CustomerInvoiceStatus, +} from "../../domain"; + +/** + * Convierte el DTO a las props validadas (CustomerProps). + * No construye directamente el agregado. + * + * @param dto - DTO con los datos de la factura de cliente + * @returns + + * + */ + +export function mapDTOToCreateCustomerInvoiceProps(dto: CreateCustomerInvoiceRequestDTO) { + try { + const errors: ValidationErrorDetail[] = []; + + const customerId = extractOrPushError(UniqueID.create(dto.id), "id", errors); + const companyId = extractOrPushError(UniqueID.create(dto.company_id), "company_id", errors); + + const invoiceNumber = extractOrPushError( + CustomerInvoiceNumber.create(dto.invoice_number), + "invoice_number", + errors + ); + const status = extractOrPushError(CustomerInvoiceStatus.create(dto.status), "status", errors); + + const series = extractOrPushError( + maybeFromNullableVO(dto.series, (value) => CustomerInvoiceSerie.create(value)), + "series", + errors + ); + + const invoiceDate = extractOrPushError( + UtcDate.createFromISO(dto.invoice_date), + "invoice_date", + 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 = mapDTOToCreateCustomerInvoiceItemsProps(dto, errors); + + if (errors.length > 0) { + return Result.fail( + new ValidationErrorCollection("Customer invoice props mapping failed", errors) + ); + } + + const invoiceProps: CustomerInvoiceProps = { + companyId: companyId!, + status: status!, + + invoiceNumber: invoiceNumber!, + invoiceDate: invoiceDate!, + + operationDate: operationDate!, + series: series!, + + notes: notes!, + + languageCode: languageCode!, + currencyCode: currencyCode!, + + discountPercentage: discountPercentage!, + }; + + return Result.ok({ id: customerId!, props: invoiceProps }); + } catch (err: unknown) { + return Result.fail(new DomainError("Customer invoice props mapping failed", { cause: err })); + } +} + +function mapDTOToCreateCustomerInvoiceItemsProps( + dto: CreateCustomerInvoiceRequestDTO, + errors: ValidationErrorDetail[] +): CustomerInvoiceItemProps[] | undefined { + const items: CustomerInvoiceItemProps[] = []; + + const languageCode = extractOrPushError( + LanguageCode.create(dto.language_code), + "language_code", + errors + ); + + const currencyCode = extractOrPushError( + CurrencyCode.create(dto.currency_code), + "currency_code", + errors + ); + + dto.items.forEach((item, index) => { + const description = extractOrPushError( + maybeFromNullableVO(item.description, (value) => + CustomerInvoiceItemDescription.create(value) + ), + "description", + errors + ); + + const quantity = extractOrPushError( + maybeFromNullableVO(item.quantity, (value) => CustomerInvoiceItemQuantity.create(value)), + "quantity", + errors + ); + + const unitAmount = extractOrPushError( + maybeFromNullableVO(item.unit_amount, (value) => CustomerInvoiceItemUnitAmount.create(value)), + "unit_amount", + errors + ); + + const discountPercentage = extractOrPushError( + maybeFromNullableVO(item.discount_percentage, (value) => + CustomerInvoiceItemDiscount.create(value) + ), + "discount_percentage", + errors + ); + + items.push({ + currencyCode: currencyCode!, + languageCode: languageCode!, + + description: description!, + quantity: quantity!, + unitAmount: unitAmount!, + discountPercentage: discountPercentage!, + }); + }); + + return items; +} diff --git a/modules/customer-invoices/src/api/application/get-customer-invoice/assembler/get-invoice-items.assembler.ts b/modules/customer-invoices/src/api/application/get-customer-invoice/assembler/get-invoice-items.assembler.ts index 163ee4b0..4af19457 100644 --- a/modules/customer-invoices/src/api/application/get-customer-invoice/assembler/get-invoice-items.assembler.ts +++ b/modules/customer-invoices/src/api/application/get-customer-invoice/assembler/get-invoice-items.assembler.ts @@ -1,8 +1,8 @@ -import { UpdateCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto"; import { toEmptyString } from "@repo/rdx-ddd"; +import { GetCustomerInvoiceByIdResponseDTO } from "../../../../common/dto"; import { CustomerInvoice } from "../../../domain"; -type GetCustomerInvoiceItemsByInvoiceIdResponseDTO = UpdateCustomerInvoiceByIdResponseDTO["items"]; +type GetCustomerInvoiceItemsByInvoiceIdResponseDTO = GetCustomerInvoiceByIdResponseDTO["items"]; export class GetCustomerInvoiceItemsAssembler { toDTO(invoice: CustomerInvoice): GetCustomerInvoiceItemsByInvoiceIdResponseDTO { diff --git a/modules/customer-invoices/src/api/application/get-customer-invoice/assembler/get-invoice.assembler.ts b/modules/customer-invoices/src/api/application/get-customer-invoice/assembler/get-invoice.assembler.ts index 06e9f50d..ea20735d 100644 --- a/modules/customer-invoices/src/api/application/get-customer-invoice/assembler/get-invoice.assembler.ts +++ b/modules/customer-invoices/src/api/application/get-customer-invoice/assembler/get-invoice.assembler.ts @@ -21,7 +21,7 @@ export class GetCustomerInvoiceAssembler { status: invoice.status.toPrimitive(), series: invoice.series.toString(), - issue_date: invoice.issueDate.toDateString(), + invoice_date: invoice.invoiceDate.toDateString(), operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()), notes: toEmptyString(invoice.notes, (value) => value.toPrimitive()), @@ -65,46 +65,6 @@ export class GetCustomerInvoiceAssembler { entity: "customer-invoices", }, - //subtotal: customerInvoice.calculateSubtotal().toPrimitive(), - - //total: customerInvoice.calculateTotal().toPrimitive(), - - /*items: - customerInvoice.items.size() > 0 - ? customerInvoice.items.map((item: CustomerInvoiceItem) => ({ - description: item.description.toString(), - quantity: item.quantity.toPrimitive(), - unit_measure: "", - unit_price: item.unitPrice.toPrimitive(), - subtotal: item.calculateSubtotal().toPrimitive(), - //tax_amount: item.calculateTaxAmount().toPrimitive(), - total: item.calculateTotal().toPrimitive(), - })) - : [],*/ - - //sender: {}, //await CustomerInvoiceParticipantAssembler(customerInvoice.senderId, context), - - /*recipient: await CustomerInvoiceParticipantAssembler(customerInvoice.recipient, context), - items: customerInvoiceItemAssembler(customerInvoice.items, context), - - payment_term: { - payment_type: "", - due_date: "", - }, - - due_amount: { - currency: customerInvoice.currency.toString(), - precision: 2, - amount: 0, - }, - - custom_fields: [], - - metadata: { - create_time: "", - last_updated_time: "", - delete_time: "", - },*/ }; } } diff --git a/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-props.ts b/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-props.ts index b78d3bcf..9a180234 100644 --- a/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-props.ts +++ b/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-props.ts @@ -36,7 +36,11 @@ export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDT "invoice_series", errors ); - const issueDate = extractOrPushError(UtcDate.createFromISO(dto.issue_date), "issue_date", errors); + const invoiceDate = extractOrPushError( + UtcDate.createFromISO(dto.invoice_date), + "invoice_date", + errors + ); const operationDate = extractOrPushError( UtcDate.createFromISO(dto.operation_date), "operation_date", @@ -59,7 +63,7 @@ export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDT const invoiceProps: CustomerInvoiceProps = { invoiceNumber: invoiceNumber!, invoiceSeries: invoiceSeries!, - issueDate: issueDate!, + invoiceDate: invoiceDate!, operationDate: operationDate!, status: CustomerInvoiceStatus.createDraft(), currency, diff --git a/modules/customer-invoices/src/api/application/list-customer-invoices/assembler/list-invoices.assembler.ts b/modules/customer-invoices/src/api/application/list-customer-invoices/assembler/list-invoices.assembler.ts index 60273b19..239fb275 100644 --- a/modules/customer-invoices/src/api/application/list-customer-invoices/assembler/list-invoices.assembler.ts +++ b/modules/customer-invoices/src/api/application/list-customer-invoices/assembler/list-invoices.assembler.ts @@ -15,7 +15,7 @@ export class ListCustomerInvoicesAssembler { invoice_status: invoice.status.toString(), invoice_number: invoice.invoiceNumber.toString(), invoice_series: invoice.invoiceSeries.toString(), - issue_date: invoice.issueDate.toISOString(), + invoice_date: invoice.invoiceDate.toISOString(), operation_date: invoice.operationDate.toISOString(), language_code: "ES", currency: "EUR", diff --git a/modules/customer-invoices/src/api/application/update-customer-invoice/assembler/update-invoice.assembler.ts b/modules/customer-invoices/src/api/application/update-customer-invoice/assembler/update-invoice.assembler.ts index 79064111..7b1e4d5e 100644 --- a/modules/customer-invoices/src/api/application/update-customer-invoice/assembler/update-invoice.assembler.ts +++ b/modules/customer-invoices/src/api/application/update-customer-invoice/assembler/update-invoice.assembler.ts @@ -21,7 +21,7 @@ export class UpdateCustomerInvoiceAssembler { status: invoice.status.toPrimitive(), series: invoice.series.toString(), - issue_date: invoice.issueDate.toDateString(), + invoice_date: invoice.invoiceDate.toDateString(), operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()), notes: toEmptyString(invoice.notes, (value) => value.toPrimitive()), diff --git a/modules/customer-invoices/src/api/application/update-customer-invoice/update-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/update-customer-invoice/update-customer-invoice.use-case.ts index e7a36454..9571481c 100644 --- a/modules/customer-invoices/src/api/application/update-customer-invoice/update-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/update-customer-invoice/update-customer-invoice.use-case.ts @@ -52,9 +52,9 @@ export class CreateCustomerInvoiceUseCase { customerInvoice_series = CustomerInvoiceSeries.create(customerInvoiceDTO.customerInvoice_series).object; } - let issue_date = CustomerInvoiceDate.create(customerInvoiceDTO.issue_date).object; - if (issue_date.isEmpty()) { - issue_date = CustomerInvoiceDate.createCurrentDate().object; + let invoice_date = CustomerInvoiceDate.create(customerInvoiceDTO.invoice_date).object; + if (invoice_date.isEmpty()) { + invoice_date = CustomerInvoiceDate.createCurrentDate().object; } let operation_date = CustomerInvoiceDate.create(customerInvoiceDTO.operation_date).object; @@ -96,7 +96,7 @@ export class CreateCustomerInvoiceUseCase { return DraftCustomerInvoice.create( { customerInvoiceSeries: customerInvoice_series, - issueDate: issue_date, + invoiceDate: invoice_date, operationDate: operation_date, customerInvoiceCurrency, language: customerInvoiceLanguage, @@ -285,9 +285,9 @@ export class UpdateCustomerInvoiceUseCase2 customerInvoice_series = CustomerInvoiceSeries.create(customerInvoiceDTO.customerInvoice_series).object; } - let issue_date = CustomerInvoiceDate.create(customerInvoiceDTO.issue_date).object; - if (issue_date.isEmpty()) { - issue_date = CustomerInvoiceDate.createCurrentDate().object; + let invoice_date = CustomerInvoiceDate.create(customerInvoiceDTO.invoice_date).object; + if (invoice_date.isEmpty()) { + invoice_date = CustomerInvoiceDate.createCurrentDate().object; } let operation_date = CustomerInvoiceDate.create(customerInvoiceDTO.operation_date).object; @@ -329,7 +329,7 @@ export class UpdateCustomerInvoiceUseCase2 return DraftCustomerInvoice.create( { customerInvoiceSeries: customerInvoice_series, - issueDate: issue_date, + invoiceDate: invoice_date, operationDate: operation_date, customerInvoiceCurrency, language: customerInvoiceLanguage, 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 56763f6a..8f1dc518 100644 --- a/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts +++ b/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts @@ -23,7 +23,7 @@ export interface CustomerInvoiceProps { status: CustomerInvoiceStatus; series: Maybe; - issueDate: UtcDate; + invoiceDate: UtcDate; operationDate: Maybe; notes: Maybe; @@ -33,7 +33,6 @@ export interface CustomerInvoiceProps { //tax: Tax; // ? --> detalles? //purchareOrderNumber: string; - //notes: Note; //senderId: UniqueID; @@ -43,18 +42,18 @@ export interface CustomerInvoiceProps { languageCode: LanguageCode; currencyCode: CurrencyCode; - subtotalAmount: MoneyValue; + //subtotalAmount: MoneyValue; discountPercentage: Percentage; //discountAmount: MoneyValue; //taxableAmount: MoneyValue; - taxAmount: MoneyValue; + //taxAmount: MoneyValue; - totalAmount: MoneyValue; + //totalAmount: MoneyValue; //customer?: CustomerInvoiceCustomer; - items?: CustomerInvoiceItems; + items: CustomerInvoiceItems; } export type CustomerInvoicePatchProps = Partial>; @@ -107,8 +106,8 @@ export class CustomerInvoice extends AggregateRoot { return this.props.invoiceNumber; } - public get issueDate(): UtcDate { - return this.props.issueDate; + public get invoiceDate(): UtcDate { + return this.props.invoiceDate; } public get operationDate(): Maybe { @@ -128,7 +127,7 @@ export class CustomerInvoice extends AggregateRoot { } public get subtotalAmount(): MoneyValue { - return this.props.subtotalAmount; + throw new Error("discountAmount not implemented"); } public get discountPercentage(): Percentage { @@ -144,7 +143,7 @@ export class CustomerInvoice extends AggregateRoot { } public get taxAmount(): MoneyValue { - return this.props.taxAmount; + throw new Error("discountAmount not implemented"); } public get totalAmount(): MoneyValue { diff --git a/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts index 080fb5b4..18da09b7 100644 --- a/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts +++ b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts @@ -1,4 +1,11 @@ -import { CurrencyCode, DomainEntity, LanguageCode, UniqueID } from "@repo/rdx-ddd"; +import { + CurrencyCode, + DomainEntity, + LanguageCode, + MoneyValue, + Percentage, + UniqueID, +} from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; import { CustomerInvoiceItemDescription, @@ -12,8 +19,8 @@ import { export interface CustomerInvoiceItemProps { description: Maybe; quantity: Maybe; // Cantidad de unidades - unitPrice: Maybe; // Precio unitario en la moneda de la factura - discount: Maybe; // % descuento + unitAmount: Maybe; // Precio unitario en la moneda de la factura + discountPercentage: Maybe; // % descuento languageCode: LanguageCode; currencyCode: CurrencyCode; @@ -49,7 +56,7 @@ export class CustomerInvoiceItem extends DomainEntity } get unitAmount(): Maybe { - return this.props.unitPrice; + return this.props.unitAmount; } get subtotalAmount(): CustomerInvoiceItemSubtotalAmount { @@ -59,8 +66,12 @@ export class CustomerInvoiceItem extends DomainEntity return this._subtotalAmount; } - get discountPercentage(): Maybe { - return this.props.discount; + get discountPercentage(): Maybe { + return this.props.discountPercentage; + } + + get discountAmount(): Maybe { + throw new Error("Not implemented"); } get totalAmount(): CustomerInvoiceItemTotalAmount { diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-quantity.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-quantity.ts deleted file mode 100644 index a307e276..00000000 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-quantity.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Quantity } from "@repo/rdx-ddd"; - -export class CustomerInvoiceItemQuantity extends Quantity {} diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-amount.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-amount.ts index e0217cda..1fca49d2 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-amount.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-amount.ts @@ -3,10 +3,10 @@ import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd"; export class CustomerInvoiceItemSubtotalAmount extends MoneyValue { public static DEFAULT_SCALE = 4; - static create({ value: amount, currency_code, scale }: MoneyValueProps) { + static create({ value, currency_code }: MoneyValueProps) { const props = { - amount: Number(amount), - scale: scale ?? MoneyValue.DEFAULT_SCALE, + value: Number(value), + scale: CustomerInvoiceItemSubtotalAmount.DEFAULT_SCALE, currency_code, }; return MoneyValue.create(props); diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-amount.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-amount.ts index 3d351285..17e8b7b7 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-amount.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-amount.ts @@ -1,6 +1,6 @@ import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd"; -export class CustomerInvoiceItemTotalAmount extends MoneyValue { +export class ItemTotalAmount extends MoneyValue { public static DEFAULT_SCALE = 4; static create({ value, currency_code, scale }: MoneyValueProps) { diff --git a/modules/customer-invoices/src/api/domain/value-objects/index.ts b/modules/customer-invoices/src/api/domain/value-objects/index.ts index 45c71057..9839be59 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/index.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/index.ts @@ -1,10 +1,10 @@ export * from "./customer-invoice-address-type"; export * from "./customer-invoice-item-description"; export * from "./customer-invoice-item-discount"; -export * from "./customer-invoice-item-quantity"; export * from "./customer-invoice-item-subtotal-amount"; export * from "./customer-invoice-item-total-amount"; export * from "./customer-invoice-item-unit-amount"; export * from "./customer-invoice-number"; export * from "./customer-invoice-serie"; export * from "./customer-invoice-status"; +export * from "./item-quantity"; diff --git a/modules/customer-invoices/src/api/domain/value-objects/item-quantity.ts b/modules/customer-invoices/src/api/domain/value-objects/item-quantity.ts new file mode 100644 index 00000000..ffd62835 --- /dev/null +++ b/modules/customer-invoices/src/api/domain/value-objects/item-quantity.ts @@ -0,0 +1,13 @@ +import { Quantity, QuantityProps } from "@repo/rdx-ddd"; + +export class ItemQuantity extends Quantity { + public static DEFAULT_SCALE = 2; + + static create({ value }: QuantityProps) { + const props = { + value: Number(value), + scale: ItemQuantity.DEFAULT_SCALE, + }; + return Quantity.create(props); + } +} diff --git a/modules/customer-invoices/src/api/infrastructure/dependencies.ts b/modules/customer-invoices/src/api/infrastructure/dependencies.ts index 84323d65..96c2bf09 100644 --- a/modules/customer-invoices/src/api/infrastructure/dependencies.ts +++ b/modules/customer-invoices/src/api/infrastructure/dependencies.ts @@ -1,18 +1,16 @@ import type { ModuleParams } from "@erp/core/api"; import { SequelizeTransactionManager } from "@erp/core/api"; import { + CreateCustomerInvoiceAssembler, CreateCustomerInvoiceUseCase, - CreateCustomerInvoicesAssembler, DeleteCustomerInvoiceUseCase, GetCustomerInvoiceAssembler, - GetCustomerInvoiceItemsAssembler, GetCustomerInvoiceUseCase, ListCustomerInvoicesAssembler, ListCustomerInvoicesUseCase, UpdateCustomerInvoiceAssembler, - UpdateCustomerInvoiceUseCase, } from "../application"; -import { CustomerInvoiceService, ICustomerInvoiceService } from "../domain"; +import { CustomerInvoiceService } from "../domain"; import { CustomerInvoiceMapper } from "./mappers"; import { CustomerInvoiceRepository } from "./sequelize"; @@ -20,11 +18,11 @@ type InvoiceDeps = { transactionManager: SequelizeTransactionManager; repo: CustomerInvoiceRepository; mapper: CustomerInvoiceMapper; - service: ICustomerInvoiceService; + service: CustomerInvoiceService; assemblers: { list: ListCustomerInvoicesAssembler; - get: GetCustomerInvoiceItemsAssembler; - create: CreateCustomerInvoicesAssembler; + get: GetCustomerInvoiceAssembler; + create: CreateCustomerInvoiceAssembler; update: UpdateCustomerInvoiceAssembler; }; build: { @@ -41,7 +39,7 @@ type InvoiceDeps = { let _repo: CustomerInvoiceRepository | null = null; let _mapper: CustomerInvoiceMapper | null = null; -let _service: ICustomerInvoiceService | null = null; +let _service: CustomerInvoiceService | null = null; let _assemblers: InvoiceDeps["assemblers"] | null = null; export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps { @@ -56,7 +54,7 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps { _assemblers = { list: new ListCustomerInvoicesAssembler(), // transforma domain → ListDTO get: new GetCustomerInvoiceAssembler(), // transforma domain → DetailDTO - create: new CreateCustomerInvoicesAssembler(), // transforma domain → CreatedDTO + create: new CreateCustomerInvoiceAssembler(), // transforma domain → CreatedDTO update: new UpdateCustomerInvoiceAssembler(), // transforma domain -> UpdateDTO }; } diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/create-customer-invoice.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/create-customer-invoice.controller.ts index fe2d1311..588e9450 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/create-customer-invoice.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/create-customer-invoice.controller.ts @@ -4,25 +4,17 @@ import { CreateCustomerInvoiceRequestDTO } from "../../../../common/dto"; import { CreateCustomerInvoiceUseCase } from "../../../application"; export class CreateCustomerInvoiceController extends ExpressController { - public constructor( - private readonly useCase: CreateCustomerInvoiceUseCase - /* private readonly presenter: any */ - ) { + public constructor(private readonly useCase: CreateCustomerInvoiceUseCase) { super(); // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } protected async executeImpl() { - const tenantId = this.getTenantId()!; // garantizado por tenantGuard + const companyId = this.getTenantId()!; // garantizado por tenantGuard const dto = this.req.body as CreateCustomerInvoiceRequestDTO; - /* - // Inyectar empresa del usuario autenticado (ownership) - dto.customerCompanyId = user.companyId; - */ - - const result = await this.useCase.execute({ tenantId, dto }); + const result = await this.useCase.execute({ dto, companyId }); return result.match( (data) => this.created(data), diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice-item.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice-item.mapper.ts index 083cce56..f9348e01 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice-item.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice-item.mapper.ts @@ -1,5 +1,11 @@ -import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core/api"; -import { UniqueID } from "@repo/rdx-ddd"; +import { + ISequelizeMapper, + MapperParamsType, + SequelizeMapper, + ValidationErrorDetail, + extractOrPushError, +} from "@erp/core/api"; +import { CurrencyCode, LanguageCode, Quantity, UniqueID, maybeFromNullableVO, toNullable } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { InferCreationAttributes } from "sequelize"; import { @@ -35,70 +41,75 @@ export class CustomerInvoiceItemMapper source: CustomerInvoiceItemModel, params?: MapperParamsType ): Result { - const { sourceParent } = params as { sourceParent: CustomerInvoiceModel }; + const { sourceParent, errors } = params as { + sourceParent: CustomerInvoiceModel; + errors: ValidationErrorDetail[]; + }; - // Validación y creación de ID único - const idOrError = UniqueID.create(source.item_id); - if (idOrError.isFailure) { - return Result.fail(idOrError.error); - } + const itemId = extractOrPushError(UniqueID.create(source.item_id), "item_id", errors); - // Validación y creación de descripción - const descriptionOrError = CustomerInvoiceItemDescription.create(source.description || ""); - if (descriptionOrError.isFailure) { - return Result.fail(descriptionOrError.error); - } + const languageCode = extractOrPushError( + LanguageCode.create(sourceParent.language_code), + "language_code", + errors + ); - // Validación y creación de cantidad - const quantityOrError = CustomerInvoiceItemQuantity.create({ - amount: source.quantity_amount, - scale: source.quantity_scale, - }); - if (quantityOrError.isFailure) { - return Result.fail(quantityOrError.error); - } + const currencyCode = extractOrPushError( + CurrencyCode.create(sourceParent.currency_code), + "currency_code", + errors + ); - // Validación y creación de precio unitario - const unitPriceOrError = CustomerInvoiceItemUnitAmount.create({ - value: source.unit_price_amount, - scale: source.unit_price_scale, - currency_code: sourceParent.invoice_currency, - }); - if (unitPriceOrError.isFailure) { - return Result.fail(unitPriceOrError.error); - } + const description = extractOrPushError( + maybeFromNullableVO(source.description, (value) => + CustomerInvoiceItemDescription.create(value) + ), + "description", + errors + ); - // Validación y creación de descuento - const discountOrError = CustomerInvoiceItemDiscount.create({ - value: source.discount_amount || 0, - scale: source.discount_scale || 0, - }); - if (discountOrError.isFailure) { - return Result.fail(discountOrError.error); - } + const quantity = extractOrPushError( + maybeFromNullableVO(source.quantity_value, (value) => + Quantity.create({ value, }) - // Combinación de resultados - const result = Result.combine([ - idOrError, - descriptionOrError, - quantityOrError, - unitPriceOrError, - discountOrError, - ]); + CustomerInvoiceItemQuantity.create({ + value: source.quantity_amouwnt, + scale: source.quantity_scale, + }), + "discount_percentage", + errors + ); + + const unitAmount = extractOrPushError( + CustomerInvoiceItemUnitAmount.create({ + value: source.unit_price_amount, + scale: source.unit_price_scale, + }), + "discount_percentage", + errors + ); + + const discountPercentage = extractOrPushError( + CustomerInvoiceItemDiscount.create({ + value: source.discount_amount, + scale: source.discount_scale, + }), + "discount_percentage", + errors + ); - if (result.isFailure) { - return Result.fail(result.error); - } // Creación del objeto de dominio return CustomerInvoiceItem.create( { - description: descriptionOrError.data, - quantity: quantityOrError.data, - unitPrice: unitPriceOrError.data, - discount: discountOrError.data, - }, - idOrError.data + languageCode: languageCode!, + currencyCode: currencyCode!, + description: description!, + quantity: quantity!, + unitAmount: unitAmount!, + discountPercentage: discountPercentage!, + },º + id ); } @@ -106,35 +117,44 @@ export class CustomerInvoiceItemMapper source: CustomerInvoiceItem, params?: MapperParamsType ): InferCreationAttributes { + 1 const { index, sourceParent } = params as { index: number; sourceParent: CustomerInvoice; }; - const lineData = { - parent_id: undefined, + + + return { + item_id: source.id.toPrimitive(), invoice_id: sourceParent.id.toPrimitive(), - item_type: "simple", position: index, - item_id: source.id.toPrimitive(), - description: source.description.toPrimitive(), + description: toNullable(source.description, (description) => description.toPrimitive()), - quantity_amount: source.quantity.toPrimitive().amount, - quantity_scale: source.quantity.toPrimitive().scale, + quantity_value: toNullable(source.quantity, (value) => value.toPrimitive().value), + quantity_scale: source.quantity.match( + (value) => value.toPrimitive().scale, + () => CustomerInvoiceItemQuantity.DEFAULT_SCALE), - unit_price_amount: source.unitAmount.toPrimitive().amount, - unit_price_scale: source.unitAmount.toPrimitive().scale, + unit_amount_value: toNullable(source.unitAmount, (value) => value.toPrimitive().value), + unit_amount_scale: source.unitAmount.match( + (value) => value.toPrimitive().scale, + () => CustomerInvoiceItemUnitAmount.DEFAULT_SCALE), - subtotal_amount: source.subtotalAmount.toPrimitive().amount, - subtotal_scale: source.subtotalAmount.toPrimitive().scale, + subtotal_amount_value: source.subtotalAmount.toPrimitive().value, + subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale, - discount_amount: source.discount.toPrimitive().amount, - discount_scale: source.discount.toPrimitive().scale, + discount_percentage_value: toNullable(source.discountPercentage, (value) => value.toPrimitive().value), + discount_percentage_scale: source.discountPercentage.match( + (value) => value.toPrimitive().scale, + () => CustomerInvoiceItemUnitAmount.DEFAULT_SCALE), - total_amount: source.totalAmount.toPrimitive().amount, - total_scale: source.totalAmount.toPrimitive().scale, + discount_amount_value: source.subtotalAmount.toPrimitive().value, + discount_amount_scale: source.subtotalAmount.toPrimitive().scale, + + total_amount_value: source.totalAmount.toPrimitive().value, + total_amount_scale: source.totalAmount.toPrimitive().scale, }; - return lineData; } } 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 5161b84a..3d44d69e 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 @@ -6,7 +6,15 @@ import { ValidationErrorDetail, extractOrPushError, } from "@erp/core/api"; -import { CurrencyCode, LanguageCode, UniqueID, UtcDate, maybeFromNullableVO } from "@repo/rdx-ddd"; +import { + CurrencyCode, + LanguageCode, + Percentage, + TextValue, + UniqueID, + UtcDate, + maybeFromNullableVO, +} from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { CustomerInvoice, @@ -29,86 +37,125 @@ export class CustomerInvoiceMapper extends SequelizeMapper implements ICustomerInvoiceMapper { - private customerInvoiceItemMapper: CustomerInvoiceItemMapper; + private _itemsMapper: CustomerInvoiceItemMapper; constructor() { super(); - this.customerInvoiceItemMapper = new CustomerInvoiceItemMapper(); // Instanciar el mapper de items + this._itemsMapper = new CustomerInvoiceItemMapper(); // Instanciar el mapper de items } public mapToDomain( source: CustomerInvoiceModel, params?: MapperParamsType ): Result { - const errors: ValidationErrorDetail[] = []; + try { + const errors: ValidationErrorDetail[] = []; - const invoiceId = extractOrPushError(UniqueID.create(source.id), "id", errors); - const companyId = extractOrPushError(UniqueID.create(source.company_id), "company_id", errors); - - const status = extractOrPushError( - CustomerInvoiceStatus.create(source.status), - "status", - errors - ); - const series = extractOrPushError(CustomerInvoiceSerie.create(source.series), "series", errors); - const invoiceNumber = extractOrPushError( - CustomerInvoiceNumber.create(source.invoice_number), - "invoice_number", - errors - ); - const issueDate = extractOrPushError( - UtcDate.createFromISO(source.issue_date), - "issue_date", - errors - ); - - const operationDate = extractOrPushError( - maybeFromNullableVO(source.operation_date, (value) => UtcDate.createFromISO(value)), - "operation_date", - errors - ); - - const languageCode = extractOrPushError( - LanguageCode.create(source.language_code), - "language_code", - errors - ); - - const currencyCode = extractOrPushError( - CurrencyCode.create(source.currency_code), - "currency_code", - errors - ); - - if (errors.length > 0) { - return Result.fail( - new ValidationErrorCollection("Customer invoice props mapping failed", errors) + const invoiceId = extractOrPushError(UniqueID.create(source.id), "id", errors); + const companyId = extractOrPushError( + UniqueID.create(source.company_id), + "company_id", + errors ); + + const status = extractOrPushError( + CustomerInvoiceStatus.create(source.status), + "status", + errors + ); + + const series = extractOrPushError( + maybeFromNullableVO(source.series, (value) => CustomerInvoiceSerie.create(value)), + "serie", + errors + ); + + const invoiceNumber = extractOrPushError( + CustomerInvoiceNumber.create(source.invoice_number), + "invoice_number", + errors + ); + + const invoiceDate = extractOrPushError( + UtcDate.createFromISO(source.invoice_date), + "invoice_date", + errors + ); + + const operationDate = extractOrPushError( + maybeFromNullableVO(source.operation_date, (value) => UtcDate.createFromISO(value)), + "operation_date", + errors + ); + + const notes = extractOrPushError( + maybeFromNullableVO(source.notes, (value) => TextValue.create(value)), + "notes", + errors + ); + + const languageCode = extractOrPushError( + LanguageCode.create(source.language_code), + "language_code", + errors + ); + + const currencyCode = extractOrPushError( + CurrencyCode.create(source.currency_code), + "currency_code", + errors + ); + + const discountPercentage = extractOrPushError( + Percentage.create({ + value: source.discount_percentage_value, + scale: source.discount_percentage_scale, + }), + "discount_percentage", + errors + ); + + if (errors.length > 0) { + return Result.fail( + new ValidationErrorCollection("Customer invoice props mapping failed", errors) + ); + } + + // Mapear los items de la factura + const items = this._itemsMapper.mapArrayToDomain(source.items, { + sourceParent: source, + errors, + ...params, + }); + + if (errors.length > 0) { + return Result.fail( + new ValidationErrorCollection("Customer invoice item props mapping failed", errors) + ); + } + + const invoiceProps: CustomerInvoiceProps = { + companyId: companyId!, + status: status!, + series: series!, + invoiceNumber: invoiceNumber!, + invoiceDate: invoiceDate!, + operationDate: operationDate!, + + notes: notes!, + + languageCode: languageCode!, + currencyCode: currencyCode!, + + discountPercentage: discountPercentage!, + + items: items!, + }; + + return CustomerInvoice.create(invoiceProps, invoiceId); + } catch (err: unknown) { + return Result.fail(err as Error); } - - // Mapear los items de la factura - /*const itemsOrErrors = this.customerInvoiceItemMapper.mapArrayToDomain(source.items, { - sourceParent: source, - ...params, - }); - - if (itemsOrErrors.isFailure) { - return Result.fail(itemsOrErrors.error); - }*/ - - const invoiceProps: CustomerInvoiceProps = { - status: status!, - series: series!, - invoiceNumber: invoiceNumber!, - issueDate: issueDate!, - operationDate: operationDate!, - - languageCode: languageCode!, - currencyCode: currencyCode!, - //items: itemsOrErrors.data, - }; - - return CustomerInvoice.create(invoiceProps, invoiceId); } public mapToPersistence( @@ -118,14 +165,14 @@ export class CustomerInvoiceMapper const subtotal = source.calculateSubtotal(); const total = source.calculateTotal(); - const items = this.customerInvoiceItemMapper.mapCollectionToPersistence(source.items, params); + const items = this._itemsMapper.mapCollectionToPersistence(source.items, params); return { id: source.id.toString(), invoice_status: source.status.toPrimitive(), invoice_series: source.invoiceSeries.toPrimitive(), invoice_number: source.invoiceNumber.toPrimitive(), - issue_date: source.issueDate.toPrimitive(), + invoice_date: source.invoiceDate.toPrimitive(), operation_date: source.operationDate.toPrimitive(), invoice_language: "es", invoice_currency: source.currency || "EUR", diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-criteria-whitelist.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-criteria-whitelist.ts index f50d8112..5f1373cd 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-criteria-whitelist.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-criteria-whitelist.ts @@ -7,7 +7,7 @@ const ALLOWED_FILTERS = { customerId: "customer_id", invoiceSeries: "invoice_series", invoiceNumber: "invoice_number", - issueDate: "issue_date", + invoiceDate: "invoice_date", status: "status", currencyCode: "currency_code", // Rango por total (en unidades menores) @@ -15,8 +15,8 @@ const ALLOWED_FILTERS = { } as const; const ALLOWED_SORT: Record = { - // Sort "issueDate" realmente ordena por (issue_date DESC, invoice_series ASC, invoice_number DESC, id DESC) - issueDate: ["issue_date"], + // Sort "invoiceDate" realmente ordena por (invoice_date DESC, invoice_series ASC, invoice_number DESC, id DESC) + invoiceDate: ["invoice_date"], invoiceNumber: ["invoice_number"], invoiceSeries: ["invoice_series"], status: ["status"], @@ -31,7 +31,7 @@ export const DEFAULT_LIST_ATTRIBUTES = [ "customer_id", "invoice_series", "invoice_number", - "issue_date", + "invoice_date", "status", "total_amount_value", "total_amount_scale", @@ -47,7 +47,7 @@ type Sanitized = { attributes: (string | any)[]; // keyset opcional keyset?: { - after?: { issueDate: string; invoiceSeries: string; invoiceNumber: number; id: string }; + after?: { invoiceDate: string; invoiceSeries: string; invoiceNumber: number; id: string }; }; }; @@ -114,9 +114,9 @@ export function sanitizeListCriteria(criteria: Criteria): Sanitized { .split(",") .filter(Boolean); if (sortArray.length === 0) { - // orden por defecto: issue_date desc, invoice_series asc, invoice_number desc, id desc + // orden por defecto: invoice_date desc, invoice_series asc, invoice_number desc, id desc order.push( - ["issue_date", "DESC"], + ["invoice_date", "DESC"], ["invoice_series", "ASC"], ["invoice_number", "DESC"], ["id", "DESC"] diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-taxes.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-taxes.model.ts new file mode 100644 index 00000000..1a3cf44d --- /dev/null +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-taxes.model.ts @@ -0,0 +1,107 @@ +import { + DataTypes, + InferAttributes, + InferCreationAttributes, + Model, + NonAttribute, + Sequelize, +} from "sequelize"; +import { CustomerInvoiceItem } from "../../domain"; + +export type CustomerInvoiceItemTaxesCreationAttributes = InferCreationAttributes< + CustomerInvoiceItemTaxesModel, + {} +>; + +export class CustomerInvoiceItemTaxesModel extends Model< + InferAttributes, + InferCreationAttributes +> { + declare id: string; + declare item_id: string; + + declare tax_code: string; //"iva_21" + + // Taxable amount (base imponible) // 100,00 € + declare taxable_amount_value: number; + declare taxable_amount_scale: number; + + // Total tax amount / taxes total // 21,00 € + declare tax_amount_value: number; + declare tax_amount_scale: number; + + // Relaciones + declare item: NonAttribute; + + static associate(database: Sequelize) { + const { CustomerInvoiceItemModel } = database.models; + + CustomerInvoiceItemTaxesModel.belongsTo(CustomerInvoiceItemModel, { + as: "item", + targetKey: "id", + foreignKey: "item_id", + onDelete: "CASCADE", + }); + } + + static hooks(database: Sequelize) {} +} + +export default (database: Sequelize) => { + CustomerInvoiceItemTaxesModel.init( + { + id: { + type: new DataTypes.UUID(), + primaryKey: true, + }, + + item_id: { + type: DataTypes.UUID, + allowNull: false, + }, + + tax_code: { + type: new DataTypes.STRING(), + allowNull: false, + }, + + taxable_amount_value: { + type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes + allowNull: true, + defaultValue: null, + }, + taxable_amount_scale: { + type: new DataTypes.SMALLINT(), + allowNull: false, + defaultValue: 2, + }, + + tax_amount_value: { + type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes + allowNull: true, + defaultValue: null, + }, + tax_amount_scale: { + type: new DataTypes.SMALLINT(), + allowNull: false, + defaultValue: 2, + }, + }, + { + sequelize: database, + tableName: "customer_invoices_item_taxes", + + underscored: true, + + indexes: [{ name: "tax_code_idx", fields: ["tax_code"], unique: false }], + + whereMergeStrategy: "and", // <- cómo tratar el merge de un scope + + defaultScope: {}, + + scopes: {}, + } + ); + + return CustomerInvoiceItemTaxesModel; +}; diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts index 8ac37181..f0919373 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts @@ -20,26 +20,39 @@ export class CustomerInvoiceItemModel extends Model< declare item_id: string; declare invoice_id: string; - declare parent_id: string; declare position: number; - declare item_type: string; declare description: string; - declare quantity_amount: number; + declare quantity_value: number; declare quantity_scale: number; - declare unit_price_amount: number; - declare unit_price_scale: number; + declare unit_amount_value: number; + declare unit_amount_scale: number; - declare subtotal_amount: number; - declare subtotal_scale: number; + // Subtotal + declare subtotal_amount_value: number; + declare subtotal_amount_scale: number; - declare discount_amount: number; - declare discount_scale: number; + // Discount percentage + declare discount_percentage_value: number; + declare discount_percentage_scale: number; - declare total_amount: number; - declare total_scale: number; + // Discount amount + declare discount_amount_value: number; + declare discount_amount_scale: number; + + // Taxable amount (base imponible) + declare taxable_amount_value: number; + declare taxable_amount_scale: number; + + // Total taxes amount / taxes total + declare taxes_amount_value: number; + declare taxes_amount_scale: number; + + // Total + declare total_amount_value: number; + declare total_amount_scale: number; declare invoice: NonAttribute; @@ -62,101 +75,119 @@ export default (database: Sequelize) => { type: new DataTypes.UUID(), primaryKey: true, }, + invoice_id: { type: new DataTypes.UUID(), allowNull: false, }, - parent_id: { - type: new DataTypes.UUID(), - allowNull: true, // Puede ser nulo para elementos de nivel superior - }, + position: { type: new DataTypes.MEDIUMINT().UNSIGNED, autoIncrement: false, allowNull: false, }, - item_type: { - type: new DataTypes.STRING(), - allowNull: false, - defaultValue: "simple", - }, + description: { type: new DataTypes.TEXT(), allowNull: true, defaultValue: null, }, - quantity_amount: { + quantity_value: { type: new DataTypes.BIGINT(), allowNull: true, defaultValue: null, }, + quantity_scale: { type: new DataTypes.SMALLINT(), - allowNull: true, - defaultValue: null, + allowNull: false, + defaultValue: 2, }, - unit_price_amount: { + unit_amount_value: { type: new DataTypes.BIGINT(), allowNull: true, defaultValue: null, }, - unit_price_scale: { + + unit_amount_scale: { type: new DataTypes.SMALLINT(), - allowNull: true, - defaultValue: null, + allowNull: false, + defaultValue: 4, }, - /*tax_slug: { - type: new DataTypes.DECIMAL(3, 2), - allowNull: true, - }, - tax_rate: { - type: new DataTypes.DECIMAL(3, 2), - allowNull: true, - }, - tax_equalization: { - type: new DataTypes.DECIMAL(3, 2), - allowNull: true, - },*/ - - subtotal_amount: { + subtotal_amount_value: { type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes allowNull: true, defaultValue: null, }, - subtotal_scale: { + + subtotal_amount_scale: { + type: new DataTypes.SMALLINT(), + allowNull: false, + defaultValue: 4, + }, + + discount_percentage_value: { type: new DataTypes.SMALLINT(), allowNull: true, defaultValue: null, }, - discount_amount: { + discount_percentage_scale: { type: new DataTypes.SMALLINT(), - allowNull: true, - defaultValue: null, - }, - discount_scale: { - type: new DataTypes.SMALLINT(), - allowNull: true, - defaultValue: null, + allowNull: false, + defaultValue: 2, }, - /*tax_amount: { - type: new DataTypes.BIGINT(), - allowNull: true, - },*/ - total_amount: { + discount_amount_value: { type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes allowNull: true, defaultValue: null, }, - total_scale: { + + discount_amount_scale: { type: new DataTypes.SMALLINT(), + allowNull: false, + defaultValue: 4, + }, + + taxable_amount_value: { + type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes allowNull: true, defaultValue: null, }, + + taxable_amount_scale: { + type: new DataTypes.SMALLINT(), + allowNull: false, + defaultValue: 4, + }, + + taxes_amount_value: { + type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes + allowNull: true, + defaultValue: null, + }, + + taxes_amount_scale: { + type: new DataTypes.SMALLINT(), + allowNull: false, + defaultValue: 4, + }, + + total_amount_value: { + type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes + allowNull: true, + defaultValue: null, + }, + + total_amount_scale: { + type: new DataTypes.SMALLINT(), + allowNull: false, + defaultValue: 4, + }, }, { sequelize: database, diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-taxes.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-taxes.model.ts new file mode 100644 index 00000000..5fbb1021 --- /dev/null +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-taxes.model.ts @@ -0,0 +1,107 @@ +import { + DataTypes, + InferAttributes, + InferCreationAttributes, + Model, + NonAttribute, + Sequelize, +} from "sequelize"; +import { CustomerInvoice } from "../../domain"; + +export type CustomerInvoiceTaxesCreationAttributes = InferCreationAttributes< + CustomerInvoiceTaxesModel, + {} +>; + +export class CustomerInvoiceTaxesModel extends Model< + InferAttributes, + InferCreationAttributes +> { + declare id: string; + declare invoice_id: string; + + declare tax_code: string; //"iva_21" + + // Taxable amount (base imponible) // 100,00 € + declare taxable_amount_value: number; + declare taxable_amount_scale: number; + + // Total tax amount / taxes total // 21,00 € + declare tax_amount_value: number; + declare tax_amount_scale: number; + + // Relaciones + declare invoice: NonAttribute; + + static associate(database: Sequelize) { + const { CustomerInvoiceModel } = database.models; + + CustomerInvoiceTaxesModel.belongsTo(CustomerInvoiceModel, { + as: "invoice", + targetKey: "id", + foreignKey: "invoice_id", + onDelete: "CASCADE", + }); + } + + static hooks(database: Sequelize) {} +} + +export default (database: Sequelize) => { + CustomerInvoiceTaxesModel.init( + { + id: { + type: new DataTypes.UUID(), + primaryKey: true, + }, + + invoice_id: { + type: DataTypes.UUID, + allowNull: false, + }, + + tax_code: { + type: new DataTypes.STRING(), + allowNull: false, + }, + + taxable_amount_value: { + type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes + allowNull: true, + defaultValue: null, + }, + taxable_amount_scale: { + type: new DataTypes.SMALLINT(), + allowNull: false, + defaultValue: 2, + }, + + tax_amount_value: { + type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes + allowNull: true, + defaultValue: null, + }, + tax_amount_scale: { + type: new DataTypes.SMALLINT(), + allowNull: false, + defaultValue: 2, + }, + }, + { + sequelize: database, + tableName: "customer_invoices_taxes", + + underscored: true, + + indexes: [{ name: "tax_code_idx", fields: ["tax_code"], unique: false }], + + whereMergeStrategy: "and", // <- cómo tratar el merge de un scope + + defaultScope: {}, + + scopes: {}, + } + ); + + return CustomerInvoiceTaxesModel; +}; 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 cf65c46d..6ef239d6 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 @@ -28,11 +28,13 @@ export class CustomerInvoiceModel extends Model< declare status: string; declare series: string; declare invoice_number: string; - declare issue_date: string; + declare invoice_date: string; declare operation_date: string; declare language_code: string; declare currency_code: string; + declare notes: string; + // Subtotal declare subtotal_amount_value: number; declare subtotal_amount_scale: number; @@ -117,7 +119,7 @@ export default (database: Sequelize) => { defaultValue: null, }, - issue_date: { + invoice_date: { type: new DataTypes.DATEONLY(), allowNull: true, defaultValue: null, @@ -138,18 +140,23 @@ export default (database: Sequelize) => { currency_code: { type: new DataTypes.STRING(3), allowNull: false, - defaultValue: "EUR", + }, + + notes: { + type: new DataTypes.TEXT(), + allowNull: true, + defaultValue: null, }, subtotal_amount_value: { type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes - allowNull: true, - defaultValue: null, + allowNull: false, + defaultValue: 0, }, subtotal_amount_scale: { type: new DataTypes.SMALLINT(), - allowNull: true, - defaultValue: null, + allowNull: false, + defaultValue: 2, }, discount_percentage_value: { @@ -160,8 +167,8 @@ export default (database: Sequelize) => { discount_percentage_scale: { type: new DataTypes.SMALLINT(), - allowNull: true, - defaultValue: null, + allowNull: false, + defaultValue: 2, }, discount_amount_value: { @@ -172,8 +179,8 @@ export default (database: Sequelize) => { discount_amount_scale: { type: new DataTypes.SMALLINT(), - allowNull: true, - defaultValue: null, + allowNull: false, + defaultValue: 2, }, taxable_amount_value: { @@ -183,8 +190,8 @@ export default (database: Sequelize) => { }, taxable_amount_scale: { type: new DataTypes.SMALLINT(), - allowNull: true, - defaultValue: null, + allowNull: false, + defaultValue: 2, }, tax_amount_value: { @@ -194,8 +201,8 @@ export default (database: Sequelize) => { }, tax_amount_scale: { type: new DataTypes.SMALLINT(), - allowNull: true, - defaultValue: null, + allowNull: false, + defaultValue: 2, }, total_amount_value: { @@ -206,8 +213,8 @@ export default (database: Sequelize) => { total_amount_scale: { type: new DataTypes.SMALLINT(), - allowNull: true, - defaultValue: null, + allowNull: false, + defaultValue: 2, }, }, { 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 09ce80cc..5cd7b011 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 @@ -1,36 +1,39 @@ +import { NumericStringSchema, PercentageSchema } from "@erp/core"; import * as z from "zod/v4"; export const CreateCustomerInvoiceRequestSchema = z.object({ id: z.uuid(), - invoice_status: z.string(), - invoice_number: z.string().min(1, "Customer invoice number is required"), - invoice_series: z.string().min(1, "Customer invoice series is required"), - issue_date: z.string().datetime({ offset: true, message: "Invalid issue date format" }), - operation_date: z.string().datetime({ offset: true, message: "Invalid operation date format" }), - description: z.string(), - language_code: z.string().min(2, "Language code must be at least 2 characters long"), - currency_code: z.string().min(3, "Currency code must be at least 3 characters long"), - notes: z.string().optional(), - items: z.array( - z.object({ - description: z.string().min(1, "Item description is required"), - quantity: z.object({ - value: z.number().positive("Quantity amount must be positive"), - scale: z.number().int().nonnegative("Quantity scale must be a non-negative integer"), - }), - unit_amount: z.object({ - value: z.number().positive("Unit price amount must be positive"), - scale: z.number().int().nonnegative("Unit price scale must be a non-negative integer"), - currency_code: z - .string() - .min(3, "Unit price currency code must be at least 3 characters long"), - }), - discount_percentage: z.object({ - value: z.number().nonnegative("Discount amount cannot be negative"), - scale: z.number().int().nonnegative("Discount scale must be a non-negative integer"), - }), - }) - ), + 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(""), + + notes: z.string().default(""), + + language_code: z.string().toLowerCase().default("es"), + currency_code: z.string().toUpperCase().default("EUR"), + + discount_percentage: PercentageSchema.default({ + value: "0", + scale: "2", + }), + + items: z + .array( + z.object({ + id: z.uuid(), + position: z.string(), + description: z.string().default(""), + quantity: NumericStringSchema.default(""), + unit_amount: NumericStringSchema.default(""), + discount_percentage: NumericStringSchema.default(""), + }) + ) + .default([]), }); export type CreateCustomerInvoiceRequestDTO = z.infer; diff --git a/modules/customer-invoices/src/common/dto/response/create-customer-invoice.response.dto.ts b/modules/customer-invoices/src/common/dto/response/create-customer-invoice.response.dto.ts new file mode 100644 index 00000000..6da09dc1 --- /dev/null +++ b/modules/customer-invoices/src/common/dto/response/create-customer-invoice.response.dto.ts @@ -0,0 +1,42 @@ +import { AmountSchema, MetadataSchema, PercentageSchema, QuantitySchema } from "@erp/core"; +import * as z from "zod/v4"; + +export const CreateCustomerInvoiceResponseSchema = z.object({ + id: z.uuid(), + company_id: z.uuid(), + + invoice_number: z.string(), + status: z.string(), + series: z.string(), + + invoice_date: z.string(), + operation_date: z.string(), + + notes: z.string(), + + language_code: z.string(), + currency_code: z.string(), + + subtotal_amount: AmountSchema, + discount_percentage: PercentageSchema, + discount_amount: AmountSchema, + taxable_amount: AmountSchema, + tax_amount: AmountSchema, + total_amount: AmountSchema, + + items: z.array( + z.object({ + id: z.uuid(), + position: z.string(), + description: z.string(), + quantity: QuantitySchema, + unit_amount: AmountSchema, + discount_percentage: PercentageSchema, + total_amount: AmountSchema, + }) + ), + + metadata: MetadataSchema.optional(), +}); + +export type CreateCustomerInvoiceResponseDTO = z.infer; diff --git a/modules/customer-invoices/src/common/dto/response/customer-invoice-creation.response.dto.ts b/modules/customer-invoices/src/common/dto/response/customer-invoice-creation.response.dto.ts deleted file mode 100644 index 0f6f0665..00000000 --- a/modules/customer-invoices/src/common/dto/response/customer-invoice-creation.response.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { MetadataSchema } from "@erp/core"; -import * as z from "zod/v4"; - -export const CustomerInvoicesCreationResponseSchema = z.object({ - id: z.uuid(), - invoice_status: z.string(), - invoice_number: z.string(), - invoice_series: z.string(), - issue_date: z.iso.datetime({ offset: true }), - operation_date: z.iso.datetime({ offset: true }), - language_code: z.string(), - currency: z.string(), - - metadata: MetadataSchema.optional(), -}); - -export type CustomerInvoicesCreationResponseDTO = z.infer< - typeof CustomerInvoicesCreationResponseSchema ->; diff --git a/modules/customer-invoices/src/common/dto/response/customer-invoices-list.response.dto.ts b/modules/customer-invoices/src/common/dto/response/customer-invoices-list.response.dto.ts index d8d30001..e6ca0bc8 100644 --- a/modules/customer-invoices/src/common/dto/response/customer-invoices-list.response.dto.ts +++ b/modules/customer-invoices/src/common/dto/response/customer-invoices-list.response.dto.ts @@ -7,7 +7,7 @@ export const CustomerInvoiceListResponseSchema = createListViewResponseSchema( invoice_status: z.string(), invoice_number: z.string(), invoice_series: z.string(), - issue_date: z.iso.datetime({ offset: true }), + invoice_date: z.iso.datetime({ offset: true }), operation_date: z.iso.datetime({ offset: true }), language_code: z.string(), currency: z.string(), diff --git a/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts b/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts index f9e2ed56..eaed4ae4 100644 --- a/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts +++ b/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts @@ -9,7 +9,7 @@ export const GetCustomerInvoiceByIdResponseSchema = z.object({ status: z.string(), series: z.string(), - issue_date: z.string(), + invoice_date: z.string(), operation_date: z.string(), notes: z.string(), diff --git a/modules/customer-invoices/src/common/dto/response/index.ts b/modules/customer-invoices/src/common/dto/response/index.ts index 5c025bd7..3b44a30f 100644 --- a/modules/customer-invoices/src/common/dto/response/index.ts +++ b/modules/customer-invoices/src/common/dto/response/index.ts @@ -1,4 +1,4 @@ -export * from "./customer-invoice-creation.response.dto"; +export * from "./create-customer-invoice.response.dto"; export * from "./customer-invoices-list.response.dto"; export * from "./get-customer-invoice-by-id.response.dto"; export * from "./update-customer-invoice-by-id.response.dto"; diff --git a/modules/customer-invoices/src/common/dto/response/update-customer-invoice-by-id.response.dto.ts b/modules/customer-invoices/src/common/dto/response/update-customer-invoice-by-id.response.dto.ts index ffd3f1fe..87255543 100644 --- a/modules/customer-invoices/src/common/dto/response/update-customer-invoice-by-id.response.dto.ts +++ b/modules/customer-invoices/src/common/dto/response/update-customer-invoice-by-id.response.dto.ts @@ -9,7 +9,7 @@ export const UpdateCustomerInvoiceByIdResponseSchema = z.object({ status: z.string(), series: z.string(), - issue_date: z.string(), + invoice_date: z.string(), operation_date: z.string(), notes: z.string(), diff --git a/modules/customer-invoices/src/common/locales/en.json b/modules/customer-invoices/src/common/locales/en.json index ccf02fbe..ee32c2ad 100644 --- a/modules/customer-invoices/src/common/locales/en.json +++ b/modules/customer-invoices/src/common/locales/en.json @@ -17,7 +17,7 @@ "invoice_number": "Inv. number", "invoice_series": "Serie", "invoice_status": "Status", - "issue_date": "Date", + "invoice_date": "Date", "total_price": "Total price" } }, @@ -52,7 +52,7 @@ "placeholder": "", "description": "" }, - "issue_date": { + "invoice_date": { "label": "Date", "placeholder": "Select a date", "description": "Invoice issue date" diff --git a/modules/customer-invoices/src/web/components/customer-invoices-list-grid.tsx b/modules/customer-invoices/src/web/components/customer-invoices-list-grid.tsx index 93636dd2..acf3ddd5 100644 --- a/modules/customer-invoices/src/web/components/customer-invoices-list-grid.tsx +++ b/modules/customer-invoices/src/web/components/customer-invoices-list-grid.tsx @@ -35,8 +35,8 @@ export const CustomerInvoicesListGrid = () => { { field: "invoice_series", headerName: t("pages.list.grid_columns.invoice_series") }, { - field: "issue_date", - headerName: t("pages.list.grid_columns.issue_date"), + field: "invoice_date", + headerName: t("pages.list.grid_columns.invoice_date"), valueFormatter: (params: ValueFormatterParams) => { return formatDate(params.value); }, diff --git a/modules/customer-invoices/src/web/pages/create/customer-invoice-edit-form.tsx b/modules/customer-invoices/src/web/pages/create/customer-invoice-edit-form.tsx index 4c3d9be8..de8b5b01 100644 --- a/modules/customer-invoices/src/web/pages/create/customer-invoice-edit-form.tsx +++ b/modules/customer-invoices/src/web/pages/create/customer-invoice-edit-form.tsx @@ -49,7 +49,7 @@ const invoiceFormSchema = z.object({ invoice_status: z.string(), invoice_number: z.string().min(1, "Número de factura requerido"), invoice_series: z.string().min(1, "Serie requerida"), - issue_date: z.string(), + invoice_date: z.string(), operation_date: z.string(), language_code: z.string(), currency: z.string(), @@ -144,7 +144,7 @@ const defaultInvoiceData = { invoice_status: "draft", invoice_number: "1", invoice_series: "A", - issue_date: "2025-04-30T00:00:00.000Z", + invoice_date: "2025-04-30T00:00:00.000Z", operation_date: "2025-04-30T00:00:00.000Z", description: "", language_code: "ES", @@ -332,11 +332,11 @@ export const CustomerInvoiceEditForm = ({ handleDateChange("issue_date", date)} + selected={invoiceDate} + onSelect={(date) => handleDateChange("invoice_date", date)} initialFocus /> diff --git a/modules/customers/src/api/application/create-customer/create-customer.use-case.ts b/modules/customers/src/api/application/create-customer/create-customer.use-case.ts index 3bf77135..342fe379 100644 --- a/modules/customers/src/api/application/create-customer/create-customer.use-case.ts +++ b/modules/customers/src/api/application/create-customer/create-customer.use-case.ts @@ -8,8 +8,8 @@ import { CreateCustomersAssembler } from "./assembler"; import { mapDTOToCreateCustomerProps } from "./map-dto-to-create-customer-props"; type CreateCustomerUseCaseInput = { - dto: CreateCustomerRequestDTO; companyId: UniqueID; + dto: CreateCustomerRequestDTO; }; export class CreateCustomerUseCase { @@ -30,7 +30,7 @@ export class CreateCustomerUseCase { const { props, id } = dtoResult.data; - // 3) Construir entidad de dominio + // 2) Construir entidad de dominio const buildResult = this.service.buildCustomerInCompany(companyId, props, id); if (buildResult.isFailure) { return Result.fail(buildResult.error); @@ -38,7 +38,7 @@ export class CreateCustomerUseCase { const newCustomer = buildResult.data; - // 4) Ejecutar bajo transacción: verificar duplicado → persistir → ensamblar vista + // 3) Ejecutar bajo transacción: verificar duplicado → persistir → ensamblar vista return this.transactionManager.complete(async (transaction: Transaction) => { const existsGuard = await this.ensureNotExists(companyId, id, transaction); if (existsGuard.isFailure) { diff --git a/modules/customers/src/api/infrastructure/express/controllers/update-customer/presenter/UpdateInvoice.presenter.ts.bak b/modules/customers/src/api/infrastructure/express/controllers/update-customer/presenter/UpdateInvoice.presenter.ts.bak index a7acad78..53903600 100644 --- a/modules/customers/src/api/infrastructure/express/controllers/update-customer/presenter/UpdateInvoice.presenter.ts.bak +++ b/modules/customers/src/api/infrastructure/express/controllers/update-customer/presenter/UpdateInvoice.presenter.ts.bak @@ -16,7 +16,7 @@ export const updateCustomerPresenter: IUpdateCustomerPresenter = { customer_status: customer.status.toString(), customer_number: customer.customerNumber.toString(), customer_series: customer.customerSeries.toString(), - issue_date: customer.issueDate.toISO8601(), + invoice_date: customer.invoiceDate.toISO8601(), operation_date: customer.operationDate.toISO8601(), language_code: customer.language.toString(), currency: customer.currency.toString(), diff --git a/modules/customers/src/api/infrastructure/sequelize/customer.model.ts b/modules/customers/src/api/infrastructure/sequelize/customer.model.ts index 7fa0149f..5dd483c0 100644 --- a/modules/customers/src/api/infrastructure/sequelize/customer.model.ts +++ b/modules/customers/src/api/infrastructure/sequelize/customer.model.ts @@ -182,7 +182,6 @@ export default (database: Sequelize) => { indexes: [ { name: "company_idx", fields: ["company_id"], unique: false }, { name: "idx_company_idx", fields: ["id", "company_id"], unique: true }, - { name: "email_idx", fields: ["email"], unique: true }, ], whereMergeStrategy: "and", // <- cómo tratar el merge de un scope