feat(reconciliation): build full 16-screen Reconciliation module
Wires every item in the Reconciliation mega-menu (Core & Books, Bank & Clearing, Digital & Payments, Instruments & Members, Compliance & Financials) to a working screen, mirroring the NPA module's pattern: one route-param-driven component over a shared two-sided (Side A vs Side B) matching model, with pagination and a "Mark as Reconciled" action on every screen. Access is split by real-world scope: activity that happens at a branch (vouchers, cheques, digital transactions, share members, fixed assets) is branch-wise with the same Head-Office/branch login behavior as NPA; activity with no branch dimension in real banking (GL/P&L consolidation, treasury's own bank accounts, statutory tax filing, vendor procurement, inter-branch netting) is Head-Office-only, with branch logins shown a restricted notice instead of the data. Also adds the .menu-item-live "working" indicator to mat-menu-item buttons (same convention as .gl-link--live) and applies it across the reconciliation menu and the pre-existing DD/Pay Order Reconciliation link.
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
<!--
|
||||
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">
|
||||
RECONCILIATION · {{ groupLabels[activeGroup] | uppercase }}
|
||||
<span class="recon-scope-chip">{{ meta.scope === 'HEAD_OFFICE' ? 'Head Office Only' : 'Branch-wise' }}</span>
|
||||
</div>
|
||||
<h1 class="pg-title">{{ meta.label }}</h1>
|
||||
<p class="pg-sub">
|
||||
{{ meta.sideALabel }} vs {{ meta.sideBLabel }}
|
||||
@if (meta.extraLabel) {
|
||||
vs {{ meta.extraLabel }}
|
||||
}
|
||||
— as on {{ asOnDate }}.
|
||||
</p>
|
||||
</div>
|
||||
<div class="pg-toolbar">
|
||||
@if (meta.scope === 'BRANCH') {
|
||||
@if (isHeadOffice) {
|
||||
<label class="pg-field">
|
||||
<span>Branch</span>
|
||||
<select class="pg-select" [ngModel]="branchFilter" (ngModelChange)="setBranch($event)">
|
||||
<option value="">-- All Branches --</option>
|
||||
@for (b of branches; track b.code) {
|
||||
<option [value]="b.code">{{ b.name }}</option>
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
} @else {
|
||||
<label class="pg-field">
|
||||
<span>Branch</span>
|
||||
<span class="pg-chip">{{ scopeName }}</span>
|
||||
</label>
|
||||
}
|
||||
} @else {
|
||||
<label class="pg-field">
|
||||
<span>Scope</span>
|
||||
<span class="pg-chip">Head Office (Whole Bank)</span>
|
||||
</label>
|
||||
}
|
||||
<label class="pg-field">
|
||||
<span>As On</span>
|
||||
<input type="text" [(ngModel)]="asOnDate" placeholder="DD-MM-YYYY" />
|
||||
</label>
|
||||
<button type="button" class="pg-btn pg-btn--primary" (click)="generate()">Generate</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@if (!isRestricted) {
|
||||
<div class="inv-kpis">
|
||||
<div class="inv-kpi">
|
||||
<span class="inv-kpi__label">{{ meta.sideALabel }}</span>
|
||||
<span class="inv-kpi__value">₹{{ totalA | number: '1.0-0' }}</span>
|
||||
<span class="inv-kpi__sub">{{ items.length }} items</span>
|
||||
</div>
|
||||
<div class="inv-kpi">
|
||||
<span class="inv-kpi__label">{{ meta.sideBLabel }}</span>
|
||||
<span class="inv-kpi__value">₹{{ totalB | number: '1.0-0' }}</span>
|
||||
<span class="inv-kpi__sub">{{ scopeName }}</span>
|
||||
</div>
|
||||
<div class="inv-kpi">
|
||||
<span class="inv-kpi__label">Absolute Difference</span>
|
||||
<span class="inv-kpi__value" [class.npa-warn]="totalDiff > 0">₹{{ totalDiff | number: '1.0-0' }}</span>
|
||||
<span class="inv-kpi__sub">{{ matchPct | number: '1.1-1' }}% matched</span>
|
||||
</div>
|
||||
<div class="inv-kpi">
|
||||
<span class="inv-kpi__label">Open Items</span>
|
||||
<span class="inv-kpi__value npa-warn">{{ unmatchedCount + pendingCount }}</span>
|
||||
<span class="inv-kpi__sub"
|
||||
>{{ unmatchedCount }} unmatched · {{ pendingCount }} pending · {{ resolvedCount }} resolved</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="cctr-tabs">
|
||||
@for (g of groups; track g) {
|
||||
<button
|
||||
type="button"
|
||||
class="pg-btn pg-btn--ghost"
|
||||
[class.pg-btn--active]="activeGroup === g"
|
||||
(click)="goToReport(groupOf(g)[0].id)"
|
||||
>
|
||||
{{ groupLabels[g] }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="cctr-tabs">
|
||||
@for (m of groupOf(activeGroup); track m.id) {
|
||||
<button
|
||||
type="button"
|
||||
class="pg-btn pg-btn--ghost"
|
||||
[class.pg-btn--active]="activeReport === m.id"
|
||||
(click)="goToReport(m.id)"
|
||||
>
|
||||
{{ m.label }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (isRestricted) {
|
||||
<div class="pg-card">
|
||||
<div class="recon-restricted">
|
||||
<fa-icon icon="university" class="recon-restricted__icon"></fa-icon>
|
||||
<div class="recon-restricted__title">Managed Centrally by Head Office</div>
|
||||
<p class="recon-restricted__body">
|
||||
{{ meta.label }} has no branch-level dimension in real-world banking — it is reconciled centrally by the Head
|
||||
Office Accounts/Treasury team. Your login ({{ currentOfficeName }}) does not have access to this screen.
|
||||
Contact Head Office if you need a figure from this reconciliation.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="pg-card">
|
||||
<div class="pos-section-label">{{ meta.label }} — {{ scopeName }}</div>
|
||||
<div class="pg-table-scroll">
|
||||
<table class="pg-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ meta.referenceLabel }}</th>
|
||||
<th>Description</th>
|
||||
@if (meta.scope === 'BRANCH') {
|
||||
<th>Branch</th>
|
||||
}
|
||||
<th>Date</th>
|
||||
<th class="num">{{ meta.sideALabel }}</th>
|
||||
<th class="num">{{ meta.sideBLabel }}</th>
|
||||
@if (meta.extraLabel) {
|
||||
<th class="num">{{ meta.extraLabel }}</th>
|
||||
}
|
||||
<th class="num">Difference</th>
|
||||
<th>Status</th>
|
||||
<th>Remarks / Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (item of paged(items); track item.id) {
|
||||
<tr>
|
||||
<td>
|
||||
<span class="pg-chip">{{ item.reference }}</span>
|
||||
</td>
|
||||
<td>{{ item.description }}</td>
|
||||
@if (meta.scope === 'BRANCH') {
|
||||
<td>{{ item.branchName }}</td>
|
||||
}
|
||||
<td>{{ item.date }}</td>
|
||||
<td class="num">₹{{ item.sideAAmount | number: '1.0-0' }}</td>
|
||||
<td class="num">₹{{ item.sideBAmount | number: '1.0-0' }}</td>
|
||||
@if (meta.extraLabel) {
|
||||
<td class="num">₹{{ item.extraAmount | number: '1.0-0' }}</td>
|
||||
}
|
||||
<td class="num" [class.col-out]="diff(item) !== 0" [class.col-in]="diff(item) === 0">
|
||||
₹{{ diff(item) | number: '1.0-0' }}
|
||||
</td>
|
||||
<td>
|
||||
<span [class]="badgeClass(item.status)">{{ item.status }}</span>
|
||||
</td>
|
||||
<td>
|
||||
@if (isItemOpen(item)) {
|
||||
<div class="recon-action-row">
|
||||
<input
|
||||
type="text"
|
||||
class="recon-remark-input"
|
||||
[(ngModel)]="remarksDraft[item.id]"
|
||||
placeholder="Remarks (optional)"
|
||||
/>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="markReconciled(item)">
|
||||
Mark as Reconciled
|
||||
</button>
|
||||
</div>
|
||||
} @else if (item.status === 'RESOLVED') {
|
||||
<span class="pg-sub">{{ item.remarks }}</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td
|
||||
[attr.colspan]="meta.scope === 'BRANCH' ? (meta.extraLabel ? 10 : 9) : meta.extraLabel ? 9 : 8"
|
||||
class="pg-empty"
|
||||
>
|
||||
No {{ meta.label }} items for this branch.
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if (items.length > pageSize) {
|
||||
<div class="pg-pagination">
|
||||
<span class="pg-pagination__info"
|
||||
>Showing {{ rangeStart(items) }}–{{ rangeEnd(items) }} of {{ items.length }}</span
|
||||
>
|
||||
<div class="pg-pagination__controls">
|
||||
<button
|
||||
type="button"
|
||||
class="pg-btn pg-btn--ghost"
|
||||
[disabled]="currentPage === 1"
|
||||
(click)="goToPage(currentPage - 1, items)"
|
||||
>
|
||||
‹ Prev
|
||||
</button>
|
||||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(items) }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="pg-btn pg-btn--ghost"
|
||||
[disabled]="currentPage === pageCount(items)"
|
||||
(click)="goToPage(currentPage + 1, items)"
|
||||
>
|
||||
Next ›
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="inv-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|
||||
Reference in New Issue
Block a user