feat(dds): agent-first Party Master, account actions menu, FD-style statuses

- 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>
This commit is contained in:
2026-10-29 18:33:15 +05:30
co-authored by Claude Opus 5.5
parent c766a6f092
commit 1f87ad475c
29 changed files with 3409 additions and 556 deletions
@@ -19,7 +19,7 @@
<span>Loading entry…</span>
</div>
} @else {
<div class="dri-body" [class.ddr-body]="isProductReview || isAgentReview || isPartyReview">
<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,
@@ -205,6 +205,190 @@
}}</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
@@ -700,7 +884,10 @@
}
@if (entry.status === 'PENDING' && !done) {
<div class="dri-actions" [class.ddr-actions]="isProductReview || isAgentReview || isPartyReview">
<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>