Líneas de proformas
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
2c6cac4859
commit
a248e8cdc0
@ -1,16 +1,26 @@
|
|||||||
import { Percentage, type PercentageProps } from "@repo/rdx-ddd";
|
import { Percentage, type PercentageProps, ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||||
import type { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
type DiscountPercentageProps = Pick<PercentageProps, "value">;
|
type DiscountPercentageProps = PercentageProps;
|
||||||
|
|
||||||
export class DiscountPercentage extends Percentage {
|
export class DiscountPercentage extends Percentage {
|
||||||
static DEFAULT_SCALE = 2;
|
static DEFAULT_SCALE = 2;
|
||||||
|
|
||||||
static create({ value }: DiscountPercentageProps): Result<Percentage> {
|
static create({ value, scale }: DiscountPercentageProps): Result<Percentage> {
|
||||||
return Percentage.create({
|
if (scale && scale !== DiscountPercentage.DEFAULT_SCALE) {
|
||||||
value,
|
return Result.fail(
|
||||||
scale: DiscountPercentage.DEFAULT_SCALE,
|
new ValidationErrorCollection("InvalidScale", [
|
||||||
});
|
{ message: `DiscountPercentage scale must be ${DiscountPercentage.DEFAULT_SCALE}` },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(
|
||||||
|
new DiscountPercentage({
|
||||||
|
value,
|
||||||
|
scale: DiscountPercentage.DEFAULT_SCALE,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static zero() {
|
static zero() {
|
||||||
|
|||||||
@ -92,6 +92,21 @@ const fromNumber = (amount: number, currency = "EUR", scale = 2): MoneyDTO => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fromNumberNulleable = (
|
||||||
|
amount: number | null,
|
||||||
|
currency = "EUR",
|
||||||
|
scale = 2
|
||||||
|
): MoneyDTO | null => {
|
||||||
|
if (amount === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: String(Math.round(amount * 10 ** scale)),
|
||||||
|
scale: String(scale),
|
||||||
|
currency_code: currency,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convierte cadena numérica a MoneyDTO.
|
* Convierte cadena numérica a MoneyDTO.
|
||||||
*/
|
*/
|
||||||
@ -126,6 +141,7 @@ export const MoneyDTOHelper = {
|
|||||||
toNumericString,
|
toNumericString,
|
||||||
toNumericNulleable,
|
toNumericNulleable,
|
||||||
fromNumber,
|
fromNumber,
|
||||||
|
fromNumberNulleable,
|
||||||
fromNumericString,
|
fromNumericString,
|
||||||
format,
|
format,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -84,6 +84,16 @@ const fromNumber = (amount: number, scale = 2): PercentageDTO => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fromNumberNulleable = (amount: number | null, scale = 2): PercentageDTO | null => {
|
||||||
|
if (amount === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: String(Math.round(amount * 10 ** scale)),
|
||||||
|
scale: String(scale),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convierte cadena numérica a PercentageDTO.
|
* Convierte cadena numérica a PercentageDTO.
|
||||||
*/
|
*/
|
||||||
@ -106,6 +116,7 @@ export const PercentageDTOHelper = {
|
|||||||
toNumericString,
|
toNumericString,
|
||||||
toNumericNulleable,
|
toNumericNulleable,
|
||||||
fromNumber,
|
fromNumber,
|
||||||
|
fromNumberNulleable,
|
||||||
fromNumericString,
|
fromNumericString,
|
||||||
format,
|
format,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -78,6 +78,16 @@ const fromNumber = (amount: number, scale = 2): QuantityDTO => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fromNumberNulleable = (amount: number | null, scale = 2): QuantityDTO | null => {
|
||||||
|
if (amount === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: String(Math.round(amount * 10 ** scale)),
|
||||||
|
scale: String(scale),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convierte cadena numérica a QuantityDTO.
|
* Convierte cadena numérica a QuantityDTO.
|
||||||
*/
|
*/
|
||||||
@ -100,6 +110,7 @@ export const QuantityDTOHelper = {
|
|||||||
toNumericString,
|
toNumericString,
|
||||||
toNumericNulleable,
|
toNumericNulleable,
|
||||||
fromNumber,
|
fromNumber,
|
||||||
|
fromNumberNulleable,
|
||||||
fromNumericString,
|
fromNumericString,
|
||||||
format,
|
format,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,12 +11,10 @@ export interface IProformaInputMappers {
|
|||||||
updateInputMapper: UpdateProformaInputMapper;
|
updateInputMapper: UpdateProformaInputMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildProformaInputMappers = (_catalogs: ICatalogs): IProformaInputMappers => {
|
export const buildProformaInputMappers = (catalogs: ICatalogs): IProformaInputMappers => {
|
||||||
//const { taxCatalog } = catalogs;
|
|
||||||
|
|
||||||
// Mappers el DTO a las props validadas (ProformaProps) y luego construir agregado
|
// Mappers el DTO a las props validadas (ProformaProps) y luego construir agregado
|
||||||
const createInputMapper = new CreateProformaInputMapper();
|
const createInputMapper = new CreateProformaInputMapper(catalogs);
|
||||||
const updateInputMapper = new UpdateProformaInputMapper();
|
const updateInputMapper = new UpdateProformaInputMapper(catalogs);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createInputMapper,
|
createInputMapper,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { DiscountPercentage } from "@erp/core/api";
|
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
|
import { DiscountPercentage, type ICatalogs, Tax } from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
CurrencyCode,
|
CurrencyCode,
|
||||||
DomainError,
|
DomainError,
|
||||||
@ -43,6 +44,11 @@ export interface ICreateProformaInputMapper {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export class CreateProformaInputMapper implements ICreateProformaInputMapper {
|
export class CreateProformaInputMapper implements ICreateProformaInputMapper {
|
||||||
|
private readonly taxCatalog: JsonTaxCatalogProvider;
|
||||||
|
|
||||||
|
constructor(catalogs: ICatalogs) {
|
||||||
|
this.taxCatalog = catalogs.taxCatalog;
|
||||||
|
}
|
||||||
public map(
|
public map(
|
||||||
dto: CreateProformaRequestDTO,
|
dto: CreateProformaRequestDTO,
|
||||||
params: { companyId: UniqueID }
|
params: { companyId: UniqueID }
|
||||||
@ -238,16 +244,35 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper {
|
|||||||
taxesDTO: CreateProformaRequestDTO["items"][number]["taxes"],
|
taxesDTO: CreateProformaRequestDTO["items"][number]["taxes"],
|
||||||
params: { itemIndex: number; errors: ValidationErrorDetail[] }
|
params: { itemIndex: number; errors: ValidationErrorDetail[] }
|
||||||
): ProformaItemTaxesProps {
|
): ProformaItemTaxesProps {
|
||||||
if (taxesDTO === "#;#;#") {
|
const parts = taxesDTO.split(";");
|
||||||
|
if (parts.length !== 3) {
|
||||||
|
params.errors.push({
|
||||||
|
path: `items[${params.itemIndex}].taxes`,
|
||||||
|
message: "Tax combination must contain exactly three elements",
|
||||||
|
});
|
||||||
return ProformaItemTaxes.empty().getProps();
|
return ProformaItemTaxes.empty().getProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
params.errors.push({
|
const [ivaCode, recCode, retentionCode] = parts;
|
||||||
path: `items[${params.itemIndex}].taxes`,
|
|
||||||
message: "Tax combination mapping is not implemented yet",
|
|
||||||
});
|
|
||||||
|
|
||||||
return ProformaItemTaxes.empty().getProps();
|
const iva = this.mapTaxCode(ivaCode, `items[${params.itemIndex}].taxes.iva`, params.errors);
|
||||||
|
const rec = this.mapTaxCode(recCode, `items[${params.itemIndex}].taxes.rec`, params.errors);
|
||||||
|
const retention = this.mapTaxCode(
|
||||||
|
retentionCode,
|
||||||
|
`items[${params.itemIndex}].taxes.retention`,
|
||||||
|
params.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
return ProformaItemTaxes.create({ iva, rec, retention }).data.getProps();
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapTaxCode(code: string, path: string, errors: ValidationErrorDetail[]): Maybe<Tax> {
|
||||||
|
if (code === "#") {
|
||||||
|
return Maybe.none();
|
||||||
|
}
|
||||||
|
|
||||||
|
const tax = extractOrPushError(Tax.createFromCode(code, this.taxCatalog), path, errors);
|
||||||
|
return tax ? Maybe.some(tax) : Maybe.none();
|
||||||
}
|
}
|
||||||
|
|
||||||
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
|
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { DiscountPercentage } from "@erp/core/api";
|
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
|
import { DiscountPercentage, type ICatalogs, Tax } from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
CurrencyCode,
|
CurrencyCode,
|
||||||
DomainError,
|
DomainError,
|
||||||
@ -11,7 +12,7 @@ import {
|
|||||||
extractOrPushError,
|
extractOrPushError,
|
||||||
maybeFromNullableResult,
|
maybeFromNullableResult,
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { NumberHelper, Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils";
|
import { Maybe, NumberHelper, Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { UpdateProformaByIdRequestDTO } from "../../../../common/dto";
|
import type { UpdateProformaByIdRequestDTO } from "../../../../common/dto";
|
||||||
import {
|
import {
|
||||||
@ -47,6 +48,11 @@ export interface IUpdateProformaInputMapper {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
||||||
|
private readonly taxCatalog: JsonTaxCatalogProvider;
|
||||||
|
constructor(catalogs: ICatalogs) {
|
||||||
|
this.taxCatalog = catalogs.taxCatalog;
|
||||||
|
}
|
||||||
|
|
||||||
public map(
|
public map(
|
||||||
dto: UpdateProformaByIdRequestDTO,
|
dto: UpdateProformaByIdRequestDTO,
|
||||||
_params: { companyId: UniqueID }
|
_params: { companyId: UniqueID }
|
||||||
@ -201,26 +207,20 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const quantity = extractOrPushError(
|
const quantity = extractOrPushError(
|
||||||
maybeFromNullableResult(item.quantity, (value) =>
|
maybeFromNullableResult(item.quantity, (dto) => ItemQuantity.fromObjectString(dto)),
|
||||||
ItemQuantity.create({ value: NumberHelper.toSafeNumber(value) })
|
|
||||||
),
|
|
||||||
`items[${index}].quantity`,
|
`items[${index}].quantity`,
|
||||||
params.errors
|
params.errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const unitAmount = extractOrPushError(
|
const unitAmount = extractOrPushError(
|
||||||
maybeFromNullableResult(item.unit_amount, (value) =>
|
maybeFromNullableResult(item.unit_amount, (dto) => ItemAmount.fromObjectString(dto)),
|
||||||
ItemAmount.create({ value: NumberHelper.toSafeNumber(value) })
|
|
||||||
),
|
|
||||||
`items[${index}].unit_amount`,
|
`items[${index}].unit_amount`,
|
||||||
params.errors
|
params.errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const itemDiscountPercentage = extractOrPushError(
|
const itemDiscountPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(item.item_discount_percentage, (value) =>
|
maybeFromNullableResult(item.item_discount_percentage, (dto) =>
|
||||||
DiscountPercentage.create({
|
DiscountPercentage.fromObjectString(dto)
|
||||||
value: NumberHelper.toSafeNumber(value.value),
|
|
||||||
})
|
|
||||||
),
|
),
|
||||||
`items[${index}].item_discount_percentage`,
|
`items[${index}].item_discount_percentage`,
|
||||||
params.errors
|
params.errors
|
||||||
@ -246,24 +246,35 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
|||||||
taxesDTO: NonNullable<UpdateProformaByIdRequestDTO["items"]>[number]["taxes"],
|
taxesDTO: NonNullable<UpdateProformaByIdRequestDTO["items"]>[number]["taxes"],
|
||||||
params: { itemIndex: number; errors: ValidationErrorDetail[] }
|
params: { itemIndex: number; errors: ValidationErrorDetail[] }
|
||||||
): ProformaItemTaxesProps {
|
): ProformaItemTaxesProps {
|
||||||
if (taxesDTO === "#;#;#") {
|
const parts = taxesDTO.split(";");
|
||||||
|
if (parts.length !== 3) {
|
||||||
|
params.errors.push({
|
||||||
|
path: `items[${params.itemIndex}].taxes`,
|
||||||
|
message: "Tax combination must contain exactly three elements",
|
||||||
|
});
|
||||||
return ProformaItemTaxes.empty().getProps();
|
return ProformaItemTaxes.empty().getProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const [ivaCode, recCode, retentionCode] = parts;
|
||||||
* 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",
|
|
||||||
});
|
|
||||||
|
|
||||||
return ProformaItemTaxes.empty().getProps();
|
const iva = this.mapTaxCode(ivaCode, `items[${params.itemIndex}].taxes.iva`, params.errors);
|
||||||
|
const rec = this.mapTaxCode(recCode, `items[${params.itemIndex}].taxes.rec`, params.errors);
|
||||||
|
const retention = this.mapTaxCode(
|
||||||
|
retentionCode,
|
||||||
|
`items[${params.itemIndex}].taxes.retention`,
|
||||||
|
params.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
return ProformaItemTaxes.create({ iva, rec, retention }).data.getProps();
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapTaxCode(code: string, path: string, errors: ValidationErrorDetail[]): Maybe<Tax> {
|
||||||
|
if (code === "#") {
|
||||||
|
return Maybe.none();
|
||||||
|
}
|
||||||
|
|
||||||
|
const tax = extractOrPushError(Tax.createFromCode(code, this.taxCatalog), path, errors);
|
||||||
|
return tax ? Maybe.some(tax) : Maybe.none();
|
||||||
}
|
}
|
||||||
|
|
||||||
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
|
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
//export * from "./proformas";
|
|
||||||
@ -1 +0,0 @@
|
|||||||
//export * from "./proforma.full.presenter";
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
import { SnapshotBuilder } from "@erp/core/api";
|
|
||||||
import type { GetProformaByIdResponseDTO } from "@erp/customer-invoices/common";
|
|
||||||
import { maybeToEmptyString } from "@repo/rdx-ddd";
|
|
||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { CustomerInvoiceItems, IssuedInvoiceItem } from "../../../../domain";
|
|
||||||
|
|
||||||
type GetProformaItemByIdResponseDTO = ArrayElement<GetProformaByIdResponseDTO["items"]>;
|
|
||||||
|
|
||||||
export class ProformaItemsFullPresenter extends SnapshotBuilder {
|
|
||||||
private _mapItem(proformaItem: IssuedInvoiceItem, index: number): GetProformaItemByIdResponseDTO {
|
|
||||||
const allAmounts = proformaItem.calculateAllAmounts();
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: proformaItem.id.toPrimitive(),
|
|
||||||
is_valued: String(proformaItem.isValued),
|
|
||||||
position: String(index),
|
|
||||||
description: maybeToEmptyString(proformaItem.description, (value) => value.toPrimitive()),
|
|
||||||
|
|
||||||
quantity: proformaItem.quantity.match(
|
|
||||||
(quantity) => quantity.toObjectString(),
|
|
||||||
() => ({ value: "", scale: "" })
|
|
||||||
),
|
|
||||||
|
|
||||||
unit_amount: proformaItem.unitAmount.match(
|
|
||||||
(unitAmount) => unitAmount.toObjectString(),
|
|
||||||
() => ({ value: "", scale: "", currency_code: "" })
|
|
||||||
),
|
|
||||||
|
|
||||||
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
|
||||||
|
|
||||||
discount_percentage: proformaItem.itemDiscountPercentage.match(
|
|
||||||
(discountPercentage) => discountPercentage.toObjectString(),
|
|
||||||
() => ({ value: "", scale: "" })
|
|
||||||
),
|
|
||||||
|
|
||||||
discount_amount: allAmounts.itemDiscountAmount.toObjectString(),
|
|
||||||
|
|
||||||
global_discount_percentage: proformaItem.globalDiscountPercentage.match(
|
|
||||||
(discountPercentage) => discountPercentage.toObjectString(),
|
|
||||||
() => ({ value: "", scale: "" })
|
|
||||||
),
|
|
||||||
|
|
||||||
global_discount_amount: allAmounts.globalDiscountAmount.toObjectString(),
|
|
||||||
|
|
||||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
|
||||||
|
|
||||||
iva_code: proformaItem.taxes.iva.match(
|
|
||||||
(iva) => iva.code,
|
|
||||||
() => ""
|
|
||||||
),
|
|
||||||
|
|
||||||
iva_percentage: proformaItem.taxes.iva.match(
|
|
||||||
(iva) => iva.percentage.toObjectString(),
|
|
||||||
() => ({ value: "", scale: "" })
|
|
||||||
),
|
|
||||||
|
|
||||||
iva_amount: allAmounts.ivaAmount.toObjectString(),
|
|
||||||
|
|
||||||
rec_code: proformaItem.taxes.rec.match(
|
|
||||||
(rec) => rec.code,
|
|
||||||
() => ""
|
|
||||||
),
|
|
||||||
|
|
||||||
rec_percentage: proformaItem.taxes.rec.match(
|
|
||||||
(rec) => rec.percentage.toObjectString(),
|
|
||||||
() => ({ value: "", scale: "" })
|
|
||||||
),
|
|
||||||
|
|
||||||
rec_amount: allAmounts.recAmount.toObjectString(),
|
|
||||||
|
|
||||||
retention_code: proformaItem.taxes.retention.match(
|
|
||||||
(retention) => retention.code,
|
|
||||||
() => ""
|
|
||||||
),
|
|
||||||
|
|
||||||
retention_percentage: proformaItem.taxes.retention.match(
|
|
||||||
(retention) => retention.percentage.toObjectString(),
|
|
||||||
() => ({ value: "", scale: "" })
|
|
||||||
),
|
|
||||||
|
|
||||||
retention_amount: allAmounts.retentionAmount.toObjectString(),
|
|
||||||
|
|
||||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
|
||||||
|
|
||||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
toOutput(proformaItems: CustomerInvoiceItems): GetProformaByIdResponseDTO["items"] {
|
|
||||||
return proformaItems.map(this._mapItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
import { SnapshotBuilder } from "@erp/core/api";
|
|
||||||
import { DomainValidationError, maybeToEmptyString } from "@repo/rdx-ddd";
|
|
||||||
|
|
||||||
import type { GetIssuedInvoiceByIdResponseDTO as GetProformaByIdResponseDTO } from "../../../../../common/dto";
|
|
||||||
import type { InvoiceRecipient, Proforma } from "../../../../domain";
|
|
||||||
|
|
||||||
type GetProformaRecipientByIdResponseDTO = GetProformaByIdResponseDTO["recipient"];
|
|
||||||
|
|
||||||
export class ProformaRecipientFullPresenter extends SnapshotBuilder {
|
|
||||||
toOutput(proforma: Proforma): GetProformaRecipientByIdResponseDTO {
|
|
||||||
if (!proforma.recipient) {
|
|
||||||
throw DomainValidationError.requiredValue("recipient", {
|
|
||||||
cause: proforma,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return proforma.recipient.match(
|
|
||||||
(recipient: InvoiceRecipient) => {
|
|
||||||
return {
|
|
||||||
id: proforma.customerId.toString(),
|
|
||||||
name: recipient.name.toString(),
|
|
||||||
tin: recipient.tin.toString(),
|
|
||||||
street: maybeToEmptyString(recipient.street, (value) => value.toString()),
|
|
||||||
street2: maybeToEmptyString(recipient.street2, (value) => value.toString()),
|
|
||||||
city: maybeToEmptyString(recipient.city, (value) => value.toString()),
|
|
||||||
province: maybeToEmptyString(recipient.province, (value) => value.toString()),
|
|
||||||
postal_code: maybeToEmptyString(recipient.postalCode, (value) => value.toString()),
|
|
||||||
country: maybeToEmptyString(recipient.country, (value) => value.toString()),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
return {
|
|
||||||
id: "",
|
|
||||||
name: "",
|
|
||||||
tin: "",
|
|
||||||
street: "",
|
|
||||||
street2: "",
|
|
||||||
city: "",
|
|
||||||
province: "",
|
|
||||||
postal_code: "",
|
|
||||||
country: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
import { Presenter } from "@erp/core/api";
|
|
||||||
import { maybeToEmptyString } from "@repo/rdx-ddd";
|
|
||||||
|
|
||||||
import type { GetProformaByIdResponseDTO } from "../../../../../common/dto";
|
|
||||||
import { InvoiceAmount, type Proforma } from "../../../../domain";
|
|
||||||
|
|
||||||
import type { ProformaItemsFullPresenter } from "./proforma-items.full.presenter";
|
|
||||||
import type { ProformaRecipientFullPresenter } from "./proforma-recipient.full.presenter";
|
|
||||||
|
|
||||||
export class ProformaFullPresenter extends Presenter<Proforma, GetProformaByIdResponseDTO> {
|
|
||||||
toOutput(proforma: Proforma): GetProformaByIdResponseDTO {
|
|
||||||
const itemsPresenter = this.presenterRegistry.getPresenter({
|
|
||||||
resource: "proforma-items",
|
|
||||||
projection: "FULL",
|
|
||||||
}) as ProformaItemsFullPresenter;
|
|
||||||
|
|
||||||
const recipientPresenter = this.presenterRegistry.getPresenter({
|
|
||||||
resource: "proforma-recipient",
|
|
||||||
projection: "FULL",
|
|
||||||
}) as ProformaRecipientFullPresenter;
|
|
||||||
|
|
||||||
const recipient = recipientPresenter.toOutput(proforma);
|
|
||||||
const items = itemsPresenter.toOutput(proforma.items);
|
|
||||||
const allAmounts = proforma.calculateAllAmounts();
|
|
||||||
|
|
||||||
const payment = proforma.paymentMethod.match(
|
|
||||||
(payment) => {
|
|
||||||
const { id, payment_description } = payment.toObjectString();
|
|
||||||
return {
|
|
||||||
payment_id: id,
|
|
||||||
payment_description,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
() => undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
let totalIvaAmount = InvoiceAmount.zero(proforma.currencyCode.code);
|
|
||||||
let totalRecAmount = InvoiceAmount.zero(proforma.currencyCode.code);
|
|
||||||
let totalRetentionAmount = InvoiceAmount.zero(proforma.currencyCode.code);
|
|
||||||
|
|
||||||
const invoiceTaxes = proforma.getTaxes().map((taxGroup) => {
|
|
||||||
const { ivaAmount, recAmount, retentionAmount, totalAmount } = taxGroup.calculateAmounts();
|
|
||||||
|
|
||||||
totalIvaAmount = totalIvaAmount.add(ivaAmount);
|
|
||||||
totalRecAmount = totalRecAmount.add(recAmount);
|
|
||||||
totalRetentionAmount = totalRetentionAmount.add(retentionAmount);
|
|
||||||
|
|
||||||
return {
|
|
||||||
taxable_amount: taxGroup.taxableAmount.toObjectString(),
|
|
||||||
|
|
||||||
iva_code: taxGroup.iva.code,
|
|
||||||
iva_percentage: taxGroup.iva.percentage.toObjectString(),
|
|
||||||
iva_amount: ivaAmount.toObjectString(),
|
|
||||||
|
|
||||||
rec_code: taxGroup.rec.match(
|
|
||||||
(rec) => rec.code,
|
|
||||||
() => ""
|
|
||||||
),
|
|
||||||
|
|
||||||
rec_percentage: taxGroup.rec.match(
|
|
||||||
(rec) => rec.percentage.toObjectString(),
|
|
||||||
() => ({ value: "", scale: "" })
|
|
||||||
),
|
|
||||||
|
|
||||||
rec_amount: recAmount.toObjectString(),
|
|
||||||
|
|
||||||
retention_code: taxGroup.retention.match(
|
|
||||||
(retention) => retention.code,
|
|
||||||
() => ""
|
|
||||||
),
|
|
||||||
|
|
||||||
retention_percentage: taxGroup.retention.match(
|
|
||||||
(retention) => retention.percentage.toObjectString(),
|
|
||||||
() => ({ value: "", scale: "" })
|
|
||||||
),
|
|
||||||
|
|
||||||
retention_amount: retentionAmount.toObjectString(),
|
|
||||||
|
|
||||||
taxes_amount: totalAmount.toObjectString(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: proforma.id.toString(),
|
|
||||||
company_id: proforma.companyId.toString(),
|
|
||||||
|
|
||||||
invoice_number: proforma.invoiceNumber.toString(),
|
|
||||||
status: proforma.status.toPrimitive(),
|
|
||||||
series: maybeToEmptyString(proforma.series, (value) => value.toString()),
|
|
||||||
|
|
||||||
invoice_date: proforma.invoiceDate.toDateString(),
|
|
||||||
operation_date: maybeToEmptyString(proforma.operationDate, (value) => value.toDateString()),
|
|
||||||
|
|
||||||
reference: maybeToEmptyString(proforma.reference, (value) => value.toString()),
|
|
||||||
description: maybeToEmptyString(proforma.description, (value) => value.toString()),
|
|
||||||
notes: maybeToEmptyString(proforma.notes, (value) => value.toString()),
|
|
||||||
|
|
||||||
language_code: proforma.languageCode.toString(),
|
|
||||||
currency_code: proforma.currencyCode.toString(),
|
|
||||||
|
|
||||||
customer_id: proforma.customerId.toString(),
|
|
||||||
recipient,
|
|
||||||
|
|
||||||
taxes: invoiceTaxes,
|
|
||||||
|
|
||||||
payment_method: payment,
|
|
||||||
|
|
||||||
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
|
||||||
items_discount_amount: allAmounts.itemDiscountAmount.toObjectString(),
|
|
||||||
|
|
||||||
discount_percentage: proforma.globalDiscountPercentage.toObjectString(),
|
|
||||||
discount_amount: allAmounts.globalDiscountAmount.toObjectString(),
|
|
||||||
|
|
||||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
|
||||||
|
|
||||||
iva_amount: totalIvaAmount.toObjectString(),
|
|
||||||
rec_amount: totalRecAmount.toObjectString(),
|
|
||||||
retention_amount: totalRetentionAmount.toObjectString(),
|
|
||||||
|
|
||||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
|
||||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
|
||||||
|
|
||||||
items,
|
|
||||||
|
|
||||||
metadata: {
|
|
||||||
entity: "proforma",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
//export * from "./domain";
|
|
||||||
//export * from "./queries";
|
|
||||||
//export * from "./reports";
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./issued-invoices";
|
|
||||||
export * from "./proformas";
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./issued-invoice.list.presenter";
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
|
||||||
import { maybeToEmptyString } from "@repo/rdx-ddd";
|
|
||||||
import type { ArrayElement, Collection } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { ListIssuedInvoicesResponseDTO } from "../../../../../common/dto";
|
|
||||||
|
|
||||||
export class IssuedInvoiceListPresenter extends Presenter {
|
|
||||||
protected _mapInvoice(invoice: CustomerInvoiceListDTO) {
|
|
||||||
const recipientDTO = invoice.recipient.toObjectString();
|
|
||||||
|
|
||||||
const verifactuDTO = invoice.verifactu.match(
|
|
||||||
(verifactu) => verifactu.toObjectString(),
|
|
||||||
() => ({
|
|
||||||
status: "",
|
|
||||||
url: "",
|
|
||||||
qr_code: "",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const invoiceDTO: ArrayElement<ListIssuedInvoicesResponseDTO["items"]> = {
|
|
||||||
id: invoice.id.toString(),
|
|
||||||
company_id: invoice.companyId.toString(),
|
|
||||||
customer_id: invoice.customerId.toString(),
|
|
||||||
|
|
||||||
invoice_number: invoice.invoiceNumber.toString(),
|
|
||||||
status: invoice.status.toPrimitive(),
|
|
||||||
series: maybeToEmptyString(invoice.series, (value) => value.toString()),
|
|
||||||
|
|
||||||
invoice_date: invoice.invoiceDate.toDateString(),
|
|
||||||
operation_date: maybeToEmptyString(invoice.operationDate, (value) => value.toDateString()),
|
|
||||||
reference: maybeToEmptyString(invoice.reference, (value) => value.toString()),
|
|
||||||
description: maybeToEmptyString(invoice.description, (value) => value.toString()),
|
|
||||||
|
|
||||||
recipient: recipientDTO,
|
|
||||||
|
|
||||||
language_code: invoice.languageCode.code,
|
|
||||||
currency_code: invoice.currencyCode.code,
|
|
||||||
|
|
||||||
subtotal_amount: invoice.subtotalAmount.toObjectString(),
|
|
||||||
discount_percentage: invoice.discountPercentage.toObjectString(),
|
|
||||||
discount_amount: invoice.discountAmount.toObjectString(),
|
|
||||||
taxable_amount: invoice.taxableAmount.toObjectString(),
|
|
||||||
taxes_amount: invoice.taxesAmount.toObjectString(),
|
|
||||||
total_amount: invoice.totalAmount.toObjectString(),
|
|
||||||
|
|
||||||
verifactu: verifactuDTO,
|
|
||||||
|
|
||||||
metadata: {
|
|
||||||
entity: "issued-invoice",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return invoiceDTO;
|
|
||||||
}
|
|
||||||
|
|
||||||
toOutput(params: {
|
|
||||||
invoices: Collection<CustomerInvoiceListDTO>;
|
|
||||||
criteria: Criteria;
|
|
||||||
}): ListIssuedInvoicesResponseDTO {
|
|
||||||
const { invoices, criteria } = params;
|
|
||||||
|
|
||||||
const _invoices = invoices.map((invoice) => this._mapInvoice(invoice));
|
|
||||||
const _totalItems = invoices.total();
|
|
||||||
|
|
||||||
return {
|
|
||||||
page: criteria.pageNumber,
|
|
||||||
per_page: criteria.pageSize,
|
|
||||||
total_pages: Math.ceil(_totalItems / criteria.pageSize),
|
|
||||||
total_items: _totalItems,
|
|
||||||
items: _invoices,
|
|
||||||
metadata: {
|
|
||||||
entity: "issued-invoices",
|
|
||||||
criteria: criteria.toJSON(),
|
|
||||||
//links: {
|
|
||||||
// self: `/api/customer-invoices?page=${criteria.pageNumber}&per_page=${criteria.pageSize}`,
|
|
||||||
// first: `/api/customer-invoices?page=1&per_page=${criteria.pageSize}`,
|
|
||||||
// last: `/api/customer-invoices?page=${Math.ceil(totalItems / criteria.pageSize)}&per_page=${criteria.pageSize}`,
|
|
||||||
//},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./proforma.list.presenter";
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
import { Presenter } from "@erp/core/api";
|
|
||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
|
||||||
import { maybeToEmptyString } from "@repo/rdx-ddd";
|
|
||||||
import type { ArrayElement, Collection } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { ListProformasResponseDTO } from "../../../../../common/dto";
|
|
||||||
import type { CustomerInvoiceListDTO } from "../../../../infrastructure";
|
|
||||||
|
|
||||||
export class ProformaListPresenter extends Presenter {
|
|
||||||
protected _mapProforma(proforma: CustomerInvoiceListDTO) {
|
|
||||||
const recipientDTO = proforma.recipient.toObjectString();
|
|
||||||
|
|
||||||
const invoiceDTO: ArrayElement<ListProformasResponseDTO["items"]> = {
|
|
||||||
id: proforma.id.toString(),
|
|
||||||
company_id: proforma.companyId.toString(),
|
|
||||||
is_proforma: proforma.isProforma,
|
|
||||||
customer_id: proforma.customerId.toString(),
|
|
||||||
|
|
||||||
invoice_number: proforma.invoiceNumber.toString(),
|
|
||||||
status: proforma.status.toPrimitive(),
|
|
||||||
series: maybeToEmptyString(proforma.series, (value) => value.toString()),
|
|
||||||
|
|
||||||
invoice_date: proforma.invoiceDate.toDateString(),
|
|
||||||
operation_date: maybeToEmptyString(proforma.operationDate, (value) => value.toDateString()),
|
|
||||||
reference: maybeToEmptyString(proforma.reference, (value) => value.toString()),
|
|
||||||
description: maybeToEmptyString(proforma.description, (value) => value.toString()),
|
|
||||||
|
|
||||||
recipient: recipientDTO,
|
|
||||||
|
|
||||||
language_code: proforma.languageCode.code,
|
|
||||||
currency_code: proforma.currencyCode.code,
|
|
||||||
|
|
||||||
subtotal_amount: proforma.subtotalAmount.toObjectString(),
|
|
||||||
discount_percentage: proforma.discountPercentage.toObjectString(),
|
|
||||||
discount_amount: proforma.discountAmount.toObjectString(),
|
|
||||||
taxable_amount: proforma.taxableAmount.toObjectString(),
|
|
||||||
taxes_amount: proforma.taxesAmount.toObjectString(),
|
|
||||||
total_amount: proforma.totalAmount.toObjectString(),
|
|
||||||
|
|
||||||
metadata: {
|
|
||||||
entity: "proforma",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return invoiceDTO;
|
|
||||||
}
|
|
||||||
|
|
||||||
toOutput(params: {
|
|
||||||
proformas: Collection<CustomerInvoiceListDTO>;
|
|
||||||
criteria: Criteria;
|
|
||||||
}): ListProformasResponseDTO {
|
|
||||||
const { proformas, criteria } = params;
|
|
||||||
|
|
||||||
const _proformas = proformas.map((proforma) => this._mapProforma(proforma));
|
|
||||||
const _totalItems = proformas.total();
|
|
||||||
|
|
||||||
return {
|
|
||||||
page: criteria.pageNumber,
|
|
||||||
per_page: criteria.pageSize,
|
|
||||||
total_pages: Math.ceil(_totalItems / criteria.pageSize),
|
|
||||||
total_items: _totalItems,
|
|
||||||
items: _proformas,
|
|
||||||
metadata: {
|
|
||||||
entity: "proformas",
|
|
||||||
criteria: criteria.toJSON(),
|
|
||||||
//links: {
|
|
||||||
// self: `/api/customer-invoices?page=${criteria.pageNumber}&per_page=${criteria.pageSize}`,
|
|
||||||
// first: `/api/customer-invoices?page=1&per_page=${criteria.pageSize}`,
|
|
||||||
// last: `/api/customer-invoices?page=${Math.ceil(totalItems / criteria.pageSize)}&per_page=${criteria.pageSize}`,
|
|
||||||
//},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./issued-invoices";
|
|
||||||
//export * from "./proformas";
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
//export * from "./issued-invoice.report.presenter";
|
|
||||||
//export * from "./issued-invoice-items.report.presenter";
|
|
||||||
//export * from "./issued-invoice-taxes.report.presenter";
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
|
|
||||||
import { type ISnapshotBuilderParams, Presenter } from "@erp/core/api";
|
|
||||||
import type { GetIssuedInvoiceByIdResponseDTO } from "@erp/customer-invoices/common";
|
|
||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
type IssuedInvoiceItemsDTO = GetIssuedInvoiceByIdResponseDTO["items"];
|
|
||||||
type IssuedInvoiceItemDTO = ArrayElement<IssuedInvoiceItemsDTO>;
|
|
||||||
|
|
||||||
export class IssuedInvoiceItemsReportPresenter extends Presenter<IssuedInvoiceItemsDTO, unknown> {
|
|
||||||
private _locale!: string;
|
|
||||||
|
|
||||||
private _mapItem(invoiceItem: IssuedInvoiceItemDTO, _index: number) {
|
|
||||||
const moneyOptions = {
|
|
||||||
hideZeros: true,
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...invoiceItem,
|
|
||||||
|
|
||||||
quantity: QuantityDTOHelper.format(invoiceItem.quantity, this._locale, {
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
}),
|
|
||||||
unit_amount: MoneyDTOHelper.format(invoiceItem.unit_amount, this._locale, moneyOptions),
|
|
||||||
subtotal_amount: MoneyDTOHelper.format(
|
|
||||||
invoiceItem.subtotal_amount,
|
|
||||||
this._locale,
|
|
||||||
moneyOptions
|
|
||||||
),
|
|
||||||
discount_percentage: PercentageDTOHelper.format(
|
|
||||||
invoiceItem.discount_percentage,
|
|
||||||
this._locale,
|
|
||||||
{
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
discount_amount: MoneyDTOHelper.format(
|
|
||||||
invoiceItem.discount_amount,
|
|
||||||
this._locale,
|
|
||||||
moneyOptions
|
|
||||||
),
|
|
||||||
taxable_amount: MoneyDTOHelper.format(invoiceItem.taxable_amount, this._locale, moneyOptions),
|
|
||||||
taxes_amount: MoneyDTOHelper.format(invoiceItem.taxes_amount, this._locale, moneyOptions),
|
|
||||||
total_amount: MoneyDTOHelper.format(invoiceItem.total_amount, this._locale, moneyOptions),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
toOutput(issuedInvoiceItems: IssuedInvoiceItemsDTO, params: ISnapshotBuilderParams): unknown {
|
|
||||||
const { locale } = params as {
|
|
||||||
locale: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
this._locale = locale;
|
|
||||||
|
|
||||||
return issuedInvoiceItems.map((item, index) => {
|
|
||||||
return this._mapItem(item, index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
import {
|
|
||||||
type JsonTaxCatalogProvider,
|
|
||||||
MoneyDTOHelper,
|
|
||||||
PercentageDTOHelper,
|
|
||||||
SpainTaxCatalogProvider,
|
|
||||||
} from "@erp/core";
|
|
||||||
import { type ISnapshotBuilderParams, Presenter } from "@erp/core/api";
|
|
||||||
import type { GetIssuedInvoiceByIdResponseDTO } from "@erp/customer-invoices/common";
|
|
||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
type IssuedInvoiceTaxesDTO = GetIssuedInvoiceByIdResponseDTO["taxes"];
|
|
||||||
type IssuedInvoiceTaxDTO = ArrayElement<IssuedInvoiceTaxesDTO>;
|
|
||||||
|
|
||||||
export class IssuedInvoiceTaxesReportPresenter extends Presenter<IssuedInvoiceTaxesDTO, unknown> {
|
|
||||||
private _locale!: string;
|
|
||||||
private _taxCatalog!: JsonTaxCatalogProvider;
|
|
||||||
|
|
||||||
private _mapTax(taxItem: IssuedInvoiceTaxDTO) {
|
|
||||||
const moneyOptions = {
|
|
||||||
hideZeros: true,
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
//const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
|
||||||
|
|
||||||
return {
|
|
||||||
taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions),
|
|
||||||
|
|
||||||
iva_code: taxItem.iva_code,
|
|
||||||
iva_percentage: PercentageDTOHelper.format(taxItem.iva_percentage, this._locale),
|
|
||||||
iva_amount: MoneyDTOHelper.format(taxItem.iva_amount, this._locale, moneyOptions),
|
|
||||||
|
|
||||||
rec_code: taxItem.rec_code,
|
|
||||||
rec_percentage: PercentageDTOHelper.format(taxItem.rec_percentage, this._locale),
|
|
||||||
rec_amount: MoneyDTOHelper.format(taxItem.rec_amount, this._locale, moneyOptions),
|
|
||||||
|
|
||||||
retention_code: taxItem.retention_code,
|
|
||||||
retention_percentage: PercentageDTOHelper.format(taxItem.retention_percentage, this._locale),
|
|
||||||
retention_amount: MoneyDTOHelper.format(taxItem.rec_amount, this._locale, moneyOptions),
|
|
||||||
|
|
||||||
taxes_amount: MoneyDTOHelper.format(taxItem.taxes_amount, this._locale, moneyOptions),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
toOutput(taxes: IssuedInvoiceTaxesDTO, params: ISnapshotBuilderParams): unknown {
|
|
||||||
const { locale } = params as {
|
|
||||||
locale: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
this._locale = locale;
|
|
||||||
this._taxCatalog = SpainTaxCatalogProvider();
|
|
||||||
|
|
||||||
return taxes?.map((item, _index) => {
|
|
||||||
return this._mapTax(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
import { MoneyDTOHelper, PercentageDTOHelper } from "@erp/core";
|
|
||||||
import { Presenter } from "@erp/core/api";
|
|
||||||
import { DateHelper } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { GetIssuedInvoiceByIdResponseDTO } from "../../../../../common/dto";
|
|
||||||
|
|
||||||
export class IssuedInvoiceReportPresenter extends Presenter<
|
|
||||||
GetIssuedInvoiceByIdResponseDTO,
|
|
||||||
unknown
|
|
||||||
> {
|
|
||||||
private _formatPaymentMethodDTO(
|
|
||||||
paymentMethod?: GetIssuedInvoiceByIdResponseDTO["payment_method"]
|
|
||||||
) {
|
|
||||||
if (!paymentMethod) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return paymentMethod.description ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
toOutput(issuedInvoiceDTO: GetIssuedInvoiceByIdResponseDTO) {
|
|
||||||
const itemsPresenter = this.presenterRegistry.getPresenter({
|
|
||||||
resource: "issued-invoice-items",
|
|
||||||
projection: "REPORT",
|
|
||||||
format: "DTO",
|
|
||||||
});
|
|
||||||
|
|
||||||
const taxesPresenter = this.presenterRegistry.getPresenter({
|
|
||||||
resource: "issued-invoice-taxes",
|
|
||||||
projection: "REPORT",
|
|
||||||
format: "DTO",
|
|
||||||
});
|
|
||||||
|
|
||||||
const locale = issuedInvoiceDTO.language_code;
|
|
||||||
const itemsDTO = itemsPresenter.toOutput(issuedInvoiceDTO.items, {
|
|
||||||
locale,
|
|
||||||
});
|
|
||||||
|
|
||||||
const taxesDTO = taxesPresenter.toOutput(issuedInvoiceDTO.taxes, {
|
|
||||||
locale,
|
|
||||||
});
|
|
||||||
|
|
||||||
const moneyOptions = {
|
|
||||||
hideZeros: true,
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...issuedInvoiceDTO,
|
|
||||||
taxes: taxesDTO,
|
|
||||||
items: itemsDTO,
|
|
||||||
|
|
||||||
recipient: {
|
|
||||||
...issuedInvoiceDTO.recipient,
|
|
||||||
format_address: this.formatAddress(issuedInvoiceDTO.recipient),
|
|
||||||
},
|
|
||||||
|
|
||||||
invoice_date: DateHelper.format(issuedInvoiceDTO.invoice_date, locale),
|
|
||||||
subtotal_amount: MoneyDTOHelper.format(
|
|
||||||
issuedInvoiceDTO.subtotal_amount,
|
|
||||||
locale,
|
|
||||||
moneyOptions
|
|
||||||
),
|
|
||||||
discount_percentage: PercentageDTOHelper.format(
|
|
||||||
issuedInvoiceDTO.discount_percentage,
|
|
||||||
locale,
|
|
||||||
{ hideZeros: true }
|
|
||||||
),
|
|
||||||
discount_amount: MoneyDTOHelper.format(
|
|
||||||
issuedInvoiceDTO.discount_amount,
|
|
||||||
locale,
|
|
||||||
moneyOptions
|
|
||||||
),
|
|
||||||
taxable_amount: MoneyDTOHelper.format(issuedInvoiceDTO.taxable_amount, locale, moneyOptions),
|
|
||||||
taxes_amount: MoneyDTOHelper.format(issuedInvoiceDTO.taxes_amount, locale, moneyOptions),
|
|
||||||
total_amount: MoneyDTOHelper.format(issuedInvoiceDTO.total_amount, locale, moneyOptions),
|
|
||||||
|
|
||||||
payment_method: this._formatPaymentMethodDTO(issuedInvoiceDTO.payment_method),
|
|
||||||
|
|
||||||
verifactu: {
|
|
||||||
...issuedInvoiceDTO.verifactu,
|
|
||||||
qr_code: issuedInvoiceDTO.verifactu.qr_code.replace("data:image/png;base64,", ""),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected formatAddress(recipient: GetIssuedInvoiceByIdResponseDTO["recipient"]): string {
|
|
||||||
const lines: string[] = [];
|
|
||||||
|
|
||||||
// Líneas de calle
|
|
||||||
if (recipient.street) {
|
|
||||||
lines.push(recipient.street);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipient.street2) {
|
|
||||||
lines.push(recipient.street2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ciudad + código postal
|
|
||||||
const cityLine = [recipient.postal_code, recipient.city].filter(Boolean).join(" ");
|
|
||||||
|
|
||||||
if (cityLine) {
|
|
||||||
lines.push(cityLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provincia
|
|
||||||
if (recipient.province && recipient.province !== recipient.city) {
|
|
||||||
lines.push(recipient.province);
|
|
||||||
}
|
|
||||||
|
|
||||||
// País
|
|
||||||
if (recipient.country && recipient.country !== "es") {
|
|
||||||
lines.push(recipient.country);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines.join("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export * from "./proforma.report.presenter";
|
|
||||||
export * from "./proforma-items.report.presenter";
|
|
||||||
export * from "./proforma-taxes.report.presenter";
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
|
|
||||||
import { type ISnapshotBuilderParams, Presenter } from "@erp/core/api";
|
|
||||||
import type { GetProformaByIdResponseDTO } from "@erp/customer-invoices/common";
|
|
||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
type ProformaItemsDTO = GetProformaByIdResponseDTO["items"];
|
|
||||||
type ProformaItemDTO = ArrayElement<ProformaItemsDTO>;
|
|
||||||
|
|
||||||
export class ProformaItemsReportPresenter extends Presenter<ProformaItemsDTO, unknown> {
|
|
||||||
private _locale!: string;
|
|
||||||
|
|
||||||
private _mapItem(proformaItem: ProformaItemDTO, _index: number) {
|
|
||||||
const moneyOptions = {
|
|
||||||
hideZeros: true,
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...proformaItem,
|
|
||||||
|
|
||||||
quantity: QuantityDTOHelper.format(proformaItem.quantity, this._locale, {
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
}),
|
|
||||||
unit_amount: MoneyDTOHelper.format(proformaItem.unit_amount, this._locale, moneyOptions),
|
|
||||||
subtotal_amount: MoneyDTOHelper.format(
|
|
||||||
proformaItem.subtotal_amount,
|
|
||||||
this._locale,
|
|
||||||
moneyOptions
|
|
||||||
),
|
|
||||||
discount_percentage: PercentageDTOHelper.format(
|
|
||||||
proformaItem.discount_percentage,
|
|
||||||
this._locale,
|
|
||||||
{
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
discount_amount: MoneyDTOHelper.format(
|
|
||||||
proformaItem.discount_amount,
|
|
||||||
this._locale,
|
|
||||||
moneyOptions
|
|
||||||
),
|
|
||||||
taxable_amount: MoneyDTOHelper.format(
|
|
||||||
proformaItem.taxable_amount,
|
|
||||||
this._locale,
|
|
||||||
moneyOptions
|
|
||||||
),
|
|
||||||
taxes_amount: MoneyDTOHelper.format(proformaItem.taxes_amount, this._locale, moneyOptions),
|
|
||||||
total_amount: MoneyDTOHelper.format(proformaItem.total_amount, this._locale, moneyOptions),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
toOutput(proformaItems: ProformaItemsDTO, params: ISnapshotBuilderParams): unknown {
|
|
||||||
const { locale } = params as {
|
|
||||||
locale: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
this._locale = locale;
|
|
||||||
|
|
||||||
return proformaItems.map((item, index) => {
|
|
||||||
return this._mapItem(item, index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
import {
|
|
||||||
type JsonTaxCatalogProvider,
|
|
||||||
MoneyDTOHelper,
|
|
||||||
PercentageDTOHelper,
|
|
||||||
SpainTaxCatalogProvider,
|
|
||||||
} from "@erp/core";
|
|
||||||
import { type ISnapshotBuilderParams, Presenter } from "@erp/core/api";
|
|
||||||
import type { GetProformaByIdResponseDTO } from "@erp/customer-invoices/common";
|
|
||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
type ProformaTaxesDTO = GetProformaByIdResponseDTO["taxes"];
|
|
||||||
type ProformaTaxDTO = ArrayElement<ProformaTaxesDTO>;
|
|
||||||
|
|
||||||
export class ProformaTaxesReportPresenter extends Presenter<ProformaTaxesDTO, unknown> {
|
|
||||||
private _locale!: string;
|
|
||||||
private _taxCatalog!: JsonTaxCatalogProvider;
|
|
||||||
|
|
||||||
private _mapTax(taxItem: ProformaTaxDTO) {
|
|
||||||
const moneyOptions = {
|
|
||||||
hideZeros: true,
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
//const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
|
||||||
|
|
||||||
return {
|
|
||||||
taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions),
|
|
||||||
|
|
||||||
iva_code: taxItem.iva_code,
|
|
||||||
iva_percentage: PercentageDTOHelper.format(taxItem.iva_percentage, this._locale),
|
|
||||||
iva_amount: MoneyDTOHelper.format(taxItem.iva_amount, this._locale, moneyOptions),
|
|
||||||
|
|
||||||
rec_code: taxItem.rec_code,
|
|
||||||
rec_percentage: PercentageDTOHelper.format(taxItem.rec_percentage, this._locale),
|
|
||||||
rec_amount: MoneyDTOHelper.format(taxItem.rec_amount, this._locale, moneyOptions),
|
|
||||||
|
|
||||||
retention_code: taxItem.retention_code,
|
|
||||||
retention_percentage: PercentageDTOHelper.format(taxItem.retention_percentage, this._locale),
|
|
||||||
retention_amount: MoneyDTOHelper.format(taxItem.rec_amount, this._locale, moneyOptions),
|
|
||||||
|
|
||||||
taxes_amount: MoneyDTOHelper.format(taxItem.taxes_amount, this._locale, moneyOptions),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
toOutput(taxes: ProformaTaxesDTO, params: ISnapshotBuilderParams): unknown {
|
|
||||||
const { locale } = params as {
|
|
||||||
locale: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
this._locale = locale;
|
|
||||||
this._taxCatalog = SpainTaxCatalogProvider();
|
|
||||||
|
|
||||||
return taxes.map((item, _index) => {
|
|
||||||
return this._mapTax(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
import { DateHelper, MoneyDTOHelper, PercentageDTOHelper } from "@erp/core";
|
|
||||||
import { Presenter } from "@erp/core/api";
|
|
||||||
|
|
||||||
import type { GetProformaByIdResponseDTO } from "../../../../../common/dto";
|
|
||||||
|
|
||||||
export class ProformaReportPresenter extends Presenter<GetProformaByIdResponseDTO, unknown> {
|
|
||||||
private _formatPaymentMethodDTO(paymentMethod?: GetProformaByIdResponseDTO["payment_method"]) {
|
|
||||||
if (!paymentMethod) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return paymentMethod.description ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
toOutput(proformaDTO: GetProformaByIdResponseDTO) {
|
|
||||||
const itemsPresenter = this.presenterRegistry.getPresenter({
|
|
||||||
resource: "proforma-items",
|
|
||||||
projection: "REPORT",
|
|
||||||
format: "JSON",
|
|
||||||
});
|
|
||||||
|
|
||||||
const taxesPresenter = this.presenterRegistry.getPresenter({
|
|
||||||
resource: "proforma-taxes",
|
|
||||||
projection: "REPORT",
|
|
||||||
format: "JSON",
|
|
||||||
});
|
|
||||||
|
|
||||||
const locale = proformaDTO.language_code;
|
|
||||||
const itemsDTO = itemsPresenter.toOutput(proformaDTO.items, {
|
|
||||||
locale,
|
|
||||||
});
|
|
||||||
|
|
||||||
const taxesDTO = taxesPresenter.toOutput(proformaDTO.taxes, {
|
|
||||||
locale,
|
|
||||||
});
|
|
||||||
|
|
||||||
const moneyOptions = {
|
|
||||||
hideZeros: true,
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...proformaDTO,
|
|
||||||
taxes: taxesDTO,
|
|
||||||
items: itemsDTO,
|
|
||||||
|
|
||||||
invoice_date: DateHelper.format(proformaDTO.invoice_date, locale),
|
|
||||||
subtotal_amount: MoneyDTOHelper.format(proformaDTO.subtotal_amount, locale, moneyOptions),
|
|
||||||
discount_percentage: PercentageDTOHelper.format(proformaDTO.discount_percentage, locale),
|
|
||||||
discount_amount: MoneyDTOHelper.format(proformaDTO.discount_amount, locale, moneyOptions),
|
|
||||||
taxable_amount: MoneyDTOHelper.format(proformaDTO.taxable_amount, locale, moneyOptions),
|
|
||||||
taxes_amount: MoneyDTOHelper.format(proformaDTO.taxes_amount, locale, moneyOptions),
|
|
||||||
total_amount: MoneyDTOHelper.format(proformaDTO.total_amount, locale, moneyOptions),
|
|
||||||
|
|
||||||
payment_method: this._formatPaymentMethodDTO(proformaDTO.payment_method),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +1,32 @@
|
|||||||
import { MoneyValue, type MoneyValueProps, type Percentage, type Quantity } from "@repo/rdx-ddd";
|
import {
|
||||||
import { Result } from "@repo/rdx-utils";
|
MoneyValue,
|
||||||
|
type MoneyValueProps,
|
||||||
|
type Percentage,
|
||||||
|
type Quantity,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { NumberHelper, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
type InvoiceAmountProps = Pick<MoneyValueProps, "value" | "currency_code">;
|
type InvoiceAmountProps = MoneyValueProps;
|
||||||
|
|
||||||
|
type InvoiceAmountObjectString = {
|
||||||
|
value: string;
|
||||||
|
scale: string;
|
||||||
|
currency_code: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class InvoiceAmount extends MoneyValue {
|
export class InvoiceAmount extends MoneyValue {
|
||||||
public static DEFAULT_SCALE = 2;
|
public static DEFAULT_SCALE = 2;
|
||||||
|
|
||||||
static create({ value, currency_code }: InvoiceAmountProps) {
|
static create({ value, currency_code, scale }: InvoiceAmountProps) {
|
||||||
|
if (scale && scale !== InvoiceAmount.DEFAULT_SCALE) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("InvalidScale", [
|
||||||
|
{ message: `InvoiceAmount scale must be ${InvoiceAmount.DEFAULT_SCALE}` },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
value: Number(value),
|
value: Number(value),
|
||||||
scale: InvoiceAmount.DEFAULT_SCALE,
|
scale: InvoiceAmount.DEFAULT_SCALE,
|
||||||
@ -23,7 +43,26 @@ export class InvoiceAmount extends MoneyValue {
|
|||||||
return InvoiceAmount.create(props).data;
|
return InvoiceAmount.create(props).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
toObjectString() {
|
static fromObjectString(dto: InvoiceAmountObjectString) {
|
||||||
|
const value = NumberHelper.toSafeNumber(dto.value);
|
||||||
|
const scale = dto.scale ? NumberHelper.toSafeNumber(dto.scale) : InvoiceAmount.DEFAULT_SCALE;
|
||||||
|
|
||||||
|
if (!(Number.isFinite(value) && Number.isInteger(scale))) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("InvalidNumericValues", [
|
||||||
|
{ message: "InvoiceAmount payload contains invalid numeric values" },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvoiceAmount.create({
|
||||||
|
value,
|
||||||
|
scale,
|
||||||
|
currency_code: dto.currency_code,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toObjectString(): InvoiceAmountObjectString {
|
||||||
return {
|
return {
|
||||||
value: String(this.value),
|
value: String(this.value),
|
||||||
scale: String(this.scale),
|
scale: String(this.scale),
|
||||||
|
|||||||
@ -1,14 +1,34 @@
|
|||||||
import { MoneyValue, type MoneyValueProps, type Percentage, type Quantity } from "@repo/rdx-ddd";
|
import {
|
||||||
import { Result } from "@repo/rdx-utils";
|
MoneyValue,
|
||||||
|
type MoneyValueProps,
|
||||||
|
type Percentage,
|
||||||
|
type Quantity,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { NumberHelper, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
type ItemAmountProps = Pick<MoneyValueProps, "value" | "currency_code">;
|
type ItemAmountProps = MoneyValueProps;
|
||||||
|
|
||||||
|
type ItemAmountObjectString = {
|
||||||
|
value: string;
|
||||||
|
scale: string;
|
||||||
|
currency_code: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class ItemAmount extends MoneyValue {
|
export class ItemAmount extends MoneyValue {
|
||||||
public static DEFAULT_SCALE = 4;
|
public static DEFAULT_SCALE = 4;
|
||||||
|
|
||||||
static create({ value, currency_code }: ItemAmountProps) {
|
static create({ value, currency_code, scale }: ItemAmountProps) {
|
||||||
|
if (scale && scale !== ItemAmount.DEFAULT_SCALE) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("InvalidScale", [
|
||||||
|
{ message: `ItemAmount scale must be ${ItemAmount.DEFAULT_SCALE}` },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
value: Number(value),
|
value,
|
||||||
scale: ItemAmount.DEFAULT_SCALE,
|
scale: ItemAmount.DEFAULT_SCALE,
|
||||||
currency_code,
|
currency_code,
|
||||||
};
|
};
|
||||||
@ -23,7 +43,26 @@ export class ItemAmount extends MoneyValue {
|
|||||||
return ItemAmount.create(props).data;
|
return ItemAmount.create(props).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
toObjectString() {
|
static fromObjectString(dto: ItemAmountObjectString) {
|
||||||
|
const value = NumberHelper.toSafeNumber(dto.value);
|
||||||
|
const scale = dto.scale ? NumberHelper.toSafeNumber(dto.scale) : ItemAmount.DEFAULT_SCALE;
|
||||||
|
|
||||||
|
if (!(Number.isFinite(value) && Number.isInteger(scale))) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("InvalidNumericValues", [
|
||||||
|
{ message: "ItemAmount payload contains invalid numeric values" },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ItemAmount.create({
|
||||||
|
value,
|
||||||
|
scale,
|
||||||
|
currency_code: dto.currency_code,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toObjectString(): ItemAmountObjectString {
|
||||||
return {
|
return {
|
||||||
value: String(this.value),
|
value: String(this.value),
|
||||||
scale: String(this.scale),
|
scale: String(this.scale),
|
||||||
|
|||||||
@ -1,15 +1,26 @@
|
|||||||
import { Quantity, type QuantityProps } from "@repo/rdx-ddd";
|
import { Quantity, type QuantityProps, ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
type ItemQuantityProps = Pick<QuantityProps, "value">;
|
type ItemQuantityProps = QuantityProps;
|
||||||
|
|
||||||
export class ItemQuantity extends Quantity {
|
export class ItemQuantity extends Quantity {
|
||||||
public static DEFAULT_SCALE = 2;
|
public static DEFAULT_SCALE = 2;
|
||||||
|
|
||||||
static create({ value }: ItemQuantityProps) {
|
static create({ value, scale }: ItemQuantityProps) {
|
||||||
return Quantity.create({
|
if (scale && scale !== ItemQuantity.DEFAULT_SCALE) {
|
||||||
value,
|
return Result.fail(
|
||||||
scale: ItemQuantity.DEFAULT_SCALE,
|
new ValidationErrorCollection("InvalidScale", [
|
||||||
});
|
{ message: `ItemQuantity scale must be ${ItemQuantity.DEFAULT_SCALE}` },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(
|
||||||
|
new ItemQuantity({
|
||||||
|
value,
|
||||||
|
scale: ItemQuantity.DEFAULT_SCALE,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static zero() {
|
static zero() {
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export const buildProformaPersistenceMappers = (
|
|||||||
const listMapper = new SequelizeProformaSummaryMapper();
|
const listMapper = new SequelizeProformaSummaryMapper();
|
||||||
|
|
||||||
// Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado
|
// Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado
|
||||||
const createMapper = new CreateProformaInputMapper();
|
const createMapper = new CreateProformaInputMapper(catalogs);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
domainMapper,
|
domainMapper,
|
||||||
|
|||||||
@ -61,6 +61,8 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
parent: Partial<IProformaCreateProps>;
|
parent: Partial<IProformaCreateProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { currencyCode } = parent;
|
||||||
|
|
||||||
const itemId = extractOrPushError(
|
const itemId = extractOrPushError(
|
||||||
UniqueID.create(raw.item_id),
|
UniqueID.create(raw.item_id),
|
||||||
`items[${index}].item_id`,
|
`items[${index}].item_id`,
|
||||||
@ -74,14 +76,20 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
);
|
);
|
||||||
|
|
||||||
const quantity = extractOrPushError(
|
const quantity = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.quantity_value, (v) => ItemQuantity.create({ value: v })),
|
maybeFromNullableResult(raw.quantity_value, (v) =>
|
||||||
|
ItemQuantity.create({ value: v, scale: raw.quantity_scale })
|
||||||
|
),
|
||||||
`items[${index}].quantity_value`,
|
`items[${index}].quantity_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const unitAmount = extractOrPushError(
|
const unitAmount = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.unit_amount_value, (value) =>
|
maybeFromNullableResult(raw.unit_amount_value, (value) =>
|
||||||
ItemAmount.create({ value, currency_code: parent.currencyCode?.code })
|
ItemAmount.create({
|
||||||
|
value,
|
||||||
|
currency_code: currencyCode?.code,
|
||||||
|
scale: raw.unit_amount_scale,
|
||||||
|
})
|
||||||
),
|
),
|
||||||
`items[${index}].unit_amount_value`,
|
`items[${index}].unit_amount_value`,
|
||||||
errors
|
errors
|
||||||
@ -89,7 +97,10 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
|
|
||||||
const itemDiscountPercentage = extractOrPushError(
|
const itemDiscountPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.item_discount_percentage_value, (v) =>
|
maybeFromNullableResult(raw.item_discount_percentage_value, (v) =>
|
||||||
DiscountPercentage.create({ value: v })
|
DiscountPercentage.create({
|
||||||
|
value: v,
|
||||||
|
scale: raw.item_discount_percentage_scale,
|
||||||
|
})
|
||||||
),
|
),
|
||||||
`items[${index}].item_discount_percentage`,
|
`items[${index}].item_discount_percentage`,
|
||||||
errors
|
errors
|
||||||
|
|||||||
@ -2,8 +2,9 @@ import {
|
|||||||
CurrencyCodeSchema,
|
CurrencyCodeSchema,
|
||||||
IsoDateSchema,
|
IsoDateSchema,
|
||||||
LanguageCodeSchema,
|
LanguageCodeSchema,
|
||||||
NumericStringSchema,
|
MoneySchema,
|
||||||
PercentageSchema,
|
PercentageSchema,
|
||||||
|
QuantitySchema,
|
||||||
} from "@erp/core";
|
} from "@erp/core";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
@ -15,8 +16,8 @@ export const UpdateProformaItemRequestSchema = z.object({
|
|||||||
|
|
||||||
description: z.string().nullable(),
|
description: z.string().nullable(),
|
||||||
|
|
||||||
quantity: NumericStringSchema.nullable(),
|
quantity: QuantitySchema.nullable(),
|
||||||
unit_amount: NumericStringSchema.nullable(),
|
unit_amount: MoneySchema.nullable(),
|
||||||
|
|
||||||
item_discount_percentage: PercentageSchema.nullable(),
|
item_discount_percentage: PercentageSchema.nullable(),
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,9 @@ import { TaxesBreakdownSchema } from "../taxes-breakdown.dto";
|
|||||||
|
|
||||||
export const ProformaItemDetailSchema = z.object({
|
export const ProformaItemDetailSchema = z.object({
|
||||||
id: z.uuid(),
|
id: z.uuid(),
|
||||||
is_valued: z.boolean(),
|
|
||||||
position: ItemPositionSchema,
|
position: ItemPositionSchema,
|
||||||
|
is_valued: z.boolean(),
|
||||||
|
|
||||||
description: z.string().nullable(),
|
description: z.string().nullable(),
|
||||||
|
|
||||||
quantity: QuantitySchema.nullable(),
|
quantity: QuantitySchema.nullable(),
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import type { ProformaItemUpdateForm } from "../entities";
|
|||||||
export const mapProformaItemsToProformaItemsUpdateForm = (
|
export const mapProformaItemsToProformaItemsUpdateForm = (
|
||||||
item: ProformaItem
|
item: ProformaItem
|
||||||
): ProformaItemUpdateForm => {
|
): ProformaItemUpdateForm => {
|
||||||
console.log(item);
|
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
position: item.position,
|
position: item.position,
|
||||||
|
|||||||
@ -74,8 +74,6 @@ export const useUpdateProformaController = (
|
|||||||
const initialValues = useMemo<ProformaUpdateForm>(() => {
|
const initialValues = useMemo<ProformaUpdateForm>(() => {
|
||||||
if (!proformaData) return buildProformaUpdateDefault();
|
if (!proformaData) return buildProformaUpdateDefault();
|
||||||
|
|
||||||
console.log("initialValues", proformaData);
|
|
||||||
|
|
||||||
return mapProformaToProformaUpdateForm(proformaData);
|
return mapProformaToProformaUpdateForm(proformaData);
|
||||||
}, [proformaData]);
|
}, [proformaData]);
|
||||||
|
|
||||||
@ -160,7 +158,7 @@ export const useUpdateProformaController = (
|
|||||||
|
|
||||||
console.log("Parche de actualización construido:", patchData);
|
console.log("Parche de actualización construido:", patchData);
|
||||||
|
|
||||||
const params = buildUpdateProformaByIdParams(proformaId, patchData);
|
const params = buildUpdateProformaByIdParams(proformaId, patchData, formData);
|
||||||
|
|
||||||
console.log("Enviando actualización con params:", params);
|
console.log("Enviando actualización con params:", params);
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/cor
|
|||||||
import { ObjectHelper } from "@repo/rdx-utils";
|
import { ObjectHelper } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { UpdateProformaByIdParams } from "../../shared/api";
|
import type { UpdateProformaByIdParams } from "../../shared/api";
|
||||||
import type { ProformaItemUpdatePatch, ProformaUpdatePatch } from "../entities";
|
import type { ProformaItemUpdatePatch, ProformaUpdateForm, ProformaUpdatePatch } from "../entities";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convierte el patch del formulario de actualización de proforma
|
* Convierte el patch del formulario de actualización de proforma
|
||||||
@ -21,12 +21,18 @@ import type { ProformaItemUpdatePatch, ProformaUpdatePatch } from "../entities";
|
|||||||
|
|
||||||
export const buildUpdateProformaByIdParams = (
|
export const buildUpdateProformaByIdParams = (
|
||||||
id: string,
|
id: string,
|
||||||
patch: ProformaUpdatePatch
|
patch: ProformaUpdatePatch,
|
||||||
|
formData: ProformaUpdateForm
|
||||||
): UpdateProformaByIdParams => {
|
): UpdateProformaByIdParams => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new Error("proformaId is required");
|
throw new Error("proformaId is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//const currencyCode = formData.currencyCode;
|
||||||
|
//const languageCode = formData.languageCode;
|
||||||
|
|
||||||
|
console.log("PATCH => ", patch);
|
||||||
|
|
||||||
const data: UpdateProformaByIdParams["data"] = {};
|
const data: UpdateProformaByIdParams["data"] = {};
|
||||||
|
|
||||||
if (ObjectHelper.hasOwn(patch, "series")) {
|
if (ObjectHelper.hasOwn(patch, "series")) {
|
||||||
@ -73,9 +79,11 @@ export const buildUpdateProformaByIdParams = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ObjectHelper.hasOwn(patch, "items")) {
|
if (ObjectHelper.hasOwn(patch, "items")) {
|
||||||
data.items = patch.items?.map(toProformaItemUpdateDTO);
|
data.items = patch.items?.map((item, index) => toProformaItemUpdateDTO(item, index, formData));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("DATA => ", data);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
data,
|
data,
|
||||||
@ -83,13 +91,20 @@ export const buildUpdateProformaByIdParams = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const toProformaItemUpdateDTO = (
|
const toProformaItemUpdateDTO = (
|
||||||
item: ProformaItemUpdatePatch
|
item: ProformaItemUpdatePatch,
|
||||||
|
_index: number,
|
||||||
|
formData: ProformaUpdateForm
|
||||||
): NonNullable<UpdateProformaByIdParams["data"]["items"]>[number] => {
|
): NonNullable<UpdateProformaByIdParams["data"]["items"]>[number] => {
|
||||||
|
const currencyCode = formData.currencyCode;
|
||||||
|
//const languageCode = formData.languageCode;
|
||||||
|
|
||||||
const quantity =
|
const quantity =
|
||||||
item.quantity === null ? null : QuantityDTOHelper.fromNumber(item.quantity, 4).value;
|
item.quantity === null ? null : QuantityDTOHelper.fromNumberNulleable(item.quantity, 2);
|
||||||
|
|
||||||
const unit_amount =
|
const unit_amount =
|
||||||
item.unitAmount === null ? null : MoneyDTOHelper.fromNumber(item.unitAmount, "EUR", 2).value;
|
item.unitAmount === null
|
||||||
|
? null
|
||||||
|
: MoneyDTOHelper.fromNumberNulleable(item.unitAmount, currencyCode, 4);
|
||||||
|
|
||||||
const is_valued = item.isValued;
|
const is_valued = item.isValued;
|
||||||
|
|
||||||
@ -105,8 +120,8 @@ const toProformaItemUpdateDTO = (
|
|||||||
item_discount_percentage:
|
item_discount_percentage:
|
||||||
item.itemDiscountPercentage === null
|
item.itemDiscountPercentage === null
|
||||||
? null
|
? null
|
||||||
: PercentageDTOHelper.fromNumber(item.itemDiscountPercentage, 2),
|
: PercentageDTOHelper.fromNumber(item.itemDiscountPercentage),
|
||||||
|
|
||||||
taxes: "#;#;#",
|
taxes: "#;#;#", // TODO: CAMBIAR!!!!
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import DineroFactory, { type Currency, type Dinero } from "dinero.js";
|
import DineroFactory, { type Currency, type Dinero } from "dinero.js";
|
||||||
|
|
||||||
|
import type { DomainError } from "../errors";
|
||||||
|
|
||||||
import type { Percentage } from "./percentage";
|
import type { Percentage } from "./percentage";
|
||||||
import type { Quantity } from "./quantity";
|
import type { Quantity } from "./quantity";
|
||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
@ -55,7 +57,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
|
|||||||
static DEFAULT_CURRENCY_CODE = DEFAULT_CURRENCY_CODE;
|
static DEFAULT_CURRENCY_CODE = DEFAULT_CURRENCY_CODE;
|
||||||
static EMPTY_MONEY_OBJECT = { value: "", scale: "", currency_code: "" };
|
static EMPTY_MONEY_OBJECT = { value: "", scale: "", currency_code: "" };
|
||||||
|
|
||||||
static create({ value, currency_code, scale }: MoneyValueProps) {
|
static create({ value, currency_code, scale }: MoneyValueProps): Result<MoneyValue, DomainError> {
|
||||||
const props = {
|
const props = {
|
||||||
value: Number(value),
|
value: Number(value),
|
||||||
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { NumberHelper, Result } from "@repo/rdx-utils";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { ValidationErrorCollection } from "../errors";
|
||||||
import { translateZodValidationError } from "../helpers";
|
import { translateZodValidationError } from "../helpers";
|
||||||
|
|
||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
@ -15,9 +16,14 @@ const DEFAULT_MAX_SCALE = 2;
|
|||||||
|
|
||||||
export interface PercentageProps {
|
export interface PercentageProps {
|
||||||
value: number;
|
value: number;
|
||||||
scale: number;
|
scale?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PercentageObjectString = {
|
||||||
|
value: string;
|
||||||
|
scale: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class Percentage extends ValueObject<PercentageProps> {
|
export class Percentage extends ValueObject<PercentageProps> {
|
||||||
static DEFAULT_SCALE = DEFAULT_SCALE;
|
static DEFAULT_SCALE = DEFAULT_SCALE;
|
||||||
static MIN_VALUE = DEFAULT_MIN_VALUE;
|
static MIN_VALUE = DEFAULT_MIN_VALUE;
|
||||||
@ -70,12 +76,30 @@ export class Percentage extends ValueObject<PercentageProps> {
|
|||||||
return Percentage.create({ value: 0, scale: Percentage.DEFAULT_SCALE }).data;
|
return Percentage.create({ value: 0, scale: Percentage.DEFAULT_SCALE }).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromObjectString(dto: PercentageObjectString) {
|
||||||
|
const value = NumberHelper.toSafeNumber(dto.value);
|
||||||
|
const scale = dto.scale ? NumberHelper.toSafeNumber(dto.scale) : Percentage.DEFAULT_SCALE;
|
||||||
|
|
||||||
|
if (!(Number.isFinite(value) && Number.isInteger(scale))) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("InvalidNumericValues", [
|
||||||
|
{ message: "Percentage payload contains invalid numeric values" },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Percentage.create({
|
||||||
|
value,
|
||||||
|
scale,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get value(): number {
|
get value(): number {
|
||||||
return this.props.value;
|
return this.props.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
get scale(): number {
|
get scale(): number {
|
||||||
return this.props.scale;
|
return this.props.scale ?? Percentage.DEFAULT_SCALE;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProps(): PercentageProps {
|
getProps(): PercentageProps {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { NumberHelper, Result } from "@repo/rdx-utils";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { ValidationErrorCollection } from "../errors";
|
||||||
import { translateZodValidationError } from "../helpers";
|
import { translateZodValidationError } from "../helpers";
|
||||||
|
|
||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
@ -11,9 +12,14 @@ const DEFAULT_MAX_SCALE = 2;
|
|||||||
|
|
||||||
export interface QuantityProps {
|
export interface QuantityProps {
|
||||||
value: number;
|
value: number;
|
||||||
scale: number;
|
scale?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QuantityObjectString = {
|
||||||
|
value: string;
|
||||||
|
scale: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class Quantity extends ValueObject<QuantityProps> {
|
export class Quantity extends ValueObject<QuantityProps> {
|
||||||
static EMPTY_QUANTITY_OBJECT = { value: "", scale: "" };
|
static EMPTY_QUANTITY_OBJECT = { value: "", scale: "" };
|
||||||
|
|
||||||
@ -43,12 +49,30 @@ export class Quantity extends ValueObject<QuantityProps> {
|
|||||||
return Result.ok(new Quantity({ ...(checkProps.data as QuantityProps) }));
|
return Result.ok(new Quantity({ ...(checkProps.data as QuantityProps) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromObjectString(dto: QuantityObjectString) {
|
||||||
|
const value = NumberHelper.toSafeNumber(dto.value);
|
||||||
|
const scale = dto.scale ? NumberHelper.toSafeNumber(dto.scale) : Quantity.DEFAULT_SCALE;
|
||||||
|
|
||||||
|
if (!(Number.isFinite(value) && Number.isInteger(scale))) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("InvalidNumericValues", [
|
||||||
|
{ message: "Quantity payload contains invalid numeric values" },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Quantity.create({
|
||||||
|
value,
|
||||||
|
scale,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get value(): number {
|
get value(): number {
|
||||||
return this.props.value;
|
return this.props.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
get scale(): number {
|
get scale(): number {
|
||||||
return this.props.scale;
|
return this.props.scale ?? Quantity.DEFAULT_SCALE;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProps(): QuantityProps {
|
getProps(): QuantityProps {
|
||||||
@ -67,7 +91,7 @@ export class Quantity extends ValueObject<QuantityProps> {
|
|||||||
return this.toNumber().toFixed(this.scale);
|
return this.toNumber().toFixed(this.scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
toObjectString() {
|
toObjectString(): QuantityObjectString {
|
||||||
return {
|
return {
|
||||||
value: String(this.value),
|
value: String(this.value),
|
||||||
scale: String(this.scale),
|
scale: String(this.scale),
|
||||||
|
|||||||
@ -5,6 +5,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"chatgpt.openOnStartup": true
|
"chatgpt.openOnStartup": true,
|
||||||
|
"chat.tools.terminal.autoApprove": {
|
||||||
|
"pnpm": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user