feat: implement product approval workflow updates

This commit is contained in:
2026-09-09 11:32:37 +05:30
parent abc966d507
commit 227949f8b2
31 changed files with 1472 additions and 277 deletions
+121
View File
@@ -0,0 +1,121 @@
/**
* 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/.
*/
/**
* Single source of truth for which maker-checker entities belong to Approvals -> Product Approval
* rather than the generic Checker Inbox.
*
* These are the entity names the application already treats as products: the five that own a
* /products/... view route and a pending-edit staging row, plus LOANPRODUCT and CHARGE, which are
* product-module entities whose maker-checker requests run entirely through the native pipeline.
* Deliberately excluded are the product-module *configuration* entities (tax groups, floating rates,
* delinquency buckets, product mixes, collaterals) - they stay in the Checker Inbox.
*/
export const PRODUCT_ENTITIES: readonly string[] = [
'LOANPRODUCT',
'SAVINGSPRODUCT',
'CURRENTPRODUCT',
'SHAREPRODUCT',
'FIXEDDEPOSITPRODUCT',
'RECURRINGDEPOSITPRODUCT',
'CHARGE'
];
/**
* The subset whose UPDATEs bypass the native command-source pipeline: the PUT handler calls
* submitEditForApproval, which stages the change in m_saving_product_temp and writes an *audit-only*
* command-source row alongside it. That row shows up in /makercheckers like any pending command, but
* approving it there replays the original handler and never clears the staging lock - so it must be
* actioned through /{producttype}/{id}/approve instead.
*
* Two consequences, both relied on below:
* - Product Approval must drop these rows from its maker-checker feed, since the pending-edit feed
* already supplies them (otherwise every product edit would be listed twice).
* - View Checker Inbox must not offer approve/reject on them.
*/
export const TEMP_TABLE_PRODUCT_ENTITIES: readonly string[] = [
'SAVINGSPRODUCT',
'CURRENTPRODUCT',
'SHAREPRODUCT',
'FIXEDDEPOSITPRODUCT',
'RECURRINGDEPOSITPRODUCT'
];
/**
* Account entities staged the same way (savings/current account Nominee and Digital-Feature edits).
* Not products - they belong to Approvals -> Pending Account Edits - but their UPDATE rows have to
* stay out of the Checker Inbox for exactly the same lock reason.
*/
export const TEMP_TABLE_ACCOUNT_ENTITIES: readonly string[] = [
'SAVINGSACCOUNT',
'CURRENTACCOUNT'
];
/** Short type badge shown in the Product Approval table, matching the pending-edit productType tags. */
export const PRODUCT_ENTITY_TYPE_LABEL: Record<string, string> = {
LOANPRODUCT: 'LOAN',
SAVINGSPRODUCT: 'SAVINGS',
CURRENTPRODUCT: 'CURRENT',
SHAREPRODUCT: 'SHARE',
FIXEDDEPOSITPRODUCT: 'FD',
RECURRINGDEPOSITPRODUCT: 'RD',
CHARGE: 'CHARGE'
};
/** Whether a maker-checker entry is a product request, and so belongs to Product Approval. */
export function isProductEntity(entityName: string | undefined | null): boolean {
return PRODUCT_ENTITIES.includes((entityName ?? '').toUpperCase());
}
/** Whether a maker-checker entry is a product edit already staged in m_saving_product_temp. */
export function isTempTableProductEdit(
entityName: string | undefined | null,
actionName: string | undefined | null
): boolean {
return (
TEMP_TABLE_PRODUCT_ENTITIES.includes((entityName ?? '').toUpperCase()) &&
(actionName ?? '').toUpperCase() === 'UPDATE'
);
}
/** Whether a maker-checker entry is an account edit already staged for Pending Account Edits. */
export function isTempTableAccountEdit(
entityName: string | undefined | null,
actionName: string | undefined | null
): boolean {
return (
TEMP_TABLE_ACCOUNT_ENTITIES.includes((entityName ?? '').toUpperCase()) &&
(actionName ?? '').toUpperCase() === 'UPDATE'
);
}
/**
* Display-only expansion of the short type badge above, for the Product Approval table.
*
* Presentation layer only: `productType` is still the value the component switches on to pick the
* right fetch/approve/reject endpoint, so the underlying row data, filtering and routing are
* untouched. Both the short code ('FD') and the spelled-out form ('FIXED DEPOSIT') are accepted so
* either feed maps correctly.
*/
export const PRODUCT_TYPE_DISPLAY_LABEL: Record<string, string> = {
CURRENT: 'CURRENT PRODUCT',
SAVINGS: 'SAVINGS PRODUCT',
SHARE: 'SHARE PRODUCT',
CHARGE: 'CHARGE',
LOAN: 'LOAN PRODUCT',
FD: 'FIXED DEPOSIT PRODUCT',
'FIXED DEPOSIT': 'FIXED DEPOSIT PRODUCT',
RD: 'RECURRING DEPOSIT PRODUCT',
'RECURRING DEPOSIT': 'RECURRING DEPOSIT PRODUCT'
};
/** Label shown in the Product Approval Type column. Unmapped types render unchanged. */
export function productTypeDisplayLabel(productType: string | undefined | null): string {
const raw = (productType ?? '').toString();
return PRODUCT_TYPE_DISPLAY_LABEL[raw.trim().toUpperCase()] ?? raw;
}