/** * 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 = { 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 = { 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; }