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 type { Result } from "@repo/rdx-utils";
|
||||
import { Percentage, type PercentageProps, ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
type DiscountPercentageProps = Pick<PercentageProps, "value">;
|
||||
type DiscountPercentageProps = PercentageProps;
|
||||
|
||||
export class DiscountPercentage extends Percentage {
|
||||
static DEFAULT_SCALE = 2;
|
||||
|
||||
static create({ value }: DiscountPercentageProps): Result<Percentage> {
|
||||
return Percentage.create({
|
||||
value,
|
||||
scale: DiscountPercentage.DEFAULT_SCALE,
|
||||
});
|
||||
static create({ value, scale }: DiscountPercentageProps): Result<Percentage> {
|
||||
if (scale && scale !== DiscountPercentage.DEFAULT_SCALE) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("InvalidScale", [
|
||||
{ message: `DiscountPercentage scale must be ${DiscountPercentage.DEFAULT_SCALE}` },
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok(
|
||||
new DiscountPercentage({
|
||||
value,
|
||||
scale: DiscountPercentage.DEFAULT_SCALE,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
@ -126,6 +141,7 @@ export const MoneyDTOHelper = {
|
||||
toNumericString,
|
||||
toNumericNulleable,
|
||||
fromNumber,
|
||||
fromNumberNulleable,
|
||||
fromNumericString,
|
||||
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.
|
||||
*/
|
||||
@ -106,6 +116,7 @@ export const PercentageDTOHelper = {
|
||||
toNumericString,
|
||||
toNumericNulleable,
|
||||
fromNumber,
|
||||
fromNumberNulleable,
|
||||
fromNumericString,
|
||||
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.
|
||||
*/
|
||||
@ -100,6 +110,7 @@ export const QuantityDTOHelper = {
|
||||
toNumericString,
|
||||
toNumericNulleable,
|
||||
fromNumber,
|
||||
fromNumberNulleable,
|
||||
fromNumericString,
|
||||
format,
|
||||
};
|
||||
|
||||
@ -11,12 +11,10 @@ export interface IProformaInputMappers {
|
||||
updateInputMapper: UpdateProformaInputMapper;
|
||||
}
|
||||
|
||||
export const buildProformaInputMappers = (_catalogs: ICatalogs): IProformaInputMappers => {
|
||||
//const { taxCatalog } = catalogs;
|
||||
|
||||
export const buildProformaInputMappers = (catalogs: ICatalogs): IProformaInputMappers => {
|
||||
// Mappers el DTO a las props validadas (ProformaProps) y luego construir agregado
|
||||
const createInputMapper = new CreateProformaInputMapper();
|
||||
const updateInputMapper = new UpdateProformaInputMapper();
|
||||
const createInputMapper = new CreateProformaInputMapper(catalogs);
|
||||
const updateInputMapper = new UpdateProformaInputMapper(catalogs);
|
||||
|
||||
return {
|
||||
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 {
|
||||
CurrencyCode,
|
||||
DomainError,
|
||||
@ -43,6 +44,11 @@ export interface ICreateProformaInputMapper {
|
||||
*/
|
||||
|
||||
export class CreateProformaInputMapper implements ICreateProformaInputMapper {
|
||||
private readonly taxCatalog: JsonTaxCatalogProvider;
|
||||
|
||||
constructor(catalogs: ICatalogs) {
|
||||
this.taxCatalog = catalogs.taxCatalog;
|
||||
}
|
||||
public map(
|
||||
dto: CreateProformaRequestDTO,
|
||||
params: { companyId: UniqueID }
|
||||
@ -238,16 +244,35 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper {
|
||||
taxesDTO: CreateProformaRequestDTO["items"][number]["taxes"],
|
||||
params: { itemIndex: number; errors: ValidationErrorDetail[] }
|
||||
): 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();
|
||||
}
|
||||
|
||||
params.errors.push({
|
||||
path: `items[${params.itemIndex}].taxes`,
|
||||
message: "Tax combination mapping is not implemented yet",
|
||||
});
|
||||
const [ivaCode, recCode, retentionCode] = parts;
|
||||
|
||||
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 {
|
||||
|
||||
@ -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 {
|
||||
CurrencyCode,
|
||||
DomainError,
|
||||
@ -11,7 +12,7 @@ import {
|
||||
extractOrPushError,
|
||||
maybeFromNullableResult,
|
||||
} 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 {
|
||||
@ -47,6 +48,11 @@ export interface IUpdateProformaInputMapper {
|
||||
*/
|
||||
|
||||
export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
||||
private readonly taxCatalog: JsonTaxCatalogProvider;
|
||||
constructor(catalogs: ICatalogs) {
|
||||
this.taxCatalog = catalogs.taxCatalog;
|
||||
}
|
||||
|
||||
public map(
|
||||
dto: UpdateProformaByIdRequestDTO,
|
||||
_params: { companyId: UniqueID }
|
||||
@ -201,26 +207,20 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
||||
);
|
||||
|
||||
const quantity = extractOrPushError(
|
||||
maybeFromNullableResult(item.quantity, (value) =>
|
||||
ItemQuantity.create({ value: NumberHelper.toSafeNumber(value) })
|
||||
),
|
||||
maybeFromNullableResult(item.quantity, (dto) => ItemQuantity.fromObjectString(dto)),
|
||||
`items[${index}].quantity`,
|
||||
params.errors
|
||||
);
|
||||
|
||||
const unitAmount = extractOrPushError(
|
||||
maybeFromNullableResult(item.unit_amount, (value) =>
|
||||
ItemAmount.create({ value: NumberHelper.toSafeNumber(value) })
|
||||
),
|
||||
maybeFromNullableResult(item.unit_amount, (dto) => ItemAmount.fromObjectString(dto)),
|
||||
`items[${index}].unit_amount`,
|
||||
params.errors
|
||||
);
|
||||
|
||||
const itemDiscountPercentage = extractOrPushError(
|
||||
maybeFromNullableResult(item.item_discount_percentage, (value) =>
|
||||
DiscountPercentage.create({
|
||||
value: NumberHelper.toSafeNumber(value.value),
|
||||
})
|
||||
maybeFromNullableResult(item.item_discount_percentage, (dto) =>
|
||||
DiscountPercentage.fromObjectString(dto)
|
||||
),
|
||||
`items[${index}].item_discount_percentage`,
|
||||
params.errors
|
||||
@ -246,24 +246,35 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
||||
taxesDTO: NonNullable<UpdateProformaByIdRequestDTO["items"]>[number]["taxes"],
|
||||
params: { itemIndex: number; errors: ValidationErrorDetail[] }
|
||||
): 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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",
|
||||
});
|
||||
const [ivaCode, recCode, retentionCode] = parts;
|
||||
|
||||
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 {
|
||||
|
||||
@ -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 { Result } from "@repo/rdx-utils";
|
||||
import {
|
||||
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 {
|
||||
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 = {
|
||||
value: Number(value),
|
||||
scale: InvoiceAmount.DEFAULT_SCALE,
|
||||
@ -23,7 +43,26 @@ export class InvoiceAmount extends MoneyValue {
|
||||
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 {
|
||||
value: String(this.value),
|
||||
scale: String(this.scale),
|
||||
|
||||
@ -1,14 +1,34 @@
|
||||
import { MoneyValue, type MoneyValueProps, type Percentage, type Quantity } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import {
|
||||
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 {
|
||||
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 = {
|
||||
value: Number(value),
|
||||
value,
|
||||
scale: ItemAmount.DEFAULT_SCALE,
|
||||
currency_code,
|
||||
};
|
||||
@ -23,7 +43,26 @@ export class ItemAmount extends MoneyValue {
|
||||
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 {
|
||||
value: String(this.value),
|
||||
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 {
|
||||
public static DEFAULT_SCALE = 2;
|
||||
|
||||
static create({ value }: ItemQuantityProps) {
|
||||
return Quantity.create({
|
||||
value,
|
||||
scale: ItemQuantity.DEFAULT_SCALE,
|
||||
});
|
||||
static create({ value, scale }: ItemQuantityProps) {
|
||||
if (scale && scale !== ItemQuantity.DEFAULT_SCALE) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("InvalidScale", [
|
||||
{ message: `ItemQuantity scale must be ${ItemQuantity.DEFAULT_SCALE}` },
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok(
|
||||
new ItemQuantity({
|
||||
value,
|
||||
scale: ItemQuantity.DEFAULT_SCALE,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
static zero() {
|
||||
|
||||
@ -23,7 +23,7 @@ export const buildProformaPersistenceMappers = (
|
||||
const listMapper = new SequelizeProformaSummaryMapper();
|
||||
|
||||
// Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado
|
||||
const createMapper = new CreateProformaInputMapper();
|
||||
const createMapper = new CreateProformaInputMapper(catalogs);
|
||||
|
||||
return {
|
||||
domainMapper,
|
||||
|
||||
@ -61,6 +61,8 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
||||
parent: Partial<IProformaCreateProps>;
|
||||
};
|
||||
|
||||
const { currencyCode } = parent;
|
||||
|
||||
const itemId = extractOrPushError(
|
||||
UniqueID.create(raw.item_id),
|
||||
`items[${index}].item_id`,
|
||||
@ -74,14 +76,20 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
||||
);
|
||||
|
||||
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`,
|
||||
errors
|
||||
);
|
||||
|
||||
const unitAmount = extractOrPushError(
|
||||
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`,
|
||||
errors
|
||||
@ -89,7 +97,10 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
||||
|
||||
const itemDiscountPercentage = extractOrPushError(
|
||||
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`,
|
||||
errors
|
||||
|
||||
@ -2,8 +2,9 @@ import {
|
||||
CurrencyCodeSchema,
|
||||
IsoDateSchema,
|
||||
LanguageCodeSchema,
|
||||
NumericStringSchema,
|
||||
MoneySchema,
|
||||
PercentageSchema,
|
||||
QuantitySchema,
|
||||
} from "@erp/core";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
@ -15,8 +16,8 @@ export const UpdateProformaItemRequestSchema = z.object({
|
||||
|
||||
description: z.string().nullable(),
|
||||
|
||||
quantity: NumericStringSchema.nullable(),
|
||||
unit_amount: NumericStringSchema.nullable(),
|
||||
quantity: QuantitySchema.nullable(),
|
||||
unit_amount: MoneySchema.nullable(),
|
||||
|
||||
item_discount_percentage: PercentageSchema.nullable(),
|
||||
|
||||
|
||||
@ -6,8 +6,9 @@ import { TaxesBreakdownSchema } from "../taxes-breakdown.dto";
|
||||
|
||||
export const ProformaItemDetailSchema = z.object({
|
||||
id: z.uuid(),
|
||||
is_valued: z.boolean(),
|
||||
position: ItemPositionSchema,
|
||||
is_valued: z.boolean(),
|
||||
|
||||
description: z.string().nullable(),
|
||||
|
||||
quantity: QuantitySchema.nullable(),
|
||||
|
||||
@ -11,7 +11,6 @@ import type { ProformaItemUpdateForm } from "../entities";
|
||||
export const mapProformaItemsToProformaItemsUpdateForm = (
|
||||
item: ProformaItem
|
||||
): ProformaItemUpdateForm => {
|
||||
console.log(item);
|
||||
return {
|
||||
id: item.id,
|
||||
position: item.position,
|
||||
|
||||
@ -74,8 +74,6 @@ export const useUpdateProformaController = (
|
||||
const initialValues = useMemo<ProformaUpdateForm>(() => {
|
||||
if (!proformaData) return buildProformaUpdateDefault();
|
||||
|
||||
console.log("initialValues", proformaData);
|
||||
|
||||
return mapProformaToProformaUpdateForm(proformaData);
|
||||
}, [proformaData]);
|
||||
|
||||
@ -160,7 +158,7 @@ export const useUpdateProformaController = (
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/cor
|
||||
import { ObjectHelper } from "@repo/rdx-utils";
|
||||
|
||||
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
|
||||
@ -21,12 +21,18 @@ import type { ProformaItemUpdatePatch, ProformaUpdatePatch } from "../entities";
|
||||
|
||||
export const buildUpdateProformaByIdParams = (
|
||||
id: string,
|
||||
patch: ProformaUpdatePatch
|
||||
patch: ProformaUpdatePatch,
|
||||
formData: ProformaUpdateForm
|
||||
): UpdateProformaByIdParams => {
|
||||
if (!id) {
|
||||
throw new Error("proformaId is required");
|
||||
}
|
||||
|
||||
//const currencyCode = formData.currencyCode;
|
||||
//const languageCode = formData.languageCode;
|
||||
|
||||
console.log("PATCH => ", patch);
|
||||
|
||||
const data: UpdateProformaByIdParams["data"] = {};
|
||||
|
||||
if (ObjectHelper.hasOwn(patch, "series")) {
|
||||
@ -73,9 +79,11 @@ export const buildUpdateProformaByIdParams = (
|
||||
}
|
||||
|
||||
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 {
|
||||
id,
|
||||
data,
|
||||
@ -83,13 +91,20 @@ export const buildUpdateProformaByIdParams = (
|
||||
};
|
||||
|
||||
const toProformaItemUpdateDTO = (
|
||||
item: ProformaItemUpdatePatch
|
||||
item: ProformaItemUpdatePatch,
|
||||
_index: number,
|
||||
formData: ProformaUpdateForm
|
||||
): NonNullable<UpdateProformaByIdParams["data"]["items"]>[number] => {
|
||||
const currencyCode = formData.currencyCode;
|
||||
//const languageCode = formData.languageCode;
|
||||
|
||||
const quantity =
|
||||
item.quantity === null ? null : QuantityDTOHelper.fromNumber(item.quantity, 4).value;
|
||||
item.quantity === null ? null : QuantityDTOHelper.fromNumberNulleable(item.quantity, 2);
|
||||
|
||||
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;
|
||||
|
||||
@ -105,8 +120,8 @@ const toProformaItemUpdateDTO = (
|
||||
item_discount_percentage:
|
||||
item.itemDiscountPercentage === 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 DineroFactory, { type Currency, type Dinero } from "dinero.js";
|
||||
|
||||
import type { DomainError } from "../errors";
|
||||
|
||||
import type { Percentage } from "./percentage";
|
||||
import type { Quantity } from "./quantity";
|
||||
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 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 = {
|
||||
value: Number(value),
|
||||
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 { ValidationErrorCollection } from "../errors";
|
||||
import { translateZodValidationError } from "../helpers";
|
||||
|
||||
import { ValueObject } from "./value-object";
|
||||
@ -15,9 +16,14 @@ const DEFAULT_MAX_SCALE = 2;
|
||||
|
||||
export interface PercentageProps {
|
||||
value: number;
|
||||
scale: number;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
type PercentageObjectString = {
|
||||
value: string;
|
||||
scale: string;
|
||||
};
|
||||
|
||||
export class Percentage extends ValueObject<PercentageProps> {
|
||||
static DEFAULT_SCALE = DEFAULT_SCALE;
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
get scale(): number {
|
||||
return this.props.scale;
|
||||
return this.props.scale ?? Percentage.DEFAULT_SCALE;
|
||||
}
|
||||
|
||||
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 { ValidationErrorCollection } from "../errors";
|
||||
import { translateZodValidationError } from "../helpers";
|
||||
|
||||
import { ValueObject } from "./value-object";
|
||||
@ -11,9 +12,14 @@ const DEFAULT_MAX_SCALE = 2;
|
||||
|
||||
export interface QuantityProps {
|
||||
value: number;
|
||||
scale: number;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
type QuantityObjectString = {
|
||||
value: string;
|
||||
scale: string;
|
||||
};
|
||||
|
||||
export class Quantity extends ValueObject<QuantityProps> {
|
||||
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) }));
|
||||
}
|
||||
|
||||
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 {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
get scale(): number {
|
||||
return this.props.scale;
|
||||
return this.props.scale ?? Quantity.DEFAULT_SCALE;
|
||||
}
|
||||
|
||||
getProps(): QuantityProps {
|
||||
@ -67,7 +91,7 @@ export class Quantity extends ValueObject<QuantityProps> {
|
||||
return this.toNumber().toFixed(this.scale);
|
||||
}
|
||||
|
||||
toObjectString() {
|
||||
toObjectString(): QuantityObjectString {
|
||||
return {
|
||||
value: String(this.value),
|
||||
scale: String(this.scale),
|
||||
|
||||
@ -5,6 +5,9 @@
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"chatgpt.openOnStartup": true
|
||||
"chatgpt.openOnStartup": true,
|
||||
"chat.tools.terminal.autoApprove": {
|
||||
"pnpm": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user