feat(cashier): GL Entries view in Closing Cash Report

- New 'GL View' toggle button in header — switches between
  denomination report and GL entries table
- GL table: Voucher No, A/C No, A/C Name (customer), DR (red), CR (green)
- Filters: Teller Head dropdown → Cashier dropdown (cascading)
- Load button resets to denomination view (showGl = false)
- Header shows Total DR / Total CR summary
- DR rows: light red bg; CR rows: light green bg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-02 10:42:48 +05:30
co-authored by Claude Sonnet 4.6
parent 1aa46397a6
commit 3be2f96210
4 changed files with 453 additions and 86 deletions
+187
View File
@@ -428,3 +428,190 @@
border: 1px solid #bfdbfe;
}
}
// GL View toggle button active state
.pg-btn--active {
background: #1e3a5f;
color: #fff;
border-color: #1e3a5f;
}
// GL Card
.gl-card {
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 12px;
overflow: hidden;
&__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
gap: 16px;
flex-wrap: wrap;
}
&__header-left {
display: flex;
align-items: center;
gap: 12px;
}
&__icon {
width: 40px;
height: 40px;
border-radius: 10px;
background: #1e3a5f;
display: flex;
align-items: center;
justify-content: center;
mat-icon {
color: #fff;
font-size: 20px;
width: 20px;
height: 20px;
}
}
&__title {
font-size: 15px;
font-weight: 700;
color: #1e293b;
}
&__sub {
font-size: 12px;
color: #64748b;
margin-top: 2px;
}
&__totals {
display: flex;
align-items: center;
gap: 16px;
}
&__total-sep {
color: #cbd5e1;
font-size: 18px;
}
&__total {
display: flex;
flex-direction: column;
align-items: flex-end;
}
&__total-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #94a3b8;
}
&__total-value {
font-size: 18px;
font-weight: 800;
font-variant-numeric: tabular-nums;
}
&__total--dr .gl-card__total-value {
color: #dc2626;
}
&__total--cr .gl-card__total-value {
color: #15803d;
}
&__filters {
display: flex;
align-items: flex-end;
gap: 12px;
padding: 14px 20px;
border-bottom: 1px solid #f1f5f9;
flex-wrap: wrap;
background: #fff;
}
}
// GL Table
.gl-table {
.gl-th--voucher {
width: 110px;
}
.gl-th--dr {
color: #dc2626 !important;
}
.gl-th--cr {
color: #15803d !important;
}
.gl-voucher {
font-family: 'Roboto Mono', 'Courier New', monospace;
font-size: 12px;
font-weight: 700;
color: #475569;
}
.gl-acno {
font-family: 'Roboto Mono', 'Courier New', monospace;
font-size: 12px;
color: #1d4ed8;
}
.gl-acname {
font-weight: 500;
color: #1e293b;
}
.gl-amt--dr {
color: #dc2626;
font-weight: 700;
}
.gl-amt--cr {
color: #15803d;
font-weight: 700;
}
.gl-row--dr td {
background: #fff5f5;
}
.gl-row--cr td {
background: #f0fdf4;
}
.gl-row--dr:hover td {
background: #fee2e2;
}
.gl-row--cr:hover td {
background: #dcfce7;
}
.gl-foot {
background: #f1f5f9;
border-top: 2px solid #e2e8f0;
&__label {
font-size: 13px;
font-weight: 800;
color: #1e293b;
padding: 12px 14px;
}
&__val {
font-size: 14px;
font-weight: 800;
padding: 12px 14px;
}
}
}
+7
View File
@@ -129,6 +129,13 @@ export class CashierService {
return this.http.get('/cashreports/cashier-day-status');
}
getGlEntries(date: string, cashierId?: number, tellerId?: number): Observable<any> {
let params = new HttpParams().set('date', date);
if (cashierId) params = params.set('cashierId', String(cashierId));
if (tellerId) params = params.set('tellerId', String(tellerId));
return this.http.get('/cashreports/gl-entries', { params });
}
checkSettlement(cashierId: number, date: string): Observable<any> {
const params = new HttpParams().set('cashierId', String(cashierId)).set('date', date);
return this.http.get('/cashsettlement/check', { params });
@@ -21,10 +21,13 @@
<button type="button" class="pg-btn pg-btn--primary" (click)="load()" [disabled]="loading">
<mat-icon>search</mat-icon> Load
</button>
<button type="button" class="pg-btn" [class.pg-btn--active]="showGl" (click)="toggleGl()">
<mat-icon>account_balance</mat-icon> GL View
</button>
</div>
</header>
@if (rows.length > 0) {
@if (!showGl && rows.length > 0) {
<!-- Summary totals bar -->
<div class="closing-summary">
<div class="closing-summary__item closing-summary__item--in">
@@ -48,98 +51,205 @@
</div>
}
<div class="pg-card">
@if (noteRows.length > 0) {
<div class="pos-section-label">Notes</div>
<table class="pg-table">
<thead>
<tr>
<th>Denomination</th>
<th class="num col-in">Deposit Pcs</th>
<th class="num col-in">Deposit Amt</th>
<th class="num col-out">Withdrawal Pcs</th>
<th class="num col-out">Withdrawal Amt</th>
<th class="num col-net">Net Pcs</th>
<th class="num col-net">Net Amount</th>
</tr>
</thead>
<tbody>
@for (r of noteRows; track r.denominationId) {
@if (!showGl) {
<div class="pg-card">
@if (noteRows.length > 0) {
<div class="pos-section-label">Notes</div>
<table class="pg-table">
<thead>
<tr>
<td>
<span class="pg-chip">₹{{ r.denominationValue }}</span>
</td>
<td class="num col-in">{{ r.depositCount }}</td>
<td class="num col-in">₹{{ r.depositAmount | number: '1.2-2' }}</td>
<td class="num col-out">{{ r.withdrawalCount }}</td>
<td class="num col-out">₹{{ r.withdrawalAmount | number: '1.2-2' }}</td>
<td
class="num col-net"
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
>
{{ r.netCount > 0 ? '+' : '' }}{{ r.netCount }}
</td>
<td
class="num col-net"
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
>
{{ r.netCount >= 0 ? '' : '−' }}₹{{ (r.netAmount < 0 ? -r.netAmount : r.netAmount) | number: '1.2-2' }}
</td>
<th>Denomination</th>
<th class="num col-in">Deposit Pcs</th>
<th class="num col-in">Deposit Amt</th>
<th class="num col-out">Withdrawal Pcs</th>
<th class="num col-out">Withdrawal Amt</th>
<th class="num col-net">Net Pcs</th>
<th class="num col-net">Net Amount</th>
</tr>
}
</tbody>
</table>
}
</thead>
<tbody>
@for (r of noteRows; track r.denominationId) {
<tr>
<td>
<span class="pg-chip">₹{{ r.denominationValue }}</span>
</td>
<td class="num col-in">{{ r.depositCount }}</td>
<td class="num col-in">₹{{ r.depositAmount | number: '1.2-2' }}</td>
<td class="num col-out">{{ r.withdrawalCount }}</td>
<td class="num col-out">₹{{ r.withdrawalAmount | number: '1.2-2' }}</td>
<td
class="num col-net"
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
>
{{ r.netCount > 0 ? '+' : '' }}{{ r.netCount }}
</td>
<td
class="num col-net"
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
>
{{ r.netCount >= 0 ? '' : '−' }}₹{{
(r.netAmount < 0 ? -r.netAmount : r.netAmount) | number: '1.2-2'
}}
</td>
</tr>
}
</tbody>
</table>
}
@if (coinRows.length > 0) {
<div class="pos-section-label pos-section-label--coin">Coins</div>
<table class="pg-table">
<thead>
<tr>
<th>Denomination</th>
<th class="num col-in">Deposit Pcs</th>
<th class="num col-in">Deposit Amt</th>
<th class="num col-out">Withdrawal Pcs</th>
<th class="num col-out">Withdrawal Amt</th>
<th class="num col-net">Net Pcs</th>
<th class="num col-net">Net Amount</th>
</tr>
</thead>
<tbody>
@for (r of coinRows; track r.denominationId) {
@if (coinRows.length > 0) {
<div class="pos-section-label pos-section-label--coin">Coins</div>
<table class="pg-table">
<thead>
<tr>
<td>
<span class="pg-chip pg-chip--coin">₹{{ r.denominationValue }}</span>
</td>
<td class="num col-in">{{ r.depositCount }}</td>
<td class="num col-in">₹{{ r.depositAmount | number: '1.2-2' }}</td>
<td class="num col-out">{{ r.withdrawalCount }}</td>
<td class="num col-out">₹{{ r.withdrawalAmount | number: '1.2-2' }}</td>
<td
class="num col-net"
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
>
{{ r.netCount > 0 ? '+' : '' }}{{ r.netCount }}
</td>
<td
class="num col-net"
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
>
{{ r.netCount >= 0 ? '' : '−' }}₹{{ (r.netAmount < 0 ? -r.netAmount : r.netAmount) | number: '1.2-2' }}
</td>
<th>Denomination</th>
<th class="num col-in">Deposit Pcs</th>
<th class="num col-in">Deposit Amt</th>
<th class="num col-out">Withdrawal Pcs</th>
<th class="num col-out">Withdrawal Amt</th>
<th class="num col-net">Net Pcs</th>
<th class="num col-net">Net Amount</th>
</tr>
}
</tbody>
</table>
}
</thead>
<tbody>
@for (r of coinRows; track r.denominationId) {
<tr>
<td>
<span class="pg-chip pg-chip--coin">₹{{ r.denominationValue }}</span>
</td>
<td class="num col-in">{{ r.depositCount }}</td>
<td class="num col-in">₹{{ r.depositAmount | number: '1.2-2' }}</td>
<td class="num col-out">{{ r.withdrawalCount }}</td>
<td class="num col-out">₹{{ r.withdrawalAmount | number: '1.2-2' }}</td>
<td
class="num col-net"
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
>
{{ r.netCount > 0 ? '+' : '' }}{{ r.netCount }}
</td>
<td
class="num col-net"
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
>
{{ r.netCount >= 0 ? '' : '−' }}₹{{
(r.netAmount < 0 ? -r.netAmount : r.netAmount) | number: '1.2-2'
}}
</td>
</tr>
}
</tbody>
</table>
}
@if (loaded && rows.length === 0 && exchangeRows.length === 0) {
<div class="pg-empty">No cash denomination data for this date.</div>
}
</div>
@if (loaded && rows.length === 0 && exchangeRows.length === 0) {
<div class="pg-empty">No cash denomination data for this date.</div>
}
</div>
}
<!-- end !showGl -->
<!-- Cash Exchange section — separate from deposits/withdrawals (net monetary = ₹0) -->
@if (exchangeRows.length > 0) {
<!-- GL View Panel -->
@if (showGl) {
<div class="gl-card">
<!-- Card Header -->
<div class="gl-card__header">
<div class="gl-card__header-left">
<div class="gl-card__icon">
<mat-icon>account_balance</mat-icon>
</div>
<div>
<div class="gl-card__title">GL Entries</div>
<div class="gl-card__sub">Cashier transaction ledger for {{ date }}</div>
</div>
</div>
<div class="gl-card__totals">
<div class="gl-card__total gl-card__total--dr">
<span class="gl-card__total-label">Total DR</span>
<span class="gl-card__total-value">₹{{ glTotalDr | number: '1.2-2' }}</span>
</div>
<div class="gl-card__total-sep">|</div>
<div class="gl-card__total gl-card__total--cr">
<span class="gl-card__total-label">Total CR</span>
<span class="gl-card__total-value">₹{{ glTotalCr | number: '1.2-2' }}</span>
</div>
</div>
</div>
<!-- Filters Bar -->
<div class="gl-card__filters">
<label class="pg-field">
<span>Teller Head</span>
<select class="pg-select" [(ngModel)]="selectedTellerId" (change)="onTellerChange()">
<option [ngValue]="null">All Tellers</option>
@for (t of tellers; track t.id) {
<option [ngValue]="t.id">{{ t.name }}</option>
}
</select>
</label>
@if (selectedTellerId) {
<label class="pg-field">
<span>Cashier</span>
<select class="pg-select" [(ngModel)]="selectedCashierId" (change)="onCashierChange()">
<option [ngValue]="null">All Cashiers</option>
@for (c of cashiers; track c.id) {
<option [ngValue]="c.id">{{ c.staffName }}</option>
}
</select>
</label>
}
<button type="button" class="pg-btn pg-btn--primary" (click)="loadGl()" [disabled]="glLoading">
<mat-icon>refresh</mat-icon> Refresh
</button>
</div>
<!-- Table -->
@if (glLoading) {
<div class="pg-empty">Loading...</div>
} @else if (glRows.length === 0 && glLoaded) {
<div class="pg-empty">No entries found for this date and filter.</div>
} @else if (glRows.length > 0) {
<table class="pg-table gl-table">
<thead>
<tr>
<th class="gl-th--voucher">Voucher No</th>
<th>A/C No</th>
<th>A/C Name</th>
<th class="num gl-th--dr">DR (₹)</th>
<th class="num gl-th--cr">CR (₹)</th>
</tr>
</thead>
<tbody>
@for (r of glRows; track $index) {
<tr [class.gl-row--dr]="r.dr > 0" [class.gl-row--cr]="r.cr > 0">
<td class="gl-voucher">{{ r.voucherNo }}</td>
<td class="gl-acno">{{ r.acNo }}</td>
<td class="gl-acname">{{ r.acName }}</td>
<td class="num gl-amt--dr">
@if (r.dr > 0) {
₹{{ r.dr | number: '1.2-2' }}
}
</td>
<td class="num gl-amt--cr">
@if (r.cr > 0) {
₹{{ r.cr | number: '1.2-2' }}
}
</td>
</tr>
}
</tbody>
<tfoot>
<tr class="gl-foot">
<td colspan="3" class="gl-foot__label">Total</td>
<td class="num gl-amt--dr gl-foot__val">₹{{ glTotalDr | number: '1.2-2' }}</td>
<td class="num gl-amt--cr gl-foot__val">₹{{ glTotalCr | number: '1.2-2' }}</td>
</tr>
</tfoot>
</table>
}
</div>
}
@if (!showGl && exchangeRows.length > 0) {
<div class="pg-card">
<div class="closing-exchange-head">
<mat-icon>swap_horiz</mat-icon>
@@ -36,6 +36,20 @@ export class ClosingCashReportComponent implements OnInit {
loading = false;
loaded = false;
// GL View
showGl = false;
glRows: any[] = [];
glTotalDr = 0;
glTotalCr = 0;
glLoading = false;
glLoaded = false;
// Filters for GL
tellers: any[] = [];
cashiers: any[] = [];
selectedTellerId: number | null = null;
selectedCashierId: number | null = null;
get noteRows(): any[] {
return this.rows.filter((r) => r.kind !== 'coin');
}
@@ -53,9 +67,11 @@ export class ClosingCashReportComponent implements OnInit {
const d = new Date();
this.date = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
this.load();
this.service.getTellers().subscribe({ next: (t: any[]) => (this.tellers = t || []), error: () => {} });
}
load(): void {
this.showGl = false;
this.loading = true;
this.service.closingCashReport(this.date).subscribe({
next: (res: any) => {
@@ -78,4 +94,51 @@ export class ClosingCashReportComponent implements OnInit {
}
});
}
toggleGl(): void {
this.showGl = !this.showGl;
if (this.showGl && !this.glLoaded) {
this.loadGl();
}
}
onTellerChange(): void {
this.selectedCashierId = null;
this.cashiers = [];
if (this.selectedTellerId) {
this.service.getCashiersForTeller(this.selectedTellerId).subscribe({
next: (res: any) => (this.cashiers = res?.cashiers || res || []),
error: () => {}
});
}
this.glLoaded = false;
this.loadGl();
}
onCashierChange(): void {
this.glLoaded = false;
this.loadGl();
}
loadGl(): void {
this.glLoading = true;
this.service
.getGlEntries(this.date, this.selectedCashierId || undefined, this.selectedTellerId || undefined)
.subscribe({
next: (res: any) => {
this.glRows = res?.rows || [];
this.glTotalDr = Number(res?.totalDr || 0);
this.glTotalCr = Number(res?.totalCr || 0);
this.glLoading = false;
this.glLoaded = true;
},
error: () => {
this.glRows = [];
this.glTotalDr = 0;
this.glTotalCr = 0;
this.glLoading = false;
this.glLoaded = true;
}
});
}
}