update loan product code and rd,fd,saving,current account validation add
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ed45c8637e
commit
d0370781e8
+2
-1
@@ -4,5 +4,6 @@
|
|||||||
"prettier-plugin-multiline-arrays"
|
"prettier-plugin-multiline-arrays"
|
||||||
],
|
],
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "none"
|
"trailingComma": "none",
|
||||||
|
"endOfLine": "auto"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
<div class="section-header__left">
|
<div class="section-header__left">
|
||||||
<i class="fa fa-bank section-header__icon"></i>
|
<i class="fa fa-bank section-header__icon"></i>
|
||||||
<h3 class="section-header__title">{{ 'labels.heading.Saving Accounts' | translate }}</h3>
|
<h3 class="section-header__title">{{ 'labels.heading.Saving Accounts' | translate }}</h3>
|
||||||
<span class="section-header__badge">{{ savingAccounts?.length || 0 }}</span>
|
<span class="section-header__badge">{{ pureSavingsCount }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="section-header__right">
|
<div class="section-header__right">
|
||||||
<button mat-raised-button color="primary" (click)="toggleSavingAccountsOverview()">
|
<button mat-raised-button color="primary" (click)="toggleSavingAccountsOverview()">
|
||||||
|
|||||||
@@ -226,8 +226,20 @@ export class GeneralTabComponent implements OnDestroy {
|
|||||||
clientAccountData: any;
|
clientAccountData: any;
|
||||||
/** Loan Accounts Data */
|
/** Loan Accounts Data */
|
||||||
loanAccounts: any[] = [];
|
loanAccounts: any[] = [];
|
||||||
/** Savings Accounts Data */
|
/** Savings Accounts Data (all: savings + FD + RD — filtered per section) */
|
||||||
savingAccounts: any[] = [];
|
savingAccounts: any[] = [];
|
||||||
|
|
||||||
|
get pureSavingsCount(): number {
|
||||||
|
return this.savingAccounts.filter((a: any) => a.depositType?.code === 'depositAccountType.savingsDeposit').length;
|
||||||
|
}
|
||||||
|
|
||||||
|
get fixedDepositCount(): number {
|
||||||
|
return this.savingAccounts.filter((a: any) => a.depositType?.code === 'depositAccountType.fixedDeposit').length;
|
||||||
|
}
|
||||||
|
|
||||||
|
get recurringDepositCount(): number {
|
||||||
|
return this.savingAccounts.filter((a: any) => a.depositType?.code === 'depositAccountType.recurringDeposit').length;
|
||||||
|
}
|
||||||
/** Current Accounts Data */
|
/** Current Accounts Data */
|
||||||
currentAccounts: any[] = [];
|
currentAccounts: any[] = [];
|
||||||
/** Shares Accounts Data */
|
/** Shares Accounts Data */
|
||||||
|
|||||||
+22
-1
@@ -19,6 +19,7 @@ import { CurrentAccountDigitalStepComponent } from '../current-account-stepper/c
|
|||||||
import { CurrentAccountsService } from '../current-accounts.service';
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
import { Dates } from 'app/core/utils/dates';
|
import { Dates } from 'app/core/utils/dates';
|
||||||
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
import {
|
import {
|
||||||
MatStepper,
|
MatStepper,
|
||||||
MatStepperIcon,
|
MatStepperIcon,
|
||||||
@@ -60,6 +61,7 @@ export class CreateCurrentAccountComponent {
|
|||||||
private dateUtils = inject(Dates);
|
private dateUtils = inject(Dates);
|
||||||
private currentAccountsService = inject(CurrentAccountsService);
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
|
private alertService = inject(AlertService);
|
||||||
|
|
||||||
/** Current Account Template */
|
/** Current Account Template */
|
||||||
currentAccountTemplate: any;
|
currentAccountTemplate: any;
|
||||||
@@ -132,6 +134,14 @@ export class CreateCurrentAccountComponent {
|
|||||||
* Creates a new current account.
|
* Creates a new current account.
|
||||||
*/
|
*/
|
||||||
submit() {
|
submit() {
|
||||||
|
const detailsStep = this.currentAccountDetailsStep;
|
||||||
|
if (detailsStep.selectedAccountStatus === 'JOINT' && detailsStep.totalJointSharePercentage !== 100) {
|
||||||
|
this.alertService.alert({
|
||||||
|
type: 'Error',
|
||||||
|
message: `Total ownership is ${detailsStep.totalJointSharePercentage}%. It must equal 100% before submitting.`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const locale = this.settingsService.language.code;
|
const locale = this.settingsService.language.code;
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
const monthDayFormat = 'dd MMMM';
|
const monthDayFormat = 'dd MMMM';
|
||||||
@@ -155,8 +165,19 @@ export class CreateCurrentAccountComponent {
|
|||||||
currentAccount.startOperationDate = this.dateUtils.formatDate(details.startOperationDate, dateFormat);
|
currentAccount.startOperationDate = this.dateUtils.formatDate(details.startOperationDate, dateFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// strip UI-only flags not needed by backend
|
// Explicitly re-set partners from the details step to ensure all are included
|
||||||
|
if (this.currentAccountDetailsStep.isNonIndividualClient) {
|
||||||
|
currentAccount.isNonIndividualClient = true;
|
||||||
|
currentAccount.partners = this.currentAccountDetailsStep.relatedClients.map((c: any) => ({
|
||||||
|
clientId: c.clientId,
|
||||||
|
clientName: c.clientName,
|
||||||
|
clientCif: c.clientCif,
|
||||||
|
sharePercentage: c.sharePercentage,
|
||||||
|
isMandatory: c.isMandatory
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
delete currentAccount.isNonIndividualClient;
|
delete currentAccount.isNonIndividualClient;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.currentAccountTemplate.clientId) {
|
if (this.currentAccountTemplate.clientId) {
|
||||||
currentAccount.clientId = this.currentAccountTemplate.clientId;
|
currentAccount.clientId = this.currentAccountTemplate.clientId;
|
||||||
|
|||||||
+16
-2
@@ -62,18 +62,32 @@
|
|||||||
|
|
||||||
<mat-form-field appearance="outline" class="sad-field" (click)="establishmentDatePicker.open()">
|
<mat-form-field appearance="outline" class="sad-field" (click)="establishmentDatePicker.open()">
|
||||||
<mat-label>Establishment Date</mat-label>
|
<mat-label>Establishment Date</mat-label>
|
||||||
<input matInput [matDatepicker]="establishmentDatePicker" formControlName="establishmentDate" />
|
<input matInput [matDatepicker]="establishmentDatePicker" formControlName="establishmentDate" [max]="today" />
|
||||||
<mat-datepicker-toggle matSuffix [for]="establishmentDatePicker"></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="establishmentDatePicker"></mat-datepicker-toggle>
|
||||||
<mat-datepicker #establishmentDatePicker></mat-datepicker>
|
<mat-datepicker #establishmentDatePicker></mat-datepicker>
|
||||||
<mat-hint>Date of entity / business establishment</mat-hint>
|
<mat-hint>Date of entity / business establishment</mat-hint>
|
||||||
|
@if (currentAccountDetailsForm.get('establishmentDate')?.hasError('matDatepickerMax')) {
|
||||||
|
<mat-error>Establishment Date cannot be a future date.</mat-error>
|
||||||
|
}
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="sad-field" (click)="startOperationDatePicker.open()">
|
<mat-form-field appearance="outline" class="sad-field" (click)="startOperationDatePicker.open()">
|
||||||
<mat-label>Start of Operation Date</mat-label>
|
<mat-label>Start of Operation Date</mat-label>
|
||||||
<input matInput [matDatepicker]="startOperationDatePicker" formControlName="startOperationDate" />
|
<input
|
||||||
|
matInput
|
||||||
|
[matDatepicker]="startOperationDatePicker"
|
||||||
|
formControlName="startOperationDate"
|
||||||
|
[max]="today"
|
||||||
|
[min]="minStartOperationDate"
|
||||||
|
/>
|
||||||
<mat-datepicker-toggle matSuffix [for]="startOperationDatePicker"></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="startOperationDatePicker"></mat-datepicker-toggle>
|
||||||
<mat-datepicker #startOperationDatePicker></mat-datepicker>
|
<mat-datepicker #startOperationDatePicker></mat-datepicker>
|
||||||
<mat-hint>Date when operations commenced</mat-hint>
|
<mat-hint>Date when operations commenced</mat-hint>
|
||||||
|
@if (currentAccountDetailsForm.get('startOperationDate')?.hasError('matDatepickerMax')) {
|
||||||
|
<mat-error>Start of Operation Date cannot be a future date.</mat-error>
|
||||||
|
} @else if (currentAccountDetailsForm.get('startOperationDate')?.hasError('matDatepickerMin')) {
|
||||||
|
<mat-error>Start of Operation Date cannot be before Establishment Date.</mat-error>
|
||||||
|
}
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+21
-4
@@ -22,6 +22,7 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
import { IntroducerSearchComponent } from 'app/shared/introducer-search/introducer-search.component';
|
import { IntroducerSearchComponent } from 'app/shared/introducer-search/introducer-search.component';
|
||||||
import { AddRelatedClientDialogComponent } from './add-related-client-dialog/add-related-client-dialog.component';
|
import { AddRelatedClientDialogComponent } from './add-related-client-dialog/add-related-client-dialog.component';
|
||||||
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current Account Details Step
|
* Current Account Details Step
|
||||||
@@ -46,6 +47,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
|||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
private clientsService = inject(ClientsService);
|
private clientsService = inject(ClientsService);
|
||||||
private dialog = inject(MatDialog);
|
private dialog = inject(MatDialog);
|
||||||
|
private alertService = inject(AlertService);
|
||||||
private dateUtils = inject(Dates);
|
private dateUtils = inject(Dates);
|
||||||
private cdr = inject(ChangeDetectorRef);
|
private cdr = inject(ChangeDetectorRef);
|
||||||
|
|
||||||
@@ -365,9 +367,13 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
ref.afterClosed().subscribe((result) => {
|
ref.afterClosed().subscribe((result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
const duplicate = this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif);
|
const isPrimary = this.primaryClientCif && this.primaryClientCif === result.clientCif;
|
||||||
|
const duplicate = isPrimary || this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif);
|
||||||
if (duplicate) {
|
if (duplicate) {
|
||||||
alert(`Account holder with CIF ${result.clientCif} is already added.`);
|
this.alertService.alert({
|
||||||
|
type: 'Error',
|
||||||
|
message: `Account holder with CIF ${result.clientCif} is already added.`
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.jointHolders = [
|
this.jointHolders = [
|
||||||
@@ -407,6 +413,17 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
|||||||
return this.modeOfOperationOptions.find((o) => o.value === this.modeOfOperation)?.sublabel ?? '';
|
return this.modeOfOperationOptions.find((o) => o.value === this.modeOfOperation)?.sublabel ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get today(): Date {
|
||||||
|
const d = new Date();
|
||||||
|
d.setHours(0, 0, 0, 0);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
get minStartOperationDate(): Date | null {
|
||||||
|
const val = this.currentAccountDetailsForm?.get('establishmentDate')?.value;
|
||||||
|
return val ? new Date(val) : null;
|
||||||
|
}
|
||||||
|
|
||||||
get majorityDate(): string {
|
get majorityDate(): string {
|
||||||
const dob = this.minorDob ?? this.clientDob;
|
const dob = this.minorDob ?? this.clientDob;
|
||||||
if (!dob) return '';
|
if (!dob) return '';
|
||||||
@@ -426,7 +443,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
|||||||
if (result) {
|
if (result) {
|
||||||
const duplicate = this.relatedClients.some((c) => c.clientCif && c.clientCif === result.clientCif);
|
const duplicate = this.relatedClients.some((c) => c.clientCif && c.clientCif === result.clientCif);
|
||||||
if (duplicate) {
|
if (duplicate) {
|
||||||
alert(`Partner with CIF ${result.clientCif} is already added.`);
|
this.alertService.alert({ type: 'Error', message: `Partner with CIF ${result.clientCif} is already added.` });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.relatedClients = [
|
this.relatedClients = [
|
||||||
@@ -520,7 +537,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
|||||||
value.guardianName = this.guardianName;
|
value.guardianName = this.guardianName;
|
||||||
value.guardianCifNumber = this.guardianCif;
|
value.guardianCifNumber = this.guardianCif;
|
||||||
value.guardianRelationship = this.guardianRelationship;
|
value.guardianRelationship = this.guardianRelationship;
|
||||||
value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'yyyy-MM-dd') : null;
|
value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'dd MMMM yyyy') : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// non-individual: partners are a separate dataset
|
// non-individual: partners are a separate dataset
|
||||||
|
|||||||
-48
@@ -649,54 +649,6 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- ══ 7. Non-Individual Details (if applicable) ════════════════ -->
|
|
||||||
@if (currentAccount?.partners?.length > 0) {
|
|
||||||
<div class="preview-section">
|
|
||||||
<div class="preview-section__header">
|
|
||||||
<i class="fas fa-building preview-section__icon"></i>
|
|
||||||
<span class="preview-section__title">Non-Individual Account Details</span>
|
|
||||||
</div>
|
|
||||||
<div class="preview-body">
|
|
||||||
<!-- Partners -->
|
|
||||||
<div class="preview-ni-label">Partners / Members</div>
|
|
||||||
<div class="preview-ni-table-wrap">
|
|
||||||
<table class="preview-ni-tbl">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Partner Name</th>
|
|
||||||
<th>CIF No</th>
|
|
||||||
<th>Ownership %</th>
|
|
||||||
<th>Signing Authority</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (p of currentAccount.partners; track p.clientId) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ p.clientName || '—' }}</td>
|
|
||||||
<td class="preview-mono">{{ p.clientCif || '—' }}</td>
|
|
||||||
<td>{{ p.sharePercentage }}%</td>
|
|
||||||
<td>
|
|
||||||
<span class="preview-badge" [class.preview-badge--yes]="p.isMandatory">
|
|
||||||
{{ p.isMandatory ? 'Mandatory' : 'Optional' }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- Signatory Rights -->
|
|
||||||
@if (currentAccount?.signatoryRights) {
|
|
||||||
<div class="preview-ni-mode">
|
|
||||||
<i class="fas fa-how_to_vote"></i>
|
|
||||||
<span class="preview-ni-mode__label">Mode of Operation:</span>
|
|
||||||
<strong>{{ currentAccount.signatoryRights }}</strong>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ══ 8. Digital Facilities ════════════════════════════════════ -->
|
<!-- ══ 8. Digital Facilities ════════════════════════════════════ -->
|
||||||
@if (hasDigitalSection) {
|
@if (hasDigitalSection) {
|
||||||
<div class="preview-section">
|
<div class="preview-section">
|
||||||
|
|||||||
+32
-3
@@ -13,6 +13,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
import { FixedDepositsService } from '../fixed-deposits.service';
|
import { FixedDepositsService } from '../fixed-deposits.service';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
|
|
||||||
/** Custom Components */
|
/** Custom Components */
|
||||||
import { FixedDepositAccountDetailsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component';
|
import { FixedDepositAccountDetailsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component';
|
||||||
@@ -59,6 +60,7 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
private dateUtils = inject(Dates);
|
private dateUtils = inject(Dates);
|
||||||
private fixedDepositsService = inject(FixedDepositsService);
|
private fixedDepositsService = inject(FixedDepositsService);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
|
private alertService = inject(AlertService);
|
||||||
|
|
||||||
@ViewChild(FixedDepositAccountDetailsStepComponent, { static: true })
|
@ViewChild(FixedDepositAccountDetailsStepComponent, { static: true })
|
||||||
fixedDepositsAccountDetailsStep: FixedDepositAccountDetailsStepComponent;
|
fixedDepositsAccountDetailsStep: FixedDepositAccountDetailsStepComponent;
|
||||||
@@ -70,7 +72,14 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
/** Fixed Deposit Account Product Template */
|
/** Fixed Deposit Account Product Template */
|
||||||
fixedDepositsAccountProductTemplate: any;
|
fixedDepositsAccountProductTemplate: any;
|
||||||
/** Tenure selected from the interest rate chart step */
|
/** Tenure selected from the interest rate chart step */
|
||||||
selectedTenure: { depositPeriod: number; depositPeriodFrequencyId: number; depositAmount?: number } | null = null;
|
selectedTenure: {
|
||||||
|
depositPeriod: number;
|
||||||
|
depositPeriodFrequencyId: number;
|
||||||
|
depositAmount?: number;
|
||||||
|
maturityAmount?: number;
|
||||||
|
interestEarned?: number;
|
||||||
|
appliedRate?: number;
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches FD account template from `resolve`
|
* Fetches FD account template from `resolve`
|
||||||
@@ -94,7 +103,14 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
this.fixedDepositsAccountProductTemplate = $event;
|
this.fixedDepositsAccountProductTemplate = $event;
|
||||||
}
|
}
|
||||||
|
|
||||||
onTenureSelected($event: { depositPeriod: number; depositPeriodFrequencyId: number; depositAmount?: number }) {
|
onTenureSelected($event: {
|
||||||
|
depositPeriod: number;
|
||||||
|
depositPeriodFrequencyId: number;
|
||||||
|
depositAmount?: number;
|
||||||
|
maturityAmount?: number;
|
||||||
|
interestEarned?: number;
|
||||||
|
appliedRate?: number;
|
||||||
|
}) {
|
||||||
this.selectedTenure = $event;
|
this.selectedTenure = $event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +142,14 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
* Submits the fixed deposit form and creates a new fixed deposit account
|
* Submits the fixed deposit form and creates a new fixed deposit account
|
||||||
*/
|
*/
|
||||||
submit() {
|
submit() {
|
||||||
|
const detailsStep = this.fixedDepositsAccountDetailsStep;
|
||||||
|
if (detailsStep?.selectedAccountStatus === 'JOINT' && detailsStep.totalJointSharePercentage !== 100) {
|
||||||
|
this.alertService.alert({
|
||||||
|
type: 'Error',
|
||||||
|
message: `Total ownership is ${detailsStep.totalJointSharePercentage}%. It must equal 100% before submitting.`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const locale = this.settingsService.language.code;
|
const locale = this.settingsService.language.code;
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
const monthDayFormat = 'dd MMMM';
|
const monthDayFormat = 'dd MMMM';
|
||||||
@@ -178,7 +202,12 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
...this.fixedDepositAccount,
|
...this.fixedDepositAccount,
|
||||||
...terms,
|
...terms,
|
||||||
...settings,
|
...settings,
|
||||||
...(this.selectedTenure ?? {}),
|
...(this.selectedTenure
|
||||||
|
? {
|
||||||
|
depositPeriod: this.selectedTenure.depositPeriod,
|
||||||
|
depositPeriodFrequencyId: this.selectedTenure.depositPeriodFrequencyId
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
clientId: this.fixedDepositsAccountTemplate.clientId,
|
clientId: this.fixedDepositsAccountTemplate.clientId,
|
||||||
charges: rawCharges.map((charge: any) => ({
|
charges: rawCharges.map((charge: any) => ({
|
||||||
chargeId: charge.chargeId ?? charge.id,
|
chargeId: charge.chargeId ?? charge.id,
|
||||||
|
|||||||
+12
-4
@@ -22,6 +22,7 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
import { MatIcon } from '@angular/material/icon';
|
import { MatIcon } from '@angular/material/icon';
|
||||||
import { AddRelatedClientDialogComponent } from 'app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component';
|
import { AddRelatedClientDialogComponent } from 'app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component';
|
||||||
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fixed Deposits Account Details Step
|
* Fixed Deposits Account Details Step
|
||||||
@@ -48,6 +49,7 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
|||||||
private cdr = inject(ChangeDetectorRef);
|
private cdr = inject(ChangeDetectorRef);
|
||||||
private clientsService = inject(ClientsService);
|
private clientsService = inject(ClientsService);
|
||||||
private dialog = inject(MatDialog);
|
private dialog = inject(MatDialog);
|
||||||
|
private alertService = inject(AlertService);
|
||||||
|
|
||||||
/** Fixed Deposits Account Template */
|
/** Fixed Deposits Account Template */
|
||||||
@Input() fixedDepositsAccountTemplate: any;
|
@Input() fixedDepositsAccountTemplate: any;
|
||||||
@@ -221,7 +223,9 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
|||||||
this.dalLoading = true;
|
this.dalLoading = true;
|
||||||
this.clientsService.getClientAccountData(clientId).subscribe({
|
this.clientsService.getClientAccountData(clientId).subscribe({
|
||||||
next: (data: any) => {
|
next: (data: any) => {
|
||||||
this.dalAllSavings = (data?.savingsAccounts ?? []).map((a: any) => ({
|
this.dalAllSavings = (data?.savingsAccounts ?? [])
|
||||||
|
.filter((a: any) => a.depositType?.code === 'depositAccountType.savingsDeposit')
|
||||||
|
.map((a: any) => ({
|
||||||
id: a.id,
|
id: a.id,
|
||||||
accountNo: a.accountNo,
|
accountNo: a.accountNo,
|
||||||
productName: a.productName ?? '',
|
productName: a.productName ?? '',
|
||||||
@@ -469,9 +473,13 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
ref.afterClosed().subscribe((result) => {
|
ref.afterClosed().subscribe((result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
const duplicate = this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif);
|
const isPrimary = this.primaryClientCif && this.primaryClientCif === result.clientCif;
|
||||||
|
const duplicate = isPrimary || this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif);
|
||||||
if (duplicate) {
|
if (duplicate) {
|
||||||
alert(`Account holder with CIF ${result.clientCif} is already added.`);
|
this.alertService.alert({
|
||||||
|
type: 'Error',
|
||||||
|
message: `Account holder with CIF ${result.clientCif} is already added.`
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.jointHolders = [
|
this.jointHolders = [
|
||||||
@@ -582,7 +590,7 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
|||||||
value.guardianName = this.guardianName;
|
value.guardianName = this.guardianName;
|
||||||
value.guardianCifNumber = this.guardianCif;
|
value.guardianCifNumber = this.guardianCif;
|
||||||
value.guardianRelationship = this.guardianRelationship;
|
value.guardianRelationship = this.guardianRelationship;
|
||||||
value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'yyyy-MM-dd') : null;
|
value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'dd MMMM yyyy') : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedLinkedAccount) {
|
if (this.selectedLinkedAccount) {
|
||||||
|
|||||||
+8
-2
@@ -76,6 +76,9 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
|
|||||||
depositPeriod: number;
|
depositPeriod: number;
|
||||||
depositPeriodFrequencyId: number;
|
depositPeriodFrequencyId: number;
|
||||||
depositAmount?: number;
|
depositAmount?: number;
|
||||||
|
maturityAmount?: number;
|
||||||
|
interestEarned?: number;
|
||||||
|
appliedRate?: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
interestRateChartData: any[] = [];
|
interestRateChartData: any[] = [];
|
||||||
@@ -254,13 +257,16 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
|
|||||||
|
|
||||||
this.matchResult = result;
|
this.matchResult = result;
|
||||||
|
|
||||||
// Emit selected tenure and principal amount to parent for backend submission
|
// Emit selected tenure and calculated maturity to parent
|
||||||
const frequencyId = this.resolveFrequencyId(this.tenurePeriodUnit);
|
const frequencyId = this.resolveFrequencyId(this.tenurePeriodUnit);
|
||||||
if (frequencyId !== null) {
|
if (frequencyId !== null) {
|
||||||
this.tenureSelected.emit({
|
this.tenureSelected.emit({
|
||||||
depositPeriod: this.tenureValue,
|
depositPeriod: this.tenureValue,
|
||||||
depositPeriodFrequencyId: frequencyId,
|
depositPeriodFrequencyId: frequencyId,
|
||||||
depositAmount: this.principalAmount || undefined
|
depositAmount: this.principalAmount || undefined,
|
||||||
|
maturityAmount: result.maturityAmount,
|
||||||
|
interestEarned: result.interestEarned,
|
||||||
|
appliedRate: result.appliedRate
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-17
@@ -95,7 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="preview-field">
|
<div class="preview-field">
|
||||||
<span class="preview-label">Share Linking Enabled</span>
|
<span class="preview-label">Deposit Account Link</span>
|
||||||
<span
|
<span
|
||||||
class="preview-value preview-badge"
|
class="preview-value preview-badge"
|
||||||
[class.preview-badge--yes]="fixedDepositsAccountProductTemplate?.shareLinkingEnabled"
|
[class.preview-badge--yes]="fixedDepositsAccountProductTemplate?.shareLinkingEnabled"
|
||||||
@@ -103,17 +103,6 @@
|
|||||||
{{ fixedDepositsAccountProductTemplate?.shareLinkingEnabled ? 'Yes' : 'No' }}
|
{{ fixedDepositsAccountProductTemplate?.shareLinkingEnabled ? 'Yes' : 'No' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@if (fixedDepositsAccountProductTemplate?.introducerRequired !== undefined) {
|
|
||||||
<div class="preview-field">
|
|
||||||
<span class="preview-label">Introducer Required</span>
|
|
||||||
<span
|
|
||||||
class="preview-value preview-badge"
|
|
||||||
[class.preview-badge--yes]="fixedDepositsAccountProductTemplate?.introducerRequired"
|
|
||||||
>
|
|
||||||
{{ fixedDepositsAccountProductTemplate?.introducerRequired ? 'Yes' : 'No' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -316,12 +305,19 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ══ 2b. Dormancy Monitoring ═══════════════════════════════════ -->
|
<!-- ══ 2b. Dormancy Monitoring ═══════════════════════════════════ -->
|
||||||
@if (fixedDepositsAccountProductTemplate?.dormancyAlert) {
|
@if (
|
||||||
|
fixedDepositsAccountProductTemplate?.dormancyAlert || fixedDepositsAccountProductTemplate?.isDormancyTrackingActive
|
||||||
|
) {
|
||||||
<div class="preview-section">
|
<div class="preview-section">
|
||||||
<div class="preview-section__header">
|
<div class="preview-section__header">
|
||||||
<i class="fas fa-moon preview-section__icon"></i>
|
<i class="fas fa-moon preview-section__icon"></i>
|
||||||
<span class="preview-section__title">Dormancy Monitoring</span>
|
<span class="preview-section__title">Dormancy Monitoring</span>
|
||||||
<span class="preview-section__badge preview-section__badge--active">Monitoring: Active</span>
|
<span
|
||||||
|
class="preview-section__badge"
|
||||||
|
[class.preview-section__badge--active]="fixedDepositsAccountProductTemplate?.isDormancyTrackingActive"
|
||||||
|
>
|
||||||
|
{{ fixedDepositsAccountProductTemplate?.isDormancyTrackingActive ? 'Active' : 'Inactive' }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="preview-body">
|
<div class="preview-body">
|
||||||
<div class="preview-dormancy-timeline">
|
<div class="preview-dormancy-timeline">
|
||||||
@@ -330,7 +326,11 @@
|
|||||||
<div class="preview-dorm-step__body">
|
<div class="preview-dorm-step__body">
|
||||||
<span class="preview-dorm-step__label">Inactive</span>
|
<span class="preview-dorm-step__label">Inactive</span>
|
||||||
<span class="preview-dorm-step__days">
|
<span class="preview-dorm-step__days">
|
||||||
After {{ fixedDepositsAccountProductTemplate?.daysToInactiveFromProduct || 30 }} days of no transaction
|
@if (fixedDepositsAccountProductTemplate?.daysToInactive != null) {
|
||||||
|
After {{ fixedDepositsAccountProductTemplate.daysToInactive }} days of no transaction
|
||||||
|
} @else {
|
||||||
|
Not configured
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -340,7 +340,11 @@
|
|||||||
<div class="preview-dorm-step__body">
|
<div class="preview-dorm-step__body">
|
||||||
<span class="preview-dorm-step__label">Dormant</span>
|
<span class="preview-dorm-step__label">Dormant</span>
|
||||||
<span class="preview-dorm-step__days">
|
<span class="preview-dorm-step__days">
|
||||||
{{ fixedDepositsAccountProductTemplate?.daysToDormancyFromProduct || 60 }} days after inactive
|
@if (fixedDepositsAccountProductTemplate?.daysToDormancy != null) {
|
||||||
|
{{ fixedDepositsAccountProductTemplate.daysToDormancy }} days after inactive
|
||||||
|
} @else {
|
||||||
|
Not configured
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -350,11 +354,31 @@
|
|||||||
<div class="preview-dorm-step__body">
|
<div class="preview-dorm-step__body">
|
||||||
<span class="preview-dorm-step__label">Escheat</span>
|
<span class="preview-dorm-step__label">Escheat</span>
|
||||||
<span class="preview-dorm-step__days">
|
<span class="preview-dorm-step__days">
|
||||||
{{ fixedDepositsAccountProductTemplate?.daysToEscheatFromProduct || 90 }} days after dormant
|
@if (fixedDepositsAccountProductTemplate?.daysToEscheat != null) {
|
||||||
|
{{ fixedDepositsAccountProductTemplate.daysToEscheat }} days after dormant
|
||||||
|
} @else {
|
||||||
|
Not configured
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="preview-dorm-flags">
|
||||||
|
<span
|
||||||
|
class="preview-dorm-flag"
|
||||||
|
[class.preview-dorm-flag--on]="fixedDepositsAccountProductTemplate?.dormancyAlert"
|
||||||
|
>
|
||||||
|
<i class="fas fa-bell"></i> Alert
|
||||||
|
{{ fixedDepositsAccountProductTemplate?.dormancyAlert ? 'Enabled' : 'Disabled' }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="preview-dorm-flag"
|
||||||
|
[class.preview-dorm-flag--on]="fixedDepositsAccountProductTemplate?.isDormancyTrackingActive"
|
||||||
|
>
|
||||||
|
<i class="fas fa-eye"></i> Tracking
|
||||||
|
{{ fixedDepositsAccountProductTemplate?.isDormancyTrackingActive ? 'Active' : 'Inactive' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-13
@@ -423,31 +423,25 @@
|
|||||||
.preview-dormancy-timeline {
|
.preview-dormancy-timeline {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 12px;
|
margin-bottom: 14px;
|
||||||
margin: 1.25rem 0;
|
|
||||||
overflow-x: auto;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-dorm-step {
|
.preview-dorm-step {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 0 0 140px;
|
flex: 1;
|
||||||
gap: 10px;
|
gap: 6px;
|
||||||
|
|
||||||
&__dot {
|
&__dot {
|
||||||
width: 32px;
|
width: 36px;
|
||||||
height: 32px;
|
height: 36px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
border: 3px solid;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 3px solid #e2e8f0;
|
flex-shrink: 0;
|
||||||
background: #f8fafc;
|
|
||||||
position: relative;
|
|
||||||
box-shadow: 0 0 0 4px #fff;
|
|
||||||
z-index: 2;
|
|
||||||
|
|
||||||
&--inactive {
|
&--inactive {
|
||||||
border-color: #f59e0b;
|
border-color: #f59e0b;
|
||||||
@@ -517,3 +511,32 @@
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview-dorm-flags {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-dorm-flag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #94a3b8;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--on {
|
||||||
|
background: #f0fdf4;
|
||||||
|
color: #15803d;
|
||||||
|
border-color: #bbf7d0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+23
-10
@@ -76,9 +76,15 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
|||||||
@Input() fixedDepositsAccountProductTemplate: any;
|
@Input() fixedDepositsAccountProductTemplate: any;
|
||||||
/** Fixed Deposits Account Data */
|
/** Fixed Deposits Account Data */
|
||||||
@Input() fixedDepositAccountData: any;
|
@Input() fixedDepositAccountData: any;
|
||||||
/** Tenure selected from the interest rate chart step */
|
/** Tenure selected from the interest rate chart step (includes pre-calculated maturity if user used calculator) */
|
||||||
@Input() selectedTenure: { depositPeriod: number; depositPeriodFrequencyId: number; depositAmount?: number } | null =
|
@Input() selectedTenure: {
|
||||||
null;
|
depositPeriod: number;
|
||||||
|
depositPeriodFrequencyId: number;
|
||||||
|
depositAmount?: number;
|
||||||
|
maturityAmount?: number;
|
||||||
|
interestEarned?: number;
|
||||||
|
appliedRate?: number;
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
/** Charges Displayed Columns */
|
/** Charges Displayed Columns */
|
||||||
chargesDisplayedColumns: string[] = [
|
chargesDisplayedColumns: string[] = [
|
||||||
@@ -237,6 +243,20 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
|||||||
this.fixedDepositsAccountProductTemplate?.depositAmount ??
|
this.fixedDepositsAccountProductTemplate?.depositAmount ??
|
||||||
0;
|
0;
|
||||||
|
|
||||||
|
// If the chart step already calculated maturity, use those values directly
|
||||||
|
if (this.selectedTenure?.maturityAmount != null && this.selectedTenure.maturityAmount > 0) {
|
||||||
|
const interestRate =
|
||||||
|
this.selectedTenure.appliedRate ?? this.fixedDepositsAccountProductTemplate?.nominalAnnualInterestRate ?? 0;
|
||||||
|
return {
|
||||||
|
depositAmount,
|
||||||
|
maturityAmount: this.selectedTenure.maturityAmount,
|
||||||
|
totalInterest: this.selectedTenure.interestEarned ?? this.selectedTenure.maturityAmount - depositAmount,
|
||||||
|
interestRate,
|
||||||
|
tenureLabel: this.selectedTenureLabel
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: recalculate using product template settings
|
||||||
const chartRate =
|
const chartRate =
|
||||||
this.matchedSlabIndex >= 0 ? this.interestRateChartData[this.matchedSlabIndex]?.annualInterestRate : null;
|
this.matchedSlabIndex >= 0 ? this.interestRateChartData[this.matchedSlabIndex]?.annualInterestRate : null;
|
||||||
const interestRate = chartRate ?? this.fixedDepositsAccountProductTemplate?.nominalAnnualInterestRate ?? 0;
|
const interestRate = chartRate ?? this.fixedDepositsAccountProductTemplate?.nominalAnnualInterestRate ?? 0;
|
||||||
@@ -248,20 +268,15 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
|||||||
this.fixedDepositsAccountProductTemplate?.interestCalculationDaysInYearType?.id === 360 ? 360 : 365;
|
this.fixedDepositsAccountProductTemplate?.interestCalculationDaysInYearType?.id === 360 ? 360 : 365;
|
||||||
|
|
||||||
if (depositPeriodFrequencyId === 0) {
|
if (depositPeriodFrequencyId === 0) {
|
||||||
// Days
|
|
||||||
t = depositPeriod / daysInYear;
|
t = depositPeriod / daysInYear;
|
||||||
} else if (depositPeriodFrequencyId === 1) {
|
} else if (depositPeriodFrequencyId === 1) {
|
||||||
// Weeks
|
|
||||||
t = (depositPeriod * 7) / daysInYear;
|
t = (depositPeriod * 7) / daysInYear;
|
||||||
} else if (depositPeriodFrequencyId === 2) {
|
} else if (depositPeriodFrequencyId === 2) {
|
||||||
// Months
|
|
||||||
t = depositPeriod / 12;
|
t = depositPeriod / 12;
|
||||||
} else if (depositPeriodFrequencyId === 3) {
|
} else if (depositPeriodFrequencyId === 3) {
|
||||||
// Years
|
|
||||||
t = depositPeriod;
|
t = depositPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compounding frequency (n)
|
|
||||||
const compPeriodValue = (
|
const compPeriodValue = (
|
||||||
this.fixedDepositsAccountProductTemplate?.interestCompoundingPeriodType?.value || ''
|
this.fixedDepositsAccountProductTemplate?.interestCompoundingPeriodType?.value || ''
|
||||||
).toLowerCase();
|
).toLowerCase();
|
||||||
@@ -273,7 +288,6 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
|||||||
else if (compPeriodValue.includes('biannual')) n = 2;
|
else if (compPeriodValue.includes('biannual')) n = 2;
|
||||||
else if (compPeriodValue.includes('annual')) n = 1;
|
else if (compPeriodValue.includes('annual')) n = 1;
|
||||||
|
|
||||||
// Interest Calculation Type
|
|
||||||
const calcTypeValue = (
|
const calcTypeValue = (
|
||||||
this.fixedDepositsAccountProductTemplate?.interestCalculationType?.value || ''
|
this.fixedDepositsAccountProductTemplate?.interestCalculationType?.value || ''
|
||||||
).toLowerCase();
|
).toLowerCase();
|
||||||
@@ -283,7 +297,6 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
|||||||
if (isSimple) {
|
if (isSimple) {
|
||||||
maturityAmount = depositAmount + depositAmount * r * t;
|
maturityAmount = depositAmount + depositAmount * r * t;
|
||||||
} else {
|
} else {
|
||||||
// Compound interest: A = P(1 + r/n)^(nt)
|
|
||||||
maturityAmount = depositAmount * Math.pow(1 + r / n, n * t);
|
maturityAmount = depositAmount * Math.pow(1 + r / n, n * t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
@@ -14,6 +14,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { RecurringDepositsService } from '../recurring-deposits.service';
|
import { RecurringDepositsService } from '../recurring-deposits.service';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
import { ProductsService } from 'app/products/products.service';
|
import { ProductsService } from 'app/products/products.service';
|
||||||
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
|
|
||||||
/** Step Components */
|
/** Step Components */
|
||||||
import { RecurringDepositsAccountDetailsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component';
|
import { RecurringDepositsAccountDetailsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component';
|
||||||
@@ -61,6 +62,7 @@ export class CreateRecurringDepositsAccountComponent {
|
|||||||
private recurringDepositsService = inject(RecurringDepositsService);
|
private recurringDepositsService = inject(RecurringDepositsService);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
private productsService = inject(ProductsService);
|
private productsService = inject(ProductsService);
|
||||||
|
private alertService = inject(AlertService);
|
||||||
|
|
||||||
@ViewChild(RecurringDepositsAccountDetailsStepComponent, { static: true })
|
@ViewChild(RecurringDepositsAccountDetailsStepComponent, { static: true })
|
||||||
recurringDepositsAccountDetailsStep: RecurringDepositsAccountDetailsStepComponent;
|
recurringDepositsAccountDetailsStep: RecurringDepositsAccountDetailsStepComponent;
|
||||||
@@ -157,6 +159,14 @@ export class CreateRecurringDepositsAccountComponent {
|
|||||||
* Submits the recurring deposit form to create a new recurring deposit account
|
* Submits the recurring deposit form to create a new recurring deposit account
|
||||||
*/
|
*/
|
||||||
submit() {
|
submit() {
|
||||||
|
const detailsStep = this.recurringDepositsAccountDetailsStep;
|
||||||
|
if (detailsStep.selectedAccountStatus === 'JOINT' && detailsStep.totalJointSharePercentage !== 100) {
|
||||||
|
this.alertService.alert({
|
||||||
|
type: 'Error',
|
||||||
|
message: `Total ownership is ${detailsStep.totalJointSharePercentage}%. It must equal 100% before submitting.`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const locale = this.settingsService.language.code;
|
const locale = this.settingsService.language.code;
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
const monthDayFormat = 'dd MMMM';
|
const monthDayFormat = 'dd MMMM';
|
||||||
|
|||||||
+12
-4
@@ -22,6 +22,7 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
import { MatIcon } from '@angular/material/icon';
|
import { MatIcon } from '@angular/material/icon';
|
||||||
import { AddRelatedClientDialogComponent } from 'app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component';
|
import { AddRelatedClientDialogComponent } from 'app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component';
|
||||||
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recurring Deposits Account Details Step
|
* Recurring Deposits Account Details Step
|
||||||
@@ -48,6 +49,7 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
|||||||
private cdr = inject(ChangeDetectorRef);
|
private cdr = inject(ChangeDetectorRef);
|
||||||
private clientsService = inject(ClientsService);
|
private clientsService = inject(ClientsService);
|
||||||
private dialog = inject(MatDialog);
|
private dialog = inject(MatDialog);
|
||||||
|
private alertService = inject(AlertService);
|
||||||
|
|
||||||
/** Recurring Deposits Account Template */
|
/** Recurring Deposits Account Template */
|
||||||
@Input() recurringDepositsAccountTemplate: any;
|
@Input() recurringDepositsAccountTemplate: any;
|
||||||
@@ -198,7 +200,9 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
|||||||
this.dalLoading = true;
|
this.dalLoading = true;
|
||||||
this.clientsService.getClientAccountData(clientId).subscribe({
|
this.clientsService.getClientAccountData(clientId).subscribe({
|
||||||
next: (data: any) => {
|
next: (data: any) => {
|
||||||
this.dalAllSavings = (data?.savingsAccounts ?? []).map((a: any) => ({
|
this.dalAllSavings = (data?.savingsAccounts ?? [])
|
||||||
|
.filter((a: any) => a.depositType?.code === 'depositAccountType.savingsDeposit')
|
||||||
|
.map((a: any) => ({
|
||||||
id: a.id,
|
id: a.id,
|
||||||
accountNo: a.accountNo,
|
accountNo: a.accountNo,
|
||||||
productName: a.productName ?? '',
|
productName: a.productName ?? '',
|
||||||
@@ -430,9 +434,13 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
ref.afterClosed().subscribe((result) => {
|
ref.afterClosed().subscribe((result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
const duplicate = this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif);
|
const isPrimary = this.primaryClientCif && this.primaryClientCif === result.clientCif;
|
||||||
|
const duplicate = isPrimary || this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif);
|
||||||
if (duplicate) {
|
if (duplicate) {
|
||||||
alert(`Account holder with CIF ${result.clientCif} is already added.`);
|
this.alertService.alert({
|
||||||
|
type: 'Error',
|
||||||
|
message: `Account holder with CIF ${result.clientCif} is already added.`
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.jointHolders = [
|
this.jointHolders = [
|
||||||
@@ -540,7 +548,7 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
|||||||
value.guardianName = this.guardianName;
|
value.guardianName = this.guardianName;
|
||||||
value.guardianCifNumber = this.guardianCif;
|
value.guardianCifNumber = this.guardianCif;
|
||||||
value.guardianRelationship = this.guardianRelationship;
|
value.guardianRelationship = this.guardianRelationship;
|
||||||
value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'yyyy-MM-dd') : null;
|
value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'dd MMMM yyyy') : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedLinkedAccount) {
|
if (this.selectedLinkedAccount) {
|
||||||
|
|||||||
+11
-13
@@ -463,7 +463,7 @@
|
|||||||
<!-- ══ 5. Dormancy Monitoring ════════════════════════════════════ -->
|
<!-- ══ 5. Dormancy Monitoring ════════════════════════════════════ -->
|
||||||
@if (
|
@if (
|
||||||
recurringDepositsAccountProductTemplate?.dormancyAlert ||
|
recurringDepositsAccountProductTemplate?.dormancyAlert ||
|
||||||
recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct
|
recurringDepositsAccountProductTemplate?.isDormancyTrackingActive
|
||||||
) {
|
) {
|
||||||
<div class="preview-section">
|
<div class="preview-section">
|
||||||
<div class="preview-section__header">
|
<div class="preview-section__header">
|
||||||
@@ -471,11 +471,9 @@
|
|||||||
<span class="preview-section__title">Dormancy Monitoring</span>
|
<span class="preview-section__title">Dormancy Monitoring</span>
|
||||||
<span
|
<span
|
||||||
class="preview-section__badge"
|
class="preview-section__badge"
|
||||||
[class.preview-section__badge--active]="
|
[class.preview-section__badge--active]="recurringDepositsAccountProductTemplate?.isDormancyTrackingActive"
|
||||||
recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
{{ recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }}
|
{{ recurringDepositsAccountProductTemplate?.isDormancyTrackingActive ? 'Active' : 'Inactive' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="preview-body">
|
<div class="preview-body">
|
||||||
@@ -485,8 +483,8 @@
|
|||||||
<div class="preview-dorm-step__body">
|
<div class="preview-dorm-step__body">
|
||||||
<span class="preview-dorm-step__label">Inactive</span>
|
<span class="preview-dorm-step__label">Inactive</span>
|
||||||
<span class="preview-dorm-step__days">
|
<span class="preview-dorm-step__days">
|
||||||
@if (recurringDepositsAccountProductTemplate?.daysToInactiveFromProduct !== null) {
|
@if (recurringDepositsAccountProductTemplate?.daysToInactive != null) {
|
||||||
After {{ recurringDepositsAccountProductTemplate.daysToInactiveFromProduct }} days of no transaction
|
After {{ recurringDepositsAccountProductTemplate.daysToInactive }} days of no transaction
|
||||||
} @else {
|
} @else {
|
||||||
Not configured
|
Not configured
|
||||||
}
|
}
|
||||||
@@ -499,8 +497,8 @@
|
|||||||
<div class="preview-dorm-step__body">
|
<div class="preview-dorm-step__body">
|
||||||
<span class="preview-dorm-step__label">Dormant</span>
|
<span class="preview-dorm-step__label">Dormant</span>
|
||||||
<span class="preview-dorm-step__days">
|
<span class="preview-dorm-step__days">
|
||||||
@if (recurringDepositsAccountProductTemplate?.daysToDormancyFromProduct !== null) {
|
@if (recurringDepositsAccountProductTemplate?.daysToDormancy != null) {
|
||||||
{{ recurringDepositsAccountProductTemplate.daysToDormancyFromProduct }} days after inactive
|
{{ recurringDepositsAccountProductTemplate.daysToDormancy }} days after inactive
|
||||||
} @else {
|
} @else {
|
||||||
Not configured
|
Not configured
|
||||||
}
|
}
|
||||||
@@ -513,8 +511,8 @@
|
|||||||
<div class="preview-dorm-step__body">
|
<div class="preview-dorm-step__body">
|
||||||
<span class="preview-dorm-step__label">Escheat</span>
|
<span class="preview-dorm-step__label">Escheat</span>
|
||||||
<span class="preview-dorm-step__days">
|
<span class="preview-dorm-step__days">
|
||||||
@if (recurringDepositsAccountProductTemplate?.daysToEscheatFromProduct !== null) {
|
@if (recurringDepositsAccountProductTemplate?.daysToEscheat != null) {
|
||||||
{{ recurringDepositsAccountProductTemplate.daysToEscheatFromProduct }} days after dormant
|
{{ recurringDepositsAccountProductTemplate.daysToEscheat }} days after dormant
|
||||||
} @else {
|
} @else {
|
||||||
Not configured
|
Not configured
|
||||||
}
|
}
|
||||||
@@ -532,10 +530,10 @@
|
|||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="preview-dorm-flag"
|
class="preview-dorm-flag"
|
||||||
[class.preview-dorm-flag--on]="recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct"
|
[class.preview-dorm-flag--on]="recurringDepositsAccountProductTemplate?.isDormancyTrackingActive"
|
||||||
>
|
>
|
||||||
<i class="fas fa-eye"></i> Tracking
|
<i class="fas fa-eye"></i> Tracking
|
||||||
{{ recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }}
|
{{ recurringDepositsAccountProductTemplate?.isDormancyTrackingActive ? 'Active' : 'Inactive' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+5
-3
@@ -9,7 +9,7 @@
|
|||||||
$primary: #1353a0;
|
$primary: #1353a0;
|
||||||
|
|
||||||
.lp-container {
|
.lp-container {
|
||||||
max-width: 960px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 24px 16px;
|
padding: 24px 16px;
|
||||||
}
|
}
|
||||||
@@ -53,11 +53,11 @@ $primary: #1353a0;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-tab-body {
|
.mat-mdc-tab-body {
|
||||||
overflow: visible !important;
|
overflow: hidden visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-tab-body-content {
|
.mat-mdc-tab-body-content {
|
||||||
overflow: visible !important;
|
overflow: hidden visible !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,6 +69,8 @@ $primary: #1353a0;
|
|||||||
|
|
||||||
.lp-tab-content {
|
.lp-tab-content {
|
||||||
padding: 24px 16px;
|
padding: 24px 16px;
|
||||||
|
min-width: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lp-action-bar {
|
.lp-action-bar {
|
||||||
|
|||||||
+30
@@ -90,4 +90,34 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Product Classification + GL Account — combined section -->
|
||||||
|
<div class="lpd-section">
|
||||||
|
<div class="lpd-section__header">
|
||||||
|
<i class="fas fa-sitemap"></i>
|
||||||
|
<h3>Product Classification</h3>
|
||||||
|
</div>
|
||||||
|
<div class="lpd-grid">
|
||||||
|
<mat-form-field appearance="outline" class="lpd-field">
|
||||||
|
<mat-label>Product Type Group</mat-label>
|
||||||
|
<mat-select formControlName="productTypeGroup">
|
||||||
|
@for (group of productTypeGroups; track group.id) {
|
||||||
|
<mat-option [value]="group.id">{{ group.label }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
@if (loanProductDetailsForm.get('productTypeGroup')?.value) {
|
||||||
|
<mat-form-field appearance="outline" class="lpd-field">
|
||||||
|
<mat-label>GL Account ({{ glClassificationLabel }})</mat-label>
|
||||||
|
<mat-select formControlName="glAccount">
|
||||||
|
@for (acc of availableGlAccounts; track acc.id) {
|
||||||
|
<mat-option [value]="acc.id"> ({{ acc.glCode }}) {{ acc.name }} </mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
<mat-error>GL Account is required</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+137
@@ -68,3 +68,140 @@ $gray-900: #202124;
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GL Section
|
||||||
|
.lpd-gl-section {
|
||||||
|
border-top: 2px solid #e0f2fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-subtype-badge {
|
||||||
|
margin-left: auto;
|
||||||
|
padding: 3px 12px;
|
||||||
|
background: #eff6ff;
|
||||||
|
border: 1px solid #bfdbfe;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-body {
|
||||||
|
padding: 16px 24px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-hint {
|
||||||
|
font-size: 12.5px;
|
||||||
|
color: #64748b;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-cards {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
border: 2px solid #e2e8f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #fff;
|
||||||
|
transition:
|
||||||
|
border-color 0.15s,
|
||||||
|
box-shadow 0.15s,
|
||||||
|
background 0.15s;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #93c5fd;
|
||||||
|
background: #f8faff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--selected {
|
||||||
|
border-color: #2563eb !important;
|
||||||
|
background: #eff6ff !important;
|
||||||
|
box-shadow: 0 0 0 3px rgb(37 99 235 / 10%);
|
||||||
|
|
||||||
|
.lpd-gl-card__check {
|
||||||
|
opacity: 1;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-radio {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-card__icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&--asset {
|
||||||
|
background: #dcfce7;
|
||||||
|
color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--liability {
|
||||||
|
background: #fee2e2;
|
||||||
|
color: #b91c1c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-card__info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-card__title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-card__desc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-card__check {
|
||||||
|
font-size: 18px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-account-field {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 480px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpd-gl-type-badge {
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-left: 6px;
|
||||||
|
|
||||||
|
&--asset {
|
||||||
|
background: #dcfce7;
|
||||||
|
color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--liability {
|
||||||
|
background: #fee2e2;
|
||||||
|
color: #b91c1c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+74
-1
@@ -57,6 +57,68 @@ export class LoanProductDetailsStepComponent implements OnInit {
|
|||||||
startDate: this.loanProductsTemplate.startDate && new Date(this.loanProductsTemplate.startDate),
|
startDate: this.loanProductsTemplate.startDate && new Date(this.loanProductsTemplate.startDate),
|
||||||
closeDate: this.loanProductsTemplate.closeDate && new Date(this.loanProductsTemplate.closeDate)
|
closeDate: this.loanProductsTemplate.closeDate && new Date(this.loanProductsTemplate.closeDate)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mappingOptions = this.loanProductsTemplate?.accountingMappingOptions ?? {};
|
||||||
|
this.assetAccountOptions = mappingOptions.assetAccountOptions ?? [];
|
||||||
|
this.liabilityAccountOptions = mappingOptions.liabilityAccountOptions ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Product Classification data */
|
||||||
|
readonly productTypeGroups = [
|
||||||
|
{ id: 'loans', label: 'Loan' },
|
||||||
|
{ id: 'advanced', label: 'Advanced' }
|
||||||
|
];
|
||||||
|
|
||||||
|
readonly productTypeSubGroups: Record<string, { id: string; label: string }[]> = {
|
||||||
|
loans: [
|
||||||
|
{ id: 'emi', label: 'EMI' },
|
||||||
|
{ id: 'cc', label: 'CC (Cash Credit)' },
|
||||||
|
{ id: 'overdraft', label: 'Overdraft' },
|
||||||
|
{ id: 'warehouse', label: 'Warehouse' },
|
||||||
|
{ id: 'gold', label: 'Gold' }
|
||||||
|
],
|
||||||
|
advanced: [
|
||||||
|
{ id: 'fd', label: 'FD (Fixed Deposit)' },
|
||||||
|
{ id: 'rd', label: 'RD (Recurring Deposit)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Default GL per product type group */
|
||||||
|
private readonly glDefaultMap: Record<string, string> = {
|
||||||
|
loans: 'asset',
|
||||||
|
advanced: 'asset'
|
||||||
|
};
|
||||||
|
|
||||||
|
get availableSubGroups() {
|
||||||
|
const group = this.loanProductDetailsForm.get('productTypeGroup')?.value;
|
||||||
|
return group ? (this.productTypeSubGroups[group] ?? []) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get glOptions(): string[] {
|
||||||
|
return [
|
||||||
|
'asset',
|
||||||
|
'liability'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
assetAccountOptions: any[] = [];
|
||||||
|
liabilityAccountOptions: any[] = [];
|
||||||
|
|
||||||
|
get availableGlAccounts(): any[] {
|
||||||
|
const type = this.loanProductDetailsForm.get('glClassification')?.value;
|
||||||
|
if (type === 'asset') return this.assetAccountOptions;
|
||||||
|
if (type === 'liability') return this.liabilityAccountOptions;
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get glClassificationLabel(): string {
|
||||||
|
const type = this.loanProductDetailsForm.get('glClassification')?.value;
|
||||||
|
return type === 'liability' ? 'Liability' : 'Asset';
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedGroupLabel(): string {
|
||||||
|
const group = this.loanProductDetailsForm.get('productTypeGroup')?.value;
|
||||||
|
return this.productTypeGroups.find((g) => g.id === group)?.label ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
createLoanProductDetailsForm() {
|
createLoanProductDetailsForm() {
|
||||||
@@ -71,7 +133,18 @@ export class LoanProductDetailsStepComponent implements OnInit {
|
|||||||
],
|
],
|
||||||
description: [''],
|
description: [''],
|
||||||
startDate: [''],
|
startDate: [''],
|
||||||
closeDate: ['']
|
closeDate: [''],
|
||||||
|
productTypeGroup: [''],
|
||||||
|
productTypeSubGroup: [''],
|
||||||
|
glClassification: ['asset'],
|
||||||
|
glAccount: ['']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-set GL based on product type group
|
||||||
|
this.loanProductDetailsForm.get('productTypeGroup')?.valueChanges.subscribe((group: string) => {
|
||||||
|
const defaultVal = this.glDefaultMap[group] ?? 'asset';
|
||||||
|
this.loanProductDetailsForm.get('glClassification')?.setValue(defaultVal);
|
||||||
|
this.loanProductDetailsForm.get('glAccount')?.setValue('');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+132
-29
@@ -467,7 +467,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Against Interest Calculation Enabled</mat-label>
|
<mat-label>Against Interest Calculation Enabled</mat-label>
|
||||||
<mat-select formControlName="againstInterestCalculationEnabled">
|
<mat-select formControlName="againstInterestCalculationEnabled">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -499,7 +499,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-3">
|
||||||
<mat-label>Consider Debit Closing Amount For Against Clearing</mat-label>
|
<mat-label>Consider Debit Closing Amount For Against Clearing</mat-label>
|
||||||
<mat-select formControlName="considerDebitClosingAmountForAgainstClearing">
|
<mat-select formControlName="considerDebitClosingAmountForAgainstClearing">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -517,7 +517,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Penalty Interest Calculation Enabled</mat-label>
|
<mat-label>Penalty Interest Calculation Enabled</mat-label>
|
||||||
<mat-select formControlName="penaltyInterestCalculationEnabled">
|
<mat-select formControlName="penaltyInterestCalculationEnabled">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -525,7 +525,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Penalty Interest Calculation Method</mat-label>
|
<mat-label>Penalty Interest Calculation Method</mat-label>
|
||||||
<mat-select formControlName="penaltyInterestCalculationMethod">
|
<mat-select formControlName="penaltyInterestCalculationMethod">
|
||||||
<mat-option value="Daywise ( 36500 )">Daywise ( 36500 )</mat-option>
|
<mat-option value="Daywise ( 36500 )">Daywise ( 36500 )</mat-option>
|
||||||
@@ -559,7 +559,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Interest On Credit Balance</mat-label>
|
<mat-label>Interest On Credit Balance</mat-label>
|
||||||
<mat-select formControlName="interestOnCreditBalance">
|
<mat-select formControlName="interestOnCreditBalance">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -567,7 +567,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Penalty Interest Date Different Than Regular Interest</mat-label>
|
<mat-label>Penalty Interest Date Different Than Regular Interest</mat-label>
|
||||||
<mat-select formControlName="penaltyInterestDateDifferentThanRegularInterest">
|
<mat-select formControlName="penaltyInterestDateDifferentThanRegularInterest">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -575,7 +575,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Fixed Deposit Overdraft Certificate Sort Order</mat-label>
|
<mat-label>Fixed Deposit Overdraft Certificate Sort Order</mat-label>
|
||||||
<mat-select formControlName="fixedDepositOverdraftCertificateSortOrder">
|
<mat-select formControlName="fixedDepositOverdraftCertificateSortOrder">
|
||||||
<mat-option value="ORDER ON INTEREST RATE">Order on Interest Rate</mat-option>
|
<mat-option value="ORDER ON INTEREST RATE">Order on Interest Rate</mat-option>
|
||||||
@@ -599,12 +599,12 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Minimum Normal Interest Amount</mat-label>
|
<mat-label>Minimum Normal Interest Amount</mat-label>
|
||||||
<input type="number" matInput formControlName="minimumNormalInterestAmount" />
|
<input type="number" matInput formControlName="minimumNormalInterestAmount" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Minimum Interest Calculation Method</mat-label>
|
<mat-label>Minimum Interest Calculation Method</mat-label>
|
||||||
<mat-select formControlName="minimumInterestCalculationMethod">
|
<mat-select formControlName="minimumInterestCalculationMethod">
|
||||||
<mat-option value="Flat Amount">Flat Amount</mat-option>
|
<mat-option value="Flat Amount">Flat Amount</mat-option>
|
||||||
@@ -613,12 +613,12 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Minimum Against Clearing Interest Amount</mat-label>
|
<mat-label>Minimum Against Clearing Interest Amount</mat-label>
|
||||||
<input type="number" matInput formControlName="minimumAgainstClearingInterestAmount" />
|
<input type="number" matInput formControlName="minimumAgainstClearingInterestAmount" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Limit Increase Upto Interest Amount In Interest Calculation</mat-label>
|
<mat-label>Limit Increase Upto Interest Amount In Interest Calculation</mat-label>
|
||||||
<mat-select formControlName="limitIncreaseUptoInterestAmountInInterestCalculation">
|
<mat-select formControlName="limitIncreaseUptoInterestAmountInInterestCalculation">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -626,22 +626,22 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Minimum Penalty Interest Amount</mat-label>
|
<mat-label>Minimum Penalty Interest Amount</mat-label>
|
||||||
<input type="number" matInput formControlName="minimumPenaltyInterestAmount" />
|
<input type="number" matInput formControlName="minimumPenaltyInterestAmount" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Days For Limit Increase Upto Interest Amount</mat-label>
|
<mat-label>Days For Limit Increase Upto Interest Amount</mat-label>
|
||||||
<input type="number" matInput formControlName="daysForLimitIncreaseUptoInterestAmount" />
|
<input type="number" matInput formControlName="daysForLimitIncreaseUptoInterestAmount" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Additional Interest Rate For Fixed Deposit Overdraft</mat-label>
|
<mat-label>Additional Interest Rate For Fixed Deposit Overdraft</mat-label>
|
||||||
<input type="number" matInput formControlName="additionalInterestRateForFixedDepositOverdraft" />
|
<input type="number" matInput formControlName="additionalInterestRateForFixedDepositOverdraft" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Consider DD/Payorder In Against Clearing Interest Calculation</mat-label>
|
<mat-label>Consider DD/Payorder In Against Clearing Interest Calculation</mat-label>
|
||||||
<mat-select formControlName="considerDdPayorderInAgainstClearingInterestCalculation">
|
<mat-select formControlName="considerDdPayorderInAgainstClearingInterestCalculation">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -649,12 +649,12 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Grace Days After Renewal Date</mat-label>
|
<mat-label>Grace Days After Renewal Date</mat-label>
|
||||||
<input type="number" matInput formControlName="graceDaysAfterRenewDate" />
|
<input type="number" matInput formControlName="graceDaysAfterRenewDate" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Apply Undebited Interest Concept Enabled</mat-label>
|
<mat-label>Apply Undebited Interest Concept Enabled</mat-label>
|
||||||
<mat-select formControlName="applyUndebitedInterestConceptEnabled">
|
<mat-select formControlName="applyUndebitedInterestConceptEnabled">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -671,12 +671,12 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Normal Grace Days After Overdue</mat-label>
|
<mat-label>Normal Grace Days After Overdue</mat-label>
|
||||||
<input type="number" matInput formControlName="normalGraceDaysAfterOverdue" />
|
<input type="number" matInput formControlName="normalGraceDaysAfterOverdue" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Allow Against Clearing Entry</mat-label>
|
<mat-label>Allow Against Clearing Entry</mat-label>
|
||||||
<mat-select formControlName="allowAgainstClearingEntry">
|
<mat-select formControlName="allowAgainstClearingEntry">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -684,7 +684,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Allow No Balance Entry</mat-label>
|
<mat-label>Allow No Balance Entry</mat-label>
|
||||||
<mat-select formControlName="allowNoBalanceEntry">
|
<mat-select formControlName="allowNoBalanceEntry">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -692,7 +692,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Fixed Deposit Overdraft Penalty Interest Combined With Normal Interest</mat-label>
|
<mat-label>Fixed Deposit Overdraft Penalty Interest Combined With Normal Interest</mat-label>
|
||||||
<mat-select formControlName="fixedDepositOverdraftPenaltyInterestCombinedWithNormalInterest">
|
<mat-select formControlName="fixedDepositOverdraftPenaltyInterestCombinedWithNormalInterest">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -700,7 +700,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Allow No Balance Entry With Password</mat-label>
|
<mat-label>Allow No Balance Entry With Password</mat-label>
|
||||||
<mat-select formControlName="allowNoBalanceEntryWithPassword">
|
<mat-select formControlName="allowNoBalanceEntryWithPassword">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -708,7 +708,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Undebited Simple Interest Account Wise</mat-label>
|
<mat-label>Undebited Simple Interest Account Wise</mat-label>
|
||||||
<mat-select formControlName="undebitedSimpleInterestAccountWise">
|
<mat-select formControlName="undebitedSimpleInterestAccountWise">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -716,7 +716,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Daily Interest Calculation Enabled</mat-label>
|
<mat-label>Daily Interest Calculation Enabled</mat-label>
|
||||||
<mat-select formControlName="dailyInterestCalculationEnabled">
|
<mat-select formControlName="dailyInterestCalculationEnabled">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -724,12 +724,12 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Daily Interest Calculation Start Date</mat-label>
|
<mat-label>Daily Interest Calculation Start Date</mat-label>
|
||||||
<input type="text" matInput formControlName="dailyInterestCalculationStartDate" />
|
<input type="text" matInput formControlName="dailyInterestCalculationStartDate" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Non Performing Asset Penal Interest Rate Update</mat-label>
|
<mat-label>Non Performing Asset Penal Interest Rate Update</mat-label>
|
||||||
<mat-select formControlName="nonPerformingAssetPenalInterestRateUpdate">
|
<mat-select formControlName="nonPerformingAssetPenalInterestRateUpdate">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -737,12 +737,12 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Non Performing Asset Penal Interest Rate</mat-label>
|
<mat-label>Non Performing Asset Penal Interest Rate</mat-label>
|
||||||
<input type="number" matInput formControlName="nonPerformingAssetPenalInterestRate" />
|
<input type="number" matInput formControlName="nonPerformingAssetPenalInterestRate" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Fixed Deposit Overdraft Penal Interest Rate Update In Branch Process</mat-label>
|
<mat-label>Fixed Deposit Overdraft Penal Interest Rate Update In Branch Process</mat-label>
|
||||||
<mat-select formControlName="fixedDepositOverdraftPenalInterestRateUpdateInBranchProcess">
|
<mat-select formControlName="fixedDepositOverdraftPenalInterestRateUpdateInBranchProcess">
|
||||||
<mat-option value="YES">YES</mat-option>
|
<mat-option value="YES">YES</mat-option>
|
||||||
@@ -750,13 +750,116 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
<mat-label>Add Penal Interest Rate</mat-label>
|
<mat-label>Add Penal Interest Rate</mat-label>
|
||||||
<input type="number" matInput formControlName="addPenalInterestRate" />
|
<input type="number" matInput formControlName="addPenalInterestRate" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- RBI Compliance & KFS Settings -->
|
||||||
|
<div class="lps-section">
|
||||||
|
<div class="lps-section__header">
|
||||||
|
<i class="fas fa-balance-scale lps-section__icon"></i>
|
||||||
|
<h3 class="lps-section__title">RBI Compliance & KFS Settings</h3>
|
||||||
|
</div>
|
||||||
|
<div class="lps-section__body lps-grid-4">
|
||||||
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
|
<mat-label>Penal Charge Method</mat-label>
|
||||||
|
<mat-select formControlName="penalChargeMethod">
|
||||||
|
<mat-option value="RBI COMPLIANT PENAL CHARGES">RBI Compliant Penal Charges (Non-Compounding)</mat-option>
|
||||||
|
<mat-option value="PENAL INTEREST">Legacy Penal Interest (Compounding)</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div class="lps-checkbox-row lps-span-2">
|
||||||
|
<mat-checkbox labelPosition="before" formControlName="isFloatingRate">
|
||||||
|
Is Floating Rate Loan? (Individual)
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
|
<mat-label>Foreclosure Charges Applicable</mat-label>
|
||||||
|
<mat-select formControlName="foreclosureChargesApplicable">
|
||||||
|
<mat-option value="YES">YES</mat-option>
|
||||||
|
<mat-option value="NO">NO</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
<mat-hint>RBI: No charges for Floating Rate Loans to Individuals</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div class="lps-checkbox-row">
|
||||||
|
<mat-checkbox labelPosition="before" formControlName="keyFactStatementEnabled">
|
||||||
|
Enable Key Fact Statement (KFS)
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lps-checkbox-row">
|
||||||
|
<mat-checkbox labelPosition="before" formControlName="annualPercentageRateCalculationEnabled">
|
||||||
|
Calculate APR
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Credit Bureau Settings (CIBIL) -->
|
||||||
|
<div class="lps-section">
|
||||||
|
<div class="lps-section__header">
|
||||||
|
<i class="fas fa-file-invoice lps-section__icon"></i>
|
||||||
|
<h3 class="lps-section__title">Credit Bureau Settings (CIBIL/Highmark)</h3>
|
||||||
|
</div>
|
||||||
|
<div class="lps-section__body lps-grid-4">
|
||||||
|
<mat-form-field appearance="outline" class="lps-full-width">
|
||||||
|
<mat-label>Loan Product Category (RBI Reporting)</mat-label>
|
||||||
|
<mat-select formControlName="loanProductCategory">
|
||||||
|
<mat-option value="PERSONAL_LOAN">Personal/Consumer Loan (Code 02)</mat-option>
|
||||||
|
<mat-option value="HOUSING_LOAN">Housing/Mortgage Loan (Code 15)</mat-option>
|
||||||
|
<mat-option value="BUSINESS_LOAN">Business/Commercial Loan (Code 50)</mat-option>
|
||||||
|
<mat-option value="VEHICLE_LOAN">Vehicle/Auto Loan (Code 04)</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
<mat-hint>Selecting a category will auto-fill the credit bureau account types below.</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
|
<mat-label>CIBIL Account Type</mat-label>
|
||||||
|
<input type="text" matInput formControlName="cibilAccountType" placeholder="e.g. 02" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
|
<mat-label>CIBIL Commercial Account Type</mat-label>
|
||||||
|
<input type="text" matInput formControlName="cibilCommercialAccountType" placeholder="e.g. 0420" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
|
<mat-label>Highmark Account Type</mat-label>
|
||||||
|
<input type="text" matInput formControlName="highmarkAccountType" placeholder="e.g. 02" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
|
<mat-label>Highmark Commercial Account Type</mat-label>
|
||||||
|
<input type="text" matInput formControlName="highmarkCommercialAccountType" placeholder="e.g. 0420" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
|
<mat-label>Experian Account Type</mat-label>
|
||||||
|
<input type="text" matInput formControlName="experianAccountType" placeholder="e.g. 02" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
|
<mat-label>Experian Commercial Account Type</mat-label>
|
||||||
|
<input type="text" matInput formControlName="experianCommercialAccountType" placeholder="e.g. 0420" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
|
<mat-label>Equifax Account Type</mat-label>
|
||||||
|
<input type="text" matInput formControlName="equifaxAccountType" placeholder="e.g. 02" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="lps-span-2">
|
||||||
|
<mat-label>Equifax Commercial Account Type</mat-label>
|
||||||
|
<input type="text" matInput formControlName="equifaxCommercialAccountType" placeholder="e.g. 0420" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Moratorium & Grace -->
|
<!-- Moratorium & Grace -->
|
||||||
<div class="lps-section">
|
<div class="lps-section">
|
||||||
<div class="lps-section__header">
|
<div class="lps-section__header">
|
||||||
|
|||||||
+8
@@ -75,6 +75,14 @@
|
|||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lps-span-2 {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lps-span-3 {
|
||||||
|
grid-column: span 3;
|
||||||
|
}
|
||||||
|
|
||||||
.lps-checkbox-row {
|
.lps-checkbox-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
+56
-1
@@ -408,13 +408,68 @@ export class LoanProductSettingsStepComponent implements OnInit {
|
|||||||
nonPerformingAssetPenalInterestRateUpdate: ['YES'],
|
nonPerformingAssetPenalInterestRateUpdate: ['YES'],
|
||||||
nonPerformingAssetPenalInterestRate: [''],
|
nonPerformingAssetPenalInterestRate: [''],
|
||||||
fixedDepositOverdraftPenalInterestRateUpdateInBranchProcess: ['YES'],
|
fixedDepositOverdraftPenalInterestRateUpdateInBranchProcess: ['YES'],
|
||||||
addPenalInterestRate: ['']
|
addPenalInterestRate: [''],
|
||||||
|
|
||||||
|
// Credit Bureau Settings (CIBIL)
|
||||||
|
cibilAccountType: [''],
|
||||||
|
cibilCommercialAccountType: [''],
|
||||||
|
highmarkAccountType: [''],
|
||||||
|
highmarkCommercialAccountType: [''],
|
||||||
|
experianAccountType: [''],
|
||||||
|
experianCommercialAccountType: [''],
|
||||||
|
equifaxAccountType: [''],
|
||||||
|
equifaxCommercialAccountType: [''],
|
||||||
|
|
||||||
|
// RBI Compliance & Product Category
|
||||||
|
loanProductCategory: [''],
|
||||||
|
penalChargeMethod: ['RBI COMPLIANT PENAL CHARGES'],
|
||||||
|
isFloatingRate: [false],
|
||||||
|
foreclosureChargesApplicable: ['NO'],
|
||||||
|
keyFactStatementEnabled: [true],
|
||||||
|
annualPercentageRateCalculationEnabled: [true]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private autoFillCibilFields(category: string) {
|
||||||
|
const cibilMappings: any = {
|
||||||
|
PERSONAL_LOAN: { ac: '02', comm: '0420' },
|
||||||
|
HOUSING_LOAN: { ac: '15', comm: '9004' },
|
||||||
|
BUSINESS_LOAN: { ac: '50', comm: '0100' },
|
||||||
|
VEHICLE_LOAN: { ac: '04', comm: '9004' }
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapping = cibilMappings[category];
|
||||||
|
if (mapping) {
|
||||||
|
this.loanProductSettingsForm.patchValue({
|
||||||
|
cibilAccountType: mapping.ac,
|
||||||
|
cibilCommercialAccountType: mapping.comm,
|
||||||
|
highmarkAccountType: mapping.ac,
|
||||||
|
highmarkCommercialAccountType: mapping.comm,
|
||||||
|
experianAccountType: mapping.ac,
|
||||||
|
experianCommercialAccountType: mapping.comm,
|
||||||
|
equifaxAccountType: mapping.ac,
|
||||||
|
equifaxCommercialAccountType: mapping.comm
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setConditionalControls() {
|
setConditionalControls() {
|
||||||
const allowAttributeOverrides = this.loanProductSettingsForm.get('allowAttributeOverrides');
|
const allowAttributeOverrides = this.loanProductSettingsForm.get('allowAttributeOverrides');
|
||||||
|
|
||||||
|
// RBI & CIBIL Category Listener
|
||||||
|
this.loanProductSettingsForm.get('loanProductCategory').valueChanges.subscribe((category: string) => {
|
||||||
|
if (category) {
|
||||||
|
this.autoFillCibilFields(category);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Floating Rate Logic (RBI: No foreclosure charges for floating rate loans to individuals)
|
||||||
|
this.loanProductSettingsForm.get('isFloatingRate').valueChanges.subscribe((isFloating: boolean) => {
|
||||||
|
if (isFloating) {
|
||||||
|
this.loanProductSettingsForm.patchValue({ foreclosureChargesApplicable: 'NO' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.loanProductSettingsForm.get('daysInYearType').valueChanges.subscribe((daysInYearType: any) => {
|
this.loanProductSettingsForm.get('daysInYearType').valueChanges.subscribe((daysInYearType: any) => {
|
||||||
if (this.isAdvancedTransactionProcessingStrategy) {
|
if (this.isAdvancedTransactionProcessingStrategy) {
|
||||||
this.useDaysInYearCustomStrategy = daysInYearType == 1;
|
this.useDaysInYearCustomStrategy = daysInYearType == 1;
|
||||||
|
|||||||
+87
@@ -264,6 +264,93 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Interest Rate Slabs Section -->
|
||||||
|
<div class="lpt-section">
|
||||||
|
<div class="lpt-section-header">
|
||||||
|
<i class="fas fa-table lpt-section-icon"></i>
|
||||||
|
<h3>Interest Rate Slabs</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lpt-slab-c">
|
||||||
|
<!-- Particular Name -->
|
||||||
|
<div class="lpt-slab-c-particular">
|
||||||
|
<label class="lpt-slab-c-particular-label">Particular</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="lpt-slab-c-name-input"
|
||||||
|
[formControl]="particularLabel"
|
||||||
|
placeholder="e.g. Consumer Loan"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 2-column slab grid (same as before, General + Special inside each card) -->
|
||||||
|
<div class="lpt-slab-c-grid">
|
||||||
|
@for (genGroup of slabGroups; track $index) {
|
||||||
|
<div class="lpt-slab-c-item">
|
||||||
|
<span class="lpt-slab-c-range">{{ interestSlabDefinitions[$index].label }}</span>
|
||||||
|
<div class="lpt-slab-c-rate-pair">
|
||||||
|
<!-- General -->
|
||||||
|
<div class="lpt-slab-c-rate-field" [formGroup]="genGroup">
|
||||||
|
<span class="lpt-slab-c-rate-label">Gen</span>
|
||||||
|
<div class="lpt-slab-c-input-wrap">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="lpt-slab-c-input"
|
||||||
|
formControlName="interestRate"
|
||||||
|
placeholder="0.00"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
<span class="lpt-slab-c-suffix">%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Special -->
|
||||||
|
<div class="lpt-slab-c-rate-field" [formGroup]="specialSlabGroups[$index]">
|
||||||
|
<span class="lpt-slab-c-rate-label lpt-slab-c-rate-label--special">Spl</span>
|
||||||
|
<div class="lpt-slab-c-input-wrap">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="lpt-slab-c-input lpt-slab-c-input--special"
|
||||||
|
formControlName="interestRate"
|
||||||
|
placeholder="0.00"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
<span class="lpt-slab-c-suffix">%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fixed Interest Rate row -->
|
||||||
|
<div class="lpt-slab-c-fixed" [formGroup]="fixedRateGroup">
|
||||||
|
<label class="lpt-slab-c-fixed-toggle">
|
||||||
|
<input type="checkbox" formControlName="hasFixed" class="lpt-slab-c-checkbox" />
|
||||||
|
<i class="fas fa-lock"></i>
|
||||||
|
<span>Fixed Interest Rate (%)</span>
|
||||||
|
</label>
|
||||||
|
@if (fixedRateGroup.get('hasFixed').value) {
|
||||||
|
<div class="lpt-slab-c-input-wrap">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="lpt-slab-c-input lpt-slab-c-input--fixed"
|
||||||
|
formControlName="fixedAmount"
|
||||||
|
placeholder="0.00"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
<span class="lpt-slab-c-suffix">%</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Repayment Schedule Section -->
|
<!-- Repayment Schedule Section -->
|
||||||
<div class="lpt-section">
|
<div class="lpt-section">
|
||||||
<div class="lpt-section-header">
|
<div class="lpt-section-header">
|
||||||
|
|||||||
+351
-29
@@ -6,14 +6,42 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// ─── Design tokens ────────────────────────────────────────────────────────────
|
||||||
|
$primary: #2563eb;
|
||||||
|
$primary-light: #eff6ff;
|
||||||
|
$primary-border: #bfdbfe;
|
||||||
|
$surface: #fff;
|
||||||
|
$surface-alt: #f8fafc;
|
||||||
|
$border: #e2e8f0;
|
||||||
|
$text-primary: #1e293b;
|
||||||
|
$text-secondary: #64748b;
|
||||||
|
$text-label: #475569;
|
||||||
|
$accent-green: #059669;
|
||||||
|
$accent-orange: #d97706;
|
||||||
|
$radius-card: 12px;
|
||||||
|
$radius-inner: 8px;
|
||||||
|
$shadow-card:
|
||||||
|
0 1px 3px rgb(0 0 0 / 7%),
|
||||||
|
0 1px 2px rgb(0 0 0 / 4%);
|
||||||
|
$shadow-hover: 0 4px 12px rgb(37 99 235 / 10%);
|
||||||
|
|
||||||
|
// ─── Card / Section ───────────────────────────────────────────────────────────
|
||||||
.lpt-section {
|
.lpt-section {
|
||||||
border: 1px solid #e2e8f0;
|
background: $surface;
|
||||||
border-radius: 10px;
|
border: 1px solid $border;
|
||||||
overflow: hidden;
|
border-radius: $radius-card;
|
||||||
background: #fff;
|
box-shadow: $shadow-card;
|
||||||
|
// No overflow:hidden here — it clips right-side content inside grids/tables.
|
||||||
|
// Border-radius is preserved; header gets matching top-radius instead.
|
||||||
|
overflow: visible;
|
||||||
|
transition: box-shadow 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: $shadow-hover;
|
||||||
|
}
|
||||||
|
|
||||||
& + & {
|
& + & {
|
||||||
margin-top: 16px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,34 +49,52 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 12px 24px;
|
padding: 14px 24px;
|
||||||
background: #f8f9fa;
|
background: linear-gradient(135deg, $surface-alt 0%, #f1f5f9 100%);
|
||||||
border-bottom: 1px solid #e8eaed;
|
border-bottom: 1px solid $border;
|
||||||
|
border-radius: $radius-card $radius-card 0 0;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
color: #202124;
|
color: $text-primary;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lpt-section-icon {
|
.lpt-section-icon {
|
||||||
color: #0ea5e9;
|
color: $primary;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: $primary-light;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid $primary-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lpt-help-icon {
|
.lpt-help-icon {
|
||||||
color: #9aa0a6;
|
color: $text-secondary;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
|
margin-left: auto;
|
||||||
|
transition: color 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Grid layout ──────────────────────────────────────────────────────────────
|
||||||
.lpt-grid {
|
.lpt-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 4px 16px;
|
gap: 8px 20px;
|
||||||
padding: 16px 24px;
|
padding: 20px 24px;
|
||||||
|
|
||||||
@media (width <= 600px) {
|
@media (width <= 600px) {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -58,6 +104,10 @@
|
|||||||
.lpt-grid-3 {
|
.lpt-grid-3 {
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
|
||||||
|
@media (width <= 768px) {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
@media (width <= 600px) {
|
@media (width <= 600px) {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
@@ -79,28 +129,42 @@
|
|||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Checkbox rows ────────────────────────────────────────────────────────────
|
||||||
.lpt-checkbox-row {
|
.lpt-checkbox-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4px 0;
|
padding: 6px 0;
|
||||||
|
|
||||||
|
mat-checkbox {
|
||||||
|
font-size: 13.5px;
|
||||||
|
color: $text-label;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Subsection ───────────────────────────────────────────────────────────────
|
||||||
.lpt-subsection {
|
.lpt-subsection {
|
||||||
padding: 0 24px 16px;
|
padding: 0 24px 20px;
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lpt-subsection-title {
|
.lpt-subsection-title {
|
||||||
font-size: 13px;
|
font-size: 12.5px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
color: #3c4043;
|
color: $text-secondary;
|
||||||
margin: 8px 0 12px;
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
margin: 12px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 3px;
|
||||||
|
height: 14px;
|
||||||
|
background: $primary;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lpt-subsection-header {
|
.lpt-subsection-header {
|
||||||
@@ -114,9 +178,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Misc ─────────────────────────────────────────────────────────────────────
|
||||||
.lpt-table-wrapper {
|
.lpt-table-wrapper {
|
||||||
border: 1px solid #e2e8f0;
|
border: 1px solid $border;
|
||||||
border-radius: 8px;
|
border-radius: $radius-inner;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
@@ -128,8 +193,265 @@
|
|||||||
.lpt-label-field {
|
.lpt-label-field {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 14px;
|
font-size: 13.5px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #3c4043;
|
color: $text-label;
|
||||||
padding: 8px 0;
|
padding: 8px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Interest Rate Slabs — Option C (2-column grid) ──────────────────────────
|
||||||
|
.lpt-slab-c {
|
||||||
|
padding: 16px 24px 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Particular row
|
||||||
|
.lpt-slab-c-particular {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
padding-bottom: 14px;
|
||||||
|
border-bottom: 1px solid $border;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-c-particular-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $text-secondary;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-c-name-input {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 320px;
|
||||||
|
padding: 7px 12px;
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-primary;
|
||||||
|
background: $surface;
|
||||||
|
outline: none;
|
||||||
|
transition:
|
||||||
|
border-color 0.15s,
|
||||||
|
box-shadow 0.15s;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #b0b8c4;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: $primary;
|
||||||
|
box-shadow: 0 0 0 3px rgb(37 99 235 / 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// General / Special tab toggle
|
||||||
|
.lpt-slab-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-tab {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 16px;
|
||||||
|
border: 1.5px solid $border;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: $surface;
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-secondary;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $primary;
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
background: $primary;
|
||||||
|
border-color: $primary;
|
||||||
|
color: $surface;
|
||||||
|
box-shadow: 0 2px 8px rgb(37 99 235 / 25%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2-column slab grid
|
||||||
|
.lpt-slab-c-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 8px 16px;
|
||||||
|
|
||||||
|
@media (width <= 540px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-c-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: $surface;
|
||||||
|
transition:
|
||||||
|
border-color 0.15s,
|
||||||
|
box-shadow 0.15s;
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border-color: $primary;
|
||||||
|
box-shadow: 0 0 0 3px rgb(37 99 235 / 8%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// General + Special pair inside each card
|
||||||
|
.lpt-slab-c-rate-pair {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-c-rate-field {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-c-rate-label {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $primary;
|
||||||
|
background: #eff6ff;
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&--special {
|
||||||
|
color: #92400e;
|
||||||
|
background: #fef9c3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-c-range {
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $text-label;
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input + suffix wrapper
|
||||||
|
.lpt-slab-c-input-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-c-input {
|
||||||
|
width: 70px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-primary;
|
||||||
|
text-align: right;
|
||||||
|
background: $surface-alt;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
appearance: textfield;
|
||||||
|
|
||||||
|
&::-webkit-outer-spin-button,
|
||||||
|
&::-webkit-inner-spin-button {
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: $primary;
|
||||||
|
background: $primary-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--fixed {
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-c-suffix {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $text-secondary;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&--special {
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-c-input--special {
|
||||||
|
&:focus {
|
||||||
|
border-color: #f59e0b;
|
||||||
|
background: #fef9c3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-c-error {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed Interest Rate row (below grid)
|
||||||
|
.lpt-slab-c-fixed {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin: 12px -24px -20px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 2px dashed $primary-border;
|
||||||
|
background: $primary-light;
|
||||||
|
border-radius: 0 0 $radius-card $radius-card;
|
||||||
|
padding-left: 24px;
|
||||||
|
padding-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-c-fixed-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $primary;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lpt-slab-c-checkbox {
|
||||||
|
accent-color: $primary;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|||||||
+82
-1
@@ -12,6 +12,9 @@ import {
|
|||||||
UntypedFormBuilder,
|
UntypedFormBuilder,
|
||||||
Validators,
|
Validators,
|
||||||
UntypedFormControl,
|
UntypedFormControl,
|
||||||
|
FormArray,
|
||||||
|
FormGroup,
|
||||||
|
FormControl,
|
||||||
ReactiveFormsModule
|
ReactiveFormsModule
|
||||||
} from '@angular/forms';
|
} from '@angular/forms';
|
||||||
import { ProcessingStrategyService } from '../../services/processing-strategy.service';
|
import { ProcessingStrategyService } from '../../services/processing-strategy.service';
|
||||||
@@ -54,6 +57,25 @@ export class LoanProductTermsStepComponent implements OnInit, OnChanges {
|
|||||||
repaymentFrequencyTypeData: any;
|
repaymentFrequencyTypeData: any;
|
||||||
repaymentStartDateTypeOptions: any;
|
repaymentStartDateTypeOptions: any;
|
||||||
|
|
||||||
|
readonly interestSlabDefinitions = [
|
||||||
|
{ label: 'Upto 10,000', maxAmount: 10000 },
|
||||||
|
{ label: 'Upto 25,000', maxAmount: 25000 },
|
||||||
|
{ label: 'Upto 1,00,000', maxAmount: 100000 },
|
||||||
|
{ label: 'Upto 2,00,000', maxAmount: 200000 },
|
||||||
|
{ label: 'Upto 5,00,000', maxAmount: 500000 },
|
||||||
|
{ label: 'Upto 10,00,000', maxAmount: 1000000 },
|
||||||
|
{ label: 'Upto 15,00,000', maxAmount: 1500000 },
|
||||||
|
{ label: 'Upto 20,00,000', maxAmount: 2000000 },
|
||||||
|
{ label: 'Upto 25,00,000', maxAmount: 2500000 },
|
||||||
|
{ label: 'More than 25,00,000', maxAmount: null }
|
||||||
|
];
|
||||||
|
|
||||||
|
interestRateSlabsArray: FormArray;
|
||||||
|
specialSlabsArray: FormArray;
|
||||||
|
fixedRateGroup: FormGroup;
|
||||||
|
particularLabel = new FormControl('');
|
||||||
|
activeSlabTab: 'general' | 'special' = 'general';
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.createLoanProductTermsForm();
|
this.createLoanProductTermsForm();
|
||||||
this.setConditionalControls();
|
this.setConditionalControls();
|
||||||
@@ -109,6 +131,35 @@ export class LoanProductTermsStepComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createLoanProductTermsForm() {
|
createLoanProductTermsForm() {
|
||||||
|
this.interestRateSlabsArray = new FormArray(
|
||||||
|
this.interestSlabDefinitions.map(
|
||||||
|
() =>
|
||||||
|
new FormGroup({
|
||||||
|
interestRate: new FormControl(null, [
|
||||||
|
Validators.min(0),
|
||||||
|
Validators.max(100)
|
||||||
|
])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.specialSlabsArray = new FormArray(
|
||||||
|
this.interestSlabDefinitions.map(
|
||||||
|
() =>
|
||||||
|
new FormGroup({
|
||||||
|
interestRate: new FormControl(null, [
|
||||||
|
Validators.min(0),
|
||||||
|
Validators.max(100)
|
||||||
|
])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.fixedRateGroup = new FormGroup({
|
||||||
|
hasFixed: new FormControl(false),
|
||||||
|
fixedAmount: new FormControl({ value: null, disabled: true }, [Validators.min(0)])
|
||||||
|
});
|
||||||
|
|
||||||
this.loanProductTermsForm = this.formBuilder.group({
|
this.loanProductTermsForm = this.formBuilder.group({
|
||||||
isLinkedToFloatingInterestRates: [false],
|
isLinkedToFloatingInterestRates: [false],
|
||||||
allowApprovedDisbursedAmountsOverApplied: [false],
|
allowApprovedDisbursedAmountsOverApplied: [false],
|
||||||
@@ -230,6 +281,15 @@ export class LoanProductTermsStepComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.fixedRateGroup.get('hasFixed').valueChanges.subscribe((hasFixed: boolean) => {
|
||||||
|
if (hasFixed) {
|
||||||
|
this.fixedRateGroup.get('fixedAmount').enable();
|
||||||
|
} else {
|
||||||
|
this.fixedRateGroup.get('fixedAmount').disable();
|
||||||
|
this.fixedRateGroup.get('fixedAmount').setValue(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.zeroInterest.valueChanges.subscribe((zeroInterest) => {
|
this.zeroInterest.valueChanges.subscribe((zeroInterest) => {
|
||||||
if (zeroInterest) {
|
if (zeroInterest) {
|
||||||
this.loanProductTermsForm.get('minInterestRatePerPeriod').patchValue(0);
|
this.loanProductTermsForm.get('minInterestRatePerPeriod').patchValue(0);
|
||||||
@@ -256,6 +316,18 @@ export class LoanProductTermsStepComponent implements OnInit, OnChanges {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get slabGroups(): FormGroup[] {
|
||||||
|
return this.interestRateSlabsArray.controls as FormGroup[];
|
||||||
|
}
|
||||||
|
|
||||||
|
get activeSlabGroups(): FormGroup[] {
|
||||||
|
return this.interestRateSlabsArray.controls as FormGroup[];
|
||||||
|
}
|
||||||
|
|
||||||
|
get specialSlabGroups(): FormGroup[] {
|
||||||
|
return this.specialSlabsArray.controls as FormGroup[];
|
||||||
|
}
|
||||||
|
|
||||||
get loanProductTerms() {
|
get loanProductTerms() {
|
||||||
const formValue = this.loanProductTermsForm.getRawValue();
|
const formValue = this.loanProductTermsForm.getRawValue();
|
||||||
// Normalize decimal separators: convert comma to dot for backend compatibility
|
// Normalize decimal separators: convert comma to dot for backend compatibility
|
||||||
@@ -270,7 +342,16 @@ export class LoanProductTermsStepComponent implements OnInit, OnChanges {
|
|||||||
...formValue,
|
...formValue,
|
||||||
minInterestRatePerPeriod: normalizeDecimal(formValue.minInterestRatePerPeriod),
|
minInterestRatePerPeriod: normalizeDecimal(formValue.minInterestRatePerPeriod),
|
||||||
interestRatePerPeriod: normalizeDecimal(formValue.interestRatePerPeriod),
|
interestRatePerPeriod: normalizeDecimal(formValue.interestRatePerPeriod),
|
||||||
maxInterestRatePerPeriod: normalizeDecimal(formValue.maxInterestRatePerPeriod)
|
maxInterestRatePerPeriod: normalizeDecimal(formValue.maxInterestRatePerPeriod),
|
||||||
|
interestRateSlabs: this.interestRateSlabsArray.getRawValue().map((slab, i) => ({
|
||||||
|
maxAmount: this.interestSlabDefinitions[i].maxAmount,
|
||||||
|
label: this.interestSlabDefinitions[i].label,
|
||||||
|
interestRate: slab.interestRate
|
||||||
|
})),
|
||||||
|
fixedRate: (() => {
|
||||||
|
const fr = this.fixedRateGroup.getRawValue();
|
||||||
|
return fr.hasFixed ? fr.fixedAmount : null;
|
||||||
|
})()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -564,6 +564,24 @@ export class ProductsService {
|
|||||||
return this.http.post('/fixeddepositproducts/baseinterestrates', payload);
|
return this.http.post('/fixeddepositproducts/baseinterestrates', payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns loan interest rate categories. Pass activeOnly=true to get only active ones. */
|
||||||
|
getLoanInterestRateCategories(activeOnly = false): Observable<any> {
|
||||||
|
const url = activeOnly
|
||||||
|
? '/loanproducts/interestratecategories?activeOnly=true'
|
||||||
|
: '/loanproducts/interestratecategories';
|
||||||
|
return this.http.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the active loan interest rate slab config (grid: loan type x amount range). */
|
||||||
|
getLoanIrSlabs(): Observable<any> {
|
||||||
|
return this.http.get('/loanproducts/interestrateslabs');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns all loan interest rate slab config versions. */
|
||||||
|
getAllLoanIrSlabs(): Observable<any> {
|
||||||
|
return this.http.get('/loanproducts/interestrateslabs/all');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<any>} Collateral data.
|
* @returns {Observable<any>} Collateral data.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { SavingsAccountSettingsStepComponent } from '../savings-account-stepper/
|
|||||||
import { SavingsService } from '../savings.service';
|
import { SavingsService } from '../savings.service';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
import { Dates } from 'app/core/utils/dates';
|
import { Dates } from 'app/core/utils/dates';
|
||||||
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
import {
|
import {
|
||||||
MatStepper,
|
MatStepper,
|
||||||
MatStepperIcon,
|
MatStepperIcon,
|
||||||
@@ -61,6 +62,7 @@ export class CreateSavingsAccountComponent {
|
|||||||
private dateUtils = inject(Dates);
|
private dateUtils = inject(Dates);
|
||||||
private savingsService = inject(SavingsService);
|
private savingsService = inject(SavingsService);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
|
private alertService = inject(AlertService);
|
||||||
|
|
||||||
/** Savings Account Template */
|
/** Savings Account Template */
|
||||||
savingsAccountTemplate: any;
|
savingsAccountTemplate: any;
|
||||||
@@ -110,6 +112,14 @@ export class CreateSavingsAccountComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
|
const detailsStep = this.savingsAccountDetailsStep;
|
||||||
|
if (detailsStep.selectedAccountStatus === 'JOINT' && detailsStep.totalJointSharePercentage !== 100) {
|
||||||
|
this.alertService.alert({
|
||||||
|
type: 'Error',
|
||||||
|
message: `Total ownership is ${detailsStep.totalJointSharePercentage}%. It must equal 100% before submitting.`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const locale = this.settingsService.language.code;
|
const locale = this.settingsService.language.code;
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
const monthDayFormat = 'dd MMMM';
|
const monthDayFormat = 'dd MMMM';
|
||||||
|
|||||||
+9
-3
@@ -22,6 +22,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
import { IntroducerSearchComponent } from 'app/shared/introducer-search/introducer-search.component';
|
import { IntroducerSearchComponent } from 'app/shared/introducer-search/introducer-search.component';
|
||||||
import { MatIcon } from '@angular/material/icon';
|
import { MatIcon } from '@angular/material/icon';
|
||||||
import { AddRelatedClientDialogComponent } from 'app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component';
|
import { AddRelatedClientDialogComponent } from 'app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component';
|
||||||
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Savings Account Details Step
|
* Savings Account Details Step
|
||||||
@@ -48,6 +49,7 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
|||||||
private dateUtils = inject(Dates);
|
private dateUtils = inject(Dates);
|
||||||
private cdr = inject(ChangeDetectorRef);
|
private cdr = inject(ChangeDetectorRef);
|
||||||
private dialog = inject(MatDialog);
|
private dialog = inject(MatDialog);
|
||||||
|
private alertService = inject(AlertService);
|
||||||
|
|
||||||
/** Savings Account Template */
|
/** Savings Account Template */
|
||||||
@Input() savingsAccountTemplate: any;
|
@Input() savingsAccountTemplate: any;
|
||||||
@@ -335,9 +337,13 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
ref.afterClosed().subscribe((result) => {
|
ref.afterClosed().subscribe((result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
const duplicate = this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif);
|
const isPrimary = this.primaryClientCif && this.primaryClientCif === result.clientCif;
|
||||||
|
const duplicate = isPrimary || this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif);
|
||||||
if (duplicate) {
|
if (duplicate) {
|
||||||
alert(`Account holder with CIF ${result.clientCif} is already added.`);
|
this.alertService.alert({
|
||||||
|
type: 'Error',
|
||||||
|
message: `Account holder with CIF ${result.clientCif} is already added.`
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.jointHolders = [
|
this.jointHolders = [
|
||||||
@@ -446,7 +452,7 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
|||||||
value.guardianName = this.guardianName;
|
value.guardianName = this.guardianName;
|
||||||
value.guardianCifNumber = this.guardianCif;
|
value.guardianCifNumber = this.guardianCif;
|
||||||
value.guardianRelationship = this.guardianRelationship;
|
value.guardianRelationship = this.guardianRelationship;
|
||||||
value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'yyyy-MM-dd') : null;
|
value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'dd MMMM yyyy') : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -290,15 +290,15 @@ $surface: #f8fafc;
|
|||||||
// ── Metric Cards ─────────────────────────────────────────────────
|
// ── Metric Cards ─────────────────────────────────────────────────
|
||||||
&__cards {
|
&__cards {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(5, 1fr);
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||||
border-bottom: 1px solid $border;
|
border-bottom: 1px solid $border;
|
||||||
|
|
||||||
&--product {
|
&--product {
|
||||||
grid-template-columns: repeat(6, 1fr);
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width <= 1024px) {
|
@media (width <= 1024px) {
|
||||||
grid-template-columns: repeat(3, 1fr) !important;
|
grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
|
||||||
|
|
||||||
.dap__card:nth-child(3n) {
|
.dap__card:nth-child(3n) {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
@@ -306,7 +306,7 @@ $surface: #f8fafc;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (width <= 600px) {
|
@media (width <= 600px) {
|
||||||
grid-template-columns: repeat(2, 1fr) !important;
|
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
|
||||||
|
|
||||||
.dap__card:nth-child(2n) {
|
.dap__card:nth-child(2n) {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
@@ -321,6 +321,7 @@ $surface: #f8fafc;
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
transition: background 0.15s;
|
transition: background 0.15s;
|
||||||
|
min-width: 0; // allow text to wrap within grid cell
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
@@ -340,6 +341,9 @@ $surface: #f8fafc;
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__card-hint {
|
&__card-hint {
|
||||||
@@ -353,6 +357,7 @@ $surface: #f8fafc;
|
|||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
color: $subtle;
|
color: $subtle;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $c-low;
|
color: $c-low;
|
||||||
|
|||||||
Reference in New Issue
Block a user