Facturas de cliente

This commit is contained in:
David Arranz 2025-09-18 10:49:32 +02:00
parent 096abdccb2
commit ac11eaffdf
11 changed files with 84 additions and 26 deletions

View File

@ -0,0 +1,12 @@
import { MoneyDTO } from "@erp/core";
import { MoneyValue } from "@repo/rdx-ddd";
export function formatMoneyDTO(amount: MoneyDTO, locale: string) {
const money = MoneyValue.create({
value: Number(amount.value),
currency_code: amount.currency_code,
scale: Number(amount.scale),
}).data;
return money.format(locale);
}

View File

@ -1 +1,2 @@
export * from "./format-money-dto";
export * from "./map-dto-to-customer-invoice-props";

View File

@ -1,16 +1,21 @@
import { ValidationErrorCollection, ValidationErrorDetail } from "@repo/rdx-ddd";
import {
ValidationErrorCollection,
ValidationErrorDetail,
extractOrPushError,
} from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { CreateCustomerInvoiceRequestDTO } from "../../../common";
import {
CustomerInvoiceItem,
CustomerInvoiceItemDescription,
ItemAmount,
ItemDiscount,
ItemQuantity,
} from "../../domain";
import { extractOrPushError } from "./extract-or-push-error";
import { hasNoUndefinedFields } from "./has-no-undefined-fields";
export function mapDTOToCustomerInvoiceItemsProps(
dtoItems: Pick<CreateCustomerInvoiceCommandDTO, "items">["items"]
dtoItems: Pick<CreateCustomerInvoiceRequestDTO, "items">["items"]
): Result<CustomerInvoiceItem[], ValidationErrorCollection> {
const errors: ValidationErrorDetail[] = [];
const items: CustomerInvoiceItem[] = [];
@ -25,9 +30,8 @@ export function mapDTOToCustomerInvoiceItemsProps(
);
const quantity = extractOrPushError(
CustomerInvoiceItemQuantity.create({
amount: item.quantity.amount,
scale: item.quantity.scale,
ItemQuantity.create({
value: Number(item.quantity),
}),
path("quantity"),
errors

View File

@ -1,14 +1,20 @@
import { ValidationErrorCollection, ValidationErrorDetail } from "@erp/core/api";
import { UniqueID, UtcDate } from "@repo/rdx-ddd";
import {
CurrencyCode,
UniqueID,
UtcDate,
ValidationErrorCollection,
ValidationErrorDetail,
extractOrPushError,
maybeFromNullableVO,
} from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { CreateCustomerInvoiceCommandDTO } from "../../../common/dto";
import { CreateCustomerInvoiceRequestDTO } from "../../../common";
import {
CustomerInvoiceNumber,
CustomerInvoiceProps,
CustomerInvoiceSerie,
CustomerInvoiceStatus,
} from "../../domain";
import { extractOrPushError } from "./extract-or-push-error";
import { mapDTOToCustomerInvoiceItemsProps } from "./map-dto-to-customer-invoice-items-props";
/**
@ -21,7 +27,7 @@ import { mapDTOToCustomerInvoiceItemsProps } from "./map-dto-to-customer-invoice
*
*/
export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDTO) {
export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceRequestDTO) {
const errors: ValidationErrorDetail[] = [];
const invoiceId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
@ -32,7 +38,7 @@ export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDT
errors
);
const invoiceSeries = extractOrPushError(
CustomerInvoiceSerie.create(dto.invoice_series),
maybeFromNullableVO(dto.invoice_series, (value) => CustomerInvoiceSerie.create(value)),
"invoice_series",
errors
);
@ -42,13 +48,16 @@ export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDT
errors
);
const operationDate = extractOrPushError(
UtcDate.createFromISO(dto.operation_date),
maybeFromNullableVO(dto.operation_date, (value) => UtcDate.createFromISO(value)),
"operation_date",
errors
);
//const currency = extractOrPushError(Currency.(dto.currency), "currency", errors);
const currency = dto.currency;
const currencyCode = extractOrPushError(
CurrencyCode.create(dto.currency_code),
"currency",
errors
);
// 🔄 Validar y construir los items de factura con helper especializado
const itemsResult = mapDTOToCustomerInvoiceItemsProps(dto.items);
@ -57,16 +66,16 @@ export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDT
}
if (errors.length > 0) {
return Result.fail(new ValidationErrorCollection(errors));
return Result.fail(new ValidationErrorCollection("Customer dto mapping failed", errors));
}
const invoiceProps: CustomerInvoiceProps = {
invoiceNumber: invoiceNumber!,
invoiceSeries: invoiceSeries!,
series: invoiceSeries!,
invoiceDate: invoiceDate!,
operationDate: operationDate!,
status: CustomerInvoiceStatus.createDraft(),
currency,
currencyCode: currencyCode!,
};
return Result.ok({ id: invoiceId!, props: invoiceProps });

View File

@ -0,0 +1,17 @@
import { Presenter } from "@erp/core/api";
import { GetCustomerInvoiceByIdResponseDTO } from "../../../../common/dto";
import { formatMoneyDTO } from "../../helpers";
export class CustomerInvoiceReportPresenter extends Presenter<
GetCustomerInvoiceByIdResponseDTO,
unknown
> {
toOutput(invoiceDTO: GetCustomerInvoiceByIdResponseDTO) {
const locale = invoiceDTO.language_code;
return {
...invoiceDTO,
total_amount: formatMoneyDTO(invoiceDTO.total_amount, locale),
};
}
}

View File

@ -1 +1,2 @@
export * from "./customer-invoice.report.presenter";
export * from "./list-customer-invoices.presenter";

View File

@ -11,13 +11,20 @@ export class CustomerInvoiceReportHTMLPresenter extends Presenter {
projection: "FULL",
});
const prePresenter = this.presenterRegistry.getPresenter({
resource: "customer-invoice",
projection: "REPORT",
format: "JSON",
});
const invoiceDTO = dtoPresenter.toOutput(customerInvoice);
const prettyDTO = prePresenter.toOutput(invoiceDTO);
// Obtener y compilar la plantilla HTML
const templateHtml = readFileSync(
path.join(__dirname, "./templates/customer-invoice/template.hbs")
).toString();
const template = handlebars.compile(templateHtml, {});
return template(invoiceDTO);
return template(prettyDTO);
}
}

View File

@ -139,7 +139,7 @@
<!-- Badge TOTAL superpuesto -->
<div class="absolute -top-7 right-0">
<div class="relative bg-[#f08119] text-white text-sm font-semibold px-3 py-1 shadow">
TOTAL: {{total_amount.value}}
TOTAL: {{total_amount}}
</div>
</div>
</div>

View File

@ -14,6 +14,7 @@ import {
CustomerInvoiceItemsFullPresenter,
CustomerInvoiceReportHTMLPresenter,
CustomerInvoiceReportPDFPresenter,
CustomerInvoiceReportPresenter,
GetCustomerInvoiceUseCase,
ListCustomerInvoicesPresenter,
ListCustomerInvoicesUseCase,
@ -99,6 +100,14 @@ export function buildCustomerInvoiceDependencies(params: ModuleParams): Customer
},
presenter: new ListCustomerInvoicesPresenter(presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "REPORT",
format: "JSON",
},
presenter: new CustomerInvoiceReportPresenter(presenterRegistry),
},
{
key: {
resource: "customer-invoice",

View File

@ -119,7 +119,7 @@ export class CustomerInvoiceDomainMapper
const discountPercentage = extractOrPushError(
Percentage.create({
value: source.discount_amount_scale,
value: source.discount_percentage_value,
scale: source.discount_percentage_scale,
}),
"discount_percentage_value",
@ -206,9 +206,7 @@ export class CustomerInvoiceDomainMapper
// 5) Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("Customer invoice mapping failed", errors)
);
return Result.fail(new ValidationErrorCollection("Customer mapping failed", errors));
}
// 6) Construcción del agregado (Dominio)

View File

@ -207,7 +207,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
/**
* Devuelve una cadena con el importe formateado.
* Ejemplo: 123456 -> 1,234.56
* Ejemplo: 123456 -> 1.234,56
* @param locale Código de idioma y país (ej. "es-ES")
* @returns Importe formateado
*/
@ -222,6 +222,6 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
minimumFractionDigits: scale,
maximumFractionDigits: scale,
useGrouping: true,
}).format(value);
}).format(this.formattedValue);
}
}