feat: cash-only registers, day book sections and DD amendments
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
|
||||
<div class="sdi-tabs">
|
||||
<button [class.active]="activeTab === 'products'" (click)="backToProducts()">Products</button>
|
||||
<button [class.active]="activeTab === 'summary'" (click)="loadSummary()">All Accounts</button>
|
||||
@if (selectedProduct) {
|
||||
<button [class.active]="activeTab === 'accounts'" (click)="backToAccounts()">
|
||||
{{ selectedProduct.name }}
|
||||
@@ -41,6 +42,89 @@
|
||||
<div class="sdi-loading">Loading…</div>
|
||||
}
|
||||
|
||||
<!--
|
||||
All Accounts: one line per savings account, across every product.
|
||||
|
||||
The ledger behind this holds a row per account per day — interest is worked out on the balance
|
||||
each day carried, and a reversed backdated entry means every day after it is recomputed. This
|
||||
reads the top of that ledger, so the whole book is one row each.
|
||||
-->
|
||||
@if (activeTab === 'summary' && !loading) {
|
||||
<div class="sdi-summary-bar">
|
||||
<input
|
||||
class="sdi-search"
|
||||
type="text"
|
||||
[(ngModel)]="summarySearch"
|
||||
placeholder="Search account number, customer or product"
|
||||
/>
|
||||
<span class="sdi-spacer"></span>
|
||||
<span class="sdi-total">
|
||||
{{ filteredSummary.length }} accounts · accrued this cycle
|
||||
<strong>{{ summaryAccruedTotal | number: '1.2-2' }}</strong> · posted to date
|
||||
<strong>{{ summaryPostedTotal | number: '1.2-2' }}</strong>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if (filteredSummary.length === 0) {
|
||||
<div class="sdi-loading">
|
||||
No accounts in the ledger yet. Run the accrual, or use Backfill to rebuild it from activation.
|
||||
</div>
|
||||
} @else {
|
||||
<table class="sdi-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account No</th>
|
||||
<th>Customer</th>
|
||||
<th>Product</th>
|
||||
<th>As On</th>
|
||||
<th class="num">Balance</th>
|
||||
<th class="num">Rate</th>
|
||||
<th class="num">Interest Today</th>
|
||||
<th>Cycle</th>
|
||||
<th class="num">Accrued This Cycle</th>
|
||||
<th>Next Posting</th>
|
||||
<th class="num">Posted To Date</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (row of filteredSummary; track row.savings_account_id) {
|
||||
<tr [class.sdi-row--posting]="row.is_posting_day" [class.sdi-row--below]="row.below_min_balance">
|
||||
<td class="mono">{{ row.account_no }}</td>
|
||||
<td>{{ row.client_name || '—' }}</td>
|
||||
<td>
|
||||
{{ row.product_name }}
|
||||
<div class="sdi-sub">{{ postingPeriodLabel(row.posting_period_enum ?? 0) }} posting</div>
|
||||
</td>
|
||||
<td class="mono">{{ formatDate(row.as_on_date) }}</td>
|
||||
<td class="num">{{ row.closing_balance | number: '1.2-2' }}</td>
|
||||
<td class="num">{{ row.interest_rate | number: '1.2-2' }}%</td>
|
||||
<td class="num">{{ row.interest_today | number: '1.4-4' }}</td>
|
||||
<td>
|
||||
#{{ row.cycle_number }}
|
||||
<div class="sdi-sub mono">{{ formatDate(row.cycle_start_date) }}</div>
|
||||
</td>
|
||||
<td class="num strong">{{ row.interest_this_cycle | number: '1.2-2' }}</td>
|
||||
<td>
|
||||
<span class="mono">{{ formatDate(row.cycle_end_date) }}</span>
|
||||
@if (row.is_posting_day) {
|
||||
<div class="sdi-sub sdi-sub--due">posting today</div>
|
||||
} @else {
|
||||
<div class="sdi-sub">in {{ row.days_to_posting }} days</div>
|
||||
}
|
||||
</td>
|
||||
<td class="num">{{ row.interest_posted_total | number: '1.2-2' }}</td>
|
||||
<td>
|
||||
<!-- The day-by-day working, for the account whose figure is being questioned. -->
|
||||
<button class="sdi-link" (click)="openLedgerFor(row)">Days</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
}
|
||||
|
||||
<!-- Products: one row per savings product, with where it stands in its timetable. -->
|
||||
@if (activeTab === 'products' && !loading) {
|
||||
<table class="sdi-table">
|
||||
@@ -71,8 +155,11 @@
|
||||
<td>{{ formatDate(product.next_posting_date) }}</td>
|
||||
<td>
|
||||
@if (product.last_run_status) {
|
||||
<span class="sdi-chip" [class.ok]="product.last_run_status === 'COMPLETED'"
|
||||
[class.bad]="product.last_run_status === 'FAILED'">
|
||||
<span
|
||||
class="sdi-chip"
|
||||
[class.ok]="product.last_run_status === 'COMPLETED'"
|
||||
[class.bad]="product.last_run_status === 'FAILED'"
|
||||
>
|
||||
{{ product.last_run_status }}
|
||||
</span>
|
||||
} @else {
|
||||
@@ -81,7 +168,9 @@
|
||||
</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr><td colspan="8" class="sdi-empty">No savings products carry a ledger yet.</td></tr>
|
||||
<tr>
|
||||
<td colspan="8" class="sdi-empty">No savings products carry a ledger yet.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -106,7 +195,9 @@
|
||||
<tbody>
|
||||
@for (account of accounts; track account.savings_account_id) {
|
||||
<tr class="clickable" (click)="openAccount(account)">
|
||||
<td><strong>{{ account.account_no }}</strong></td>
|
||||
<td>
|
||||
<strong>{{ account.account_no }}</strong>
|
||||
</td>
|
||||
<td>{{ account.client_name || '—' }}</td>
|
||||
<td>{{ formatDate(account.balance_date) }}</td>
|
||||
<td class="num">{{ account.opening_balance | number: '1.2-2' }}</td>
|
||||
@@ -114,7 +205,9 @@
|
||||
<td class="num" [class.zero]="account.below_min_balance">
|
||||
{{ account.daily_interest | number: '1.4-4' }}
|
||||
@if (account.below_min_balance) {
|
||||
<span class="sdi-flag" title="Closed below the minimum balance, so this day earned nothing">below min</span>
|
||||
<span class="sdi-flag" title="Closed below the minimum balance, so this day earned nothing"
|
||||
>below min</span
|
||||
>
|
||||
}
|
||||
</td>
|
||||
<td class="num accent">{{ account.cycle_cumulative_interest | number: '1.2-2' }}</td>
|
||||
@@ -122,7 +215,9 @@
|
||||
<td>{{ formatDate(account.cycle_end_date) }}</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr><td colspan="9" class="sdi-empty">No ledger rows for this product yet.</td></tr>
|
||||
<tr>
|
||||
<td colspan="9" class="sdi-empty">No ledger rows for this product yet.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -156,7 +251,9 @@
|
||||
<td class="num" [class.zero]="day.below_min_balance">
|
||||
{{ day.daily_interest | number: '1.4-4' }}
|
||||
@if (day.below_min_balance) {
|
||||
<span class="sdi-flag" title="Closed below the minimum balance, so this day earned nothing">below min</span>
|
||||
<span class="sdi-flag" title="Closed below the minimum balance, so this day earned nothing"
|
||||
>below min</span
|
||||
>
|
||||
}
|
||||
</td>
|
||||
<td class="num accent">{{ day.cycle_cumulative_interest | number: '1.4-4' }}</td>
|
||||
@@ -170,7 +267,9 @@
|
||||
</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr><td colspan="7" class="sdi-empty">No days recorded for this account.</td></tr>
|
||||
<tr>
|
||||
<td colspan="7" class="sdi-empty">No days recorded for this account.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -200,15 +299,18 @@
|
||||
<td class="num">{{ run.distributed_amount | number: '1.2-2' }}</td>
|
||||
<td class="num">{{ run.rounding_residual | number: '1.2-2' }}</td>
|
||||
<td>
|
||||
<span class="sdi-chip" [class.ok]="run.status === 'COMPLETED'"
|
||||
[class.bad]="run.status === 'FAILED'">{{ run.status }}</span>
|
||||
<span class="sdi-chip" [class.ok]="run.status === 'COMPLETED'" [class.bad]="run.status === 'FAILED'">{{
|
||||
run.status
|
||||
}}</span>
|
||||
@if (run.failure_reason) {
|
||||
<div class="sdi-sub">{{ run.failure_reason }}</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr><td colspan="7" class="sdi-empty">No posting runs yet.</td></tr>
|
||||
<tr>
|
||||
<td colspan="7" class="sdi-empty">No posting runs yet.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
flex-wrap: wrap;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
border-bottom: 1px solid rgb(0 0 0 / 12%);
|
||||
margin-bottom: 1rem;
|
||||
|
||||
button {
|
||||
@@ -87,12 +87,12 @@
|
||||
td {
|
||||
padding: 0.5rem 0.75rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-bottom: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 600;
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
background: rgb(0 0 0 / 3%);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
@@ -106,13 +106,13 @@
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: rgba(21, 101, 192, 0.06);
|
||||
background: rgb(21 101 192 / 6%);
|
||||
}
|
||||
}
|
||||
|
||||
// The day the cycle is paid out and the sum goes back to zero.
|
||||
tr.posting-row {
|
||||
background: rgba(46, 125, 50, 0.08);
|
||||
background: rgb(46 125 50 / 8%);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
@@ -144,15 +144,15 @@
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 10px;
|
||||
font-size: 0.75rem;
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
background: rgb(0 0 0 / 8%);
|
||||
|
||||
&.ok {
|
||||
background: rgba(46, 125, 50, 0.15);
|
||||
background: rgb(46 125 50 / 15%);
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
&.bad {
|
||||
background: rgba(198, 40, 40, 0.15);
|
||||
background: rgb(198 40 40 / 15%);
|
||||
color: #c62828;
|
||||
}
|
||||
}
|
||||
@@ -164,10 +164,74 @@
|
||||
border-radius: 3px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
background: rgb(0 0 0 / 8%);
|
||||
|
||||
&.paid {
|
||||
background: rgba(46, 125, 50, 0.2);
|
||||
background: rgb(46 125 50 / 20%);
|
||||
color: #2e7d32;
|
||||
}
|
||||
}
|
||||
|
||||
// ── All Accounts: one line per account ───────────────────────────────────────
|
||||
|
||||
.sdi-summary-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.sdi-search {
|
||||
min-width: 20rem;
|
||||
padding: 0.45rem 0.7rem;
|
||||
border: 1px solid rgb(0 0 0 / 20%);
|
||||
border-radius: 4px;
|
||||
font: inherit;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.sdi-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sdi-total {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
// The day the cycle is paid out — the accrued figure goes back to zero after it.
|
||||
tr.sdi-row--posting {
|
||||
background: rgb(46 125 50 / 8%);
|
||||
}
|
||||
|
||||
// Under the product minimum, so this day earns nothing at all.
|
||||
tr.sdi-row--below {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.sdi-sub--due {
|
||||
color: #2e7d32;
|
||||
font-weight: 700;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sdi-link {
|
||||
border: 0;
|
||||
background: none;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
font-size: 0.8rem;
|
||||
color: #1565c0;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
SavingsInterestAccount,
|
||||
SavingsInterestDay,
|
||||
SavingsInterestProduct,
|
||||
SavingsInterestRun
|
||||
SavingsInterestRun,
|
||||
SavingsInterestSummaryRow
|
||||
} from './savings-daily-interest.service';
|
||||
|
||||
/**
|
||||
@@ -37,13 +38,17 @@ import {
|
||||
export class SavingsDailyInterestComponent implements OnInit {
|
||||
private service = inject(SavingsDailyInterestService);
|
||||
|
||||
activeTab: 'products' | 'accounts' | 'ledger' | 'runs' = 'products';
|
||||
activeTab: 'products' | 'summary' | 'accounts' | 'ledger' | 'runs' = 'products';
|
||||
|
||||
products: SavingsInterestProduct[] = [];
|
||||
accounts: SavingsInterestAccount[] = [];
|
||||
ledger: SavingsInterestDay[] = [];
|
||||
runs: SavingsInterestRun[] = [];
|
||||
|
||||
/** One row per account across every product. */
|
||||
summary: SavingsInterestSummaryRow[] = [];
|
||||
summarySearch = '';
|
||||
|
||||
selectedProduct: SavingsInterestProduct | null = null;
|
||||
selectedAccount: SavingsInterestAccount | null = null;
|
||||
|
||||
@@ -95,6 +100,39 @@ export class SavingsDailyInterestComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the day-by-day working for a row on the All Accounts list.
|
||||
*
|
||||
* The summary row carries what the ledger tab needs to caption itself, so it is mapped across
|
||||
* rather than the account being re-fetched: the two views read the same ledger, and a second call
|
||||
* would only be able to disagree with the row the user just clicked.
|
||||
*/
|
||||
openLedgerFor(row: SavingsInterestSummaryRow): void {
|
||||
this.selectedAccount = {
|
||||
savings_account_id: row.savings_account_id,
|
||||
account_no: row.account_no,
|
||||
client_name: row.client_name ?? '',
|
||||
balance_date: row.as_on_date,
|
||||
opening_balance: row.opening_balance,
|
||||
closing_balance: row.closing_balance,
|
||||
daily_interest: row.interest_today,
|
||||
cycle_cumulative_interest: row.interest_this_cycle,
|
||||
cycle_number: row.cycle_number,
|
||||
cycle_start_date: row.cycle_start_date,
|
||||
cycle_end_date: row.cycle_end_date,
|
||||
below_min_balance: row.below_min_balance
|
||||
};
|
||||
this.activeTab = 'ledger';
|
||||
this.loading = true;
|
||||
this.service.getAccountLedger(row.savings_account_id).subscribe({
|
||||
next: (data) => {
|
||||
this.ledger = data ?? [];
|
||||
this.loading = false;
|
||||
},
|
||||
error: (err) => this.fail(err)
|
||||
});
|
||||
}
|
||||
|
||||
loadRuns(): void {
|
||||
this.activeTab = 'runs';
|
||||
this.loading = true;
|
||||
@@ -107,6 +145,43 @@ export class SavingsDailyInterestComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Every account on one line: today's balance and interest, the cycle it is in, and what it has
|
||||
* been paid.
|
||||
*
|
||||
* Loaded unfiltered and narrowed in the browser. The whole book is one row per account, which is
|
||||
* small enough to hold, and filtering locally means typing in the search box does not re-query.
|
||||
*/
|
||||
loadSummary(): void {
|
||||
this.activeTab = 'summary';
|
||||
this.loading = true;
|
||||
this.service.getSummary().subscribe({
|
||||
next: (data) => {
|
||||
this.summary = data ?? [];
|
||||
this.loading = false;
|
||||
},
|
||||
error: (err) => this.fail(err)
|
||||
});
|
||||
}
|
||||
|
||||
get filteredSummary(): SavingsInterestSummaryRow[] {
|
||||
const needle = this.summarySearch.trim().toLowerCase();
|
||||
if (!needle) {
|
||||
return this.summary;
|
||||
}
|
||||
return this.summary.filter((r) =>
|
||||
`${r.account_no} ${r.client_name ?? ''} ${r.product_name}`.toLowerCase().includes(needle)
|
||||
);
|
||||
}
|
||||
|
||||
get summaryAccruedTotal(): number {
|
||||
return this.filteredSummary.reduce((s, r) => s + Number(r.interest_this_cycle ?? 0), 0);
|
||||
}
|
||||
|
||||
get summaryPostedTotal(): number {
|
||||
return this.filteredSummary.reduce((s, r) => s + Number(r.interest_posted_total ?? 0), 0);
|
||||
}
|
||||
|
||||
// -------------------- actions --------------------
|
||||
|
||||
/**
|
||||
@@ -160,7 +235,11 @@ export class SavingsDailyInterestComponent implements OnInit {
|
||||
if (!value || value.length < 3) {
|
||||
return '—';
|
||||
}
|
||||
const [year, month, day] = value;
|
||||
const [
|
||||
year,
|
||||
month,
|
||||
day
|
||||
] = value;
|
||||
return `${String(day).padStart(2, '0')}/${String(month).padStart(2, '0')}/${year}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,85 @@ export interface SavingsInterestAccount {
|
||||
below_min_balance: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* One row per savings account: where that account stands right now.
|
||||
*
|
||||
* The ledger behind this holds a row per account per day, because interest is worked out on the
|
||||
* balance each day carried and a reversed backdated entry means every day after it is recomputed.
|
||||
* This is the current position read off the top of that ledger — today's figures, the cycle the
|
||||
* account is in, and what it has been paid so far — so a screen can show one line per account
|
||||
* without reading a month of rows to find it.
|
||||
*/
|
||||
export interface SavingsInterestSummaryRow {
|
||||
savings_account_id: number;
|
||||
account_no: string;
|
||||
client_id: number | null;
|
||||
client_name: string | null;
|
||||
office_name: string | null;
|
||||
savings_product_id: number;
|
||||
product_name: string;
|
||||
currency_code: string;
|
||||
|
||||
/** The latest day the ledger has accrued for. */
|
||||
as_on_date: number[];
|
||||
opening_balance: number;
|
||||
closing_balance: number;
|
||||
interest_today: number;
|
||||
|
||||
cycle_number: number;
|
||||
cycle_start_date: number[];
|
||||
cycle_end_date: number[];
|
||||
/** Accrued since the cycle began; resets to the day's interest after a posting. */
|
||||
interest_this_cycle: number;
|
||||
days_to_posting: number;
|
||||
is_posting_day: boolean;
|
||||
posting_date: number[] | null;
|
||||
|
||||
interest_rate: number;
|
||||
days_in_year: number;
|
||||
compounding_period_enum: number | null;
|
||||
posting_period_enum: number | null;
|
||||
calculation_type_enum: number | null;
|
||||
min_balance_for_interest: number | null;
|
||||
below_min_balance: boolean;
|
||||
|
||||
/** Everything ever posted to the account from this ledger. */
|
||||
interest_posted_total: number;
|
||||
last_posted_on: number[] | null;
|
||||
/** How many days the ledger holds for the account. */
|
||||
days_accrued: number;
|
||||
last_recomputed_on: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* One interest posting: what a member was actually paid, and on what.
|
||||
*
|
||||
* Read from the savings transaction rather than the ledger — the ledger clears its posted amount
|
||||
* when the next cycle opens, so it cannot answer what was paid last quarter. The transaction is
|
||||
* the payment and stays.
|
||||
*/
|
||||
export interface SavingsInterestPostingRow {
|
||||
transaction_id: number;
|
||||
savings_account_id: number;
|
||||
account_no: string;
|
||||
client_id: number | null;
|
||||
client_name: string | null;
|
||||
office_name: string | null;
|
||||
savings_product_id: number;
|
||||
product_name: string;
|
||||
/** The product's own rhythm: 1 daily, 4 monthly, 5 quarterly, 6 half-yearly, 7 annual. */
|
||||
posting_period_enum: number;
|
||||
interest_rate: number;
|
||||
currency_code: string;
|
||||
/** ISO yyyy-MM-dd — formatted server-side, because this endpoint serialises with a plain Gson. */
|
||||
posting_date: string;
|
||||
interest_amount: number;
|
||||
/** Balance the interest was credited onto, and what it became. */
|
||||
balance_before: number;
|
||||
balance_after: number;
|
||||
recorded_on: string | null;
|
||||
}
|
||||
|
||||
/** One day of one account: the working behind that day's interest. */
|
||||
export interface SavingsInterestDay {
|
||||
balance_date: number[];
|
||||
@@ -100,6 +179,61 @@ export class SavingsDailyInterestService {
|
||||
return this.http.get<SavingsInterestAccount[]>(`${this.base}/products/${productId}/accounts`);
|
||||
}
|
||||
|
||||
/**
|
||||
* One row per account, across every product — the whole book's current interest position.
|
||||
*
|
||||
* Narrow it with any of the filters; all three are optional and combine.
|
||||
*/
|
||||
getSummary(filters?: {
|
||||
productId?: number;
|
||||
officeName?: string;
|
||||
accountNo?: string;
|
||||
}): Observable<SavingsInterestSummaryRow[]> {
|
||||
let params = new HttpParams();
|
||||
if (filters?.productId) {
|
||||
params = params.set('productId', filters.productId);
|
||||
}
|
||||
if (filters?.officeName) {
|
||||
params = params.set('officeName', filters.officeName);
|
||||
}
|
||||
if (filters?.accountNo) {
|
||||
params = params.set('accountNo', filters.accountNo);
|
||||
}
|
||||
return this.http.get<SavingsInterestSummaryRow[]>(`${this.base}/summary`, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* Interest actually paid to members, one row per posting.
|
||||
*
|
||||
* `period` narrows to one posting rhythm (1 daily, 4 monthly, 5 quarterly, 6 half-yearly,
|
||||
* 7 annual), matching the product's own posting period.
|
||||
*/
|
||||
getPostings(filters?: {
|
||||
period?: number;
|
||||
productId?: number;
|
||||
fromDate?: string;
|
||||
toDate?: string;
|
||||
officeName?: string;
|
||||
}): Observable<SavingsInterestPostingRow[]> {
|
||||
let params = new HttpParams();
|
||||
if (filters?.period) {
|
||||
params = params.set('period', filters.period);
|
||||
}
|
||||
if (filters?.productId) {
|
||||
params = params.set('productId', filters.productId);
|
||||
}
|
||||
if (filters?.fromDate) {
|
||||
params = params.set('fromDate', filters.fromDate);
|
||||
}
|
||||
if (filters?.toDate) {
|
||||
params = params.set('toDate', filters.toDate);
|
||||
}
|
||||
if (filters?.officeName) {
|
||||
params = params.set('officeName', filters.officeName);
|
||||
}
|
||||
return this.http.get<SavingsInterestPostingRow[]>(`${this.base}/postings`, { params });
|
||||
}
|
||||
|
||||
getAccountLedger(accountId: number, limit = 400): Observable<SavingsInterestDay[]> {
|
||||
const params = new HttpParams().set('limit', limit);
|
||||
return this.http.get<SavingsInterestDay[]>(`${this.base}/accounts/${accountId}`, { params });
|
||||
|
||||
Reference in New Issue
Block a user