From 7513553f49e6b5f20018834e1e696f0768db27de Mon Sep 17 00:00:00 2001 From: david Date: Sat, 22 Nov 2025 18:22:12 +0100 Subject: [PATCH] Facturas de cliente --- .../use-change-status-dialog-controller.ts | 6 +--- .../api/issue-proforma-invoice.api.ts | 6 ++-- .../use-issue-proforma-dialog.controller.ts | 33 ++++++++++------- .../proformas/issue-proforma/hooks/index.ts | 1 + .../hooks/use-issue-proforma-mutation.ts | 33 ++++++++++++----- .../ui/issue-proforma-dialog.tsx | 36 +++++++++++-------- .../use-proforma-grid-columns.tsx | 11 ++++-- .../list/ui/pages/proforma-list-page.tsx | 4 +-- 8 files changed, 82 insertions(+), 48 deletions(-) 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 index 0ab44ebc..2f4b1b17 100644 --- 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 @@ -11,7 +11,7 @@ interface ChangeStatusDialogState { } export function useChangeStatusDialogController() { - const { changeStatus } = useChangeProformaStatus(); + const { changeStatus, isPending } = useChangeProformaStatus(); const [state, setState] = React.useState({ open: false, @@ -31,10 +31,6 @@ export function useChangeStatusDialogController() { setState((s) => ({ ...s, open: false })); }; - const setTargetStatus = (status: string | null) => { - setState((s) => ({ ...s, targetStatus: status })); - }; - const confirmChangeStatus = async (status: PROFORMA_STATUS) => { if (!state.proformas.length) return; 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 index 2cfb4008..900b5100 100644 --- 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 @@ -1,6 +1,6 @@ import type { IDataSource } from "@erp/core/client"; -export interface IssueInvoiceResponse { +export interface IssueProformaInvoiceResponse { invoiceId: string; proformaId: string; customerId: string; @@ -9,8 +9,8 @@ export interface IssueInvoiceResponse { export async function issueProformaInvoiceApi( dataSource: IDataSource, proformaId: string -): Promise { - return dataSource.custom({ +): Promise { + return dataSource.custom({ path: `proformas/${proformaId}/issue`, method: "put", data: {}, 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 index a4e53255..8829d0d2 100644 --- 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 @@ -1,3 +1,4 @@ +import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers"; import * as React from "react"; import type { ProformaSummaryData } from "../../types"; @@ -6,19 +7,21 @@ import { useIssueProformaMutation } from "../hooks/use-issue-proforma-mutation"; interface State { open: boolean; proforma: ProformaSummaryData | null; + loading: boolean; } export function useProformaIssueDialogController() { - const { mutate, isPending } = useIssueProformaMutation(); + const { issueProforma, isPending } = useIssueProformaMutation(); const [state, setState] = React.useState({ open: false, proforma: null, + loading: false, }); // abrir diálogo - const openDialog = (p: ProformaSummaryData) => { - setState({ open: true, proforma: p }); + const openDialog = (proforma: ProformaSummaryData) => { + setState({ open: true, proforma: proforma, loading: false }); }; // cerrar diálogo @@ -27,17 +30,23 @@ export function useProformaIssueDialogController() { }; // confirmar emisión - const confirmIssue = () => { + const confirmIssue = async () => { if (!state.proforma) return; - mutate( - { proformaId: state.proforma.id }, - { - onSuccess() { - closeDialog(); - }, - } - ); + setState((s) => ({ ...s, loading: true })); + + await issueProforma(state.proforma.id, { + onSuccess: () => { + showSuccessToast("Proforma emitida a factura"); + }, + onError: (err: unknown) => { + const error = err as Error; + showErrorToast("Error al emitir la proforma a factura", error.message); + }, + }); + + setState((s) => ({ ...s, loading: false })); + closeDialog(); }; return { 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 index e69de29b..fa5a77b0 100644 --- a/modules/customer-invoices/src/web/proformas/issue-proforma/hooks/index.ts +++ b/modules/customer-invoices/src/web/proformas/issue-proforma/hooks/index.ts @@ -0,0 +1 @@ +export * from "./use-issue-proforma-mutation"; 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 index a30ebc52..5888ac5e 100644 --- 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 @@ -1,24 +1,24 @@ import { useDataSource } from "@erp/core/hooks"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query"; -import { issueProformaInvoiceApi } from "../api"; +import { type IssueProformaInvoiceResponse, issueProformaInvoiceApi } from "../api"; interface IssueProformaPayload { proformaId: string; } -interface IssueProformaResponse { - proformaId: string; - success: boolean; +interface IssueProformaOptions { + onSuccess?: () => void; + onError?: (err: unknown) => void; + onLoadingChange?: (loading: boolean) => void; } export function useIssueProformaMutation() { const dataSource = useDataSource(); const queryClient = useQueryClient(); - return useMutation({ - mutationFn: ({ proformaId }: IssueProformaPayload) => - issueProformaInvoiceApi(dataSource, proformaId), + const mutation = useMutation({ + mutationFn: ({ proformaId }) => issueProformaInvoiceApi(dataSource, proformaId), onSuccess(_data, _vars, _ctx) { // Refresca el listado de proformas @@ -28,4 +28,21 @@ export function useIssueProformaMutation() { queryClient.invalidateQueries({ queryKey: ["invoices"] }); }, }); + + async function issueProforma(proformaId: string, opts?: IssueProformaOptions) { + try { + opts?.onLoadingChange?.(true); + await mutation.mutateAsync({ proformaId }); + opts?.onSuccess?.(); + } catch (err) { + opts?.onError?.(err); + } finally { + opts?.onLoadingChange?.(false); + } + } + + return { + issueProforma, + isPending: mutation.isPending, + }; } 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 index 14955214..e6cda30a 100644 --- 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 @@ -9,31 +9,40 @@ import { Spinner, } from "@repo/shadcn-ui/components"; -import { useProformaIssueDialogController } from "../controllers"; +import { useTranslation } from "../../../i18n"; +import type { ProformaSummaryData } from "../../types"; interface ProformaIssueDialogProps { open: boolean; onOpenChange: (open: boolean) => void; - proformaId: string; - proformaReference: string; + proforma: ProformaSummaryData | null; + isSubmitting: boolean; + onConfirm: (proforma?: ProformaSummaryData) => void; } export function ProformaIssueDialog({ open, onOpenChange, - proformaId, - proformaReference, + proforma, + isSubmitting, + onConfirm, }: ProformaIssueDialogProps) { - const { issue, isSubmitting } = useProformaIssueDialogController(); + const { t } = useTranslation(); return ( - Emitir factura - - ¿Seguro que quieres emitir la factura desde la proforma{" "} - {proformaReference}? Esta acción es irreversible. + Emitir a factura + +

+ ¿Seguro que quieres emitir la factura desde la proforma{" "} + {proforma?.reference}? +

+

+ Esta acción creará una nueva factura definitiva y la proforma pasará al estado + "Emitida", no pudiendo modificarse ni eliminarse posteriormente. +

@@ -42,17 +51,14 @@ export function ProformaIssueDialog({ Cancelar - diff --git a/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/use-proforma-grid-columns.tsx b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/use-proforma-grid-columns.tsx index 136ba582..c56d790f 100644 --- a/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/use-proforma-grid-columns.tsx +++ b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/use-proforma-grid-columns.tsx @@ -93,13 +93,18 @@ export function useProformasGridColumns( {/* Enlace discreto a factura real */} - {isIssued && invoiceId && ( + {isIssued && ( - 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 index 44cf7754..c16ee4d2 100644 --- 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 @@ -121,8 +121,7 @@ export const ProformaListPage = () => { onConfirm={issueDialogCtrl.confirmIssue} onOpenChange={(open) => !open && issueDialogCtrl.closeDialog()} open={issueDialogCtrl.open} - proformaId={issueDialogCtrl.proforma?.id ?? 0} - proformaReference={issueDialogCtrl.proforma?.reference ?? ""} + proforma={issueDialogCtrl.proforma} /> {/* Cambiar estado */} @@ -141,6 +140,7 @@ export const ProformaListPage = () => { onOpenChange={(open) => !open && deleteDialogCtrl.closeDialog()} open={deleteDialogCtrl.open} proformas={deleteDialogCtrl.proformas} + requireSecondConfirm={true} />