- Party Master opens on agents (paged, from the server summary), then one agent's dashboard: accounts, maturity, money and collection cards, each with a View that filters the accounts table. The page title follows the view, and Back from an account returns to its agent. - DDS account page: an actions menu with Edit, Premature Closure, and Maturity Payout & Close. The manual Close Account buttons are gone, since payout and premature closure close the account themselves. - Statuses Active / Matured / Closed / Premature Closed are shown and filtered on every DDS screen through a shared dds-status helper. - Premature closure dialog redesigned: term progress, numbered steps, and a payout calculation for each choice using the server's formulas. - Agent & Slab Approval tab: slab approvals moved in beside agents, with toggle buttons and labels named for the action; richer slab review. - Batch processing: calculation runs are reported as applied, and an empty run is reported as nothing to do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
928 lines
39 KiB
HTML
928 lines
39 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/.
|
||
-->
|
||
|
||
<!-- Laid out as Disbursement & Transfers reviews its requests, and styled by the same sheet. -->
|
||
<div class="dri dri--dialog">
|
||
<nav class="cashier-nav cashier-nav--clerk">
|
||
<span class="dri-header-title">DDS {{ entry.type }} Review</span>
|
||
<span class="cashier-nav__esc-hint" (click)="close()">✕ Close</span>
|
||
</nav>
|
||
|
||
@if (loading) {
|
||
<div class="dri-loading">
|
||
<mat-icon class="dri-loading__icon">hourglass_empty</mat-icon>
|
||
<span>Loading entry…</span>
|
||
</div>
|
||
} @else {
|
||
<div class="dri-body" [class.ddr-body]="isProductReview || isAgentReview || isPartyReview || isSlabReview">
|
||
@if (isProductReview) {
|
||
<!--
|
||
A DDS product request reads as the product it creates or changes: who and what at the top,
|
||
the four terms a checker looks at first, then every term grouped as the Account Type screen
|
||
asks for them. An edit shows each changed term as old → new.
|
||
-->
|
||
<section class="ddr-hero">
|
||
<div class="ddr-hero__icon">
|
||
<mat-icon>{{ isEdit ? 'edit_note' : 'savings' }}</mat-icon>
|
||
</div>
|
||
<div class="ddr-hero__main">
|
||
<div class="ddr-hero__eyebrow">
|
||
<span class="ddr-chip" [class.ddr-chip--edit]="isEdit">{{
|
||
isEdit ? 'Edit request' : 'New product'
|
||
}}</span>
|
||
<span class="dri-status" [ngClass]="statusClass(entry.status)">{{ entry.status }}</span>
|
||
</div>
|
||
<h2 class="ddr-hero__title">{{ valueOf('accountTypeDescription') || entry.details || 'DDS Product' }}</h2>
|
||
<div class="ddr-hero__code">
|
||
<span class="ddr-mono">{{ valueOf('accountTypeCode') || '—' }}</span>
|
||
@if (valueOf('accountTypeGroup')) {
|
||
<span class="ddr-dot"></span>
|
||
<span>{{ valueOf('accountTypeGroup') }} group</span>
|
||
}
|
||
</div>
|
||
</div>
|
||
<div class="ddr-hero__meta">
|
||
<span class="ddr-meta">
|
||
<mat-icon>person</mat-icon>
|
||
<span>
|
||
<b>{{ entry.makerName || entry.maker }}</b>
|
||
<small>{{ entry.maker }}</small>
|
||
</span>
|
||
</span>
|
||
<span class="ddr-meta">
|
||
<mat-icon>schedule</mat-icon>
|
||
<span>
|
||
<b>{{ formatDateTime(entry.madeOn) }}</b>
|
||
<small>Submitted</small>
|
||
</span>
|
||
</span>
|
||
@if (entry.status !== 'PENDING' && entry.checker) {
|
||
<span class="ddr-meta">
|
||
<mat-icon>verified_user</mat-icon>
|
||
<span>
|
||
<b>{{ entry.checkerName || entry.checker }}</b>
|
||
<small>{{ entry.status === 'APPROVED' ? 'Approved by' : 'Rejected by' }}</small>
|
||
</span>
|
||
</span>
|
||
}
|
||
</div>
|
||
</section>
|
||
|
||
<!-- The terms a checker reads first. -->
|
||
<section class="ddr-stats">
|
||
<div class="ddr-stat ddr-stat--accent">
|
||
<span class="ddr-stat__label"><mat-icon>percent</mat-icon>Interest Rate</span>
|
||
<span class="ddr-stat__value">{{ valueOf('interestRate') || '—' }}</span>
|
||
<span class="ddr-stat__hint">p.a., worked out daily</span>
|
||
</div>
|
||
<div class="ddr-stat">
|
||
<span class="ddr-stat__label"><mat-icon>event_repeat</mat-icon>Interest Posting</span>
|
||
<span class="ddr-stat__value">
|
||
{{ valueOf('autoInterestPost') === 'No' ? 'Manual' : valueOf('autoInterestPostWhen') || 'Monthly' }}
|
||
</span>
|
||
<span class="ddr-stat__hint">
|
||
{{ valueOf('autoInterestPost') === 'No' ? 'Posted from the batch screen' : 'Automatic, at period end' }}
|
||
</span>
|
||
</div>
|
||
<div class="ddr-stat">
|
||
<span class="ddr-stat__label"><mat-icon>lock_clock</mat-icon>Lock-in</span>
|
||
<span class="ddr-stat__value">{{ valueOf('prematureLockinMonths') || '—' }}</span>
|
||
<span class="ddr-stat__hint">before early closure</span>
|
||
</div>
|
||
<div class="ddr-stat">
|
||
<span class="ddr-stat__label"><mat-icon>event_available</mat-icon>Maturity</span>
|
||
<span class="ddr-stat__value">{{
|
||
valueOf('maturityAutoClose') === 'Yes' ? 'Auto close' : 'Manual payout'
|
||
}}</span>
|
||
<span class="ddr-stat__hint">on the maturity date</span>
|
||
</div>
|
||
</section>
|
||
|
||
@if (compareFields) {
|
||
<div class="ddr-banner" [class.ddr-banner--none]="!changedCount">
|
||
<mat-icon>{{ changedCount ? 'compare_arrows' : 'info' }}</mat-icon>
|
||
@if (changedCount) {
|
||
<span>
|
||
<b>{{ changedCount }} term{{ changedCount !== 1 ? 's' : '' }}</b> will change on approval - highlighted
|
||
below as current → proposed.
|
||
</span>
|
||
} @else {
|
||
<span>This edit changes nothing - every term matches the product as it stands.</span>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
@for (group of fieldGroups; track group.name) {
|
||
<section class="ddr-section">
|
||
<header class="ddr-section__head">
|
||
<span class="ddr-section__icon"
|
||
><mat-icon>{{ group.icon }}</mat-icon></span
|
||
>
|
||
<span class="ddr-section__title">{{ group.name }}</span>
|
||
@if (group.changed) {
|
||
<span class="ddr-chip ddr-chip--edit">{{ group.changed }} changed</span>
|
||
}
|
||
</header>
|
||
@if (group.name === 'Accounting') {
|
||
<!--
|
||
The ledger accounts the product posts through, laid out as the product's own Accounting
|
||
tab lays them out: the kind of account, what it is for, and the account by name and code.
|
||
-->
|
||
<div class="ddr-acct">
|
||
@for (field of group.fields; track field.key) {
|
||
<div class="ddr-acct__row" [class.ddr-acct__row--changed]="field.changed">
|
||
<div class="ddr-acct__meta">
|
||
<span class="ddr-acct__kind" [ngClass]="'ddr-acct__kind--' + glKind(field.key).toLowerCase()">{{
|
||
glKind(field.key)
|
||
}}</span>
|
||
<span class="ddr-acct__label">
|
||
{{ field.label }}
|
||
@if (field.changed) {
|
||
<span class="ddr-tile__flag">Changed</span>
|
||
}
|
||
</span>
|
||
</div>
|
||
<div class="ddr-acct__value">
|
||
@if (field.changed) {
|
||
<span class="ddr-acct__gl ddr-acct__gl--old">
|
||
{{ glName(field.current) }}
|
||
@if (glCode(field.current)) {
|
||
<span class="ddr-acct__code">{{ glCode(field.current) }}</span>
|
||
}
|
||
</span>
|
||
<mat-icon class="ddr-acct__arrow">arrow_forward</mat-icon>
|
||
}
|
||
<span class="ddr-acct__gl" [class.ddr-acct__gl--none]="!field.value">
|
||
{{ glName(field.value) }}
|
||
@if (glCode(field.value)) {
|
||
<span class="ddr-acct__code">{{ glCode(field.value) }}</span>
|
||
}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
} @else {
|
||
<div class="ddr-grid">
|
||
@for (field of group.fields; track field.key) {
|
||
<div class="ddr-tile" [class.ddr-tile--changed]="field.changed">
|
||
<span class="ddr-tile__label">
|
||
{{ field.label }}
|
||
@if (field.changed) {
|
||
<span class="ddr-tile__flag">Changed</span>
|
||
}
|
||
</span>
|
||
@if (field.changed) {
|
||
<span class="ddr-tile__diff">
|
||
<span class="ddr-tile__old">{{ field.current || '—' }}</span>
|
||
<mat-icon>arrow_forward</mat-icon>
|
||
<ng-container *ngTemplateOutlet="valueTpl; context: { field: field }"></ng-container>
|
||
</span>
|
||
} @else {
|
||
<ng-container *ngTemplateOutlet="valueTpl; context: { field: field }"></ng-container>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
</section>
|
||
}
|
||
|
||
<ng-template #valueTpl let-field="field">
|
||
@if (field.format === 'yesno' && field.value) {
|
||
<span class="ddr-pill" [class.ddr-pill--yes]="field.value === 'Yes'">
|
||
<mat-icon>{{ field.value === 'Yes' ? 'check_circle' : 'remove_circle_outline' }}</mat-icon>
|
||
{{ field.value }}
|
||
</span>
|
||
} @else {
|
||
<span class="ddr-tile__value" [class.ddr-mono]="field.key === 'accountTypeCode'">{{
|
||
field.value || '—'
|
||
}}</span>
|
||
}
|
||
</ng-template>
|
||
} @else if (isSlabReview) {
|
||
<!--
|
||
A commission slab request reads as the slab: the volume band and the rate it pays, what an
|
||
agent earns at the band's start, the change for an edit as old → new, and the whole chart of
|
||
that type with this slab in place - so the checker sees it still gives one rate per volume.
|
||
-->
|
||
<section class="ddr-hero">
|
||
<div class="ddr-hero__icon">
|
||
<mat-icon>{{ isEdit ? 'edit_note' : 'stacked_bar_chart' }}</mat-icon>
|
||
</div>
|
||
<div class="ddr-hero__main">
|
||
<div class="ddr-hero__eyebrow">
|
||
<span class="ddr-chip" [class.ddr-chip--edit]="isEdit">{{ isEdit ? 'Edit request' : 'New slab' }}</span>
|
||
<span class="dri-status" [ngClass]="statusClass(entry.status)">{{ entry.status }}</span>
|
||
</div>
|
||
<h2 class="ddr-hero__title">{{ valueOf('slabFrom') || '—' }} – {{ valueOf('slabTo') || '—' }}</h2>
|
||
<div class="ddr-hero__code">
|
||
<span>{{ valueOf('slabType') || 'Commission' }} slab</span>
|
||
@if (entry.resourceId) {
|
||
<span class="ddr-dot"></span>
|
||
<span class="ddr-mono">#{{ entry.resourceId }}</span>
|
||
}
|
||
</div>
|
||
</div>
|
||
<div class="ddr-hero__meta">
|
||
<span class="ddr-meta">
|
||
<mat-icon>person</mat-icon>
|
||
<span>
|
||
<b>{{ entry.makerName || entry.maker }}</b>
|
||
<small>{{ entry.maker }}</small>
|
||
</span>
|
||
</span>
|
||
<span class="ddr-meta">
|
||
<mat-icon>schedule</mat-icon>
|
||
<span>
|
||
<b>{{ formatDateTime(entry.madeOn) }}</b>
|
||
<small>Submitted</small>
|
||
</span>
|
||
</span>
|
||
@if (entry.status !== 'PENDING' && entry.checker) {
|
||
<span class="ddr-meta">
|
||
<mat-icon>verified_user</mat-icon>
|
||
<span>
|
||
<b>{{ entry.checkerName || entry.checker }}</b>
|
||
<small>{{ entry.status === 'APPROVED' ? 'Approved by' : 'Rejected by' }}</small>
|
||
</span>
|
||
</span>
|
||
}
|
||
</div>
|
||
</section>
|
||
|
||
<section class="ddr-stats ddr-stats--three">
|
||
<div class="ddr-stat ddr-stat--accent">
|
||
<span class="ddr-stat__label"><mat-icon>percent</mat-icon>Commission</span>
|
||
<span class="ddr-stat__value">{{ valueOf('commissionPercent') || '—' }}</span>
|
||
<span class="ddr-stat__hint">of the month's collection</span>
|
||
</div>
|
||
<div class="ddr-stat">
|
||
<span class="ddr-stat__label"><mat-icon>straighten</mat-icon>Collection band</span>
|
||
<span class="ddr-stat__value ddr-stat__value--small"
|
||
>{{ valueOf('slabFrom') || '—' }} – {{ valueOf('slabTo') || '—' }}</span
|
||
>
|
||
<span class="ddr-stat__hint">agent's collection volume</span>
|
||
</div>
|
||
<div class="ddr-stat">
|
||
<span class="ddr-stat__label"><mat-icon>calculate</mat-icon>Example</span>
|
||
<span class="ddr-stat__value ddr-stat__value--small">{{ slabExample || '—' }}</span>
|
||
<span class="ddr-stat__hint">commission at the band's start</span>
|
||
</div>
|
||
</section>
|
||
|
||
@if (compareFields) {
|
||
<div class="ddr-banner" [class.ddr-banner--none]="!changedCount">
|
||
<mat-icon>{{ changedCount ? 'compare_arrows' : 'info' }}</mat-icon>
|
||
@if (changedCount) {
|
||
<span>
|
||
<b>{{ changedCount }} term{{ changedCount !== 1 ? 's' : '' }}</b> will change on approval - shown below
|
||
as current → proposed.
|
||
</span>
|
||
} @else {
|
||
<span>This edit changes nothing - every term matches the slab as it stands.</span>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
@for (group of fieldGroups; track group.name) {
|
||
<section class="ddr-section">
|
||
<header class="ddr-section__head">
|
||
<span class="ddr-section__icon"
|
||
><mat-icon>{{ group.icon }}</mat-icon></span
|
||
>
|
||
<span class="ddr-section__title">{{ group.name }}</span>
|
||
@if (group.changed) {
|
||
<span class="ddr-chip ddr-chip--edit">{{ group.changed }} changed</span>
|
||
}
|
||
</header>
|
||
<div class="ddr-grid">
|
||
@for (field of group.fields; track field.key) {
|
||
<div class="ddr-tile" [class.ddr-tile--changed]="field.changed">
|
||
<span class="ddr-tile__label">
|
||
{{ field.label }}
|
||
@if (field.changed) {
|
||
<span class="ddr-tile__flag">Changed</span>
|
||
}
|
||
</span>
|
||
@if (field.changed) {
|
||
<span class="ddr-tile__diff">
|
||
<span class="ddr-tile__old">{{ field.current || '—' }}</span>
|
||
<mat-icon>arrow_forward</mat-icon>
|
||
<span class="ddr-tile__value">{{ field.value || '—' }}</span>
|
||
</span>
|
||
} @else {
|
||
<span class="ddr-tile__value">{{ field.value || '—' }}</span>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
</section>
|
||
}
|
||
|
||
@if (slabChart.length) {
|
||
<section class="ddr-section">
|
||
<header class="ddr-section__head">
|
||
<span class="ddr-section__icon"><mat-icon>table_rows</mat-icon></span>
|
||
<span class="ddr-section__title">{{ valueOf('slabType') || 'Commission' }} slab chart</span>
|
||
<span class="ddr-slabs__note">{{
|
||
entry.status === 'PENDING' ? 'as it will read once approved' : 'as it reads now'
|
||
}}</span>
|
||
</header>
|
||
@if (slabOverlaps) {
|
||
<div class="ddr-banner ddr-banner--danger">
|
||
<mat-icon>warning</mat-icon>
|
||
<span>
|
||
This slab's band overlaps
|
||
<b>{{ slabOverlaps }} other slab{{ slabOverlaps !== 1 ? 's' : '' }}</b> - a collection in the overlap
|
||
would match more than one rate.
|
||
</span>
|
||
</div>
|
||
}
|
||
<div class="ddr-slabs">
|
||
<div class="ddr-slabs__row ddr-slabs__row--head">
|
||
<span>#</span>
|
||
<span>Collection from</span>
|
||
<span>Collection to</span>
|
||
<span class="ddr-slabs__num">Commission</span>
|
||
</div>
|
||
@for (slab of slabChart; track $index) {
|
||
<div
|
||
class="ddr-slabs__row"
|
||
[class.ddr-slabs__row--requested]="slab.requested"
|
||
[class.ddr-slabs__row--overlap]="slab.overlaps"
|
||
>
|
||
<span class="ddr-mono">
|
||
{{ slab.id ? slab.id : 'New' }}
|
||
@if (slab.requested) {
|
||
<span class="ddr-tile__flag">{{ isEdit ? 'Edited' : 'New' }}</span>
|
||
}
|
||
@if (slab.overlaps) {
|
||
<span class="ddr-slabs__flag">Overlaps</span>
|
||
}
|
||
</span>
|
||
<span>
|
||
@if (slab.was && !same(slab.was.from, slab.from)) {
|
||
<span class="ddr-tile__old">{{ rupees(slab.was.from) }}</span>
|
||
}
|
||
{{ rupees(slab.from) }}
|
||
</span>
|
||
<span>
|
||
@if (slab.was && !same(slab.was.to, slab.to)) {
|
||
<span class="ddr-tile__old">{{ rupees(slab.was.to) }}</span>
|
||
}
|
||
{{ rupees(slab.to) }}
|
||
</span>
|
||
<span class="ddr-slabs__num">
|
||
@if (slab.was && !same(slab.was.rate, slab.rate)) {
|
||
<span class="ddr-tile__old">{{ percent(slab.was.rate) }}</span>
|
||
}
|
||
<b>{{ percent(slab.rate) }}</b>
|
||
</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
</section>
|
||
}
|
||
} @else if (isAgentReview) {
|
||
<!--
|
||
An agent request reads as the agent it opens or changes: who and when at the top, then the
|
||
client, address, branch and accounts - the branch, the ledger account and the client by name,
|
||
not number - and the guarantors named with it. An edit lists only what it changes.
|
||
-->
|
||
<section class="ddr-hero">
|
||
<div class="ddr-hero__icon">
|
||
<mat-icon>{{ isEdit ? 'manage_accounts' : 'person_add' }}</mat-icon>
|
||
</div>
|
||
<div class="ddr-hero__main">
|
||
<div class="ddr-hero__eyebrow">
|
||
<span class="ddr-chip" [class.ddr-chip--edit]="isEdit">{{ isEdit ? 'Edit request' : 'New agent' }}</span>
|
||
<span class="dri-status" [ngClass]="statusClass(entry.status)">{{ entry.status }}</span>
|
||
</div>
|
||
<h2 class="ddr-hero__title">{{ valueOf('agentNameId') || entry.details || 'DDS Agent' }}</h2>
|
||
<div class="ddr-hero__code">
|
||
@if (valueOf('clientId')) {
|
||
<span>Client {{ valueOf('clientId') }}</span>
|
||
}
|
||
@if (valueOf('agentSavingAccountNo')) {
|
||
<span class="ddr-dot"></span>
|
||
<span class="ddr-mono">{{ valueOf('agentSavingAccountNo') }}</span>
|
||
}
|
||
</div>
|
||
</div>
|
||
<div class="ddr-hero__meta">
|
||
<span class="ddr-meta">
|
||
<mat-icon>person</mat-icon>
|
||
<span>
|
||
<b>{{ entry.makerName || entry.maker }}</b>
|
||
<small>{{ entry.maker }}</small>
|
||
</span>
|
||
</span>
|
||
<span class="ddr-meta">
|
||
<mat-icon>schedule</mat-icon>
|
||
<span>
|
||
<b>{{ formatDateTime(entry.madeOn) }}</b>
|
||
<small>Submitted</small>
|
||
</span>
|
||
</span>
|
||
@if (entry.status !== 'PENDING' && entry.checker) {
|
||
<span class="ddr-meta">
|
||
<mat-icon>verified_user</mat-icon>
|
||
<span>
|
||
<b>{{ entry.checkerName || entry.checker }}</b>
|
||
<small>{{ entry.status === 'APPROVED' ? 'Approved by' : 'Rejected by' }}</small>
|
||
</span>
|
||
</span>
|
||
}
|
||
</div>
|
||
</section>
|
||
|
||
@for (group of fieldGroups; track group.name) {
|
||
<section class="ddr-section">
|
||
<header class="ddr-section__head">
|
||
<span class="ddr-section__icon"
|
||
><mat-icon>{{ group.icon }}</mat-icon></span
|
||
>
|
||
<span class="ddr-section__title">{{ group.name }}</span>
|
||
</header>
|
||
<div class="ddr-grid">
|
||
@for (field of group.fields; track field.key) {
|
||
<div class="ddr-tile">
|
||
<span class="ddr-tile__label">{{ field.label }}</span>
|
||
<span class="ddr-tile__value" [class.ddr-mono]="field.key === 'agentSavingAccountNo'">{{
|
||
field.value || '—'
|
||
}}</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
</section>
|
||
}
|
||
|
||
<section class="ddr-section">
|
||
<header class="ddr-section__head">
|
||
<span class="ddr-section__icon"><mat-icon>shield_person</mat-icon></span>
|
||
<span class="ddr-section__title">Guarantors</span>
|
||
<span class="ddr-chip ddr-chip--edit">{{ guarantors.length }}</span>
|
||
</header>
|
||
@if (guarantors.length) {
|
||
<table class="dri-cr-table">
|
||
<thead>
|
||
<tr>
|
||
<th>#</th>
|
||
<th>Client</th>
|
||
<th>Name</th>
|
||
<th>Telephone</th>
|
||
<th>Address</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (g of guarantors; track $index; let i = $index) {
|
||
<tr>
|
||
<td class="dri-cr-idx">{{ i + 1 }}</td>
|
||
<td>{{ g.client || '—' }}</td>
|
||
<td>{{ g.name || '—' }}</td>
|
||
<td class="cell-mono">{{ g.telephone || '—' }}</td>
|
||
<td>{{ g.address || '—' }}</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
} @else {
|
||
<div class="ddr-tile">
|
||
<span class="ddr-tile__value">No guarantors named in this request.</span>
|
||
</div>
|
||
}
|
||
</section>
|
||
} @else if (isPartyReview) {
|
||
<!--
|
||
A DDS account request reads as the account it opens or changes: who it is for and when, the
|
||
terms a checker looks at first, then every detail in the sections the Party Master Entry screen
|
||
has - the client, scheme, agent and branch by name, not number - and the nominees named with it.
|
||
An edit lists only what it changes.
|
||
-->
|
||
<section class="ddr-hero">
|
||
<div class="ddr-hero__icon">
|
||
<mat-icon>{{ isEdit ? 'edit_note' : 'account_balance_wallet' }}</mat-icon>
|
||
</div>
|
||
<div class="ddr-hero__main">
|
||
<div class="ddr-hero__eyebrow">
|
||
<span class="ddr-chip" [class.ddr-chip--edit]="isEdit">{{
|
||
isEdit ? 'Edit request' : 'New DDS account'
|
||
}}</span>
|
||
<span class="dri-status" [ngClass]="statusClass(entry.status)">{{ entry.status }}</span>
|
||
</div>
|
||
<h2 class="ddr-hero__title">{{ valueOf('partyName') || entry.details || 'DDS Account' }}</h2>
|
||
<div class="ddr-hero__code">
|
||
@if (valueOf('subType')) {
|
||
<span>{{ valueOf('subType') }}</span>
|
||
}
|
||
@if (valueOf('clientId')) {
|
||
<span class="ddr-dot"></span>
|
||
<span>Client {{ valueOf('clientId') }}</span>
|
||
}
|
||
</div>
|
||
</div>
|
||
<div class="ddr-hero__meta">
|
||
<span class="ddr-meta">
|
||
<mat-icon>person</mat-icon>
|
||
<span>
|
||
<b>{{ entry.makerName || entry.maker }}</b>
|
||
<small>{{ entry.maker }}</small>
|
||
</span>
|
||
</span>
|
||
<span class="ddr-meta">
|
||
<mat-icon>schedule</mat-icon>
|
||
<span>
|
||
<b>{{ formatDateTime(entry.madeOn) }}</b>
|
||
<small>Submitted</small>
|
||
</span>
|
||
</span>
|
||
@if (entry.status !== 'PENDING' && entry.checker) {
|
||
<span class="ddr-meta">
|
||
<mat-icon>verified_user</mat-icon>
|
||
<span>
|
||
<b>{{ entry.checkerName || entry.checker }}</b>
|
||
<small>{{ entry.status === 'APPROVED' ? 'Approved by' : 'Rejected by' }}</small>
|
||
</span>
|
||
</span>
|
||
}
|
||
</div>
|
||
</section>
|
||
|
||
<!-- The terms a checker reads first. -->
|
||
<section class="ddr-stats">
|
||
<div class="ddr-stat ddr-stat--accent">
|
||
<span class="ddr-stat__label"><mat-icon>payments</mat-icon>Daily Deposit</span>
|
||
<span class="ddr-stat__value">{{ valueOf('dailyDepositAmount') || '—' }}</span>
|
||
<span class="ddr-stat__hint">collected every day</span>
|
||
</div>
|
||
<div class="ddr-stat">
|
||
<span class="ddr-stat__label"><mat-icon>date_range</mat-icon>Term</span>
|
||
<span class="ddr-stat__value">{{ valueOf('days') ? valueOf('days') + ' days' : '—' }}</span>
|
||
<span class="ddr-stat__hint">{{ valueOf('month') ? valueOf('month') + ' months' : '' }}</span>
|
||
</div>
|
||
<div class="ddr-stat">
|
||
<span class="ddr-stat__label"><mat-icon>event</mat-icon>Opening Date</span>
|
||
<span class="ddr-stat__value">{{ valueOf('openingDate') || '—' }}</span>
|
||
<span class="ddr-stat__hint">{{
|
||
valueOf('maturityDate') ? 'Matures ' + valueOf('maturityDate') : 'account opens'
|
||
}}</span>
|
||
</div>
|
||
<div class="ddr-stat">
|
||
<span class="ddr-stat__label"><mat-icon>support_agent</mat-icon>Agent</span>
|
||
<span class="ddr-stat__value ddr-stat__value--small">{{ valueOf('agentId') || '—' }}</span>
|
||
<span class="ddr-stat__hint">{{ valueOf('officeId') || '' }}</span>
|
||
</div>
|
||
</section>
|
||
|
||
@for (group of fieldGroups; track group.name) {
|
||
<section class="ddr-section">
|
||
<header class="ddr-section__head">
|
||
<span class="ddr-section__icon"
|
||
><mat-icon>{{ group.icon }}</mat-icon></span
|
||
>
|
||
<span class="ddr-section__title">{{ group.name }}</span>
|
||
</header>
|
||
<div class="ddr-grid">
|
||
@for (field of group.fields; track field.key) {
|
||
<div class="ddr-tile">
|
||
<span class="ddr-tile__label">{{ field.label }}</span>
|
||
@if (field.format === 'yesno') {
|
||
<span class="ddr-pill" [class.ddr-pill--yes]="field.value === 'Yes'">
|
||
<mat-icon>{{ field.value === 'Yes' ? 'check_circle' : 'remove_circle_outline' }}</mat-icon>
|
||
{{ field.value }}
|
||
</span>
|
||
} @else {
|
||
<span class="ddr-tile__value">{{ field.value || '—' }}</span>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
</section>
|
||
}
|
||
|
||
<section class="ddr-section">
|
||
<header class="ddr-section__head">
|
||
<span class="ddr-section__icon"><mat-icon>family_restroom</mat-icon></span>
|
||
<span class="ddr-section__title">Nominees</span>
|
||
@if (detail?.nomineeSplit && nominees.length) {
|
||
<span class="ddr-chip ddr-chip--edit">Split by {{ detail.nomineeSplit }}</span>
|
||
}
|
||
<span class="ddr-chip ddr-chip--edit">{{ nominees.length }}</span>
|
||
</header>
|
||
@if (nominees.length) {
|
||
<table class="dri-cr-table">
|
||
<thead>
|
||
<tr>
|
||
<th>#</th>
|
||
<th>Name</th>
|
||
<th>Relation</th>
|
||
<th>Gender</th>
|
||
<th>Mobile</th>
|
||
<th>Birth Date</th>
|
||
<th>Guardian</th>
|
||
<th class="num">Share</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (n of nominees; track $index; let i = $index) {
|
||
<tr>
|
||
<td class="dri-cr-idx">{{ i + 1 }}</td>
|
||
<td>{{ n.name || '—' }}</td>
|
||
<td>{{ n.relation || '—' }}</td>
|
||
<td>{{ n.gender || '—' }}</td>
|
||
<td class="cell-mono">{{ n.mobileNo || '—' }}</td>
|
||
<td>{{ n.birthDate || '—' }}</td>
|
||
<td>{{ n.guardianName || '—' }}</td>
|
||
<td class="num">{{ n.share || '—' }}</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
} @else {
|
||
<div class="ddr-tile">
|
||
<span class="ddr-tile__value">No nominee named in this request.</span>
|
||
</div>
|
||
}
|
||
</section>
|
||
} @else {
|
||
<!-- Header card: the voucher it moves under, its status, who made it and when -->
|
||
<div class="dri-card dri-card--header">
|
||
<div class="dri-card-header">
|
||
<div class="dri-card-header__left">
|
||
<!-- No voucher slot for a change that moves no money - it would only ever read "—". -->
|
||
@if (voucherNo || movesMoney) {
|
||
<span class="dri-voucher">{{ voucherNo || '—' }}</span>
|
||
}
|
||
<span class="dri-status" [ngClass]="statusClass(entry.status)">
|
||
{{ entry.status }}{{ entry.voucherCancelled ? ' · VOUCHER CANCELLED' : '' }}
|
||
</span>
|
||
</div>
|
||
<div class="dri-card-header__meta">
|
||
<span class="dri-meta-item">
|
||
<mat-icon>person</mat-icon>
|
||
{{ entry.makerName || entry.maker }} ({{ entry.maker }})
|
||
</span>
|
||
<span class="dri-meta-item">
|
||
<mat-icon>schedule</mat-icon>
|
||
{{ formatDateTime(entry.madeOn) }}
|
||
</span>
|
||
@if (entry.status !== 'PENDING' && entry.checker) {
|
||
<span class="dri-meta-item">
|
||
<mat-icon>check_circle</mat-icon>
|
||
Actioned by: {{ entry.checkerName || entry.checker }}
|
||
</span>
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Summary -->
|
||
<div class="dri-card">
|
||
<div class="dri-section-title">
|
||
<mat-icon>swap_horiz</mat-icon>
|
||
{{ entry.type }} Summary
|
||
</div>
|
||
<div class="dri-detail-grid">
|
||
<!-- Amount and sides only for an entry that moves money; a product or agent change has neither. -->
|
||
@if (movesMoney) {
|
||
<div class="dri-detail-item">
|
||
<span class="dri-detail-label">Total Amount</span>
|
||
<span class="dri-detail-value dri-amount">
|
||
{{
|
||
entry.amount !== null && entry.amount !== undefined ? '₹' + (entry.amount | number: '1.2-2') : '—'
|
||
}}
|
||
</span>
|
||
</div>
|
||
@if (entriesProjected) {
|
||
<div class="dri-detail-item dri-detail-item--full">
|
||
<span class="dri-detail-label">Ledger</span>
|
||
<span class="dri-detail-value"
|
||
>No journal entry is made when this is approved - it records what has been earned. The entries below
|
||
are what the matching Posting run will book.</span
|
||
>
|
||
</div>
|
||
}
|
||
<div class="dri-detail-item">
|
||
<span class="dri-detail-label">{{
|
||
entriesProjected ? 'DR Accounts (when posted)' : 'DR Accounts'
|
||
}}</span>
|
||
<span class="dri-detail-value"
|
||
>{{ debitLines.length }} account{{ debitLines.length !== 1 ? 's' : '' }}</span
|
||
>
|
||
</div>
|
||
<div class="dri-detail-item">
|
||
<span class="dri-detail-label">{{
|
||
entriesProjected ? 'CR Accounts (when posted)' : 'CR Accounts'
|
||
}}</span>
|
||
<span class="dri-detail-value"
|
||
>{{ creditLines.length }} account{{ creditLines.length !== 1 ? 's' : '' }}</span
|
||
>
|
||
</div>
|
||
}
|
||
<div class="dri-detail-item dri-detail-item--full">
|
||
<span class="dri-detail-label">Details</span>
|
||
<span class="dri-detail-value">{{ entry.details || '—' }}</span>
|
||
</div>
|
||
@if (detail?.description) {
|
||
<div class="dri-detail-item dri-detail-item--full">
|
||
<span class="dri-detail-label">Description</span>
|
||
<span class="dri-detail-value">{{ detail.description }}</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- Debit entries -->
|
||
@if (debitLines.length) {
|
||
<div class="dri-card">
|
||
<div class="dri-section-title">
|
||
<mat-icon>arrow_upward</mat-icon>
|
||
{{ entriesProjected ? 'Debit entries when posted' : 'Debit Entries' }} ({{ debitLines.length }} account{{
|
||
debitLines.length !== 1 ? 's' : ''
|
||
}}
|
||
— funds out)
|
||
</div>
|
||
<table class="dri-cr-table">
|
||
<thead>
|
||
<tr>
|
||
<th>#</th>
|
||
<th>Account No</th>
|
||
<th>Account Name</th>
|
||
<th>Type</th>
|
||
<th class="num">Amount (DR)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (line of debitLines; track $index; let i = $index) {
|
||
<tr>
|
||
<td class="dri-cr-idx">{{ i + 1 }}</td>
|
||
<td class="cell-mono">{{ line.accountNo || '—' }}</td>
|
||
<td>{{ line.accountName || '—' }}</td>
|
||
<td>
|
||
<span class="dri-type-badge">{{ line.type || '—' }}</span>
|
||
</td>
|
||
<td class="num dri-cr-amount">₹{{ line.amount | number: '1.2-2' }}</td>
|
||
</tr>
|
||
}
|
||
<tr class="dri-cr-total-row">
|
||
<td colspan="4" class="dri-cr-total-label">Total Debit</td>
|
||
<td class="num dri-cr-total-amount">₹{{ total(debitLines) | number: '1.2-2' }}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
}
|
||
|
||
<!-- Credit entries -->
|
||
@if (creditLines.length) {
|
||
<div class="dri-card">
|
||
<div class="dri-section-title">
|
||
<mat-icon>arrow_downward</mat-icon>
|
||
{{ entriesProjected ? 'Credit entries when posted' : 'Credit Entries' }} ({{ creditLines.length }} account{{
|
||
creditLines.length !== 1 ? 's' : ''
|
||
}}
|
||
— funds in)
|
||
</div>
|
||
<table class="dri-cr-table">
|
||
<thead>
|
||
<tr>
|
||
<th>#</th>
|
||
<th>Account No</th>
|
||
<th>Account Name</th>
|
||
<th>Type</th>
|
||
<th class="num">Amount (CR)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (line of creditLines; track $index; let i = $index) {
|
||
<tr>
|
||
<td class="dri-cr-idx">{{ i + 1 }}</td>
|
||
<td class="cell-mono">{{ line.accountNo || '—' }}</td>
|
||
<td>{{ line.accountName || '—' }}</td>
|
||
<td>
|
||
<span class="dri-type-badge">{{ line.type || '—' }}</span>
|
||
</td>
|
||
<td class="num dri-cr-amount">₹{{ line.amount | number: '1.2-2' }}</td>
|
||
</tr>
|
||
}
|
||
<tr class="dri-cr-total-row">
|
||
<td colspan="4" class="dri-cr-total-label">Total Credit</td>
|
||
<td class="num dri-cr-total-amount">₹{{ total(creditLines) | number: '1.2-2' }}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
}
|
||
|
||
@if (entry.status === 'PENDING' && isMine && !done) {
|
||
<div class="dri-card dri-card--processing">
|
||
<div class="dri-section-title">
|
||
<mat-icon>info</mat-icon>
|
||
Your own entry
|
||
</div>
|
||
<p class="dri-processing-text">
|
||
You made this entry, so another user with checker rights has to approve or reject it.
|
||
</p>
|
||
</div>
|
||
}
|
||
|
||
@if (entry.status === 'REJECTED') {
|
||
<div class="dri-card dri-card--rejected">
|
||
<div class="dri-section-title">
|
||
<mat-icon>cancel</mat-icon>
|
||
Rejected
|
||
</div>
|
||
<p class="dri-rejection-text">
|
||
Nothing was done{{ voucherNo ? '. Voucher ' + voucherNo + ' is cancelled and will not be used' : '' }}.
|
||
</p>
|
||
@if (entry.checker) {
|
||
<div class="dri-meta-item" style="margin-top: 8px">
|
||
<mat-icon>manage_accounts</mat-icon>
|
||
{{ entry.checker }}
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
@if (entry.status === 'APPROVED') {
|
||
<div class="dri-card dri-card--approved">
|
||
<div class="dri-section-title">
|
||
<mat-icon>check_circle</mat-icon>
|
||
Approved and carried out
|
||
</div>
|
||
<p class="dri-txn-refs cell-mono">
|
||
{{
|
||
voucherNo
|
||
? 'Voucher ' + voucherNo
|
||
: entry.entity === 'DDSBATCH'
|
||
? 'Posting is running - refresh for its voucher'
|
||
: '—'
|
||
}}
|
||
</p>
|
||
<div class="dri-meta-item" style="margin-top: 8px">
|
||
<mat-icon>manage_accounts</mat-icon>
|
||
Approved by: {{ entry.checker || '—' }}
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
@if (error) {
|
||
<div class="dri-card dri-card--failed">
|
||
<div class="dri-section-title">
|
||
<mat-icon>error</mat-icon>
|
||
Could not complete
|
||
</div>
|
||
<p class="dri-rejection-text">{{ error }}</p>
|
||
</div>
|
||
}
|
||
|
||
@if (entry.status === 'PENDING' && !done) {
|
||
<div
|
||
class="dri-actions"
|
||
[class.ddr-actions]="isProductReview || isAgentReview || isPartyReview || isSlabReview"
|
||
>
|
||
@if (!confirmReject) {
|
||
<button type="button" class="dri-btn dri-btn--approve" [disabled]="!canAct" (click)="act('approve')">
|
||
<mat-icon>check_circle</mat-icon>
|
||
{{ working ? 'Processing…' : 'Approve ' + entry.type }}
|
||
</button>
|
||
<button type="button" class="dri-btn dri-btn--reject" [disabled]="!canAct" (click)="confirmReject = true">
|
||
<mat-icon>cancel</mat-icon>
|
||
Reject
|
||
</button>
|
||
} @else {
|
||
<div class="dri-reject-form">
|
||
<label class="dri-reject-label">
|
||
Reject this entry? Nothing will be done{{
|
||
voucherNo ? ' and voucher ' + voucherNo + ' will be cancelled' : ''
|
||
}}.
|
||
</label>
|
||
<div class="dri-reject-actions">
|
||
<button type="button" class="dri-btn dri-btn--reject" [disabled]="!canAct" (click)="act('reject')">
|
||
<mat-icon>cancel</mat-icon>
|
||
{{ working ? 'Processing…' : 'Confirm Rejection' }}
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="dri-btn dri-btn--ghost"
|
||
[disabled]="working"
|
||
(click)="confirmReject = false"
|
||
>
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|