- Agent master: tabbed layout, customer search dialog, GL dropdown, account card, view page - Party master: tabbed layout, expected party code, nominee cards, relation from code values - Daily transaction entry: compact style, collections popup, Apply DDS Receipt placement - Approvals: new review modal, Agent Approval tab, DDS routing into product/pending account - DDS product: view page and list buttons; drop Auto Interest Post Day Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
170 lines
6.5 KiB
TypeScript
170 lines
6.5 KiB
TypeScript
/**
|
|
* 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'
|
|
];
|
|
|
|
/**
|
|
* DDS keeps its own approval queue (/dds/approvals), which also describes each entry in the clerk's
|
|
* terms. Its product and account requests are listed in Product Approval and Pending Account as well,
|
|
* so a checker finds them where every other product and account waits - but they are read from, and
|
|
* actioned through, that DDS queue rather than /makercheckers.
|
|
*/
|
|
export function isDdsProductEntry(entry: any): boolean {
|
|
return isDdsRoutedRequest(entry?.entity, entry?.action) && entry?.entity === 'DDSACCOUNTTYPE';
|
|
}
|
|
|
|
export function isDdsAccountEntry(entry: any): boolean {
|
|
return isDdsRoutedRequest(entry?.entity, entry?.action) && entry?.entity === 'PARTY';
|
|
}
|
|
|
|
/**
|
|
* Whether a command is a DDS product or DDS account create/edit - one listed under Product Approval or
|
|
* Pending Account, and so left out of DDS Approvals and the Checker Inbox, which would otherwise show
|
|
* the same request a second and third time.
|
|
*/
|
|
export function isDdsRoutedRequest(
|
|
entityName: string | undefined | null,
|
|
actionName: string | undefined | null
|
|
): boolean {
|
|
const entity = (entityName ?? '').toUpperCase();
|
|
// An agent request of any kind waits under Agent Approval: an agent has no voucher and no amount,
|
|
// so the money columns of DDS Approvals say nothing about one.
|
|
if (entity === 'AGENT') {
|
|
return true;
|
|
}
|
|
return [
|
|
'DDSACCOUNTTYPE',
|
|
'PARTY'
|
|
].includes(entity) && [
|
|
'CREATE',
|
|
'UPDATE'
|
|
].includes((actionName ?? '').toUpperCase());
|
|
}
|
|
|
|
/** The DDS queue's "2026-10-27 11:04" as a Date, for the tables' date columns. */
|
|
export function ddsMadeOn(madeOn: string | undefined | null): Date | null {
|
|
if (!madeOn) {
|
|
return null;
|
|
}
|
|
const d = new Date(String(madeOn).replace(' ', 'T'));
|
|
return isNaN(d.getTime()) ? null : d;
|
|
}
|
|
|
|
/** 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',
|
|
DDS: 'DDS 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;
|
|
}
|