feat: cash-only registers, day book sections and DD amendments
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
/** rxjs Imports */
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { catchError, map, shareReplay } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* Charges that are mapped to an account facility (ATM, UPI, Cheque Book, ...) are priced by the
|
||||
* Facility Charge Mapping screen and levied automatically when that facility is enabled. Offering
|
||||
* them again in a product/account charge picker would attach the same charge a second time and
|
||||
* double-charge the customer, so they are filtered out of those pickers.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FacilityChargeFilterService {
|
||||
private http = inject(HttpClient);
|
||||
|
||||
/** Facility mappings rarely change within a session, so the lookup is fetched once and shared. */
|
||||
private mappedChargeIds$: Observable<Set<number>>;
|
||||
|
||||
/**
|
||||
* Ids of every charge currently mapped to a facility.
|
||||
* Falls back to an empty set if the lookup fails, so a charge picker still renders.
|
||||
*/
|
||||
getFacilityMappedChargeIds(): Observable<Set<number>> {
|
||||
if (!this.mappedChargeIds$) {
|
||||
this.mappedChargeIds$ = this.http.get('/facilitychargemappings').pipe(
|
||||
map(
|
||||
(mappings: any) =>
|
||||
new Set<number>(
|
||||
((mappings as any[]) || [])
|
||||
.map((mapping) => Number(mapping?.chargeId))
|
||||
.filter((chargeId) => Number.isFinite(chargeId))
|
||||
)
|
||||
),
|
||||
catchError(() => of(new Set<number>())),
|
||||
shareReplay({ bufferSize: 1, refCount: false })
|
||||
);
|
||||
}
|
||||
return this.mappedChargeIds$;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes facility-mapped charges from a charge option list.
|
||||
* @param charges Charge options straight off a product/account template.
|
||||
* @returns The same list without any charge priced by a facility mapping.
|
||||
*/
|
||||
excludeFacilityCharges(charges: any[]): Observable<any[]> {
|
||||
return this.getFacilityMappedChargeIds().pipe(
|
||||
map((mappedChargeIds) =>
|
||||
(charges || []).filter((charge) => !mappedChargeIds.has(Number(charge?.id ?? charge?.chargeId)))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,9 @@
|
||||
<div class="dap__header">
|
||||
<div class="dap__header-left">
|
||||
<div class="dap__icon-wrap">
|
||||
<i [class]="isProductMode ? 'fas fa-shield-alt' : 'fas fa-exclamation-triangle'"></i>
|
||||
<!-- A healthy account gets the shield, not the warning triangle: the panel is showing it
|
||||
its thresholds and the benefit exemption, not raising an alarm. -->
|
||||
<i [class]="isProductMode || isHealthy ? 'fas fa-shield-alt' : 'fas fa-exclamation-triangle'"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="dap__title">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
export type DormancyRisk = 'safe' | 'low' | 'medium' | 'high' | 'critical' | 'escheat';
|
||||
@@ -17,7 +17,7 @@ export type DormancyRisk = 'safe' | 'low' | 'medium' | 'high' | 'critical' | 'es
|
||||
styleUrls: ['./dormancy-alert-panel.component.scss'],
|
||||
imports: [...STANDALONE_SHARED_IMPORTS]
|
||||
})
|
||||
export class DormancyAlertPanelComponent implements OnInit {
|
||||
export class DormancyAlertPanelComponent implements OnInit, OnChanges {
|
||||
/** Pass full account data — for ACCOUNT VIEW (general tab) */
|
||||
@Input() accountData: any;
|
||||
/** Pass product template — for ACCOUNT CREATION (currency step) */
|
||||
@@ -48,13 +48,40 @@ export class DormancyAlertPanelComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* The stepper reuses one panel instance while the user switches products, so the thresholds have
|
||||
* to be recomputed on every input change — without this the panel keeps showing the previously
|
||||
* selected product's days.
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['productTemplate'] || changes['accountData']) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private refresh(): void {
|
||||
if (this.productTemplate && !this.accountData) {
|
||||
this.initFromProductTemplate();
|
||||
} else if (this.accountData) {
|
||||
this.initFromAccountData();
|
||||
} else {
|
||||
this.resetThresholds();
|
||||
}
|
||||
}
|
||||
|
||||
/** Clears stale thresholds when neither a product nor an account is bound. */
|
||||
private resetThresholds(): void {
|
||||
this.daysToInactive = null;
|
||||
this.daysToDormancy = null;
|
||||
this.daysToEscheat = null;
|
||||
this.isMonitoringActive = false;
|
||||
this.isAlertEnabled = false;
|
||||
this.shouldShow = false;
|
||||
}
|
||||
|
||||
// ── Product Template Mode (Currency Step) ─────────────────────
|
||||
private initFromProductTemplate(): void {
|
||||
const pt = this.productTemplate;
|
||||
@@ -113,10 +140,15 @@ export class DormancyAlertPanelComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.computeRiskFromAccount();
|
||||
// Only surface this panel once the account has actually left "Active" —
|
||||
// i.e. subStatus is Inactive/Dormant/Escheat. A healthy active account
|
||||
// (statusId === 0) should never show a danger-colored dormancy alert.
|
||||
this.shouldShow = this.statusId > 0;
|
||||
// Shown wherever dormancy monitoring applies, so a branch can read the thresholds and how far
|
||||
// the account is from each of them. The template drops the risk timeline while the account is
|
||||
// still healthy, where there is no risk to show.
|
||||
this.shouldShow = this.statusId > 0 || this.isMonitoringActive;
|
||||
}
|
||||
|
||||
/** True when the account has not left "Active", so there is no risk timeline worth showing. */
|
||||
get isHealthy(): boolean {
|
||||
return this.statusId === 0;
|
||||
}
|
||||
|
||||
private computeRiskFromAccount(): void {
|
||||
|
||||
Reference in New Issue
Block a user