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:
@@ -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>
|
||||
Reference in New Issue
Block a user