- Generate opens the run on /organization/dds-batch-processing/run/:id with a back arrow, Post and Discard beside the figures, agent-wise table, View popup and CSV export; the table scrolls inside the page. A posted, running or failed run opens there read-only - Batch screen lists generated-but-unposted runs as buttons to reopen them; Previous runs moved into a popup with status and job chips, search, date range, pagination, row detail and export; posting progress is followed from the run status - Shared helpers for job names, money and agent grouping - Approval review shows, for a calculation run, the entries its matching Posting run will book, marked "when posted" Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
339 lines
13 KiB
HTML
339 lines
13 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="bp-container bprv">
|
||
<div class="bp-header">
|
||
<button
|
||
mat-icon-button
|
||
type="button"
|
||
class="bprv-back"
|
||
(click)="back()"
|
||
matTooltip="Back to DDS Interest & Commission"
|
||
aria-label="Back"
|
||
>
|
||
<i class="fas fa-arrow-left"></i>
|
||
</button>
|
||
<div class="bp-header__ic"><i class="fas fa-table"></i></div>
|
||
<div>
|
||
<h2 class="bp-header__title">
|
||
{{ jobTypeLabel || 'Batch run' }} — {{ canAct ? 'preview' : 'run' }}
|
||
@if (generatedRun) {
|
||
<span
|
||
class="bp-status"
|
||
[class.bp-status--posted]="generatedRun.status === 'POSTED'"
|
||
[class.bp-status--failed]="generatedRun.status === 'FAILED'"
|
||
[class.bp-status--running]="generatedRun.status === 'POSTING'"
|
||
>{{ statusText }}</span
|
||
>
|
||
}
|
||
</h2>
|
||
<p class="bp-header__sub">
|
||
@if (generatedRun) {
|
||
Run #{{ generatedRun.id }} ·
|
||
{{
|
||
canAct
|
||
? 'nothing applied yet — check the figures, then Post.'
|
||
: 'read only — this run is ' + statusText.toLowerCase() + '.'
|
||
}}
|
||
}
|
||
</p>
|
||
</div>
|
||
<span class="bp-header__spacer"></span>
|
||
@if (canAct) {
|
||
<button mat-stroked-button type="button" [disabled]="working" (click)="discard()">
|
||
<i class="fas fa-times"></i> Discard
|
||
</button>
|
||
<button
|
||
mat-flat-button
|
||
color="primary"
|
||
type="button"
|
||
[disabled]="working || previewRows.length === 0"
|
||
(click)="post()"
|
||
>
|
||
<i class="fas fa-check"></i> Post
|
||
</button>
|
||
}
|
||
</div>
|
||
|
||
@if (loading) {
|
||
<div class="bp-card">
|
||
<div class="bp-skeleton">
|
||
@for (row of [1, 2, 3, 4, 5]; track row) {
|
||
<span class="bp-skeleton__bar"></span>
|
||
}
|
||
</div>
|
||
</div>
|
||
} @else if (notFound) {
|
||
<div class="bp-card">
|
||
<p class="bp-empty bp-empty--box">
|
||
<i class="fas fa-inbox"></i> This run no longer exists - a new Generate or Discard may have replaced it.
|
||
<button mat-stroked-button type="button" (click)="back()">Back</button>
|
||
</p>
|
||
</div>
|
||
} @else {
|
||
@if (generatedRun?.status === 'FAILED') {
|
||
<p class="bpr-detail__fail bprv-banner">
|
||
<i class="fas fa-exclamation-circle"></i> {{ generatedRun.failureReason || 'No reason recorded' }}
|
||
</p>
|
||
}
|
||
<div class="bp-card bprv-card">
|
||
@if (previewRows.length === 0) {
|
||
<p class="bp-empty bp-empty--box">
|
||
<i class="fas fa-inbox"></i>
|
||
Nothing to apply. Either this period has already been processed, or there was no activity in it.
|
||
</p>
|
||
} @else {
|
||
<div class="bp-kpis">
|
||
<div class="bp-kpi">
|
||
<span>Agents</span><strong>{{ agentGroups.length }}</strong>
|
||
</div>
|
||
<div class="bp-kpi">
|
||
<span>{{ isCommissionJob ? 'Entries' : 'Accounts' }}</span
|
||
><strong>{{ previewRows.length }}</strong>
|
||
</div>
|
||
<div class="bp-kpi bp-kpi--accent">
|
||
<span>{{ showsCommissionBreakdown ? 'Total payable' : 'Total' }}</span>
|
||
<strong>₹ {{ money(previewTotal) }}</strong>
|
||
</div>
|
||
<div class="bp-kpi">
|
||
<span>Date</span><strong>{{ generatedRun.jobDate | dateFormat }}</strong>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bp-tools">
|
||
<span class="bp-tools__hint">
|
||
{{
|
||
isCommissionJob
|
||
? 'One line per agent - View shows how their commission was worked out.'
|
||
: 'Grouped by collecting agent - View shows the DDS accounts under each.'
|
||
}}
|
||
</span>
|
||
<span class="bp-tools__spacer"></span>
|
||
<button mat-stroked-button type="button" (click)="exportPreview()">
|
||
<i class="fas fa-file-export"></i> Export all details
|
||
</button>
|
||
</div>
|
||
|
||
<div class="bp-table-scroll">
|
||
@if (isCommissionJob) {
|
||
<table class="bp-table bp-table--breakdown">
|
||
<thead>
|
||
<tr>
|
||
<th>Agent</th>
|
||
<th>Period</th>
|
||
@if (showsCommissionBreakdown) {
|
||
<th class="bp-num">Collection</th>
|
||
<th class="bp-num">Rate</th>
|
||
<th class="bp-num">Commission</th>
|
||
<th class="bp-num">TDS</th>
|
||
<th class="bp-num">Deductions</th>
|
||
}
|
||
<th class="bp-num">{{ showsCommissionBreakdown ? 'Payable' : 'Amount' }}</th>
|
||
<th class="bp-view-col"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (group of agentGroups; track group.key) {
|
||
@for (row of group.rows; track row.id) {
|
||
<tr>
|
||
<td>
|
||
<span class="bp-code">{{ row.agentCode }}</span>
|
||
<span class="bp-sub">{{ row.agentName }}</span>
|
||
</td>
|
||
<td class="bp-period">{{ row.fromDate | dateFormat }} – {{ row.toDate | dateFormat }}</td>
|
||
@if (showsCommissionBreakdown) {
|
||
<td class="bp-num">{{ money(row.baseAmount) }}</td>
|
||
<td class="bp-num">
|
||
<span class="bp-rate">{{ row.ratePercent }}%</span>
|
||
</td>
|
||
<td class="bp-num">{{ money(row.grossAmount) }}</td>
|
||
<td class="bp-num bp-minus">{{ money(row.tdsAmount) }}</td>
|
||
<td class="bp-num bp-minus">{{ money(row.deductionAmount) }}</td>
|
||
}
|
||
<td class="bp-num bp-total">{{ money(row.amount) }}</td>
|
||
<td class="bp-view-col">
|
||
<button mat-stroked-button type="button" class="bp-view-btn" (click)="viewAgent(group)">
|
||
<i class="fas fa-eye"></i> View
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
}
|
||
}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<td [attr.colspan]="showsCommissionBreakdown ? 7 : 2">Total</td>
|
||
<td class="bp-num bp-total">{{ money(previewTotal) }}</td>
|
||
<td></td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
} @else {
|
||
<table class="bp-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Agent</th>
|
||
<th class="bp-num">Accounts</th>
|
||
<th class="bp-num">Amount</th>
|
||
<th class="bp-view-col"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (group of agentGroups; track group.key) {
|
||
<tr class="bp-agent-row">
|
||
<td>
|
||
<span class="bp-avatar">{{ group.initials }}</span>
|
||
<span class="bp-code bp-code--inline">{{ group.agentCode }}</span>
|
||
<span class="bp-sub bp-sub--inline">{{ group.agentName }}</span>
|
||
</td>
|
||
<td class="bp-num">{{ group.rows.length }}</td>
|
||
<td class="bp-num bp-total">{{ money(group.total) }}</td>
|
||
<td class="bp-view-col">
|
||
<button mat-stroked-button type="button" class="bp-view-btn" (click)="viewAgent(group)">
|
||
<i class="fas fa-eye"></i> View
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<td>Total</td>
|
||
<td class="bp-num">{{ previewRows.length }}</td>
|
||
<td class="bp-num bp-total">{{ money(previewTotal) }}</td>
|
||
<td></td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<!-- One agent's accounts (or commission breakdown) in a popup, opened from View. -->
|
||
<ng-template #agentDialog>
|
||
@if (selectedGroup; as group) {
|
||
<div class="bpd">
|
||
<div class="bpd-head">
|
||
<span class="bpd-av">{{ group.initials }}</span>
|
||
<div class="bpd-head__text">
|
||
<h3>
|
||
<span class="bp-code bp-code--inline">{{ group.agentCode }}</span> {{ group.agentName }}
|
||
</h3>
|
||
<p>
|
||
{{ labelOf(generatedRun?.jobType) }} · Run #{{ generatedRun?.id }} ·
|
||
{{ generatedRun?.jobDate | dateFormat }}
|
||
</p>
|
||
</div>
|
||
<button type="button" class="bpd-close" mat-dialog-close aria-label="Close">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="bpd-body">
|
||
@if (isCommissionJob) {
|
||
@for (row of group.rows; track row.id) {
|
||
<div class="bpd-grid">
|
||
@if (showsCommissionBreakdown) {
|
||
<div class="bpd-cell">
|
||
<span>Collection</span><strong>₹ {{ money(row.baseAmount) }}</strong>
|
||
</div>
|
||
<div class="bpd-cell">
|
||
<span>Slab rate</span><strong>{{ row.ratePercent }}%</strong>
|
||
</div>
|
||
<div class="bpd-cell">
|
||
<span>Commission</span><strong>₹ {{ money(row.grossAmount) }}</strong>
|
||
</div>
|
||
<div class="bpd-cell">
|
||
<span>– TDS</span><strong class="bp-minus">₹ {{ money(row.tdsAmount) }}</strong>
|
||
</div>
|
||
<div class="bpd-cell">
|
||
<span>– Deductions</span><strong class="bp-minus">₹ {{ money(row.deductionAmount) }}</strong>
|
||
</div>
|
||
}
|
||
<div class="bpd-cell bpd-cell--net">
|
||
<span>{{ showsCommissionBreakdown ? 'Payable to agent' : 'To be paid out' }}</span>
|
||
<strong>₹ {{ money(row.amount) }}</strong>
|
||
</div>
|
||
</div>
|
||
<p class="bpd-note">
|
||
Period {{ row.fromDate | dateFormat }} – {{ row.toDate | dateFormat }}. The agent's collection from all
|
||
their customers is added up and one slab rate applies to the whole figure.
|
||
</p>
|
||
}
|
||
} @else {
|
||
<div class="bpd-kpis">
|
||
<div class="bpd-cell">
|
||
<span>Accounts</span><strong>{{ group.rows.length }}</strong>
|
||
</div>
|
||
<div class="bpd-cell bpd-cell--net">
|
||
<span>Total</span><strong>₹ {{ money(group.total) }}</strong>
|
||
</div>
|
||
</div>
|
||
|
||
<input
|
||
type="search"
|
||
class="bpd-search"
|
||
placeholder="Search party code or name"
|
||
[value]="agentSearch"
|
||
(input)="agentSearch = $any($event.target).value"
|
||
/>
|
||
|
||
<div class="bp-table-scroll bpd-scroll">
|
||
<table class="bp-table">
|
||
<thead>
|
||
<tr>
|
||
<th>#</th>
|
||
<th>Party</th>
|
||
<th>Period</th>
|
||
<th class="bp-num">Rate</th>
|
||
<th class="bp-num">Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (row of popupRows; track row.id; let i = $index) {
|
||
<tr>
|
||
<td class="bp-runid">{{ i + 1 }}</td>
|
||
<td>
|
||
<span class="bp-code bp-code--inline">{{ row.partyCode }}</span>
|
||
<span class="bp-sub bp-sub--inline">{{ row.partyName }}</span>
|
||
</td>
|
||
<td class="bp-period">{{ row.fromDate | dateFormat }} – {{ row.toDate | dateFormat }}</td>
|
||
<td class="bp-num">{{ row.ratePercent ? row.ratePercent + '%' : '—' }}</td>
|
||
<td class="bp-num bp-total">{{ money(row.amount) }}</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="5" class="bp-empty">No account matches the search.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<td colspan="4">{{ agentSearch ? 'Total of the accounts shown' : 'Total' }}</td>
|
||
<td class="bp-num bp-total">{{ money(popupTotal) }}</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
}
|
||
</div>
|
||
|
||
<div class="bpd-actions">
|
||
<button mat-stroked-button type="button" mat-dialog-close>Close</button>
|
||
<span class="bp-action-bar__spacer"></span>
|
||
<button mat-flat-button color="primary" type="button" (click)="exportAgent(group)">
|
||
<i class="fas fa-file-export"></i> Export this agent
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
</ng-template>
|
||
</div>
|