Files
cbsnexora/src/app/investment/investment-reports/investment-reports.component.html
T
nitesh 0832017508 feat(investment,reconciliation): add Export to Excel across Investment, fix export popup
Adds a shared Export to Excel utility (investment-export.ts) and wires it
into every listing/report screen in the Investment module — Dashboard
(break-up + allocation), Reports (all ~20 report shapes), Interest (per
active tab), the FD/Bond/Share Holdings registers, and Masters (per active
master tab). Entry/transaction forms intentionally have no export button.
No sign-off is included here, matching the request to keep that feature
reconciliation-only.

Also fixes the Reconciliation export flow: the Prepared/Checked/Approved
sign-off is now an optional one-off popup shown when "Export to Excel" is
clicked (fields are blank by default and downloading works whether or not
they're filled), replacing the earlier always-visible sign-off card. Also
fixes three reconciliation reports (Sundry Dr/Cr, NEFT/RTGS, UDIR) whose
labels contain "/" — an Excel-forbidden worksheet-name character — which
was silently aborting their export.
2026-08-01 19:20:53 +05:30

702 lines
22 KiB
HTML

<!--
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 · REPORTS</div>
<h1 class="pg-title">Investment Reports</h1>
<p class="pg-sub">
Balance reports, ledgers, registers, maturity lists and the consolidated portfolio — all derived live from the
same holdings/transactions used by the Entry screens.
</p>
</div>
<div class="pg-toolbar">
<button type="button" class="pg-btn pg-btn--ghost" [disabled]="generating" (click)="exportExcel()">
{{ generating ? 'Exporting…' : 'Export to Excel' }}
</button>
</div>
</header>
<div class="cctr-tabs">
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeGroup === 'balance'"
(click)="setGroup('balance', 'govtSecBalance')"
>
Balance Reports
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeGroup === 'ledger'"
(click)="setGroup('ledger', 'fdLedger')"
>
Ledgers &amp; Registers
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeGroup === 'maturity'"
(click)="setGroup('maturity', 'maturedFd')"
>
Maturity &amp; Lists
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeGroup === 'portfolio'"
(click)="setGroup('portfolio', 'portfolio')"
>
Portfolio
</button>
</div>
<!-- ===================== BALANCE REPORTS ===================== -->
@if (activeGroup === 'balance') {
<div class="cctr-tabs">
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'govtSecBalance'"
(click)="setReport('govtSecBalance')"
>
Govt Security Balance
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'shareBalance'"
(click)="setReport('shareBalance')"
>
Share Balance
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'fdrBalance'"
(click)="setReport('fdrBalance')"
>
FDR Balance
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'bankInvList'"
(click)="setReport('bankInvList')"
>
Bank Investment List
</button>
</div>
@if (activeReport === 'govtSecBalance' || activeReport === 'shareBalance' || activeReport === 'fdrBalance') {
<div class="pg-card">
<div class="pos-section-label">
{{
activeReport === 'govtSecBalance'
? 'Govt Security Balance Report'
: activeReport === 'shareBalance'
? 'Share Balance Report'
: 'FDR Balance Report'
}}
</div>
<table class="pg-table">
<thead>
<tr>
<th>Cert No</th>
<th>Script / Party</th>
<th class="num">Units</th>
<th class="num">Face Value</th>
<th class="num">Market/Purchase Rate</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@for (
h of activeReport === 'govtSecBalance'
? govtSecHoldings
: activeReport === 'shareBalance'
? shareHoldings
: fdHoldings;
track h.certNo
) {
<tr>
<td>
<span class="pg-chip">{{ h.certNo }}</span>
</td>
<td>{{ h.scriptName }} — {{ h.party }}</td>
<td class="num">{{ h.units | number }}</td>
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
<td class="num">{{ marketPrice(h.scriptCode) || h.purchaseRate }}</td>
<td>
<span [class]="statusClass(h)">{{ h.status }}</span>
</td>
</tr>
}
</tbody>
</table>
</div>
}
@if (activeReport === 'bankInvList') {
<div class="pg-card">
<div class="pos-section-label">Bank Investment List</div>
<table class="pg-table">
<thead>
<tr>
<th>Party / Bank</th>
<th class="num">No. of Holdings</th>
<th class="num">Total Face Value</th>
</tr>
</thead>
<tbody>
@for (r of bankInvestmentList; track r.party) {
<tr>
<td>{{ r.party }}</td>
<td class="num">{{ r.count }}</td>
<td class="num">₹{{ r.totalFaceValue | number: '1.2-2' }}</td>
</tr>
}
</tbody>
</table>
</div>
}
}
<!-- ===================== LEDGERS & REGISTERS ===================== -->
@if (activeGroup === 'ledger') {
<div class="cctr-tabs">
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'fdLedger'"
(click)="setReport('fdLedger')"
>
FD Ledger
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'bondLedger'"
(click)="setReport('bondLedger')"
>
Govt/Bond Ledger
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'shareLedger'"
(click)="setReport('shareLedger')"
>
Share Ledger
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'intDueRegister'"
(click)="setReport('intDueRegister')"
>
Interest Due Register
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'slrRegister'"
(click)="setReport('slrRegister')"
>
SLR Bond Register
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'pendingReceived'"
(click)="setReport('pendingReceived')"
>
Pending/Received Interest
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'amortizeLedger'"
(click)="setReport('amortizeLedger')"
>
Amortize Ledger
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'dealSlip'"
(click)="setReport('dealSlip')"
>
Deal Slip
</button>
</div>
@if (
activeReport === 'fdLedger' ||
activeReport === 'bondLedger' ||
activeReport === 'shareLedger' ||
activeReport === 'amortizeLedger' ||
activeReport === 'dealSlip'
) {
<div class="pg-card">
<div class="pos-section-label">
{{
activeReport === 'fdLedger'
? 'FD Ledger'
: activeReport === 'bondLedger'
? 'Govt/Bond Ledger'
: activeReport === 'shareLedger'
? 'Share Ledger'
: activeReport === 'amortizeLedger'
? 'Amortize Ledger'
: 'Deal Slip Register'
}}
</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 activeReport === 'fdLedger'
? fdTxns
: activeReport === 'bondLedger'
? bondTxns
: activeReport === 'shareLedger'
? shareTxns
: activeReport === 'amortizeLedger'
? amortizeTxns
: dealSlips;
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>
} @empty {
<tr>
<td colspan="6" class="pg-empty">No entries.</td>
</tr>
}
</tbody>
</table>
</div>
}
@if (activeReport === 'intDueRegister') {
<div class="pg-card">
<div class="pos-section-label">Interest Due Register (Non-Cumulative Holdings)</div>
<table class="pg-table">
<thead>
<tr>
<th>Cert No</th>
<th>Script / Party</th>
<th>Frequency</th>
<th class="num">Rate</th>
<th class="num col-in">Due Amount (as on {{ asOnDate }})</th>
</tr>
</thead>
<tbody>
@for (h of interestDueRegister; track h.certNo) {
<tr>
<td>
<span class="pg-chip">{{ h.certNo }}</span>
</td>
<td>{{ h.scriptName }} — {{ h.party }}</td>
<td>{{ h.interestFreq }}</td>
<td class="num">{{ h.interestRate }}%</td>
<td class="num col-in">₹{{ h.dueAmount | number: '1.2-2' }}</td>
</tr>
}
</tbody>
</table>
</div>
}
@if (activeReport === 'slrRegister') {
<div class="pg-card">
<div class="pos-section-label">SLR Bond Register</div>
<table class="pg-table">
<thead>
<tr>
<th>Cert No</th>
<th>Script</th>
<th class="num">Face Value</th>
<th class="num">Rate</th>
<th>Maturity Date</th>
</tr>
</thead>
<tbody>
@for (h of slrBondRegister; track h.certNo) {
<tr>
<td>
<span class="pg-chip">{{ h.certNo }}</span>
</td>
<td>{{ h.scriptName }}</td>
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
<td class="num">{{ h.interestRate }}%</td>
<td>{{ h.maturityDate }}</td>
</tr>
}
</tbody>
</table>
</div>
}
@if (activeReport === 'pendingReceived') {
<div class="pg-card">
<div class="pos-section-label">Received Interest / Dividend</div>
<table class="pg-table">
<thead>
<tr>
<th>Date</th>
<th>Cert No</th>
<th>Particulars</th>
<th class="num col-in">Amount</th>
</tr>
</thead>
<tbody>
@for (t of receivedInterestTxns; track t.voucherNo) {
<tr>
<td>{{ t.txnDate }}</td>
<td>{{ t.certNo }}</td>
<td>{{ t.particulars }}</td>
<td class="num col-in">₹{{ t.amount | number: '1.2-2' }}</td>
</tr>
}
</tbody>
</table>
</div>
<div class="pg-card">
<div class="pos-section-label">Pending Interest (No Interest Posted Yet)</div>
<table class="pg-table">
<thead>
<tr>
<th>Cert No</th>
<th>Script / Party</th>
<th class="num col-out">Accrued (Pending)</th>
</tr>
</thead>
<tbody>
@for (h of pendingInterestHoldings; track h.certNo) {
<tr>
<td>
<span class="pg-chip">{{ h.certNo }}</span>
</td>
<td>{{ h.scriptName }} — {{ h.party }}</td>
<td class="num col-out">₹{{ accrued(h) | number: '1.2-2' }}</td>
</tr>
}
</tbody>
</table>
</div>
}
}
<!-- ===================== MATURITY & LISTS ===================== -->
@if (activeGroup === 'maturity') {
<div class="cctr-tabs">
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'maturedFd'"
(click)="setReport('maturedFd')"
>
List of Matured FD
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'maturingList'"
(click)="setReport('maturingList')"
>
Govt/FD Mature List
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'maturityPattern'"
(click)="setReport('maturityPattern')"
>
Maturity Pattern Register
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'govtSecList'"
(click)="setReport('govtSecList')"
>
Govt Security List
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'fdSecList'"
(click)="setReport('fdSecList')"
>
FD Security List
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'shareSecList'"
(click)="setReport('shareSecList')"
>
Share Security List
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'mfList'"
(click)="setReport('mfList')"
>
Mutual Fund List
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[class.pg-btn--active]="activeReport === 'shiftingReport'"
(click)="setReport('shiftingReport')"
>
Shifting Report
</button>
</div>
@if (activeReport === 'maturedFd') {
<div class="pg-card">
<div class="pos-section-label">List of Matured FD</div>
<table class="pg-table">
<thead>
<tr>
<th>Cert No</th>
<th>Party</th>
<th class="num">Face Value</th>
<th>Matured On</th>
</tr>
</thead>
<tbody>
@for (h of maturedFdList; track h.certNo) {
<tr>
<td>
<span class="pg-chip">{{ h.certNo }}</span>
</td>
<td>{{ h.party }}</td>
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
<td>{{ h.maturityDate }}</td>
</tr>
} @empty {
<tr>
<td colspan="4" class="pg-empty">No matured FDs.</td>
</tr>
}
</tbody>
</table>
</div>
}
@if (activeReport === 'maturingList') {
<div class="pg-card">
<div class="pos-section-label">Govt Security / FD Maturity List (sorted by days-to-maturity)</div>
<table class="pg-table">
<thead>
<tr>
<th>Cert No</th>
<th>Instrument</th>
<th>Script / Party</th>
<th>Maturity Date</th>
<th class="num">Days Left</th>
<th class="num col-net">Maturity Value</th>
</tr>
</thead>
<tbody>
@for (h of maturingList; track h.certNo) {
<tr>
<td>
<span class="pg-chip">{{ h.certNo }}</span>
</td>
<td>{{ instrumentLabel(h.instrumentType) }}</td>
<td>{{ h.scriptName }} — {{ h.party }}</td>
<td>{{ h.maturityDate }}</td>
<td class="num">{{ daysLeft(h) }}</td>
<td class="num col-net">₹{{ maturity(h) | number: '1.2-2' }}</td>
</tr>
}
</tbody>
</table>
</div>
}
@if (activeReport === 'maturityPattern') {
<div class="pg-card">
<div class="pos-section-label">Maturity Pattern Register</div>
<table class="pg-table">
<thead>
<tr>
<th>Bucket</th>
<th class="num">Holdings</th>
<th class="num">Face Value</th>
</tr>
</thead>
<tbody>
@for (b of maturityBuckets; track b.label) {
<tr>
<td>{{ b.label }}</td>
<td class="num">{{ b.count }}</td>
<td class="num">₹{{ b.faceValue | number: '1.2-2' }}</td>
</tr>
}
</tbody>
</table>
</div>
}
@if (
activeReport === 'govtSecList' ||
activeReport === 'fdSecList' ||
activeReport === 'shareSecList' ||
activeReport === 'mfList'
) {
<div class="pg-card">
<div class="pos-section-label">
{{
activeReport === 'govtSecList'
? 'Govt Security List'
: activeReport === 'fdSecList'
? 'FD Security List'
: activeReport === 'shareSecList'
? 'Share Security List'
: 'Mutual Fund List'
}}
</div>
<table class="pg-table">
<thead>
<tr>
<th>Cert No</th>
<th>Script</th>
<th>Party</th>
<th class="num">Face Value</th>
<th class="num">Rate</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@for (
h of activeReport === 'govtSecList'
? govtSecList
: activeReport === 'fdSecList'
? fdSecList
: activeReport === 'shareSecList'
? shareSecList
: mutualFundList;
track h.certNo
) {
<tr>
<td>
<span class="pg-chip">{{ h.certNo }}</span>
</td>
<td>{{ h.scriptName }}</td>
<td>{{ h.party }}</td>
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
<td class="num">{{ h.interestRate }}%</td>
<td>
<span [class]="statusClass(h)">{{ h.status }}</span>
</td>
</tr>
}
</tbody>
</table>
</div>
}
@if (activeReport === 'shiftingReport') {
<div class="pg-card">
<div class="pos-section-label">
Shifting Report — SLR Securities with Market Deviation ≥ 1% (AFS/HTM review candidates)
</div>
<table class="pg-table">
<thead>
<tr>
<th>Cert No</th>
<th>Script</th>
<th class="num">Purchase Rate</th>
<th class="num">Market Price</th>
<th class="num">Deviation %</th>
</tr>
</thead>
<tbody>
@for (h of shiftingCandidates; track h.certNo) {
<tr>
<td>
<span class="pg-chip">{{ h.certNo }}</span>
</td>
<td>{{ h.scriptName }}</td>
<td class="num">{{ h.purchaseRate }}</td>
<td class="num">{{ h.marketPrice }}</td>
<td class="num" [class.col-in]="h.deviationPct > 0" [class.col-out]="h.deviationPct < 0">
{{ h.deviationPct }}%
</td>
</tr>
} @empty {
<tr>
<td colspan="5" class="pg-empty">No securities crossing the 1% deviation threshold today.</td>
</tr>
}
</tbody>
</table>
</div>
}
}
<!-- ===================== PORTFOLIO ===================== -->
@if (activeGroup === 'portfolio') {
<div class="pg-card">
<div class="pos-section-label">Investment Portfolio — Consolidated Summary</div>
<table class="pg-table">
<thead>
<tr>
<th>Instrument Type</th>
<th class="num">Holdings</th>
<th class="num">Face Value</th>
<th class="num col-in">Accrued Interest</th>
<th class="num col-net">Maturity Value</th>
</tr>
</thead>
<tbody>
@for (r of portfolioSummary; track r.label) {
<tr>
<td>{{ r.label }}</td>
<td class="num">{{ r.count }}</td>
<td class="num">₹{{ r.faceValue | number: '1.2-2' }}</td>
<td class="num col-in">₹{{ r.accrued | number: '1.2-2' }}</td>
<td class="num col-net">₹{{ r.maturityValue | number: '1.2-2' }}</td>
</tr>
}
</tbody>
<tfoot>
<tr class="cctr-total-row">
<td>Total</td>
<td class="num"></td>
<td class="num">₹{{ portfolioTotalFaceValue | number: '1.2-2' }}</td>
<td class="num">₹{{ portfolioTotalAccrued | number: '1.2-2' }}</td>
<td class="num">₹{{ portfolioTotalMaturity | number: '1.2-2' }}</td>
</tr>
</tfoot>
</table>
</div>
}
</div>
<div class="inv-toast" [class.show]="toastVisible">{{ toastMsg }}</div>