Files
cbsnexora/src/app/deposits/daily-transaction-entry/daily-transaction-entry.component.html
T
niteshandClaude Sonnet 5.5 293b671966 feat(dds): upgrade DDS UI - agent, party, daily entry, approvals, product view
- Agent master: tabbed layout, customer search dialog, GL dropdown, account card, view page
- Party master: tabbed layout, expected party code, nominee cards, relation from code values
- Daily transaction entry: compact style, collections popup, Apply DDS Receipt placement
- Approvals: new review modal, Agent Approval tab, DDS routing into product/pending account
- DDS product: view page and list buttons; drop Auto Interest Post Day

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 10:50:03 +05:30

413 lines
20 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="dte-container">
<div class="dte-header">
<button mat-stroked-button [routerLink]="['../']" class="dte-back">
<i class="fas fa-arrow-left"></i> Deposits
</button>
<div class="dte-header__icon"><i class="fas fa-coins"></i></div>
<div class="dte-header__text">
<h2 class="dte-header__title">Daily Transaction Detail</h2>
<p class="dte-header__sub">
An agent's round - what was collected, the cash handed in and applied - and transfers between accounts.
</p>
</div>
</div>
<div class="dte-card">
<form [formGroup]="entryForm">
<div class="dte-info-panels">
<div class="dte-info-panel">
<div class="dte-info-panel__title"><i class="fas fa-user-tie"></i>Agent Information</div>
<dl>
<dt>Name</dt>
<dd>{{ agentInfo.name || '—' }}</dd>
<dt>Address</dt>
<dd>{{ agentInfo.address || '—' }}</dd>
<dt>City</dt>
<dd>{{ agentInfo.city || '—' }}</dd>
<dt>A/C</dt>
<dd>{{ agentInfo.accountNo || '—' }}</dd>
<!-- One line for each guarantor the agent actually has - not two fixed lines, one of them empty. -->
@for (guarantor of agentInfo.guarantors; track $index; let i = $index) {
<dt>{{ agentInfo.guarantors.length > 1 ? 'Guarantor ' + (i + 1) : 'Guarantor' }}</dt>
<dd>{{ guarantor }}</dd>
} @empty {
<dt>Guarantor</dt>
<dd>—</dd>
}
<dt>Curr Bal</dt>
<dd>{{ agentInfo.currentBalance | number: '1.2-2' }}</dd>
</dl>
</div>
<!-- Wider than the agent's card, with its details two pairs to a line, so a party's account is read without scrolling. -->
<div class="dte-info-panel dte-info-panel--party">
<div class="dte-info-panel__title"><i class="fas fa-user"></i>Party Information</div>
@if (partyInfo) {
<dl>
<dt>Client Id</dt>
<dd>{{ partyInfo.clientId }}</dd>
<dt>Name</dt>
<dd>{{ partyInfo.partyName }}</dd>
<dt>Address</dt>
<dd>{{ partyInfo.address1 || '—' }}</dd>
<dt>City</dt>
<dd>{{ partyInfo.city || '—' }}</dd>
<dt>Op. Date</dt>
<dd>{{ partyInfo.openingDate ? (partyInfo.openingDate | dateFormat) : '—' }}</dd>
<dt>Maturity</dt>
<dd>{{ partyInfo.maturityDate ? (partyInfo.maturityDate | dateFormat) : '—' }}</dd>
<dt>Daily Depo</dt>
<dd>{{ partyInfo.dailyDepositAmount | number: '1.2-2' }}</dd>
<dt>Ledger</dt>
<dd>{{ partyInfo.ledgerNo || '—' }}</dd>
<dt>Aadhar No</dt>
<dd>{{ partyInfo.aadharNo || '—' }}</dd>
<dt>Curr Bal</dt>
<dd>{{ partyInfo.currentBalance | number: '1.2-2' }}</dd>
<!--
What should have been collected by today against what has been. Beside the balance
rather than instead of it: the balance is what the customer holds, this is whether
they have kept to the scheme.
-->
<dt>Due So Far</dt>
<dd>{{ partyInfo.expectedToDate | number: '1.2-2' }}</dd>
<dt>Collected</dt>
<dd>{{ partyInfo.collectedToDate | number: '1.2-2' }}</dd>
@if (schedule(partyInfo); as sched) {
<dt [class]="'dte-sched dte-sched--' + sched.state">Schedule</dt>
<dd [class]="'dte-sched dte-sched--' + sched.state">{{ sched.text }}</dd>
}
<dt class="dte-after">Bal. After This Tr</dt>
<dd class="dte-after">{{ partyBalanceAfter | number: '1.2-2' }}</dd>
</dl>
} @else {
<p class="dte-info-panel__hint">
<i class="fas fa-info-circle"></i>
Click an entry in the collections list below — this panel shows whose account it is and what the balance
becomes.
</p>
}
</div>
</div>
<!-- The round at a glance, once an agent is picked: nothing here is typed or saved. -->
@if (agentInfo.name) {
<div class="dte-kpis">
<div class="dte-kpi">
<span class="dte-kpi__label"><i class="fas fa-users"></i>Parties</span>
<strong class="dte-kpi__value">{{ agentParties.length }}</strong>
<span class="dte-kpi__hint">to collect from</span>
</div>
@if (isPartyTran) {
<div class="dte-kpi">
<span class="dte-kpi__label"><i class="fas fa-list-ol"></i>Entries</span>
<strong class="dte-kpi__value">{{ pendingEntries.length }}</strong>
<span class="dte-kpi__hint">written down, not handed in</span>
</div>
<div class="dte-kpi dte-kpi--accent">
<span class="dte-kpi__label"><i class="fas fa-rupee-sign"></i>To count</span>
<strong class="dte-kpi__value">{{ pendingTotal | number: '1.2-2' }}</strong>
<span class="dte-kpi__hint">the agent should hand over</span>
</div>
}
<!-- Agent Tran: what the agent owes on everything written down, what the cashier has taken in, and what is left. -->
@if (isAgentTran) {
<div class="dte-kpi dte-kpi--owed">
<span class="dte-kpi__label"><i class="fas fa-file-invoice"></i>To take</span>
<strong class="dte-kpi__value">{{ agentOwed | number: '1.2-2' }}</strong>
<span class="dte-kpi__hint">written down, not handed in</span>
</div>
<div class="dte-kpi dte-kpi--received">
<span class="dte-kpi__label"><i class="fas fa-check-circle"></i>Received (approved)</span>
<strong class="dte-kpi__value">{{ agentReceived | number: '1.2-2' }}</strong>
<span class="dte-kpi__hint">taken in by the cashier</span>
</div>
<div class="dte-kpi" [class.dte-kpi--due]="agentBalance > 0">
<span class="dte-kpi__label"><i class="fas fa-hourglass-half"></i>Still to take</span>
<strong class="dte-kpi__value">{{ agentBalance | number: '1.2-2' }}</strong>
<span class="dte-kpi__hint">left to receive</span>
</div>
}
<div class="dte-kpi">
<span class="dte-kpi__label"><i class="fas fa-wallet"></i>Agent Balance</span>
<strong class="dte-kpi__value">{{ agentInfo.currentBalance | number: '1.2-2' }}</strong>
<span class="dte-kpi__hint">held by {{ agentInfo.name }}</span>
</div>
</div>
}
<div class="dte-controls">
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="dte-field dte-field--agent">
<mat-label>Agent</mat-label>
<mat-select required formControlName="agentId" (selectionChange)="loadAgent()">
@for (agent of selectableAgents; track agent.id) {
<mat-option [value]="agent.id">
{{ agent.agentCode }} · {{ agent.agentNameId }}
@if (!agent.active) {
<span class="dte-agent-off">— not collecting</span>
}
</mat-option>
}
</mat-select>
@if (selectableAgents.length === 0) {
<mat-hint class="dte-hint--missing">No active agent on file</mat-hint>
} @else if (pickedAgentIsInactive) {
<!-- Only reachable on a settlement: this agent no longer collects, but the cash they
already hold still has to be able to reach a branch. -->
<mat-hint class="dte-hint--missing">This agent no longer collects — settling held cash only</mat-hint>
} @else if (agentInfo.name) {
<mat-hint class="dte-hint--found">
{{ agentParties.length }} {{ agentParties.length === 1 ? 'party' : 'parties' }} to collect from
</mat-hint>
}
</mat-form-field>
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="dte-field">
<mat-label>Date</mat-label>
<!-- The date picks out a round, not just a posting date: a Party Tran settles whatever
the agent wrote down for that day, so changing it has to fetch that day's list. -->
<input
matInput
required
[matDatepicker]="txnDatePicker"
formControlName="transactionDate"
(dateChange)="loadPendingEntries(); loadAgentMoney()"
/>
<mat-datepicker-toggle matIconSuffix [for]="txnDatePicker"></mat-datepicker-toggle>
<mat-datepicker #txnDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="dte-field">
<mat-label>Tran. Type</mat-label>
<mat-select required formControlName="transactionType">
@for (type of transactionTypes; track type.label) {
<mat-option [value]="type.label" [disabled]="!type.supported">
{{ type.label }}
@if (!type.supported) {
<span class="dte-soon"> — not available yet</span>
}
</mat-option>
}
</mat-select>
</mat-form-field>
<!-- Agent Tran: the cashier's receipts are applied to the customers from here - the dialog shows the days and receipts. -->
@if (isAgentTran && agentInfo.name) {
<div class="dte-agent-actions">
<button mat-stroked-button type="button" class="dte-refresh-btn" (click)="loadAgentMoney()">
<i class="fas fa-sync"></i> Refresh
</button>
<button mat-raised-button color="primary" type="button" class="dte-apply-btn" (click)="openApplyReceipt()">
<i class="fas fa-receipt"></i> Apply DDS Receipt
</button>
</div>
}
<!-- The agent's written-down round, in a window of its own and paged. -->
@if (isPartyTran && agentInfo.name) {
<button mat-stroked-button type="button" class="dte-view-btn" (click)="openCollections()">
<i class="fas fa-list"></i> View Collections
<span class="dte-view-btn__count">{{ pendingEntries.length }}</span>
</button>
}
</div>
<!--
What the agent wrote down on their round, waiting to be counted, is no longer listed down this
page - a long round pushed everything else out of sight. It opens from View Collections above, in a
window of its own and paged, and a line clicked there shows that customer in Party Information.
-->
<!--
The agent's money used to have a section of its own down the page: what they owe on everything
written down, what the cashier has taken in, what is left, then the outstanding days and the
cashier's receipts. Apply DDS Receipt shows the same days and receipts, so the section is gone -
the three figures sit in the summary above, the button is beside the Date, and what is below is
only what the dialog does not say: which GL the cash goes into and anything that looks wrong.
-->
@if (isAgentTran && agentInfo.name) {
<div class="dte-notes">
@if (!agentGlCode) {
<p class="dte-expected dte-expected--note">
{{ agentInfo.name }} has no GL account, so the cashier has nowhere to take their cash. Set one on Agent
Master.
</p>
} @else {
<!-- The GL the cashier takes this agent's cash into - the code to key at Transaction Entry. -->
<p class="dte-expected">
Agent GL: <strong>{{ agentGlCode }}{{ agentGlName ? ' · ' + agentGlName : '' }}</strong> — the cashier
takes {{ agentInfo.name }}'s cash into this GL (Entry Type DDS).
</p>
}
@if (agentReceivedBeyondDay > 0) {
<p class="dte-expected dte-expected--note">
{{ agentReceivedBeyondDay | number: '1.2-2' }} received at the counter is more than everything outstanding
up to {{ entryForm.get('transactionDate')?.value | dateFormat }}. Check the voucher was taken for the
right agent.
</p>
}
@if (agentWaiting > 0) {
<p class="dte-expected dte-expected--note">
{{ agentWaiting | number: '1.2-2' }} more is at the counter awaiting the manager's approval.
</p>
}
</div>
}
@if (isWithdrawDeposite) {
<div class="dte-section">
<div class="dte-section__header">
<i class="fas fa-exchange-alt"></i>
<h3>Withdraw Deposite</h3>
</div>
<div class="dte-grid dte-grid--transfer">
<!-- The account the money goes into. -->
<div class="dte-party-card dte-party-card--to">
<div class="dte-party-card__title"><i class="fas fa-arrow-circle-down"></i>Money goes into</div>
<div class="dte-party-card__fields">
<mat-form-field appearance="outline" class="dte-field">
<mat-label>Creditor Party Code</mat-label>
<input
matInput
formControlName="creditorPartyCode"
[matAutocomplete]="creditorAuto"
#creditorTrigger="matAutocompleteTrigger"
(click)="creditorTrigger.openPanel()"
(focus)="creditorTrigger.openPanel()"
(blur)="settleTransferParty('creditor')"
placeholder="Search by code or name…"
autocomplete="off"
/>
<mat-icon
matSuffix
class="dte-list-arrow"
(click)="$event.stopPropagation(); creditorTrigger.openPanel()"
>arrow_drop_down</mat-icon
>
<mat-autocomplete #creditorAuto="matAutocomplete" panelWidth="min(30rem, 92vw)">
@for (party of transferPartyOptions('creditor'); track party.id) {
<mat-option [value]="party.partyCode">
{{ party.partyCode }} · {{ party.partyName }} · ₹{{ party.currentBalance | number: '1.2-2' }}
</mat-option>
}
@if (transferPartyOptions('creditor').length === 0) {
<mat-option disabled>No open DDS account matches</mat-option>
}
</mat-autocomplete>
<mat-hint>Account the money goes into</mat-hint>
</mat-form-field>
<mat-form-field appearance="outline" class="dte-field">
<mat-label>Creditor Party Name</mat-label>
<input matInput formControlName="creditorPartyName" readonly />
</mat-form-field>
<mat-form-field appearance="outline" class="dte-field">
<mat-label>Creditor Curr Bal</mat-label>
<input matInput formControlName="creditorPartyBalance" readonly />
</mat-form-field>
</div>
</div>
<!-- The account the money is taken from. -->
<div class="dte-party-card dte-party-card--from">
<div class="dte-party-card__title"><i class="fas fa-arrow-circle-up"></i>Money is taken from</div>
<div class="dte-party-card__fields">
<mat-form-field appearance="outline" class="dte-field">
<mat-label>Debit Party Code</mat-label>
<input
matInput
formControlName="debitPartyCode"
[matAutocomplete]="debitAuto"
#debitTrigger="matAutocompleteTrigger"
(click)="debitTrigger.openPanel()"
(focus)="debitTrigger.openPanel()"
(blur)="settleTransferParty('debit')"
placeholder="Search by code or name…"
autocomplete="off"
/>
<mat-icon
matSuffix
class="dte-list-arrow"
(click)="$event.stopPropagation(); debitTrigger.openPanel()"
>arrow_drop_down</mat-icon
>
<mat-autocomplete #debitAuto="matAutocomplete" panelWidth="min(30rem, 92vw)">
@for (party of transferPartyOptions('debit'); track party.id) {
<mat-option [value]="party.partyCode">
{{ party.partyCode }} · {{ party.partyName }} · ₹{{ party.currentBalance | number: '1.2-2' }}
</mat-option>
}
@if (transferPartyOptions('debit').length === 0) {
<mat-option disabled>No open DDS account matches</mat-option>
}
</mat-autocomplete>
@if (transferToItself) {
<mat-hint class="dte-same-party">Pick a different account - it cannot transfer to itself</mat-hint>
} @else {
<mat-hint>Account the money is taken from</mat-hint>
}
</mat-form-field>
<mat-form-field appearance="outline" class="dte-field">
<mat-label>Debit Party Name</mat-label>
<input matInput formControlName="debitPartyName" readonly />
</mat-form-field>
<mat-form-field appearance="outline" class="dte-field">
<mat-label>Debit Curr Bal</mat-label>
<input matInput formControlName="debitPartyBalance" readonly />
</mat-form-field>
</div>
</div>
<mat-form-field appearance="outline" class="dte-field">
<mat-label>Amount</mat-label>
<input matInput type="number" formControlName="withdrawAmount" />
</mat-form-field>
<mat-form-field appearance="outline" class="dte-field dte-field--wide">
<mat-label>Description</mat-label>
<input matInput formControlName="withdrawDescription" />
</mat-form-field>
</div>
</div>
}
@if (!isPartyTran && !isWithdrawDeposite && !isAgentTran) {
<div class="dte-section">
<p class="dte-empty">
"{{ entryForm.get('transactionType').value }}" entry screen — same shape as Party Tran, to be filled in once
the backend distinguishes these transaction types.
</p>
</div>
}
<div class="dte-action-bar">
<button mat-raised-button type="button" (click)="exit()">Exit</button>
<button mat-stroked-button type="button" (click)="cancel()">Cancel</button>
<button mat-stroked-button type="button" (click)="retrieve()">Retrieve</button>
<button mat-stroked-button type="button" (click)="viewLedger()">View Ledger</button>
<button mat-stroked-button type="button" (click)="signature()">Signature</button>
<!-- Party Tran and Agent Tran are read here, never saved: the agent writes the round down on
their own screen and the cash is taken in at the cashier's counter. Agent Tran applies the
cashier's receipts from its own Apply DDS Receipt. Only Withdraw Deposite still saves. -->
@if (!isPartyTran && !isAgentTran) {
<button
mat-raised-button
color="primary"
type="button"
[disabled]="entryForm.invalid || transferToItself"
(click)="save()"
>
Save
</button>
}
</div>
</form>
</div>
</div>