Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
David Arranz 2026-05-13 13:16:09 +02:00
parent 9961383a9f
commit 4f331ceb14
50 changed files with 464 additions and 329 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/factuges-server", "name": "@erp/factuges-server",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "tsup src/index.ts --config tsup.config.ts", "build": "tsup src/index.ts --config tsup.config.ts",

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/factuges-web", "name": "@erp/factuges-web",
"private": true, "private": true,
"version": "0.6.5", "version": "0.6.6",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host --clearScreen false", "dev": "vite --host --clearScreen false",

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/auth", "name": "@erp/auth",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/core", "name": "@erp/core",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -118,7 +118,7 @@
}, },
{ {
"name": "Retenc. 35%", "name": "Retenc. 35%",
"code": "retencion_35", "code": "retention_35",
"value": "3500", "value": "3500",
"scale": "2", "scale": "2",
"group": "retention", "group": "retention",
@ -127,7 +127,7 @@
}, },
{ {
"name": "Retenc. 19%", "name": "Retenc. 19%",
"code": "retencion_19", "code": "retention_19",
"value": "1900", "value": "1900",
"scale": "2", "scale": "2",
"group": "retention", "group": "retention",
@ -136,7 +136,7 @@
}, },
{ {
"name": "Retenc. 15%", "name": "Retenc. 15%",
"code": "retencion_15", "code": "retention_15",
"value": "1500", "value": "1500",
"scale": "2", "scale": "2",
"group": "retention", "group": "retention",
@ -145,7 +145,7 @@
}, },
{ {
"name": "Retenc. 7%", "name": "Retenc. 7%",
"code": "retencion_7", "code": "retention_7",
"value": "700", "value": "700",
"scale": "2", "scale": "2",
"group": "retention", "group": "retention",
@ -154,7 +154,7 @@
}, },
{ {
"name": "Retenc. 2%", "name": "Retenc. 2%",
"code": "retencion_2", "code": "retention_2",
"value": "200", "value": "200",
"scale": "2", "scale": "2",
"group": "retention", "group": "retention",

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/customer-invoices", "name": "@erp/customer-invoices",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -188,7 +188,8 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
return Result.ok(proformaPatchProps); return Result.ok(proformaPatchProps);
} catch (err: unknown) { } catch (err: unknown) {
return Result.fail(new DomainError("Proforma props mapping failed", { cause: err })); console.error(err);
return Result.fail(new DomainError("Proforma props mapping failed (update)", { cause: err }));
} }
} }

View File

@ -24,8 +24,7 @@ import { prepareIssueProformaTarget } from "../../../issue-proforma/utils";
import type { ProformaListRow } from "../../../shared"; import type { ProformaListRow } from "../../../shared";
import { useListProformasPageController } from "../../controllers"; import { useListProformasPageController } from "../../controllers";
import { ProformaSummaryPanel, ProformasGrid, useProformasGridColumns } from "../blocks"; import { ProformaSummaryPanel, ProformasGrid, useProformasGridColumns } from "../blocks";
import { ProformaStatusBadge } from "../components";
import { OrdersTable } from "./orders-table";
export const ListProformasPage = () => { export const ListProformasPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -86,7 +85,30 @@ export const ListProformasPage = () => {
pageSize={listCtrl.pageSize} pageSize={listCtrl.pageSize}
/> />
<OrdersTable /> {/* Explicación técnica */}
<div className="mt-8 rounded border border-border bg-card p-3 sm:p-4 text-xs sm:text-sm text-muted-foreground space-y-2">
<p className="font-semibold text-foreground">Estado de proforma</p>
<ul className="list-disc list-inside space-y-1">
<li>
<ProformaStatusBadge status="draft" />
<strong className="text-foreground">Borrador:</strong> Un{" "}
<code className="rounded bg-muted px-1 text-xs">div</code> absoluto con{" "}
<code className="rounded bg-muted px-1 text-xs">pointer-events: none</code> y{" "}
<code className="rounded bg-muted px-1 text-xs">linear-gradient</code> se superpone
encima del scroll.
</li>
<li>
<strong className="text-foreground">Columna sticky:</strong> La celda de acciones usa{" "}
<code className="rounded bg-muted px-1 text-xs">sticky right-0 z-20</code>.
</li>
<li>
<strong className="text-foreground">Responsive:</strong> Columnas ocultas en móviles (
<code className="rounded bg-muted px-1 text-xs">hidden sm:</code>,{" "}
<code className="rounded bg-muted px-1 text-xs">hidden md:</code>,{" "}
<code className="rounded bg-muted px-1 text-xs">hidden lg:</code>).
</li>
</ul>
</div>
</div> </div>
</div> </div>
); );

View File

@ -43,7 +43,7 @@ export const GetProformaByIdAdapter = {
recipient: mapRecipient(dto), recipient: mapRecipient(dto),
taxes: dto.taxes.map(mapTaxSummary), taxes: dto.taxes.map(mapTaxSummary),
paymentMethod: dto.payment_method?.id, paymentMethodId: dto.payment_method?.id ?? null,
subtotalAmount: MoneyDTOHelper.toNumber(dto.subtotal_amount), subtotalAmount: MoneyDTOHelper.toNumber(dto.subtotal_amount),
@ -85,24 +85,20 @@ const mapItem = (dto: GetProformaByIdResponseDTO["items"][number]): ProformaItem
taxableAmount: MoneyDTOHelper.toNumber(dto.taxable_amount), taxableAmount: MoneyDTOHelper.toNumber(dto.taxable_amount),
ivaCode: dto.iva_code, taxCode: dto.iva_code,
ivaPercentage: PercentageDTOHelper.toNumber(dto.iva_percentage), taxPercentage: PercentageDTOHelper.toNumericNulleable(dto.iva_percentage),
ivaAmount: MoneyDTOHelper.toNumber(dto.iva_amount), taxAmount: MoneyDTOHelper.toNumber(dto.iva_amount),
recCode: dto.rec_code, recCode: dto.rec_code,
recPercentage: PercentageDTOHelper.toNumber(dto.rec_percentage), recPercentage: PercentageDTOHelper.toNumericNulleable(dto.rec_percentage),
recAmount: MoneyDTOHelper.toNumber(dto.rec_amount), recAmount: MoneyDTOHelper.toNumber(dto.rec_amount),
retentionCode: dto.retention_code, retentionCode: dto.retention_code,
retentionPercentage: PercentageDTOHelper.toNumber(dto.retention_percentage), retentionPercentage: PercentageDTOHelper.toNumericNulleable(dto.retention_percentage),
retentionAmount: MoneyDTOHelper.toNumber(dto.retention_amount), retentionAmount: MoneyDTOHelper.toNumber(dto.retention_amount),
taxesAmount: MoneyDTOHelper.toNumber(dto.taxes_amount), taxesAmount: MoneyDTOHelper.toNumber(dto.taxes_amount),
totalAmount: MoneyDTOHelper.toNumber(dto.total_amount), totalAmount: MoneyDTOHelper.toNumber(dto.total_amount),
// Nota: el DTO de detalle actual no incluye `taxes` por línea.
// Dejamos fallback explícito para no inventar parseos.
taxes: "",
}; };
return item; return item;

View File

@ -1,3 +1,2 @@
export * from "./payment-method-options.constants"; export * from "./payment-method-options.constants";
export * from "./retentions-options.constants"; export * from "./proforma-fiscal-options.constants";
export * from "./taxes-options.constants";

View File

@ -0,0 +1,90 @@
export type ProformaTaxPercentageOption = 21 | 10 | 4 | 0;
export type ProformaRecPercentageOption = 5.2 | 1.4 | 0.5 | 0;
export type ProformaRetentionPercentageOption = 15 | 7;
export interface ProformaSelectOption<TValue extends string = string> {
value: TValue;
label: string;
}
export interface ProformaTaxDefinition {
taxCode: string;
taxPercentage: ProformaTaxPercentageOption;
taxLabel: string;
recCode: string;
recPercentage: ProformaRecPercentageOption;
}
export interface ProformaRetentionDefinition {
retentionCode: string;
retentionPercentage: ProformaRetentionPercentageOption;
retentionLabel: string;
}
export const PROFORMA_TAX_DEFINITIONS: Record<ProformaTaxPercentageOption, ProformaTaxDefinition> =
{
21: {
taxCode: "iva_21",
taxPercentage: 21,
taxLabel: "General",
recCode: "rec_5_2",
recPercentage: 5.2,
},
10: {
taxCode: "iva_10",
taxPercentage: 10,
taxLabel: "Reducido",
recCode: "rec_1_4",
recPercentage: 1.4,
},
4: {
taxCode: "iva_4",
taxPercentage: 4,
taxLabel: "Superreducido",
recCode: "rec_0_5",
recPercentage: 0.5,
},
0: {
taxCode: "iva_0",
taxPercentage: 0,
taxLabel: "Exento",
recCode: "rec_0",
recPercentage: 0,
},
} as const;
export const PROFORMA_RETENTION_DEFINITIONS: Record<
ProformaRetentionPercentageOption,
ProformaRetentionDefinition
> = {
15: {
retentionCode: "retention_15",
retentionPercentage: 15,
retentionLabel: "General",
},
7: {
retentionCode: "retention_7",
retentionPercentage: 7,
retentionLabel: "Inicio actividad",
},
} as const;
export const PROFORMA_TAX_OPTIONS: ProformaSelectOption<`${ProformaTaxPercentageOption}`>[] = [
{ value: "0", label: "0%" },
{ value: "4", label: "4%" },
{ value: "10", label: "10%" },
{ value: "21", label: "21%" },
];
export const PROFORMA_REC_OPTIONS: ProformaSelectOption<`${ProformaRecPercentageOption}`>[] = [
{ value: "0", label: "0%" },
{ value: "0.5", label: "0,5%" },
{ value: "1.4", label: "1,4%" },
{ value: "5.2", label: "5,2%" },
];
export const PROFORMA_RETENTION_OPTIONS: ProformaSelectOption<`${ProformaRetentionPercentageOption}`>[] =
[
{ value: "15", label: "15%" },
{ value: "7", label: "7%" },
];

View File

@ -1,25 +0,0 @@
export type RetentionPercentageOption = 15 | 7;
export interface ProformaRetentionsDefinition {
retentionPercentage: RetentionPercentageOption;
retentionLabel: string;
}
export const PROFORMA_RETENTION_DEFINITIONS: Record<
RetentionPercentageOption,
ProformaRetentionsDefinition
> = {
15: {
retentionPercentage: 15,
retentionLabel: "General",
},
7: {
retentionPercentage: 7,
retentionLabel: "Reducido",
},
};
export const PROFORMA_RETENTION_OPTIONS = [
{ value: "15", label: "15%" },
{ value: "7", label: "7%" },
];

View File

@ -1,37 +0,0 @@
export type TaxPercentageOption = 21 | 10 | 4 | 0;
export interface ProformaTaxesDefinition {
taxPercentage: TaxPercentageOption;
taxLabel: string;
recPercentage: number;
}
export const PROFORMA_TAX_DEFINITIONS: Record<TaxPercentageOption, ProformaTaxesDefinition> = {
21: {
taxPercentage: 21,
taxLabel: "General",
recPercentage: 5.2,
},
10: {
taxPercentage: 10,
taxLabel: "Reducido",
recPercentage: 1.4,
},
4: {
taxPercentage: 4,
taxLabel: "Superreducido",
recPercentage: 0.5,
},
0: {
taxPercentage: 0,
taxLabel: "Exento",
recPercentage: 0,
},
};
export const PROFORMA_TAX_OPTIONS = [
{ value: "0", label: "0%" },
{ value: "4", label: "4%" },
{ value: "10", label: "10%" },
{ value: "21", label: "21%" },
];

View File

@ -24,21 +24,19 @@ export interface ProformaItem {
taxableAmount: number; taxableAmount: number;
ivaCode: string; taxCode: string | null;
ivaPercentage: number; taxPercentage: number | null;
ivaAmount: number; taxAmount: number;
recCode: string | null; recCode: string | null;
recPercentage: number; recPercentage: number | null;
recAmount: number; recAmount: number;
retentionCode: string | null; retentionCode: string | null;
retentionPercentage: number; retentionPercentage: number | null;
retentionAmount: number; retentionAmount: number;
taxesAmount: number; taxesAmount: number;
totalAmount: number; totalAmount: number;
taxes: string;
} }

View File

@ -7,8 +7,8 @@
export interface ProformaTaxSummary { export interface ProformaTaxSummary {
taxableAmount: number; taxableAmount: number;
ivaCode: string; ivaCode: string | null;
ivaPercentage: number; ivaPercentage: number | null;
ivaAmount: number; ivaAmount: number;
recCode: string | null; recCode: string | null;

View File

@ -33,7 +33,7 @@ export interface Proforma {
taxes: ProformaTaxSummary[]; taxes: ProformaTaxSummary[];
paymentMethod?: string; paymentMethodId: string | null;
subtotalAmount: number; subtotalAmount: number;

View File

@ -1,5 +0,0 @@
import { PROFORMA_TAX_DEFINITIONS, type TaxPercentageOption } from "../constants";
export const getProformaRecPercentage = (taxPercentage: TaxPercentageOption): number => {
return PROFORMA_TAX_DEFINITIONS[taxPercentage].recPercentage;
};

View File

@ -1,3 +0,0 @@
import { PROFORMA_RETENTION_OPTIONS } from "../constants";
export const getProformaRetentionOptions = () => PROFORMA_RETENTION_OPTIONS;

View File

@ -1,3 +0,0 @@
import { PROFORMA_TAX_OPTIONS } from "../constants";
export const getProformaTaxOptions = () => PROFORMA_TAX_OPTIONS;

View File

@ -1,3 +1 @@
export * from "./get-proforma-rec-percentage"; export * from "./proforma-fiscal-options.utils";
export * from "./get-proforma-retention-options";
export * from "./get-proforma-tax-options";

View File

@ -0,0 +1,96 @@
import { PercentageHelper, isNullishOrEmpty } from "@repo/rdx-utils";
import {
PROFORMA_REC_OPTIONS,
PROFORMA_RETENTION_DEFINITIONS,
PROFORMA_RETENTION_OPTIONS,
PROFORMA_TAX_DEFINITIONS,
PROFORMA_TAX_OPTIONS,
type ProformaRecPercentageOption,
type ProformaRetentionPercentageOption,
type ProformaTaxPercentageOption,
} from "../constants";
export const parseProformaTaxPercentage = (
value: number | string | null | undefined
): ProformaTaxPercentageOption | null => {
if (isNullishOrEmpty(value)) {
return null;
}
const numericValue = Number(value);
const normalizedValue = PercentageHelper.normalizePercentage(numericValue);
if (normalizedValue in PROFORMA_TAX_DEFINITIONS) {
return normalizedValue as ProformaTaxPercentageOption;
}
return null;
};
export const parseProformaRetentionPercentage = (
value: number | string | null | undefined
): ProformaRetentionPercentageOption | null => {
if (isNullishOrEmpty(value)) {
return null;
}
const numericValue = Number(value);
const normalizedValue = PercentageHelper.normalizePercentage(numericValue);
if (normalizedValue in PROFORMA_RETENTION_DEFINITIONS) {
return normalizedValue as ProformaRetentionPercentageOption;
}
return null;
};
export const getProformaTaxCode = (
taxPercentage: number | string | null | undefined
): string | null => {
const parsedTaxPercentage = parseProformaTaxPercentage(taxPercentage);
if (parsedTaxPercentage === null) return null;
return PROFORMA_TAX_DEFINITIONS[parsedTaxPercentage].taxCode;
};
export const getProformaRecPercentage = (
taxPercentage: ProformaTaxPercentageOption | null | undefined
): ProformaRecPercentageOption | null => {
if (taxPercentage === null || taxPercentage === undefined) return null;
return PROFORMA_TAX_DEFINITIONS[taxPercentage].recPercentage;
};
export const getProformaRecCode = (
recPercentage: number | string | null | undefined
): string | null => {
if (recPercentage === null || recPercentage === undefined || recPercentage === "") {
return null;
}
const numericValue = Number(recPercentage);
const definition = Object.values(PROFORMA_TAX_DEFINITIONS).find(
(taxDefinition) => taxDefinition.recPercentage === numericValue
);
return definition?.recCode ?? null;
};
export const getProformaRetentionCode = (
retentionPercentage: number | string | null | undefined
): string | null => {
const parsedRetentionPercentage = parseProformaRetentionPercentage(retentionPercentage);
if (parsedRetentionPercentage === null) return null;
return PROFORMA_RETENTION_DEFINITIONS[parsedRetentionPercentage].retentionCode;
};
export const getProformaTaxOptions = () => PROFORMA_TAX_OPTIONS;
export const getProformaRecOptions = () => PROFORMA_REC_OPTIONS;
export const getProformaRetentionOptions = () => PROFORMA_RETENTION_OPTIONS;

View File

@ -23,7 +23,7 @@ export const mapProformaItemsToProformaItemsUpdateForm = (
itemDiscountPercentage: item.itemDiscountPercentage ?? null, itemDiscountPercentage: item.itemDiscountPercentage ?? null,
taxPercentage: item.ivaPercentage ?? null, taxPercentage: item.taxPercentage ?? null,
recPercentage: item.recPercentage ?? null, recPercentage: item.recPercentage ?? null,
}; };
}; };

View File

@ -1,34 +1,24 @@
import { PercentageHelper } from "@repo/rdx-utils"; import type { Proforma } from "../../shared";
import type { Proforma, ProformaItem } from "../../shared";
import type { ProformaTaxMode, ProformaUpdateForm } from "../entities"; import type { ProformaTaxMode, ProformaUpdateForm } from "../entities";
import { buildProformaUpdateDefault } from "../utils"; import { buildProformaUpdateDefault } from "../utils";
import { mapProformaItemsToProformaItemsUpdateForm } from "./map-proforma-items-to-proforma-items-update-form.adapter"; import { mapProformaItemsToProformaItemsUpdateForm } from "./map-proforma-items-to-proforma-items-update-form.adapter";
interface FiscalDefaults {
taxMode: ProformaTaxMode;
defaultTaxPercentage: number | null;
defaultRecPercentage: number | null;
defaultRetentionPercentage: number | null;
}
/** /**
* Mapea una proforma a un formulario de actualización de proforma. * Mapea una proforma a un formulario de actualización de proforma.
*/ */
export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpdateForm => { export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpdateForm => {
const proformaDefaults = buildProformaUpdateDefault(); const proformaDefaults = buildProformaUpdateDefault();
const fiscalDefaults = resolveFiscalDefaults(proforma, proformaDefaults);
const taxMode = inferProformaTaxMode(proforma.items);
const defaultTaxSummary = proforma.taxes[0];
const firstTaxableItem = getFirstTaxableItem(proforma.items);
const defaultTaxPercentage =
defaultTaxSummary?.ivaPercentage ??
firstTaxableItem?.ivaPercentage ??
proformaDefaults.defaultTaxPercentage;
const defaultRecPercentage =
defaultTaxSummary?.recPercentage ?? firstTaxableItem?.recPercentage ?? null;
const defaultRetentionPercentage =
defaultTaxSummary?.retentionPercentage ?? firstTaxableItem?.retentionPercentage ?? null;
console.log({ defaultTaxPercentage, defaultRecPercentage, defaultRetentionPercentage, taxMode });
return { return {
series: proforma.series ?? proformaDefaults.series, series: proforma.series ?? proformaDefaults.series,
@ -48,55 +38,56 @@ export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpd
globalDiscountPercentage: globalDiscountPercentage:
proforma.globalDiscountPercentage ?? proformaDefaults.globalDiscountPercentage, proforma.globalDiscountPercentage ?? proformaDefaults.globalDiscountPercentage,
taxMode, taxMode: fiscalDefaults.taxMode,
taxRegimeCode: proformaDefaults.taxRegimeCode, taxRegimeCode: "01", //taxRegimeCode: proforma.taxRegimeCode ?? proformaDefaults.taxRegimeCode, // TODO: implementar en API
hasTaxPercentage: PercentageHelper.hasPositivePercentage(defaultTaxPercentage), hasTaxPercentage: fiscalDefaults.defaultTaxPercentage !== null,
defaultTaxPercentage: defaultTaxPercentage, taxPercentage: fiscalDefaults.defaultTaxPercentage,
hasRecPercentage: PercentageHelper.hasPositivePercentage(defaultRecPercentage), hasRecPercentage: fiscalDefaults.defaultRecPercentage !== null,
defaultRecPercentage: defaultRecPercentage, recPercentage: fiscalDefaults.defaultRecPercentage,
hasRetentionPercentage: PercentageHelper.hasPositivePercentage(defaultRetentionPercentage), hasRetentionPercentage: fiscalDefaults.defaultRetentionPercentage !== null,
defaultRetentionPercentage: defaultRetentionPercentage, retentionPercentage: fiscalDefaults.defaultRetentionPercentage,
paymentMethodId: proforma.paymentMethod ?? proformaDefaults.paymentMethodId, paymentMethodId: proforma.paymentMethodId ?? proformaDefaults.paymentMethodId,
items: proforma.items.map(mapProformaItemsToProformaItemsUpdateForm), items: proforma.items.map(mapProformaItemsToProformaItemsUpdateForm),
}; };
}; };
const getFirstTaxableItem = (items: ProformaItem[]): ProformaItem | undefined => { const resolveFiscalDefaults = (
return items.find((item) => item.isValued) ?? items[0]; proforma: Proforma,
}; proformaDefaults: ProformaUpdateForm
): FiscalDefaults => {
// Caso habitual: una sola combinación de impuestos
if (proforma.taxes.length === 1) {
const taxSummary = proforma.taxes[0];
const inferProformaTaxMode = (items: ProformaItem[]): ProformaTaxMode => { return {
const comparableItems = items.filter((item) => item.isValued); taxMode: "single",
defaultTaxPercentage: taxSummary.ivaCode === null ? null : taxSummary.ivaPercentage,
if (comparableItems.length === 0) { defaultRecPercentage: taxSummary.recCode === null ? null : taxSummary.recPercentage,
return "single"; defaultRetentionPercentage:
taxSummary.retentionCode === null ? null : taxSummary.retentionPercentage,
};
} }
const sourceItems = comparableItems; // Caso excepcional: proforma sin impuestos
if (proforma.taxes.length === 0) {
return {
taxMode: "single",
defaultTaxPercentage: proformaDefaults.taxPercentage,
defaultRecPercentage: proformaDefaults.recPercentage,
defaultRetentionPercentage: proformaDefaults.retentionPercentage,
};
}
const ivaPercentages = uniquePercentageValues(sourceItems.map((item) => item.ivaPercentage)); // Proforma con varias combinaciones de impuestos
const recPercentages = uniquePercentageValues(sourceItems.map((item) => item.recPercentage)); return {
const retentionPercentages = uniquePercentageValues( taxMode: "perLine",
sourceItems.map((item) => item.retentionPercentage) defaultTaxPercentage: proformaDefaults.taxPercentage,
); defaultRecPercentage: proformaDefaults.recPercentage,
defaultRetentionPercentage: proformaDefaults.retentionPercentage,
const hasSingleTaxSetup = };
ivaPercentages.length <= 1 && recPercentages.length <= 1 && retentionPercentages.length <= 1;
return hasSingleTaxSetup ? "single" : "perLine";
};
const uniquePercentageValues = (values: Array<number | null | undefined>): number[] => {
return Array.from(
new Set(
values
.filter((value): value is number => value !== null && value !== undefined)
.map((value) => PercentageHelper.normalizePercentage(value))
)
);
}; };

View File

@ -156,12 +156,8 @@ export const useUpdateProformaController = (
return; return;
} }
console.log("Parche de actualización construido:", patchData);
const params = buildUpdateProformaByIdParams(proformaId, patchData, formData); const params = buildUpdateProformaByIdParams(proformaId, patchData, formData);
console.log("Enviando actualización con params:", params);
try { try {
// Enviamos cambios al servidor // Enviamos cambios al servidor
const updated = await mutateAsync(params); const updated = await mutateAsync(params);
@ -224,7 +220,7 @@ export const useUpdateProformaController = (
} }
}, },
(errors: FieldErrors<ProformaUpdateForm>) => { (errors: FieldErrors<ProformaUpdateForm>) => {
console.log(errors); console.error(errors);
focusFirstInputFormError(form); focusFirstInputFormError(form);
showWarningToast( showWarningToast(

View File

@ -2,8 +2,8 @@ import { useCallback, useEffect, useRef } from "react";
import { type UseFormReturn, useWatch } from "react-hook-form"; import { type UseFormReturn, useWatch } from "react-hook-form";
import { import {
type RetentionPercentageOption, type ProformaRetentionPercentageOption,
type TaxPercentageOption, type ProformaTaxPercentageOption,
getProformaRecPercentage, getProformaRecPercentage,
} from "../../shared"; } from "../../shared";
import type { ProformaTaxMode, ProformaUpdateForm } from "../entities"; import type { ProformaTaxMode, ProformaUpdateForm } from "../entities";
@ -16,13 +16,13 @@ export interface UseUpdateProformaTaxControllerResult {
taxMode: ProformaTaxMode; taxMode: ProformaTaxMode;
hasTaxPercentage: boolean; hasTaxPercentage: boolean;
defaultTaxPercentage: number | null; taxPercentage: number | null;
hasRecPercentage: boolean; hasRecPercentage: boolean;
defaultRecPercentage: number | null; recPercentage: number | null;
hasRetentionPercentage: boolean; hasRetentionPercentage: boolean;
defaultRetentionPercentage: number | null; retentionPercentage: number | null;
usesSingleTax: boolean; usesSingleTax: boolean;
usesPerLineTax: boolean; usesPerLineTax: boolean;
@ -30,20 +30,20 @@ export interface UseUpdateProformaTaxControllerResult {
enablePerLineTaxes: () => void; enablePerLineTaxes: () => void;
disablePerLineTaxes: () => void; disablePerLineTaxes: () => void;
updateDefaultTaxPercentage: (newTaxPercentage: RetentionPercentageOption) => void; updateTaxPercentage: (newTaxPercentage: ProformaTaxPercentageOption) => void;
updateDefaultRecPercentage: (enabled: boolean) => void; updateRecPercentage: (enabled: boolean) => void;
updateDefaultRetentionPercentage: (enabled: boolean) => void; updateRetentionPercentage: (newRetentionPercentage: ProformaRetentionPercentageOption) => void;
} }
const resolveRecPercentage = ( const resolveRecPercentage = (
enabled: boolean, enabled: boolean,
taxPercentage: RetentionPercentageOption | null taxPercentage: ProformaTaxPercentageOption | null
): number | null => { ): number | null => {
if (!enabled || taxPercentage === null) { if (!enabled || taxPercentage === null) {
return null; return null;
} }
return getProformaRecPercentage(taxPercentage as TaxPercentageOption); return getProformaRecPercentage(taxPercentage as ProformaTaxPercentageOption);
}; };
export const useUpdateProformaTaxController = ({ export const useUpdateProformaTaxController = ({
@ -54,14 +54,13 @@ export const useUpdateProformaTaxController = ({
const taxMode = useWatch({ control, name: "taxMode" }); const taxMode = useWatch({ control, name: "taxMode" });
const hasTaxPercentage = useWatch({ control, name: "hasTaxPercentage" }) ?? false; const hasTaxPercentage = useWatch({ control, name: "hasTaxPercentage" }) ?? false;
const defaultTaxPercentage = useWatch({ control, name: "defaultTaxPercentage" }) ?? null; const taxPercentage = useWatch({ control, name: "taxPercentage" }) ?? null;
const hasRecPercentage = useWatch({ control, name: "hasRecPercentage" }) ?? false; const hasRecPercentage = useWatch({ control, name: "hasRecPercentage" }) ?? false;
const defaultRecPercentage = useWatch({ control, name: "defaultRecPercentage" }) ?? null; const recPercentage = useWatch({ control, name: "recPercentage" }) ?? null;
const hasRetentionPercentage = useWatch({ control, name: "hasRetentionPercentage" }) ?? false; const hasRetentionPercentage = useWatch({ control, name: "hasRetentionPercentage" }) ?? false;
const defaultRetentionPercentage = const retentionPercentage = useWatch({ control, name: "retentionPercentage" }) ?? null;
useWatch({ control, name: "defaultRetentionPercentage" }) ?? null;
const hasMountedRef = useRef(false); const hasMountedRef = useRef(false);
@ -72,14 +71,14 @@ export const useUpdateProformaTaxController = ({
const nextRecPercentage = resolveRecPercentage( const nextRecPercentage = resolveRecPercentage(
hasRecPercentage, hasRecPercentage,
defaultTaxPercentage as RetentionPercentageOption | null taxPercentage as ProformaTaxPercentageOption | null
); );
const shouldMarkDirty = hasMountedRef.current; const shouldMarkDirty = hasMountedRef.current;
currentItems.forEach((item, index) => { currentItems.forEach((item, index) => {
if (item.taxPercentage !== defaultTaxPercentage) { if (item.taxPercentage !== taxPercentage) {
setValue(`items.${index}.taxPercentage`, defaultTaxPercentage, { setValue(`items.${index}.taxPercentage`, taxPercentage, {
shouldDirty: shouldMarkDirty, shouldDirty: shouldMarkDirty,
shouldTouch: false, shouldTouch: false,
shouldValidate: true, shouldValidate: true,
@ -96,7 +95,7 @@ export const useUpdateProformaTaxController = ({
}); });
hasMountedRef.current = true; hasMountedRef.current = true;
}, [defaultTaxPercentage, getValues, hasRecPercentage, setValue, taxMode]); }, [taxPercentage, getValues, hasRecPercentage, setValue, taxMode]);
const enablePerLineTaxes = useCallback(() => { const enablePerLineTaxes = useCallback(() => {
setValue("taxMode", "perLine", { setValue("taxMode", "perLine", {
@ -114,20 +113,25 @@ export const useUpdateProformaTaxController = ({
}); });
}, [setValue]); }, [setValue]);
const updateDefaultTaxPercentage = useCallback( const updateTaxPercentage = useCallback(
(newTaxPercentage: RetentionPercentageOption): void => { (newTaxPercentage: ProformaTaxPercentageOption): void => {
setValue("defaultTaxPercentage", newTaxPercentage, { setValue("taxPercentage", newTaxPercentage, {
shouldDirty: true, shouldDirty: true,
shouldTouch: true, shouldTouch: true,
shouldValidate: true, shouldValidate: true,
}); });
setValue("hasTaxPercentage", true, {
shouldDirty: true,
shouldValidate: true,
});
const isRecPercentageEnabled = getValues("hasRecPercentage"); const isRecPercentageEnabled = getValues("hasRecPercentage");
if (isRecPercentageEnabled) { if (isRecPercentageEnabled) {
setValue( setValue(
"defaultRecPercentage", "recPercentage",
getProformaRecPercentage(newTaxPercentage as TaxPercentageOption), getProformaRecPercentage(newTaxPercentage as ProformaTaxPercentageOption),
{ {
shouldDirty: true, shouldDirty: true,
shouldValidate: true, shouldValidate: true,
@ -138,7 +142,7 @@ export const useUpdateProformaTaxController = ({
[getValues, setValue] [getValues, setValue]
); );
const updateDefaultRecPercentage = useCallback( const updateRecPercentage = useCallback(
(enabled: boolean): void => { (enabled: boolean): void => {
setValue("hasRecPercentage", enabled, { setValue("hasRecPercentage", enabled, {
shouldDirty: true, shouldDirty: true,
@ -146,9 +150,9 @@ export const useUpdateProformaTaxController = ({
shouldValidate: true, shouldValidate: true,
}); });
const taxPercentage = getValues("defaultTaxPercentage") as RetentionPercentageOption | null; const taxPercentage = getValues("taxPercentage") as ProformaTaxPercentageOption | null;
setValue("defaultRecPercentage", resolveRecPercentage(enabled, taxPercentage), { setValue("recPercentage", resolveRecPercentage(enabled, taxPercentage), {
shouldDirty: true, shouldDirty: true,
shouldValidate: true, shouldValidate: true,
}); });
@ -156,13 +160,18 @@ export const useUpdateProformaTaxController = ({
[getValues, setValue] [getValues, setValue]
); );
const updateDefaultRetentionPercentage = useCallback( const updateRetentionPercentage = useCallback(
(enabled: boolean): void => { (newRetentionPercentage: ProformaRetentionPercentageOption): void => {
setValue("hasRetentionPercentage", enabled, { setValue("retentionPercentage", newRetentionPercentage, {
shouldDirty: true, shouldDirty: true,
shouldTouch: true, shouldTouch: true,
shouldValidate: true, shouldValidate: true,
}); });
setValue("hasRetentionPercentage", true, {
shouldDirty: true,
shouldValidate: true,
});
}, },
[setValue] [setValue]
); );
@ -171,13 +180,13 @@ export const useUpdateProformaTaxController = ({
taxMode, taxMode,
hasTaxPercentage, hasTaxPercentage,
defaultTaxPercentage, taxPercentage,
hasRecPercentage, hasRecPercentage,
defaultRecPercentage, recPercentage,
hasRetentionPercentage, hasRetentionPercentage,
defaultRetentionPercentage, retentionPercentage,
usesSingleTax: taxMode === "single", usesSingleTax: taxMode === "single",
usesPerLineTax: taxMode === "perLine", usesPerLineTax: taxMode === "perLine",
@ -185,8 +194,8 @@ export const useUpdateProformaTaxController = ({
enablePerLineTaxes, enablePerLineTaxes,
disablePerLineTaxes, disablePerLineTaxes,
updateDefaultTaxPercentage, updateTaxPercentage,
updateDefaultRecPercentage, updateRecPercentage,
updateDefaultRetentionPercentage, updateRetentionPercentage,
}; };
}; };

View File

@ -21,7 +21,7 @@ export const useUpdateProformaTotalsController = ({
const items = useWatch({ control, name: "items" }); const items = useWatch({ control, name: "items" });
const hasRetentionPercentage = useWatch({ control, name: "hasRetentionPercentage" }); const hasRetentionPercentage = useWatch({ control, name: "hasRetentionPercentage" });
const retentionPercentage = useWatch({ control, name: "defaultRetentionPercentage" }); const retentionPercentage = useWatch({ control, name: "retentionPercentage" });
const totals = useMemo(() => { const totals = useMemo(() => {
return calculateProformaTotals({ return calculateProformaTotals({

View File

@ -5,4 +5,3 @@ export * from "./proforma-item-update-patch.entity";
export * from "./proforma-update-form.entity"; export * from "./proforma-update-form.entity";
export * from "./proforma-update-form.schema"; export * from "./proforma-update-form.schema";
export * from "./proforma-update-patch.entity"; export * from "./proforma-update-patch.entity";
export * from "./proforma-update-totals.entity";

View File

@ -38,13 +38,13 @@ export interface ProformaUpdateForm {
taxRegimeCode: string | null; taxRegimeCode: string | null;
hasTaxPercentage: boolean; hasTaxPercentage: boolean;
defaultTaxPercentage: number | null; taxPercentage: number | null;
hasRecPercentage: boolean; hasRecPercentage: boolean;
defaultRecPercentage: number | null; recPercentage: number | null;
hasRetentionPercentage: boolean; hasRetentionPercentage: boolean;
defaultRetentionPercentage: number | null; retentionPercentage: number | null;
paymentMethodId: string | null; paymentMethodId: string | null;

View File

@ -39,13 +39,13 @@ export const ProformaUpdateFormSchema = z
taxRegimeCode: z.string(), taxRegimeCode: z.string(),
hasTaxPercentage: z.boolean(), hasTaxPercentage: z.boolean(),
defaultTaxPercentage: z.number().nullable(), taxPercentage: z.number().nullable(),
hasRecPercentage: z.boolean(), hasRecPercentage: z.boolean(),
defaultRecPercentage: z.number().nullable(), recPercentage: z.number().nullable(),
hasRetentionPercentage: z.boolean(), hasRetentionPercentage: z.boolean(),
defaultRetentionPercentage: z.number().nullable(), retentionPercentage: z.number().nullable(),
paymentMethodId: z.string().nullable(), paymentMethodId: z.string().nullable(),
@ -55,7 +55,7 @@ export const ProformaUpdateFormSchema = z
(formValues) => { (formValues) => {
if ( if (
formValues.hasRetentionPercentage && formValues.hasRetentionPercentage &&
!PercentageHelper.hasPositivePercentage(formValues.defaultRetentionPercentage) !PercentageHelper.hasPositivePercentage(formValues.retentionPercentage)
) { ) {
return false; return false;
} }

View File

@ -29,13 +29,13 @@ export type ProformaUpdatePatch = {
taxRegimeCode?: string | null; taxRegimeCode?: string | null;
hasTaxPercentage?: boolean; hasTaxPercentage?: boolean;
defaultTaxPercentage?: number | null; taxPercentage?: number | null;
hasRecPercentage?: boolean; hasRecPercentage?: boolean;
defaultRecPercentage?: number | null; recPercentage?: number | null;
hasRetentionPercentage?: boolean; hasRetentionPercentage?: boolean;
defaultRetentionPercentage?: number | null; retentionPercentage?: number | null;
paymentMethodId?: string | null; paymentMethodId?: string | null;

View File

@ -1,17 +0,0 @@
import type { ProformaHeaderTotals } from "./proforma-calculation.entity";
export interface ProformaTaxBreakdownLine {
taxableBase: number;
ivaPercentage: number;
ivaAmount: number;
recPercentage: number | null;
recAmount: number;
}
export interface ProformaTotals extends ProformaHeaderTotals {
recPercentage: number | null;
recAmount: number;
retentionPercentage: number | null;
retentionAmount: number;
}

View File

@ -113,8 +113,8 @@ export const ProformaLineEditor = ({
}, },
{ {
id: "itemDiscountPercentage", id: "itemDiscountPercentage",
header: t("form_fields.items.discount_percentage.label", "% Dto"), header: t("form_fields.items.discount_percentage.label", "Dto (%)"),
headClassName: "w-[100px] text-right", headClassName: "w-[100px] text-left",
cell: ({ index }) => ( cell: ({ index }) => (
<PercentageField <PercentageField
inputClassName="border-none" inputClassName="border-none"
@ -129,8 +129,8 @@ export const ProformaLineEditor = ({
? [ ? [
{ {
id: "taxPercentage", id: "taxPercentage",
header: t("form_fields.items.tax_percentage.label", "IVA"), header: t("form_fields.items.tax_percentage.label", "IVA (%)"),
headClassName: "w-[110px] text-right", headClassName: "w-[110px] text-left",
cell: ({ index }) => ( cell: ({ index }) => (
<SelectField <SelectField
deserialize={(value) => (value === null || value === "" ? null : Number(value))} deserialize={(value) => (value === null || value === "" ? null : Number(value))}

View File

@ -15,7 +15,6 @@ import type {
} from "../../controllers"; } from "../../controllers";
import { ProformaTotalsSummary } from "../blocks"; import { ProformaTotalsSummary } from "../blocks";
import { ProformaTaxesCard } from "./proforma-taxes-card";
import { ProformaUpdateHeaderEditor } from "./proforma-update-header-editor"; import { ProformaUpdateHeaderEditor } from "./proforma-update-header-editor";
import { ProformaUpdateItemsEditor } from "./proforma-update-items-editor"; import { ProformaUpdateItemsEditor } from "./proforma-update-items-editor";
import { ProformaUpdateTaxEditor } from "./proforma-update-tax-editor"; import { ProformaUpdateTaxEditor } from "./proforma-update-tax-editor";
@ -95,10 +94,6 @@ export const ProformaUpdateEditorForm = ({
/> />
</div> </div>
<div className="grid grid-cols-1">
<ProformaTaxesCard />
</div>
<div className="flex flex-col-reverse gap-3 border-t pt-4 sm:flex-row sm:justify-end"> <div className="flex flex-col-reverse gap-3 border-t pt-4 sm:flex-row sm:justify-end">
<Button disabled={isSubmitting} onClick={onReset} type="button" variant="outline"> <Button disabled={isSubmitting} onClick={onReset} type="button" variant="outline">
{t("common.reset", "Restablecer")} {t("common.reset", "Restablecer")}

View File

@ -23,7 +23,12 @@ import { cn } from "@repo/shadcn-ui/lib/utils";
import { ReceiptTextIcon } from "lucide-react"; import { ReceiptTextIcon } from "lucide-react";
import { useTranslation } from "../../../../i18n"; import { useTranslation } from "../../../../i18n";
import { getProformaRetentionOptions, getProformaTaxOptions } from "../../../shared"; import {
getProformaRetentionOptions,
getProformaTaxOptions,
parseProformaRetentionPercentage,
parseProformaTaxPercentage,
} from "../../../shared";
import type { UseUpdateProformaTaxControllerResult } from "../../controllers"; import type { UseUpdateProformaTaxControllerResult } from "../../controllers";
interface ProformaUpdateTaxEditorProps { interface ProformaUpdateTaxEditorProps {
@ -167,7 +172,13 @@ export const ProformaUpdateTaxEditor = ({
inputClassName="bg-background" inputClassName="bg-background"
items={getProformaTaxOptions()} items={getProformaTaxOptions()}
label={t("form_fields.proformas.default_tax_percentage.label", "IVA por defecto")} label={t("form_fields.proformas.default_tax_percentage.label", "IVA por defecto")}
name="defaultTaxPercentage" name="taxPercentage"
onChange={(value) => {
const parsed = parseProformaTaxPercentage(value as number | null);
if (parsed !== null) {
taxCtrl.updateTaxPercentage(parsed);
}
}}
placeholder={t( placeholder={t(
"form_fields.proformas.default_tax_percentage.placeholder", "form_fields.proformas.default_tax_percentage.placeholder",
"Selecciona IVA" "Selecciona IVA"
@ -198,13 +209,13 @@ export const ProformaUpdateTaxEditor = ({
{taxCtrl.hasRecPercentage ? ( {taxCtrl.hasRecPercentage ? (
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
{PercentageHelper.formatPercent(taxCtrl.defaultRecPercentage ?? 0)} {PercentageHelper.formatPercent(taxCtrl.recPercentage ?? 0)}
</span> </span>
) : null} ) : null}
</> </>
} }
name="hasRecPercentage" name="hasRecPercentage"
onCheckedChange={taxCtrl.updateDefaultRecPercentage} onCheckedChange={taxCtrl.updateRecPercentage}
readOnly={readOnly} readOnly={readOnly}
/> />
@ -223,7 +234,13 @@ export const ProformaUpdateTaxEditor = ({
inputClassName="bg-background" inputClassName="bg-background"
items={getProformaRetentionOptions()} items={getProformaRetentionOptions()}
label={t("form_fields.proformas.retention_percentage.label", "Retención")} label={t("form_fields.proformas.retention_percentage.label", "Retención")}
name="defaultRetentionPercentage" name="retentionPercentage"
onChange={(value) => {
const parsed = parseProformaRetentionPercentage(value as number | null);
if (parsed !== null) {
taxCtrl.updateRetentionPercentage(parsed);
}
}}
placeholder={t( placeholder={t(
"form_fields.proformas.default_tax_percentage.placeholder", "form_fields.proformas.default_tax_percentage.placeholder",
"Selecciona IVA" "Selecciona IVA"

View File

@ -22,13 +22,13 @@ export const buildProformaUpdateDefault = (): ProformaUpdateForm => {
taxRegimeCode: "01", taxRegimeCode: "01",
hasTaxPercentage: true, hasTaxPercentage: true,
defaultTaxPercentage: 21, taxPercentage: 21,
hasRecPercentage: false, hasRecPercentage: false,
defaultRecPercentage: null, recPercentage: null,
hasRetentionPercentage: false, hasRetentionPercentage: false,
defaultRetentionPercentage: 15, retentionPercentage: 15,
paymentMethodId: null, paymentMethodId: null,

View File

@ -2,13 +2,17 @@ import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/cor
import { toNullable } from "@repo/rdx-ddd"; import { toNullable } from "@repo/rdx-ddd";
import { ObjectHelper } from "@repo/rdx-utils"; import { ObjectHelper } from "@repo/rdx-utils";
import { getProformaRecCode, getProformaRetentionCode, getProformaTaxCode } from "../../shared";
import type { UpdateProformaByIdParams } from "../../shared/api"; import type { UpdateProformaByIdParams } from "../../shared/api";
import type { import type { ProformaItemUpdateForm, ProformaUpdateForm, ProformaUpdatePatch } from "../entities";
ProformaItemUpdateForm,
ProformaItemUpdatePatch, interface ProformaItemUpdateDTOContext {
ProformaUpdateForm, currencyCode: string;
ProformaUpdatePatch, taxMode: ProformaUpdateForm["taxMode"];
} from "../entities"; ivaCode: string | null;
recCode: string | null;
retentionCode: string | null;
}
/** /**
* Convierte el patch del formulario de actualización de proforma * Convierte el patch del formulario de actualización de proforma
@ -85,9 +89,8 @@ export const buildUpdateProformaByIdParams = (
// Si se han tocado los detalles, se envían todos // Si se han tocado los detalles, se envían todos
if (shouldReplaceItems(patch)) { if (shouldReplaceItems(patch)) {
data.items = formData.items.map((item, index) => const context = buildProformaItemUpdateDTOContext(formData);
toProformaItemUpdateDTO(item, index, formData) data.items = formData.items.map((item, index) => toProformaItemUpdateDTO(item, index, context));
);
} }
return { return {
@ -97,41 +100,40 @@ export const buildUpdateProformaByIdParams = (
}; };
const toProformaItemUpdateDTO = ( const toProformaItemUpdateDTO = (
item: ProformaItemUpdatePatch, item: ProformaItemUpdateForm,
index: number, index: number,
formData: ProformaUpdateForm context: ProformaItemUpdateDTOContext
): NonNullable<UpdateProformaByIdParams["data"]["items"]>[number] => { ): NonNullable<UpdateProformaByIdParams["data"]["items"]>[number] => {
const currencyCode = formData.currencyCode;
const quantity =
item.quantity === null ? null : QuantityDTOHelper.fromNumberNulleable(item.quantity, 2);
const unit_amount =
item.unitAmount === null
? null
: MoneyDTOHelper.fromNumberNulleable(item.unitAmount, currencyCode, 4);
const ivaPercentage = resolveLineIvaPercentage(item, formData);
const recPercentage = resolveLineRecPercentage(item, formData);
const retentionPercentage = resolveLineRetentionPercentage(formData);
return { return {
position: index, position: index,
description: item.description, description: item.description,
quantity, quantity: toNullable(item.quantity, (value) => QuantityDTOHelper.fromNumberNulleable(value, 2)),
unit_amount,
unit_amount: toNullable(item.unitAmount, (value) =>
MoneyDTOHelper.fromNumberNulleable(value, context.currencyCode, 4)
),
item_discount_percentage: toNullable(item.itemDiscountPercentage, (value) => item_discount_percentage: toNullable(item.itemDiscountPercentage, (value) =>
PercentageDTOHelper.fromNumber(value, 2) PercentageDTOHelper.fromNumber(value, 2)
), ),
iva_percentage: toNullable(ivaPercentage, (value) => PercentageDTOHelper.fromNumber(value, 2)), iva_code: resolveItemIvaCode(item, context),
rec_percentage: toNullable(recPercentage, (value) => PercentageDTOHelper.fromNumber(value, 2)), rec_code: resolveItemRecCode(item, context),
retention_percentage: toNullable(retentionPercentage, (value) => retention_code: context.retentionCode,
PercentageDTOHelper.fromNumber(value, 2) };
), };
const buildProformaItemUpdateDTOContext = (
formData: ProformaUpdateForm
): ProformaItemUpdateDTOContext => {
return {
currencyCode: formData.currencyCode,
taxMode: formData.taxMode,
ivaCode: formData.taxMode === "single" ? resolveSingleIvaCode(formData) : null,
recCode: formData.taxMode === "single" ? resolveSingleRecCode(formData) : null,
retentionCode: resolveRetentionCode(formData),
}; };
}; };
@ -140,38 +142,56 @@ const shouldReplaceItems = (patch: ProformaUpdatePatch): boolean => {
ObjectHelper.hasOwn(patch, "items") || ObjectHelper.hasOwn(patch, "items") ||
ObjectHelper.hasOwn(patch, "taxMode") || ObjectHelper.hasOwn(patch, "taxMode") ||
ObjectHelper.hasOwn(patch, "hasTaxPercentage") || ObjectHelper.hasOwn(patch, "hasTaxPercentage") ||
ObjectHelper.hasOwn(patch, "defaultTaxPercentage") || ObjectHelper.hasOwn(patch, "taxPercentage") ||
ObjectHelper.hasOwn(patch, "hasRecPercentage") || ObjectHelper.hasOwn(patch, "hasRecPercentage") ||
ObjectHelper.hasOwn(patch, "defaultRecPercentage") ObjectHelper.hasOwn(patch, "recPercentage") ||
ObjectHelper.hasOwn(patch, "hasRetentionPercentage") ||
ObjectHelper.hasOwn(patch, "retentionPercentage")
); );
}; };
const resolveLineIvaPercentage = ( const resolveSingleIvaCode = (formData: ProformaUpdateForm): string | null => {
item: ProformaItemUpdateForm, if (!formData.hasTaxPercentage) {
formData: ProformaUpdateForm return null;
): number | null => {
if (formData.taxMode === "single") {
return formData.hasTaxPercentage ? formData.defaultTaxPercentage : null;
} }
return item.taxPercentage; return getProformaTaxCode(formData.taxPercentage);
}; };
const resolveLineRecPercentage = ( const resolveSingleRecCode = (formData: ProformaUpdateForm): string | null => {
item: ProformaItemUpdateForm, if (!formData.hasRecPercentage) {
formData: ProformaUpdateForm return null;
): number | null => {
if (formData.taxMode === "single") {
return formData.hasRecPercentage ? formData.defaultRecPercentage : null;
} }
return item.recPercentage; return getProformaRecCode(formData.recPercentage);
}; };
const resolveLineRetentionPercentage = (formData: ProformaUpdateForm): number | null => { const resolveRetentionCode = (formData: ProformaUpdateForm): string | null => {
return formData.hasRetentionPercentage ? formData.defaultRetentionPercentage : null; if (!formData.hasRetentionPercentage) {
return null;
}
return getProformaRetentionCode(formData.retentionPercentage);
}; };
const toPercentageDTOOrNull = (value: number | null) => { const resolveItemIvaCode = (
return value === null ? null : PercentageDTOHelper.fromNumber(value, 2); item: ProformaItemUpdateForm,
context: ProformaItemUpdateDTOContext
): string | null => {
if (context.taxMode === "single") {
return context.ivaCode;
}
return getProformaTaxCode(item.taxPercentage);
};
const resolveItemRecCode = (
item: ProformaItemUpdateForm,
context: ProformaItemUpdateDTOContext
): string | null => {
if (context.taxMode === "single") {
return context.recCode;
}
return getProformaRecCode(item.recPercentage);
}; };

View File

@ -5,21 +5,15 @@ import { calculateProformaTotalsFromLines } from "./calculations";
export interface CalculateProformaTotalsParams { export interface CalculateProformaTotalsParams {
globalDiscountPercentage: ProformaUpdateForm["globalDiscountPercentage"]; globalDiscountPercentage: ProformaUpdateForm["globalDiscountPercentage"];
items: ProformaItemUpdateForm[]; items: ProformaItemUpdateForm[];
hasRetentionPercentage: boolean; hasRetentionPercentage: boolean;
retentionPercentage: number | null; retentionPercentage: number | null;
} }
export const calculateProformaTotals = ({ export const calculateProformaTotals = ({
globalDiscountPercentage, globalDiscountPercentage,
items, items,
hasRetentionPercentage, hasRetentionPercentage,
retentionPercentage, retentionPercentage,
}: CalculateProformaTotalsParams): ProformaTotals => { }: CalculateProformaTotalsParams): ProformaTotals => {
const lines = mapProformaItemFormToProformaLineInputs(items, { const lines = mapProformaItemFormToProformaLineInputs(items, {
@ -28,9 +22,7 @@ export const calculateProformaTotals = ({
return calculateProformaTotalsFromLines({ return calculateProformaTotalsFromLines({
lines, lines,
globalDiscountPercentage, globalDiscountPercentage,
retentionPercentage: hasRetentionPercentage ? retentionPercentage : null, retentionPercentage: hasRetentionPercentage ? retentionPercentage : null,
}); });
}; };

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/customers", "name": "@erp/customers",
"description": "Customers", "description": "Customers",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/factuges", "name": "@erp/factuges",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/supplier-invoices", "name": "@erp/supplier-invoices",
"description": "Supplier invoices", "description": "Supplier invoices",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/suppliers", "name": "@erp/suppliers",
"description": "Suppliers", "description": "Suppliers",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,7 +1,7 @@
{ {
"name": "uecko-erp-2025", "name": "uecko-erp-2025",
"private": true, "private": true,
"version": "0.6.5", "version": "0.6.6",
"workspaces": [ "workspaces": [
"apps/*", "apps/*",
"modules/*", "modules/*",

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-criteria", "name": "@repo/rdx-criteria",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-ddd", "name": "@repo/rdx-ddd",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-logger", "name": "@repo/rdx-logger",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-ui", "name": "@repo/rdx-ui",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -36,6 +36,7 @@ type SelectFieldProps<TFormValues extends FieldValues> = {
orientation?: "vertical" | "horizontal" | "responsive"; orientation?: "vertical" | "horizontal" | "responsive";
onChange?: (value: unknown) => void;
serialize?: (value: unknown) => string; serialize?: (value: unknown) => string;
deserialize?: (value: SelectFieldValue) => unknown; deserialize?: (value: SelectFieldValue) => unknown;
@ -59,6 +60,7 @@ export function SelectField<TFormValues extends FieldValues>({
orientation = "vertical", orientation = "vertical",
onChange,
serialize, serialize,
deserialize, deserialize,
@ -102,7 +104,11 @@ export function SelectField<TFormValues extends FieldValues>({
<Select <Select
disabled={isDisabled} disabled={isDisabled}
onValueChange={(value) => field.onChange(deserializeValue(value))} onValueChange={(value) =>
onChange &&
onChange(deserializeValue(value)) &&
field.onChange(deserializeValue(value))
}
value={fieldValue} value={fieldValue}
> >
<SelectTrigger <SelectTrigger

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-utils", "name": "@repo/rdx-utils",
"version": "0.6.5", "version": "0.6.6",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,