PROFORMAS ISSUE
This commit is contained in:
parent
994417e48f
commit
b2dee4ae8b
@ -96,14 +96,30 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"proformas": {
|
"proformas": {
|
||||||
|
"issue_proforma_dialog": {
|
||||||
|
"title": "Issue to invoice",
|
||||||
|
"description": "Are you sure you want to issue an invoice from proforma {{reference}}?",
|
||||||
|
"warning": "This action will create a final invoice and the proforma will be marked as \"Issued\". It will no longer be editable or deletable.",
|
||||||
|
"confirm": "Issue invoice",
|
||||||
|
"submitting": "Issuing...",
|
||||||
|
"success_title": "Proforma successfully issued",
|
||||||
|
"success_description": "Proforma {{reference}} has been successfully issued to an invoice.",
|
||||||
|
"error_title": "Error issuing proforma",
|
||||||
|
"not_allowed_title": "Action not allowed",
|
||||||
|
"not_allowed_description": "Only proformas in \"Approved\" status can be issued.",
|
||||||
|
"unknown_error": "An unexpected error occurred"
|
||||||
|
},
|
||||||
"delete_proforma_dialog": {
|
"delete_proforma_dialog": {
|
||||||
"title": "Delete proforma",
|
"single_title": "Delete proforma {{reference}}",
|
||||||
"description": "Are you sure you want to delete proforma <strong>{{proformaRef}}</strong>? This action cannot be undone.",
|
"single_description": "Are you sure you want to delete this proforma? This action cannot be undone.",
|
||||||
"cancel": "Cancel",
|
"second_confirm_single_title": "Additional confirmation",
|
||||||
"delete": "Delete",
|
"second_confirm_single_description": "You are about to delete the proforma. This action cannot be undone. Are you sure you want to continue?",
|
||||||
|
"multiple_description": "Are you sure you want to delete the {{total}} selected proformas? This action cannot be undone.",
|
||||||
|
"confirm_delete": "Confirm deletion",
|
||||||
"deleting": "Deleting...",
|
"deleting": "Deleting...",
|
||||||
"success_title": "Proforma deleted",
|
"cancel": "Cancel",
|
||||||
"error_title": "Error deleting proforma"
|
"continue": "Delete",
|
||||||
|
"list_item": "Proforma {{reference}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pages": {
|
"pages": {
|
||||||
|
|||||||
@ -96,20 +96,30 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"proformas": {
|
"proformas": {
|
||||||
|
"issue_proforma_dialog": {
|
||||||
|
"title": "Emitir a factura",
|
||||||
|
"description": "¿Seguro que quieres emitir la factura desde la proforma {{reference}}?",
|
||||||
|
"warning": "Esta acción creará una nueva factura definitiva y la proforma pasará al estado \"Emitida\", no pudiendo modificarse ni eliminarse posteriormente.",
|
||||||
|
"confirm": "Emitir factura",
|
||||||
|
"submitting": "Emitiendo...",
|
||||||
|
"success_title": "Proforma emitida correctamente",
|
||||||
|
"success_description": "La proforma {{reference}} se ha emitido a factura correctamente.",
|
||||||
|
"error_title": "Error al emitir la proforma",
|
||||||
|
"not_allowed_title": "Acción no permitida",
|
||||||
|
"not_allowed_description": "Solo se pueden emitir proformas en estado \"Aprobada\".",
|
||||||
|
"unknown_error": "Ha ocurrido un error inesperado",
|
||||||
|
"cancel": "Cancelar"
|
||||||
|
},
|
||||||
"delete_proforma_dialog": {
|
"delete_proforma_dialog": {
|
||||||
"title": "Eliminar proforma",
|
|
||||||
"description": "¿Seguro que deseas eliminar la proforma <strong>{{proformaRef}}</strong>? Esta acción no se puede deshacer.",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"delete": "Eliminar",
|
|
||||||
"deleting": "Eliminando...",
|
|
||||||
"success_title": "Proforma eliminada",
|
|
||||||
"error_title": "Error al eliminar la proforma",
|
|
||||||
"single_title": "Eliminar proforma {{reference}}",
|
"single_title": "Eliminar proforma {{reference}}",
|
||||||
"second_confirm_title": "Confirmación adicional",
|
|
||||||
"multiple_title": "Eliminar {{count}} proformas",
|
|
||||||
"second_confirm_description": "Estás a punto de borrar {{total}} proformas. Esta acción masiva no se puede deshacer. ¿Seguro que quieres continuar?",
|
|
||||||
"single_description": "¿Seguro que deseas eliminar esta proforma? Esta acción no se puede deshacer.",
|
"single_description": "¿Seguro que deseas eliminar esta proforma? Esta acción no se puede deshacer.",
|
||||||
|
"second_confirm_single_title": "Confirmación adicional",
|
||||||
|
"second_confirm_single_description": "Estás a punto de borrar la proforma. Esta acción masiva no se puede deshacer. ¿Seguro que quieres continuar?",
|
||||||
"multiple_description": "¿Seguro que deseas eliminar las {{total}} proformas seleccionadas? Esta acción no se puede deshacer.",
|
"multiple_description": "¿Seguro que deseas eliminar las {{total}} proformas seleccionadas? Esta acción no se puede deshacer.",
|
||||||
|
"confirm_delete": "Confirmar eliminación",
|
||||||
|
"deleting": "Eliminando...",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"continue": "Eliminar",
|
||||||
"list_item": "Proforma {{reference}}"
|
"list_item": "Proforma {{reference}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -59,8 +59,8 @@ export const useDeleteProformaDialogController = () => {
|
|||||||
(targets: DeleteProformaTarget[]) => {
|
(targets: DeleteProformaTarget[]) => {
|
||||||
if (targets.length === 1) {
|
if (targets.length === 1) {
|
||||||
showSuccessToast(
|
showSuccessToast(
|
||||||
t("pages.proformas.delete.successTitle"),
|
t("proformas.delete_proforma_dialog.success_title"),
|
||||||
t("pages.proformas.delete.successSingleMessage", {
|
t("proformas.delete_proforma_dialog.success_single_message", {
|
||||||
reference: getProformaLabel(targets[0]),
|
reference: getProformaLabel(targets[0]),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -68,8 +68,8 @@ export const useDeleteProformaDialogController = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showSuccessToast(
|
showSuccessToast(
|
||||||
t("pages.proformas.delete.successTitle"),
|
t("proformas.delete_proforma_dialog.success_title"),
|
||||||
t("pages.proformas.delete.successMultipleMessage", {
|
t("proformas.delete_proforma_dialog.success_multiple_message", {
|
||||||
count: targets.length,
|
count: targets.length,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -81,15 +81,15 @@ export const useDeleteProformaDialogController = () => {
|
|||||||
(targets: DeleteProformaTarget[], errorCount: number) => {
|
(targets: DeleteProformaTarget[], errorCount: number) => {
|
||||||
if (targets.length === 1) {
|
if (targets.length === 1) {
|
||||||
showErrorToast(
|
showErrorToast(
|
||||||
t("pages.proformas.delete.errorTitle"),
|
t("proformas.delete_proforma_dialog.error_title"),
|
||||||
t("pages.proformas.delete.errorSingleMessage")
|
t("proformas.delete_proforma_dialog.error_single_message")
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
showErrorToast(
|
showErrorToast(
|
||||||
t("pages.proformas.delete.errorTitle"),
|
t("proformas.delete_proforma_dialog.error_title"),
|
||||||
t("pages.proformas.delete.errorMultipleMessage", {
|
t("proformas.delete_proforma_dialog.error_multiple_message", {
|
||||||
count: errorCount,
|
count: errorCount,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -143,8 +143,8 @@ export const useDeleteProformaDialogController = () => {
|
|||||||
await submitDelete();
|
await submitDelete();
|
||||||
} catch {
|
} catch {
|
||||||
showErrorToast(
|
showErrorToast(
|
||||||
t("pages.proformas.delete.errorTitle"),
|
t("proformas.delete.error_title"),
|
||||||
t("pages.proformas.delete.errorUnexpectedMessage")
|
t("proformas.delete.error_unexpected_message")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [deleteMutation.isPending, moveToSecondConfirmStep, state, submitDelete, t]);
|
}, [deleteMutation.isPending, moveToSecondConfirmStep, state, submitDelete, t]);
|
||||||
|
|||||||
@ -39,29 +39,29 @@ export const DeleteProformaDialog = ({
|
|||||||
|
|
||||||
const title = isSecondConfirmStep
|
const title = isSecondConfirmStep
|
||||||
? isSingle
|
? isSingle
|
||||||
? t("components.delete_proforma_dialog.second_confirm_single_title", {
|
? t("proformas.delete_proforma_dialog.second_confirm_single_title", {
|
||||||
reference: getTargetLabel(firstTarget),
|
reference: getTargetLabel(firstTarget),
|
||||||
})
|
})
|
||||||
: t("components.delete_proforma_dialog.second_confirm_multiple_title", {
|
: t("proformas.delete_proforma_dialog.second_confirm_multiple_title", {
|
||||||
count: total,
|
count: total,
|
||||||
})
|
})
|
||||||
: isSingle
|
: isSingle
|
||||||
? t("components.delete_proforma_dialog.single_title", {
|
? t("proformas.delete_proforma_dialog.single_title", {
|
||||||
reference: getTargetLabel(firstTarget),
|
reference: getTargetLabel(firstTarget),
|
||||||
})
|
})
|
||||||
: t("components.delete_proforma_dialog.multiple_title", {
|
: t("proformas.delete_proforma_dialog.multiple_title", {
|
||||||
count: total,
|
count: total,
|
||||||
});
|
});
|
||||||
|
|
||||||
const description = isSecondConfirmStep
|
const description = isSecondConfirmStep
|
||||||
? isSingle
|
? isSingle
|
||||||
? t("components.delete_proforma_dialog.second_confirm_single_description")
|
? t("proformas.delete_proforma_dialog.second_confirm_single_description")
|
||||||
: t("components.delete_proforma_dialog.second_confirm_multiple_description", {
|
: t("proformas.delete_proforma_dialog.second_confirm_multiple_description", {
|
||||||
count: total,
|
count: total,
|
||||||
})
|
})
|
||||||
: isSingle
|
: isSingle
|
||||||
? t("components.delete_proforma_dialog.single_description")
|
? t("proformas.delete_proforma_dialog.single_description")
|
||||||
: t("components.delete_proforma_dialog.multiple_description", {
|
: t("proformas.delete_proforma_dialog.multiple_description", {
|
||||||
count: total,
|
count: total,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ export const DeleteProformaDialog = ({
|
|||||||
<ul className="space-y-1">
|
<ul className="space-y-1">
|
||||||
{targets.map((target) => (
|
{targets.map((target) => (
|
||||||
<li className="text-muted-foreground" key={target.id}>
|
<li className="text-muted-foreground" key={target.id}>
|
||||||
{t("components.delete_proforma_dialog.list_item", {
|
{t("proformas.delete_proforma_dialog.list_item", {
|
||||||
reference: getTargetLabel(target),
|
reference: getTargetLabel(target),
|
||||||
})}
|
})}
|
||||||
</li>
|
</li>
|
||||||
@ -95,19 +95,19 @@ export const DeleteProformaDialog = ({
|
|||||||
|
|
||||||
<AlertDialogFooter className="sm:justify-between">
|
<AlertDialogFooter className="sm:justify-between">
|
||||||
<Button disabled={isSubmitting} onClick={() => onOpenChange(false)} variant="outline">
|
<Button disabled={isSubmitting} onClick={() => onOpenChange(false)} variant="outline">
|
||||||
{t("components.delete_proforma_dialog.cancel")}
|
{t("proformas.delete_proforma_dialog.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button disabled={isSubmitting || total === 0} onClick={onConfirm} variant="destructive">
|
<Button disabled={isSubmitting || total === 0} onClick={onConfirm} variant="destructive">
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<>
|
<>
|
||||||
<Spinner className="mr-2 size-4" />
|
<Spinner className="mr-2 size-4" />
|
||||||
{t("components.delete_proforma_dialog.deleting")}
|
{t("proformas.delete_proforma_dialog.deleting")}
|
||||||
</>
|
</>
|
||||||
) : isSecondConfirmStep ? (
|
) : isSecondConfirmStep ? (
|
||||||
t("components.delete_proforma_dialog.confirm_delete")
|
t("proformas.delete_proforma_dialog.confirm_delete")
|
||||||
) : (
|
) : (
|
||||||
t("components.delete_proforma_dialog.continue")
|
t("proformas.delete_proforma_dialog.continue")
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
|
|||||||
@ -1,61 +1,91 @@
|
|||||||
import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers";
|
import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers";
|
||||||
import * as React from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import type { ProformaSummaryData } from "../../types";
|
import { useTranslation } from "../../../i18n";
|
||||||
import { useIssueProformaMutation } from "../hooks/use-issue-proforma-mutation";
|
import { useProformaIssueMutation } from "../../shared";
|
||||||
|
import type { IssueProformaTarget } from "../entities";
|
||||||
|
|
||||||
interface State {
|
interface IssueDialogState {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
proforma: ProformaSummaryData | null;
|
target: IssueProformaTarget | null;
|
||||||
loading: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useProformaIssueDialogController() {
|
const canIssueProforma = (target: IssueProformaTarget) => target.status === "approved";
|
||||||
const { issueProforma, isPending } = useIssueProformaMutation();
|
|
||||||
|
|
||||||
const [state, setState] = React.useState<State>({
|
export const useIssueProformaDialogController = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const issueMutation = useProformaIssueMutation();
|
||||||
|
|
||||||
|
const [state, setState] = useState<IssueDialogState>({
|
||||||
open: false,
|
open: false,
|
||||||
proforma: null,
|
target: null,
|
||||||
loading: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// abrir diálogo
|
const openDialog = (target: IssueProformaTarget) => {
|
||||||
const openDialog = (proforma: ProformaSummaryData) => {
|
if (!canIssueProforma(target)) {
|
||||||
setState({ open: true, proforma: proforma, loading: false });
|
showErrorToast(
|
||||||
};
|
t("proformas.issue_proforma_dialog.not_allowed_title"),
|
||||||
|
t("proformas.issue_proforma_dialog.not_allowed_description")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// cerrar diálogo
|
setState({
|
||||||
const closeDialog = () => {
|
open: true,
|
||||||
setState((s) => ({ ...s, open: false }));
|
target,
|
||||||
};
|
|
||||||
|
|
||||||
// confirmar emisión
|
|
||||||
const confirmIssue = async () => {
|
|
||||||
if (!state.proforma) return;
|
|
||||||
|
|
||||||
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 }));
|
const closeDialog = () => {
|
||||||
closeDialog();
|
setState((current) => ({
|
||||||
|
...current,
|
||||||
|
open: false,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmIssue = async () => {
|
||||||
|
const target = state.target;
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canIssueProforma(target)) {
|
||||||
|
showErrorToast(
|
||||||
|
t("proformas.issue_proforma_dialog.not_allowed_title"),
|
||||||
|
t("proformas.issue_proforma_dialog.not_allowed_description")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await issueMutation.mutateAsync({ id: target.id });
|
||||||
|
|
||||||
|
showSuccessToast(
|
||||||
|
t("proformas.issue_proforma_dialog.success_title"),
|
||||||
|
t("proformas.issue_proforma_dialog.success_description", {
|
||||||
|
reference: target.reference,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setState({
|
||||||
|
open: false,
|
||||||
|
target: null,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const message =
|
||||||
|
error instanceof Error ? error.message : t("proformas.issue_proforma_dialog.unknown_error");
|
||||||
|
|
||||||
|
showErrorToast(t("proformas.issue_proforma_dialog.error_title"), message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
open: state.open,
|
open: state.open,
|
||||||
proforma: state.proforma,
|
target: state.target,
|
||||||
isSubmitting: isPending,
|
isSubmitting: issueMutation.isPending,
|
||||||
|
|
||||||
openDialog,
|
openDialog,
|
||||||
closeDialog,
|
closeDialog,
|
||||||
confirmIssue,
|
confirmIssue,
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './issue-proforma-target.entity';
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import type { ProformaStatus } from "../../shared";
|
||||||
|
|
||||||
|
// proformas/issue/entities/issue-proforma-target.entity.ts
|
||||||
|
export interface IssueProformaTarget {
|
||||||
|
id: string;
|
||||||
|
reference: string;
|
||||||
|
status: ProformaStatus;
|
||||||
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./use-issue-proforma-mutation";
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
import { useDataSource } from "@erp/core/hooks";
|
|
||||||
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
||||||
|
|
||||||
import { type IssueProformaInvoiceResponse, issueProformaInvoiceApi } from "../api";
|
|
||||||
|
|
||||||
interface IssueProformaPayload {
|
|
||||||
proformaId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IssueProformaOptions {
|
|
||||||
onSuccess?: () => void;
|
|
||||||
onError?: (err: unknown) => void;
|
|
||||||
onLoadingChange?: (loading: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useIssueProformaMutation() {
|
|
||||||
const dataSource = useDataSource();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const mutation = useMutation<IssueProformaInvoiceResponse, DefaultError, IssueProformaPayload>({
|
|
||||||
mutationFn: ({ proformaId }) => 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"] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,2 +1,3 @@
|
|||||||
export * from "./controllers";
|
export * from "./controllers";
|
||||||
export * from "./ui";
|
export * from "./ui";
|
||||||
|
export * from "./utils";
|
||||||
|
|||||||
@ -10,59 +10,58 @@ import {
|
|||||||
} from "@repo/shadcn-ui/components";
|
} from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
import { useTranslation } from "../../../i18n";
|
import { useTranslation } from "../../../i18n";
|
||||||
import type { ProformaSummaryData } from "../../types";
|
import type { IssueProformaTarget } from "../entities";
|
||||||
|
|
||||||
interface ProformaIssueDialogProps {
|
interface IssueProformaDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
proforma: ProformaSummaryData | null;
|
target: IssueProformaTarget | null;
|
||||||
isSubmitting: boolean;
|
isSubmitting: boolean;
|
||||||
onConfirm: (proforma?: ProformaSummaryData) => void;
|
onConfirm: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProformaIssueDialog({
|
export const IssueProformaDialog = ({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
proforma,
|
target,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
}: ProformaIssueDialogProps) {
|
}: IssueProformaDialogProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertDialog onOpenChange={onOpenChange} open={open}>
|
<AlertDialog onOpenChange={onOpenChange} open={open}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Emitir a factura</AlertDialogTitle>
|
<AlertDialogTitle>{t("proformas.issue_proforma_dialog.title")}</AlertDialogTitle>
|
||||||
|
|
||||||
<AlertDialogDescription className="space-y-4">
|
<AlertDialogDescription className="space-y-4">
|
||||||
<p>
|
{t("proformas.issue_proforma_dialog.description", {
|
||||||
¿Seguro que quieres emitir la factura desde la proforma{" "}
|
reference: target?.reference ?? "",
|
||||||
<strong>{proforma?.reference}</strong>?
|
})}
|
||||||
</p>
|
<br />
|
||||||
<p>
|
<br />
|
||||||
Esta acción creará una nueva factura definitiva y la proforma pasará al estado
|
{t("proformas.issue_proforma_dialog.warning")}
|
||||||
"Emitida", no pudiendo modificarse ni eliminarse posteriormente.
|
|
||||||
</p>
|
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
|
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<Button disabled={isSubmitting} onClick={() => onOpenChange(false)} variant="outline">
|
<Button disabled={isSubmitting} onClick={() => onOpenChange(false)} variant="outline">
|
||||||
Cancelar
|
{t("proformas.issue_proforma_dialog.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button disabled={isSubmitting} onClick={() => onConfirm(proforma)}>
|
<Button disabled={isSubmitting} onClick={onConfirm}>
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<>
|
<>
|
||||||
<Spinner className="mr-2 size-4" />
|
<Spinner aria-hidden className="mr-2 size-4" />
|
||||||
Emitiendo...
|
<span aria-live="polite">{t("proformas.issue_proforma_dialog.submitting")}</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>Emitir factura</>
|
t("proformas.issue_proforma_dialog.confirm")
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './prepare-issue-proforma-targets';
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
// proformas/issue/utils/prepare-issue-proforma-targets.ts
|
||||||
|
import type { Proforma, ProformaListRow } from "../../shared";
|
||||||
|
import type { IssueProformaTarget } from "../entities";
|
||||||
|
|
||||||
|
type SupportedIssueSource = Proforma | ProformaListRow;
|
||||||
|
type SupportedIssueInput = SupportedIssueSource | SupportedIssueSource[];
|
||||||
|
|
||||||
|
const mapToIssueProformaTarget = (proforma: SupportedIssueSource): IssueProformaTarget => ({
|
||||||
|
id: proforma.id,
|
||||||
|
reference: proforma.reference,
|
||||||
|
status: proforma.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const prepareIssueProformaTargets = (input: SupportedIssueInput): IssueProformaTarget[] => {
|
||||||
|
const items = Array.isArray(input) ? input : [input];
|
||||||
|
return items.map(mapToIssueProformaTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const prepareIssueProformaTarget = (
|
||||||
|
input: Proforma | ProformaListRow
|
||||||
|
): IssueProformaTarget => ({
|
||||||
|
id: input.id,
|
||||||
|
reference: input.reference,
|
||||||
|
status: input.status,
|
||||||
|
});
|
||||||
@ -2,6 +2,7 @@ import type { RightPanelMode } from "@repo/rdx-ui/hooks";
|
|||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
import { useDeleteProformaDialogController } from "../../delete";
|
import { useDeleteProformaDialogController } from "../../delete";
|
||||||
|
import { useIssueProformaDialogController } from "../../issue-proforma";
|
||||||
|
|
||||||
import { useListProformasController } from "./use-list-proformas.controller";
|
import { useListProformasController } from "./use-list-proformas.controller";
|
||||||
import { useProformaSummaryPanelController } from "./use-proforma-summary-panel.controller";
|
import { useProformaSummaryPanelController } from "./use-proforma-summary-panel.controller";
|
||||||
@ -9,6 +10,7 @@ import { useProformaSummaryPanelController } from "./use-proforma-summary-panel.
|
|||||||
export const useListProformasPageController = () => {
|
export const useListProformasPageController = () => {
|
||||||
const listCtrl = useListProformasController();
|
const listCtrl = useListProformasController();
|
||||||
const deleteDialogCtrl = useDeleteProformaDialogController();
|
const deleteDialogCtrl = useDeleteProformaDialogController();
|
||||||
|
const issueDialogCtrl = useIssueProformaDialogController();
|
||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
@ -26,5 +28,6 @@ export const useListProformasPageController = () => {
|
|||||||
panelCtrl,
|
panelCtrl,
|
||||||
|
|
||||||
deleteDialogCtrl,
|
deleteDialogCtrl,
|
||||||
|
issueDialogCtrl,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -53,6 +53,7 @@ export const ProformasGrid = ({
|
|||||||
manualPagination
|
manualPagination
|
||||||
onPageChange={onPageChange}
|
onPageChange={onPageChange}
|
||||||
onPageSizeChange={onPageSizeChange}
|
onPageSizeChange={onPageSizeChange}
|
||||||
|
//onRowClick={(row) => onRowClick?.(row.id)}
|
||||||
pageIndex={pageIndex}
|
pageIndex={pageIndex}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
totalItems={totalItems}
|
totalItems={totalItems}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import * as React from "react";
|
|||||||
|
|
||||||
import { useTranslation } from "../../../../../i18n";
|
import { useTranslation } from "../../../../../i18n";
|
||||||
import {
|
import {
|
||||||
|
PROFORMA_STATUS,
|
||||||
PROFORMA_STATUS_TRANSITIONS,
|
PROFORMA_STATUS_TRANSITIONS,
|
||||||
type ProformaListRow,
|
type ProformaListRow,
|
||||||
type ProformaStatus,
|
type ProformaStatus,
|
||||||
@ -227,12 +228,12 @@ export function useProformasGridColumns(
|
|||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const proforma = row.original;
|
const proforma = row.original;
|
||||||
const isIssued = proforma.status === "issued";
|
const isIssued = proforma.status === PROFORMA_STATUS.ISSUED;
|
||||||
const isApproved = proforma.status === "approved";
|
const isApproved = proforma.status === PROFORMA_STATUS.APPROVED;
|
||||||
const availableTransitions =
|
const availableTransitions =
|
||||||
PROFORMA_STATUS_TRANSITIONS[proforma.status as ProformaStatus] ?? [];
|
PROFORMA_STATUS_TRANSITIONS[proforma.status as ProformaStatus] ?? [];
|
||||||
|
|
||||||
console.log(availableTransitions);
|
console.log(availableTransitions, proforma.status);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@ -280,7 +281,7 @@ export function useProformasGridColumns(
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Emitir factura: solo si approved */}
|
{/* Emitir factura: solo si approved */}
|
||||||
{!isIssued && proforma.status === "approved" && actionHandlers.onIssueClick && (
|
{!isIssued && isApproved && actionHandlers.onIssueClick && (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
|
|||||||
@ -17,6 +17,8 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { useTranslation } from "../../../../i18n";
|
import { useTranslation } from "../../../../i18n";
|
||||||
import { DeleteProformaDialog } from "../../../delete";
|
import { DeleteProformaDialog } from "../../../delete";
|
||||||
import { prepareDeleteProformaTargets } from "../../../delete/utils";
|
import { prepareDeleteProformaTargets } from "../../../delete/utils";
|
||||||
|
import { IssueProformaDialog } from "../../../issue-proforma";
|
||||||
|
import { prepareIssueProformaTarget } from "../../../issue-proforma/utils";
|
||||||
import type { ProformaListRow } from "../../../shared";
|
import type { ProformaListRow } from "../../../shared";
|
||||||
import { useListProformasPageController } from "../../controllers";
|
import { useListProformasPageController } from "../../controllers";
|
||||||
import { ProformaSummaryPanel, ProformasGrid, useProformasGridColumns } from "../blocks";
|
import { ProformaSummaryPanel, ProformasGrid, useProformasGridColumns } from "../blocks";
|
||||||
@ -25,16 +27,15 @@ export const ListProformasPage = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { listCtrl, panelCtrl, deleteDialogCtrl } = useListProformasPageController();
|
const { listCtrl, panelCtrl, deleteDialogCtrl, issueDialogCtrl } =
|
||||||
|
useListProformasPageController();
|
||||||
const handleDeleteProforma = (proformaRow: ProformaListRow) => {
|
|
||||||
deleteDialogCtrl.openDialog(prepareDeleteProformaTargets(proformaRow));
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns = useProformasGridColumns({
|
const columns = useProformasGridColumns({
|
||||||
onEditClick: (proforma) => navigate(`/proformas/${proforma.id}/edit`),
|
onEditClick: (proforma) => navigate(`/proformas/${proforma.id}/edit`),
|
||||||
//onIssueClick: handleIssueProforma,
|
onIssueClick: (proformaRow) =>
|
||||||
onDeleteClick: handleDeleteProforma,
|
issueDialogCtrl.openDialog(prepareIssueProformaTarget(proformaRow)),
|
||||||
|
onDeleteClick: (proformaRow: ProformaListRow) =>
|
||||||
|
deleteDialogCtrl.openDialog(prepareDeleteProformaTargets(proformaRow)),
|
||||||
//onChangeStatusClick: handleChangeStatusProforma,
|
//onChangeStatusClick: handleChangeStatusProforma,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -148,6 +149,19 @@ export const ListProformasPage = () => {
|
|||||||
<div className="flex min-h-0 flex-1 overflow-hidden">{listContent}</div>
|
<div className="flex min-h-0 flex-1 overflow-hidden">{listContent}</div>
|
||||||
)}
|
)}
|
||||||
<>
|
<>
|
||||||
|
{/* Issue */}
|
||||||
|
<IssueProformaDialog
|
||||||
|
isSubmitting={issueDialogCtrl.isSubmitting}
|
||||||
|
onConfirm={issueDialogCtrl.confirmIssue}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
|
issueDialogCtrl.closeDialog();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
open={issueDialogCtrl.open}
|
||||||
|
target={issueDialogCtrl.target}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Eliminar */}
|
{/* Eliminar */}
|
||||||
<DeleteProformaDialog
|
<DeleteProformaDialog
|
||||||
isSecondConfirmStep={deleteDialogCtrl.isSecondConfirmStep}
|
isSecondConfirmStep={deleteDialogCtrl.isSecondConfirmStep}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
export * from "./keys";
|
export * from "./keys";
|
||||||
|
export * from "./use-proforma-change-status-mutation";
|
||||||
export * from "./use-proforma-create-mutation";
|
export * from "./use-proforma-create-mutation";
|
||||||
export * from "./use-proforma-delete-mutation";
|
export * from "./use-proforma-delete-mutation";
|
||||||
export * from "./use-proforma-get-query";
|
export * from "./use-proforma-get-query";
|
||||||
|
export * from "./use-proforma-issue-mutation";
|
||||||
export * from "./use-proforma-update-mutation";
|
export * from "./use-proforma-update-mutation";
|
||||||
export * from "./use-proformas-list-query";
|
export * from "./use-proformas-list-query";
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export const useProformaIssueMutation = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const dataSource = useDataSource();
|
const dataSource = useDataSource();
|
||||||
|
|
||||||
useMutation<
|
return useMutation<
|
||||||
IssueProformaByIdResult,
|
IssueProformaByIdResult,
|
||||||
DefaultError,
|
DefaultError,
|
||||||
IssueProformaByIdParams,
|
IssueProformaByIdParams,
|
||||||
|
|||||||
@ -192,16 +192,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"delete_proforma_dialog": {
|
|
||||||
"single_title": "Eliminar proforoma",
|
|
||||||
"single_description": "",
|
|
||||||
"second_confirm_single_title": "Confirmar eliminación",
|
|
||||||
"second_confirm_single_description": "",
|
|
||||||
"confirm_delete": "Confirmar eliminación",
|
|
||||||
"deleting": "Eliminando...",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"continue": "Eliminar"
|
|
||||||
},
|
|
||||||
"entity_selector": {
|
"entity_selector": {
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"select_entity": "Select entity",
|
"select_entity": "Select entity",
|
||||||
|
|||||||
@ -194,18 +194,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"delete_proforma_dialog": {
|
|
||||||
"single_title": "Eliminar proforoma",
|
|
||||||
"single_description": "",
|
|
||||||
"second_confirm_single_title": "Confirmar eliminación",
|
|
||||||
"second_confirm_single_description": "",
|
|
||||||
"multiple_description": "",
|
|
||||||
"confirm_delete": "Confirmar eliminación",
|
|
||||||
"deleting": "Eliminando...",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"continue": "Eliminar",
|
|
||||||
"list_item": ""
|
|
||||||
},
|
|
||||||
"entity_selector": {
|
"entity_selector": {
|
||||||
"close": "Cerrar",
|
"close": "Cerrar",
|
||||||
"select_entity": "Seleccionar entidad",
|
"select_entity": "Seleccionar entidad",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user