- 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>
413 lines
20 KiB
HTML
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>
|