241 lines
9.5 KiB
HTML
241 lines
9.5 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="pl-root">
|
|
<div class="pl-header">
|
|
<div class="pl-header-left">
|
|
<div class="pl-header-ic"><fa-icon icon="users"></fa-icon></div>
|
|
<div class="pl-header-text">
|
|
<h2>Party Master</h2>
|
|
<p>The DDS accounts on file — each one a customer collected from daily.</p>
|
|
</div>
|
|
</div>
|
|
<div class="pl-header-right">
|
|
<div class="pl-count-chip" matTooltip="Total parties">
|
|
<fa-icon icon="users"></fa-icon>
|
|
<strong>{{ totalCount }}</strong>
|
|
<span>{{ totalCount === 1 ? 'party' : 'parties' }}</span>
|
|
</div>
|
|
<button class="pl-create-btn" type="button" [routerLink]="['create']">
|
|
<fa-icon icon="plus"></fa-icon>
|
|
<span>Create Party</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!--
|
|
Matured accounts still open. Money the bank owes a customer that nobody has been told about, so
|
|
it is called out on the screen an officer already opens rather than left on a report they would
|
|
have to think to run.
|
|
-->
|
|
@if (dueForClosureCount > 0) {
|
|
<a class="pm-due-banner" [routerLink]="['dueforclosure']">
|
|
<fa-icon icon="clipboard-check" class="pm-due-banner__ic"></fa-icon>
|
|
<span class="pm-due-banner__text">
|
|
<strong>{{ dueForClosureCount }}</strong>
|
|
{{ dueForClosureCount === 1 ? 'account has' : 'accounts have' }} matured and
|
|
{{ dueForClosureCount === 1 ? 'is' : 'are' }} still open —
|
|
<!-- Nil once every one of them has been paid: what is left then is the closing, not the money. -->
|
|
@if (dueForClosurePayable > 0) {
|
|
<strong>{{ dueForClosurePayable | number: '1.2-2' }}</strong> still owed to customers.
|
|
} @else {
|
|
paid in full, waiting to be closed.
|
|
}
|
|
</span>
|
|
<span class="pm-due-banner__cta"
|
|
>Settle {{ dueForClosureCount === 1 ? 'it' : 'them' }} <fa-icon icon="arrow-right"></fa-icon
|
|
></span>
|
|
</a>
|
|
}
|
|
|
|
<div class="pl-kpis">
|
|
<div class="pl-kpi pl-kpi--blue">
|
|
<div class="pl-kpi-ic"><fa-icon icon="users"></fa-icon></div>
|
|
<div>
|
|
<div class="pl-kpi-n">{{ activeCount | number }}</div>
|
|
<div class="pl-kpi-l">Open Accounts</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="pl-kpi pl-kpi--purple"
|
|
matTooltip="What the agents should collect between them on a full collection day"
|
|
>
|
|
<div class="pl-kpi-ic"><fa-icon icon="calendar-day"></fa-icon></div>
|
|
<div>
|
|
<div class="pl-kpi-n">{{ totalDailyDue | number: '1.2-2' }}</div>
|
|
<div class="pl-kpi-l">Daily Collection Due</div>
|
|
</div>
|
|
</div>
|
|
<div class="pl-kpi pl-kpi--teal" matTooltip="Every open account's running balance added up">
|
|
<div class="pl-kpi-ic"><fa-icon icon="wallet"></fa-icon></div>
|
|
<div>
|
|
<div class="pl-kpi-n">{{ totalCurrentBalance | number: '1.2-2' }}</div>
|
|
<div class="pl-kpi-l">Total Deposits Held</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pl-card">
|
|
<div class="pl-toolbar">
|
|
<div class="pl-search">
|
|
<fa-icon icon="search" class="pl-search-ic"></fa-icon>
|
|
<input
|
|
type="text"
|
|
(input)="applyFilter($any($event.target).value)"
|
|
placeholder="Search by code, name or agent…"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
@if (totalCount === 0) {
|
|
<div class="pl-empty">
|
|
<div class="pl-empty-ic"><fa-icon icon="users"></fa-icon></div>
|
|
<h3>No parties yet</h3>
|
|
<p>Open one so an agent can start collecting daily deposits against it.</p>
|
|
<button class="pl-create-btn" type="button" [routerLink]="['create']">
|
|
<fa-icon icon="plus"></fa-icon>
|
|
<span>Create Party</span>
|
|
</button>
|
|
</div>
|
|
} @else {
|
|
<div class="pl-table-wrap">
|
|
<table mat-table [dataSource]="dataSource" matSort class="pl-table">
|
|
<ng-container matColumnDef="partyCode">
|
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Party Code</th>
|
|
<td mat-cell *matCellDef="let p">
|
|
<div class="pl-product-cell">
|
|
<div class="pl-product-icon"><fa-icon icon="user"></fa-icon></div>
|
|
<div class="pl-product-meta">
|
|
<div class="pl-product-name">{{ p.partyCode }}</div>
|
|
<div class="pl-product-sub">{{ p.city || '—' }}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="partyName">
|
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Party Name</th>
|
|
<td mat-cell *matCellDef="let p">{{ p.partyName }}</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="subType">
|
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Sub Type</th>
|
|
<td mat-cell *matCellDef="let p">
|
|
<span class="pl-tag" [matTooltip]="p.subType">{{ p.subTypeDescription || p.subType }}</span>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="agentName">
|
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Collected By</th>
|
|
<td mat-cell *matCellDef="let p">
|
|
<div class="pl-product-meta">
|
|
<div class="pl-product-name">{{ p.agentName || '—' }}</div>
|
|
<div class="pl-product-sub">{{ p.agentCode }}</div>
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!--
|
|
The customer's real deposit account. A party without one is money the bank's books do not
|
|
know about, so it is called out rather than shown as a blank cell.
|
|
-->
|
|
<ng-container matColumnDef="savingsAccountNo">
|
|
<th mat-header-cell *matHeaderCellDef>DDS Account</th>
|
|
<td mat-cell *matCellDef="let p">
|
|
@if (p.accountNo) {
|
|
<div class="pl-product-meta">
|
|
<div class="pm-list-account">{{ p.accountNo }}</div>
|
|
<div class="pl-product-sub">{{ p.subTypeDescription || p.subType }}</div>
|
|
</div>
|
|
} @else if (p.savingsAccountNo) {
|
|
<div class="pl-product-meta">
|
|
<div class="pm-list-account">{{ p.savingsAccountNo }}</div>
|
|
<div class="pl-product-sub">{{ p.savingsAccountStatus }}</div>
|
|
</div>
|
|
} @else {
|
|
<span class="pm-list-no-account">Not opened</span>
|
|
}
|
|
</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="dailyDepositAmount">
|
|
<th mat-header-cell *matHeaderCellDef mat-sort-header class="num">Daily Deposit</th>
|
|
<td mat-cell *matCellDef="let p" class="num">{{ p.dailyDepositAmount | number: '1.2-2' }}</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="currentBalance">
|
|
<th mat-header-cell *matHeaderCellDef mat-sort-header class="num">Current Balance</th>
|
|
<td mat-cell *matCellDef="let p" class="num">
|
|
<strong>{{ p.currentBalance | number: '1.2-2' }}</strong>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="status">
|
|
<th mat-header-cell *matHeaderCellDef>Status</th>
|
|
<td mat-cell *matCellDef="let p">
|
|
<span class="pm-pill" [class.pm-pill--off]="p.status === 'CLOSED'">
|
|
{{ p.status === 'CLOSED' ? 'Closed' : 'Active' }}
|
|
</span>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="nominees">
|
|
<th mat-header-cell *matHeaderCellDef>Nominees</th>
|
|
<td mat-cell *matCellDef="let p">
|
|
@if (nomineeCount(p) > 0) {
|
|
<button
|
|
type="button"
|
|
class="pl-nominee-btn"
|
|
(click)="showNominees(p)"
|
|
matTooltip="See who this account is nominated to"
|
|
>
|
|
{{ nomineeCount(p) }} nominated
|
|
</button>
|
|
} @else {
|
|
<span class="pl-muted">None</span>
|
|
}
|
|
</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="actions">
|
|
<th mat-header-cell *matHeaderCellDef class="num">Actions</th>
|
|
<td mat-cell *matCellDef="let p" class="num">
|
|
<button
|
|
type="button"
|
|
class="pl-action-btn pl-action-btn--edit"
|
|
[routerLink]="[p.id, 'edit']"
|
|
[matTooltip]="p.status === 'CLOSED' ? 'View this closed party' : 'Edit this party'"
|
|
>
|
|
<fa-icon [icon]="p.status === 'CLOSED' ? 'eye' : 'edit'"></fa-icon>
|
|
<span>{{ p.status === 'CLOSED' ? 'View' : 'Edit' }}</span>
|
|
</button>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
|
<tr
|
|
mat-row
|
|
*matRowDef="let row; columns: displayedColumns"
|
|
class="pl-row"
|
|
[class.pm-row--closed]="row.status === 'CLOSED'"
|
|
></tr>
|
|
</table>
|
|
|
|
@if (filteredCount === 0 && totalCount > 0) {
|
|
<div class="pl-no-match">
|
|
<fa-icon icon="search"></fa-icon>
|
|
<span>No parties match your filter.</span>
|
|
</div>
|
|
}
|
|
|
|
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" [pageSize]="10" showFirstLastButtons></mat-paginator>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|