feat: add CKYC management tab, checker inbox views, and maker-checker diff utilities
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { ProductsService } from 'app/products/products.service';
|
||||
import { ClientsService } from 'app/clients/clients.service';
|
||||
import { SavingsService } from 'app/savings/savings.service';
|
||||
import { FixedDepositsService } from 'app/deposits/fixed-deposits/fixed-deposits.service';
|
||||
import { RecurringDepositsService } from 'app/deposits/recurring-deposits/recurring-deposits.service';
|
||||
import { SystemService } from 'app/system/system.service';
|
||||
import { AccountingService } from 'app/accounting/accounting.service';
|
||||
import { UsersService } from 'app/users/users.service';
|
||||
|
||||
/**
|
||||
* The services a maker-checker review screen needs in order to read an entity's current state.
|
||||
* Passed in rather than injected here so this stays a plain lookup table with no Angular wiring.
|
||||
*/
|
||||
export interface EntityFetchServices {
|
||||
products: ProductsService;
|
||||
clients: ClientsService;
|
||||
savings: SavingsService;
|
||||
fixedDeposits: FixedDepositsService;
|
||||
recurringDeposits: RecurringDepositsService;
|
||||
system: SystemService;
|
||||
accounting: AccountingService;
|
||||
users: UsersService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a maker-checker `entityName` to the existing API that returns that entity as it stands today,
|
||||
* so an UPDATE can be shown as OLD -> NEW and a DELETE can show what is about to be removed.
|
||||
*
|
||||
* Every entry reuses an API the application already calls elsewhere - nothing new was added on the
|
||||
* backend. Products are fetched with `template=true` because that response also carries the option
|
||||
* lists (`accountingRuleOptions`, `interestCompoundingPeriodTypeOptions`, ...) the payload's bare ids
|
||||
* refer to.
|
||||
*
|
||||
* An entity absent from this table simply has no current-state lookup; callers fall back to rendering
|
||||
* the submitted payload, which is what every entity did before this table existed.
|
||||
*/
|
||||
const ENTITY_FETCHERS: Record<string, (id: string, s: EntityFetchServices) => Observable<any>> = {
|
||||
// ---- Products ----------------------------------------------------------------
|
||||
SAVINGSPRODUCT: (id, s) => s.products.getSavingProduct(id, true),
|
||||
CURRENTPRODUCT: (id, s) => s.products.getCurrentProduct(id, true),
|
||||
SHAREPRODUCT: (id, s) => s.products.getShareProduct(id, true),
|
||||
LOANPRODUCT: (id, s) => s.products.getLoanProduct(id, true),
|
||||
FIXEDDEPOSITPRODUCT: (id, s) => s.products.getFixedDepositProductAndTemplate(id),
|
||||
RECURRINGDEPOSITPRODUCT: (id, s) => s.products.getRecurringDepositProduct(id, true),
|
||||
CHARGE: (id, s) => s.products.getCharge(id, true),
|
||||
|
||||
// ---- Accounts ----------------------------------------------------------------
|
||||
SAVINGSACCOUNT: (id, s) => s.savings.getSavingsAccountData(id),
|
||||
// Deposit accounts must be read through their own endpoints: /savingsaccounts omits the deposit
|
||||
// terms (depositAmount, depositPeriod, charts, ...), so comparing against it reports every one of
|
||||
// those as newly added.
|
||||
FIXEDDEPOSITACCOUNT: (id, s) => s.fixedDeposits.getFixedDepositsAccountData(id),
|
||||
RECURRINGDEPOSITACCOUNT: (id, s) => s.recurringDeposits.getRecurringDepositsAccountAndTemplate(id),
|
||||
|
||||
// ---- People and administration ----------------------------------------------
|
||||
CLIENT: (id, s) => s.clients.getClientData(id, true),
|
||||
ROLE: (id, s) => s.system.getRole(id),
|
||||
USER: (id, s) => s.users.getUser(id),
|
||||
GLACCOUNT: (id, s) => s.accounting.getGlAccount(id, true)
|
||||
};
|
||||
|
||||
/** Whether this screen can read the entity's current state, and therefore produce a real diff. */
|
||||
export function canFetchEntity(entityName: string | undefined | null): boolean {
|
||||
return !!ENTITY_FETCHERS[(entityName ?? '').toUpperCase()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity as it stands today, or null when there is no mapping for it or no resource to
|
||||
* read - in which case the caller keeps showing the submitted payload rather than failing.
|
||||
*/
|
||||
export function fetchCurrentEntity(
|
||||
entityName: string | undefined | null,
|
||||
resourceId: string | number | undefined | null,
|
||||
services: EntityFetchServices
|
||||
): Observable<any> | null {
|
||||
const fetcher = ENTITY_FETCHERS[(entityName ?? '').toUpperCase()];
|
||||
if (!fetcher || resourceId === null || resourceId === undefined || resourceId === '') {
|
||||
return null;
|
||||
}
|
||||
return fetcher(String(resourceId), services);
|
||||
}
|
||||
|
||||
/**
|
||||
* The handful of fields worth showing for a DELETE, whose payload is empty - the checker needs to
|
||||
* recognise *which* record is about to go, not read its full configuration. Keys are tried in order
|
||||
* and the first present one wins; anything not listed falls back to a generic set.
|
||||
*/
|
||||
const DELETE_SUMMARY_KEYS: Record<string, string[]> = {
|
||||
SAVINGSPRODUCT: [
|
||||
'name',
|
||||
'shortName',
|
||||
'description',
|
||||
'currency'
|
||||
],
|
||||
CURRENTPRODUCT: [
|
||||
'name',
|
||||
'shortName',
|
||||
'description',
|
||||
'currency'
|
||||
],
|
||||
SHAREPRODUCT: [
|
||||
'name',
|
||||
'shortName',
|
||||
'description',
|
||||
'currency'
|
||||
],
|
||||
LOANPRODUCT: [
|
||||
'name',
|
||||
'shortName',
|
||||
'description',
|
||||
'currency'
|
||||
],
|
||||
FIXEDDEPOSITPRODUCT: [
|
||||
'name',
|
||||
'shortName',
|
||||
'description',
|
||||
'currency'
|
||||
],
|
||||
RECURRINGDEPOSITPRODUCT: [
|
||||
'name',
|
||||
'shortName',
|
||||
'description',
|
||||
'currency'
|
||||
],
|
||||
CHARGE: [
|
||||
'name',
|
||||
'currency',
|
||||
'amount',
|
||||
'chargeAppliesTo',
|
||||
'chargeTimeType'
|
||||
],
|
||||
CLIENT: [
|
||||
'displayName',
|
||||
'accountNo',
|
||||
'officeName',
|
||||
'status',
|
||||
'mobileNo',
|
||||
'emailAddress'
|
||||
],
|
||||
SAVINGSACCOUNT: [
|
||||
'accountNo',
|
||||
'clientName',
|
||||
'savingsProductName',
|
||||
'status',
|
||||
'currency'
|
||||
],
|
||||
FIXEDDEPOSITACCOUNT: [
|
||||
'accountNo',
|
||||
'clientName',
|
||||
'savingsProductName',
|
||||
'status',
|
||||
'currency'
|
||||
],
|
||||
RECURRINGDEPOSITACCOUNT: [
|
||||
'accountNo',
|
||||
'clientName',
|
||||
'savingsProductName',
|
||||
'status',
|
||||
'currency'
|
||||
],
|
||||
ROLE: [
|
||||
'name',
|
||||
'description',
|
||||
'disabled'
|
||||
],
|
||||
USER: [
|
||||
'username',
|
||||
'firstname',
|
||||
'lastname',
|
||||
'email',
|
||||
'officeName'
|
||||
],
|
||||
GLACCOUNT: [
|
||||
'name',
|
||||
'glCode',
|
||||
'type',
|
||||
'usage',
|
||||
'description'
|
||||
]
|
||||
};
|
||||
|
||||
const GENERIC_SUMMARY_KEYS = [
|
||||
'name',
|
||||
'displayName',
|
||||
'accountNo',
|
||||
'shortName',
|
||||
'username',
|
||||
'description',
|
||||
'status'
|
||||
];
|
||||
|
||||
/** Field names worth surfacing for the entity about to be deleted. */
|
||||
export function deleteSummaryKeys(entityName: string | undefined | null): string[] {
|
||||
return DELETE_SUMMARY_KEYS[(entityName ?? '').toUpperCase()] ?? GENERIC_SUMMARY_KEYS;
|
||||
}
|
||||
Reference in New Issue
Block a user