This commit is contained in:
David Arranz 2026-05-12 20:56:22 +02:00
parent 3b77fb7cb8
commit 5d5ec76ad6
16 changed files with 184 additions and 262 deletions

View File

@ -12,7 +12,7 @@ import {
extractOrPushError,
maybeFromNullableResult,
} from "@repo/rdx-ddd";
import { Maybe, NumberHelper, Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils";
import { Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils";
import type { UpdateProformaByIdRequestDTO } from "../../../../common/dto";
import {
@ -21,7 +21,6 @@ import {
ItemDescription,
ItemQuantity,
type ProformaItemPatchProps,
ProformaItemTaxes,
type ProformaItemTaxesProps,
type ProformaPatchProps,
} from "../../../domain";
@ -167,9 +166,7 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
toPatchField(dto.global_discount_percentage).ifSet((globalDiscountPercentage) => {
proformaPatchProps.globalDiscountPercentage = extractOrPushError(
DiscountPercentage.create({
value: NumberHelper.toSafeNumber(globalDiscountPercentage.value),
}),
DiscountPercentage.fromObjectString(globalDiscountPercentage),
"global_discount_percentage",
errors
);
@ -226,7 +223,7 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
params.errors
);
const taxes = this.mapTaxesProps(item.taxes, {
const taxes = this.mapTaxesProps(item, {
itemIndex: index,
errors: params.errors,
});
@ -243,38 +240,34 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
}
private mapTaxesProps(
taxesDTO: NonNullable<UpdateProformaByIdRequestDTO["items"]>[number]["taxes"],
itemDTO: NonNullable<UpdateProformaByIdRequestDTO["items"]>[number],
params: { itemIndex: number; errors: ValidationErrorDetail[] }
): ProformaItemTaxesProps {
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();
}
const [ivaCode, recCode, retentionCode] = parts;
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`,
const iva = extractOrPushError(
maybeFromNullableResult(itemDTO.iva_code, (v) => Tax.createFromCode(v, this.taxCatalog)),
`items[${params.itemIndex}].iva_code`,
params.errors
);
return ProformaItemTaxes.create({ iva, rec, retention }).data.getProps();
}
const rec = extractOrPushError(
maybeFromNullableResult(itemDTO.rec_code, (v) => Tax.createFromCode(v, this.taxCatalog)),
`items[${params.itemIndex}].rec_code`,
params.errors
);
private mapTaxCode(code: string, path: string, errors: ValidationErrorDetail[]): Maybe<Tax> {
if (code === "#") {
return Maybe.none();
}
const retention = extractOrPushError(
maybeFromNullableResult(itemDTO.retention_code, (v) =>
Tax.createFromCode(v, this.taxCatalog)
),
`items[${params.itemIndex}].retention_code`,
params.errors
);
const tax = extractOrPushError(Tax.createFromCode(code, this.taxCatalog), path, errors);
return tax ? Maybe.some(tax) : Maybe.none();
return {
iva: iva!,
rec: rec!,
retention: retention!,
};
}
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {

View File

@ -1,10 +1,6 @@
import type { ISnapshotBuilder } from "@erp/core/api";
import type { ProformaItemDetailDTO } from "@erp/customer-invoices/common";
import {
maybeToEmptyPercentageObjectString,
maybeToEmptyString,
maybeToNullable,
} from "@repo/rdx-ddd";
import { maybeToNullable } from "@repo/rdx-ddd";
import { ItemAmount, type ProformaItem, type ProformaItems } from "../../../../domain";
@ -52,20 +48,26 @@ export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnaps
? allAmounts.taxableAmount.toObjectString()
: ItemAmount.zero(currencyCode).toObjectString(),
iva_code: maybeToEmptyString(proformaItem.ivaCode()),
iva_percentage: maybeToEmptyPercentageObjectString(proformaItem.ivaPercentage()),
iva_code: maybeToNullable(proformaItem.ivaCode(), (value) => value.toString()),
iva_percentage: maybeToNullable(proformaItem.ivaPercentage(), (value) =>
value.toObjectString()
),
iva_amount: isValued
? allAmounts.ivaAmount.toObjectString()
: ItemAmount.zero(currencyCode).toObjectString(),
rec_code: maybeToEmptyString(proformaItem.recCode()),
rec_percentage: maybeToEmptyPercentageObjectString(proformaItem.recPercentage()),
rec_code: maybeToNullable(proformaItem.recCode(), (value) => value.toString()),
rec_percentage: maybeToNullable(proformaItem.recPercentage(), (value) =>
value.toObjectString()
),
rec_amount: isValued
? allAmounts.recAmount.toObjectString()
: ItemAmount.zero(currencyCode).toObjectString(),
retention_code: maybeToEmptyString(proformaItem.retentionCode()),
retention_percentage: maybeToEmptyPercentageObjectString(proformaItem.retentionPercentage()),
retention_code: maybeToNullable(proformaItem.retentionCode(), (value) => value.toString()),
retention_percentage: maybeToNullable(proformaItem.retentionPercentage(), (value) =>
value.toObjectString()
),
retention_amount: isValued
? allAmounts.retentionAmount.toObjectString()
: ItemAmount.zero(currencyCode).toObjectString(),

View File

@ -1,5 +1,5 @@
import type { ISnapshotBuilder } from "@erp/core/api";
import { maybeToEmptyPercentageObjectString, maybeToEmptyString } from "@repo/rdx-ddd";
import { maybeToNullable } from "@repo/rdx-ddd";
import type { Collection } from "@repo/rdx-utils";
import type { TaxesBreakdownDTO } from "../../../../../common";
@ -13,16 +13,18 @@ export class ProformaTaxesFullSnapshotBuilder implements IProformaTaxesFullSnaps
return {
taxable_amount: proformaTax.taxableAmount.toObjectString(),
iva_code: maybeToEmptyString(proformaTax.ivaCode),
iva_percentage: maybeToEmptyPercentageObjectString(proformaTax.ivaPercentage),
iva_code: maybeToNullable(proformaTax.ivaCode, (value) => value.toString()),
iva_percentage: maybeToNullable(proformaTax.ivaPercentage, (value) => value.toObjectString()),
iva_amount: proformaTax.ivaAmount.toObjectString(),
rec_code: maybeToEmptyString(proformaTax.recCode),
rec_percentage: maybeToEmptyPercentageObjectString(proformaTax.recPercentage),
rec_code: maybeToNullable(proformaTax.recCode, (value) => value.toString()),
rec_percentage: maybeToNullable(proformaTax.recPercentage, (value) => value.toObjectString()),
rec_amount: proformaTax.recAmount.toObjectString(),
retention_code: maybeToEmptyString(proformaTax.retentionCode),
retention_percentage: maybeToEmptyPercentageObjectString(proformaTax.retentionPercentage),
retention_code: maybeToNullable(proformaTax.retentionCode, (value) => value.toString()),
retention_percentage: maybeToNullable(proformaTax.retentionPercentage, (value) =>
value.toObjectString()
),
retention_amount: proformaTax.retentionAmount.toObjectString(),
taxes_amount: proformaTax.taxesAmount.toObjectString(),

View File

@ -40,15 +40,15 @@ export interface IProformaItems {
totals(): IProformaItemTotals;
taxes(): Collection<IProformaTaxTotals>;
readonly globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
readonly languageCode: LanguageCode; // Para formateos específicos de idioma
readonly currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
languageCode: LanguageCode; // Para formateos específicos de idioma
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
}
export class ProformaItems extends Collection<ProformaItem> implements IProformaItems {
public readonly languageCode!: LanguageCode;
public readonly currencyCode!: CurrencyCode;
public readonly globalDiscountPercentage!: DiscountPercentage;
public languageCode!: LanguageCode;
public currencyCode!: CurrencyCode;
public globalDiscountPercentage!: DiscountPercentage;
private constructor(props: IProformaItemsProps) {
super(props.items ?? []);
@ -67,6 +67,22 @@ export class ProformaItems extends Collection<ProformaItem> implements IProforma
}
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) &&

View File

@ -8,11 +8,10 @@ import {
} from "@erp/core";
import { z } from "zod/v4";
import { ItemPositionSchema, TaxCombinationCodeSchema } from "../../shared";
import { ItemPositionSchema } from "../../shared";
export const UpdateProformaItemRequestSchema = z.object({
position: ItemPositionSchema,
is_valued: z.boolean(),
description: z.string().nullable(),
@ -21,7 +20,14 @@ export const UpdateProformaItemRequestSchema = z.object({
item_discount_percentage: PercentageSchema.nullable(),
taxes: TaxCombinationCodeSchema,
iva_code: z.string().nullable().optional(),
iva_percentage: PercentageSchema.nullable().optional(),
rec_code: z.string().nullable().optional(),
rec_percentage: PercentageSchema.nullable().optional(),
retention_code: z.string().nullable().optional(),
retention_percentage: PercentageSchema.nullable().optional(),
});
export const UpdateProformaByIdParamsRequestSchema = z.object({
@ -47,6 +53,8 @@ export const UpdateProformaByIdRequestSchema = z.object({
payment_method_id: z.uuid().nullable().optional(),
// retención como código??? retencion_15
items: z.array(UpdateProformaItemRequestSchema).optional(),
});

View File

@ -4,16 +4,16 @@ import { z } from "zod/v4";
export const TaxesBreakdownSchema = z.object({
taxable_amount: MoneySchema,
iva_code: z.string(),
iva_percentage: PercentageSchema,
iva_code: z.string().nullable(),
iva_percentage: PercentageSchema.nullable(),
iva_amount: MoneySchema,
rec_code: z.string().nullable(),
rec_percentage: PercentageSchema,
rec_percentage: PercentageSchema.nullable(),
rec_amount: MoneySchema,
retention_code: z.string().nullable(),
retention_percentage: PercentageSchema,
retention_percentage: PercentageSchema.nullable(),
retention_amount: MoneySchema,
taxes_amount: MoneySchema,

View File

@ -162,8 +162,6 @@ export const useUpdateProformaController = (
console.log("Enviando actualización con params:", params);
return;
try {
// Enviamos cambios al servidor
const updated = await mutateAsync(params);

View File

@ -23,4 +23,7 @@ export interface ProformaItemUpdatePatch {
unitAmount: number | null;
itemDiscountPercentage: number | null;
taxPercentage: number | null;
recPercentage: number | null;
}

View File

@ -25,7 +25,19 @@ export type ProformaUpdatePatch = {
globalDiscountPercentage?: number;
paymentMehodId?: string;
taxMode?: string;
taxRegimeCode?: string | null;
hasTaxPercentage?: boolean;
defaultTaxPercentage?: number | null;
hasRecPercentage?: boolean;
defaultRecPercentage?: number | null;
hasRetentionPercentage?: boolean;
defaultRetentionPercentage?: number | null;
paymentMethodId?: string | null;
languageCode?: string;
currencyCode?: string;

View File

@ -4,10 +4,12 @@ import {
LineDescriptionField,
PercentageField,
QuantityField,
SelectField,
} from "@repo/rdx-ui/components";
import { MoneyHelper } from "@repo/rdx-utils";
import { useTranslation } from "../../../../i18n";
import { getProformaTaxOptions } from "../../../shared";
import type {
ProformaItemAmounts,
ProformaItemField,
@ -130,11 +132,12 @@ export const ProformaLineEditor = ({
header: t("form_fields.items.tax_percentage.label", "IVA"),
headClassName: "w-[110px] text-right",
cell: ({ index }) => (
<PercentageField
<SelectField
deserialize={(value) => (value === null || value === "" ? null : Number(value))}
inputClassName="border-none"
maxFractionDigits={2}
minFractionDigits={0}
items={getProformaTaxOptions()}
name={`items.${index}.taxPercentage`}
serialize={(value) => (typeof value === "number" ? String(value) : "")}
/>
),
} satisfies LineEditorColumn<ProformaItemField>,

View File

@ -1,8 +1,14 @@
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
import { toNullable } from "@repo/rdx-ddd";
import { ObjectHelper } from "@repo/rdx-utils";
import type { UpdateProformaByIdParams } from "../../shared/api";
import type { ProformaItemUpdatePatch, ProformaUpdateForm, ProformaUpdatePatch } from "../entities";
import type {
ProformaItemUpdateForm,
ProformaItemUpdatePatch,
ProformaUpdateForm,
ProformaUpdatePatch,
} from "../entities";
/**
* Convierte el patch del formulario de actualización de proforma
@ -28,11 +34,6 @@ export const buildUpdateProformaByIdParams = (
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")) {
@ -63,13 +64,6 @@ export const buildUpdateProformaByIdParams = (
data.notes = patch.notes;
}
if (ObjectHelper.hasOwn(patch, "globalDiscountPercentage")) {
data.global_discount_percentage = PercentageDTOHelper.fromNumber(
patch.globalDiscountPercentage!,
2
);
}
if (ObjectHelper.hasOwn(patch, "languageCode")) {
data.language_code = patch.languageCode;
}
@ -78,11 +72,23 @@ export const buildUpdateProformaByIdParams = (
data.currency_code = patch.currencyCode;
}
if (ObjectHelper.hasOwn(patch, "items")) {
data.items = patch.items?.map((item, index) => toProformaItemUpdateDTO(item, index, formData));
if (ObjectHelper.hasOwn(patch, "paymentMethodId")) {
data.payment_method_id = patch.paymentMethodId;
}
console.log("DATA => ", data);
if (ObjectHelper.hasOwn(patch, "globalDiscountPercentage")) {
data.global_discount_percentage = PercentageDTOHelper.fromNumber(
patch.globalDiscountPercentage!,
2
);
}
// Si se han tocado los detalles, se envían todos
if (shouldReplaceItems(patch)) {
data.items = formData.items.map((item, index) =>
toProformaItemUpdateDTO(item, index, formData)
);
}
return {
id,
@ -92,11 +98,10 @@ export const buildUpdateProformaByIdParams = (
const toProformaItemUpdateDTO = (
item: ProformaItemUpdatePatch,
_index: number,
index: number,
formData: ProformaUpdateForm
): NonNullable<UpdateProformaByIdParams["data"]["items"]>[number] => {
const currencyCode = formData.currencyCode;
//const languageCode = formData.languageCode;
const quantity =
item.quantity === null ? null : QuantityDTOHelper.fromNumberNulleable(item.quantity, 2);
@ -106,22 +111,67 @@ const toProformaItemUpdateDTO = (
? null
: MoneyDTOHelper.fromNumberNulleable(item.unitAmount, currencyCode, 4);
const is_valued = item.isValued;
const ivaPercentage = resolveLineIvaPercentage(item, formData);
const recPercentage = resolveLineRecPercentage(item, formData);
const retentionPercentage = resolveLineRetentionPercentage(formData);
return {
position: item.position,
is_valued,
position: index,
description: item.description,
quantity,
unit_amount,
item_discount_percentage:
item.itemDiscountPercentage === null
? null
: PercentageDTOHelper.fromNumber(item.itemDiscountPercentage),
item_discount_percentage: toNullable(item.itemDiscountPercentage, (value) =>
PercentageDTOHelper.fromNumber(value, 2)
),
taxes: "#;#;#", // TODO: CAMBIAR!!!!
iva_percentage: toNullable(ivaPercentage, (value) => PercentageDTOHelper.fromNumber(value, 2)),
rec_percentage: toNullable(recPercentage, (value) => PercentageDTOHelper.fromNumber(value, 2)),
retention_percentage: toNullable(retentionPercentage, (value) =>
PercentageDTOHelper.fromNumber(value, 2)
),
};
};
const shouldReplaceItems = (patch: ProformaUpdatePatch): boolean => {
return (
ObjectHelper.hasOwn(patch, "items") ||
ObjectHelper.hasOwn(patch, "taxMode") ||
ObjectHelper.hasOwn(patch, "hasTaxPercentage") ||
ObjectHelper.hasOwn(patch, "defaultTaxPercentage") ||
ObjectHelper.hasOwn(patch, "hasRecPercentage") ||
ObjectHelper.hasOwn(patch, "defaultRecPercentage")
);
};
const resolveLineIvaPercentage = (
item: ProformaItemUpdateForm,
formData: ProformaUpdateForm
): number | null => {
if (formData.taxMode === "single") {
return formData.hasTaxPercentage ? formData.defaultTaxPercentage : null;
}
return item.taxPercentage;
};
const resolveLineRecPercentage = (
item: ProformaItemUpdateForm,
formData: ProformaUpdateForm
): number | null => {
if (formData.taxMode === "single") {
return formData.hasRecPercentage ? formData.defaultRecPercentage : null;
}
return item.recPercentage;
};
const resolveLineRetentionPercentage = (formData: ProformaUpdateForm): number | null => {
return formData.hasRetentionPercentage ? formData.defaultRetentionPercentage : null;
};
const toPercentageDTOOrNull = (value: number | null) => {
return value === null ? null : PercentageDTOHelper.fromNumber(value, 2);
};

View File

@ -1,28 +0,0 @@
import type { CommercialDocumentLineAmounts, CommercialDocumentLineInput } from "../../entities";
import { percentageAmount, roundMoney, toCalculationNumber } from "./money-calculation";
export const calculateCommercialDocumentLineAmounts = (
line?: CommercialDocumentLineInput
): CommercialDocumentLineAmounts => {
if (!line) {
return {
grossAmount: 0,
itemDiscountAmount: 0,
taxableBaseBeforeGlobalDiscount: 0,
};
}
const quantity = toCalculationNumber(line.quantity);
const unitAmount = toCalculationNumber(line.unitAmount);
const itemDiscountPercentage = toCalculationNumber(line.itemDiscountPercentage);
const grossAmount = quantity * unitAmount;
const itemDiscountAmount = percentageAmount(grossAmount, itemDiscountPercentage);
return {
grossAmount: roundMoney(grossAmount),
itemDiscountAmount: roundMoney(itemDiscountAmount),
taxableBaseBeforeGlobalDiscount: roundMoney(grossAmount - itemDiscountAmount),
};
};

View File

@ -1,33 +0,0 @@
import type { CommercialDocumentLineInput } from "../../entities";
import { calculateCommercialDocumentLineAmounts } from "./calculate-commercial-document-line-amounts";
import { roundMoney } from "./money-calculation";
export interface CommercialDocumentLinesTotals {
subtotalBeforeDiscounts: number;
lineDiscountTotal: number;
taxableBaseBeforeGlobalDiscount: number;
}
export const calculateCommercialDocumentLinesTotals = (
lines: CommercialDocumentLineInput[]
): CommercialDocumentLinesTotals => {
return lines.reduce<CommercialDocumentLinesTotals>(
(acc, line) => {
const amounts = calculateCommercialDocumentLineAmounts(line);
return {
subtotalBeforeDiscounts: roundMoney(acc.subtotalBeforeDiscounts + amounts.grossAmount),
lineDiscountTotal: roundMoney(acc.lineDiscountTotal + amounts.itemDiscountAmount),
taxableBaseBeforeGlobalDiscount: roundMoney(
acc.taxableBaseBeforeGlobalDiscount + amounts.taxableBaseBeforeGlobalDiscount
),
};
},
{
subtotalBeforeDiscounts: 0,
lineDiscountTotal: 0,
taxableBaseBeforeGlobalDiscount: 0,
}
);
};

View File

@ -1,56 +0,0 @@
import type {
CommercialDocumentLineInput,
CommercialDocumentTaxBreakdownLine,
} from "../../entities";
import { calculateCommercialDocumentLineAmounts } from "./calculate-commercial-document-line-amounts";
import { calculateProportionalDiscount } from "./calculate-proportional-discount";
import { percentageAmount, roundMoney, toCalculationNumber } from "./money-calculation";
interface CalculateCommercialDocumentTaxBreakdownParams {
lines: CommercialDocumentLineInput[];
taxableBaseBeforeGlobalDiscount: number;
globalDiscountAmount: number;
}
export const calculateCommercialDocumentTaxBreakdown = ({
lines,
taxableBaseBeforeGlobalDiscount,
globalDiscountAmount,
}: CalculateCommercialDocumentTaxBreakdownParams): CommercialDocumentTaxBreakdownLine[] => {
const taxMap = new Map<number, { taxableBase: number; taxAmount: number }>();
for (const line of lines) {
const lineAmounts = calculateCommercialDocumentLineAmounts(line);
const proportionalGlobalDiscount = calculateProportionalDiscount(
lineAmounts.taxableBaseBeforeGlobalDiscount,
taxableBaseBeforeGlobalDiscount,
globalDiscountAmount
);
const lineTaxableBase =
lineAmounts.taxableBaseBeforeGlobalDiscount - proportionalGlobalDiscount;
const taxPercentage = toCalculationNumber(line.taxPercentage);
const taxAmount = percentageAmount(lineTaxableBase, taxPercentage);
const current = taxMap.get(taxPercentage) ?? {
taxableBase: 0,
taxAmount: 0,
};
taxMap.set(taxPercentage, {
taxableBase: current.taxableBase + lineTaxableBase,
taxAmount: current.taxAmount + taxAmount,
});
}
return Array.from(taxMap.entries())
.sort(([a], [b]) => a - b)
.map(([taxPercentage, value]) => ({
taxPercentage,
taxableBase: roundMoney(value.taxableBase),
taxAmount: roundMoney(value.taxAmount),
}));
};

View File

@ -1,49 +0,0 @@
import type { CommercialDocumentLineInput, CommercialDocumentTotals } from "../../entities";
import { calculateCommercialDocumentLinesTotals } from "./calculate-commercial-document-lines-totals";
import { calculateCommercialDocumentTaxBreakdown } from "./calculate-commercial-document-tax-breakdown";
import { percentageAmount, roundMoney, toCalculationNumber } from "./money-calculation";
interface CalculateCommercialDocumentTotalsParams {
lines: CommercialDocumentLineInput[];
globalDiscountPercentage: number | null | undefined;
}
export const calculateCommercialDocumentTotals = ({
lines,
globalDiscountPercentage,
}: CalculateCommercialDocumentTotalsParams): CommercialDocumentTotals => {
const normalizedGlobalDiscountPercentage = toCalculationNumber(globalDiscountPercentage);
const linesTotals = calculateCommercialDocumentLinesTotals(lines);
const globalDiscountAmount = percentageAmount(
linesTotals.taxableBaseBeforeGlobalDiscount,
normalizedGlobalDiscountPercentage
);
const taxableBase = linesTotals.taxableBaseBeforeGlobalDiscount - globalDiscountAmount;
const taxBreakdown = calculateCommercialDocumentTaxBreakdown({
lines,
taxableBaseBeforeGlobalDiscount: linesTotals.taxableBaseBeforeGlobalDiscount,
globalDiscountAmount,
});
const taxTotal = taxBreakdown.reduce((total, tax) => total + tax.taxAmount, 0);
return {
subtotalBeforeDiscounts: roundMoney(linesTotals.subtotalBeforeDiscounts),
lineDiscountTotal: roundMoney(linesTotals.lineDiscountTotal),
globalDiscountPercentage: normalizedGlobalDiscountPercentage,
globalDiscountAmount: roundMoney(globalDiscountAmount),
taxableBase: roundMoney(taxableBase),
taxBreakdown,
taxTotal: roundMoney(taxTotal),
total: roundMoney(taxableBase + taxTotal),
};
};

View File

@ -83,16 +83,17 @@ function serializeMaybe<T, R>(maybe: Maybe<T>, onNone: R, map: (value: T) => R):
*/
export function toNullable<V, R>(input: null | undefined, map: (t: V) => R): null;
export function toNullable<V, R>(input: Maybe<V>, map: (t: V) => R): R | null;
export function toNullable<V, R>(input: V | null, map: (t: V) => R): R | null;
export function toNullable<V, R>(input: V, map: (t: V) => R): R;
export function toNullable<V, R>(
input: V | Maybe<V> | null | undefined,
map: (t: V) => R
): R | null {
if (isMaybe<V>(input)) {
return maybeToNullable(input, map);
return maybeToNullable<V, R>(input, map);
}
return mapNullable(input, map);
return mapNullable<V, R>(input, map);
}
/**