From 6a19698adccec26e36037edb3246ee996d38385f Mon Sep 17 00:00:00 2001 From: david Date: Mon, 4 May 2026 12:12:06 +0200 Subject: [PATCH] . --- .../account-service.integration.test.ts | 11 +- .../src/config/load-company-certificates.ts | 1 - apps/server/src/lib/modules/module-loader.ts | 2 - modules/auth/src/web/pages/LoginPage.tsx | 5 +- .../components/form-commit-button-group.tsx | 2 +- .../axios/create-axios-data-source.ts | 5 - .../models/customer-invoice.model.ts | 12 +- .../repositories/issued-invoice.repository.ts | 2 + .../repositories/proforma.repository.ts | 2 + ...forma-form-to-commercial-document-lines.ts | 2 +- ...s-to-proforma-items-update-form.adapter.ts | 2 +- ...roforma-to-proforma-update-form.adapter.ts | 37 ++- .../use-update-proforma-tax-controller.ts | 49 ++-- .../commercial-document-calculation.entity.ts | 2 + .../proforma-item-update-form.entity.ts | 2 +- .../proforma-item-update-form.schema.ts | 2 +- .../entities/proforma-update-form.entity.ts | 3 +- .../entities/proforma-update-form.schema.ts | 3 +- .../entities/proforma-update-totals.entity.ts | 2 +- .../ui/blocks/proforma-totals-summary.tsx | 4 +- .../editors/proforma-update-editor-form.tsx | 2 +- .../ui/editors/proforma-update-tax-editor.tsx | 239 ++++++++++-------- .../update/ui/pages/proforma-update-page.tsx | 8 +- .../build-proforma-item-update-default.ts | 3 + .../utils/build-proforma-update-default.ts | 13 +- .../mappers/update-customer-input.mapper.ts | 2 - .../models/sequelize-customer.model.ts | 5 +- .../ui/editor/customer-basic-info-fields.tsx | 2 - .../create/ui/pages/customer-create-page.tsx | 18 +- .../customer-proformas-section.tsx | 2 +- ...ustomer-to-customer-update-form.adapter.ts | 11 +- .../use-customer-update.controller.ts | 1 - .../update/ui/pages/customer-update-page.tsx | 80 +++--- .../models/supplier-invoice.model.ts | 6 +- .../supplier-invoice.sequelize-repository.ts | 6 +- .../models/sequelize-supplier.model.ts | 5 +- .../src/criteria-to-sequelize-converter.ts | 27 +- .../src/components/form/form-section-card.tsx | 8 +- .../src/components/form/select-field.tsx | 35 ++- .../src/helpers/percentage-helper.ts | 6 + 40 files changed, 367 insertions(+), 262 deletions(-) diff --git a/apps/server/archive/contexts/accounts/domain/services/account-service.integration.test.ts b/apps/server/archive/contexts/accounts/domain/services/account-service.integration.test.ts index 74cd91c4..a937a832 100644 --- a/apps/server/archive/contexts/accounts/domain/services/account-service.integration.test.ts +++ b/apps/server/archive/contexts/accounts/domain/services/account-service.integration.test.ts @@ -1,3 +1,5 @@ +import { Maybe, Result } from "@repo/rdx-utils"; + import { EmailAddress, PhoneNumber, @@ -5,10 +7,11 @@ import { TINNumber, UniqueID, } from "@/core/common/domain"; -import { Maybe, Result } from "@repo/rdx-utils"; -import { Account, IAccountProps } from "../aggregates"; -import { IAccountRepository } from "../repositories"; + +import { Account, type IAccountProps } from "../aggregates"; +import type { IAccountRepository } from "../repositories"; import { AccountStatus } from "../value-objects"; + import { AccountService } from "./account.service"; const mockAccountRepository: IAccountRepository = { @@ -113,8 +116,6 @@ describe("AccountService - Integración", () => { const result = await accountService.activateAccount(existingAccount.id); - console.log(result); - expect(result.isSuccess).toBe(true); expect(result.data.isActive).toBeTruthy(); expect(mockAccountRepository.update).toHaveBeenCalledWith(result.data); diff --git a/apps/server/src/config/load-company-certificates.ts b/apps/server/src/config/load-company-certificates.ts index 91144f9d..2ae9ad0f 100644 --- a/apps/server/src/config/load-company-certificates.ts +++ b/apps/server/src/config/load-company-certificates.ts @@ -40,7 +40,6 @@ function listarDirectorio(directorio: string): string[] { // Retornar rutas absolutas (opcional) const result = archivos.map((nombre) => path.join(directorio, nombre)); - console.log(result); return result; } catch (error: any) { throw new Error(`Error al listar el directorio: ${error.message}`); diff --git a/apps/server/src/lib/modules/module-loader.ts b/apps/server/src/lib/modules/module-loader.ts index ad879692..399ec67f 100644 --- a/apps/server/src/lib/modules/module-loader.ts +++ b/apps/server/src/lib/modules/module-loader.ts @@ -218,8 +218,6 @@ function validateModuleDependencies() { const declared = new Set(pkg.dependencies ?? []); const used = usedDependenciesByModule.get(moduleName) ?? new Set(); - console.log(declared, used); - // ❌ usadas pero no declaradas const undeclaredUsed = [...used].filter((d) => !declared.has(d)); diff --git a/modules/auth/src/web/pages/LoginPage.tsx b/modules/auth/src/web/pages/LoginPage.tsx index ecd095ac..fac00625 100644 --- a/modules/auth/src/web/pages/LoginPage.tsx +++ b/modules/auth/src/web/pages/LoginPage.tsx @@ -5,14 +5,13 @@ export const LoginPage = () => { const { login } = useAuth(); const handleOnSubmit = (data) => { - console.log(data); const { email, password } = data; login(email, password); }; return ( -
-
+
+
diff --git a/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/form-commit-button-group.tsx b/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/form-commit-button-group.tsx index 0ab979e2..abb06eaf 100644 --- a/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/form-commit-button-group.tsx +++ b/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/form-commit-button-group.tsx @@ -75,7 +75,7 @@ export const FormCommitButtonGroup = ({ const { t } = useTranslation(); const ctx = useFormContext(); - const rhfIsSubmitting = !!ctx.formState.isSubmitting; + const rhfIsSubmitting = ctx.formState.isSubmitting; const busy = isLoading ?? rhfIsSubmitting; const showCancel = cancel?.show ?? true; diff --git a/modules/core/src/web/lib/data-source/axios/create-axios-data-source.ts b/modules/core/src/web/lib/data-source/axios/create-axios-data-source.ts index be18279a..9d76d934 100644 --- a/modules/core/src/web/lib/data-source/axios/create-axios-data-source.ts +++ b/modules/core/src/web/lib/data-source/axios/create-axios-data-source.ts @@ -72,11 +72,6 @@ export const createAxiosDataSource = (client: AxiosInstance): IDataSource => { ): Promise => { const url = `${resource}/${id}`; - console.log("Axios updateOne => ", { - url, - data, - }); - const res = await client.put, TData>( url, data, diff --git a/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice.model.ts b/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice.model.ts index ca2b7451..61cbbba6 100644 --- a/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/common/persistence/sequelize/models/customer-invoice.model.ts @@ -533,12 +533,16 @@ export default (database: Sequelize) => { { name: "uq_invoice_proforma_id", fields: ["proforma_id"], unique: true }, // <- para asegurar que una proforma solo tenga una factura vinculada - // Para búsquedas simples - { + // Para búsquedas simples => se hace con el "CriteriaToSequelizeConverter" + /*{ name: "ft_customer_invoice", type: "FULLTEXT", - fields: ["invoice_number", "reference", "description"], - }, + fields: [ + "CustomerInvoiceModel.invoice_number", + "CustomerInvoiceModel.reference", + "CustomerInvoiceModel.description", + ], + },*/ ], whereMergeStrategy: "and", // <- cómo tratar el merge de un scope diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/repositories/issued-invoice.repository.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/repositories/issued-invoice.repository.ts index 38e029d2..457f49e1 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/repositories/issued-invoice.repository.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/repositories/issued-invoice.repository.ts @@ -220,7 +220,9 @@ export class IssuedInvoiceRepository const query = criteriaConverter.convert(criteria, { searchableFields: ["invoice_number", "reference", "description"], mappings: { + invoice_number: "CustomerInvoiceModel.invoice_number", reference: "CustomerInvoiceModel.reference", + description: "CustomerInvoiceModel.description", }, allowedFields: ["invoice_date", "id", "created_at"], enableFullText: true, diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/repositories/proforma.repository.ts b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/repositories/proforma.repository.ts index 7cd749cd..5fec2766 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/repositories/proforma.repository.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/repositories/proforma.repository.ts @@ -437,7 +437,9 @@ export class ProformaRepository const query = criteriaConverter.convert(criteria, { searchableFields: ["invoice_number", "reference", "description"], mappings: { + invoice_number: "CustomerInvoiceModel.invoice_number", reference: "CustomerInvoiceModel.reference", + description: "CustomerInvoiceModel.description", }, allowedFields: ["invoice_date", "id", "created_at"], enableFullText: true, diff --git a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-form-to-commercial-document-lines.ts b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-form-to-commercial-document-lines.ts index a4498d25..2b25f5d4 100644 --- a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-form-to-commercial-document-lines.ts +++ b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-form-to-commercial-document-lines.ts @@ -10,6 +10,6 @@ export const mapProformaFormToCommercialDocumentLines = ( unitAmount: item.unitAmount, itemDiscountPercentage: item.itemDiscountPercentage, taxPercentage: item.taxPercentage, - equivalenceSurchargePercentage: item.equivalenceSurchargePercentage, + recPercentage: item.recPercentage, })); }; diff --git a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-items-to-proforma-items-update-form.adapter.ts b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-items-to-proforma-items-update-form.adapter.ts index 4414c673..e5aa0317 100644 --- a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-items-to-proforma-items-update-form.adapter.ts +++ b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-items-to-proforma-items-update-form.adapter.ts @@ -24,6 +24,6 @@ export const mapProformaItemsToProformaItemsUpdateForm = ( itemDiscountPercentage: item.itemDiscountPercentage ?? null, taxPercentage: item.ivaPercentage ?? null, - equivalenceSurchargePercentage: item.recPercentage ?? null, + recPercentage: item.recPercentage ?? null, }; }; diff --git a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts index e47a859e..7e8a35ba 100644 --- a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts +++ b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts @@ -1,5 +1,8 @@ +import { PercentageHelper } from "@repo/rdx-utils"; + import type { Proforma, ProformaItem } from "../../shared"; import type { ProformaTaxMode, ProformaUpdateForm } from "../entities"; +import { buildProformaUpdateDefault } from "../utils"; import { mapProformaItemsToProformaItemsUpdateForm } from "./map-proforma-items-to-proforma-items-update-form.adapter"; @@ -7,40 +10,46 @@ import { mapProformaItemsToProformaItemsUpdateForm } from "./map-proforma-items- * Mapea una proforma a un formulario de actualización de proforma. */ export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpdateForm => { + const proformaDefaults = buildProformaUpdateDefault(); + const taxMode = inferProformaTaxMode(proforma.items); const defaultTaxSummary = proforma.taxes[0]; const firstTaxableItem = getFirstTaxableItem(proforma.items); const defaultTaxPercentage = - defaultTaxSummary?.ivaPercentage ?? firstTaxableItem?.ivaPercentage ?? null; + defaultTaxSummary?.ivaPercentage ?? + firstTaxableItem?.ivaPercentage ?? + proformaDefaults.defaultTaxPercentage; - const defaultEquivalenceSurchargePercentage = + const defaultRecPercentage = defaultTaxSummary?.recPercentage ?? firstTaxableItem?.recPercentage ?? null; const defaultRetentionPercentage = defaultTaxSummary?.retentionPercentage ?? firstTaxableItem?.retentionPercentage ?? null; return { - series: proforma.series ?? "", + series: proforma.series ?? proformaDefaults.series, - invoiceDate: proforma.invoiceDate ?? "", - operationDate: proforma.operationDate ?? "", + invoiceDate: proforma.invoiceDate ?? proformaDefaults.invoiceDate, + operationDate: proforma.operationDate ?? proformaDefaults.operationDate, - customerId: proforma.customerId ?? "", + customerId: proforma.customerId ?? proformaDefaults.customerId, - description: proforma.description ?? "", - reference: proforma.reference ?? "", - notes: proforma.notes ?? "", + description: proforma.description ?? proformaDefaults.description, + reference: proforma.reference ?? proformaDefaults.reference, + notes: proforma.notes ?? proformaDefaults.notes, - languageCode: proforma.languageCode ?? "es", - currencyCode: proforma.currencyCode ?? "EUR", + languageCode: proforma.languageCode ?? proformaDefaults.languageCode, + currencyCode: proforma.currencyCode ?? proformaDefaults.currencyCode, - globalDiscountPercentage: proforma.globalDiscountPercentage ?? 0, + globalDiscountPercentage: + proforma.globalDiscountPercentage ?? proformaDefaults.globalDiscountPercentage, taxMode, + taxRegimeCode: proformaDefaults.taxRegimeCode, defaultTaxPercentage, - hasEquivalenceSurcharge: hasPositivePercentage(defaultEquivalenceSurchargePercentage), + hasRecPercentage: hasPositivePercentage(defaultRecPercentage), hasRetention: hasPositivePercentage(defaultRetentionPercentage), retentionPercentage: defaultRetentionPercentage, @@ -84,5 +93,5 @@ const normalizePercentage = (value: number): number => { }; const hasPositivePercentage = (value: number | null | undefined): boolean => { - return value !== null && value !== undefined && value > 0; + return PercentageHelper.hasPositivePercentage(value); }; diff --git a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-tax-controller.ts b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-tax-controller.ts index aba8a672..5c8aeb15 100644 --- a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-tax-controller.ts +++ b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-tax-controller.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect } from "react"; +import { useCallback, useEffect, useRef } from "react"; import { type UseFormReturn, useWatch } from "react-hook-form"; import type { ProformaTaxMode, ProformaUpdateForm } from "../entities"; @@ -11,7 +11,7 @@ export interface UseUpdateProformaTaxControllerResult { taxMode: ProformaTaxMode; defaultTaxPercentage: number | null; - hasEquivalenceSurcharge: boolean; + hasRecPercentage: boolean; hasRetention: boolean; retentionPercentage: number | null; @@ -22,9 +22,7 @@ export interface UseUpdateProformaTaxControllerResult { disablePerLineTaxes: () => void; } -const getEquivalenceSurchargePercentage = ( - taxPercentage: number | null | undefined -): number | null => { +const getRecPercentage = (taxPercentage: number | null | undefined): number | null => { if (taxPercentage === 21) return 5.2; if (taxPercentage === 10) return 1.4; if (taxPercentage === 4) return 0.5; @@ -38,33 +36,40 @@ export const useUpdateProformaTaxController = ({ const { control, getValues, setValue } = form; const taxMode = useWatch({ control, name: "taxMode" }); - const hasEquivalenceSurcharge = useWatch({ control, name: "hasEquivalenceSurcharge" }) ?? false; + const hasRecPercentage = useWatch({ control, name: "hasRecPercentage" }) ?? false; const hasRetention = useWatch({ control, name: "hasRetention" }) ?? false; const retentionPercentage = useWatch({ control, name: "retentionPercentage" }) ?? null; const defaultTaxPercentage = useWatch({ control, name: "defaultTaxPercentage" }) ?? null; + const hasMountedRef = useRef(false); useEffect(() => { if (taxMode !== "single") return; const currentItems = getValues("items") ?? []; - const equivalenceSurchargePercentage = hasEquivalenceSurcharge - ? getEquivalenceSurchargePercentage(defaultTaxPercentage) - : null; + const recPercentage = hasRecPercentage ? getRecPercentage(defaultTaxPercentage) : null; - currentItems.forEach((_, index) => { - setValue(`items.${index}.taxPercentage`, defaultTaxPercentage ?? null, { - shouldDirty: true, - shouldTouch: true, - shouldValidate: true, - }); + const shouldMarkDirty = hasMountedRef.current; - setValue(`items.${index}.equivalenceSurchargePercentage`, equivalenceSurchargePercentage, { - shouldDirty: true, - shouldTouch: true, - shouldValidate: true, - }); + currentItems.forEach((item, index) => { + if (item.taxPercentage !== defaultTaxPercentage) { + setValue(`items.${index}.taxPercentage`, defaultTaxPercentage, { + shouldDirty: shouldMarkDirty, + shouldTouch: false, + shouldValidate: true, + }); + } + + if (item.recPercentage !== recPercentage) { + setValue(`items.${index}.recPercentage`, recPercentage, { + shouldDirty: shouldMarkDirty, + shouldTouch: false, + shouldValidate: true, + }); + } }); - }, [defaultTaxPercentage, getValues, hasEquivalenceSurcharge, setValue, taxMode]); + + hasMountedRef.current = true; + }, [defaultTaxPercentage, getValues, hasRecPercentage, setValue, taxMode]); const enablePerLineTaxes = useCallback(() => { setValue("taxMode", "perLine", { @@ -86,7 +91,7 @@ export const useUpdateProformaTaxController = ({ taxMode, defaultTaxPercentage, - hasEquivalenceSurcharge, + hasRecPercentage, hasRetention, retentionPercentage, diff --git a/modules/customer-invoices/src/web/proformas/update/entities/commercial-document-calculation.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/commercial-document-calculation.entity.ts index 8c8e903c..df5edee4 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/commercial-document-calculation.entity.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/commercial-document-calculation.entity.ts @@ -3,6 +3,8 @@ export interface CommercialDocumentLineInput { unitAmount: number | null; itemDiscountPercentage: number | null; taxPercentage: number | null; + + recPercentage: number | null; } export interface CommercialDocumentLineAmounts { diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.entity.ts index e67f38f4..5e49915c 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.entity.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.entity.ts @@ -11,5 +11,5 @@ export interface ProformaItemUpdateForm { itemDiscountPercentage: number | null; taxPercentage: number | null; - equivalenceSurchargePercentage: number | null; + recPercentage: number | null; } diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.schema.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.schema.ts index 20e9a82a..e384da1d 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.schema.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.schema.ts @@ -27,7 +27,7 @@ export const ProformaItemUpdateFormSchema = z.object({ itemDiscountPercentage: z.number().nullable(), taxPercentage: z.number().nullable(), - equivalenceSurchargePercentage: z.number().nullable(), + recPercentage: z.number().nullable(), }); export type ProformaItemUpdateFormSchemaType = z.infer; diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.entity.ts index aa6fd196..a3a7e739 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.entity.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.entity.ts @@ -35,9 +35,10 @@ export interface ProformaUpdateForm { globalDiscountPercentage: number; taxMode: ProformaTaxMode; + taxRegimeCode: string | null; defaultTaxPercentage: number | null; - hasEquivalenceSurcharge: boolean; + hasRecPercentage: boolean; hasRetention: boolean; retentionPercentage: number | null; diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.schema.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.schema.ts index f535b467..87d68f8c 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.schema.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.schema.ts @@ -34,9 +34,10 @@ export const ProformaUpdateFormSchema = z.object({ globalDiscountPercentage: z.number().min(0).max(100), taxMode: z.enum(["single", "perLine"]), + taxRegimeCode: z.string(), defaultTaxPercentage: z.number().nullable(), - hasEquivalenceSurcharge: z.boolean(), + hasRecPercentage: z.boolean(), hasRetention: z.boolean(), retentionPercentage: z.number().nullable(), diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-totals.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-totals.entity.ts index 885a0b46..257ce1d2 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-totals.entity.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-totals.entity.ts @@ -2,7 +2,7 @@ export interface ProformaTaxBreakdownLine { taxPercentage: number; taxableBase: number; taxAmount: number; - equivalenceSurchargePercentage: number | null; + recPercentage: number | null; equivalenceSurchargeAmount: number; } diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx index e81477b9..eb16d2a8 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx @@ -104,13 +104,13 @@ export const ProformaTotalsSummary = ({ value={formatMoney(tax.taxAmount)} /> - {showEquivalenceSurcharge && tax.equivalenceSurchargePercentage !== null ? ( + {showEquivalenceSurcharge && tax.recPercentage !== null ? ( ) : null} diff --git a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx index 7a95a8e2..cde0e129 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx @@ -80,7 +80,7 @@ export const ProformaUpdateEditorForm = ({ className="md:col-span-6" currency={currencyCode} disabled={isSubmitting} - showEquivalenceSurcharge={taxCtrl.hasEquivalenceSurcharge} + showEquivalenceSurcharge={taxCtrl.hasRecPercentage} showRetention={taxCtrl.hasRetention} totals={totalsCtrl.totals} /> diff --git a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-tax-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-tax-editor.tsx index 1d64b7c5..18b1836f 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-tax-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-tax-editor.tsx @@ -6,6 +6,8 @@ import { SelectField, } from "@repo/rdx-ui/components"; import { + Card, + CardContent, Checkbox, Field, FieldDescription, @@ -14,6 +16,7 @@ import { FieldSet, Label, } from "@repo/shadcn-ui/components"; +import { cn } from "@repo/shadcn-ui/lib/utils"; import { ReceiptTextIcon } from "lucide-react"; import { useTranslation } from "../../../../i18n"; @@ -47,116 +50,144 @@ export const ProformaUpdateTaxEditor = ({ icon={} title={t("form_groups.proformas.taxes.title", "Impuestos")} > - - - - - - -
- Billing Address - The billing address associated with your payment method - + 1. Régimen fiscal + + Selecciona el régimen fiscal que aplica a esta proforma. + - - checked ? taxCtrl.disablePerLineTaxes() : taxCtrl.enablePerLineTaxes() - } - /> - + +
+ - + + +
+ Configuración de IVA + + Puedes usar un tipo único para todos las líneas de detalle o permitir que cada línea + tenga su propio IVA. + + + + + checked ? taxCtrl.disablePerLineTaxes() : taxCtrl.enablePerLineTaxes() + } + /> + + + + (value === null || value === "" ? null : Number(value))} + disabled={disabled} + items={[ + { value: "0", label: "0%" }, + { value: "4", label: "4%" }, + { value: "10", label: "10%" }, + { value: "21", label: "21%" }, + ]} + label={t("form_fields.proformas.default_tax_percentage.label", "IVA por defecto")} + name="defaultTaxPercentage" + placeholder={t( + "form_fields.proformas.default_tax_percentage.placeholder", + "Selecciona IVA" + )} + readOnly={readOnly || taxCtrl.usesPerLineTax} + serialize={(value) => (typeof value === "number" ? String(value) : "")} + /> + +
{" "} +
+
+ + +
+ Impuestos adicionales + + Activa opciones fiscales adicionales como recargo de equivalencia o retenciones (IRPF), + según el tipo de cliente y normativa aplicable. + + + diff --git a/modules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx b/modules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx index 33a8044d..4647def3 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx @@ -3,7 +3,7 @@ import { ErrorAlert, NotFoundCard, PageHeader } from "@erp/core/components"; import { FormCommitButtonGroup, UnsavedChangesProvider } from "@erp/core/hooks"; import { SelectCustomerDialog } from "@erp/customers"; import { AppContent, AppHeader, BackHistoryButton } from "@repo/rdx-ui/components"; -import { useEffect, useMemo } from "react"; +import { useMemo } from "react"; import { FormProvider } from "react-hook-form"; import { useNavigate } from "react-router-dom"; @@ -19,12 +19,6 @@ export const ProformaUpdatePage = () => { const { updateCtrl, selectCustomerCtrl } = useUpdateProformaPageController(); - useEffect(() => { - console.log("[ProformaUpdatePage] isDirty:", updateCtrl.form.formState.isDirty); - console.log("[ProformaUpdatePage] dirtyFields:", updateCtrl.form.formState.dirtyFields); - console.log("[ProformaUpdatePage] values:", updateCtrl.form.getValues()); - }, [updateCtrl.form.formState.isDirty, updateCtrl.form.formState.dirtyFields, updateCtrl.form]); - if (updateCtrl.isLoading) { return ; } diff --git a/modules/customer-invoices/src/web/proformas/update/utils/build-proforma-item-update-default.ts b/modules/customer-invoices/src/web/proformas/update/utils/build-proforma-item-update-default.ts index 2a6d703e..d96d6cef 100644 --- a/modules/customer-invoices/src/web/proformas/update/utils/build-proforma-item-update-default.ts +++ b/modules/customer-invoices/src/web/proformas/update/utils/build-proforma-item-update-default.ts @@ -13,5 +13,8 @@ export const buildProformaItemUpdateDefault = (position: number): ProformaItemUp quantity: null, unitAmount: null, itemDiscountPercentage: null, + + taxPercentage: null, + recPercentage: null, }; }; diff --git a/modules/customer-invoices/src/web/proformas/update/utils/build-proforma-update-default.ts b/modules/customer-invoices/src/web/proformas/update/utils/build-proforma-update-default.ts index 13d34fef..a5a8db59 100644 --- a/modules/customer-invoices/src/web/proformas/update/utils/build-proforma-update-default.ts +++ b/modules/customer-invoices/src/web/proformas/update/utils/build-proforma-update-default.ts @@ -16,10 +16,19 @@ export const buildProformaUpdateDefault = (): ProformaUpdateForm => { languageCode: "es", currencyCode: "EUR", - paymentMethod: "", - globalDiscountPercentage: 0, + taxMode: "single", + taxRegimeCode: "01", + defaultTaxPercentage: 21, + + hasRecPercentage: false, + hasRetention: false, + + retentionPercentage: null, + + paymentMethod: "", + items: [], }; }; diff --git a/modules/customers/src/api/application/mappers/update-customer-input.mapper.ts b/modules/customers/src/api/application/mappers/update-customer-input.mapper.ts index cf391084..de637d6c 100644 --- a/modules/customers/src/api/application/mappers/update-customer-input.mapper.ts +++ b/modules/customers/src/api/application/mappers/update-customer-input.mapper.ts @@ -140,8 +140,6 @@ export class UpdateCustomerInputMapper implements IUpdateCustomerInputMapper { ); } - console.log("Mapped CustomerPatchProps:", customerPatchProps); - return Result.ok(customerPatchProps); } catch (err: unknown) { return Result.fail(new DomainError("Customer props mapping failed (update)", { cause: err })); diff --git a/modules/customers/src/api/infrastructure/persistence/sequelize/models/sequelize-customer.model.ts b/modules/customers/src/api/infrastructure/persistence/sequelize/models/sequelize-customer.model.ts index dceb64e7..033187f8 100644 --- a/modules/customers/src/api/infrastructure/persistence/sequelize/models/sequelize-customer.model.ts +++ b/modules/customers/src/api/infrastructure/persistence/sequelize/models/sequelize-customer.model.ts @@ -242,11 +242,12 @@ export default (database: Sequelize) => { { name: "idx_company_idx", fields: ["id", "company_id"], unique: true }, // <- para consulta get { name: "idx_factuges", fields: ["factuges_id"], unique: true }, // <- para el proceso python - { + // Para búsquedas simples => se hace con el "CriteriaToSequelizeConverter" + /*{ name: "ft_customer", type: "FULLTEXT", fields: ["name", "trade_name", "reference", "tin", "email_primary", "mobile_primary"], - }, + },*/ ], whereMergeStrategy: "and", // <- cómo tratar el merge de un scope diff --git a/modules/customers/src/web/create/ui/editor/customer-basic-info-fields.tsx b/modules/customers/src/web/create/ui/editor/customer-basic-info-fields.tsx index 99d25165..1e603364 100644 --- a/modules/customers/src/web/create/ui/editor/customer-basic-info-fields.tsx +++ b/modules/customers/src/web/create/ui/editor/customer-basic-info-fields.tsx @@ -48,7 +48,6 @@ export const CustomerBasicInfoFields = ({ className, ...props }: CustomerBasicIn control={control} name="isCompany" render={({ field, fieldState }) => { - console.log(field.value); return ( { - console.log("Pongo ", value); field.onChange(value === "true"); }} required diff --git a/modules/customers/src/web/create/ui/pages/customer-create-page.tsx b/modules/customers/src/web/create/ui/pages/customer-create-page.tsx index bceb4ca4..c48f0b80 100644 --- a/modules/customers/src/web/create/ui/pages/customer-create-page.tsx +++ b/modules/customers/src/web/create/ui/pages/customer-create-page.tsx @@ -2,6 +2,7 @@ import { ErrorAlert, PageHeader } from "@erp/core/components"; import { FormCommitButtonGroup, UnsavedChangesProvider } from "@erp/core/hooks"; import { AppContent, AppHeader } from "@repo/rdx-ui/components"; import { FormProvider } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; import { useTranslation } from "../../../i18n"; import { useCustomerCreatePageController } from "../../controllers"; @@ -9,6 +10,7 @@ import { CustomerCreateEditorForm } from "../editor"; export const CustomerCreatePage = () => { const { t } = useTranslation(); + const navigate = useNavigate(); const { createCtrl } = useCustomerCreatePageController(); const { form, formId, onSubmit, resetForm, isCreating, isCreateError, createError } = createCtrl; @@ -17,15 +19,19 @@ export const CustomerCreatePage = () => { navigate("/customers/list")} rightSlot={ } title={t("pages.create.title")} diff --git a/modules/customers/src/web/list/ui/blocks/customer-summary-panel/customer-proformas-section.tsx b/modules/customers/src/web/list/ui/blocks/customer-summary-panel/customer-proformas-section.tsx index 32cc18e2..8d9bfdbd 100644 --- a/modules/customers/src/web/list/ui/blocks/customer-summary-panel/customer-proformas-section.tsx +++ b/modules/customers/src/web/list/ui/blocks/customer-summary-panel/customer-proformas-section.tsx @@ -1,4 +1,4 @@ -import { DateHelper } from "@erp/core"; +import { DateHelper } from "@repo/rdx-utils"; import { Badge, Button } from "@repo/shadcn-ui/components"; import { FileTextIcon } from "lucide-react"; diff --git a/modules/customers/src/web/update/adapters/customer-to-customer-update-form.adapter.ts b/modules/customers/src/web/update/adapters/customer-to-customer-update-form.adapter.ts index ed5453ae..5c4881dc 100644 --- a/modules/customers/src/web/update/adapters/customer-to-customer-update-form.adapter.ts +++ b/modules/customers/src/web/update/adapters/customer-to-customer-update-form.adapter.ts @@ -1,5 +1,5 @@ import type { Customer } from "../../shared"; -import { type CustomerUpdateForm, defaultCustomerUpdateForm } from "../entities"; /** +import type { CustomerUpdateForm } from "../entities"; /** * Mapea un cliente a un formulario de actualización de cliente. * * Reglas: @@ -11,10 +11,11 @@ import { type CustomerUpdateForm, defaultCustomerUpdateForm } from "../entities" * @param customer * @returns */ +import { buildCustomerUpdateDefault } from "../utils"; export const mapCustomerToCustomerUpdateForm = (customer: Customer): CustomerUpdateForm => ({ reference: customer.reference ?? "", - isCompany: customer.isCompany ?? defaultCustomerUpdateForm.isCompany, + isCompany: customer.isCompany ?? buildCustomerUpdateDefault().isCompany, name: customer.name, tradeName: customer.tradeName ?? "", tin: customer.tin ?? "", @@ -26,7 +27,7 @@ export const mapCustomerToCustomerUpdateForm = (customer: Customer): CustomerUpd city: customer.address.city ?? "", province: customer.address.province ?? "", postalCode: customer.address.postalCode ?? "", - country: customer.address.country ?? defaultCustomerUpdateForm.country, + country: customer.address.country ?? buildCustomerUpdateDefault().country, primaryEmail: customer.contact.primaryEmail ?? "", secondaryEmail: customer.contact.secondaryEmail ?? "", @@ -40,6 +41,6 @@ export const mapCustomerToCustomerUpdateForm = (customer: Customer): CustomerUpd legalRecord: customer.legalRecord ?? "", - languageCode: customer.languageCode ?? defaultCustomerUpdateForm.languageCode, - currencyCode: customer.currencyCode ?? defaultCustomerUpdateForm.currencyCode, + languageCode: customer.languageCode ?? buildCustomerUpdateDefault().languageCode, + currencyCode: customer.currencyCode ?? buildCustomerUpdateDefault().currencyCode, }); diff --git a/modules/customers/src/web/update/controllers/use-customer-update.controller.ts b/modules/customers/src/web/update/controllers/use-customer-update.controller.ts index 5d08cb12..8624d998 100644 --- a/modules/customers/src/web/update/controllers/use-customer-update.controller.ts +++ b/modules/customers/src/web/update/controllers/use-customer-update.controller.ts @@ -196,7 +196,6 @@ export const useCustomerUpdateController = ( } }, (errors: FieldErrors) => { - console.log(errors); focusFirstInputFormError(form); showWarningToast( diff --git a/modules/customers/src/web/update/ui/pages/customer-update-page.tsx b/modules/customers/src/web/update/ui/pages/customer-update-page.tsx index e672627c..c8880406 100644 --- a/modules/customers/src/web/update/ui/pages/customer-update-page.tsx +++ b/modules/customers/src/web/update/ui/pages/customer-update-page.tsx @@ -3,6 +3,7 @@ import { FormCommitButtonGroup, UnsavedChangesProvider } from "@erp/core/hooks"; import { AppContent, AppHeader, BackHistoryButton } from "@repo/rdx-ui/components"; import { Spinner } from "@repo/shadcn-ui/components"; import { FormProvider } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; import { useTranslation } from "../../../i18n"; import { useCustomerUpdatePageController } from "../../controllers"; @@ -11,7 +12,7 @@ import { CustomerUpdateEditorForm } from "../editor"; export const CustomerUpdatePage = () => { const { t } = useTranslation(); - + const navigate = useNavigate(); const { updateCtrl } = useCustomerUpdatePageController(); if (updateCtrl.isLoading) { @@ -48,55 +49,52 @@ export const CustomerUpdatePage = () => { ); return ( - - - - } - title={t("pages.update.title")} - /> - - - {/* Alerta de error de actualización (si ha fallado el último intento) */} - {updateCtrl.isUpdateError && ( - + + + navigate("/customers/list")} + rightSlot={ + } - title={t("pages.update.errorTitle", "No se pudo guardar los cambios")} + title={t("pages.update.title")} /> - )} + + + {/* Alerta de error de actualización (si ha fallado el último intento) */} + {updateCtrl.isUpdateError && ( + + )} - {updateCtrl.isLoading && } + {updateCtrl.isLoading && } - {!updateCtrl.isLoading && ( - + {!updateCtrl.isLoading && ( - - )} - - + )} + + + ); }; diff --git a/modules/supplier-invoices/src/api/infrastucture/persistence/sequelize/models/supplier-invoice.model.ts b/modules/supplier-invoices/src/api/infrastucture/persistence/sequelize/models/supplier-invoice.model.ts index 66b6b487..81926be5 100644 --- a/modules/supplier-invoices/src/api/infrastucture/persistence/sequelize/models/supplier-invoice.model.ts +++ b/modules/supplier-invoices/src/api/infrastucture/persistence/sequelize/models/supplier-invoice.model.ts @@ -300,11 +300,13 @@ export default (database: Sequelize) => { name: "idx_supplier_invoice_document_id", fields: ["document_id"], }, - { + + // Para búsquedas simples => se hace con el "CriteriaToSequelizeConverter" + /*{ name: "ft_supplier_invoice", type: "FULLTEXT", fields: ["invoice_number", "description", "notes"], - }, + },*/ ], whereMergeStrategy: "and", diff --git a/modules/supplier-invoices/src/api/infrastucture/persistence/sequelize/repositories/supplier-invoice.sequelize-repository.ts b/modules/supplier-invoices/src/api/infrastucture/persistence/sequelize/repositories/supplier-invoice.sequelize-repository.ts index 76ed705b..70f4972e 100644 --- a/modules/supplier-invoices/src/api/infrastucture/persistence/sequelize/repositories/supplier-invoice.sequelize-repository.ts +++ b/modules/supplier-invoices/src/api/infrastucture/persistence/sequelize/repositories/supplier-invoice.sequelize-repository.ts @@ -311,7 +311,11 @@ export class SupplierInvoiceRepository const query = criteriaConverter.convert(criteria, { searchableFields: ["invoice_number", "description", "internal_notes"], - mappings: {}, + mappings: { + invoice_number: "SupplierInvoiceModel.invoice_number", + reference: "SupplierInvoiceModel.description", + description: "SupplierInvoiceModel.internal_notes", + }, allowedFields: ["invoice_date", "due_date", "id", "created_at"], enableFullText: true, database: this.database, diff --git a/modules/supplier/src/api/infrastructure/persistence/sequelize/models/sequelize-supplier.model.ts b/modules/supplier/src/api/infrastructure/persistence/sequelize/models/sequelize-supplier.model.ts index f1133b19..7ff7f2af 100644 --- a/modules/supplier/src/api/infrastructure/persistence/sequelize/models/sequelize-supplier.model.ts +++ b/modules/supplier/src/api/infrastructure/persistence/sequelize/models/sequelize-supplier.model.ts @@ -218,11 +218,12 @@ export default (database: Sequelize) => { { name: "idx_tin", fields: ["tin"] }, // <- para servicios externos { name: "idx_company_idx", fields: ["id", "company_id"], unique: true }, // <- para consulta get - { + // Para búsquedas simples => se hace con el "CriteriaToSequelizeConverter" + /*{ name: "ft_supplier", type: "FULLTEXT", fields: ["name", "trade_name", "reference", "tin", "email_primary", "mobile_primary"], - }, + },*/ ], whereMergeStrategy: "and", // <- cómo tratar el merge de un scope diff --git a/packages/rdx-criteria/src/criteria-to-sequelize-converter.ts b/packages/rdx-criteria/src/criteria-to-sequelize-converter.ts index f5d09496..ab0ee854 100644 --- a/packages/rdx-criteria/src/criteria-to-sequelize-converter.ts +++ b/packages/rdx-criteria/src/criteria-to-sequelize-converter.ts @@ -61,12 +61,15 @@ export class CriteriaToSequelizeConverter implements ICriteriaToOrmConverter { searchableFields = [], database, enableFullText = false, - } = params as ConvertParams & { database?: Sequelize }; + fullTextTableAlias, + } = params as ConvertParams & { + database?: Sequelize; + fullTextTableAlias?: string; + }; const term = typeof criteria.quickSearch === "string" ? criteria.quickSearch.trim() : ""; if (!term || searchableFields.length === 0 || !enableFullText) return; - // Validación defensiva if (!database) { const msg = `[CriteriaToSequelizeConverter] enableFullText=true pero falta 'database' en params.`; if (params.strictMode) throw new Error(msg); @@ -80,13 +83,19 @@ export class CriteriaToSequelizeConverter implements ICriteriaToOrmConverter { .join(" "); const mappedFields = searchableFields.map((f) => mappings[f] || f); - const matchExpr = `MATCH(${mappedFields.join(", ")}) AGAINST (${database.escape( + + const qualifiedFields = mappedFields.map((field) => + this.qualifyField(field, fullTextTableAlias) + ); + + const matchExpr = `MATCH(${qualifiedFields.join(", ")}) AGAINST (${database.escape( booleanTerm )} IN BOOLEAN MODE)`; + const matchLiteral = Sequelize.literal(matchExpr); - // Añadir campo virtual "score" if (!options.attributes) options.attributes = { include: [] }; + if (Array.isArray(options.attributes)) { options.attributes.push([matchLiteral, "score"]); } else { @@ -94,13 +103,12 @@ export class CriteriaToSequelizeConverter implements ICriteriaToOrmConverter { options.attributes.include.push([matchLiteral, "score"]); } - // WHERE score > 0 const scoreCondition = Sequelize.where(matchLiteral, { [Op.gt]: 0 }); + options.where = options.where ? { [Op.and]: [options.where, scoreCondition] } : { [Op.and]: [scoreCondition] }; - // Ordenar por relevancia prependOrder(options, [Sequelize.literal("score"), "DESC"]); } @@ -167,4 +175,11 @@ export class CriteriaToSequelizeConverter implements ICriteriaToOrmConverter { if (operator === Op.like || operator === Op.notLike) return `%${value}%`; return value; } + + private qualifyField(field: string, tableAlias?: string): string { + if (field.includes(".") || field.includes("`")) return field; + if (!tableAlias) return field; + + return `\`${tableAlias}\`.\`${field}\``; + } } diff --git a/packages/rdx-ui/src/components/form/form-section-card.tsx b/packages/rdx-ui/src/components/form/form-section-card.tsx index 79c05177..0b9f4614 100644 --- a/packages/rdx-ui/src/components/form/form-section-card.tsx +++ b/packages/rdx-ui/src/components/form/form-section-card.tsx @@ -40,7 +40,7 @@ export const FormSectionCard = ({ {icon ? (
@@ -50,8 +50,10 @@ export const FormSectionCard = ({ ) : null}
- {title ? {title} : null} - {description ? {description} : null} + {title ? {title} : null} + {description ? ( + {description} + ) : null}
diff --git a/packages/rdx-ui/src/components/form/select-field.tsx b/packages/rdx-ui/src/components/form/select-field.tsx index 1f51d739..3caf4485 100644 --- a/packages/rdx-ui/src/components/form/select-field.tsx +++ b/packages/rdx-ui/src/components/form/select-field.tsx @@ -6,7 +6,6 @@ import { SelectContent, SelectItem, SelectTrigger, - SelectValue, } from "@repo/shadcn-ui/components"; import { cn } from "@repo/shadcn-ui/lib/utils"; import React from "react"; @@ -19,6 +18,8 @@ interface SelectFieldItem { label: string; } +type SelectFieldValue = string | null; + type SelectFieldProps = { name: FieldPath; @@ -35,6 +36,9 @@ type SelectFieldProps = { orientation?: "vertical" | "horizontal" | "responsive"; + serialize?: (value: unknown) => string; + deserialize?: (value: SelectFieldValue) => unknown; + className?: string; inputClassName?: string; }; @@ -55,6 +59,9 @@ export function SelectField({ orientation = "vertical", + serialize, + deserialize, + className, inputClassName, }: SelectFieldProps) { @@ -62,18 +69,25 @@ export function SelectField({ const { control, formState } = useFormContext(); const isDisabled = Boolean(disabled || readOnly || formState.isSubmitting); + const serializeValue = + serialize ?? ((value: unknown): string => (typeof value === "string" ? value : "")); + + const deserializeValue = deserialize ?? ((value: SelectFieldValue): string => value ?? ""); + return ( { - const fieldValue = typeof field.value === "string" ? field.value.trim() : ""; + const fieldValue = serializeValue(field.value); const normalizedItems = fieldValue && !items.some((item) => item.value === fieldValue) ? [{ value: fieldValue, label: fieldValue }, ...items] : items; + const selectedItem = normalizedItems.find((item) => item.value === fieldValue); + return ( ({