Skip to content

Commit

Permalink
feat(*): added an endpoint to update a document's decision (#796)
Browse files Browse the repository at this point in the history
* feat(*): added an endpoint to update a document's decision

* refactor(workflows-service): minimized changes made to decision object on document
  • Loading branch information
Omri-Levy authored Aug 9, 2023
1 parent 1a55535 commit 4c74f7c
Show file tree
Hide file tree
Showing 15 changed files with 326 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { workflowsQueryKeys } from '../../../../workflows/query-keys';
import { Action } from '../../../../../common/enums';

// @TODO: Refactor to be under cases/workflows domain
export const useApproveEntityMutation = ({
export const useApproveCaseMutation = ({
workflowId,
onSelectNextEntity,
}: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import toast from 'react-hot-toast';
import { t } from 'i18next';
import { fetchWorkflowDecision } from '../../../../workflows/fetchers';
import { workflowsQueryKeys } from '../../../../workflows/query-keys';
import { Action } from '../../../../../common/enums';

export const useApproveTaskByIdMutation = (workflowId: string) => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: ({ documentId }: { documentId: string }) =>
fetchWorkflowDecision({
workflowId,
documentId,
body: {
decision: Action.APPROVE.toLowerCase(),
},
}),
onSuccess: () => {
// workflowsQueryKeys._def is the base key for all workflows queries
void queryClient.invalidateQueries(workflowsQueryKeys._def);

toast.success(t('toast:approve_document.success'));
},
onError: () => {
toast.error(t('toast:approve_document.error'));
},
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import toast from 'react-hot-toast';
import { t } from 'i18next';
import { fetchWorkflowDecision } from '../../../../workflows/fetchers';
import { workflowsQueryKeys } from '../../../../workflows/query-keys';
import { Action } from '../../../../../common/enums';

export const useRejectTaskByIdMutation = (workflowId: string) => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: ({ documentId, reason }: { documentId: string; reason?: string }) =>
fetchWorkflowDecision({
workflowId,
documentId,
body: {
decision: Action.REJECT.toLowerCase(),
reason,
},
}),
onSuccess: () => {
// workflowsQueryKeys._def is the base key for all workflows queries
void queryClient.invalidateQueries(workflowsQueryKeys._def);

toast.success(t('toast:reject_document.success'));
},
onError: () => {
toast.error(t('toast:reject_document.error'));
},
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import toast from 'react-hot-toast';
import { t } from 'i18next';
import { fetchWorkflowDecision } from '../../../../workflows/fetchers';
import { workflowsQueryKeys } from '../../../../workflows/query-keys';
import { Action } from '../../../../../common/enums';

export const useRevisionTaskByIdMutation = (workflowId: string) => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: ({ documentId, reason }: { documentId: string; reason?: string }) =>
fetchWorkflowDecision({
workflowId,
documentId,
body: {
decision: Action.REVISION.toLowerCase(),
reason,
},
}),
onSuccess: () => {
// workflowsQueryKeys._def is the base key for all workflows queries
void queryClient.invalidateQueries(workflowsQueryKeys._def);

toast.success(t('toast:ask_revision_document.success'));
},
onError: () => {
toast.error(t('toast:ask_revision_document.error'));
},
});
};
21 changes: 21 additions & 0 deletions apps/backoffice-v2/src/domains/workflows/fetchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,27 @@ export const fetchWorkflowEvent = async ({
return handleZodError(error, workflow);
};

export const fetchWorkflowDecision = async ({
workflowId,
documentId,
body,
}: IWorkflowId & {
documentId: string;
body: {
decision: string;
reason?: string;
};
}) => {
const [workflow, error] = await apiClient({
endpoint: `workflows/${workflowId}/decision/${documentId}`,
method: Method.PATCH,
body,
schema: WorkflowByIdSchema,
});

return handleZodError(error, workflow);
};

export const fetchWorkflowEventDecision = async ({
workflowId,
body,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import { useCallToActionLogic } from './hooks/useCallToActionLogic/useCallToActi

export const CallToAction: FunctionComponent<ICallToActionProps> = ({ value, data }) => {
const {
onMutateUpdateWorkflowById,
isLoadingUpdateWorkflowById,
onMutateTaskDecisionById,
isLoadingTaskDecisionById,
caseState,
action,
actions,
Expand Down Expand Up @@ -119,9 +119,9 @@ export const CallToAction: FunctionComponent<ICallToActionProps> = ({ value, dat
// revisionReason,
// })}
// disabled={!revisionReason}
onClick={onMutateUpdateWorkflowById({
onClick={onMutateTaskDecisionById({
id: data?.id,
approvalStatus: action,
decision: action,
reason: comment ? `${reason} - ${comment}` : reason,
})}
>
Expand All @@ -135,12 +135,12 @@ export const CallToAction: FunctionComponent<ICallToActionProps> = ({ value, dat
<Button
variant={`success`}
className={ctw({
loading: isLoadingUpdateWorkflowById,
loading: isLoadingTaskDecisionById,
})}
disabled={isLoadingUpdateWorkflowById || data?.disabled || !caseState.actionButtonsEnabled}
onClick={onMutateUpdateWorkflowById({
disabled={isLoadingTaskDecisionById || data?.disabled || !caseState.actionButtonsEnabled}
onClick={onMutateTaskDecisionById({
id: data?.id,
approvalStatus: data?.approvalStatus,
decision: data?.decision,
})}
>
{value}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,91 +3,24 @@ import { useFilterId } from '../../../../../../common/hooks/useFilterId/useFilte
import { useWorkflowQuery } from '../../../../../../domains/workflows/hooks/queries/useWorkflowQuery/useWorkflowQuery';
import { useAuthenticatedUserQuery } from '../../../../../../domains/auth/hooks/queries/useAuthenticatedUserQuery/useAuthenticatedUserQuery';
import { useCaseState } from '../../../Case/hooks/useCaseState/useCaseState';
import { useUpdateWorkflowByIdMutation } from '../../../../../../domains/workflows/hooks/mutations/useUpdateWorkflowByIdMutation/useUpdateWorkflowByIdMutation';
import toast from 'react-hot-toast';
import { useCallback, useState } from 'react';
import { useApproveTaskByIdMutation } from '../../../../../../domains/entities/hooks/mutations/useApproveTaskByIdMutation/useApproveTaskByIdMutation';
import { useRejectTaskByIdMutation } from '../../../../../../domains/entities/hooks/mutations/useRejectTaskByIdMutation/useRejectTaskByIdMutation';
import { useRevisionTaskByIdMutation } from '../../../../../../domains/entities/hooks/mutations/useRevisionTaskByIdMutation/useRevisionTaskByIdMutation';

export const useCallToActionLogic = () => {
const { entityId } = useParams();
const filterId = useFilterId();
const { data: workflow } = useWorkflowQuery({ workflowId: entityId, filterId });
const { data: session } = useAuthenticatedUserQuery();
const caseState = useCaseState(session?.user, workflow);
const { mutate: mutateUpdateWorkflowById, isLoading: isLoadingUpdateWorkflowById } =
useUpdateWorkflowByIdMutation({
workflowId: workflow?.id,
});
const onMutateUpdateWorkflowById =
(
payload:
| {
id: string;
approvalStatus: 'approved';
}
| {
id: string;
approvalStatus: 'revision' | 'rejected';
reason: string;
},
) =>
() => {
if (!payload?.id) {
toast.error('Invalid task id');

return;
}

const action = (
{
approved: 'approve_document',
rejected: 'reject_document',
revision: 'ask_revision_document',
} as const
)[payload.approvalStatus];

const context = {
documents: workflow?.context?.documents?.map(document => {
if (document?.id !== payload?.id) return document;

switch (payload?.approvalStatus) {
case 'approved':
return {
...document,
decision: {
revisionReason: null,
rejectionReason: null,
status: payload?.approvalStatus,
},
};
case 'rejected':
return {
...document,
decision: {
revisionReason: null,
// Change when rejection reason is implemented.
rejectionReason: payload?.reason,
status: payload?.approvalStatus,
},
};
case 'revision':
return {
...document,
decision: {
revisionReason: payload?.reason,
rejectionReason: null,
status: payload?.approvalStatus,
},
};
default:
return document;
}
}),
};
return mutateUpdateWorkflowById({
context,
action,
});
};
const { mutate: mutateApproveTaskById, isLoading: isLoadingApproveTaskById } =
useApproveTaskByIdMutation(workflow?.id);
const { mutate: mutateRejectTaskById, isLoading: isLoadingRejectTaskById } =
useRejectTaskByIdMutation(workflow?.id);
const { mutate: mutateRevisionTaskById, isLoading: isLoadingRevisionTaskById } =
useRevisionTaskByIdMutation(workflow?.id);
const revisionReasons =
workflow?.workflowDefinition?.contextSchema?.schema?.properties?.documents?.items?.properties?.decision?.properties?.revisionReason?.anyOf?.find(
({ enum: enum_ }) => !!enum_,
Expand All @@ -103,7 +36,7 @@ export const useCallToActionLogic = () => {
},
{
label: 'Block',
value: 'rejected',
value: 'reject',
},
] as const;
const [action, setAction] = useState<(typeof actions)[number]['value']>(actions[0].value);
Expand All @@ -114,10 +47,56 @@ export const useCallToActionLogic = () => {
const onReasonChange = useCallback((value: string) => setReason(value), [setReason]);
const onActionChange = useCallback((value: typeof action) => setAction(value), [setAction]);
const onCommentChange = useCallback((value: string) => setComment(value), [setComment]);
const isLoadingTaskDecisionById =
isLoadingApproveTaskById || isLoadingRejectTaskById || isLoadingRevisionTaskById;
const onMutateTaskDecisionById = useCallback(
(
payload:
| {
id: string;
decision: 'approve';
}
| {
id: string;
decision: 'reject' | 'revision';
reason?: string;
},
) =>
() => {
if (!payload?.id) {
toast.error('Invalid task id');

return;
}

if (payload?.decision === 'approve') {
return mutateApproveTaskById({
documentId: payload?.id,
});
}

if (payload?.decision === 'reject') {
return mutateRejectTaskById({
documentId: payload?.id,
reason: payload?.reason,
});
}

if (payload?.decision === 'revision') {
return mutateRevisionTaskById({
documentId: payload?.id,
reason: payload?.reason,
});
}

toast.error('Invalid decision');
},
[mutateApproveTaskById, mutateRejectTaskById, mutateRevisionTaskById],
);

return {
onMutateUpdateWorkflowById,
isLoadingUpdateWorkflowById,
onMutateTaskDecisionById,
isLoadingTaskDecisionById,
caseState,
action,
actions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ export interface ICallToActionProps {
data: {
id: string;
disabled: boolean;
approvalStatus: 'rejected' | 'approved' | 'revision';
decision: 'reject' | 'approve' | 'revision';
};
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback } from 'react';
import { useApproveEntityMutation } from '../../../../../../domains/entities/hooks/mutations/useApproveEntityMutation/useApproveEntityMutation';
import { useApproveCaseMutation } from '../../../../../../domains/entities/hooks/mutations/useApproveCaseMutation/useApproveCaseMutation';
import { useDebounce } from '../../../../../../common/hooks/useDebounce/useDebounce';
import { createInitials } from '../../../../../../common/utils/create-initials/create-initials';
import { IUseActions } from './interfaces';
Expand All @@ -23,11 +23,12 @@ export const useActions = ({ workflowId, fullName }: IUseActions) => {
const onSelectNextEntity = useSelectNextEntity();
const filterId = useFilterId();
const { data: workflow, isLoading: isLoadingCase } = useWorkflowQuery({ workflowId, filterId });
const { mutate: mutateApproveEntity, isLoading: isLoadingApproveEntity } =
useApproveEntityMutation({
const { mutate: mutateApproveEntity, isLoading: isLoadingApproveEntity } = useApproveCaseMutation(
{
workflowId: workflowId,
onSelectNextEntity,
});
},
);
const { mutate: mutateRevisionCase, isLoading: isLoadingRevisionCase } = useRevisionCaseMutation({
workflowId: workflowId,
onSelectNextEntity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ export interface ICaseCallToActionProps {
childWorkflowId: string;
childWorkflowContextSchema: TWorkflowById['childWorkflows'][number]['workflowDefinition']['contextSchema'];
disabled: boolean;
approvalStatus: 'rejected' | 'approved' | 'revision';
decision: 'rejected' | 'approved' | 'revision';
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const useTasks = ({
data: {
id,
disabled: !isDoneWithRevision && Boolean(decision?.status),
approvalStatus: 'rejected',
decision: 'reject',
},
},
{
Expand All @@ -119,7 +119,7 @@ export const useTasks = ({
data: {
id,
disabled: !isDoneWithRevision && Boolean(decision?.status),
approvalStatus: 'approved',
decision: 'approve',
},
},
],
Expand Down
Loading

0 comments on commit 4c74f7c

Please sign in to comment.