.
This commit is contained in:
parent
3b77fb7cb8
commit
5d5ec76ad6
@ -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 {
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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) &&
|
||||
|
||||
@ -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(),
|
||||
});
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -23,4 +23,7 @@ export interface ProformaItemUpdatePatch {
|
||||
unitAmount: number | null;
|
||||
|
||||
itemDiscountPercentage: number | null;
|
||||
|
||||
taxPercentage: number | null;
|
||||
recPercentage: number | null;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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),
|
||||
};
|
||||
};
|
||||
@ -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,
|
||||
}
|
||||
);
|
||||
};
|
||||
@ -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),
|
||||
}));
|
||||
};
|
||||
@ -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),
|
||||
};
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user