feat(investment): add Treasury Investment module (FD, Govt Sec/Non-SLR/Bond/MF, Share)

Adds full frontend-only Investment module mirroring the legacy AutoBank CBS
Investment menu: Dashboard, FD/Bond/Share entry screens (purchase, interest,
sell/redeem, write-off, amortize and more), 10 master screens, an Interest
calculation/register screen, and ~21 reports. All figures are derived from a
single holdings/transactions dataset so entry, interest, and reports can
never drift out of sync. Wires the new "Investment" mega-menu into the shell
and routes the previously inert Investment stub buttons.
This commit is contained in:
2026-08-01 09:17:06 +05:30
parent fd28788d23
commit 63ac900e42
22 changed files with 8249 additions and 6 deletions
@@ -0,0 +1,472 @@
<!--
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/.
-->
<div class="pg">
<header class="pg-head">
<div>
<div class="pg-eyebrow">TREASURY · INVESTMENT · FIXED DEPOSIT</div>
<h1 class="pg-title">FD Investment</h1>
<p class="pg-sub">
Bank's own Fixed Deposit investments with other banks — Purchase, Interest, Sell/Premature, Write-off and
Part-Payment entries.
</p>
</div>
</header>
<div class="cctr-tabs">
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeTab === 'holdings'"
(click)="setTab('holdings')"
>
Holdings
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeTab === 'purchase'"
(click)="setTab('purchase')"
>
01 Purchase Entry
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeTab === 'interest'"
(click)="setTab('interest')"
>
02 Interest Entry
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeTab === 'sell'"
(click)="setTab('sell')"
>
03 Sell / Premature Entry
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeTab === 'writeoff'"
(click)="setTab('writeoff')"
>
04 Write Off Entry
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeTab === 'partpayment'"
(click)="setTab('partpayment')"
>
05 FD Part Payment Entry
</button>
</div>
<!-- HOLDINGS -->
@if (activeTab === 'holdings') {
<div class="pg-card">
<div class="pos-section-label">FD Holdings Register</div>
<table class="pg-table">
<thead>
<tr>
<th>Cert No</th>
<th>Party</th>
<th>Type</th>
<th class="num">Face Value</th>
<th class="num">Rate</th>
<th>Purchase Date</th>
<th>Maturity Date</th>
<th class="num col-in">Accrued Interest</th>
<th class="num col-net">Maturity Value</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@for (h of holdings; track h.certNo) {
<tr>
<td>
<span class="pg-chip">{{ h.certNo }}</span>
</td>
<td>{{ h.party }}</td>
<td>{{ h.interestFreq === 'CUMULATIVE' ? 'Cumulative' : 'Payout' }}</td>
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
<td class="num">{{ h.interestRate }}%</td>
<td>{{ h.purchaseDate }}</td>
<td>{{ h.maturityDate }}</td>
<td class="num col-in">₹{{ accrued(h) | number: '1.2-2' }}</td>
<td class="num col-net">₹{{ maturity(h) | number: '1.2-2' }}</td>
<td>
<span [class]="statusClass(h)">{{ h.status }}</span>
</td>
</tr>
}
</tbody>
</table>
</div>
}
<!-- PURCHASE ENTRY -->
@if (activeTab === 'purchase') {
<div class="pg-card">
<div class="pos-section-label">Investment - FD Purchase Entry</div>
<div class="inv-form-grid">
<label class="inv-form-field">
<span>Tran. Type</span>
<select [(ngModel)]="purchaseForm.tranType">
<option value="FRESH">FRESH</option>
<option value="RENEWAL">RENEWAL</option>
</select>
</label>
@if (purchaseForm.tranType === 'RENEWAL') {
<label class="inv-form-field"
><span>OLD CertNo</span><input type="text" [(ngModel)]="purchaseForm.oldCertNo"
/></label>
}
<label class="inv-form-field">
<span>FD Type</span>
<select [(ngModel)]="purchaseForm.fdType">
@for (t of fdTypeOptions; track t) {
<option [value]="t">{{ t }}</option>
}
</select>
</label>
<label class="inv-form-field">
<span>Mature Amt.calc.Type</span>
<select [(ngModel)]="purchaseForm.matureAmtCalcType">
<option value="GENERAL">GENERAL</option>
<option value="DATEWISE">DATEWISE</option>
</select>
</label>
<label class="inv-form-field">
<span>Party *</span>
<select [(ngModel)]="purchaseForm.party">
<option value="">-- Select --</option>
@for (p of partyOptions; track p.partyCode) {
<option [value]="p.partyName">{{ p.partyName }}</option>
}
</select>
</label>
<label class="inv-form-field">
<span>Sub Party</span>
<select [(ngModel)]="purchaseForm.subParty">
<option value="">-- Select --</option>
@for (p of partyOptions; track p.partyCode) {
<option [value]="p.partyName">{{ p.partyName }}</option>
}
</select>
</label>
<label class="inv-form-field"
><span>GL ACNO</span><input type="text" [(ngModel)]="purchaseForm.glAcNo"
/></label>
<label class="inv-form-field"
><span>GL A/c Name</span><input type="text" [(ngModel)]="purchaseForm.glAcName" readonly
/></label>
<label class="inv-form-field"
><span>Certificate No.</span><input type="text" [(ngModel)]="purchaseForm.certNo"
/></label>
<label class="inv-form-field"
><span>Certificate Date</span><input type="text" [(ngModel)]="purchaseForm.certDate" placeholder="DD-MM-YYYY"
/></label>
<label class="inv-form-field">
<span>Sub Type</span>
<select [(ngModel)]="purchaseForm.subType">
@for (s of subTypeOptions; track s.subTypeCode + s.subTypeName) {
<option [value]="s.subTypeName">{{ s.subTypeCode }} - {{ s.subTypeName }}</option>
}
</select>
</label>
<label class="inv-form-field">
<span>Reciept/Advice</span>
<select [(ngModel)]="purchaseForm.receiptAdvice">
<option value="RECEIPT">RECEIPT</option>
<option value="ADVICE">ADVICE</option>
</select>
</label>
<label class="inv-form-field"
><span>Transaction Date</span
><input type="text" [(ngModel)]="purchaseForm.transactionDate" placeholder="DD-MM-YYYY"
/></label>
<label class="inv-form-field"
><span>Deposit Date *</span
><input type="text" [(ngModel)]="purchaseForm.depositDate" placeholder="DD-MM-YYYY"
/></label>
<label class="inv-form-field"
><span>Deposit Amount *</span><input type="number" [(ngModel)]="purchaseForm.depositAmount" min="0"
/></label>
<label class="inv-form-field">
<span>Interest[M/Q/H/Y]</span>
<select [(ngModel)]="purchaseForm.interestFreq">
@for (i of intTypeOptions; track i.value) {
<option [value]="i.value">{{ i.label }}</option>
}
</select>
</label>
<label class="inv-form-field"
><span>Interest Date</span
><input type="text" [(ngModel)]="purchaseForm.interestDate" placeholder="DD-MM-YYYY"
/></label>
<label class="inv-form-field"
><span>Interest Rate *</span><input type="number" [(ngModel)]="purchaseForm.interestRate" min="0" step="0.01"
/></label>
@if (purchaseForm.matureAmtCalcType === 'GENERAL') {
<label class="inv-form-field"
><span>Period Month</span><input type="number" [(ngModel)]="purchaseForm.periodMonth" min="0" max="11"
/></label>
<label class="inv-form-field"
><span>Year</span><input type="number" [(ngModel)]="purchaseForm.periodYear" min="0"
/></label>
<label class="inv-form-field"
><span>Days</span><input type="number" [(ngModel)]="purchaseForm.periodDays" min="0"
/></label>
} @else {
<label class="inv-form-field"
><span>Maturity Date (manual) *</span
><input type="text" [(ngModel)]="purchaseForm.manualMaturityDate" placeholder="DD-MM-YYYY"
/></label>
}
<label class="inv-form-field"
><span>Resolution No.</span><input type="text" [(ngModel)]="purchaseForm.resolutionNo"
/></label>
<label class="inv-form-field">
<span>CR. Branch Code</span>
<select [(ngModel)]="purchaseForm.crBranchCode">
@for (b of branchOptions; track b) {
<option [value]="b">{{ b }}</option>
}
</select>
</label>
<label class="inv-form-field"
><span>CR. To A/c</span><input type="text" [(ngModel)]="purchaseForm.crToAc"
/></label>
<label class="inv-form-field"
><span>CR. A/c Name</span><input type="text" [(ngModel)]="purchaseForm.crToAcName" readonly
/></label>
<label class="inv-form-field"
><span>Int.Receivable Code</span><input type="text" [(ngModel)]="purchaseForm.intReceivableCode"
/></label>
<label class="inv-form-field">
<span>LIEN Y/N</span>
<select [(ngModel)]="purchaseForm.lien">
<option value="NO">NO</option>
<option value="YES">YES</option>
</select>
</label>
@if (purchaseForm.lien === 'YES') {
<label class="inv-form-field"
><span>LIEN AMT.</span><input type="number" [(ngModel)]="purchaseForm.lienAmt"
/></label>
<label class="inv-form-field">
<span>LIEN Reason</span>
<select [(ngModel)]="purchaseForm.lienReason">
<option value="">-- Select --</option>
@for (l of lienReasonOptions; track l.reasonCode) {
<option [value]="l.reasonDescription">{{ l.reasonDescription }}</option>
}
</select>
</label>
}
<label class="inv-form-field">
<span>Classification</span>
<select [(ngModel)]="purchaseForm.classification">
<option value="">-- Select --</option>
<option value="STANDARD">STANDARD</option>
<option value="SUB-STANDARD">SUB-STANDARD</option>
<option value="DOUBTFUL">DOUBTFUL</option>
</select>
</label>
<label class="inv-form-field">
<span>Collable</span>
<select [(ngModel)]="purchaseForm.collable">
<option value="NO">NO</option>
<option value="YES">YES</option>
</select>
</label>
</div>
<div class="pos-section-label">Mature / Premature Details</div>
<div class="inv-form-grid">
<label class="inv-form-field"
><span>* Maturity Date</span><input type="text" [value]="purchaseMaturityDate" readonly
/></label>
<label class="inv-form-field"
><span>* Maturity Amt</span><input type="text" [value]="purchaseMaturityAmount | number: '1.0-0'" readonly
/></label>
<label class="inv-form-field"
><span>Search by Cert No</span><input type="text" [(ngModel)]="searchCertNo" placeholder="Enter Cert No"
/></label>
</div>
<div class="inv-form-actions">
<button type="button" class="pg-btn pg-btn--primary" (click)="savePurchase()">Save</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="cancelPurchase()">Cancel</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="searchPurchase()">Search</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="dropPurchase()">Drop</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="exitPurchase()">Exit</button>
</div>
</div>
}
<!-- INTEREST ENTRY -->
@if (activeTab === 'interest') {
<div class="pg-card">
<div class="pos-section-label">Investment - FD Interest Entry / Calculation</div>
<div class="inv-form-grid">
<label class="inv-form-field">
<span>Certificate No *</span>
<select [(ngModel)]="interestCertNo">
<option value="">-- Select --</option>
@for (h of activeHoldings; track h.certNo) {
<option [value]="h.certNo">{{ h.certNo }} — {{ h.party }}</option>
}
</select>
</label>
@if (interestHolding; as h) {
<label class="inv-form-field"><span>Party</span><input type="text" [value]="h.party" readonly /></label>
<label class="inv-form-field"
><span>Interest Rate</span><input type="text" [value]="h.interestRate + '%'" readonly
/></label>
<label class="inv-form-field"
><span>Frequency</span><input type="text" [value]="h.interestFreq" readonly
/></label>
<label class="inv-form-field"
><span>Accrued Interest (as on {{ asOnDate }})</span
><input type="text" [value]="'₹' + (interestAccrued(h) | number: '1.2-2')" readonly
/></label>
}
</div>
<div class="inv-form-actions">
<button type="button" class="pg-btn pg-btn--primary" (click)="postInterest()">Process / Post Interest</button>
</div>
</div>
}
<!-- SELL / PREMATURE ENTRY -->
@if (activeTab === 'sell') {
<div class="pg-card">
<div class="pos-section-label">Investment - FD Sell / Premature Closure Entry</div>
<div class="inv-form-grid">
<label class="inv-form-field">
<span>Certificate No *</span>
<select [(ngModel)]="sellCertNo">
<option value="">-- Select --</option>
@for (h of activeHoldings; track h.certNo) {
<option [value]="h.certNo">{{ h.certNo }} — {{ h.party }}</option>
}
</select>
</label>
@if (sellHolding; as h) {
<label class="inv-form-field"
><span>Face Value</span><input type="text" [value]="'₹' + (h.faceValue | number: '1.2-2')" readonly
/></label>
<label class="inv-form-field"
><span>Accrued Interest till Sell Date</span
><input type="text" [value]="'₹' + (accrued(h) | number: '1.2-2')" readonly
/></label>
}
<label class="inv-form-field"
><span>Sell / Premature Date *</span><input type="text" [(ngModel)]="sellDate" placeholder="DD-MM-YYYY"
/></label>
<label class="inv-form-field"><span>Remarks</span><input type="text" [(ngModel)]="sellRemark" /></label>
</div>
<div class="inv-form-actions">
<button type="button" class="pg-btn pg-btn--primary" (click)="saveSell()">Save</button>
</div>
</div>
}
<!-- WRITE OFF ENTRY -->
@if (activeTab === 'writeoff') {
<div class="pg-card">
<div class="pos-section-label">Investment - FD Write Off Entry</div>
<div class="inv-form-grid">
<label class="inv-form-field">
<span>Certificate No *</span>
<select [(ngModel)]="writeoffCertNo">
<option value="">-- Select --</option>
@for (h of activeHoldings; track h.certNo) {
<option [value]="h.certNo">{{ h.certNo }} — {{ h.party }}</option>
}
</select>
</label>
@if (writeoffHolding; as h) {
<label class="inv-form-field"
><span>Face Value to be Written Off</span
><input type="text" [value]="'₹' + (h.faceValue | number: '1.2-2')" readonly
/></label>
}
<label class="inv-form-field"><span>Reason *</span><input type="text" [(ngModel)]="writeoffReason" /></label>
</div>
<div class="inv-form-actions">
<button type="button" class="pg-btn pg-btn--primary" (click)="saveWriteoff()">Save</button>
</div>
</div>
}
<!-- PART PAYMENT ENTRY -->
@if (activeTab === 'partpayment') {
<div class="pg-card">
<div class="pos-section-label">Investment - FD Part Payment Entry</div>
<div class="inv-form-grid">
<label class="inv-form-field">
<span>Certificate No *</span>
<select [(ngModel)]="partPayCertNo">
<option value="">-- Select --</option>
@for (h of activeHoldings; track h.certNo) {
<option [value]="h.certNo">{{ h.certNo }} — {{ h.party }}</option>
}
</select>
</label>
@if (partPayHolding; as h) {
<label class="inv-form-field"
><span>Current Face Value</span><input type="text" [value]="'₹' + (h.faceValue | number: '1.2-2')" readonly
/></label>
}
<label class="inv-form-field"
><span>Part Payment Amount *</span><input type="number" [(ngModel)]="partPayAmount" min="0"
/></label>
</div>
<div class="inv-form-actions">
<button type="button" class="pg-btn pg-btn--primary" (click)="savePartPayment()">Save</button>
</div>
</div>
}
@if (activeTab !== 'holdings') {
<div class="pg-card">
<div class="pos-section-label">FD Transaction Log</div>
<table class="pg-table">
<thead>
<tr>
<th>Date</th>
<th>Voucher No</th>
<th>Cert No</th>
<th>Type</th>
<th>Particulars</th>
<th class="num">Amount</th>
</tr>
</thead>
<tbody>
@for (t of txns; track t.voucherNo) {
<tr>
<td>{{ t.txnDate }}</td>
<td>
<span class="pg-chip">{{ t.voucherNo }}</span>
</td>
<td>{{ t.certNo }}</td>
<td>{{ t.txnType }}</td>
<td>{{ t.particulars }}</td>
<td class="num">₹{{ t.amount | number: '1.2-2' }}</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
<div class="inv-toast" [class.show]="toastVisible">{{ toastMsg }}</div>