PROFORMAS CAMBIO DE ESTADO

This commit is contained in:
David Arranz 2026-04-05 22:30:01 +02:00
parent b2dee4ae8b
commit cd0940cb20
15 changed files with 285 additions and 197 deletions

View File

@ -120,6 +120,27 @@
"cancel": "Cancel",
"continue": "Delete",
"list_item": "Proforma {{reference}}"
},
"change_proforma_status_dialog": {
"title": "Change proforma status",
"single_description": "Select the new status for proforma {{reference}}.",
"multiple_description": "Select the new status for {{count}} selected proformas.",
"empty_available_statuses": "No status transitions are available for the selected proformas.",
"cancel": "Cancel",
"confirm": "Change status",
"submitting": "Changing...",
"success_title": "Status updated successfully",
"success_single_description": "Proforma {{reference}} status has been updated successfully.",
"success_multiple_description": "The status of {{count}} proformas has been updated.",
"error_title": "Error changing status",
"error_single_description": "The status of the selected proforma could not be changed.",
"error_multiple_description": "The status of {{count}} proformas could not be changed.",
"error_unexpected_description": "An unexpected error has occurred.",
"legend": {
"current": "Current status",
"available": "Available statuses",
"unavailable": "Unavailable statuses"
}
}
},
"pages": {

View File

@ -121,6 +121,27 @@
"cancel": "Cancelar",
"continue": "Eliminar",
"list_item": "Proforma {{reference}}"
},
"change_proforma_status_dialog": {
"title": "Cambiar estado de la proforma",
"single_description": "Selecciona el nuevo estado para la proforma {{reference}}.",
"multiple_description": "Selecciona el nuevo estado para {{count}} proformas seleccionadas.",
"empty_available_statuses": "No hay transiciones de estado disponibles para las proformas seleccionadas.",
"cancel": "Cancelar",
"confirm": "Cambiar estado",
"submitting": "Cambiando...",
"success_title": "Estado actualizado correctamente",
"success_single_description": "La proforma {{reference}} ha cambiado de estado correctamente.",
"success_multiple_description": "Se ha actualizado el estado de {{count}} proformas.",
"error_title": "Error al cambiar el estado",
"error_single_description": "No se ha podido cambiar el estado de la proforma seleccionada.",
"error_multiple_description": "No se ha podido cambiar el estado de {{count}} proformas.",
"error_unexpected_description": "Ha ocurrido un error inesperado.",
"legend": {
"current": "Estado actual",
"available": "Estados disponibles",
"unavailable": "Estados no disponibles"
}
}
},
"pages": {

View File

@ -1,160 +1,122 @@
// proformas/change-status/controllers/use-change-proforma-status-dialog.controller.ts
import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers";
import * as React from "react";
import { useState } from "react";
import { useTranslation } from "../../../i18n";
import type { ProformaListRow } from "../../shared";
import type { PROFORMA_STATUS } from "../../shared/entities";
import { useChangeProformaStatusMutation } from "../../shared/hooks";
import { type ProformaStatus, useProformaChangeStatusMutation } from "../../shared";
import type { ChangeProformaStatusTarget } from "../entities";
interface ChangeStatusDialogState {
open: boolean;
proformas: ProformaListRow[];
isSubmitting: boolean;
targets: ChangeProformaStatusTarget[];
}
const INITIAL_STATE: ChangeStatusDialogState = {
open: false,
proformas: [],
isSubmitting: false,
targets: [],
};
function canChangeStatus(isSubmitting: boolean, proformas: ProformaListRow[]): boolean {
return !isSubmitting && proformas.length > 0;
}
const canChangeStatus = (targets: ChangeProformaStatusTarget[]) => targets.length > 0;
export function useChangeProformaStatusDialogController() {
export const useChangeProformaStatusDialogController = () => {
const { t } = useTranslation();
const { changeProformaStatus } = useChangeProformaStatusMutation();
const changeStatusMutation = useProformaChangeStatusMutation();
const [state, setState] = React.useState<ChangeStatusDialogState>(INITIAL_STATE);
const { isSubmitting, proformas } = state;
const [state, setState] = useState<ChangeStatusDialogState>(INITIAL_STATE);
const openDialog = (targets: ChangeProformaStatusTarget[]) => {
if (!canChangeStatus(targets)) {
return;
}
const openDialog = React.useCallback((proformas: ProformaListRow[]) => {
setState({
open: true,
proformas,
isSubmitting: false,
targets,
});
}, []);
};
const closeDialog = React.useCallback(() => {
const closeDialog = () => {
setState(INITIAL_STATE);
}, []);
};
const changeStatusSelectedProformas = React.useCallback(
async (proformas: ProformaListRow[], newStatus: PROFORMA_STATUS) => {
const notifyChangeResult = (
targets: ChangeProformaStatusTarget[],
successCount: number,
errorCount: number
) => {
if (targets.length === 1 && successCount === 1) {
const target = targets[0];
showSuccessToast(
t("proformas.change_proforma_status_dialog.success_title"),
t("proformas.change_proforma_status_dialog.success_single_description", {
reference: target.reference || `#${target.id}`,
})
);
} else if (successCount > 0) {
showSuccessToast(
t("proformas.change_proforma_status_dialog.success_title"),
t("proformas.change_proforma_status_dialog.success_multiple_description", {
count: successCount,
})
);
}
if (errorCount > 0) {
showErrorToast(
t("proformas.change_proforma_status_dialog.error_title"),
targets.length === 1
? t("proformas.change_proforma_status_dialog.error_single_description")
: t("proformas.change_proforma_status_dialog.error_multiple_description", {
count: errorCount,
})
);
}
};
const confirmChangeStatus = async (newStatus: ProformaStatus) => {
const targets = state.targets;
if (!canChangeStatus(targets) || changeStatusMutation.isPending) {
return;
}
try {
const results = await Promise.allSettled(
proformas.map((proforma) =>
changeProformaStatus({
proformaId: proforma.id,
newStatus,
targets.map((target) =>
changeStatusMutation.mutateAsync({
id: target.id,
data: {
new_status: newStatus,
},
})
)
);
const successCount = results.filter((result) => result.status === "fulfilled").length;
const errorCount = results.length - successCount;
return {
successCount,
errorCount,
};
},
[changeProformaStatus]
);
notifyChangeResult(targets, successCount, errorCount);
const notifyChangeResult = React.useCallback(
(proformas: ProformaListRow[], successCount: number, errorCount: number) => {
if (proformas.length === 1 && successCount === 1) {
const proforma = proformas[0];
showSuccessToast(
t("pages.proformas.change_status.successTitle"),
t("pages.proformas.change_status.successSingleMessage", {
reference: proforma.reference || `#${proforma.id}`,
})
);
} else if (successCount > 0) {
showSuccessToast(
t("pages.proformas.change_status.successTitle"),
t("pages.proformas.change_status.successMultipleMessage", {
count: successCount,
})
);
if (errorCount === 0) {
closeDialog();
}
if (errorCount > 0) {
showErrorToast(
t("pages.proformas.change_status.errorTitle"),
proformas.length === 1
? t("pages.proformas.change_status.errorSingleMessage")
: t("pages.proformas.change_status.errorMultipleMessage", {
count: errorCount,
})
);
}
},
[t]
);
const changeStatus = React.useCallback(
async (newStatus: PROFORMA_STATUS) => {
setState((current) => ({
...current,
isSubmitting: true,
}));
try {
const { successCount, errorCount } = await changeStatusSelectedProformas(
proformas,
newStatus
);
notifyChangeResult(proformas, successCount, errorCount);
if (errorCount === 0) {
closeDialog();
return;
}
setState((current) => ({
...current,
isSubmitting: false,
}));
} catch {
showErrorToast(
t("pages.proformas.change_status.errorTitle"),
t("pages.proformas.change_status.errorUnexpectedMessage")
);
setState((current) => ({
...current,
isSubmitting: false,
}));
}
},
[closeDialog, changeStatusSelectedProformas, notifyChangeResult, proformas, t]
);
const confirmChangeStatus = React.useCallback(
async (newStatus: PROFORMA_STATUS) => {
if (!canChangeStatus(isSubmitting, proformas)) {
return;
}
await changeStatus(newStatus);
},
[changeStatus, isSubmitting, proformas]
);
} catch {
showErrorToast(
t("proformas.change_proforma_status_dialog.error_title"),
t("proformas.change_proforma_status_dialog.error_unexpected_description")
);
}
};
return {
open: state.open,
proformas: state.proformas,
isSubmitting: state.isSubmitting,
isBulkDelete: state.proformas.length > 1,
targets: state.targets,
isSubmitting: changeStatusMutation.isPending,
isBulkChange: state.targets.length > 1,
openDialog,
closeDialog,
confirmChangeStatus,
};
}
};

View File

@ -0,0 +1,11 @@
import type { ProformaStatus } from "../../shared";
/**
* Representa la proforma a la que se le va a cambiar de estado.
*/
export interface ChangeProformaStatusTarget {
id: string;
reference: string;
status: ProformaStatus;
}

View File

@ -0,0 +1 @@
export * from './change-proforma-status-target.entity';

View File

@ -1,3 +1,4 @@
// proformas/change-status/helpers/proforma-status-ui.ts
import {
CheckCircle2Icon,
FileCheckIcon,

View File

@ -8,21 +8,22 @@ import {
DialogTitle,
Spinner,
} from "@repo/shadcn-ui/components";
import { useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "../../../i18n";
import { PROFORMA_STATUS, PROFORMA_STATUS_TRANSITIONS, type ProformaListRow } from "../../shared";
import { PROFORMA_STATUS, type ProformaStatus } from "../../shared";
import type { ChangeProformaStatusTarget } from "../entities";
import { getProformaStatusIcon } from "../helpers";
import { getCommonAvailableProformaStatuses } from "../utils";
import { StatusNode, TimelineConnector } from "./components";
import { StatusLegend, StatusNode, TimelineConnector } from "./components";
interface ChangeStatusDialogProps {
interface ChangeProformaStatusDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
proformas: ProformaListRow[];
targetStatus?: string;
targets: ChangeProformaStatusTarget[];
isSubmitting: boolean;
onConfirm: (status: PROFORMA_STATUS) => void;
onConfirm: (status: ProformaStatus) => void;
}
const STATUS_FLOW = [
@ -63,84 +64,86 @@ const STATUS_FLOW = [
},
] as const;
export function ChangeStatusDialog({
export const ChangeProformaStatusDialog = ({
open,
onOpenChange,
proformas,
targets,
isSubmitting,
onConfirm,
}: ChangeStatusDialogProps) {
}: ChangeProformaStatusDialogProps) => {
const { t } = useTranslation();
const [selectedStatus, setSelectedStatus] = useState<PROFORMA_STATUS | null>(null);
const [selectedStatus, setSelectedStatus] = useState<ProformaStatus | null>(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));
useEffect(() => {
if (!open) {
setSelectedStatus(null);
}
}, [open]);
return availableStatuses;
};
const availableStatuses = useMemo(() => getCommonAvailableProformaStatuses(targets), [targets]);
const availableStatuses = getAvailableStatuses();
const currentStatus = proformas.length === 1 ? proformas[0].status : null;
const currentStatus = targets.length === 1 ? targets[0].status : null;
const getStatusType = (
status: PROFORMA_STATUS
status: ProformaStatus
): "past" | "current" | "available" | "unavailable" => {
if (currentStatus === status) return "current";
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);
const currentIndex = STATUS_FLOW.findIndex((item) => item.status === currentStatus);
const statusIndex = STATUS_FLOW.findIndex((item) => item.status === status);
if (statusIndex < currentIndex) {
return "past";
}
if (availableStatuses.includes(status)) {
return "available";
}
if (statusIndex < currentIndex) return "past";
if (availableStatuses.includes(status)) return "available";
return "unavailable";
};
return (
<Dialog
onOpenChange={(nextOpen) => {
if (isSubmitting) return;
if (isSubmitting) {
return;
}
onOpenChange(nextOpen);
}}
open={open}
>
<DialogContent className="sm:max-w-4xl">
<DialogHeader>
<DialogTitle>Cambiar estado de la proforma</DialogTitle>
<DialogTitle>{t("proformas.change_proforma_status_dialog.title")}</DialogTitle>
<DialogDescription>
{proformas.length === 1
? `Selecciona el nuevo estado para la proforma #${proformas[0].reference}`
: `Selecciona el nuevo estado para ${proformas.length} proformas seleccionadas`}
{targets.length === 1
? t("proformas.change_proforma_status_dialog.single_description", {
reference: targets[0].reference,
})
: t("proformas.change_proforma_status_dialog.multiple_description", {
count: targets.length,
})}
</DialogDescription>
</DialogHeader>
{availableStatuses.length === 0 ? (
<div className="py-8 text-center text-sm text-muted-foreground">
No hay transiciones de estado disponibles para las proformas seleccionadas.
{t("proformas.change_proforma_status_dialog.empty_available_statuses")}
</div>
) : (
<div className="py-6">
<div className="relative">
<div className="relative grid grid-cols-5 gap-2 mb-8">
{STATUS_FLOW.map((statusConfig, index) => {
<div className="relative mb-8 grid grid-cols-5 gap-2">
{STATUS_FLOW.map((statusConfig) => {
const statusType = getStatusType(statusConfig.status);
const isSelected = selectedStatus === statusConfig.status;
const isClickable = statusType === "available";
@ -176,31 +179,39 @@ export function ChangeStatusDialog({
}))}
/>
</div>
<StatusLegend hasCurrentStatus={currentStatus !== null} />
</div>
)}
<DialogFooter className="sm:justify-between">
<Button disabled={isSubmitting} onClick={() => onOpenChange(false)} variant="outline">
Cancelar
{t("proformas.change_proforma_status_dialog.cancel")}
</Button>
<Button
disabled={!selectedStatus || availableStatuses.length === 0 || isSubmitting}
onClick={() => {
if (!selectedStatus) return;
onConfirm(selectedStatus); // ← enviamos el estado al controlador
if (!selectedStatus) {
return;
}
onConfirm(selectedStatus);
}}
>
{isSubmitting ? (
<>
<Spinner className="mr-2 size-4" />
Cambiando...
<Spinner aria-hidden className="mr-2 size-4" />
<span aria-live="polite">
{t("proformas.change_proforma_status_dialog.submitting")}
</span>
</>
) : (
"Cambiar estado"
t("proformas.change_proforma_status_dialog.confirm")
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
};

View File

@ -1,5 +1,3 @@
"use client";
import { useTranslation } from "../../../../i18n";
interface StatusLegendProps {
@ -8,22 +6,31 @@ interface StatusLegendProps {
export const StatusLegend = ({ hasCurrentStatus }: StatusLegendProps) => {
const { t } = useTranslation();
return (
<div className="mt-8 p-4 bg-muted/50 rounded-lg">
<div className="mt-8 rounded-lg bg-muted/50 p-4">
<div className="flex items-start gap-6 text-sm">
{hasCurrentStatus && (
<div className="flex items-center gap-2">
<div className="size-3 rounded-full bg-amber-500 ring-2 ring-amber-200" />
<span className="text-muted-foreground">Estado actual</span>
<span className="text-muted-foreground">
{t("proformas.change_proforma_status_dialog.legend.current")}
</span>
</div>
)}
<div className="flex items-center gap-2">
<div className="size-3 rounded-full bg-linear-to-r from-blue-400 to-green-400" />
<span className="text-muted-foreground">Estados disponibles (colores originales)</span>
<span className="text-muted-foreground">
{t("proformas.change_proforma_status_dialog.legend.available")}
</span>
</div>
<div className="flex items-center gap-2">
<div className="size-3 rounded-full bg-gray-300 opacity-50" />
<span className="text-muted-foreground">Estados no disponibles</span>
<span className="text-muted-foreground">
{t("proformas.change_proforma_status_dialog.legend.unavailable")}
</span>
</div>
</div>
</div>

View File

@ -1,13 +1,7 @@
"use client";
import { cn } from "@repo/shadcn-ui/lib/utils";
import { CheckCircle2, type LucideIcon } from "lucide-react";
import { useTranslation } from "../../../../i18n";
import type { PROFORMA_STATUS } from "../../../shared";
interface StatusNodeProps {
status: PROFORMA_STATUS;
label: string;
description: string;
icon: LucideIcon;
@ -21,7 +15,6 @@ interface StatusNodeProps {
}
export const StatusNode = ({
status,
label,
description,
icon: Icon,
@ -33,7 +26,6 @@ export const StatusNode = ({
isSelected,
onClick,
}: StatusNodeProps) => {
const { t } = useTranslation();
const isClickable = statusType === "available";
return (
@ -42,7 +34,7 @@ export const StatusNode = ({
className={cn(
"relative z-10 flex size-24 flex-col items-center justify-center rounded-xl border-2 transition-all",
statusType === "past" && ["border-gray-200 bg-gray-50", "opacity-50 saturate-50"],
statusType === "current" && [activeBorder, activeBg, "border-4 scale-110 shadow-lg"],
statusType === "current" && [activeBorder, activeBg, "scale-110 border-4 shadow-lg"],
statusType === "available" && [
activeBorder,
activeBg,
@ -52,7 +44,7 @@ export const StatusNode = ({
],
statusType === "unavailable" && [
"border-gray-200 bg-gray-50",
"opacity-30 saturate-0 cursor-not-allowed",
"cursor-not-allowed opacity-30 saturate-0",
]
)}
disabled={!isClickable}
@ -61,7 +53,7 @@ export const StatusNode = ({
>
<Icon
className={cn(
"size-8 mb-1",
"mb-1 size-8",
statusType === "past" && "text-gray-400",
statusType === "current" && activeColor,
statusType === "available" && activeColor,
@ -70,7 +62,7 @@ export const StatusNode = ({
/>
{statusType === "current" && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2 px-2 py-0.5 rounded-full bg-primary text-primary-foreground text-[10px] font-bold tracking-wider shadow-md">
<div className="absolute -top-3 left-1/2 rounded-full bg-primary px-2 py-0.5 text-[10px] font-bold tracking-wider text-primary-foreground shadow-md -translate-x-1/2">
ACTUAL
</div>
)}
@ -78,7 +70,7 @@ export const StatusNode = ({
{isSelected && statusType !== "current" && (
<div
className={cn(
"absolute -top-2 -right-2 size-6 rounded-full border-2 border-white flex items-center justify-center",
"absolute -top-2 -right-2 flex size-6 items-center justify-center rounded-full border-2 border-white",
activeBg
)}
>
@ -87,7 +79,7 @@ export const StatusNode = ({
)}
</button>
<div className="mt-3 text-center space-y-1 px-1">
<div className="mt-3 space-y-1 px-1 text-center">
<p
className={cn(
"text-sm font-medium",
@ -99,7 +91,8 @@ export const StatusNode = ({
>
{label}
</p>
<p className="text-xs text-muted-foreground line-clamp-2">{description}</p>
<p className="line-clamp-2 text-xs text-muted-foreground">{description}</p>
</div>
</div>
);

View File

@ -0,0 +1,20 @@
import { PROFORMA_STATUS_TRANSITIONS, type ProformaStatus } from "../../shared";
import type { ChangeProformaStatusTarget } from "../entities";
export const getCommonAvailableProformaStatuses = (
targets: ChangeProformaStatusTarget[]
): ProformaStatus[] => {
if (targets.length === 0) {
return [];
}
let availableStatuses = PROFORMA_STATUS_TRANSITIONS[targets[0].status] ?? [];
for (let index = 1; index < targets.length; index += 1) {
const currentStatuses = PROFORMA_STATUS_TRANSITIONS[targets[index].status] ?? [];
availableStatuses = availableStatuses.filter((status) => currentStatuses.includes(status));
}
return availableStatuses;
};

View File

@ -0,0 +1,2 @@
export * from './get-common-available-proforma-statuses';
export * from './prepare-change-proforma-status-targets';

View File

@ -0,0 +1,20 @@
import type { Proforma, ProformaListRow } from "../../shared";
import type { ChangeProformaStatusTarget } from "../entities";
type SupportedSource = Proforma | ProformaListRow;
type SupportedInput = SupportedSource | SupportedSource[];
const mapToChangeProformaStatusTarget = (
proforma: SupportedSource
): ChangeProformaStatusTarget => ({
id: proforma.id,
reference: proforma.reference,
status: proforma.status,
});
export const prepareChangeProformaStatusTargets = (
input: SupportedInput
): ChangeProformaStatusTarget[] => {
const items = Array.isArray(input) ? input : [input];
return items.map(mapToChangeProformaStatusTarget);
};

View File

@ -1,6 +1,7 @@
import type { RightPanelMode } from "@repo/rdx-ui/hooks";
import { useSearchParams } from "react-router-dom";
import { useChangeProformaStatusDialogController } from "../../change-status";
import { useDeleteProformaDialogController } from "../../delete";
import { useIssueProformaDialogController } from "../../issue-proforma";
@ -11,6 +12,7 @@ export const useListProformasPageController = () => {
const listCtrl = useListProformasController();
const deleteDialogCtrl = useDeleteProformaDialogController();
const issueDialogCtrl = useIssueProformaDialogController();
const changeStatusDialogCtrl = useChangeProformaStatusDialogController();
const [searchParams] = useSearchParams();
@ -29,5 +31,6 @@ export const useListProformasPageController = () => {
deleteDialogCtrl,
issueDialogCtrl,
changeStatusDialogCtrl,
};
};

View File

@ -15,6 +15,8 @@ import { FilterIcon, PlusIcon } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "../../../../i18n";
import { ChangeProformaStatusDialog } from "../../../change-status";
import { prepareChangeProformaStatusTargets } from "../../../change-status/utils";
import { DeleteProformaDialog } from "../../../delete";
import { prepareDeleteProformaTargets } from "../../../delete/utils";
import { IssueProformaDialog } from "../../../issue-proforma";
@ -27,7 +29,7 @@ export const ListProformasPage = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const { listCtrl, panelCtrl, deleteDialogCtrl, issueDialogCtrl } =
const { listCtrl, panelCtrl, deleteDialogCtrl, issueDialogCtrl, changeStatusDialogCtrl } =
useListProformasPageController();
const columns = useProformasGridColumns({
@ -36,7 +38,8 @@ export const ListProformasPage = () => {
issueDialogCtrl.openDialog(prepareIssueProformaTarget(proformaRow)),
onDeleteClick: (proformaRow: ProformaListRow) =>
deleteDialogCtrl.openDialog(prepareDeleteProformaTargets(proformaRow)),
//onChangeStatusClick: handleChangeStatusProforma,
onChangeStatusClick: (proforma) =>
changeStatusDialogCtrl.openDialog(prepareChangeProformaStatusTargets(proforma)),
});
const isPanelOpen = panelCtrl.panelState.isOpen;
@ -162,6 +165,18 @@ export const ListProformasPage = () => {
target={issueDialogCtrl.target}
/>
<ChangeProformaStatusDialog
isSubmitting={changeStatusDialogCtrl.isSubmitting}
onConfirm={changeStatusDialogCtrl.confirmChangeStatus}
onOpenChange={(open) => {
if (!open) {
changeStatusDialogCtrl.closeDialog();
}
}}
open={changeStatusDialogCtrl.open}
targets={changeStatusDialogCtrl.targets}
/>
{/* Eliminar */}
<DeleteProformaDialog
isSecondConfirmStep={deleteDialogCtrl.isSecondConfirmStep}

View File

@ -20,7 +20,7 @@ export const useProformaChangeStatusMutation = () => {
const dataSource = useDataSource();
const schema = ChangeStatusProformaByIdRequestSchema;
useMutation<
return useMutation<
ChangeProformaStatusByIdResult,
DefaultError,
ChangeProformaStatusByIdParams,