From 5064494b12f35a12e5b932f32652e290c14e2176 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 3 Sep 2025 20:04:09 +0200 Subject: [PATCH] Facturas de cliente --- .../core/src/common/dto/amount-money.dto.ts | 23 ++++ modules/core/src/common/dto/base.schemas.ts | 19 +++ modules/core/src/common/dto/critera.dto.ts | 4 +- modules/core/src/common/dto/index.ts | 3 +- modules/core/src/common/dto/money.dto.ts | 5 - modules/core/src/common/dto/percentage.dto.ts | 18 ++- modules/core/src/common/dto/quantity.dto.ts | 18 ++- modules/core/src/common/dto/tax-type.dto.ts | 6 +- .../assembler/get-invoice-items.assembler.ts | 48 +++++++ .../assembler/get-invoice.assembler.ts | 31 +++-- .../get-customer-invoice.use-case.ts | 25 ++-- ...map-dto-to-customer-invoice-items-props.ts | 4 +- .../api/domain/aggregates/customer-invoice.ts | 45 +++++- .../customer-invoice-item.test.ts | 0 .../customer-invoice-item.ts | 34 ++--- .../customer-invoice-items.ts | 0 .../index.ts | 0 .../src/api/domain/entities/index.ts | 2 +- .../customer-invoice-service.interface.ts | 33 ----- .../services/customer-invoice.service.ts | 128 ++++++++---------- .../src/api/domain/services/index.ts | 1 - ... customer-invoice-item-subtotal-amount.ts} | 2 +- ... => customer-invoice-item-total-amount.ts} | 2 +- ...s => customer-invoice-item-unit-amount.ts} | 4 +- .../src/api/domain/value-objects/index.ts | 6 +- .../src/api/infrastructure/dependencies.ts | 6 +- .../get-customer-invoice.controller.ts | 11 +- .../express/customer-invoices.routes.ts | 38 ++++-- .../mappers/customer-invoice-item.mapper.ts | 12 +- .../sequelize/customer-invoice.model.ts | 79 +++++++++-- .../get-customer-invoice-by-id.request.dto.ts | 2 +- ...get-customer-invoice-by-id.response.dto.ts | 29 +++- .../api/domain/services/customer.service.ts | 6 +- .../express/customers.routes.ts | 17 ++- packages/rdx-utils/src/helpers/utils.ts | 2 +- 35 files changed, 444 insertions(+), 219 deletions(-) create mode 100644 modules/core/src/common/dto/amount-money.dto.ts create mode 100644 modules/core/src/common/dto/base.schemas.ts delete mode 100644 modules/core/src/common/dto/money.dto.ts create mode 100644 modules/customer-invoices/src/api/application/get-customer-invoice/assembler/get-invoice-items.assembler.ts rename modules/customer-invoices/src/api/domain/entities/{invoice-items => customer-invoice-items}/customer-invoice-item.test.ts (100%) rename modules/customer-invoices/src/api/domain/entities/{invoice-items => customer-invoice-items}/customer-invoice-item.ts (72%) rename modules/customer-invoices/src/api/domain/entities/{invoice-items => customer-invoice-items}/customer-invoice-items.ts (100%) rename modules/customer-invoices/src/api/domain/entities/{invoice-items => customer-invoice-items}/index.ts (100%) delete mode 100644 modules/customer-invoices/src/api/domain/services/customer-invoice-service.interface.ts rename modules/customer-invoices/src/api/domain/value-objects/{customer-invoice-item-subtotal-price.ts => customer-invoice-item-subtotal-amount.ts} (83%) rename modules/customer-invoices/src/api/domain/value-objects/{customer-invoice-item-total-price.ts => customer-invoice-item-total-amount.ts} (83%) rename modules/customer-invoices/src/api/domain/value-objects/{customer-invoice-item-unit-price.ts => customer-invoice-item-unit-amount.ts} (84%) diff --git a/modules/core/src/common/dto/amount-money.dto.ts b/modules/core/src/common/dto/amount-money.dto.ts new file mode 100644 index 00000000..8738a5f5 --- /dev/null +++ b/modules/core/src/common/dto/amount-money.dto.ts @@ -0,0 +1,23 @@ +import * as z from "zod/v4"; +import { AmountBaseSchema } from "./base.schemas"; + +/** + Esquema del DTO para valores monetarios con/sin código de moneda. + No aplica defaults ni correciones: solo valida. + + - Con moneda -> "Money" + - Sin moneda -> "Amount" + +*/ + +// 🔹 Con moneda +export const MoneySchema = AmountBaseSchema.extend({ + currency_code: z.string(), +}); + +// 🔹 Sin moneda +export const AmountSchema = AmountBaseSchema; + +// 🔹 Tipos DTO +export type MoneyDTO = z.infer; +export type AmountDTO = z.infer; diff --git a/modules/core/src/common/dto/base.schemas.ts b/modules/core/src/common/dto/base.schemas.ts new file mode 100644 index 00000000..aef3a04f --- /dev/null +++ b/modules/core/src/common/dto/base.schemas.ts @@ -0,0 +1,19 @@ +import * as z from "zod/v4"; + +/** + * Cadena con valor numérico: + * + * - Acepta: "" o "123456" (solo dígitos). + * - Rechaza: "1 23", "abc123", "12v34", "+123", "12.3" + * + * */ + +export const NumericStringSchema = z + .string() + .regex(/^\d$/, { message: "Must be empty or contain only digits (0-9)." }); + +// Cantidad de dinero (base): solo para la cantidad y la escala, sin moneda +export const AmountBaseSchema = z.object({ + amount: NumericStringSchema, + scale: NumericStringSchema, +}); diff --git a/modules/core/src/common/dto/critera.dto.ts b/modules/core/src/common/dto/critera.dto.ts index 516c3438..b563ad47 100644 --- a/modules/core/src/common/dto/critera.dto.ts +++ b/modules/core/src/common/dto/critera.dto.ts @@ -1,8 +1,8 @@ import * as z from "zod/v4"; /** -Esquema del objeto normalizado esperado por Criteria.fromPrimitives(...) -No aplica defaults ni correciones: solo valida. + Esquema del DTO para Criteria.fromPrimitives(...) + No aplica defaults ni correciones: solo valida. */ export const FilterPrimitiveSchema = z.object({ // Campos mínimos ya normalizados por el conversor diff --git a/modules/core/src/common/dto/index.ts b/modules/core/src/common/dto/index.ts index 7ec54e01..b3617d3e 100644 --- a/modules/core/src/common/dto/index.ts +++ b/modules/core/src/common/dto/index.ts @@ -1,8 +1,9 @@ +export * from "./amount-money.dto"; +export * from "./base.schemas"; export * from "./critera.dto"; export * from "./error.dto"; export * from "./list-view.response.dto"; export * from "./metadata.dto"; -export * from "./money.dto"; export * from "./percentage.dto"; export * from "./quantity.dto"; export * from "./tax-type.dto"; diff --git a/modules/core/src/common/dto/money.dto.ts b/modules/core/src/common/dto/money.dto.ts deleted file mode 100644 index 1e675f51..00000000 --- a/modules/core/src/common/dto/money.dto.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type MoneyDTO = { - amount: number | null; - scale: number; - currency_code: string; -}; diff --git a/modules/core/src/common/dto/percentage.dto.ts b/modules/core/src/common/dto/percentage.dto.ts index 668fd1ca..6117fd1c 100644 --- a/modules/core/src/common/dto/percentage.dto.ts +++ b/modules/core/src/common/dto/percentage.dto.ts @@ -1,4 +1,14 @@ -export type IPercentageDTO = { - amount: number | null; - scale: number; -}; +import * as z from "zod/v4"; +import { NumericStringSchema } from "./base.schemas"; + +/** + Esquema del DTO para valores de porcentajes. + No aplica defaults ni correciones: solo valida. +*/ + +export const PercentageSchema = z.object({ + amount: NumericStringSchema, + scale: NumericStringSchema, +}); + +export type PercentageDTO = z.infer; diff --git a/modules/core/src/common/dto/quantity.dto.ts b/modules/core/src/common/dto/quantity.dto.ts index 20e75ea7..e1370a0a 100644 --- a/modules/core/src/common/dto/quantity.dto.ts +++ b/modules/core/src/common/dto/quantity.dto.ts @@ -1,4 +1,14 @@ -export type IQuantityDTO = { - amount: number | null; - scale: number; -}; +import * as z from "zod/v4"; +import { NumericStringSchema } from "./base.schemas"; + +/** + Esquema del DTO para valores de cantidades. + No aplica defaults ni correciones: solo valida. +*/ + +export const QuantitySchema = z.object({ + amount: NumericStringSchema, + scale: NumericStringSchema, +}); + +export type QuantityDTO = z.infer; diff --git a/modules/core/src/common/dto/tax-type.dto.ts b/modules/core/src/common/dto/tax-type.dto.ts index d232d5d3..cbaad322 100644 --- a/modules/core/src/common/dto/tax-type.dto.ts +++ b/modules/core/src/common/dto/tax-type.dto.ts @@ -1,11 +1,11 @@ -import { IPercentageDTO } from "./percentage.dto"; +import { PercentageDTO } from "./percentage.dto"; export interface ITaxTypeDTO { id: string; typecode: string; taxslug: string; - taxrate: IPercentageDTO; - equivalencesurcharge: IPercentageDTO; + taxrate: PercentageDTO; + equivalencesurcharge: PercentageDTO; } export interface ITaxTypeResponseDTO extends ITaxTypeDTO {} 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 new file mode 100644 index 00000000..134c2923 --- /dev/null +++ b/modules/customer-invoices/src/api/application/get-customer-invoice/assembler/get-invoice-items.assembler.ts @@ -0,0 +1,48 @@ +import { GetCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto"; +import { toEmptyString } from "@repo/rdx-ddd"; +import { CustomerInvoice } from "../../../domain"; + +type GetCustomerInvoiceItemsByInvoiceIdResponseDTO = GetCustomerInvoiceByIdResponseDTO["items"]; + +export class GetCustomerInvoiceItemsAssembler { + toDTO(invoice: CustomerInvoice): GetCustomerInvoiceItemsByInvoiceIdResponseDTO { + const { items } = invoice; + return items.map((item, index) => ({ + //id: item. + position: index, + description: toEmptyString(item.description, (value) => value.toPrimitive()), + + quantity: item.quantity.match( + (quantity) => { + const { amount, scale } = quantity.toPrimitive(); + return { amount: amount.toString(), scale: scale.toString() }; + }, + () => ({ amount: "", scale: "" }) + ), + + unit_price_amount: item.unitAmount.match( + (unitPrice) => { + const { amount, scale } = unitPrice.toPrimitive(); + return { amount: amount.toString(), scale: scale.toString() }; + }, + () => ({ amount: "", scale: "" }) + ), + + discount: item.discount.match( + (discount) => { + const { amount, scale } = discount.toPrimitive(); + return { amount: amount.toString(), scale: scale.toString() }; + }, + () => ({ amount: "", scale: "" }) + ), + + total_amount: item.totalPrice.match( + (discount) => { + const { amount, scale } = discount.toPrimitive(); + return { amount: amount.toString(), scale: scale.toString() }; + }, + () => ({ amount: "", scale: "" }) + ), + })); + } +} 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 95fff942..1bc2808b 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 @@ -1,18 +1,29 @@ import { GetCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto"; +import { toEmptyString } from "@repo/rdx-ddd"; import { CustomerInvoice } from "../../../domain"; export class GetCustomerInvoiceAssembler { - toDTO(customerInvoice: CustomerInvoice): GetCustomerInvoiceByIdResponseDTO { - return { - id: customerInvoice.id.toPrimitive(), + private _itemsAssembler!: GetCu; + constructor() {} - invoice_status: customerInvoice.status.toString(), - invoice_number: customerInvoice.invoiceNumber.toString(), - invoice_series: customerInvoice.invoiceSeries.toString(), - issue_date: customerInvoice.issueDate.toDateString(), - operation_date: customerInvoice.operationDate.toDateString(), - language_code: "ES", - currency: customerInvoice.currency, + public toDTO(invoice: CustomerInvoice): GetCustomerInvoiceByIdResponseDTO { + //const items = invoice.items. + + return { + id: invoice.id.toPrimitive(), + company_id: invoice.companyId.toPrimitive(), + + invoice_number: invoice.invoiceNumber.toString(), + status: invoice.status.toPrimitive(), + series: invoice.series.toString(), + + issue_date: invoice.issueDate.toDateString(), + operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()), + + notes: toEmptyString(invoice.notes, (value) => value.toPrimitive()), + + language_code: invoice.languageCode.toPrimitive(), + currency_code: invoice.currencyCode.toPrimitive(), metadata: { entity: "customer-invoices", 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 c835b6c3..96ea7a16 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 @@ -1,32 +1,39 @@ import { ITransactionManager } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; -import { ICustomerInvoiceService } from "../../domain"; -import { GetCustomerInvoiceAssembler } from "./assembler"; +import { CustomerInvoiceService } from "../../domain"; +import { GetCustomerInvoiceItemsAssembler } from "./assembler"; type GetCustomerInvoiceUseCaseInput = { - tenantId: string; - id: string; + companyId: UniqueID; + invoice_id: string; }; export class GetCustomerInvoiceUseCase { constructor( - private readonly service: ICustomerInvoiceService, + private readonly service: CustomerInvoiceService, private readonly transactionManager: ITransactionManager, - private readonly assembler: GetCustomerInvoiceAssembler + private readonly assembler: GetCustomerInvoiceItemsAssembler ) {} public execute(params: GetCustomerInvoiceUseCaseInput) { - const { id, tenantId } = params; - const idOrError = UniqueID.create(id); + const { invoice_id, companyId } = params; + + const idOrError = UniqueID.create(invoice_id); if (idOrError.isFailure) { return Result.fail(idOrError.error); } + const invoiceId = idOrError.data; + return this.transactionManager.complete(async (transaction) => { try { - const invoiceOrError = await this.service.getById(idOrError.data, transaction); + const invoiceOrError = await this.service.getInvoiceByIdInCompany( + companyId, + invoiceId, + transaction + ); if (invoiceOrError.isFailure) { return Result.fail(invoiceOrError.error); } diff --git a/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-items-props.ts b/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-items-props.ts index cee02cec..2fce4862 100644 --- a/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-items-props.ts +++ b/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-items-props.ts @@ -6,7 +6,7 @@ import { CustomerInvoiceItemDescription, CustomerInvoiceItemDiscount, CustomerInvoiceItemQuantity, - CustomerInvoiceItemUnitPrice, + CustomerInvoiceItemUnitAmount, } from "../../domain"; import { extractOrPushError } from "./extract-or-push-error"; import { hasNoUndefinedFields } from "./has-no-undefined-fields"; @@ -36,7 +36,7 @@ export function mapDTOToCustomerInvoiceItemsProps( ); const unitPrice = extractOrPushError( - CustomerInvoiceItemUnitPrice.create({ + CustomerInvoiceItemUnitAmount.create({ amount: item.unitPrice.amount, scale: item.unitPrice.scale, currency_code: item.unitPrice.currency, 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 01100b8f..56763f6a 100644 --- a/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts +++ b/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts @@ -2,6 +2,8 @@ import { AggregateRoot, CurrencyCode, LanguageCode, + MoneyValue, + Percentage, TextValue, UniqueID, UtcDate, @@ -16,9 +18,10 @@ import { export interface CustomerInvoiceProps { companyId: UniqueID; + + invoiceNumber: CustomerInvoiceNumber; status: CustomerInvoiceStatus; series: Maybe; - invoiceNumber: CustomerInvoiceNumber; issueDate: UtcDate; operationDate: Maybe; @@ -40,6 +43,16 @@ export interface CustomerInvoiceProps { languageCode: LanguageCode; currencyCode: CurrencyCode; + subtotalAmount: MoneyValue; + + discountPercentage: Percentage; + //discountAmount: MoneyValue; + + //taxableAmount: MoneyValue; + taxAmount: MoneyValue; + + totalAmount: MoneyValue; + //customer?: CustomerInvoiceCustomer; items?: CustomerInvoiceItems; } @@ -82,6 +95,10 @@ export class CustomerInvoice extends AggregateRoot { return this.props.companyId; } + public get status(): CustomerInvoiceStatus { + return this.props.status; + } + public get series(): Maybe { return this.props.series; } @@ -110,8 +127,32 @@ export class CustomerInvoice extends AggregateRoot { return this.props.currencyCode; } + public get subtotalAmount(): MoneyValue { + return this.props.subtotalAmount; + } + + public get discountPercentage(): Percentage { + return this.props.discountPercentage; + } + + public get discountAmount(): MoneyValue { + throw new Error("discountAmount not implemented"); + } + + public get taxableAmount(): MoneyValue { + throw new Error("taxableAmount not implemented"); + } + + public get taxAmount(): MoneyValue { + return this.props.taxAmount; + } + + public get totalAmount(): MoneyValue { + throw new Error("totalAmount not implemented"); + } + // Method to get the complete list of line items - get lineItems(): CustomerInvoiceItems { + get items(): CustomerInvoiceItems { return this._items; } diff --git a/modules/customer-invoices/src/api/domain/entities/invoice-items/customer-invoice-item.test.ts b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.test.ts similarity index 100% rename from modules/customer-invoices/src/api/domain/entities/invoice-items/customer-invoice-item.test.ts rename to modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.test.ts diff --git a/modules/customer-invoices/src/api/domain/entities/invoice-items/customer-invoice-item.ts b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts similarity index 72% rename from modules/customer-invoices/src/api/domain/entities/invoice-items/customer-invoice-item.ts rename to modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts index 79ee7ca1..75b09d01 100644 --- a/modules/customer-invoices/src/api/domain/entities/invoice-items/customer-invoice-item.ts +++ b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts @@ -4,15 +4,15 @@ import { CustomerInvoiceItemDescription, CustomerInvoiceItemDiscount, CustomerInvoiceItemQuantity, - CustomerInvoiceItemSubtotalPrice, - CustomerInvoiceItemTotalPrice, - CustomerInvoiceItemUnitPrice, + CustomerInvoiceItemSubtotalAmount, + CustomerInvoiceItemTotalAmount, + CustomerInvoiceItemUnitAmount, } from "../../value-objects"; export interface CustomerInvoiceItemProps { description: Maybe; quantity: Maybe; // Cantidad de unidades - unitPrice: Maybe; // Precio unitario en la moneda de la factura + unitPrice: Maybe; // Precio unitario en la moneda de la factura discount: Maybe; // % descuento languageCode: LanguageCode; @@ -20,8 +20,8 @@ export interface CustomerInvoiceItemProps { } export class CustomerInvoiceItem extends DomainEntity { - private _subtotalPrice!: CustomerInvoiceItemSubtotalPrice; - private _totalPrice!: CustomerInvoiceItemTotalPrice; + private _subtotalAmount!: CustomerInvoiceItemSubtotalAmount; + private _totalAmount!: CustomerInvoiceItemTotalAmount; public static create( props: CustomerInvoiceItemProps, @@ -48,26 +48,26 @@ export class CustomerInvoiceItem extends DomainEntity return this.props.quantity; } - get unitPrice(): Maybe { + get unitAmount(): Maybe { return this.props.unitPrice; } - get subtotalPrice(): CustomerInvoiceItemSubtotalPrice { - if (!this._subtotalPrice) { - this._subtotalPrice = this.calculateSubtotal(); + get subtotalAmount(): CustomerInvoiceItemSubtotalAmount { + if (!this._subtotalAmount) { + this._subtotalAmount = this.calculateSubtotal(); } - return this._subtotalPrice; + return this._subtotalAmount; } get discount(): Maybe { return this.props.discount; } - get totalPrice(): CustomerInvoiceItemTotalPrice { - if (!this._totalPrice) { - this._totalPrice = this.calculateTotal(); + get totalPrice(): CustomerInvoiceItemTotalAmount { + if (!this._totalAmount) { + this._totalAmount = this.calculateTotal(); } - return this._totalPrice; + return this._totalAmount; } public get languageCode(): LanguageCode { @@ -86,7 +86,7 @@ export class CustomerInvoiceItem extends DomainEntity return this.getValue(); } - calculateSubtotal(): CustomerInvoiceItemSubtotalPrice { + calculateSubtotal(): CustomerInvoiceItemSubtotalAmount { throw new Error("Not implemented"); /*const unitPrice = this.unitPrice.isSome() @@ -95,7 +95,7 @@ export class CustomerInvoiceItem extends DomainEntity return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad*/ } - calculateTotal(): CustomerInvoiceItemTotalPrice { + calculateTotal(): CustomerInvoiceItemTotalAmount { throw new Error("Not implemented"); //return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber())); } diff --git a/modules/customer-invoices/src/api/domain/entities/invoice-items/customer-invoice-items.ts b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-items.ts similarity index 100% rename from modules/customer-invoices/src/api/domain/entities/invoice-items/customer-invoice-items.ts rename to modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-items.ts diff --git a/modules/customer-invoices/src/api/domain/entities/invoice-items/index.ts b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/index.ts similarity index 100% rename from modules/customer-invoices/src/api/domain/entities/invoice-items/index.ts rename to modules/customer-invoices/src/api/domain/entities/customer-invoice-items/index.ts diff --git a/modules/customer-invoices/src/api/domain/entities/index.ts b/modules/customer-invoices/src/api/domain/entities/index.ts index 4c02277f..1751fa67 100644 --- a/modules/customer-invoices/src/api/domain/entities/index.ts +++ b/modules/customer-invoices/src/api/domain/entities/index.ts @@ -1,2 +1,2 @@ +export * from "./customer-invoice-items"; export * from "./invoice-customer"; -export * from "./invoice-items"; diff --git a/modules/customer-invoices/src/api/domain/services/customer-invoice-service.interface.ts b/modules/customer-invoices/src/api/domain/services/customer-invoice-service.interface.ts deleted file mode 100644 index 8c2c4f49..00000000 --- a/modules/customer-invoices/src/api/domain/services/customer-invoice-service.interface.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Criteria } from "@repo/rdx-criteria/server"; -import { UniqueID } from "@repo/rdx-ddd"; -import { Collection, Result } from "@repo/rdx-utils"; -import { CustomerInvoice, CustomerInvoiceProps } from "../aggregates"; - -export interface ICustomerInvoiceService { - build(props: CustomerInvoiceProps, id?: UniqueID): Result; - - save(invoice: CustomerInvoice, transaction: any): Promise>; - - existsById(id: UniqueID, transaction?: any): Promise>; - - findByCriteria( - criteria: Criteria, - transaction?: any - ): Promise, Error>>; - - getById(id: UniqueID, transaction?: any): Promise>; - - updateById( - id: UniqueID, - data: Partial, - transaction?: any - ): Promise>; - - createCustomerInvoice( - id: UniqueID, - data: CustomerInvoiceProps, - transaction?: any - ): Promise>; - - deleteById(id: UniqueID, transaction?: any): Promise>; -} diff --git a/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts b/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts index 10d680d6..b9b7aeab 100644 --- a/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts +++ b/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts @@ -2,22 +2,26 @@ import { Criteria } from "@repo/rdx-criteria/server"; import { UniqueID } from "@repo/rdx-ddd"; import { Collection, Result } from "@repo/rdx-utils"; import { Transaction } from "sequelize"; -import { CustomerInvoice, CustomerInvoiceProps } from "../aggregates"; +import { CustomerInvoice, CustomerInvoicePatchProps, CustomerInvoiceProps } from "../aggregates"; import { ICustomerInvoiceRepository } from "../repositories"; -import { ICustomerInvoiceService } from "./customer-invoice-service.interface"; -export class CustomerInvoiceService implements ICustomerInvoiceService { +export class CustomerInvoiceService { constructor(private readonly repository: ICustomerInvoiceRepository) {} /** * Construye un nuevo agregado CustomerInvoice a partir de props validadas. * + * @param companyId - Identificador de la empresa a la que pertenece el cliente. * @param props - Las propiedades ya validadas para crear la factura. - * @param id - Identificador UUID de la factura (opcional). + * @param invoiceId - Identificador UUID de la factura (opcional). * @returns Result - El agregado construido o un error si falla la creación. */ - build(props: CustomerInvoiceProps, id?: UniqueID): Result { - return CustomerInvoice.create(props, id); + buildInvoiceInCompany( + companyId: UniqueID, + props: Omit, + invoiceId?: UniqueID + ): Result { + return CustomerInvoice.create({ ...props, companyId }, invoiceId); } /** @@ -27,124 +31,108 @@ export class CustomerInvoiceService implements ICustomerInvoiceService { * @param transaction - Transacción activa para la operación. * @returns Result - El agregado guardado o un error si falla la operación. */ - async save(invoice: CustomerInvoice, transaction: any): Promise> { - const saved = await this.repository.save(invoice, transaction); - return saved.isSuccess ? Result.ok(invoice) : Result.fail(saved.error); + async saveInvoice( + invoice: CustomerInvoice, + transaction: any + ): Promise> { + return this.repository.save(invoice, transaction); } /** * * Comprueba si existe o no en persistencia una factura con el ID proporcionado * - * @param id - Identificador UUID de la factura. + * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. + * @param invoiceId - Identificador UUID de la factura. * @param transaction - Transacción activa para la operación. * @returns Result - Existe la factura o no. */ - async existsById(id: UniqueID, transaction?: any): Promise> { - return this.repository.existsById(id, transaction); + async existsByIdInCompany( + companyId: UniqueID, + invoiceId: UniqueID, + transaction?: any + ): Promise> { + return this.repository.existsByIdInCompany(companyId, invoiceId, transaction); } /** * Obtiene una colección de facturas que cumplen con los filtros definidos en un objeto Criteria. * + * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. * @param criteria - Objeto con condiciones de filtro, paginación y orden. * @param transaction - Transacción activa para la operación. * @returns Result, Error> - Colección de facturas o error. */ - async findByCriteria( + async findInvoiceByCriteriaInCompany( + companyId: UniqueID, criteria: Criteria, transaction?: Transaction ): Promise, Error>> { - const customerInvoicesOrError = await this.repository.findByCriteria(criteria, transaction); - if (customerInvoicesOrError.isFailure) { - return Result.fail(customerInvoicesOrError.error); - } - - // Solo devolver usuarios activos - //const allCustomerInvoices = customerInvoicesOrError.data.filter((customerInvoice) => customerInvoice.isActive); - //return Result.ok(new Collection(allCustomerInvoices)); - - return customerInvoicesOrError; + return this.repository.findByCriteriaInCompany(companyId, criteria, transaction); } /** * Recupera una factura por su identificador único. * - * @param id - Identificador UUID de la factura. + * @param invoiceId - Identificador UUID de la factura. * @param transaction - Transacción activa para la operación. * @returns Result - Factura encontrada o error. */ - async getById(id: UniqueID, transaction?: Transaction): Promise> { - return await this.repository.findById(id, transaction); + async getInvoiceByIdInCompany( + companyId: UniqueID, + invoiceId: UniqueID, + transaction?: Transaction + ): Promise> { + return await this.repository.getByIdInCompany(companyId, invoiceId, transaction); } /** * Actualiza parcialmente una factura existente con nuevos datos. + * No lo guarda en el repositorio. * - * @param id - Identificador de la factura a actualizar. + * @param companyId - Identificador de la empresa a la que pertenece el cliente. + * @param invoiceId - Identificador de la factura a actualizar. * @param changes - Subconjunto de props válidas para aplicar. * @param transaction - Transacción activa para la operación. * @returns Result - Factura actualizada o error. */ - async updateById( - customerInvoiceId: UniqueID, - changes: Partial, + async updateInvoiceByIdInCompany( + companyId: UniqueID, + invoiceId: UniqueID, + changes: CustomerInvoicePatchProps, transaction?: Transaction ): Promise> { // Verificar si la factura existe - const customerInvoiceOrError = await this.repository.getById(customerInvoiceId, transaction); - if (customerInvoiceOrError.isFailure) { - return Result.fail(new Error("CustomerInvoice not found")); + const invoiceResult = await this.getInvoiceByIdInCompany(companyId, invoiceId, transaction); + + if (invoiceResult.isFailure) { + return Result.fail(invoiceResult.error); } - return Result.fail(new Error("No implementado")); + const invoice = invoiceResult.data; + const updatedInvoice = invoice.update(changes); - /*const updatedCustomerInvoiceOrError = CustomerInvoice.update(customerInvoiceOrError.data, data); - if (updatedCustomerInvoiceOrError.isFailure) { - return Result.fail( - new Error(`Error updating customerInvoice: ${updatedCustomerInvoiceOrError.error.message}`) - ); + if (updatedInvoice.isFailure) { + return Result.fail(updatedInvoice.error); } - const updateCustomerInvoice = updatedCustomerInvoiceOrError.data; - - await this.repo.update(updateCustomerInvoice, transaction); - return Result.ok(updateCustomerInvoice);*/ - } - - async createCustomerInvoice( - customerInvoiceId: UniqueID, - data: CustomerInvoiceProps, - transaction?: Transaction - ): Promise> { - // Verificar si la factura existe - const customerInvoiceOrError = await this.repository.getById(customerInvoiceId, transaction); - if (customerInvoiceOrError.isSuccess) { - return Result.fail(new Error("CustomerInvoice exists")); - } - - const newCustomerInvoiceOrError = CustomerInvoice.create(data, customerInvoiceId); - if (newCustomerInvoiceOrError.isFailure) { - return Result.fail( - new Error(`Error creating customerInvoice: ${newCustomerInvoiceOrError.error.message}`) - ); - } - - const newCustomerInvoice = newCustomerInvoiceOrError.data; - - await this.repository.create(newCustomerInvoice, transaction); - return Result.ok(newCustomerInvoice); + return Result.ok(updatedInvoice.data); } /** * Elimina (o marca como eliminada) una factura según su ID. * - * @param id - Identificador UUID de la factura. + * @param companyId - Identificador de la empresa a la que pertenece el cliente. + * @param invoiceId - Identificador UUID de la factura. * @param transaction - Transacción activa para la operación. * @returns Result - Resultado de la operación. */ - async deleteById(id: UniqueID, transaction?: Transaction): Promise> { - return this.repository.deleteById(id, transaction); + async deleteById( + companyId: UniqueID, + invoiceId: UniqueID, + transaction?: Transaction + ): Promise> { + return this.repository.deleteByIdInCompany(companyId, invoiceId, transaction); } } diff --git a/modules/customer-invoices/src/api/domain/services/index.ts b/modules/customer-invoices/src/api/domain/services/index.ts index 9927fb2f..3b0a9973 100644 --- a/modules/customer-invoices/src/api/domain/services/index.ts +++ b/modules/customer-invoices/src/api/domain/services/index.ts @@ -1,2 +1 @@ -export * from "./customer-invoice-service.interface"; export * from "./customer-invoice.service"; diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-price.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-amount.ts similarity index 83% rename from modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-price.ts rename to modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-amount.ts index bf0ba908..13d4179d 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-price.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-amount.ts @@ -1,6 +1,6 @@ import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd"; -export class CustomerInvoiceItemSubtotalPrice extends MoneyValue { +export class CustomerInvoiceItemSubtotalAmount extends MoneyValue { public static DEFAULT_SCALE = 4; static create({ amount, currency_code, scale }: MoneyValueProps) { diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-price.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-amount.ts similarity index 83% rename from modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-price.ts rename to modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-amount.ts index 3a5d91de..07463a47 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-price.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 CustomerInvoiceItemTotalPrice extends MoneyValue { +export class CustomerInvoiceItemTotalAmount extends MoneyValue { public static DEFAULT_SCALE = 4; static create({ amount, currency_code, scale }: MoneyValueProps) { diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-unit-price.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-unit-amount.ts similarity index 84% rename from modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-unit-price.ts rename to modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-unit-amount.ts index f3499a0c..f85d6681 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-unit-price.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-unit-amount.ts @@ -1,6 +1,6 @@ import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd"; -export class CustomerInvoiceItemUnitPrice extends MoneyValue { +export class CustomerInvoiceItemUnitAmount extends MoneyValue { public static DEFAULT_SCALE = 4; static create({ amount, currency_code, scale }: MoneyValueProps) { @@ -12,7 +12,7 @@ export class CustomerInvoiceItemUnitPrice extends MoneyValue { return MoneyValue.create(props); } - static zero(currency_code: string, scale: number = CustomerInvoiceItemUnitPrice.DEFAULT_SCALE) { + static zero(currency_code: string, scale: number = CustomerInvoiceItemUnitAmount.DEFAULT_SCALE) { const props: MoneyValueProps = { amount: 0, scale, 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 382b9fb3..45c71057 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/index.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/index.ts @@ -2,9 +2,9 @@ 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-price"; -export * from "./customer-invoice-item-total-price"; -export * from "./customer-invoice-item-unit-price"; +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"; diff --git a/modules/customer-invoices/src/api/infrastructure/dependencies.ts b/modules/customer-invoices/src/api/infrastructure/dependencies.ts index 5693e5b3..9f59b6a2 100644 --- a/modules/customer-invoices/src/api/infrastructure/dependencies.ts +++ b/modules/customer-invoices/src/api/infrastructure/dependencies.ts @@ -4,7 +4,7 @@ import { CreateCustomerInvoiceUseCase, CreateCustomerInvoicesAssembler, DeleteCustomerInvoiceUseCase, - GetCustomerInvoiceAssembler, + GetCustomerInvoiceItemsAssembler, GetCustomerInvoiceUseCase, ListCustomerInvoicesAssembler, ListCustomerInvoicesUseCase, @@ -22,7 +22,7 @@ type InvoiceDeps = { service: ICustomerInvoiceService; assemblers: { list: ListCustomerInvoicesAssembler; - get: GetCustomerInvoiceAssembler; + get: GetCustomerInvoiceItemsAssembler; create: CreateCustomerInvoicesAssembler; update: UpdateCustomerInvoiceAssembler; }; @@ -54,7 +54,7 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps { if (!_assemblers) { _assemblers = { list: new ListCustomerInvoicesAssembler(), // transforma domain → ListDTO - get: new GetCustomerInvoiceAssembler(), // transforma domain → DetailDTO + get: new GetCustomerInvoiceItemsAssembler(), // transforma domain → DetailDTO create: new CreateCustomerInvoicesAssembler(), // transforma domain → CreatedDTO update: new UpdateCustomerInvoiceAssembler(), // transforma domain -> UpdateDTO }; diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/get-customer-invoice.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/get-customer-invoice.controller.ts index 2fc50f3b..ada27adb 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/get-customer-invoice.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/get-customer-invoice.controller.ts @@ -2,20 +2,17 @@ import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from import { GetCustomerInvoiceUseCase } from "../../../application"; export class GetCustomerInvoiceController extends ExpressController { - public constructor( - private readonly useCase: GetCustomerInvoiceUseCase - /* private readonly presenter: any */ - ) { + public constructor(private readonly useCase: GetCustomerInvoiceUseCase) { 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 { id } = this.req.params; + const companyId = this.getTenantId()!; // garantizado por tenantGuard + const { invoice_id } = this.req.params; - const result = await this.useCase.execute({ id, tenantId }); + const result = await this.useCase.execute({ invoice_id, companyId }); return result.match( (data) => this.ok(data), diff --git a/modules/customer-invoices/src/api/infrastructure/express/customer-invoices.routes.ts b/modules/customer-invoices/src/api/infrastructure/express/customer-invoices.routes.ts index c5b07685..96dc06f8 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/customer-invoices.routes.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/customer-invoices.routes.ts @@ -1,4 +1,4 @@ -import { RequestWithAuth, enforceTenant } from "@erp/auth/api"; +import { RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth/api"; import { ILogger, ModuleParams, validateRequest } from "@erp/core/api"; import { Application, NextFunction, Request, Response, Router } from "express"; import { Sequelize } from "sequelize"; @@ -24,17 +24,33 @@ export const customerInvoicesRouter = (params: ModuleParams) => { logger: ILogger; }; - const router: Router = Router({ mergeParams: true }); const deps = getInvoiceDependencies(params); + const router: Router = Router({ mergeParams: true }); + // 🔐 Autenticación + Tenancy para TODO el router - router.use(/* authenticateJWT(), */ enforceTenant() /*checkTabContext*/); + if (process.env.NODE_ENV === "development") { + router.use( + (req: Request, res: Response, next: NextFunction) => + mockUser(req as RequestWithAuth, res, next) // Debe ir antes de las rutas protegidas + ); + } + + router.use([ + (req: Request, res: Response, next: NextFunction) => + enforceUser()(req as RequestWithAuth, res, next), // Debe ir antes de las rutas protegidas + + (req: Request, res: Response, next: NextFunction) => + enforceTenant()(req as RequestWithAuth, res, next), // Debe ir antes de las rutas protegidas + ]); + + // ---------------------------------------------- router.get( "/", //checkTabContext, validateRequest(CustomerInvoiceListRequestSchema, "params"), - async (req: RequestWithAuth, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const useCase = deps.build.list(); const controller = new ListCustomerInvoicesController(useCase /*, deps.presenters.list */); return controller.execute(req, res, next); @@ -64,13 +80,15 @@ export const customerInvoicesRouter = (params: ModuleParams) => { } ); - /*routes.put( - "/:customerInvoiceId", - validateAndParseBody(IUpdateCustomerInvoiceRequestSchema), - checkTabContext, - + /*router.put( + "/:customer_id", + //checkTabContext, + validateRequest(UpdateCustomerInvoiceByIdParamsRequestSchema, "params"), + validateRequest(UpdateCustomerInvoiceByIdRequestSchema, "body"), (req: Request, res: Response, next: NextFunction) => { - buildUpdateCustomerInvoiceController().execute(req, res, next); + const useCase = deps.build.update(); + const controller = new UpdateCustomerInvoiceController(useCase); + return controller.execute(req, res, next); } );*/ 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 697f0b41..cc8487ea 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 @@ -8,7 +8,7 @@ import { CustomerInvoiceItemDescription, CustomerInvoiceItemDiscount, CustomerInvoiceItemQuantity, - CustomerInvoiceItemUnitPrice, + CustomerInvoiceItemUnitAmount, } from "../../domain"; import { CustomerInvoiceItemCreationAttributes, @@ -59,7 +59,7 @@ export class CustomerInvoiceItemMapper } // Validación y creación de precio unitario - const unitPriceOrError = CustomerInvoiceItemUnitPrice.create({ + const unitPriceOrError = CustomerInvoiceItemUnitAmount.create({ amount: source.unit_price_amount, scale: source.unit_price_scale, currency_code: sourceParent.invoice_currency, @@ -123,11 +123,11 @@ export class CustomerInvoiceItemMapper quantity_amount: source.quantity.toPrimitive().amount, quantity_scale: source.quantity.toPrimitive().scale, - unit_price_amount: source.unitPrice.toPrimitive().amount, - unit_price_scale: source.unitPrice.toPrimitive().scale, + unit_price_amount: source.unitAmount.toPrimitive().amount, + unit_price_scale: source.unitAmount.toPrimitive().scale, - subtotal_amount: source.subtotalPrice.toPrimitive().amount, - subtotal_scale: source.subtotalPrice.toPrimitive().scale, + subtotal_amount: source.subtotalAmount.toPrimitive().amount, + subtotal_scale: source.subtotalAmount.toPrimitive().scale, discount_amount: source.discount.toPrimitive().amount, discount_scale: source.discount.toPrimitive().scale, 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 cb5f9fed..cf65c46d 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 @@ -34,12 +34,28 @@ export class CustomerInvoiceModel extends Model< declare currency_code: string; // Subtotal - declare subtotal_amount: number; - declare subtotal_scale: number; + declare subtotal_amount_value: number; + declare subtotal_amount_scale: number; + + // Discount percentage + declare discount_percentage_value: number; + declare discount_percentage_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 tax amount / taxes total + declare tax_amount_value: number; + declare tax_amount_scale: number; // Total - declare total_amount: number; - declare total_scale: number; + declare total_amount_value: number; + declare total_amount_scale: number; // Relaciones declare items: NonAttribute; @@ -125,23 +141,70 @@ export default (database: Sequelize) => { defaultValue: "EUR", }, - 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: true, defaultValue: null, }, - total_amount: { + discount_percentage_value: { + type: new DataTypes.SMALLINT(), + allowNull: true, + defaultValue: null, + }, + + discount_percentage_scale: { + type: new DataTypes.SMALLINT(), + allowNull: true, + defaultValue: null, + }, + + discount_amount_value: { + type: new DataTypes.BIGINT(), + allowNull: true, + defaultValue: null, + }, + + discount_amount_scale: { + type: new DataTypes.SMALLINT(), + allowNull: true, + defaultValue: null, + }, + + taxable_amount_value: { type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes allowNull: true, defaultValue: null, }, - total_scale: { + taxable_amount_scale: { + type: new DataTypes.SMALLINT(), + allowNull: true, + defaultValue: null, + }, + + 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: true, + defaultValue: null, + }, + + 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: true, defaultValue: null, diff --git a/modules/customer-invoices/src/common/dto/request/get-customer-invoice-by-id.request.dto.ts b/modules/customer-invoices/src/common/dto/request/get-customer-invoice-by-id.request.dto.ts index e7f84ec6..b5eec449 100644 --- a/modules/customer-invoices/src/common/dto/request/get-customer-invoice-by-id.request.dto.ts +++ b/modules/customer-invoices/src/common/dto/request/get-customer-invoice-by-id.request.dto.ts @@ -1,7 +1,7 @@ import * as z from "zod/v4"; export const GetCustomerInvoiceByIdRequestSchema = z.object({ - id: z.string(), + invoice_id: z.string(), }); export type GetCustomerInvoiceByIdRequestDTO = z.infer; 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 5a0746ef..c1c13dd0 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 @@ -1,15 +1,32 @@ -import { MetadataSchema } from "@erp/core"; +import { AmountSchema, MetadataSchema, PercentageSchema, QuantitySchema } from "@erp/core"; import * as z from "zod/v4"; export const GetCustomerInvoiceByIdResponseSchema = z.object({ id: z.uuid(), - invoice_status: z.string(), + company_id: z.uuid(), + invoice_number: z.string(), - invoice_series: z.string(), - issue_date: z.iso.datetime({ offset: true }), - operation_date: z.iso.datetime({ offset: true }), + status: z.string(), + series: z.string(), + + issue_date: z.string(), + operation_date: z.string(), + + notes: z.string(), + language_code: z.string(), - currency: z.string(), + currency_code: z.string(), + + items: z.array( + z.object({ + position: z.string(), + description: z.string(), + quantity: QuantitySchema, + unit_price_amount: AmountSchema, + discount: PercentageSchema, + total_amount: AmountSchema, + }) + ), metadata: MetadataSchema.optional(), }); diff --git a/modules/customers/src/api/domain/services/customer.service.ts b/modules/customers/src/api/domain/services/customer.service.ts index 03f83c2e..092c9fba 100644 --- a/modules/customers/src/api/domain/services/customer.service.ts +++ b/modules/customers/src/api/domain/services/customer.service.ts @@ -90,14 +90,14 @@ export class CustomerService { * * @param companyId - Identificador de la empresa a la que pertenece el cliente. * @param customerId - Identificador del cliente a actualizar. - * @param partial - Subconjunto de props válidas para aplicar. + * @param changes - Subconjunto de props válidas para aplicar. * @param transaction - Transacción activa para la operación. * @returns Result - Cliente actualizado o error. */ async updateCustomerByIdInCompany( companyId: UniqueID, customerId: UniqueID, - partial: CustomerPatchProps, + changes: CustomerPatchProps, transaction?: any ): Promise> { const customerResult = await this.getCustomerByIdInCompany(companyId, customerId, transaction); @@ -107,7 +107,7 @@ export class CustomerService { } const customer = customerResult.data; - const updatedCustomer = customer.update(partial); + const updatedCustomer = customer.update(changes); if (updatedCustomer.isFailure) { return Result.fail(updatedCustomer.error); diff --git a/modules/customers/src/api/infrastructure/express/customers.routes.ts b/modules/customers/src/api/infrastructure/express/customers.routes.ts index 3fe0f7b8..4394cf88 100644 --- a/modules/customers/src/api/infrastructure/express/customers.routes.ts +++ b/modules/customers/src/api/infrastructure/express/customers.routes.ts @@ -1,4 +1,4 @@ -import { enforceTenant, enforceUser, mockUser } from "@erp/auth/api"; +import { RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth/api"; import { ILogger, ModuleParams, validateRequest } from "@erp/core/api"; import { Application, NextFunction, Request, Response, Router } from "express"; import { Sequelize } from "sequelize"; @@ -33,11 +33,22 @@ export const customersRouter = (params: ModuleParams) => { // 🔐 Autenticación + Tenancy para TODO el router if (process.env.NODE_ENV === "development") { - router.use(mockUser); // Debe ir antes de las rutas protegidas + router.use( + (req: Request, res: Response, next: NextFunction) => + mockUser(req as RequestWithAuth, res, next) // Debe ir antes de las rutas protegidas + ); } //router.use(/*authenticateJWT(),*/ enforceTenant() /*checkTabContext*/); - router.use([enforceUser(), enforceTenant()]); + router.use([ + (req: Request, res: Response, next: NextFunction) => + enforceUser()(req as RequestWithAuth, res, next), // Debe ir antes de las rutas protegidas + + (req: Request, res: Response, next: NextFunction) => + enforceTenant()(req as RequestWithAuth, res, next), // Debe ir antes de las rutas protegidas + ]); + + // ---------------------------------------------- router.get( "/", diff --git a/packages/rdx-utils/src/helpers/utils.ts b/packages/rdx-utils/src/helpers/utils.ts index 7714abd3..2aad1834 100644 --- a/packages/rdx-utils/src/helpers/utils.ts +++ b/packages/rdx-utils/src/helpers/utils.ts @@ -5,7 +5,7 @@ export function isNullishOrEmpty(input: unknown): boolean { } // Función genérica para asegurar valores básicos -function ensure(value: T | undefined | null, defaultValue: T): T { +export function ensure(value: T | undefined | null, defaultValue: T): T { return value ?? defaultValue; }