Añadir funcionalidad para eliminar procesos y mejorar la gestión de procesos en la aplicación
This commit is contained in:
parent
a622eacfe7
commit
564b666fcc
@ -19,6 +19,7 @@ export interface IProcessRepository {
|
|||||||
action: (context: ProcessCatalogContext) => Result<ProcessTemplate, ProcessError>
|
action: (context: ProcessCatalogContext) => Result<ProcessTemplate, ProcessError>
|
||||||
): Promise<Result<{ template: ProcessTemplate; context: ProcessCatalogContext }, ProcessError>>;
|
): Promise<Result<{ template: ProcessTemplate; context: ProcessCatalogContext }, ProcessError>>;
|
||||||
get(company: UniqueID): Promise<Result<ProcessContext, ProcessError>>;
|
get(company: UniqueID): Promise<Result<ProcessContext, ProcessError>>;
|
||||||
|
remove(company: UniqueID, process: UniqueID): Promise<Result<void, ProcessError>>;
|
||||||
save(
|
save(
|
||||||
company: UniqueID,
|
company: UniqueID,
|
||||||
action: (context: ProcessContext) => Result<PersonProcess, ProcessError>
|
action: (context: ProcessContext) => Result<PersonProcess, ProcessError>
|
||||||
|
|||||||
@ -105,6 +105,12 @@ export class ProcessUseCases {
|
|||||||
: values;
|
: values;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
remove(companyId: string, id: string) {
|
||||||
|
const company = UniqueID.create(companyId);
|
||||||
|
const process = UniqueID.create(id);
|
||||||
|
if (!(company.isSuccess && process.isSuccess)) return Promise.resolve(Result.fail(new ProcessError("INVALID_REQUEST")));
|
||||||
|
return this.repository.remove(company.data, process.data);
|
||||||
|
}
|
||||||
update(companyId: string, id: string, input: UpdateProcessInput, actorId: string) {
|
update(companyId: string, id: string, input: UpdateProcessInput, actorId: string) {
|
||||||
const company = UniqueID.create(companyId);
|
const company = UniqueID.create(companyId);
|
||||||
const actor = UniqueID.create(actorId);
|
const actor = UniqueID.create(actorId);
|
||||||
|
|||||||
@ -146,6 +146,15 @@ export class SequelizeProcessRepository implements IProcessRepository {
|
|||||||
return Result.ok({ template: candidate.data, context });
|
return Result.ok({ template: candidate.data, context });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
remove(company: UniqueID, process: UniqueID) {
|
||||||
|
return this.database.transaction(async (transaction) => {
|
||||||
|
const rows = await this.database.query<{ id: string }>("SELECT id FROM person_processes WHERE id=:id AND companyId=:companyId FOR UPDATE", { replacements: { id: process.toPrimitive(), companyId: company.toPrimitive() }, transaction, type: QueryTypes.SELECT });
|
||||||
|
if (!rows.length) return Result.fail(new ProcessError("PROCESS_NOT_FOUND"));
|
||||||
|
await this.database.query("DELETE FROM person_process_revisions WHERE itemId=:id", { replacements: { id: process.toPrimitive() }, transaction });
|
||||||
|
await this.database.query("DELETE FROM person_processes WHERE id=:id AND companyId=:companyId", { replacements: { id: process.toPrimitive(), companyId: company.toPrimitive() }, transaction });
|
||||||
|
return Result.ok(undefined);
|
||||||
|
});
|
||||||
|
}
|
||||||
save(company: UniqueID, action: Parameters<IProcessRepository["save"]>[1]) {
|
save(company: UniqueID, action: Parameters<IProcessRepository["save"]>[1]) {
|
||||||
return this.database.transaction(async (transaction) => {
|
return this.database.transaction(async (transaction) => {
|
||||||
const loaded = await this.load(company, transaction, true);
|
const loaded = await this.load(company, transaction, true);
|
||||||
|
|||||||
@ -327,7 +327,9 @@
|
|||||||
"INTERNAL_ERROR": "The operation could not be completed. Try again.",
|
"INTERNAL_ERROR": "The operation could not be completed. Try again.",
|
||||||
"FORBIDDEN": "Sign in again.",
|
"FORBIDDEN": "Sign in again.",
|
||||||
"UNAUTHENTICATED": "Sign in again."
|
"UNAUTHENTICATED": "Sign in again."
|
||||||
}
|
},
|
||||||
|
"delete": "Delete process",
|
||||||
|
"deleteConfirm": "Do you want to delete the process “{{name}}”? This also deletes its history."
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
"title": "Remote access",
|
"title": "Remote access",
|
||||||
|
|||||||
@ -327,7 +327,9 @@
|
|||||||
"INTERNAL_ERROR": "No se ha podido completar la operación. Inténtalo de nuevo.",
|
"INTERNAL_ERROR": "No se ha podido completar la operación. Inténtalo de nuevo.",
|
||||||
"FORBIDDEN": "Vuelve a iniciar sesión.",
|
"FORBIDDEN": "Vuelve a iniciar sesión.",
|
||||||
"UNAUTHENTICATED": "Vuelve a iniciar sesión."
|
"UNAUTHENTICATED": "Vuelve a iniciar sesión."
|
||||||
}
|
},
|
||||||
|
"delete": "Eliminar proceso",
|
||||||
|
"deleteConfirm": "¿Quieres eliminar el proceso «{{name}}»? Esta acción también elimina su historial."
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
"title": "Accesos remotos",
|
"title": "Accesos remotos",
|
||||||
|
|||||||
@ -4,4 +4,6 @@ export type ProcessDetailProps = {
|
|||||||
process: PersonProcessDTO;
|
process: PersonProcessDTO;
|
||||||
person: string;
|
person: string;
|
||||||
csrfToken: string;
|
csrfToken: string;
|
||||||
|
onDelete?: (id: string) => void;
|
||||||
|
onDeleted?: () => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Button } from "@repo/shadcn-ui/components/button";
|
import { Button } from "@repo/shadcn-ui/components/button";
|
||||||
|
import { useState } from "react";
|
||||||
import { Card, CardContent } from "@repo/shadcn-ui/components/card";
|
import { Card, CardContent } from "@repo/shadcn-ui/components/card";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@ -9,9 +10,10 @@ import { useProcessDetailController } from "../controllers";
|
|||||||
import type { ProcessDetailProps } from "../types";
|
import type { ProcessDetailProps } from "../types";
|
||||||
|
|
||||||
export function ProcessDetail(props: ProcessDetailProps) {
|
export function ProcessDetail(props: ProcessDetailProps) {
|
||||||
const { process, person, csrfToken } = props;
|
const { process, person, csrfToken, onDelete, onDeleted } = props;
|
||||||
const { edit, setEdit, refresh, open, resolved } = useProcessDetailController(props);
|
const { edit, setEdit, refresh, open, resolved } = useProcessDetailController(props);
|
||||||
const { t, i18n } = useTranslation("companies");
|
const { t, i18n } = useTranslation("companies");
|
||||||
|
const [deleting, setDeleting] = useState(false);
|
||||||
return (
|
return (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<header className="flex flex-wrap items-start justify-between gap-3">
|
<header className="flex flex-wrap items-start justify-between gap-3">
|
||||||
@ -39,6 +41,18 @@ export function ProcessDetail(props: ProcessDetailProps) {
|
|||||||
<Button disabled={refresh.isPending} onClick={() => setEdit(true)}>
|
<Button disabled={refresh.isPending} onClick={() => setEdit(true)}>
|
||||||
{t("processes.update")}
|
{t("processes.update")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={deleting}
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => {
|
||||||
|
if (!window.confirm(t("processes.deleteConfirm", { name: process.values.name }))) return;
|
||||||
|
setDeleting(true);
|
||||||
|
onDelete?.(process.id);
|
||||||
|
onDeleted?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("processes.delete")}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@ -4,3 +4,7 @@ import { processRequest } from "../../shared/api/process-request";
|
|||||||
export async function getProcesses(company: string) {
|
export async function getProcesses(company: string) {
|
||||||
return ProcessesResponseSchema.parse(await processRequest(company));
|
return ProcessesResponseSchema.parse(await processRequest(company));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteProcess(company: string, id: string, token: string) {
|
||||||
|
await processRequest(`${company}/${id}/delete`, {}, token);
|
||||||
|
}
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export { getProcesses } from "./get-processes.api";
|
export { deleteProcess, getProcesses } from "./get-processes.api";
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
import { useBrowserSearchParams, useEntityNavigation } from "@metis/core/client";
|
import { useBrowserSearchParams, useEntityNavigation } from "@metis/core/client";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { processKeys } from "../../shared/queries";
|
import { processKeys } from "../../shared/queries";
|
||||||
import { getProcesses } from "../api";
|
import { deleteProcess, getProcesses } from "../api";
|
||||||
import type { ProcessesPageProps } from "../types";
|
import type { ProcessesPageProps } from "../types";
|
||||||
|
|
||||||
export function useProcessesPageController({ company }: ProcessesPageProps) {
|
export function useProcessesPageController({ company }: ProcessesPageProps) {
|
||||||
const { t } = useTranslation("companies");
|
const { t } = useTranslation("companies");
|
||||||
|
const client = useQueryClient();
|
||||||
const [create, setCreate] = useState(false);
|
const [create, setCreate] = useState(false);
|
||||||
const { selectedId: selected, selectEntity: setSelected } = useEntityNavigation(
|
const { selectedId: selected, selectEntity: setSelected } = useEntityNavigation(
|
||||||
`/companies/${company.id}/processes`
|
`/companies/${company.id}/processes`
|
||||||
@ -32,6 +33,7 @@ export function useProcessesPageController({ company }: ProcessesPageProps) {
|
|||||||
params.delete("closed");
|
params.delete("closed");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const remove = useMutation({ mutationFn: ({ id, token }: { id: string; token: string }) => deleteProcess(company.id, id, token), onSuccess: async () => { await client.invalidateQueries({ queryKey: processKeys.company(company.id) }); setSelected(undefined); } });
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: processKeys.company(company.id),
|
queryKey: processKeys.company(company.id),
|
||||||
queryFn: () => getProcesses(company.id),
|
queryFn: () => getProcesses(company.id),
|
||||||
@ -56,6 +58,7 @@ export function useProcessesPageController({ company }: ProcessesPageProps) {
|
|||||||
closed,
|
closed,
|
||||||
setClosed,
|
setClosed,
|
||||||
query,
|
query,
|
||||||
|
remove,
|
||||||
person,
|
person,
|
||||||
current,
|
current,
|
||||||
missingView: Boolean(selected && query.isSuccess && !current),
|
missingView: Boolean(selected && query.isSuccess && !current),
|
||||||
|
|||||||
@ -28,6 +28,7 @@ export function ProcessesPage(props: ProcessesPageProps) {
|
|||||||
closed,
|
closed,
|
||||||
setClosed,
|
setClosed,
|
||||||
query,
|
query,
|
||||||
|
remove,
|
||||||
person,
|
person,
|
||||||
current,
|
current,
|
||||||
missingView,
|
missingView,
|
||||||
@ -66,6 +67,8 @@ export function ProcessesPage(props: ProcessesPageProps) {
|
|||||||
csrfToken={csrfToken}
|
csrfToken={csrfToken}
|
||||||
person={person(current.values.personId)}
|
person={person(current.values.personId)}
|
||||||
process={current}
|
process={current}
|
||||||
|
onDeleted={() => setSelected(undefined)}
|
||||||
|
onDelete={(id) => remove.mutate({ id, token: csrfToken })}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
!selected && (
|
!selected && (
|
||||||
@ -150,10 +153,16 @@ export function ProcessesPage(props: ProcessesPageProps) {
|
|||||||
hideable: false,
|
hideable: false,
|
||||||
render: (p) => (
|
render: (p) => (
|
||||||
<EntityRowActions label={t("actions")}>
|
<EntityRowActions label={t("actions")}>
|
||||||
{" "}
|
<EntityAction onClick={() => setSelected(p.id)}>{t("processes.openDetail")}</EntityAction>
|
||||||
<EntityAction onClick={() => setSelected(p.id)}>
|
<EntityAction
|
||||||
{t("processes.openDetail")}
|
onClick={() => {
|
||||||
</EntityAction>{" "}
|
if (window.confirm(t("processes.deleteConfirm", { name: p.values.name }))) {
|
||||||
|
remove.mutate({ id: p.id, token: csrfToken });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("processes.delete")}
|
||||||
|
</EntityAction>
|
||||||
</EntityRowActions>
|
</EntityRowActions>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export async function processRequest(path: string, body?: unknown, token?: strin
|
|||||||
body === undefined ? {} : { "content-type": "application/json", "x-csrf-token": token ?? "" },
|
body === undefined ? {} : { "content-type": "application/json", "x-csrf-token": token ?? "" },
|
||||||
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
||||||
});
|
});
|
||||||
const data: unknown = await response.json();
|
const data: unknown = response.status === 204 ? undefined : await response.json();
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = z.object({ code: z.string() }).safeParse(data);
|
const error = z.object({ code: z.string() }).safeParse(data);
|
||||||
throw new ProcessApiError(error.success ? error.data.code : "INTERNAL_ERROR");
|
throw new ProcessApiError(error.success ? error.data.code : "INTERNAL_ERROR");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user