feat(investment): show sent-for-approval message on investment actions

This commit is contained in:
2026-10-27 17:21:33 +05:30
parent d26cb89c5a
commit ad0a1a0b1d
4 changed files with 121 additions and 29 deletions
@@ -75,6 +75,8 @@ import {
InvestmentValuationDto, InvestmentValuationDto,
NON_SLR_TYPES, NON_SLR_TYPES,
apiErrorMessage, apiErrorMessage,
awaitingApproval,
sentForApproval,
toHolding, toHolding,
toInterestTxn, toInterestTxn,
toPurchaseTxn, toPurchaseTxn,
@@ -1003,7 +1005,9 @@ export class BondInvestmentComponent implements OnInit {
this.investmentService.createPurchase(body).subscribe({ this.investmentService.createPurchase(body).subscribe({
next: (res) => { next: (res) => {
this.toast( 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.activeTab = 'holdings';
this.loadPurchases(); this.loadPurchases();
@@ -1116,8 +1120,8 @@ export class BondInvestmentComponent implements OnInit {
next: (res) => { next: (res) => {
this.settlingDealId = null; this.settlingDealId = null;
this.toast( this.toast(
res.rollbackTransaction awaitingApproval(res)
? `Settlement of deal ${h.dealNo} sent for approval (Checker Inbox).` ? sentForApproval(`Settlement of deal ${h.dealNo}`)
: `Deal ${h.dealNo} settled and posted to GL (transaction ${res.transactionId}).` : `Deal ${h.dealNo} settled and posted to GL (transaction ${res.transactionId}).`
); );
this.loadPurchases(); this.loadPurchases();
@@ -1136,8 +1140,12 @@ export class BondInvestmentComponent implements OnInit {
return; return;
} }
this.investmentService.deletePurchase(h.id).subscribe({ this.investmentService.deletePurchase(h.id).subscribe({
next: () => { next: (res) => {
this.toast(`Deal ${h.dealNo} (Cert No ${h.certNo}) dropped.`); this.toast(
awaitingApproval(res)
? sentForApproval(`Drop of deal ${h.dealNo}`)
: `Deal ${h.dealNo} (Cert No ${h.certNo}) dropped.`
);
this.loadPurchases(); this.loadPurchases();
}, },
error: (err) => this.toast(apiErrorMessage(err)) error: (err) => this.toast(apiErrorMessage(err))
@@ -1211,7 +1219,11 @@ export class BondInvestmentComponent implements OnInit {
.subscribe({ .subscribe({
next: (res) => { next: (res) => {
this.redeeming = false; 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.redeemPurchaseId = null;
this.redeemQuote = null; this.redeemQuote = null;
this.redeemRemarks = ''; this.redeemRemarks = '';
@@ -1315,8 +1327,11 @@ export class BondInvestmentComponent implements OnInit {
this.investmentService.postInterest(sc.purchaseId, body).subscribe({ this.investmentService.postInterest(sc.purchaseId, body).subscribe({
next: (res) => { next: (res) => {
this.intPosting = false; this.intPosting = false;
const line = `${r.type === 'COUPON' ? 'Coupon' : 'Provision'} of ${r.intDate}`;
this.toast( 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.retrieveInterestSchedule();
this.loadDueInterest(); this.loadDueInterest();
@@ -1450,9 +1465,13 @@ export class BondInvestmentComponent implements OnInit {
}; };
this.savingSale = true; this.savingSale = true;
this.investmentService.createSale(body).subscribe({ this.investmentService.createSale(body).subscribe({
next: () => { next: (res) => {
this.savingSale = false; 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.sale = this.blankSale();
this.saleQuote = null; this.saleQuote = null;
this.loadInterestLog(); this.loadInterestLog();
@@ -1475,6 +1494,10 @@ export class BondInvestmentComponent implements OnInit {
this.investmentService.settleSale(s.id).subscribe({ this.investmentService.settleSale(s.id).subscribe({
next: (res) => { next: (res) => {
this.busySaleId = null; 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.sales = this.sales.filter((x) => x.id !== s.id);
this.toast(`Sale of deal ${s.dealNo} settled — voucher ${res.resourceIdentifier ?? ''}.`); this.toast(`Sale of deal ${s.dealNo} settled — voucher ${res.resourceIdentifier ?? ''}.`);
this.loadPurchases(); this.loadPurchases();
@@ -1492,8 +1515,12 @@ export class BondInvestmentComponent implements OnInit {
} }
this.busySaleId = s.id; this.busySaleId = s.id;
this.investmentService.deleteSale(s.id).subscribe({ this.investmentService.deleteSale(s.id).subscribe({
next: () => { next: (res) => {
this.busySaleId = null; 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.sales = this.sales.filter((x) => x.id !== s.id);
this.toast(`Pending sale of deal ${s.dealNo} dropped.`); this.toast(`Pending sale of deal ${s.dealNo} dropped.`);
this.loadInterestLog(); this.loadInterestLog();
@@ -1549,7 +1576,11 @@ export class BondInvestmentComponent implements OnInit {
this.investmentService.writeOff(h.id, { amount, remarks: this.writeoffReason.trim(), locale: 'en' }).subscribe({ this.investmentService.writeOff(h.id, { amount, remarks: this.writeoffReason.trim(), locale: 'en' }).subscribe({
next: (res) => { next: (res) => {
this.writingOff = false; 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.writeoffPurchaseId = null;
this.writeoffAmount = null; this.writeoffAmount = null;
this.writeoffReason = ''; this.writeoffReason = '';
@@ -1594,7 +1625,11 @@ export class BondInvestmentComponent implements OnInit {
.subscribe({ .subscribe({
next: (res) => { next: (res) => {
this.amortizing = false; 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.onAmortizeDealChange();
this.loadPurchases(); this.loadPurchases();
}, },
@@ -1645,7 +1680,9 @@ export class BondInvestmentComponent implements OnInit {
next: (res) => { next: (res) => {
this.amortizing = false; this.amortizing = false;
this.toast( 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.loadAmortizeDue();
this.loadPurchases(); this.loadPurchases();
@@ -1721,7 +1758,11 @@ export class BondInvestmentComponent implements OnInit {
.subscribe({ .subscribe({
next: (res) => { next: (res) => {
this.goiSaving = false; 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.goiPurchaseId = null;
this.goiToScriptCode = ''; this.goiToScriptCode = '';
this.goiRemarks = ''; this.goiRemarks = '';
@@ -1794,7 +1835,9 @@ export class BondInvestmentComponent implements OnInit {
next: (res) => { next: (res) => {
this.valuationPosting = false; this.valuationPosting = false;
this.toast( this.toast(
v.delta === 0 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} recorded; the IDR needed is unchanged, so no voucher.`
: `Valuation of ${v.valuationDate} posted — ${res.resourceIdentifier ?? ''}.` : `Valuation of ${v.valuationDate} posted — ${res.resourceIdentifier ?? ''}.`
); );
@@ -1956,7 +1999,9 @@ export class BondInvestmentComponent implements OnInit {
next: (res) => { next: (res) => {
this.mfSwitchSaving = false; this.mfSwitchSaving = false;
this.toast( 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.mfSwitchPurchaseId = null;
this.mfSwitchUnits = null; this.mfSwitchUnits = null;
@@ -23,7 +23,9 @@ import {
InvestmentGlAccount, InvestmentGlAccount,
InvestmentPartyDto, InvestmentPartyDto,
InvestmentService, InvestmentService,
apiErrorMessage apiErrorMessage,
awaitingApproval,
sentForApproval
} from '../investment.service'; } from '../investment.service';
type FdTab = 'holdings' | 'purchase' | 'closure' | 'interest'; type FdTab = 'holdings' | 'purchase' | 'closure' | 'interest';
@@ -266,9 +268,13 @@ export class FdInvestmentComponent implements OnInit {
this.investmentService this.investmentService
.setDepositBase({ asOnDate: asOn, totalDeposits: this.baseAmount, locale: 'en', dateFormat: 'dd-MM-yyyy' }) .setDepositBase({ asOnDate: asOn, totalDeposits: this.baseAmount, locale: 'en', dateFormat: 'dd-MM-yyyy' })
.subscribe({ .subscribe({
next: () => { next: (res) => {
this.savingBase = false; 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(); this.refreshQuote();
}, },
error: (err) => { error: (err) => {
@@ -319,7 +325,9 @@ export class FdInvestmentComponent implements OnInit {
next: (res) => { next: (res) => {
this.saving = false; this.saving = false;
this.toast( 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.form = this.blankForm();
this.quote = null; this.quote = null;
@@ -394,7 +402,11 @@ export class FdInvestmentComponent implements OnInit {
this.investmentService.postFdInterest(fdId, body).subscribe({ this.investmentService.postFdInterest(fdId, body).subscribe({
next: (res) => { next: (res) => {
this.postingInterest = false; 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(); this.loadSchedule();
}, },
error: (err) => { error: (err) => {
@@ -552,7 +564,12 @@ export class FdInvestmentComponent implements OnInit {
: c.type === 'PREMATURE' : c.type === 'PREMATURE'
? 'closed' ? 'closed'
: 'matured'; : '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.closure = this.blankClosure();
this.closureQuote = null; this.closureQuote = null;
this.loadFds(); this.loadFds();
@@ -40,7 +40,14 @@ import {
periodBetween periodBetween
} from '../investment-data'; } from '../investment-data';
import { exportWorkbook, ExportSheetSpec } from '../investment-export'; 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 = type MasterTab =
| 'party' | 'party'
@@ -250,7 +257,11 @@ export class InvestmentMastersComponent implements OnInit {
request.subscribe({ request.subscribe({
next: (res) => { next: (res) => {
this.saving = false; 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.partyForm = {};
this.reloadMasters(); this.reloadMasters();
}, },
@@ -283,8 +294,10 @@ export class InvestmentMastersComponent implements OnInit {
return; return;
} }
this.investmentService.deleteParty(f.id).subscribe({ this.investmentService.deleteParty(f.id).subscribe({
next: () => { next: (res) => {
this.toast(`Party ${f.partyCode} dropped.`); this.toast(
awaitingApproval(res) ? sentForApproval(`Drop of party ${f.partyCode}`) : `Party ${f.partyCode} dropped.`
);
this.partyForm = {}; this.partyForm = {};
this.reloadMasters(); this.reloadMasters();
}, },
@@ -400,7 +413,11 @@ export class InvestmentMastersComponent implements OnInit {
request.subscribe({ request.subscribe({
next: (res) => { next: (res) => {
this.saving = false; 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.scriptForm = {};
this.reloadMasters(); this.reloadMasters();
}, },
+13
View File
@@ -31,6 +31,19 @@ export const NON_SLR_TYPES: string[] = [
'MUTUAL_FUND' '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. */ /** Investment party as returned by /investment/parties. */
export interface InvestmentPartyDto { export interface InvestmentPartyDto {
id: number; id: number;