.
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
5d5ec76ad6
commit
9961383a9f
@ -127,7 +127,13 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
|
||||
proforma: Proforma,
|
||||
item: ReturnType<Proforma["items"]["getAll"]>[number]
|
||||
): Result<IssuedInvoiceItem, Error> {
|
||||
const itemTotals = item.totals();
|
||||
const calculationContext = {
|
||||
languageCode: proforma.languageCode,
|
||||
currencyCode: proforma.currencyCode,
|
||||
globalDiscountPercentage: proforma.globalDiscountPercentage,
|
||||
};
|
||||
|
||||
const itemTotals = item.totals(calculationContext);
|
||||
|
||||
return IssuedInvoiceItem.create({
|
||||
description: item.description,
|
||||
@ -139,7 +145,7 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
|
||||
itemDiscountPercentage: item.itemDiscountPercentage,
|
||||
itemDiscountAmount: itemTotals.itemDiscountAmount,
|
||||
|
||||
globalDiscountPercentage: item.globalDiscountPercentage,
|
||||
globalDiscountPercentage: proforma.globalDiscountPercentage,
|
||||
globalDiscountAmount: itemTotals.globalDiscountAmount,
|
||||
|
||||
totalDiscountAmount: itemTotals.totalDiscountAmount,
|
||||
|
||||
@ -22,7 +22,13 @@ export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder
|
||||
) {}
|
||||
|
||||
toOutput(proforma: Proforma): ProformaFullSnapshot {
|
||||
const items = this.itemsBuilder.toOutput(proforma.items);
|
||||
const calculationContext = {
|
||||
languageCode: proforma.languageCode,
|
||||
currencyCode: proforma.currencyCode,
|
||||
globalDiscountPercentage: proforma.globalDiscountPercentage,
|
||||
};
|
||||
|
||||
const items = this.itemsBuilder.toOutput(proforma.items, calculationContext);
|
||||
const recipient = this.recipientBuilder.toOutput(proforma);
|
||||
const taxes = this.taxesBuilder.toOutput(proforma.taxes());
|
||||
//const paymentMethod = this.paymentMethodBuilder.toOutput(proforma.paymentMethod);
|
||||
|
||||
@ -1,17 +1,21 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
import type { ProformaItemDetailDTO } from "@erp/customer-invoices/common";
|
||||
import { maybeToNullable } from "@repo/rdx-ddd";
|
||||
|
||||
import { ItemAmount, type ProformaItem, type ProformaItems } from "../../../../domain";
|
||||
import { ItemAmount, type ProformaCalculationContext, type ProformaItem, type ProformaItems } from "../../../../domain";
|
||||
|
||||
export interface IProformaItemsFullSnapshotBuilder
|
||||
extends ISnapshotBuilder<ProformaItems, ProformaItemDetailDTO[]> {}
|
||||
export interface IProformaItemsFullSnapshotBuilder {
|
||||
toOutput(invoiceItems: ProformaItems, context: ProformaCalculationContext): ProformaItemDetailDTO[];
|
||||
}
|
||||
|
||||
export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnapshotBuilder {
|
||||
private mapItem(proformaItem: ProformaItem, index: number): ProformaItemDetailDTO {
|
||||
const allAmounts = proformaItem.totals();
|
||||
private mapItem(
|
||||
proformaItem: ProformaItem,
|
||||
context: ProformaCalculationContext,
|
||||
index: number
|
||||
): ProformaItemDetailDTO {
|
||||
const allAmounts = proformaItem.totals(context);
|
||||
const isValued = proformaItem.isValued();
|
||||
const currencyCode = proformaItem.currencyCode.code;
|
||||
const currencyCode = context.currencyCode.code;
|
||||
|
||||
return {
|
||||
id: proformaItem.id.toPrimitive(),
|
||||
@ -34,7 +38,7 @@ export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnaps
|
||||
? allAmounts.itemDiscountAmount.toObjectString()
|
||||
: ItemAmount.zero(currencyCode).toObjectString(),
|
||||
|
||||
global_discount_percentage: proformaItem.globalDiscountPercentage.toObjectString(),
|
||||
global_discount_percentage: context.globalDiscountPercentage.toObjectString(),
|
||||
|
||||
global_discount_amount: isValued
|
||||
? allAmounts.globalDiscountAmount.toObjectString()
|
||||
@ -81,7 +85,7 @@ export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnaps
|
||||
};
|
||||
}
|
||||
|
||||
toOutput(invoiceItems: ProformaItems): ProformaItemDetailDTO[] {
|
||||
return invoiceItems.map((item, index) => this.mapItem(item, index));
|
||||
toOutput(invoiceItems: ProformaItems, context: ProformaCalculationContext): ProformaItemDetailDTO[] {
|
||||
return invoiceItems.map((item, index) => this.mapItem(item, context, index));
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ import {
|
||||
ProformaItems,
|
||||
} from "../entities";
|
||||
import { ProformaItemMismatch } from "../errors";
|
||||
import type { IProformaTaxTotals } from "../services";
|
||||
import type { IProformaTaxTotals, ProformaCalculationContext } from "../services";
|
||||
import { ProformaItemTaxes } from "../value-objects";
|
||||
|
||||
export interface IProformaCreateProps {
|
||||
@ -128,9 +128,6 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
||||
|
||||
const internalItems = ProformaItems.create({
|
||||
items: [],
|
||||
languageCode: props.languageCode,
|
||||
currencyCode: props.currencyCode,
|
||||
globalDiscountPercentage: props.globalDiscountPercentage,
|
||||
});
|
||||
|
||||
const { items, ...internalProps } = props;
|
||||
@ -201,9 +198,6 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
||||
for (const [index, itemProps] of itemsProps.entries()) {
|
||||
const itemResult = ProformaItem.create({
|
||||
...itemProps,
|
||||
languageCode: this.languageCode,
|
||||
currencyCode: this.currencyCode,
|
||||
globalDiscountPercentage: this.globalDiscountPercentage,
|
||||
});
|
||||
|
||||
if (itemResult.isFailure) {
|
||||
@ -413,7 +407,7 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
||||
* La cabecera NO recalcula lógica de porcentaje — toda la lógica está en Item/Items.
|
||||
*/
|
||||
public totals(): IProformaTotals {
|
||||
const itemsTotals = this.items.totals();
|
||||
const itemsTotals = this.items.totals(this.calculationContext());
|
||||
|
||||
return {
|
||||
subtotalAmount: this.toInvoiceAmount(itemsTotals.subtotalAmount),
|
||||
@ -434,7 +428,7 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
||||
}
|
||||
|
||||
public taxes(): Collection<IProformaTaxTotals> {
|
||||
return this.items.taxes();
|
||||
return this.items.taxes(this.calculationContext());
|
||||
}
|
||||
|
||||
public addItem(props: IProformaItemCreateProps): Result<void, Error> {
|
||||
@ -457,6 +451,14 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
private calculationContext(): ProformaCalculationContext {
|
||||
return {
|
||||
languageCode: this.languageCode,
|
||||
currencyCode: this.currencyCode,
|
||||
globalDiscountPercentage: this.globalDiscountPercentage,
|
||||
};
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { DiscountPercentage, type Tax, type TaxPercentage } from "@erp/core/api";
|
||||
import { type CurrencyCode, DomainEntity, type LanguageCode, type UniqueID } from "@repo/rdx-ddd";
|
||||
import { DomainEntity, type UniqueID } from "@repo/rdx-ddd";
|
||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import { ItemAmount, type ItemDescription, type ItemQuantity } from "../../../common";
|
||||
import type { ProformaCalculationContext } from "../../services";
|
||||
import {
|
||||
ProformaItemTaxes,
|
||||
type ProformaItemTaxesProps,
|
||||
@ -34,18 +35,9 @@ export interface IProformaItemCreateProps {
|
||||
itemDiscountPercentage: Maybe<DiscountPercentage>; // % descuento de línea
|
||||
|
||||
taxes: ProformaItemTaxesProps;
|
||||
|
||||
// Estos campos vienen de la cabecera,
|
||||
// pero se necesitan para cálculos y representaciones de la línea.
|
||||
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
||||
languageCode: LanguageCode; // Para formateos específicos de idioma
|
||||
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
|
||||
}
|
||||
|
||||
export type ProformaItemPatchProps = Omit<
|
||||
IProformaItemCreateProps,
|
||||
"globalDiscountPercentage" | "languageCode" | "currencyCode"
|
||||
>;
|
||||
export type ProformaItemPatchProps = IProformaItemCreateProps;
|
||||
|
||||
export interface IProformaItemTotals {
|
||||
subtotalAmount: ItemAmount;
|
||||
@ -67,16 +59,12 @@ export interface IProformaItemTotals {
|
||||
export interface IProformaItem {
|
||||
description: Maybe<ItemDescription>;
|
||||
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
|
||||
quantity: Maybe<ItemQuantity>;
|
||||
unitAmount: Maybe<ItemAmount>;
|
||||
|
||||
taxes: ProformaItemTaxes;
|
||||
|
||||
itemDiscountPercentage: Maybe<DiscountPercentage>; // Descuento en línea
|
||||
globalDiscountPercentage: DiscountPercentage; // Descuento en cabecera
|
||||
|
||||
ivaCode(): Maybe<string>;
|
||||
ivaPercentage(): Maybe<TaxPercentage>;
|
||||
@ -87,7 +75,8 @@ export interface IProformaItem {
|
||||
retentionCode(): Maybe<string>;
|
||||
retentionPercentage(): Maybe<TaxPercentage>;
|
||||
|
||||
totals(): IProformaItemTotals;
|
||||
totals(context: ProformaCalculationContext): IProformaItemTotals;
|
||||
subtotalAmount(context: ProformaCalculationContext): ItemAmount;
|
||||
|
||||
isValued(): boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado"
|
||||
}
|
||||
@ -135,14 +124,6 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
|
||||
return this.props.description;
|
||||
}
|
||||
|
||||
get languageCode() {
|
||||
return this.props.languageCode;
|
||||
}
|
||||
|
||||
get currencyCode() {
|
||||
return this.props.currencyCode;
|
||||
}
|
||||
|
||||
get quantity() {
|
||||
return this.props.quantity;
|
||||
}
|
||||
@ -155,10 +136,6 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
|
||||
return this.props.itemDiscountPercentage;
|
||||
}
|
||||
|
||||
get globalDiscountPercentage() {
|
||||
return this.props.globalDiscountPercentage;
|
||||
}
|
||||
|
||||
get taxes() {
|
||||
return this.props.taxes;
|
||||
}
|
||||
@ -178,9 +155,9 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
|
||||
return this.props.quantity.isSome() && this.props.unitAmount.isSome();
|
||||
}
|
||||
|
||||
public subtotalAmount(): ItemAmount {
|
||||
public subtotalAmount(context: ProformaCalculationContext): ItemAmount {
|
||||
if (!this.isValued()) {
|
||||
return ItemAmount.zero(this.currencyCode.code);
|
||||
return ItemAmount.zero(context.currencyCode.code);
|
||||
}
|
||||
|
||||
const quantity = this.quantity.unwrap();
|
||||
@ -240,13 +217,14 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
|
||||
* - totalAmount
|
||||
*
|
||||
*/
|
||||
public totals(): IProformaItemTotals {
|
||||
const subtotalAmount = this._calculateSubtotalAmount();
|
||||
public totals(context: ProformaCalculationContext): IProformaItemTotals {
|
||||
const subtotalAmount = this._calculateSubtotalAmount(context);
|
||||
|
||||
const itemDiscountAmount = this._calculateItemDiscountAmount(subtotalAmount);
|
||||
const itemDiscountAmount = this._calculateItemDiscountAmount(subtotalAmount, context);
|
||||
const globalDiscountAmount = this._calculateGlobalDiscountAmount(
|
||||
subtotalAmount,
|
||||
itemDiscountAmount
|
||||
itemDiscountAmount,
|
||||
context
|
||||
);
|
||||
const totalDiscountAmount = this._calculateTotalDiscountAmount(
|
||||
itemDiscountAmount,
|
||||
@ -286,9 +264,9 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
|
||||
/**
|
||||
* @summary Helper puro para calcular el subtotal.
|
||||
*/
|
||||
private _calculateSubtotalAmount(): ItemAmount {
|
||||
private _calculateSubtotalAmount(context: ProformaCalculationContext): ItemAmount {
|
||||
if (!this.isValued()) {
|
||||
return ItemAmount.zero(this.currencyCode.code);
|
||||
return ItemAmount.zero(context.currencyCode.code);
|
||||
}
|
||||
|
||||
const quantity = this.quantity.unwrap();
|
||||
@ -300,9 +278,12 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
|
||||
/**
|
||||
* @summary Helper puro para calcular el descuento de línea.
|
||||
*/
|
||||
private _calculateItemDiscountAmount(subtotal: ItemAmount): ItemAmount {
|
||||
private _calculateItemDiscountAmount(
|
||||
subtotal: ItemAmount,
|
||||
context: ProformaCalculationContext
|
||||
): ItemAmount {
|
||||
if (!this.isValued() || this.props.itemDiscountPercentage.isNone()) {
|
||||
return ItemAmount.zero(this.currencyCode.code);
|
||||
return ItemAmount.zero(context.currencyCode.code);
|
||||
}
|
||||
|
||||
const discountPercentage = this.props.itemDiscountPercentage.match(
|
||||
@ -318,15 +299,16 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
|
||||
*/
|
||||
private _calculateGlobalDiscountAmount(
|
||||
subtotalAmount: ItemAmount,
|
||||
discountAmount: ItemAmount
|
||||
discountAmount: ItemAmount,
|
||||
context: ProformaCalculationContext
|
||||
): ItemAmount {
|
||||
if (!this.isValued()) {
|
||||
return ItemAmount.zero(this.currencyCode.code);
|
||||
return ItemAmount.zero(context.currencyCode.code);
|
||||
}
|
||||
|
||||
const amountAfterLineDiscount = subtotalAmount.subtract(discountAmount);
|
||||
|
||||
const globalDiscount = this.props.globalDiscountPercentage;
|
||||
const globalDiscount = context.globalDiscountPercentage;
|
||||
return amountAfterLineDiscount.percentage(globalDiscount);
|
||||
}
|
||||
|
||||
|
||||
@ -1,126 +1,40 @@
|
||||
import type { DiscountPercentage } from "@erp/core/api";
|
||||
import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { Collection } from "@repo/rdx-utils";
|
||||
|
||||
import { ProformaItemMismatch } from "../../errors";
|
||||
import { type IProformaTaxTotals, ProformaTaxesCalculator } from "../../services";
|
||||
import { ProformaItemsTotalsCalculator } from "../../services/proforma-items-totals-calculator";
|
||||
import {
|
||||
type IProformaTaxTotals,
|
||||
type ProformaCalculationContext,
|
||||
ProformaItemsTotalsCalculator,
|
||||
ProformaTaxesCalculator,
|
||||
} from "../../services";
|
||||
|
||||
import type { IProformaItem, IProformaItemTotals, ProformaItem } from "./proforma-item.entity";
|
||||
|
||||
export interface IProformaItemsProps {
|
||||
items?: ProformaItem[];
|
||||
|
||||
// Estos campos vienen de la cabecera,
|
||||
// pero se necesitan para cálculos y representaciones de la línea.
|
||||
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
||||
languageCode: LanguageCode; // Para formateos específicos de idioma
|
||||
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
|
||||
}
|
||||
|
||||
/*type ProformaItemCreateProps = {
|
||||
items: IProformaItemCreateProps[];
|
||||
|
||||
// Estos campos vienen de la cabecera,
|
||||
// pero se necesitan para cálculos y representaciones de la línea.
|
||||
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
||||
languageCode: LanguageCode; // Para formateos específicos de idioma
|
||||
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
|
||||
};
|
||||
|
||||
type InternalProformaProps = Omit<ProformaItemCreateProps, "items"> & {
|
||||
items: ProformaItem[];
|
||||
};*/
|
||||
|
||||
export interface IProformaItems {
|
||||
// OJO, no extendemos de Collection<IProformaItem> para no exponer
|
||||
// públicamente métodos para manipular la colección.
|
||||
|
||||
valued(): IProformaItem[]; // Devuelve solo las líneas valoradas.
|
||||
totals(): IProformaItemTotals;
|
||||
taxes(): Collection<IProformaTaxTotals>;
|
||||
|
||||
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
||||
languageCode: LanguageCode; // Para formateos específicos de idioma
|
||||
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
|
||||
totals(context: ProformaCalculationContext): IProformaItemTotals;
|
||||
taxes(context: ProformaCalculationContext): Collection<IProformaTaxTotals>;
|
||||
}
|
||||
|
||||
export class ProformaItems extends Collection<ProformaItem> implements IProformaItems {
|
||||
public languageCode!: LanguageCode;
|
||||
public currencyCode!: CurrencyCode;
|
||||
public globalDiscountPercentage!: DiscountPercentage;
|
||||
|
||||
private constructor(props: IProformaItemsProps) {
|
||||
super(props.items ?? []);
|
||||
this.languageCode = props.languageCode;
|
||||
this.currencyCode = props.currencyCode;
|
||||
this.globalDiscountPercentage = props.globalDiscountPercentage;
|
||||
|
||||
if (this.items.length > 0) {
|
||||
this.ensureSameContext(this.items);
|
||||
}
|
||||
4;
|
||||
}
|
||||
|
||||
static create(props: IProformaItemsProps): ProformaItems {
|
||||
return new ProformaItems(props);
|
||||
}
|
||||
|
||||
public add(item: ProformaItem): boolean {
|
||||
console.log("adding item", {
|
||||
item: {
|
||||
description: item.description.getOrUndefined()?.toString(),
|
||||
quantity: item.quantity.getOrUndefined()?.toString(),
|
||||
unitAmount: item.unitAmount.getOrUndefined()?.toObjectString(),
|
||||
itemDiscountPercentage: item.itemDiscountPercentage.getOrUndefined()?.toObjectString(),
|
||||
ivaPercentage: item.ivaPercentage.toString(),
|
||||
recPercentage: item.recPercentage.toString(),
|
||||
languageCode: item.languageCode.code,
|
||||
currencyCode: item.currencyCode.code,
|
||||
globalDiscountPercentage: item.globalDiscountPercentage.toObjectString(),
|
||||
},
|
||||
languageCode: this.languageCode,
|
||||
currencyCode: this.currencyCode,
|
||||
globalDiscountPercentage: this.globalDiscountPercentage,
|
||||
});
|
||||
const same =
|
||||
this.languageCode.equals(item.languageCode) &&
|
||||
this.currencyCode.equals(item.currencyCode) &&
|
||||
this.globalDiscountPercentage.equals(item.globalDiscountPercentage);
|
||||
|
||||
if (!same) return false;
|
||||
|
||||
return super.add(item);
|
||||
}
|
||||
|
||||
public valued(): IProformaItem[] {
|
||||
return this.filter((item) => item.isValued());
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Añade un nuevo ítem a la colección.
|
||||
* @param item - El ítem de factura a añadir.
|
||||
* @returns `true` si el ítem fue añadido correctamente; `false` si fue rechazado.
|
||||
* @remarks
|
||||
* Sólo se aceptan ítems cuyo `LanguageCode` y `CurrencyCode` coincidan con
|
||||
* los de la colección. Si no coinciden, el método devuelve un resultado fallido sin modificar
|
||||
* la colección.
|
||||
*/
|
||||
public addItem(item: ProformaItem): Result<void, Error> {
|
||||
// Antes de añadir un nuevo item, debo comprobar que el item a añadir
|
||||
// tiene el mismo "currencyCode" y "languageCode" que la colección de items.
|
||||
const same =
|
||||
this.languageCode.equals(item.languageCode) &&
|
||||
this.currencyCode.equals(item.currencyCode) &&
|
||||
this.globalDiscountPercentage.equals(item.globalDiscountPercentage);
|
||||
|
||||
if (!same) {
|
||||
return Result.fail(new ProformaItemMismatch(this.size()));
|
||||
}
|
||||
super.add(item);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
// Cálculos
|
||||
|
||||
/**
|
||||
@ -128,24 +42,11 @@ export class ProformaItems extends Collection<ProformaItem> implements IProforma
|
||||
* @remarks
|
||||
* Delega en los ítems individuales (DDD correcto) pero evita múltiples recorridos.
|
||||
*/
|
||||
public totals(): IProformaItemTotals {
|
||||
return new ProformaItemsTotalsCalculator(this).calculate();
|
||||
public totals(context: ProformaCalculationContext): IProformaItemTotals {
|
||||
return new ProformaItemsTotalsCalculator(this, context).calculate();
|
||||
}
|
||||
|
||||
public taxes(): Collection<IProformaTaxTotals> {
|
||||
return new ProformaTaxesCalculator(this).calculate();
|
||||
}
|
||||
|
||||
private ensureSameContext(items: IProformaItem[]): void {
|
||||
for (const item of items) {
|
||||
const same =
|
||||
item.languageCode.equals(this.languageCode) &&
|
||||
item.currencyCode.equals(this.currencyCode) &&
|
||||
item.globalDiscountPercentage.equals(this.globalDiscountPercentage);
|
||||
|
||||
if (!same) {
|
||||
throw new Error("[ProformaItems] All items must share the same context.");
|
||||
}
|
||||
}
|
||||
public taxes(context: ProformaCalculationContext): Collection<IProformaTaxTotals> {
|
||||
return new ProformaTaxesCalculator(this, context).calculate();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1,4 @@
|
||||
export * from "./proforma-compare-tax-totals";
|
||||
export * from "./proforma-compute-tax-groups";
|
||||
export * from "./proforma-items-totals-calculator";
|
||||
export * from "./proforma-taxes-calculator";
|
||||
|
||||
@ -4,6 +4,8 @@ import { Maybe } from "@repo/rdx-utils";
|
||||
import { ItemAmount } from "../../common";
|
||||
import type { IProformaItems } from "../entities";
|
||||
|
||||
import type { ProformaCalculationContext } from "./proforma-items-totals-calculator";
|
||||
|
||||
type TaxGroupState = {
|
||||
taxableAmount: ItemAmount;
|
||||
|
||||
@ -30,9 +32,12 @@ type TaxGroupState = {
|
||||
* - REC y RETENTION pueden ser None.
|
||||
* - No se recalculan porcentajes (se suma lo ya calculado por línea).
|
||||
*/
|
||||
export function proformaComputeTaxGroups(items: IProformaItems): Map<string, TaxGroupState> {
|
||||
export function proformaComputeTaxGroups(
|
||||
items: IProformaItems,
|
||||
context: ProformaCalculationContext
|
||||
): Map<string, TaxGroupState> {
|
||||
const map = new Map<string, TaxGroupState>();
|
||||
const currency = items.currencyCode;
|
||||
const currency = context.currencyCode;
|
||||
|
||||
for (const item of items.valued()) {
|
||||
const iva = item.taxes.iva;
|
||||
@ -65,7 +70,7 @@ export function proformaComputeTaxGroups(items: IProformaItems): Map<string, Tax
|
||||
|
||||
const g = map.get(key)!;
|
||||
|
||||
const itemTotals = item.totals();
|
||||
const itemTotals = item.totals(context);
|
||||
|
||||
g.taxableAmount = g.taxableAmount.add(itemTotals.taxableAmount);
|
||||
g.ivaAmount = g.ivaAmount.add(itemTotals.ivaAmount);
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import type { DiscountPercentage } from "@erp/core/api";
|
||||
import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
||||
|
||||
import { ItemAmount } from "../../common";
|
||||
import type { ProformaItems } from "../entities";
|
||||
|
||||
@ -18,15 +21,24 @@ export interface IProformaItemsTotals {
|
||||
totalAmount: ItemAmount;
|
||||
}
|
||||
|
||||
export interface ProformaCalculationContext {
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
globalDiscountPercentage: DiscountPercentage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acumula los totales (scale 4) a partir de los totales de las líneas valoradas.
|
||||
* Aquí no se hace ningúna operación de cálculo.
|
||||
*/
|
||||
export class ProformaItemsTotalsCalculator {
|
||||
constructor(private readonly items: ProformaItems) {}
|
||||
constructor(
|
||||
private readonly items: ProformaItems,
|
||||
private readonly context: ProformaCalculationContext
|
||||
) {}
|
||||
|
||||
public calculate(): IProformaItemsTotals {
|
||||
const zero = ItemAmount.zero(this.items.currencyCode.code);
|
||||
const zero = ItemAmount.zero(this.context.currencyCode.code);
|
||||
|
||||
let subtotalAmount = zero;
|
||||
|
||||
@ -44,7 +56,7 @@ export class ProformaItemsTotalsCalculator {
|
||||
let totalAmount = zero;
|
||||
|
||||
for (const item of this.items.getAll()) {
|
||||
const amounts = item.totals();
|
||||
const amounts = item.totals(this.context);
|
||||
|
||||
// Subtotales
|
||||
subtotalAmount = subtotalAmount.add(amounts.subtotalAmount);
|
||||
|
||||
@ -6,6 +6,7 @@ import type { IProformaItems } from "../entities";
|
||||
|
||||
import { proformaCompareTaxTotals } from "./proforma-compare-tax-totals";
|
||||
import { proformaComputeTaxGroups } from "./proforma-compute-tax-groups";
|
||||
import type { ProformaCalculationContext } from "./proforma-items-totals-calculator";
|
||||
|
||||
export interface IProformaTaxTotals {
|
||||
taxableAmount: InvoiceAmount;
|
||||
@ -26,10 +27,13 @@ export interface IProformaTaxTotals {
|
||||
}
|
||||
|
||||
export class ProformaTaxesCalculator {
|
||||
constructor(private readonly items: IProformaItems) {}
|
||||
constructor(
|
||||
private readonly items: IProformaItems,
|
||||
private readonly context: ProformaCalculationContext
|
||||
) {}
|
||||
|
||||
public calculate(): Collection<IProformaTaxTotals> {
|
||||
const groups = proformaComputeTaxGroups(this.items); // <- devuelve en escala 4
|
||||
const groups = proformaComputeTaxGroups(this.items, this.context); // <- devuelve en escala 4
|
||||
|
||||
// Vamos acumulando los importes, redondeando previamente a 2 decimales
|
||||
const rows = Array.from(groups.values()).map((g) => {
|
||||
@ -65,7 +69,7 @@ export class ProformaTaxesCalculator {
|
||||
private toInvoiceAmount(amount: ItemAmount): InvoiceAmount {
|
||||
return InvoiceAmount.create({
|
||||
value: amount.convertScale(InvoiceAmount.DEFAULT_SCALE).value,
|
||||
currency_code: this.items.currencyCode.code,
|
||||
currency_code: this.context.currencyCode.code,
|
||||
}).data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,9 +241,6 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
||||
|
||||
// 6) Construcción del agregado (Dominio)
|
||||
const items = ProformaItems.create({
|
||||
languageCode: attributes.languageCode!,
|
||||
currencyCode: attributes.currencyCode!,
|
||||
globalDiscountPercentage: attributes.globalDiscountPercentage!,
|
||||
items: itemCollectionResults.data.getAll(),
|
||||
});
|
||||
|
||||
|
||||
@ -128,15 +128,10 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
||||
|
||||
return {
|
||||
itemId,
|
||||
|
||||
languageCode: parent.languageCode,
|
||||
currencyCode: parent.currencyCode,
|
||||
description,
|
||||
quantity,
|
||||
unitAmount,
|
||||
itemDiscountPercentage,
|
||||
globalDiscountPercentage: parent.globalDiscountPercentage,
|
||||
|
||||
iva,
|
||||
rec,
|
||||
retention,
|
||||
@ -179,11 +174,7 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
||||
unitAmount: attributes.unitAmount!,
|
||||
|
||||
itemDiscountPercentage: attributes.itemDiscountPercentage!,
|
||||
globalDiscountPercentage: attributes.globalDiscountPercentage!,
|
||||
taxes: taxesResult.data,
|
||||
|
||||
languageCode: attributes.languageCode!,
|
||||
currencyCode: attributes.currencyCode!,
|
||||
},
|
||||
itemId
|
||||
);
|
||||
@ -201,7 +192,11 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
||||
errors: ValidationErrorDetail[];
|
||||
};
|
||||
|
||||
const allAmounts = source.totals();
|
||||
const allAmounts = source.totals({
|
||||
currencyCode: parent.currencyCode,
|
||||
globalDiscountPercentage: parent.globalDiscountPercentage,
|
||||
languageCode: parent.languageCode,
|
||||
});
|
||||
|
||||
return Result.ok({
|
||||
item_id: source.id.toPrimitive(),
|
||||
@ -236,8 +231,9 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
||||
item_discount_amount_scale: allAmounts.itemDiscountAmount.scale,
|
||||
|
||||
//
|
||||
global_discount_percentage_value: source.globalDiscountPercentage.value,
|
||||
global_discount_percentage_scale: source.globalDiscountPercentage.scale,
|
||||
global_discount_percentage_value: parent.globalDiscountPercentage.toPrimitive().value,
|
||||
global_discount_percentage_scale:
|
||||
parent.globalDiscountPercentage.toPrimitive().scale ?? DiscountPercentage.DEFAULT_SCALE,
|
||||
|
||||
global_discount_amount_value: allAmounts.globalDiscountAmount.value,
|
||||
global_discount_amount_scale: allAmounts.globalDiscountAmount.scale,
|
||||
|
||||
@ -28,6 +28,8 @@ export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpd
|
||||
const defaultRetentionPercentage =
|
||||
defaultTaxSummary?.retentionPercentage ?? firstTaxableItem?.retentionPercentage ?? null;
|
||||
|
||||
console.log({ defaultTaxPercentage, defaultRecPercentage, defaultRetentionPercentage, taxMode });
|
||||
|
||||
return {
|
||||
series: proforma.series ?? proformaDefaults.series,
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user