-
Notifications
You must be signed in to change notification settings - Fork 256
feat: reworked kyc block approval logic #3463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
feat: reworked kyc block approval logic #3463
Conversation
|
WalkthroughThis change set introduces a structured approval decision workflow for end users in a KYC context. It defines explicit approval states, adds API endpoints and DTOs for updating an individual's approval decision, updates related service and controller logic, centralizes status computation, and refactors React hooks and UI logic to use the new workflow and types. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as React UI
participant Hook as useMutateIndividualApprovalDecision
participant API as updateIndividualApprovalDecision (fetcher)
participant BE as EndUserControllerInternal (POST /:id/decision)
participant Service as EndUserService
participant Repo as EndUserRepository
UI->>Hook: Call mutate({ endUserId, decision })
Hook->>API: updateIndividualApprovalDecision({ endUserId, decision })
API->>BE: POST /end-users/{id}/decision { decision }
BE->>Service: updateDecisionById(id, decision, projectIds)
Service->>Repo: findById(id, projectIds)
Service->>Service: Validate current approval state
Service->>Repo: update approvalState
Repo-->>Service: Updated EndUser
Service-->>BE: Updated EndUser
BE-->>API: Updated EndUser
API-->>Hook: Updated EndUser
Hook-->>UI: Success/Error feedback
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ Context from checks skipped due to timeout of 90000ms (6)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
services/workflows-service/src/end-user/dtos/end-user-decision.ts (1)
5-12
: Well-structured DTO with proper validation.The implementation correctly uses validation decorators and Swagger documentation. The
oneOf
validator ensures only valid approval states are accepted.Consider adding a more descriptive validation message:
- @oneOf([ApprovalState.APPROVED, ApprovalState.REJECTED]) + @oneOf([ApprovalState.APPROVED, ApprovalState.REJECTED], { + message: 'Decision must be either APPROVED or REJECTED' + })apps/backoffice-v2/src/lib/blocks/variants/DefaultBlocks/hooks/useCaseBlocksLogic/utils/compute-individual-kyc-check-status.ts (1)
14-30
: Consider adding a default return value forgetStatusFromTags
The function can return
undefined
if none of the tag conditions match. Since this is called at line 47 as part of the status computation flow, consider either:
- Adding a default return of
INDIVIDUAL_KYC_CHECK_STATUS_ENUM.pending
- Documenting that
undefined
is an expected return valueconst getStatusFromTags = (tags: string[]) => { if (tags?.includes(StateTag.REVISION)) { return INDIVIDUAL_KYC_CHECK_STATUS_ENUM.revision; } if (tags?.includes(StateTag.APPROVED)) { return INDIVIDUAL_KYC_CHECK_STATUS_ENUM.approved; } if (tags?.includes(StateTag.REJECTED)) { return INDIVIDUAL_KYC_CHECK_STATUS_ENUM.rejected; } if (tags?.includes(StateTag.PENDING_PROCESS)) { return INDIVIDUAL_KYC_CHECK_STATUS_ENUM.pending; } + + return INDIVIDUAL_KYC_CHECK_STATUS_ENUM.pending; };apps/backoffice-v2/src/domains/individuals/fetchers.ts (2)
19-31
: Consider removing redundantEndUserApprovalStates
arrayThe array duplicates the values from
EndUserApprovalState
. You can derive it dynamically to maintain a single source of truth.export const EndUserApprovalState = { NEW: 'NEW', PROCESSING: 'PROCESSING', APPROVED: 'APPROVED', REJECTED: 'REJECTED', } as const; -export const EndUserApprovalStates = [ - EndUserApprovalState.NEW, - EndUserApprovalState.PROCESSING, - EndUserApprovalState.APPROVED, - EndUserApprovalState.REJECTED, -] as const; +export const EndUserApprovalStates = Object.values(EndUserApprovalState) as [ + (typeof EndUserApprovalState)[keyof typeof EndUserApprovalState], + ...(typeof EndUserApprovalState)[keyof typeof EndUserApprovalState][] +];
95-97
: Consider simplifying theTIndividualDecision
typeWhile the template literal type works, it could be more explicit and easier to understand.
-export type TIndividualDecision = - | `${typeof EndUserApprovalState.APPROVED}` - | `${typeof EndUserApprovalState.REJECTED}`; +export type TIndividualDecision = 'APPROVED' | 'REJECTED';Or if you want to maintain the connection to the constants:
-export type TIndividualDecision = - | `${typeof EndUserApprovalState.APPROVED}` - | `${typeof EndUserApprovalState.REJECTED}`; +export type TIndividualDecision = + | typeof EndUserApprovalState.APPROVED + | typeof EndUserApprovalState.REJECTED;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (10)
apps/backoffice-v2/src/domains/individuals/fetchers.ts
(4 hunks)apps/backoffice-v2/src/domains/individuals/mutations/useMutateIndividualApprovalDecision/useMutateIndividualApprovalDecision.ts
(1 hunks)apps/backoffice-v2/src/lib/blocks/components/KycBlock/hooks/useKycBlock/useKycBlock.tsx
(5 hunks)apps/backoffice-v2/src/lib/blocks/variants/DefaultBlocks/hooks/useCaseBlocksLogic/utils/compute-individual-kyc-check-status.ts
(1 hunks)apps/backoffice-v2/src/lib/blocks/variants/DefaultBlocks/hooks/useCaseBlocksLogic/utils/useTabsToBlocksMap.tsx
(12 hunks)services/workflows-service/prisma/data-migrations
(1 hunks)services/workflows-service/src/end-user/dtos/end-user-decision.ts
(1 hunks)services/workflows-service/src/end-user/end-user.controller.internal.ts
(2 hunks)services/workflows-service/src/end-user/end-user.service.ts
(2 hunks)services/workflows-service/src/workflow/workflow-runtime-data.repository.ts
(1 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/data-migrations.mdc:0-0
Timestamp: 2025-06-24T09:35:54.963Z
Learning: Use consistent naming conventions for related components (workflows, UI definitions, filters) in Ballerine migrations to improve maintainability and clarity.
services/workflows-service/prisma/data-migrations (2)
undefined
<retrieved_learning>
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/data-migrations.mdc:0-0
Timestamp: 2025-06-24T09:35:54.963Z
Learning: In Ballerine's workflow migration scripts (TypeScript), always establish the relationship between workflow definitions and UI definitions solely through the 'workflowDefinitionId' field in the UiDefinition model; do not create a separate junction table or relation.
</retrieved_learning>
<retrieved_learning>
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/data-migrations.mdc:0-0
Timestamp: 2025-06-24T09:35:54.963Z
Learning: Use consistent naming conventions for related components (workflows, UI definitions, filters) in Ballerine migrations to improve maintainability and clarity.
</retrieved_learning>
services/workflows-service/src/workflow/workflow-runtime-data.repository.ts (1)
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/data-migrations.mdc:0-0
Timestamp: 2025-06-24T09:35:54.963Z
Learning: In Ballerine's workflow migration scripts (TypeScript), always establish the relationship between workflow definitions and UI definitions solely through the 'workflowDefinitionId' field in the UiDefinition model; do not create a separate junction table or relation.
services/workflows-service/src/end-user/dtos/end-user-decision.ts (2)
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/kyb-app.mdc:0-0
Timestamp: 2025-06-24T09:36:16.111Z
Learning: Use discriminated unions in TypeScript for managing complex state, enabling exhaustive type checking.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/backoffice-v2.mdc:0-0
Timestamp: 2025-06-24T09:35:48.303Z
Learning: Use discriminated unions for managing complex state shapes, and leverage TypeScript's type inference to reduce redundancy.
apps/backoffice-v2/src/domains/individuals/mutations/useMutateIndividualApprovalDecision/useMutateIndividualApprovalDecision.ts (8)
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/backoffice-v2.mdc:0-0
Timestamp: 2025-06-24T09:35:48.303Z
Learning: Extract business logic into custom React hooks, placing them in dedicated hooks directories and using the 'use' prefix for naming, to promote reusability and separation of concerns.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/kyb-app.mdc:0-0
Timestamp: 2025-06-24T09:36:16.111Z
Learning: Always use the 'use' prefix for custom React hooks to ensure they are recognized by React's rules of hooks.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/workflows-dashboard.mdc:0-0
Timestamp: 2025-06-24T09:37:03.204Z
Learning: Custom hooks in React should be single-purpose, follow the 'use' prefix naming convention, and be placed within feature directories for better organization and reusability.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/backoffice-v2.mdc:0-0
Timestamp: 2025-06-24T09:35:48.303Z
Learning: In the apps/backoffice-v2 React TypeScript codebase, always use functional components and TypeScript for all UI components to ensure consistency and type safety.
Learnt from: chesterkmr
PR: ballerine-io/ballerine#3265
File: apps/backoffice-v2/src/pages/MerchantMonitoring/components/MerchantMonitoringReportStatus/MerchantMonitoringReportStatus.tsx:121-124
Timestamp: 2025-04-29T09:05:55.562Z
Learning: When using React Query's `mutateAsync`, if the mutation fails, it throws an exception and halts execution of the surrounding function, preventing subsequent code from running. This provides automatic protection against partial operations when multiple related mutations need to happen atomically.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/kyb-app.mdc:0-0
Timestamp: 2025-06-24T09:36:16.111Z
Learning: Use React Query for managing server state and data fetching, and React Context for global UI state, to separate concerns effectively.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/backoffice-v2.mdc:0-0
Timestamp: 2025-06-24T09:35:48.303Z
Learning: Use React Query for all server state management and API calls, leveraging its caching, loading, and error handling features.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/workflows-dashboard.mdc:0-0
Timestamp: 2025-06-24T09:37:03.204Z
Learning: React Query is the preferred solution for managing API state and data fetching in modern React applications, providing built-in caching, loading, and error handling.
apps/backoffice-v2/src/lib/blocks/components/KycBlock/hooks/useKycBlock/useKycBlock.tsx (1)
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/backoffice-v2.mdc:0-0
Timestamp: 2025-06-24T09:35:48.303Z
Learning: In the apps/backoffice-v2 React TypeScript codebase, always use functional components and TypeScript for all UI components to ensure consistency and type safety.
services/workflows-service/src/end-user/end-user.service.ts (2)
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/workflows-service.mdc:0-0
Timestamp: 2025-06-24T09:37:14.531Z
Learning: Use custom exception classes extending from base NestJS exceptions for consistent and meaningful error handling.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/workflows-service.mdc:0-0
Timestamp: 2025-06-24T09:37:14.531Z
Learning: Services must use the @Injectable() decorator and have names ending with the 'Service' suffix to align with NestJS conventions.
apps/backoffice-v2/src/domains/individuals/fetchers.ts (4)
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/kyb-app.mdc:0-0
Timestamp: 2025-06-24T09:36:16.111Z
Learning: Use discriminated unions in TypeScript for managing complex state, enabling exhaustive type checking.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/backoffice-v2.mdc:0-0
Timestamp: 2025-06-24T09:35:48.303Z
Learning: Use discriminated unions for managing complex state shapes, and leverage TypeScript's type inference to reduce redundancy.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/workflows-service.mdc:0-0
Timestamp: 2025-06-24T09:37:14.531Z
Learning: Use TypeScript 'as const' for fixed sets of values to ensure literal type inference.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/backoffice-v2.mdc:0-0
Timestamp: 2025-06-24T09:35:48.303Z
Learning: Use React Hook Form for form state management and Zod for schema validation, providing clear validation feedback and handling form submission states.
apps/backoffice-v2/src/lib/blocks/variants/DefaultBlocks/hooks/useCaseBlocksLogic/utils/useTabsToBlocksMap.tsx (8)
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/backoffice-v2.mdc:0-0
Timestamp: 2025-06-24T09:35:48.303Z
Learning: In the apps/backoffice-v2 React TypeScript codebase, always use functional components and TypeScript for all UI components to ensure consistency and type safety.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/backoffice-v2.mdc:0-0
Timestamp: 2025-06-24T09:35:48.303Z
Learning: Extract business logic into custom React hooks, placing them in dedicated hooks directories and using the 'use' prefix for naming, to promote reusability and separation of concerns.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/kyb-app.mdc:0-0
Timestamp: 2025-06-24T09:36:16.111Z
Learning: Always use the 'use' prefix for custom React hooks to ensure they are recognized by React's rules of hooks.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/workflows-dashboard.mdc:0-0
Timestamp: 2025-06-24T09:37:03.204Z
Learning: Custom hooks in React should be single-purpose, follow the 'use' prefix naming convention, and be placed within feature directories for better organization and reusability.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/kyb-app.mdc:0-0
Timestamp: 2025-06-24T09:36:16.111Z
Learning: Use React Query for managing server state and data fetching, and React Context for global UI state, to separate concerns effectively.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/backoffice-v2.mdc:0-0
Timestamp: 2025-06-24T09:35:48.303Z
Learning: Use React Query for all server state management and API calls, leveraging its caching, loading, and error handling features.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/backoffice-v2.mdc:0-0
Timestamp: 2025-06-24T09:35:48.303Z
Learning: Use React Context for shared state that needs to be accessed by multiple components, and implement state machines for managing complex flows.
Learnt from: CR
PR: ballerine-io/ballerine#0
File: .cursor/rules/data-migrations.mdc:0-0
Timestamp: 2025-06-24T09:35:54.963Z
Learning: In Ballerine's workflow migration scripts (TypeScript), always establish the relationship between workflow definitions and UI definitions solely through the 'workflowDefinitionId' field in the UiDefinition model; do not create a separate junction table or relation.
🧬 Code Graph Analysis (6)
services/workflows-service/src/end-user/dtos/end-user-decision.ts (1)
services/workflows-service/src/common/decorators/one-of.decorator.ts (1)
oneOf
(3-19)
apps/backoffice-v2/src/lib/blocks/components/KycBlock/hooks/useKycBlock/useKycBlock.tsx (1)
apps/backoffice-v2/src/lib/blocks/variants/DefaultBlocks/hooks/useCaseBlocksLogic/utils/compute-individual-kyc-check-status.ts (2)
TIndividualKycCheckStatus
(12-12)INDIVIDUAL_KYC_CHECK_STATUS_ENUM
(5-10)
services/workflows-service/src/end-user/end-user.controller.internal.ts (3)
services/workflows-service/src/common/decorators/project-ids.decorator.ts (1)
ProjectIds
(6-32)services/workflows-service/src/types.ts (1)
TProjectIds
(16-16)services/workflows-service/src/end-user/dtos/end-user-decision.ts (1)
EndUserDecisionDto
(5-12)
services/workflows-service/src/end-user/end-user.service.ts (2)
services/workflows-service/src/alert/alert.controller.external.ts (1)
decision
(290-314)services/workflows-service/src/types.ts (1)
TProjectIds
(16-16)
apps/backoffice-v2/src/lib/blocks/variants/DefaultBlocks/hooks/useCaseBlocksLogic/utils/compute-individual-kyc-check-status.ts (3)
packages/common/src/consts/index.ts (1)
StateTag
(3-16)apps/backoffice-v2/src/domains/individuals/fetchers.ts (2)
TEndUser
(68-68)EndUserApprovalState
(19-24)apps/backoffice-v2/src/domains/workflows/fetchers.ts (1)
TWorkflowById
(211-211)
apps/backoffice-v2/src/lib/blocks/variants/DefaultBlocks/hooks/useCaseBlocksLogic/utils/useTabsToBlocksMap.tsx (3)
apps/backoffice-v2/src/domains/individuals/mutations/useMutateIndividualApprovalDecision/useMutateIndividualApprovalDecision.ts (1)
useMutateIndividualApprovalDecision
(8-31)apps/backoffice-v2/src/lib/blocks/variants/DefaultBlocks/hooks/useCaseBlocksLogic/utils/compute-individual-kyc-check-status.ts (1)
computeIndividualKycCheckStatus
(32-57)apps/backoffice-v2/src/domains/individuals/fetchers.ts (1)
EndUserApprovalState
(19-24)
⏰ Context from checks skipped due to timeout of 90000ms (6)
- GitHub Check: Analyze (javascript)
- GitHub Check: lint
- GitHub Check: spell_check
- GitHub Check: test_linux
- GitHub Check: build (ubuntu-latest)
- GitHub Check: format
🔇 Additional comments (12)
services/workflows-service/prisma/data-migrations (1)
1-1
: Confirm the new submodule SHA includes the intended DB migration onlyOnly the gitlink SHA changed; please double-check that commit
493a222
contains exclusively the KYC-approval migrations (e.g.,approvalState
column addition) and no unintended schema or data changes.
Make sure the migration can be applied/rolled back cleanly in all environments and that dependent services are using the same revision.services/workflows-service/src/workflow/workflow-runtime-data.repository.ts (1)
311-312
: LGTM: Essential data enrichment for approval workflow.The addition of
approvalState
to the individuals CTE properly supports the new approval decision functionality throughout the system.services/workflows-service/src/end-user/end-user.service.ts (1)
120-138
: Excellent business logic implementation.The method properly validates that approval decisions cannot be changed once they're finalized (APPROVED or REJECTED). The error handling with
BadRequestException
provides clear feedback to API consumers.services/workflows-service/src/end-user/end-user.controller.internal.ts (1)
64-74
: Well-implemented REST endpoint with proper documentation.The controller method correctly uses NestJS decorators for parameter extraction and validation. The Swagger documentation appropriately describes the possible responses.
apps/backoffice-v2/src/lib/blocks/components/KycBlock/hooks/useKycBlock/useKycBlock.tsx (1)
31-34
: Excellent type safety improvement.The migration from string literals to the centralized
INDIVIDUAL_KYC_CHECK_STATUS_ENUM
enhances type safety and maintainability. The typed function signature ensures compile-time validation of status values.Also applies to: 306-307, 322-322, 337-337, 352-352
apps/backoffice-v2/src/domains/individuals/mutations/useMutateIndividualApprovalDecision/useMutateIndividualApprovalDecision.ts (1)
1-31
: Well-implemented custom hook following best practices!The hook correctly uses React Query's
useMutation
, follows the 'use' prefix naming convention, and properly handles success/error states with toast notifications. The use ofvoid
operator for the promise is appropriate here.apps/backoffice-v2/src/lib/blocks/variants/DefaultBlocks/hooks/useCaseBlocksLogic/utils/compute-individual-kyc-check-status.ts (1)
32-57
: Status computation logic is well-structuredThe function properly prioritizes verification check status, then approval state, and falls back to tag-based status. The explicit handling of each approval state ensures clear status mapping.
apps/backoffice-v2/src/domains/individuals/fetchers.ts (1)
99-117
: API function implementation follows established patternsThe
updateIndividualApprovalDecision
function correctly uses the existingapiClient
pattern and error handling approach consistent with other functions in this file.apps/backoffice-v2/src/lib/blocks/variants/DefaultBlocks/hooks/useCaseBlocksLogic/utils/useTabsToBlocksMap.tsx (4)
27-29
: Proper integration of the new individual approval decision mutationThe hook correctly replaces the generic approval mutation with the specific individual approval decision mutation, following the new structured workflow.
Also applies to: 138-141
177-200
: Efficient refactoring of status computation logicGood optimization by finding the
endUser
once and reusing it for both property extraction and status computation. The centralized status computation usingcomputeIndividualKycCheckStatus
improves maintainability.
261-267
: Clear and explicit approval decision parametersThe
onApprove
handler now clearly specifies theendUserId
and uses the typedEndUserApprovalState.APPROVED
constant, making the intent explicit and type-safe.
422-427
: Consistent implementation across all individual adaptersThe same pattern of finding the endUser once and computing status is consistently applied to directors and people of interest adapters, maintaining code consistency.
Also applies to: 450-460
...viduals/mutations/useMutateIndividualApprovalDecision/useMutateIndividualApprovalDecision.ts
Outdated
Show resolved
Hide resolved
...viduals/mutations/useMutateIndividualApprovalDecision/useMutateIndividualApprovalDecision.ts
Show resolved
Hide resolved
apps/backoffice-v2/src/lib/blocks/components/KycBlock/hooks/useKycBlock/useKycBlock.tsx
Outdated
Show resolved
Hide resolved
[EndUserApprovalState.NEW, EndUserApprovalState.PROCESSING].includes(endUser?.approvalState) | ||
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Thinking that new and processing should lead to pending (if somehow we can sync the end-user state with the check that would be great) 2. Tags should be the last control flow, having no approvalState would not guarantee there are tags 3. Between the check status and end-user state the tags might be an impossible state
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove !endUser?.approvalState.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes