- Reports > Interest Posting Reports: per branch and product, generate/regenerate at any time, approve; branch column, HO-on-behalf marker, posted txn per account - Reports > Interest Posting Summary (branch-wise) for the head office, with Excel export - Day Close warns and disables Close Day while the branch's interest report is unapproved - Savings product accounting: Interest On Savings (expense), Savings Interest Payable and Paid (liability rows, required) - RBI Form 1 statutory and Form 9 return screens, reserve config updates - Interest posting report GETs exempted from the response cache Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
191 lines
6.9 KiB
HTML
191 lines
6.9 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="ipr">
|
|
<div class="ipr-head">
|
|
<div>
|
|
<h2 class="ipr-head-t"><fa-icon icon="percentage"></fa-icon> Interest Posting Reports</h2>
|
|
<div class="ipr-head-s">
|
|
Each branch gets its own report for each savings product, generated automatically at 12:00 PM on the posting
|
|
date and open to generating or regenerating at any time. The branch approves it, and its approved interest is
|
|
posted when the branch closes the day — it cannot close until then. The head office closes last.
|
|
</div>
|
|
</div>
|
|
<div class="ipr-head-a">
|
|
<label class="ipr-date">
|
|
Posting date
|
|
<input type="date" [(ngModel)]="postingDate" (change)="load()" />
|
|
</label>
|
|
<button class="ipr-btn" (click)="load()" [disabled]="loading"><fa-icon icon="undo"></fa-icon> Reload</button>
|
|
</div>
|
|
</div>
|
|
|
|
@if (errorMessage) {
|
|
<div class="ipr-alert ipr-alert--error">
|
|
<fa-icon icon="exclamation-triangle"></fa-icon>
|
|
<div>{{ errorMessage }}</div>
|
|
</div>
|
|
}
|
|
@if (notice) {
|
|
<div class="ipr-alert ipr-alert--ok">
|
|
<fa-icon icon="check"></fa-icon>
|
|
<div>{{ notice }}</div>
|
|
</div>
|
|
}
|
|
@if (pendingCount > 0) {
|
|
<div class="ipr-alert ipr-alert--warn">
|
|
<fa-icon icon="clock"></fa-icon>
|
|
<div>{{ pendingCount }} report(s) waiting for approval. The day cannot be closed until they are approved.</div>
|
|
</div>
|
|
}
|
|
|
|
@if (due.length > 0) {
|
|
<div class="ipr-alert ipr-alert--warn ipr-due">
|
|
<fa-icon icon="exclamation-triangle"></fa-icon>
|
|
<div class="ipr-due-body">
|
|
<div>Interest is due but no report has been generated yet for:</div>
|
|
@for (d of due; track d.productId + '-' + d.officeId) {
|
|
<div class="ipr-due-row">
|
|
<strong>{{ d.productName }}</strong> <span>· {{ d.officeName }} · {{ d.postingDate }}</span>
|
|
<button class="ipr-btn ipr-btn--primary ipr-btn--slim" (click)="generate(d)" [disabled]="busy">
|
|
<fa-icon icon="sync"></fa-icon> Generate report
|
|
</button>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="ipr-card">
|
|
<table class="ipr-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Branch</th>
|
|
<th>Product</th>
|
|
<th>Posting date</th>
|
|
<th class="num">Accounts</th>
|
|
<th class="num">Total interest</th>
|
|
<th>Status</th>
|
|
<th>Generated</th>
|
|
<th>Approved</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (r of reports; track r.id) {
|
|
<tr [class.is-selected]="selected?.id === r.id" (click)="open(r)">
|
|
<td>{{ r.officeName }}</td>
|
|
<td>
|
|
<strong>{{ r.productName }}</strong>
|
|
</td>
|
|
<td>{{ r.postingDate }}</td>
|
|
<td class="num">{{ r.accountCount }}</td>
|
|
<td class="num">₹{{ r.totalInterest | number: '1.2-2' }}</td>
|
|
<td>
|
|
<span class="ipr-badge" [ngClass]="statusClass(r.status)">{{ r.status }}</span>
|
|
@if (r.failureReason) {
|
|
<div class="ipr-fail" [title]="r.failureReason">Posting failed: {{ r.failureReason }}</div>
|
|
}
|
|
</td>
|
|
<td class="muted">
|
|
{{ r.generatedBy || 'System' }} · {{ r.generationMode }}<br />{{ r.generatedAt | date: 'dd MMM, HH:mm' }}
|
|
</td>
|
|
<td class="muted">
|
|
@if (r.approvedAt) {
|
|
{{ r.approvedBy }}
|
|
@if (r.approvedOnBehalf) {
|
|
<span class="ipr-behalf">(HO)</span>
|
|
}
|
|
<br />{{ r.approvedAt | date: 'dd MMM, HH:mm' }}
|
|
} @else {
|
|
—
|
|
}
|
|
</td>
|
|
<td class="actions" (click)="$event.stopPropagation()">
|
|
@if (canRegenerate(r)) {
|
|
<button class="ipr-btn ipr-btn--slim" (click)="regenerate(r)" [disabled]="busy">
|
|
<fa-icon icon="sync"></fa-icon> Regenerate
|
|
</button>
|
|
}
|
|
@if (canApprove(r)) {
|
|
<button class="ipr-btn ipr-btn--primary ipr-btn--slim" (click)="approve(r)" [disabled]="busy">
|
|
<fa-icon icon="check"></fa-icon> Approve
|
|
</button>
|
|
}
|
|
</td>
|
|
</tr>
|
|
} @empty {
|
|
<tr>
|
|
<td colspan="9" class="ipr-empty">
|
|
@if (loading) {
|
|
Loading…
|
|
} @else {
|
|
No interest posting report for {{ postingDate || 'this date' }}. Reports are generated at 12:00 PM on a
|
|
product's posting date.
|
|
}
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
@if (selected) {
|
|
<div class="ipr-card">
|
|
<div class="ipr-detail-head">
|
|
<div>
|
|
<h3>{{ selected.officeName }} · {{ selected.productName }} — {{ selected.postingDate }}</h3>
|
|
<div class="muted">
|
|
Dr {{ selected.expenseGlCode }} {{ selected.expenseGlName }} → Cr {{ selected.payableGlCode }}
|
|
{{ selected.payableGlName }}; Dr {{ selected.payableGlCode }} → Cr {{ selected.paidGlCode }}
|
|
{{ selected.paidGlName }}; then Dr {{ selected.paidGlCode }} → Cr each account below
|
|
</div>
|
|
</div>
|
|
<div class="ipr-total">
|
|
<span>Total interest</span>
|
|
<strong>₹{{ selected.totalInterest | number: '1.2-2' }}</strong>
|
|
<span class="muted">{{ selected.accountCount }} account(s)</span>
|
|
</div>
|
|
</div>
|
|
<table class="ipr-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Account no</th>
|
|
<th>Account holder</th>
|
|
<th class="num">Interest</th>
|
|
<th class="num">Balance before posting</th>
|
|
<th class="num">Balance after posting</th>
|
|
<th>Posted txn</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (l of selected.lines; track l.savingsAccountId) {
|
|
<tr>
|
|
<td>{{ l.accountNo }}</td>
|
|
<td>{{ l.clientName }}</td>
|
|
<td class="num">₹{{ l.interestAmount | number: '1.2-2' }}</td>
|
|
<td class="num">₹{{ l.balanceBefore | number: '1.2-2' }}</td>
|
|
<td class="num">₹{{ l.balanceAfter | number: '1.2-2' }}</td>
|
|
<td class="muted">{{ l.savingsTransactionId ? '#' + l.savingsTransactionId : '—' }}</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
<tfoot>
|
|
<tr>
|
|
<td colspan="2"><strong>Total</strong></td>
|
|
<td class="num">
|
|
<strong>₹{{ selected.totalInterest | number: '1.2-2' }}</strong>
|
|
</td>
|
|
<td colspan="3"></td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
}
|
|
</div>
|