Files
cbsnexora/src/app/organization/dds-batch-run-preview/dds-batch-run-preview.component.html
T
niteshandClaude Sonnet 5.5 c766a6f092 feat(dds): batch run preview on its own page, runs popup, projected GLs in approvals
- 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>
2026-10-28 22:04:26 +05:30

339 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
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 &amp; 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>