Facturas de cliente
This commit is contained in:
parent
323860b5b8
commit
7513553f49
@ -11,7 +11,7 @@ interface ChangeStatusDialogState {
|
||||
}
|
||||
|
||||
export function useChangeStatusDialogController() {
|
||||
const { changeStatus } = useChangeProformaStatus();
|
||||
const { changeStatus, isPending } = useChangeProformaStatus();
|
||||
|
||||
const [state, setState] = React.useState<ChangeStatusDialogState>({
|
||||
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;
|
||||
|
||||
|
||||
@ -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<IssueInvoiceResponse> {
|
||||
return dataSource.custom<IssueInvoiceResponse>({
|
||||
): Promise<IssueProformaInvoiceResponse> {
|
||||
return dataSource.custom<IssueProformaInvoiceResponse>({
|
||||
path: `proformas/${proformaId}/issue`,
|
||||
method: "put",
|
||||
data: {},
|
||||
|
||||
@ -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<State>({
|
||||
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 {
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export * from "./use-issue-proforma-mutation";
|
||||
@ -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<IssueProformaInvoiceResponse, DefaultError, IssueProformaPayload>({
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<AlertDialog onOpenChange={onOpenChange} open={open}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Emitir factura</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
¿Seguro que quieres emitir la factura desde la proforma{" "}
|
||||
<strong>{proformaReference}</strong>? Esta acción es irreversible.
|
||||
<AlertDialogTitle>Emitir a factura</AlertDialogTitle>
|
||||
<AlertDialogDescription className="space-y-4">
|
||||
<p>
|
||||
¿Seguro que quieres emitir la factura desde la proforma{" "}
|
||||
<strong>{proforma?.reference}</strong>?
|
||||
</p>
|
||||
<p>
|
||||
Esta acción creará una nueva factura definitiva y la proforma pasará al estado
|
||||
"Emitida", no pudiendo modificarse ni eliminarse posteriormente.
|
||||
</p>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
|
||||
@ -42,17 +51,14 @@ export function ProformaIssueDialog({
|
||||
Cancelar
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
onClick={() => issue(proformaId, () => onOpenChange(false))}
|
||||
>
|
||||
<Button disabled={isSubmitting} onClick={() => onConfirm(proforma)}>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Spinner className="mr-2 size-4" />
|
||||
Emitiendo...
|
||||
</>
|
||||
) : (
|
||||
"Emitir factura"
|
||||
<>Emitir factura</>
|
||||
)}
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
|
||||
@ -93,13 +93,18 @@ export function useProformasGridColumns(
|
||||
<ProformaStatusBadge status={proforma.status} />
|
||||
|
||||
{/* Enlace discreto a factura real */}
|
||||
{isIssued && invoiceId && (
|
||||
{isIssued && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button asChild className="size-6" size="icon" variant="ghost">
|
||||
<Button
|
||||
asChild
|
||||
className="size-6 text-foreground hover:text-primary"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<a href={`/facturas/${invoiceId}`}>
|
||||
<ExternalLinkIcon className="size-3 text-muted-foreground" />
|
||||
<ExternalLinkIcon />
|
||||
<span className="sr-only">Ver factura {invoiceId}</span>
|
||||
</a>
|
||||
</Button>
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
</AppContent>
|
||||
</>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user