741 lines
32 KiB
HTML
741 lines
32 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/.
|
||
-->
|
||
|
||
<!-- ══════════════════════════════════════════════════════════
|
||
HERO HEADER — glassmorphism + animated gradient
|
||
══════════════════════════════════════════════════════════ -->
|
||
<div class="gl-hero">
|
||
<div class="gl-hero__bg"></div>
|
||
<div class="gl-hero__content">
|
||
<div class="gl-hero__left">
|
||
<div class="gl-hero__icon-wrap">
|
||
<fa-icon icon="building-columns" class="gl-hero__icon"></fa-icon>
|
||
<div class="gl-hero__icon-pulse"></div>
|
||
</div>
|
||
<div class="gl-hero__text">
|
||
<div class="gl-hero__breadcrumb">
|
||
<fa-icon icon="home" class="gl-hero__bc-icon"></fa-icon>
|
||
<span>Accounting</span>
|
||
<fa-icon icon="chevron-right" class="gl-hero__bc-sep"></fa-icon>
|
||
<span class="gl-hero__bc-active">General Ledger</span>
|
||
</div>
|
||
<h1 class="gl-hero__title">General Ledger</h1>
|
||
<p class="gl-hero__subtitle" *ngIf="!glAccountInfo">
|
||
Full double-entry journal — view, filter & analyse all accounting entries
|
||
</p>
|
||
<div class="gl-hero__acct-pill" *ngIf="glAccountInfo">
|
||
<span [class]="'gl-pill gl-pill--' + (glAccountInfo.type?.value | lowercase)">
|
||
<fa-icon [icon]="getAccountTypeIcon(glAccountInfo.type?.value)" class="gl-pill__icon"></fa-icon>
|
||
{{ glAccountInfo.type?.value }}
|
||
</span>
|
||
<span class="gl-hero__acct-code">{{ glAccountInfo.glCode }}</span>
|
||
<span class="gl-hero__acct-sep">·</span>
|
||
<span class="gl-hero__acct-name">{{ glAccountInfo.name }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="gl-hero__actions">
|
||
<div class="gl-hero__meta" *ngIf="ledgerData?.fromDate || ledgerData?.toDate">
|
||
<fa-icon icon="calendar-alt" class="gl-hero__meta-icon"></fa-icon>
|
||
<span>{{ formatLedgerDate(ledgerData?.fromDate) }} → {{ formatLedgerDate(ledgerData?.toDate) }}</span>
|
||
</div>
|
||
<button
|
||
class="gl-hero-btn gl-hero-btn--ghost"
|
||
(click)="loadLedgerSummary(); loadJournalEntriesPage()"
|
||
matTooltip="Refresh data"
|
||
>
|
||
<fa-icon icon="sync" [class.fa-spin]="ledgerLoading"></fa-icon>
|
||
Refresh
|
||
</button>
|
||
<!-- New Entry — hidden while Create Journal Entries is disabled, see disabled-features.ts -->
|
||
@if (!disabledFeatures.createJournalEntries) {
|
||
<button class="gl-hero-btn gl-hero-btn--primary" [routerLink]="['/accounting/journal-entries/create']">
|
||
<fa-icon icon="plus"></fa-icon>
|
||
New Entry
|
||
</button>
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════════════════════════════════════════════════════
|
||
LOADING SKELETON / PROGRESS BAR
|
||
══════════════════════════════════════════════════════════ -->
|
||
<div class="gl-progress-bar" *ngIf="ledgerLoading">
|
||
<div class="gl-progress-bar__fill"></div>
|
||
</div>
|
||
|
||
<!-- ══════════════════════════════════════════════════════════
|
||
GL ACCOUNT DETAIL STRIP (visible when account selected)
|
||
══════════════════════════════════════════════════════════ -->
|
||
<div class="gl-acct-strip" *ngIf="glAccountInfo">
|
||
<div class="gl-acct-strip__item">
|
||
<div class="gl-acct-strip__label">Account Code</div>
|
||
<div class="gl-acct-strip__value gl-mono">{{ glAccountInfo.glCode }}</div>
|
||
</div>
|
||
<div class="gl-acct-strip__divider"></div>
|
||
<div class="gl-acct-strip__item">
|
||
<div class="gl-acct-strip__label">Account Name</div>
|
||
<div class="gl-acct-strip__value">{{ glAccountInfo.name }}</div>
|
||
</div>
|
||
<div class="gl-acct-strip__divider"></div>
|
||
<div class="gl-acct-strip__item">
|
||
<div class="gl-acct-strip__label">Classification</div>
|
||
<div class="gl-acct-strip__value">
|
||
<span [class]="'gl-pill gl-pill--sm gl-pill--' + (glAccountInfo.type?.value | lowercase)">
|
||
<fa-icon [icon]="getAccountTypeIcon(glAccountInfo.type?.value)" class="gl-pill__icon"></fa-icon>
|
||
{{ glAccountInfo.type?.value }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="gl-acct-strip__divider"></div>
|
||
<div class="gl-acct-strip__item">
|
||
<div class="gl-acct-strip__label">Usage</div>
|
||
<div class="gl-acct-strip__value">{{ glAccountInfo.usage?.value || '—' }}</div>
|
||
</div>
|
||
<div class="gl-acct-strip__divider"></div>
|
||
<div class="gl-acct-strip__item">
|
||
<div class="gl-acct-strip__label">Manual Entries</div>
|
||
<div class="gl-acct-strip__value">
|
||
<span [class]="glAccountInfo.manualEntriesAllowed ? 'gl-status gl-status--on' : 'gl-status gl-status--off'">
|
||
<fa-icon [icon]="glAccountInfo.manualEntriesAllowed ? 'check-circle' : 'times-circle'"></fa-icon>
|
||
{{ glAccountInfo.manualEntriesAllowed ? 'Allowed' : 'Restricted' }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="gl-acct-strip__divider" *ngIf="glAccountInfo.description"></div>
|
||
<div class="gl-acct-strip__item gl-acct-strip__item--desc" *ngIf="glAccountInfo.description">
|
||
<div class="gl-acct-strip__label">Description</div>
|
||
<div class="gl-acct-strip__value gl-acct-strip__value--muted">{{ glAccountInfo.description }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════════════════════════════════════════════════════
|
||
KPI CARDS — 6 metrics with micro bar indicators
|
||
══════════════════════════════════════════════════════════ -->
|
||
<div class="gl-kpi-grid" [class.gl-kpi-grid--loading]="ledgerLoading">
|
||
<!-- Opening Balance -->
|
||
<div class="gl-kpi gl-kpi--opening">
|
||
<div class="gl-kpi__header">
|
||
<div class="gl-kpi__icon-box">
|
||
<fa-icon icon="door-open"></fa-icon>
|
||
</div>
|
||
<div class="gl-kpi__trend gl-kpi__trend--neutral" matTooltip="Balance before the selected period">
|
||
<fa-icon icon="info-circle"></fa-icon>
|
||
</div>
|
||
</div>
|
||
<div class="gl-kpi__body">
|
||
<div class="gl-kpi__label">Opening Balance</div>
|
||
<div class="gl-kpi__value" [class.gl-kpi__value--neg]="openingBalance < 0">
|
||
{{ openingBalance | formatNumber }}
|
||
</div>
|
||
<div class="gl-kpi__caption">Before {{ formatLedgerDate(ledgerData?.fromDate) || 'period start' }}</div>
|
||
</div>
|
||
<div class="gl-kpi__bar gl-kpi__bar--opening"></div>
|
||
</div>
|
||
|
||
<!-- Total Debit -->
|
||
<div class="gl-kpi gl-kpi--dr">
|
||
<div class="gl-kpi__header">
|
||
<div class="gl-kpi__icon-box">
|
||
<fa-icon icon="arrow-up"></fa-icon>
|
||
</div>
|
||
<div class="gl-kpi__trend gl-kpi__trend--dr" matTooltip="Sum of all debit entries in period">
|
||
<fa-icon icon="arrow-up"></fa-icon> DR
|
||
</div>
|
||
</div>
|
||
<div class="gl-kpi__body">
|
||
<div class="gl-kpi__label">Total Debit</div>
|
||
<div class="gl-kpi__value gl-kpi__value--dr">{{ totalDebit | formatNumber }}</div>
|
||
<div class="gl-kpi__caption">Debit entries in period</div>
|
||
</div>
|
||
<div class="gl-kpi__bar gl-kpi__bar--dr"></div>
|
||
</div>
|
||
|
||
<!-- Total Credit -->
|
||
<div class="gl-kpi gl-kpi--cr">
|
||
<div class="gl-kpi__header">
|
||
<div class="gl-kpi__icon-box">
|
||
<fa-icon icon="arrow-down"></fa-icon>
|
||
</div>
|
||
<div class="gl-kpi__trend gl-kpi__trend--cr" matTooltip="Sum of all credit entries in period">
|
||
<fa-icon icon="arrow-down"></fa-icon> CR
|
||
</div>
|
||
</div>
|
||
<div class="gl-kpi__body">
|
||
<div class="gl-kpi__label">Total Credit</div>
|
||
<div class="gl-kpi__value gl-kpi__value--cr">{{ totalCredit | formatNumber }}</div>
|
||
<div class="gl-kpi__caption">Credit entries in period</div>
|
||
</div>
|
||
<div class="gl-kpi__bar gl-kpi__bar--cr"></div>
|
||
</div>
|
||
|
||
<!-- Net Movement -->
|
||
<div class="gl-kpi gl-kpi--net">
|
||
<div class="gl-kpi__header">
|
||
<div class="gl-kpi__icon-box">
|
||
<fa-icon icon="scale-balanced"></fa-icon>
|
||
</div>
|
||
<div
|
||
[class]="netMovementPositive ? 'gl-kpi__trend gl-kpi__trend--pos' : 'gl-kpi__trend gl-kpi__trend--neg'"
|
||
[matTooltip]="isDebitNormal ? 'DR − CR (Debit-normal account)' : 'CR − DR (Credit-normal account)'"
|
||
>
|
||
<fa-icon [icon]="netMovementPositive ? 'arrow-up' : 'arrow-down'"></fa-icon>
|
||
<span class="gl-kpi__norm-tag">{{ isDebitNormal ? 'DR−CR' : 'CR−DR' }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="gl-kpi__body">
|
||
<div class="gl-kpi__label">Net Movement</div>
|
||
<div
|
||
class="gl-kpi__value"
|
||
[class.gl-kpi__value--pos]="netMovementPositive"
|
||
[class.gl-kpi__value--neg]="!netMovementPositive"
|
||
>
|
||
{{ netMovement | formatNumber }}
|
||
</div>
|
||
<div class="gl-kpi__caption">
|
||
{{ netMovementPositive ? 'Net Positive' : 'Net Negative' }} ·
|
||
{{ isDebitNormal ? 'Debit Normal' : 'Credit Normal' }}
|
||
</div>
|
||
</div>
|
||
<div class="gl-kpi__bar gl-kpi__bar--net"></div>
|
||
</div>
|
||
|
||
<!-- Closing Balance -->
|
||
<div class="gl-kpi gl-kpi--closing">
|
||
<div class="gl-kpi__header">
|
||
<div class="gl-kpi__icon-box">
|
||
<fa-icon icon="box-open"></fa-icon>
|
||
</div>
|
||
<div
|
||
[class]="closingBalance >= 0 ? 'gl-kpi__trend gl-kpi__trend--pos' : 'gl-kpi__trend gl-kpi__trend--neg'"
|
||
matTooltip="Opening + Net Movement"
|
||
>
|
||
<fa-icon [icon]="closingBalance >= 0 ? 'arrow-up' : 'arrow-down'"></fa-icon>
|
||
</div>
|
||
</div>
|
||
<div class="gl-kpi__body">
|
||
<div class="gl-kpi__label">Closing Balance</div>
|
||
<div class="gl-kpi__value" [class.gl-kpi__value--neg]="closingBalance < 0">
|
||
{{ closingBalance | formatNumber }}
|
||
</div>
|
||
<div class="gl-kpi__caption">As of {{ formatLedgerDate(ledgerData?.toDate) || 'period end' }}</div>
|
||
</div>
|
||
<div class="gl-kpi__bar gl-kpi__bar--closing"></div>
|
||
</div>
|
||
|
||
<!-- Entry Count -->
|
||
<div class="gl-kpi gl-kpi--count">
|
||
<div class="gl-kpi__header">
|
||
<div class="gl-kpi__icon-box">
|
||
<fa-icon icon="receipt"></fa-icon>
|
||
</div>
|
||
<div class="gl-kpi__trend gl-kpi__trend--neutral" matTooltip="Total filtered records">
|
||
<fa-icon icon="filter"></fa-icon>
|
||
</div>
|
||
</div>
|
||
<div class="gl-kpi__body">
|
||
<div class="gl-kpi__label">Total Entries</div>
|
||
<div class="gl-kpi__value gl-kpi__value--count">{{ totalEntries || (dataSource?.records$ | async) || 0 }}</div>
|
||
<div class="gl-kpi__caption">Matching journal entries</div>
|
||
</div>
|
||
<div class="gl-kpi__bar gl-kpi__bar--count"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════════════════════════════════════════════════════
|
||
QUICK DATE CHIPS
|
||
══════════════════════════════════════════════════════════ -->
|
||
<div class="gl-quick-bar">
|
||
<div class="gl-quick-bar__label"><fa-icon icon="clock"></fa-icon> Quick Range</div>
|
||
<div class="gl-quick-bar__chips">
|
||
<button
|
||
*ngFor="let q of quickFilters"
|
||
[class.active]="activeQuickFilter === q.label"
|
||
class="gl-qchip"
|
||
(click)="applyQuickFilter(q)"
|
||
>
|
||
<fa-icon [icon]="q.icon"></fa-icon>
|
||
{{ q.label }}
|
||
</button>
|
||
<button *ngIf="activeQuickFilter" class="gl-qchip gl-qchip--clear" (click)="clearQuickFilter()">
|
||
<fa-icon icon="times"></fa-icon> Clear
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════════════════════════════════════════════════════
|
||
FILTER PANEL
|
||
══════════════════════════════════════════════════════════ -->
|
||
<div class="gl-filter-panel">
|
||
<!-- Primary filters -->
|
||
<div class="gl-filter-panel__primary">
|
||
<div class="gl-fgroup">
|
||
<label class="gl-flabel"><fa-icon icon="calendar-alt"></fa-icon> From Date</label>
|
||
<mat-form-field appearance="outline" class="gl-ffield">
|
||
<input matInput [matDatepicker]="dpFrom" [min]="minDate" [max]="maxDate" [formControl]="transactionDateFrom" />
|
||
<mat-datepicker-toggle matSuffix [for]="dpFrom"></mat-datepicker-toggle>
|
||
<mat-datepicker #dpFrom></mat-datepicker>
|
||
</mat-form-field>
|
||
</div>
|
||
|
||
<div class="gl-fgroup">
|
||
<label class="gl-flabel"><fa-icon icon="calendar-alt"></fa-icon> To Date</label>
|
||
<mat-form-field appearance="outline" class="gl-ffield">
|
||
<input matInput [matDatepicker]="dpTo" [min]="minDate" [max]="maxDate" [formControl]="transactionDateTo" />
|
||
<mat-datepicker-toggle matSuffix [for]="dpTo"></mat-datepicker-toggle>
|
||
<mat-datepicker #dpTo></mat-datepicker>
|
||
</mat-form-field>
|
||
</div>
|
||
|
||
<div class="gl-fgroup gl-fgroup--wide">
|
||
<label class="gl-flabel"><fa-icon icon="building-columns"></fa-icon> GL Account</label>
|
||
<div class="gl-ffield gl-ffield--selector">
|
||
<mifosx-gl-account-selector
|
||
[inputFormControl]="glAccount"
|
||
[glAccountList]="glAccountData"
|
||
></mifosx-gl-account-selector>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="gl-fgroup">
|
||
<label class="gl-flabel"><fa-icon icon="toggle-on"></fa-icon> Entry Source</label>
|
||
<mat-form-field appearance="outline" class="gl-ffield">
|
||
<mat-select [formControl]="entryTypeFilter">
|
||
<mat-option *ngFor="let et of entryTypeFilterData" [value]="et.value">{{ et.option }}</mat-option>
|
||
</mat-select>
|
||
</mat-form-field>
|
||
</div>
|
||
|
||
<div class="gl-filter-actions">
|
||
<button
|
||
class="gl-fab gl-fab--adv"
|
||
(click)="showAdvancedFilters = !showAdvancedFilters"
|
||
[class.gl-fab--adv-on]="showAdvancedFilters"
|
||
matTooltip="More filters"
|
||
>
|
||
<fa-icon icon="sliders-h"></fa-icon>
|
||
<span *ngIf="activeFiltersCount > 0" class="gl-fab__badge">{{ activeFiltersCount }}</span>
|
||
</button>
|
||
<button class="gl-fab gl-fab--reset" (click)="clearFilters()" matTooltip="Reset all filters">
|
||
<fa-icon icon="times"></fa-icon>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Advanced filters (collapsible) -->
|
||
<div class="gl-filter-panel__advanced" [class.gl-filter-panel__advanced--open]="showAdvancedFilters">
|
||
<div class="gl-filter-panel__adv-inner">
|
||
<div class="gl-fgroup">
|
||
<label class="gl-flabel"><fa-icon icon="university"></fa-icon> Office</label>
|
||
<mat-form-field appearance="outline" class="gl-ffield">
|
||
<input matInput [formControl]="officeName" [matAutocomplete]="officeAc" placeholder="Search office…" />
|
||
<mat-autocomplete #officeAc="matAutocomplete" [displayWith]="displayOfficeName">
|
||
<mat-option *ngFor="let o of filteredOfficeData | async" [value]="o">{{ o.name }}</mat-option>
|
||
</mat-autocomplete>
|
||
</mat-form-field>
|
||
</div>
|
||
|
||
<div class="gl-fgroup">
|
||
<label class="gl-flabel"><fa-icon icon="hashtag"></fa-icon> Transaction ID</label>
|
||
<mat-form-field appearance="outline" class="gl-ffield">
|
||
<input matInput [formControl]="transactionIdCtrl" placeholder="e.g. L000001" />
|
||
<fa-icon icon="search" matSuffix class="gl-field-suffix-icon"></fa-icon>
|
||
</mat-form-field>
|
||
</div>
|
||
|
||
<div class="gl-fgroup">
|
||
<label class="gl-flabel"><fa-icon icon="calendar-check"></fa-icon> Submitted From</label>
|
||
<mat-form-field appearance="outline" class="gl-ffield">
|
||
<input
|
||
matInput
|
||
[matDatepicker]="dpSubFrom"
|
||
[min]="minDate"
|
||
[max]="maxDate"
|
||
[formControl]="submittedOnDateFrom"
|
||
/>
|
||
<mat-datepicker-toggle matSuffix [for]="dpSubFrom"></mat-datepicker-toggle>
|
||
<mat-datepicker #dpSubFrom></mat-datepicker>
|
||
</mat-form-field>
|
||
</div>
|
||
|
||
<div class="gl-fgroup">
|
||
<label class="gl-flabel"><fa-icon icon="calendar-check"></fa-icon> Submitted To</label>
|
||
<mat-form-field appearance="outline" class="gl-ffield">
|
||
<input matInput [matDatepicker]="dpSubTo" [min]="minDate" [max]="maxDate" [formControl]="submittedOnDateTo" />
|
||
<mat-datepicker-toggle matSuffix [for]="dpSubTo"></mat-datepicker-toggle>
|
||
<mat-datepicker #dpSubTo></mat-datepicker>
|
||
</mat-form-field>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════════════════════════════════════════════════════
|
||
TIP CARD — shown when no GL account selected
|
||
══════════════════════════════════════════════════════════ -->
|
||
<div class="gl-tip" *ngIf="!hasGlAccountFilter">
|
||
<div class="gl-tip__icon"><fa-icon icon="lightbulb"></fa-icon></div>
|
||
<div class="gl-tip__body">
|
||
<strong>Select a GL Account</strong> to unlock full ledger details — opening balance, day-wise running balance,
|
||
closing balance, and per-entry running totals.
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════════════════════════════════════════════════════
|
||
DAY-WISE BALANCE TIMELINE
|
||
══════════════════════════════════════════════════════════ -->
|
||
<div class="gl-timeline-section" *ngIf="dayWiseData.length > 0">
|
||
<div class="gl-section-head">
|
||
<div class="gl-section-head__left">
|
||
<div class="gl-section-head__dot gl-section-head__dot--purple"></div>
|
||
<span class="gl-section-head__title">Day-wise Balance Timeline</span>
|
||
<span class="gl-section-head__badge">{{ dayWiseData.length }} days</span>
|
||
</div>
|
||
<div class="gl-section-head__right">
|
||
<span class="gl-legend-pill gl-legend-pill--open">Opening</span>
|
||
<span class="gl-legend-pill gl-legend-pill--dr">Debit</span>
|
||
<span class="gl-legend-pill gl-legend-pill--cr">Credit</span>
|
||
<span class="gl-legend-pill gl-legend-pill--net">Net</span>
|
||
<span class="gl-legend-pill gl-legend-pill--close">Closing</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="gl-timeline-wrap">
|
||
<table class="gl-tl-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="gl-tl-th">Date</th>
|
||
<th class="gl-tl-th gl-tl-th--num">Opening Bal</th>
|
||
<th class="gl-tl-th gl-tl-th--num gl-tl-th--dr">Debit (DR)</th>
|
||
<th class="gl-tl-th gl-tl-th--num gl-tl-th--cr">Credit (CR)</th>
|
||
<th class="gl-tl-th gl-tl-th--bar">Movement</th>
|
||
<th class="gl-tl-th gl-tl-th--num">Net</th>
|
||
<th class="gl-tl-th gl-tl-th--num gl-tl-th--close">Closing Bal</th>
|
||
<th class="gl-tl-th gl-tl-th--ctr">Entries</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr
|
||
*ngFor="let row of dayWiseData; let i = index"
|
||
class="gl-tl-row"
|
||
[class.gl-tl-row--first]="i === 0"
|
||
[class.gl-tl-row--last]="i === dayWiseData.length - 1"
|
||
>
|
||
<td class="gl-tl-date">
|
||
<div class="gl-tl-date__inner">
|
||
<div
|
||
class="gl-tl-date__dot"
|
||
[class.gl-tl-date__dot--up]="row.netMovement >= 0"
|
||
[class.gl-tl-date__dot--down]="row.netMovement < 0"
|
||
></div>
|
||
<div class="gl-tl-date__text">{{ formatLedgerDate(row.date) }}</div>
|
||
</div>
|
||
</td>
|
||
|
||
<td class="gl-tl-num">{{ row.openingBalance | formatNumber }}</td>
|
||
|
||
<td class="gl-tl-num gl-tl-num--dr">
|
||
<span *ngIf="row.totalDebit > 0">{{ row.totalDebit | formatNumber }}</span>
|
||
<span *ngIf="row.totalDebit === 0" class="gl-tl-nil">—</span>
|
||
</td>
|
||
|
||
<td class="gl-tl-num gl-tl-num--cr">
|
||
<span *ngIf="row.totalCredit > 0">{{ row.totalCredit | formatNumber }}</span>
|
||
<span *ngIf="row.totalCredit === 0" class="gl-tl-nil">—</span>
|
||
</td>
|
||
|
||
<!-- Mini visual bar — DR vs CR side by side as % of their combined total -->
|
||
<td class="gl-tl-bar-cell">
|
||
<div class="gl-minibar">
|
||
<div
|
||
class="gl-minibar__dr"
|
||
[style.flex]="row.totalDebit"
|
||
matTooltip="DR: {{ row.totalDebit | formatNumber }}"
|
||
></div>
|
||
<div
|
||
class="gl-minibar__cr"
|
||
[style.flex]="row.totalCredit"
|
||
matTooltip="CR: {{ row.totalCredit | formatNumber }}"
|
||
></div>
|
||
</div>
|
||
</td>
|
||
|
||
<td
|
||
class="gl-tl-num"
|
||
[class.gl-tl-num--pos]="row.netMovement >= 0"
|
||
[class.gl-tl-num--neg]="row.netMovement < 0"
|
||
>
|
||
<fa-icon [icon]="row.netMovement >= 0 ? 'arrow-up' : 'arrow-down'" class="gl-tl-net-icon"></fa-icon>
|
||
{{ row.netMovement | formatNumber }}
|
||
</td>
|
||
|
||
<td class="gl-tl-num gl-tl-num--close" [class.gl-tl-num--neg]="row.closingBalance < 0">
|
||
<strong>{{ row.closingBalance | formatNumber }}</strong>
|
||
</td>
|
||
|
||
<td class="gl-tl-ctr">
|
||
<span class="gl-tl-badge">{{ row.entryCount }}</span>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr class="gl-tl-footer">
|
||
<td class="gl-tl-footer__label">Period Total</td>
|
||
<td class="gl-tl-num">{{ openingBalance | formatNumber }}</td>
|
||
<td class="gl-tl-num gl-tl-num--dr">
|
||
<strong>{{ totalDebit | formatNumber }}</strong>
|
||
</td>
|
||
<td class="gl-tl-num gl-tl-num--cr">
|
||
<strong>{{ totalCredit | formatNumber }}</strong>
|
||
</td>
|
||
<td></td>
|
||
<td
|
||
class="gl-tl-num"
|
||
[class.gl-tl-num--pos]="netMovementPositive"
|
||
[class.gl-tl-num--neg]="!netMovementPositive"
|
||
>
|
||
<strong>{{ netMovement | formatNumber }}</strong>
|
||
</td>
|
||
<td class="gl-tl-num gl-tl-num--close" [class.gl-tl-num--neg]="closingBalance < 0">
|
||
<strong>{{ closingBalance | formatNumber }}</strong>
|
||
</td>
|
||
<td class="gl-tl-ctr">
|
||
<span class="gl-tl-badge gl-tl-badge--total">{{ (dataSource?.records$ | async) || 0 }}</span>
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════════════════════════════════════════════════════
|
||
JOURNAL ENTRIES TABLE
|
||
══════════════════════════════════════════════════════════ -->
|
||
<div class="gl-entries-section">
|
||
<div class="gl-section-head">
|
||
<div class="gl-section-head__left">
|
||
<div class="gl-section-head__dot gl-section-head__dot--blue"></div>
|
||
<span class="gl-section-head__title">Journal Entries</span>
|
||
<span class="gl-section-head__badge">{{ (dataSource?.records$ | async) || 0 }}</span>
|
||
</div>
|
||
<div class="gl-section-head__right">
|
||
<div class="gl-entries-totals">
|
||
<span class="gl-entries-totals__dr">
|
||
<fa-icon icon="arrow-up"></fa-icon> DR <strong>{{ totalDebit | formatNumber }}</strong>
|
||
</span>
|
||
<div class="gl-entries-totals__sep"></div>
|
||
<span class="gl-entries-totals__cr">
|
||
<fa-icon icon="arrow-down"></fa-icon> CR <strong>{{ totalCredit | formatNumber }}</strong>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="gl-table-shell">
|
||
<mat-table [dataSource]="dataSource" matSort class="gl-mat-table">
|
||
<!-- #ID -->
|
||
<ng-container matColumnDef="id">
|
||
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">#</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<a [routerLink]="['/accounting/journal-entries/transactions/view', e.transactionId]" class="gl-id-link"
|
||
>#{{ e.id }}</a
|
||
>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Transaction Date -->
|
||
<ng-container matColumnDef="transactionDate">
|
||
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">Date</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<div class="gl-date-cell">
|
||
<div class="gl-date-cell__main">{{ formatLedgerDate(e.transactionDate) }}</div>
|
||
</div>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Transaction ID -->
|
||
<ng-container matColumnDef="transactionId">
|
||
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">Txn ID</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<a
|
||
[routerLink]="['/accounting/journal-entries/transactions/view', e.transactionId]"
|
||
class="gl-txn-link gl-mono"
|
||
matTooltip="{{ e.transactionId }}"
|
||
>
|
||
{{ e.transactionId }}
|
||
</a>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Savings Account No -->
|
||
<ng-container matColumnDef="savingsAccountNo">
|
||
<mat-header-cell *matHeaderCellDef class="gl-th">Account No</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<span *ngIf="e.savingsAccountNo" class="gl-acct-no-chip" [matTooltip]="e.savingsAccountNo">{{
|
||
e.savingsAccountNo
|
||
}}</span>
|
||
<span *ngIf="!e.savingsAccountNo" class="gl-na">—</span>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Office -->
|
||
<ng-container matColumnDef="officeName">
|
||
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">Office</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<div class="gl-office-cell">
|
||
<fa-icon icon="university" class="gl-office-icon"></fa-icon>
|
||
{{ e.officeName }}
|
||
</div>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- GL Code -->
|
||
<ng-container matColumnDef="glAccountCode">
|
||
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">Code</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<span class="gl-code-chip">{{ e.glAccountCode }}</span>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- GL Account Name -->
|
||
<ng-container matColumnDef="glAccountName">
|
||
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">Account</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<span class="gl-acct-cell" matTooltip="{{ e.glAccountName }}">{{ e.glAccountName }}</span>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Account Type -->
|
||
<ng-container matColumnDef="glAccountType">
|
||
<mat-header-cell *matHeaderCellDef class="gl-th">Type</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<span [class]="'gl-pill gl-pill--sm gl-pill--' + (e.glAccountType?.value | lowercase)">
|
||
<fa-icon [icon]="getAccountTypeIcon(e.glAccountType?.value)" class="gl-pill__icon"></fa-icon>
|
||
{{ e.glAccountType?.value }}
|
||
</span>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Entry Type DR/CR -->
|
||
<ng-container matColumnDef="entryType">
|
||
<mat-header-cell *matHeaderCellDef class="gl-th">Type</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<span [class]="e.entryType?.value === 'DEBIT' ? 'gl-drcr gl-drcr--dr' : 'gl-drcr gl-drcr--cr'">
|
||
{{ e.entryType?.value === 'DEBIT' ? 'DR' : 'CR' }}
|
||
</span>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Debit -->
|
||
<ng-container matColumnDef="debit">
|
||
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th gl-th--num">Debit</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td gl-td--num">
|
||
<span *ngIf="e.entryType?.value === 'DEBIT'" class="gl-amt gl-amt--dr">{{ e.amount | formatNumber }}</span>
|
||
<span *ngIf="e.entryType?.value !== 'DEBIT'" class="gl-amt-nil">—</span>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Credit -->
|
||
<ng-container matColumnDef="credit">
|
||
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th gl-th--num">Credit</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td gl-td--num">
|
||
<span *ngIf="e.entryType?.value === 'CREDIT'" class="gl-amt gl-amt--cr">{{ e.amount | formatNumber }}</span>
|
||
<span *ngIf="e.entryType?.value !== 'CREDIT'" class="gl-amt-nil">—</span>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Running Balance -->
|
||
<ng-container matColumnDef="runningBalance">
|
||
<mat-header-cell *matHeaderCellDef class="gl-th gl-th--num">Running Bal</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td gl-td--num">
|
||
<span
|
||
class="gl-running"
|
||
[class.gl-running--neg]="getRunningBalance(e) < 0"
|
||
matTooltip="Office running balance"
|
||
>
|
||
{{ getRunningBalance(e) | formatNumber }}
|
||
</span>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Source (Manual/System) -->
|
||
<ng-container matColumnDef="manualEntry">
|
||
<mat-header-cell *matHeaderCellDef class="gl-th">Source</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<span [class]="e.manualEntry ? 'gl-src gl-src--manual' : 'gl-src gl-src--system'">
|
||
<fa-icon [icon]="e.manualEntry ? 'pencil-alt' : 'cog'"></fa-icon>
|
||
{{ e.manualEntry ? 'Manual' : 'System' }}
|
||
</span>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Created By -->
|
||
<ng-container matColumnDef="createdByUserName">
|
||
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">By</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<div class="gl-user-cell">
|
||
<div class="gl-user-avatar">{{ (e.createdByUserName || 'U').charAt(0).toUpperCase() }}</div>
|
||
<span>{{ e.createdByUserName }}</span>
|
||
</div>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Submitted On -->
|
||
<ng-container matColumnDef="submittedOnDate">
|
||
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">Submitted</mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<span class="gl-ts">{{ e.submittedOnDate | datetimeFormat }}</span>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<!-- Actions -->
|
||
<ng-container matColumnDef="actions">
|
||
<mat-header-cell *matHeaderCellDef class="gl-th"></mat-header-cell>
|
||
<mat-cell *matCellDef="let e" class="gl-td">
|
||
<a
|
||
[routerLink]="['/accounting/journal-entries/transactions/view', e.transactionId]"
|
||
class="gl-view-btn"
|
||
matTooltip="View transaction"
|
||
>
|
||
<fa-icon icon="eye"></fa-icon>
|
||
</a>
|
||
</mat-cell>
|
||
</ng-container>
|
||
|
||
<mat-header-row *matHeaderRowDef="displayedColumns; sticky: true" class="gl-header-row"></mat-header-row>
|
||
<mat-row
|
||
*matRowDef="let row; columns: displayedColumns"
|
||
[class.gl-data-row--dr]="row.entryType?.value === 'DEBIT'"
|
||
[class.gl-data-row--cr]="row.entryType?.value === 'CREDIT'"
|
||
class="gl-data-row"
|
||
></mat-row>
|
||
</mat-table>
|
||
|
||
<!-- Keyed off the rows actually rendered, not totalEntries: that count comes from the ledger
|
||
summary, which only reports a figure once a GL account is chosen, so the search view would
|
||
otherwise show "No entries found" above a full table. -->
|
||
@if (((dataSource?.entries$ | async) || []).length === 0) {
|
||
<div class="gl-empty">
|
||
<div class="gl-empty__icon"><fa-icon icon="search"></fa-icon></div>
|
||
<div class="gl-empty__title">No entries found</div>
|
||
<div class="gl-empty__sub">Try adjusting your filters or date range</div>
|
||
</div>
|
||
}
|
||
|
||
<div class="gl-pager-wrap">
|
||
<mat-paginator
|
||
[length]="(dataSource?.records$ | async) || 0"
|
||
[pageSizeOptions]="[10, 25, 50, 100]"
|
||
showFirstLastButtons
|
||
></mat-paginator>
|
||
</div>
|
||
</div>
|
||
</div>
|