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:
+189
-2
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user