diff --git a/.vscode/settings.json b/.vscode/settings.json index 735080f1..5761145c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,7 +33,7 @@ "editor.defaultFormatter": "biomejs.biome" }, "[json]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "vscode.json-language-features" }, "[jsonc]": { "editor.defaultFormatter": "biomejs.biome" diff --git a/biome.json b/biome.json index 548a61c5..5e649d87 100644 --- a/biome.json +++ b/biome.json @@ -173,7 +173,7 @@ "noThisInStatic": "error", "noUselessCatch": "error", "noUselessConstructor": "error", - "noUselessFragments": "error", + "noUselessFragments": "off", "noUselessLabel": "error", "noUselessRename": "error", "noUselessSwitchCase": "error", @@ -184,7 +184,7 @@ "useLiteralKeys": "error", "useOptionalChain": "error", "useSimpleNumberKeys": "error", - "useSimplifiedLogicExpression": "error" + "useSimplifiedLogicExpression": "info" }, "security": { "noDangerouslySetInnerHtml": "error", diff --git a/modules/auth/src/api/lib/express/mock-user.middleware.ts b/modules/auth/src/api/lib/express/mock-user.middleware.ts index 00a81f21..0a029634 100644 --- a/modules/auth/src/api/lib/express/mock-user.middleware.ts +++ b/modules/auth/src/api/lib/express/mock-user.middleware.ts @@ -7,7 +7,7 @@ export function mockUser(req: RequestWithAuth, _res: Response, next: NextFunctio req.user = { userId: UniqueID.create("9e4dc5b3-96b9-4968-9490-14bd032fec5f").data, email: EmailAddress.create("dev@example.com").data, - companyId: UniqueID.create("5e4dc5b3-96b9-4968-9490-14bd032fec5f").data, + companyId: UniqueID.create("019a9667-6a65-767a-a737-48234ee50a3a").data, companySlug: "acana", roles: ["admin"], }; diff --git a/modules/core/src/common/helpers/money-dto-helper.ts b/modules/core/src/common/helpers/money-dto-helper.ts index 9c01f815..040c03bb 100644 --- a/modules/core/src/common/helpers/money-dto-helper.ts +++ b/modules/core/src/common/helpers/money-dto-helper.ts @@ -58,6 +58,7 @@ const format = ( // Respetar fracciones si no vienen dadas en options. const nfOptions: Intl.NumberFormatOptions = { style: "currency", + useGrouping: true, currency: normalizedDTO.currency_code, minimumFractionDigits: options?.minimumFractionDigits ?? scale, maximumFractionDigits: options?.maximumFractionDigits ?? scale, diff --git a/modules/core/src/common/helpers/percentage-dto-helpers.ts b/modules/core/src/common/helpers/percentage-dto-helpers.ts index 37c5e8b4..2df94612 100644 --- a/modules/core/src/common/helpers/percentage-dto-helpers.ts +++ b/modules/core/src/common/helpers/percentage-dto-helpers.ts @@ -41,7 +41,7 @@ const toNumericString = (dto?: PercentageDTO | null, fallbackScale = 2): string const format = ( dto: PercentageDTO, locale?: string, - options?: Intl.NumberFormatOptions, + options?: { hideZeros?: boolean } & Intl.NumberFormatOptions, fallbackScale = 2 ): string => { if (isEmptyPercentageDTO(dto)) { @@ -59,6 +59,9 @@ const format = ( }; const absolute = toNumber(dto, fallbackScale); // ej. 12.5 + + if (absolute === 0 && options?.hideZeros) return ""; + const fraction = absolute / 100; // ej. 0.125 para Intl percent return new Intl.NumberFormat(locale, nfOptions).format(fraction); 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 cbb1ce2a..980209e4 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 @@ -1,5 +1,7 @@ -import { AxiosInstance } from "axios"; -import { ICustomParams, IDataSource } from "../datasource.interface"; +import type { AxiosInstance } from "axios"; + +import type { ICustomParams, IDataSource } from "../datasource.interface"; + import { defaultAxiosRequestConfig } from "./create-axios-instance"; /** diff --git a/modules/customer-invoices/package.json b/modules/customer-invoices/package.json index 3018217d..90628187 100644 --- a/modules/customer-invoices/package.json +++ b/modules/customer-invoices/package.json @@ -58,6 +58,7 @@ "puppeteer": "^24.30.0", "react-hook-form": "^7.58.1", "react-i18next": "^15.5.1", + "react-qr-code": "^2.0.18", "react-router-dom": "^6.26.0", "sequelize": "^6.37.5", "zod": "^4.1.11" diff --git a/modules/customer-invoices/src/api/application/presenters/reports/issued-invoices/issued-invoice.report.presenter.ts b/modules/customer-invoices/src/api/application/presenters/reports/issued-invoices/issued-invoice.report.presenter.ts index 7310af43..e6336958 100644 --- a/modules/customer-invoices/src/api/application/presenters/reports/issued-invoices/issued-invoice.report.presenter.ts +++ b/modules/customer-invoices/src/api/application/presenters/reports/issued-invoices/issued-invoice.report.presenter.ts @@ -55,7 +55,11 @@ export class IssuedInvoiceReportPresenter extends Presenter< locale, moneyOptions ), - discount_percentage: PercentageDTOHelper.format(issuedInvoiceDTO.discount_percentage, locale), + discount_percentage: PercentageDTOHelper.format( + issuedInvoiceDTO.discount_percentage, + locale, + { hideZeros: true } + ), discount_amount: MoneyDTOHelper.format( issuedInvoiceDTO.discount_amount, locale, diff --git a/modules/customer-invoices/src/api/application/use-cases/issued-invoices/report-issued-invoices/reporter/issued-invoice.report.html.ts b/modules/customer-invoices/src/api/application/use-cases/issued-invoices/report-issued-invoices/reporter/issued-invoice.report.html.ts index a4d13099..fe99ab63 100644 --- a/modules/customer-invoices/src/api/application/use-cases/issued-invoices/report-issued-invoices/reporter/issued-invoice.report.html.ts +++ b/modules/customer-invoices/src/api/application/use-cases/issued-invoices/report-issued-invoices/reporter/issued-invoice.report.html.ts @@ -23,6 +23,9 @@ export class IssuedInvoiceReportHTMLPresenter extends TemplatePresenter { const invoiceDTO = dtoPresenter.toOutput(invoice); const prettyDTO = prePresenter.toOutput(invoiceDTO); + console.log(prettyDTO.verifactu); + + // Obtener y compilar la plantilla HTML const template = this.templateResolver.compileTemplate( "customer-invoices", diff --git a/modules/customer-invoices/src/api/application/use-cases/issued-invoices/report-issued-invoices/reporter/issued-invoice.report.pdf.ts b/modules/customer-invoices/src/api/application/use-cases/issued-invoices/report-issued-invoices/reporter/issued-invoice.report.pdf.ts index 770f96d5..9ce6b19f 100644 --- a/modules/customer-invoices/src/api/application/use-cases/issued-invoices/report-issued-invoices/reporter/issued-invoice.report.pdf.ts +++ b/modules/customer-invoices/src/api/application/use-cases/issued-invoices/report-issued-invoices/reporter/issued-invoice.report.pdf.ts @@ -23,6 +23,8 @@ export class IssuedInvoiceReportPDFPresenter extends Presenter< format: "HTML", }) as IssuedInvoiceReportHTMLPresenter; + console.log(invoice); + const htmlData = htmlPresenter.toOutput(invoice, params); // Generar el PDF con Puppeteer @@ -33,8 +35,8 @@ export class IssuedInvoiceReportPDFPresenter extends Presenter< }); const page = await browser.newPage(); - //page.setDefaultNavigationTimeout(60000); - //page.setDefaultTimeout(60000); + page.setDefaultNavigationTimeout(60000); + page.setDefaultTimeout(60000); await page.setContent(htmlData, { waitUntil: "networkidle2", diff --git a/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/proforma.report.pdf.ts b/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/proforma.report.pdf.ts index 885057a1..aaef9079 100644 --- a/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/proforma.report.pdf.ts +++ b/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/proforma.report.pdf.ts @@ -33,8 +33,8 @@ export class ProformaReportPDFPresenter extends Presenter< }); const page = await browser.newPage(); - //page.setDefaultNavigationTimeout(60000); - //page.setDefaultTimeout(60000); + page.setDefaultNavigationTimeout(60000); + page.setDefaultTimeout(60000); await page.setContent(htmlData, { waitUntil: "networkidle2", diff --git a/modules/customer-invoices/src/api/domain/entities/item-taxes/item-taxes.ts b/modules/customer-invoices/src/api/domain/entities/item-taxes/item-taxes.ts index 640ed5eb..d4c3acfe 100644 --- a/modules/customer-invoices/src/api/domain/entities/item-taxes/item-taxes.ts +++ b/modules/customer-invoices/src/api/domain/entities/item-taxes/item-taxes.ts @@ -1,4 +1,5 @@ -import { Tax, Taxes } from "@erp/core/api"; +import { type Tax, Taxes } from "@erp/core/api"; + import { ItemAmount } from "../../value-objects"; export type ItemTaxTotal = { diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts index fe2413fa..71453352 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts @@ -360,7 +360,7 @@ export class CustomerInvoiceRepository model: VerifactuRecordModel, as: "verifactu", required: false, - attributes: ["id", "estado", "url", "uuid"], + attributes: ["id", "estado", "url", "uuid", "qr"], }, { model: CustomerModel, @@ -578,7 +578,7 @@ export class CustomerInvoiceRepository model: VerifactuRecordModel, as: "verifactu", required: false, - attributes: ["id", "estado", "url", "uuid"], + attributes: ["id", "estado", "url", "uuid", "qr"], }, { model: CustomerModel, diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice-item-tax.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice-item-tax.model.ts index 905fc0b3..1a5a377e 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice-item-tax.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice-item-tax.model.ts @@ -57,7 +57,9 @@ export class CustomerInvoiceItemTaxModel extends Model< }); } - static hooks(_database: Sequelize) {} + static hooks(_database: Sequelize) { + // + } } export default (database: Sequelize) => { diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice.model.ts index 41f03508..d4c465b9 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice.model.ts @@ -421,7 +421,7 @@ export default (database: Sequelize) => { { name: "idx_invoice_company_series_number", - fields: ["company_id", "series", "invoice_number"], + fields: ["company_id", "series", "invoice_number", "is_proforma"], unique: true, }, // <- para consulta get diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/models/verifactu-record.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/models/verifactu-record.model.ts index 117320c6..31faab8c 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/models/verifactu-record.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/models/verifactu-record.model.ts @@ -21,7 +21,7 @@ export class VerifactuRecordModel extends Model< declare estado: string; declare url: CreationOptional; - declare qr: CreationOptional; + declare qr: CreationOptional; declare uuid: CreationOptional; declare operacion: CreationOptional; @@ -77,7 +77,7 @@ export default (database: Sequelize) => { }, qr: { - type: new DataTypes.BLOB(), + type: new DataTypes.TEXT(), allowNull: false, defaultValue: "", }, diff --git a/modules/customer-invoices/src/common/locales/en.json b/modules/customer-invoices/src/common/locales/en.json index ed3034bc..bffa8f2d 100644 --- a/modules/customer-invoices/src/common/locales/en.json +++ b/modules/customer-invoices/src/common/locales/en.json @@ -50,6 +50,17 @@ } } }, + "proformas": { + "delete_proforma_dialog": { + "title": "Delete proforma", + "description": "Are you sure you want to delete proforma {{proformaRef}}? This action cannot be undone.", + "cancel": "Cancel", + "delete": "Delete", + "deleting": "Deleting...", + "success_title": "Proforma deleted", + "error_title": "Error deleting proforma" + } + }, "pages": { "proformas": { "title": "Proformas", diff --git a/modules/customer-invoices/src/common/locales/es.json b/modules/customer-invoices/src/common/locales/es.json index fcd0b845..359757fa 100644 --- a/modules/customer-invoices/src/common/locales/es.json +++ b/modules/customer-invoices/src/common/locales/es.json @@ -49,6 +49,17 @@ } } }, + "proformas": { + "delete_proforma_dialog": { + "title": "Eliminar proforma", + "description": "¿Seguro que deseas eliminar la proforma {{proformaRef}}? Esta acción no se puede deshacer.", + "cancel": "Cancelar", + "delete": "Eliminar", + "deleting": "Eliminando...", + "success_title": "Proforma eliminada", + "error_title": "Error al eliminar la proforma" + } + }, "pages": { "proformas": { "title": "Proformas", diff --git a/modules/customer-invoices/src/web/customer-invoice-routes.tsx b/modules/customer-invoices/src/web/customer-invoice-routes.tsx index 013d1792..956d1466 100644 --- a/modules/customer-invoices/src/web/customer-invoice-routes.tsx +++ b/modules/customer-invoices/src/web/customer-invoice-routes.tsx @@ -11,7 +11,7 @@ const IssuedInvoicesLayout = lazy(() => ); const ProformasListPage = lazy(() => - import("./proformas/pages").then((m) => ({ default: m.ProformaListPage })) + import("./proformas/list").then((m) => ({ default: m.ProformaListPage })) ); const IssuedInvoiceListPage = lazy(() => diff --git a/modules/customer-invoices/src/web/hooks/calcs/use-proforma-auto-recalc.ts b/modules/customer-invoices/src/web/hooks/calcs/use-proforma-auto-recalc.ts index 611674da..5095077e 100644 --- a/modules/customer-invoices/src/web/hooks/calcs/use-proforma-auto-recalc.ts +++ b/modules/customer-invoices/src/web/hooks/calcs/use-proforma-auto-recalc.ts @@ -7,7 +7,7 @@ import { calculateInvoiceHeaderAmounts, calculateInvoiceItemAmounts, } from "../../domain"; -import type { ProformaFormData } from "../../proformas/schema"; +import type { ProformaFormData } from "../../proformas/types"; import type { InvoiceFormData, InvoiceItemFormData } from "../../schemas"; export type UseProformaAutoRecalcParams = { diff --git a/modules/customer-invoices/src/web/hooks/use-create-customer-invoice-mutation.ts b/modules/customer-invoices/src/web/hooks/use-create-customer-invoice-mutation.ts index b7cf8444..9726919c 100644 --- a/modules/customer-invoices/src/web/hooks/use-create-customer-invoice-mutation.ts +++ b/modules/customer-invoices/src/web/hooks/use-create-customer-invoice-mutation.ts @@ -3,7 +3,7 @@ import { UniqueID, ValidationErrorCollection } from "@repo/rdx-ddd"; import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query"; import { CreateProformaRequestSchema } from "../../common"; -import type { Proforma } from "../proformas/schema/proforma.api.schema"; +import type { Proforma } from "../proformas/types/proforma.api.schema"; import type { InvoiceFormData } from "../schemas"; type CreateCustomerInvoicePayload = { diff --git a/modules/customer-invoices/src/web/i18n.ts b/modules/customer-invoices/src/web/i18n.ts index 106cc77c..f363c9cd 100644 --- a/modules/customer-invoices/src/web/i18n.ts +++ b/modules/customer-invoices/src/web/i18n.ts @@ -1,7 +1,12 @@ -import { KeyPrefix, Namespace, i18n } from "i18next"; -import { UseTranslationResponse, useTranslation as useI18NextTranslation } from "react-i18next"; +import type { KeyPrefix, Namespace, i18n } from "i18next"; +import { + type UseTranslationResponse, + useTranslation as useI18NextTranslation, +} from "react-i18next"; + import enResources from "../common/locales/en.json"; import esResources from "../common/locales/es.json"; + import { MODULE_NAME } from "./manifest"; const addMissingBundles = (i18n: i18n) => { diff --git a/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/use-issued-invoices-grid-columns.tsx b/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/use-issued-invoices-grid-columns.tsx index 29f2e881..72c54af4 100644 --- a/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/use-issued-invoices-grid-columns.tsx +++ b/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/use-issued-invoices-grid-columns.tsx @@ -12,8 +12,9 @@ import { TooltipTrigger, } from "@repo/shadcn-ui/components"; import type { ColumnDef } from "@tanstack/react-table"; -import { DownloadIcon, MailIcon, MoreVerticalIcon } from "lucide-react"; +import { DownloadIcon, MailIcon, MoreVerticalIcon, QrCodeIcon } from "lucide-react"; import * as React from "react"; +import QRCode from "react-qr-code"; import { useTranslation } from "../../../../i18n"; import type { IssuedInvoiceSummaryData } from "../../../schema"; @@ -84,38 +85,39 @@ export function useIssuedInvoicesGridColumns( /> ), accessorFn: (row) => row.verifactu.qr_code, // para ordenar/buscar por nombre - cell: ({ row }) => ( -
{row.original.verifactu.qr_code}
- ), + cell: ({ row }) => { + const { verifactu } = row.original; + const isPending = verifactu.status === "Pendiente"; + console.log(verifactu.status); + return ( + <> + {isPending ? ( + + ) : ( + + + + + + + + + + + )} + + ); + }, + enableHiding: false, enableSorting: false, - size: 140, - minSize: 120, + maxSize: 64, + size: 64, + minSize: 64, meta: { title: t("pages.issued_invoices.list.grid_columns.verifactu_qr_code"), }, }, - { - id: "verifactu_url", - header: ({ column }) => ( - - ), - accessorFn: (row) => row.verifactu.url, // para ordenar/buscar por nombre - cell: ({ row }) => ( -
{row.original.verifactu.url}
- ), - enableHiding: false, - enableSorting: false, - size: 140, - minSize: 120, - meta: { - title: t("pages.issued_invoices.list.grid_columns.verifactu_url"), - }, - }, { id: "recipient", header: ({ column }) => ( diff --git a/modules/customer-invoices/src/web/proformas/adapters/proforma-dto.adapter.ts b/modules/customer-invoices/src/web/proformas/adapters/proforma-dto.adapter.ts index 6c7ec348..c707d2e3 100644 --- a/modules/customer-invoices/src/web/proformas/adapters/proforma-dto.adapter.ts +++ b/modules/customer-invoices/src/web/proformas/adapters/proforma-dto.adapter.ts @@ -5,7 +5,7 @@ import { type TaxCatalogProvider, } from "@erp/core"; -import type { Proforma, ProformaFormData, UpdateProformaInput } from "../schema"; +import type { Proforma, ProformaFormData, UpdateProformaInput } from "../types"; export type ProformaDtoAdapterContext = { taxCatalog: TaxCatalogProvider; diff --git a/modules/customer-invoices/src/web/proformas/adapters/proforma-summary-dto.adapter.ts b/modules/customer-invoices/src/web/proformas/adapters/proforma-summary-dto.adapter.ts index f34e10b7..8448d203 100644 --- a/modules/customer-invoices/src/web/proformas/adapters/proforma-summary-dto.adapter.ts +++ b/modules/customer-invoices/src/web/proformas/adapters/proforma-summary-dto.adapter.ts @@ -1,10 +1,10 @@ import { MoneyDTOHelper, PercentageDTOHelper, formatCurrency } from "@erp/core"; -import type { ProformaSummaryPage } from "../schema/proforma.api.schema"; +import type { ProformaSummaryPage } from "../types/proforma.api.schema"; import type { ProformaSummaryData, ProformaSummaryPageData, -} from "../schema/proforma-summary.web.schema"; +} from "../types/proforma-summary.web.schema"; /** * Convierte el DTO completo de API a datos numéricos para el formulario. diff --git a/modules/customer-invoices/src/web/proformas/change-status/api/change-proforma-status.api.ts b/modules/customer-invoices/src/web/proformas/change-status/api/change-proforma-status.api.ts new file mode 100644 index 00000000..83215124 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/change-status/api/change-proforma-status.api.ts @@ -0,0 +1,17 @@ +import type { IDataSource } from "@erp/core/client"; + +export interface ChangeStatusResponse { + success: boolean; +} + +export async function changeProformaStatusApi( + dataSource: IDataSource, + proformaId: string, + newStatus: string +): Promise { + return dataSource.custom({ + path: `proformas/${proformaId}/status`, + method: "patch", + data: { new_status: newStatus }, + }); +} diff --git a/modules/customer-invoices/src/web/proformas/change-status/api/index.ts b/modules/customer-invoices/src/web/proformas/change-status/api/index.ts new file mode 100644 index 00000000..0d8419a6 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/change-status/api/index.ts @@ -0,0 +1 @@ +export * from "./change-proforma-status.api"; diff --git a/modules/customer-invoices/src/web/proformas/change-status/controllers/index.ts b/modules/customer-invoices/src/web/proformas/change-status/controllers/index.ts new file mode 100644 index 00000000..44081230 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/change-status/controllers/index.ts @@ -0,0 +1 @@ +export * from "./use-change-status-dialog-controller"; diff --git a/modules/customer-invoices/src/web/proformas/change-status/controllers/use-change-status-dialog-controller.ts b/modules/customer-invoices/src/web/proformas/change-status/controllers/use-change-status-dialog-controller.ts new file mode 100644 index 00000000..af447e66 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/change-status/controllers/use-change-status-dialog-controller.ts @@ -0,0 +1,75 @@ +import { showErrorToast } from "@repo/rdx-ui/helpers"; +import * as React from "react"; + +import type { ProformaSummaryData } from "../../types"; +import { useChangeProformaStatus } from "../hooks/use-change-proforma-status"; + +type ProformasType = { + id: string; + status: string; +}[]; + +interface ChangeStatusDialogState { + open: boolean; + targetStatus: string | null; + proformas: ProformaSummaryData[]; + loading: boolean; +} + +export function useChangeStatusDialogController() { + const { changeStatus } = useChangeProformaStatus(); + + const [state, setState] = React.useState({ + open: false, + targetStatus: null, + proformas: [], + loading: false, + }); + + const openDialog = (proformas: ProformaSummaryData[]) => { + setState({ + open: true, + targetStatus: null, + proformas, + loading: false, + }); + }; + + const closeDialog = () => { + setState((s) => ({ ...s, open: false })); + }; + + const setTargetStatus = (status: string | null) => { + setState((s) => ({ ...s, targetStatus: status })); + }; + + const confirmChangeStatus = async () => { + if (!state.targetStatus || state.proformas.length === 0) return; + + setState((s) => ({ ...s, loading: true })); + + for (const proforma of state.proformas) { + await changeStatus(proforma.id.toString(), state.targetStatus, { + onError: (err: unknown) => { + const error = err as Error; + showErrorToast("Error cambiando estado", error.message); + }, + }); + } + + setState((s) => ({ ...s, loading: false })); + closeDialog(); + }; + + return { + open: state.open, + proformas: state.proformas, + targetStatus: state.targetStatus, + isSubmitting: state.loading, + + openDialog, + closeDialog, + setTargetStatus, + confirmChangeStatus, + }; +} diff --git a/modules/customer-invoices/src/web/proformas/change-status/hooks/index.ts b/modules/customer-invoices/src/web/proformas/change-status/hooks/index.ts new file mode 100644 index 00000000..3e7d18eb --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/change-status/hooks/index.ts @@ -0,0 +1 @@ +export * from "./use-change-proforma-status"; diff --git a/modules/customer-invoices/src/web/proformas/change-status/hooks/use-change-proforma-status.ts b/modules/customer-invoices/src/web/proformas/change-status/hooks/use-change-proforma-status.ts new file mode 100644 index 00000000..5e81d971 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/change-status/hooks/use-change-proforma-status.ts @@ -0,0 +1,51 @@ +import { useDataSource } from "@erp/core/hooks"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import type { PROFORMA_STATUS } from "../../types"; +import { changeProformaStatusApi } from "../api/change-proforma-status.api"; + +interface ChangeProformaStatusOptions { + onSuccess?: () => void; + onError?: (err: unknown) => void; + onLoadingChange?: (loading: boolean) => void; +} + +interface ChangeProformaStatusPayload { + proformaId: string; + newStatus: PROFORMA_STATUS; +} + +export function useChangeProformaStatus() { + const dataSource = useDataSource(); + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: ({ proformaId, newStatus }: ChangeProformaStatusPayload) => + changeProformaStatusApi(dataSource, proformaId, newStatus), + + onSuccess() { + queryClient.invalidateQueries({ queryKey: ["proformas"] }); + }, + }); + + async function changeStatus( + proformaId: string, + newStatus: string, + opts?: ChangeProformaStatusOptions + ) { + try { + opts?.onLoadingChange?.(true); + await mutation.mutateAsync({ proformaId, newStatus: newStatus as PROFORMA_STATUS }); + opts?.onSuccess?.(); + } catch (err) { + opts?.onError?.(err); + } finally { + opts?.onLoadingChange?.(false); + } + } + + return { + changeStatus, + isPending: mutation.isPending, + }; +} diff --git a/modules/customer-invoices/src/web/proformas/change-status/index.ts b/modules/customer-invoices/src/web/proformas/change-status/index.ts new file mode 100644 index 00000000..0d88782e --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/change-status/index.ts @@ -0,0 +1,2 @@ +export * from "./controllers"; +export * from "./ui"; diff --git a/modules/customer-invoices/src/web/proformas/change-status/ui/change-status-dialog.tsx b/modules/customer-invoices/src/web/proformas/change-status/ui/change-status-dialog.tsx new file mode 100644 index 00000000..11787a82 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/change-status/ui/change-status-dialog.tsx @@ -0,0 +1,300 @@ +import { + Button, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + Spinner, +} from "@repo/shadcn-ui/components"; +import { cn } from "@repo/shadcn-ui/lib/utils"; +import { + CheckCircle2Icon, + ChevronRightIcon, + FileCheckIcon, + FileTextIcon, + SendIcon, + XCircleIcon, +} from "lucide-react"; +import { useState } from "react"; + +import { useTranslation } from "../../../i18n"; +import { PROFORMA_STATUS, PROFORMA_STATUS_TRANSITIONS } from "../../types"; + +interface ChangeStatusDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + proformas: { id: string; status: string }[]; + targetStatus?: string; + isSubmitting: boolean; + onConfirm: () => void; +} + +const STATUS_FLOW = [ + { + status: PROFORMA_STATUS.DRAFT, + icon: FileTextIcon, + color: "text-gray-500", + bgColor: "bg-gray-100", + borderColor: "border-gray-300", + }, + { + status: PROFORMA_STATUS.SENT, + icon: SendIcon, + color: "text-blue-600", + bgColor: "bg-blue-100", + borderColor: "border-blue-300", + }, + { + status: PROFORMA_STATUS.APPROVED, + icon: CheckCircle2Icon, + color: "text-green-600", + bgColor: "bg-green-100", + borderColor: "border-green-300", + }, + { + status: PROFORMA_STATUS.REJECTED, + icon: XCircleIcon, + color: "text-red-600", + bgColor: "bg-red-100", + borderColor: "border-red-300", + }, + { + status: PROFORMA_STATUS.ISSUED, + icon: FileCheckIcon, + color: "text-purple-600", + bgColor: "bg-purple-100", + borderColor: "border-purple-300", + }, +] as const; + +export function ChangeStatusDialog({ + open, + onOpenChange, + proformas, + isSubmitting, + onConfirm, +}: ChangeStatusDialogProps) { + const { t } = useTranslation(); + const [selectedStatus, setSelectedStatus] = useState(null); + + // Obtener estados disponibles comunes para todas las proformas seleccionadas + const getAvailableStatuses = () => { + if (!proformas || proformas.length === 0) return []; + + // Intersección de estados disponibles para todas las proformas + const firstProforma = proformas[0]; + let availableStatuses = + PROFORMA_STATUS_TRANSITIONS[firstProforma.status as PROFORMA_STATUS] || []; + + for (let i = 1; i < proformas.length; i++) { + const currentStatuses = + PROFORMA_STATUS_TRANSITIONS[proformas[i].status as PROFORMA_STATUS] || []; + availableStatuses = availableStatuses.filter((status) => currentStatuses.includes(status)); + } + + return availableStatuses; + }; + + const availableStatuses = getAvailableStatuses(); + + const currentStatus = proformas.length === 1 ? proformas[0].status : null; + + const getStatusType = ( + status: PROFORMA_STATUS + ): "past" | "current" | "available" | "unavailable" => { + if (currentStatus === status) return "current"; + + // Si no hay un estado actual único, solo mostrar disponibles + if (!currentStatus) { + return availableStatuses.includes(status) ? "available" : "unavailable"; + } + + // Determinar si es pasado basado en el flujo lógico + const currentIndex = STATUS_FLOW.findIndex((s) => s.status === currentStatus); + const statusIndex = STATUS_FLOW.findIndex((s) => s.status === status); + + if (statusIndex < currentIndex) return "past"; + if (availableStatuses.includes(status)) return "available"; + return "unavailable"; + }; + + return ( + + + + Cambiar estado de la proforma + + {proformas.length === 1 + ? `Selecciona el nuevo estado para la proforma #${proformas[0].id}` + : `Selecciona el nuevo estado para ${proformas.length} proformas seleccionadas`} + + + + {availableStatuses.length === 0 ? ( +
+ No hay transiciones de estado disponibles para las proformas seleccionadas. +
+ ) : ( +
+
+ {/* Línea de conexión */} +
+ + {/* Estados */} +
+ {STATUS_FLOW.map((statusConfig, index) => { + const statusType = getStatusType(statusConfig.status); + const Icon = statusConfig.icon; + const isSelected = selectedStatus === statusConfig.status; + const isClickable = statusType === "available"; + + return ( +
+ {/* Botón de estado */} + + + {/* Etiqueta y descripción */} +
+

+ {t(`catalog.proformas.status.${statusConfig.status}`)} +

+

+ {t(`catalog.proformas.status.${statusConfig.status}.description`)} +

+
+ + {/* Flecha de conexión (excepto el último) */} + {index < STATUS_FLOW.length - 1 && ( + + )} +
+ ); + })} +
+
+ +
+
+ {currentStatus && ( +
+
+ Estado actual +
+ )} +
+
+ Estados disponibles +
+
+
+ Estados no disponibles +
+
+
+
+ )} + + + + + + +
+ ); +} diff --git a/modules/customer-invoices/src/web/proformas/change-status/ui/index.ts b/modules/customer-invoices/src/web/proformas/change-status/ui/index.ts new file mode 100644 index 00000000..4d158d4c --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/change-status/ui/index.ts @@ -0,0 +1 @@ +export * from "./change-status-dialog"; diff --git a/modules/customer-invoices/src/web/proformas/delete/api/delete-proforma.api.ts b/modules/customer-invoices/src/web/proformas/delete/api/delete-proforma.api.ts new file mode 100644 index 00000000..fb84b3ca --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/api/delete-proforma.api.ts @@ -0,0 +1,8 @@ +import type { IDataSource } from "@erp/core/client"; + +export async function deleteProformaApi( + dataSource: IDataSource, + proformaId: string +): Promise { + await dataSource.deleteOne("proformas", proformaId); +} diff --git a/modules/customer-invoices/src/web/proformas/delete/api/index.ts b/modules/customer-invoices/src/web/proformas/delete/api/index.ts new file mode 100644 index 00000000..63e61c01 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/api/index.ts @@ -0,0 +1 @@ +export * from "./delete-proforma.api"; diff --git a/modules/customer-invoices/src/web/proformas/delete/controllers/index.ts b/modules/customer-invoices/src/web/proformas/delete/controllers/index.ts new file mode 100644 index 00000000..4d30293b --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/controllers/index.ts @@ -0,0 +1 @@ +export * from "./use-delete-proforma-dialog-controller"; diff --git a/modules/customer-invoices/src/web/proformas/delete/controllers/use-delete-proforma-dialog-controller.ts b/modules/customer-invoices/src/web/proformas/delete/controllers/use-delete-proforma-dialog-controller.ts new file mode 100644 index 00000000..ca4f3cc6 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/controllers/use-delete-proforma-dialog-controller.ts @@ -0,0 +1,59 @@ +import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers"; +import React from "react"; + +import { useTranslation } from "../../../i18n"; +import type { ProformaSummaryData } from "../../types"; +import { useDeleteProforma } from "../hooks"; + +interface ProformaState { + open: boolean; + proforma: ProformaSummaryData | null; +} + +export function useDeleteProformaDialogController() { + const { t } = useTranslation(); + const { mutate, isPending } = useDeleteProforma(); + + const [state, setState] = React.useState({ + open: false, + proforma: null, + }); + + const openDialog = (proforma: ProformaSummaryData) => { + setState({ open: true, proforma }); + }; + + const closeDialog = () => { + setState((s) => ({ ...s, open: false })); + }; + + const confirmDelete = () => { + if (!state.proforma) return; + + mutate( + { proformaId: state.proforma.id }, + { + onSuccess(data, variables, onMutateResult, context) { + console.log("adios"); + console.log(data); + showSuccessToast(t("proformas.delete_proforma_dialog.success_title")); + closeDialog(); + }, + onError(error, variables, onMutateResult, context) { + showErrorToast(t("proformas.delete_proforma_dialog.error_title"), error.message); + }, + } + ); + }; + + return { + open: state.open, + proforma: state.proforma, + isSubmitting: isPending, + + openDialog, + closeDialog, + + confirmDelete, + }; +} diff --git a/modules/customer-invoices/src/web/proformas/delete/hooks/index.ts b/modules/customer-invoices/src/web/proformas/delete/hooks/index.ts new file mode 100644 index 00000000..feacd21f --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/hooks/index.ts @@ -0,0 +1 @@ +export * from "./use-delete-proforma"; diff --git a/modules/customer-invoices/src/web/proformas/delete/hooks/use-delete-proforma.ts b/modules/customer-invoices/src/web/proformas/delete/hooks/use-delete-proforma.ts new file mode 100644 index 00000000..39efd1d5 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/hooks/use-delete-proforma.ts @@ -0,0 +1,23 @@ +import { useDataSource } from "@erp/core/hooks"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { deleteProformaApi } from "../api"; + +interface useDeleteProformaPayload { + proformaId: string; +} + +export function useDeleteProforma() { + const dataSource = useDataSource(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ proformaId }: useDeleteProformaPayload) => + deleteProformaApi(dataSource, proformaId), + + onSuccess() { + console.log("hola"); + queryClient.invalidateQueries({ queryKey: ["proformas"] }); + }, + }); +} diff --git a/modules/customer-invoices/src/web/proformas/delete/index.ts b/modules/customer-invoices/src/web/proformas/delete/index.ts new file mode 100644 index 00000000..6b67c80e --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/index.ts @@ -0,0 +1 @@ +export * from "./controllers"; diff --git a/modules/customer-invoices/src/web/proformas/delete/ui/delete-proforma-dialog.tsx b/modules/customer-invoices/src/web/proformas/delete/ui/delete-proforma-dialog.tsx new file mode 100644 index 00000000..03ec1918 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/ui/delete-proforma-dialog.tsx @@ -0,0 +1,63 @@ +import { + AlertDialog, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + Button, + Spinner, +} from "@repo/shadcn-ui/components"; +import { Trans } from "react-i18next"; + +import { useTranslation } from "../../../i18n"; + +interface DeleteProformaDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + proformaRef?: string; + isSubmitting: boolean; + onConfirm: () => void; +} + +export function DeleteProformaDialog({ + open, + onOpenChange, + proformaRef, + isSubmitting, + onConfirm, +}: DeleteProformaDialogProps) { + const { t } = useTranslation(); + return ( + + + + {t("proformas.delete_proforma_dialog.title")} + + + + + + + + + + + + + ); +} diff --git a/modules/customer-invoices/src/web/proformas/delete/ui/index.ts b/modules/customer-invoices/src/web/proformas/delete/ui/index.ts new file mode 100644 index 00000000..2b81d3b9 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/ui/index.ts @@ -0,0 +1 @@ +export * from "./delete-proforma-dialog"; diff --git a/modules/customer-invoices/src/web/proformas/hooks/index.ts b/modules/customer-invoices/src/web/proformas/hooks/index.ts index e48fb5de..635178f6 100644 --- a/modules/customer-invoices/src/web/proformas/hooks/index.ts +++ b/modules/customer-invoices/src/web/proformas/hooks/index.ts @@ -1,4 +1,4 @@ -export * from "./use-proforma-items-columns"; +export * from "./use-issue-proforma-invoice"; export * from "./use-proforma-query"; export * from "./use-proforma-update-mutation"; export * from "./use-proformas-query"; diff --git a/modules/customer-invoices/src/web/proformas/hooks/use-delete-proforma-mutation.ts b/modules/customer-invoices/src/web/proformas/hooks/use-delete-proforma-mutation.ts index bf6910b7..63a07c14 100644 --- a/modules/customer-invoices/src/web/proformas/hooks/use-delete-proforma-mutation.ts +++ b/modules/customer-invoices/src/web/proformas/hooks/use-delete-proforma-mutation.ts @@ -1,4 +1,10 @@ -import { useDataSource } from "@erp/core/hooks"; +11111import +{ + useDataSource; +} +from; +("@erp/core/hooks"); + import { useMutation, useQueryClient } from "@tanstack/react-query"; import type { InvoiceFormData } from "../schemas"; diff --git a/modules/customer-invoices/src/web/proformas/hooks/use-issue-proforma-invoice.ts b/modules/customer-invoices/src/web/proformas/hooks/use-issue-proforma-invoice.ts new file mode 100644 index 00000000..39d7c10b --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/hooks/use-issue-proforma-invoice.ts @@ -0,0 +1,26 @@ +// hooks/use-issue-proforma-invoice.ts + +import { useDataSource } from "@erp/core/hooks"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +export const ISSUE_PROFORMA_INVOICE_KEY = ["proformas", "issue"] as const; + +interface IssueProformaInvoicePayload { + proformaId: string; +} + +export function useIssueProformaInvoice() { + const dataSource = useDataSource(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: ISSUE_PROFORMA_INVOICE_KEY, + mutationFn: ({ proformaId }: IssueProformaInvoicePayload) => + issueProformaInvoiceApi(dataSource, proformaId), + + onSuccess() { + queryClient.invalidateQueries({ queryKey: ["proformas"] }); + queryClient.invalidateQueries({ queryKey: ["invoices"] }); + }, + }); +} diff --git a/modules/customer-invoices/src/web/proformas/hooks/use-proforma-query.ts b/modules/customer-invoices/src/web/proformas/hooks/use-proforma-query.ts index 35f68a6b..33f732fc 100644 --- a/modules/customer-invoices/src/web/proformas/hooks/use-proforma-query.ts +++ b/modules/customer-invoices/src/web/proformas/hooks/use-proforma-query.ts @@ -1,7 +1,7 @@ import { useDataSource } from "@erp/core/hooks"; import { type DefaultError, type QueryKey, useQuery } from "@tanstack/react-query"; -import type { Proforma } from "../schema/proforma.api.schema"; +import type { Proforma } from "../types/proforma.api.schema"; export const PROFORMA_QUERY_KEY = (id: string): QueryKey => ["proforma", id] as const; diff --git a/modules/customer-invoices/src/web/proformas/hooks/use-proformas-query.ts b/modules/customer-invoices/src/web/proformas/hooks/use-proformas-query.ts index 8a526b75..9fcc3516 100644 --- a/modules/customer-invoices/src/web/proformas/hooks/use-proformas-query.ts +++ b/modules/customer-invoices/src/web/proformas/hooks/use-proformas-query.ts @@ -3,7 +3,7 @@ import { useDataSource } from "@erp/core/hooks"; import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@repo/rdx-criteria"; import { type DefaultError, type QueryKey, useQuery } from "@tanstack/react-query"; -import type { ProformaSummaryPage } from "../schema/proforma.api.schema"; +import type { ProformaSummaryPage } from "../types/proforma.api.schema"; export const PROFORMAS_QUERY_KEY = (criteria?: CriteriaDTO): QueryKey => [ "proforma", diff --git a/modules/customer-invoices/src/web/proformas/index.ts b/modules/customer-invoices/src/web/proformas/index.ts index c4e34b27..491ccf0c 100644 --- a/modules/customer-invoices/src/web/proformas/index.ts +++ b/modules/customer-invoices/src/web/proformas/index.ts @@ -1 +1 @@ -export * from "./pages"; +export * from "./list"; diff --git a/modules/customer-invoices/src/web/proformas/issue-proforma/api/index.ts b/modules/customer-invoices/src/web/proformas/issue-proforma/api/index.ts new file mode 100644 index 00000000..b69fff16 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/issue-proforma/api/index.ts @@ -0,0 +1 @@ +export * from "./issue-proforma-invoice.api"; diff --git a/modules/customer-invoices/src/web/proformas/issue-proforma/api/issue-proforma-invoice.api.ts b/modules/customer-invoices/src/web/proformas/issue-proforma/api/issue-proforma-invoice.api.ts new file mode 100644 index 00000000..2cfb4008 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/issue-proforma/api/issue-proforma-invoice.api.ts @@ -0,0 +1,18 @@ +import type { IDataSource } from "@erp/core/client"; + +export interface IssueInvoiceResponse { + invoiceId: string; + proformaId: string; + customerId: string; +} + +export async function issueProformaInvoiceApi( + dataSource: IDataSource, + proformaId: string +): Promise { + return dataSource.custom({ + path: `proformas/${proformaId}/issue`, + method: "put", + data: {}, + }); +} diff --git a/modules/customer-invoices/src/web/proformas/issue-proforma/controllers/index.ts b/modules/customer-invoices/src/web/proformas/issue-proforma/controllers/index.ts new file mode 100644 index 00000000..3008ea5d --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/issue-proforma/controllers/index.ts @@ -0,0 +1 @@ +export * from "./use-issue-proforma-dialog.controller"; diff --git a/modules/customer-invoices/src/web/proformas/issue-proforma/controllers/use-issue-proforma-dialog.controller.ts b/modules/customer-invoices/src/web/proformas/issue-proforma/controllers/use-issue-proforma-dialog.controller.ts new file mode 100644 index 00000000..a4e53255 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/issue-proforma/controllers/use-issue-proforma-dialog.controller.ts @@ -0,0 +1,52 @@ +import * as React from "react"; + +import type { ProformaSummaryData } from "../../types"; +import { useIssueProformaMutation } from "../hooks/use-issue-proforma-mutation"; + +interface State { + open: boolean; + proforma: ProformaSummaryData | null; +} + +export function useProformaIssueDialogController() { + const { mutate, isPending } = useIssueProformaMutation(); + + const [state, setState] = React.useState({ + open: false, + proforma: null, + }); + + // abrir diálogo + const openDialog = (p: ProformaSummaryData) => { + setState({ open: true, proforma: p }); + }; + + // cerrar diálogo + const closeDialog = () => { + setState((s) => ({ ...s, open: false })); + }; + + // confirmar emisión + const confirmIssue = () => { + if (!state.proforma) return; + + mutate( + { proformaId: state.proforma.id }, + { + onSuccess() { + closeDialog(); + }, + } + ); + }; + + return { + open: state.open, + proforma: state.proforma, + isSubmitting: isPending, + + openDialog, + closeDialog, + confirmIssue, + }; +} diff --git a/modules/customer-invoices/src/web/proformas/issue-proforma/hooks/index.ts b/modules/customer-invoices/src/web/proformas/issue-proforma/hooks/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/modules/customer-invoices/src/web/proformas/issue-proforma/hooks/use-issue-proforma-mutation.ts b/modules/customer-invoices/src/web/proformas/issue-proforma/hooks/use-issue-proforma-mutation.ts new file mode 100644 index 00000000..a30ebc52 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/issue-proforma/hooks/use-issue-proforma-mutation.ts @@ -0,0 +1,31 @@ +import { useDataSource } from "@erp/core/hooks"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { issueProformaInvoiceApi } from "../api"; + +interface IssueProformaPayload { + proformaId: string; +} + +interface IssueProformaResponse { + proformaId: string; + success: boolean; +} + +export function useIssueProformaMutation() { + const dataSource = useDataSource(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ proformaId }: IssueProformaPayload) => + issueProformaInvoiceApi(dataSource, proformaId), + + onSuccess(_data, _vars, _ctx) { + // Refresca el listado de proformas + queryClient.invalidateQueries({ queryKey: ["proformas"] }); + + // Opcional: refrescar facturas si existe la feature + queryClient.invalidateQueries({ queryKey: ["invoices"] }); + }, + }); +} diff --git a/modules/customer-invoices/src/web/proformas/issue-proforma/index.ts b/modules/customer-invoices/src/web/proformas/issue-proforma/index.ts new file mode 100644 index 00000000..6b67c80e --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/issue-proforma/index.ts @@ -0,0 +1 @@ +export * from "./controllers"; diff --git a/modules/customer-invoices/src/web/proformas/issue-proforma/ui/index.ts b/modules/customer-invoices/src/web/proformas/issue-proforma/ui/index.ts new file mode 100644 index 00000000..6582bb8a --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/issue-proforma/ui/index.ts @@ -0,0 +1 @@ +export * from "./issue-proforma-dialog"; diff --git a/modules/customer-invoices/src/web/proformas/issue-proforma/ui/issue-proforma-dialog.tsx b/modules/customer-invoices/src/web/proformas/issue-proforma/ui/issue-proforma-dialog.tsx new file mode 100644 index 00000000..14955214 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/issue-proforma/ui/issue-proforma-dialog.tsx @@ -0,0 +1,62 @@ +import { + AlertDialog, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + Button, + Spinner, +} from "@repo/shadcn-ui/components"; + +import { useProformaIssueDialogController } from "../controllers"; + +interface ProformaIssueDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + proformaId: string; + proformaReference: string; +} + +export function ProformaIssueDialog({ + open, + onOpenChange, + proformaId, + proformaReference, +}: ProformaIssueDialogProps) { + const { issue, isSubmitting } = useProformaIssueDialogController(); + + return ( + + + + Emitir factura + + ¿Seguro que quieres emitir la factura desde la proforma{" "} + {proformaReference}? Esta acción es irreversible. + + + + + + + + + + + ); +} diff --git a/modules/customer-invoices/src/web/proformas/list/adapters/index.ts b/modules/customer-invoices/src/web/proformas/list/adapters/index.ts new file mode 100644 index 00000000..e2a4d99c --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/adapters/index.ts @@ -0,0 +1 @@ +export * from "./proforma-summary-dto.adapter"; diff --git a/modules/customer-invoices/src/web/proformas/list/adapters/proforma-summary-dto.adapter.ts b/modules/customer-invoices/src/web/proformas/list/adapters/proforma-summary-dto.adapter.ts new file mode 100644 index 00000000..913005c8 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/adapters/proforma-summary-dto.adapter.ts @@ -0,0 +1,71 @@ +import { MoneyDTOHelper, PercentageDTOHelper, formatCurrency } from "@erp/core"; + +import type { + ProformaSummaryData, + ProformaSummaryPage, + ProformaSummaryPageData, +} from "../../types"; + +/** + * Convierte el DTO completo de API a datos numéricos para el formulario. + */ +export const ProformaSummaryDtoAdapter = { + fromDto(pageDto: ProformaSummaryPage, context?: unknown): ProformaSummaryPageData { + return { + ...pageDto, + items: pageDto.items.map( + (summaryDto) => + ({ + ...summaryDto, + + subtotal_amount: MoneyDTOHelper.toNumber(summaryDto.subtotal_amount), + subtotal_amount_fmt: formatCurrency( + MoneyDTOHelper.toNumber(summaryDto.subtotal_amount), + Number(summaryDto.total_amount.scale || 2), + summaryDto.currency_code, + summaryDto.language_code + ), + + discount_percentage: PercentageDTOHelper.toNumber(summaryDto.discount_percentage), + discount_percentage_fmt: PercentageDTOHelper.toNumericString( + summaryDto.discount_percentage + ), + + discount_amount: MoneyDTOHelper.toNumber(summaryDto.discount_amount), + discount_amount_fmt: formatCurrency( + MoneyDTOHelper.toNumber(summaryDto.discount_amount), + Number(summaryDto.total_amount.scale || 2), + summaryDto.currency_code, + summaryDto.language_code + ), + + taxable_amount: MoneyDTOHelper.toNumber(summaryDto.taxable_amount), + taxable_amount_fmt: formatCurrency( + MoneyDTOHelper.toNumber(summaryDto.taxable_amount), + Number(summaryDto.total_amount.scale || 2), + summaryDto.currency_code, + summaryDto.language_code + ), + + taxes_amount: MoneyDTOHelper.toNumber(summaryDto.taxes_amount), + taxes_amount_fmt: formatCurrency( + MoneyDTOHelper.toNumber(summaryDto.taxes_amount), + Number(summaryDto.total_amount.scale || 2), + summaryDto.currency_code, + summaryDto.language_code + ), + + total_amount: MoneyDTOHelper.toNumber(summaryDto.total_amount), + total_amount_fmt: formatCurrency( + MoneyDTOHelper.toNumber(summaryDto.total_amount), + Number(summaryDto.total_amount.scale || 2), + summaryDto.currency_code, + summaryDto.language_code + ), + + //taxes: dto.taxes, + }) as unknown as ProformaSummaryData + ), + }; + }, +}; diff --git a/modules/customer-invoices/src/web/proformas/list/api/get-proformas-list.api.ts b/modules/customer-invoices/src/web/proformas/list/api/get-proformas-list.api.ts new file mode 100644 index 00000000..ac1a4cf6 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/api/get-proformas-list.api.ts @@ -0,0 +1,18 @@ +import type { CriteriaDTO } from "@erp/core"; +import type { IDataSource } from "@erp/core/client"; + +import type { ProformaSummaryPage } from "../../types"; + +export async function getProformaListApi( + dataSource: IDataSource, + signal: AbortSignal, + criteria: CriteriaDTO +) { + const response = dataSource.getList("proformas", { + signal, + ...criteria, + }); + + //return mapProformaList(raw); + return response; +} diff --git a/modules/customer-invoices/src/web/proformas/list/api/index.ts b/modules/customer-invoices/src/web/proformas/list/api/index.ts new file mode 100644 index 00000000..01e25c03 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/api/index.ts @@ -0,0 +1 @@ +export * from "./get-proformas-list.api"; diff --git a/modules/customer-invoices/src/web/proformas/list/controllers/index.ts b/modules/customer-invoices/src/web/proformas/list/controllers/index.ts new file mode 100644 index 00000000..a2a5ee16 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/controllers/index.ts @@ -0,0 +1,2 @@ +export * from "./use-proforma-list.controller.ts"; +export * from "./use-proforma-list-page.controller.ts"; diff --git a/modules/customer-invoices/src/web/proformas/list/controllers/use-proforma-list-page.controller.ts.ts b/modules/customer-invoices/src/web/proformas/list/controllers/use-proforma-list-page.controller.ts.ts new file mode 100644 index 00000000..dff07ef3 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/controllers/use-proforma-list-page.controller.ts.ts @@ -0,0 +1,64 @@ +import React from "react"; + +import { useChangeStatusDialogController } from "../../change-status"; +import { useDeleteProformaDialogController } from "../../delete"; +import { useProformaIssueDialogController } from "../../issue-proforma"; +import { + type PROFORMA_STATUS, + PROFORMA_STATUS_TRANSITIONS, + type ProformaSummaryData, +} from "../../types"; + +import { useProformaListController } from "./use-proforma-list.controller"; + +export function useProformaListPageController() { + const listCtrl = useProformaListController(); + + // Controlador de diálogos + const issueDialogCtrl = useProformaIssueDialogController(); + const changeStatusDialogCtrl = useChangeStatusDialogController(); + const deleteDialogCtrl = useDeleteProformaDialogController(); + + const handleIssueProforma = React.useCallback( + (proforma: ProformaSummaryData) => { + // Solo si approved → issued + issueDialogCtrl.openDialog(proforma); + }, + [issueDialogCtrl] + ); + + const handleChangeStatusProforma = React.useCallback( + (proforma: ProformaSummaryData, nextStatus: string) => { + const proforma_status = proforma.status as PROFORMA_STATUS; + const transitions = PROFORMA_STATUS_TRANSITIONS[proforma_status] ?? []; + + if (!transitions.includes(nextStatus as PROFORMA_STATUS)) { + console.warn(`Transición inválida: ${proforma.status} → ${nextStatus}`); + return; + } + + changeStatusDialogCtrl.openDialog(proforma, nextStatus); + }, + [changeStatusDialogCtrl] + ); + + const handleDeleteProforma = React.useCallback( + (proforma: ProformaSummaryData) => { + console.log(proforma); + deleteDialogCtrl.openDialog(proforma); + }, + [deleteDialogCtrl] + ); + + return { + listCtrl, + + issueDialogCtrl, + changeStatusDialogCtrl, + deleteDialogCtrl, + + handleIssueProforma, + handleChangeStatusProforma, + handleDeleteProforma, + }; +} diff --git a/modules/customer-invoices/src/web/proformas/pages/list/hooks/use-proformas-list.ts b/modules/customer-invoices/src/web/proformas/list/controllers/use-proforma-list.controller.ts similarity index 80% rename from modules/customer-invoices/src/web/proformas/pages/list/hooks/use-proformas-list.ts rename to modules/customer-invoices/src/web/proformas/list/controllers/use-proforma-list.controller.ts index 792776c8..91ba60e2 100644 --- a/modules/customer-invoices/src/web/proformas/pages/list/hooks/use-proformas-list.ts +++ b/modules/customer-invoices/src/web/proformas/list/controllers/use-proforma-list.controller.ts @@ -1,13 +1,11 @@ -// src/modules/proformas/hooks/use-proformas-list.ts - import type { CriteriaDTO } from "@erp/core"; import { useDebounce } from "@repo/rdx-ui/components"; import { useMemo, useState } from "react"; -import { ProformaSummaryDtoAdapter } from "../../../adapters/proforma-summary-dto.adapter"; -import { useProformasQuery } from "../../../hooks"; +import { ProformaSummaryDtoAdapter } from "../adapters"; +import { useProformaListQuery } from "../hooks"; -export const useProformasList = () => { +export const useProformaListController = () => { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); const [search, setSearch] = useState(""); @@ -29,7 +27,7 @@ export const useProformasList = () => { }; }, [pageSize, pageIndex, debouncedQ, status]); - const query = useProformasQuery({ criteria }); + const query = useProformaListQuery({ criteria }); const data = useMemo( () => (query.data ? ProformaSummaryDtoAdapter.fromDto(query.data) : undefined), [query.data] diff --git a/modules/customer-invoices/src/web/proformas/list/hooks/index.ts b/modules/customer-invoices/src/web/proformas/list/hooks/index.ts new file mode 100644 index 00000000..6342d288 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/hooks/index.ts @@ -0,0 +1 @@ +export * from "./use-proforma-list-query"; diff --git a/modules/customer-invoices/src/web/proformas/list/hooks/use-proforma-list-query.ts b/modules/customer-invoices/src/web/proformas/list/hooks/use-proforma-list-query.ts new file mode 100644 index 00000000..f1d0e922 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/hooks/use-proforma-list-query.ts @@ -0,0 +1,38 @@ +import type { CriteriaDTO } from "@erp/core"; +import { useDataSource } from "@erp/core/hooks"; +import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@repo/rdx-criteria"; +import { type DefaultError, type QueryKey, useQuery } from "@tanstack/react-query"; + +import type { ProformaSummaryPage } from "../../types"; +import { getProformaListApi } from "../api"; + +export const PROFORMAS_QUERY_KEY = (criteria?: CriteriaDTO): QueryKey => [ + "proforma", + { + pageNumber: criteria?.pageNumber ?? INITIAL_PAGE_INDEX, + pageSize: criteria?.pageSize ?? INITIAL_PAGE_SIZE, + q: criteria?.q ?? "", + filters: criteria?.filters ?? [], + orderBy: criteria?.orderBy ?? "", + order: criteria?.order ?? "", + }, +]; + +type ProformasQueryOptions = { + enabled?: boolean; + criteria?: CriteriaDTO; +}; + +// Obtener todas las facturas +export const useProformaListQuery = (options?: ProformasQueryOptions) => { + const dataSource = useDataSource(); + const enabled = options?.enabled ?? true; + const criteria = options?.criteria ?? {}; + + return useQuery({ + queryKey: PROFORMAS_QUERY_KEY(criteria), + queryFn: async ({ signal }) => getProformaListApi(dataSource, signal, criteria), + enabled, + placeholderData: (previousData, _previousQuery) => previousData, // Mantener datos previos mientras se carga nueva datos (antiguo `keepPreviousData`) + }); +}; diff --git a/modules/customer-invoices/src/web/proformas/list/index.ts b/modules/customer-invoices/src/web/proformas/list/index.ts new file mode 100644 index 00000000..4aedf593 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/index.ts @@ -0,0 +1 @@ +export * from "./ui"; diff --git a/modules/customer-invoices/src/web/proformas/list/ui/blocks/index.ts b/modules/customer-invoices/src/web/proformas/list/ui/blocks/index.ts new file mode 100644 index 00000000..184a41b8 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/ui/blocks/index.ts @@ -0,0 +1,3 @@ +export * from "../../../issue-proforma/ui/issue-proforma-dialog"; + +export * from "./proformas-grid"; diff --git a/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/index.ts b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/index.ts new file mode 100644 index 00000000..cf0040bf --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/index.ts @@ -0,0 +1 @@ +export * from "./proformas-grid"; diff --git a/modules/customer-invoices/src/web/proformas/pages/list/ui/proformas-grid.tsx b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.tsx similarity index 55% rename from modules/customer-invoices/src/web/proformas/pages/list/ui/proformas-grid.tsx rename to modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.tsx index 9623ce0e..6c430ee1 100644 --- a/modules/customer-invoices/src/web/proformas/pages/list/ui/proformas-grid.tsx +++ b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.tsx @@ -1,27 +1,28 @@ import { DataTable, SkeletonDataTable } from "@repo/rdx-ui/components"; +import type { ColumnDef } from "@tanstack/react-table"; import { useNavigate } from "react-router-dom"; -import { useTranslation } from "../../../../i18n"; -import type { ProformaSummaryPageData } from "../../../schema/proforma-summary.web.schema"; -import { useProformasGridColumns } from "../hooks"; +import { useTranslation } from "../../../../../i18n"; +import type { ProformaSummaryData, ProformaSummaryPageData } from "../../../../types"; interface ProformasGridProps { data: ProformaSummaryPageData; - loading?: boolean; + loading: boolean; + + columns: ColumnDef[]; + pageIndex: number; pageSize: number; - searchValue: string; - onSearchChange: (v: string) => void; - onPageChange: (p: number) => void; - onPageSizeChange: (s: number) => void; - onRowClick?: (id: string) => void; - onExportClick?: () => void; - onStatusFilterChange?: (newStatus: string) => void; + onPageChange: (pageIndex: number) => void; + onPageSizeChange: (size: number) => void; + + onRowClick?: (proformaId: string) => void; } export const ProformasGrid = ({ data, loading, + columns, pageIndex, pageSize, onPageChange, @@ -32,14 +33,6 @@ export const ProformasGrid = ({ const { t } = useTranslation(); const { items, total_items } = data; - const columns = useProformasGridColumns({ - onEdit: (proforma) => navigate(`/proformas/${proforma.id}/edit`), - onDuplicate: (proforma) => null, //duplicateInvoice(inv.id), - onDownloadPdf: (proforma) => null, //downloadInvoicePdf(inv.id), - onSendEmail: (proforma) => null, //sendInvoiceEmail(inv.id), - onDelete: (proforma) => null, //confirmDelete(inv.id), - }); - if (loading) return ( void; - onDuplicate?: (proforma: ProformaSummaryData) => void; - onDownloadPdf?: (proforma: ProformaSummaryData) => void; - onSendEmail?: (proforma: ProformaSummaryData) => void; - onDelete?: (proforma: ProformaSummaryData) => void; + onEditClick?: (proforma: ProformaSummaryData) => void; + onIssueClick?: (proforma: ProformaSummaryData) => void; + onChangeStatusClick?: (proforma: ProformaSummaryData, nextStatus: string) => void; + onDeleteClick?: (proforma: ProformaSummaryData) => void; }; export function useProformasGridColumns( actionHandlers: GridActionHandlers = {} ): ColumnDef[] { const { t } = useTranslation(); - const { onEdit, onDuplicate, onDownloadPdf, onSendEmail, onDelete } = actionHandlers; - return React.useMemo[]>( + return React.useMemo[]>( () => [ { id: "select", @@ -75,29 +77,34 @@ export function useProformasGridColumns( }, cell: ({ row }) =>
{row.getValue("invoice_number")}
, }, + + // Estado { accessorKey: "status", header: "Estado", cell: ({ row }) => { - const status = String(row.getValue("status")); - const isIssued = status === "issued"; const proforma = row.original; + const isIssued = proforma.status === "issued"; + const invoiceId = proforma.linked_invoice_id; + return (
- - {isIssued && proforma.id && ( + + + {/* Enlace discreto a factura real */} + {isIssued && invoiceId && ( - Ver factura #{proforma.id} + Ver factura {invoiceId} )} @@ -105,6 +112,8 @@ export function useProformasGridColumns( ); }, }, + + // Cliente { accessorKey: "client_name", header: ({ column }) => { @@ -124,7 +133,7 @@ export function useProformasGridColumns( return (
{proforma.recipient.name} @@ -205,88 +214,105 @@ export function useProformasGridColumns( { id: "actions", header: "Acciones", + enableSorting: false, cell: ({ row }) => { const proforma = row.original; const isIssued = proforma.status === "issued"; const isApproved = proforma.status === "approved"; + const availableTransitions = + PROFORMA_STATUS_TRANSITIONS[proforma.status as ProformaStatus] ?? []; return (
- - - - - - Editar - - - {!isIssued && ( - <> + - Cambiar estado + Editar + + )} - {isApproved && ( + {/* Cambiar estado */} + {!isIssued && + availableTransitions.map((next_status) => ( + - Emitir a factura + + Cambiar a {t(`catalog.proformas.status.${next_status}`)} + - )} + + ))} + {/* Emitir factura: solo si approved */} + {!isIssued && proforma.status === "approved" && ( + + + Emitir a factura + + + )} + + {/* Eliminar */} + {!isIssued && ( + + + + Eliminar - + )}
); }, }, ], - [t, onEdit, onDuplicate, onDownloadPdf, onSendEmail, onDelete] + [t, actionHandlers] ); } diff --git a/modules/customer-invoices/src/web/proformas/pages/list/ui/index.ts b/modules/customer-invoices/src/web/proformas/list/ui/components/index.ts similarity index 54% rename from modules/customer-invoices/src/web/proformas/pages/list/ui/index.ts rename to modules/customer-invoices/src/web/proformas/list/ui/components/index.ts index c4e562db..df695438 100644 --- a/modules/customer-invoices/src/web/proformas/pages/list/ui/index.ts +++ b/modules/customer-invoices/src/web/proformas/list/ui/components/index.ts @@ -1,2 +1 @@ export * from "./proforma-status-badge"; -export * from "./proformas-grid"; diff --git a/modules/customer-invoices/src/web/proformas/list/ui/components/proforma-status-badge.tsx b/modules/customer-invoices/src/web/proformas/list/ui/components/proforma-status-badge.tsx new file mode 100644 index 00000000..e524c7bc --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/ui/components/proforma-status-badge.tsx @@ -0,0 +1,30 @@ +import { Badge } from "@repo/shadcn-ui/components"; +import { cn } from "@repo/shadcn-ui/lib/utils"; + +import { useTranslation } from "../../../../i18n"; +import { + type ProformaStatus, + getProformaStatusButtonVariant, + getProformaStatusColor, +} from "../../../types"; + +export type ProformaStatusBadgeProps = { + status: string | ProformaStatus; // permitir cualquier valor + className?: string; +}; + +export const ProformaStatusBadge = ({ status, className }: ProformaStatusBadgeProps) => { + const { t } = useTranslation(); + const normalizedStatus = status.toLowerCase() as ProformaStatus; + + return ( + + {t(`catalog.proformas.status.${normalizedStatus}`, { defaultValue: status })} + + ); +}; + +ProformaStatusBadge.displayName = "ProformaStatusBadge"; diff --git a/modules/customer-invoices/src/web/proformas/list/ui/index.ts b/modules/customer-invoices/src/web/proformas/list/ui/index.ts new file mode 100644 index 00000000..c4e34b27 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/ui/index.ts @@ -0,0 +1 @@ +export * from "./pages"; diff --git a/modules/customer-invoices/src/web/proformas/pages/list/index.ts b/modules/customer-invoices/src/web/proformas/list/ui/pages/index.ts similarity index 100% rename from modules/customer-invoices/src/web/proformas/pages/list/index.ts rename to modules/customer-invoices/src/web/proformas/list/ui/pages/index.ts diff --git a/modules/customer-invoices/src/web/proformas/list/ui/pages/proforma-list-page.tsx b/modules/customer-invoices/src/web/proformas/list/ui/pages/proforma-list-page.tsx new file mode 100644 index 00000000..177144d4 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/ui/pages/proforma-list-page.tsx @@ -0,0 +1,148 @@ +import { PageHeader, SimpleSearchInput } from "@erp/core/components"; +import { ErrorAlert } from "@erp/customers/components"; +import { AppContent, AppHeader, BackHistoryButton } from "@repo/rdx-ui/components"; +import { + Button, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@repo/shadcn-ui/components"; +import { FilterIcon, PlusIcon } from "lucide-react"; +import { useNavigate } from "react-router-dom"; + +import { useTranslation } from "../../../../i18n"; +import { ChangeStatusDialog } from "../../../change-status"; +import { DeleteProformaDialog } from "../../../delete/ui"; +import { useProformaListPageController } from "../../controllers"; +import { ProformaIssueDialog } from "../blocks"; +import { ProformasGrid } from "../blocks/proformas-grid"; +import { useProformasGridColumns } from "../blocks/proformas-grid/use-proforma-grid-columns"; + +export const ProformaListPage = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const { + listCtrl, + + issueDialogCtrl, + changeStatusDialogCtrl, + deleteDialogCtrl, + + handleChangeStatusProforma, + handleDeleteProforma, + handleIssueProforma, + } = useProformaListPageController(); + + const columns = useProformasGridColumns({ + onEditClick: (proforma) => navigate(`/proformas/${proforma.id}/edit`), + onIssueClick: handleIssueProforma, + onDeleteClick: handleDeleteProforma, + onChangeStatusClick: handleChangeStatusProforma, + }); + + if (listCtrl.isError || !listCtrl.data) { + return ( + + + + + ); + } + + return ( + <> + + navigate("/proformas/create")} + > + + {t("pages.proformas.create.title")} + + } + title={t("pages.proformas.list.title")} + /> + + + {/* Search and filters */} +
+ + + +
+ + navigate(`/proformas/${id}`)} + pageIndex={listCtrl.pageIndex} + pageSize={listCtrl.pageSize} + /> + + {/* Emitir factura */} + !open && issueDialogCtrl.closeDialog()} + open={issueDialogCtrl.open} + proformaId={issueDialogCtrl.proforma?.id ?? 0} + proformaReference={issueDialogCtrl.proforma?.reference ?? ""} + /> + + {/* Cambiar estado */} + { + if (!open) changeStatusDialogCtrl.closeDialog(); + }} + open={changeStatusDialogCtrl.open} + proformaRef={changeStatusDialogCtrl.proforma?.reference} + proformas={changeStatusDialogCtrl.proformas} + targetStatus={changeStatusDialogCtrl.targetStatus ?? undefined} + /> + + {/* Eliminar */} + !o && deleteDialogCtrl.closeDialog()} + open={deleteDialogCtrl.open} + proformaRef={deleteDialogCtrl.proforma?.reference} + /> +
+ + ); +}; diff --git a/modules/customer-invoices/src/web/proformas/pages/list/hooks/index.ts b/modules/customer-invoices/src/web/proformas/pages/list/hooks/index.ts deleted file mode 100644 index 928ebc83..00000000 --- a/modules/customer-invoices/src/web/proformas/pages/list/hooks/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./use-proformas-grid-columns"; -export * from "./use-proformas-list"; diff --git a/modules/customer-invoices/src/web/proformas/pages/list/hooks/use-proformas-grid-columns copy.tsx b/modules/customer-invoices/src/web/proformas/pages/list/hooks/use-proformas-grid-columns copy.tsx deleted file mode 100644 index 1e25575e..00000000 --- a/modules/customer-invoices/src/web/proformas/pages/list/hooks/use-proformas-grid-columns copy.tsx +++ /dev/null @@ -1,513 +0,0 @@ -import { formatDate } from "@erp/core/client"; -import { DataTableColumnHeader } from "@repo/rdx-ui/components"; -import { - Button, - ButtonGroup, - Checkbox, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@repo/shadcn-ui/components"; -import type { ColumnDef } from "@tanstack/react-table"; -import { - ArrowBigRightDashIcon, - CopyIcon, - DownloadIcon, - EditIcon, - ExternalLinkIcon, - MailIcon, - MoreVerticalIcon, - Trash2Icon, -} from "lucide-react"; -import * as React from "react"; - -import { useTranslation } from "../../../../i18n"; -import type { ProformaSummaryData } from "../../../schema"; -import { ProformaStatusBadge } from "../ui"; - -type GridActionHandlers = { - onEdit?: (proforma: ProformaSummaryData) => void; - onDuplicate?: (proforma: ProformaSummaryData) => void; - onDownloadPdf?: (proforma: ProformaSummaryData) => void; - onSendEmail?: (proforma: ProformaSummaryData) => void; - onDelete?: (proforma: ProformaSummaryData) => void; -}; - -export function useProformasGridColumns( - actionHandlers: GridActionHandlers = {} -): ColumnDef[] { - const { t } = useTranslation(); - const { onEdit, onDuplicate, onDownloadPdf, onSendEmail, onDelete } = actionHandlers; - - return React.useMemo[]>( - () => [ - // Select - { - id: "select", - header: ({ table }) => ( - table.toggleAllPageRowsSelected(!!value)} - /> - ), - cell: ({ row }) => ( - row.toggleSelected(!!value)} - /> - ), - enableSorting: false, - enableHiding: false, - }, - - // Nº - { - accessorKey: "invoice_number", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
{row.getValue("invoice_number")}
- ), - enableHiding: false, - meta: { - title: t("pages.proformas.list.grid_columns.invoice_number"), - }, - }, - // Estado - { - accessorKey: "status", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
- - {row.original.status === "issued" && ( - - - - - - Ver factura #{row.original.issued_invoice_id} - - - )} -
- ), - enableSorting: false, - size: 64, - minSize: 64, - meta: { - title: t("pages.proformas.list.grid_columns.status"), - }, - }, - { - id: "recipient", - header: ({ column }) => ( - - ), - accessorFn: (row) => row.recipient.name, // para ordenar/buscar por nombre - enableHiding: false, - minSize: 120, - cell: ({ row }) => { - const c = row.original.recipient; - return ( -
-
-
- {c.name} -
-
- {c.tin && {c.tin}} -
-
-
- ); - }, - meta: { - title: t("pages.proformas.list.grid_columns.recipient"), - }, - }, - // Serie - { - accessorKey: "series", - header: ({ column }) => ( - - ), - cell: ({ row }) =>
{row.original.series}
, - enableSorting: false, - size: 64, - minSize: 64, - meta: { - title: t("pages.proformas.list.grid_columns.series"), - }, - }, - // Referencia - { - accessorKey: "reference", - header: ({ column }) => ( - - ), - cell: ({ row }) =>
{row.original.reference}
, - enableSorting: false, - size: 120, - minSize: 100, - meta: { - title: t("pages.proformas.list.grid_columns.reference"), - }, - }, - - // Fecha factura - { - accessorKey: "invoice_date", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
- {formatDate(row.original.invoice_date)} -
- ), - size: 96, - minSize: 96, - meta: { - title: t("pages.proformas.list.grid_columns.invoice_date"), - }, - }, - // Fecha operación - { - accessorKey: "operation_date", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
- {formatDate(row.original.operation_date)} -
- ), - size: 96, - minSize: 96, - meta: { - title: t("pages.proformas.list.grid_columns.operation_date"), - }, - }, - - // Subtotal amount - { - accessorKey: "subtotal_amount_fmt", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
- {row.original.subtotal_amount_fmt} -
- ), - enableSorting: false, - size: 120, - minSize: 100, - meta: { - title: t("pages.proformas.list.grid_columns.subtotal_amount"), - }, - }, - - // Discount amount - { - accessorKey: "discount_amount_fmt", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
- {row.original.discount_amount_fmt} -
- ), - enableSorting: false, - size: 120, - minSize: 100, - meta: { - title: t("pages.proformas.list.grid_columns.discount_amount"), - }, - }, - - // Taxes amount - { - accessorKey: "taxes_amount_fmt", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
{row.original.taxes_amount_fmt}
- ), - enableSorting: false, - size: 120, - minSize: 100, - meta: { - title: t("pages.proformas.list.grid_columns.taxes_amount"), - }, - }, - - // Total amount - { - accessorKey: "total_amount_fmt", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
- {row.original.total_amount_fmt} -
- ), - enableSorting: false, - size: 140, - minSize: 120, - meta: { - title: t("pages.proformas.list.grid_columns.total_amount"), - }, - }, - - // ───────────────────────────── - // Acciones - // ───────────────────────────── - { - id: "actions", - header: ({ column }) => ( - - ), - enableSorting: false, - enableHiding: false, - size: 110, - minSize: 96, - cell: ({ row }) => { - const proforma = row.original; - const stop = (e: React.MouseEvent | React.KeyboardEvent) => e.stopPropagation(); - - return ( - - {/* Emitir factura: approved -> issued */} - - - - - {t("common.edit_row")} - - - {/* Editar (acción primaria) */} - - - - - {t("common.edit_row")} - - - {/* Duplicar */} - - - - - {t("common.duplicate_row")} - - - {/* Descargar en PDF */} - - - - - {t("common.download_pdf")} - - - - - - - {t("common.delete_row")} - - - {/* Menú demás acciones */} - {/** biome-ignore lint/suspicious/noSelfCompare: */} - {false !== false && ( - - - - - - onDuplicate?.(proforma)} - > - - {t("common.duplicate_row")} - - - onDownloadPdf?.(proforma)} - > - - {t("common.download_pdf")} - - onSendEmail?.(proforma)} - > - - {t("common.send_email")} - {" "} - - onDelete?.(proforma)} - > - - {t("common.delete_row")} - - - - )} - - ); - }, - meta: { - title: t("common.actions"), - }, - }, - ], - [t, onEdit, onDuplicate, onDownloadPdf, onSendEmail, onDelete] - ); -} diff --git a/modules/customer-invoices/src/web/proformas/pages/list/proforma-list-page.tsx b/modules/customer-invoices/src/web/proformas/pages/list/proforma-list-page.tsx deleted file mode 100644 index 6185c9c6..00000000 --- a/modules/customer-invoices/src/web/proformas/pages/list/proforma-list-page.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { PageHeader, SimpleSearchInput } from "@erp/core/components"; -import { ErrorAlert } from "@erp/customers/components"; -import { AppContent, AppHeader, BackHistoryButton } from "@repo/rdx-ui/components"; -import { - Button, - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@repo/shadcn-ui/components"; -import { FilterIcon, PlusIcon } from "lucide-react"; -import { useNavigate } from "react-router-dom"; - -import { useTranslation } from "../../../i18n"; - -import { useProformasList } from "./hooks"; -import { ProformasGrid } from "./ui"; - -export const ProformaListPage = () => { - const { t } = useTranslation(); - const navigate = useNavigate(); - const list = useProformasList(); - - if (list.isError || !list.data) { - return ( - - - - - ); - } - - return ( - <> - - navigate("/proformas/create")} - > - - {t("pages.proformas.create.title")} - - } - title={t("pages.proformas.list.title")} - /> - - - {/* Search and filters */} -
- - - -
- - -
- - ); -}; diff --git a/modules/customer-invoices/src/web/proformas/pages/list/ui/proforma-status-badge.tsx b/modules/customer-invoices/src/web/proformas/pages/list/ui/proforma-status-badge.tsx deleted file mode 100644 index d6fd57ce..00000000 --- a/modules/customer-invoices/src/web/proformas/pages/list/ui/proforma-status-badge.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Badge } from "@repo/shadcn-ui/components"; -import { cn } from "@repo/shadcn-ui/lib/utils"; - -import { useTranslation } from "../../../../i18n"; - -export type ProformaStatus = "draft" | "sent" | "approved" | "rejected" | "issued"; - -export type ProformaStatusBadgeProps = { - status: string | ProformaStatus; // permitir cualquier valor - className?: string; -}; - -export const ProformaStatusBadge = ({ status, className }: ProformaStatusBadgeProps) => { - const { t } = useTranslation(); - const normalizedStatus = status.toLowerCase() as ProformaStatus; - - const getVariant = ( - status: ProformaStatus - ): "default" | "secondary" | "outline" | "destructive" => { - switch (status) { - case "draft": - return "outline"; - case "sent": - return "secondary"; - case "approved": - return "default"; - case "rejected": - return "destructive"; - case "issued": - return "default"; - default: - return "outline"; - } - }; - - const getColor = (status: ProformaStatus): string => { - switch (status) { - case "draft": - return "bg-gray-100 text-gray-700 hover:bg-gray-100"; - case "sent": - return "bg-yellow-100 text-yellow-700 hover:bg-yellow-100"; - case "approved": - return "bg-green-100 text-green-700 hover:bg-green-100"; - case "rejected": - return "bg-red-100 text-red-700 hover:bg-red-100"; - case "issued": - return "bg-blue-100 text-blue-700 hover:bg-blue-100"; - default: - return "bg-gray-100 text-gray-700 hover:bg-gray-100"; - } - }; - - return ( - - {t(`catalog.proformas.status.${normalizedStatus}`, { defaultValue: status })} - - ); -}; - -ProformaStatusBadge.displayName = "ProformaStatusBadge"; diff --git a/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-comp.tsx b/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-comp.tsx index 1f9bace8..22b7e476 100644 --- a/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-comp.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-comp.tsx @@ -14,7 +14,7 @@ import { type ProformaFormData, ProformaFormSchema, defaultProformaFormData, -} from "../../schema"; +} from "../../types"; import { useProformaContext } from "./context"; import { ProformaUpdateForm } from "./proforma-update-form"; diff --git a/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-form.tsx b/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-form.tsx index 5b71b6be..f59c01ad 100644 --- a/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-form.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-form.tsx @@ -2,7 +2,7 @@ import { FormDebug } from "@erp/core/components"; import { cn } from "@repo/shadcn-ui/lib/utils"; import { type FieldErrors, useFormContext } from "react-hook-form"; -import type { ProformaFormData } from "../../schema"; +import type { ProformaFormData } from "../../types"; import { ProformaBasicInfoFields, ProformaItems, ProformaRecipient, ProformaTotals } from "./ui"; diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-basic-info-fields.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-basic-info-fields.tsx index 375c98c2..8c623f50 100644 --- a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-basic-info-fields.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-basic-info-fields.tsx @@ -4,7 +4,7 @@ import type { ComponentProps } from "react"; import { useFormContext } from "react-hook-form"; import { useTranslation } from "../../../../../i18n"; -import type { ProformaFormData } from "../../../../schema"; +import type { ProformaFormData } from "../../../../types"; export const ProformaBasicInfoFields = (props: ComponentProps<"fieldset">) => { const { t } = useTranslation(); diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-totals.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-totals.tsx index e33ab2b5..f1bc4bb2 100644 --- a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-totals.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-totals.tsx @@ -12,7 +12,7 @@ import type { ComponentProps } from "react"; import { useFormContext, useWatch } from "react-hook-form"; import { useTranslation } from "../../../../../i18n"; -import type { ProformaFormData } from "../../../../schema"; +import type { ProformaFormData } from "../../../../types"; import { useProformaContext } from "../../context"; export const ProformaTotals = (props: ComponentProps<"fieldset">) => { diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/item-row-editor.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/item-row-editor.tsx index acaadd95..d07c37ff 100644 --- a/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/item-row-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/item-row-editor.tsx @@ -1,7 +1,7 @@ import { Button, Input, Label, Textarea } from "@repo/shadcn-ui/components"; import { useFormContext } from "react-hook-form"; -import type { ProformaFormData, ProformaItemFormData } from "../../../../../schema"; +import type { ProformaFormData, ProformaItemFormData } from "../../../../../types"; export function ItemRowEditor({ row, diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/items-editor.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/items-editor.tsx index fb12f06c..ad5df7c4 100644 --- a/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/items-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/items-editor.tsx @@ -1,14 +1,14 @@ /** biome-ignore-all lint/complexity/noForEach: */ /** biome-ignore-all lint/suspicious/useIterableCallbackReturn: */ -import { useProformaItemsColumns } from "@erp/customer-invoices/web/proformas/hooks"; +import { useProformaGridColumns } from "@erp/customer-invoices/web/proformas/hooks"; import { DataTable, useWithRowSelection } from "@repo/rdx-ui/components"; import { useMemo } from "react"; import { useFieldArray, useFormContext } from "react-hook-form"; import { useProformaAutoRecalc } from "../../../../../../hooks"; import { useTranslation } from "../../../../../../i18n"; -import { type ProformaFormData, defaultProformaItemFormData } from "../../../../../schema"; +import { type ProformaFormData, defaultProformaItemFormData } from "../../../../../types"; import { useProformaContext } from "../../../context"; import { ItemRowEditor } from "./item-row-editor"; @@ -28,7 +28,7 @@ export const ItemsEditor = () => { name: "items", }); - const baseColumns = useWithRowSelection(useProformaItemsColumns(), true); + const baseColumns = useWithRowSelection(useProformaGridColumns(), true); const columns = useMemo(() => baseColumns, [baseColumns]); return ( diff --git a/modules/customer-invoices/src/web/proformas/schema/index.ts b/modules/customer-invoices/src/web/proformas/types/index.ts similarity index 78% rename from modules/customer-invoices/src/web/proformas/schema/index.ts rename to modules/customer-invoices/src/web/proformas/types/index.ts index 33c6f0d9..4469cea9 100644 --- a/modules/customer-invoices/src/web/proformas/schema/index.ts +++ b/modules/customer-invoices/src/web/proformas/types/index.ts @@ -1,3 +1,4 @@ export * from "./proforma.api.schema"; export * from "./proforma.form.schema"; +export * from "./proforma-status"; export * from "./proforma-summary.web.schema"; diff --git a/modules/customer-invoices/src/web/proformas/types/proforma-status.ts b/modules/customer-invoices/src/web/proformas/types/proforma-status.ts new file mode 100644 index 00000000..ec499360 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/types/proforma-status.ts @@ -0,0 +1,54 @@ +export enum PROFORMA_STATUS { + DRAFT = "draft", + SENT = "sent", + APPROVED = "approved", + REJECTED = "rejected", + ISSUED = "issued", +} + +// Transiciones válidas según reglas del dominio +export const PROFORMA_STATUS_TRANSITIONS: Record = { + [PROFORMA_STATUS.DRAFT]: [PROFORMA_STATUS.SENT], + [PROFORMA_STATUS.SENT]: [PROFORMA_STATUS.APPROVED, PROFORMA_STATUS.REJECTED], + [PROFORMA_STATUS.APPROVED]: [PROFORMA_STATUS.ISSUED, PROFORMA_STATUS.DRAFT], + [PROFORMA_STATUS.REJECTED]: [PROFORMA_STATUS.DRAFT], + [PROFORMA_STATUS.ISSUED]: [], +}; + +export type ProformaStatus = `${PROFORMA_STATUS}`; + +export const getProformaStatusButtonVariant = ( + status: ProformaStatus +): "default" | "secondary" | "outline" | "destructive" => { + switch (status) { + case "draft": + return "outline"; + case "sent": + return "secondary"; + case "approved": + return "default"; + case "rejected": + return "destructive"; + case "issued": + return "default"; + default: + return "outline"; + } +}; + +export const getProformaStatusColor = (status: ProformaStatus): string => { + switch (status) { + case "draft": + return "bg-gray-100 text-gray-700 hover:bg-gray-100"; + case "sent": + return "bg-yellow-100 text-yellow-700 hover:bg-yellow-100"; + case "approved": + return "bg-green-100 text-green-700 hover:bg-green-100"; + case "rejected": + return "bg-red-100 text-red-700 hover:bg-red-100"; + case "issued": + return "bg-blue-100 text-blue-700 hover:bg-blue-100"; + default: + return "bg-gray-100 text-gray-700 hover:bg-gray-100"; + } +}; diff --git a/modules/customer-invoices/src/web/proformas/schema/proforma-summary.web.schema.ts b/modules/customer-invoices/src/web/proformas/types/proforma-summary.web.schema.ts similarity index 100% rename from modules/customer-invoices/src/web/proformas/schema/proforma-summary.web.schema.ts rename to modules/customer-invoices/src/web/proformas/types/proforma-summary.web.schema.ts diff --git a/modules/customer-invoices/src/web/proformas/schema/proforma.api.schema.ts b/modules/customer-invoices/src/web/proformas/types/proforma.api.schema.ts similarity index 100% rename from modules/customer-invoices/src/web/proformas/schema/proforma.api.schema.ts rename to modules/customer-invoices/src/web/proformas/types/proforma.api.schema.ts diff --git a/modules/customer-invoices/src/web/proformas/schema/proforma.form.schema.ts b/modules/customer-invoices/src/web/proformas/types/proforma.form.schema.ts similarity index 100% rename from modules/customer-invoices/src/web/proformas/schema/proforma.form.schema.ts rename to modules/customer-invoices/src/web/proformas/types/proforma.form.schema.ts diff --git a/modules/customer-invoices/src/web/proformas/ui/blocks/index.ts b/modules/customer-invoices/src/web/proformas/ui/blocks/index.ts index 140aa597..403af02f 100644 --- a/modules/customer-invoices/src/web/proformas/ui/blocks/index.ts +++ b/modules/customer-invoices/src/web/proformas/ui/blocks/index.ts @@ -1,4 +1,5 @@ +export * from "../../issue-proforma/ui/issue-proforma-dialog"; + export * from "./proforma-delete-dialog"; -export * from "./proforma-issue-dialog"; export * from "./proforma-layout"; export * from "./proforma-tax-summary"; diff --git a/modules/customer-invoices/src/web/proformas/ui/blocks/proforma-issue-dialog.tsx b/modules/customer-invoices/src/web/proformas/ui/blocks/proforma-issue-dialog.tsx deleted file mode 100644 index d8575951..00000000 --- a/modules/customer-invoices/src/web/proformas/ui/blocks/proforma-issue-dialog.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { - AlertDialog, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - Button, - Spinner, -} from "@repo/shadcn-ui/components"; -import { useState } from "react"; - -interface IssueInvoiceDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - proformaId: number; - proformaReference: string; -} - -export function ProformaIssueDialog({ - open, - onOpenChange, - proformaId, - proformaReference, -}: IssueInvoiceDialogProps) { - const [isSubmitting, setIsSubmitting] = useState(false); - //const { toast } = useToast(); - - const handleIssue = async () => { - setIsSubmitting(true); - - /*try { - const result = await issueInvoiceFromProforma(proformaId); - - if (result.success) { - toast({ - title: "Factura emitida", - description: `Se ha emitido la factura #${result.invoiceId} desde la proforma.`, - }); - onOpenChange(false); - } else { - throw new Error(result.error); - } - } catch (error) { - toast({ - title: "Error", - description: error instanceof Error ? error.message : "Error al emitir la factura", - variant: "destructive", - }); - } finally { - setIsSubmitting(false); - }*/ - }; - - return ( - - - - Emitir factura - - ¿Estás seguro de que deseas emitir una factura de cliente desde la proforma{" "} - {proformaReference}? -
-
- Esta acción creará una nueva factura definitiva y la proforma pasará al estado - "Emitida", no pudiendo modificarse ni eliminarse posteriormente. -
-
- - - - -
-
- ); -} diff --git a/modules/customer-invoices/src/web/proformas/ui/blocks/proforma-tax-summary.tsx b/modules/customer-invoices/src/web/proformas/ui/blocks/proforma-tax-summary.tsx index 90ec0459..dabeea02 100644 --- a/modules/customer-invoices/src/web/proformas/ui/blocks/proforma-tax-summary.tsx +++ b/modules/customer-invoices/src/web/proformas/ui/blocks/proforma-tax-summary.tsx @@ -12,7 +12,7 @@ import { useFormContext, useWatch } from "react-hook-form"; import { useTranslation } from "../../../i18n"; import { useProformaContext } from "../../pages/update/context"; -import type { ProformaFormData } from "../../schema"; +import type { ProformaFormData } from "../../types"; export const ProformaTaxSummary = (props: ComponentProps<"fieldset">) => { const { t } = useTranslation(); diff --git a/modules/customer-invoices/templates/acana/issued-invoice.hbs b/modules/customer-invoices/templates/acana/issued-invoice.hbs index b84980f7..73591ded 100644 --- a/modules/customer-invoices/templates/acana/issued-invoice.hbs +++ b/modules/customer-invoices/templates/acana/issued-invoice.hbs @@ -3,9 +3,10 @@ - + + Factura - + + Factura proforma