.
This commit is contained in:
parent
e87e3ec609
commit
c9ba2d0370
@ -1,10 +1,9 @@
|
||||
import { type JsonTaxCatalogProvider, NumberHelper } from "@erp/core";
|
||||
import { NumberHelper } from "@erp/core";
|
||||
import { DiscountPercentage } from "@erp/core/api";
|
||||
import {
|
||||
CurrencyCode,
|
||||
DomainError,
|
||||
LanguageCode,
|
||||
Percentage,
|
||||
TextValue,
|
||||
UniqueID,
|
||||
UtcDate,
|
||||
@ -20,7 +19,6 @@ import {
|
||||
type IProformaCreateProps,
|
||||
type IProformaItemCreateProps,
|
||||
InvoiceNumber,
|
||||
InvoicePaymentMethod,
|
||||
type InvoiceRecipient,
|
||||
InvoiceSerie,
|
||||
InvoiceStatus,
|
||||
@ -31,23 +29,6 @@ import {
|
||||
type ProformaItemTaxesProps,
|
||||
} from "../../../domain";
|
||||
|
||||
/**
|
||||
* CreateProformaPropsMapper
|
||||
* Convierte el DTO a las props validadas (CustomerProps).
|
||||
* No construye directamente el agregado.
|
||||
*
|
||||
* @param dto - DTO con los datos de la factura de cliente
|
||||
* @returns
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
/*export interface ICreateProformaInputMapper
|
||||
extends IDTOInputToPropsMapper<
|
||||
CreateProformaRequestDTO,
|
||||
{ id: UniqueID; props: Omit<IProformaProps, "items"> & { items: IProformaItemProps[] } }
|
||||
> {}*/
|
||||
|
||||
export interface ICreateProformaInputMapper {
|
||||
map(
|
||||
dto: CreateProformaRequestDTO,
|
||||
@ -55,23 +36,21 @@ export interface ICreateProformaInputMapper {
|
||||
): Result<{ id: UniqueID; props: IProformaCreateProps }>;
|
||||
}
|
||||
|
||||
export class CreateProformaInputMapper /*implements ICreateProformaInputMapper*/ {
|
||||
private readonly taxCatalog: JsonTaxCatalogProvider;
|
||||
|
||||
constructor(params: { taxCatalog: JsonTaxCatalogProvider }) {
|
||||
this.taxCatalog = params.taxCatalog;
|
||||
}
|
||||
/**
|
||||
* @summary Convierte el DTO de creación de proforma en props de dominio.
|
||||
* @remarks
|
||||
* No construye el agregado. Solo valida y convierte primitivas de transporte
|
||||
* a Value Objects y props necesarias para `Proforma.create`.
|
||||
*/
|
||||
|
||||
export class CreateProformaInputMapper implements ICreateProformaInputMapper {
|
||||
public map(
|
||||
dto: CreateProformaRequestDTO,
|
||||
params: { companyId: UniqueID }
|
||||
): Result<{ id: UniqueID; props: IProformaCreateProps }> {
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
const { companyId } = params;
|
||||
|
||||
try {
|
||||
const defaultStatus = InvoiceStatus.draft();
|
||||
|
||||
const proformaId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
||||
|
||||
const customerId = extractOrPushError(
|
||||
@ -80,9 +59,7 @@ export class CreateProformaInputMapper /*implements ICreateProformaInputMapper*/
|
||||
errors
|
||||
);
|
||||
|
||||
const recipient = Maybe.none<InvoiceRecipient>();
|
||||
|
||||
const proformaNumber = extractOrPushError(
|
||||
const invoiceNumber = extractOrPushError(
|
||||
InvoiceNumber.create(dto.invoice_number),
|
||||
"invoice_number",
|
||||
errors
|
||||
@ -113,7 +90,7 @@ export class CreateProformaInputMapper /*implements ICreateProformaInputMapper*/
|
||||
);
|
||||
|
||||
const description = extractOrPushError(
|
||||
maybeFromNullableResult(dto.reference, (value) => Result.ok(String(value))),
|
||||
maybeFromNullableResult(dto.description, (value) => Result.ok(String(value))),
|
||||
"description",
|
||||
errors
|
||||
);
|
||||
@ -136,24 +113,21 @@ export class CreateProformaInputMapper /*implements ICreateProformaInputMapper*/
|
||||
errors
|
||||
);
|
||||
|
||||
const paymentMethod = extractOrPushError(
|
||||
maybeFromNullableResult(dto.payment_method, (value) =>
|
||||
InvoicePaymentMethod.create({ paymentDescription: value })
|
||||
),
|
||||
"payment_method",
|
||||
errors
|
||||
);
|
||||
|
||||
const globalDiscountPercentage = extractOrPushError(
|
||||
Percentage.create({
|
||||
value: Number(dto.global_discount_percentage.value),
|
||||
scale: Number(dto.global_discount_percentage.scale),
|
||||
DiscountPercentage.create({
|
||||
value: NumberHelper.toSafeNumber(dto.global_discount_percentage.value),
|
||||
}),
|
||||
"discount_percentage",
|
||||
"global_discount_percentage",
|
||||
errors
|
||||
);
|
||||
|
||||
const itemsProps = this.mapItemsProps(dto, {
|
||||
const paymentMethodId = extractOrPushError(
|
||||
maybeFromNullableResult(dto.payment_method_id, (value) => UniqueID.create(value)),
|
||||
"payment_method_id",
|
||||
errors
|
||||
);
|
||||
|
||||
const items = this.mapItemsProps(dto.items, {
|
||||
languageCode: languageCode!,
|
||||
currencyCode: currencyCode!,
|
||||
globalDiscountPercentage: globalDiscountPercentage!,
|
||||
@ -163,17 +137,17 @@ export class CreateProformaInputMapper /*implements ICreateProformaInputMapper*/
|
||||
this.throwIfValidationErrors(errors);
|
||||
|
||||
const props: IProformaCreateProps = {
|
||||
companyId,
|
||||
status: defaultStatus,
|
||||
companyId: params.companyId,
|
||||
status: InvoiceStatus.draft(),
|
||||
|
||||
invoiceNumber: proformaNumber!,
|
||||
invoiceNumber: invoiceNumber!,
|
||||
series: series!,
|
||||
|
||||
invoiceDate: invoiceDate!,
|
||||
operationDate: operationDate!,
|
||||
|
||||
customerId: customerId!,
|
||||
recipient,
|
||||
recipient: Maybe.none<InvoiceRecipient>(),
|
||||
|
||||
reference: reference!,
|
||||
description: description!,
|
||||
@ -182,10 +156,12 @@ export class CreateProformaInputMapper /*implements ICreateProformaInputMapper*/
|
||||
languageCode: languageCode!,
|
||||
currencyCode: currencyCode!,
|
||||
|
||||
paymentMethod: paymentMethod!,
|
||||
globalDiscountPercentage: globalDiscountPercentage!,
|
||||
linkedInvoiceId: Maybe.none(),
|
||||
|
||||
items: itemsProps, // ← IProformaItemProps[]
|
||||
paymentMethodId: paymentMethodId!,
|
||||
|
||||
globalDiscountPercentage: globalDiscountPercentage!,
|
||||
items,
|
||||
};
|
||||
|
||||
return Result.ok({
|
||||
@ -193,18 +169,12 @@ export class CreateProformaInputMapper /*implements ICreateProformaInputMapper*/
|
||||
props,
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
return Result.fail(new DomainError("Customer invoice props mapping failed", { cause: err }));
|
||||
}
|
||||
}
|
||||
|
||||
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
|
||||
if (errors.length > 0) {
|
||||
throw new ValidationErrorCollection("Customer proforma props mapping failed", errors);
|
||||
return Result.fail(new DomainError("Proforma props mapping failed", { cause: err }));
|
||||
}
|
||||
}
|
||||
|
||||
private mapItemsProps(
|
||||
dto: CreateProformaRequestDTO,
|
||||
itemsDTO: CreateProformaRequestDTO["items"],
|
||||
params: {
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
@ -212,132 +182,78 @@ export class CreateProformaInputMapper /*implements ICreateProformaInputMapper*/
|
||||
errors: ValidationErrorDetail[];
|
||||
}
|
||||
): IProformaItemCreateProps[] {
|
||||
const itemsProps: IProformaItemCreateProps[] = [];
|
||||
|
||||
dto.items.forEach((item, index) => {
|
||||
return itemsDTO.map((item, index) => {
|
||||
const description = extractOrPushError(
|
||||
maybeFromNullableResult(item.description, (v) => ItemDescription.create(v)),
|
||||
maybeFromNullableResult(item.description, (value) => ItemDescription.create(value)),
|
||||
`items[${index}].description`,
|
||||
params.errors
|
||||
);
|
||||
|
||||
const quantity = extractOrPushError(
|
||||
maybeFromNullableResult(item.quantity, (v) =>
|
||||
ItemQuantity.create({ value: NumberHelper.toSafeNumber(v) })
|
||||
maybeFromNullableResult(item.quantity, (value) =>
|
||||
ItemQuantity.create({ value: NumberHelper.toSafeNumber(value) })
|
||||
),
|
||||
`items[${index}].quantity`,
|
||||
params.errors
|
||||
);
|
||||
|
||||
const unitAmount = extractOrPushError(
|
||||
maybeFromNullableResult(item.unit_amount, (v) =>
|
||||
ItemAmount.create({ value: NumberHelper.toSafeNumber(v) })
|
||||
maybeFromNullableResult(item.unit_amount, (value) =>
|
||||
ItemAmount.create({ value: NumberHelper.toSafeNumber(value) })
|
||||
),
|
||||
`items[${index}].unit_amount`,
|
||||
params.errors
|
||||
);
|
||||
|
||||
const discountPercentage = extractOrPushError(
|
||||
maybeFromNullableResult(item.item_discount_percentage, (v) =>
|
||||
DiscountPercentage.create({ value: NumberHelper.toSafeNumber(v.value) })
|
||||
const itemDiscountPercentage = extractOrPushError(
|
||||
maybeFromNullableResult(item.item_discount_percentage, (value) =>
|
||||
DiscountPercentage.create({
|
||||
value: NumberHelper.toSafeNumber(value.value),
|
||||
})
|
||||
),
|
||||
`items[${index}].discount_percentage`,
|
||||
`items[${index}].item_discount_percentage`,
|
||||
params.errors
|
||||
);
|
||||
|
||||
const taxes = this.mapTaxesProps(item.taxes, {
|
||||
itemIndex: index,
|
||||
errors: params.errors,
|
||||
});
|
||||
|
||||
this.throwIfValidationErrors(params.errors);
|
||||
|
||||
itemsProps.push({
|
||||
globalDiscountPercentage: params.globalDiscountPercentage,
|
||||
languageCode: params.languageCode,
|
||||
currencyCode: params.currencyCode,
|
||||
return {
|
||||
position: item.position,
|
||||
|
||||
description: description!,
|
||||
quantity: quantity!,
|
||||
unitAmount: unitAmount!,
|
||||
itemDiscountPercentage: discountPercentage!,
|
||||
taxes,
|
||||
});
|
||||
itemDiscountPercentage: itemDiscountPercentage!,
|
||||
|
||||
taxes: this.mapTaxesProps(item.taxes, {
|
||||
itemIndex: index,
|
||||
errors: params.errors,
|
||||
}),
|
||||
|
||||
languageCode: params.languageCode,
|
||||
currencyCode: params.currencyCode,
|
||||
globalDiscountPercentage: params.globalDiscountPercentage,
|
||||
};
|
||||
});
|
||||
|
||||
return itemsProps;
|
||||
}
|
||||
|
||||
/* Devuelve las propiedades de los impustos de una línea de detalle */
|
||||
|
||||
private mapTaxesProps(
|
||||
taxesDTO: NonNullable<CreateProformaRequestDTO["items"]>[number]["taxes"],
|
||||
taxesDTO: CreateProformaRequestDTO["items"][number]["taxes"],
|
||||
params: { itemIndex: number; errors: ValidationErrorDetail[] }
|
||||
): ProformaItemTaxesProps {
|
||||
// TODO: POR AHORA SE QUEDA ASÍ
|
||||
if (taxesDTO === "#;#;#") {
|
||||
return ProformaItemTaxes.empty().getProps();
|
||||
}
|
||||
|
||||
return ProformaItemTaxes.empty().getProps();
|
||||
|
||||
/*const { itemIndex, errors } = params;
|
||||
|
||||
const taxesProps: ProformaItemTaxesProps = {
|
||||
iva: Maybe.none(),
|
||||
retention: Maybe.none(),
|
||||
rec: Maybe.none(),
|
||||
};
|
||||
|
||||
const taxStrCodes = taxesDTO
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0);
|
||||
|
||||
taxStrCodes.forEach((strCode, taxIndex) => {
|
||||
const taxResult = Tax.createFromCode(strCode, this.taxCatalog);
|
||||
|
||||
if (!taxResult.isSuccess) {
|
||||
errors.push({
|
||||
path: `items[${itemIndex}].taxes[${taxIndex}]`,
|
||||
message: taxResult.error.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const tax = taxResult.data;
|
||||
|
||||
if (tax.isVATLike()) {
|
||||
if (taxesProps.iva.isSome()) {
|
||||
errors.push({
|
||||
path: `items[${itemIndex}].taxes`,
|
||||
message: "Multiple taxes for group VAT are not allowed",
|
||||
});
|
||||
}
|
||||
taxesProps.iva = Maybe.some(tax);
|
||||
}
|
||||
|
||||
if (tax.isRetention()) {
|
||||
if (taxesProps.retention.isSome()) {
|
||||
errors.push({
|
||||
path: `items[${itemIndex}].taxes`,
|
||||
message: "Multiple taxes for group retention are not allowed",
|
||||
});
|
||||
}
|
||||
taxesProps.retention = Maybe.some(tax);
|
||||
}
|
||||
|
||||
if (tax.isRec()) {
|
||||
if (taxesProps.rec.isSome()) {
|
||||
errors.push({
|
||||
path: `items[${itemIndex}].taxes`,
|
||||
message: "Multiple taxes for group rec are not allowed",
|
||||
});
|
||||
}
|
||||
taxesProps.rec = Maybe.some(tax);
|
||||
}
|
||||
params.errors.push({
|
||||
path: `items[${params.itemIndex}].taxes`,
|
||||
message: "Tax combination mapping is not implemented yet",
|
||||
});
|
||||
|
||||
this.throwIfValidationErrors(errors);
|
||||
return ProformaItemTaxes.empty().getProps();
|
||||
}
|
||||
|
||||
return taxesProps;
|
||||
*/
|
||||
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
|
||||
if (errors.length > 0) {
|
||||
throw new ValidationErrorCollection("Proforma props mapping failed", errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,20 +26,6 @@ import {
|
||||
type ProformaPatchProps,
|
||||
} from "../../../domain";
|
||||
|
||||
/**
|
||||
* UpdateProformaPropsMapper
|
||||
* Convierte el DTO a las props validadas (ProformaInvoiceProps).
|
||||
* No construye directamente el agregado.
|
||||
* Tri-estado:
|
||||
* - campo omitido → no se cambia
|
||||
* - campo con valor null/"" → se quita el valor -> set(None()),
|
||||
* - campo con valor no-vacío → se pone el nuevo valor -> set(Some(VO)).
|
||||
*
|
||||
* @param dto - DTO con los datos a cambiar en la factura de cliente
|
||||
* @returns Cambios en las propiedades de la factura de cliente
|
||||
*
|
||||
*/
|
||||
|
||||
export interface IUpdateProformaInputMapper {
|
||||
map(
|
||||
dto: UpdateProformaByIdRequestDTO,
|
||||
@ -47,6 +33,20 @@ export interface IUpdateProformaInputMapper {
|
||||
): Result<ProformaPatchProps>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Convierte el DTO de update de proforma en props de dominio.
|
||||
* @remarks
|
||||
* Respeta semántica PATCH en cabecera:
|
||||
* - omitido: no modificar
|
||||
* - null: limpiar valor cuando el campo lo permite
|
||||
* - valor: asignar nuevo valor
|
||||
*
|
||||
* Para `items`, no aplica patch granular:
|
||||
* - undefined: no tocar líneas
|
||||
* - []: borrar todas las líneas
|
||||
* - [...]: reemplazar colección completa
|
||||
*/
|
||||
|
||||
export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
||||
public map(
|
||||
dto: UpdateProformaByIdRequestDTO,
|
||||
@ -54,46 +54,58 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
||||
): Result<ProformaPatchProps> {
|
||||
try {
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
const props: ProformaPatchProps = {};
|
||||
const proformaPatchProps: ProformaPatchProps = {};
|
||||
|
||||
toPatchField(dto.series).ifSet((series) => {
|
||||
props.series = extractOrPushError(
|
||||
proformaPatchProps.series = extractOrPushError(
|
||||
maybeFromNullableResult(series, (value) => InvoiceSerie.create(value)),
|
||||
"reference",
|
||||
"series",
|
||||
errors
|
||||
);
|
||||
});
|
||||
|
||||
toPatchField(dto.invoice_date).ifSet((invoice_date) => {
|
||||
if (isNullishOrEmpty(invoice_date)) {
|
||||
errors.push({ path: "invoice_date", message: "Invoice date cannot be empty" });
|
||||
toPatchField(dto.invoice_date).ifSet((invoiceDate) => {
|
||||
if (isNullishOrEmpty(invoiceDate)) {
|
||||
errors.push({
|
||||
path: "invoice_date",
|
||||
message: "Invoice date cannot be empty",
|
||||
});
|
||||
return;
|
||||
}
|
||||
props.invoiceDate = extractOrPushError(
|
||||
UtcDate.createFromISO(invoice_date!),
|
||||
|
||||
proformaPatchProps.invoiceDate = extractOrPushError(
|
||||
UtcDate.createFromISO(invoiceDate),
|
||||
"invoice_date",
|
||||
errors
|
||||
);
|
||||
});
|
||||
|
||||
toPatchField(dto.operation_date).ifSet((operation_date) => {
|
||||
props.operationDate = extractOrPushError(
|
||||
maybeFromNullableResult(operation_date, (value) => UtcDate.createFromISO(value)),
|
||||
toPatchField(dto.operation_date).ifSet((operationDate) => {
|
||||
proformaPatchProps.operationDate = extractOrPushError(
|
||||
maybeFromNullableResult(operationDate, (value) => UtcDate.createFromISO(value)),
|
||||
"operation_date",
|
||||
errors
|
||||
);
|
||||
});
|
||||
|
||||
toPatchField(dto.customer_id).ifSet((customer_id) => {
|
||||
if (isNullishOrEmpty(customer_id)) {
|
||||
errors.push({ path: "customer_id", message: "Proforma cannot be empty" });
|
||||
toPatchField(dto.customer_id).ifSet((customerId) => {
|
||||
if (isNullishOrEmpty(customerId)) {
|
||||
errors.push({
|
||||
path: "customer_id",
|
||||
message: "Customer id cannot be empty",
|
||||
});
|
||||
return;
|
||||
}
|
||||
props.customerId = extractOrPushError(UniqueID.create(customer_id!), "customer_id", errors);
|
||||
|
||||
proformaPatchProps.customerId = extractOrPushError(
|
||||
UniqueID.create(customerId),
|
||||
"customer_id",
|
||||
errors
|
||||
);
|
||||
});
|
||||
|
||||
toPatchField(dto.reference).ifSet((reference) => {
|
||||
props.reference = extractOrPushError(
|
||||
proformaPatchProps.reference = extractOrPushError(
|
||||
maybeFromNullableResult(reference, (value) => Result.ok(String(value))),
|
||||
"reference",
|
||||
errors
|
||||
@ -101,7 +113,7 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
||||
});
|
||||
|
||||
toPatchField(dto.description).ifSet((description) => {
|
||||
props.description = extractOrPushError(
|
||||
proformaPatchProps.description = extractOrPushError(
|
||||
maybeFromNullableResult(description, (value) => Result.ok(String(value))),
|
||||
"description",
|
||||
errors
|
||||
@ -109,7 +121,7 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
||||
});
|
||||
|
||||
toPatchField(dto.notes).ifSet((notes) => {
|
||||
props.notes = extractOrPushError(
|
||||
proformaPatchProps.notes = extractOrPushError(
|
||||
maybeFromNullableResult(notes, (value) => TextValue.create(value)),
|
||||
"notes",
|
||||
errors
|
||||
@ -118,12 +130,15 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
||||
|
||||
toPatchField(dto.language_code).ifSet((languageCode) => {
|
||||
if (isNullishOrEmpty(languageCode)) {
|
||||
errors.push({ path: "language_code", message: "Language code cannot be empty" });
|
||||
errors.push({
|
||||
path: "language_code",
|
||||
message: "Language code cannot be empty",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
props.languageCode = extractOrPushError(
|
||||
LanguageCode.create(languageCode!),
|
||||
proformaPatchProps.languageCode = extractOrPushError(
|
||||
LanguageCode.create(languageCode),
|
||||
"language_code",
|
||||
errors
|
||||
);
|
||||
@ -131,72 +146,84 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
||||
|
||||
toPatchField(dto.currency_code).ifSet((currencyCode) => {
|
||||
if (isNullishOrEmpty(currencyCode)) {
|
||||
errors.push({ path: "currency_code", message: "Currency code cannot be empty" });
|
||||
errors.push({
|
||||
path: "currency_code",
|
||||
message: "Currency code cannot be empty",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
props.currencyCode = extractOrPushError(
|
||||
CurrencyCode.create(currencyCode!),
|
||||
proformaPatchProps.currencyCode = extractOrPushError(
|
||||
CurrencyCode.create(currencyCode),
|
||||
"currency_code",
|
||||
errors
|
||||
);
|
||||
});
|
||||
|
||||
if (dto.items) {
|
||||
const itemsProps = this.mapItemsProps(dto, { errors });
|
||||
props.items = itemsProps;
|
||||
toPatchField(dto.global_discount_percentage).ifSet((globalDiscountPercentage) => {
|
||||
proformaPatchProps.globalDiscountPercentage = extractOrPushError(
|
||||
DiscountPercentage.create({
|
||||
value: NumberHelper.toSafeNumber(globalDiscountPercentage.value),
|
||||
}),
|
||||
"global_discount_percentage",
|
||||
errors
|
||||
);
|
||||
});
|
||||
|
||||
toPatchField(dto.payment_method_id).ifSet((paymentMethodId) => {
|
||||
proformaPatchProps.paymentMethodId = extractOrPushError(
|
||||
maybeFromNullableResult(paymentMethodId, (value) => UniqueID.create(value)),
|
||||
"payment_method_id",
|
||||
errors
|
||||
);
|
||||
});
|
||||
|
||||
if (dto.items !== undefined) {
|
||||
proformaPatchProps.items = this.mapItemsProps(dto.items, { errors });
|
||||
}
|
||||
|
||||
this.throwIfValidationErrors(errors);
|
||||
|
||||
return Result.ok(props);
|
||||
return Result.ok(proformaPatchProps);
|
||||
} catch (err: unknown) {
|
||||
return Result.fail(new DomainError("Proforma proforma props mapping failed", { cause: err }));
|
||||
}
|
||||
}
|
||||
|
||||
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
|
||||
if (errors.length > 0) {
|
||||
throw new ValidationErrorCollection("Customer proforma props mapping failed", errors);
|
||||
return Result.fail(new DomainError("Proforma props mapping failed", { cause: err }));
|
||||
}
|
||||
}
|
||||
|
||||
private mapItemsProps(
|
||||
dto: UpdateProformaByIdRequestDTO,
|
||||
params: {
|
||||
errors: ValidationErrorDetail[];
|
||||
}
|
||||
itemsDTO: NonNullable<UpdateProformaByIdRequestDTO["items"]>,
|
||||
params: { errors: ValidationErrorDetail[] }
|
||||
): ProformaItemPatchProps[] {
|
||||
const itemsProps: ProformaItemPatchProps[] = [];
|
||||
|
||||
dto.items?.forEach((item, index) => {
|
||||
return itemsDTO.map((item, index) => {
|
||||
const description = extractOrPushError(
|
||||
maybeFromNullableResult(item.description, (v) => ItemDescription.create(v)),
|
||||
maybeFromNullableResult(item.description, (value) => ItemDescription.create(value)),
|
||||
`items[${index}].description`,
|
||||
params.errors
|
||||
);
|
||||
|
||||
const quantity = extractOrPushError(
|
||||
maybeFromNullableResult(item.quantity, (v) =>
|
||||
ItemQuantity.create({ value: NumberHelper.toSafeNumber(v) })
|
||||
maybeFromNullableResult(item.quantity, (value) =>
|
||||
ItemQuantity.create({ value: NumberHelper.toSafeNumber(value) })
|
||||
),
|
||||
`items[${index}].quantity`,
|
||||
params.errors
|
||||
);
|
||||
|
||||
const unitAmount = extractOrPushError(
|
||||
maybeFromNullableResult(item.unit_amount, (v) =>
|
||||
ItemAmount.create({ value: NumberHelper.toSafeNumber(v) })
|
||||
maybeFromNullableResult(item.unit_amount, (value) =>
|
||||
ItemAmount.create({ value: NumberHelper.toSafeNumber(value) })
|
||||
),
|
||||
`items[${index}].unit_amount`,
|
||||
params.errors
|
||||
);
|
||||
|
||||
const discountPercentage = extractOrPushError(
|
||||
maybeFromNullableResult(item.item_discount_percentage, (v) =>
|
||||
DiscountPercentage.create({ value: NumberHelper.toSafeNumber(v.value) })
|
||||
const itemDiscountPercentage = extractOrPushError(
|
||||
maybeFromNullableResult(item.item_discount_percentage, (value) =>
|
||||
DiscountPercentage.create({
|
||||
value: NumberHelper.toSafeNumber(value.value),
|
||||
})
|
||||
),
|
||||
`items[${index}].discount_percentage`,
|
||||
`items[${index}].item_discount_percentage`,
|
||||
params.errors
|
||||
);
|
||||
|
||||
@ -205,89 +232,44 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
||||
errors: params.errors,
|
||||
});
|
||||
|
||||
this.throwIfValidationErrors(params.errors);
|
||||
|
||||
itemsProps.push({
|
||||
return {
|
||||
position: item.position,
|
||||
description: description!,
|
||||
quantity: quantity!,
|
||||
unitAmount: unitAmount!,
|
||||
itemDiscountPercentage: discountPercentage!,
|
||||
itemDiscountPercentage: itemDiscountPercentage!,
|
||||
taxes,
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
return itemsProps;
|
||||
}
|
||||
|
||||
/* Devuelve las propiedades de los impuestos de una línea de detalle */
|
||||
|
||||
private mapTaxesProps(
|
||||
taxesDTO: NonNullable<UpdateProformaByIdRequestDTO["items"]>[number]["taxes"],
|
||||
params: { itemIndex: number; errors: ValidationErrorDetail[] }
|
||||
): ProformaItemTaxesProps {
|
||||
// TODO: POR AHORA SE QUEDA ASÍ
|
||||
if (taxesDTO === "#;#;#") {
|
||||
return ProformaItemTaxes.empty().getProps();
|
||||
}
|
||||
|
||||
return ProformaItemTaxes.empty().getProps();
|
||||
|
||||
/*const { itemIndex, errors } = params;
|
||||
|
||||
const taxesProps: ProformaItemTaxesProps = {
|
||||
iva: Maybe.none(),
|
||||
retention: Maybe.none(),
|
||||
rec: Maybe.none(),
|
||||
};
|
||||
|
||||
const taxStrCodes = taxesDTO
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0);
|
||||
|
||||
taxStrCodes.forEach((strCode, taxIndex) => {
|
||||
const taxResult = Tax.createFromCode(strCode, this.taxCatalog);
|
||||
|
||||
if (!taxResult.isSuccess) {
|
||||
errors.push({
|
||||
path: `items[${itemIndex}].taxes[${taxIndex}]`,
|
||||
message: taxResult.error.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const tax = taxResult.data;
|
||||
|
||||
if (tax.isVATLike()) {
|
||||
if (taxesProps.iva.isSome()) {
|
||||
errors.push({
|
||||
path: `items[${itemIndex}].taxes`,
|
||||
message: "Multiple taxes for group VAT are not allowed",
|
||||
});
|
||||
}
|
||||
taxesProps.iva = Maybe.some(tax);
|
||||
}
|
||||
|
||||
if (tax.isRetention()) {
|
||||
if (taxesProps.retention.isSome()) {
|
||||
errors.push({
|
||||
path: `items[${itemIndex}].taxes`,
|
||||
message: "Multiple taxes for group retention are not allowed",
|
||||
});
|
||||
}
|
||||
taxesProps.retention = Maybe.some(tax);
|
||||
}
|
||||
|
||||
if (tax.isRec()) {
|
||||
if (taxesProps.rec.isSome()) {
|
||||
errors.push({
|
||||
path: `items[${itemIndex}].taxes`,
|
||||
message: "Multiple taxes for group rec are not allowed",
|
||||
});
|
||||
}
|
||||
taxesProps.rec = Maybe.some(tax);
|
||||
}
|
||||
/**
|
||||
* Pendiente: resolver códigos contra catálogo fiscal.
|
||||
*
|
||||
* taxesDTO llega como:
|
||||
* - iva_21;#;retention_10
|
||||
* - iva_10;rec_5_2;#
|
||||
* - #;#;#
|
||||
*/
|
||||
params.errors.push({
|
||||
path: `items[${params.itemIndex}].taxes`,
|
||||
message: "Tax combination mapping is not implemented yet",
|
||||
});
|
||||
|
||||
this.throwIfValidationErrors(errors);
|
||||
return ProformaItemTaxes.empty().getProps();
|
||||
}
|
||||
|
||||
return taxesProps;*/
|
||||
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
|
||||
if (errors.length > 0) {
|
||||
throw new ValidationErrorCollection("Proforma props mapping failed", errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
export * from "./proforma-full-snapshot.interface";
|
||||
export * from "./proforma-full-snapshot-builder";
|
||||
export * from "./proforma-item-full-snapshot.interface";
|
||||
export * from "./proforma-items-full-snapshot-builder";
|
||||
export * from "./proforma-recipient-full-snapshot.interface";
|
||||
export * from "./proforma-recipient-full-snapshot-builder";
|
||||
export * from "./proforma-tax-full-snapshot-interface";
|
||||
export * from "./proforma-taxes-full-snapshot-builder";
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import { maybeToNullable } from "@repo/rdx-ddd";
|
||||
|
||||
import type { GetProformaByIdResponseDTO } from "../../../../../common";
|
||||
import type { Proforma } from "../../../../domain";
|
||||
|
||||
import type { IProformaFullSnapshot } from "./proforma-full-snapshot.interface";
|
||||
import type { IProformaItemsFullSnapshotBuilder } from "./proforma-items-full-snapshot-builder";
|
||||
import type { IProformaRecipientFullSnapshotBuilder } from "./proforma-recipient-full-snapshot-builder";
|
||||
import type { IProformaTaxesFullSnapshotBuilder } from "./proforma-taxes-full-snapshot-builder";
|
||||
|
||||
export interface IProformaFullSnapshotBuilder
|
||||
extends ISnapshotBuilder<Proforma, IProformaFullSnapshot> {}
|
||||
extends ISnapshotBuilder<Proforma, GetProformaByIdResponseDTO> {}
|
||||
|
||||
export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder {
|
||||
constructor(
|
||||
@ -18,7 +18,7 @@ export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder
|
||||
private readonly taxesBuilder: IProformaTaxesFullSnapshotBuilder
|
||||
) {}
|
||||
|
||||
toOutput(proforma: Proforma): IProformaFullSnapshot {
|
||||
toOutput(proforma: Proforma): GetProformaByIdResponseDTO {
|
||||
const items = this.itemsBuilder.toOutput(proforma.items);
|
||||
const recipient = this.recipientBuilder.toOutput(proforma);
|
||||
const taxes = this.taxesBuilder.toOutput(proforma.taxes());
|
||||
@ -27,8 +27,8 @@ export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder
|
||||
(payment) => {
|
||||
const { id, payment_description } = payment.toObjectString();
|
||||
return {
|
||||
payment_id: id,
|
||||
payment_description,
|
||||
id: id,
|
||||
description: payment_description,
|
||||
};
|
||||
},
|
||||
() => null
|
||||
@ -40,9 +40,8 @@ export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder
|
||||
id: proforma.id.toString(),
|
||||
company_id: proforma.companyId.toString(),
|
||||
|
||||
is_proforma: true,
|
||||
invoice_number: proforma.invoiceNumber.toString(),
|
||||
status: proforma.status.toPrimitive(),
|
||||
status: proforma.status.toPrimitive() as GetProformaByIdResponseDTO["status"],
|
||||
series: maybeToNullable(proforma.series, (value) => value.toString()),
|
||||
|
||||
invoice_date: proforma.invoiceDate.toDateString(),
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
import type { IProformaItemFullSnapshot } from "./proforma-item-full-snapshot.interface";
|
||||
import type { IProformaRecipientFullSnapshot } from "./proforma-recipient-full-snapshot.interface";
|
||||
import type { IProformaTaxFullSnapshot } from "./proforma-tax-full-snapshot-interface";
|
||||
|
||||
/**
|
||||
* Fijarse en GetProformaByIdResponseDTO
|
||||
*/
|
||||
|
||||
export interface IProformaFullSnapshot {
|
||||
id: string;
|
||||
company_id: string;
|
||||
|
||||
is_proforma: boolean;
|
||||
invoice_number: string;
|
||||
status: string;
|
||||
series: string | null;
|
||||
|
||||
invoice_date: string;
|
||||
operation_date: string | null;
|
||||
|
||||
reference: string | null;
|
||||
description: string | null;
|
||||
notes: string | null;
|
||||
|
||||
language_code: string;
|
||||
currency_code: string;
|
||||
|
||||
customer_id: string;
|
||||
recipient: IProformaRecipientFullSnapshot;
|
||||
|
||||
linked_invoice_id: string | null;
|
||||
|
||||
taxes: IProformaTaxFullSnapshot[];
|
||||
|
||||
payment_method: {
|
||||
payment_id: string;
|
||||
payment_description: string;
|
||||
} | null;
|
||||
|
||||
subtotal_amount: { value: string; scale: string; currency_code: string };
|
||||
items_discount_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
global_discount_percentage: { value: string; scale: string };
|
||||
global_discount_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
total_discount_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
taxable_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
iva_amount: { value: string; scale: string; currency_code: string };
|
||||
rec_amount: { value: string; scale: string; currency_code: string };
|
||||
retention_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
taxes_amount: { value: string; scale: string; currency_code: string };
|
||||
total_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
items: IProformaItemFullSnapshot[];
|
||||
|
||||
metadata: Record<string, string> | null;
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
export interface IProformaItemFullSnapshot {
|
||||
id: string;
|
||||
is_valued: boolean;
|
||||
position: number;
|
||||
description: string | null;
|
||||
|
||||
quantity: { value: string; scale: string };
|
||||
unit_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
subtotal_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
item_discount_percentage: { value: string; scale: string };
|
||||
item_discount_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
global_discount_percentage: { value: string; scale: string };
|
||||
global_discount_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
total_discount_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
taxable_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
iva_code: string;
|
||||
iva_percentage: { value: string; scale: string };
|
||||
iva_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
rec_code: string;
|
||||
rec_percentage: { value: string; scale: string };
|
||||
rec_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
retention_code: string;
|
||||
retention_percentage: { value: string; scale: string };
|
||||
retention_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
taxes_amount: { value: string; scale: string; currency_code: string };
|
||||
total_amount: { value: string; scale: string; currency_code: string };
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import type { ProformaItemDetailDTO } from "@erp/customer-invoices/common";
|
||||
import {
|
||||
maybeToEmptyMoneyObjectString,
|
||||
maybeToEmptyPercentageObjectString,
|
||||
@ -9,13 +10,11 @@ import {
|
||||
|
||||
import { ItemAmount, type ProformaItem, type ProformaItems } from "../../../../domain";
|
||||
|
||||
import type { IProformaItemFullSnapshot } from "./proforma-item-full-snapshot.interface";
|
||||
|
||||
export interface IProformaItemsFullSnapshotBuilder
|
||||
extends ISnapshotBuilder<ProformaItems, IProformaItemFullSnapshot[]> {}
|
||||
extends ISnapshotBuilder<ProformaItems, ProformaItemDetailDTO[]> {}
|
||||
|
||||
export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnapshotBuilder {
|
||||
private mapItem(proformaItem: ProformaItem, index: number): IProformaItemFullSnapshot {
|
||||
private mapItem(proformaItem: ProformaItem, index: number): ProformaItemDetailDTO {
|
||||
const allAmounts = proformaItem.totals();
|
||||
const isValued = proformaItem.isValued();
|
||||
|
||||
@ -77,7 +76,7 @@ export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnaps
|
||||
};
|
||||
}
|
||||
|
||||
toOutput(invoiceItems: ProformaItems): IProformaItemFullSnapshot[] {
|
||||
toOutput(invoiceItems: ProformaItems): ProformaItemDetailDTO[] {
|
||||
return invoiceItems.map((item, index) => this.mapItem(item, index));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import { DomainValidationError, maybeToNullable } from "@repo/rdx-ddd";
|
||||
|
||||
import type { ProformaRecipientSummaryDTO } from "../../../../../common";
|
||||
import type { InvoiceRecipient, Proforma } from "../../../../domain";
|
||||
|
||||
import type { IProformaRecipientFullSnapshot } from "./proforma-recipient-full-snapshot.interface";
|
||||
|
||||
export interface IProformaRecipientFullSnapshotBuilder
|
||||
extends ISnapshotBuilder<Proforma, IProformaRecipientFullSnapshot> {}
|
||||
extends ISnapshotBuilder<Proforma, ProformaRecipientSummaryDTO> {}
|
||||
|
||||
export class ProformaRecipientFullSnapshotBuilder implements IProformaRecipientFullSnapshotBuilder {
|
||||
toOutput(proforma: Proforma): IProformaRecipientFullSnapshot {
|
||||
toOutput(proforma: Proforma): ProformaRecipientSummaryDTO {
|
||||
if (!proforma.recipient) {
|
||||
throw DomainValidationError.requiredValue("recipient", {
|
||||
cause: proforma,
|
||||
@ -39,7 +38,7 @@ export class ProformaRecipientFullSnapshotBuilder implements IProformaRecipientF
|
||||
province: null,
|
||||
postal_code: null,
|
||||
country: null,
|
||||
}) as IProformaRecipientFullSnapshot
|
||||
}) as ProformaRecipientSummaryDTO
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
/**
|
||||
* Fijarse en ProformaRecipientSummarySchema
|
||||
*/
|
||||
|
||||
export interface IProformaRecipientFullSnapshot {
|
||||
id: string | null;
|
||||
name: string | null;
|
||||
tin: string | null;
|
||||
street: string | null;
|
||||
street2: string | null;
|
||||
city: string | null;
|
||||
province: string | null;
|
||||
postal_code: string | null;
|
||||
country: string | null;
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
export interface IProformaTaxFullSnapshot {
|
||||
taxable_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
iva_code: string;
|
||||
iva_percentage: { value: string; scale: string };
|
||||
iva_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
rec_code: string;
|
||||
rec_percentage: { value: string; scale: string };
|
||||
rec_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
retention_code: string;
|
||||
retention_percentage: { value: string; scale: string };
|
||||
retention_amount: { value: string; scale: string; currency_code: string };
|
||||
|
||||
taxes_amount: { value: string; scale: string; currency_code: string };
|
||||
}
|
||||
@ -2,15 +2,14 @@ import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import { maybeToEmptyPercentageObjectString, maybeToEmptyString } from "@repo/rdx-ddd";
|
||||
import type { Collection } from "@repo/rdx-utils";
|
||||
|
||||
import type { TaxesBreakdownDTO } from "../../../../../common";
|
||||
import type { IProformaTaxTotals } from "../../../../domain";
|
||||
|
||||
import type { IProformaTaxFullSnapshot } from "./proforma-tax-full-snapshot-interface";
|
||||
|
||||
export interface IProformaTaxesFullSnapshotBuilder
|
||||
extends ISnapshotBuilder<Collection<IProformaTaxTotals>, IProformaTaxFullSnapshot[]> {}
|
||||
extends ISnapshotBuilder<Collection<IProformaTaxTotals>, TaxesBreakdownDTO[]> {}
|
||||
|
||||
export class ProformaTaxesFullSnapshotBuilder implements IProformaTaxesFullSnapshotBuilder {
|
||||
private mapItem(proformaTax: IProformaTaxTotals, index: number): IProformaTaxFullSnapshot {
|
||||
private mapItem(proformaTax: IProformaTaxTotals, index: number): TaxesBreakdownDTO {
|
||||
return {
|
||||
taxable_amount: proformaTax.taxableAmount.toObjectString(),
|
||||
|
||||
@ -30,7 +29,7 @@ export class ProformaTaxesFullSnapshotBuilder implements IProformaTaxesFullSnaps
|
||||
};
|
||||
}
|
||||
|
||||
toOutput(invoiceTaxes: Collection<IProformaTaxTotals>): IProformaTaxFullSnapshot[] {
|
||||
toOutput(invoiceTaxes: Collection<IProformaTaxTotals>): TaxesBreakdownDTO[] {
|
||||
return invoiceTaxes.map((item, index) => this.mapItem(item, index));
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ export class ProformaSummarySnapshotBuilder implements IProformaSummarySnapshotB
|
||||
return {
|
||||
id: proforma.id.toString(),
|
||||
company_id: proforma.companyId.toString(),
|
||||
is_proforma: proforma.isProforma,
|
||||
|
||||
invoice_number: proforma.invoiceNumber.toString(),
|
||||
status: proforma.status.toPrimitive() as ProformaSummaryDTO["status"],
|
||||
|
||||
@ -4,7 +4,7 @@ import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { UpdateProformaByIdRequestDTO } from "../../../../common";
|
||||
import type { ProformaPatchProps } from "../../../domain";
|
||||
import type { UpdateProformaInputMapper } from "../mappers";
|
||||
import type { IUpdateProformaInputMapper } from "../mappers";
|
||||
import type { IProformaUpdater } from "../services";
|
||||
import type { IProformaFullSnapshotBuilder } from "../snapshot-builders";
|
||||
|
||||
@ -15,14 +15,14 @@ type UpdateProformaUseCaseInput = {
|
||||
};
|
||||
|
||||
type UpdateProformaUseCaseDeps = {
|
||||
dtoMapper: UpdateProformaInputMapper;
|
||||
dtoMapper: IUpdateProformaInputMapper;
|
||||
updater: IProformaUpdater;
|
||||
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||
transactionManager: ITransactionManager;
|
||||
};
|
||||
|
||||
export class UpdateProformaUseCase {
|
||||
private readonly dtoMapper: UpdateProformaInputMapper;
|
||||
private readonly dtoMapper: IUpdateProformaInputMapper;
|
||||
private readonly updater: IProformaUpdater;
|
||||
private readonly fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||
private readonly transactionManager: ITransactionManager;
|
||||
|
||||
@ -14,7 +14,7 @@ export class IssuedInvoiceReportPresenter extends Presenter<
|
||||
return "";
|
||||
}
|
||||
|
||||
return paymentMethod.payment_description ?? "";
|
||||
return paymentMethod.description ?? "";
|
||||
}
|
||||
|
||||
toOutput(issuedInvoiceDTO: GetIssuedInvoiceByIdResponseDTO) {
|
||||
|
||||
@ -9,7 +9,7 @@ export class ProformaReportPresenter extends Presenter<GetProformaByIdResponseDT
|
||||
return "";
|
||||
}
|
||||
|
||||
return paymentMethod.payment_description ?? "";
|
||||
return paymentMethod.description ?? "";
|
||||
}
|
||||
|
||||
toOutput(proformaDTO: GetProformaByIdResponseDTO) {
|
||||
|
||||
@ -11,7 +11,6 @@ import {
|
||||
} from "@repo/rdx-ddd";
|
||||
import { type Collection, type Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { InvoicePaymentMethod } from "../../common/entities";
|
||||
import {
|
||||
InvoiceAmount,
|
||||
type InvoiceNumber,
|
||||
@ -53,14 +52,14 @@ export interface IProformaCreateProps {
|
||||
|
||||
linkedInvoiceId: Maybe<UniqueID>;
|
||||
|
||||
paymentMethod: Maybe<InvoicePaymentMethod>;
|
||||
paymentMethodId: Maybe<UniqueID>;
|
||||
|
||||
items: IProformaItemCreateProps[];
|
||||
globalDiscountPercentage: DiscountPercentage;
|
||||
}
|
||||
|
||||
export type ProformaPatchProps = Partial<Omit<IProformaCreateProps, "companyId" | "items">> & {
|
||||
items?: ProformaItemPatchProps[];
|
||||
items?: ProformaItemPatchProps[]; // update no es patch granular, sino reemplazo completo si se proporciona
|
||||
};
|
||||
|
||||
export interface IProformaTotals {
|
||||
@ -100,7 +99,7 @@ export interface IProforma {
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
|
||||
paymentMethod: Maybe<InvoicePaymentMethod>;
|
||||
paymentMethodId: Maybe<UniqueID>;
|
||||
|
||||
linkedInvoiceId: Maybe<UniqueID>;
|
||||
|
||||
@ -176,8 +175,12 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
||||
Object.assign(this.props, candidateProps);
|
||||
|
||||
// Reemplazo de items (si se proporciona)
|
||||
if (items) {
|
||||
this.initializeItems(items);
|
||||
if (items !== undefined) {
|
||||
const initializeResult = this.initializeItems(items);
|
||||
|
||||
if (initializeResult.isFailure) {
|
||||
return Result.fail(initializeResult.error);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok();
|
||||
@ -189,18 +192,11 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
||||
this._items.reset();
|
||||
|
||||
for (const [index, itemProps] of itemsProps.entries()) {
|
||||
const { languageCode, currencyCode, globalDiscountPercentage, ...restProps } = {
|
||||
const itemResult = ProformaItem.create({
|
||||
...itemProps,
|
||||
languageCode: this.languageCode,
|
||||
currencyCode: this.currencyCode,
|
||||
globalDiscountPercentage: this.globalDiscountPercentage,
|
||||
...itemProps,
|
||||
};
|
||||
|
||||
const itemResult = ProformaItem.create({
|
||||
...restProps,
|
||||
languageCode,
|
||||
currencyCode,
|
||||
globalDiscountPercentage,
|
||||
});
|
||||
|
||||
if (itemResult.isFailure) {
|
||||
@ -261,8 +257,8 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
||||
return this.props.recipient;
|
||||
}
|
||||
|
||||
public get paymentMethod(): Maybe<InvoicePaymentMethod> {
|
||||
return this.props.paymentMethod;
|
||||
public get paymentMethodId(): Maybe<UniqueID> {
|
||||
return this.props.paymentMethodId;
|
||||
}
|
||||
|
||||
public get linkedInvoiceId(): Maybe<UniqueID> {
|
||||
@ -290,7 +286,7 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
||||
}
|
||||
|
||||
public get hasPaymentMethod() {
|
||||
return this.paymentMethod.isSome();
|
||||
return this.paymentMethodId.isSome();
|
||||
}
|
||||
|
||||
public issue(): Result<void, Error> {
|
||||
@ -355,7 +351,7 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
||||
);
|
||||
}*/
|
||||
|
||||
if (this.paymentMethod.isNone()) {
|
||||
if (this.paymentMethodId.isNone()) {
|
||||
return Result.fail(
|
||||
new DomainValidationError(
|
||||
"MISSING_PAYMENT_METHOD",
|
||||
|
||||
@ -1,44 +1,68 @@
|
||||
import { NumericStringSchema, PercentageSchema } from "@erp/core";
|
||||
import {
|
||||
CurrencyCodeSchema,
|
||||
IsoDateSchema,
|
||||
LanguageCodeSchema,
|
||||
NumericStringSchema,
|
||||
PercentageSchema,
|
||||
} from "@erp/core";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const CreateProformaItemRequestSchema = z.object({
|
||||
id: z.uuid(),
|
||||
position: z.string(),
|
||||
description: z.string().default(""),
|
||||
quantity: NumericStringSchema.default(""),
|
||||
unit_amount: NumericStringSchema.default(""),
|
||||
item_discount_percentage: PercentageSchema.default({
|
||||
value: "0",
|
||||
scale: "2",
|
||||
}),
|
||||
taxes: z.string().default(""),
|
||||
});
|
||||
import { ItemPositionSchema, TaxCombinationCodeSchema } from "../../shared";
|
||||
|
||||
export const CreateProformaItemRequestSchema = z
|
||||
.object({
|
||||
position: ItemPositionSchema,
|
||||
|
||||
is_valued: z.boolean(),
|
||||
description: z.string().nullable(),
|
||||
|
||||
quantity: NumericStringSchema.nullable(),
|
||||
unit_amount: NumericStringSchema.nullable(),
|
||||
|
||||
item_discount_percentage: PercentageSchema.nullable(),
|
||||
|
||||
taxes: TaxCombinationCodeSchema,
|
||||
})
|
||||
.refine(
|
||||
(item) => {
|
||||
if (!item.is_valued) {
|
||||
return item.quantity === null && item.unit_amount === null;
|
||||
}
|
||||
|
||||
return item.quantity !== null && item.unit_amount !== null;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"quantity and unit_amount must be null when is_valued is false and non-null when is_valued is true",
|
||||
path: ["is_valued"],
|
||||
}
|
||||
);
|
||||
|
||||
export type CreateProformaItemRequestDTO = z.infer<typeof CreateProformaItemRequestSchema>;
|
||||
|
||||
export const CreateProformaRequestSchema = z.object({
|
||||
id: z.uuid(),
|
||||
|
||||
invoice_number: z.string(),
|
||||
series: z.string().default(""),
|
||||
series: z.string().nullable(),
|
||||
|
||||
invoice_date: z.string(),
|
||||
operation_date: z.string().default(""),
|
||||
invoice_date: IsoDateSchema,
|
||||
operation_date: IsoDateSchema.nullable().optional(),
|
||||
|
||||
customer_id: z.uuid(),
|
||||
|
||||
reference: z.string().default(""),
|
||||
notes: z.string().default(""),
|
||||
reference: z.string().nullable().optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
notes: z.string().nullable().optional(),
|
||||
|
||||
language_code: z.string().toLowerCase().default("es"),
|
||||
currency_code: z.string().toUpperCase().default("EUR"),
|
||||
language_code: LanguageCodeSchema,
|
||||
currency_code: CurrencyCodeSchema,
|
||||
|
||||
global_discount_percentage: PercentageSchema.default({
|
||||
value: "0",
|
||||
scale: "2",
|
||||
}),
|
||||
global_discount_percentage: PercentageSchema,
|
||||
|
||||
payment_method: z.string().default(""),
|
||||
payment_method_id: z.uuid().nullable().optional(),
|
||||
|
||||
items: z.array(CreateProformaItemRequestSchema).default([]),
|
||||
items: z.array(CreateProformaItemRequestSchema),
|
||||
});
|
||||
|
||||
export type CreateProformaRequestDTO = z.infer<typeof CreateProformaRequestSchema>;
|
||||
|
||||
@ -1,39 +1,71 @@
|
||||
import { NumericStringSchema, PercentageSchema } from "@erp/core";
|
||||
import {
|
||||
CurrencyCodeSchema,
|
||||
IsoDateSchema,
|
||||
LanguageCodeSchema,
|
||||
NumericStringSchema,
|
||||
PercentageSchema,
|
||||
} from "@erp/core";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const UpdateProformaItemRequestSchema = z.object({
|
||||
id: z.uuid(),
|
||||
position: z.string(),
|
||||
description: z.string().default(""),
|
||||
quantity: NumericStringSchema.default(""),
|
||||
unit_amount: NumericStringSchema.default(""),
|
||||
item_discount_percentage: PercentageSchema.default({
|
||||
value: "0",
|
||||
scale: "2",
|
||||
}),
|
||||
taxes: z.string().default(""),
|
||||
});
|
||||
import { ItemPositionSchema, TaxCombinationCodeSchema } from "../../shared";
|
||||
|
||||
export const UpdateProformaItemRequestSchema = z
|
||||
.object({
|
||||
position: ItemPositionSchema,
|
||||
is_valued: z.boolean(),
|
||||
|
||||
description: z.string().nullable(),
|
||||
|
||||
quantity: NumericStringSchema.nullable(),
|
||||
unit_amount: NumericStringSchema.nullable(),
|
||||
|
||||
item_discount_percentage: PercentageSchema.nullable(),
|
||||
|
||||
taxes: TaxCombinationCodeSchema,
|
||||
})
|
||||
.refine(
|
||||
(item) => {
|
||||
if (!item.is_valued) {
|
||||
return item.quantity === null && item.unit_amount === null;
|
||||
}
|
||||
|
||||
return item.quantity !== null && item.unit_amount !== null;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"quantity and unit_amount must be null when is_valued is false and non-null when is_valued is true",
|
||||
path: ["is_valued"],
|
||||
}
|
||||
);
|
||||
|
||||
export const UpdateProformaByIdParamsRequestSchema = z.object({
|
||||
proforma_id: z.string(),
|
||||
proforma_id: z.uuid(),
|
||||
});
|
||||
|
||||
export const UpdateProformaByIdRequestSchema = z.object({
|
||||
series: z.string().optional(),
|
||||
series: z.string().nullable().optional(),
|
||||
|
||||
invoice_date: z.string().optional(),
|
||||
operation_date: z.string().optional(),
|
||||
invoice_date: IsoDateSchema.optional(),
|
||||
operation_date: IsoDateSchema.nullable().optional(),
|
||||
|
||||
customer_id: z.uuid().optional(),
|
||||
|
||||
reference: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
notes: z.string().optional(),
|
||||
reference: z.string().nullable().optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
notes: z.string().nullable().optional(),
|
||||
|
||||
language_code: z.string().optional(),
|
||||
currency_code: z.string().optional(),
|
||||
language_code: LanguageCodeSchema.optional(),
|
||||
currency_code: CurrencyCodeSchema.optional(),
|
||||
|
||||
items: z.array(UpdateProformaItemRequestSchema).default([]),
|
||||
global_discount_percentage: PercentageSchema.optional(),
|
||||
|
||||
payment_method_id: z.uuid().nullable().optional(),
|
||||
|
||||
items: z.array(UpdateProformaItemRequestSchema).optional(),
|
||||
});
|
||||
|
||||
export type UpdateProformaByIdRequestDTO = Partial<z.infer<typeof UpdateProformaByIdRequestSchema>>;
|
||||
export type UpdateProformaByIdRequestDTO = z.infer<typeof UpdateProformaByIdRequestSchema>;
|
||||
|
||||
export type UpdateProformaByIdParamsRequestDTO = z.infer<
|
||||
typeof UpdateProformaByIdParamsRequestSchema
|
||||
>;
|
||||
|
||||
@ -19,7 +19,6 @@ export const GetProformaByIdResponseSchema = z.object({
|
||||
id: z.uuid(),
|
||||
company_id: z.uuid(),
|
||||
|
||||
is_proforma: z.boolean(),
|
||||
invoice_number: z.string(),
|
||||
status: ProformaStatusSchema,
|
||||
series: z.string().nullable(),
|
||||
@ -39,7 +38,7 @@ export const GetProformaByIdResponseSchema = z.object({
|
||||
|
||||
linked_invoice_id: z.uuid().nullable(),
|
||||
|
||||
taxes: TaxesBreakdownSchema,
|
||||
taxes: z.array(TaxesBreakdownSchema),
|
||||
|
||||
payment_method: PaymentMethodRefSchema.nullable(),
|
||||
|
||||
@ -47,6 +46,7 @@ export const GetProformaByIdResponseSchema = z.object({
|
||||
items_discount_amount: MoneySchema,
|
||||
global_discount_percentage: PercentageSchema,
|
||||
global_discount_amount: MoneySchema,
|
||||
total_discount_amount: MoneySchema,
|
||||
taxable_amount: MoneySchema,
|
||||
iva_amount: MoneySchema,
|
||||
rec_amount: MoneySchema,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export * from "./issued-invoices";
|
||||
export * from "./item-taxes-breakdown.dto";
|
||||
export * from "./payment-methof-ref.dto";
|
||||
export * from "./item-position.dto";
|
||||
export * from "./payment-method-ref.dto";
|
||||
export * from "./proforma";
|
||||
export * from "./tax-combination-code.dto";
|
||||
export * from "./taxes-breakdown.dto";
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { MoneySchema, PercentageSchema, QuantitySchema } from "@erp/core";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { ItemTaxesBreakdownSchema } from "../item-taxes-breakdown.dto";
|
||||
import { ItemPositionSchema } from "../item-position.dto";
|
||||
import { TaxesBreakdownSchema } from "../taxes-breakdown.dto";
|
||||
|
||||
export const IssuedInvoiceItemDetailSchema = z.object({
|
||||
id: z.uuid(),
|
||||
is_valued: z.boolean(),
|
||||
position: z.number(),
|
||||
position: ItemPositionSchema,
|
||||
description: z.string().nullable(),
|
||||
|
||||
quantity: QuantitySchema,
|
||||
@ -20,7 +21,7 @@ export const IssuedInvoiceItemDetailSchema = z.object({
|
||||
global_discount_percentage: PercentageSchema,
|
||||
global_discount_amount: MoneySchema,
|
||||
|
||||
...ItemTaxesBreakdownSchema.shape,
|
||||
...TaxesBreakdownSchema.shape,
|
||||
|
||||
total_amount: MoneySchema,
|
||||
});
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const ItemPositionSchema = z.number().int().nonnegative();
|
||||
|
||||
export type ItemPositionDTO = z.infer<typeof ItemPositionSchema>;
|
||||
@ -1,6 +0,0 @@
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import { TaxesBreakdownSchema } from "./taxes-breakdown.dto";
|
||||
|
||||
export const ItemTaxesBreakdownSchema = TaxesBreakdownSchema;
|
||||
export type ItemTaxesBreakdownDTO = z.infer<typeof ItemTaxesBreakdownSchema>;
|
||||
@ -1,8 +1,8 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const PaymentMethodRefSchema = z.object({
|
||||
payment_id: z.uuid(),
|
||||
payment_description: z.string(),
|
||||
id: z.uuid(),
|
||||
description: z.string(),
|
||||
});
|
||||
|
||||
export type PaymentMethodRefDTO = z.infer<typeof PaymentMethodRefSchema>;
|
||||
@ -1,12 +1,13 @@
|
||||
import { MoneySchema, PercentageSchema, QuantitySchema } from "@erp/core";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { ItemTaxesBreakdownSchema } from "../item-taxes-breakdown.dto";
|
||||
import { ItemPositionSchema } from "../item-position.dto";
|
||||
import { TaxesBreakdownSchema } from "../taxes-breakdown.dto";
|
||||
|
||||
export const ProformaItemDetailSchema = z.object({
|
||||
id: z.uuid(),
|
||||
is_valued: z.boolean(),
|
||||
position: z.number(),
|
||||
position: ItemPositionSchema,
|
||||
description: z.string().nullable(),
|
||||
|
||||
quantity: QuantitySchema,
|
||||
@ -20,7 +21,9 @@ export const ProformaItemDetailSchema = z.object({
|
||||
global_discount_percentage: PercentageSchema,
|
||||
global_discount_amount: MoneySchema,
|
||||
|
||||
...ItemTaxesBreakdownSchema.shape,
|
||||
total_discount_amount: MoneySchema,
|
||||
|
||||
...TaxesBreakdownSchema.shape,
|
||||
|
||||
total_amount: MoneySchema,
|
||||
});
|
||||
|
||||
@ -7,7 +7,6 @@ import { ProformaStatusSchema } from "./proforma-status.dto";
|
||||
export const ProformaSummarySchema = z.object({
|
||||
id: z.uuid(),
|
||||
company_id: z.uuid(),
|
||||
is_proforma: z.boolean(),
|
||||
|
||||
invoice_number: z.string(),
|
||||
status: ProformaStatusSchema,
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
const TAX_CODE_PATTERN = /^[a-z0-9_]+$/i;
|
||||
const EMPTY_TAX_SLOT = "#";
|
||||
|
||||
export const TaxCombinationCodeSchema = z.string().refine(
|
||||
(value) => {
|
||||
const parts = value.split(";");
|
||||
|
||||
if (parts.length !== 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parts.every((part) => {
|
||||
return part === EMPTY_TAX_SLOT || TAX_CODE_PATTERN.test(part);
|
||||
});
|
||||
},
|
||||
{
|
||||
message: "taxes must use format '<iva_code|#>;<rec_code|#>;<retention_code|#>'",
|
||||
}
|
||||
);
|
||||
|
||||
export type TaxCombinationCodeDTO = z.infer<typeof TaxCombinationCodeSchema>;
|
||||
@ -44,7 +44,7 @@ export const GetProformaByIdAdapter = {
|
||||
recipient: mapRecipient(dto.recipient),
|
||||
taxes: dto.taxes.map(mapTaxSummary),
|
||||
|
||||
paymentMethod: dto.payment_method?.payment_id,
|
||||
paymentMethod: dto.payment_method?.id,
|
||||
|
||||
subtotalAmount: MoneyDTOHelper.toNumber(dto.subtotal_amount),
|
||||
|
||||
|
||||
@ -55,8 +55,6 @@ export class UpdateCustomerInputMapper implements IUpdateCustomerInputMapper {
|
||||
dto: UpdateCustomerByIdRequestDTO,
|
||||
params: { companyId: UniqueID }
|
||||
): Result<CustomerPatchProps, Error> {
|
||||
console.log("Mapping UpdateCustomerByIdRequestDTO to CustomerPatchProps:", dto);
|
||||
|
||||
try {
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
const customerPatchProps: CustomerPatchProps = {};
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import {
|
||||
CountryCodeSchema,
|
||||
CurrencyCodeSchema,
|
||||
EmailSchema,
|
||||
LandPhoneSchema,
|
||||
LanguageCodeSchema,
|
||||
MobilePhoneSchema,
|
||||
PostalCodeSchema,
|
||||
TinSchema,
|
||||
@ -9,6 +11,8 @@ import {
|
||||
} from "@erp/core";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { TaxCombinationCodeSchema } from "../shared";
|
||||
|
||||
export const UpdateCustomerByIdParamsRequestSchema = z.object({
|
||||
customer_id: z.uuid(),
|
||||
});
|
||||
@ -45,15 +49,15 @@ export const UpdateCustomerByIdRequestSchema = z.object({
|
||||
trade_name: z.string().nullable().optional(),
|
||||
tin: TinSchema.nullable().optional(),
|
||||
|
||||
default_taxes: z.string().nullable().optional(),
|
||||
default_taxes: TaxCombinationCodeSchema.optional(),
|
||||
|
||||
address: UpdateCustomerAddressPatchRequestSchema.optional(),
|
||||
contact: UpdateCustomerContactPatchRequestSchema.optional(),
|
||||
|
||||
legal_record: z.string().nullable().optional(),
|
||||
|
||||
language_code: z.string().optional(),
|
||||
currency_code: z.string().optional(),
|
||||
language_code: LanguageCodeSchema.optional(),
|
||||
currency_code: CurrencyCodeSchema.optional(),
|
||||
});
|
||||
|
||||
export type UpdateCustomerAddressPatchRequestDTO = z.infer<
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
} from "@erp/core";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { TaxCombinationCodeSchema } from "../shared";
|
||||
import { CustomerStatusSchema } from "../shared/customer-status.dto";
|
||||
|
||||
export const GetCustomerByIdResponseSchema = z.object({
|
||||
@ -48,7 +49,7 @@ export const GetCustomerByIdResponseSchema = z.object({
|
||||
|
||||
legal_record: z.string().nullable(),
|
||||
|
||||
default_taxes: z.string().nullable(),
|
||||
default_taxes: TaxCombinationCodeSchema,
|
||||
|
||||
language_code: LanguageCodeSchema,
|
||||
currency_code: CurrencyCodeSchema,
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./customer-status.dto";
|
||||
export * from "./customer-summary.dto";
|
||||
export * from "./tax-combination-code.dto";
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
const TAX_CODE_PATTERN = /^[a-z0-9_]+$/i;
|
||||
const EMPTY_TAX_SLOT = "#";
|
||||
|
||||
export const TaxCombinationCodeSchema = z.string().refine(
|
||||
(value) => {
|
||||
const parts = value.split(";");
|
||||
|
||||
if (parts.length !== 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parts.every((part) => {
|
||||
return part === EMPTY_TAX_SLOT || TAX_CODE_PATTERN.test(part);
|
||||
});
|
||||
},
|
||||
{
|
||||
message: "taxes must use format '<iva_code|#>;<rec_code|#>;<retention_code|#>'",
|
||||
}
|
||||
);
|
||||
|
||||
export type TaxCombinationCodeDTO = z.infer<typeof TaxCombinationCodeSchema>;
|
||||
@ -36,7 +36,7 @@ export const GetSupplierByIdResponseSchema = z.object({
|
||||
|
||||
legal_record: z.string(),
|
||||
|
||||
default_taxes: z.array(z.string()),
|
||||
default_taxes: TaxCombinationCodeSchema,
|
||||
status: z.string(),
|
||||
language_code: LanguageCodeSchema,
|
||||
currency_code: CurrencyCodeSchema,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user