From ad0a1a0b1d2741795fd5dda9387f5e8da243b20c Mon Sep 17 00:00:00 2001 From: anshul Date: Tue, 27 Oct 2026 17:21:33 +0530 Subject: [PATCH] feat(investment): show sent-for-approval message on investment actions --- .../bond-investment.component.ts | 81 ++++++++++++++----- .../fd-investment/fd-investment.component.ts | 29 +++++-- .../investment-masters.component.ts | 27 +++++-- src/app/investment/investment.service.ts | 13 +++ 4 files changed, 121 insertions(+), 29 deletions(-) diff --git a/src/app/investment/bond-investment/bond-investment.component.ts b/src/app/investment/bond-investment/bond-investment.component.ts index edd0056dc..d3e42200e 100644 --- a/src/app/investment/bond-investment/bond-investment.component.ts +++ b/src/app/investment/bond-investment/bond-investment.component.ts @@ -75,6 +75,8 @@ import { InvestmentValuationDto, NON_SLR_TYPES, apiErrorMessage, + awaitingApproval, + sentForApproval, toHolding, toInterestTxn, toPurchaseTxn, @@ -1003,7 +1005,9 @@ export class BondInvestmentComponent implements OnInit { this.investmentService.createPurchase(body).subscribe({ next: (res) => { this.toast( - `Deal ${res.resourceIdentifier} saved — pending settlement. Settle it from Holdings on or after the settlement date.` + awaitingApproval(res) + ? sentForApproval('Purchase deal') + : `Deal ${res.resourceIdentifier} saved — pending settlement. Settle it from Holdings on or after the settlement date.` ); this.activeTab = 'holdings'; this.loadPurchases(); @@ -1116,8 +1120,8 @@ export class BondInvestmentComponent implements OnInit { next: (res) => { this.settlingDealId = null; this.toast( - res.rollbackTransaction - ? `Settlement of deal ${h.dealNo} sent for approval (Checker Inbox).` + awaitingApproval(res) + ? sentForApproval(`Settlement of deal ${h.dealNo}`) : `Deal ${h.dealNo} settled and posted to GL (transaction ${res.transactionId}).` ); this.loadPurchases(); @@ -1136,8 +1140,12 @@ export class BondInvestmentComponent implements OnInit { return; } this.investmentService.deletePurchase(h.id).subscribe({ - next: () => { - this.toast(`Deal ${h.dealNo} (Cert No ${h.certNo}) dropped.`); + next: (res) => { + this.toast( + awaitingApproval(res) + ? sentForApproval(`Drop of deal ${h.dealNo}`) + : `Deal ${h.dealNo} (Cert No ${h.certNo}) dropped.` + ); this.loadPurchases(); }, error: (err) => this.toast(apiErrorMessage(err)) @@ -1211,7 +1219,11 @@ export class BondInvestmentComponent implements OnInit { .subscribe({ next: (res) => { this.redeeming = false; - this.toast(`Deal ${q.dealNo} redeemed — voucher ${res.resourceIdentifier ?? ''}.`); + this.toast( + awaitingApproval(res) + ? sentForApproval(`Redemption of deal ${q.dealNo}`) + : `Deal ${q.dealNo} redeemed — voucher ${res.resourceIdentifier ?? ''}.` + ); this.redeemPurchaseId = null; this.redeemQuote = null; this.redeemRemarks = ''; @@ -1315,8 +1327,11 @@ export class BondInvestmentComponent implements OnInit { this.investmentService.postInterest(sc.purchaseId, body).subscribe({ next: (res) => { this.intPosting = false; + const line = `${r.type === 'COUPON' ? 'Coupon' : 'Provision'} of ${r.intDate}`; this.toast( - `${r.type === 'COUPON' ? 'Coupon' : 'Provision'} of ${r.intDate} posted — voucher ${res.resourceIdentifier ?? ''}.` + awaitingApproval(res) + ? sentForApproval(`${line} voucher`) + : `${line} posted — voucher ${res.resourceIdentifier ?? ''}.` ); this.retrieveInterestSchedule(); this.loadDueInterest(); @@ -1450,9 +1465,13 @@ export class BondInvestmentComponent implements OnInit { }; this.savingSale = true; this.investmentService.createSale(body).subscribe({ - next: () => { + next: (res) => { this.savingSale = false; - this.toast(`Sale saved — settle it on ${this.saleQuote?.settlementDate ?? 'the settlement date'} to post it.`); + this.toast( + awaitingApproval(res) + ? sentForApproval('Sale') + : `Sale saved — settle it on ${this.saleQuote?.settlementDate ?? 'the settlement date'} to post it.` + ); this.sale = this.blankSale(); this.saleQuote = null; this.loadInterestLog(); @@ -1475,6 +1494,10 @@ export class BondInvestmentComponent implements OnInit { this.investmentService.settleSale(s.id).subscribe({ next: (res) => { this.busySaleId = null; + if (awaitingApproval(res)) { + this.toast(sentForApproval(`Settlement of the sale of deal ${s.dealNo}`)); + return; + } this.sales = this.sales.filter((x) => x.id !== s.id); this.toast(`Sale of deal ${s.dealNo} settled — voucher ${res.resourceIdentifier ?? ''}.`); this.loadPurchases(); @@ -1492,8 +1515,12 @@ export class BondInvestmentComponent implements OnInit { } this.busySaleId = s.id; this.investmentService.deleteSale(s.id).subscribe({ - next: () => { + next: (res) => { this.busySaleId = null; + if (awaitingApproval(res)) { + this.toast(sentForApproval(`Drop of the pending sale of deal ${s.dealNo}`)); + return; + } this.sales = this.sales.filter((x) => x.id !== s.id); this.toast(`Pending sale of deal ${s.dealNo} dropped.`); this.loadInterestLog(); @@ -1549,7 +1576,11 @@ export class BondInvestmentComponent implements OnInit { this.investmentService.writeOff(h.id, { amount, remarks: this.writeoffReason.trim(), locale: 'en' }).subscribe({ next: (res) => { this.writingOff = false; - this.toast(`Deal ${h.dealNo}: ₹${amount} written off — voucher ${res.resourceIdentifier ?? ''}.`); + this.toast( + awaitingApproval(res) + ? sentForApproval(`Write-off of ₹${amount} on deal ${h.dealNo}`) + : `Deal ${h.dealNo}: ₹${amount} written off — voucher ${res.resourceIdentifier ?? ''}.` + ); this.writeoffPurchaseId = null; this.writeoffAmount = null; this.writeoffReason = ''; @@ -1594,7 +1625,11 @@ export class BondInvestmentComponent implements OnInit { .subscribe({ next: (res) => { this.amortizing = false; - this.toast(`Deal ${d.dealNo}: premium amortised for ${fyEnd} — voucher ${res.resourceIdentifier ?? ''}.`); + this.toast( + awaitingApproval(res) + ? sentForApproval(`Premium amortisation of deal ${d.dealNo} for ${fyEnd}`) + : `Deal ${d.dealNo}: premium amortised for ${fyEnd} — voucher ${res.resourceIdentifier ?? ''}.` + ); this.onAmortizeDealChange(); this.loadPurchases(); }, @@ -1645,7 +1680,9 @@ export class BondInvestmentComponent implements OnInit { next: (res) => { this.amortizing = false; this.toast( - `Amortisation for ${this.amortizeFyEnd} posted for ${this.amortizeDue.length} holding(s) — vouchers ${res.resourceIdentifier ?? ''}.` + awaitingApproval(res) + ? sentForApproval(`Amortisation for ${this.amortizeFyEnd} (${this.amortizeDue.length} holding(s))`) + : `Amortisation for ${this.amortizeFyEnd} posted for ${this.amortizeDue.length} holding(s) — vouchers ${res.resourceIdentifier ?? ''}.` ); this.loadAmortizeDue(); this.loadPurchases(); @@ -1721,7 +1758,11 @@ export class BondInvestmentComponent implements OnInit { .subscribe({ next: (res) => { this.goiSaving = false; - this.toast(`Deal ${h.dealNo} transferred to ${to.scriptCode} — ${res.resourceIdentifier ?? ''}.`); + this.toast( + awaitingApproval(res) + ? sentForApproval(`Transfer of deal ${h.dealNo} to ${to.scriptCode}`) + : `Deal ${h.dealNo} transferred to ${to.scriptCode} — ${res.resourceIdentifier ?? ''}.` + ); this.goiPurchaseId = null; this.goiToScriptCode = ''; this.goiRemarks = ''; @@ -1794,9 +1835,11 @@ export class BondInvestmentComponent implements OnInit { next: (res) => { this.valuationPosting = false; this.toast( - v.delta === 0 - ? `Valuation of ${v.valuationDate} recorded; the IDR needed is unchanged, so no voucher.` - : `Valuation of ${v.valuationDate} posted — ${res.resourceIdentifier ?? ''}.` + awaitingApproval(res) + ? sentForApproval(`${v.category} valuation of ${v.valuationDate}`) + : v.delta === 0 + ? `Valuation of ${v.valuationDate} recorded; the IDR needed is unchanged, so no voucher.` + : `Valuation of ${v.valuationDate} posted — ${res.resourceIdentifier ?? ''}.` ); this.valuationPreview = null; this.loadValuationRuns(); @@ -1956,7 +1999,9 @@ export class BondInvestmentComponent implements OnInit { next: (res) => { this.mfSwitchSaving = false; this.toast( - `Switched ${this.mfSwitchUnits} units of deal ${h.dealNo} into ${to.scriptCode} — new deal ${res.resourceIdentifier ?? ''}.` + awaitingApproval(res) + ? sentForApproval(`Switch of ${this.mfSwitchUnits} units of deal ${h.dealNo} into ${to.scriptCode}`) + : `Switched ${this.mfSwitchUnits} units of deal ${h.dealNo} into ${to.scriptCode} — new deal ${res.resourceIdentifier ?? ''}.` ); this.mfSwitchPurchaseId = null; this.mfSwitchUnits = null; diff --git a/src/app/investment/fd-investment/fd-investment.component.ts b/src/app/investment/fd-investment/fd-investment.component.ts index d3096fede..7caba691c 100644 --- a/src/app/investment/fd-investment/fd-investment.component.ts +++ b/src/app/investment/fd-investment/fd-investment.component.ts @@ -23,7 +23,9 @@ import { InvestmentGlAccount, InvestmentPartyDto, InvestmentService, - apiErrorMessage + apiErrorMessage, + awaitingApproval, + sentForApproval } from '../investment.service'; type FdTab = 'holdings' | 'purchase' | 'closure' | 'interest'; @@ -266,9 +268,13 @@ export class FdInvestmentComponent implements OnInit { this.investmentService .setDepositBase({ asOnDate: asOn, totalDeposits: this.baseAmount, locale: 'en', dateFormat: 'dd-MM-yyyy' }) .subscribe({ - next: () => { + next: (res) => { this.savingBase = false; - this.toast(`Total deposits as on ${asOn} saved.`); + this.toast( + awaitingApproval(res) + ? sentForApproval(`Total deposits as on ${asOn}`) + : `Total deposits as on ${asOn} saved.` + ); this.refreshQuote(); }, error: (err) => { @@ -319,7 +325,9 @@ export class FdInvestmentComponent implements OnInit { next: (res) => { this.saving = false; this.toast( - `FD ${res.changes?.dealNo ?? ''} placed — voucher ${res.changes?.voucherNo ?? res.resourceIdentifier ?? ''}.` + awaitingApproval(res) + ? sentForApproval(`FD of ₹${f.amount} with certificate ${f.certNo.trim()}`) + : `FD ${res.changes?.dealNo ?? ''} placed — voucher ${res.changes?.voucherNo ?? res.resourceIdentifier ?? ''}.` ); this.form = this.blankForm(); this.quote = null; @@ -394,7 +402,11 @@ export class FdInvestmentComponent implements OnInit { this.investmentService.postFdInterest(fdId, body).subscribe({ next: (res) => { this.postingInterest = false; - this.toast(`${this.lineLabel(r.lineType)} of ${r.intDate} posted — voucher ${res.resourceIdentifier ?? ''}.`); + this.toast( + awaitingApproval(res) + ? sentForApproval(`${this.lineLabel(r.lineType)} of ${r.intDate} voucher`) + : `${this.lineLabel(r.lineType)} of ${r.intDate} posted — voucher ${res.resourceIdentifier ?? ''}.` + ); this.loadSchedule(); }, error: (err) => { @@ -552,7 +564,12 @@ export class FdInvestmentComponent implements OnInit { : c.type === 'PREMATURE' ? 'closed' : 'matured'; - this.toast(`FD ${f.dealNo} ${what} — voucher ${res.changes?.voucherNo ?? res.resourceIdentifier ?? ''}.`); + const action = c.type === 'RENEWAL' ? 'Renewal' : c.type === 'PREMATURE' ? 'Premature closure' : 'Maturity'; + this.toast( + awaitingApproval(res) + ? sentForApproval(`${action} of FD ${f.dealNo}`) + : `FD ${f.dealNo} ${what} — voucher ${res.changes?.voucherNo ?? res.resourceIdentifier ?? ''}.` + ); this.closure = this.blankClosure(); this.closureQuote = null; this.loadFds(); diff --git a/src/app/investment/investment-masters/investment-masters.component.ts b/src/app/investment/investment-masters/investment-masters.component.ts index c706aa571..0149701ec 100644 --- a/src/app/investment/investment-masters/investment-masters.component.ts +++ b/src/app/investment/investment-masters/investment-masters.component.ts @@ -40,7 +40,14 @@ import { periodBetween } from '../investment-data'; import { exportWorkbook, ExportSheetSpec } from '../investment-export'; -import { InvestmentGlAccount, InvestmentService, apiErrorMessage, toDailyScriptRate } from '../investment.service'; +import { + InvestmentGlAccount, + InvestmentService, + apiErrorMessage, + awaitingApproval, + sentForApproval, + toDailyScriptRate +} from '../investment.service'; type MasterTab = | 'party' @@ -250,7 +257,11 @@ export class InvestmentMastersComponent implements OnInit { request.subscribe({ next: (res) => { this.saving = false; - this.toast(`Party ${res.resourceIdentifier ?? f.partyCode} ${f.id ? 'updated' : 'saved'}.`); + this.toast( + awaitingApproval(res) + ? sentForApproval(f.id ? `Change to party ${f.partyCode}` : `New party ${f.partyName ?? ''}`) + : `Party ${res.resourceIdentifier ?? f.partyCode} ${f.id ? 'updated' : 'saved'}.` + ); this.partyForm = {}; this.reloadMasters(); }, @@ -283,8 +294,10 @@ export class InvestmentMastersComponent implements OnInit { return; } this.investmentService.deleteParty(f.id).subscribe({ - next: () => { - this.toast(`Party ${f.partyCode} dropped.`); + next: (res) => { + this.toast( + awaitingApproval(res) ? sentForApproval(`Drop of party ${f.partyCode}`) : `Party ${f.partyCode} dropped.` + ); this.partyForm = {}; this.reloadMasters(); }, @@ -400,7 +413,11 @@ export class InvestmentMastersComponent implements OnInit { request.subscribe({ next: (res) => { this.saving = false; - this.toast(`Script ${res.resourceIdentifier ?? f.scriptCode} ${f.id ? 'updated' : 'saved'}.`); + this.toast( + awaitingApproval(res) + ? sentForApproval(f.id ? `Change to script ${f.scriptCode}` : `New script ${f.scriptName ?? ''}`) + : `Script ${res.resourceIdentifier ?? f.scriptCode} ${f.id ? 'updated' : 'saved'}.` + ); this.scriptForm = {}; this.reloadMasters(); }, diff --git a/src/app/investment/investment.service.ts b/src/app/investment/investment.service.ts index bac963d46..cd763f8d7 100644 --- a/src/app/investment/investment.service.ts +++ b/src/app/investment/investment.service.ts @@ -31,6 +31,19 @@ export const NON_SLR_TYPES: string[] = [ 'MUTUAL_FUND' ]; +/** + * Every investment entry goes through maker-checker (Jitendra Sir): the server then holds it for a checker and answers + * with its Checker Inbox entry and `rollbackTransaction` instead of carrying it out. Daily rates are not held. + */ +export function awaitingApproval(res: unknown): boolean { + return !!(res as { rollbackTransaction?: boolean } | null)?.rollbackTransaction; +} + +/** What to tell the user when `what` went to the Checker Inbox instead of taking effect. */ +export function sentForApproval(what: string): string { + return `${what} sent for approval (Checker Inbox) — nothing is saved or posted until a checker approves it.`; +} + /** Investment party as returned by /investment/parties. */ export interface InvestmentPartyDto { id: number;