Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/backoffice-v2/public/locales/en/toast.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,5 +160,8 @@
"success": "Assessment status updated successfully.",
"unexpected_error": "Something went wrong while updating the status of the assessment.",
"error": "Error occurred while updating assessment status."
},
"common": {
"unexpected_error": "Something went wrong. Please try again later."
}
}
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 @@ -398,3 +398,24 @@ export const fetchWorkflowDocumentOCRResult = async ({

return handleZodError(error, workflow);
};

export const fetchCaseRevisionForDocuments = async ({
workflowId,
documentIds,
}: {
workflowId: string;
documentIds: string[];
}) => {
const [workflow, error] = await apiClient({
method: Method.POST,
url: `${getOriginUrl(
env.VITE_API_URL,
)}/api/v1/case-management/workflows/${workflowId}/revision`,
schema: z.any(),
body: {
documentIds,
},
});

return handleZodError(error, workflow);
};
Original file line number Diff line number Diff line change
@@ -1,35 +1,26 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Action } from '../../../../../common/enums';
import { toast } from 'sonner';
import { t } from 'i18next';
import { fetchWorkflowEvent } from '../../../fetchers';
import { fetchCaseRevisionForDocuments } from '../../../fetchers';
import { workflowsQueryKeys } from '../../../query-keys';
import { collectionFlowQueryKeys } from '@/domains/collection-flow/query-keys';
import { documentsQueryKeys } from '@/domains/documents/hooks/query-keys';

export const useRevisionCaseMutation = ({
onSelectNextCase,
}: {
onSelectNextCase?: VoidFunction;
}) => {
export const useRevisionCaseMutation = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: ({ workflowId }: { workflowId: string }) =>
fetchWorkflowEvent({
mutationFn: ({ workflowId, documentIds }: { workflowId: string; documentIds: string[] }) =>
fetchCaseRevisionForDocuments({
workflowId,
body: {
name: Action.REVISION,
},
documentIds,
}),
onSuccess: () => {
// workflowsQueryKeys._def is the base key for all workflows queries
void queryClient.invalidateQueries(workflowsQueryKeys._def);
void queryClient.invalidateQueries(collectionFlowQueryKeys._def);
void queryClient.invalidateQueries(documentsQueryKeys._def);

toast.success(t(`toast:ask_revision_case.success`));

// TODO: Re-implement
// onSelectNextEntity();
},
onError: () => {
toast.error(t(`toast:ask_revision_case.error`));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ export const useDefaultActionsLogic = () => {
workflowId: workflow?.id,
onSelectNextCase,
});
const { mutate: mutateRevisionCase, isLoading: isLoadingRevisionCase } = useRevisionCaseMutation({
onSelectNextCase,
});
const { mutate: mutateRevisionCase, isLoading: isLoadingRevisionCase } =
useRevisionCaseMutation();

const { isLoading: isLoadingAssignWorkflow } = useAssignWorkflowMutation({
workflowRuntimeId: workflow?.id,
Expand All @@ -50,15 +49,24 @@ export const useDefaultActionsLogic = () => {
const onMutateApproveCase = useCallback(() => mutateApproveCase(), [mutateApproveCase]);
const onMutateRejectCase = useCallback(() => mutateRejectCase(), [mutateRejectCase]);

const { onMutateRevisionCase } = usePendingRevisionEvents(mutateRevisionCase, workflow);

const { documents } = useDocuments(workflow as TWorkflowById);

const documentsToReviseCount = useMemo(
() => [...documents].filter(document => document?.decision?.status === 'revision').length,
const documentsUnderRevision = useMemo(
() => documents?.filter(document => document?.decision?.status === 'revision'),
[documents],
);

const documentsUnderRevisionIds = useMemo(
() =>
documentsUnderRevision?.filter(document => document?.id).map(document => document.id) || [],
[documentsUnderRevision],
);

const { onMutateRevisionCase } = usePendingRevisionEvents({
mutateRevisionCase,
workflow,
documentIds: documentsUnderRevisionIds,
});

// Only display the button spinners if the request is longer than 300ms
const debouncedIsLoadingRejectCase = useDebounce(isLoadingRejectCase, 300);
const debouncedIsLoadingRevisionCase = useDebounce(isLoadingRevisionCase, 300);
Expand All @@ -70,7 +78,7 @@ export const useDefaultActionsLogic = () => {
canRevision &&
workflow?.tags?.some(tag => [StateTag.MANUAL_REVIEW, StateTag.PENDING_PROCESS].includes(tag)),
debouncedIsLoadingRejectCase,
documentsToReviseCount,
documentsToReviseCount: documentsUnderRevision?.length ?? 0,
debouncedIsLoadingRevisionCase,
onMutateRevisionCase,
onMutateRejectCase,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,44 @@ import { useCallback } from 'react';
import { buildCollectionFlowUrl, CommonWorkflowEvent } from '@ballerine/common';
import { checkIsKybExampleVariant } from '@/lib/blocks/variants/variant-checkers';
import { useRevisionCaseMutation } from '@/domains/workflows/hooks/mutations/useRevisionCaseMutation/useRevisionCaseMutation';
import { toast } from 'sonner';
import { t } from 'i18next';

export const usePendingRevisionEvents = (
mutateRevisionCase: ReturnType<typeof useRevisionCaseMutation>['mutate'],
workflow?: TWorkflowById,
) => {
export const usePendingRevisionEvents = ({
mutateRevisionCase,
workflow,
documentIds,
}: {
mutateRevisionCase: ReturnType<typeof useRevisionCaseMutation>['mutate'];
workflow?: TWorkflowById;
documentIds: string[];
}) => {
const onMutateRevisionCase = useCallback(() => {
if (!workflow) {
console.error('Workflow not found.');
toast.error(t('toast:common.unexpected_error'));
return;
}

if (!workflow?.nextEvents?.some(nextEvent => nextEvent === CommonWorkflowEvent.REVISION)) {
console.error('Workflow does not have a revision event.');
toast.error(t('toast:common.unexpected_error'));
return;
}

if (!documentIds?.length) {
console.error('No document IDs provided.');
toast.error(t('toast:common.unexpected_error'));
return;
}

mutateRevisionCase({ workflowId: workflow?.id });
mutateRevisionCase({ workflowId: workflow.id, documentIds });

const isKybExampleVariant = checkIsKybExampleVariant(workflow?.workflowDefinition);

if (!isKybExampleVariant) {
console.error('Workflow is not a KYB example variant.');
toast.error(t('toast:common.unexpected_error'));
return;
}

Expand All @@ -27,7 +50,7 @@ export const usePendingRevisionEvents = (
token: workflow?.context?.metadata?.token,
}),
);
}, [mutateRevisionCase, workflow]);
}, [mutateRevisionCase, workflow, documentIds]);

return { onMutateRevisionCase };
};
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ export const EntityFieldGroup: TDynamicFormField<IEntityFieldGroupParams> = ({

useEffect(() => {
void createEntitiesCreationTaskOnChange();

return () => {
removeTask(element.id);
};
}, [value, documents, files.files, createEntitiesCreationTaskOnChange]);

if (hidden) {
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion services/workflows-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"prod:worker": "npm run db:migrate-up && npm run start:prod:worker",
"prod:next": "npm run db:migrate-up && npm run db:data-sync && npm run start:prod",
"start:watch": "nest start --watch",
"start:debug": "nest start --debug",
"start:debug": "nest start --debug --watch",
"build": "nest build --path=tsconfig.build.json",
"test": "jest --runInBand",
"test:unit": "cross-env SKIP_DB_SETUP_TEARDOWN=true jest --testRegex '.*\\.unit\\.test\\.ts$'",
Expand Down
2 changes: 1 addition & 1 deletion services/workflows-service/prisma/data-migrations
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Document" ADD COLUMN "deletedAt" TIMESTAMP(3);

-- AlterTable
ALTER TABLE "DocumentFile" ADD COLUMN "deletedAt" TIMESTAMP(3);
10 changes: 6 additions & 4 deletions services/workflows-service/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -1016,8 +1016,9 @@ model Document {
projectId String
project Project @relation(fields: [projectId], references: [id])

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?

@@index([businessId])
@@index([endUserId])
Expand Down Expand Up @@ -1062,8 +1063,9 @@ model DocumentFile {
projectId String
project Project @relation(fields: [projectId], references: [id])

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?

@@index([documentId])
@@index([fileId])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Module } from '@nestjs/common';
import { AlertModule } from '@/alert/alert.module';
import { EndUserModule } from '@/end-user/end-user.module';
import { UiDefinitionModule } from '@/ui-definition/ui-definition.module';
import { DocumentFileModule } from '@/document-file/document-file.module';

@Module({
imports: [
Expand All @@ -16,6 +17,7 @@ import { UiDefinitionModule } from '@/ui-definition/ui-definition.module';
EndUserModule,
AlertModule,
UiDefinitionModule,
DocumentFileModule,
],
providers: [CaseManagementService],
controllers: [CaseManagementController],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ValidationError } from '@/errors';
import { TProjectId, TProjectIds } from '@/types';
import { PrismaTransaction, TProjectId, TProjectIds } from '@/types';
import { WorkflowDefinitionService } from '@/workflow-defintion/workflow-definition.service';
import { WorkflowRunDto } from '@/workflow/dtos/workflow-run';
import { ajv } from '@/common/ajv/ajv.validator';
Expand All @@ -12,6 +12,12 @@ import { randomUUID } from 'crypto';
import { BusinessPosition } from '@prisma/client';
import { ARRAY_MERGE_OPTION, BUILT_IN_EVENT } from '@ballerine/workflow-core';
import { UboToEntityAdapter } from './types';
import { assertIsValidProjectIds } from '@/project/project-scope.service';
import { DocumentFileService } from '@/document-file/document-file.service';
import {
beginTransactionIfNotExistCurry,
defaultPrismaTransactionOptions,
} from '@/prisma/prisma.util';

@Injectable()
export class CaseManagementService {
Expand All @@ -20,6 +26,7 @@ export class CaseManagementService {
protected readonly workflowService: WorkflowService,
protected readonly prismaService: PrismaService,
protected readonly endUserService: EndUserService,
protected readonly documentFileService: DocumentFileService,
) {}

async create(
Expand Down Expand Up @@ -258,4 +265,55 @@ export class CaseManagementService {
});
});
}

async caseRevision(
workflowId: string,
documentIds: string[],
projectIds: TProjectIds,
transaction: PrismaTransaction = this.prismaService,
) {
assertIsValidProjectIds(projectIds);

const beginTransaction = beginTransactionIfNotExistCurry({
prismaService: this.prismaService,
options: defaultPrismaTransactionOptions,
transaction,
});

return beginTransaction(async transaction => {
const deletedDocumentFiles = await this.removeFilesFromDocuments(
documentIds,
projectIds,
transaction,
);

await this.workflowService.event(
{
id: workflowId,
name: 'revision',
},
projectIds,
projectIds[0]!,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validate over null assertion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transaction,
);

return deletedDocumentFiles;
});
}

private async removeFilesFromDocuments(
documentIds: string[],
projectIds: TProjectIds,
transaction: PrismaTransaction,
) {
assertIsValidProjectIds(projectIds);

const deletedDocumentFiles = await this.documentFileService.deleteManyByDocumentIds(
documentIds,
projectIds,
transaction,
);

return deletedDocumentFiles;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type { Business, EndUsersOnBusinesses, UiDefinition } from '@prisma/clien
import { TranslationService } from '@/providers/translation/translation.service';
import { UiDefinitionService } from '@/ui-definition/ui-definition.service';
import { assertIsValidProjectIds } from '@/project/project-scope.service';
import { CaseRevisionDto } from '../dtos/case-revision.dto';

@Controller('case-management')
@ApiExcludeController()
Expand Down Expand Up @@ -279,4 +280,13 @@ export class CaseManagementController {
deletedBy: authenticatedEntity?.user?.id,
});
}

@common.Post('/workflows/:workflowId/revision')
async caseRevision(
@common.Param('workflowId') workflowId: string,
@common.Body() body: CaseRevisionDto,
@ProjectIds() projectIds: TProjectIds,
) {
return this.caseManagementService.caseRevision(workflowId, body.documentIds, projectIds);
}
}
Loading