Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fa6c2d814 | ||
|
|
227949f8b2 | ||
|
|
abc966d507 | ||
|
|
fa2522e678 | ||
|
|
1b2ef5126e | ||
|
|
b953afe2da | ||
|
|
ff781e50e3 |
+1
-1
@@ -119,7 +119,7 @@
|
|||||||
<button
|
<button
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
color="primary"
|
color="primary"
|
||||||
[disabled]="!glAccountForm.valid"
|
[disabled]="!glAccountForm.valid || submitting"
|
||||||
*mifosxHasPermission="'CREATE_GLACCOUNT'"
|
*mifosxHasPermission="'CREATE_GLACCOUNT'"
|
||||||
>
|
>
|
||||||
{{ 'labels.buttons.Submit' | translate }}
|
{{ 'labels.buttons.Submit' | translate }}
|
||||||
|
|||||||
+34
-14
@@ -14,6 +14,7 @@ import { MatDialog } from '@angular/material/dialog';
|
|||||||
|
|
||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
import { AccountingService } from '../../accounting.service';
|
import { AccountingService } from '../../accounting.service';
|
||||||
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
import { PopoverService } from '../../../configuration-wizard/popover/popover.service';
|
import { PopoverService } from '../../../configuration-wizard/popover/popover.service';
|
||||||
import { ConfigurationWizardService } from '../../../configuration-wizard/configuration-wizard.service';
|
import { ConfigurationWizardService } from '../../../configuration-wizard/configuration-wizard.service';
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
export class CreateGlAccountComponent implements OnInit, AfterViewInit {
|
export class CreateGlAccountComponent implements OnInit, AfterViewInit {
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
private accountingService = inject(AccountingService);
|
private accountingService = inject(AccountingService);
|
||||||
|
private alertService = inject(AlertService);
|
||||||
private route = inject(ActivatedRoute);
|
private route = inject(ActivatedRoute);
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private configurationWizardService = inject(ConfigurationWizardService);
|
private configurationWizardService = inject(ConfigurationWizardService);
|
||||||
@@ -50,6 +52,8 @@ export class CreateGlAccountComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
/** GL account form. */
|
/** GL account form. */
|
||||||
glAccountForm: UntypedFormGroup;
|
glAccountForm: UntypedFormGroup;
|
||||||
|
/** Submitting state to prevent duplicate submissions. */
|
||||||
|
submitting = false;
|
||||||
/** Chart of accounts data. */
|
/** Chart of accounts data. */
|
||||||
chartOfAccountsData: any;
|
chartOfAccountsData: any;
|
||||||
/** Account type data. */
|
/** Account type data. */
|
||||||
@@ -166,24 +170,40 @@ export class CreateGlAccountComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits the gl account form and creates gl account,
|
* Submits the gl account form and creates gl account,
|
||||||
* if successful redirects to view created account.
|
* if successful redirects to view created account or back to list if awaiting approval.
|
||||||
*/
|
*/
|
||||||
submit() {
|
submit() {
|
||||||
if (this.glAccountForm.invalid) {
|
if (this.glAccountForm.invalid || this.submitting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.accountingService.createGlAccount(this.glAccountForm.value).subscribe((response: any) => {
|
this.submitting = true;
|
||||||
if (this.configurationWizardService.showChartofAccounts) {
|
this.accountingService.createGlAccount(this.glAccountForm.value).subscribe({
|
||||||
this.configurationWizardService.showChartofAccounts = false;
|
next: (response: any) => {
|
||||||
this.openDialog();
|
if (response?.resourceId) {
|
||||||
} else {
|
if (this.configurationWizardService.showChartofAccounts) {
|
||||||
this.router.navigate(
|
this.configurationWizardService.showChartofAccounts = false;
|
||||||
[
|
this.openDialog();
|
||||||
'../view',
|
} else {
|
||||||
response.resourceId
|
this.router.navigate(
|
||||||
],
|
[
|
||||||
{ relativeTo: this.route }
|
'../view',
|
||||||
);
|
response.resourceId
|
||||||
|
],
|
||||||
|
{ relativeTo: this.route }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (response?.rollbackTransaction) {
|
||||||
|
this.alertService.alert({
|
||||||
|
type: 'Success',
|
||||||
|
message: 'GL Account submitted successfully for approval.'
|
||||||
|
});
|
||||||
|
this.router.navigate([this.cancelRoute], { relativeTo: this.route });
|
||||||
|
} else {
|
||||||
|
this.router.navigate([this.cancelRoute], { relativeTo: this.route });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.submitting = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,6 @@ import { Routes, RouterModule } from '@angular/router';
|
|||||||
|
|
||||||
import { Route } from '../core/route/route.service';
|
import { Route } from '../core/route/route.service';
|
||||||
import { AccountsListComponent } from './accounts-list/accounts-list.component';
|
import { AccountsListComponent } from './accounts-list/accounts-list.component';
|
||||||
import { PartyMasterComponent } from '../deposits/party-master/party-master.component';
|
|
||||||
import { DailyTransactionEntryComponent } from '../deposits/daily-transaction-entry/daily-transaction-entry.component';
|
|
||||||
import { DdsReportsComponent } from '../deposits/dds-reports/dds-reports.component';
|
|
||||||
import { ReportViewerComponent } from '../deposits/dds-reports/report-viewer/report-viewer.component';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
Route.withShell([
|
Route.withShell([
|
||||||
@@ -45,28 +41,6 @@ const routes: Routes = [
|
|||||||
title: 'Recurring Deposit Accounts',
|
title: 'Recurring Deposit Accounts',
|
||||||
breadcrumb: 'Recurring Deposit Accounts'
|
breadcrumb: 'Recurring Deposit Accounts'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'dds',
|
|
||||||
component: PartyMasterComponent,
|
|
||||||
data: { title: 'Daily Deposit Scheme — Party Master', breadcrumb: 'Party Master' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'dds-collection',
|
|
||||||
component: DailyTransactionEntryComponent,
|
|
||||||
data: { title: 'Daily Transaction Entry', breadcrumb: 'Daily Transaction Entry' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'dds-reports',
|
|
||||||
data: { title: 'DDS Reports', breadcrumb: 'DDS Reports' },
|
|
||||||
children: [
|
|
||||||
{ path: '', component: DdsReportsComponent },
|
|
||||||
{
|
|
||||||
path: 'view/:reportName',
|
|
||||||
component: ReportViewerComponent,
|
|
||||||
data: { title: 'DDS Report', breadcrumb: 'reportName', routeParamBreadcrumb: 'reportName' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,7 +263,6 @@
|
|||||||
<mat-form-field appearance="outline" class="name-field last-name">
|
<mat-form-field appearance="outline" class="name-field last-name">
|
||||||
<mat-label>{{ 'labels.inputs.Last Name' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Last Name' | translate }}</mat-label>
|
||||||
<span matPrefix class="field-prefix-icon"><mat-icon>person</mat-icon></span>
|
<span matPrefix class="field-prefix-icon"><mat-icon>person</mat-icon></span>
|
||||||
<<<<<<< Updated upstream
|
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
required
|
required
|
||||||
@@ -272,9 +271,6 @@
|
|||||||
(blur)="capitalizeControl('lastname')"
|
(blur)="capitalizeControl('lastname')"
|
||||||
style="text-transform: capitalize"
|
style="text-transform: capitalize"
|
||||||
/>
|
/>
|
||||||
=======
|
|
||||||
<input matInput required formControlName="lastname" autocomplete="off" (input)="formatName('lastname')" />
|
|
||||||
>>>>>>> Stashed changes
|
|
||||||
@if (createClientForm.controls.lastname.hasError('required')) {
|
@if (createClientForm.controls.lastname.hasError('required')) {
|
||||||
<mat-error>Last name is <strong>required</strong></mat-error>
|
<mat-error>Last name is <strong>required</strong></mat-error>
|
||||||
} @else if (createClientForm.controls.lastname.hasError('pattern')) {
|
} @else if (createClientForm.controls.lastname.hasError('pattern')) {
|
||||||
|
|||||||
@@ -2590,28 +2590,6 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
dormantFlag: 'ACTIVE'
|
dormantFlag: 'ACTIVE'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically capitalizes the first letter of each word and lowercases the rest.
|
|
||||||
* e.g., "sri rAm" -> "Sri Ram"
|
|
||||||
*/
|
|
||||||
formatName(controlName: string): void {
|
|
||||||
const control = this.createClientForm.get(controlName);
|
|
||||||
if (!control || typeof control.value !== 'string') return;
|
|
||||||
|
|
||||||
const val = control.value;
|
|
||||||
const formatted = val
|
|
||||||
.split(' ')
|
|
||||||
.map((word: string) => {
|
|
||||||
if (!word) return '';
|
|
||||||
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
||||||
})
|
|
||||||
.join(' ');
|
|
||||||
|
|
||||||
if (formatted !== val) {
|
|
||||||
control.setValue(formatted, { emitEvent: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { Observable, of, throwError } from 'rxjs';
|
|||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { map, catchError } from 'rxjs/operators';
|
import { map, catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
import { environment } from 'environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
import { dataUrlToFile } from 'app/shared/utils/data-url.util';
|
import { dataUrlToFile } from 'app/shared/utils/data-url.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { UntypedFormGroup } from '@angular/forms';
|
|||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { debounceTime, distinctUntilChanged, filter, switchMap, takeUntil, timeout, catchError } from 'rxjs/operators';
|
import { debounceTime, distinctUntilChanged, filter, switchMap, takeUntil, timeout, catchError } from 'rxjs/operators';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { environment } from 'environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { ClientsService } from '../clients.service';
|
import { ClientsService } from '../clients.service';
|
||||||
import {
|
import {
|
||||||
EXTERNAL_ID_STATUS_KEYS,
|
EXTERNAL_ID_STATUS_KEYS,
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
/**
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable, inject, OnDestroy } from '@angular/core';
|
||||||
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
|
import { fromEvent, Subscription, BehaviorSubject } from 'rxjs';
|
||||||
|
import { KeyboardShortcutsConfiguration } from '../../keyboards-shortcut-config';
|
||||||
|
import { AuthenticationService } from '../authentication/authentication.service';
|
||||||
|
import { UserPreferenceService } from './user-preference.service';
|
||||||
|
import { DocumentationLinksService } from '../../shared/services/documentation-links.service';
|
||||||
|
|
||||||
|
export interface ShortcutItem {
|
||||||
|
title: string;
|
||||||
|
ctrlKey: boolean;
|
||||||
|
shiftKey: boolean;
|
||||||
|
altKey: boolean;
|
||||||
|
key: string;
|
||||||
|
route: string;
|
||||||
|
id?: string;
|
||||||
|
custom?: boolean;
|
||||||
|
action?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class KeyboardShortcutService implements OnDestroy {
|
||||||
|
private router = inject(Router);
|
||||||
|
private activatedRoute = inject(ActivatedRoute);
|
||||||
|
private authenticationService = inject(AuthenticationService);
|
||||||
|
private userPreferenceService = inject(UserPreferenceService);
|
||||||
|
private documentationLinks = inject(DocumentationLinksService);
|
||||||
|
|
||||||
|
private keydownSubscription: Subscription;
|
||||||
|
private activeShortcutsSubject = new BehaviorSubject<ShortcutItem[]>([]);
|
||||||
|
public activeShortcuts$ = this.activeShortcutsSubject.asObservable();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.keydownSubscription = fromEvent<KeyboardEvent>(window, 'keydown').subscribe((event) => {
|
||||||
|
this.handleKeydown(event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (this.keydownSubscription) {
|
||||||
|
this.keydownSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public initShortcuts(): void {
|
||||||
|
const credentials = this.authenticationService.getCredentials();
|
||||||
|
if (!credentials || !credentials.userId) {
|
||||||
|
this.loadDefaults();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.userPreferenceService.getPreference(credentials.userId, 'keyboard_shortcuts').subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
if (data && data.value) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(data.value);
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
parsed.forEach((shortcut: ShortcutItem) => {
|
||||||
|
if (shortcut.custom && shortcut.action === 'navigate' && shortcut.route) {
|
||||||
|
shortcut.route = this.normalizeRouteStr(shortcut.route);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.activeShortcutsSubject.next(parsed);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing shortcuts', e);
|
||||||
|
this.loadDefaults();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.loadDefaults();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.loadDefaults();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public loadDefaults(): void {
|
||||||
|
const config = new KeyboardShortcutsConfiguration();
|
||||||
|
this.activeShortcutsSubject.next(config.buttonsArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateShortcuts(shortcuts: ShortcutItem[]): void {
|
||||||
|
const credentials = this.authenticationService.getCredentials();
|
||||||
|
if (credentials && credentials.userId) {
|
||||||
|
const cleanedShortcuts = shortcuts.map((s) => {
|
||||||
|
const clean: any = {
|
||||||
|
title: s.title,
|
||||||
|
ctrlKey: s.ctrlKey,
|
||||||
|
shiftKey: s.shiftKey,
|
||||||
|
altKey: s.altKey,
|
||||||
|
key: s.key,
|
||||||
|
route: s.route
|
||||||
|
};
|
||||||
|
if (s.id !== undefined) clean.id = s.id;
|
||||||
|
if (s.custom !== undefined) clean.custom = s.custom;
|
||||||
|
if (s.action !== undefined) clean.action = s.action;
|
||||||
|
return clean;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.userPreferenceService
|
||||||
|
.updatePreference(credentials.userId, 'keyboard_shortcuts', JSON.stringify(cleanedShortcuts))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.activeShortcutsSubject.next(shortcuts);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.activeShortcutsSubject.next(shortcuts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetToDefaults(): void {
|
||||||
|
const credentials = this.authenticationService.getCredentials();
|
||||||
|
const config = new KeyboardShortcutsConfiguration();
|
||||||
|
if (credentials && credentials.userId) {
|
||||||
|
this.userPreferenceService
|
||||||
|
.updatePreference(credentials.userId, 'keyboard_shortcuts', JSON.stringify(config.buttonsArray))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.activeShortcutsSubject.next(config.buttonsArray);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.activeShortcutsSubject.next(config.buttonsArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleKeydown(event: KeyboardEvent): void {
|
||||||
|
const activeShortcuts = this.activeShortcutsSubject.getValue();
|
||||||
|
const routeD = activeShortcuts.find(
|
||||||
|
(x) =>
|
||||||
|
x.ctrlKey === event.ctrlKey &&
|
||||||
|
x.shiftKey === event.shiftKey &&
|
||||||
|
x.altKey === event.altKey &&
|
||||||
|
x.key.toLowerCase() === event.key.toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (routeD !== undefined) {
|
||||||
|
event.preventDefault(); // Prevent default browser actions for matched shortcuts
|
||||||
|
|
||||||
|
if (routeD.custom) {
|
||||||
|
if (routeD.action === 'navigate' && routeD.route) {
|
||||||
|
this.router.navigate([routeD.route]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (routeD.id) {
|
||||||
|
case 'logout':
|
||||||
|
this.authenticationService.logout().subscribe(() => this.router.navigate(['/login'], { replaceUrl: true }));
|
||||||
|
break;
|
||||||
|
case 'help':
|
||||||
|
this.documentationLinks.open('userManual');
|
||||||
|
break;
|
||||||
|
case 'runReport':
|
||||||
|
const runReportElement = document.getElementById('runReport');
|
||||||
|
if (runReportElement) runReportElement.click();
|
||||||
|
break;
|
||||||
|
case 'cancel':
|
||||||
|
const cancelEl = document.querySelector('[data-shortcut-action="cancel"]') as HTMLElement;
|
||||||
|
if (cancelEl) {
|
||||||
|
cancelEl.click();
|
||||||
|
} else {
|
||||||
|
const cancelButtons = Array.from(document.querySelectorAll('button')).filter(
|
||||||
|
(el) => el.textContent && el.textContent.trim() === 'Cancel'
|
||||||
|
);
|
||||||
|
if (cancelButtons.length > 0) cancelButtons[0].click();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'submit':
|
||||||
|
const submitEl = document.querySelector('[data-shortcut-action="submit"]') as HTMLElement;
|
||||||
|
if (submitEl) {
|
||||||
|
submitEl.click();
|
||||||
|
} else {
|
||||||
|
const submitButtons = Array.from(document.querySelectorAll('button')).filter(
|
||||||
|
(el) => el.textContent && el.textContent.trim() === 'Submit'
|
||||||
|
);
|
||||||
|
if (submitButtons.length > 0) submitButtons[0].click();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.router.navigate([routeD.route], { relativeTo: this.activatedRoute });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public normalizeRouteStr(route: string | undefined | null): string {
|
||||||
|
if (!route) return '';
|
||||||
|
let r = route.trim();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (r.startsWith('http://') || r.startsWith('https://')) {
|
||||||
|
const url = new URL(r);
|
||||||
|
if (url.hash && url.hash.startsWith('#')) {
|
||||||
|
let path = url.hash.substring(1);
|
||||||
|
if (!path.startsWith('/')) {
|
||||||
|
path = '/' + path;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return url.pathname + url.search;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.includes('#/')) {
|
||||||
|
r = r.substring(r.indexOf('#/') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^[a-zA-Z0-9]+:/.test(r)) {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!r.startsWith('/')) {
|
||||||
|
r = '/' + r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/**
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable, inject, OnDestroy } from '@angular/core';
|
||||||
|
import { Router, NavigationEnd } from '@angular/router';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { AuthenticationService } from '../authentication/authentication.service';
|
||||||
|
import { KeyboardShortcutService } from './keyboard-shortcut.service';
|
||||||
|
import { UserPreferenceService } from './user-preference.service';
|
||||||
|
import { KeyboardShortcutsDialogComponent } from '../../shared/keyboard-shortcuts-dialog/keyboard-shortcuts-dialog.component';
|
||||||
|
import { filter, take } from 'rxjs/operators';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ShortcutSuggestionService implements OnDestroy {
|
||||||
|
private router = inject(Router);
|
||||||
|
private titleService = inject(Title);
|
||||||
|
private snackBar = inject(MatSnackBar);
|
||||||
|
private dialog = inject(MatDialog);
|
||||||
|
private authenticationService = inject(AuthenticationService);
|
||||||
|
private keyboardShortcutService = inject(KeyboardShortcutService);
|
||||||
|
private userPreferenceService = inject(UserPreferenceService);
|
||||||
|
|
||||||
|
private readonly FREQUENCY_THRESHOLD = 5;
|
||||||
|
private readonly PREF_KEY = 'shortcut_suggestions_state';
|
||||||
|
private routerSubscription!: Subscription;
|
||||||
|
|
||||||
|
private state: { dismissed: string[] } = { dismissed: [] };
|
||||||
|
|
||||||
|
private get userId(): number | null {
|
||||||
|
const creds = this.authenticationService.getCredentials();
|
||||||
|
return creds ? creds.userId : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getVisitsKey(): string {
|
||||||
|
return `route_visits_${this.userId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getVisits(): Record<string, number> {
|
||||||
|
const key = this.getVisitsKey();
|
||||||
|
if (!key) return {};
|
||||||
|
const val = localStorage.getItem(key);
|
||||||
|
return val ? JSON.parse(val) : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveVisits(visits: Record<string, number>) {
|
||||||
|
const key = this.getVisitsKey();
|
||||||
|
if (key) {
|
||||||
|
localStorage.setItem(key, JSON.stringify(visits));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isSuggestionOpen = false;
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
const uid = this.userId;
|
||||||
|
if (uid) {
|
||||||
|
this.userPreferenceService.getPreference(uid, this.PREF_KEY).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
if (data && data.value) {
|
||||||
|
try {
|
||||||
|
this.state = JSON.parse(data.value);
|
||||||
|
if (!this.state.dismissed) this.state.dismissed = [];
|
||||||
|
} catch (e) {
|
||||||
|
this.state = { dismissed: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.routerSubscription = this.router.events
|
||||||
|
.pipe(filter((event) => event instanceof NavigationEnd))
|
||||||
|
.subscribe((event: any) => {
|
||||||
|
this.handleNavigation(event.urlAfterRedirects);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleNavigation(url: string) {
|
||||||
|
if (!this.userId) return;
|
||||||
|
|
||||||
|
let path = url.split('?')[0];
|
||||||
|
path = this.keyboardShortcutService.normalizeRouteStr(path);
|
||||||
|
|
||||||
|
const visits = this.getVisits();
|
||||||
|
visits[path] = (visits[path] || 0) + 1;
|
||||||
|
this.saveVisits(visits);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUsageData(): { route: string; pageName: string; visits: number; hasShortcut: boolean }[] {
|
||||||
|
const visits = this.getVisits();
|
||||||
|
const result: { route: string; pageName: string; visits: number; hasShortcut: boolean }[] = [];
|
||||||
|
|
||||||
|
// Get current shortcuts (sync)
|
||||||
|
let currentShortcuts: any[] = [];
|
||||||
|
this.keyboardShortcutService.activeShortcuts$.pipe(take(1)).subscribe((s) => (currentShortcuts = s));
|
||||||
|
|
||||||
|
for (const route of Object.keys(visits)) {
|
||||||
|
const count = visits[route];
|
||||||
|
const hasShortcut = currentShortcuts.some(
|
||||||
|
(s) =>
|
||||||
|
(s.custom && s.action === 'navigate' && this.keyboardShortcutService.normalizeRouteStr(s.route) === route) ||
|
||||||
|
(!s.custom && this.keyboardShortcutService.normalizeRouteStr(s.route) === route)
|
||||||
|
);
|
||||||
|
|
||||||
|
let pageName = route.split('/').pop() || route;
|
||||||
|
// Capitalize and clean
|
||||||
|
pageName = pageName.replace(/-/g, ' ');
|
||||||
|
pageName = pageName.charAt(0).toUpperCase() + pageName.slice(1);
|
||||||
|
|
||||||
|
result.push({ route, pageName, visits: count, hasShortcut });
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.sort((a, b) => b.visits - a.visits);
|
||||||
|
}
|
||||||
|
|
||||||
|
public markAsDismissed(route: string) {
|
||||||
|
if (!this.state.dismissed.includes(route)) {
|
||||||
|
this.state.dismissed.push(route);
|
||||||
|
const uid = this.userId;
|
||||||
|
if (uid) {
|
||||||
|
this.userPreferenceService.updatePreference(uid, this.PREF_KEY, JSON.stringify(this.state)).subscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public openCreateShortcutDialog(pageName: string, route: string) {
|
||||||
|
this.dialog
|
||||||
|
.open(KeyboardShortcutsDialogComponent, {
|
||||||
|
data: {
|
||||||
|
prefill: {
|
||||||
|
title: pageName,
|
||||||
|
route: route,
|
||||||
|
action: 'navigate'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.afterClosed()
|
||||||
|
.subscribe((saved) => {
|
||||||
|
if (saved) {
|
||||||
|
this.markAsDismissed(route);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.routerSubscription) {
|
||||||
|
this.routerSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable, inject } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class UserPreferenceService {
|
||||||
|
private http = inject(HttpClient);
|
||||||
|
|
||||||
|
getPreference(userId: number, preferenceName: string): Observable<{ name: string; value: string }> {
|
||||||
|
const timestamp = new Date().getTime();
|
||||||
|
return this.http.get<{ name: string; value: string }>(
|
||||||
|
`/users/${userId}/preferences/${preferenceName}?_t=${timestamp}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePreference(userId: number, preferenceName: string, value: string): Observable<{ name: string; value: string }> {
|
||||||
|
return this.http.put<{ name: string; value: string }>(`/users/${userId}/preferences/${preferenceName}`, { value });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1048,16 +1048,14 @@
|
|||||||
<button mat-menu-item tabindex="0" [routerLink]="['/accounts/recurring-deposits']">
|
<button mat-menu-item tabindex="0" [routerLink]="['/accounts/recurring-deposits']">
|
||||||
<fa-icon icon="sync" class="menu-item-icon"></fa-icon> Recurring
|
<fa-icon icon="sync" class="menu-item-icon"></fa-icon> Recurring
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item tabindex="0" [routerLink]="['/accounts/dds']">
|
<button mat-menu-item tabindex="0"><fa-icon icon="coins" class="menu-item-icon"></fa-icon> DDS</button>
|
||||||
<fa-icon icon="coins" class="menu-item-icon"></fa-icon> DDS
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="deposit-menu-group">
|
<div class="deposit-menu-group">
|
||||||
<div class="deposit-group-title">Agent</div>
|
<div class="deposit-group-title">Agent</div>
|
||||||
<button mat-menu-item tabindex="0">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="hand-holding-usd" class="menu-item-icon"></fa-icon> Agent Commission
|
<fa-icon icon="hand-holding-usd" class="menu-item-icon"></fa-icon> Agent Commission
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item tabindex="0" [routerLink]="['/accounts/dds-collection']">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="calendar-check" class="menu-item-icon"></fa-icon> Today Collection
|
<fa-icon icon="calendar-check" class="menu-item-icon"></fa-icon> Today Collection
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item tabindex="0">
|
<button mat-menu-item tabindex="0">
|
||||||
@@ -1072,9 +1070,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="deposit-menu-group">
|
<div class="deposit-menu-group">
|
||||||
<div class="deposit-group-title">Reports</div>
|
<div class="deposit-group-title">Reports</div>
|
||||||
<button mat-menu-item tabindex="0" [routerLink]="['/accounts/dds-reports']">
|
|
||||||
<fa-icon icon="coins" class="menu-item-icon"></fa-icon> DDS Reports
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item tabindex="0">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="clock" class="menu-item-icon"></fa-icon> Account Expiry
|
<fa-icon icon="clock" class="menu-item-icon"></fa-icon> Account Expiry
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+2
-31
@@ -12,9 +12,7 @@
|
|||||||
<fa-icon icon="university" class="cca-header__icon"></fa-icon>
|
<fa-icon icon="university" class="cca-header__icon"></fa-icon>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="cca-header__title">Create Current Account</h2>
|
<h2 class="cca-header__title">Create Current Account</h2>
|
||||||
<div class="cca-header__sub">
|
<div class="cca-header__sub">Pick a product, confirm currency and review before submit.</div>
|
||||||
Pick a product, confirm currency, set digital channels and review before submit.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -78,34 +76,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
|
|
||||||
<!-- Tab 3: Digital -->
|
<!-- Tab 3: Preview -->
|
||||||
<mat-tab [disabled]="!currentAccountProductTemplate">
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<fa-icon icon="mobile-alt" class="cca-tab__icon"></fa-icon>
|
|
||||||
Digital
|
|
||||||
</ng-template>
|
|
||||||
<div class="cca-tab-body">
|
|
||||||
@if (currentAccountProductTemplate) {
|
|
||||||
<mifosx-current-account-digital-step
|
|
||||||
[currentAccountProductTemplate]="currentAccountProductTemplate"
|
|
||||||
></mifosx-current-account-digital-step>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="cca-actions">
|
|
||||||
<button mat-stroked-button (click)="prev()">
|
|
||||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
|
||||||
{{ 'labels.buttons.Previous' | translate }}
|
|
||||||
</button>
|
|
||||||
<span class="cca-actions__spacer"></span>
|
|
||||||
<button mat-raised-button color="primary" (click)="next()">
|
|
||||||
{{ 'labels.buttons.Next' | translate }}
|
|
||||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<!-- Tab 4: Preview -->
|
|
||||||
<mat-tab [disabled]="!currentAccountProductTemplate">
|
<mat-tab [disabled]="!currentAccountProductTemplate">
|
||||||
<ng-template mat-tab-label>
|
<ng-template mat-tab-label>
|
||||||
<fa-icon icon="eye" class="cca-tab__icon"></fa-icon>
|
<fa-icon icon="eye" class="cca-tab__icon"></fa-icon>
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ export class CreateCurrentAccountComponent {
|
|||||||
currentAccountProductTemplate: any;
|
currentAccountProductTemplate: any;
|
||||||
/** Currently visible wizard tab (0-indexed). */
|
/** Currently visible wizard tab (0-indexed). */
|
||||||
selectedTabIndex = 0;
|
selectedTabIndex = 0;
|
||||||
/** Total wizard tabs — Product, Currency, Digital, Preview. */
|
/** Total wizard tabs — Product, Currency, Preview. */
|
||||||
private readonly totalTabs = 4;
|
private readonly totalTabs = 3;
|
||||||
|
|
||||||
@ViewChild(CurrentAccountDetailsStepComponent, { static: true })
|
@ViewChild(CurrentAccountDetailsStepComponent, { static: true })
|
||||||
currentAccountDetailsStep: CurrentAccountDetailsStepComponent;
|
currentAccountDetailsStep: CurrentAccountDetailsStepComponent;
|
||||||
|
|||||||
-12
@@ -30,18 +30,6 @@
|
|||||||
</mat-error>
|
</mat-error>
|
||||||
}
|
}
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field class="flex-fill">
|
|
||||||
<mat-label>Opening Balance</mat-label>
|
|
||||||
<input matInput type="number" [min]="minRequiredOpeningBalance ?? 0" formControlName="openingBalance" />
|
|
||||||
<span matPrefix>₹ </span>
|
|
||||||
@if (minRequiredOpeningBalance) {
|
|
||||||
<mat-hint>Minimum required: ₹{{ minRequiredOpeningBalance }}</mat-hint>
|
|
||||||
}
|
|
||||||
@if (activateCurrentAccountForm.controls.openingBalance.hasError('min')) {
|
|
||||||
<mat-error>Opening balance must be at least ₹{{ minRequiredOpeningBalance ?? 0 }}</mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
</div>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
|
|
||||||
|
|||||||
+1
-11
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/** Angular Imports */
|
/** Angular Imports */
|
||||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||||
import { Dates } from 'app/core/utils/dates';
|
import { Dates } from 'app/core/utils/dates';
|
||||||
@@ -36,8 +36,6 @@ export class ActivateCurrentAccountComponent implements OnInit {
|
|||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
@Input() minRequiredOpeningBalance: number | null = null;
|
|
||||||
|
|
||||||
/** Minimum date allowed. */
|
/** Minimum date allowed. */
|
||||||
minDate = new Date(2000, 0, 1);
|
minDate = new Date(2000, 0, 1);
|
||||||
/** Maximum date allowed. */
|
/** Maximum date allowed. */
|
||||||
@@ -71,18 +69,10 @@ export class ActivateCurrentAccountComponent implements OnInit {
|
|||||||
* Creates the activate current account form.
|
* Creates the activate current account form.
|
||||||
*/
|
*/
|
||||||
createActivateCurrentAccountForm() {
|
createActivateCurrentAccountForm() {
|
||||||
const minBalance = this.minRequiredOpeningBalance ?? 0;
|
|
||||||
this.activateCurrentAccountForm = this.formBuilder.group({
|
this.activateCurrentAccountForm = this.formBuilder.group({
|
||||||
activatedOnDate: [
|
activatedOnDate: [
|
||||||
'',
|
'',
|
||||||
Validators.required
|
Validators.required
|
||||||
],
|
|
||||||
openingBalance: [
|
|
||||||
minBalance,
|
|
||||||
[
|
|
||||||
Validators.required,
|
|
||||||
Validators.min(minBalance)
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -10,6 +10,7 @@
|
|||||||
<mat-card>
|
<mat-card>
|
||||||
<form [formGroup]="approveCurrentAccountForm" (ngSubmit)="submit()">
|
<form [formGroup]="approveCurrentAccountForm" (ngSubmit)="submit()">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
|
<p class="mat-body-2">This approves and activates the account in one step -- it goes straight to Active.</p>
|
||||||
<div class="layout-column">
|
<div class="layout-column">
|
||||||
<mat-form-field (click)="approvedOnDatePicker.open()">
|
<mat-form-field (click)="approvedOnDatePicker.open()">
|
||||||
<mat-label>{{ 'labels.inputs.Approved On Date' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Approved On Date' | translate }}</mat-label>
|
||||||
|
|||||||
+13
-3
@@ -81,8 +81,9 @@ export class ApproveCurrentAccountComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits the form and approves the current account,
|
* Submits the form, approves the current account, then immediately activates it on the same
|
||||||
* if successful redirects to the current account.
|
* date -- Approve and Activate are still two separate Fineract commands underneath (each shows
|
||||||
|
* up in the audit trail), but the officer only sees and clicks through one step.
|
||||||
*/
|
*/
|
||||||
submit() {
|
submit() {
|
||||||
const approveCurrentAccountFormData = this.approveCurrentAccountForm.value;
|
const approveCurrentAccountFormData = this.approveCurrentAccountForm.value;
|
||||||
@@ -98,7 +99,16 @@ export class ApproveCurrentAccountComponent implements OnInit {
|
|||||||
locale
|
locale
|
||||||
};
|
};
|
||||||
this.currentAccountsService.executeCurrentAccountCommand(this.accountId, 'approve', data).subscribe(() => {
|
this.currentAccountsService.executeCurrentAccountCommand(this.accountId, 'approve', data).subscribe(() => {
|
||||||
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
const activationData = {
|
||||||
|
activatedOnDate: approveCurrentAccountFormData.approvedOnDate,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.currentAccountsService
|
||||||
|
.executeCurrentAccountCommand(this.accountId, 'activate', activationData)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -13,9 +13,7 @@
|
|||||||
<mifosx-reject-current-account></mifosx-reject-current-account>
|
<mifosx-reject-current-account></mifosx-reject-current-account>
|
||||||
}
|
}
|
||||||
@if (actions['Activate']) {
|
@if (actions['Activate']) {
|
||||||
<mifosx-activate-current-account
|
<mifosx-activate-current-account></mifosx-activate-current-account>
|
||||||
[minRequiredOpeningBalance]="accountData?.minRequiredOpeningBalance"
|
|
||||||
></mifosx-activate-current-account>
|
|
||||||
}
|
}
|
||||||
@if (actions['Undo Approval']) {
|
@if (actions['Undo Approval']) {
|
||||||
<mifosx-undo-approval-current-account></mifosx-undo-approval-current-account>
|
<mifosx-undo-approval-current-account></mifosx-undo-approval-current-account>
|
||||||
|
|||||||
-1
@@ -15,7 +15,6 @@
|
|||||||
<h3 class="dig-header__title">Digital & Account Features</h3>
|
<h3 class="dig-header__title">Digital & Account Features</h3>
|
||||||
<p class="dig-header__sub">Toggle the facilities available for this account (as per RBI guidelines)</p>
|
<p class="dig-header__sub">Toggle the facilities available for this account (as per RBI guidelines)</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="dig-header__badge">FROM PRODUCT</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dig-grid">
|
<div class="dig-grid">
|
||||||
|
|||||||
+4
-5
@@ -52,12 +52,11 @@ export class CurrentAccountDigitalStepComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: a new account always starts with every digital facility off, even if the product has
|
||||||
|
// its own defaults configured -- those are enabled deliberately, one at a time, after the
|
||||||
|
// account is created and active, not inherited automatically at opening.
|
||||||
@Input()
|
@Input()
|
||||||
set currentAccountProductTemplate(value: any) {
|
currentAccountProductTemplate: any;
|
||||||
if (!this.hasExistingAccount && value?.accountFacilityConfig) {
|
|
||||||
this.facilityConfig = { ...DEFAULT_FACILITY_CONFIG, ...value.accountFacilityConfig };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get currentAccountDigital() {
|
get currentAccountDigital() {
|
||||||
return { accountFacilityConfig: { ...this.facilityConfig } };
|
return { accountFacilityConfig: { ...this.facilityConfig } };
|
||||||
|
|||||||
+4
-5
@@ -136,12 +136,11 @@ export class CurrentAccountPreviewStepComponent implements OnChanges {
|
|||||||
return this.currentAccountSettingsForm?.getRawValue?.() || this.currentAccount || {};
|
return this.currentAccountSettingsForm?.getRawValue?.() || this.currentAccount || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Digital facility config as actually captured by a digital step in this wizard -- no fallback
|
||||||
|
* to the product's own defaults, since account creation no longer has a digital step at all and
|
||||||
|
* must not preview facilities the officer never actually chose. */
|
||||||
get digitalFacilityConfig(): any {
|
get digitalFacilityConfig(): any {
|
||||||
return (
|
return this.currentAccountDigital?.accountFacilityConfig ?? null;
|
||||||
this.currentAccountDigital?.accountFacilityConfig ??
|
|
||||||
this.currentAccountProductTemplate?.accountFacilityConfig ??
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasDigitalSection(): boolean {
|
get hasDigitalSection(): boolean {
|
||||||
|
|||||||
@@ -1,241 +0,0 @@
|
|||||||
<!--
|
|
||||||
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>
|
|
||||||
<h2 class="dte-header__title">Daily Transaction Detail</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dte-card">
|
|
||||||
<form [formGroup]="entryForm">
|
|
||||||
<div class="dte-info-panels">
|
|
||||||
<div class="dte-info-panel">
|
|
||||||
<div class="dte-info-panel__title">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>
|
|
||||||
<dt>Guarantor</dt>
|
|
||||||
<dd>{{ agentInfo.guarantor1 || '—' }}</dd>
|
|
||||||
<dt>Guarantor</dt>
|
|
||||||
<dd>{{ agentInfo.guarantor2 || '—' }}</dd>
|
|
||||||
<dt>Curr Bal</dt>
|
|
||||||
<dd>{{ agentInfo.currentBalance | number: '1.2-2' }}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dte-info-panel">
|
|
||||||
<div class="dte-info-panel__title">Party Information</div>
|
|
||||||
<p class="dte-info-panel__hint">Populated once a party is selected in a collection row below.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dte-controls">
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Agent</mat-label>
|
|
||||||
<input matInput required formControlName="agentId" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<button mat-stroked-button type="button" (click)="loadAgent()"><i class="fas fa-search"></i> Load</button>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Date</mat-label>
|
|
||||||
<input matInput required [matDatepicker]="txnDatePicker" formControlName="transactionDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="txnDatePicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #txnDatePicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Tran. Type</mat-label>
|
|
||||||
<mat-select required formControlName="transactionType">
|
|
||||||
@for (type of transactionTypes; track type) {
|
|
||||||
<mat-option [value]="type">{{ type }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (isPartyTran) {
|
|
||||||
<div class="dte-section">
|
|
||||||
<div class="dte-section__header">
|
|
||||||
<i class="fas fa-list"></i>
|
|
||||||
<h3>Today's Collections</h3>
|
|
||||||
<button mat-stroked-button type="button" class="dte-add-row" (click)="addCollectionRow()">
|
|
||||||
<i class="fas fa-plus"></i> Add Row
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (collections.length === 0) {
|
|
||||||
<p class="dte-empty">No collection rows added yet.</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
@for (row of collections.controls; track row; let i = $index) {
|
|
||||||
<div class="dte-collection-row" [formGroup]="$any(row)">
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Party Code</mat-label>
|
|
||||||
<input matInput required formControlName="partyCode" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>TRTP</mat-label>
|
|
||||||
<mat-select required formControlName="transactionMode">
|
|
||||||
@for (mode of transactionModes; track mode) {
|
|
||||||
<mat-option [value]="mode">{{ mode }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>AC Type</mat-label>
|
|
||||||
<input matInput formControlName="accountType" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>A/C No.</mat-label>
|
|
||||||
<input matInput required formControlName="accountNo" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Amount</mat-label>
|
|
||||||
<input matInput required type="number" formControlName="amount" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Cheque No</mat-label>
|
|
||||||
<input matInput formControlName="chequeNo" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Token No</mat-label>
|
|
||||||
<input matInput formControlName="tokenNo" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field dte-field--wide">
|
|
||||||
<mat-label>Description</mat-label>
|
|
||||||
<input matInput formControlName="description" />
|
|
||||||
</mat-form-field>
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
type="button"
|
|
||||||
class="dte-remove-row"
|
|
||||||
(click)="removeCollectionRow(i)"
|
|
||||||
matTooltip="Remove row"
|
|
||||||
>
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (isAgentTran) {
|
|
||||||
<div class="dte-section">
|
|
||||||
<div class="dte-section__header">
|
|
||||||
<i class="fas fa-hand-holding-usd"></i>
|
|
||||||
<h3>Agent Settlement</h3>
|
|
||||||
</div>
|
|
||||||
<p class="dte-info-panel__hint">
|
|
||||||
Cash the agent hands over to the branch, deducted from the agent's collected balance shown above.
|
|
||||||
</p>
|
|
||||||
<div class="dte-grid">
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Amount</mat-label>
|
|
||||||
<input matInput required type="number" formControlName="settlementAmount" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>TRTP</mat-label>
|
|
||||||
<mat-select required formControlName="settlementMode">
|
|
||||||
@for (mode of transactionModes; track mode) {
|
|
||||||
<mat-option [value]="mode">{{ mode }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>AC Type</mat-label>
|
|
||||||
<input matInput formControlName="settlementAccountType" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>A/C No.</mat-label>
|
|
||||||
<input matInput formControlName="settlementAccountNo" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field dte-field--wide">
|
|
||||||
<mat-label>Description</mat-label>
|
|
||||||
<input matInput formControlName="settlementDescription" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</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">
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Creditor Party Code</mat-label>
|
|
||||||
<input matInput formControlName="creditorPartyCode" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Creditor Party Name</mat-label>
|
|
||||||
<input matInput formControlName="creditorPartyName" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Creditor Curr Bal</mat-label>
|
|
||||||
<input matInput formControlName="creditorPartyBalance" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Debit Party Code</mat-label>
|
|
||||||
<input matInput formControlName="debitPartyCode" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Debit Party Name</mat-label>
|
|
||||||
<input matInput formControlName="debitPartyName" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="dte-field">
|
|
||||||
<mat-label>Debit Curr Bal</mat-label>
|
|
||||||
<input matInput formControlName="debitPartyBalance" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
<button mat-raised-button color="primary" type="button" [disabled]="entryForm.invalid" (click)="save()">
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$primary: #0f766e;
|
|
||||||
|
|
||||||
.dte-container {
|
|
||||||
max-width: 1080px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-back {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-card {
|
|
||||||
background: var(--md-sys-color-surface, #fff);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
|
||||||
padding: 20px 24px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-info-panels {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-info-panel {
|
|
||||||
border: 1px solid rgb(0 0 0 / 10%);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
background: rgb(15 118 110 / 4%);
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 13px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
color: $primary;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__hint {
|
|
||||||
margin: 0;
|
|
||||||
color: rgb(0 0 0 / 50%);
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 1fr;
|
|
||||||
gap: 4px 12px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt {
|
|
||||||
font-weight: 500;
|
|
||||||
color: rgb(0 0 0 / 60%);
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 12px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-field {
|
|
||||||
min-width: 180px;
|
|
||||||
|
|
||||||
&--wide {
|
|
||||||
grid-column: span 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-section {
|
|
||||||
padding: 16px 0;
|
|
||||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-add-row {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-empty {
|
|
||||||
color: rgb(0 0 0 / 50%);
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 8px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-collection-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(7, 1fr) auto;
|
|
||||||
gap: 8px 12px;
|
|
||||||
align-items: start;
|
|
||||||
padding: 10px 0;
|
|
||||||
border-top: 1px dashed rgb(0 0 0 / 10%);
|
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-collection-row .dte-field--wide {
|
|
||||||
grid-column: span 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-remove-row {
|
|
||||||
color: #b91c1c;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-action-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding-top: 16px;
|
|
||||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 1000px) {
|
|
||||||
.dte-info-panels {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dte-collection-row {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { Component, inject } from '@angular/core';
|
|
||||||
import { UntypedFormBuilder, UntypedFormGroup, UntypedFormArray, Validators } from '@angular/forms';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
/** Custom Services */
|
|
||||||
import { OrganizationService } from 'app/organization/organization.service';
|
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
|
||||||
import { Dates } from 'app/core/utils/dates';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Daily Transaction Entry component.
|
|
||||||
*
|
|
||||||
* The screen a collection agent uses at the end of a day's round: pick a
|
|
||||||
* transaction type, then batch-enter that day's collections against
|
|
||||||
* assigned parties (Party Tran), settle collected cash to the branch
|
|
||||||
* (Agent Tran), or record a withdrawal/transfer between accounts (Withdraw
|
|
||||||
* Deposite). Modelled directly on the legacy Daily Transaction Detail
|
|
||||||
* screen this replaces.
|
|
||||||
*
|
|
||||||
* Backed by the DDS Party Transaction API (`/dds/partytransactions`) for
|
|
||||||
* Party Tran, Agent Tran and Withdraw Deposite - the three types this
|
|
||||||
* screen actually collects fields for; Party Payment / OD Entry / OD
|
|
||||||
* Payment remain placeholders until the backend distinguishes them too.
|
|
||||||
* "Retrieve" (a ready-made list of today's assigned parties) isn't wired -
|
|
||||||
* that needs a dedicated per-agent Party lookup this MVP doesn't have yet.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-daily-transaction-entry',
|
|
||||||
templateUrl: './daily-transaction-entry.component.html',
|
|
||||||
styleUrls: ['./daily-transaction-entry.component.scss'],
|
|
||||||
imports: [...STANDALONE_SHARED_IMPORTS]
|
|
||||||
})
|
|
||||||
export class DailyTransactionEntryComponent {
|
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
|
||||||
private router = inject(Router);
|
|
||||||
private organizationService = inject(OrganizationService);
|
|
||||||
private settingsService = inject(SettingsService);
|
|
||||||
private dateUtils = inject(Dates);
|
|
||||||
|
|
||||||
private static readonly TRANSACTION_TYPE_CODES: Record<string, string> = {
|
|
||||||
'Party Tran': 'PARTY_TRAN',
|
|
||||||
'Agent Tran': 'AGENT_TRAN',
|
|
||||||
'Withdraw Deposite': 'WITHDRAW_DEPOSITE'
|
|
||||||
};
|
|
||||||
|
|
||||||
transactionTypes = [
|
|
||||||
'Party Tran',
|
|
||||||
'Agent Tran',
|
|
||||||
'Party Payment',
|
|
||||||
'OD Entry',
|
|
||||||
'OD Payment',
|
|
||||||
'Withdraw Deposite'
|
|
||||||
];
|
|
||||||
|
|
||||||
transactionModes = [
|
|
||||||
'Cash',
|
|
||||||
'Transfer',
|
|
||||||
'Clearing'
|
|
||||||
];
|
|
||||||
|
|
||||||
entryForm: UntypedFormGroup = this.formBuilder.group({
|
|
||||||
agentId: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
transactionType: [
|
|
||||||
'Party Tran',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
transactionDate: [
|
|
||||||
new Date(),
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
// Withdraw Deposite fields
|
|
||||||
creditorPartyCode: [''],
|
|
||||||
creditorPartyName: [
|
|
||||||
{
|
|
||||||
value: '',
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
creditorPartyBalance: [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
debitPartyCode: [''],
|
|
||||||
debitPartyName: [
|
|
||||||
{
|
|
||||||
value: '',
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
debitPartyBalance: [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
withdrawAmount: [0],
|
|
||||||
withdrawDescription: [''],
|
|
||||||
// Agent Tran fields
|
|
||||||
settlementAmount: [0],
|
|
||||||
settlementMode: ['Cash'],
|
|
||||||
settlementAccountType: [''],
|
|
||||||
settlementAccountNo: [''],
|
|
||||||
settlementDescription: [''],
|
|
||||||
collections: this.formBuilder.array([])
|
|
||||||
});
|
|
||||||
|
|
||||||
agentInfo = {
|
|
||||||
name: '',
|
|
||||||
address: '',
|
|
||||||
city: '',
|
|
||||||
accountNo: '',
|
|
||||||
guarantor1: '',
|
|
||||||
guarantor2: '',
|
|
||||||
currentBalance: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
get isPartyTran(): boolean {
|
|
||||||
return this.entryForm.get('transactionType').value === 'Party Tran';
|
|
||||||
}
|
|
||||||
|
|
||||||
get isWithdrawDeposite(): boolean {
|
|
||||||
return this.entryForm.get('transactionType').value === 'Withdraw Deposite';
|
|
||||||
}
|
|
||||||
|
|
||||||
get isAgentTran(): boolean {
|
|
||||||
return this.entryForm.get('transactionType').value === 'Agent Tran';
|
|
||||||
}
|
|
||||||
|
|
||||||
get collections(): UntypedFormArray {
|
|
||||||
return this.entryForm.get('collections') as UntypedFormArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildCollectionRow(): UntypedFormGroup {
|
|
||||||
return this.formBuilder.group({
|
|
||||||
partyCode: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
transactionMode: [
|
|
||||||
'Cash',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
accountType: [''],
|
|
||||||
accountNo: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
amount: [
|
|
||||||
0,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
chequeNo: [''],
|
|
||||||
tokenNo: [''],
|
|
||||||
description: [''],
|
|
||||||
partyName: [
|
|
||||||
{
|
|
||||||
value: '',
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
balanceAfterTransaction: [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addCollectionRow(): void {
|
|
||||||
this.collections.push(this.buildCollectionRow());
|
|
||||||
}
|
|
||||||
|
|
||||||
removeCollectionRow(index: number): void {
|
|
||||||
this.collections.removeAt(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadAgent(): void {
|
|
||||||
const agentId = this.entryForm.get('agentId')?.value;
|
|
||||||
if (!agentId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.organizationService.getAgent(agentId).subscribe((agent: any) => {
|
|
||||||
const guarantors = agent.guarantors || [];
|
|
||||||
this.agentInfo = {
|
|
||||||
name: agent.agentNameId || '',
|
|
||||||
address: agent.agentAddress1 || '',
|
|
||||||
city: agent.agentCity || '',
|
|
||||||
accountNo: agent.agentSavingAccountNo || '',
|
|
||||||
guarantor1: guarantors[0]?.guarantorName || '',
|
|
||||||
guarantor2: guarantors[1]?.guarantorName || '',
|
|
||||||
currentBalance: agent.agentCurrentBalance || 0
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
retrieve(): void {
|
|
||||||
// No "my assigned parties" read API exists yet — this will pre-fill
|
|
||||||
// the collections grid with today's assigned parties once available.
|
|
||||||
}
|
|
||||||
|
|
||||||
viewLedger(): void {
|
|
||||||
// No ledger view exists yet for this agent.
|
|
||||||
}
|
|
||||||
|
|
||||||
signature(): void {
|
|
||||||
// No signature capture/verification flow exists yet.
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel(): void {
|
|
||||||
this.entryForm.reset({
|
|
||||||
transactionType: 'Party Tran',
|
|
||||||
transactionDate: new Date()
|
|
||||||
});
|
|
||||||
this.collections.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(): void {
|
|
||||||
this.router.navigate(['/accounts/savings']);
|
|
||||||
}
|
|
||||||
|
|
||||||
save(): void {
|
|
||||||
const raw = this.entryForm.getRawValue();
|
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
|
||||||
const locale = this.settingsService.language.code;
|
|
||||||
const transactionType = DailyTransactionEntryComponent.TRANSACTION_TYPE_CODES[raw.transactionType];
|
|
||||||
|
|
||||||
const payload: any = {
|
|
||||||
agentId: Number(raw.agentId),
|
|
||||||
transactionType,
|
|
||||||
transactionDate: this.dateUtils.formatDate(raw.transactionDate, dateFormat),
|
|
||||||
dateFormat,
|
|
||||||
locale
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.isPartyTran) {
|
|
||||||
payload.collections = (raw.collections || []).map((row: any) => ({
|
|
||||||
partyCode: row.partyCode,
|
|
||||||
transactionMode: row.transactionMode,
|
|
||||||
accountType: row.accountType || undefined,
|
|
||||||
accountNo: row.accountNo,
|
|
||||||
amount: row.amount,
|
|
||||||
chequeNo: row.chequeNo || undefined,
|
|
||||||
tokenNo: row.tokenNo || undefined,
|
|
||||||
description: row.description || undefined
|
|
||||||
}));
|
|
||||||
} else if (this.isWithdrawDeposite) {
|
|
||||||
payload.creditorPartyCode = raw.creditorPartyCode;
|
|
||||||
payload.debitPartyCode = raw.debitPartyCode;
|
|
||||||
payload.withdrawAmount = raw.withdrawAmount;
|
|
||||||
payload.withdrawDescription = raw.withdrawDescription || undefined;
|
|
||||||
} else if (this.isAgentTran) {
|
|
||||||
payload.amount = raw.settlementAmount;
|
|
||||||
payload.transactionMode = raw.settlementMode;
|
|
||||||
payload.accountType = raw.settlementAccountType || undefined;
|
|
||||||
payload.accountNo = raw.settlementAccountNo || undefined;
|
|
||||||
payload.description = raw.settlementDescription || undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.organizationService.createPartyTransaction(payload).subscribe(() => {
|
|
||||||
this.collections.clear();
|
|
||||||
this.entryForm.patchValue({
|
|
||||||
creditorPartyCode: '',
|
|
||||||
debitPartyCode: '',
|
|
||||||
withdrawAmount: 0,
|
|
||||||
withdrawDescription: '',
|
|
||||||
settlementAmount: 0,
|
|
||||||
settlementAccountType: '',
|
|
||||||
settlementAccountNo: '',
|
|
||||||
settlementDescription: ''
|
|
||||||
});
|
|
||||||
this.loadAgent();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="dr-container">
|
|
||||||
<div class="dr-header">
|
|
||||||
<button mat-stroked-button [routerLink]="['../']" class="dr-back">
|
|
||||||
<i class="fas fa-arrow-left"></i> Deposits
|
|
||||||
</button>
|
|
||||||
<h2 class="dr-header__title">DDS Reports</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@for (group of reportGroups; track group.title) {
|
|
||||||
<div class="dr-group">
|
|
||||||
<div class="dr-group__title">{{ group.title }}</div>
|
|
||||||
<div class="dr-grid">
|
|
||||||
@for (report of group.reports; track report) {
|
|
||||||
<a class="dr-card" [routerLink]="['view', report]">
|
|
||||||
<div class="dr-card__icon">
|
|
||||||
<i class="fas fa-file-alt"></i>
|
|
||||||
</div>
|
|
||||||
<div class="dr-card__title">{{ report }}</div>
|
|
||||||
<i class="fas fa-chevron-right dr-card__arrow"></i>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$primary: #0f766e;
|
|
||||||
|
|
||||||
.dr-container {
|
|
||||||
max-width: 1080px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dr-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dr-back {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dr-group {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
font-size: 13px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
color: $primary;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dr-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dr-card {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
background: var(--md-sys-color-surface, #fff);
|
|
||||||
border: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px 14px;
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
transition: box-shadow 0.15s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__icon {
|
|
||||||
color: $primary;
|
|
||||||
font-size: 16px;
|
|
||||||
flex: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__arrow {
|
|
||||||
color: rgb(0 0 0 / 30%);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 900px) {
|
|
||||||
.dr-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 600px) {
|
|
||||||
.dr-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { Component } from '@angular/core';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DDS Reports hub component.
|
|
||||||
*
|
|
||||||
* Lists every DDS report from the legacy Reports menu, grouped the same
|
|
||||||
* way (As-On-Date, Interest, Daily/Balance/Master, Transaction). Each
|
|
||||||
* links to the shared report viewer, parameterised by report name.
|
|
||||||
*
|
|
||||||
* UI only for now: no report actually queries the backend yet — see
|
|
||||||
* ReportViewerComponent.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-dds-reports',
|
|
||||||
templateUrl: './dds-reports.component.html',
|
|
||||||
styleUrls: ['./dds-reports.component.scss'],
|
|
||||||
imports: [...STANDALONE_SHARED_IMPORTS]
|
|
||||||
})
|
|
||||||
export class DdsReportsComponent {
|
|
||||||
reportGroups = [
|
|
||||||
{
|
|
||||||
title: 'As On Date',
|
|
||||||
reports: [
|
|
||||||
'Supplementary-DDS (As On)',
|
|
||||||
'Agent Check List',
|
|
||||||
'Party Check List-As On',
|
|
||||||
'Party Balance',
|
|
||||||
'Agent Wise Balance',
|
|
||||||
'Agent Wise Monthly Collection',
|
|
||||||
'Commission Register',
|
|
||||||
'Collection Summary Agent Wise',
|
|
||||||
'Close Party Ac Register',
|
|
||||||
'Agent Partywise Daily Collection',
|
|
||||||
'Daily Collection Balance',
|
|
||||||
'Interest Certificate',
|
|
||||||
'Mature Date Wise Report'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Interest',
|
|
||||||
reports: [
|
|
||||||
'Party Int. Register',
|
|
||||||
'Commission Register'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Daily / Balance / Master',
|
|
||||||
reports: [
|
|
||||||
'Daily Collection Report',
|
|
||||||
'Balance Summary',
|
|
||||||
'Master Data Export'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Transaction',
|
|
||||||
reports: ['Transaction Register']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Statements',
|
|
||||||
reports: ['Agent Commission Statement']
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="rv-container">
|
|
||||||
<div class="rv-header">
|
|
||||||
<button mat-stroked-button [routerLink]="['../../']" class="rv-back">
|
|
||||||
<i class="fas fa-arrow-left"></i> DDS Reports
|
|
||||||
</button>
|
|
||||||
<h2 class="rv-header__title">{{ reportName }}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rv-card">
|
|
||||||
<form [formGroup]="filterForm">
|
|
||||||
<div class="rv-grid">
|
|
||||||
<mat-form-field appearance="outline" class="rv-field">
|
|
||||||
<mat-label>From Date</mat-label>
|
|
||||||
<input matInput required [matDatepicker]="fromPicker" formControlName="fromDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="fromPicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #fromPicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rv-field">
|
|
||||||
<mat-label>To Date</mat-label>
|
|
||||||
<input matInput required [matDatepicker]="toPicker" formControlName="toDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="toPicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #toPicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rv-field">
|
|
||||||
<mat-label>Agent From</mat-label>
|
|
||||||
<input matInput type="number" formControlName="agentFrom" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rv-field">
|
|
||||||
<mat-label>Agent To</mat-label>
|
|
||||||
<input matInput type="number" formControlName="agentTo" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rv-field">
|
|
||||||
<mat-label>Party From</mat-label>
|
|
||||||
<input matInput type="number" formControlName="partyFrom" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rv-field">
|
|
||||||
<mat-label>Party To</mat-label>
|
|
||||||
<input matInput type="number" formControlName="partyTo" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rv-action-bar">
|
|
||||||
<button mat-raised-button type="button" (click)="exit()">Exit</button>
|
|
||||||
<button mat-raised-button color="primary" type="button" [disabled]="filterForm.invalid" (click)="runReport()">
|
|
||||||
<i class="fas fa-play" style="margin-right: 6px"></i> Run Report
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (hasRun) {
|
|
||||||
<div class="rv-results">
|
|
||||||
@if (!available) {
|
|
||||||
<p class="rv-empty">{{ unavailableMessage }}</p>
|
|
||||||
} @else if (rows.length === 0) {
|
|
||||||
<p class="rv-empty">No rows matched these filters.</p>
|
|
||||||
} @else {
|
|
||||||
<div class="rv-table-scroll">
|
|
||||||
<table class="rv-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
@for (column of columns; track column) {
|
|
||||||
<th>{{ column }}</th>
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (row of rows; track row) {
|
|
||||||
<tr>
|
|
||||||
@for (column of columns; track column) {
|
|
||||||
<td>{{ row[column] }}</td>
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$primary: #0f766e;
|
|
||||||
|
|
||||||
.rv-container {
|
|
||||||
max-width: 1100px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rv-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rv-back {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rv-card {
|
|
||||||
background: var(--md-sys-color-surface, #fff);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
|
||||||
padding: 20px 24px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rv-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 8px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rv-field {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rv-action-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding-top: 16px;
|
|
||||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rv-results {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding-top: 16px;
|
|
||||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rv-empty {
|
|
||||||
color: rgb(0 0 0 / 50%);
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
padding: 24px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rv-table-scroll {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rv-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: left;
|
|
||||||
font-size: 13px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
color: rgb(0 0 0 / 55%);
|
|
||||||
padding: 8px 10px;
|
|
||||||
border-bottom: 2px solid rgb(0 0 0 / 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 8px 10px;
|
|
||||||
border-bottom: 1px solid rgb(0 0 0 / 6%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 900px) {
|
|
||||||
.rv-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { Component, inject } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
/** Custom Services */
|
|
||||||
import { OrganizationService } from 'app/organization/organization.service';
|
|
||||||
import { Dates } from 'app/core/utils/dates';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic DDS report viewer.
|
|
||||||
*
|
|
||||||
* One shared screen for all 13+ DDS reports — the report name comes from
|
|
||||||
* the route and is mapped to a backend report key. Every report in the
|
|
||||||
* legacy system shared the same basic filter shape (date, agent range,
|
|
||||||
* party range), so this avoids building a bespoke screen per report - the
|
|
||||||
* backend returns a generic {columns, rows} shape this renders as one
|
|
||||||
* table regardless of which report it is.
|
|
||||||
*
|
|
||||||
* One legacy report (Close Party Ac Register) has no corresponding data in
|
|
||||||
* the backend yet - Party accounts have no closed/open status at all. Run
|
|
||||||
* Report still calls through, and the backend's `available: false` +
|
|
||||||
* message is shown instead of a table.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-dds-report-viewer',
|
|
||||||
templateUrl: './report-viewer.component.html',
|
|
||||||
styleUrls: ['./report-viewer.component.scss'],
|
|
||||||
imports: [...STANDALONE_SHARED_IMPORTS]
|
|
||||||
})
|
|
||||||
export class ReportViewerComponent {
|
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private router = inject(Router);
|
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
|
||||||
private organizationService = inject(OrganizationService);
|
|
||||||
private dateUtils = inject(Dates);
|
|
||||||
|
|
||||||
private static readonly REPORT_KEYS: Record<string, string> = {
|
|
||||||
'Supplementary-DDS (As On)': 'SUPPLEMENTARY_DDS',
|
|
||||||
'Agent Check List': 'AGENT_CHECK_LIST',
|
|
||||||
'Party Check List-As On': 'PARTY_CHECK_LIST',
|
|
||||||
'Party Balance': 'PARTY_BALANCE',
|
|
||||||
'Agent Wise Balance': 'AGENT_WISE_BALANCE',
|
|
||||||
'Agent Wise Monthly Collection': 'AGENT_WISE_MONTHLY_COLLECTION',
|
|
||||||
'Commission Register': 'COMMISSION_REGISTER',
|
|
||||||
'Collection Summary Agent Wise': 'COLLECTION_SUMMARY_AGENT_WISE',
|
|
||||||
'Close Party Ac Register': 'CLOSE_PARTY_AC_REGISTER',
|
|
||||||
'Agent Partywise Daily Collection': 'AGENT_PARTYWISE_DAILY_COLLECTION',
|
|
||||||
'Daily Collection Balance': 'DAILY_COLLECTION_REPORT',
|
|
||||||
'Interest Certificate': 'INTEREST_CERTIFICATE',
|
|
||||||
'Mature Date Wise Report': 'MATURE_DATE_WISE',
|
|
||||||
'Party Int. Register': 'PARTY_INTEREST_REGISTER',
|
|
||||||
'Daily Collection Report': 'DAILY_COLLECTION_REPORT',
|
|
||||||
'Balance Summary': 'BALANCE_SUMMARY',
|
|
||||||
'Master Data Export': 'MASTER_DATA_EXPORT',
|
|
||||||
'Transaction Register': 'TRANSACTION_REGISTER',
|
|
||||||
'Agent Commission Statement': 'AGENT_COMMISSION_STATEMENT'
|
|
||||||
};
|
|
||||||
|
|
||||||
reportName: string = this.route.snapshot.paramMap.get('reportName') || 'Report';
|
|
||||||
|
|
||||||
hasRun = false;
|
|
||||||
available = true;
|
|
||||||
unavailableMessage = '';
|
|
||||||
columns: string[] = [];
|
|
||||||
rows: Array<Record<string, any>> = [];
|
|
||||||
|
|
||||||
filterForm: UntypedFormGroup = this.formBuilder.group({
|
|
||||||
fromDate: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
toDate: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentFrom: [1],
|
|
||||||
agentTo: [99999],
|
|
||||||
partyFrom: [1],
|
|
||||||
partyTo: [99999999]
|
|
||||||
});
|
|
||||||
|
|
||||||
runReport(): void {
|
|
||||||
const reportKey = ReportViewerComponent.REPORT_KEYS[this.reportName];
|
|
||||||
if (!reportKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const raw = this.filterForm.getRawValue();
|
|
||||||
|
|
||||||
this.organizationService
|
|
||||||
.runDdsReport(reportKey, {
|
|
||||||
fromDate: this.dateUtils.getDate(raw.fromDate),
|
|
||||||
toDate: this.dateUtils.getDate(raw.toDate),
|
|
||||||
agentFrom: raw.agentFrom,
|
|
||||||
agentTo: raw.agentTo,
|
|
||||||
partyFrom: raw.partyFrom,
|
|
||||||
partyTo: raw.partyTo
|
|
||||||
})
|
|
||||||
.subscribe((result: any) => {
|
|
||||||
this.available = result.available;
|
|
||||||
this.unavailableMessage = result.message || '';
|
|
||||||
this.columns = result.columns || [];
|
|
||||||
this.rows = result.rows || [];
|
|
||||||
this.hasRun = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(): void {
|
|
||||||
this.router.navigate(['../../'], { relativeTo: this.route });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
@@ -10,6 +10,7 @@
|
|||||||
<mat-card>
|
<mat-card>
|
||||||
<form [formGroup]="approveFixedDepositsAccountForm" (ngSubmit)="submit()">
|
<form [formGroup]="approveFixedDepositsAccountForm" (ngSubmit)="submit()">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
|
<p class="mat-body-2">This approves and activates the account in one step -- it goes straight to Active.</p>
|
||||||
<div class="layout-column">
|
<div class="layout-column">
|
||||||
<mat-form-field (click)="approvedOnDatePicker.open()">
|
<mat-form-field (click)="approvedOnDatePicker.open()">
|
||||||
<mat-label>{{ 'labels.inputs.Approved On Date' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Approved On Date' | translate }}</mat-label>
|
||||||
|
|||||||
+13
-3
@@ -81,8 +81,9 @@ export class ApproveFixedDepositsAccountComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits the form and approves the fixed deposit account,
|
* Submits the form, approves the fixed deposit account, then immediately activates it on the
|
||||||
* if successful redirects to the fixed deposit account.
|
* same date -- Approve and Activate are still two separate Fineract commands underneath (each
|
||||||
|
* shows up in the audit trail), but the officer only sees and clicks through one step.
|
||||||
*/
|
*/
|
||||||
submit() {
|
submit() {
|
||||||
const approveFixedDepositsAccountFormData = this.approveFixedDepositsAccountForm.value;
|
const approveFixedDepositsAccountFormData = this.approveFixedDepositsAccountForm.value;
|
||||||
@@ -98,7 +99,16 @@ export class ApproveFixedDepositsAccountComponent implements OnInit {
|
|||||||
locale
|
locale
|
||||||
};
|
};
|
||||||
this.fixedDepositsService.executeFixedDepositsAccountCommand(this.accountId, 'approve', data).subscribe(() => {
|
this.fixedDepositsService.executeFixedDepositsAccountCommand(this.accountId, 'approve', data).subscribe(() => {
|
||||||
this.router.navigate(['../../'], { relativeTo: this.route });
|
const activationData = {
|
||||||
|
activatedOnDate: approveFixedDepositsAccountFormData.approvedOnDate,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.fixedDepositsService
|
||||||
|
.executeFixedDepositsAccountCommand(this.accountId, 'activate', activationData)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.router.navigate(['../../'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,543 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="pm-container">
|
|
||||||
<div class="pm-header">
|
|
||||||
<button mat-stroked-button [routerLink]="['../']" class="pm-back">
|
|
||||||
<i class="fas fa-arrow-left"></i> Deposits
|
|
||||||
</button>
|
|
||||||
<h2 class="pm-header__title">Party Master Entry</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pm-card">
|
|
||||||
<form [formGroup]="partyForm">
|
|
||||||
<mat-tab-group class="pm-tabs" [(selectedIndex)]="selectedTabIndex" animationDuration="200ms">
|
|
||||||
<mat-tab label="Main Detail">
|
|
||||||
<div class="pm-tab-content">
|
|
||||||
<div class="pm-section">
|
|
||||||
<div class="pm-section__header">
|
|
||||||
<i class="fas fa-id-card"></i>
|
|
||||||
<h3>Identity</h3>
|
|
||||||
</div>
|
|
||||||
<div class="pm-grid">
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Client ID</mat-label>
|
|
||||||
<input matInput required formControlName="clientId" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Sub Type</mat-label>
|
|
||||||
<mat-select required formControlName="subType">
|
|
||||||
@for (type of subTypes; track type) {
|
|
||||||
<mat-option [value]="type">{{ type }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Agent ID</mat-label>
|
|
||||||
<input matInput required formControlName="agentId" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
@if (linkedSavingsAccountId) {
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Savings Account</mat-label>
|
|
||||||
<input matInput [value]="'Linked (id ' + linkedSavingsAccountId + ')'" disabled />
|
|
||||||
</mat-form-field>
|
|
||||||
} @else {
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Savings Account No (if any)</mat-label>
|
|
||||||
<input matInput formControlName="savingsAccountNo" />
|
|
||||||
</mat-form-field>
|
|
||||||
}
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Party Code</mat-label>
|
|
||||||
<input matInput formControlName="partyCode" placeholder="Auto-generated" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Last A/c</mat-label>
|
|
||||||
<input matInput formControlName="lastAccount" placeholder="—" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Nominal</mat-label>
|
|
||||||
<mat-select required formControlName="nominal">
|
|
||||||
<mat-option value="Yes">Yes</mat-option>
|
|
||||||
<mat-option value="No">No</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field pm-field--wide">
|
|
||||||
<mat-label>Party Name</mat-label>
|
|
||||||
<input matInput required formControlName="partyName" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field pm-field--wide">
|
|
||||||
<mat-label>Party Name 1</mat-label>
|
|
||||||
<input matInput formControlName="partyName1" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pm-section">
|
|
||||||
<div class="pm-section__header">
|
|
||||||
<i class="fas fa-map-marker-alt"></i>
|
|
||||||
<h3>Address & Contact</h3>
|
|
||||||
</div>
|
|
||||||
<div class="pm-grid">
|
|
||||||
<mat-form-field appearance="outline" class="pm-field pm-field--wide">
|
|
||||||
<mat-label>Address 1</mat-label>
|
|
||||||
<input matInput required formControlName="address1" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field pm-field--wide">
|
|
||||||
<mat-label>Address 2</mat-label>
|
|
||||||
<input matInput formControlName="address2" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field pm-field--wide">
|
|
||||||
<mat-label>Address 3</mat-label>
|
|
||||||
<input matInput formControlName="address3" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>City</mat-label>
|
|
||||||
<input matInput required formControlName="city" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Pincode</mat-label>
|
|
||||||
<input matInput required formControlName="pincode" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Gender</mat-label>
|
|
||||||
<mat-select formControlName="gender">
|
|
||||||
<mat-option value="Male">Male</mat-option>
|
|
||||||
<mat-option value="Female">Female</mat-option>
|
|
||||||
<mat-option value="Other">Other</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Telephone</mat-label>
|
|
||||||
<input matInput formControlName="telephone" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Mobile No (SMS)</mat-label>
|
|
||||||
<input matInput required formControlName="mobileNoSms" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pm-section">
|
|
||||||
<div class="pm-section__header">
|
|
||||||
<i class="fas fa-shield-alt"></i>
|
|
||||||
<h3>KYC</h3>
|
|
||||||
</div>
|
|
||||||
<div class="pm-grid">
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Aadhar No</mat-label>
|
|
||||||
<input matInput formControlName="aadharNo" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>PAN Card No</mat-label>
|
|
||||||
<input matInput formControlName="panCardNo" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>CKYC ID</mat-label>
|
|
||||||
<input matInput formControlName="ckycId" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Operation</mat-label>
|
|
||||||
<mat-select required formControlName="operation">
|
|
||||||
@for (op of operations; track op) {
|
|
||||||
<mat-option [value]="op">{{ op }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Birth Date</mat-label>
|
|
||||||
<input matInput [matDatepicker]="birthPicker" formControlName="birthDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="birthPicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #birthPicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Occupation</mat-label>
|
|
||||||
<mat-select formControlName="occupation">
|
|
||||||
@for (occ of occupations; track occ) {
|
|
||||||
<mat-option [value]="occ">{{ occ }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Risk</mat-label>
|
|
||||||
<mat-select formControlName="risk">
|
|
||||||
@for (r of riskLevels; track r) {
|
|
||||||
<mat-option [value]="r">{{ r }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pm-section">
|
|
||||||
<div class="pm-section__header">
|
|
||||||
<i class="fas fa-user-friends"></i>
|
|
||||||
<h3>Minor / Guardian</h3>
|
|
||||||
</div>
|
|
||||||
<div class="pm-grid">
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Adult / Minor</mat-label>
|
|
||||||
<mat-select required formControlName="adultMinor">
|
|
||||||
@for (opt of adultMinorOptions; track opt) {
|
|
||||||
<mat-option [value]="opt">{{ opt }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Minor Date</mat-label>
|
|
||||||
<input matInput [matDatepicker]="minorPicker" formControlName="minorDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="minorPicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #minorPicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Guardian Client ID</mat-label>
|
|
||||||
<input matInput formControlName="guardianClientId" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Min. A/c Opt</mat-label>
|
|
||||||
<input matInput formControlName="minAccountOpt" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Relation</mat-label>
|
|
||||||
<input matInput formControlName="relation" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pm-section">
|
|
||||||
<div class="pm-section__header">
|
|
||||||
<i class="fas fa-calendar-day"></i>
|
|
||||||
<h3>Scheme Terms</h3>
|
|
||||||
</div>
|
|
||||||
<div class="pm-grid">
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Opening Date</mat-label>
|
|
||||||
<input matInput required [matDatepicker]="openingPicker" formControlName="openingDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="openingPicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #openingPicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Month</mat-label>
|
|
||||||
<input matInput type="number" formControlName="month" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Maturity Date</mat-label>
|
|
||||||
<input matInput formControlName="maturityDate" placeholder="Calculated" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Reopen Date</mat-label>
|
|
||||||
<input matInput [matDatepicker]="reopenPicker" formControlName="reopenDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="reopenPicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #reopenPicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Deposit Amt</mat-label>
|
|
||||||
<input matInput type="number" formControlName="depositAmount" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Daily Deposit Amt</mat-label>
|
|
||||||
<input matInput required type="number" formControlName="dailyDepositAmount" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Days</mat-label>
|
|
||||||
<input matInput required type="number" formControlName="days" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Ledger No</mat-label>
|
|
||||||
<input matInput formControlName="ledgerNo" placeholder="Auto-generated" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Mat. Amt</mat-label>
|
|
||||||
<input matInput formControlName="maturityAmount" placeholder="Calculated" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Interest Rate</mat-label>
|
|
||||||
<input matInput formControlName="interestRate" placeholder="From product" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Withdraw Type</mat-label>
|
|
||||||
<mat-select formControlName="withdrawType">
|
|
||||||
@for (type of withdrawTypes; track type) {
|
|
||||||
<mat-option [value]="type">{{ type }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pm-section">
|
|
||||||
<div class="pm-section__header">
|
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
|
||||||
<h3>Status & Overdue Flags</h3>
|
|
||||||
</div>
|
|
||||||
<div class="pm-grid">
|
|
||||||
<div class="pm-checkbox-field">
|
|
||||||
<mat-checkbox formControlName="od1Flag">OD1 Flag</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="pm-checkbox-field">
|
|
||||||
<mat-checkbox formControlName="od2Flag">OD2 Flag</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Lien</mat-label>
|
|
||||||
<mat-select formControlName="lien">
|
|
||||||
<mat-option value="Yes">Yes</mat-option>
|
|
||||||
<mat-option value="No">No</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Pay Date</mat-label>
|
|
||||||
<input matInput [matDatepicker]="payDatePicker" formControlName="payDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="payDatePicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #payDatePicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Payment Amount</mat-label>
|
|
||||||
<input matInput type="number" formControlName="paymentAmount" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Current Bal</mat-label>
|
|
||||||
<input matInput formControlName="currentBalance" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Opening Bal</mat-label>
|
|
||||||
<input matInput type="number" formControlName="openingBalance" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<div class="pm-checkbox-field">
|
|
||||||
<mat-checkbox formControlName="isSmsActive">Is SMS Active</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="pm-checkbox-field">
|
|
||||||
<mat-checkbox formControlName="isEmailActive">Is Email Active</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Int.Calc</mat-label>
|
|
||||||
<input matInput formControlName="interestCalc" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Interest Date</mat-label>
|
|
||||||
<input matInput [matDatepicker]="interestDatePicker" formControlName="interestDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="interestDatePicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #interestDatePicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field pm-field--wide">
|
|
||||||
<mat-label>Sp.Description</mat-label>
|
|
||||||
<input matInput formControlName="spDescription" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Fr'z CD</mat-label>
|
|
||||||
<mat-select formControlName="freezeCode">
|
|
||||||
@for (code of freezeCodes; track code) {
|
|
||||||
<mat-option [value]="code">{{ code }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Adv Code</mat-label>
|
|
||||||
<mat-select formControlName="advCode">
|
|
||||||
@for (code of advCodes; track code) {
|
|
||||||
<mat-option [value]="code">{{ code }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (partyForm.get('advCode').value === 'Transfer To Saving') {
|
|
||||||
<div class="pm-section">
|
|
||||||
<div class="pm-section__header">
|
|
||||||
<i class="fas fa-exchange-alt"></i>
|
|
||||||
<h3>Maturity Payout Routing</h3>
|
|
||||||
</div>
|
|
||||||
<div class="pm-grid">
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Credit To Branch</mat-label>
|
|
||||||
<input matInput required formControlName="creditToBranch" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Credit To A/C Type</mat-label>
|
|
||||||
<input matInput required formControlName="creditToAccountType" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Credit To A/C</mat-label>
|
|
||||||
<input matInput required formControlName="creditToAccount" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<mat-tab label="Nominee's Detail">
|
|
||||||
<div class="pm-tab-content">
|
|
||||||
<div class="pm-section">
|
|
||||||
<div class="pm-grid">
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Nominee</mat-label>
|
|
||||||
<mat-select formControlName="nomineeApplicable">
|
|
||||||
<mat-option value="Yes">Yes</mat-option>
|
|
||||||
<mat-option value="No">No</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-radio-group formControlName="nomineeSplitMode" class="pm-radio-group">
|
|
||||||
<mat-radio-button value="Percentage">Percentage</mat-radio-button>
|
|
||||||
<mat-radio-button value="Sequence">Sequence</mat-radio-button>
|
|
||||||
</mat-radio-group>
|
|
||||||
|
|
||||||
<button mat-stroked-button type="button" class="pm-add-nominee" (click)="addNominee()">
|
|
||||||
<i class="fas fa-plus"></i> Add Nominee
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (nominees.length === 0) {
|
|
||||||
<p class="pm-empty">No nominees added yet.</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
@for (nominee of nominees.controls; track nominee; let i = $index) {
|
|
||||||
<div class="pm-nominee-row" [formGroup]="$any(nominee)">
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Nom.Dt</mat-label>
|
|
||||||
<input matInput required [matDatepicker]="nomDatePicker" formControlName="nominationDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="nomDatePicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #nomDatePicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Name</mat-label>
|
|
||||||
<input matInput required formControlName="name" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Mobile No</mat-label>
|
|
||||||
<input matInput formControlName="mobileNo" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Birth Dt</mat-label>
|
|
||||||
<input matInput [matDatepicker]="nomBirthPicker" formControlName="birthDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="nomBirthPicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #nomBirthPicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Minor</mat-label>
|
|
||||||
<mat-select required formControlName="minor">
|
|
||||||
<mat-option value="Yes">Yes</mat-option>
|
|
||||||
<mat-option value="No">No</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Relation</mat-label>
|
|
||||||
<input matInput required formControlName="relation" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Gender</mat-label>
|
|
||||||
<mat-select required formControlName="gender">
|
|
||||||
<mat-option value="Male">Male</mat-option>
|
|
||||||
<mat-option value="Female">Female</mat-option>
|
|
||||||
<mat-option value="Other">Other</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="pm-field">
|
|
||||||
<mat-label>Guar. Name</mat-label>
|
|
||||||
<input matInput formControlName="guardianName" />
|
|
||||||
</mat-form-field>
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
type="button"
|
|
||||||
class="pm-remove-nominee"
|
|
||||||
(click)="removeNominee(i)"
|
|
||||||
matTooltip="Remove nominee"
|
|
||||||
>
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
</mat-tab-group>
|
|
||||||
|
|
||||||
@if (isClosed) {
|
|
||||||
<div class="pm-callout">
|
|
||||||
<i class="fas fa-lock"></i>
|
|
||||||
<span>This account is closed. No further collections or edits are possible.</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="pm-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>
|
|
||||||
@if (partyId && !isClosed) {
|
|
||||||
<button
|
|
||||||
mat-stroked-button
|
|
||||||
type="button"
|
|
||||||
color="warn"
|
|
||||||
matTooltip="Balance must be zero before closing"
|
|
||||||
(click)="closeAccount()"
|
|
||||||
>
|
|
||||||
<i class="fas fa-lock" style="margin-right: 6px"></i> Close Account
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
<button
|
|
||||||
mat-raised-button
|
|
||||||
color="primary"
|
|
||||||
type="button"
|
|
||||||
[disabled]="partyForm.invalid || isClosed"
|
|
||||||
(click)="save()"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$primary: #0f766e;
|
|
||||||
|
|
||||||
.pm-container {
|
|
||||||
max-width: 1080px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-back {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-card {
|
|
||||||
background: var(--md-sys-color-surface, #fff);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-tabs {
|
|
||||||
::ng-deep .mat-mdc-tab-body-wrapper {
|
|
||||||
min-height: 300px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-tab-content {
|
|
||||||
padding: 20px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-section {
|
|
||||||
padding: 16px 0;
|
|
||||||
border-bottom: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
gap: 8px 16px;
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-field {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--wide {
|
|
||||||
grid-column: span 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-checkbox-field {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-radio-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
height: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-add-nominee {
|
|
||||||
color: $primary;
|
|
||||||
height: fit-content;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-empty {
|
|
||||||
color: rgb(0 0 0 / 50%);
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 0 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-nominee-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(8, 1fr) auto;
|
|
||||||
gap: 8px 12px;
|
|
||||||
align-items: start;
|
|
||||||
padding: 10px 0;
|
|
||||||
border-top: 1px dashed rgb(0 0 0 / 10%);
|
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-remove-nominee {
|
|
||||||
color: #b91c1c;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-action-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 16px 24px;
|
|
||||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-callout {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin: 0 24px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
background: rgb(185 28 28 / 8%);
|
|
||||||
border: 1px solid rgb(185 28 28 / 25%);
|
|
||||||
border-radius: 6px;
|
|
||||||
color: #b91c1c;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 1000px) {
|
|
||||||
.pm-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pm-nominee-row {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,527 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { Component, inject } from '@angular/core';
|
|
||||||
import { UntypedFormBuilder, UntypedFormGroup, UntypedFormArray, Validators } from '@angular/forms';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
|
||||||
import { MatCheckbox } from '@angular/material/checkbox';
|
|
||||||
import { MatRadioModule } from '@angular/material/radio';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
/** Custom Services */
|
|
||||||
import { OrganizationService } from 'app/organization/organization.service';
|
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
|
||||||
import { Dates } from 'app/core/utils/dates';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Party Master Entry component.
|
|
||||||
*
|
|
||||||
* The customer's DDS account — linked to an Agent for daily collection,
|
|
||||||
* carrying its own scheme terms, KYC, two-stage overdue flags, and a
|
|
||||||
* nominee list. Modelled directly on the legacy Party Master Entry screen
|
|
||||||
* this replaces.
|
|
||||||
*
|
|
||||||
* Backed by its own DDS Party API (`/dds/parties`). The underlying account
|
|
||||||
* does not yet reuse the Recurring Deposit account engine (see Product
|
|
||||||
* Setup) — that linkage is a follow-up once the RD-account-specific fields
|
|
||||||
* this screen doesn't collect (interest/compounding periods, chart
|
|
||||||
* selection, ...) have a home. Until then the scheme/balance fields here
|
|
||||||
* are the Party's own.
|
|
||||||
*
|
|
||||||
* "Close Account" (command=close) permanently closes the Party once its
|
|
||||||
* balance is zero - the backend rejects the command otherwise. A closed
|
|
||||||
* Party can no longer be edited or receive new Party Tran collections.
|
|
||||||
*
|
|
||||||
* savingsAccountNo optionally links this Party to an existing, non-closed real Savings Account of
|
|
||||||
* the same client (resolved by account number, since that's what an officer has to hand) -
|
|
||||||
* clarified with the business (2026-08-24) as "link it if the client already has one, otherwise
|
|
||||||
* leave it". There is no unlink once linked.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-party-master',
|
|
||||||
templateUrl: './party-master.component.html',
|
|
||||||
styleUrls: ['./party-master.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
MatTabsModule,
|
|
||||||
MatCheckbox,
|
|
||||||
MatRadioModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class PartyMasterComponent {
|
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
|
||||||
private router = inject(Router);
|
|
||||||
private organizationService = inject(OrganizationService);
|
|
||||||
private settingsService = inject(SettingsService);
|
|
||||||
private dateUtils = inject(Dates);
|
|
||||||
|
|
||||||
partyId: number | null = null;
|
|
||||||
selectedTabIndex = 0;
|
|
||||||
partyStatus = 'ACTIVE';
|
|
||||||
linkedSavingsAccountId: number | null = null;
|
|
||||||
|
|
||||||
get isClosed(): boolean {
|
|
||||||
return this.partyStatus === 'CLOSED';
|
|
||||||
}
|
|
||||||
|
|
||||||
subTypes = [
|
|
||||||
'Daily Deposit Scheme',
|
|
||||||
'Masik Dhan Varsha Deposit'
|
|
||||||
];
|
|
||||||
operations = [
|
|
||||||
'Self',
|
|
||||||
'Guardian',
|
|
||||||
'Joint'
|
|
||||||
];
|
|
||||||
occupations = [
|
|
||||||
'Business',
|
|
||||||
'Service',
|
|
||||||
'Agriculture',
|
|
||||||
'Homemaker',
|
|
||||||
'Student',
|
|
||||||
'Retired',
|
|
||||||
'Other'
|
|
||||||
];
|
|
||||||
riskLevels = [
|
|
||||||
'Low',
|
|
||||||
'Medium',
|
|
||||||
'High'
|
|
||||||
];
|
|
||||||
adultMinorOptions = [
|
|
||||||
'Adult',
|
|
||||||
'Minor'
|
|
||||||
];
|
|
||||||
withdrawTypes = [
|
|
||||||
'Cash',
|
|
||||||
'Transfer',
|
|
||||||
'Clearing'
|
|
||||||
];
|
|
||||||
freezeCodes = [
|
|
||||||
'Normal',
|
|
||||||
'Debit Freeze',
|
|
||||||
'Credit Freeze',
|
|
||||||
'Total Freeze'
|
|
||||||
];
|
|
||||||
advCodes = [
|
|
||||||
'None',
|
|
||||||
'Transfer To Saving',
|
|
||||||
'Renew'
|
|
||||||
];
|
|
||||||
|
|
||||||
partyForm: UntypedFormGroup = this.formBuilder.group({
|
|
||||||
clientId: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
subType: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentId: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
savingsAccountNo: [''],
|
|
||||||
lastAccount: [
|
|
||||||
{
|
|
||||||
value: '',
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
partyCode: [
|
|
||||||
{
|
|
||||||
value: '',
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
nominal: [
|
|
||||||
'No',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
aadharNo: [''],
|
|
||||||
partyName: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
partyName1: [''],
|
|
||||||
address1: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
address2: [''],
|
|
||||||
address3: [''],
|
|
||||||
city: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
pincode: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
gender: [''],
|
|
||||||
panCardNo: [''],
|
|
||||||
telephone: [''],
|
|
||||||
mobileNoSms: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
operation: [
|
|
||||||
'Self',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
birthDate: [''],
|
|
||||||
occupation: [''],
|
|
||||||
risk: [''],
|
|
||||||
ckycId: [''],
|
|
||||||
adultMinor: [
|
|
||||||
'Adult',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
minorDate: [''],
|
|
||||||
guardianClientId: [''],
|
|
||||||
minAccountOpt: [''],
|
|
||||||
relation: [''],
|
|
||||||
openingDate: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
month: [''],
|
|
||||||
maturityDate: [
|
|
||||||
{
|
|
||||||
value: '',
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
reopenDate: [''],
|
|
||||||
depositAmount: [0],
|
|
||||||
dailyDepositAmount: [
|
|
||||||
0,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
days: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
ledgerNo: [
|
|
||||||
{
|
|
||||||
value: '',
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
maturityAmount: [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
interestRate: [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
withdrawType: [''],
|
|
||||||
od1Flag: [
|
|
||||||
{
|
|
||||||
value: false,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
od2Flag: [
|
|
||||||
{
|
|
||||||
value: false,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
lien: ['No'],
|
|
||||||
payDate: [''],
|
|
||||||
paymentAmount: [0],
|
|
||||||
currentBalance: [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
openingBalance: [0],
|
|
||||||
isSmsActive: [true],
|
|
||||||
isEmailActive: [false],
|
|
||||||
interestCalc: [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
interestDate: [''],
|
|
||||||
spDescription: [''],
|
|
||||||
freezeCode: ['Normal'],
|
|
||||||
advCode: ['None'],
|
|
||||||
creditToBranch: [''],
|
|
||||||
creditToAccountType: [''],
|
|
||||||
creditToAccount: [''],
|
|
||||||
nomineeApplicable: ['No'],
|
|
||||||
nomineeSplitMode: ['Percentage'],
|
|
||||||
nominees: this.formBuilder.array([])
|
|
||||||
});
|
|
||||||
|
|
||||||
get nominees(): UntypedFormArray {
|
|
||||||
return this.partyForm.get('nominees') as UntypedFormArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildNomineeGroup(): UntypedFormGroup {
|
|
||||||
return this.formBuilder.group({
|
|
||||||
nominationDate: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
name: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
mobileNo: [''],
|
|
||||||
birthDate: [''],
|
|
||||||
minor: [
|
|
||||||
'No',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
relation: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
gender: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
guardianName: ['']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addNominee(): void {
|
|
||||||
this.nominees.push(this.buildNomineeGroup());
|
|
||||||
}
|
|
||||||
|
|
||||||
removeNominee(index: number): void {
|
|
||||||
this.nominees.removeAt(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
retrieve(): void {
|
|
||||||
const clientId = this.partyForm.get('clientId')?.value;
|
|
||||||
if (!clientId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.organizationService.getParties().subscribe((parties: any[]) => {
|
|
||||||
const match = (parties || []).find((party) => String(party.clientId) === String(clientId));
|
|
||||||
if (match) {
|
|
||||||
this.patchFormFromParty(match);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel(): void {
|
|
||||||
this.partyId = null;
|
|
||||||
this.partyStatus = 'ACTIVE';
|
|
||||||
this.linkedSavingsAccountId = null;
|
|
||||||
this.partyForm.reset();
|
|
||||||
this.partyForm.patchValue({
|
|
||||||
nominal: 'No',
|
|
||||||
operation: 'Self',
|
|
||||||
adultMinor: 'Adult',
|
|
||||||
lien: 'No',
|
|
||||||
isSmsActive: true,
|
|
||||||
isEmailActive: false,
|
|
||||||
freezeCode: 'Normal',
|
|
||||||
advCode: 'None',
|
|
||||||
nomineeApplicable: 'No',
|
|
||||||
nomineeSplitMode: 'Percentage',
|
|
||||||
currentBalance: 0,
|
|
||||||
maturityAmount: 0,
|
|
||||||
interestRate: 0
|
|
||||||
});
|
|
||||||
this.nominees.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(): void {
|
|
||||||
this.router.navigate(['/accounts/savings']);
|
|
||||||
}
|
|
||||||
|
|
||||||
save(): void {
|
|
||||||
const raw = this.partyForm.getRawValue();
|
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
|
||||||
const locale = this.settingsService.language.code;
|
|
||||||
const asDate = (value: any) => (value ? this.dateUtils.formatDate(value, dateFormat) : undefined);
|
|
||||||
|
|
||||||
const payload: any = {
|
|
||||||
clientId: Number(raw.clientId),
|
|
||||||
agentId: Number(raw.agentId),
|
|
||||||
savingsAccountNo: this.linkedSavingsAccountId ? undefined : raw.savingsAccountNo || undefined,
|
|
||||||
subType: raw.subType,
|
|
||||||
lastAccount: raw.lastAccount || undefined,
|
|
||||||
nominal: raw.nominal === 'Yes',
|
|
||||||
aadharNo: raw.aadharNo || undefined,
|
|
||||||
partyName: raw.partyName,
|
|
||||||
partyName1: raw.partyName1 || undefined,
|
|
||||||
address1: raw.address1,
|
|
||||||
address2: raw.address2 || undefined,
|
|
||||||
address3: raw.address3 || undefined,
|
|
||||||
city: raw.city,
|
|
||||||
pincode: raw.pincode,
|
|
||||||
gender: raw.gender || undefined,
|
|
||||||
panCardNo: raw.panCardNo || undefined,
|
|
||||||
telephone: raw.telephone || undefined,
|
|
||||||
mobileNoSms: raw.mobileNoSms,
|
|
||||||
operation: raw.operation,
|
|
||||||
birthDate: asDate(raw.birthDate),
|
|
||||||
occupation: raw.occupation || undefined,
|
|
||||||
risk: raw.risk || undefined,
|
|
||||||
ckycId: raw.ckycId || undefined,
|
|
||||||
adultMinor: raw.adultMinor,
|
|
||||||
minorDate: asDate(raw.minorDate),
|
|
||||||
guardianClientId: raw.guardianClientId ? Number(raw.guardianClientId) : undefined,
|
|
||||||
minAccountOpt: raw.minAccountOpt || undefined,
|
|
||||||
relation: raw.relation || undefined,
|
|
||||||
openingDate: asDate(raw.openingDate),
|
|
||||||
month: raw.month || undefined,
|
|
||||||
reopenDate: asDate(raw.reopenDate),
|
|
||||||
depositAmount: raw.depositAmount || undefined,
|
|
||||||
dailyDepositAmount: raw.dailyDepositAmount,
|
|
||||||
days: raw.days,
|
|
||||||
withdrawType: raw.withdrawType || undefined,
|
|
||||||
lien: raw.lien === 'Yes',
|
|
||||||
payDate: asDate(raw.payDate),
|
|
||||||
paymentAmount: raw.paymentAmount || undefined,
|
|
||||||
openingBalance: raw.openingBalance || 0,
|
|
||||||
isSmsActive: !!raw.isSmsActive,
|
|
||||||
isEmailActive: !!raw.isEmailActive,
|
|
||||||
interestDate: asDate(raw.interestDate),
|
|
||||||
spDescription: raw.spDescription || undefined,
|
|
||||||
freezeCode: raw.freezeCode || undefined,
|
|
||||||
advCode: raw.advCode || undefined,
|
|
||||||
creditToBranch: raw.creditToBranch || undefined,
|
|
||||||
creditToAccountType: raw.creditToAccountType || undefined,
|
|
||||||
creditToAccount: raw.creditToAccount || undefined,
|
|
||||||
nomineeApplicable: raw.nomineeApplicable === 'Yes',
|
|
||||||
nomineeSplitMode: raw.nomineeSplitMode || undefined,
|
|
||||||
dateFormat,
|
|
||||||
locale,
|
|
||||||
nominees: (raw.nominees || []).map((nominee: any) => ({
|
|
||||||
nominationDate: this.dateUtils.getDate(nominee.nominationDate),
|
|
||||||
name: nominee.name,
|
|
||||||
mobileNo: nominee.mobileNo || undefined,
|
|
||||||
birthDate: nominee.birthDate ? this.dateUtils.getDate(nominee.birthDate) : undefined,
|
|
||||||
minor: nominee.minor === 'Yes',
|
|
||||||
relation: nominee.relation,
|
|
||||||
gender: nominee.gender,
|
|
||||||
guardianName: nominee.guardianName || undefined
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.partyId) {
|
|
||||||
this.organizationService.updateParty(this.partyId, payload).subscribe(() => this.exit());
|
|
||||||
} else {
|
|
||||||
this.organizationService.createParty(payload).subscribe(() => this.exit());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closeAccount(): void {
|
|
||||||
if (!this.partyId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
|
||||||
const locale = this.settingsService.language.code;
|
|
||||||
this.organizationService
|
|
||||||
.closeParty(this.partyId, {
|
|
||||||
closeDate: this.dateUtils.formatDate(new Date(), dateFormat),
|
|
||||||
dateFormat,
|
|
||||||
locale
|
|
||||||
})
|
|
||||||
.subscribe(() => this.exit());
|
|
||||||
}
|
|
||||||
|
|
||||||
private patchFormFromParty(party: any): void {
|
|
||||||
this.partyId = party.id;
|
|
||||||
this.partyStatus = party.status || 'ACTIVE';
|
|
||||||
this.linkedSavingsAccountId = party.savingsAccountId || null;
|
|
||||||
this.partyForm.patchValue({
|
|
||||||
clientId: party.clientId,
|
|
||||||
subType: party.subType,
|
|
||||||
agentId: party.agentId,
|
|
||||||
partyCode: party.partyCode,
|
|
||||||
lastAccount: party.lastAccount,
|
|
||||||
nominal: party.nominal ? 'Yes' : 'No',
|
|
||||||
aadharNo: party.aadharNo,
|
|
||||||
partyName: party.partyName,
|
|
||||||
partyName1: party.partyName1,
|
|
||||||
address1: party.address1,
|
|
||||||
address2: party.address2,
|
|
||||||
address3: party.address3,
|
|
||||||
city: party.city,
|
|
||||||
pincode: party.pincode,
|
|
||||||
gender: party.gender,
|
|
||||||
panCardNo: party.panCardNo,
|
|
||||||
telephone: party.telephone,
|
|
||||||
mobileNoSms: party.mobileNoSms,
|
|
||||||
operation: party.operation,
|
|
||||||
birthDate: party.birthDate ? new Date(party.birthDate) : null,
|
|
||||||
occupation: party.occupation,
|
|
||||||
risk: party.risk,
|
|
||||||
ckycId: party.ckycId,
|
|
||||||
adultMinor: party.adultMinor,
|
|
||||||
minorDate: party.minorDate ? new Date(party.minorDate) : null,
|
|
||||||
guardianClientId: party.guardianClientId,
|
|
||||||
minAccountOpt: party.minAccountOpt,
|
|
||||||
relation: party.relation,
|
|
||||||
openingDate: party.openingDate ? new Date(party.openingDate) : null,
|
|
||||||
month: party.month,
|
|
||||||
maturityDate: party.maturityDate,
|
|
||||||
reopenDate: party.reopenDate ? new Date(party.reopenDate) : null,
|
|
||||||
depositAmount: party.depositAmount,
|
|
||||||
dailyDepositAmount: party.dailyDepositAmount,
|
|
||||||
days: party.days,
|
|
||||||
ledgerNo: party.ledgerNo,
|
|
||||||
maturityAmount: party.maturityAmount,
|
|
||||||
interestRate: party.interestRate,
|
|
||||||
withdrawType: party.withdrawType,
|
|
||||||
od1Flag: party.od1Flag,
|
|
||||||
od2Flag: party.od2Flag,
|
|
||||||
lien: party.lien ? 'Yes' : 'No',
|
|
||||||
payDate: party.payDate ? new Date(party.payDate) : null,
|
|
||||||
paymentAmount: party.paymentAmount,
|
|
||||||
currentBalance: party.currentBalance,
|
|
||||||
openingBalance: party.openingBalance,
|
|
||||||
isSmsActive: party.isSmsActive,
|
|
||||||
isEmailActive: party.isEmailActive,
|
|
||||||
interestCalc: party.interestCalc,
|
|
||||||
interestDate: party.interestDate ? new Date(party.interestDate) : null,
|
|
||||||
spDescription: party.spDescription,
|
|
||||||
freezeCode: party.freezeCode,
|
|
||||||
advCode: party.advCode,
|
|
||||||
creditToBranch: party.creditToBranch,
|
|
||||||
creditToAccountType: party.creditToAccountType,
|
|
||||||
creditToAccount: party.creditToAccount,
|
|
||||||
nomineeApplicable: party.nomineeApplicable ? 'Yes' : 'No',
|
|
||||||
nomineeSplitMode: party.nomineeSplitMode
|
|
||||||
});
|
|
||||||
|
|
||||||
this.nominees.clear();
|
|
||||||
(party.nominees || []).forEach((nominee: any) => {
|
|
||||||
const group = this.buildNomineeGroup();
|
|
||||||
group.patchValue({
|
|
||||||
nominationDate: nominee.nominationDate ? new Date(nominee.nominationDate) : null,
|
|
||||||
name: nominee.name,
|
|
||||||
mobileNo: nominee.mobileNo,
|
|
||||||
birthDate: nominee.birthDate ? new Date(nominee.birthDate) : null,
|
|
||||||
minor: nominee.minor ? 'Yes' : 'No',
|
|
||||||
relation: nominee.relation,
|
|
||||||
gender: nominee.gender,
|
|
||||||
guardianName: nominee.guardianName
|
|
||||||
});
|
|
||||||
this.nominees.push(group);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
@@ -10,6 +10,7 @@
|
|||||||
<mat-card>
|
<mat-card>
|
||||||
<form [formGroup]="approveRecurringDepositsAccountForm" (ngSubmit)="submit()">
|
<form [formGroup]="approveRecurringDepositsAccountForm" (ngSubmit)="submit()">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
|
<p class="mat-body-2">This approves and activates the account in one step -- it goes straight to Active.</p>
|
||||||
<div class="layout-column">
|
<div class="layout-column">
|
||||||
<mat-form-field (click)="approvedOnDatePicker.open()">
|
<mat-form-field (click)="approvedOnDatePicker.open()">
|
||||||
<mat-label>{{ 'labels.inputs.Approved On Date' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Approved On Date' | translate }}</mat-label>
|
||||||
|
|||||||
+13
-3
@@ -81,8 +81,9 @@ export class ApproveRecurringDepositsAccountComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits the form and approves the recurring deposit account,
|
* Submits the form, approves the recurring deposit account, then immediately activates it on
|
||||||
* if successful redirects to the recurring deposit account.
|
* the same date -- Approve and Activate are still two separate Fineract commands underneath
|
||||||
|
* (each shows up in the audit trail), but the officer only sees and clicks through one step.
|
||||||
*/
|
*/
|
||||||
submit() {
|
submit() {
|
||||||
const approveRecurringDepositsAccountFormData = this.approveRecurringDepositsAccountForm.value;
|
const approveRecurringDepositsAccountFormData = this.approveRecurringDepositsAccountForm.value;
|
||||||
@@ -103,7 +104,16 @@ export class ApproveRecurringDepositsAccountComponent implements OnInit {
|
|||||||
this.recurringDepositsService
|
this.recurringDepositsService
|
||||||
.executeRecurringDepositsAccountCommand(this.accountId, 'approve', data)
|
.executeRecurringDepositsAccountCommand(this.accountId, 'approve', data)
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.router.navigate(['../../'], { relativeTo: this.route });
|
const activationData = {
|
||||||
|
activatedOnDate: approveRecurringDepositsAccountFormData.approvedOnDate,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.recurringDepositsService
|
||||||
|
.executeRecurringDepositsAccountCommand(this.accountId, 'activate', activationData)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.router.navigate(['../../'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ export class KeyboardShortcutsConfiguration {
|
|||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
altKey: false,
|
altKey: false,
|
||||||
key: 'n',
|
key: 'n',
|
||||||
route: 'navigation'
|
route: 'navigation',
|
||||||
|
id: 'navigation_page'
|
||||||
},
|
},
|
||||||
// Run Report - ctrl + t
|
// Run Report - ctrl + t
|
||||||
{
|
{
|
||||||
@@ -50,17 +51,9 @@ export class KeyboardShortcutsConfiguration {
|
|||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
altKey: false,
|
altKey: false,
|
||||||
key: 'i',
|
key: 'i',
|
||||||
route: 'checker-inbox-and-tasks/checker-inbox'
|
route: 'checker-inbox-and-tasks/checker-inbox',
|
||||||
|
id: 'checker_inbox'
|
||||||
},
|
},
|
||||||
// Collection Sheet- ctrl + alt + o
|
|
||||||
// {
|
|
||||||
// title: 'Collection Sheet',
|
|
||||||
// ctrlKey: true,
|
|
||||||
// shiftKey: false,
|
|
||||||
// altKey: true,
|
|
||||||
// key: 'o',
|
|
||||||
// route: ''
|
|
||||||
// },
|
|
||||||
// Create Client - ctrl + c
|
// Create Client - ctrl + c
|
||||||
{
|
{
|
||||||
title: 'Create Client',
|
title: 'Create Client',
|
||||||
@@ -68,7 +61,8 @@ export class KeyboardShortcutsConfiguration {
|
|||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
altKey: true,
|
altKey: true,
|
||||||
key: 'c',
|
key: 'c',
|
||||||
route: 'clients/create'
|
route: 'clients/create',
|
||||||
|
id: 'create_client'
|
||||||
},
|
},
|
||||||
// Create Group - ctrl + g
|
// Create Group - ctrl + g
|
||||||
{
|
{
|
||||||
@@ -77,7 +71,8 @@ export class KeyboardShortcutsConfiguration {
|
|||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
altKey: false,
|
altKey: false,
|
||||||
key: 'g',
|
key: 'g',
|
||||||
route: 'groups/create'
|
route: 'groups/create',
|
||||||
|
id: 'create_group'
|
||||||
},
|
},
|
||||||
// Create Center - ctrl + q
|
// Create Center - ctrl + q
|
||||||
{
|
{
|
||||||
@@ -86,7 +81,8 @@ export class KeyboardShortcutsConfiguration {
|
|||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
altKey: false,
|
altKey: false,
|
||||||
key: 'q',
|
key: 'q',
|
||||||
route: 'centers/create'
|
route: 'centers/create',
|
||||||
|
id: 'create_center'
|
||||||
},
|
},
|
||||||
// Frequent Posting - ctrl + f
|
// Frequent Posting - ctrl + f
|
||||||
{
|
{
|
||||||
@@ -95,7 +91,8 @@ export class KeyboardShortcutsConfiguration {
|
|||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
altKey: false,
|
altKey: false,
|
||||||
key: 'f',
|
key: 'f',
|
||||||
route: 'accounting/journal-entries/frequent-postings'
|
route: 'accounting/journal-entries/frequent-postings',
|
||||||
|
id: 'frequent_posting'
|
||||||
},
|
},
|
||||||
// Closure Entries - ctrl + e
|
// Closure Entries - ctrl + e
|
||||||
{
|
{
|
||||||
@@ -104,7 +101,8 @@ export class KeyboardShortcutsConfiguration {
|
|||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
altKey: false,
|
altKey: false,
|
||||||
key: 'e',
|
key: 'e',
|
||||||
route: 'accounting/closing-entries'
|
route: 'accounting/closing-entries',
|
||||||
|
id: 'closure_entries'
|
||||||
},
|
},
|
||||||
// Journal Entry - ctrl + j
|
// Journal Entry - ctrl + j
|
||||||
{
|
{
|
||||||
@@ -113,7 +111,8 @@ export class KeyboardShortcutsConfiguration {
|
|||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
altKey: false,
|
altKey: false,
|
||||||
key: 'j',
|
key: 'j',
|
||||||
route: 'accounting/journal-entries/create'
|
route: 'accounting/journal-entries/create',
|
||||||
|
id: 'journal_entry'
|
||||||
},
|
},
|
||||||
// Reports - ctrl + r
|
// Reports - ctrl + r
|
||||||
{
|
{
|
||||||
@@ -122,7 +121,8 @@ export class KeyboardShortcutsConfiguration {
|
|||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
altKey: false,
|
altKey: false,
|
||||||
key: 'r',
|
key: 'r',
|
||||||
route: 'reports'
|
route: 'reports',
|
||||||
|
id: 'reports_page'
|
||||||
},
|
},
|
||||||
// Accounting - ctrl + a
|
// Accounting - ctrl + a
|
||||||
{
|
{
|
||||||
@@ -131,7 +131,8 @@ export class KeyboardShortcutsConfiguration {
|
|||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
altKey: true,
|
altKey: true,
|
||||||
key: 'a',
|
key: 'a',
|
||||||
route: 'accounting'
|
route: 'accounting',
|
||||||
|
id: 'accounting_page'
|
||||||
},
|
},
|
||||||
// Save/Submit Forms - ctrl + s
|
// Save/Submit Forms - ctrl + s
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,355 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="am-container">
|
|
||||||
<div class="am-header">
|
|
||||||
<button mat-stroked-button [routerLink]="['../']" class="am-back">
|
|
||||||
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Organization' | translate }}
|
|
||||||
</button>
|
|
||||||
<h2 class="am-header__title">Agent Master Entry</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="am-card">
|
|
||||||
<form [formGroup]="agentForm">
|
|
||||||
<div class="am-section">
|
|
||||||
<div class="am-section__header">
|
|
||||||
<i class="fas fa-id-card"></i>
|
|
||||||
<h3>Agent Details</h3>
|
|
||||||
</div>
|
|
||||||
<div class="am-grid">
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Client ID</mat-label>
|
|
||||||
<input matInput required formControlName="clientId" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent Code</mat-label>
|
|
||||||
<input matInput formControlName="agentCode" placeholder="Auto-generated" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field am-field--wide">
|
|
||||||
<mat-label>Agent Name ID</mat-label>
|
|
||||||
<input matInput required formControlName="agentNameId" placeholder="Person or institution name" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field am-field--wide">
|
|
||||||
<mat-label>Agent Address (Line 1)</mat-label>
|
|
||||||
<input matInput required formControlName="agentAddress1" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field am-field--wide">
|
|
||||||
<mat-label>Agent Address (Line 2)</mat-label>
|
|
||||||
<input matInput formControlName="agentAddress2" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent City</mat-label>
|
|
||||||
<input matInput required formControlName="agentCity" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent PinCode</mat-label>
|
|
||||||
<input matInput required formControlName="agentPinCode" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent Telephone</mat-label>
|
|
||||||
<input matInput formControlName="agentTelephone" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Home Branch</mat-label>
|
|
||||||
<input matInput required formControlName="homeBranch" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="am-section">
|
|
||||||
<div class="am-section__header">
|
|
||||||
<i class="fas fa-percent"></i>
|
|
||||||
<h3>Collection & Commission Settings</h3>
|
|
||||||
</div>
|
|
||||||
<div class="am-grid">
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Com. Calculation</mat-label>
|
|
||||||
<mat-select required formControlName="commissionCalculation">
|
|
||||||
<mat-option value="Yes">Yes</mat-option>
|
|
||||||
<mat-option value="No">No</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>TDS</mat-label>
|
|
||||||
<mat-select required formControlName="tdsApplicable">
|
|
||||||
<mat-option value="Yes">Yes</mat-option>
|
|
||||||
<mat-option value="No">No</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>TDS %</mat-label>
|
|
||||||
<input matInput type="number" formControlName="tdsPercent" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Cur.TDS</mat-label>
|
|
||||||
<input matInput formControlName="currentTds" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>TDS Amt</mat-label>
|
|
||||||
<input matInput formControlName="tdsAmount" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Limit</mat-label>
|
|
||||||
<input matInput type="number" formControlName="limit" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Machine Rent</mat-label>
|
|
||||||
<input matInput type="number" formControlName="machineRent" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="am-section">
|
|
||||||
<div class="am-section__header">
|
|
||||||
<i class="fas fa-piggy-bank"></i>
|
|
||||||
<h3>Settlement & Security Accounts</h3>
|
|
||||||
</div>
|
|
||||||
<div class="am-grid">
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Saving Branch</mat-label>
|
|
||||||
<input matInput required formControlName="savingBranch" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent Saving A/C Type</mat-label>
|
|
||||||
<mat-select required formControlName="agentSavingAccountType">
|
|
||||||
@for (accountType of agentSavingAccountTypes; track accountType) {
|
|
||||||
<mat-option [value]="accountType">{{ accountType }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent Saving A/C No</mat-label>
|
|
||||||
<input matInput required formControlName="agentSavingAccountNo" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Trf Branch</mat-label>
|
|
||||||
<input matInput formControlName="transferBranch" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Trf.Amt A/C Type</mat-label>
|
|
||||||
<input matInput formControlName="transferAmountAccountType" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Trf.Amt A/C No</mat-label>
|
|
||||||
<input matInput formControlName="transferAccountNo" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent Security Branch</mat-label>
|
|
||||||
<input matInput formControlName="agentSecurityBranch" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent Security A/C Type</mat-label>
|
|
||||||
<mat-select formControlName="agentSecurityAccountType">
|
|
||||||
@for (accountType of agentSavingAccountTypes; track accountType) {
|
|
||||||
<mat-option [value]="accountType">{{ accountType }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent Security A/C No</mat-label>
|
|
||||||
<input matInput formControlName="agentSecurityAccountNo" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent Opening Date</mat-label>
|
|
||||||
<input matInput required [matDatepicker]="openingDatePicker" formControlName="agentOpeningDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="openingDatePicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #openingDatePicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent Birth Date</mat-label>
|
|
||||||
<input
|
|
||||||
matInput
|
|
||||||
required
|
|
||||||
[max]="maxDate"
|
|
||||||
[matDatepicker]="birthDatePicker"
|
|
||||||
formControlName="agentBirthDate"
|
|
||||||
/>
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="birthDatePicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #birthDatePicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent Current Balance</mat-label>
|
|
||||||
<input matInput formControlName="agentCurrentBalance" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Agent Opening Balance</mat-label>
|
|
||||||
<input matInput formControlName="agentOpeningBalance" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="am-section">
|
|
||||||
<div class="am-section__header">
|
|
||||||
<i class="fas fa-clock"></i>
|
|
||||||
<h3>Collection Limit History</h3>
|
|
||||||
<button mat-stroked-button type="button" class="am-add-guarantor" (click)="addLimit()">
|
|
||||||
<i class="fas fa-plus"></i> Add Limit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (limits.length === 0) {
|
|
||||||
<p class="am-empty-guarantors">No limit periods added yet.</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
@for (limitRow of limits.controls; track limitRow; let i = $index) {
|
|
||||||
<div class="am-limit-row" [formGroup]="$any(limitRow)">
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>From Date</mat-label>
|
|
||||||
<input matInput required [matDatepicker]="fromPicker" formControlName="fromDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="fromPicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #fromPicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>To Date</mat-label>
|
|
||||||
<input matInput required [matDatepicker]="toPicker" formControlName="toDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="toPicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #toPicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Limit</mat-label>
|
|
||||||
<input matInput required type="number" formControlName="limit" />
|
|
||||||
</mat-form-field>
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
type="button"
|
|
||||||
class="am-remove-guarantor"
|
|
||||||
(click)="removeLimit(i)"
|
|
||||||
matTooltip="Remove limit period"
|
|
||||||
>
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="am-section">
|
|
||||||
<div class="am-section__header">
|
|
||||||
<i class="fas fa-user-shield"></i>
|
|
||||||
<h3>Guarantor's Detail</h3>
|
|
||||||
<button mat-stroked-button type="button" class="am-add-guarantor" (click)="addGuarantor()">
|
|
||||||
<i class="fas fa-plus"></i> Add Guarantor
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (guarantors.length === 0) {
|
|
||||||
<p class="am-empty-guarantors">No guarantors added yet.</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
@for (guarantor of guarantors.controls; track guarantor; let i = $index) {
|
|
||||||
<div class="am-guarantor-row" [formGroup]="$any(guarantor)">
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Client ID</mat-label>
|
|
||||||
<input matInput required formControlName="clientId" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Guarantor Name</mat-label>
|
|
||||||
<input matInput required formControlName="guarantorName" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Address 1</mat-label>
|
|
||||||
<input matInput required formControlName="address1" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Address 2</mat-label>
|
|
||||||
<input matInput formControlName="address2" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>City</mat-label>
|
|
||||||
<input matInput required formControlName="city" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Pin Code</mat-label>
|
|
||||||
<input matInput required formControlName="pinCode" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Telephone</mat-label>
|
|
||||||
<input matInput formControlName="telephone" />
|
|
||||||
</mat-form-field>
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
type="button"
|
|
||||||
class="am-remove-guarantor"
|
|
||||||
(click)="removeGuarantor(i)"
|
|
||||||
matTooltip="Remove guarantor"
|
|
||||||
>
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="am-section">
|
|
||||||
<div class="am-section__header">
|
|
||||||
<i class="fas fa-wallet"></i>
|
|
||||||
<h3>Deductions & Deposits</h3>
|
|
||||||
</div>
|
|
||||||
<div class="am-grid">
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Comm</mat-label>
|
|
||||||
<input matInput formControlName="commission" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Other Comm</mat-label>
|
|
||||||
<input matInput type="number" formControlName="otherCommission" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Ag.Deposit</mat-label>
|
|
||||||
<input matInput type="number" formControlName="agentDeposit" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Adv.Festival</mat-label>
|
|
||||||
<input matInput type="number" formControlName="festivalAdvance" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="am-field">
|
|
||||||
<mat-label>Other Deduction</mat-label>
|
|
||||||
<input matInput type="number" formControlName="otherDeduction" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="am-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-raised-button color="primary" type="button" [disabled]="agentForm.invalid" (click)="save()">
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$primary: #0f766e;
|
|
||||||
|
|
||||||
.am-container {
|
|
||||||
max-width: 1040px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-back {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-card {
|
|
||||||
background: var(--md-sys-color-surface, #fff);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
|
||||||
padding: 8px 24px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-section {
|
|
||||||
padding: 20px 0;
|
|
||||||
border-bottom: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 8px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-field {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--wide {
|
|
||||||
grid-column: span 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-add-guarantor {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-empty-guarantors {
|
|
||||||
color: rgb(0 0 0 / 50%);
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 0 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-guarantor-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(7, 1fr) auto;
|
|
||||||
gap: 8px 12px;
|
|
||||||
align-items: start;
|
|
||||||
padding: 10px 0;
|
|
||||||
border-top: 1px dashed rgb(0 0 0 / 10%);
|
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-limit-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr) auto;
|
|
||||||
gap: 8px 12px;
|
|
||||||
align-items: start;
|
|
||||||
padding: 10px 0;
|
|
||||||
border-top: 1px dashed rgb(0 0 0 / 10%);
|
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-remove-guarantor {
|
|
||||||
color: #b91c1c;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-action-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding-top: 16px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 900px) {
|
|
||||||
.am-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.am-guarantor-row,
|
|
||||||
.am-limit-row {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,381 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { Component, inject } from '@angular/core';
|
|
||||||
import { UntypedFormBuilder, UntypedFormGroup, UntypedFormArray, Validators } from '@angular/forms';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
/** Custom Services */
|
|
||||||
import { OrganizationService } from 'app/organization/organization.service';
|
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
|
||||||
import { Dates } from 'app/core/utils/dates';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Agent Master Entry component.
|
|
||||||
*
|
|
||||||
* A DDS collection agent — either a person or an institution — registered
|
|
||||||
* with its own settlement savings account and one or more guarantors. As of
|
|
||||||
* 2026-08-24 the Agent also does the daily collection work directly (the
|
|
||||||
* SubAgent tier was removed): it carries the collection-limit history,
|
|
||||||
* TDS/commission settings and security account SubAgent used to own.
|
|
||||||
*
|
|
||||||
* Backed by its own DDS Agent API (`/dds/agents`) - independent of Staff/
|
|
||||||
* Client CRUD, per the DDS agent/commission hierarchy being genuinely new
|
|
||||||
* feature work.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-agent-master',
|
|
||||||
templateUrl: './agent-master.component.html',
|
|
||||||
styleUrls: ['./agent-master.component.scss'],
|
|
||||||
imports: [...STANDALONE_SHARED_IMPORTS]
|
|
||||||
})
|
|
||||||
export class AgentMasterComponent {
|
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
|
||||||
private router = inject(Router);
|
|
||||||
private organizationService = inject(OrganizationService);
|
|
||||||
private settingsService = inject(SettingsService);
|
|
||||||
private dateUtils = inject(Dates);
|
|
||||||
|
|
||||||
maxDate = new Date();
|
|
||||||
agentId: number | null = null;
|
|
||||||
|
|
||||||
agentSavingAccountTypes = [
|
|
||||||
'Savings Deposit Individual A/C',
|
|
||||||
'Savings Deposit Joint A/C',
|
|
||||||
'Current Account'
|
|
||||||
];
|
|
||||||
|
|
||||||
agentForm: UntypedFormGroup = this.formBuilder.group({
|
|
||||||
clientId: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentCode: [
|
|
||||||
{
|
|
||||||
value: '',
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
agentNameId: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentAddress1: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentAddress2: [''],
|
|
||||||
agentCity: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentPinCode: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentTelephone: [''],
|
|
||||||
agentSavingAccountType: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentSavingAccountNo: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentOpeningDate: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentBirthDate: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentCurrentBalance: [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
agentOpeningBalance: [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
homeBranch: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
tdsApplicable: [
|
|
||||||
'No',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
tdsPercent: [0],
|
|
||||||
currentTds: [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
tdsAmount: [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
savingBranch: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
transferBranch: [''],
|
|
||||||
transferAmountAccountType: [''],
|
|
||||||
transferAccountNo: [''],
|
|
||||||
commissionCalculation: [
|
|
||||||
'Yes',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
limit: [0],
|
|
||||||
machineRent: [0],
|
|
||||||
agentSecurityBranch: [''],
|
|
||||||
agentSecurityAccountType: [''],
|
|
||||||
agentSecurityAccountNo: [''],
|
|
||||||
commission: [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
otherCommission: [0],
|
|
||||||
agentDeposit: [0],
|
|
||||||
festivalAdvance: [0],
|
|
||||||
otherDeduction: [0],
|
|
||||||
limits: this.formBuilder.array([]),
|
|
||||||
guarantors: this.formBuilder.array([])
|
|
||||||
});
|
|
||||||
|
|
||||||
get limits(): UntypedFormArray {
|
|
||||||
return this.agentForm.get('limits') as UntypedFormArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
get guarantors(): UntypedFormArray {
|
|
||||||
return this.agentForm.get('guarantors') as UntypedFormArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildLimitGroup(): UntypedFormGroup {
|
|
||||||
return this.formBuilder.group({
|
|
||||||
fromDate: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
toDate: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
limit: [
|
|
||||||
0,
|
|
||||||
Validators.required
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildGuarantorGroup(): UntypedFormGroup {
|
|
||||||
return this.formBuilder.group({
|
|
||||||
clientId: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
guarantorName: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
address1: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
address2: [''],
|
|
||||||
city: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
pinCode: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
telephone: ['']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addLimit(): void {
|
|
||||||
this.limits.push(this.buildLimitGroup());
|
|
||||||
}
|
|
||||||
|
|
||||||
removeLimit(index: number): void {
|
|
||||||
this.limits.removeAt(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
addGuarantor(): void {
|
|
||||||
this.guarantors.push(this.buildGuarantorGroup());
|
|
||||||
}
|
|
||||||
|
|
||||||
removeGuarantor(index: number): void {
|
|
||||||
this.guarantors.removeAt(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
retrieve(): void {
|
|
||||||
const clientId = this.agentForm.get('clientId')?.value;
|
|
||||||
if (!clientId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.organizationService.getAgents().subscribe((agents: any[]) => {
|
|
||||||
const match = (agents || []).find((agent) => String(agent.clientId) === String(clientId));
|
|
||||||
if (match) {
|
|
||||||
this.patchFormFromAgent(match);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel(): void {
|
|
||||||
this.agentId = null;
|
|
||||||
this.agentForm.reset();
|
|
||||||
this.agentForm.patchValue({
|
|
||||||
agentCurrentBalance: 0,
|
|
||||||
agentOpeningBalance: 0,
|
|
||||||
tdsApplicable: 'No',
|
|
||||||
commissionCalculation: 'Yes',
|
|
||||||
currentTds: 0,
|
|
||||||
tdsAmount: 0,
|
|
||||||
commission: 0
|
|
||||||
});
|
|
||||||
this.limits.clear();
|
|
||||||
this.guarantors.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(): void {
|
|
||||||
this.router.navigate(['/organization']);
|
|
||||||
}
|
|
||||||
|
|
||||||
save(): void {
|
|
||||||
const raw = this.agentForm.getRawValue();
|
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
|
||||||
const locale = this.settingsService.language.code;
|
|
||||||
|
|
||||||
const payload: any = {
|
|
||||||
clientId: Number(raw.clientId),
|
|
||||||
agentNameId: raw.agentNameId,
|
|
||||||
agentAddress1: raw.agentAddress1,
|
|
||||||
agentAddress2: raw.agentAddress2 || undefined,
|
|
||||||
agentCity: raw.agentCity,
|
|
||||||
agentPinCode: raw.agentPinCode,
|
|
||||||
agentTelephone: raw.agentTelephone || undefined,
|
|
||||||
agentSavingAccountType: raw.agentSavingAccountType,
|
|
||||||
agentSavingAccountNo: raw.agentSavingAccountNo,
|
|
||||||
agentOpeningDate: this.dateUtils.formatDate(raw.agentOpeningDate, dateFormat),
|
|
||||||
agentBirthDate: this.dateUtils.formatDate(raw.agentBirthDate, dateFormat),
|
|
||||||
agentOpeningBalance: raw.agentOpeningBalance || 0,
|
|
||||||
homeBranch: raw.homeBranch,
|
|
||||||
tdsApplicable: raw.tdsApplicable === 'Yes',
|
|
||||||
tdsPercent: raw.tdsPercent || undefined,
|
|
||||||
savingBranch: raw.savingBranch,
|
|
||||||
transferBranch: raw.transferBranch || undefined,
|
|
||||||
transferAmountAccountType: raw.transferAmountAccountType || undefined,
|
|
||||||
transferAccountNo: raw.transferAccountNo || undefined,
|
|
||||||
commissionCalculation: raw.commissionCalculation === 'Yes',
|
|
||||||
limit: raw.limit || undefined,
|
|
||||||
machineRent: raw.machineRent || undefined,
|
|
||||||
agentSecurityBranch: raw.agentSecurityBranch || undefined,
|
|
||||||
agentSecurityAccountType: raw.agentSecurityAccountType || undefined,
|
|
||||||
agentSecurityAccountNo: raw.agentSecurityAccountNo || undefined,
|
|
||||||
otherCommission: raw.otherCommission || undefined,
|
|
||||||
agentDeposit: raw.agentDeposit || undefined,
|
|
||||||
festivalAdvance: raw.festivalAdvance || undefined,
|
|
||||||
otherDeduction: raw.otherDeduction || undefined,
|
|
||||||
dateFormat,
|
|
||||||
locale,
|
|
||||||
limits: (raw.limits || []).map((limit: any) => ({
|
|
||||||
fromDate: this.dateUtils.getDate(limit.fromDate),
|
|
||||||
toDate: this.dateUtils.getDate(limit.toDate),
|
|
||||||
limit: limit.limit
|
|
||||||
})),
|
|
||||||
guarantors: (raw.guarantors || []).map((guarantor: any) => ({
|
|
||||||
clientId: Number(guarantor.clientId),
|
|
||||||
guarantorName: guarantor.guarantorName,
|
|
||||||
address1: guarantor.address1,
|
|
||||||
address2: guarantor.address2 || undefined,
|
|
||||||
city: guarantor.city,
|
|
||||||
pinCode: guarantor.pinCode,
|
|
||||||
telephone: guarantor.telephone || undefined
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.agentId) {
|
|
||||||
this.organizationService.updateAgent(this.agentId, payload).subscribe(() => this.exit());
|
|
||||||
} else {
|
|
||||||
this.organizationService.createAgent(payload).subscribe(() => this.exit());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private patchFormFromAgent(agent: any): void {
|
|
||||||
this.agentId = agent.id;
|
|
||||||
this.agentForm.patchValue({
|
|
||||||
clientId: agent.clientId,
|
|
||||||
agentCode: agent.agentCode,
|
|
||||||
agentNameId: agent.agentNameId,
|
|
||||||
agentAddress1: agent.agentAddress1,
|
|
||||||
agentAddress2: agent.agentAddress2,
|
|
||||||
agentCity: agent.agentCity,
|
|
||||||
agentPinCode: agent.agentPinCode,
|
|
||||||
agentTelephone: agent.agentTelephone,
|
|
||||||
agentSavingAccountType: agent.agentSavingAccountType,
|
|
||||||
agentSavingAccountNo: agent.agentSavingAccountNo,
|
|
||||||
agentOpeningDate: agent.agentOpeningDate ? new Date(agent.agentOpeningDate) : null,
|
|
||||||
agentBirthDate: agent.agentBirthDate ? new Date(agent.agentBirthDate) : null,
|
|
||||||
agentCurrentBalance: agent.agentCurrentBalance,
|
|
||||||
agentOpeningBalance: agent.agentOpeningBalance,
|
|
||||||
homeBranch: agent.homeBranch,
|
|
||||||
tdsApplicable: agent.tdsApplicable ? 'Yes' : 'No',
|
|
||||||
tdsPercent: agent.tdsPercent,
|
|
||||||
currentTds: agent.currentTds,
|
|
||||||
tdsAmount: agent.tdsAmount,
|
|
||||||
savingBranch: agent.savingBranch,
|
|
||||||
transferBranch: agent.transferBranch,
|
|
||||||
transferAmountAccountType: agent.transferAmountAccountType,
|
|
||||||
transferAccountNo: agent.transferAccountNo,
|
|
||||||
commissionCalculation: agent.commissionCalculation ? 'Yes' : 'No',
|
|
||||||
limit: agent.limit,
|
|
||||||
machineRent: agent.machineRent,
|
|
||||||
agentSecurityBranch: agent.agentSecurityBranch,
|
|
||||||
agentSecurityAccountType: agent.agentSecurityAccountType,
|
|
||||||
agentSecurityAccountNo: agent.agentSecurityAccountNo,
|
|
||||||
commission: agent.commission,
|
|
||||||
otherCommission: agent.otherCommission,
|
|
||||||
agentDeposit: agent.agentDeposit,
|
|
||||||
festivalAdvance: agent.festivalAdvance,
|
|
||||||
otherDeduction: agent.otherDeduction
|
|
||||||
});
|
|
||||||
|
|
||||||
this.limits.clear();
|
|
||||||
(agent.limits || []).forEach((limit: any) => {
|
|
||||||
const group = this.buildLimitGroup();
|
|
||||||
group.patchValue({
|
|
||||||
fromDate: limit.fromDate ? new Date(limit.fromDate) : null,
|
|
||||||
toDate: limit.toDate ? new Date(limit.toDate) : null,
|
|
||||||
limit: limit.limit
|
|
||||||
});
|
|
||||||
this.limits.push(group);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.guarantors.clear();
|
|
||||||
(agent.guarantors || []).forEach((guarantor: any) => {
|
|
||||||
const group = this.buildGuarantorGroup();
|
|
||||||
group.patchValue(guarantor);
|
|
||||||
this.guarantors.push(group);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="csm-container">
|
|
||||||
<div class="csm-header">
|
|
||||||
<button mat-stroked-button [routerLink]="['../']" class="csm-back">
|
|
||||||
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Organization' | translate }}
|
|
||||||
</button>
|
|
||||||
<h2 class="csm-header__title">Commission Slab Master</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="csm-card">
|
|
||||||
<table class="csm-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Slab Type</th>
|
|
||||||
<th>Slab From</th>
|
|
||||||
<th>Slab To</th>
|
|
||||||
<th>Comm Per</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (slab of slabs; track slab; let i = $index) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ slab.slabType }}</td>
|
|
||||||
<td>{{ slab.slabFrom }}</td>
|
|
||||||
<td>{{ slab.slabTo }}</td>
|
|
||||||
<td>{{ slab.commissionPercent }}%</td>
|
|
||||||
<td class="csm-row-actions">
|
|
||||||
<button mat-icon-button type="button" (click)="removeSlab(i)" matTooltip="Remove slab">
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
|
|
||||||
<tr [formGroup]="newSlabForm" class="csm-new-row">
|
|
||||||
<td>
|
|
||||||
<mat-form-field appearance="outline" class="csm-inline-field">
|
|
||||||
<mat-select required formControlName="slabType" placeholder="Slab Type">
|
|
||||||
@for (type of slabTypes; track type) {
|
|
||||||
<mat-option [value]="type">{{ type }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<mat-form-field appearance="outline" class="csm-inline-field">
|
|
||||||
<input matInput required type="number" formControlName="slabFrom" />
|
|
||||||
</mat-form-field>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<mat-form-field appearance="outline" class="csm-inline-field">
|
|
||||||
<input matInput required type="number" formControlName="slabTo" />
|
|
||||||
</mat-form-field>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<mat-form-field appearance="outline" class="csm-inline-field">
|
|
||||||
<input matInput required type="number" formControlName="commissionPercent" />
|
|
||||||
</mat-form-field>
|
|
||||||
</td>
|
|
||||||
<td class="csm-row-actions">
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
type="button"
|
|
||||||
[disabled]="newSlabForm.invalid"
|
|
||||||
(click)="addSlab()"
|
|
||||||
matTooltip="Add slab"
|
|
||||||
>
|
|
||||||
<i class="fas fa-plus"></i>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
@if (slabs.length === 0) {
|
|
||||||
<p class="csm-empty">No commission slabs defined yet — add one using the row above.</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="csm-action-bar">
|
|
||||||
<button mat-raised-button type="button" (click)="exit()">Exit</button>
|
|
||||||
<button mat-raised-button color="primary" type="button" (click)="save()">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$primary: #0f766e;
|
|
||||||
|
|
||||||
.csm-container {
|
|
||||||
max-width: 760px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.csm-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.csm-back {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.csm-card {
|
|
||||||
background: var(--md-sys-color-surface, #fff);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
|
||||||
padding: 16px 24px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.csm-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: left;
|
|
||||||
font-size: 13px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
color: rgb(0 0 0 / 55%);
|
|
||||||
padding: 8px 10px;
|
|
||||||
border-bottom: 2px solid rgb(0 0 0 / 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 6px 10px;
|
|
||||||
border-bottom: 1px solid rgb(0 0 0 / 6%);
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.csm-inline-field {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
::ng-deep .mat-mdc-text-field-wrapper {
|
|
||||||
height: 44px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.csm-row-actions {
|
|
||||||
text-align: center;
|
|
||||||
width: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.csm-new-row {
|
|
||||||
background: rgb(15 118 110 / 4%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.csm-empty {
|
|
||||||
color: rgb(0 0 0 / 50%);
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 12px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.csm-action-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding-top: 16px;
|
|
||||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { Component, OnInit, inject } from '@angular/core';
|
|
||||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
/** Custom Services */
|
|
||||||
import { OrganizationService } from 'app/organization/organization.service';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commission Slab Master component.
|
|
||||||
*
|
|
||||||
* Two kinds of slab: ACCOUNT (commission percentage on collection volume)
|
|
||||||
* and COMMISSION (a senior-agent override on another agent's commission -
|
|
||||||
* deferred: there is no agent hierarchy modelled yet, see the DDS punch
|
|
||||||
* list). Modelled directly on the legacy Commission Slab Master screen this
|
|
||||||
* replaces.
|
|
||||||
*
|
|
||||||
* Backed by its own DDS Commission Slab API (`/dds/commissionslabs`) - an
|
|
||||||
* append-only chart, same as the legacy screen: existing rows load on open,
|
|
||||||
* newly-added rows post on Save, and there is no edit/delete of a
|
|
||||||
* previously-saved row.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-commission-slab-master',
|
|
||||||
templateUrl: './commission-slab-master.component.html',
|
|
||||||
styleUrls: ['./commission-slab-master.component.scss'],
|
|
||||||
imports: [...STANDALONE_SHARED_IMPORTS]
|
|
||||||
})
|
|
||||||
export class CommissionSlabMasterComponent implements OnInit {
|
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
|
||||||
private router = inject(Router);
|
|
||||||
private organizationService = inject(OrganizationService);
|
|
||||||
|
|
||||||
private static readonly SLAB_TYPE_CODES: Record<string, string> = {
|
|
||||||
Account: 'ACCOUNT',
|
|
||||||
Commission: 'COMMISSION'
|
|
||||||
};
|
|
||||||
private static readonly SLAB_TYPE_LABELS: Record<string, string> = {
|
|
||||||
ACCOUNT: 'Account',
|
|
||||||
COMMISSION: 'Commission'
|
|
||||||
};
|
|
||||||
|
|
||||||
slabTypes = [
|
|
||||||
'Account',
|
|
||||||
'Commission'
|
|
||||||
];
|
|
||||||
|
|
||||||
displayedColumns: string[] = [
|
|
||||||
'slabType',
|
|
||||||
'slabFrom',
|
|
||||||
'slabTo',
|
|
||||||
'commissionPercent',
|
|
||||||
'actions'
|
|
||||||
];
|
|
||||||
|
|
||||||
slabs: Array<{ id?: number; slabType: string; slabFrom: number; slabTo: number; commissionPercent: number }> = [];
|
|
||||||
|
|
||||||
newSlabForm: UntypedFormGroup = this.formBuilder.group({
|
|
||||||
slabType: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
slabFrom: [
|
|
||||||
0,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
slabTo: [
|
|
||||||
0,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
commissionPercent: [
|
|
||||||
0,
|
|
||||||
Validators.required
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.loadSlabs();
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadSlabs(): void {
|
|
||||||
this.organizationService.getCommissionSlabs().subscribe((slabs: any[]) => {
|
|
||||||
this.slabs = (slabs || []).map((slab) => ({
|
|
||||||
id: slab.id,
|
|
||||||
slabType: CommissionSlabMasterComponent.SLAB_TYPE_LABELS[slab.slabType] || slab.slabType,
|
|
||||||
slabFrom: slab.slabFrom,
|
|
||||||
slabTo: slab.slabTo,
|
|
||||||
commissionPercent: slab.commissionPercent
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addSlab(): void {
|
|
||||||
if (this.newSlabForm.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.slabs.push(this.newSlabForm.getRawValue());
|
|
||||||
this.newSlabForm.reset({
|
|
||||||
slabType: '',
|
|
||||||
slabFrom: 0,
|
|
||||||
slabTo: 0,
|
|
||||||
commissionPercent: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
removeSlab(index: number): void {
|
|
||||||
this.slabs.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(): void {
|
|
||||||
this.router.navigate(['/organization']);
|
|
||||||
}
|
|
||||||
|
|
||||||
save(): void {
|
|
||||||
const newSlabs = this.slabs.filter((slab) => !slab.id);
|
|
||||||
if (newSlabs.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = newSlabs.map((slab) => ({
|
|
||||||
slabType: CommissionSlabMasterComponent.SLAB_TYPE_CODES[slab.slabType] || slab.slabType,
|
|
||||||
slabFrom: slab.slabFrom,
|
|
||||||
slabTo: slab.slabTo,
|
|
||||||
commissionPercent: slab.commissionPercent
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.organizationService.createCommissionSlabs(payload).subscribe(() => this.loadSlabs());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="bp-container">
|
|
||||||
<div class="bp-header">
|
|
||||||
<button mat-stroked-button [routerLink]="['../']" class="bp-back">
|
|
||||||
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Organization' | translate }}
|
|
||||||
</button>
|
|
||||||
<h2 class="bp-header__title">DDS Interest & Commission Batch Processing</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bp-card">
|
|
||||||
<form [formGroup]="batchForm">
|
|
||||||
<div class="bp-grid">
|
|
||||||
<mat-form-field appearance="outline" class="bp-field bp-field--wide">
|
|
||||||
<mat-label>Job Type</mat-label>
|
|
||||||
<mat-select required formControlName="jobType">
|
|
||||||
@for (type of jobTypes; track type) {
|
|
||||||
<mat-option [value]="type">{{ type }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="bp-field">
|
|
||||||
<mat-label>Branch</mat-label>
|
|
||||||
<mat-select required formControlName="branchScope">
|
|
||||||
@for (scope of branchScopes; track scope) {
|
|
||||||
<mat-option [value]="scope">{{ scope }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
@if (isBranchSelected) {
|
|
||||||
<div class="bp-field bp-field--wide bp-branch-checklist">
|
|
||||||
@for (office of offices; track office.id) {
|
|
||||||
<mat-checkbox [checked]="selectedOfficeNames.has(office.name)" (change)="toggleOffice(office.name)">
|
|
||||||
{{ office.name }}
|
|
||||||
</mat-checkbox>
|
|
||||||
}
|
|
||||||
@if (offices.length === 0) {
|
|
||||||
<span class="bp-empty">Loading branches...</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="bp-field">
|
|
||||||
<mat-label>{{ isPostingJob ? 'Posting Date' : 'As-On Date' }}</mat-label>
|
|
||||||
<input matInput required [matDatepicker]="jobDatePicker" formControlName="jobDate" />
|
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="jobDatePicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #jobDatePicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="bp-field">
|
|
||||||
<mat-label>Agent From</mat-label>
|
|
||||||
<input matInput required type="number" formControlName="agentFrom" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="bp-field">
|
|
||||||
<mat-label>Agent To</mat-label>
|
|
||||||
<input matInput required type="number" formControlName="agentTo" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="bp-field">
|
|
||||||
<mat-label>Party From</mat-label>
|
|
||||||
<input matInput required type="number" formControlName="partyFrom" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="bp-field">
|
|
||||||
<mat-label>Party To</mat-label>
|
|
||||||
<input matInput required type="number" formControlName="partyTo" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bp-callout">
|
|
||||||
<i class="fas fa-info-circle"></i>
|
|
||||||
<span>
|
|
||||||
<strong>Generate</strong> runs a preview only — nothing is applied to any account. <strong>Post</strong> is a
|
|
||||||
separate, explicit step that commits the previewed amounts.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (hasGenerated) {
|
|
||||||
<div class="bp-section">
|
|
||||||
<div class="bp-section__header">
|
|
||||||
<i class="fas fa-table"></i>
|
|
||||||
<h3>Preview</h3>
|
|
||||||
</div>
|
|
||||||
@if (previewRows.length === 0) {
|
|
||||||
<p class="bp-empty">
|
|
||||||
No calculation preview available yet — this appears once the backend batch-job endpoint for "{{
|
|
||||||
batchForm.get('jobType').value
|
|
||||||
}}" is connected.
|
|
||||||
</p>
|
|
||||||
} @else {
|
|
||||||
<table class="bp-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Agent</th>
|
|
||||||
<th>Party</th>
|
|
||||||
<th>Amount</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (row of previewRows; track row) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ row.agent }}</td>
|
|
||||||
<td>{{ row.party }}</td>
|
|
||||||
<td>{{ row.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="bp-action-bar">
|
|
||||||
<button mat-raised-button type="button" (click)="exit()">Exit</button>
|
|
||||||
<button mat-stroked-button type="button" [disabled]="batchForm.invalid" (click)="generate()">
|
|
||||||
<i class="fas fa-play" style="margin-right: 6px"></i> Generate
|
|
||||||
</button>
|
|
||||||
<button mat-raised-button color="primary" type="button" [disabled]="!hasGenerated" (click)="post()">
|
|
||||||
<i class="fas fa-check" style="margin-right: 6px"></i> Post
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$primary: #0f766e;
|
|
||||||
|
|
||||||
.bp-container {
|
|
||||||
max-width: 900px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp-back {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp-card {
|
|
||||||
background: var(--md-sys-color-surface, #fff);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
|
||||||
padding: 20px 24px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 8px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp-field {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--wide {
|
|
||||||
grid-column: span 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp-branch-checklist {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px 20px;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp-callout {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: flex-start;
|
|
||||||
background: rgb(15 118 110 / 6%);
|
|
||||||
border-left: 3px solid $primary;
|
|
||||||
border-radius: 0 8px 8px 0;
|
|
||||||
padding: 12px 16px;
|
|
||||||
margin: 16px 0;
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $primary;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp-section {
|
|
||||||
padding: 16px 0;
|
|
||||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp-empty {
|
|
||||||
color: rgb(0 0 0 / 50%);
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: left;
|
|
||||||
font-size: 13px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
color: rgb(0 0 0 / 55%);
|
|
||||||
padding: 8px 10px;
|
|
||||||
border-bottom: 2px solid rgb(0 0 0 / 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 8px 10px;
|
|
||||||
border-bottom: 1px solid rgb(0 0 0 / 6%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp-action-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding-top: 16px;
|
|
||||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 900px) {
|
|
||||||
.bp-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp-field--wide {
|
|
||||||
grid-column: span 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { Component, OnInit, inject } from '@angular/core';
|
|
||||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
/** Custom Services */
|
|
||||||
import { OrganizationService } from 'app/organization/organization.service';
|
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
|
||||||
import { Dates } from 'app/core/utils/dates';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DDS Interest & Commission Batch Processing component.
|
|
||||||
*
|
|
||||||
* Six batch job types, each following the same deliberate two-step
|
|
||||||
* pattern from the legacy system: Calculate first (a dry-run preview),
|
|
||||||
* then Post as a separate, explicit confirmation — so a wrong calculation
|
|
||||||
* is never applied by accident. Modelled on the legacy Interest
|
|
||||||
* Calculation screen this replaces.
|
|
||||||
*
|
|
||||||
* Backed by the DDS Batch Run API (`/dds/batchruns`). Only the three
|
|
||||||
* "Calculation" job types can actually be generated - the three "Posting"
|
|
||||||
* entries exist in the dropdown for parity with the legacy menu, but Post
|
|
||||||
* always applies whatever run was most recently generated in this session,
|
|
||||||
* regardless of which dropdown entry is currently selected (matching how
|
|
||||||
* the Post button here is only ever gated on hasGenerated, not on jobType).
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-dds-batch-processing',
|
|
||||||
templateUrl: './dds-batch-processing.component.html',
|
|
||||||
styleUrls: ['./dds-batch-processing.component.scss'],
|
|
||||||
imports: [...STANDALONE_SHARED_IMPORTS]
|
|
||||||
})
|
|
||||||
export class DdsBatchProcessingComponent implements OnInit {
|
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
|
||||||
private router = inject(Router);
|
|
||||||
private organizationService = inject(OrganizationService);
|
|
||||||
private settingsService = inject(SettingsService);
|
|
||||||
private dateUtils = inject(Dates);
|
|
||||||
|
|
||||||
private static readonly JOB_TYPE_CODES: Record<string, string> = {
|
|
||||||
'Interest Calculation': 'INTEREST_CALCULATION',
|
|
||||||
'OD Interest Calculation': 'OD_INTEREST_CALCULATION',
|
|
||||||
'Commission Calculation': 'COMMISSION_CALCULATION'
|
|
||||||
};
|
|
||||||
|
|
||||||
private currentBatchRunId: number | null = null;
|
|
||||||
|
|
||||||
offices: any[] = [];
|
|
||||||
selectedOfficeNames = new Set<string>();
|
|
||||||
|
|
||||||
jobTypes = [
|
|
||||||
'Interest Calculation',
|
|
||||||
'Interest Posting',
|
|
||||||
'OD Interest Calculation',
|
|
||||||
'OD Interest Posting',
|
|
||||||
'Commission Calculation',
|
|
||||||
'Commission Posting'
|
|
||||||
];
|
|
||||||
|
|
||||||
branchScopes = [
|
|
||||||
'All',
|
|
||||||
'Selected'
|
|
||||||
];
|
|
||||||
|
|
||||||
hasGenerated = false;
|
|
||||||
previewRows: Array<{ agent: string; party: string; amount: number }> = [];
|
|
||||||
|
|
||||||
batchForm: UntypedFormGroup = this.formBuilder.group({
|
|
||||||
jobType: [
|
|
||||||
'Interest Calculation',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
branchScope: [
|
|
||||||
'All',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
jobDate: [
|
|
||||||
new Date(),
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentFrom: [
|
|
||||||
1,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
agentTo: [
|
|
||||||
99999,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
partyFrom: [
|
|
||||||
1,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
partyTo: [
|
|
||||||
99999999,
|
|
||||||
Validators.required
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
get isPostingJob(): boolean {
|
|
||||||
return (this.batchForm.get('jobType').value as string).includes('Posting');
|
|
||||||
}
|
|
||||||
|
|
||||||
get isBranchSelected(): boolean {
|
|
||||||
return this.batchForm.get('branchScope')?.value === 'Selected';
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.organizationService.getOffices().subscribe((offices: any[]) => {
|
|
||||||
this.offices = offices || [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleOffice(officeName: string): void {
|
|
||||||
if (this.selectedOfficeNames.has(officeName)) {
|
|
||||||
this.selectedOfficeNames.delete(officeName);
|
|
||||||
} else {
|
|
||||||
this.selectedOfficeNames.add(officeName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
generate(): void {
|
|
||||||
const raw = this.batchForm.getRawValue();
|
|
||||||
const jobType = DdsBatchProcessingComponent.JOB_TYPE_CODES[raw.jobType];
|
|
||||||
|
|
||||||
if (!jobType) {
|
|
||||||
// A "Posting" entry was selected - there is nothing to calculate for
|
|
||||||
// it, only to post (see class-level note on how Post is wired).
|
|
||||||
this.hasGenerated = true;
|
|
||||||
this.previewRows = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
|
||||||
const payload: any = {
|
|
||||||
jobType,
|
|
||||||
branchScope: raw.branchScope,
|
|
||||||
jobDate: this.dateUtils.formatDate(raw.jobDate, dateFormat),
|
|
||||||
agentFrom: raw.agentFrom,
|
|
||||||
agentTo: raw.agentTo,
|
|
||||||
partyFrom: raw.partyFrom,
|
|
||||||
partyTo: raw.partyTo,
|
|
||||||
dateFormat,
|
|
||||||
locale: this.settingsService.language.code
|
|
||||||
};
|
|
||||||
|
|
||||||
if (raw.branchScope === 'Selected') {
|
|
||||||
payload.officeNames = Array.from(this.selectedOfficeNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.organizationService.generateDdsBatchRun(payload).subscribe((result: any) => {
|
|
||||||
this.currentBatchRunId = result.resourceId;
|
|
||||||
this.organizationService.getDdsBatchRun(this.currentBatchRunId).subscribe((batchRun: any) => {
|
|
||||||
this.previewRows = (batchRun.rows || []).map((row: any) => ({
|
|
||||||
agent: row.agentCode,
|
|
||||||
party: row.partyCode || '—',
|
|
||||||
amount: row.amount
|
|
||||||
}));
|
|
||||||
this.hasGenerated = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
post(): void {
|
|
||||||
if (!this.currentBatchRunId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.organizationService.postDdsBatchRun(this.currentBatchRunId).subscribe(() => {
|
|
||||||
this.currentBatchRunId = null;
|
|
||||||
this.hasGenerated = false;
|
|
||||||
this.previewRows = [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(): void {
|
|
||||||
this.router.navigate(['/organization']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,9 +17,6 @@ import { Route } from '../core/route/route.service';
|
|||||||
import { OrganizationComponent } from './organization.component';
|
import { OrganizationComponent } from './organization.component';
|
||||||
import { LoanProvisioningCriteriaComponent } from './loan-provisioning-criteria/loan-provisioning-criteria.component';
|
import { LoanProvisioningCriteriaComponent } from './loan-provisioning-criteria/loan-provisioning-criteria.component';
|
||||||
import { OfficesComponent } from './offices/offices.component';
|
import { OfficesComponent } from './offices/offices.component';
|
||||||
import { AgentMasterComponent } from './agent-master/agent-master.component';
|
|
||||||
import { CommissionSlabMasterComponent } from './commission-slab-master/commission-slab-master.component';
|
|
||||||
import { DdsBatchProcessingComponent } from './dds-batch-processing/dds-batch-processing.component';
|
|
||||||
import { EmployeesComponent } from './employees/employees.component';
|
import { EmployeesComponent } from './employees/employees.component';
|
||||||
import { CreateEmployeeComponent } from './employees/create-employee/create-employee.component';
|
import { CreateEmployeeComponent } from './employees/create-employee/create-employee.component';
|
||||||
import { ViewEmployeeComponent } from './employees/view-employee/view-employee.component';
|
import { ViewEmployeeComponent } from './employees/view-employee/view-employee.component';
|
||||||
@@ -240,21 +237,6 @@ const routes: Routes = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'agent-master',
|
|
||||||
component: AgentMasterComponent,
|
|
||||||
data: { title: 'Agent Master', breadcrumb: 'Agent Master' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'commission-slab-master',
|
|
||||||
component: CommissionSlabMasterComponent,
|
|
||||||
data: { title: 'Commission Slab Master', breadcrumb: 'Commission Slab Master' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'dds-batch-processing',
|
|
||||||
component: DdsBatchProcessingComponent,
|
|
||||||
data: { title: 'DDS Interest & Commission Batch', breadcrumb: 'DDS Batch Processing' }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'employees',
|
path: 'employees',
|
||||||
data: { title: 'Manage Employees', breadcrumb: 'Manage Employees' },
|
data: { title: 'Manage Employees', breadcrumb: 'Manage Employees' },
|
||||||
|
|||||||
@@ -90,47 +90,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div #agentMaster>
|
|
||||||
<a class="org-card" [routerLink]="['agent-master']">
|
|
||||||
<div class="org-card__icon org-card__icon--teal">
|
|
||||||
<i class="fas fa-user-tie"></i>
|
|
||||||
</div>
|
|
||||||
<div class="org-card__body">
|
|
||||||
<h3 class="org-card__title">Agent Master</h3>
|
|
||||||
<p class="org-card__desc">
|
|
||||||
Register Daily Deposit Scheme collection agents, their commission/TDS settings, limits and guarantors
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<i class="fas fa-chevron-right org-card__arrow"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div #commissionSlabMaster>
|
|
||||||
<a class="org-card" [routerLink]="['commission-slab-master']">
|
|
||||||
<div class="org-card__icon org-card__icon--teal">
|
|
||||||
<i class="fas fa-percentage"></i>
|
|
||||||
</div>
|
|
||||||
<div class="org-card__body">
|
|
||||||
<h3 class="org-card__title">Commission Slab Master</h3>
|
|
||||||
<p class="org-card__desc">Define collection-volume and override commission percentage slabs</p>
|
|
||||||
</div>
|
|
||||||
<i class="fas fa-chevron-right org-card__arrow"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div #ddsBatchProcessing>
|
|
||||||
<a class="org-card" [routerLink]="['dds-batch-processing']">
|
|
||||||
<div class="org-card__icon org-card__icon--teal">
|
|
||||||
<i class="fas fa-cogs"></i>
|
|
||||||
</div>
|
|
||||||
<div class="org-card__body">
|
|
||||||
<h3 class="org-card__title">DDS Interest & Commission Batch</h3>
|
|
||||||
<p class="org-card__desc">Calculate then post interest, OD interest, and commission for DDS accounts</p>
|
|
||||||
</div>
|
|
||||||
<i class="fas fa-chevron-right org-card__arrow"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Manage Loan Originators -->
|
<!-- Manage Loan Originators -->
|
||||||
<div #manageLoanOriginators>
|
<div #manageLoanOriginators>
|
||||||
<a class="org-card" [routerLink]="['manage-loan-originators']" *mifosxHasPermission="'READ_LOAN_ORIGINATOR'">
|
<a class="org-card" [routerLink]="['manage-loan-originators']" *mifosxHasPermission="'READ_LOAN_ORIGINATOR'">
|
||||||
|
|||||||
@@ -233,141 +233,6 @@ export class OrganizationService {
|
|||||||
return this.http.get('/currencies');
|
return this.http.get('/currencies');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Observable<any>} DDS Agents data
|
|
||||||
*/
|
|
||||||
getAgents(): Observable<any> {
|
|
||||||
return this.http.get('/dds/agents');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string | number} agentId Agent ID.
|
|
||||||
* @returns {Observable<any>} DDS Agent.
|
|
||||||
*/
|
|
||||||
getAgent(agentId: string | number): Observable<any> {
|
|
||||||
return this.http.get(`/dds/agents/${agentId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} agent Agent to be created.
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
createAgent(agent: any): Observable<any> {
|
|
||||||
return this.http.post('/dds/agents', agent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string | number} agentId Agent ID.
|
|
||||||
* @param {any} agent Agent changes to apply.
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
updateAgent(agentId: string | number, agent: any): Observable<any> {
|
|
||||||
return this.http.put(`/dds/agents/${agentId}`, agent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Observable<any>} DDS Parties data
|
|
||||||
*/
|
|
||||||
getParties(): Observable<any> {
|
|
||||||
return this.http.get('/dds/parties');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string | number} partyId Party ID.
|
|
||||||
* @returns {Observable<any>} DDS Party.
|
|
||||||
*/
|
|
||||||
getParty(partyId: string | number): Observable<any> {
|
|
||||||
return this.http.get(`/dds/parties/${partyId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} party Party to be created.
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
createParty(party: any): Observable<any> {
|
|
||||||
return this.http.post('/dds/parties', party);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string | number} partyId Party ID.
|
|
||||||
* @param {any} party Party changes to apply.
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
updateParty(partyId: string | number, party: any): Observable<any> {
|
|
||||||
return this.http.put(`/dds/parties/${partyId}`, party);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string | number} partyId Party ID.
|
|
||||||
* @param {any} closeCommand { closeDate }
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
closeParty(partyId: string | number, closeCommand: any): Observable<any> {
|
|
||||||
return this.http.post(`/dds/parties/${partyId}?command=close`, closeCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} transaction Daily transaction batch (PARTY_TRAN collections or a WITHDRAW_DEPOSITE transfer) to post.
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
createPartyTransaction(transaction: any): Observable<any> {
|
|
||||||
return this.http.post('/dds/partytransactions', transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Observable<any>} DDS Commission Slabs data
|
|
||||||
*/
|
|
||||||
getCommissionSlabs(): Observable<any> {
|
|
||||||
return this.http.get('/dds/commissionslabs');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any[]} slabs New commission slab rows to add to the chart.
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
createCommissionSlabs(slabs: any[]): Observable<any> {
|
|
||||||
return this.http.post('/dds/commissionslabs', { slabs });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} batch Batch run filters (jobType, branchScope, jobDate, agentFrom/To, partyFrom/To) to generate a preview for.
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
generateDdsBatchRun(batch: any): Observable<any> {
|
|
||||||
return this.http.post('/dds/batchruns', batch);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string | number} batchRunId Batch run ID.
|
|
||||||
* @returns {Observable<any>} Batch run, including its previewed/posted rows.
|
|
||||||
*/
|
|
||||||
getDdsBatchRun(batchRunId: string | number): Observable<any> {
|
|
||||||
return this.http.get(`/dds/batchruns/${batchRunId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string | number} batchRunId Batch run ID to post - applies exactly the rows it previewed.
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
postDdsBatchRun(batchRunId: string | number): Observable<any> {
|
|
||||||
return this.http.post(`/dds/batchruns/${batchRunId}`, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} reportKey One of the backend DDS report keys.
|
|
||||||
* @param {any} filters fromDate, toDate (ISO strings), agentFrom/To, partyFrom/To.
|
|
||||||
* @returns {Observable<any>} { reportKey, available, message, columns, rows }
|
|
||||||
*/
|
|
||||||
runDdsReport(reportKey: string, filters: any): Observable<any> {
|
|
||||||
let httpParams = new HttpParams();
|
|
||||||
Object.keys(filters || {}).forEach((key) => {
|
|
||||||
if (filters[key] !== undefined && filters[key] !== null && filters[key] !== '') {
|
|
||||||
httpParams = httpParams.set(key, filters[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return this.http.get(`/dds/reports/${reportKey}`, { params: httpParams });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {any[]} currencies
|
* @param {any[]} currencies
|
||||||
* @returns {Observable<any>} Currencies data
|
* @returns {Observable<any>} Currencies data
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ export class DateFormatPipe implements PipeTransform, OnDestroy {
|
|||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
// Backend sometimes returns [yyyy, MM, dd]
|
// Backend sometimes returns [yyyy, MM, dd]
|
||||||
dateVal = moment(value.join('-'), 'YYYY-MM-DD');
|
dateVal = moment(value.join('-'), 'YYYY-MM-DD');
|
||||||
|
} else if (typeof value === 'number' && value < 1e12) {
|
||||||
|
// Unix timestamp in seconds (e.g. /makercheckers madeOnDate).
|
||||||
|
// moment(seconds) would treat it as milliseconds → 1970; use moment.unix() instead.
|
||||||
|
dateVal = moment.unix(value);
|
||||||
} else {
|
} else {
|
||||||
dateVal = moment(value);
|
dateVal = moment(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { Router, ActivatedRoute, RouterLink } from '@angular/router';
|
|||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
import { ProductsService } from '../../products.service';
|
import { ProductsService } from '../../products.service';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
import { Dates } from 'app/core/utils/dates';
|
import { Dates } from 'app/core/utils/dates';
|
||||||
import { minNumberValueValidator } from 'app/shared/validators/min-number-value.validator';
|
import { minNumberValueValidator } from 'app/shared/validators/min-number-value.validator';
|
||||||
import { maxNumberValueValidator } from 'app/shared/validators/max-number-value.validator';
|
import { maxNumberValueValidator } from 'app/shared/validators/max-number-value.validator';
|
||||||
@@ -45,6 +46,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
export class CreateChargeComponent implements OnInit {
|
export class CreateChargeComponent implements OnInit {
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
private productsService = inject(ProductsService);
|
private productsService = inject(ProductsService);
|
||||||
|
private alertService = inject(AlertService);
|
||||||
private route = inject(ActivatedRoute);
|
private route = inject(ActivatedRoute);
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private dateUtils = inject(Dates);
|
private dateUtils = inject(Dates);
|
||||||
@@ -352,8 +354,16 @@ export class CreateChargeComponent implements OnInit {
|
|||||||
if (!data.maxCap) {
|
if (!data.maxCap) {
|
||||||
delete data.maxCap;
|
delete data.maxCap;
|
||||||
}
|
}
|
||||||
this.productsService.createCharge(data).subscribe((response: any) => {
|
this.productsService.createCharge(data).subscribe({
|
||||||
this.router.navigate(['../'], { relativeTo: this.route });
|
next: (response: any) => {
|
||||||
|
if (response?.rollbackTransaction) {
|
||||||
|
this.alertService.alert({
|
||||||
|
type: 'Success',
|
||||||
|
message: 'Charge submitted successfully for approval.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.router.navigate(['../'], { relativeTo: this.route });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
|||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
import { ProductsService } from 'app/products/products.service';
|
import { ProductsService } from 'app/products/products.service';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
import { maxNumberValueValidator } from 'app/shared/validators/max-number-value.validator';
|
import { maxNumberValueValidator } from 'app/shared/validators/max-number-value.validator';
|
||||||
import { minNumberValueValidator } from 'app/shared/validators/min-number-value.validator';
|
import { minNumberValueValidator } from 'app/shared/validators/min-number-value.validator';
|
||||||
import { ValidateOnFocusDirective } from '../../../directives/validate-on-focus.directive';
|
import { ValidateOnFocusDirective } from '../../../directives/validate-on-focus.directive';
|
||||||
@@ -37,6 +38,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
})
|
})
|
||||||
export class EditChargeComponent implements OnInit {
|
export class EditChargeComponent implements OnInit {
|
||||||
private productsService = inject(ProductsService);
|
private productsService = inject(ProductsService);
|
||||||
|
private alertService = inject(AlertService);
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
private route = inject(ActivatedRoute);
|
private route = inject(ActivatedRoute);
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
@@ -222,8 +224,16 @@ export class EditChargeComponent implements OnInit {
|
|||||||
if (!charges.maxCap) {
|
if (!charges.maxCap) {
|
||||||
delete charges.maxCap;
|
delete charges.maxCap;
|
||||||
}
|
}
|
||||||
this.productsService.updateCharge(this.chargeData.id.toString(), charges).subscribe((response: any) => {
|
this.productsService.updateCharge(this.chargeData.id.toString(), charges).subscribe({
|
||||||
this.router.navigate(['../'], { relativeTo: this.route });
|
next: (response: any) => {
|
||||||
|
if (response?.rollbackTransaction) {
|
||||||
|
this.alertService.alert({
|
||||||
|
type: 'Success',
|
||||||
|
message: 'Charge update submitted successfully for approval.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.router.navigate(['../'], { relativeTo: this.route });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-8
@@ -18,6 +18,7 @@ import { CurrentProductAccountingStepComponent } from '../current-product-steppe
|
|||||||
import { CurrentProductPreviewStepComponent } from '../current-product-stepper/current-product-preview-step/current-product-preview-step.component';
|
import { CurrentProductPreviewStepComponent } from '../current-product-stepper/current-product-preview-step/current-product-preview-step.component';
|
||||||
import { ProductsService } from 'app/products/products.service';
|
import { ProductsService } from 'app/products/products.service';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
import { Accounting } from 'app/core/utils/accounting';
|
import { Accounting } from 'app/core/utils/accounting';
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
@@ -42,6 +43,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
export class CreateCurrentProductComponent {
|
export class CreateCurrentProductComponent {
|
||||||
private route = inject(ActivatedRoute);
|
private route = inject(ActivatedRoute);
|
||||||
private productsService = inject(ProductsService);
|
private productsService = inject(ProductsService);
|
||||||
|
private alertService = inject(AlertService);
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
private accounting = inject(Accounting);
|
private accounting = inject(Accounting);
|
||||||
@@ -144,14 +146,24 @@ export class CreateCurrentProductComponent {
|
|||||||
submit() {
|
submit() {
|
||||||
const currentProduct = this.prepareCurrentProductPayload();
|
const currentProduct = this.prepareCurrentProductPayload();
|
||||||
console.log('Current product submit payload:', currentProduct);
|
console.log('Current product submit payload:', currentProduct);
|
||||||
this.productsService.createCurrentProduct(currentProduct).subscribe((response: any) => {
|
this.productsService.createCurrentProduct(currentProduct).subscribe({
|
||||||
this.router.navigate(
|
next: (response: any) => {
|
||||||
[
|
if (response?.rollbackTransaction) {
|
||||||
'../',
|
this.alertService.alert({
|
||||||
response.resourceId
|
type: 'Success',
|
||||||
],
|
message: 'Current product creation request submitted successfully for approval.'
|
||||||
{ relativeTo: this.route }
|
});
|
||||||
);
|
this.router.navigate(['../'], { relativeTo: this.route });
|
||||||
|
} else {
|
||||||
|
this.router.navigate(
|
||||||
|
[
|
||||||
|
'../',
|
||||||
|
response.resourceId
|
||||||
|
],
|
||||||
|
{ relativeTo: this.route }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-131
@@ -1,131 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="rdp-container">
|
|
||||||
<div class="rdp-header">
|
|
||||||
<i class="fas fa-calendar-day rdp-header__icon"></i>
|
|
||||||
<h2 class="rdp-header__title">{{ 'labels.heading.Create Daily Deposit Scheme Product' | translate }}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Currency/Terms/Settings/Charges/Accounting still run underneath (the account-creation
|
|
||||||
engine needs them) but are pre-filled with fixed defaults in ngAfterViewInit() rather than
|
|
||||||
shown - the legacy Account Type Creation screen never asked for any of this either. Kept
|
|
||||||
mounted (just hidden) so their forms exist for the "product" payload to read from. -->
|
|
||||||
<div hidden>
|
|
||||||
<mifosx-daily-deposit-scheme-product-currency-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="productsTemplate"
|
|
||||||
></mifosx-daily-deposit-scheme-product-currency-step>
|
|
||||||
<mifosx-daily-deposit-scheme-product-terms-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="productsTemplate"
|
|
||||||
></mifosx-daily-deposit-scheme-product-terms-step>
|
|
||||||
<mifosx-daily-deposit-scheme-product-settings-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="productsTemplate"
|
|
||||||
></mifosx-daily-deposit-scheme-product-settings-step>
|
|
||||||
<mifosx-daily-deposit-scheme-product-charges-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="productsTemplate"
|
|
||||||
[currencyCode]="currencyForm.get('currencyCode')"
|
|
||||||
></mifosx-daily-deposit-scheme-product-charges-step>
|
|
||||||
<mifosx-daily-deposit-scheme-product-accounting-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="productsTemplate"
|
|
||||||
[accountingRuleData]="accountingRuleData"
|
|
||||||
[DailyDepositSchemeProductFormValid]="productFormValid"
|
|
||||||
[isDormancyTrackingActive]="currencyForm.get('dormancyAlert')"
|
|
||||||
></mifosx-daily-deposit-scheme-product-accounting-step>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mat-tab-group
|
|
||||||
class="rdp-tabs"
|
|
||||||
animationDuration="200ms"
|
|
||||||
[preserveContent]="true"
|
|
||||||
[(selectedIndex)]="selectedTabIndex"
|
|
||||||
>
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<i class="fas fa-info-circle rdp-tab-icon"></i>
|
|
||||||
{{ 'labels.inputs.DETAILS' | translate }}
|
|
||||||
</ng-template>
|
|
||||||
<div class="rdp-tab-content">
|
|
||||||
<mifosx-daily-deposit-scheme-product-details-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="productsTemplate"
|
|
||||||
></mifosx-daily-deposit-scheme-product-details-step>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<i class="fas fa-chart-area rdp-tab-icon"></i>
|
|
||||||
CBS Base Chart
|
|
||||||
</ng-template>
|
|
||||||
<div class="rdp-tab-content">
|
|
||||||
@if (activeBaseVersion) {
|
|
||||||
<div class="base-rate-card">
|
|
||||||
<div class="base-rate-header">
|
|
||||||
<div>
|
|
||||||
<h3 class="base-rate-title">{{ activeBaseVersion.versionLabel }}</h3>
|
|
||||||
<p class="base-rate-meta">
|
|
||||||
Active CBS base chart
|
|
||||||
<span>Effective from {{ activeBaseVersion.effectiveFromDate }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<span class="base-rate-badge">Active</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (activeBaseVersion.remarks) {
|
|
||||||
<p class="base-rate-remarks">{{ activeBaseVersion.remarks }}</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<table class="base-rate-table" mat-table [dataSource]="activeBaseVersion.slabs">
|
|
||||||
<ng-container matColumnDef="srNo">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Sr.</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.srNo }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="description">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Particular</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.description }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="fromPeriod">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.fromPeriod }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="toPeriod">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.toPeriod ?? 'N/A' }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="periodTypeLabel">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Period Type</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.periodTypeLabel }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="annualInterestRate">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Annual Rate (%)</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">
|
|
||||||
<strong>{{ slab.annualInterestRate | number: '1.2-2' }}</strong>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="rdSlabColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: rdSlabColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
} @else {
|
|
||||||
<p>Loading CBS base chart...</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
</mat-tab-group>
|
|
||||||
|
|
||||||
<div class="rdp-action-bar">
|
|
||||||
<button mat-raised-button [routerLink]="['../']" class="rdp-action-bar__cancel">
|
|
||||||
{{ 'labels.buttons.Cancel' | translate }}
|
|
||||||
</button>
|
|
||||||
<div class="rdp-action-bar__right">
|
|
||||||
<button mat-raised-button color="primary" [disabled]="!productFormValid" (click)="submit()">
|
|
||||||
<i class="fas fa-check" style="margin-right: 8px"></i>
|
|
||||||
{{ 'labels.buttons.Submit' | translate }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
-138
@@ -1,138 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$primary: #0f766e;
|
|
||||||
|
|
||||||
.rdp-container {
|
|
||||||
max-width: 960px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&__icon {
|
|
||||||
font-size: 20px;
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-tabs {
|
|
||||||
background: var(--md-sys-color-surface, #fff);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
|
||||||
|
|
||||||
::ng-deep {
|
|
||||||
.mat-mdc-tab-header {
|
|
||||||
border-bottom: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-mdc-tab {
|
|
||||||
min-width: 100px;
|
|
||||||
padding: 0 12px;
|
|
||||||
height: 52px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-mdc-tab-body-wrapper {
|
|
||||||
min-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-mdc-tab-body {
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-mdc-tab-body-content {
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-tab-icon {
|
|
||||||
margin-right: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-tab-content {
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-card {
|
|
||||||
padding: 16px;
|
|
||||||
border: 1px solid #dbe4f0;
|
|
||||||
border-radius: 14px;
|
|
||||||
background: linear-gradient(180deg, #f8fbff 0%, #fff 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: start;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-meta {
|
|
||||||
margin: 6px 0 0;
|
|
||||||
color: #5b6b7f;
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-badge {
|
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: #dcfce7;
|
|
||||||
color: #166534;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-remarks {
|
|
||||||
margin: 0 0 12px;
|
|
||||||
color: #334155;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-action-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 16px 0;
|
|
||||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
|
|
||||||
&__cancel {
|
|
||||||
color: rgb(0 0 0 / 60%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__right {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-346
@@ -1,346 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { AfterViewInit, Component, ViewChild, inject } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
|
|
||||||
/** Custom Components — Daily Deposit Scheme's own stepper, kept independent
|
|
||||||
* of Recurring Deposit Products so future changes to either never cross over. */
|
|
||||||
import { DailyDepositSchemeProductDetailsStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-details-step/daily-deposit-scheme-product-details-step.component';
|
|
||||||
import { DailyDepositSchemeProductCurrencyStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-currency-step/daily-deposit-scheme-product-currency-step.component';
|
|
||||||
import { DailyDepositSchemeProductTermsStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-terms-step/daily-deposit-scheme-product-terms-step.component';
|
|
||||||
import { DailyDepositSchemeProductSettingsStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-settings-step/daily-deposit-scheme-product-settings-step.component';
|
|
||||||
import { DailyDepositSchemeProductChargesStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-charges-step/daily-deposit-scheme-product-charges-step.component';
|
|
||||||
import { DailyDepositSchemeProductAccountingStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-accounting-step/daily-deposit-scheme-product-accounting-step.component';
|
|
||||||
import {
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow
|
|
||||||
} from '@angular/material/table';
|
|
||||||
|
|
||||||
/** Custom Services */
|
|
||||||
import { ProductsService } from 'app/products/products.service';
|
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
|
||||||
import { Accounting } from 'app/core/utils/accounting';
|
|
||||||
import { Dates } from 'app/core/utils/dates';
|
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-create-daily-deposit-scheme-product',
|
|
||||||
templateUrl: './create-daily-deposit-scheme-product.component.html',
|
|
||||||
styleUrls: ['./create-daily-deposit-scheme-product.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
MatTabsModule,
|
|
||||||
DailyDepositSchemeProductDetailsStepComponent,
|
|
||||||
DailyDepositSchemeProductCurrencyStepComponent,
|
|
||||||
DailyDepositSchemeProductTermsStepComponent,
|
|
||||||
DailyDepositSchemeProductSettingsStepComponent,
|
|
||||||
DailyDepositSchemeProductChargesStepComponent,
|
|
||||||
DailyDepositSchemeProductAccountingStepComponent,
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class CreateDailyDepositSchemeProductComponent implements AfterViewInit {
|
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private productsService = inject(ProductsService);
|
|
||||||
private router = inject(Router);
|
|
||||||
private settingsService = inject(SettingsService);
|
|
||||||
private accounting = inject(Accounting);
|
|
||||||
private dateUtils = inject(Dates);
|
|
||||||
|
|
||||||
@ViewChild(DailyDepositSchemeProductDetailsStepComponent, { static: true })
|
|
||||||
detailsStep: DailyDepositSchemeProductDetailsStepComponent;
|
|
||||||
@ViewChild(DailyDepositSchemeProductCurrencyStepComponent, { static: true })
|
|
||||||
currencyStep: DailyDepositSchemeProductCurrencyStepComponent;
|
|
||||||
@ViewChild(DailyDepositSchemeProductTermsStepComponent, { static: true })
|
|
||||||
termsStep: DailyDepositSchemeProductTermsStepComponent;
|
|
||||||
@ViewChild(DailyDepositSchemeProductSettingsStepComponent, { static: true })
|
|
||||||
settingsStep: DailyDepositSchemeProductSettingsStepComponent;
|
|
||||||
@ViewChild(DailyDepositSchemeProductChargesStepComponent, { static: true })
|
|
||||||
chargesStep: DailyDepositSchemeProductChargesStepComponent;
|
|
||||||
@ViewChild(DailyDepositSchemeProductAccountingStepComponent, { static: true })
|
|
||||||
accountingStep: DailyDepositSchemeProductAccountingStepComponent;
|
|
||||||
|
|
||||||
productsTemplate: any;
|
|
||||||
accountingRuleData: string[] = [];
|
|
||||||
selectedTabIndex = 0;
|
|
||||||
rdInterestRates: any[] = [];
|
|
||||||
readonly rdSlabColumns = [
|
|
||||||
'srNo',
|
|
||||||
'description',
|
|
||||||
'fromPeriod',
|
|
||||||
'toPeriod',
|
|
||||||
'periodTypeLabel',
|
|
||||||
'annualInterestRate'
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.route.data.subscribe((data: { dailyDepositSchemeProductsTemplate: any }) => {
|
|
||||||
this.productsTemplate = data.dailyDepositSchemeProductsTemplate;
|
|
||||||
});
|
|
||||||
this.accountingRuleData = this.accounting.getAccountingRulesForSavings();
|
|
||||||
this.productsService.getFdBaseInterestRates().subscribe({
|
|
||||||
next: (rates: any[]) => {
|
|
||||||
this.rdInterestRates = rates || [];
|
|
||||||
},
|
|
||||||
error: () => {
|
|
||||||
this.rdInterestRates = [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
|
||||||
// The DDS product screen only shows the legacy Account Type Creation fields (Details tab) -
|
|
||||||
// currency/terms/settings/charges/accounting still exist underneath (the account-creation
|
|
||||||
// engine needs them), but they're filled in here with fixed sensible defaults rather than
|
|
||||||
// asked of the user, since the legacy screen never asked for them either.
|
|
||||||
const template = this.productsTemplate || {};
|
|
||||||
|
|
||||||
this.currencyForm.patchValue({
|
|
||||||
currencyCode: this.currencyForm.value.currencyCode || 'INR',
|
|
||||||
digitsAfterDecimal: this.currencyForm.value.digitsAfterDecimal ?? 2
|
|
||||||
});
|
|
||||||
|
|
||||||
const firstOptionId = (options: any[]) => options?.[0]?.id ?? '';
|
|
||||||
this.termsForm.patchValue({
|
|
||||||
interestCompoundingPeriodType: firstOptionId(template.interestCompoundingPeriodTypeOptions),
|
|
||||||
interestCalculationDaysInYearType: firstOptionId(template.interestCalculationDaysInYearTypeOptions),
|
|
||||||
minDepositAmount: this.termsForm.value.minDepositAmount || 1,
|
|
||||||
maxDepositAmount: this.termsForm.value.maxDepositAmount || 999999999
|
|
||||||
});
|
|
||||||
|
|
||||||
// accountingRule 1 = None, matching the legacy screen (which never asked for GL mappings) -
|
|
||||||
// this also clears every accounting-tab required-field validator, since those only apply
|
|
||||||
// under Cash/Accrual accounting.
|
|
||||||
this.accountingForm.patchValue({ accountingRule: 1 });
|
|
||||||
|
|
||||||
this.detailsForm.get('autoInterestPost')?.valueChanges.subscribe(() => this.syncAutoInterestPost());
|
|
||||||
this.detailsForm.get('autoInterestPostWhen')?.valueChanges.subscribe(() => this.syncAutoInterestPost());
|
|
||||||
this.syncAutoInterestPost();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Mirrors the simple "Auto Interest Post" / "Auto Interest Post When" / "Auto Interest Post
|
|
||||||
* Day" fields onto the (hidden) terms-step fields that the account-creation engine actually
|
|
||||||
* reads. */
|
|
||||||
private syncAutoInterestPost(): void {
|
|
||||||
const autoPost = this.detailsForm?.value.autoInterestPost ?? true;
|
|
||||||
const postingPeriodType = this.detailsForm?.value.autoInterestPostWhen;
|
|
||||||
const postingDay = this.detailsForm?.value.autoInterestPostDay;
|
|
||||||
this.termsForm?.patchValue({
|
|
||||||
autoPost,
|
|
||||||
interestPostingPeriodType: postingPeriodType || this.termsForm.value.interestPostingPeriodType,
|
|
||||||
frequencyFixDay: postingDay || this.termsForm.value.frequencyFixDay
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get detailsForm() {
|
|
||||||
return this.detailsStep?.DailyDepositSchemeProductDetailsForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
get currencyForm() {
|
|
||||||
return this.currencyStep?.DailyDepositSchemeProductCurrencyForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
get termsForm() {
|
|
||||||
return this.termsStep?.DailyDepositSchemeProductTermsForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
get settingsForm() {
|
|
||||||
return this.settingsStep?.DailyDepositSchemeProductSettingsForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
get accountingForm() {
|
|
||||||
return this.accountingStep?.DailyDepositSchemeProductAccountingForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
get productFormValid() {
|
|
||||||
return !!this.detailsForm?.valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
get activeBaseVersion() {
|
|
||||||
const chart =
|
|
||||||
this.rdInterestRates.find((version: any) => version.fdType === 'DOUBLE' && version.isActive) ??
|
|
||||||
this.rdInterestRates.find((version: any) => version.fdType === 'DOUBLE') ??
|
|
||||||
this.rdInterestRates[0];
|
|
||||||
if (chart) {
|
|
||||||
return {
|
|
||||||
...chart,
|
|
||||||
versionLabel: chart.versionLabel?.replace('FD', 'DDS'),
|
|
||||||
remarks: chart.remarks?.replace('FD', 'DDS')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get activeBaseSlabs() {
|
|
||||||
// The base chart is borrowed from an FD product purely for its period/amount-range
|
|
||||||
// *structure* - the account-creation engine needs some chart to exist. Its actual rate is
|
|
||||||
// never used: every slab's annualInterestRate is overridden with the Details tab's own
|
|
||||||
// Interest Rate field, so DDS never depends on FD's real interest figures.
|
|
||||||
const rate = this.detailsForm?.value.interestRate;
|
|
||||||
return (this.activeBaseVersion?.slabs || []).map((slab: any) => ({
|
|
||||||
...slab,
|
|
||||||
annualInterestRate: rate ?? slab.annualInterestRate
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
get product() {
|
|
||||||
// accountTypeGroup / autoInterestPost / autoInterestPostWhen / autoInterestPostDay are the
|
|
||||||
// legacy Account Type Creation screen's own fields - not real product API parameters. The
|
|
||||||
// auto-interest-post trio is already mirrored onto termsStep (autoPost /
|
|
||||||
// interestPostingPeriodType / frequencyFixDay) via syncAutoInterestPost(); accountTypeGroup
|
|
||||||
// is folded into the description here instead.
|
|
||||||
const {
|
|
||||||
accountTypeGroup,
|
|
||||||
autoInterestPost,
|
|
||||||
autoInterestPostWhen,
|
|
||||||
autoInterestPostDay,
|
|
||||||
interestRate,
|
|
||||||
...detailsPayload
|
|
||||||
} = this.detailsStep.DailyDepositSchemeProductDetails;
|
|
||||||
const description = [
|
|
||||||
accountTypeGroup,
|
|
||||||
detailsPayload.description
|
|
||||||
]
|
|
||||||
.filter((part) => !!part)
|
|
||||||
.join(' - ');
|
|
||||||
|
|
||||||
return {
|
|
||||||
...detailsPayload,
|
|
||||||
description,
|
|
||||||
...this.currencyStep.DailyDepositSchemeProductCurrency,
|
|
||||||
...this.termsStep.DailyDepositSchemeProductTerms,
|
|
||||||
...this.settingsStep.DailyDepositSchemeProductSettings,
|
|
||||||
...this.chargesStep.DailyDepositSchemeProductCharges,
|
|
||||||
...this.accountingStep.DailyDepositSchemeProductAccounting,
|
|
||||||
charts: this.productCharts,
|
|
||||||
rdSlabs: this.activeBaseSlabs
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get productCharts() {
|
|
||||||
const activeBaseVersion = this.activeBaseVersion;
|
|
||||||
if (!activeBaseVersion?.slabs?.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: activeBaseVersion.versionLabel || 'CBS Base Chart',
|
|
||||||
description: activeBaseVersion.remarks || activeBaseVersion.versionLabel || 'CBS base chart',
|
|
||||||
fromDate:
|
|
||||||
this.dateUtils.formatDate(activeBaseVersion.effectiveFromDate, dateFormat) ||
|
|
||||||
activeBaseVersion.effectiveFromDate,
|
|
||||||
...(activeBaseVersion.effectiveToDate
|
|
||||||
? {
|
|
||||||
endDate:
|
|
||||||
this.dateUtils.formatDate(activeBaseVersion.effectiveToDate, dateFormat) ||
|
|
||||||
activeBaseVersion.effectiveToDate
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
isPrimaryGroupingByAmount: false,
|
|
||||||
locale: this.settingsService.language.code,
|
|
||||||
dateFormat,
|
|
||||||
chartSlabs: this.activeBaseSlabs.map((slab: any) => {
|
|
||||||
const chartSlab: any = {
|
|
||||||
periodType: slab.periodTypeEnum,
|
|
||||||
fromPeriod: slab.fromPeriod,
|
|
||||||
toPeriod: slab.toPeriod,
|
|
||||||
annualInterestRate: slab.annualInterestRate,
|
|
||||||
description: slab.description || '',
|
|
||||||
incentives: [] as any[]
|
|
||||||
};
|
|
||||||
|
|
||||||
if (slab.amountRangeFrom !== null && slab.amountRangeFrom !== undefined && slab.amountRangeFrom !== '') {
|
|
||||||
chartSlab.amountRangeFrom = slab.amountRangeFrom;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slab.amountRangeTo !== null && slab.amountRangeTo !== undefined && slab.amountRangeTo !== '') {
|
|
||||||
chartSlab.amountRangeTo = slab.amountRangeTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
return chartSlab;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
] as any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
submit() {
|
|
||||||
const product: any = this.pruneEmptyValues({
|
|
||||||
...this.product,
|
|
||||||
charts: this.productCharts,
|
|
||||||
charges: (this.product.charges ?? []).map((charge: any) => ({ id: charge.id })),
|
|
||||||
locale: this.settingsService.language.code,
|
|
||||||
productCategory: 'DDS'
|
|
||||||
});
|
|
||||||
if (!product.description) {
|
|
||||||
product.description = '';
|
|
||||||
}
|
|
||||||
delete product.advancedAccountingRules;
|
|
||||||
this.productsService.createRecurringDepositProduct(product).subscribe((response: any) => {
|
|
||||||
this.router.navigate(
|
|
||||||
[
|
|
||||||
'../',
|
|
||||||
response.resourceId
|
|
||||||
],
|
|
||||||
{ relativeTo: this.route }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private pruneEmptyValues<T>(value: T): T {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.map((item) => this.pruneEmptyValues(item)).filter((item) => item !== undefined) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value && typeof value === 'object') {
|
|
||||||
return Object.entries(value as Record<string, any>).reduce(
|
|
||||||
(acc, [
|
|
||||||
key,
|
|
||||||
currentValue
|
|
||||||
]) => {
|
|
||||||
const cleanedValue = this.pruneEmptyValues(currentValue);
|
|
||||||
if (cleanedValue === undefined) {
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
[key]: cleanedValue
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{} as Record<string, any>
|
|
||||||
) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === '' || value === null || value === undefined) {
|
|
||||||
return undefined as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-35
@@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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';
|
|
||||||
|
|
||||||
/** rxjs Imports */
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
/** Custom Services */
|
|
||||||
import { ProductsService } from '../../products.service';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Daily Deposit Scheme Products template resolver.
|
|
||||||
*
|
|
||||||
* Reuses the Recurring Deposit product template — a Daily Deposit Scheme
|
|
||||||
* product is created through the same underlying product-creation engine.
|
|
||||||
*/
|
|
||||||
@Injectable()
|
|
||||||
export class DailyDepositSchemeProductsTemplateResolver {
|
|
||||||
private productsService = inject(ProductsService);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Daily Deposit Scheme products template.
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
resolve(): Observable<any> {
|
|
||||||
return this.productsService.getRecurringDepositProductsTemplate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-121
@@ -1,121 +0,0 @@
|
|||||||
<!--
|
|
||||||
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/.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<form [formGroup]="DailyDepositSchemeProductAccountingForm">
|
|
||||||
<div class="rdpa-section">
|
|
||||||
<div class="rdpa-section__header">
|
|
||||||
<span class="rdpa-section__icon">
|
|
||||||
<i class="fas fa-book"></i>
|
|
||||||
</span>
|
|
||||||
<h3>Accounting</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rdpa-section__body">
|
|
||||||
<div class="rdpa-group">
|
|
||||||
<div class="rdpa-group__header">
|
|
||||||
<h4>Accounting Rule</h4>
|
|
||||||
</div>
|
|
||||||
<div class="rdpa-radio-group">
|
|
||||||
<mat-radio-button [value]="2" [checked]="true" [disabled]="true">Cash</mat-radio-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (isCashOrAccrualAccounting()) {
|
|
||||||
@for (section of accountingSectionConfigs; track section.key) {
|
|
||||||
<div class="rdpa-group">
|
|
||||||
<div class="rdpa-group__header">
|
|
||||||
<h4>{{ section.title }}</h4>
|
|
||||||
<button type="button" class="rdpa-add-btn" (click)="addOtherGlMapping(section.otherMappingsControlName)">
|
|
||||||
+ Add Other
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="rdpa-list">
|
|
||||||
@for (row of section.rows; track row.key) {
|
|
||||||
@if (
|
|
||||||
(!row.accrualOnly || isAccrualAccounting()) && (!row.dormancyOnly || isDormancyTrackingEnabled())
|
|
||||||
) {
|
|
||||||
<div class="rdpa-row">
|
|
||||||
<div class="rdpa-row__meta">
|
|
||||||
<span
|
|
||||||
class="rdpa-pill"
|
|
||||||
[class.rdpa-pill--green]="section.pillClass === 'green'"
|
|
||||||
[class.rdpa-pill--amber]="section.pillClass === 'amber'"
|
|
||||||
[class.rdpa-pill--blue]="section.pillClass === 'blue'"
|
|
||||||
>
|
|
||||||
{{ section.pillLabel }}
|
|
||||||
</span>
|
|
||||||
<span class="rdpa-row__label">{{ row.label }}</span>
|
|
||||||
</div>
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="rdpa-row__input"
|
|
||||||
[inputFormControl]="DailyDepositSchemeProductAccountingForm.get(row.controlName)"
|
|
||||||
[glAccountList]="row.options"
|
|
||||||
[required]="row.required"
|
|
||||||
[inputLabel]="row.inputLabel"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rdpa-list" [formArrayName]="section.otherMappingsControlName">
|
|
||||||
@for (
|
|
||||||
otherRow of getOtherGlMappingsArray(section.otherMappingsControlName).controls;
|
|
||||||
track $index;
|
|
||||||
let i = $index
|
|
||||||
) {
|
|
||||||
<div class="rdpa-other-row-wrap">
|
|
||||||
<div class="rdpa-row rdpa-row--other" [formGroupName]="i">
|
|
||||||
<div class="rdpa-row__meta rdpa-row__meta--other">
|
|
||||||
<span
|
|
||||||
class="rdpa-pill"
|
|
||||||
[class.rdpa-pill--green]="section.pillClass === 'green'"
|
|
||||||
[class.rdpa-pill--amber]="section.pillClass === 'amber'"
|
|
||||||
[class.rdpa-pill--blue]="section.pillClass === 'blue'"
|
|
||||||
>
|
|
||||||
{{ section.pillLabel }}
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
class="rdpa-row__name rdpa-row__name--other-account"
|
|
||||||
formControlName="accountName"
|
|
||||||
placeholder="Enter account name..."
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rdpa-row__actions rdpa-row__actions--other">
|
|
||||||
<mat-form-field class="rdpa-row__other-field" appearance="outline" subscriptSizing="dynamic">
|
|
||||||
<mat-label>GL Code</mat-label>
|
|
||||||
<mat-select
|
|
||||||
[formControl]="getOtherGlMappingControl(section.otherMappingsControlName, i, 'glAccountId')"
|
|
||||||
>
|
|
||||||
@for (glAccount of section.otherAccountOptions; track glAccount.id) {
|
|
||||||
<mat-option [value]="glAccount.id">
|
|
||||||
{{ '(' + glAccount.glCode + ') ' + glAccount.name }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="rdpa-remove-btn rdpa-remove-btn--side"
|
|
||||||
(click)="removeOtherGlMapping(section.otherMappingsControlName, i)"
|
|
||||||
>
|
|
||||||
<i class="fas fa-times"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
-303
@@ -1,303 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$border: #e2e8f0;
|
|
||||||
$gray-50: #f8f9fa;
|
|
||||||
$gray-200: #e8eaed;
|
|
||||||
$gray-700: #334155;
|
|
||||||
$gray-900: #202124;
|
|
||||||
|
|
||||||
.rdpa-section {
|
|
||||||
border: 1px solid $border;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-section__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 12px 24px;
|
|
||||||
background: $gray-50;
|
|
||||||
border-bottom: 1px solid $gray-200;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $gray-900;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-section__icon {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: #eff6ff;
|
|
||||||
color: #2563eb;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-section__body {
|
|
||||||
padding: 16px 24px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-group {
|
|
||||||
& + & {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 16px;
|
|
||||||
margin: 0 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: 0 0 12px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: #94a3b8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-add-btn {
|
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
color: #2563eb;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-list + .rdpa-list {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 16px;
|
|
||||||
padding: 10px 12px;
|
|
||||||
border: 1px solid $border;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
@media (width <= 900px) {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row:has(mifosx-gl-account-selector) {
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row--other {
|
|
||||||
border-style: dashed;
|
|
||||||
border-color: #cbd5e1;
|
|
||||||
background: #f8fafc;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px 16px;
|
|
||||||
|
|
||||||
@media (width <= 1100px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-other-row-wrap {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
@media (width <= 1100px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
min-width: 0;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__meta--other {
|
|
||||||
min-width: 0;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
align-self: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__label {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $gray-700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__input {
|
|
||||||
flex: 0 0 22rem;
|
|
||||||
max-width: 22rem;
|
|
||||||
min-width: 18rem;
|
|
||||||
|
|
||||||
@media (width <= 900px) {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__input :is(mifosx-gl-account-selector) {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__input :is(mifosx-gl-account-selector) :is(.layout-column) {
|
|
||||||
width: 100%;
|
|
||||||
margin-right: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__input :is(mifosx-gl-account-selector) :is(mat-form-field) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__actions--other {
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-self: stretch;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
@media (width <= 1100px) {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__name {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
max-width: 24rem;
|
|
||||||
height: 40px;
|
|
||||||
border: 1px solid #cbd5e1;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
@media (width <= 900px) {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__name--other-account {
|
|
||||||
max-width: 100%;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__other-field {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__other-field :is(mat-form-field) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__other-field :is(.mat-mdc-form-field-required-marker) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-remove-btn {
|
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
color: #ef4444;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-remove-btn--side {
|
|
||||||
place-self: center end;
|
|
||||||
margin-right: 4px;
|
|
||||||
|
|
||||||
@media (width <= 1100px) {
|
|
||||||
justify-self: start;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-radio-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 24px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-pill {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&--green {
|
|
||||||
background: #dcfce7;
|
|
||||||
color: #16a34a;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--amber {
|
|
||||||
background: #fef3c7;
|
|
||||||
color: #d97706;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--blue {
|
|
||||||
background: #dbeafe;
|
|
||||||
color: #2563eb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 900px) {
|
|
||||||
.rdpa-row__meta {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpa-row__input {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-423
@@ -1,423 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, Input, OnInit, inject } from '@angular/core';
|
|
||||||
import {
|
|
||||||
FormsModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
UntypedFormArray,
|
|
||||||
UntypedFormBuilder,
|
|
||||||
UntypedFormControl,
|
|
||||||
UntypedFormGroup,
|
|
||||||
Validators
|
|
||||||
} from '@angular/forms';
|
|
||||||
import { MatRadioButton, MatRadioGroup } from '@angular/material/radio';
|
|
||||||
import { GlAccountSelectorComponent } from 'app/shared/accounting/gl-account-selector/gl-account-selector.component';
|
|
||||||
import { Accounting } from 'app/core/utils/accounting';
|
|
||||||
import { GLAccount } from 'app/shared/models/general.model';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
type AccountingSectionKey = 'assets' | 'liabilities' | 'expenses' | 'income';
|
|
||||||
|
|
||||||
type AccountingRowConfig = {
|
|
||||||
key: string;
|
|
||||||
label: string;
|
|
||||||
inputLabel: string;
|
|
||||||
controlName: string;
|
|
||||||
options: GLAccount[];
|
|
||||||
required?: boolean;
|
|
||||||
accrualOnly?: boolean;
|
|
||||||
dormancyOnly?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type AccountingSectionConfig = {
|
|
||||||
key: AccountingSectionKey;
|
|
||||||
title: string;
|
|
||||||
pillLabel: string;
|
|
||||||
pillClass: 'green' | 'amber' | 'blue';
|
|
||||||
rows: AccountingRowConfig[];
|
|
||||||
otherMappingsControlName: string;
|
|
||||||
otherAccountOptions: GLAccount[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type OtherGlMappingValue = {
|
|
||||||
accountName: string;
|
|
||||||
glAccountId: number | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-daily-deposit-scheme-product-accounting-step',
|
|
||||||
templateUrl: './daily-deposit-scheme-product-accounting-step.component.html',
|
|
||||||
styleUrls: ['./daily-deposit-scheme-product-accounting-step.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
FormsModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
MatRadioGroup,
|
|
||||||
MatRadioButton,
|
|
||||||
GlAccountSelectorComponent
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class DailyDepositSchemeProductAccountingStepComponent implements OnInit {
|
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
|
||||||
private accounting = inject(Accounting);
|
|
||||||
|
|
||||||
@Input() DailyDepositSchemeProductsTemplate: any;
|
|
||||||
@Input() accountingRuleData: any;
|
|
||||||
@Input() DailyDepositSchemeProductFormValid: boolean;
|
|
||||||
@Input() isDormancyTrackingActive: any;
|
|
||||||
|
|
||||||
DailyDepositSchemeProductAccountingForm: UntypedFormGroup;
|
|
||||||
assetAccountOptions: GLAccount[] = [];
|
|
||||||
liabilityAccountOptions: GLAccount[] = [];
|
|
||||||
expenseAccountOptions: GLAccount[] = [];
|
|
||||||
incomeAccountOptions: GLAccount[] = [];
|
|
||||||
accountingSectionConfigs: AccountingSectionConfig[] = [];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.createDailyDepositSchemeProductAccountingForm();
|
|
||||||
this.setConditionalControls();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
const accountingMappings = this.DailyDepositSchemeProductsTemplate?.accountingMappings || {};
|
|
||||||
const mappings = {
|
|
||||||
...accountingMappings,
|
|
||||||
otherGlMappingsMap: this.DailyDepositSchemeProductsTemplate?.otherGlMappingsMap
|
|
||||||
};
|
|
||||||
const accountingMappingOptions = this.DailyDepositSchemeProductsTemplate?.accountingMappingOptions || {};
|
|
||||||
// Defaults to Cash (2) for a brand-new product (no existing accountingRule yet), but preserves
|
|
||||||
// the product's own current accounting rule when editing - previously this was hardcoded to 2
|
|
||||||
// unconditionally, which silently forced every edited product onto Cash-based accounting (and
|
|
||||||
// its required GL mappings) even for products that were and should remain "None".
|
|
||||||
const accountingRuleId = this.DailyDepositSchemeProductsTemplate?.accountingRule?.id ?? 2;
|
|
||||||
|
|
||||||
this.assetAccountOptions = accountingMappingOptions.assetAccountOptions || [];
|
|
||||||
this.liabilityAccountOptions = accountingMappingOptions.liabilityAccountOptions || [];
|
|
||||||
this.expenseAccountOptions = accountingMappingOptions.expenseAccountOptions || [];
|
|
||||||
this.incomeAccountOptions = accountingMappingOptions.incomeAccountOptions || [];
|
|
||||||
|
|
||||||
this.buildAccountingSectionConfigs();
|
|
||||||
|
|
||||||
this.DailyDepositSchemeProductAccountingForm.patchValue({ accountingRule: accountingRuleId });
|
|
||||||
this.syncAccountingControls(accountingRuleId);
|
|
||||||
this.patchAccountingValues(mappings);
|
|
||||||
this.setDormancyControl();
|
|
||||||
}
|
|
||||||
|
|
||||||
createDailyDepositSchemeProductAccountingForm() {
|
|
||||||
this.DailyDepositSchemeProductAccountingForm = this.formBuilder.group({
|
|
||||||
accountingRule: [2],
|
|
||||||
savingsReferenceAccountId: [''],
|
|
||||||
savingsControlAccountId: [''],
|
|
||||||
transfersInSuspenseAccountId: [''],
|
|
||||||
interestOnSavingsAccountId: [''],
|
|
||||||
incomeFromFeeAccountId: [''],
|
|
||||||
incomeFromPenaltyAccountId: [''],
|
|
||||||
feesReceivableAccountId: [''],
|
|
||||||
penaltiesReceivableAccountId: [''],
|
|
||||||
interestPayableAccountId: [''],
|
|
||||||
escheatLiabilityId: [''],
|
|
||||||
assetsOtherGlMappings: this.formBuilder.array([]),
|
|
||||||
liabilitiesOtherGlMappings: this.formBuilder.array([]),
|
|
||||||
expensesOtherGlMappings: this.formBuilder.array([]),
|
|
||||||
incomeOtherGlMappings: this.formBuilder.array([])
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private setConditionalControls() {
|
|
||||||
this.DailyDepositSchemeProductAccountingForm.get('accountingRule')?.valueChanges.subscribe((rule: number) => {
|
|
||||||
this.syncAccountingControls(rule);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private patchAccountingValues(mappings: any) {
|
|
||||||
this.DailyDepositSchemeProductAccountingForm.patchValue({
|
|
||||||
savingsReferenceAccountId: mappings.savingsReferenceAccount?.id || '',
|
|
||||||
savingsControlAccountId: mappings.savingsControlAccount?.id || '',
|
|
||||||
transfersInSuspenseAccountId: mappings.transfersInSuspenseAccount?.id || '',
|
|
||||||
interestOnSavingsAccountId: mappings.interestOnSavingsAccount?.id || '',
|
|
||||||
incomeFromFeeAccountId: mappings.incomeFromFeeAccount?.id || '',
|
|
||||||
incomeFromPenaltyAccountId: mappings.incomeFromPenaltyAccount?.id || '',
|
|
||||||
feesReceivableAccountId: mappings.feeReceivableAccount?.id || '',
|
|
||||||
penaltiesReceivableAccountId: mappings.penaltyReceivableAccount?.id || '',
|
|
||||||
interestPayableAccountId: mappings.interestPayableAccount?.id || '',
|
|
||||||
escheatLiabilityId: mappings.escheatLiabilityAccount?.id || ''
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.isCashOrAccrualAccounting()) {
|
|
||||||
const glMappings = mappings.otherGlMappingsMap || mappings.otherGlMappings;
|
|
||||||
this.setOtherGlMappings('assetsOtherGlMappings', glMappings?.assets || [], this.assetAccountOptions);
|
|
||||||
this.setOtherGlMappings(
|
|
||||||
'liabilitiesOtherGlMappings',
|
|
||||||
glMappings?.liabilities || [],
|
|
||||||
this.liabilityAccountOptions
|
|
||||||
);
|
|
||||||
this.setOtherGlMappings('expensesOtherGlMappings', glMappings?.expenses || [], this.expenseAccountOptions);
|
|
||||||
this.setOtherGlMappings('incomeOtherGlMappings', glMappings?.income || [], this.incomeAccountOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private createOtherGlMappingGroup(otherGlMapping: Partial<OtherGlMappingValue> = {}) {
|
|
||||||
return this.formBuilder.group({
|
|
||||||
accountName: [
|
|
||||||
otherGlMapping.accountName || '',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
glAccountId: [
|
|
||||||
otherGlMapping.glAccountId || '',
|
|
||||||
Validators.required
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getOtherGlMappingsArray(controlName: string): UntypedFormArray {
|
|
||||||
return this.DailyDepositSchemeProductAccountingForm.get(controlName) as UntypedFormArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
private setOtherGlMappings(controlName: string, otherGlMappings: any[], options: GLAccount[]) {
|
|
||||||
const arr = this.getOtherGlMappingsArray(controlName);
|
|
||||||
arr.clear();
|
|
||||||
(otherGlMappings || []).forEach((m) => {
|
|
||||||
arr.push(
|
|
||||||
this.createOtherGlMappingGroup({
|
|
||||||
accountName: m.accountName,
|
|
||||||
glAccountId: m.glAccountId || options.find((o) => o.glCode === m.glCode)?.id || null
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addOtherGlMapping(controlName: string) {
|
|
||||||
this.getOtherGlMappingsArray(controlName).push(this.createOtherGlMappingGroup());
|
|
||||||
}
|
|
||||||
|
|
||||||
removeOtherGlMapping(controlName: string, index: number) {
|
|
||||||
this.getOtherGlMappingsArray(controlName).removeAt(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
getOtherGlMappingControl(controlName: string, index: number, fieldName: string): UntypedFormControl {
|
|
||||||
return this.getOtherGlMappingsArray(controlName).at(index).get(fieldName) as UntypedFormControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private clearOtherGlMappings() {
|
|
||||||
[
|
|
||||||
'assetsOtherGlMappings',
|
|
||||||
'liabilitiesOtherGlMappings',
|
|
||||||
'expensesOtherGlMappings',
|
|
||||||
'incomeOtherGlMappings'
|
|
||||||
].forEach((c) => this.getOtherGlMappingsArray(c).clear());
|
|
||||||
}
|
|
||||||
|
|
||||||
private getOtherGlMappings() {
|
|
||||||
const map = (controlName: string, options: GLAccount[]) =>
|
|
||||||
this.getOtherGlMappingsArray(controlName)
|
|
||||||
.value.filter((item: OtherGlMappingValue) => !!item.accountName || !!item.glAccountId)
|
|
||||||
.map((item: OtherGlMappingValue) => ({
|
|
||||||
accountName: item.accountName,
|
|
||||||
glCode: options.find((o) => o.id === item.glAccountId)?.glCode || ''
|
|
||||||
}))
|
|
||||||
.filter((item: { accountName: string; glCode: string }) => !!item.accountName || !!item.glCode);
|
|
||||||
|
|
||||||
const otherGlMappings = {
|
|
||||||
assets: map('assetsOtherGlMappings', this.assetAccountOptions),
|
|
||||||
liabilities: map('liabilitiesOtherGlMappings', this.liabilityAccountOptions),
|
|
||||||
expenses: map('expensesOtherGlMappings', this.expenseAccountOptions),
|
|
||||||
income: map('incomeOtherGlMappings', this.incomeAccountOptions)
|
|
||||||
};
|
|
||||||
return Object.values(otherGlMappings).some((v) => v.length) ? otherGlMappings : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private setDormancyControl() {
|
|
||||||
this.isDormancyTrackingActive?.valueChanges?.subscribe(() => {
|
|
||||||
this.syncAccountingControls(this.DailyDepositSchemeProductAccountingForm.value.accountingRule);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
isDormancyTrackingEnabled(): boolean {
|
|
||||||
return this.isDormancyTrackingActive?.value ?? this.isDormancyTrackingActive ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private syncAccountingControls(accountingRule: number) {
|
|
||||||
const setRequired = (name: string, required: boolean) => {
|
|
||||||
const ctrl = this.DailyDepositSchemeProductAccountingForm.get(name);
|
|
||||||
if (!ctrl) return;
|
|
||||||
if (required) {
|
|
||||||
ctrl.setValidators(Validators.required);
|
|
||||||
} else {
|
|
||||||
ctrl.clearValidators();
|
|
||||||
}
|
|
||||||
ctrl.updateValueAndValidity({ emitEvent: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
const cashOrAccrual = this.accounting.isCashOrAccrualAccountingRuleId(accountingRule);
|
|
||||||
const accrualOnly = this.accounting.isAccrualAccountingRuleId(accountingRule);
|
|
||||||
|
|
||||||
setRequired('savingsReferenceAccountId', cashOrAccrual);
|
|
||||||
setRequired('savingsControlAccountId', cashOrAccrual);
|
|
||||||
setRequired('transfersInSuspenseAccountId', cashOrAccrual);
|
|
||||||
setRequired('interestOnSavingsAccountId', cashOrAccrual);
|
|
||||||
setRequired('incomeFromFeeAccountId', cashOrAccrual);
|
|
||||||
setRequired('incomeFromPenaltyAccountId', cashOrAccrual);
|
|
||||||
setRequired('feesReceivableAccountId', accrualOnly);
|
|
||||||
setRequired('penaltiesReceivableAccountId', accrualOnly);
|
|
||||||
setRequired('interestPayableAccountId', accrualOnly);
|
|
||||||
setRequired('escheatLiabilityId', cashOrAccrual && this.isDormancyTrackingEnabled());
|
|
||||||
|
|
||||||
if (!cashOrAccrual) {
|
|
||||||
this.clearOtherGlMappings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildAccountingSectionConfigs() {
|
|
||||||
this.accountingSectionConfigs = [
|
|
||||||
{
|
|
||||||
key: 'assets',
|
|
||||||
title: 'Assets',
|
|
||||||
pillLabel: 'Asset',
|
|
||||||
pillClass: 'green',
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
key: 'savingsReferenceAccountId',
|
|
||||||
label: 'Savings Reference Account',
|
|
||||||
inputLabel: 'Savings reference account',
|
|
||||||
controlName: 'savingsReferenceAccountId',
|
|
||||||
options: this.assetAccountOptions,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'feesReceivableAccountId',
|
|
||||||
label: 'Fees Receivable Account',
|
|
||||||
inputLabel: 'Fees receivable account',
|
|
||||||
controlName: 'feesReceivableAccountId',
|
|
||||||
options: this.assetAccountOptions,
|
|
||||||
accrualOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'penaltiesReceivableAccountId',
|
|
||||||
label: 'Penalties Receivable Account',
|
|
||||||
inputLabel: 'Penalties receivable account',
|
|
||||||
controlName: 'penaltiesReceivableAccountId',
|
|
||||||
options: this.assetAccountOptions,
|
|
||||||
accrualOnly: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
otherMappingsControlName: 'assetsOtherGlMappings',
|
|
||||||
otherAccountOptions: this.assetAccountOptions
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'liabilities',
|
|
||||||
title: 'Liabilities',
|
|
||||||
pillLabel: 'Liability',
|
|
||||||
pillClass: 'green',
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
key: 'savingsControlAccountId',
|
|
||||||
label: 'Savings Control Account',
|
|
||||||
inputLabel: 'Savings control account',
|
|
||||||
controlName: 'savingsControlAccountId',
|
|
||||||
options: this.liabilityAccountOptions,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'transfersInSuspenseAccountId',
|
|
||||||
label: 'Transfers In Suspense Account',
|
|
||||||
inputLabel: 'Transfers in suspense account',
|
|
||||||
controlName: 'transfersInSuspenseAccountId',
|
|
||||||
options: this.liabilityAccountOptions,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'interestPayableAccountId',
|
|
||||||
label: 'Interest Payable Account',
|
|
||||||
inputLabel: 'Interest payable account',
|
|
||||||
controlName: 'interestPayableAccountId',
|
|
||||||
options: this.liabilityAccountOptions,
|
|
||||||
accrualOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'escheatLiabilityId',
|
|
||||||
label: 'Escheat Liability Account',
|
|
||||||
inputLabel: 'Escheat liability account',
|
|
||||||
controlName: 'escheatLiabilityId',
|
|
||||||
options: this.liabilityAccountOptions,
|
|
||||||
dormancyOnly: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
otherMappingsControlName: 'liabilitiesOtherGlMappings',
|
|
||||||
otherAccountOptions: this.liabilityAccountOptions
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'expenses',
|
|
||||||
title: 'Expenses',
|
|
||||||
pillLabel: 'Expense',
|
|
||||||
pillClass: 'amber',
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
key: 'interestOnSavingsAccountId',
|
|
||||||
label: 'Interest On Savings Account',
|
|
||||||
inputLabel: 'Interest on savings account',
|
|
||||||
controlName: 'interestOnSavingsAccountId',
|
|
||||||
options: this.expenseAccountOptions,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
otherMappingsControlName: 'expensesOtherGlMappings',
|
|
||||||
otherAccountOptions: this.expenseAccountOptions
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'income',
|
|
||||||
title: 'Income',
|
|
||||||
pillLabel: 'Income',
|
|
||||||
pillClass: 'blue',
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
key: 'incomeFromFeeAccountId',
|
|
||||||
label: 'Income From Fees',
|
|
||||||
inputLabel: 'Income from fees',
|
|
||||||
controlName: 'incomeFromFeeAccountId',
|
|
||||||
options: this.incomeAccountOptions,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'incomeFromPenaltyAccountId',
|
|
||||||
label: 'Income From Penalties',
|
|
||||||
inputLabel: 'Income from penalties',
|
|
||||||
controlName: 'incomeFromPenaltyAccountId',
|
|
||||||
options: this.incomeAccountOptions,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
otherMappingsControlName: 'incomeOtherGlMappings',
|
|
||||||
otherAccountOptions: this.incomeAccountOptions
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductAccounting() {
|
|
||||||
const {
|
|
||||||
assetsOtherGlMappings,
|
|
||||||
liabilitiesOtherGlMappings,
|
|
||||||
expensesOtherGlMappings,
|
|
||||||
incomeOtherGlMappings,
|
|
||||||
...accounting
|
|
||||||
} = this.DailyDepositSchemeProductAccountingForm.value;
|
|
||||||
const otherGlMappings = this.getOtherGlMappings();
|
|
||||||
return { ...accounting, ...(otherGlMappings ? { otherGlMappings } : {}) };
|
|
||||||
}
|
|
||||||
|
|
||||||
isCashOrAccrualAccounting(): boolean {
|
|
||||||
return this.accounting.isCashOrAccrualAccountingRuleId(
|
|
||||||
this.DailyDepositSchemeProductAccountingForm.value.accountingRule
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
isAccrualAccounting(): boolean {
|
|
||||||
return this.accounting.isAccrualAccountingRuleId(this.DailyDepositSchemeProductAccountingForm.value.accountingRule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-85
@@ -1,85 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="rdpch-section">
|
|
||||||
<div class="rdpch-section__header">
|
|
||||||
<i class="fa fa-file-invoice-dollar"></i>
|
|
||||||
<h3>{{ 'labels.heading.Charges' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="rdpch-section__body">
|
|
||||||
<div class="rdpch-add-row">
|
|
||||||
<mat-form-field appearance="outline" class="rdpch-add-row__select">
|
|
||||||
<mat-label>{{ 'labels.inputs.Charge' | translate }}</mat-label>
|
|
||||||
<mat-select #charge>
|
|
||||||
@for (charge of chargeData | chargesFilter: chargesDataSource : currencyCode.value; track charge) {
|
|
||||||
<mat-option [value]="charge">
|
|
||||||
{{ charge.name }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
mat-raised-button
|
|
||||||
color="primary"
|
|
||||||
class="rdpch-add-btn"
|
|
||||||
(click)="addCharge(charge)"
|
|
||||||
[disabled]="!charge.value"
|
|
||||||
>
|
|
||||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
|
||||||
{{ 'labels.buttons.Add' | translate }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (chargesDataSource.length > 0) {
|
|
||||||
<div class="rdpch-table-wrapper">
|
|
||||||
<table mat-table [dataSource]="chargesDataSource">
|
|
||||||
<ng-container matColumnDef="name">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">
|
|
||||||
{{ charge.name + ', ' + charge.currency.displaySymbol }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="chargeCalculationType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">
|
|
||||||
{{ charge.chargeCalculationType.value }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="amount">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">
|
|
||||||
{{ charge.amount }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="chargeTimeType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">
|
|
||||||
{{ charge.chargeTimeType.value }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="action">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">
|
|
||||||
<button mat-icon-button color="warn" (click)="deleteCharge(charge)">
|
|
||||||
<fa-icon icon="trash"></fa-icon>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
-69
@@ -1,69 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.rdpch-section {
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 12px 24px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-bottom: 1px solid #e8eaed;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #0ea5e9;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #202124;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__body {
|
|
||||||
padding: 16px 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpch-add-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
&__select {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpch-add-btn {
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpch-table-wrapper {
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-top: 12px;
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-103
@@ -1,103 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, Input, OnInit, inject } from '@angular/core';
|
|
||||||
import { UntypedFormControl } from '@angular/forms';
|
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
|
||||||
import { MatButton, MatIconButton } from '@angular/material/button';
|
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
||||||
import {
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow
|
|
||||||
} from '@angular/material/table';
|
|
||||||
import { ChargesFilterPipe } from '../../../../pipes/charges-filter.pipe';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-daily-deposit-scheme-product-charges-step',
|
|
||||||
templateUrl: './daily-deposit-scheme-product-charges-step.component.html',
|
|
||||||
styleUrls: ['./daily-deposit-scheme-product-charges-step.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
FaIconComponent,
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatIconButton,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow,
|
|
||||||
ChargesFilterPipe
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class DailyDepositSchemeProductChargesStepComponent implements OnInit {
|
|
||||||
dialog = inject(MatDialog);
|
|
||||||
private translateService = inject(TranslateService);
|
|
||||||
|
|
||||||
@Input() DailyDepositSchemeProductsTemplate: any;
|
|
||||||
@Input() currencyCode: UntypedFormControl;
|
|
||||||
|
|
||||||
chargeData: any;
|
|
||||||
|
|
||||||
chargesDataSource: {}[];
|
|
||||||
displayedColumns: string[] = [
|
|
||||||
'name',
|
|
||||||
'chargeCalculationType',
|
|
||||||
'amount',
|
|
||||||
'chargeTimeType',
|
|
||||||
'action'
|
|
||||||
];
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.chargeData = this.DailyDepositSchemeProductsTemplate.chargeOptions;
|
|
||||||
if (!(this.DailyDepositSchemeProductsTemplate === undefined) && this.DailyDepositSchemeProductsTemplate.id) {
|
|
||||||
this.chargesDataSource = this.DailyDepositSchemeProductsTemplate.charges;
|
|
||||||
} else {
|
|
||||||
this.chargesDataSource = [];
|
|
||||||
}
|
|
||||||
this.currencyCode.valueChanges.subscribe(() => (this.chargesDataSource = []));
|
|
||||||
}
|
|
||||||
|
|
||||||
addCharge(charge: any) {
|
|
||||||
this.chargesDataSource = this.chargesDataSource.concat([charge.value]);
|
|
||||||
charge.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteCharge(charge: any) {
|
|
||||||
const deleteChargeDialogRef = this.dialog.open(DeleteDialogComponent, {
|
|
||||||
data: { deleteContext: this.translateService.instant('labels.inputs.Charge') + ' ' + charge.name }
|
|
||||||
});
|
|
||||||
deleteChargeDialogRef.afterClosed().subscribe((response: any) => {
|
|
||||||
if (response.delete) {
|
|
||||||
this.chargesDataSource.splice(this.chargesDataSource.indexOf(charge), 1);
|
|
||||||
this.chargesDataSource = this.chargesDataSource.concat([]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductCharges() {
|
|
||||||
return {
|
|
||||||
charges: this.chargesDataSource
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-89
@@ -1,89 +0,0 @@
|
|||||||
<!--
|
|
||||||
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/.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<form [formGroup]="DailyDepositSchemeProductCurrencyForm">
|
|
||||||
<div class="rdpc-section">
|
|
||||||
<div class="rdpc-section__header">
|
|
||||||
<i class="fas fa-coins"></i>
|
|
||||||
<span>Currency & Account Status Configuration</span>
|
|
||||||
</div>
|
|
||||||
<div class="rdpc-body">
|
|
||||||
<div class="rdpc-grid">
|
|
||||||
<mat-form-field appearance="outline" class="rdpc-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
|
|
||||||
<mat-select
|
|
||||||
formControlName="currencyCode"
|
|
||||||
matTooltip="{{ 'tooltips.The currency in which the deposit will be disbursed' | translate }}"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
@for (currency of currencyData; track currency) {
|
|
||||||
<mat-option [value]="currency.code">
|
|
||||||
{{ currency.name }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
<mat-hint>Default: Indian Rupee - auto-set for domestic products</mat-hint>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpc-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
|
|
||||||
<mat-select
|
|
||||||
formControlName="digitsAfterDecimal"
|
|
||||||
matTooltip="{{ 'tooltips.Decimal places to be used to track and report' | translate }}"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
@for (places of decimalPlacesData; track places) {
|
|
||||||
<mat-option [value]="places">{{ places }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rdpc-alert">
|
|
||||||
<div class="rdpc-alert__content">
|
|
||||||
<h4>Dormancy Alert</h4>
|
|
||||||
<p>Enable automated notifications when an account becomes inactive based on product rules.</p>
|
|
||||||
</div>
|
|
||||||
<mat-radio-group class="rdpc-alert__choices" formControlName="dormancyAlert">
|
|
||||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
|
||||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
|
||||||
</mat-radio-group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rdpc-alert">
|
|
||||||
<div class="rdpc-alert__content">
|
|
||||||
<h4>Nomination Facility</h4>
|
|
||||||
<p>Allow account holders to designate individuals to receive funds in the event of account holder's death.</p>
|
|
||||||
</div>
|
|
||||||
<mat-radio-group class="rdpc-alert__choices" formControlName="nominationFacility">
|
|
||||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
|
||||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
|
||||||
</mat-radio-group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rdpc-alert">
|
|
||||||
<div class="rdpc-alert__content">
|
|
||||||
<h4>Auto Debit Facility</h4>
|
|
||||||
<p>Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.</p>
|
|
||||||
</div>
|
|
||||||
<mat-radio-group class="rdpc-alert__choices" formControlName="autoDebitFacility">
|
|
||||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
|
||||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
|
||||||
</mat-radio-group>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
-102
@@ -1,102 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$accent: #0ea5e9;
|
|
||||||
$border: #e2e8f0;
|
|
||||||
$gray-50: #f8f9fa;
|
|
||||||
$gray-200: #e8eaed;
|
|
||||||
$gray-900: #202124;
|
|
||||||
|
|
||||||
.rdpc-section {
|
|
||||||
border: 1px solid $border;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
& + & {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 12px 24px;
|
|
||||||
background: $gray-50;
|
|
||||||
border-bottom: 1px solid $gray-200;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px;
|
|
||||||
color: $accent;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $gray-900;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpc-body {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpc-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 4px 16px;
|
|
||||||
padding: 16px 24px;
|
|
||||||
|
|
||||||
@media (width <= 600px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpc-field {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpc-alert {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 24px;
|
|
||||||
padding: 18px 24px 24px;
|
|
||||||
border-top: 1px solid $border;
|
|
||||||
background: #f8fafc;
|
|
||||||
|
|
||||||
@media (width <= 760px) {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
h4 {
|
|
||||||
margin: 0 0 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $gray-900;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: #64748b;
|
|
||||||
max-width: 540px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__choices {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 18px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-104
@@ -1,104 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
|
||||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
|
||||||
import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-daily-deposit-scheme-product-currency-step',
|
|
||||||
templateUrl: './daily-deposit-scheme-product-currency-step.component.html',
|
|
||||||
styleUrls: ['./daily-deposit-scheme-product-currency-step.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
MatTooltip,
|
|
||||||
MatRadioGroup,
|
|
||||||
MatRadioButton
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class DailyDepositSchemeProductCurrencyStepComponent implements OnInit {
|
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
|
||||||
|
|
||||||
@Input() DailyDepositSchemeProductsTemplate: any;
|
|
||||||
|
|
||||||
DailyDepositSchemeProductCurrencyForm: UntypedFormGroup;
|
|
||||||
|
|
||||||
currencyData: any;
|
|
||||||
decimalPlacesData = [
|
|
||||||
2,
|
|
||||||
0,
|
|
||||||
3
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.createDailyDepositSchemeProductCurrencyForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.currencyData = this.DailyDepositSchemeProductsTemplate.currencyOptions.filter(
|
|
||||||
(c: { code: string }) => c.code === 'INR'
|
|
||||||
);
|
|
||||||
|
|
||||||
this.DailyDepositSchemeProductCurrencyForm.patchValue({
|
|
||||||
currencyCode: this.DailyDepositSchemeProductsTemplate.currency?.code || this.currencyData?.[0]?.code || '',
|
|
||||||
digitsAfterDecimal:
|
|
||||||
this.DailyDepositSchemeProductsTemplate.digitsAfterDecimal ??
|
|
||||||
this.DailyDepositSchemeProductsTemplate.currency?.decimalPlaces ??
|
|
||||||
2,
|
|
||||||
dormancyAlert: this.DailyDepositSchemeProductsTemplate.dormancyAlert ?? false,
|
|
||||||
isDormancyTrackingActive: this.DailyDepositSchemeProductsTemplate.isDormancyTrackingActive ?? false,
|
|
||||||
daysToInactive: this.DailyDepositSchemeProductsTemplate.daysToInactive ?? 30,
|
|
||||||
daysToDormancy: this.DailyDepositSchemeProductsTemplate.daysToDormancy ?? 60,
|
|
||||||
daysToEscheat: this.DailyDepositSchemeProductsTemplate.daysToEscheat ?? 90,
|
|
||||||
nominationFacility: this.DailyDepositSchemeProductsTemplate.nominationFacility ?? false,
|
|
||||||
autoDebitFacility: this.DailyDepositSchemeProductsTemplate.autoDebitFacility ?? false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
createDailyDepositSchemeProductCurrencyForm() {
|
|
||||||
this.DailyDepositSchemeProductCurrencyForm = this.formBuilder.group({
|
|
||||||
currencyCode: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
digitsAfterDecimal: [
|
|
||||||
2,
|
|
||||||
[
|
|
||||||
Validators.required,
|
|
||||||
Validators.min(0)
|
|
||||||
]
|
|
||||||
],
|
|
||||||
dormancyAlert: [false],
|
|
||||||
isDormancyTrackingActive: [false],
|
|
||||||
daysToInactive: [30],
|
|
||||||
daysToDormancy: [60],
|
|
||||||
daysToEscheat: [90],
|
|
||||||
nominationFacility: [false],
|
|
||||||
autoDebitFacility: [false]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductCurrency() {
|
|
||||||
const formValue = this.DailyDepositSchemeProductCurrencyForm.value;
|
|
||||||
const result: any = {
|
|
||||||
currencyCode: formValue.currencyCode,
|
|
||||||
digitsAfterDecimal: formValue.digitsAfterDecimal,
|
|
||||||
dormancyAlert: formValue.dormancyAlert,
|
|
||||||
nominationFacility: formValue.nominationFacility,
|
|
||||||
autoDebitFacility: formValue.autoDebitFacility
|
|
||||||
};
|
|
||||||
if (formValue.dormancyAlert) {
|
|
||||||
result.daysToInactive = formValue.daysToInactive;
|
|
||||||
result.daysToDormancy = formValue.daysToDormancy;
|
|
||||||
result.daysToEscheat = formValue.daysToEscheat;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-77
@@ -1,77 +0,0 @@
|
|||||||
<!--
|
|
||||||
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/.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<form [formGroup]="DailyDepositSchemeProductDetailsForm">
|
|
||||||
<div class="rdpd-section">
|
|
||||||
<div class="rdpd-section__header">
|
|
||||||
<i class="fa fa-info-circle"></i>
|
|
||||||
<h3>Account Type Creation</h3>
|
|
||||||
</div>
|
|
||||||
<div class="rdpd-grid">
|
|
||||||
<mat-form-field appearance="outline" class="rdpd-field">
|
|
||||||
<mat-label>Account Type Group</mat-label>
|
|
||||||
<input matInput formControlName="accountTypeGroup" required />
|
|
||||||
@if (DailyDepositSchemeProductDetailsForm.controls.accountTypeGroup.hasError('required')) {
|
|
||||||
<mat-error>Account Type Group <strong>is required</strong></mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpd-field">
|
|
||||||
<mat-label>A/c Type</mat-label>
|
|
||||||
<input matInput formControlName="shortName" maxlength="4" required />
|
|
||||||
@if (DailyDepositSchemeProductDetailsForm.controls.shortName.hasError('required')) {
|
|
||||||
<mat-error>A/c Type <strong>is required</strong></mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpd-field rdpd-field--full">
|
|
||||||
<mat-label>A/c Type Description</mat-label>
|
|
||||||
<input matInput formControlName="name" required />
|
|
||||||
@if (DailyDepositSchemeProductDetailsForm.controls.name.hasError('required')) {
|
|
||||||
<mat-error>A/c Type Description <strong>is required</strong></mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpd-field">
|
|
||||||
<mat-label>Interest Rate (%)</mat-label>
|
|
||||||
<input matInput type="number" formControlName="interestRate" required />
|
|
||||||
@if (DailyDepositSchemeProductDetailsForm.controls.interestRate.hasError('required')) {
|
|
||||||
<mat-error>Interest Rate <strong>is required</strong></mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpd-field">
|
|
||||||
<mat-label>Auto Interest Post</mat-label>
|
|
||||||
<mat-select formControlName="autoInterestPost">
|
|
||||||
@for (option of yesNoOptions; track option.value) {
|
|
||||||
<mat-option [value]="option.value">{{ option.label }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProductDetailsForm.get('autoInterestPost')?.value) {
|
|
||||||
<mat-form-field appearance="outline" class="rdpd-field">
|
|
||||||
<mat-label>Auto Interest Post When</mat-label>
|
|
||||||
<mat-select formControlName="autoInterestPostWhen" required>
|
|
||||||
@for (option of autoInterestPostWhenData; track option.id) {
|
|
||||||
<mat-option [value]="option.id">{{ option.value }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
@if (DailyDepositSchemeProductDetailsForm.controls.autoInterestPostWhen.hasError('required')) {
|
|
||||||
<mat-error>Auto Interest Post When <strong>is required</strong></mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpd-field">
|
|
||||||
<mat-label>Auto Interest Post Day</mat-label>
|
|
||||||
<input matInput type="number" formControlName="autoInterestPostDay" />
|
|
||||||
</mat-form-field>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
-81
@@ -1,81 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$accent: #0ea5e9;
|
|
||||||
$border: #e2e8f0;
|
|
||||||
$gray-50: #f8f9fa;
|
|
||||||
$gray-200: #e8eaed;
|
|
||||||
$gray-900: #202124;
|
|
||||||
|
|
||||||
.rdpd-section {
|
|
||||||
border: 1px solid $border;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
& + & {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 12px 24px;
|
|
||||||
background: $gray-50;
|
|
||||||
border-bottom: 1px solid $gray-200;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px;
|
|
||||||
color: $accent;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $gray-900;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpd-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 4px 16px;
|
|
||||||
padding: 16px 24px;
|
|
||||||
|
|
||||||
@media (width <= 600px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpd-grid--classification {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
|
|
||||||
@media (width <= 900px) {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 600px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpd-field {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--full {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpd-checkbox-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 56px;
|
|
||||||
}
|
|
||||||
-177
@@ -1,177 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, OnInit, OnChanges, Input, SimpleChanges, inject, ChangeDetectorRef } from '@angular/core';
|
|
||||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
|
||||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
|
||||||
import { Dates } from 'app/core/utils/dates';
|
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-daily-deposit-scheme-product-details-step',
|
|
||||||
templateUrl: './daily-deposit-scheme-product-details-step.component.html',
|
|
||||||
styleUrls: ['./daily-deposit-scheme-product-details-step.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
MatTooltip,
|
|
||||||
CdkTextareaAutosize
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class DailyDepositSchemeProductDetailsStepComponent implements OnInit, OnChanges {
|
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
|
||||||
private dateUtils = inject(Dates);
|
|
||||||
private settingsService = inject(SettingsService);
|
|
||||||
private cdr = inject(ChangeDetectorRef);
|
|
||||||
|
|
||||||
@Input() DailyDepositSchemeProductsTemplate: any;
|
|
||||||
|
|
||||||
DailyDepositSchemeProductDetailsForm: UntypedFormGroup;
|
|
||||||
accountTypeData: string[] = [];
|
|
||||||
customerSegmentData: string[] = [];
|
|
||||||
liabilityAccountOptions: any[] = [];
|
|
||||||
/** "Auto Interest Post When" options - the legacy screen's own field, sourced from the same
|
|
||||||
* enum the (hidden) interest-posting-period setting uses, so the two always stay in sync. */
|
|
||||||
autoInterestPostWhenData: any[] = [];
|
|
||||||
yesNoOptions: Array<{ value: boolean; label: string }> = [
|
|
||||||
{ value: true, label: 'Yes' },
|
|
||||||
{ value: false, label: 'No' }
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.createDailyDepositSchemeProductDetailsForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
if (this.DailyDepositSchemeProductsTemplate) {
|
|
||||||
this.applyTemplate(this.DailyDepositSchemeProductsTemplate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
|
||||||
if (changes.DailyDepositSchemeProductsTemplate?.currentValue) {
|
|
||||||
this.applyTemplate(changes.DailyDepositSchemeProductsTemplate.currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createDailyDepositSchemeProductDetailsForm() {
|
|
||||||
this.DailyDepositSchemeProductDetailsForm = this.formBuilder.group({
|
|
||||||
accountTypeGroup: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
name: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
shortName: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
description: [''],
|
|
||||||
autoInterestPost: [true],
|
|
||||||
autoInterestPostWhen: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
autoInterestPostDay: [''],
|
|
||||||
interestRate: [
|
|
||||||
2,
|
|
||||||
[
|
|
||||||
Validators.required,
|
|
||||||
Validators.min(0)
|
|
||||||
]
|
|
||||||
],
|
|
||||||
accountType: [
|
|
||||||
null,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
glAccountId: [null],
|
|
||||||
effectiveTo: [''],
|
|
||||||
endDate: [''],
|
|
||||||
introducerRequired: [false],
|
|
||||||
shareLinkingEnabled: [false]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get minEndDate(): Date | null {
|
|
||||||
const v = this.DailyDepositSchemeProductDetailsForm.get('effectiveTo')?.value;
|
|
||||||
if (!v) return null;
|
|
||||||
const d = new Date(v);
|
|
||||||
d.setDate(d.getDate() + 1);
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getOptionNames(options: any[]): string[] {
|
|
||||||
if (!Array.isArray(options)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return options
|
|
||||||
.map((option) => option?.name ?? option?.value ?? option)
|
|
||||||
.filter((value) => typeof value === 'string' && value.trim().length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseDate(dateValue: any): Date | null {
|
|
||||||
if (!dateValue) return null;
|
|
||||||
if (Array.isArray(dateValue)) {
|
|
||||||
return new Date(dateValue[0], dateValue[1] - 1, dateValue[2]);
|
|
||||||
}
|
|
||||||
if (dateValue instanceof Date) {
|
|
||||||
return dateValue;
|
|
||||||
}
|
|
||||||
return new Date(dateValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
compareById(o1: any, o2: any): boolean {
|
|
||||||
return o1 != null && o2 != null && Number(o1) === Number(o2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private applyTemplate(template: any) {
|
|
||||||
this.accountTypeData = this.getOptionNames(template.accountTypeOptions);
|
|
||||||
this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? [];
|
|
||||||
this.autoInterestPostWhenData = template.interestPostingPeriodTypeOptions ?? [];
|
|
||||||
this.cdr.detectChanges();
|
|
||||||
|
|
||||||
const isEditing = !!template.id;
|
|
||||||
this.DailyDepositSchemeProductDetailsForm.patchValue({
|
|
||||||
accountTypeGroup: template.accountTypeGroup || 'DDS',
|
|
||||||
name: isEditing ? template.name : template.name || '',
|
|
||||||
shortName: isEditing ? template.shortName : template.shortName || '',
|
|
||||||
description: template.description || '',
|
|
||||||
autoInterestPost: template.interestPostingPeriodType ? true : (template.autoInterestPost ?? true),
|
|
||||||
autoInterestPostWhen: template.interestPostingPeriodType?.id ?? this.autoInterestPostWhenData[0]?.id ?? '',
|
|
||||||
autoInterestPostDay: template.autoInterestPostDay ?? template.frequencyFixDay ?? '',
|
|
||||||
interestRate: template.nominalAnnualInterestRate ?? 2,
|
|
||||||
accountType: template.accountType ?? this.accountTypeData[0] ?? null,
|
|
||||||
glAccountId: template.glAccountId ?? null,
|
|
||||||
effectiveTo: this.parseDate(template.effectiveTo),
|
|
||||||
endDate: this.parseDate(template.endDate),
|
|
||||||
introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes',
|
|
||||||
shareLinkingEnabled: !!template.shareLinkingEnabled
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductDetails() {
|
|
||||||
const formValue = { ...this.DailyDepositSchemeProductDetailsForm.value };
|
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
|
||||||
|
|
||||||
if (formValue.effectiveTo instanceof Date) {
|
|
||||||
formValue.effectiveTo = this.dateUtils.formatDate(formValue.effectiveTo, dateFormat) || '';
|
|
||||||
}
|
|
||||||
if (formValue.endDate instanceof Date) {
|
|
||||||
formValue.endDate = this.dateUtils.formatDate(formValue.endDate, dateFormat) || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// accountTypeGroup / autoInterestPost / autoInterestPostWhen are the legacy screen's own
|
|
||||||
// fields - they don't map to a real product API parameter by that name, so they're folded
|
|
||||||
// into the actual payload fields (description, autoPost, interestPostingPeriodType) by the
|
|
||||||
// parent component rather than sent through as-is.
|
|
||||||
return formValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-109
@@ -1,109 +0,0 @@
|
|||||||
<!--
|
|
||||||
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/.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- ── Bank Interest Rate Chart (editable reference table from DB) ───────── -->
|
|
||||||
<div class="rdpi-section rdpi-ref-section">
|
|
||||||
<div class="rdpi-section__header">
|
|
||||||
<i class="fa fa-table"></i>
|
|
||||||
<h3>Bank Interest Rate Chart <span class="rdpi-ref-badge">Reference / Editable</span></h3>
|
|
||||||
<span class="rdpi-spacer"></span>
|
|
||||||
<button type="button" mat-raised-button color="primary" class="rdpi-add-btn" (click)="addSlab()">
|
|
||||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
|
||||||
Add Slab
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (_referenceVersionLabel) {
|
|
||||||
<div class="rdpi-ref-meta">
|
|
||||||
<span><strong>Version:</strong> {{ _referenceVersionLabel }}</span>
|
|
||||||
@if (_referenceRemarks) {
|
|
||||||
<span> | {{ _referenceRemarks }}</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (_editableSlabs.length === 0) {
|
|
||||||
<div class="rdpi-empty-msg">
|
|
||||||
<h3>No interest rate slabs defined. Click <strong>Add Slab</strong> to add one.</h3>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (_editableSlabs.length > 0) {
|
|
||||||
<div class="rdpi-table-wrapper">
|
|
||||||
<table mat-table [dataSource]="_editableSlabs" class="rdpi-ref-table">
|
|
||||||
<ng-container matColumnDef="srNo">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Sr.</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.srNo }}</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="description">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Particular</th>
|
|
||||||
<td mat-cell *matCellDef="let slab; let i = index">
|
|
||||||
<input
|
|
||||||
class="rdpi-inline-input"
|
|
||||||
[value]="slab.description"
|
|
||||||
(change)="editSlab(i, 'description', $any($event.target).value)"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="fromPeriod">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
|
||||||
<td mat-cell *matCellDef="let slab; let i = index">
|
|
||||||
<input
|
|
||||||
class="rdpi-inline-input rdpi-inline-input--num"
|
|
||||||
type="number"
|
|
||||||
[value]="slab.fromPeriod"
|
|
||||||
(change)="editSlab(i, 'fromPeriod', +$any($event.target).value)"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="toPeriod">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
|
||||||
<td mat-cell *matCellDef="let slab; let i = index">
|
|
||||||
<input
|
|
||||||
class="rdpi-inline-input rdpi-inline-input--num"
|
|
||||||
type="number"
|
|
||||||
[value]="slab.toPeriod ?? ''"
|
|
||||||
(change)="editSlab(i, 'toPeriod', $any($event.target).value ? +$any($event.target).value : null)"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="periodTypeLabel">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Period</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.periodTypeLabel }}</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="annualInterestRate">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Rate (%)</th>
|
|
||||||
<td mat-cell *matCellDef="let slab; let i = index">
|
|
||||||
<input
|
|
||||||
class="rdpi-inline-input rdpi-inline-input--rate"
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
[value]="slab.annualInterestRate"
|
|
||||||
(change)="editSlab(i, 'annualInterestRate', +$any($event.target).value)"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="refSlabColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: refSlabColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rdpi-ref-footer">
|
|
||||||
<p class="rdpi-ref-hint">
|
|
||||||
Edit the rates above. When you <strong>Save</strong> the product, a new version will be inserted in the database
|
|
||||||
— previous rates are preserved as history.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
-215
@@ -1,215 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.rdpi-section {
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
& + & {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 12px 24px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-bottom: 1px solid #e8eaed;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #0ea5e9;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #202124;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-spacer {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-content {
|
|
||||||
padding: 16px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 4px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-field {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--full {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-checkbox-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-add-btn {
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-chart-block {
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 16px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
background: #fafbfc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-chart-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-slab-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 8px 0 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-empty-msg {
|
|
||||||
text-align: center;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #5f6368;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-table-wrapper {
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-top: 8px;
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr.incentives-row {
|
|
||||||
height: 0;
|
|
||||||
|
|
||||||
> td {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-incentives {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-incentive-card {
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-incentive-title {
|
|
||||||
margin: 0 0 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #202124;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-incentive-add {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 600px) {
|
|
||||||
.rdpi-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Editable reference-rate table ───────────────────────────── */
|
|
||||||
.rdpi-ref-section {
|
|
||||||
margin-top: 24px;
|
|
||||||
border-color: #bfdbfe;
|
|
||||||
background: #f0f7ff;
|
|
||||||
|
|
||||||
.rdpi-section__header {
|
|
||||||
background: #dbeafe;
|
|
||||||
color: #1e3a5f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-ref-badge {
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 500;
|
|
||||||
background: #3b82f6;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 2px 8px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-ref-meta {
|
|
||||||
padding: 8px 16px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #555;
|
|
||||||
border-bottom: 1px solid #bfdbfe;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-ref-table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-inline-input {
|
|
||||||
border: 1px solid #cbd5e1;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 3px 6px;
|
|
||||||
font-size: 13px;
|
|
||||||
width: 100%;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #3b82f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--num {
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--rate {
|
|
||||||
width: 80px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-ref-footer {
|
|
||||||
padding: 10px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpi-ref-hint {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #64748b;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
-717
@@ -1,717 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { Component, OnInit, OnChanges, Input, inject } from '@angular/core';
|
|
||||||
import {
|
|
||||||
UntypedFormGroup,
|
|
||||||
UntypedFormBuilder,
|
|
||||||
Validators,
|
|
||||||
UntypedFormArray,
|
|
||||||
ReactiveFormsModule
|
|
||||||
} from '@angular/forms';
|
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
|
||||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
|
||||||
|
|
||||||
/** Custom Components */
|
|
||||||
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
|
|
||||||
import { DepositProductIncentiveFormDialogComponent } from 'app/products/deposit-product-incentive-form-dialog/deposit-product-incentive-form-dialog.component';
|
|
||||||
|
|
||||||
/** Dialog Components */
|
|
||||||
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
|
|
||||||
import { SelectBase } from 'app/shared/form-dialog/formfield/model/select-base';
|
|
||||||
import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base';
|
|
||||||
|
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
|
||||||
import { Dates } from 'app/core/utils/dates';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
import { MatButton } from '@angular/material/button';
|
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
||||||
|
|
||||||
import { MatDivider } from '@angular/material/divider';
|
|
||||||
import { MatCheckbox } from '@angular/material/checkbox';
|
|
||||||
import {
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow
|
|
||||||
} from '@angular/material/table';
|
|
||||||
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
|
||||||
import { FindPipe } from '../../../../pipes/find.pipe';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-daily-deposit-scheme-product-interest-rate-chart-step',
|
|
||||||
templateUrl: './daily-deposit-scheme-product-interest-rate-chart-step.component.html',
|
|
||||||
styleUrls: ['./daily-deposit-scheme-product-interest-rate-chart-step.component.scss'],
|
|
||||||
animations: [
|
|
||||||
trigger('expandChartSlab', [
|
|
||||||
state('collapsed', style({ height: '0px', minHeight: '0' })),
|
|
||||||
state('expanded', style({ height: '*' })),
|
|
||||||
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
|
|
||||||
])
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
FaIconComponent,
|
|
||||||
MatDivider,
|
|
||||||
MatButton,
|
|
||||||
MatCheckbox,
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow,
|
|
||||||
FindPipe
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class DailyDepositSchemeProductInterestRateChartStepComponent implements OnInit {
|
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
|
||||||
dialog = inject(MatDialog);
|
|
||||||
private dateUtils = inject(Dates);
|
|
||||||
private settingsService = inject(SettingsService);
|
|
||||||
private translateService = inject(TranslateService);
|
|
||||||
|
|
||||||
@Input() DailyDepositSchemeProductsTemplate: any;
|
|
||||||
/** Reference rate chart fetched from m_rd_interest_rate_effective / m_rd_interest_rate_slab_history */
|
|
||||||
@Input() set rdInterestRates(rates: any[]) {
|
|
||||||
if (rates && rates.length > 0) {
|
|
||||||
// Use the first active version's slabs as the editable working copy
|
|
||||||
const activeVersion = rates.find((v: any) => v.isActive) ?? rates[0];
|
|
||||||
this._referenceVersionLabel = activeVersion.versionLabel;
|
|
||||||
this._referenceRemarks = activeVersion.remarks;
|
|
||||||
// Deep-clone slabs so the original is not mutated
|
|
||||||
this._editableSlabs = (activeVersion.slabs ?? []).map((s: any) => ({ ...s }));
|
|
||||||
this.ensureSlabRangeRules(this._editableSlabs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Slabs the user is editing in the reference-rate table */
|
|
||||||
_editableSlabs: any[] = [];
|
|
||||||
_referenceVersionLabel = '';
|
|
||||||
_referenceRemarks = '';
|
|
||||||
|
|
||||||
/** Columns for the editable reference-rate table */
|
|
||||||
readonly refSlabColumns = [
|
|
||||||
'srNo',
|
|
||||||
'description',
|
|
||||||
'fromPeriod',
|
|
||||||
'toPeriod',
|
|
||||||
'periodTypeLabel',
|
|
||||||
'annualInterestRate'
|
|
||||||
];
|
|
||||||
|
|
||||||
/** Returns the payload to save as a new rate version (called by parent after product save) */
|
|
||||||
get modifiedRdInterestRatePayload(): any {
|
|
||||||
return {
|
|
||||||
versionLabel: this._referenceVersionLabel || 'Rate Version ' + new Date().toISOString().slice(0, 10),
|
|
||||||
remarks: this._referenceRemarks,
|
|
||||||
slabs: this._editableSlabs.map((s: any, idx: number) => ({
|
|
||||||
srNo: s.srNo ?? idx + 1,
|
|
||||||
description: s.description,
|
|
||||||
periodTypeEnum: s.periodTypeEnum ?? 1,
|
|
||||||
fromPeriod: s.fromPeriod,
|
|
||||||
toPeriod: s.toPeriod ?? null,
|
|
||||||
annualInterestRate: s.annualInterestRate
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
editSlab(index: number, field: string, value: any) {
|
|
||||||
const slabs = this._editableSlabs.map((slab: any) => ({ ...slab }));
|
|
||||||
const current = slabs[index];
|
|
||||||
if (!current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextValue = this.normalizeSlabValue(value);
|
|
||||||
const oldFrom = this.normalizeSlabValue(current.fromPeriod);
|
|
||||||
const oldTo = this.normalizeSlabValue(current.toPeriod);
|
|
||||||
|
|
||||||
current[field] = nextValue;
|
|
||||||
|
|
||||||
// Keep the first slab anchored at 0 so the chart always starts from zero.
|
|
||||||
if (index === 0 && field === 'fromPeriod') {
|
|
||||||
current.fromPeriod = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field === 'fromPeriod' && nextValue === null) {
|
|
||||||
current.fromPeriod = oldFrom;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field === 'fromPeriod') {
|
|
||||||
const effectiveFrom = this.normalizeSlabValue(current.fromPeriod);
|
|
||||||
const delta = effectiveFrom - oldFrom;
|
|
||||||
|
|
||||||
if (current.toPeriod !== null && current.toPeriod !== undefined) {
|
|
||||||
current.toPeriod = this.normalizeSlabValue(current.toPeriod) + delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.shiftFollowingSlabs(slabs, index, delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field === 'toPeriod') {
|
|
||||||
if (index < slabs.length - 1 && nextValue === null) {
|
|
||||||
// Non-final slabs should always have a closing boundary so later slabs can stay contiguous.
|
|
||||||
current.toPeriod = oldTo;
|
|
||||||
} else {
|
|
||||||
const delta = (this.normalizeSlabValue(current.toPeriod) ?? 0) - (oldTo ?? 0);
|
|
||||||
this.shiftFollowingSlabs(slabs, index, delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ensureSlabRangeRules(slabs);
|
|
||||||
this._editableSlabs = slabs;
|
|
||||||
}
|
|
||||||
|
|
||||||
addSlab() {
|
|
||||||
const slabs = this._editableSlabs.map((slab: any) => ({ ...slab }));
|
|
||||||
const nextSr = slabs.length + 1;
|
|
||||||
const lastSlab = slabs[slabs.length - 1];
|
|
||||||
const lastFrom = this.normalizeSlabValue(lastSlab?.fromPeriod) ?? 0;
|
|
||||||
|
|
||||||
if (lastSlab && this.normalizeSlabValue(lastSlab.toPeriod) === null) {
|
|
||||||
// Split the open-ended tail so the new slab can become the new last row.
|
|
||||||
// This keeps the sequence contiguous while preserving the existing tail start.
|
|
||||||
lastSlab.toPeriod = lastFrom;
|
|
||||||
}
|
|
||||||
|
|
||||||
const previousSlab = slabs[slabs.length - 1];
|
|
||||||
this._editableSlabs = [
|
|
||||||
...slabs,
|
|
||||||
{
|
|
||||||
srNo: nextSr,
|
|
||||||
description: '',
|
|
||||||
periodTypeEnum: 1,
|
|
||||||
periodTypeLabel: 'Days',
|
|
||||||
fromPeriod: slabs.length === 0 ? 0 : this.nextFromPeriod(previousSlab),
|
|
||||||
toPeriod: null,
|
|
||||||
annualInterestRate: null
|
|
||||||
}
|
|
||||||
];
|
|
||||||
this.ensureSlabRangeRules(this._editableSlabs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private normalizeSlabValue(value: any): number | null {
|
|
||||||
if (value === null || value === undefined || value === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsed = Number(value);
|
|
||||||
return Number.isNaN(parsed) ? null : parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private nextFromPeriod(previousSlab: any): number {
|
|
||||||
const previousTo = this.normalizeSlabValue(previousSlab?.toPeriod);
|
|
||||||
const previousFrom = this.normalizeSlabValue(previousSlab?.fromPeriod) ?? 0;
|
|
||||||
return previousTo === null ? previousFrom + 1 : previousTo + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private shiftFollowingSlabs(slabs: any[], index: number, delta: number): void {
|
|
||||||
if (!delta) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = index + 1; i < slabs.length; i++) {
|
|
||||||
const slab = slabs[i];
|
|
||||||
const currentFrom = this.normalizeSlabValue(slab.fromPeriod);
|
|
||||||
const currentTo = this.normalizeSlabValue(slab.toPeriod);
|
|
||||||
|
|
||||||
if (currentFrom !== null) {
|
|
||||||
slab.fromPeriod = currentFrom + delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentTo !== null) {
|
|
||||||
slab.toPeriod = currentTo + delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ensureSlabRangeRules(slabs: any[]): void {
|
|
||||||
if (slabs.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
slabs[0].fromPeriod = 0;
|
|
||||||
|
|
||||||
for (let i = 1; i < slabs.length; i++) {
|
|
||||||
const previous = slabs[i - 1];
|
|
||||||
const previousTo = this.normalizeSlabValue(previous.toPeriod);
|
|
||||||
if (previousTo === null) {
|
|
||||||
slabs[i].fromPeriod = this.normalizeSlabValue(slabs[i].fromPeriod);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
slabs[i].fromPeriod = previousTo + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep the last slab open-ended so the chart ends with null.
|
|
||||||
slabs[slabs.length - 1].toPeriod = null;
|
|
||||||
|
|
||||||
// Prevent inverted ranges on intermediate rows.
|
|
||||||
for (let i = 0; i < slabs.length - 1; i++) {
|
|
||||||
const fromPeriod = this.normalizeSlabValue(slabs[i].fromPeriod);
|
|
||||||
const toPeriod = this.normalizeSlabValue(slabs[i].toPeriod);
|
|
||||||
if (fromPeriod !== null && toPeriod !== null && toPeriod < fromPeriod) {
|
|
||||||
slabs[i].toPeriod = fromPeriod;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DailyDepositSchemeProductInterestRateChartForm: UntypedFormGroup;
|
|
||||||
|
|
||||||
periodTypeData: any;
|
|
||||||
entityTypeData: any;
|
|
||||||
attributeNameData: any;
|
|
||||||
conditionTypeData: any;
|
|
||||||
genderData: any;
|
|
||||||
clientTypeData: any;
|
|
||||||
clientClassificationData: any;
|
|
||||||
incentiveTypeData: any;
|
|
||||||
|
|
||||||
chartSlabsDisplayedColumns: any[] = [];
|
|
||||||
chartSlabsIncentivesDisplayedColumns: string[] = ['incentives'];
|
|
||||||
incentivesDisplayedColumns: string[] = [
|
|
||||||
'entityType',
|
|
||||||
'attributeName',
|
|
||||||
'conditionType',
|
|
||||||
'attributeValue',
|
|
||||||
'incentiveType',
|
|
||||||
'amount',
|
|
||||||
'actions'
|
|
||||||
];
|
|
||||||
|
|
||||||
minDate = new Date(2000, 0, 1);
|
|
||||||
maxDate = new Date(new Date().setFullYear(new Date().getFullYear() + 10));
|
|
||||||
|
|
||||||
expandChartSlabIndex: number[] = [];
|
|
||||||
chartDetailData: any = [];
|
|
||||||
chartsDetail: any[] = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {FormBuilder} formBuilder Form Builder.
|
|
||||||
* @param {MatDialog} dialog Dialog reference.
|
|
||||||
* @param {Dates} dateUtils Date Utils to format date.
|
|
||||||
* @param {SettingsService} settingsService Settings Service.
|
|
||||||
*/
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.createDailyDepositSchemeProductInterestRateChartForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.periodTypeData = this.DailyDepositSchemeProductsTemplate.chartTemplate.periodTypes;
|
|
||||||
this.entityTypeData = this.DailyDepositSchemeProductsTemplate.chartTemplate.entityTypeOptions;
|
|
||||||
this.attributeNameData = this.DailyDepositSchemeProductsTemplate.chartTemplate.attributeNameOptions;
|
|
||||||
this.conditionTypeData = this.DailyDepositSchemeProductsTemplate.chartTemplate.conditionTypeOptions;
|
|
||||||
this.genderData = this.DailyDepositSchemeProductsTemplate.chartTemplate.genderOptions;
|
|
||||||
this.clientTypeData = this.DailyDepositSchemeProductsTemplate.chartTemplate.clientTypeOptions;
|
|
||||||
this.clientClassificationData = this.DailyDepositSchemeProductsTemplate.chartTemplate.clientClassificationOptions;
|
|
||||||
this.incentiveTypeData = this.DailyDepositSchemeProductsTemplate.chartTemplate.incentiveTypeOptions;
|
|
||||||
|
|
||||||
// assignFormData() intentionally not called — old chart form is removed from UI.
|
|
||||||
// The DailyDepositSchemeProductInterestRateChartForm is kept only for stepControl
|
|
||||||
// validity; it remains empty (valid) so the preview tab becomes available.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign form the values for edit Daily Deposit Scheme
|
|
||||||
assignFormData() {
|
|
||||||
this.addChart();
|
|
||||||
const isChartArray = Array.isArray(this.DailyDepositSchemeProductsTemplate.activeChart);
|
|
||||||
if (this.DailyDepositSchemeProductsTemplate.activeChart) {
|
|
||||||
if (!isChartArray) {
|
|
||||||
this.chartDetailData.push(this.DailyDepositSchemeProductsTemplate.activeChart);
|
|
||||||
} else {
|
|
||||||
this.chartDetailData = this.DailyDepositSchemeProductsTemplate.activeChart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the array of Objects from the retrived value
|
|
||||||
this.getChartsDetailsData();
|
|
||||||
|
|
||||||
// Iterates for every chart in charts
|
|
||||||
this.charts.controls.forEach((chartDetailControl: UntypedFormGroup, i: number) => {
|
|
||||||
if (!this.chartsDetail[i]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate for every chartSlab in chart
|
|
||||||
this.chartsDetail[i].chartSlabs.forEach((chartSlabDetail: any, j: number) => {
|
|
||||||
const chartSlabInfo = this.formBuilder.group({
|
|
||||||
id: [chartSlabDetail.id],
|
|
||||||
amountRangeFrom: [chartSlabDetail.amountRangeFrom],
|
|
||||||
amountRangeTo: [chartSlabDetail.amountRangeTo],
|
|
||||||
annualInterestRate: [
|
|
||||||
chartSlabDetail.annualInterestRate,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
description: [
|
|
||||||
chartSlabDetail.description,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
fromPeriod: [
|
|
||||||
chartSlabDetail.fromPeriod,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
toPeriod: [chartSlabDetail.toPeriod],
|
|
||||||
periodType: [
|
|
||||||
chartSlabDetail.periodType,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
incentives: this.formBuilder.array([])
|
|
||||||
});
|
|
||||||
const formArray = chartDetailControl.controls['chartSlabs'] as UntypedFormArray;
|
|
||||||
formArray.push(chartSlabInfo);
|
|
||||||
|
|
||||||
// Iterate for every slab in chartSlab
|
|
||||||
const chartIncentiveControl = (chartDetailControl.controls['chartSlabs'] as UntypedFormArray).controls[j];
|
|
||||||
|
|
||||||
// Iterate to input all the incentive for particular chart slab
|
|
||||||
this.chartsDetail[i].chartSlabs[j].incentives.forEach((chartIncentiveDetail: any) => {
|
|
||||||
const incentiveInfo = this.formBuilder.group({
|
|
||||||
amount: [
|
|
||||||
chartIncentiveDetail.amount,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
attributeName: [
|
|
||||||
chartIncentiveDetail.attributeName,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
attribureValue: [
|
|
||||||
chartIncentiveDetail.attribureValue,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
conditionType: [
|
|
||||||
chartIncentiveDetail.conditionType,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
entityType: [
|
|
||||||
chartIncentiveDetail.entityType,
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
incentiveType: [
|
|
||||||
chartIncentiveDetail.incentiveType,
|
|
||||||
Validators.required
|
|
||||||
]
|
|
||||||
});
|
|
||||||
const newFormArray = (chartIncentiveControl as UntypedFormGroup).controls['incentives'] as UntypedFormArray;
|
|
||||||
newFormArray.push(incentiveInfo);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getChartsDetailsData() {
|
|
||||||
this.chartDetailData.forEach((chartData: any) => {
|
|
||||||
const chart: {
|
|
||||||
id?: any;
|
|
||||||
endDate: string | Date;
|
|
||||||
fromDate: string | Date;
|
|
||||||
isPrimaryGroupingByAmount: any;
|
|
||||||
name: any;
|
|
||||||
chartSlabs: any[];
|
|
||||||
} = {
|
|
||||||
endDate: chartData.endDate ? new Date(chartData.endDate) : '',
|
|
||||||
fromDate: chartData.fromDate ? new Date(chartData.fromDate) : '',
|
|
||||||
isPrimaryGroupingByAmount: chartData.isPrimaryGroupingByAmount,
|
|
||||||
name: chartData.name,
|
|
||||||
chartSlabs: this.getChartSlabsData(chartData)
|
|
||||||
};
|
|
||||||
if (chartData.id) {
|
|
||||||
chart['id'] = chartData.id;
|
|
||||||
}
|
|
||||||
this.chartsDetail.push(chart);
|
|
||||||
});
|
|
||||||
this.DailyDepositSchemeProductInterestRateChartForm.patchValue({
|
|
||||||
charts: this.chartsDetail
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getChartSlabsData(chartData: any) {
|
|
||||||
const chartSlabs: any[] = [];
|
|
||||||
let chartSlabData: any[] = [];
|
|
||||||
const isChartSlabArray = Array.isArray(chartData.chartSlabs);
|
|
||||||
if (!isChartSlabArray) {
|
|
||||||
chartSlabData.push(chartData.chartSlabs);
|
|
||||||
} else {
|
|
||||||
chartSlabData = chartData.chartSlabs;
|
|
||||||
}
|
|
||||||
|
|
||||||
chartSlabData.forEach((eachChartSlabData: any) => {
|
|
||||||
const chartSlab: {
|
|
||||||
id?: any;
|
|
||||||
periodType: any;
|
|
||||||
amountRangeFrom: any;
|
|
||||||
amountRangeTo: any;
|
|
||||||
annualInterestRate: any;
|
|
||||||
description: any;
|
|
||||||
fromPeriod: any;
|
|
||||||
toPeriod: any;
|
|
||||||
incentives: any[];
|
|
||||||
} = {
|
|
||||||
periodType: eachChartSlabData.periodType.id,
|
|
||||||
amountRangeFrom: eachChartSlabData.amountRangeFrom,
|
|
||||||
amountRangeTo: eachChartSlabData.amountRangeTo,
|
|
||||||
annualInterestRate: eachChartSlabData.annualInterestRate,
|
|
||||||
description: eachChartSlabData.description ? eachChartSlabData.description : '',
|
|
||||||
fromPeriod: eachChartSlabData.fromPeriod,
|
|
||||||
toPeriod: eachChartSlabData.toPeriod,
|
|
||||||
incentives: this.getIncentivesData(chartSlabData)
|
|
||||||
};
|
|
||||||
if (eachChartSlabData.id) {
|
|
||||||
chartSlab['id'] = eachChartSlabData.id;
|
|
||||||
}
|
|
||||||
chartSlabs.push(chartSlab);
|
|
||||||
});
|
|
||||||
return chartSlabs;
|
|
||||||
}
|
|
||||||
|
|
||||||
getIncentivesData(chartSlabData: any) {
|
|
||||||
const incentives: any[] = [];
|
|
||||||
let incentiveDatas: any[] = [];
|
|
||||||
if (chartSlabData.incentives) {
|
|
||||||
const isChartIncentiveArray = Array.isArray(chartSlabData.incentives);
|
|
||||||
if (!isChartIncentiveArray) {
|
|
||||||
incentiveDatas.push(chartSlabData.incentives);
|
|
||||||
} else {
|
|
||||||
incentiveDatas = chartSlabData.incentives;
|
|
||||||
}
|
|
||||||
incentiveDatas.forEach((incentiveData: any) => {
|
|
||||||
const incentive = {
|
|
||||||
amount: incentiveData.amount,
|
|
||||||
attributeName: incentiveData.attributeName,
|
|
||||||
attributeValue: incentiveData.attributeValue,
|
|
||||||
conditionType: incentiveData.conditionType,
|
|
||||||
entityType: incentiveData.entityType,
|
|
||||||
incentiveType: incentiveData.incentiveType
|
|
||||||
};
|
|
||||||
incentives.push(incentive);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return incentives;
|
|
||||||
}
|
|
||||||
|
|
||||||
createDailyDepositSchemeProductInterestRateChartForm() {
|
|
||||||
this.DailyDepositSchemeProductInterestRateChartForm = this.formBuilder.group({
|
|
||||||
charts: this.formBuilder.array([])
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get charts(): UntypedFormArray {
|
|
||||||
return this.DailyDepositSchemeProductInterestRateChartForm.get('charts') as UntypedFormArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
createChartForm(): UntypedFormGroup {
|
|
||||||
return this.formBuilder.group({
|
|
||||||
id: [null],
|
|
||||||
name: [''],
|
|
||||||
description: [''],
|
|
||||||
fromDate: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
endDate: [''],
|
|
||||||
isPrimaryGroupingByAmount: [false],
|
|
||||||
chartSlabs: this.formBuilder.array([], Validators.required)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addChart() {
|
|
||||||
this.charts.push(this.createChartForm());
|
|
||||||
this.setConditionalControls(this.charts.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
setConditionalControls(chartIndex: number) {
|
|
||||||
this.chartSlabsDisplayedColumns[chartIndex] = [
|
|
||||||
'period',
|
|
||||||
'amountRange',
|
|
||||||
'annualInterestRate',
|
|
||||||
'description',
|
|
||||||
'actions'
|
|
||||||
];
|
|
||||||
this.charts
|
|
||||||
.at(chartIndex)
|
|
||||||
.get('isPrimaryGroupingByAmount')
|
|
||||||
.valueChanges.subscribe((isPrimaryGroupingByAmount: boolean) => {
|
|
||||||
this.chartSlabsDisplayedColumns[chartIndex] = isPrimaryGroupingByAmount ? [
|
|
||||||
'amountRange',
|
|
||||||
'period'
|
|
||||||
] : [
|
|
||||||
'period',
|
|
||||||
'amountRange'
|
|
||||||
];
|
|
||||||
this.chartSlabsDisplayedColumns[chartIndex].push('annualInterestRate', 'description', 'actions');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getIncentives(chartSlabs: UntypedFormArray, chartSlabIndex: number): UntypedFormArray {
|
|
||||||
return chartSlabs.at(chartSlabIndex).get('incentives') as UntypedFormArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
addChartSlab(chartSlabs: UntypedFormArray) {
|
|
||||||
const data = { ...this.getData('Slab') };
|
|
||||||
const dialogRef = this.dialog.open(FormDialogComponent, { data });
|
|
||||||
dialogRef.afterClosed().subscribe((response: any) => {
|
|
||||||
if (response.data) {
|
|
||||||
response.data.addControl('incentives', this.formBuilder.array([]));
|
|
||||||
chartSlabs.push(response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addIncentive(incentives: UntypedFormArray) {
|
|
||||||
const data = { ...this.getData('Incentive'), entityType: this.entityTypeData[0].id };
|
|
||||||
const dialogRef = this.dialog.open(DepositProductIncentiveFormDialogComponent, { data });
|
|
||||||
dialogRef.afterClosed().subscribe((response: any) => {
|
|
||||||
if (response.data) {
|
|
||||||
incentives.push(response.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
editChartSlab(chartSlabs: UntypedFormArray, chartSlabIndex: number) {
|
|
||||||
const data = { ...this.getData('Slab', chartSlabs.at(chartSlabIndex).value), layout: { addButtonText: 'Edit' } };
|
|
||||||
const dialogRef = this.dialog.open(FormDialogComponent, { data });
|
|
||||||
dialogRef.afterClosed().subscribe((response: any) => {
|
|
||||||
if (response.data) {
|
|
||||||
chartSlabs.at(chartSlabIndex).patchValue(response.data.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
editIncentive(incentives: UntypedFormArray, incentiveIndex: number) {
|
|
||||||
const data = {
|
|
||||||
...this.getData('Incentive', incentives.at(incentiveIndex).value),
|
|
||||||
layout: { addButtonText: 'Edit' }
|
|
||||||
};
|
|
||||||
const dialogRef = this.dialog.open(DepositProductIncentiveFormDialogComponent, { data });
|
|
||||||
dialogRef.afterClosed().subscribe((response: any) => {
|
|
||||||
if (response.data) {
|
|
||||||
incentives.at(incentiveIndex).patchValue(response.data.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getData(formType: string, values?: any) {
|
|
||||||
switch (formType) {
|
|
||||||
case 'Slab':
|
|
||||||
return {
|
|
||||||
title: this.translateService.instant('labels.inputs.Slab'),
|
|
||||||
formfields: this.getSlabFormfields(values)
|
|
||||||
};
|
|
||||||
case 'Incentive':
|
|
||||||
return { values, chartTemplate: this.DailyDepositSchemeProductsTemplate.chartTemplate };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getSlabFormfields(values?: any) {
|
|
||||||
const formfields: FormfieldBase[] = [
|
|
||||||
new SelectBase({
|
|
||||||
controlName: 'periodType',
|
|
||||||
label: this.translateService.instant('labels.inputs.Period Type'),
|
|
||||||
value: values ? values.periodType : this.periodTypeData[0].id,
|
|
||||||
options: { label: 'value', value: 'id', data: this.periodTypeData },
|
|
||||||
required: true,
|
|
||||||
order: 1
|
|
||||||
}),
|
|
||||||
new InputBase({
|
|
||||||
controlName: 'fromPeriod',
|
|
||||||
label: this.translateService.instant('labels.inputs.Period From'),
|
|
||||||
value: values ? values.fromPeriod : undefined,
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
order: 2,
|
|
||||||
min: 0
|
|
||||||
}),
|
|
||||||
new InputBase({
|
|
||||||
controlName: 'toPeriod',
|
|
||||||
label: this.translateService.instant('labels.inputs.Period To'),
|
|
||||||
value: values ? values.toPeriod : undefined,
|
|
||||||
type: 'number',
|
|
||||||
order: 3,
|
|
||||||
min: 0
|
|
||||||
}),
|
|
||||||
new InputBase({
|
|
||||||
controlName: 'amountRangeFrom',
|
|
||||||
label: this.translateService.instant('labels.inputs.Amount Range From'),
|
|
||||||
value: values ? values.amountRangeFrom : undefined,
|
|
||||||
type: 'number',
|
|
||||||
order: 4,
|
|
||||||
min: 0
|
|
||||||
}),
|
|
||||||
new InputBase({
|
|
||||||
controlName: 'amountRangeTo',
|
|
||||||
label: this.translateService.instant('labels.inputs.Amount Range To'),
|
|
||||||
value: values ? values.amountRangeTo : undefined,
|
|
||||||
type: 'number',
|
|
||||||
order: 5,
|
|
||||||
min: 0
|
|
||||||
}),
|
|
||||||
new InputBase({
|
|
||||||
controlName: 'annualInterestRate',
|
|
||||||
label: this.translateService.instant('labels.inputs.Interest'),
|
|
||||||
value: values ? values.annualInterestRate : undefined,
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
order: 6,
|
|
||||||
min: 0
|
|
||||||
}),
|
|
||||||
new InputBase({
|
|
||||||
controlName: 'description',
|
|
||||||
label: this.translateService.instant('labels.inputs.Description'),
|
|
||||||
value: values ? values.description : undefined,
|
|
||||||
required: true,
|
|
||||||
order: 7
|
|
||||||
})
|
|
||||||
];
|
|
||||||
return formfields;
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductInterestRateChart() {
|
|
||||||
// TODO: Update once language and date settings are setup
|
|
||||||
const dateFormat = 'YYYY-MM-DD';
|
|
||||||
const locale = this.settingsService.language.code;
|
|
||||||
const DailyDepositSchemeProductInterestRateChart = this.DailyDepositSchemeProductInterestRateChartForm.value;
|
|
||||||
for (const chart of DailyDepositSchemeProductInterestRateChart.charts) {
|
|
||||||
chart.locale = locale;
|
|
||||||
chart.dateFormat = 'yyyy-MM-dd';
|
|
||||||
if (chart.fromDate instanceof Date) {
|
|
||||||
chart.fromDate = this.dateUtils.formatDateAsString(chart.fromDate, dateFormat);
|
|
||||||
}
|
|
||||||
if (chart.endDate) {
|
|
||||||
if (chart.endDate instanceof Date) {
|
|
||||||
chart.endDate = this.dateUtils.formatDateAsString(chart.endDate, dateFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (chart.endDate === '') {
|
|
||||||
delete chart.endDate;
|
|
||||||
}
|
|
||||||
if (chart.description === '') {
|
|
||||||
delete chart.description;
|
|
||||||
}
|
|
||||||
if (chart.id === null) {
|
|
||||||
delete chart.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...DailyDepositSchemeProductInterestRateChart,
|
|
||||||
rdSlabs: this._editableSlabs
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-732
@@ -1,732 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="responsive-column">
|
|
||||||
<!-- ── Product Name ── -->
|
|
||||||
<h2 class="mat-h2 flex-100">{{ DailyDepositSchemeProduct.name }}</h2>
|
|
||||||
|
|
||||||
<!-- ── Details ── -->
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Details' | translate }}</h3>
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Short Name' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.shortName }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.description) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Description' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.description }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ── Product Classification ── -->
|
|
||||||
@if (
|
|
||||||
DailyDepositSchemeProduct.accountType ||
|
|
||||||
DailyDepositSchemeProduct.customerSegment ||
|
|
||||||
DailyDepositSchemeProduct.glAccountId ||
|
|
||||||
DailyDepositSchemeProduct.effectiveTo ||
|
|
||||||
DailyDepositSchemeProduct.introducerRequired !== undefined ||
|
|
||||||
DailyDepositSchemeProduct.shareLinkingEnabled !== undefined
|
|
||||||
) {
|
|
||||||
<h3 class="mat-h3 flex-100">Product Classification</h3>
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.accountType) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Account Type:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.accountType }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.glAccountId) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">GL Account (Liability):</span>
|
|
||||||
<span class="flex-60">{{ getGlAccountName(DailyDepositSchemeProduct.glAccountId) }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.effectiveTo) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Effective To:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.effectiveTo | dateFormat }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.endDate) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">End Date:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.endDate | dateFormat }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.introducerRequired !== undefined) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Introducer Required:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
DailyDepositSchemeProduct.introducerRequired === true ||
|
|
||||||
DailyDepositSchemeProduct.introducerRequired === 'Yes'
|
|
||||||
? 'Yes'
|
|
||||||
: 'No'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.shareLinkingEnabled !== undefined) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Deposit Account Link:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.shareLinkingEnabled | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ── Currency ── -->
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Currency' | translate }}</h3>
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Currency' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
DailyDepositSchemeProduct.currencyCode
|
|
||||||
| find: DailyDepositSchemeProductsTemplate.currencyOptions : 'code' : 'name'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Decimal Places' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.digitsAfterDecimal }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.dormancyAlert !== undefined) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Dormancy Alert:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.dormancyAlert | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.nominationFacility !== undefined) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Nomination Facility:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.nominationFacility | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.autoDebitFacility !== undefined) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Auto Debit Facility:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.autoDebitFacility | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (
|
|
||||||
DailyDepositSchemeProduct.minDepositAmount !== undefined || DailyDepositSchemeProduct.maxDepositAmount !== undefined
|
|
||||||
) {
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
<h3 class="mat-h3 flex-100">Deposit Amount</h3>
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.minDepositAmount !== undefined) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Minimum:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.minDepositAmount }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.maxDepositAmount !== undefined) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Maximum:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.maxDepositAmount }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ── Terms ── -->
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Terms' | translate }}</h3>
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Nominal Annual Interest Rate' | translate }} (%):</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.nominalAnnualInterestRate | formatNumber }} %</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Interest Compounding Period' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
DailyDepositSchemeProduct.interestCompoundingPeriodType
|
|
||||||
| find: DailyDepositSchemeProductsTemplate.interestCompoundingPeriodTypeOptions : 'id' : 'value'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Interest Posting Period' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
DailyDepositSchemeProduct.interestPostingPeriodType
|
|
||||||
| find: DailyDepositSchemeProductsTemplate.interestPostingPeriodTypeOptions : 'id' : 'value'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.interestCalculateSelector) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Interest Calculation Type' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
DailyDepositSchemeProduct.interestCalculateSelector
|
|
||||||
| find: DailyDepositSchemeProductsTemplate.interestCalculationTypeOption : 'id' : 'value'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Interest Calculated using' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
DailyDepositSchemeProduct.interestCalculationType
|
|
||||||
| find: DailyDepositSchemeProductsTemplate.interestCalculationTypeOptions : 'id' : 'value'
|
|
||||||
| translateKey: 'catalogs'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Days in Year' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
DailyDepositSchemeProduct.interestCalculationDaysInYearType
|
|
||||||
| find: DailyDepositSchemeProductsTemplate.interestCalculationDaysInYearTypeOptions : 'id' : 'value'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.effectiveDate) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Effective Date:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.effectiveDate | dateFormat }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.averagePeriod) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Average Period:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.averagePeriod }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Auto Interest Post:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.autoPost | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (
|
|
||||||
DailyDepositSchemeProduct.frequencyFixDay !== undefined &&
|
|
||||||
DailyDepositSchemeProduct.frequencyFixDay !== null &&
|
|
||||||
DailyDepositSchemeProduct.frequencyFixDay !== ''
|
|
||||||
) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Frequency (Fix Day):</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.frequencyFixDay }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.lastInterestDate) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Last Interest Date:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.lastInterestDate | dateFormat }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ── Settings ── -->
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Settings' | translate }}</h3>
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Loan Facility:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.loanFacility | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Premature Withdrawal:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.prematureWithdrawalOption | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Tenure Flexibility:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.tenureFlexibility | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Lien Marked:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.lienMarked | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.withdrawalFeeForTransfers | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Enable Lock-in Period:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.enableLockinPeriod | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.enableLockinPeriod && DailyDepositSchemeProduct.lockinPeriodFrequency) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Lock-in Period:</span>
|
|
||||||
<span class="flex-60">
|
|
||||||
{{ DailyDepositSchemeProduct.lockinPeriodFrequency }}
|
|
||||||
{{ getLockinPeriodFrequencyTypeLabel(DailyDepositSchemeProduct.lockinPeriodFrequencyType) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Maturity Instructions:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.maturityInstructions | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Credit Maturity Proceeds:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.creditMaturityProceeds | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Penalty Grace Period (Missed Installments):</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.penaltyGracePeriodMissedInstallments | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Panel Request Applicable:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.panelRequestApplicable | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">No Penalty (Nominee / Heir Closure):</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.noPenaltyNomineeHeirClosure | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Deceased Settlement:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.deceasedSettlement | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Allow Debit Txn After Due Date:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.allowDebitTxnAfterDueDate | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Account Closed with Zero Balance in Day Closing:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.accountClosedZeroBalanceInDayClosing | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.minBalanceForInterestCalculation) {
|
|
||||||
<div class="flex-100 layout-row">
|
|
||||||
<span class="flex-40">Min Balance for Interest Calculation:</span>
|
|
||||||
<span class="flex-60">{{ DailyDepositSchemeProduct.minBalanceForInterestCalculation | formatNumber }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ── Interest Rate Slabs ── -->
|
|
||||||
@if (DailyDepositSchemeProduct.rdSlabs?.length) {
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Interest Rate Charts' | translate }}</h3>
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
|
|
||||||
<div class="layout-row-wrap responsive-column flex-100">
|
|
||||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="DailyDepositSchemeProduct.rdSlabs">
|
|
||||||
<ng-container matColumnDef="srNo">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Sr.</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.srNo }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="description">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Particular</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.description }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="fromPeriod">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.fromPeriod }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="toPeriod">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.toPeriod ?? '—' }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="periodTypeLabel">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Period</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.periodTypeLabel }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="annualInterestRate">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Rate (%)</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">
|
|
||||||
<strong>{{ slab.annualInterestRate }}</strong>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="rdSlabColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: rdSlabColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ── Charges ── -->
|
|
||||||
@if (DailyDepositSchemeProduct.charges?.length) {
|
|
||||||
<div class="layout-row-wrap responsive-column flex-100">
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Charges' | translate }}</h3>
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="DailyDepositSchemeProduct.charges">
|
|
||||||
<ng-container matColumnDef="name">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">{{ charge.name + ', ' + charge.currency.displaySymbol }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="chargeCalculationType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">{{ charge.chargeCalculationType.value }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="amount">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">{{ charge.amount | formatNumber }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="chargeTimeType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">{{ charge.chargeTimeType.value }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="chargesDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: chargesDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ── Accounting ── -->
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Accounting' | translate }}</h3>
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
|
|
||||||
@if (isCashOrAccrualAccounting()) {
|
|
||||||
<div class="spa-gl-summary flex-100">
|
|
||||||
<div class="spa-gl-summary__header">
|
|
||||||
<h4 class="mat-h4">GL Mapping</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spa-gl-section">
|
|
||||||
<h5 class="mat-h5">Assets</h5>
|
|
||||||
<div class="spa-gl-row">
|
|
||||||
<span class="spa-gl-row__label">Savings Reference Account</span>
|
|
||||||
<span class="spa-gl-row__value">{{
|
|
||||||
formatGlAccountById(
|
|
||||||
DailyDepositSchemeProduct.savingsReferenceAccountId,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.assetAccountOptions
|
|
||||||
)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
@if (isAccrualAccounting()) {
|
|
||||||
<div class="spa-gl-row">
|
|
||||||
<span class="spa-gl-row__label">Fees Receivable Account</span>
|
|
||||||
<span class="spa-gl-row__value">{{
|
|
||||||
formatGlAccountById(
|
|
||||||
DailyDepositSchemeProduct.feesReceivableAccountId,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.assetAccountOptions
|
|
||||||
)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="spa-gl-row">
|
|
||||||
<span class="spa-gl-row__label">Penalties Receivable Account</span>
|
|
||||||
<span class="spa-gl-row__value">{{
|
|
||||||
formatGlAccountById(
|
|
||||||
DailyDepositSchemeProduct.penaltiesReceivableAccountId,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.assetAccountOptions
|
|
||||||
)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spa-gl-section">
|
|
||||||
<h5 class="mat-h5">Liabilities</h5>
|
|
||||||
<div class="spa-gl-row">
|
|
||||||
<span class="spa-gl-row__label">Savings Control Account</span>
|
|
||||||
<span class="spa-gl-row__value">{{
|
|
||||||
formatGlAccountById(
|
|
||||||
DailyDepositSchemeProduct.savingsControlAccountId,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.liabilityAccountOptions
|
|
||||||
)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="spa-gl-row">
|
|
||||||
<span class="spa-gl-row__label">Transfers In Suspense Account</span>
|
|
||||||
<span class="spa-gl-row__value">{{
|
|
||||||
formatGlAccountById(
|
|
||||||
DailyDepositSchemeProduct.transfersInSuspenseAccountId,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.liabilityAccountOptions
|
|
||||||
)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
@if (isAccrualAccounting()) {
|
|
||||||
<div class="spa-gl-row">
|
|
||||||
<span class="spa-gl-row__label">Interest Payable Account</span>
|
|
||||||
<span class="spa-gl-row__value">{{
|
|
||||||
formatGlAccountById(
|
|
||||||
DailyDepositSchemeProduct.interestPayableAccountId,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.liabilityAccountOptions
|
|
||||||
)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spa-gl-section">
|
|
||||||
<h5 class="mat-h5">Expenses</h5>
|
|
||||||
<div class="spa-gl-row">
|
|
||||||
<span class="spa-gl-row__label">Interest on Savings Account</span>
|
|
||||||
<span class="spa-gl-row__value">{{
|
|
||||||
formatGlAccountById(
|
|
||||||
DailyDepositSchemeProduct.interestOnSavingsAccountId,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.expenseAccountOptions
|
|
||||||
)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spa-gl-section">
|
|
||||||
<h5 class="mat-h5">Income</h5>
|
|
||||||
<div class="spa-gl-row">
|
|
||||||
<span class="spa-gl-row__label">Income from Fees</span>
|
|
||||||
<span class="spa-gl-row__value">{{
|
|
||||||
formatGlAccountById(
|
|
||||||
DailyDepositSchemeProduct.incomeFromFeeAccountId,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.incomeAccountOptions
|
|
||||||
)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="spa-gl-row">
|
|
||||||
<span class="spa-gl-row__label">Income from Penalties</span>
|
|
||||||
<span class="spa-gl-row__value">{{
|
|
||||||
formatGlAccountById(
|
|
||||||
DailyDepositSchemeProduct.incomeFromPenaltyAccountId,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.incomeAccountOptions
|
|
||||||
)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Other GL Mappings -->
|
|
||||||
@if (DailyDepositSchemeProduct.otherGlMappings) {
|
|
||||||
<div class="spa-other-gl-summary flex-100">
|
|
||||||
<div class="spa-gl-summary__header">
|
|
||||||
<h4 class="mat-h4">Other GL Mappings</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.otherGlMappings.assets?.length) {
|
|
||||||
<table
|
|
||||||
class="spa-other-gl-table flex-100 mat-elevation-z1"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="DailyDepositSchemeProduct.otherGlMappings.assets"
|
|
||||||
>
|
|
||||||
<ng-container matColumnDef="accountName">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Asset Account</th>
|
|
||||||
<td mat-cell *matCellDef="let row">{{ row.accountName }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="glCode">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>GL Account</th>
|
|
||||||
<td mat-cell *matCellDef="let row">
|
|
||||||
{{
|
|
||||||
formatGlAccountByCode(
|
|
||||||
row.glCode,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.assetAccountOptions
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="['accountName', 'glCode']"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: ['accountName', 'glCode']"></tr>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.otherGlMappings.liabilities?.length) {
|
|
||||||
<table
|
|
||||||
class="spa-other-gl-table flex-100 mat-elevation-z1"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="DailyDepositSchemeProduct.otherGlMappings.liabilities"
|
|
||||||
>
|
|
||||||
<ng-container matColumnDef="accountName">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Liability Account</th>
|
|
||||||
<td mat-cell *matCellDef="let row">{{ row.accountName }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="glCode">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>GL Account</th>
|
|
||||||
<td mat-cell *matCellDef="let row">
|
|
||||||
{{
|
|
||||||
formatGlAccountByCode(
|
|
||||||
row.glCode,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.liabilityAccountOptions
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="['accountName', 'glCode']"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: ['accountName', 'glCode']"></tr>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.otherGlMappings.expenses?.length) {
|
|
||||||
<table
|
|
||||||
class="spa-other-gl-table flex-100 mat-elevation-z1"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="DailyDepositSchemeProduct.otherGlMappings.expenses"
|
|
||||||
>
|
|
||||||
<ng-container matColumnDef="accountName">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Expense Account</th>
|
|
||||||
<td mat-cell *matCellDef="let row">{{ row.accountName }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="glCode">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>GL Account</th>
|
|
||||||
<td mat-cell *matCellDef="let row">
|
|
||||||
{{
|
|
||||||
formatGlAccountByCode(
|
|
||||||
row.glCode,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.expenseAccountOptions
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="['accountName', 'glCode']"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: ['accountName', 'glCode']"></tr>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.otherGlMappings.income?.length) {
|
|
||||||
<table
|
|
||||||
class="spa-other-gl-table flex-100 mat-elevation-z1"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="DailyDepositSchemeProduct.otherGlMappings.income"
|
|
||||||
>
|
|
||||||
<ng-container matColumnDef="accountName">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Income Account</th>
|
|
||||||
<td mat-cell *matCellDef="let row">{{ row.accountName }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="glCode">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>GL Account</th>
|
|
||||||
<td mat-cell *matCellDef="let row">
|
|
||||||
{{
|
|
||||||
formatGlAccountByCode(
|
|
||||||
row.glCode,
|
|
||||||
DailyDepositSchemeProductsTemplate?.accountingMappingOptions.incomeAccountOptions
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="['accountName', 'glCode']"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: ['accountName', 'glCode']"></tr>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Advanced Accounting Rules -->
|
|
||||||
@if (
|
|
||||||
DailyDepositSchemeProduct.advancedAccountingRules &&
|
|
||||||
(DailyDepositSchemeProduct.paymentChannelToFundSourceMappings?.length ||
|
|
||||||
DailyDepositSchemeProduct.feeToIncomeAccountMappings?.length ||
|
|
||||||
DailyDepositSchemeProduct.penaltyToIncomeAccountMappings?.length)
|
|
||||||
) {
|
|
||||||
<div class="layout-row-wrap responsive-column flex-100">
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Advanced Accounting Rules' | translate }}</h3>
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.paymentChannelToFundSourceMappings?.length) {
|
|
||||||
<div class="flex-100 layout-row-wrap responsive-column">
|
|
||||||
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Fund Sources for Payment Channels' | translate }}</h4>
|
|
||||||
<table
|
|
||||||
class="mat-elevation-z1 flex-100"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="DailyDepositSchemeProduct.paymentChannelToFundSourceMappings"
|
|
||||||
>
|
|
||||||
<ng-container matColumnDef="paymentTypeId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Payment Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let item">
|
|
||||||
{{ item.paymentTypeId | find: DailyDepositSchemeProductsTemplate.paymentTypeOptions : 'id' : 'name' }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="fundSourceAccountId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fund Source' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let item">
|
|
||||||
{{
|
|
||||||
item.fundSourceAccountId
|
|
||||||
| find
|
|
||||||
: DailyDepositSchemeProductsTemplate?.accountingMappingOptions.assetAccountOptions
|
|
||||||
: 'id'
|
|
||||||
: 'name'
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="paymentFundSourceDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: paymentFundSourceDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.feeToIncomeAccountMappings?.length) {
|
|
||||||
<div class="flex-100 layout-row-wrap responsive-column">
|
|
||||||
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Fees to Specific Income Accounts' | translate }}</h4>
|
|
||||||
<table
|
|
||||||
class="flex-100 mat-elevation-z1"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="DailyDepositSchemeProduct.feeToIncomeAccountMappings"
|
|
||||||
>
|
|
||||||
<ng-container matColumnDef="chargeId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fees' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let item">
|
|
||||||
{{ item.chargeId | find: DailyDepositSchemeProductsTemplate.chargeOptions : 'id' : 'name' }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="incomeAccountId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let item">
|
|
||||||
{{
|
|
||||||
item.incomeAccountId
|
|
||||||
| find
|
|
||||||
: DailyDepositSchemeProductsTemplate?.accountingMappingOptions.incomeAccountOptions
|
|
||||||
: 'id'
|
|
||||||
: 'name'
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProduct.penaltyToIncomeAccountMappings?.length) {
|
|
||||||
<div class="flex-100 layout-row-wrap responsive-column">
|
|
||||||
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Penalties to Specific Income Accounts' | translate }}</h4>
|
|
||||||
<table
|
|
||||||
class="flex-100 mat-elevation-z1"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="DailyDepositSchemeProduct.penaltyToIncomeAccountMappings"
|
|
||||||
>
|
|
||||||
<ng-container matColumnDef="chargeId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Penalty' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let item">
|
|
||||||
{{ item.chargeId | find: DailyDepositSchemeProductsTemplate.penaltyOptions : 'id' : 'name' }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="incomeAccountId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let item">
|
|
||||||
{{
|
|
||||||
item.incomeAccountId
|
|
||||||
| find
|
|
||||||
: DailyDepositSchemeProductsTemplate?.accountingMappingOptions.incomeAccountOptions
|
|
||||||
: 'id'
|
|
||||||
: 'name'
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
-131
@@ -1,131 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use 'assets/styles/colours' as *;
|
|
||||||
|
|
||||||
.mat-h3 {
|
|
||||||
margin-top: 10px !important;
|
|
||||||
font-weight: 300;
|
|
||||||
color: $status-approved;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-elevation-z1 {
|
|
||||||
margin: 1em 0 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4 {
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-divider {
|
|
||||||
margin: 0 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── GL mapping summary (same as savings product preview) ── */
|
|
||||||
.spa-gl-summary,
|
|
||||||
.spa-other-gl-summary {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.9rem;
|
|
||||||
margin-top: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spa-gl-summary__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0.25rem 0 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spa-gl-section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.75rem;
|
|
||||||
padding: 1rem 1.15rem;
|
|
||||||
border: 1px solid #e3e7f2;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: #91a0bc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spa-gl-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(220px, 40%) 1fr;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: baseline;
|
|
||||||
padding: 0.2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spa-gl-row__label {
|
|
||||||
color: #1f2f4a;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spa-gl-row__value {
|
|
||||||
color: #101828;
|
|
||||||
font-weight: 600;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spa-other-gl-table {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
table-layout: fixed;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
th.mat-mdc-header-cell,
|
|
||||||
td.mat-mdc-cell {
|
|
||||||
padding-left: 1.25rem;
|
|
||||||
padding-right: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
th.mat-mdc-header-cell:first-child,
|
|
||||||
td.mat-mdc-cell:first-child {
|
|
||||||
width: 38%;
|
|
||||||
}
|
|
||||||
|
|
||||||
th.mat-mdc-header-cell:last-child,
|
|
||||||
td.mat-mdc-cell:last-child {
|
|
||||||
width: 62%;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spa-other-gl-summary .mat-elevation-z1 {
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 768px) {
|
|
||||||
.spa-gl-row {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 0.35rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-142
@@ -1,142 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges, inject } from '@angular/core';
|
|
||||||
import { Accounting } from 'app/core/utils/accounting';
|
|
||||||
import { OptionData } from 'app/shared/models/option-data.model';
|
|
||||||
import { MatDivider } from '@angular/material/divider';
|
|
||||||
import {
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow
|
|
||||||
} from '@angular/material/table';
|
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
||||||
import { FindPipe } from '../../../../pipes/find.pipe';
|
|
||||||
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
|
||||||
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
|
|
||||||
import { YesnoPipe } from '../../../../pipes/yesno.pipe';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-daily-deposit-scheme-product-preview-step',
|
|
||||||
templateUrl: './daily-deposit-scheme-product-preview-step.component.html',
|
|
||||||
styleUrls: ['./daily-deposit-scheme-product-preview-step.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
MatDivider,
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
FaIconComponent,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow,
|
|
||||||
FindPipe,
|
|
||||||
DateFormatPipe,
|
|
||||||
FormatNumberPipe,
|
|
||||||
YesnoPipe
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class DailyDepositSchemeProductPreviewStepComponent implements OnInit, OnChanges {
|
|
||||||
private accounting = inject(Accounting);
|
|
||||||
|
|
||||||
@Input() DailyDepositSchemeProductsTemplate: any;
|
|
||||||
@Input() chartSlabsDisplayedColumns: any[] = [];
|
|
||||||
@Input() accountingRuleData: any;
|
|
||||||
@Input() DailyDepositSchemeProduct: any;
|
|
||||||
@Output() submitEvent = new EventEmitter();
|
|
||||||
|
|
||||||
chargesDisplayedColumns: string[] = [
|
|
||||||
'name',
|
|
||||||
'chargeCalculationType',
|
|
||||||
'amount',
|
|
||||||
'chargeTimeType'
|
|
||||||
];
|
|
||||||
paymentFundSourceDisplayedColumns: string[] = [
|
|
||||||
'paymentTypeId',
|
|
||||||
'fundSourceAccountId'
|
|
||||||
];
|
|
||||||
feesPenaltyIncomeDisplayedColumns: string[] = [
|
|
||||||
'chargeId',
|
|
||||||
'incomeAccountId'
|
|
||||||
];
|
|
||||||
|
|
||||||
readonly rdSlabColumns = [
|
|
||||||
'srNo',
|
|
||||||
'description',
|
|
||||||
'fromPeriod',
|
|
||||||
'toPeriod',
|
|
||||||
'periodTypeLabel',
|
|
||||||
'annualInterestRate'
|
|
||||||
];
|
|
||||||
|
|
||||||
accountingRule: OptionData | undefined;
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.setCurrentValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnChanges(_changes: SimpleChanges): void {
|
|
||||||
this.setCurrentValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentValues(): void {
|
|
||||||
if (this.DailyDepositSchemeProduct) {
|
|
||||||
this.accountingRule = this.accounting.getAccountingRuleFrom(this.DailyDepositSchemeProduct.accountingRule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isNoneAccounting(): boolean {
|
|
||||||
return this.accounting.isNoneAccountingRuleId(this.DailyDepositSchemeProduct.accountingRule);
|
|
||||||
}
|
|
||||||
|
|
||||||
isCashOrAccrualAccounting(): boolean {
|
|
||||||
return this.accounting.isCashOrAccrualAccountingRuleId(this.DailyDepositSchemeProduct.accountingRule);
|
|
||||||
}
|
|
||||||
|
|
||||||
isAccrualAccounting(): boolean {
|
|
||||||
return this.accounting.isAccrualAccountingRuleId(this.DailyDepositSchemeProduct.accountingRule);
|
|
||||||
}
|
|
||||||
|
|
||||||
getLockinPeriodFrequencyTypeLabel(value: any): string {
|
|
||||||
if (!value && value !== 0) return '';
|
|
||||||
const options = this.DailyDepositSchemeProductsTemplate?.lockinPeriodFrequencyTypeOptions || [];
|
|
||||||
const found = options.find((o: any) => o.id === value);
|
|
||||||
return found?.value || String(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
getGlAccountName(id: any): string {
|
|
||||||
if (!id) return '';
|
|
||||||
const accounts = this.DailyDepositSchemeProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions ?? [];
|
|
||||||
const found = accounts.find((a: any) => a.id === id || a.id === +id);
|
|
||||||
return found ? `${found.name} (${found.glCode})` : String(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
formatGlAccountById(glAccountId: number, options: any[]): string {
|
|
||||||
if (!glAccountId || !Array.isArray(options)) return '-';
|
|
||||||
const account = this.accounting.glAccountLookUp(glAccountId, options as any);
|
|
||||||
return account ? `(${account.glCode}) ${account.name}` : '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
formatGlAccountByCode(glCode: string, options: any[]): string {
|
|
||||||
if (!glCode || !Array.isArray(options)) return glCode || '-';
|
|
||||||
const account = options.find((o: any) => o?.glCode === glCode);
|
|
||||||
return account ? `(${account.glCode}) ${account.name}` : glCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-123
@@ -1,123 +0,0 @@
|
|||||||
<!--
|
|
||||||
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/.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<form [formGroup]="DailyDepositSchemeProductSettingsForm">
|
|
||||||
<!-- Premature option -->
|
|
||||||
<div class="rdps-section">
|
|
||||||
<div class="rdps-section__header">
|
|
||||||
<span class="rdps-section__badge"><i class="fas fa-balance-scale"></i></span>
|
|
||||||
<span>Premature option</span>
|
|
||||||
</div>
|
|
||||||
<div class="rdps-body">
|
|
||||||
<div class="rdps-premature-row">
|
|
||||||
<mat-checkbox formControlName="loanFacility">Loan Facility</mat-checkbox>
|
|
||||||
<mat-checkbox formControlName="prematureWithdrawalOption">Premature Withdrawal option</mat-checkbox>
|
|
||||||
<mat-checkbox formControlName="tenureFlexibility">Tenure Flexibility</mat-checkbox>
|
|
||||||
<mat-checkbox formControlName="lienMarked">Lien Marked</mat-checkbox>
|
|
||||||
<mat-checkbox formControlName="withdrawalFeeForTransfers">Apply Withdrawal Fee for Transfers</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Lock-in Period -->
|
|
||||||
<div class="rdps-section">
|
|
||||||
<div class="rdps-section__header">
|
|
||||||
<span class="rdps-section__badge"><i class="fas fa-lock"></i></span>
|
|
||||||
<span>{{ 'labels.inputs.Lock-in Period' | translate }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="rdps-body">
|
|
||||||
<div class="rdps-lockin-row">
|
|
||||||
<span>Enable Lock-in Period</span>
|
|
||||||
<mat-checkbox formControlName="enableLockinPeriod"></mat-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProductSettingsForm.value.enableLockinPeriod) {
|
|
||||||
<div class="rdps-lockin-fields">
|
|
||||||
<mat-form-field appearance="outline" class="rdps-field">
|
|
||||||
<mat-label>Frequency*</mat-label>
|
|
||||||
<input type="number" matInput formControlName="lockinPeriodFrequency" min="1" step="1" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdps-field">
|
|
||||||
<mat-label>Type*</mat-label>
|
|
||||||
<mat-select formControlName="lockinPeriodFrequencyType">
|
|
||||||
@for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) {
|
|
||||||
<mat-option [value]="lockinPeriodFrequencyType.id">
|
|
||||||
{{ lockinPeriodFrequencyType.value | translateKey: 'catalogs' }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Maturity Instructions -->
|
|
||||||
<div class="rdps-section">
|
|
||||||
<div class="rdps-section__header">
|
|
||||||
<span class="rdps-section__badge"><i class="fas fa-lock"></i></span>
|
|
||||||
<span>Maturity Instructions</span>
|
|
||||||
</div>
|
|
||||||
<div class="rdps-body rdps-body--list">
|
|
||||||
<div class="rdps-list-row">
|
|
||||||
<span>Maturity Instructions</span>
|
|
||||||
<mat-checkbox formControlName="maturityInstructions"></mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="rdps-list-row">
|
|
||||||
<span
|
|
||||||
>Option to credit maturity proceeds directly to the linked account or reinvest in another product (like
|
|
||||||
FD).</span
|
|
||||||
>
|
|
||||||
<mat-checkbox formControlName="creditMaturityProceeds"></mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="rdps-list-row">
|
|
||||||
<span>Penalty / Grace Period for Missed Installments</span>
|
|
||||||
<mat-checkbox formControlName="penaltyGracePeriodMissedInstallments"></mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="rdps-list-row">
|
|
||||||
<span>Panel Request Applicable</span>
|
|
||||||
<mat-checkbox formControlName="panelRequestApplicable"></mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="rdps-list-row">
|
|
||||||
<span>No penalty for nominee / legal heir closure</span>
|
|
||||||
<mat-checkbox formControlName="noPenaltyNomineeHeirClosure"></mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="rdps-list-row">
|
|
||||||
<span>Deceased Settlement</span>
|
|
||||||
<mat-checkbox formControlName="deceasedSettlement"></mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="rdps-list-row">
|
|
||||||
<span>Allow Debit Txn after due date</span>
|
|
||||||
<mat-checkbox formControlName="allowDebitTxnAfterDueDate"></mat-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Additional Rules -->
|
|
||||||
<div class="rdps-section">
|
|
||||||
<div class="rdps-section__header">
|
|
||||||
<span class="rdps-section__badge"><i class="fas fa-cog"></i></span>
|
|
||||||
<span>Additional Rules</span>
|
|
||||||
</div>
|
|
||||||
<div class="rdps-body">
|
|
||||||
<div class="rdps-layout rdps-layout--additional">
|
|
||||||
<div class="rdps-toggle rdps-toggle--inline">
|
|
||||||
<mat-checkbox formControlName="accountClosedZeroBalanceInDayClosing">
|
|
||||||
<span>Account Closed with Zero balance in Day Closing</span>
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdps-field">
|
|
||||||
<mat-label>Balance Required for Interest Calculation</mat-label>
|
|
||||||
<input type="number" matInput formControlName="minBalanceForInterestCalculation" min="0" step="0.01" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
-164
@@ -1,164 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$accent: #0ea5e9;
|
|
||||||
$border: #e2e8f0;
|
|
||||||
$gray-50: #f8f9fa;
|
|
||||||
$gray-200: #e8eaed;
|
|
||||||
$gray-900: #202124;
|
|
||||||
|
|
||||||
.rdps-section {
|
|
||||||
border: 1px solid $border;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
& + & {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 12px 24px;
|
|
||||||
background: $gray-50;
|
|
||||||
border-bottom: 1px solid $gray-200;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px;
|
|
||||||
color: $accent;
|
|
||||||
}
|
|
||||||
|
|
||||||
span:not(.rdps-section__badge) {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $gray-900;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdps-section__badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: #eff6ff;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdps-body {
|
|
||||||
background: #fff;
|
|
||||||
padding: 18px 24px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdps-field {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--full {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdps-premature-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 24px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
mat-checkbox {
|
|
||||||
font-size: 14px;
|
|
||||||
color: $gray-900;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdps-layout {
|
|
||||||
display: grid;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdps-layout--additional {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
|
|
||||||
@media (width <= 1100px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdps-toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 40px;
|
|
||||||
|
|
||||||
mat-checkbox {
|
|
||||||
font-size: 14px;
|
|
||||||
color: $gray-900;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--full {
|
|
||||||
min-height: auto;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdps-body--list {
|
|
||||||
padding: 0 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdps-list-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 16px;
|
|
||||||
padding: 14px 0;
|
|
||||||
border-bottom: 1px solid $gray-200;
|
|
||||||
font-size: 14px;
|
|
||||||
color: $gray-900;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
max-width: 85%;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdps-lockin-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
border-bottom: 1px solid $gray-200;
|
|
||||||
font-size: 14px;
|
|
||||||
color: $gray-900;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdps-lockin-fields {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 16px;
|
|
||||||
|
|
||||||
&--single {
|
|
||||||
grid-template-columns: minmax(0, 1fr);
|
|
||||||
max-width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 640px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
|
|
||||||
&--single {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-95
@@ -1,95 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
|
||||||
import { UntypedFormGroup, UntypedFormBuilder, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { MatCheckbox } from '@angular/material/checkbox';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-daily-deposit-scheme-product-settings-step',
|
|
||||||
templateUrl: './daily-deposit-scheme-product-settings-step.component.html',
|
|
||||||
styleUrls: ['./daily-deposit-scheme-product-settings-step.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
MatCheckbox
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class DailyDepositSchemeProductSettingsStepComponent implements OnInit {
|
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
|
||||||
|
|
||||||
@Input() DailyDepositSchemeProductsTemplate: any;
|
|
||||||
|
|
||||||
DailyDepositSchemeProductSettingsForm: UntypedFormGroup;
|
|
||||||
|
|
||||||
lockinPeriodFrequencyTypeData: any;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.createDailyDepositSchemeProductSettingsForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.lockinPeriodFrequencyTypeData = this.DailyDepositSchemeProductsTemplate.lockinPeriodFrequencyTypeOptions;
|
|
||||||
|
|
||||||
if (this.DailyDepositSchemeProductsTemplate?.id) {
|
|
||||||
this.DailyDepositSchemeProductSettingsForm.patchValue({
|
|
||||||
loanFacility: this.DailyDepositSchemeProductsTemplate.loanFacility ?? false,
|
|
||||||
prematureWithdrawalOption: this.DailyDepositSchemeProductsTemplate.prematureWithdrawalOption ?? false,
|
|
||||||
tenureFlexibility: this.DailyDepositSchemeProductsTemplate.tenureFlexibility ?? false,
|
|
||||||
lienMarked: this.DailyDepositSchemeProductsTemplate.lienMarked ?? false,
|
|
||||||
withdrawalFeeForTransfers: this.DailyDepositSchemeProductsTemplate.withdrawalFeeForTransfers ?? false,
|
|
||||||
enableLockinPeriod: this.DailyDepositSchemeProductsTemplate.enableLockinPeriod ?? false,
|
|
||||||
lockinPeriodFrequency: this.DailyDepositSchemeProductsTemplate.lockinPeriodFrequency ?? '',
|
|
||||||
lockinPeriodFrequencyType: this.DailyDepositSchemeProductsTemplate.lockinPeriodFrequencyType?.id ?? '',
|
|
||||||
maturityInstructions: this.DailyDepositSchemeProductsTemplate.maturityInstructions ?? false,
|
|
||||||
creditMaturityProceeds: this.DailyDepositSchemeProductsTemplate.creditMaturityProceeds ?? false,
|
|
||||||
penaltyGracePeriodMissedInstallments:
|
|
||||||
this.DailyDepositSchemeProductsTemplate.penaltyGracePeriodMissedInstallments ?? false,
|
|
||||||
panelRequestApplicable: this.DailyDepositSchemeProductsTemplate.panelRequestApplicable ?? false,
|
|
||||||
noPenaltyNomineeHeirClosure: this.DailyDepositSchemeProductsTemplate.noPenaltyNomineeHeirClosure ?? false,
|
|
||||||
deceasedSettlement: this.DailyDepositSchemeProductsTemplate.deceasedSettlement ?? false,
|
|
||||||
allowDebitTxnAfterDueDate: this.DailyDepositSchemeProductsTemplate.allowDebitTxnAfterDueDate ?? false,
|
|
||||||
accountClosedZeroBalanceInDayClosing:
|
|
||||||
this.DailyDepositSchemeProductsTemplate.accountClosedZeroBalanceInDayClosing ?? false,
|
|
||||||
minBalanceForInterestCalculation: this.DailyDepositSchemeProductsTemplate.minBalanceForInterestCalculation ?? ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createDailyDepositSchemeProductSettingsForm() {
|
|
||||||
this.DailyDepositSchemeProductSettingsForm = this.formBuilder.group({
|
|
||||||
loanFacility: [false],
|
|
||||||
prematureWithdrawalOption: [false],
|
|
||||||
tenureFlexibility: [false],
|
|
||||||
lienMarked: [false],
|
|
||||||
withdrawalFeeForTransfers: [false],
|
|
||||||
enableLockinPeriod: [false],
|
|
||||||
lockinPeriodFrequency: [''],
|
|
||||||
lockinPeriodFrequencyType: [''],
|
|
||||||
maturityInstructions: [false],
|
|
||||||
creditMaturityProceeds: [false],
|
|
||||||
penaltyGracePeriodMissedInstallments: [false],
|
|
||||||
panelRequestApplicable: [false],
|
|
||||||
noPenaltyNomineeHeirClosure: [false],
|
|
||||||
deceasedSettlement: [false],
|
|
||||||
allowDebitTxnAfterDueDate: [false],
|
|
||||||
accountClosedZeroBalanceInDayClosing: [false],
|
|
||||||
minBalanceForInterestCalculation: ['']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductSettings() {
|
|
||||||
const settings = { ...this.DailyDepositSchemeProductSettingsForm.value };
|
|
||||||
for (const key in settings) {
|
|
||||||
if (settings[key] === '') {
|
|
||||||
delete settings[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-192
@@ -1,192 +0,0 @@
|
|||||||
<!--
|
|
||||||
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/.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<form [formGroup]="DailyDepositSchemeProductTermsForm">
|
|
||||||
<div class="rdpt-section">
|
|
||||||
<div class="rdpt-section__header">
|
|
||||||
<span class="rdpt-section__badge"><i class="fas fa-coins"></i></span>
|
|
||||||
<span>Deposit Amount</span>
|
|
||||||
</div>
|
|
||||||
<div class="rdpt-body">
|
|
||||||
<div class="rdpt-grid rdpt-grid--three">
|
|
||||||
<mat-form-field appearance="outline" class="rdpt-field">
|
|
||||||
<mat-label>Minimum</mat-label>
|
|
||||||
<input type="number" matInput formControlName="minDepositAmount" min="0" step="0.01" />
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpt-field">
|
|
||||||
<mat-label>Maximum</mat-label>
|
|
||||||
<input type="number" matInput formControlName="maxDepositAmount" min="0" step="0.01" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="rdpt-section">
|
|
||||||
<div class="rdpt-section__header">
|
|
||||||
<span class="rdpt-section__badge"><i class="fas fa-chart-line"></i></span>
|
|
||||||
<span>Interest Rate & Posting Terms</span>
|
|
||||||
</div>
|
|
||||||
<div class="rdpt-body">
|
|
||||||
<div class="rdpt-banner">
|
|
||||||
<span class="rdpt-banner__icon">ℹ️</span>
|
|
||||||
<span>
|
|
||||||
Daily Deposit Scheme base rate:
|
|
||||||
<b>{{ DailyDepositSchemeProductTermsForm.get('nominalAnnualInterestRate').value }}%</b>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rdpt-grid rdpt-grid--three">
|
|
||||||
<mat-form-field appearance="outline" class="rdpt-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Nominal Annual Interest' | translate }}</mat-label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
matInput
|
|
||||||
matTooltip="{{ 'tooltips.The default interest rate set' | translate }}"
|
|
||||||
formControlName="nominalAnnualInterestRate"
|
|
||||||
required
|
|
||||||
min="0"
|
|
||||||
step="0.01"
|
|
||||||
/>
|
|
||||||
@if (DailyDepositSchemeProductTermsForm.get('nominalAnnualInterestRate').hasError('required')) {
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Nominal Annual Interest' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProductTermsForm.get('nominalAnnualInterestRate').hasError('min')) {
|
|
||||||
<mat-error>{{ 'labels.inputs.Nominal Annual Interest' | translate }} must be zero or greater</mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpt-field">
|
|
||||||
<mat-label>Effective Date</mat-label>
|
|
||||||
<input matInput [matDatepicker]="effectiveDatePicker" formControlName="effectiveDate" />
|
|
||||||
<mat-datepicker-toggle matSuffix [for]="effectiveDatePicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #effectiveDatePicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpt-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Interest Calculation Type' | translate }}</mat-label>
|
|
||||||
<mat-select
|
|
||||||
formControlName="interestCalculateSelector"
|
|
||||||
matTooltip="{{ 'tooltips.The method used to calculate interest' | translate }}"
|
|
||||||
>
|
|
||||||
@for (opt of interestCalculationTypeOptionData; track opt.id) {
|
|
||||||
<mat-option [value]="opt.id">{{ opt.value }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rdpt-grid rdpt-grid--three">
|
|
||||||
<mat-form-field appearance="outline" class="rdpt-field">
|
|
||||||
<mat-label>Interest Calculate</mat-label>
|
|
||||||
<mat-select formControlName="interestCalculationType" required>
|
|
||||||
@for (mode of interestCalculationTypeData; track mode.id) {
|
|
||||||
<mat-option [value]="mode.id">{{ mode.value | translateKey: 'catalogs' }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Interest Calculation Type' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpt-field">
|
|
||||||
<mat-label>Interest Compounding Period</mat-label>
|
|
||||||
<mat-select formControlName="interestCompoundingPeriodType" required>
|
|
||||||
@for (opt of interestCompoundingPeriodTypeData; track opt.id) {
|
|
||||||
<mat-option [value]="opt.id">{{ opt.value | translateKey: 'catalogs' }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
<mat-error>Interest Compounding Period <strong>is required</strong></mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpt-field">
|
|
||||||
<mat-label>Days in Year</mat-label>
|
|
||||||
<mat-select formControlName="interestCalculationDaysInYearType" required>
|
|
||||||
@for (opt of interestCalculationDaysInYearTypeData; track opt.id) {
|
|
||||||
<mat-option [value]="opt.id">{{ opt.value | translateKey: 'catalogs' }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
<mat-error>Days in Year <strong>is required</strong></mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProductTermsForm.value.interestCalculationType === 2) {
|
|
||||||
<mat-form-field appearance="outline" class="rdpt-field">
|
|
||||||
<mat-label>Average Period</mat-label>
|
|
||||||
<mat-select formControlName="averagePeriod">
|
|
||||||
@for (period of periodFrequencyData; track period.id) {
|
|
||||||
<mat-option [value]="period.value">{{ period.value }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
}
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpt-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Interest Posting Period' | translate }}</mat-label>
|
|
||||||
<mat-select
|
|
||||||
formControlName="interestPostingPeriodType"
|
|
||||||
matTooltip="{{ 'tooltips.Interest rate is posted or credited to a saving account' | translate }}"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
@for (interestPostingPeriodType of interestPostingPeriodTypeData; track interestPostingPeriodType) {
|
|
||||||
<mat-option [value]="interestPostingPeriodType.id">
|
|
||||||
{{ interestPostingPeriodType.value | translateKey: 'catalogs' }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Interest Posting Period' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rdpt-footer">
|
|
||||||
<div class="rdpt-footer__grid">
|
|
||||||
<div class="rdpt-toggle-card">
|
|
||||||
<div>
|
|
||||||
<h4>Auto Interest Post</h4>
|
|
||||||
</div>
|
|
||||||
<div class="rdpt-toggle-card__choices">
|
|
||||||
@for (option of yesNoOptions; track option.value) {
|
|
||||||
<label class="inline-flex items-center cursor-pointer">
|
|
||||||
<input type="radio" class="w-4 h-4" formControlName="autoPost" [value]="option.value" />
|
|
||||||
<span class="ml-2 text-sm">{{ option.label }}</span>
|
|
||||||
</label>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpt-field">
|
|
||||||
<mat-label>Frequency (Fix Day)</mat-label>
|
|
||||||
<input type="number" matInput formControlName="frequencyFixDay" />
|
|
||||||
<mat-hint>Days</mat-hint>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="rdpt-field">
|
|
||||||
<mat-label>Last Interest Date</mat-label>
|
|
||||||
<input matInput [matDatepicker]="lastInterestDatePicker" formControlName="lastInterestDate" />
|
|
||||||
<mat-datepicker-toggle matSuffix [for]="lastInterestDatePicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #lastInterestDatePicker></mat-datepicker>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProductTermsForm.value.interestCalculationType === 1) {
|
|
||||||
<div class="rdpt-note">
|
|
||||||
<span>⚠️</span>
|
|
||||||
<span
|
|
||||||
>Daily product-based interest accrual enabled - interest calculated EOD on daily closing balance.</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
-151
@@ -1,151 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$accent: #0ea5e9;
|
|
||||||
$border: #e2e8f0;
|
|
||||||
$gray-50: #f8f9fa;
|
|
||||||
$gray-200: #e8eaed;
|
|
||||||
$gray-900: #202124;
|
|
||||||
|
|
||||||
.rdpt-section {
|
|
||||||
border: 1px solid $border;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
& + & {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 12px 24px;
|
|
||||||
background: $gray-50;
|
|
||||||
border-bottom: 1px solid $gray-200;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px;
|
|
||||||
color: $accent;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3,
|
|
||||||
span:not(.rdpt-section__badge) {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $gray-900;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpt-section__badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: #eff6ff;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpt-body {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpt-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 4px 16px;
|
|
||||||
padding: 16px 24px;
|
|
||||||
|
|
||||||
&--three {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 600px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
|
|
||||||
&--three {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpt-field {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--full {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpt-banner {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin: 16px 24px 0;
|
|
||||||
padding: 12px 14px;
|
|
||||||
background: #eff6ff;
|
|
||||||
border: 1px solid #dbeafe;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: #1d4ed8;
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
&__icon {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpt-footer {
|
|
||||||
padding: 0 24px 24px;
|
|
||||||
border-top: 1px solid #f1f5f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpt-footer__grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 16px;
|
|
||||||
padding-top: 18px;
|
|
||||||
|
|
||||||
@media (width <= 900px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpt-toggle-card {
|
|
||||||
padding: 12px 0;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: 0 0 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $gray-900;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__choices {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdpt-note {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 18px;
|
|
||||||
padding: 12px 14px;
|
|
||||||
border: 1px solid #fde68a;
|
|
||||||
background: #fffbeb;
|
|
||||||
color: #b45309;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
-169
@@ -1,169 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
|
||||||
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
|
||||||
import { Dates } from 'app/core/utils/dates';
|
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-daily-deposit-scheme-product-terms-step',
|
|
||||||
templateUrl: './daily-deposit-scheme-product-terms-step.component.html',
|
|
||||||
styleUrls: ['./daily-deposit-scheme-product-terms-step.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
MatTooltip
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class DailyDepositSchemeProductTermsStepComponent implements OnInit {
|
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
|
||||||
private dateUtils = inject(Dates);
|
|
||||||
private settingsService = inject(SettingsService);
|
|
||||||
|
|
||||||
@Input() DailyDepositSchemeProductsTemplate: any;
|
|
||||||
|
|
||||||
DailyDepositSchemeProductTermsForm: UntypedFormGroup;
|
|
||||||
|
|
||||||
interestCompoundingPeriodTypeData: any;
|
|
||||||
interestPostingPeriodTypeData: any;
|
|
||||||
interestCalculationTypeData: any;
|
|
||||||
interestCalculationTypeOptionData: any;
|
|
||||||
interestCalculationDaysInYearTypeData: any;
|
|
||||||
periodFrequencyData: any[] = [];
|
|
||||||
yesNoOptions: Array<{ value: boolean; label: string }> = [
|
|
||||||
{ value: true, label: 'Yes' },
|
|
||||||
{ value: false, label: 'No' }
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.createDailyDepositSchemeProductTermsForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.interestCompoundingPeriodTypeData =
|
|
||||||
this.DailyDepositSchemeProductsTemplate.interestCompoundingPeriodTypeOptions;
|
|
||||||
this.interestPostingPeriodTypeData = this.DailyDepositSchemeProductsTemplate.interestPostingPeriodTypeOptions;
|
|
||||||
// Standard enum (1=Daily, 2=Average) → "Interest Calculate" dropdown → interestCalculationType form control
|
|
||||||
this.interestCalculationTypeData = this.DailyDepositSchemeProductsTemplate.interestCalculationTypeOptions;
|
|
||||||
// CBS code values → "Interest Calculation Type" dropdown → interestCalculateSelector form control
|
|
||||||
this.interestCalculationTypeOptionData =
|
|
||||||
this.DailyDepositSchemeProductsTemplate.interestCalculationTypeOption || [];
|
|
||||||
this.interestCalculationDaysInYearTypeData =
|
|
||||||
this.DailyDepositSchemeProductsTemplate.interestCalculationDaysInYearTypeOptions;
|
|
||||||
this.periodFrequencyData = this.DailyDepositSchemeProductsTemplate.periodFrequencyOptions || [];
|
|
||||||
this.setYesNoOptions(this.DailyDepositSchemeProductsTemplate?.yesNoOptions);
|
|
||||||
|
|
||||||
const isEditing = !!this.DailyDepositSchemeProductsTemplate.id;
|
|
||||||
const defaultPeriod = this.periodFrequencyData[0]?.value || 'Monthly';
|
|
||||||
const savedEnumTypeId =
|
|
||||||
this.DailyDepositSchemeProductsTemplate.interestCalculationType?.id ||
|
|
||||||
this.interestCalculationTypeData?.[0]?.id ||
|
|
||||||
1;
|
|
||||||
const savedSelectorId = isEditing
|
|
||||||
? (this.DailyDepositSchemeProductsTemplate.interestCalculateSelector ??
|
|
||||||
this.interestCalculationTypeOptionData?.[0]?.id ??
|
|
||||||
null)
|
|
||||||
: (this.interestCalculationTypeOptionData?.[0]?.id ?? null);
|
|
||||||
|
|
||||||
this.DailyDepositSchemeProductTermsForm.patchValue({
|
|
||||||
nominalAnnualInterestRate: this.DailyDepositSchemeProductsTemplate.nominalAnnualInterestRate ?? 0,
|
|
||||||
interestCompoundingPeriodType: this.DailyDepositSchemeProductsTemplate.interestCompoundingPeriodType?.id,
|
|
||||||
interestPostingPeriodType: this.DailyDepositSchemeProductsTemplate.interestPostingPeriodType?.id,
|
|
||||||
interestCalculationType: savedEnumTypeId,
|
|
||||||
interestCalculationDaysInYearType: this.DailyDepositSchemeProductsTemplate.interestCalculationDaysInYearType?.id,
|
|
||||||
effectiveDate: this.DailyDepositSchemeProductsTemplate.effectiveDate
|
|
||||||
? this.parseDate(this.DailyDepositSchemeProductsTemplate.effectiveDate)
|
|
||||||
: '',
|
|
||||||
interestCalculateSelector: savedSelectorId,
|
|
||||||
averagePeriod: isEditing ? this.DailyDepositSchemeProductsTemplate.averagePeriod || defaultPeriod : defaultPeriod,
|
|
||||||
autoPost: isEditing ? (this.DailyDepositSchemeProductsTemplate.autoPost ?? true) : true,
|
|
||||||
frequencyFixDay: isEditing ? (this.DailyDepositSchemeProductsTemplate.frequencyFixDay ?? 30) : 30,
|
|
||||||
lastInterestDate: this.DailyDepositSchemeProductsTemplate.lastInterestDate
|
|
||||||
? this.parseDate(this.DailyDepositSchemeProductsTemplate.lastInterestDate)
|
|
||||||
: '',
|
|
||||||
minDepositAmount: this.DailyDepositSchemeProductsTemplate.minDepositAmount ?? '',
|
|
||||||
maxDepositAmount: this.DailyDepositSchemeProductsTemplate.maxDepositAmount ?? ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseDate(dateValue: any): Date | null {
|
|
||||||
if (!dateValue) return null;
|
|
||||||
if (Array.isArray(dateValue)) {
|
|
||||||
return new Date(dateValue[0], dateValue[1] - 1, dateValue[2]);
|
|
||||||
}
|
|
||||||
if (dateValue instanceof Date) return dateValue;
|
|
||||||
return new Date(dateValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setYesNoOptions(options: any[]) {
|
|
||||||
if (!Array.isArray(options)) return;
|
|
||||||
const mapped = options
|
|
||||||
.map((opt: any) => {
|
|
||||||
const raw = opt?.name ?? opt?.value ?? opt;
|
|
||||||
const norm = typeof raw === 'string' ? raw.trim().toUpperCase() : raw;
|
|
||||||
if (norm === 'YES') return { value: true, label: 'Yes' };
|
|
||||||
if (norm === 'NO') return { value: false, label: 'No' };
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.filter(Boolean);
|
|
||||||
if (mapped.length) this.yesNoOptions = mapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
createDailyDepositSchemeProductTermsForm() {
|
|
||||||
this.DailyDepositSchemeProductTermsForm = this.formBuilder.group({
|
|
||||||
nominalAnnualInterestRate: [
|
|
||||||
'',
|
|
||||||
[
|
|
||||||
Validators.required,
|
|
||||||
Validators.min(0)
|
|
||||||
]
|
|
||||||
],
|
|
||||||
interestCompoundingPeriodType: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
interestPostingPeriodType: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
interestCalculationType: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
interestCalculationDaysInYearType: [
|
|
||||||
'',
|
|
||||||
Validators.required
|
|
||||||
],
|
|
||||||
effectiveDate: [''],
|
|
||||||
interestCalculateSelector: [1],
|
|
||||||
averagePeriod: ['Monthly'],
|
|
||||||
autoPost: [true],
|
|
||||||
frequencyFixDay: [
|
|
||||||
30,
|
|
||||||
[Validators.min(0)]
|
|
||||||
],
|
|
||||||
lastInterestDate: [''],
|
|
||||||
minDepositAmount: [''],
|
|
||||||
maxDepositAmount: ['']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductTerms() {
|
|
||||||
const formValue = { ...this.DailyDepositSchemeProductTermsForm.value };
|
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
|
||||||
|
|
||||||
if (formValue.effectiveDate instanceof Date) {
|
|
||||||
formValue.effectiveDate = this.dateUtils.formatDate(formValue.effectiveDate, dateFormat) || '';
|
|
||||||
}
|
|
||||||
formValue.minDepositAmount = formValue.minDepositAmount;
|
|
||||||
formValue.maxDepositAmount = formValue.maxDepositAmount;
|
|
||||||
return formValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-37
@@ -1,37 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { ActivatedRouteSnapshot } from '@angular/router';
|
|
||||||
|
|
||||||
/** rxjs Imports */
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
/** Custom Services */
|
|
||||||
import { ProductsService } from '../products.service';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Daily Deposit Scheme Product data resolver.
|
|
||||||
*
|
|
||||||
* Reuses the Recurring Deposit product read API — a Daily Deposit Scheme
|
|
||||||
* product is a Recurring Deposit product under the hood.
|
|
||||||
*/
|
|
||||||
@Injectable()
|
|
||||||
export class DailyDepositSchemeProductResolver {
|
|
||||||
private productsService = inject(ProductsService);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Daily Deposit Scheme product data.
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
|
||||||
const productId = route.parent.paramMap.get('productId');
|
|
||||||
return this.productsService.getRecurringDepositProduct(productId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-172
@@ -1,172 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="pl-root">
|
|
||||||
<div class="pl-header">
|
|
||||||
<div class="pl-header-left">
|
|
||||||
<div class="pl-header-ic"><fa-icon icon="calendar-day"></fa-icon></div>
|
|
||||||
<div class="pl-header-text">
|
|
||||||
<h2>{{ 'labels.heading.Daily Deposit Scheme Products' | translate }}</h2>
|
|
||||||
<p>Manage Daily Deposit Scheme products — daily instalment amount, interest rates and charges.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pl-header-right">
|
|
||||||
<div class="pl-count-chip" matTooltip="Total configured Daily Deposit Scheme products">
|
|
||||||
<fa-icon icon="layer-group"></fa-icon>
|
|
||||||
<strong>{{ totalCount }}</strong>
|
|
||||||
<span>{{ totalCount === 1 ? 'product' : 'products' }}</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="pl-create-btn"
|
|
||||||
type="button"
|
|
||||||
[routerLink]="['create']"
|
|
||||||
*mifosxHasPermission="'CREATE_RECURRINGDEPOSITPRODUCT'"
|
|
||||||
matTooltip="{{ 'tooltips.Create Daily Deposit Scheme Product' | translate }}"
|
|
||||||
>
|
|
||||||
<fa-icon icon="plus"></fa-icon>
|
|
||||||
<span>{{ 'labels.buttons.Create Daily Deposit Scheme Product' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pl-kpis">
|
|
||||||
<div class="pl-kpi pl-kpi--blue">
|
|
||||||
<div class="pl-kpi-ic"><fa-icon icon="calendar-day"></fa-icon></div>
|
|
||||||
<div>
|
|
||||||
<div class="pl-kpi-n">{{ totalCount | number }}</div>
|
|
||||||
<div class="pl-kpi-l">Total DDS Products</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pl-kpi pl-kpi--teal">
|
|
||||||
<div class="pl-kpi-ic"><fa-icon icon="coins"></fa-icon></div>
|
|
||||||
<div>
|
|
||||||
<div class="pl-kpi-n">{{ currencyCount | number }}</div>
|
|
||||||
<div class="pl-kpi-l">{{ currencyCount === 1 ? 'Currency' : 'Currencies' }} in use</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pl-kpi pl-kpi--purple">
|
|
||||||
<div class="pl-kpi-ic"><fa-icon icon="filter"></fa-icon></div>
|
|
||||||
<div>
|
|
||||||
<div class="pl-kpi-n">{{ filteredCount | number }}</div>
|
|
||||||
<div class="pl-kpi-l">Showing after filter</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pl-card">
|
|
||||||
<div class="pl-toolbar">
|
|
||||||
<div class="pl-search">
|
|
||||||
<fa-icon icon="search" class="pl-search-ic"></fa-icon>
|
|
||||||
<input
|
|
||||||
#filter
|
|
||||||
type="text"
|
|
||||||
(input)="applyFilter($any($event.target).value)"
|
|
||||||
placeholder="Search by name or short name…"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (totalCount === 0) {
|
|
||||||
<div class="pl-empty">
|
|
||||||
<div class="pl-empty-ic"><fa-icon icon="calendar-day"></fa-icon></div>
|
|
||||||
<h3>No Daily Deposit Scheme products yet</h3>
|
|
||||||
<p>Create your first Daily Deposit Scheme product to enable daily-instalment onboarding.</p>
|
|
||||||
<button
|
|
||||||
class="pl-create-btn"
|
|
||||||
type="button"
|
|
||||||
[routerLink]="['create']"
|
|
||||||
*mifosxHasPermission="'CREATE_RECURRINGDEPOSITPRODUCT'"
|
|
||||||
>
|
|
||||||
<fa-icon icon="plus"></fa-icon>
|
|
||||||
<span>{{ 'labels.buttons.Create Daily Deposit Scheme Product' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
} @else {
|
|
||||||
<div class="pl-table-wrap">
|
|
||||||
<table mat-table [dataSource]="dataSource" matSort class="pl-table">
|
|
||||||
<ng-container matColumnDef="name">
|
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Name' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let p">
|
|
||||||
<div class="pl-product-cell">
|
|
||||||
<div class="pl-product-icon"><fa-icon icon="calendar-day"></fa-icon></div>
|
|
||||||
<div class="pl-product-meta">
|
|
||||||
<div class="pl-product-name">{{ p.name }}</div>
|
|
||||||
@if (p.currency?.code) {
|
|
||||||
<div class="pl-product-sub">{{ p.currency.code }}</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="shortName">
|
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Short Name' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let p">
|
|
||||||
<span class="pl-tag">{{ p.shortName }}</span>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="currency">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Currency</th>
|
|
||||||
<td mat-cell *matCellDef="let p">
|
|
||||||
@if (p.currency?.code) {
|
|
||||||
<span class="pl-currency-pill">{{ p.currency.code }}</span>
|
|
||||||
} @else {
|
|
||||||
<span class="muted">—</span>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="actions">
|
|
||||||
<th mat-header-cell *matHeaderCellDef class="num">Actions</th>
|
|
||||||
<td mat-cell *matCellDef="let p" class="num">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pl-action-btn pl-action-btn--view"
|
|
||||||
[routerLink]="[p.id]"
|
|
||||||
(click)="$event.stopPropagation()"
|
|
||||||
matTooltip="View product details"
|
|
||||||
>
|
|
||||||
<fa-icon icon="eye"></fa-icon><span>View</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pl-action-btn pl-action-btn--edit"
|
|
||||||
[class.pl-action-btn--disabled]="p.isPendingApproval"
|
|
||||||
[disabled]="p.isPendingApproval"
|
|
||||||
[routerLink]="p.isPendingApproval ? null : [p.id, 'edit']"
|
|
||||||
(click)="$event.stopPropagation()"
|
|
||||||
[matTooltip]="
|
|
||||||
p.isPendingApproval
|
|
||||||
? 'This product is locked because an edit is pending approval'
|
|
||||||
: 'Edit this product'
|
|
||||||
"
|
|
||||||
*mifosxHasPermission="'UPDATE_RECURRINGDEPOSITPRODUCT'"
|
|
||||||
>
|
|
||||||
<fa-icon [icon]="p.isPendingApproval ? 'lock' : 'edit'"></fa-icon>
|
|
||||||
<span>{{ p.isPendingApproval ? 'Locked' : 'Edit' }}</span>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="pl-row"></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
@if (filteredCount === 0 && totalCount > 0) {
|
|
||||||
<div class="pl-no-match">
|
|
||||||
<fa-icon icon="search"></fa-icon>
|
|
||||||
<span>No products match your filter.</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" [pageSize]="10" showFirstLastButtons></mat-paginator>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
-17
@@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use 'assets/styles/product-list' as pl;
|
|
||||||
|
|
||||||
@include pl.product-list-styles;
|
|
||||||
|
|
||||||
/* Daily Deposit Scheme accent */
|
|
||||||
:host {
|
|
||||||
--pl-accent: #0f766e;
|
|
||||||
--pl-accent-l: #ccfbf1;
|
|
||||||
}
|
|
||||||
-124
@@ -1,124 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { Component, OnInit, ViewChild, inject } from '@angular/core';
|
|
||||||
import { MatPaginator } from '@angular/material/paginator';
|
|
||||||
import { MatSort, MatSortHeader } from '@angular/material/sort';
|
|
||||||
import {
|
|
||||||
MatTableDataSource,
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow
|
|
||||||
} from '@angular/material/table';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Daily Deposit Scheme Products component.
|
|
||||||
*
|
|
||||||
* Lists Daily Deposit Scheme products. These are Recurring Deposit products
|
|
||||||
* under the hood (same create/read API) so this list reuses that data source.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-daily-deposit-scheme-products',
|
|
||||||
templateUrl: './daily-deposit-scheme-products.component.html',
|
|
||||||
styleUrls: ['./daily-deposit-scheme-products.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
MatTooltip,
|
|
||||||
FaIconComponent,
|
|
||||||
MatTable,
|
|
||||||
MatSort,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatSortHeader,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow,
|
|
||||||
MatPaginator
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class DailyDepositSchemeProductsComponent implements OnInit {
|
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
|
|
||||||
/** Data table data. */
|
|
||||||
productData: any[] = [];
|
|
||||||
/** Columns to be displayed in the Daily Deposit Scheme products table. */
|
|
||||||
displayedColumns: string[] = [
|
|
||||||
'name',
|
|
||||||
'shortName',
|
|
||||||
'currency',
|
|
||||||
'actions'
|
|
||||||
];
|
|
||||||
/** Data source for the table. */
|
|
||||||
dataSource: MatTableDataSource<any>;
|
|
||||||
|
|
||||||
filteredCount = 0;
|
|
||||||
|
|
||||||
get totalCount(): number {
|
|
||||||
return this.productData?.length || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get currencyCount(): number {
|
|
||||||
const set = new Set<string>();
|
|
||||||
for (const p of this.productData || []) {
|
|
||||||
if (p?.currency?.code) set.add(p.currency.code);
|
|
||||||
}
|
|
||||||
return set.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Paginator for the table. */
|
|
||||||
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
|
|
||||||
/** Sorter for the table. */
|
|
||||||
@ViewChild(MatSort, { static: true }) sort: MatSort;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.route.data.subscribe((data: { dailyDepositSchemeProducts: any }) => {
|
|
||||||
this.productData = (data.dailyDepositSchemeProducts || []).filter(
|
|
||||||
(product: any) => product.productCategory === 'DDS'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.setProducts();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters data in the table based on the passed value.
|
|
||||||
* @param {string} filterValue Value to filter data.
|
|
||||||
*/
|
|
||||||
applyFilter(filterValue: string) {
|
|
||||||
this.dataSource.filter = filterValue.trim().toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the data source, paginator and sorter for the table.
|
|
||||||
*/
|
|
||||||
setProducts() {
|
|
||||||
this.dataSource = new MatTableDataSource(this.productData);
|
|
||||||
this.dataSource.paginator = this.paginator;
|
|
||||||
this.dataSource.sort = this.sort;
|
|
||||||
this.filteredCount = this.totalCount;
|
|
||||||
this.dataSource.connect().subscribe((rows) => (this.filteredCount = rows.length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-35
@@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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';
|
|
||||||
|
|
||||||
/** rxjs Imports */
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
/** Custom Services */
|
|
||||||
import { ProductsService } from '../products.service';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Daily Deposit Scheme Products data resolver.
|
|
||||||
*
|
|
||||||
* A Daily Deposit Scheme product is a Recurring Deposit product under the hood,
|
|
||||||
* so this reuses the same read API as Recurring Deposit Products.
|
|
||||||
*/
|
|
||||||
@Injectable()
|
|
||||||
export class DailyDepositSchemeProductsResolver {
|
|
||||||
private productsService = inject(ProductsService);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Daily Deposit Scheme products data.
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
resolve(): Observable<any> {
|
|
||||||
return this.productsService.getRecurringDepositProducts();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-35
@@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { ActivatedRouteSnapshot } from '@angular/router';
|
|
||||||
|
|
||||||
/** rxjs Imports */
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
/** Custom Services */
|
|
||||||
import { ProductsService } from '../../products.service';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recurring Deposits Account Template resolver.
|
|
||||||
*/
|
|
||||||
@Injectable()
|
|
||||||
export class DailyDepositSchemeProductAndTemplateResolver {
|
|
||||||
private productsService = inject(ProductsService);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Recurring Deposits Product and Template.
|
|
||||||
* @param {ActivatedRouteSnapshot} route Route Snapshot
|
|
||||||
* @returns {Observable<any>}
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
|
||||||
const productId = route.parent.paramMap.get('productId');
|
|
||||||
return this.productsService.getRecurringDepositProductAndTemplate(productId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-202
@@ -1,202 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="rdp-container">
|
|
||||||
<div class="rdp-header">
|
|
||||||
<i class="fas fa-sync-alt rdp-header__icon"></i>
|
|
||||||
<h2 class="rdp-header__title">{{ 'labels.heading.Edit Daily Deposit Scheme Product' | translate }}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mat-tab-group
|
|
||||||
class="rdp-tabs"
|
|
||||||
animationDuration="200ms"
|
|
||||||
[preserveContent]="true"
|
|
||||||
[(selectedIndex)]="selectedTabIndex"
|
|
||||||
>
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<i class="fas fa-info-circle rdp-tab-icon"></i>
|
|
||||||
{{ 'labels.inputs.DETAILS' | translate }}
|
|
||||||
</ng-template>
|
|
||||||
<div class="rdp-tab-content">
|
|
||||||
<mifosx-daily-deposit-scheme-product-details-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="DailyDepositSchemeProductsTemplate"
|
|
||||||
></mifosx-daily-deposit-scheme-product-details-step>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<i class="fas fa-coins rdp-tab-icon"></i>
|
|
||||||
{{ 'labels.inputs.CURRENCY' | translate }}
|
|
||||||
</ng-template>
|
|
||||||
<div class="rdp-tab-content">
|
|
||||||
<mifosx-daily-deposit-scheme-product-currency-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="DailyDepositSchemeProductsTemplate"
|
|
||||||
></mifosx-daily-deposit-scheme-product-currency-step>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<i class="fas fa-sliders-h rdp-tab-icon"></i>
|
|
||||||
{{ 'labels.inputs.TERMS' | translate }}
|
|
||||||
</ng-template>
|
|
||||||
<div class="rdp-tab-content">
|
|
||||||
<mifosx-daily-deposit-scheme-product-terms-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="DailyDepositSchemeProductsTemplate"
|
|
||||||
></mifosx-daily-deposit-scheme-product-terms-step>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<i class="fas fa-cog rdp-tab-icon"></i>
|
|
||||||
{{ 'labels.inputs.SETTINGS' | translate }}
|
|
||||||
</ng-template>
|
|
||||||
<div class="rdp-tab-content">
|
|
||||||
<mifosx-daily-deposit-scheme-product-settings-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="DailyDepositSchemeProductsTemplate"
|
|
||||||
></mifosx-daily-deposit-scheme-product-settings-step>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<i class="fas fa-chart-area rdp-tab-icon"></i>
|
|
||||||
CBS Base Chart
|
|
||||||
</ng-template>
|
|
||||||
<div class="rdp-tab-content">
|
|
||||||
@if (activeBaseVersion) {
|
|
||||||
<div class="base-rate-card">
|
|
||||||
<div class="base-rate-header">
|
|
||||||
<div>
|
|
||||||
<h3 class="base-rate-title">{{ activeBaseVersion.versionLabel }}</h3>
|
|
||||||
<p class="base-rate-meta">
|
|
||||||
Active CBS base chart
|
|
||||||
<span>Effective from {{ activeBaseVersion.effectiveFromDate }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<span class="base-rate-badge">Active</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (activeBaseVersion.remarks) {
|
|
||||||
<p class="base-rate-remarks">{{ activeBaseVersion.remarks }}</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<table class="base-rate-table" mat-table [dataSource]="activeBaseVersion.slabs">
|
|
||||||
<ng-container matColumnDef="srNo">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Sr.</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.srNo }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="description">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Particular</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.description }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="fromPeriod">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.fromPeriod }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="toPeriod">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.toPeriod ?? 'N/A' }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="periodTypeLabel">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Period Type</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.periodTypeLabel }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="annualInterestRate">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Annual Rate (%)</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">
|
|
||||||
<strong>{{ slab.annualInterestRate | number: '1.2-2' }}</strong>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="rdSlabColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: rdSlabColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
} @else {
|
|
||||||
<p>Loading CBS base chart...</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<i class="fas fa-receipt rdp-tab-icon"></i>
|
|
||||||
{{ 'labels.inputs.CHARGES' | translate }}
|
|
||||||
</ng-template>
|
|
||||||
<div class="rdp-tab-content">
|
|
||||||
<mifosx-daily-deposit-scheme-product-charges-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="DailyDepositSchemeProductsTemplate"
|
|
||||||
[currencyCode]="DailyDepositSchemeProductCurrencyForm.get('currencyCode')"
|
|
||||||
></mifosx-daily-deposit-scheme-product-charges-step>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<i class="fas fa-book rdp-tab-icon"></i>
|
|
||||||
{{ 'labels.inputs.ACCOUNTING' | translate }}
|
|
||||||
</ng-template>
|
|
||||||
<div class="rdp-tab-content">
|
|
||||||
<mifosx-daily-deposit-scheme-product-accounting-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="DailyDepositSchemeProductsTemplate"
|
|
||||||
[accountingRuleData]="accountingRuleData"
|
|
||||||
[DailyDepositSchemeProductFormValid]="DailyDepositSchemeProductFormValid"
|
|
||||||
[isDormancyTrackingActive]="DailyDepositSchemeProductCurrencyForm.get('dormancyAlert')"
|
|
||||||
>
|
|
||||||
</mifosx-daily-deposit-scheme-product-accounting-step>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
@if (DailyDepositSchemeProductFormValid) {
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<i class="fas fa-eye rdp-tab-icon"></i>
|
|
||||||
{{ 'labels.inputs.PREVIEW' | translate }}
|
|
||||||
</ng-template>
|
|
||||||
<div class="rdp-tab-content">
|
|
||||||
<mifosx-daily-deposit-scheme-product-preview-step
|
|
||||||
[DailyDepositSchemeProductsTemplate]="DailyDepositSchemeProductsTemplate"
|
|
||||||
[chartSlabsDisplayedColumns]="rdSlabColumns"
|
|
||||||
[accountingRuleData]="accountingRuleData"
|
|
||||||
[DailyDepositSchemeProduct]="DailyDepositSchemeProductPreview"
|
|
||||||
(submitEvent)="submit()"
|
|
||||||
></mifosx-daily-deposit-scheme-product-preview-step>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
}
|
|
||||||
</mat-tab-group>
|
|
||||||
|
|
||||||
<div class="rdp-action-bar">
|
|
||||||
<button mat-raised-button [routerLink]="['../../']" class="rdp-action-bar__cancel">
|
|
||||||
{{ 'labels.buttons.Cancel' | translate }}
|
|
||||||
</button>
|
|
||||||
<div class="rdp-action-bar__right">
|
|
||||||
@if (selectedTabIndex > 0) {
|
|
||||||
<button mat-raised-button (click)="selectedTabIndex = selectedTabIndex - 1">
|
|
||||||
<i class="fas fa-arrow-left" style="margin-right: 8px"></i>
|
|
||||||
{{ 'labels.buttons.Previous' | translate }}
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
@if (selectedTabIndex < totalTabs - 1) {
|
|
||||||
<button mat-raised-button color="primary" (click)="selectedTabIndex = selectedTabIndex + 1">
|
|
||||||
{{ 'labels.buttons.Next' | translate }}
|
|
||||||
<i class="fas fa-arrow-right" style="margin-left: 8px"></i>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProductFormValid && selectedTabIndex === totalTabs - 1) {
|
|
||||||
<button mat-raised-button color="primary" (click)="submit()">
|
|
||||||
<i class="fas fa-check" style="margin-right: 8px"></i>
|
|
||||||
{{ 'labels.buttons.Submit' | translate }}
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
-138
@@ -1,138 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$primary: #1353a0;
|
|
||||||
|
|
||||||
.rdp-container {
|
|
||||||
max-width: 960px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&__icon {
|
|
||||||
font-size: 20px;
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-tabs {
|
|
||||||
background: var(--md-sys-color-surface, #fff);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
|
||||||
|
|
||||||
::ng-deep {
|
|
||||||
.mat-mdc-tab-header {
|
|
||||||
border-bottom: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-mdc-tab {
|
|
||||||
min-width: 100px;
|
|
||||||
padding: 0 12px;
|
|
||||||
height: 52px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-mdc-tab-body-wrapper {
|
|
||||||
min-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-mdc-tab-body {
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-mdc-tab-body-content {
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-tab-icon {
|
|
||||||
margin-right: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-tab-content {
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-card {
|
|
||||||
padding: 16px;
|
|
||||||
border: 1px solid #dbe4f0;
|
|
||||||
border-radius: 14px;
|
|
||||||
background: linear-gradient(180deg, #f8fbff 0%, #fff 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: start;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-meta {
|
|
||||||
margin: 6px 0 0;
|
|
||||||
color: #5b6b7f;
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-badge {
|
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: #dcfce7;
|
|
||||||
color: #166534;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-remarks {
|
|
||||||
margin: 0 0 12px;
|
|
||||||
color: #334155;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-rate-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-action-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 16px 0;
|
|
||||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
|
||||||
|
|
||||||
&__cancel {
|
|
||||||
color: rgb(0 0 0 / 60%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__right {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-305
@@ -1,305 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { Component, ViewChild, inject } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
|
|
||||||
/** Custom Components */
|
|
||||||
import { DailyDepositSchemeProductDetailsStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-details-step/daily-deposit-scheme-product-details-step.component';
|
|
||||||
import { DailyDepositSchemeProductCurrencyStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-currency-step/daily-deposit-scheme-product-currency-step.component';
|
|
||||||
import { DailyDepositSchemeProductTermsStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-terms-step/daily-deposit-scheme-product-terms-step.component';
|
|
||||||
import { DailyDepositSchemeProductSettingsStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-settings-step/daily-deposit-scheme-product-settings-step.component';
|
|
||||||
import { DailyDepositSchemeProductChargesStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-charges-step/daily-deposit-scheme-product-charges-step.component';
|
|
||||||
import { DailyDepositSchemeProductAccountingStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-accounting-step/daily-deposit-scheme-product-accounting-step.component';
|
|
||||||
import {
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow
|
|
||||||
} from '@angular/material/table';
|
|
||||||
|
|
||||||
/** Custom Services */
|
|
||||||
import { ProductsService } from 'app/products/products.service';
|
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
|
||||||
import { Accounting } from 'app/core/utils/accounting';
|
|
||||||
import { Dates } from 'app/core/utils/dates';
|
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
|
||||||
import { DailyDepositSchemeProductPreviewStepComponent } from '../daily-deposit-scheme-product-stepper/daily-deposit-scheme-product-preview-step/daily-deposit-scheme-product-preview-step.component';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-edit-daily-deposit-scheme-product',
|
|
||||||
templateUrl: './edit-daily-deposit-scheme-product.component.html',
|
|
||||||
styleUrls: ['./edit-daily-deposit-scheme-product.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
MatTabsModule,
|
|
||||||
DailyDepositSchemeProductDetailsStepComponent,
|
|
||||||
DailyDepositSchemeProductCurrencyStepComponent,
|
|
||||||
DailyDepositSchemeProductTermsStepComponent,
|
|
||||||
DailyDepositSchemeProductSettingsStepComponent,
|
|
||||||
DailyDepositSchemeProductChargesStepComponent,
|
|
||||||
DailyDepositSchemeProductAccountingStepComponent,
|
|
||||||
DailyDepositSchemeProductPreviewStepComponent,
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class EditDailyDepositSchemeProductComponent {
|
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private productsService = inject(ProductsService);
|
|
||||||
private router = inject(Router);
|
|
||||||
private settingsService = inject(SettingsService);
|
|
||||||
private accounting = inject(Accounting);
|
|
||||||
private dateUtils = inject(Dates);
|
|
||||||
|
|
||||||
@ViewChild(DailyDepositSchemeProductDetailsStepComponent, { static: true })
|
|
||||||
DailyDepositSchemeProductDetailsStep: DailyDepositSchemeProductDetailsStepComponent;
|
|
||||||
@ViewChild(DailyDepositSchemeProductCurrencyStepComponent, { static: true })
|
|
||||||
DailyDepositSchemeProductCurrencyStep: DailyDepositSchemeProductCurrencyStepComponent;
|
|
||||||
@ViewChild(DailyDepositSchemeProductTermsStepComponent, { static: true })
|
|
||||||
DailyDepositSchemeProductTermsStep: DailyDepositSchemeProductTermsStepComponent;
|
|
||||||
@ViewChild(DailyDepositSchemeProductSettingsStepComponent, { static: true })
|
|
||||||
DailyDepositSchemeProductSettingsStep: DailyDepositSchemeProductSettingsStepComponent;
|
|
||||||
@ViewChild(DailyDepositSchemeProductChargesStepComponent, { static: true })
|
|
||||||
DailyDepositSchemeProductChargesStep: DailyDepositSchemeProductChargesStepComponent;
|
|
||||||
@ViewChild(DailyDepositSchemeProductAccountingStepComponent, { static: true })
|
|
||||||
DailyDepositSchemeProductAccountingStep: DailyDepositSchemeProductAccountingStepComponent;
|
|
||||||
|
|
||||||
DailyDepositSchemeProductsTemplate: any;
|
|
||||||
accountingRuleData: string[] = [];
|
|
||||||
rdInterestRates: any[] = [];
|
|
||||||
readonly rdSlabColumns = [
|
|
||||||
'srNo',
|
|
||||||
'description',
|
|
||||||
'fromPeriod',
|
|
||||||
'toPeriod',
|
|
||||||
'periodTypeLabel',
|
|
||||||
'annualInterestRate'
|
|
||||||
];
|
|
||||||
selectedTabIndex = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {ActivatedRoute} route Activated Route.
|
|
||||||
* @param {ProductsService} productsService Products Service.
|
|
||||||
* @param {Router} router Router for navigation.
|
|
||||||
* @param {SettingsService} settingsService Settings Service.
|
|
||||||
*/
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.route.data.subscribe((data: { DailyDepositSchemeProductAndTemplate: any }) => {
|
|
||||||
this.DailyDepositSchemeProductsTemplate = data.DailyDepositSchemeProductAndTemplate;
|
|
||||||
this.productsService.getFdBaseInterestRates().subscribe({
|
|
||||||
next: (rates: any[]) => {
|
|
||||||
this.rdInterestRates = rates || [];
|
|
||||||
},
|
|
||||||
error: () => {
|
|
||||||
this.rdInterestRates = [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.accountingRuleData = this.accounting.getAccountingRulesForSavings();
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductDetailsForm() {
|
|
||||||
return this.DailyDepositSchemeProductDetailsStep?.DailyDepositSchemeProductDetailsForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductCurrencyForm() {
|
|
||||||
return this.DailyDepositSchemeProductCurrencyStep?.DailyDepositSchemeProductCurrencyForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductTermsForm() {
|
|
||||||
return this.DailyDepositSchemeProductTermsStep?.DailyDepositSchemeProductTermsForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductSettingsForm() {
|
|
||||||
return this.DailyDepositSchemeProductSettingsStep?.DailyDepositSchemeProductSettingsForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductAccountingForm() {
|
|
||||||
return this.DailyDepositSchemeProductAccountingStep?.DailyDepositSchemeProductAccountingForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
get totalTabs(): number {
|
|
||||||
return this.DailyDepositSchemeProductFormValid ? 8 : 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductFormValid() {
|
|
||||||
return !!this.DailyDepositSchemeProductDetailsForm?.valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
get activeBaseVersion() {
|
|
||||||
const chart =
|
|
||||||
this.rdInterestRates.find((version: any) => version.fdType === 'DOUBLE' && version.isActive) ??
|
|
||||||
this.rdInterestRates.find((version: any) => version.fdType === 'DOUBLE') ??
|
|
||||||
this.rdInterestRates[0];
|
|
||||||
if (chart) {
|
|
||||||
return {
|
|
||||||
...chart,
|
|
||||||
versionLabel: chart.versionLabel?.replace('FD', 'RD'),
|
|
||||||
remarks: chart.remarks?.replace('FD', 'RD')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get activeBaseSlabs() {
|
|
||||||
return this.activeBaseVersion?.slabs || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProduct() {
|
|
||||||
// accountTypeGroup / autoInterestPost / autoInterestPostWhen are the legacy Account Type
|
|
||||||
// Creation screen's own fields (added for the simplified Create form) - not real product API
|
|
||||||
// parameters, so they must not leak into the update payload the way a plain spread would.
|
|
||||||
// accountTypeGroup is deliberately NOT re-folded into description here (unlike Create): it
|
|
||||||
// was already folded in once at creation time, and this getter re-runs on every change
|
|
||||||
// detection cycle while editing, so re-prepending it here would keep stacking duplicate
|
|
||||||
// "Group - " prefixes onto the same description. autoInterestPost/When aren't re-synced onto
|
|
||||||
// the Terms tab either, since Edit's own Terms/Settings/Accounting tabs remain fully visible
|
|
||||||
// and directly editable.
|
|
||||||
const { accountTypeGroup, autoInterestPost, autoInterestPostWhen, autoInterestPostDay, ...detailsPayload } =
|
|
||||||
this.DailyDepositSchemeProductDetailsStep.DailyDepositSchemeProductDetails;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...detailsPayload,
|
|
||||||
...this.DailyDepositSchemeProductCurrencyStep.DailyDepositSchemeProductCurrency,
|
|
||||||
...this.DailyDepositSchemeProductTermsStep.DailyDepositSchemeProductTerms,
|
|
||||||
...this.DailyDepositSchemeProductSettingsStep.DailyDepositSchemeProductSettings,
|
|
||||||
...this.DailyDepositSchemeProductChargesStep.DailyDepositSchemeProductCharges,
|
|
||||||
...this.DailyDepositSchemeProductAccountingStep.DailyDepositSchemeProductAccounting,
|
|
||||||
// charts intentionally omitted here - the backend no longer requires a charts payload for RD
|
|
||||||
// updates once rdSlabs is present, and this getter's chart data comes from the shared CBS
|
|
||||||
// base-rate template (not this product's own chart), which caused spurious
|
|
||||||
// "chart.overlapping.from.and.end.dates" errors when a product's real chart had different
|
|
||||||
// dates. Still included in DailyDepositSchemeProductPreview below for the read-only display tab.
|
|
||||||
rdSlabs: this.activeBaseSlabs
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductPreview() {
|
|
||||||
return {
|
|
||||||
...this.DailyDepositSchemeProduct,
|
|
||||||
charts: this.DailyDepositSchemeProductCharts,
|
|
||||||
rdSlabs: this.activeBaseSlabs
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get DailyDepositSchemeProductCharts() {
|
|
||||||
const activeBaseVersion = this.activeBaseVersion;
|
|
||||||
if (!activeBaseVersion?.slabs?.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: activeBaseVersion.versionLabel || 'CBS Base Chart',
|
|
||||||
description: activeBaseVersion.remarks || activeBaseVersion.versionLabel || 'CBS base chart',
|
|
||||||
fromDate:
|
|
||||||
this.dateUtils.formatDate(activeBaseVersion.effectiveFromDate, dateFormat) ||
|
|
||||||
activeBaseVersion.effectiveFromDate,
|
|
||||||
...(activeBaseVersion.effectiveToDate
|
|
||||||
? {
|
|
||||||
endDate:
|
|
||||||
this.dateUtils.formatDate(activeBaseVersion.effectiveToDate, dateFormat) ||
|
|
||||||
activeBaseVersion.effectiveToDate
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
isPrimaryGroupingByAmount: false,
|
|
||||||
locale: this.settingsService.language.code,
|
|
||||||
dateFormat,
|
|
||||||
chartSlabs: activeBaseVersion.slabs.map((slab: any) => {
|
|
||||||
const chartSlab: any = {
|
|
||||||
periodType: slab.periodTypeEnum,
|
|
||||||
fromPeriod: slab.fromPeriod,
|
|
||||||
toPeriod: slab.toPeriod,
|
|
||||||
annualInterestRate: slab.annualInterestRate,
|
|
||||||
description: slab.description || '',
|
|
||||||
incentives: [] as any[]
|
|
||||||
};
|
|
||||||
|
|
||||||
if (slab.amountRangeFrom !== null && slab.amountRangeFrom !== undefined && slab.amountRangeFrom !== '') {
|
|
||||||
chartSlab.amountRangeFrom = slab.amountRangeFrom;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slab.amountRangeTo !== null && slab.amountRangeTo !== undefined && slab.amountRangeTo !== '') {
|
|
||||||
chartSlab.amountRangeTo = slab.amountRangeTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
return chartSlab;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
] as any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
submit() {
|
|
||||||
const DailyDepositSchemeProduct: any = this.pruneEmptyValues({
|
|
||||||
...this.DailyDepositSchemeProduct,
|
|
||||||
charges: (this.DailyDepositSchemeProduct.charges ?? []).map((charge: any) => ({ id: charge.id })),
|
|
||||||
locale: this.settingsService.language.code,
|
|
||||||
dateFormat: this.settingsService.dateFormat
|
|
||||||
});
|
|
||||||
if (!DailyDepositSchemeProduct.description) {
|
|
||||||
DailyDepositSchemeProduct.description = '';
|
|
||||||
}
|
|
||||||
delete DailyDepositSchemeProduct.advancedAccountingRules;
|
|
||||||
const productId = this.DailyDepositSchemeProductsTemplate.id;
|
|
||||||
this.productsService
|
|
||||||
.updateRecurringDepositProduct(productId, DailyDepositSchemeProduct)
|
|
||||||
.subscribe((response: any) => {
|
|
||||||
this.router.navigate(['../'], { relativeTo: this.route });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private pruneEmptyValues<T>(value: T): T {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.map((item) => this.pruneEmptyValues(item)).filter((item) => item !== undefined) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value && typeof value === 'object') {
|
|
||||||
return Object.entries(value as Record<string, any>).reduce(
|
|
||||||
(acc, [
|
|
||||||
key,
|
|
||||||
currentValue
|
|
||||||
]) => {
|
|
||||||
const cleanedValue = this.pruneEmptyValues(currentValue);
|
|
||||||
if (cleanedValue === undefined) {
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
[key]: cleanedValue
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{} as Record<string, any>
|
|
||||||
) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === '' || value === null || value === undefined) {
|
|
||||||
return undefined as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-17
@@ -1,17 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="tab-container mat-typography">
|
|
||||||
<mifosx-entity-datatable-tab
|
|
||||||
entityType="Daily Deposit Scheme Product"
|
|
||||||
[entityId]="entityId"
|
|
||||||
[multiRowDatatableFlag]="multiRowDatatableFlag"
|
|
||||||
[entityDatatable]="entityDatatable"
|
|
||||||
>
|
|
||||||
</mifosx-entity-datatable-tab>
|
|
||||||
</div>
|
|
||||||
-38
@@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, inject } from '@angular/core';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { EntityDatatableTabComponent } from '../../../../shared/tabs/entity-datatable-tab/entity-datatable-tab.component';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-daily-deposit-scheme-datatable-tab',
|
|
||||||
templateUrl: './daily-deposit-scheme-datatable-tab.component.html',
|
|
||||||
styleUrls: ['./daily-deposit-scheme-datatable-tab.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
EntityDatatableTabComponent
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class DailyDepositSchemeDatatableTabComponent {
|
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
|
|
||||||
entityId: string;
|
|
||||||
entityDatatable: any;
|
|
||||||
multiRowDatatableFlag: boolean;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.entityId = this.route.parent.parent.snapshot.paramMap.get('productId');
|
|
||||||
|
|
||||||
this.route.data.subscribe((data: { recurringDepositDatatable: any }) => {
|
|
||||||
this.entityDatatable = data.recurringDepositDatatable;
|
|
||||||
this.multiRowDatatableFlag = this.entityDatatable.columnHeaders[0].columnName === 'id' ? true : false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-843
@@ -1,843 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="vsp-root">
|
|
||||||
<!-- HERO -->
|
|
||||||
<div class="vsp-hero">
|
|
||||||
<div class="vsp-hero-left">
|
|
||||||
<div class="vsp-hero-icon"><fa-icon icon="calendar-check"></fa-icon></div>
|
|
||||||
<div class="vsp-hero-text">
|
|
||||||
<div class="vsp-hero-eyebrow">Daily Deposit Scheme Product</div>
|
|
||||||
<h2 class="vsp-hero-title">{{ DailyDepositSchemeProduct.name }}</h2>
|
|
||||||
<div class="vsp-hero-meta">
|
|
||||||
<span class="vsp-pill vsp-pill--code">{{ DailyDepositSchemeProduct.shortName }}</span>
|
|
||||||
@if (DailyDepositSchemeProduct.currency?.code) {
|
|
||||||
<span class="vsp-pill vsp-pill--teal">{{ DailyDepositSchemeProduct.currency.code }}</span>
|
|
||||||
}
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="!DailyDepositSchemeProduct.endDate"
|
|
||||||
[class.vsp-pill--amber]="DailyDepositSchemeProduct.endDate"
|
|
||||||
>
|
|
||||||
<fa-icon [icon]="DailyDepositSchemeProduct.endDate ? 'times-circle' : 'check-circle'"></fa-icon>
|
|
||||||
{{ DailyDepositSchemeProduct.endDate ? 'Inactive' : 'Active' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
@if (DailyDepositSchemeProduct.description) {
|
|
||||||
<p class="vsp-hero-desc">{{ DailyDepositSchemeProduct.description }}</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="vsp-edit-btn"
|
|
||||||
type="button"
|
|
||||||
[routerLink]="['../edit']"
|
|
||||||
*mifosxHasPermission="'UPDATE_DailyDepositSchemeProduct'"
|
|
||||||
>
|
|
||||||
<fa-icon icon="edit"></fa-icon>
|
|
||||||
<span>{{ 'labels.buttons.Edit' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- DETAILS -->
|
|
||||||
<section class="vsp-section">
|
|
||||||
<div class="vsp-section-h">
|
|
||||||
<div class="vsp-section-ic vsp-section-ic--blue"><fa-icon icon="info-circle"></fa-icon></div>
|
|
||||||
<h3>{{ 'labels.inputs.Details' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-grid">
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Short Name' | translate }}</span>
|
|
||||||
<span class="vsp-v"
|
|
||||||
><strong>{{ DailyDepositSchemeProduct.shortName }}</strong></span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
@if (DailyDepositSchemeProduct.description) {
|
|
||||||
<div class="vsp-kv vsp-kv--span">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Description' | translate }}</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.description }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- PRODUCT CLASSIFICATION -->
|
|
||||||
<section class="vsp-section">
|
|
||||||
<div class="vsp-section-h">
|
|
||||||
<div class="vsp-section-ic vsp-section-ic--purple"><fa-icon icon="layer-group"></fa-icon></div>
|
|
||||||
<h3>Product Classification</h3>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-grid">
|
|
||||||
@if (DailyDepositSchemeProduct.accountType) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Account Type</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.accountType }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.customerSegment) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Customer Segment</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.customerSegment }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.glAccountId) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">GL Account (Liability)</span>
|
|
||||||
<span class="vsp-v">{{
|
|
||||||
DailyDepositSchemeProduct.glAccountName || DailyDepositSchemeProduct.glAccountId
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.effectiveTo) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Effective To</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.effectiveTo | dateFormat }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.endDate) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">End Date</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.endDate | dateFormat }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Introducer Required</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.introducerRequired"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.introducerRequired"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.introducerRequired | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Deposit Account Link</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.shareLinkingEnabled"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.shareLinkingEnabled"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.shareLinkingEnabled | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- CURRENCY -->
|
|
||||||
<section class="vsp-section">
|
|
||||||
<div class="vsp-section-h">
|
|
||||||
<div class="vsp-section-ic vsp-section-ic--teal"><fa-icon icon="coins"></fa-icon></div>
|
|
||||||
<h3>{{ 'labels.inputs.Currency' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-grid">
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Currency' | translate }}</span>
|
|
||||||
<span class="vsp-v"
|
|
||||||
><strong
|
|
||||||
>({{ DailyDepositSchemeProduct.currency.code }}) {{ DailyDepositSchemeProduct.currency.name }}</strong
|
|
||||||
></span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Decimal Places' | translate }}</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.currency.decimalPlaces }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Dormancy Alert</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.dormancyAlert | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Balance Swap Option</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.balanceSwapOption | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">MSME</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.msme | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Nomination Facility</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.nominationFacility | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Auto Debit Facility</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.autoDebitFacility | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- TERMS -->
|
|
||||||
<section class="vsp-section">
|
|
||||||
<div class="vsp-section-h">
|
|
||||||
<div class="vsp-section-ic vsp-section-ic--amber"><fa-icon icon="percentage"></fa-icon></div>
|
|
||||||
<h3>{{ 'labels.inputs.Terms' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-grid">
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Deposit Amount' | translate }}</span>
|
|
||||||
<span class="vsp-v vsp-v--accent">
|
|
||||||
{{ DailyDepositSchemeProduct.depositAmount | formatNumber }}
|
|
||||||
<span class="vsp-suffix"
|
|
||||||
>(Min: {{ DailyDepositSchemeProduct.minDepositAmount || DailyDepositSchemeProduct.depositAmount }}, Max:
|
|
||||||
{{ DailyDepositSchemeProduct.maxDepositAmount || DailyDepositSchemeProduct.depositAmount }})</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Nominal Annual Interest' | translate }}</span>
|
|
||||||
<span class="vsp-v vsp-v--accent"
|
|
||||||
>{{ DailyDepositSchemeProduct.nominalAnnualInterestRate | formatNumber
|
|
||||||
}}<span class="vsp-suffix">%</span></span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
@if (DailyDepositSchemeProduct.effectiveDate) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Effective Date</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.effectiveDate | dateFormat }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.interestCalculationTypeName) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Interest Calculation Type' | translate }}</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.interestCalculationTypeName }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Interest Calculate</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.interestCalculationType?.value }}</span>
|
|
||||||
</div>
|
|
||||||
@if (DailyDepositSchemeProduct.averagePeriod) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Average Period</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.averagePeriod }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Interest Posting Period' | translate }}</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.interestPostingPeriodType.value }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Interest Compounding Period' | translate }}</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.interestCompoundingPeriodType.value }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Days in Year' | translate }}</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.interestCalculationDaysInYearType.value }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Auto Post</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.autoPost"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.autoPost"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.autoPost | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
@if (DailyDepositSchemeProduct.frequencyFixDay) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Frequency (Fix Day)</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.frequencyFixDay }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.lastInterestDate) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Last Interest Date</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.lastInterestDate | dateFormat }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- SETTINGS -->
|
|
||||||
<section class="vsp-section">
|
|
||||||
<div class="vsp-section-h">
|
|
||||||
<div class="vsp-section-ic vsp-section-ic--indigo"><fa-icon icon="sliders-h"></fa-icon></div>
|
|
||||||
<h3>{{ 'labels.inputs.Settings' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4 class="vsp-sub-h">Premature Option</h4>
|
|
||||||
<div class="vsp-grid">
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Loan Facility</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.loanFacility"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.loanFacility"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.loanFacility | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Premature Withdrawal Option</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.prematureWithdrawalOption"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.prematureWithdrawalOption"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.prematureWithdrawalOption | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Tenure Flexibility</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.tenureFlexibility"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.tenureFlexibility"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.tenureFlexibility | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Lien Marked</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.lienMarked"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.lienMarked"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.lienMarked | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }}</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.withdrawalFeeForTransfers"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.withdrawalFeeForTransfers"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.withdrawalFeeForTransfers | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4 class="vsp-sub-h">{{ 'labels.inputs.Lock-in Period' | translate }}</h4>
|
|
||||||
<div class="vsp-grid">
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Enable Lock-in Period</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.enableLockinPeriod"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.enableLockinPeriod"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.enableLockinPeriod | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
@if (DailyDepositSchemeProduct.lockinPeriodFrequency) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Lock-in Period' | translate }}</span>
|
|
||||||
<span class="vsp-v"
|
|
||||||
>{{ DailyDepositSchemeProduct.lockinPeriodFrequency }}
|
|
||||||
{{ DailyDepositSchemeProduct.lockinPeriodFrequencyType?.value }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4 class="vsp-sub-h">Maturity Instructions</h4>
|
|
||||||
<div class="vsp-grid">
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Maturity Instructions</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.maturityInstructions"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.maturityInstructions"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.maturityInstructions | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Credit Maturity Proceeds</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.creditMaturityProceeds"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.creditMaturityProceeds"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.creditMaturityProceeds | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Penalty Grace Period (Missed Installments)</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.penaltyGracePeriodMissedInstallments"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.penaltyGracePeriodMissedInstallments"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.penaltyGracePeriodMissedInstallments | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Panel Request Applicable</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.panelRequestApplicable"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.panelRequestApplicable"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.panelRequestApplicable | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">No Penalty (Nominee / Heir Closure)</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.noPenaltyNomineeHeirClosure"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.noPenaltyNomineeHeirClosure"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.noPenaltyNomineeHeirClosure | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Deceased Settlement</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.deceasedSettlement"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.deceasedSettlement"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.deceasedSettlement | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Allow Debit Txn After Due Date</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.allowDebitTxnAfterDueDate"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.allowDebitTxnAfterDueDate"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.allowDebitTxnAfterDueDate | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4 class="vsp-sub-h">Additional Rules</h4>
|
|
||||||
<div class="vsp-grid">
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">Account Closed Zero Balance in Day Closing</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.accountClosedZeroBalanceInDayClosing"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.accountClosedZeroBalanceInDayClosing"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.accountClosedZeroBalanceInDayClosing | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
@if (DailyDepositSchemeProduct.minBalanceForInterestCalculation) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.minBalanceForInterestCalculation | formatNumber }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.minDepositTerm) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Minimum Deposit Term' | translate }}</span>
|
|
||||||
<span class="vsp-v"
|
|
||||||
>{{ DailyDepositSchemeProduct.minDepositTerm }}
|
|
||||||
{{ DailyDepositSchemeProduct.minDepositTermType?.value }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.maxDepositTerm) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Maximum Deposit Term' | translate }}</span>
|
|
||||||
<span class="vsp-v"
|
|
||||||
>{{ DailyDepositSchemeProduct.maxDepositTerm }}
|
|
||||||
{{ DailyDepositSchemeProduct.maxDepositTermType?.value }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.inMultipleOfDepositTerm) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Deposit Term In multiple of' | translate }}</span>
|
|
||||||
<span class="vsp-v"
|
|
||||||
>{{ DailyDepositSchemeProduct.inMultipleOfDepositTerm }} -
|
|
||||||
{{ DailyDepositSchemeProduct.inMultipleOfDepositTermType?.value }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.isMandatoryDeposit !== undefined) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Daily Deposit Scheme Type' | translate }}</span>
|
|
||||||
<span class="vsp-v">{{
|
|
||||||
DailyDepositSchemeProduct.isMandatoryDeposit
|
|
||||||
? ('labels.text.Mandatory' | translate)
|
|
||||||
: ('labels.text.Voluntary' | translate)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.preClosurePenalInterest) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Apply penal interest (less)' | translate }}</span>
|
|
||||||
<span class="vsp-v"
|
|
||||||
>{{ DailyDepositSchemeProduct.preClosurePenalInterest }}% On
|
|
||||||
{{ DailyDepositSchemeProduct.preClosurePenalInterestOnType?.value }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Withhold Tax is Applicable' | translate }}</span>
|
|
||||||
<span class="vsp-v">
|
|
||||||
<span
|
|
||||||
class="vsp-pill"
|
|
||||||
[class.vsp-pill--green]="DailyDepositSchemeProduct.withHoldTax"
|
|
||||||
[class.vsp-pill--gray]="!DailyDepositSchemeProduct.withHoldTax"
|
|
||||||
>
|
|
||||||
{{ DailyDepositSchemeProduct.withHoldTax | yesNo }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
@if (DailyDepositSchemeProduct.withHoldTax) {
|
|
||||||
<div class="vsp-kv">
|
|
||||||
<span class="vsp-k">{{ 'labels.inputs.Tax Group' | translate }}</span>
|
|
||||||
<span class="vsp-v">{{ DailyDepositSchemeProduct.taxGroup?.name }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- INTEREST RATE CHART -->
|
|
||||||
@if (DailyDepositSchemeProduct.activeChart) {
|
|
||||||
<section class="vsp-section">
|
|
||||||
<div class="vsp-section-h">
|
|
||||||
<div class="vsp-section-ic vsp-section-ic--amber"><fa-icon icon="chart-line"></fa-icon></div>
|
|
||||||
<h3>{{ 'labels.heading.Interest Rate Charts' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Chart meta pills -->
|
|
||||||
<div class="vsp-chart-meta">
|
|
||||||
@if (DailyDepositSchemeProduct.activeChart.name) {
|
|
||||||
<span class="vsp-chart-pill"
|
|
||||||
><fa-icon icon="tag"></fa-icon> {{ DailyDepositSchemeProduct.activeChart.name }}</span
|
|
||||||
>
|
|
||||||
}
|
|
||||||
<span class="vsp-chart-pill"
|
|
||||||
><fa-icon icon="calendar"></fa-icon> {{ DailyDepositSchemeProduct.activeChart.fromDate | dateFormat }}</span
|
|
||||||
>
|
|
||||||
@if (DailyDepositSchemeProduct.activeChart.endDate) {
|
|
||||||
<span class="vsp-chart-pill vsp-chart-pill--red"
|
|
||||||
><fa-icon icon="calendar-times"></fa-icon>
|
|
||||||
{{ DailyDepositSchemeProduct.activeChart.endDate | dateFormat }}</span
|
|
||||||
>
|
|
||||||
}
|
|
||||||
<span
|
|
||||||
class="vsp-chart-pill"
|
|
||||||
[class.vsp-chart-pill--green]="DailyDepositSchemeProduct.activeChart.isPrimaryGroupingByAmount"
|
|
||||||
>
|
|
||||||
Primary by Amount: {{ DailyDepositSchemeProduct.activeChart.isPrimaryGroupingByAmount | yesNo }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="vsp-table-wrap">
|
|
||||||
<table
|
|
||||||
class="vsp-table"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="DailyDepositSchemeProduct.activeChart.chartSlabs"
|
|
||||||
multiTemplateDataRows
|
|
||||||
>
|
|
||||||
<ng-container matColumnDef="srNo">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Sr.</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab; let i = dataIndex">{{ i + 1 }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="description">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Particular</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.description }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="fromPeriod">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.fromPeriod }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="toPeriod">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.toPeriod ?? '—' }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="periodTypeLabel">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Period</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.periodType?.value }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="annualInterestRate">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Rate (%)</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">
|
|
||||||
<strong>{{ chartSlab.annualInterestRate }}</strong>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="actions">
|
|
||||||
<th mat-header-cell *matHeaderCellDef></th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab; let chartSlabIndex = dataIndex">
|
|
||||||
<button
|
|
||||||
class="vsp-incentive-btn"
|
|
||||||
type="button"
|
|
||||||
(click)="expandChartSlabIndex = expandChartSlabIndex === chartSlabIndex ? null : chartSlabIndex"
|
|
||||||
>
|
|
||||||
<fa-icon [icon]="expandChartSlabIndex === chartSlabIndex ? 'eye-slash' : 'eye'"></fa-icon>
|
|
||||||
{{
|
|
||||||
expandChartSlabIndex === chartSlabIndex
|
|
||||||
? ('labels.buttons.Hide Incentives' | translate)
|
|
||||||
: ('labels.buttons.View Incentives' | translate)
|
|
||||||
}}
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="incentives">
|
|
||||||
<td
|
|
||||||
mat-cell
|
|
||||||
*matCellDef="let chartSlab; let chartSlabIndex = dataIndex"
|
|
||||||
[attr.colspan]="chartSlabsDisplayedColumns.length"
|
|
||||||
class="vsp-incentive-cell"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
|
||||||
class="vsp-incentive-panel"
|
|
||||||
>
|
|
||||||
<p class="vsp-incentive-title">{{ 'labels.heading.Incentives' | translate }}</p>
|
|
||||||
<div class="vsp-table-wrap">
|
|
||||||
<table class="vsp-table" mat-table [dataSource]="chartSlab.incentives">
|
|
||||||
<ng-container matColumnDef="entityType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{
|
|
||||||
incentive.entityType
|
|
||||||
| find: DailyDepositSchemeProductTemplate.chartTemplate.entityTypeOptions : 'id' : 'value'
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="attributeName">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{
|
|
||||||
incentive.attributeName
|
|
||||||
| find
|
|
||||||
: DailyDepositSchemeProductTemplate.chartTemplate.attributeNameOptions
|
|
||||||
: 'id'
|
|
||||||
: 'value'
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="conditionType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{
|
|
||||||
incentive.conditionType
|
|
||||||
| find
|
|
||||||
: DailyDepositSchemeProductTemplate.chartTemplate.conditionTypeOptions
|
|
||||||
: 'id'
|
|
||||||
: 'value'
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="attributeValue">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
@switch (incentive.attributeName) {
|
|
||||||
@case (2) {
|
|
||||||
<span>{{
|
|
||||||
incentive.attributeValue
|
|
||||||
| find: DailyDepositSchemeProductTemplate.chartTemplate.genderOptions : 'id' : 'name'
|
|
||||||
}}</span>
|
|
||||||
}
|
|
||||||
@case (3) {
|
|
||||||
<span>{{ incentive.attributeValue }}</span>
|
|
||||||
}
|
|
||||||
@case (4) {
|
|
||||||
<span>{{
|
|
||||||
incentive.attributeValue
|
|
||||||
| find
|
|
||||||
: DailyDepositSchemeProductTemplate.chartTemplate.clientTypeOptions
|
|
||||||
: 'id'
|
|
||||||
: 'name'
|
|
||||||
}}</span>
|
|
||||||
}
|
|
||||||
@case (5) {
|
|
||||||
<span>{{
|
|
||||||
incentive.attributeValue
|
|
||||||
| find
|
|
||||||
: DailyDepositSchemeProductTemplate.chartTemplate.clientClassificationOptions
|
|
||||||
: 'id'
|
|
||||||
: 'name'
|
|
||||||
}}</span>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="incentiveType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{
|
|
||||||
incentive.incentiveType
|
|
||||||
| find
|
|
||||||
: DailyDepositSchemeProductTemplate.chartTemplate.incentiveTypeOptions
|
|
||||||
: 'id'
|
|
||||||
: 'value'
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="amount">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">{{ incentive.amount }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
|
|
||||||
<tr
|
|
||||||
mat-row
|
|
||||||
*matRowDef="let row; columns: chartSlabsDisplayedColumns; let i = dataIndex"
|
|
||||||
[class.vsp-row-even]="i % 2 === 1"
|
|
||||||
></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns" class="incentives-row"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- CHARGES -->
|
|
||||||
@if (DailyDepositSchemeProduct.charges.length) {
|
|
||||||
<section class="vsp-section">
|
|
||||||
<div class="vsp-section-h">
|
|
||||||
<div class="vsp-section-ic vsp-section-ic--red"><fa-icon icon="receipt"></fa-icon></div>
|
|
||||||
<h3>{{ 'labels.heading.Charges' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="vsp-table-wrap">
|
|
||||||
<table class="vsp-table" mat-table [dataSource]="DailyDepositSchemeProduct.charges">
|
|
||||||
<ng-container matColumnDef="name">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">{{ charge.name }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="type">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">{{ charge.chargeCalculationType.value }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="amount">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">{{ charge.amount | formatNumber }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="collectedon">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">{{ charge.chargeTimeType.value }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="chargesDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: chargesDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ACCOUNTING -->
|
|
||||||
<section class="vsp-section">
|
|
||||||
<div class="vsp-section-h">
|
|
||||||
<div class="vsp-section-ic vsp-section-ic--green"><fa-icon icon="book"></fa-icon></div>
|
|
||||||
<h3>{{ 'labels.heading.Accounting' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<mifosx-view-savings-accounting-details
|
|
||||||
[accountingRule]="DailyDepositSchemeProduct.accountingRule"
|
|
||||||
[accountingMappings]="DailyDepositSchemeProduct.accountingMappings"
|
|
||||||
></mifosx-view-savings-accounting-details>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- ADVANCED ACCOUNTING RULES -->
|
|
||||||
@if (
|
|
||||||
DailyDepositSchemeProduct.paymentChannelToFundSourceMappings?.length ||
|
|
||||||
DailyDepositSchemeProduct.feeToIncomeAccountMappings?.length ||
|
|
||||||
DailyDepositSchemeProduct.penaltyToIncomeAccountMappings?.length
|
|
||||||
) {
|
|
||||||
<section class="vsp-section">
|
|
||||||
<div class="vsp-section-h">
|
|
||||||
<div class="vsp-section-ic vsp-section-ic--purple"><fa-icon icon="sitemap"></fa-icon></div>
|
|
||||||
<h3>{{ 'labels.inputs.Advanced Accounting Rules' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
@if (DailyDepositSchemeProduct.paymentChannelToFundSourceMappings?.length) {
|
|
||||||
<h4 class="vsp-sub-h">{{ 'labels.inputs.Fund Sources for Payment Channels' | translate }}</h4>
|
|
||||||
<div class="vsp-table-wrap">
|
|
||||||
<table
|
|
||||||
class="vsp-table"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="DailyDepositSchemeProduct.paymentChannelToFundSourceMappings"
|
|
||||||
>
|
|
||||||
<ng-container matColumnDef="paymentTypeId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Payment Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let p">{{ p.paymentType.name }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="fundSourceAccountId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fund Source' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let p">{{ p.fundSourceAccount.name }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="paymentFundSourceDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: paymentFundSourceDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.feeToIncomeAccountMappings?.length) {
|
|
||||||
<h4 class="vsp-sub-h">{{ 'labels.inputs.Fees to Specific Income Accounts' | translate }}</h4>
|
|
||||||
<div class="vsp-table-wrap">
|
|
||||||
<table class="vsp-table" mat-table [dataSource]="DailyDepositSchemeProduct.feeToIncomeAccountMappings">
|
|
||||||
<ng-container matColumnDef="chargeId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fee/Charge' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let f">{{ f.charge.name }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="incomeAccountId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let f">{{ f.incomeAccount.name }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (DailyDepositSchemeProduct.penaltyToIncomeAccountMappings?.length) {
|
|
||||||
<h4 class="vsp-sub-h">{{ 'labels.inputs.Penalties to Specific Income Accounts' | translate }}</h4>
|
|
||||||
<div class="vsp-table-wrap">
|
|
||||||
<table class="vsp-table" mat-table [dataSource]="DailyDepositSchemeProduct.penaltyToIncomeAccountMappings">
|
|
||||||
<ng-container matColumnDef="chargeId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Penalty' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let p">{{ p.charge.name }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="incomeAccountId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let p">{{ p.incomeAccount.name }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</section>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
-528
@@ -1,528 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:host {
|
|
||||||
--vsp-navy: #0b1e3d;
|
|
||||||
--vsp-blue: #1d4ed8;
|
|
||||||
--vsp-blue-l: #eff6ff;
|
|
||||||
--vsp-teal: #0e7e6b;
|
|
||||||
--vsp-teal-l: #ccfbf1;
|
|
||||||
--vsp-purple: #6d28d9;
|
|
||||||
--vsp-purple-l: #ede9fe;
|
|
||||||
--vsp-indigo: #3730a3;
|
|
||||||
--vsp-indigo-l: #e0e7ff;
|
|
||||||
--vsp-amber: #b45309;
|
|
||||||
--vsp-amber-l: #fef3c7;
|
|
||||||
--vsp-green: #15803d;
|
|
||||||
--vsp-green-l: #dcfce7;
|
|
||||||
--vsp-red: #c0392b;
|
|
||||||
--vsp-red-l: #fee2e2;
|
|
||||||
--vsp-pink: #be185d;
|
|
||||||
--vsp-pink-l: #fce7f3;
|
|
||||||
--vsp-g50: #f8fafc;
|
|
||||||
--vsp-g100: #f1f5f9;
|
|
||||||
--vsp-g200: #e2e8f0;
|
|
||||||
--vsp-g500: #64748b;
|
|
||||||
--vsp-g700: #334155;
|
|
||||||
--vsp-g900: #0f172a;
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
font-family:
|
|
||||||
'DM Sans',
|
|
||||||
system-ui,
|
|
||||||
-apple-system,
|
|
||||||
sans-serif;
|
|
||||||
color: var(--vsp-g900);
|
|
||||||
font-size: 13.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-root {
|
|
||||||
padding: 4px 4px 24px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-hero {
|
|
||||||
background: linear-gradient(135deg, var(--vsp-navy) 0%, var(--vsp-blue) 100%);
|
|
||||||
border-radius: 14px;
|
|
||||||
padding: 22px 22px 22px 18px;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 18px;
|
|
||||||
box-shadow:
|
|
||||||
0 10px 24px rgb(11 30 61 / 18%),
|
|
||||||
0 4px 10px rgb(11 30 61 / 12%);
|
|
||||||
color: #fff;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-hero::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -60px;
|
|
||||||
right: -60px;
|
|
||||||
width: 240px;
|
|
||||||
height: 240px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: radial-gradient(circle, rgb(255 255 255 / 8%) 0%, transparent 70%);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-hero-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 16px;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-hero-icon {
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
border-radius: 14px;
|
|
||||||
background: rgb(255 255 255 / 16%);
|
|
||||||
border: 1px solid rgb(255 255 255 / 22%);
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 24px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-hero-text {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-hero-eyebrow {
|
|
||||||
font-size: 10.5px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
color: rgb(255 255 255 / 70%);
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-hero-title {
|
|
||||||
margin: 0 0 10px;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.01em;
|
|
||||||
color: #fff;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-hero-meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-hero-desc {
|
|
||||||
margin: 12px 0 0;
|
|
||||||
font-size: 13px;
|
|
||||||
color: rgb(255 255 255 / 82%);
|
|
||||||
line-height: 1.5;
|
|
||||||
max-width: 720px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-edit-btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
height: 38px;
|
|
||||||
padding: 0 16px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: rgb(255 255 255 / 14%);
|
|
||||||
color: #fff;
|
|
||||||
border: 1px solid rgb(255 255 255 / 28%);
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-family: inherit;
|
|
||||||
flex-shrink: 0;
|
|
||||||
transition: all 0.16s ease;
|
|
||||||
|
|
||||||
fa-icon {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgb(255 255 255 / 24%);
|
|
||||||
border-color: rgb(255 255 255 / 50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-pill {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-family: inherit;
|
|
||||||
|
|
||||||
fa-icon {
|
|
||||||
font-size: 9.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--code {
|
|
||||||
background: rgb(255 255 255 / 18%);
|
|
||||||
color: #fff;
|
|
||||||
border: 1px solid rgb(255 255 255 / 24%);
|
|
||||||
font-family: 'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--teal {
|
|
||||||
background: var(--vsp-teal-l);
|
|
||||||
color: var(--vsp-teal);
|
|
||||||
border: 1px solid #99f6e4;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--blue {
|
|
||||||
background: var(--vsp-blue-l);
|
|
||||||
color: var(--vsp-blue);
|
|
||||||
border: 1px solid #dbeafe;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--green {
|
|
||||||
background: var(--vsp-green-l);
|
|
||||||
color: var(--vsp-green);
|
|
||||||
border: 1px solid #bbf7d0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--amber {
|
|
||||||
background: var(--vsp-amber-l);
|
|
||||||
color: var(--vsp-amber);
|
|
||||||
border: 1px solid #fde68a;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--red {
|
|
||||||
background: var(--vsp-red-l);
|
|
||||||
color: var(--vsp-red);
|
|
||||||
border: 1px solid #fecaca;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--gray {
|
|
||||||
background: var(--vsp-g100);
|
|
||||||
color: var(--vsp-g700);
|
|
||||||
border: 1px solid var(--vsp-g200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-section {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid var(--vsp-g200);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 16px 20px;
|
|
||||||
box-shadow: 0 1px 2px rgb(15 23 42 / 4%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-section-h {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
padding-bottom: 12px;
|
|
||||||
border-bottom: 1px dashed var(--vsp-g200);
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--vsp-g900);
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-section-ic {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 10px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 14px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&--blue {
|
|
||||||
background: var(--vsp-blue-l);
|
|
||||||
color: var(--vsp-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--teal {
|
|
||||||
background: var(--vsp-teal-l);
|
|
||||||
color: var(--vsp-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--purple {
|
|
||||||
background: var(--vsp-purple-l);
|
|
||||||
color: var(--vsp-purple);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--indigo {
|
|
||||||
background: var(--vsp-indigo-l);
|
|
||||||
color: var(--vsp-indigo);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--amber {
|
|
||||||
background: var(--vsp-amber-l);
|
|
||||||
color: var(--vsp-amber);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--green {
|
|
||||||
background: var(--vsp-green-l);
|
|
||||||
color: var(--vsp-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--red {
|
|
||||||
background: var(--vsp-red-l);
|
|
||||||
color: var(--vsp-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--pink {
|
|
||||||
background: var(--vsp-pink-l);
|
|
||||||
color: var(--vsp-pink);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-sub-h {
|
|
||||||
margin: 16px 0 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--vsp-g700);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 10px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <=600px) {
|
|
||||||
.vsp-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-kv {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 3px;
|
|
||||||
padding: 8px 0;
|
|
||||||
border-bottom: 1px dashed var(--vsp-g100);
|
|
||||||
|
|
||||||
&--span {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-k {
|
|
||||||
font-size: 10.5px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
color: var(--vsp-g500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-v {
|
|
||||||
font-size: 13.5px;
|
|
||||||
color: var(--vsp-g900);
|
|
||||||
font-weight: 500;
|
|
||||||
word-break: break-word;
|
|
||||||
|
|
||||||
strong {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-v--accent {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--vsp-blue);
|
|
||||||
letter-spacing: 0.01em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-suffix {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--vsp-g500);
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-table-wrap {
|
|
||||||
overflow-x: auto;
|
|
||||||
border: 1px solid var(--vsp-g200);
|
|
||||||
border-radius: 10px;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ───────── CHART META PILLS ───────── */
|
|
||||||
.vsp-chart-meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-chart-pill {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 5px 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--vsp-g100);
|
|
||||||
border: 1px solid var(--vsp-g200);
|
|
||||||
color: var(--vsp-g700);
|
|
||||||
|
|
||||||
fa-icon {
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--vsp-g500);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--green {
|
|
||||||
background: var(--vsp-green-l);
|
|
||||||
border-color: #bbf7d0;
|
|
||||||
color: var(--vsp-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--red {
|
|
||||||
background: var(--vsp-red-l);
|
|
||||||
border-color: #fecaca;
|
|
||||||
color: var(--vsp-red);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ───────── RATE BADGE ───────── */
|
|
||||||
.vsp-rate-badge {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--vsp-blue);
|
|
||||||
background: var(--vsp-blue-l);
|
|
||||||
border: 1px solid #dbeafe;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 3px 10px;
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ───────── CELL SUB TEXT ───────── */
|
|
||||||
.vsp-cell-sub {
|
|
||||||
display: block;
|
|
||||||
font-size: 10.5px;
|
|
||||||
color: var(--vsp-g500);
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ───────── INCENTIVE EXPAND ───────── */
|
|
||||||
.vsp-incentive-btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
font-size: 11.5px;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 5px 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid var(--vsp-g200);
|
|
||||||
background: var(--vsp-g50);
|
|
||||||
color: var(--vsp-blue);
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: inherit;
|
|
||||||
transition: all 0.14s ease;
|
|
||||||
|
|
||||||
fa-icon {
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--vsp-blue-l);
|
|
||||||
border-color: #dbeafe;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-incentive-cell {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-incentive-panel {
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 0 14px;
|
|
||||||
background: var(--vsp-g50);
|
|
||||||
border-top: 1px dashed var(--vsp-g200);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-incentive-title {
|
|
||||||
margin: 12px 0 8px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
color: var(--vsp-g500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsp-table {
|
|
||||||
width: 100%;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
th.mat-mdc-header-cell {
|
|
||||||
background: var(--vsp-g50);
|
|
||||||
color: var(--vsp-g700);
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
padding: 11px 14px;
|
|
||||||
border-bottom: 1px solid var(--vsp-g200);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.mat-mdc-cell {
|
|
||||||
padding: 10px 14px;
|
|
||||||
border-bottom: 1px solid var(--vsp-g100);
|
|
||||||
font-size: 12.5px;
|
|
||||||
color: var(--vsp-g900);
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr:last-child td.mat-mdc-cell {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr:not(.incentives-row, .vsp-row-even) td.mat-mdc-cell {
|
|
||||||
background: #fff !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr.vsp-row-even td.mat-mdc-cell {
|
|
||||||
background: var(--vsp-g50) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr.incentives-row {
|
|
||||||
height: 0;
|
|
||||||
|
|
||||||
> td {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.incentives {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-121
@@ -1,121 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, inject } from '@angular/core';
|
|
||||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
|
||||||
import { trigger, state, transition, animate, style } from '@angular/animations';
|
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
||||||
import { MatDivider } from '@angular/material/divider';
|
|
||||||
|
|
||||||
import {
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow
|
|
||||||
} from '@angular/material/table';
|
|
||||||
import { ViewSavingsAccountingDetailsComponent } from '../../../../shared/accounting/view-savings-accounting-details/view-savings-accounting-details.component';
|
|
||||||
import { FindPipe } from '../../../../pipes/find.pipe';
|
|
||||||
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
|
||||||
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
|
|
||||||
import { YesnoPipe } from '../../../../pipes/yesno.pipe';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-daily-deposit-scheme-general-tab',
|
|
||||||
templateUrl: './daily-deposit-scheme-general-tab.component.html',
|
|
||||||
styleUrls: ['./daily-deposit-scheme-general-tab.component.scss'],
|
|
||||||
animations: [
|
|
||||||
trigger('expandChartSlab', [
|
|
||||||
state('collapsed', style({ height: '0px', minHeight: '0' })),
|
|
||||||
state('expanded', style({ height: '*' })),
|
|
||||||
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
|
|
||||||
])
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
RouterLink,
|
|
||||||
FaIconComponent,
|
|
||||||
MatDivider,
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow,
|
|
||||||
ViewSavingsAccountingDetailsComponent,
|
|
||||||
FindPipe,
|
|
||||||
DateFormatPipe,
|
|
||||||
FormatNumberPipe,
|
|
||||||
YesnoPipe
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class DailyDepositSchemeGeneralTabComponent {
|
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
|
|
||||||
DailyDepositSchemeProduct: any;
|
|
||||||
DailyDepositSchemeProductTemplate: any;
|
|
||||||
expandChartSlabIndex: number | null = null;
|
|
||||||
|
|
||||||
chartSlabsIncentivesDisplayedColumns: string[] = ['incentives'];
|
|
||||||
readonly chartSlabsBaseColumns = [
|
|
||||||
'srNo',
|
|
||||||
'description',
|
|
||||||
'fromPeriod',
|
|
||||||
'toPeriod',
|
|
||||||
'periodTypeLabel',
|
|
||||||
'annualInterestRate'
|
|
||||||
];
|
|
||||||
|
|
||||||
get chartSlabsDisplayedColumns(): string[] {
|
|
||||||
const slabs = this.DailyDepositSchemeProduct?.activeChart?.chartSlabs || [];
|
|
||||||
const hasIncentives = slabs.some((s: any) => s.incentives?.length);
|
|
||||||
return hasIncentives ? [
|
|
||||||
...this.chartSlabsBaseColumns,
|
|
||||||
'actions'
|
|
||||||
] : this.chartSlabsBaseColumns;
|
|
||||||
}
|
|
||||||
incentivesDisplayedColumns: string[] = [
|
|
||||||
'entityType',
|
|
||||||
'attributeName',
|
|
||||||
'conditionType',
|
|
||||||
'attributeValue',
|
|
||||||
'incentiveType',
|
|
||||||
'amount'
|
|
||||||
];
|
|
||||||
chargesDisplayedColumns: string[] = [
|
|
||||||
'name',
|
|
||||||
'type',
|
|
||||||
'amount',
|
|
||||||
'collectedon'
|
|
||||||
];
|
|
||||||
paymentFundSourceDisplayedColumns: string[] = [
|
|
||||||
'paymentTypeId',
|
|
||||||
'fundSourceAccountId'
|
|
||||||
];
|
|
||||||
feesPenaltyIncomeDisplayedColumns: string[] = [
|
|
||||||
'chargeId',
|
|
||||||
'incomeAccountId'
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.route.data.subscribe((data: { DailyDepositSchemeProduct: any; DailyDepositSchemeProductsTemplate: any }) => {
|
|
||||||
this.DailyDepositSchemeProduct = data.DailyDepositSchemeProduct;
|
|
||||||
this.DailyDepositSchemeProductTemplate = data.DailyDepositSchemeProductsTemplate;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-68
@@ -1,68 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="tab-container mat-typography">
|
|
||||||
@if (!rdInterestRates || rdInterestRates.length === 0) {
|
|
||||||
<p class="no-rates">No CBS base interest rate versions defined.</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<mat-accordion multi>
|
|
||||||
@for (version of rdInterestRates; track version.id) {
|
|
||||||
<mat-expansion-panel [expanded]="version.isActive">
|
|
||||||
<mat-expansion-panel-header>
|
|
||||||
<mat-panel-title>
|
|
||||||
<span class="rd-version-header">
|
|
||||||
<strong>{{ version.versionLabel }}</strong>
|
|
||||||
<span [class]="version.isActive ? 'rd-active-badge' : 'rd-inactive-badge'">
|
|
||||||
{{ version.isActive ? 'Active' : 'Inactive' }}
|
|
||||||
</span>
|
|
||||||
<span style="color: #757575; font-size: 13px">Effective from: {{ version.effectiveFromDate }}</span>
|
|
||||||
</span>
|
|
||||||
</mat-panel-title>
|
|
||||||
</mat-expansion-panel-header>
|
|
||||||
|
|
||||||
@if (version.remarks) {
|
|
||||||
<p>
|
|
||||||
<em>{{ version.remarks }}</em>
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<table class="rd-slab-table mat-elevation-z1" mat-table [dataSource]="version.slabs">
|
|
||||||
<ng-container matColumnDef="srNo">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Sr.</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.srNo }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="description">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Particular</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.description }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="fromPeriod">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.fromPeriod }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="toPeriod">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.toPeriod ?? '—' }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="periodTypeLabel">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Period Type</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">{{ slab.periodTypeLabel }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="annualInterestRate">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Annual Rate (%)</th>
|
|
||||||
<td mat-cell *matCellDef="let slab">
|
|
||||||
<strong>{{ slab.annualInterestRate | number: '1.2-2' }}</strong>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="slabColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: slabColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</mat-expansion-panel>
|
|
||||||
}
|
|
||||||
</mat-accordion>
|
|
||||||
</div>
|
|
||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.rd-version-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rd-active-badge {
|
|
||||||
background: #4caf50;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 2px 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rd-inactive-badge {
|
|
||||||
background: #9e9e9e;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 2px 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rd-slab-table {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-rates {
|
|
||||||
padding: 24px;
|
|
||||||
color: #757575;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
-66
@@ -1,66 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, inject } from '@angular/core';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { DecimalPipe } from '@angular/common';
|
|
||||||
import {
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow
|
|
||||||
} from '@angular/material/table';
|
|
||||||
import { MatExpansionModule } from '@angular/material/expansion';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-rd-interest-rates-tab',
|
|
||||||
templateUrl: './rd-interest-rates-tab.component.html',
|
|
||||||
styleUrls: ['./rd-interest-rates-tab.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow,
|
|
||||||
MatExpansionModule,
|
|
||||||
DecimalPipe
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class DailyDepositSchemeInterestRatesTabComponent {
|
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
|
|
||||||
rdInterestRates: any[] = [];
|
|
||||||
|
|
||||||
readonly slabColumns = [
|
|
||||||
'srNo',
|
|
||||||
'description',
|
|
||||||
'fromPeriod',
|
|
||||||
'toPeriod',
|
|
||||||
'periodTypeLabel',
|
|
||||||
'annualInterestRate'
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.route.data.subscribe((data: { rdInterestRates: any[] }) => {
|
|
||||||
this.rdInterestRates = data.rdInterestRates || [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { inject } from '@angular/core';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
import { ProductsService } from '../../../products.service';
|
|
||||||
|
|
||||||
export const rdInterestRatesResolver: ResolveFn<any[]> = (route) => {
|
|
||||||
const productsService = inject(ProductsService);
|
|
||||||
return productsService.getRdBaseInterestRates();
|
|
||||||
};
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user