From bba38e67f23722141553400dfda0ea129580beca Mon Sep 17 00:00:00 2001 From: david Date: Tue, 3 Mar 2026 17:00:32 +0100 Subject: [PATCH] Alta de proformas --- apps/server/package.json | 3 +- .../inputs/create-proforma-input.mapper.ts | 31 +++-- .../inputs/update-proforma-input.mapper.ts | 2 +- .../proformas/services/proforma-creator.ts | 34 +++--- .../create-proforma.use-case.ts | 10 +- .../aggregates/proforma.aggregate.ts | 70 ++++++------ .../proforma-items/proforma-item.entity.ts | 108 +++++++++--------- .../proforma-items.collection.ts | 26 ++++- modules/customer-invoices/src/api/index.ts | 3 +- .../src/api/infrastructure/express/index.ts | 2 - .../express/issued-invoices/index.ts | 2 - .../infrastructure/express/proformas/index.ts | 4 - .../get-issued-invoice-by-id.controller.ts | 4 +- .../list-issued-invoices.controller.ts | 4 +- .../report-issued-invoice.controller.ts | 4 +- .../issued-invoices-api-error-mapper.ts | 2 +- .../di/proforma-persistence-mappers.di.ts | 7 +- .../controllers/create-proforma.controller.ts | 4 +- .../controllers/delete-proforma.controller.ts | 4 +- .../controllers/get-proforma.controller.ts | 8 +- .../controllers/issue-proforma.controller.ts | 4 +- .../controllers/list-proformas.controller.ts | 4 +- .../controllers/report-proforma.controller.ts | 4 +- .../controllers/update-proforma.controller.ts | 4 +- .../infrastructure/proformas/express/index.ts | 1 + .../mappers/create-proforma-request-mapper.ts | 6 +- .../express}/proformas-api-error-mapper.ts | 9 +- .../express}/proformas.routes.ts | 21 ++-- .../proformas/create-proforma.request.dto.ts | 7 +- package.json | 3 - 30 files changed, 208 insertions(+), 187 deletions(-) delete mode 100644 modules/customer-invoices/src/api/infrastructure/express/index.ts delete mode 100644 modules/customer-invoices/src/api/infrastructure/express/issued-invoices/index.ts delete mode 100644 modules/customer-invoices/src/api/infrastructure/express/proformas/index.ts rename modules/customer-invoices/src/api/infrastructure/{express/issued-invoices => issued-invoices/express}/issued-invoices-api-error-mapper.ts (98%) rename modules/customer-invoices/src/api/infrastructure/{express/proformas => proformas/express}/proformas-api-error-mapper.ts (88%) rename modules/customer-invoices/src/api/infrastructure/{express/proformas => proformas/express}/proformas.routes.ts (95%) diff --git a/apps/server/package.json b/apps/server/package.json index a085fc3e..d47bdf51 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -8,8 +8,7 @@ "dev": "node --import=tsx --watch src/index.ts", "clean": "rimraf .turbo node_modules dist", "typecheck": "tsc --noEmit", - "lint": "biome check . && eslint .", - "lint:fix": "biome check --write . && eslint . --fix", + "lint": "biome lint --fix", "format": "biome format --write" }, "devDependencies": { diff --git a/modules/customer-invoices/src/api/application/proformas/mappers/inputs/create-proforma-input.mapper.ts b/modules/customer-invoices/src/api/application/proformas/mappers/inputs/create-proforma-input.mapper.ts index 721cc186..5029cf3f 100644 --- a/modules/customer-invoices/src/api/application/proformas/mappers/inputs/create-proforma-input.mapper.ts +++ b/modules/customer-invoices/src/api/application/proformas/mappers/inputs/create-proforma-input.mapper.ts @@ -41,13 +41,20 @@ import { * */ -export interface ICreateProformaInputMapper +/*export interface ICreateProformaInputMapper extends IDTOInputToPropsMapper< CreateProformaRequestDTO, { id: UniqueID; props: Omit & { items: IProformaItemProps[] } } - > {} + > {}*/ -export class CreateProformaInputMapper implements ICreateProformaInputMapper { +export interface ICreateProformaInputMapper { + map( + dto: CreateProformaRequestDTO, + params: { companyId: UniqueID } + ): Result<{ id: UniqueID; props: IProformaProps }>; +} + +export class CreateProformaInputMapper /*implements ICreateProformaInputMapper*/ { private readonly taxCatalog: JsonTaxCatalogProvider; constructor(params: { taxCatalog: JsonTaxCatalogProvider }) { @@ -138,14 +145,14 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper { const globalDiscountPercentage = extractOrPushError( Percentage.create({ - value: Number(dto.discount_percentage.value), - scale: Number(dto.discount_percentage.scale), + value: Number(dto.global_discount_percentage.value), + scale: Number(dto.global_discount_percentage.scale), }), "discount_percentage", errors ); - const items = this.mapItems(dto, { + const itemsProps = this.mapItemsProps(dto, { languageCode: languageCode!, currencyCode: currencyCode!, globalDiscountPercentage: globalDiscountPercentage!, @@ -158,7 +165,7 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper { ); } - const props: Omit & { items: IProformaItemProps[] } = { + const props: IProformaProps = { companyId, status: defaultStatus, @@ -181,7 +188,7 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper { paymentMethod: paymentMethod!, globalDiscountPercentage: globalDiscountPercentage!, - items, // ← IProformaItemProps[] + items: itemsProps, // ← IProformaItemProps[] }; return Result.ok({ @@ -193,7 +200,7 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper { } } - private mapItems( + private mapItemsProps( dto: CreateProformaRequestDTO, params: { languageCode: LanguageCode; @@ -224,12 +231,12 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper { ); const discountPercentage = extractOrPushError( - maybeFromNullableResult(item.discount_percentage, (v) => DiscountPercentage.create(v)), + maybeFromNullableResult(item.item_discount_percentage, (v) => DiscountPercentage.create(v)), `items[${index}].discount_percentage`, params.errors ); - const taxes = this.mapTaxes(item.taxes, { + const taxes = this.mapTaxesProps(item.taxes, { itemIndex: index, errors: params.errors, }); @@ -252,7 +259,7 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper { /* Devuelve las propiedades de los impustos de una línea de detalle */ - private mapTaxes( + private mapTaxesProps( taxesDTO: Pick["taxes"], params: { itemIndex: number; errors: ValidationErrorDetail[] } ): ProformaItemTaxesProps { diff --git a/modules/customer-invoices/src/api/application/proformas/mappers/inputs/update-proforma-input.mapper.ts b/modules/customer-invoices/src/api/application/proformas/mappers/inputs/update-proforma-input.mapper.ts index de4ab4c6..4aa6c288 100644 --- a/modules/customer-invoices/src/api/application/proformas/mappers/inputs/update-proforma-input.mapper.ts +++ b/modules/customer-invoices/src/api/application/proformas/mappers/inputs/update-proforma-input.mapper.ts @@ -1,4 +1,3 @@ -import { InvoiceSerie, type ProformaPatchProps } from "@erp/customer-invoices/api/domain"; import { CurrencyCode, DomainError, @@ -14,6 +13,7 @@ import { import { Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils"; import type { UpdateProformaByIdRequestDTO } from "../../../../../common/dto"; +import type { ProformaPatchProps } from "../../../../domain"; /** * UpdateProformaPropsMapper diff --git a/modules/customer-invoices/src/api/application/proformas/services/proforma-creator.ts b/modules/customer-invoices/src/api/application/proformas/services/proforma-creator.ts index f782bd69..07d2e1de 100644 --- a/modules/customer-invoices/src/api/application/proformas/services/proforma-creator.ts +++ b/modules/customer-invoices/src/api/application/proformas/services/proforma-creator.ts @@ -2,31 +2,31 @@ import type { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import type { Transaction } from "sequelize"; -import type { ICustomerInvoiceRepository } from "../../../domain"; -import type { CustomerInvoice, CustomerInvoiceProps } from "../../../domain/aggregates"; +import type { IProformaProps, Proforma } from "../../../domain"; import type { IProformaFactory } from "../factories"; +import type { IProformaRepository } from "../repositories"; import type { IProformaNumberGenerator } from "./proforma-number-generator.interface"; export interface IProformaCreator { - create( - companyId: UniqueID, - id: UniqueID, - props: CustomerInvoiceProps, - transaction: Transaction - ): Promise>; + create(params: { + companyId: UniqueID; + id: UniqueID; + props: IProformaProps; + transaction: Transaction; + }): Promise>; } type ProformaCreatorDeps = { numberService: IProformaNumberGenerator; factory: IProformaFactory; - repository: ICustomerInvoiceRepository; + repository: IProformaRepository; }; export class ProformaCreator implements IProformaCreator { private readonly numberService: IProformaNumberGenerator; private readonly factory: IProformaFactory; - private readonly repository: ICustomerInvoiceRepository; + private readonly repository: IProformaRepository; constructor(deps: ProformaCreatorDeps) { this.numberService = deps.numberService; @@ -34,12 +34,14 @@ export class ProformaCreator implements IProformaCreator { this.repository = deps.repository; } - async create( - companyId: UniqueID, - id: UniqueID, - props: CustomerInvoiceProps, - transaction: Transaction - ): Promise> { + async create(params: { + companyId: UniqueID; + id: UniqueID; + props: IProformaProps; + transaction: Transaction; + }): Promise> { + const { companyId, id, props, transaction } = params; + // 1. Obtener siguiente número const { series } = props; const numberResult = await this.numberService.getNextForCompany(companyId, series, transaction); diff --git a/modules/customer-invoices/src/api/application/proformas/use-cases/create-proforma/create-proforma.use-case.ts b/modules/customer-invoices/src/api/application/proformas/use-cases/create-proforma/create-proforma.use-case.ts index 1d30c053..cba7ba46 100644 --- a/modules/customer-invoices/src/api/application/proformas/use-cases/create-proforma/create-proforma.use-case.ts +++ b/modules/customer-invoices/src/api/application/proformas/use-cases/create-proforma/create-proforma.use-case.ts @@ -36,16 +36,16 @@ export class CreateProformaUseCase { const { dto, companyId } = params; // 1) Mapear DTO → props de dominio - const mappedResult = this.dtoMapper.map(dto, companyId); - if (mappedResult.isFailure) { - return Result.fail(mappedResult.error); + const mappedPropsResult = this.dtoMapper.map(dto, { companyId }); + if (mappedPropsResult.isFailure) { + return Result.fail(mappedPropsResult.error); } - const { props, id } = mappedResult.data; + const { props, id } = mappedPropsResult.data; return this.transactionManager.complete(async (transaction) => { try { - const createResult = await this.creator.create(companyId, id, props, transaction); + const createResult = await this.creator.create({ companyId, id, props, transaction }); if (createResult.isFailure) { return Result.fail(createResult.error); diff --git a/modules/customer-invoices/src/api/domain/proformas/aggregates/proforma.aggregate.ts b/modules/customer-invoices/src/api/domain/proformas/aggregates/proforma.aggregate.ts index 802f2e7a..d637850c 100644 --- a/modules/customer-invoices/src/api/domain/proformas/aggregates/proforma.aggregate.ts +++ b/modules/customer-invoices/src/api/domain/proformas/aggregates/proforma.aggregate.ts @@ -23,10 +23,9 @@ import { import { type IProformaItemProps, type IProformaItems, - type IProformaItemsProps, ProformaItem, ProformaItems, -} from "../entities/proforma-items"; +} from "../entities"; import { ProformaItemMismatch } from "../errors"; import { type IProformaTaxTotals, ProformaTaxesCalculator } from "../services"; import { ProformaItemTaxes } from "../value-objects"; @@ -53,7 +52,7 @@ export interface IProformaProps { paymentMethod: Maybe; - items: IProformaItemsProps[]; + items: IProformaItemProps[]; globalDiscountPercentage: DiscountPercentage; } @@ -96,21 +95,19 @@ export interface IProforma { paymentMethod: Maybe; - items: IProformaItems; + items: IProformaItems; // <- Colección taxes(): Collection; totals(): IProformaTotals; } export type ProformaPatchProps = Partial> & { - items?: ProformaItems; + //items?: ProformaItems; }; type CreateProformaProps = IProformaProps; type InternalProformaProps = Omit; export class Proforma extends AggregateRoot implements IProforma { - private readonly _items: ProformaItems; - // Creación funcional static create(props: CreateProformaProps, id?: UniqueID): Result { const { items, ...internalProps } = props; @@ -136,6 +133,8 @@ export class Proforma extends AggregateRoot implements IP return new Proforma(props, id); } + private readonly _items: ProformaItems; + protected constructor(props: InternalProformaProps, id?: UniqueID) { super(props, id); @@ -147,36 +146,7 @@ export class Proforma extends AggregateRoot implements IP }); } - // Mutabilidad - public update( - partialProforma: Partial> - ): Result { - const updatedProps = { - ...this.props, - ...partialProforma, - } as IProformaProps; - - return Proforma.create(updatedProps, this.id); - } - - public issue(): Result { - if (!this.props.status.canTransitionTo("ISSUED")) { - return Result.fail( - new DomainValidationError( - "INVALID_STATE", - "status", - "Proforma cannot be issued from current state" - ) - ); - } - - // Falta - //this.props.status = this.props.status.canTransitionTo("ISSUED"); - return Result.ok(); - } - // Getters - public get companyId(): UniqueID { return this.props.companyId; } @@ -249,6 +219,34 @@ export class Proforma extends AggregateRoot implements IP return this.paymentMethod.isSome(); } + // Mutabilidad + public update( + partialProforma: Partial> + ): Result { + const updatedProps = { + ...this.props, + ...partialProforma, + } as IProformaProps; + + return Proforma.create(updatedProps, this.id); + } + + public issue(): Result { + if (!this.props.status.canTransitionTo("ISSUED")) { + return Result.fail( + new DomainValidationError( + "INVALID_STATE", + "status", + "Proforma cannot be issued from current state" + ) + ); + } + + // Falta + //this.props.status = this.props.status.canTransitionTo("ISSUED"); + return Result.ok(); + } + // Cálculos /** diff --git a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-item.entity.ts b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-item.entity.ts index fb18c080..b90ed4db 100644 --- a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-item.entity.ts +++ b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-item.entity.ts @@ -217,6 +217,60 @@ export class ProformaItem extends DomainEntity implem return this.taxes.retention.map((tax) => tax.percentage); } + /** + * @summary Cálculo centralizado de todos los valores intermedios. + * @returns Devuelve un objeto inmutable con todos los valores necesarios: + * - subtotal + * - itemDiscount + * - globalDiscount + * - totalDiscount + * - taxableAmount + * - ivaAmount + * - recAmount + * - retentionAmount + * - taxesAmount + * - totalAmount + * + */ + public totals(): IProformaItemTotals { + const subtotalAmount = this._calculateSubtotalAmount(); + + const itemDiscountAmount = this._calculateItemDiscountAmount(subtotalAmount); + const globalDiscountAmount = this._calculateGlobalDiscountAmount( + subtotalAmount, + itemDiscountAmount + ); + const totalDiscountAmount = this._calculateTotalDiscountAmount( + itemDiscountAmount, + globalDiscountAmount + ); + + const taxableAmount = subtotalAmount.subtract(totalDiscountAmount); + + // Calcular impuestos individuales a partir de la base imponible + const { ivaAmount, recAmount, retentionAmount } = this.taxes.totals(taxableAmount); + + const taxesAmount = ivaAmount.add(recAmount).add(retentionAmount); + const totalAmount = taxableAmount.add(taxesAmount); + + return { + subtotalAmount, + + itemDiscountAmount, + globalDiscountAmount, + totalDiscountAmount, + + taxableAmount, + + ivaAmount, + recAmount, + retentionAmount, + + taxesAmount, + totalAmount, + }; + } + // Cálculos / Ayudantes /** @@ -276,58 +330,4 @@ export class ProformaItem extends DomainEntity implem ) { return itemDiscountAmount.add(globalDiscountAmount); } - - /** - * @summary Cálculo centralizado de todos los valores intermedios. - * @returns Devuelve un objeto inmutable con todos los valores necesarios: - * - subtotal - * - itemDiscount - * - globalDiscount - * - totalDiscount - * - taxableAmount - * - ivaAmount - * - recAmount - * - retentionAmount - * - taxesAmount - * - totalAmount - * - */ - public totals(): IProformaItemTotals { - const subtotalAmount = this._calculateSubtotalAmount(); - - const itemDiscountAmount = this._calculateItemDiscountAmount(subtotalAmount); - const globalDiscountAmount = this._calculateGlobalDiscountAmount( - subtotalAmount, - itemDiscountAmount - ); - const totalDiscountAmount = this._calculateTotalDiscountAmount( - itemDiscountAmount, - globalDiscountAmount - ); - - const taxableAmount = subtotalAmount.subtract(totalDiscountAmount); - - // Calcular impuestos individuales a partir de la base imponible - const { ivaAmount, recAmount, retentionAmount } = this.taxes.totals(taxableAmount); - - const taxesAmount = ivaAmount.add(recAmount).add(retentionAmount); - const totalAmount = taxableAmount.add(taxesAmount); - - return { - subtotalAmount, - - itemDiscountAmount, - globalDiscountAmount, - totalDiscountAmount, - - taxableAmount, - - ivaAmount, - recAmount, - retentionAmount, - - taxesAmount, - totalAmount, - }; - } } diff --git a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-items.collection.ts b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-items.collection.ts index ef769637..bddde6d6 100644 --- a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-items.collection.ts +++ b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-items.collection.ts @@ -6,14 +6,14 @@ import { ProformaItemMismatch } from "../../errors"; import { ProformaItemsTotalsCalculator } from "../../services/proforma-items-totals-calculator"; import type { - ICreateProformaItemProps, IProformaItem, + IProformaItemProps, IProformaItemTotals, ProformaItem, } from "./proforma-item.entity"; export interface IProformaItemsProps { - items?: ICreateProformaItemProps[]; + items: IProformaItemProps[]; // Estos campos vienen de la cabecera, // pero se necesitan para cálculos y representaciones de la línea. @@ -22,6 +22,9 @@ export interface IProformaItemsProps { currencyCode: CurrencyCode; // Para cálculos y formateos de moneda } +type CreateProformaProps = IProformaItemsProps; +type InternalProformaProps = Omit; + export interface IProformaItems { // OJO, no extendemos de Collection para no exponer // públicamente métodos para manipular la colección. @@ -35,12 +38,16 @@ export interface IProformaItems { } export class ProformaItems extends Collection implements IProformaItems { + static create(props: CreateProformaProps): ProformaItems { + return new ProformaItems(props); + } + public readonly languageCode!: LanguageCode; public readonly currencyCode!: CurrencyCode; public readonly globalDiscountPercentage!: DiscountPercentage; - constructor(props: IProformaItemsProps) { - super(props.items ?? []); + protected constructor(props: InternalProformaProps) { + super([]); this.languageCode = props.languageCode; this.currencyCode = props.currencyCode; this.globalDiscountPercentage = props.globalDiscountPercentage; @@ -48,8 +55,15 @@ export class ProformaItems extends Collection implements IProforma this.ensureSameCurrencyAndLanguage(this.items); } - public static create(props: IProformaItemsProps): ProformaItems { - return new ProformaItems(props); + public add(item: ProformaItem): boolean { + const same = + this.languageCode.equals(item.languageCode) && + this.currencyCode.equals(item.currencyCode) && + this.globalDiscountPercentage.equals(item.globalDiscountPercentage); + + if (!same) return false; + + return super.add(item); } public valued(): IProformaItem[] { diff --git a/modules/customer-invoices/src/api/index.ts b/modules/customer-invoices/src/api/index.ts index 33fd4cde..53d7501c 100644 --- a/modules/customer-invoices/src/api/index.ts +++ b/modules/customer-invoices/src/api/index.ts @@ -7,9 +7,10 @@ import { buildIssuedInvoicesDependencies, buildProformaServices, buildProformasDependencies, + issuedInvoicesRouter, models, + proformasRouter, } from "./infrastructure"; -import { issuedInvoicesRouter, proformasRouter } from "./infrastructure/express"; export const customerInvoicesAPIModule: IModuleServer = { name: "customer-invoices", diff --git a/modules/customer-invoices/src/api/infrastructure/express/index.ts b/modules/customer-invoices/src/api/infrastructure/express/index.ts deleted file mode 100644 index de2b09ca..00000000 --- a/modules/customer-invoices/src/api/infrastructure/express/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./issued-invoices"; -export * from "./proformas"; diff --git a/modules/customer-invoices/src/api/infrastructure/express/issued-invoices/index.ts b/modules/customer-invoices/src/api/infrastructure/express/issued-invoices/index.ts deleted file mode 100644 index bf22fa3e..00000000 --- a/modules/customer-invoices/src/api/infrastructure/express/issued-invoices/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "../../issued-invoices/express/controllers"; -export * from "../../issued-invoices/express/issued-invoices.routes"; diff --git a/modules/customer-invoices/src/api/infrastructure/express/proformas/index.ts b/modules/customer-invoices/src/api/infrastructure/express/proformas/index.ts deleted file mode 100644 index 2cb86132..00000000 --- a/modules/customer-invoices/src/api/infrastructure/express/proformas/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "../../proformas/express/controllers"; - -export * from "./proformas.routes"; -export * from "./proformas-api-error-mapper"; diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/controllers/get-issued-invoice-by-id.controller.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/controllers/get-issued-invoice-by-id.controller.ts index b42243cc..e7198c74 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/controllers/get-issued-invoice-by-id.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/controllers/get-issued-invoice-by-id.controller.ts @@ -7,12 +7,12 @@ import { import { GetIssuedInvoiceByIdResponseSchema } from "../../../../../common/index.ts"; import type { GetIssuedInvoiceByIdUseCase } from "../../../../application/issued-invoices/index.ts"; -import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts"; +import { proformasApiErrorMapper } from "../../../proformas/express/proformas-api-error-mapper.ts"; export class GetIssuedInvoiceByIdController extends ExpressController { public constructor(private readonly useCase: GetIssuedInvoiceByIdUseCase) { super(); - this.errorMapper = customerInvoicesApiErrorMapper; + this.errorMapper = proformasApiErrorMapper; // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards( diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/controllers/list-issued-invoices.controller.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/controllers/list-issued-invoices.controller.ts index 4acf8187..e4bdd2aa 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/controllers/list-issued-invoices.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/controllers/list-issued-invoices.controller.ts @@ -8,12 +8,12 @@ import { Criteria } from "@repo/rdx-criteria/server"; import { ListIssuedInvoicesResponseSchema } from "../../../../../common/index.ts"; import type { ListIssuedInvoicesUseCase } from "../../../../application/issued-invoices/index.ts"; -import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts"; +import { proformasApiErrorMapper } from "../../../proformas/express/proformas-api-error-mapper.ts"; export class ListIssuedInvoicesController extends ExpressController { public constructor(private readonly useCase: ListIssuedInvoicesUseCase) { super(); - this.errorMapper = customerInvoicesApiErrorMapper; + this.errorMapper = proformasApiErrorMapper; // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards( diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/controllers/report-issued-invoice.controller.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/controllers/report-issued-invoice.controller.ts index e223c717..ebeed65b 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/controllers/report-issued-invoice.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/controllers/report-issued-invoice.controller.ts @@ -8,12 +8,12 @@ import { import type { ReportIssueInvoiceByIdQueryRequestDTO } from "../../../../../common"; import type { ReportIssuedInvoiceUseCase } from "../../../../application/index.ts"; -import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts"; +import { proformasApiErrorMapper } from "../../../proformas/express/proformas-api-error-mapper.ts"; export class ReportIssuedInvoiceController extends ExpressController { public constructor(private readonly useCase: ReportIssuedInvoiceUseCase) { super(); - this.errorMapper = customerInvoicesApiErrorMapper; + this.errorMapper = proformasApiErrorMapper; // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards( diff --git a/modules/customer-invoices/src/api/infrastructure/express/issued-invoices/issued-invoices-api-error-mapper.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/issued-invoices-api-error-mapper.ts similarity index 98% rename from modules/customer-invoices/src/api/infrastructure/express/issued-invoices/issued-invoices-api-error-mapper.ts rename to modules/customer-invoices/src/api/infrastructure/issued-invoices/express/issued-invoices-api-error-mapper.ts index 0ffc0aa4..243ac245 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/issued-invoices/issued-invoices-api-error-mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/express/issued-invoices-api-error-mapper.ts @@ -18,7 +18,7 @@ import { isInvalidProformaTransitionError, isProformaCannotBeConvertedToInvoiceError, isProformaCannotBeDeletedError, -} from "../../domain"; +} from "../../../domain"; // Crea una regla específica (prioridad alta para sobreescribir mensajes) const invoiceDuplicateRule: ErrorToApiRule = { diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-persistence-mappers.di.ts b/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-persistence-mappers.di.ts index e5bb51a8..3bda0abd 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-persistence-mappers.di.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-persistence-mappers.di.ts @@ -1,4 +1,9 @@ -import type { ICatalogs, IProformaDomainMapper, IProformaListMapper } from "../../../application"; +import { + CreateProformaInputMapper, + type ICatalogs, + type IProformaDomainMapper, + type IProformaListMapper, +} from "../../../application"; import { SequelizeProformaDomainMapper, SequelizeProformaListMapper } from "../persistence"; export interface IProformaPersistenceMappers { diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/create-proforma.controller.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/create-proforma.controller.ts index 1f8a1903..af5940a2 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/create-proforma.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/create-proforma.controller.ts @@ -7,12 +7,12 @@ import { import type { CreateProformaRequestDTO } from "../../../../../common/dto/index.ts"; import type { CreateProformaUseCase } from "../../../../application/index.ts"; -import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts"; +import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts"; export class CreateProformaController extends ExpressController { public constructor(private readonly useCase: CreateProformaUseCase) { super(); - this.errorMapper = customerInvoicesApiErrorMapper; + this.errorMapper = proformasApiErrorMapper; // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards( diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/delete-proforma.controller.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/delete-proforma.controller.ts index 84f0aa51..39fe30c5 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/delete-proforma.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/delete-proforma.controller.ts @@ -6,12 +6,12 @@ import { } from "@erp/core/api"; import type { DeleteProformaUseCase } from "../../../../application/index.ts"; -import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts"; +import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts"; export class DeleteProformaController extends ExpressController { public constructor(private readonly useCase: DeleteProformaUseCase) { super(); - this.errorMapper = customerInvoicesApiErrorMapper; + this.errorMapper = proformasApiErrorMapper; // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards( diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/get-proforma.controller.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/get-proforma.controller.ts index a25dc49d..de06d707 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/get-proforma.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/get-proforma.controller.ts @@ -5,13 +5,13 @@ import { requireCompanyContextGuard, } from "@erp/core/api"; -import type { GetProformaUseCase } from "../../../../application/index.ts"; -import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts"; +import type { GetProformaByIdUseCase } from "../../../../application"; +import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts"; export class GetProformaController extends ExpressController { - public constructor(private readonly useCase: GetProformaUseCase) { + public constructor(private readonly useCase: GetProformaByIdUseCase) { super(); - this.errorMapper = customerInvoicesApiErrorMapper; + this.errorMapper = proformasApiErrorMapper; // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards( diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/issue-proforma.controller.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/issue-proforma.controller.ts index 21520ef2..e1b2fb65 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/issue-proforma.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/issue-proforma.controller.ts @@ -6,12 +6,12 @@ import { } from "@erp/core/api"; import type { IssueProformaUseCase } from "../../../../application/index.ts"; -import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts"; +import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts"; export class IssueProformaController extends ExpressController { public constructor(private readonly useCase: IssueProformaUseCase) { super(); - this.errorMapper = customerInvoicesApiErrorMapper; + this.errorMapper = proformasApiErrorMapper; // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards( diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/list-proformas.controller.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/list-proformas.controller.ts index 07803550..43860c75 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/list-proformas.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/list-proformas.controller.ts @@ -7,12 +7,12 @@ import { import { Criteria } from "@repo/rdx-criteria/server"; import type { ListProformasUseCase } from "../../../../application/index.ts"; -import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts"; +import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts"; export class ListProformasController extends ExpressController { public constructor(private readonly useCase: ListProformasUseCase) { super(); - this.errorMapper = customerInvoicesApiErrorMapper; + this.errorMapper = proformasApiErrorMapper; // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards( diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/report-proforma.controller.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/report-proforma.controller.ts index 851e8efb..477dd10b 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/report-proforma.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/report-proforma.controller.ts @@ -8,12 +8,12 @@ import { import type { ReportProformaByIdQueryRequestDTO } from "../../../../../common"; import type { ReportProformaUseCase } from "../../../../application/index.ts"; -import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts"; +import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts"; export class ReportProformaController extends ExpressController { public constructor(private readonly useCase: ReportProformaUseCase) { super(); - this.errorMapper = customerInvoicesApiErrorMapper; + this.errorMapper = proformasApiErrorMapper; // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards( diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/update-proforma.controller.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/update-proforma.controller.ts index fec8ab62..98f79577 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/update-proforma.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/update-proforma.controller.ts @@ -7,12 +7,12 @@ import { import type { UpdateProformaByIdRequestDTO } from "../../../../../common/dto/index.ts"; import type { UpdateProformaUseCase } from "../../../../application/index.ts"; -import { customerInvoicesApiErrorMapper } from "../../../express/proformas/proformas-api-error-mapper.ts"; +import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts"; export class UpdateProformaController extends ExpressController { public constructor(private readonly useCase: UpdateProformaUseCase) { super(); - this.errorMapper = customerInvoicesApiErrorMapper; + this.errorMapper = proformasApiErrorMapper; // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards( diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/express/index.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/index.ts index 6b67c80e..7ae6a44c 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/express/index.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/index.ts @@ -1 +1,2 @@ export * from "./controllers"; +export * from "./proformas.routes"; diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/express/mappers/create-proforma-request-mapper.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/mappers/create-proforma-request-mapper.ts index 345890d4..7b33d845 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/express/mappers/create-proforma-request-mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/mappers/create-proforma-request-mapper.ts @@ -134,8 +134,8 @@ export class CreateProformaRequestMapper { const globalDiscountPercentage = extractOrPushError( Percentage.create({ - value: Number(dto.discount_percentage.value), - scale: Number(dto.discount_percentage.scale), + value: Number(dto.global_discount_percentage.value), + scale: Number(dto.global_discount_percentage.scale), }), "discount_percentage", this.errors @@ -209,7 +209,7 @@ export class CreateProformaRequestMapper { ); const discountPercentage = extractOrPushError( - maybeFromNullableResult(item.discount_percentage, (value) => + maybeFromNullableResult(item.item_discount_percentage, (value) => ItemDiscountPercentage.create(value) ), "discount_percentage", diff --git a/modules/customer-invoices/src/api/infrastructure/express/proformas/proformas-api-error-mapper.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/proformas-api-error-mapper.ts similarity index 88% rename from modules/customer-invoices/src/api/infrastructure/express/proformas/proformas-api-error-mapper.ts rename to modules/customer-invoices/src/api/infrastructure/proformas/express/proformas-api-error-mapper.ts index ae7c7cf2..600586f2 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/proformas/proformas-api-error-mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/proformas-api-error-mapper.ts @@ -1,6 +1,3 @@ -// Ejemplo: regla específica para Billing → InvoiceIdAlreadyExistsError -// (si defines un error más ubicuo dentro del BC con su propia clase) - import { ApiErrorMapper, ConflictApiError, @@ -23,7 +20,7 @@ import { } from "../../../domain"; // Crea una regla específica (prioridad alta para sobreescribir mensajes) -const invoiceDuplicateRule: ErrorToApiRule = { +const proformaDuplicateRule: ErrorToApiRule = { priority: 120, matches: (e) => isCustomerInvoiceIdAlreadyExistsError(e), build: (e) => @@ -81,8 +78,8 @@ const proformaCannotBeDeletedRule: ErrorToApiRule = { }; // Cómo aplicarla: crea una nueva instancia del mapper con la regla extra -export const customerInvoicesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default() - .register(invoiceDuplicateRule) +export const proformasApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default() + .register(proformaDuplicateRule) .register(proformaItemMismatchError) .register(entityIsNotProformaError) .register(proformaConversionRule) diff --git a/modules/customer-invoices/src/api/infrastructure/express/proformas/proformas.routes.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/proformas.routes.ts similarity index 95% rename from modules/customer-invoices/src/api/infrastructure/express/proformas/proformas.routes.ts rename to modules/customer-invoices/src/api/infrastructure/proformas/express/proformas.routes.ts index be9aae6f..a5e601e7 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/proformas/proformas.routes.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/proformas.routes.ts @@ -3,17 +3,21 @@ import { type ModuleParams, type RequestWithAuth, validateRequest } from "@erp/c import { type NextFunction, type Request, type Response, Router } from "express"; import { + GetProformaController, + ListProformasController, + type ProformasInternalDeps, + ReportProformaController, +} from ".."; + +import { + CreateProformaRequestSchema, GetProformaByIdRequestSchema, ListProformasRequestSchema, ReportProformaByIdParamsRequestSchema, ReportProformaByIdQueryRequestSchema, } from "../../../../common"; -import { - GetProformaController, - ListProformasController, - type ProformasInternalDeps, - ReportProformaController, -} from "../../proformas"; + +import { CreateProformaController } from "./controllers/create-proforma.controller"; export const proformasRouter = (params: ModuleParams, deps: ProformasInternalDeps) => { const { app, config } = params; @@ -73,18 +77,19 @@ export const proformasRouter = (params: ModuleParams, deps: ProformasInternalDep } ); - /*router.post( + router.post( "/", //checkTabContext, validateRequest(CreateProformaRequestSchema, "body"), (req: Request, res: Response, next: NextFunction) => { - const useCase = deps.useCases.create_proforma(); + const useCase = deps.useCases.createProforma(); const controller = new CreateProformaController(useCase); return controller.execute(req, res, next); } ); + /* router.put( "/:proforma_id", //checkTabContext, diff --git a/modules/customer-invoices/src/common/dto/request/proformas/create-proforma.request.dto.ts b/modules/customer-invoices/src/common/dto/request/proformas/create-proforma.request.dto.ts index d0563c8a..30e7d5a7 100644 --- a/modules/customer-invoices/src/common/dto/request/proformas/create-proforma.request.dto.ts +++ b/modules/customer-invoices/src/common/dto/request/proformas/create-proforma.request.dto.ts @@ -7,7 +7,10 @@ export const CreateProformaItemRequestSchema = z.object({ description: z.string().default(""), quantity: NumericStringSchema.default(""), unit_amount: NumericStringSchema.default(""), - discount_percentage: NumericStringSchema.default(""), + item_discount_percentage: PercentageSchema.default({ + value: "0", + scale: "2", + }), taxes: z.string().default(""), }); export type CreateProformaItemRequestDTO = z.infer; @@ -29,7 +32,7 @@ export const CreateProformaRequestSchema = z.object({ language_code: z.string().toLowerCase().default("es"), currency_code: z.string().toUpperCase().default("EUR"), - discount_percentage: PercentageSchema.default({ + global_discount_percentage: PercentageSchema.default({ value: "0", scale: "2", }), diff --git a/package.json b/package.json index 3645516c..c0230898 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,6 @@ "dev": "turbo dev", "dev:server": "turbo dev --filter=server", "dev:client": "turbo dev --filter=client", - "lint": "turbo run lint", - "lint:fix": "turbo run lint:fix", "format-and-lint": "biome check .", "format-and-lint:fix": "biome check . --write", "ui:add": "pnpm --filter @repo/shadcn-ui ui:add", @@ -28,7 +26,6 @@ "@typescript-eslint/eslint-plugin": "^8.56.1", "@typescript-eslint/parser": "^8.56.1", "change-case": "^5.4.4", - "eslint": "^10.0.2", "inquirer": "^12.10.0", "plop": "^4.0.4", "rimraf": "^5.0.5",