From 096abdccb2e8684d4cc32642a1acaf2df1daac97 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 17 Sep 2025 19:37:41 +0200 Subject: [PATCH] Clientes y facturas de cliente --- apps/web/src/app.tsx | 2 +- .../lib/data-source/datasource.interface.ts | 4 +- .../customer-invoice-items.full.presenter.ts | 5 +- .../domain/customer-invoice.full.presenter.ts | 10 + .../application/presenters/domain/index.ts | 1 + .../recipient-invoice.full.representer.ts | 45 ++++ .../templates/customer-invoice/template.hbs | 246 ++++++------------ .../api/domain/aggregates/customer-invoice.ts | 51 +++- .../customer-invoice-item.ts | 52 ++-- .../customer-invoice-items.ts | 30 ++- .../domain/value-objects/invoice-amount.ts | 36 ++- .../api/domain/value-objects/item-amount.ts | 39 ++- .../src/api/infrastructure/dependencies.ts | 8 + .../sequelize/customer-invoice.repository.ts | 1 + .../models/customer-invoice-item.model.ts | 4 +- ...get-customer-invoice-by-id.response.dto.ts | 13 + .../components/customer-editor-skeleton.tsx | 33 +++ .../src/web/components/error-alert.tsx | 16 ++ modules/customers/src/web/components/index.ts | 3 + .../src/web/components/not-found-card.tsx | 19 ++ .../src/web/constants/customer.constants.ts | 28 ++ modules/customers/src/web/constants/index.ts | 1 + .../web/hooks/use-create-customer-mutation.ts | 21 +- .../web/hooks/use-update-customer-mutation.ts | 38 +-- .../web/pages/create/customer-edit-form.tsx | 4 +- .../web/pages/update/customer-edit-form.tsx | 78 +++--- .../customers/src/web/pages/update/update.tsx | 107 +++----- .../src/web/schemas/customer.schema.ts | 9 +- .../rdx-ddd/src/value-objects/money-value.ts | 4 + packages/shadcn-ui/src/components/sonner.tsx | 16 +- packages/shadcn-ui/src/lib/utils.ts | 19 ++ 31 files changed, 573 insertions(+), 370 deletions(-) create mode 100644 modules/customer-invoices/src/api/application/presenters/domain/recipient-invoice.full.representer.ts create mode 100644 modules/customers/src/web/components/customer-editor-skeleton.tsx create mode 100644 modules/customers/src/web/components/error-alert.tsx create mode 100644 modules/customers/src/web/components/not-found-card.tsx create mode 100644 modules/customers/src/web/constants/customer.constants.ts create mode 100644 modules/customers/src/web/constants/index.ts diff --git a/apps/web/src/app.tsx b/apps/web/src/app.tsx index cf101870..34fa44bd 100644 --- a/apps/web/src/app.tsx +++ b/apps/web/src/app.tsx @@ -62,7 +62,7 @@ export const App = () => { - + {import.meta.env.DEV && } diff --git a/modules/core/src/web/lib/data-source/datasource.interface.ts b/modules/core/src/web/lib/data-source/datasource.interface.ts index b3e81b1c..b4119a1d 100644 --- a/modules/core/src/web/lib/data-source/datasource.interface.ts +++ b/modules/core/src/web/lib/data-source/datasource.interface.ts @@ -17,8 +17,8 @@ export interface IDataSource { getList(resource: string, params?: Record): Promise; getOne(resource: string, id: string | number): Promise; getMany(resource: string, ids: Array): Promise; - createOne(resource: string, data: Partial): Promise; - updateOne(resource: string, id: string | number, data: Partial): Promise; + createOne(resource: string, data: Partial): Promise; + updateOne(resource: string, id: string | number, data: Partial): Promise; deleteOne(resource: string, id: string | number): Promise; custom: (customParams: ICustomParams) => Promise; diff --git a/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice-items.full.presenter.ts b/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice-items.full.presenter.ts index 179b2063..d9bcdd23 100644 --- a/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice-items.full.presenter.ts +++ b/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice-items.full.presenter.ts @@ -30,8 +30,6 @@ export class CustomerInvoiceItemsFullPresenter extends Presenter { () => ({ value: "", scale: "", currency_code: "" }) ), - taxes: invoiceItem.taxes.getCodesToString(), - subtotal_amount: allAmounts.subtotalAmount.toObjectString(), discount_percentage: invoiceItem.discountPercentage.match( @@ -40,8 +38,11 @@ export class CustomerInvoiceItemsFullPresenter extends Presenter { ), discount_amount: allAmounts.discountAmount.toObjectString(), + taxable_amount: allAmounts.taxableAmount.toObjectString(), + taxes: invoiceItem.taxes.getCodesToString(), taxes_amount: allAmounts.taxesAmount.toObjectString(), + total_amount: allAmounts.totalAmount.toObjectString(), }; } diff --git a/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice.full.presenter.ts b/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice.full.presenter.ts index 2b6cca82..dffab74e 100644 --- a/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice.full.presenter.ts +++ b/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice.full.presenter.ts @@ -3,6 +3,7 @@ import { toEmptyString } from "@repo/rdx-ddd"; import { GetCustomerInvoiceByIdResponseDTO } from "../../../../common/dto"; import { CustomerInvoice } from "../../../domain"; import { CustomerInvoiceItemsFullPresenter } from "./customer-invoice-items.full.presenter"; +import { RecipientInvoiceFullPresenter } from "./recipient-invoice.full.representer"; export class CustomerInvoiceFullPresenter extends Presenter< CustomerInvoice, @@ -14,6 +15,12 @@ export class CustomerInvoiceFullPresenter extends Presenter< projection: "FULL", }) as CustomerInvoiceItemsFullPresenter; + const recipientPresenter = this.presenterRegistry.getPresenter({ + resource: "recipient-invoice", + projection: "FULL", + }) as RecipientInvoiceFullPresenter; + + const recipient = recipientPresenter.toOutput(invoice); const items = itemsPresenter.toOutput(invoice.items); const allAmounts = invoice.getAllAmounts(); @@ -33,6 +40,9 @@ export class CustomerInvoiceFullPresenter extends Presenter< language_code: invoice.languageCode.toString(), currency_code: invoice.currencyCode.toString(), + customer_id: invoice.customerId.toString(), + recipient, + taxes: invoice.taxes.getCodesToString(), subtotal_amount: allAmounts.subtotalAmount.toObjectString(), diff --git a/modules/customer-invoices/src/api/application/presenters/domain/index.ts b/modules/customer-invoices/src/api/application/presenters/domain/index.ts index 87f61fac..19f03545 100644 --- a/modules/customer-invoices/src/api/application/presenters/domain/index.ts +++ b/modules/customer-invoices/src/api/application/presenters/domain/index.ts @@ -1,2 +1,3 @@ export * from "./customer-invoice-items.full.presenter"; export * from "./customer-invoice.full.presenter"; +export * from "./recipient-invoice.full.representer"; diff --git a/modules/customer-invoices/src/api/application/presenters/domain/recipient-invoice.full.representer.ts b/modules/customer-invoices/src/api/application/presenters/domain/recipient-invoice.full.representer.ts new file mode 100644 index 00000000..1ed19743 --- /dev/null +++ b/modules/customer-invoices/src/api/application/presenters/domain/recipient-invoice.full.representer.ts @@ -0,0 +1,45 @@ +import { Presenter } from "@erp/core/api"; +import { DomainValidationError, toEmptyString } from "@repo/rdx-ddd"; +import { GetCustomerInvoiceByIdResponseDTO } from "../../../../common/dto"; +import { CustomerInvoice, InvoiceRecipient } from "../../../domain"; + +type GetRecipientInvoiceByInvoiceIdResponseDTO = GetCustomerInvoiceByIdResponseDTO["recipient"]; + +export class RecipientInvoiceFullPresenter extends Presenter { + toOutput(invoice: CustomerInvoice): GetRecipientInvoiceByInvoiceIdResponseDTO { + if (!invoice.recipient) { + throw DomainValidationError.requiredValue("recipient", { + cause: invoice, + }); + } + + return invoice.recipient.match( + (recipient: InvoiceRecipient) => { + return { + id: invoice.customerId.toString(), + name: recipient.name.toString(), + tin: recipient.tin.toString(), + street: toEmptyString(recipient.street, (value) => value.toString()), + street2: toEmptyString(recipient.street2, (value) => value.toString()), + city: toEmptyString(recipient.city, (value) => value.toString()), + province: toEmptyString(recipient.province, (value) => value.toString()), + postal_code: toEmptyString(recipient.postalCode, (value) => value.toString()), + country: toEmptyString(recipient.country, (value) => value.toString()), + }; + }, + () => { + return { + id: "", + name: "", + tin: "", + street: "", + street2: "", + city: "", + province: "", + postal_code: "", + country: "", + }; + } + ); + } +} diff --git a/modules/customer-invoices/src/api/application/use-cases/report/reporter/templates/customer-invoice/template.hbs b/modules/customer-invoices/src/api/application/use-cases/report/reporter/templates/customer-invoice/template.hbs index c2e62555..bbaba5ce 100644 --- a/modules/customer-invoices/src/api/application/use-cases/report/reporter/templates/customer-invoice/template.hbs +++ b/modules/customer-invoices/src/api/application/use-cases/report/reporter/templates/customer-invoice/template.hbs @@ -45,12 +45,13 @@ table { width: 100%; border-collapse: collapse; - margin-top: 25px; + margin-top: 0px; + margin-bottom: 15px; } table th, table td { - border: 1px solid #ccc; + border: 0px solid #ccc; padding: 10px; text-align: left; vertical-align: top; @@ -98,22 +99,21 @@
- - -
-
- -
-
- TOTAL: 960,56 € - - +
+
+ +
+ +
+
+ TOTAL: {{total_amount.value}} € +
+
-
- - - - - - - - - - + +
ConceptoCantidadPrecio unidadImporte total
+ + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ConceptoCantidadPrecio unidadImporte total
Mantenimiento de sistemas informáticos - Agosto (1 Equipo Servidor, 30 Ordenadores, 2 Impresoras, Disco - copias de seguridad)10,14 €0,14 €
Mantenimiento del programa FactuGES140,00 €40,00 €
Control VPN para portátil Rubén16,00 €6,00 €
Control VPN para portátil Míriam16,00 €6,00 €
Control VPN para portátil Fernando16,00 €6,00 €
Control VPN para portátil Elena16,00 €6,00 €
Control VPN para portátil Miguel16,00 €6,00 €
Control VPN para portátil Adrian16,00 €6,00 €
Control VPN para portátil David Lablanca16,00 €6,00 €
Control VPN para portátil Noemí16,00 €6,00 €
Control VPN para portátil John16,00 €6,00 €
Control VPN para portátil Eva16,00 €6,00 €
Control VPN para portátil Alberto16,00 €6,00 €
Mantenimiento mensual copia de seguridad remota y VPN (Uecko Madrid)140,00 €40,00 €
50% dto fidelización servicios contratados-120,00 €-20,00 €
Mantenimiento de presupuestador web para distribuidores (Agosto)1375,00 €375,00 €
Informe de compras de artículos (Presupuesto 22/04/25)1260,00 €260,00 €
Informe presupuestos cliente (Gunni Tentrino) – modificación funcionalidad visible. Sin cargo.10,00 €0,00 €
+ + {{#each items}} + + {{description}} + {{quantity.value}} + {{unit_amount.value}} € + {{total_amount.value}} € + + {{/each}} + + + - - - - - - - - - - - - - -
Base imponible:761,14 €
IVA (21%):159,84 €
Total factura:960,56 €
+
-
+
+
+

Forma de pago: {{payment_method}}

+
+
+

Notas: {{notes}}

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
Importe neto761,14 €
Descuento 0%0
Base imponible765,14€
IVA 21%159,84 €
Total factura960,56 €
+
+
+ + +
+
+ + 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 ddadea7a..006fc90f 100644 --- a/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts +++ b/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts @@ -1,7 +1,7 @@ -import { DomainValidationError } from "@erp/core/api"; import { AggregateRoot, CurrencyCode, + DomainValidationError, LanguageCode, Percentage, TextValue, @@ -168,8 +168,27 @@ export class CustomerInvoice return this.recipient.isSome(); } + private _getDiscountAmount(subtotalAmount: InvoiceAmount): InvoiceAmount { + return subtotalAmount.percentage(this.discountPercentage); + } + + private _getTaxableAmount( + subtotalAmount: InvoiceAmount, + discountAmount: InvoiceAmount + ): InvoiceAmount { + return subtotalAmount.subtract(discountAmount); + } + + private _getTaxesAmount(taxableAmount: InvoiceAmount): InvoiceAmount { + return this._taxes.getTaxesAmount(taxableAmount); + } + + private _getTotalAmount(taxableAmount: InvoiceAmount, taxesAmount: InvoiceAmount): InvoiceAmount { + return taxableAmount.add(taxesAmount); + } + public getSubtotalAmount(): InvoiceAmount { - const itemsSubtotal = this.items.getTotalAmount().convertScale(2); + const itemsSubtotal = this.items.getSubtotalAmount().convertScale(2); return InvoiceAmount.create({ value: itemsSubtotal.value, @@ -178,11 +197,11 @@ export class CustomerInvoice } public getDiscountAmount(): InvoiceAmount { - return this.getSubtotalAmount().percentage(this.discountPercentage) as InvoiceAmount; + return this._getDiscountAmount(this.getSubtotalAmount()); } public getTaxableAmount(): InvoiceAmount { - return this.getSubtotalAmount().subtract(this.getDiscountAmount()) as InvoiceAmount; + return this._getTaxableAmount(this.getSubtotalAmount(), this.getDiscountAmount()); } public getTaxesAmount(): InvoiceAmount { @@ -191,20 +210,24 @@ export class CustomerInvoice public getTotalAmount(): InvoiceAmount { const taxableAmount = this.getTaxableAmount(); - return taxableAmount.add(this._getTaxesAmount(taxableAmount)) as InvoiceAmount; + const taxesAmount = this._getTaxesAmount(taxableAmount); + + return this._getTotalAmount(taxableAmount, taxesAmount); } public getAllAmounts() { + const subtotalAmount = this.getSubtotalAmount(); + const discountAmount = this._getDiscountAmount(subtotalAmount); + const taxableAmount = this._getTaxableAmount(subtotalAmount, discountAmount); + const taxesAmount = this._getTaxesAmount(taxableAmount); + const totalAmount = this._getTotalAmount(taxableAmount, taxesAmount); + return { - subtotalAmount: this.getSubtotalAmount(), - discountAmount: this.getDiscountAmount(), - taxableAmount: this.getTaxableAmount(), - taxesAmount: this.getTaxesAmount(), - totalAmount: this.getTotalAmount(), + subtotalAmount, + discountAmount, + taxableAmount, + taxesAmount, + totalAmount, }; } - - private _getTaxesAmount(_taxableAmount: InvoiceAmount): InvoiceAmount { - return this._taxes.getTaxesAmount(_taxableAmount); - } } diff --git a/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts index 544d1e81..b8977891 100644 --- a/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts +++ b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts @@ -95,6 +95,26 @@ export class CustomerInvoiceItem return this.getProps(); } + private _getDiscountAmount(subtotalAmount: ItemAmount): ItemAmount { + const discount = this.discountPercentage.match( + (percentage) => percentage, + () => ItemDiscount.zero() + ); + return subtotalAmount.percentage(discount); + } + + private _getTaxableAmount(subtotalAmount: ItemAmount, discountAmount: ItemAmount): ItemAmount { + return subtotalAmount.subtract(discountAmount); + } + + private _getTaxesAmount(taxableAmount: ItemAmount): ItemAmount { + return this.props.taxes.getTaxesAmount(taxableAmount); + } + + private _getTotalAmount(taxableAmount: ItemAmount, taxesAmount: ItemAmount): ItemAmount { + return taxableAmount.add(taxesAmount); + } + public getSubtotalAmount(): ItemAmount { const curCode = this.currencyCode.code; const quantity = this.quantity.match( @@ -110,15 +130,11 @@ export class CustomerInvoiceItem } public getDiscountAmount(): ItemAmount { - const discount = this.discountPercentage.match( - (percentage) => percentage, - () => ItemDiscount.zero() - ); - return this.getSubtotalAmount().percentage(discount); + return this._getDiscountAmount(this.getSubtotalAmount()); } public getTaxableAmount(): ItemAmount { - return this.getSubtotalAmount().subtract(this.getDiscountAmount()); + return this._getTaxableAmount(this.getSubtotalAmount(), this.getDiscountAmount()); } public getTaxesAmount(): ItemAmount { @@ -127,20 +143,24 @@ export class CustomerInvoiceItem public getTotalAmount(): ItemAmount { const taxableAmount = this.getTaxableAmount(); - return taxableAmount.add(this._getTaxesAmount(taxableAmount)); + const taxesAmount = this._getTaxesAmount(taxableAmount); + + return this._getTotalAmount(taxableAmount, taxesAmount); } public getAllAmounts() { + const subtotalAmount = this.getSubtotalAmount(); + const discountAmount = this._getDiscountAmount(subtotalAmount); + const taxableAmount = this._getTaxableAmount(subtotalAmount, discountAmount); + const taxesAmount = this._getTaxesAmount(taxableAmount); + const totalAmount = this._getTotalAmount(taxableAmount, taxesAmount); + return { - subtotalAmount: this.getSubtotalAmount(), - discountAmount: this.getDiscountAmount(), - taxableAmount: this.getTaxableAmount(), - taxesAmount: this.getTaxesAmount(), - totalAmount: this.getTotalAmount(), + subtotalAmount, + discountAmount, + taxableAmount, + taxesAmount, + totalAmount, }; } - - private _getTaxesAmount(_taxableAmount: ItemAmount): ItemAmount { - return this.props.taxes.getTaxesAmount(_taxableAmount); - } } diff --git a/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-items.ts b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-items.ts index d79bd499..8710a2b8 100644 --- a/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-items.ts +++ b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-items.ts @@ -36,9 +36,37 @@ export class CustomerInvoiceItems extends Collection { return super.add(item); } + public getSubtotalAmount(): ItemAmount { + return this.getAll().reduce( + (total, tax) => total.add(tax.getSubtotalAmount()), + ItemAmount.zero(this._currencyCode.code) + ); + } + + public getDiscountAmount(): ItemAmount { + return this.getAll().reduce( + (total, item) => total.add(item.getDiscountAmount()), + ItemAmount.zero(this._currencyCode.code) + ); + } + + public getTaxableAmount(): ItemAmount { + return this.getAll().reduce( + (total, item) => total.add(item.getTaxableAmount()), + ItemAmount.zero(this._currencyCode.code) + ); + } + + public getTaxesAmount(): ItemAmount { + return this.getAll().reduce( + (total, item) => total.add(item.getTaxesAmount()), + ItemAmount.zero(this._currencyCode.code) + ); + } + public getTotalAmount(): ItemAmount { return this.getAll().reduce( - (total, tax) => total.add(tax.getTotalAmount()), + (total, item) => total.add(item.getTotalAmount()), ItemAmount.zero(this._currencyCode.code) ); } diff --git a/modules/customer-invoices/src/api/domain/value-objects/invoice-amount.ts b/modules/customer-invoices/src/api/domain/value-objects/invoice-amount.ts index a00f9d14..e4cbcae1 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/invoice-amount.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/invoice-amount.ts @@ -35,36 +35,60 @@ export class InvoiceAmount extends MoneyValue { convertScale(newScale: number) { const mv = super.convertScale(newScale); const p = mv.toPrimitive(); - return new InvoiceAmount({ value: p.value, currency_code: p.currency_code }); + return new InvoiceAmount({ + value: p.value, + currency_code: p.currency_code, + scale: InvoiceAmount.DEFAULT_SCALE, + }); } add(addend: MoneyValue) { const mv = super.add(addend); const p = mv.toPrimitive(); - return new InvoiceAmount({ value: p.value, currency_code: p.currency_code }); + return new InvoiceAmount({ + value: p.value, + currency_code: p.currency_code, + scale: InvoiceAmount.DEFAULT_SCALE, + }); } subtract(subtrahend: MoneyValue) { const mv = super.subtract(subtrahend); const p = mv.toPrimitive(); - return new InvoiceAmount({ value: p.value, currency_code: p.currency_code }); + return new InvoiceAmount({ + value: p.value, + currency_code: p.currency_code, + scale: InvoiceAmount.DEFAULT_SCALE, + }); } multiply(multiplier: number | Quantity) { const mv = super.multiply(multiplier); const p = mv.toPrimitive(); - return new InvoiceAmount({ value: p.value, currency_code: p.currency_code }); + return new InvoiceAmount({ + value: p.value, + currency_code: p.currency_code, + scale: InvoiceAmount.DEFAULT_SCALE, + }); } divide(divisor: number | Quantity) { const mv = super.divide(divisor); const p = mv.toPrimitive(); - return new InvoiceAmount({ value: p.value, currency_code: p.currency_code }); + return new InvoiceAmount({ + value: p.value, + currency_code: p.currency_code, + scale: InvoiceAmount.DEFAULT_SCALE, + }); } percentage(percentage: number | Percentage) { const mv = super.percentage(percentage); const p = mv.toPrimitive(); - return new InvoiceAmount({ value: p.value, currency_code: p.currency_code }); + return new InvoiceAmount({ + value: p.value, + currency_code: p.currency_code, + scale: InvoiceAmount.DEFAULT_SCALE, + }); } } diff --git a/modules/customer-invoices/src/api/domain/value-objects/item-amount.ts b/modules/customer-invoices/src/api/domain/value-objects/item-amount.ts index 6f63dfd0..04d22c79 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/item-amount.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/item-amount.ts @@ -35,36 +35,63 @@ export class ItemAmount extends MoneyValue { convertScale(newScale: number) { const mv = super.convertScale(newScale); const p = mv.toPrimitive(); - return new ItemAmount({ value: p.value, currency_code: p.currency_code }); + return new ItemAmount({ + value: p.value, + currency_code: p.currency_code, + scale: ItemAmount.DEFAULT_SCALE, + }); } add(addend: MoneyValue) { const mv = super.add(addend); const p = mv.toPrimitive(); - return new ItemAmount({ value: p.value, currency_code: p.currency_code }); + return new ItemAmount({ + value: p.value, + currency_code: p.currency_code, + scale: ItemAmount.DEFAULT_SCALE, + }); } subtract(subtrahend: MoneyValue) { const mv = super.subtract(subtrahend); const p = mv.toPrimitive(); - return new ItemAmount({ value: p.value, currency_code: p.currency_code }); + return new ItemAmount({ + value: p.value, + currency_code: p.currency_code, + scale: ItemAmount.DEFAULT_SCALE, + }); } multiply(multiplier: number | Quantity) { const mv = super.multiply(multiplier); const p = mv.toPrimitive(); - return new ItemAmount({ value: p.value, currency_code: p.currency_code }); + + const result = new ItemAmount({ + value: p.value, + currency_code: p.currency_code, + scale: ItemAmount.DEFAULT_SCALE, + }); + + return result; } divide(divisor: number | Quantity) { const mv = super.divide(divisor); const p = mv.toPrimitive(); - return new ItemAmount({ value: p.value, currency_code: p.currency_code }); + return new ItemAmount({ + value: p.value, + currency_code: p.currency_code, + scale: ItemAmount.DEFAULT_SCALE, + }); } percentage(percentage: number | Percentage) { const mv = super.percentage(percentage); const p = mv.toPrimitive(); - return new ItemAmount({ value: p.value, currency_code: p.currency_code }); + return new ItemAmount({ + value: p.value, + currency_code: p.currency_code, + scale: ItemAmount.DEFAULT_SCALE, + }); } } diff --git a/modules/customer-invoices/src/api/infrastructure/dependencies.ts b/modules/customer-invoices/src/api/infrastructure/dependencies.ts index 84b68c67..62a4716e 100644 --- a/modules/customer-invoices/src/api/infrastructure/dependencies.ts +++ b/modules/customer-invoices/src/api/infrastructure/dependencies.ts @@ -17,6 +17,7 @@ import { GetCustomerInvoiceUseCase, ListCustomerInvoicesPresenter, ListCustomerInvoicesUseCase, + RecipientInvoiceFullPresenter, ReportCustomerInvoiceUseCase, } from "../application"; @@ -77,6 +78,13 @@ export function buildCustomerInvoiceDependencies(params: ModuleParams): Customer }, presenter: new CustomerInvoiceItemsFullPresenter(presenterRegistry), }, + { + key: { + resource: "recipient-invoice", + projection: "FULL", + }, + presenter: new RecipientInvoiceFullPresenter(presenterRegistry), + }, { key: { resource: "customer-invoice", diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts index e1573ef0..038bf453 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts @@ -130,6 +130,7 @@ export class CustomerInvoiceRepository const row = await CustomerInvoiceModel.findOne({ where: { id: id.toString(), company_id: companyId.toString() }, + order: [[{ model: CustomerInvoiceItemModel, as: "items" }, "position", "ASC"]], include: [ { model: CustomerModel, diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice-item.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice-item.model.ts index ce08af9d..ea6f87a9 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice-item.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice-item.model.ts @@ -217,7 +217,9 @@ export default (database: Sequelize) => { whereMergeStrategy: "and", // <- cómo tratar el merge de un scope - defaultScope: {}, + defaultScope: { + order: [["position", "ASC"]], + }, scopes: {}, } 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 75fd924f..52df31e1 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 @@ -17,6 +17,19 @@ export const GetCustomerInvoiceByIdResponseSchema = z.object({ language_code: z.string(), currency_code: z.string(), + customer_id: z.string(), + recipient: z.object({ + id: z.string(), + name: z.string(), + tin: z.string(), + street: z.string(), + street2: z.string(), + city: z.string(), + province: z.string(), + postal_code: z.string(), + country: z.string(), + }), + taxes: z.string(), subtotal_amount: MoneySchema, diff --git a/modules/customers/src/web/components/customer-editor-skeleton.tsx b/modules/customers/src/web/components/customer-editor-skeleton.tsx new file mode 100644 index 00000000..bfc3eb87 --- /dev/null +++ b/modules/customers/src/web/components/customer-editor-skeleton.tsx @@ -0,0 +1,33 @@ +// components/CustomerSkeleton.tsx +import { AppBreadcrumb, AppContent, BackHistoryButton } from "@repo/rdx-ui/components"; +import { Button } from "@repo/shadcn-ui/components"; +import { useTranslation } from "../i18n"; + +export const CustomerEditorSkeleton = () => { + const { t } = useTranslation(); + return ( + <> + + +
+