From fbaec18552ef30de5f1ca5cc288f1ca45a74e74c Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Fri, 24 Apr 2026 16:24:28 +0530 Subject: [PATCH 1/3] update code account saving,current,RD --- .../create-current-account.component.html | 96 +-- .../create-current-account.component.scss | 3 + .../create-current-account.component.ts | 81 +-- ...rrent-account-currency-step.component.html | 56 +- ...rrent-account-currency-step.component.scss | 14 + ...current-account-currency-step.component.ts | 46 +- ...urrent-account-details-step.component.html | 12 +- ...urrent-account-details-step.component.scss | 60 +- .../current-account-details-step.component.ts | 26 +- ...urrent-account-digital-step.component.html | 408 +++++------ ...urrent-account-digital-step.component.scss | 319 ++++---- .../current-account-digital-step.component.ts | 27 +- ...urrent-account-preview-step.component.html | 273 ------- .../current-account-term-step.component.html | 8 +- ...-recurring-deposits-account.component.html | 80 +- ...-recurring-deposits-account.component.scss | 12 + ...te-recurring-deposits-account.component.ts | 75 +- ...osits-account-currency-step.component.html | 82 +++ ...osits-account-currency-step.component.scss | 141 ++++ ...eposits-account-currency-step.component.ts | 60 ++ ...posits-account-details-step.component.html | 103 +-- ...posits-account-details-step.component.scss | 60 +- ...deposits-account-details-step.component.ts | 26 +- ...nt-interest-rate-chart-step.component.html | 11 - ...posits-account-preview-step.component.html | 386 ---------- ...deposit-product-currency-step.component.ts | 16 +- .../create-savings-account.component.html | 120 +-- .../create-savings-account.component.scss | 3 + .../create-savings-account.component.ts | 59 +- ...vings-account-currency-step.component.html | 6 + ...savings-account-currency-step.component.ts | 14 +- ...avings-account-details-step.component.html | 17 +- ...avings-account-details-step.component.scss | 2 +- .../savings-account-details-step.component.ts | 15 +- ...avings-account-digital-step.component.html | 408 +++++------ ...avings-account-digital-step.component.scss | 319 ++++---- .../savings-account-digital-step.component.ts | 27 +- ...avings-account-preview-step.component.html | 303 -------- .../savings-account-term-step.component.html | 8 +- .../dormancy-alert-panel.component.html | 308 ++++++++ .../dormancy-alert-panel.component.scss | 684 ++++++++++++++++++ .../dormancy-alert-panel.component.ts | 195 +++++ .../introducer-search.component.html | 130 ++++ .../introducer-search.component.scss | 316 ++++++++ .../introducer-search.component.ts | 138 ++++ .../nominee-form/nominee-form.component.html | 282 ++++++++ .../nominee-form/nominee-form.component.scss | 387 ++++++++++ .../nominee-form/nominee-form.component.ts | 254 +++++++ src/environments/.env.ts | 5 +- 49 files changed, 4221 insertions(+), 2260 deletions(-) create mode 100644 src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.html create mode 100644 src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.scss create mode 100644 src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.ts create mode 100644 src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.html create mode 100644 src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.scss create mode 100644 src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.ts create mode 100644 src/app/shared/introducer-search/introducer-search.component.html create mode 100644 src/app/shared/introducer-search/introducer-search.component.scss create mode 100644 src/app/shared/introducer-search/introducer-search.component.ts create mode 100644 src/app/shared/nominee-form/nominee-form.component.html create mode 100644 src/app/shared/nominee-form/nominee-form.component.scss create mode 100644 src/app/shared/nominee-form/nominee-form.component.ts diff --git a/src/app/current-accounts/create-current-account/create-current-account.component.html b/src/app/current-accounts/create-current-account/create-current-account.component.html index 9b5c5211b..1f07c596b 100644 --- a/src/app/current-accounts/create-current-account/create-current-account.component.html +++ b/src/app/current-accounts/create-current-account/create-current-account.component.html @@ -35,7 +35,7 @@ - + {{ 'labels.inputs.DETAILS' | translate }} - - - CURRENCY + @if (currentAccountProductTemplate) { + + + CURRENCY - - - - - - {{ 'labels.inputs.TERMS' | translate }} - - - - - - - SETTINGS - - - - - - - DIGITAL - - - - - - - {{ 'labels.inputs.CHARGES' | translate }} - - - - - -
- -
-
+ > - - @if (currentAccountFormValid) { +
+ + +
+
+ + + + DIGITAL + + + +
+ + +
+
+ + {{ 'labels.inputs.PREVIEW' | translate }} { @@ -100,32 +94,17 @@ export class CreateCurrentAccountComponent { return this.currentAccountDetailsStep.currentAccountDetailsForm; } - /** - * Retrieves new term form (interest rate). - */ - get currentAccountTermForm() { - return this.currentAccountTermStep.currentAccountTermForm; + /** Stub forms needed by preview step inputs */ + get currentAccountTermForm(): FormGroup { + return new FormGroup({}); } - /** - * Retrieves settings form. - */ - get currentAccountSettingsForm() { - return this.currentAccountSettingsStep.currentAccountSettingsForm; + get currentAccountSettingsForm(): FormGroup { + return new FormGroup({}); } - /** - * Retrieves old terms form — still used for currencyCode by charges step. - */ - get currentAccountTermsForm() { - return this.currentAccountTermsStep.currentAccountTermsForm; - } - - /** - * Checks validity of overall current account form. - */ - get currentAccountFormValid() { - return this.currentAccountDetailsForm.valid && this.currentAccountTermForm.valid; + get currentAccountTermsForm(): FormGroup { + return new FormGroup({}); } /** @@ -133,10 +112,7 @@ export class CreateCurrentAccountComponent { */ get currentAccount() { return { - ...this.currentAccountDetailsStep.currentAccountDetails, - ...this.currentAccountTermStep.currentAccountTerms, - ...this.currentAccountSettingsStep.currentAccountSettings, - ...this.currentAccountChargesStep.currentAccountCharges + ...this.currentAccountDetailsStep.currentAccountDetails }; } @@ -149,15 +125,8 @@ export class CreateCurrentAccountComponent { const monthDayFormat = 'dd MMMM'; const currentAccount = { ...this.currentAccount, - charges: this.currentAccount.charges.map((charge: any) => ({ - chargeId: charge.id, - amount: charge.amount, - dueDate: charge.dueDate ? this.dateUtils.formatDate(charge.dueDate, dateFormat) : charge.dueDate, - feeOnMonthDay: charge.feeOnMonthDay - ? this.dateUtils.formatDate(charge.feeOnMonthDay, monthDayFormat) - : charge.feeOnMonthDay, - feeInterval: charge.feeInterval - })), + ...this.currentAccountCurrencyStep.currencyStepData, + charges: [], submittedOnDate: this.dateUtils.formatDate(this.currentAccount.submittedOnDate, dateFormat), dateFormat, monthDayFormat, diff --git a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.html index e9ba49bac..e790fd695 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.html @@ -26,6 +26,30 @@ +
+
+

Balance Swap Option

+

Allows balance to be swapped or transferred between linked accounts as per product configuration.

+
+ +
+ +
+
+

MSME

+

Account is categorized under the Micro, Small and Medium Enterprises segment.

+
+ +
+ +
+
+

Auto Debit Facility

+

Enable automatic debit instructions for recurring payments linked to this account.

+
+ +
+

Dormancy Alert

@@ -36,25 +60,7 @@
-
-
-

Balance Swap Option

-

Allows balance to be swapped or transferred between linked accounts as per product configuration.

-
- - {{ currentAccountProductTemplate?.balanceSwapOption ? 'Yes' : 'No' }} - -
- -
-
-

MSME

-

Account is categorized under the Micro, Small and Medium Enterprises segment.

-
- - {{ currentAccountProductTemplate?.msme ? 'Yes' : 'No' }} - -
+
@@ -66,14 +72,8 @@
-
-
-

Auto Debit Facility

-

Enable automatic debit instructions for recurring payments linked to this account.

-
- - {{ currentAccountProductTemplate?.autoDebitFacility ? 'Yes' : 'No' }} - -
+ @if (currentAccountProductTemplate?.nominationFacility) { + + }
diff --git a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.scss b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.scss index 17712a898..68c9a94a2 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.scss +++ b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.scss @@ -114,3 +114,17 @@ $gray-900: #202124; border-color: #bbf7d0; } } + +.sac-alert--editable { + border-color: #bae6fd; + background: #f0f9ff; + + &:hover { + border-color: #7dd3fc; + box-shadow: 0 2px 8px rgb(2 132 199 / 10%); + } + + h4 { + color: #0369a1; + } +} diff --git a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.ts b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.ts index 805c4336b..c3cae7dce 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.ts +++ b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.ts @@ -6,15 +6,55 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { NomineeFormComponent } from 'app/shared/nominee-form/nominee-form.component'; +import { DormancyAlertPanelComponent } from 'app/shared/dormancy-alert-panel/dormancy-alert-panel.component'; +import { MatSlideToggle } from '@angular/material/slide-toggle'; +import { FormsModule } from '@angular/forms'; @Component({ selector: 'mifosx-current-account-currency-step', templateUrl: './current-account-currency-step.component.html', styleUrls: ['./current-account-currency-step.component.scss'], - imports: [...STANDALONE_SHARED_IMPORTS] + imports: [ + ...STANDALONE_SHARED_IMPORTS, + NomineeFormComponent, + DormancyAlertPanelComponent, + MatSlideToggle, + FormsModule + ] }) -export class CurrentAccountCurrencyStepComponent { +export class CurrentAccountCurrencyStepComponent implements OnChanges { @Input() currentAccountProductTemplate: any; + + nomineeData: any = null; + + // Editable feature toggles (pre-filled from product, overridable by user) + balanceSwapOption = false; + msme = false; + autoDebitFacility = false; + + ngOnChanges(changes: SimpleChanges): void { + if (changes['currentAccountProductTemplate']?.currentValue) { + const pt = changes['currentAccountProductTemplate'].currentValue; + this.balanceSwapOption = !!pt.balanceSwapOption; + this.msme = !!pt.msme; + this.autoDebitFacility = !!pt.autoDebitFacility; + } + } + + onNomineeChange(event: any): void { + this.nomineeData = event; + } + + /** Called by parent to collect currency step values */ + get currencyStepData() { + return { + balanceSwapOption: this.balanceSwapOption, + msme: this.msme, + autoDebitFacility: this.autoDebitFacility, + ...(this.nomineeData ? { nominee: this.nomineeData } : {}) + }; + } } diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html index e5914fe18..440bf7b4f 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html @@ -7,7 +7,7 @@ -->
- +
@@ -45,9 +45,7 @@ {{ 'labels.commons.required' | translate }} - } - @if (currentAccountProductSelected) { {{ 'labels.inputs.Field Officer' | translate }} @@ -56,9 +54,7 @@ } - } - @if (currentAccountProductSelected) { {{ 'labels.inputs.External ID' | translate }} @@ -67,8 +63,8 @@
- @if (currentAccountProductSelected && productTemplate) { +
@@ -125,6 +121,10 @@
+ + @if (productTemplate?.introducerRequired) { + + } }
diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.scss b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.scss index 7c9847ae8..cd7492271 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.scss +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.scss @@ -15,7 +15,7 @@ $gray-900: #202124; .sad-section { border: 1px solid $border; border-radius: 10px; - overflow: hidden; + overflow: visible; background: #fff; &--mt { @@ -103,3 +103,61 @@ $gray-900: #202124; border-color: #bbf7d0; } } + +// ── Account Feature Toggles ──────────────────────────────────────────────── +.sad-toggles { + display: flex; + flex-direction: column; + gap: 10px; + padding: 4px 0; +} + +.sad-toggle-card { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 18px; + border: 1.5px solid #e2e8f0; + border-radius: 10px; + background: #fff; + transition: + border-color 0.2s, + box-shadow 0.2s; + + &:hover { + border-color: #93c5fd; + box-shadow: 0 1px 6px rgb(59 130 246 / 10%); + } + + &__left { + display: flex; + align-items: center; + gap: 12px; + + > i { + width: 36px; + height: 36px; + border-radius: 8px; + background: #f0f9ff; + border: 1px solid #bae6fd; + color: #0284c7; + display: flex; + align-items: center; + justify-content: center; + font-size: 15px; + flex: 0 0 auto; + } + } + + &__label { + font-size: 13.5px; + font-weight: 600; + color: #1e293b; + } + + &__hint { + font-size: 11.5px; + color: #64748b; + margin-top: 2px; + } +} diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts index c547fedcf..bacad5cdb 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts @@ -17,6 +17,7 @@ import { SettingsService } from 'app/settings/settings.service'; import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { IntroducerSearchComponent } from 'app/shared/introducer-search/introducer-search.component'; /** * Current Account Details Step @@ -29,7 +30,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; ...STANDALONE_SHARED_IMPORTS, MatStepperPrevious, FaIconComponent, - MatStepperNext + MatStepperNext, + IntroducerSearchComponent ] }) export class CurrentAccountDetailsStepComponent implements OnInit { @@ -56,6 +58,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit { currentAccountDetailsForm: UntypedFormGroup; currentAccountProductSelected = false; + introducerId: number | null = null; /** Current Account Template with product data */ @Output() currentAccountProductTemplate = new EventEmitter(); @@ -102,7 +105,10 @@ export class CurrentAccountDetailsStepComponent implements OnInit { Validators.required ], fieldOfficerId: [''], - externalId: [''] + externalId: [''], + balanceSwapOption: [false], + msme: [false], + autoDebitFacility: [false] }); } @@ -131,6 +137,12 @@ export class CurrentAccountDetailsStepComponent implements OnInit { this.currentAccountProductTemplate.emit(response); this.fieldOfficerData = response.fieldOfficerOptions; this.currentAccountProductSelected = true; + // Pre-populate toggles from product defaults (user can override) + this.currentAccountDetailsForm.patchValue({ + balanceSwapOption: !!response.balanceSwapOption, + msme: !!response.msme, + autoDebitFacility: !!response.autoDebitFacility + }); this.cdr.detectChanges(); if (!this.isFieldOfficerPatched && this.currentAccountTemplate.fieldOfficerId) { this.currentAccountDetailsForm.get('fieldOfficerId').patchValue(this.currentAccountTemplate.fieldOfficerId); @@ -166,7 +178,15 @@ export class CurrentAccountDetailsStepComponent implements OnInit { /** * Returns current account form value. */ + onIntroducerChange(event: { clientId: number; displayName: string } | null): void { + this.introducerId = event?.clientId ?? null; + } + get currentAccountDetails() { - return this.currentAccountDetailsForm.getRawValue(); + const value = this.currentAccountDetailsForm.getRawValue(); + if (this.introducerId) { + value.introducerId = this.introducerId; + } + return value; } } diff --git a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.html index 096e08a7b..5b0970996 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.html @@ -6,229 +6,209 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
-
- - - -
Transaction & Product Limits
-
FROM PRODUCT
+
+
+
+ +
+
+

Digital & Account Features

+

Toggle the facilities available for this account (as per RBI guidelines)

+
+ FROM PRODUCT
-
-
-
Account Features (as per RBI guidelines)
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+
+
+
+ ATM Facility + {{ facilityConfig.atmFacility ? 'Enabled' : 'Disabled' }}
+
- @if (accountFacilityLimits.length > 0) { -
- - - - - - - - - - - - - - - - - @for (row of accountFacilityLimits; track row.limitType) { - - - - - - - } - -
Limit TypeGeneral Limit (Daily)Minor Account LimitSmall Saving Limit
{{ row.limitType }}
+
+
+
+ ATM Withdrawal + {{ facilityConfig.atmWithdrawalFacility ? 'Enabled' : 'Disabled' }}
- } + +
- @if (depositLimitKyc?.general) { -
-

Deposit Limit (KYC)

- - - - - - - - - - - - - - - - - - - - - - - - - -
Account TypeKYC TypeLimit
General - -
Minor - -
Small Saving - -
+
+
+
+ UPI Facility + {{ facilityConfig.upiFacility ? 'Enabled' : 'Disabled' }}
- } + +
-
- Note: - These facilities are defined at product level and applied to this account. Small Saving Account limits are - strictly capped at Rs.1,00,000 maximum balance per RBI Master Circular. +
+
+
+ POS Transactions + {{ facilityConfig.posTransactionsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Cheque Book + {{ facilityConfig.chequeBook ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ IMPS Facility + {{ facilityConfig.impsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ SMS Alerts + {{ facilityConfig.smsAlerts ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ BBPS Facility + {{ facilityConfig.bbpsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Insurance Cover + {{ facilityConfig.insuranceCover ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ BEPG Facility + {{ facilityConfig.bepgFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ NEFT Facility + {{ facilityConfig.neftFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ RTGS Facility + {{ facilityConfig.rtgsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Inter-Bank Total Cap + {{ facilityConfig.interBankTotalCapFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Withdrawal Facility + {{ facilityConfig.withdrawalFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Deposit Facility (KYC) + {{ facilityConfig.depositFacilityKyc ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Virtual Debit Card + {{ facilityConfig.virtualDebitCard ? 'Enabled' : 'Disabled' }} +
+
diff --git a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.scss b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.scss index 7b58a1abb..038aa7fc7 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.scss +++ b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.scss @@ -6,194 +6,205 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -$border: #d9e2ec; -$border-strong: #c7d2de; -$text: #24364b; +$blue: #2563eb; +$blue-light: #eff6ff; +$green: #16a34a; +$green-light: #f0fdf4; +$green-border: #bbf7d0; +$border: #e2e8f0; +$text: #1e293b; $muted: #64748b; -$header-bg: #f8fafc; +$gray-bg: #f8fafc; -.spl-section { +// ── Wrapper ────────────────────────────────────────────── +.dig-wrapper { border: 1px solid $border; - border-radius: 12px; + border-radius: 14px; overflow: hidden; background: #fff; + box-shadow: 0 1px 4px rgb(0 0 0 / 6%); } -.spl-section__header { +// ── Header ─────────────────────────────────────────────── +.dig-header { display: flex; align-items: center; - gap: 12px; - padding: 16px 20px; - background: $header-bg; + gap: 14px; + padding: 18px 24px; + background: $gray-bg; border-bottom: 1px solid $border; -} -.spl-section__icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - border-radius: 8px; - background: #eff6ff; - color: #2563eb; - font-size: 14px; - flex: 0 0 auto; -} - -.spl-section__title { - flex: 1 1 auto; - font-size: 16px; - font-weight: 600; - color: $text; -} - -.spl-section__badge { - flex: 0 0 auto; - padding: 5px 10px; - border-radius: 999px; - background: #dbeafe; - color: #1d4ed8; - font-size: 10px; - font-weight: 700; - letter-spacing: 0.05em; - white-space: nowrap; -} - -.spl-section__body { - padding: 18px 20px 20px; -} - -.spl-table-wrap { - overflow-x: auto; - margin-top: 16px; -} - -.spl-table { - width: 100%; - min-width: 0; - table-layout: fixed; - border-collapse: collapse; - border: 1px solid $border; - - col.spl-table__col--label { - width: 22%; + &__icon { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 10px; + background: $blue-light; + color: $blue; + font-size: 16px; + flex: 0 0 auto; } - col.spl-table__col--value { - width: 26%; + &__text { + flex: 1; } - th, - td { - border: 1px solid $border; - padding: 6px 8px; - vertical-align: middle; - } - - th { - background: #f8fafc; - color: #64748b; - text-align: center; - font-size: 11px; + &__title { + margin: 0 0 2px; + font-size: 15px; font-weight: 700; - } - - td:first-child { - background: #f8fafc; - font-weight: 600; color: $text; - white-space: nowrap; } - td { - font-size: 13px; - overflow: hidden; + &__sub { + margin: 0; + font-size: 12px; + color: $muted; + } + + &__badge { + flex: 0 0 auto; + padding: 4px 12px; + border-radius: 999px; + background: #dbeafe; + color: $blue; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.06em; } } -.spl-input, -.spl-select { - width: 100%; - box-sizing: border-box; - font: inherit; -} - -.spl-input { - height: 30px; - border: 1px solid $border-strong; - border-radius: 2px; - padding: 0 8px; - background: #f8fafc; - color: $muted; -} - -.spl-select { - height: 30px; - border: 1px solid $border-strong; - border-radius: 2px; - padding: 0 6px; - background: #f8fafc; - color: $muted; -} - -.spl-features { - margin-bottom: 16px; -} - -.spl-features__header { - font-size: 13px; - font-weight: 600; - color: $muted; - margin-bottom: 12px; - text-transform: uppercase; - letter-spacing: 0.04em; -} - -.spl-features__grid { +// ── Grid ───────────────────────────────────────────────── +.dig-grid { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 12px 16px; + grid-template-columns: repeat(3, 1fr); + gap: 0; - @media (width <= 900px) { - grid-template-columns: repeat(2, minmax(0, 1fr)); + @media (width <= 960px) { + grid-template-columns: repeat(2, 1fr); } - @media (width <= 640px) { + @media (width <= 580px) { grid-template-columns: 1fr; } } -.spl-feature { +// ── Card ───────────────────────────────────────────────── +.dig-card { display: flex; - flex-direction: column; - gap: 6px; - font-size: 13px; - color: $text; -} - -.spl-feature__label { - display: inline-flex; align-items: center; - gap: 8px; - font-weight: 600; - color: #334155; + gap: 12px; + padding: 14px 18px; + border-right: 1px solid $border; + border-bottom: 1px solid $border; + background: #fff; + transition: background 0.18s ease; + cursor: pointer; - i { - color: #2563eb; - font-size: 12px; - width: 14px; - text-align: center; + &:nth-child(3n) { + border-right: none; + } + + &--on { + background: $green-light; + + .dig-card__icon { + background: $green-border; + color: $green; + } + + .dig-card__status { + color: $green; + font-weight: 600; + } + } + + &__icon { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 9px; + background: $blue-light; + color: $blue; + font-size: 14px; + flex: 0 0 auto; + transition: + background 0.18s, + color 0.18s; + } + + &__body { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 2px; + } + + &__label { + font-size: 13px; + font-weight: 600; + color: $text; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &__status { + font-size: 11px; + color: $muted; + transition: color 0.18s; } } -.spl-note { - margin-top: 16px; - padding: 12px 14px; - border: 1px solid $border; - border-radius: 8px; - background: #f8fafc; - color: $muted; - font-size: 11px; - line-height: 1.6; +// ── Toggle Switch ───────────────────────────────────────── +.dig-toggle { + position: relative; + flex: 0 0 auto; + cursor: pointer; + + input { + position: absolute; + opacity: 0; + width: 0; + height: 0; + + &:checked + .dig-toggle__track { + background: $green; + border-color: $green; + } + + &:checked + .dig-toggle__track .dig-toggle__thumb { + transform: translateX(18px); + } + } + + &__track { + display: block; + width: 40px; + height: 22px; + border-radius: 999px; + background: #cbd5e1; + border: 1.5px solid #94a3b8; + position: relative; + transition: + background 0.2s, + border-color 0.2s; + } + + &__thumb { + position: absolute; + top: 2px; + left: 2px; + width: 16px; + height: 16px; + border-radius: 50%; + background: #fff; + box-shadow: 0 1px 3px rgb(0 0 0 / 20%); + transition: transform 0.2s ease; + } } diff --git a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.ts b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.ts index 9ffb1d278..321d9984d 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.ts +++ b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.ts @@ -39,31 +39,16 @@ const DEFAULT_FACILITY_CONFIG = { ] }) export class CurrentAccountDigitalStepComponent { - private _currentAccountProductTemplate: any; + facilityConfig = { ...DEFAULT_FACILITY_CONFIG }; @Input() set currentAccountProductTemplate(value: any) { - this._currentAccountProductTemplate = value; - } - get currentAccountProductTemplate(): any { - return this._currentAccountProductTemplate; + if (value?.accountFacilityConfig) { + this.facilityConfig = { ...DEFAULT_FACILITY_CONFIG, ...value.accountFacilityConfig }; + } } - get accountFacilityConfig() { - return this._currentAccountProductTemplate?.accountFacilityConfig || DEFAULT_FACILITY_CONFIG; - } - - get accountFacilityLimits(): any[] { - return this._currentAccountProductTemplate?.accountFacilityLimits || []; - } - - get depositLimitKyc() { - return ( - this._currentAccountProductTemplate?.depositLimitKyc || { - general: { kycType: '', limit: '' }, - minor: { kycType: '', limit: '' }, - smallSaving: { kycType: '', limit: '' } - } - ); + get currentAccountDigital() { + return { accountFacilityConfig: { ...this.facilityConfig } }; } } diff --git a/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.html index 5cf37b020..3a893481d 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.html @@ -218,174 +218,6 @@
- -
-
- {{ 'labels.heading.Terms' | translate }} -
-
-
-
- Interest Posting Frequency - {{ - termPreview?.interestPostingPeriodType - | find: currentAccountProductTemplate?.interestPostingPeriodTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
-
- Auto Interest Post - - {{ termPreview?.autoPost | yesNo }} - -
- @if (termPreview?.frequencyFixDay !== undefined && termPreview?.frequencyFixDay !== null) { -
- Frequency Fix Day - {{ termPreview?.frequencyFixDay }} Days -
- } - @if (termPreview?.lastInterestDate) { -
- Last Interest Date - {{ termPreview?.lastInterestDate | dateFormat }} -
- } -
-
-
- - -
-
- {{ 'labels.heading.Settings' | translate }} -
-
-
- @if ( - settingsPreview?.minRequiredOpeningBalance !== undefined && - settingsPreview?.minRequiredOpeningBalance !== null && - settingsPreview?.minRequiredOpeningBalance !== '' - ) { -
- {{ 'labels.inputs.Minimum Opening Balance' | translate }} - {{ settingsPreview?.minRequiredOpeningBalance | formatNumber }} -
- } -
- {{ 'labels.inputs.Enforce Minimum Balance' | translate }} - - {{ settingsPreview?.enforceMinRequiredBalance | yesNo }} - -
- @if ( - settingsPreview?.minRequiredBalance !== undefined && - settingsPreview?.minRequiredBalance !== null && - settingsPreview?.minRequiredBalance !== '' - ) { -
- {{ 'labels.inputs.Minimum Balance' | translate }} - {{ settingsPreview?.minRequiredBalance | formatNumber }} -
- } -
- Balance Frequency - {{ settingsPreview?.balanceFrequency || 'Unassigned' }} -
-
- {{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }} - - {{ settingsPreview?.withdrawalFeeForTransfers | yesNo }} - -
-
- Enable Lock-in Period - - {{ settingsPreview?.enableLockinPeriod || !!settingsPreview?.lockinPeriodFrequency | yesNo }} - -
- @if (settingsPreview?.lockinPeriodFrequency) { -
- {{ 'labels.inputs.Lock-in Period' | translate }} - - {{ settingsPreview?.lockinPeriodFrequency }} - {{ - settingsPreview?.lockinPeriodFrequencyType - | find: currentAccountProductTemplate?.lockinPeriodFrequencyTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} - -
- } -
- Account Closed Zero Balance In Day Closing - - {{ settingsPreview?.accountClosedZeroBalanceInDayClosing | yesNo }} - -
-
- {{ 'labels.inputs.Is Overdraft Allowed' | translate }} - - {{ settingsPreview?.allowOverdraft | yesNo }} - -
- @if (settingsPreview?.allowOverdraft) { -
- Avg Balance Calc Period - {{ settingsPreview?.avgBalanceCalcPeriod || 'Unassigned' }} -
-
- Valid For Period - {{ settingsPreview?.validForPeriod || 'Unassigned' }} -
- @if ( - settingsPreview?.overdraftLimit !== undefined && - settingsPreview?.overdraftLimit !== null && - settingsPreview?.overdraftLimit !== '' - ) { -
- OD Limit % - {{ settingsPreview?.overdraftLimit | formatNumber }} -
- } - @if ( - settingsPreview?.nominalAnnualInterestRateOverdraft !== undefined && - settingsPreview?.nominalAnnualInterestRateOverdraft !== null && - settingsPreview?.nominalAnnualInterestRateOverdraft !== '' - ) { -
- Interest on OD % - {{ - settingsPreview?.nominalAnnualInterestRateOverdraft | formatNumber - }} -
- } -
- OD Account Closed Zero Balance In Day Closing - - {{ settingsPreview?.odAccountClosedZeroBalanceInDayClosing | yesNo }} - -
- } -
-
-
- @if (hasDigitalSection) {
@@ -405,111 +237,6 @@
}
- - @if (digitalPreview?.accountFacilityLimits?.length) { - - - - - - - - - - - @for (row of digitalPreview.accountFacilityLimits; track row.limitType) { - - - - - - - } - -
Limit TypeGeneral LimitMinor Account LimitSmall Saving Limit
{{ row.limitType }}{{ row.generalLimit || 'Unassigned' }}{{ row.minorAccountLimit || 'Unassigned' }}{{ row.smallSavingLimit || 'Unassigned' }}
- } - - @if (digitalPreview?.depositLimitKyc?.general) { - - - - - - - - - - - - - - - - - - - - - - - - - -
Account TypeKYC TypeLimit
General{{ digitalPreview.depositLimitKyc.general.kycType || 'Unassigned' }}{{ digitalPreview.depositLimitKyc.general.limit || 'Unassigned' }}
Minor{{ digitalPreview.depositLimitKyc.minor?.kycType || 'Unassigned' }}{{ digitalPreview.depositLimitKyc.minor?.limit || 'Unassigned' }}
Small Saving{{ digitalPreview.depositLimitKyc.smallSaving?.kycType || 'Unassigned' }}{{ digitalPreview.depositLimitKyc.smallSaving?.limit || 'Unassigned' }}
- } -
-
- } - - - @if (currentAccount?.charges?.length) { -
-
- {{ 'labels.inputs.Charges' | translate }} -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ 'labels.inputs.Name' | translate }}{{ charge.name + ', ' + charge.currency?.displaySymbol }}{{ 'labels.inputs.Type' | translate }} - {{ charge.chargeCalculationType?.value | translateKey: 'catalogs' }} - {{ 'labels.inputs.Amount' | translate }}{{ charge.amount | formatNumber }}{{ 'labels.inputs.Collected On' | translate }}{{ charge.chargeTimeType?.value }}{{ 'labels.inputs.Date' | translate }} - @if ( - charge.chargeTimeType?.value === 'Specified due date' || charge.chargeTimeType?.value === 'Weekly Fee' - ) { - {{ (charge.dueDate | dateFormat) || 'Unassigned' }} - } @else if ( - charge.chargeTimeType?.value === 'Monthly Fee' || charge.chargeTimeType?.value === 'Annual Fee' - ) { - {{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }} - } @else { - N/A - } - {{ 'labels.inputs.Repayments Every' | translate }}{{ charge.feeInterval || 'Not Provided' }}
} diff --git a/src/app/current-accounts/current-account-stepper/current-account-term-step/current-account-term-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-term-step/current-account-term-step.component.html index 3dd3b12b0..b98fbacaf 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-term-step/current-account-term-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-term-step/current-account-term-step.component.html @@ -49,13 +49,7 @@ Last Interest Date - + diff --git a/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.html b/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.html index 889528426..7431eddc8 100644 --- a/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.html +++ b/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.html @@ -34,7 +34,8 @@ - + + {{ 'labels.inputs.DETAILS' | translate }} - - {{ 'labels.inputs.TERMS' | translate }} + + @if (recurringDepositsAccountProductTemplate) { + + Currency - - - + + - - {{ 'labels.inputs.SETTINGS' | translate }} +
+ + +
+
- - -
+ + + {{ 'labels.inputs.INTEREST RATE CHART' | translate }} - - {{ 'labels.inputs.INTEREST RATE CHART' | translate }} + + - - - +
+ + +
+
- - {{ 'labels.inputs.CHARGES' | translate }} - - - - - - @if (recurringDepositAccountFormValid) { + {{ 'labels.inputs.PREVIEW' | translate }} ({ - chargeId: charge.id, - amount: charge.amount, - dueDate: charge.dueDate && this.dateUtils.formatDate(charge.dueDate, dateFormat), - feeOnMonthDay: charge.feeOnMonthDay, - feeInterval: charge.feeInterval - })), - isCalendarInherited: this.recurringDepositAccount.recurringDepositAccount - ? this.recurringDepositAccount.recurringDepositAccount - : false, + charges: [], + isCalendarInherited: false, submittedOnDate: this.dateUtils.formatDate(this.recurringDepositAccount.submittedOnDate, dateFormat), expectedFirstDepositOnDate: this.dateUtils.formatDate( this.recurringDepositAccount.expectedFirstDepositOnDate, diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.html b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.html new file mode 100644 index 000000000..568ebc0c2 --- /dev/null +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.html @@ -0,0 +1,82 @@ + + +
+
+ + Currency & Account Status Configuration + From Product +
+
+
+ + Currency + + Inherited from selected product + + + + Decimal Places + + Inherited from selected product + +
+ +
+
+

Balance Swap Option

+

Enable automated transfer of excess funds to linked deposits or vice-versa to maintain liquidity.

+
+ +
+ +
+
+

MSME

+

Micro, Small and Medium Enterprises — mark this account under MSME category.

+
+ +
+ +
+
+

Auto Debit Facility

+

Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.

+
+ +
+ +
+
+

Dormancy Alert

+

Enable automated notifications when an account becomes inactive based on product rules.

+
+ + {{ recurringDepositsAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }} + +
+ + + +
+
+

Nomination Facility

+

Allow account holders to designate individuals to receive funds in the event of account holder's death.

+
+ + {{ recurringDepositsAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }} + +
+ + @if (recurringDepositsAccountProductTemplate?.nominationFacility) { + + } +
+
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.scss b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.scss new file mode 100644 index 000000000..3ed604577 --- /dev/null +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.scss @@ -0,0 +1,141 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$accent: #0ea5e9; +$border: #e2e8f0; +$gray-50: #f8f9fa; +$gray-200: #e8eaed; +$gray-900: #202124; + +.racc-section { + border: 1px solid $border; + border-radius: 10px; + overflow: hidden; + background: #fff; + + &__header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + background: $gray-50; + border-bottom: 1px solid $gray-200; + + i { + font-size: 14px; + color: $accent; + } + + span { + margin: 0; + font-size: 14px; + font-weight: 600; + color: $gray-900; + } + } +} + +.racc-body { + background: #fff; +} + +.racc-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 4px 16px; + padding: 16px 24px; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} + +.racc-field { + width: 100%; +} + +.racc-alert { + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + padding: 18px 24px; + border-top: 1px solid $border; + background: #f8fafc; + + @media (width <= 760px) { + flex-direction: column; + align-items: flex-start; + } + + &__content { + h4 { + margin: 0 0 6px; + font-size: 14px; + font-weight: 600; + color: $gray-900; + } + + p { + margin: 0; + font-size: 12px; + line-height: 1.5; + color: #64748b; + max-width: 540px; + } + } +} + +.racc-yn { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 48px; + padding: 4px 14px; + border-radius: 999px; + font-size: 12px; + font-weight: 700; + background: #fee2e2; + color: #b91c1c; + border: 1px solid #fecaca; + + &--yes { + background: #dcfce7; + color: #15803d; + border-color: #bbf7d0; + } +} + +.racc-badge { + display: inline-flex; + align-items: center; + padding: 2px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 600; + + &--from { + background: #eff6ff; + color: #1d4ed8; + border: 1px solid #bfdbfe; + } +} + +.racc-alert--editable { + border-color: #bae6fd; + background: #f0f9ff; + + &:hover { + border-color: #7dd3fc; + box-shadow: 0 2px 8px rgb(2 132 199 / 10%); + } + + h4 { + color: #0369a1; + } +} diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.ts b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.ts new file mode 100644 index 000000000..d793ae8fe --- /dev/null +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.ts @@ -0,0 +1,60 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { NomineeFormComponent } from 'app/shared/nominee-form/nominee-form.component'; +import { DormancyAlertPanelComponent } from 'app/shared/dormancy-alert-panel/dormancy-alert-panel.component'; +import { MatSlideToggle } from '@angular/material/slide-toggle'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'mifosx-recurring-deposits-account-currency-step', + templateUrl: './recurring-deposits-account-currency-step.component.html', + styleUrls: ['./recurring-deposits-account-currency-step.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + NomineeFormComponent, + DormancyAlertPanelComponent, + MatSlideToggle, + FormsModule + ] +}) +export class RecurringDepositsAccountCurrencyStepComponent implements OnChanges { + @Input() recurringDepositsAccountProductTemplate: any; + + nomineeData: any = null; + + // Editable feature toggles (pre-filled from product, overridable by user) + balanceSwapOption = false; + msme = false; + autoDebitFacility = false; + + ngOnChanges(changes: SimpleChanges): void { + if (changes['recurringDepositsAccountProductTemplate']?.currentValue) { + const pt = changes['recurringDepositsAccountProductTemplate'].currentValue; + this.balanceSwapOption = !!pt.balanceSwapOption; + this.msme = !!pt.msme; + this.autoDebitFacility = !!pt.autoDebitFacility; + } + } + + onNomineeChange(event: any): void { + this.nomineeData = event; + } + + /** Called by parent to collect currency step values */ + get currencyStepData() { + return { + balanceSwapOption: this.balanceSwapOption, + msme: this.msme, + autoDebitFacility: this.autoDebitFacility, + ...(this.nomineeData ? { nominee: this.nomineeData } : {}) + }; + } +} diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html index 194eeefec..1b01888e6 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html @@ -7,7 +7,7 @@ --> - +
@@ -64,7 +64,7 @@
@if (isProductSelected && productTemplate) { - +
@@ -115,101 +115,8 @@
- -
-
- -

Currency & Account Status Configuration

- From Product -
-
- @if (productTemplate.currency) { -
- - Currency - - Default: Indian Rupee - auto-set for domestic products - - - @if ( - productTemplate.currency.decimalPlaces !== undefined && productTemplate.currency.decimalPlaces !== null - ) { - - Decimal Places - - - } -
- } - -
-
-

Dormancy Alert

-

Enable automated notifications when an account becomes inactive based on product rules.

-
-
- yes - no -
-
- -
-
-

Balance Swap Option

-

Enable automated transfer of excess funds to linked deposits or vice-versa to maintain liquidity.

-
-
- yes - no -
-
- -
-
-

MSME

-

Micro, Small and Medium Enterprises

-
-
- yes - no -
-
- -
-
-

Nomination Facility

-

- Allow account holders to designate individuals to receive funds in the event of account holder's death. -

-
-
- yes - no -
-
- -
-
-

Auto Debit Facility

-

Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.

-
-
- yes - no -
-
-
-
+ @if (productTemplate?.introducerRequired) { + + } } - -
- - -
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.scss b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.scss index 45ea9fcab..cd6bfa982 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.scss +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.scss @@ -16,7 +16,7 @@ $gray-900: #202124; .rad-section { border: 1px solid $border; border-radius: 10px; - overflow: hidden; + overflow: visible; background: #fff; &--mt { @@ -183,3 +183,61 @@ $gray-900: #202124; } } } + +// ── Account Feature Toggles ──────────────────────────────────────────────── +.rad-toggles { + display: flex; + flex-direction: column; + gap: 10px; + padding: 4px 0; +} + +.rad-toggle-card { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 18px; + border: 1.5px solid #e2e8f0; + border-radius: 10px; + background: #fff; + transition: + border-color 0.2s, + box-shadow 0.2s; + + &:hover { + border-color: #93c5fd; + box-shadow: 0 1px 6px rgb(59 130 246 / 10%); + } + + &__left { + display: flex; + align-items: center; + gap: 12px; + + > i { + width: 36px; + height: 36px; + border-radius: 8px; + background: #f0f9ff; + border: 1px solid #bae6fd; + color: #0284c7; + display: flex; + align-items: center; + justify-content: center; + font-size: 15px; + flex: 0 0 auto; + } + } + + &__label { + font-size: 13.5px; + font-weight: 600; + color: #1e293b; + } + + &__hint { + font-size: 11.5px; + color: #64748b; + margin-top: 2px; + } +} diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.ts b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.ts index a399f42da..8caea5ac7 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.ts +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.ts @@ -18,6 +18,7 @@ import { MatTooltip } from '@angular/material/tooltip'; import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { IntroducerSearchComponent } from 'app/shared/introducer-search/introducer-search.component'; /** * Recurring Deposits Account Details Step @@ -31,7 +32,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; MatTooltip, MatStepperPrevious, FaIconComponent, - MatStepperNext + MatStepperNext, + IntroducerSearchComponent ] }) export class RecurringDepositsAccountDetailsStepComponent implements OnInit { @@ -58,6 +60,7 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { recurringDepositAccountDetailsForm!: UntypedFormGroup; isProductSelected = false; + introducerId: number | null = null; /** Recurring Deposits Account Template with product data */ @Output() recurringDepositsAccountProductTemplate = new EventEmitter(); @@ -106,7 +109,10 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { Validators.required ], fieldOfficerId: [''], - externalId: [''] + externalId: [''], + balanceSwapOption: [false], + msme: [false], + autoDebitFacility: [false] }); } @@ -135,6 +141,12 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { this.recurringDepositsAccountProductTemplate.emit(response); this.fieldOfficerData = response.fieldOfficerOptions; this.isProductSelected = true; + // Pre-populate toggles from product defaults (user can override) + this.recurringDepositAccountDetailsForm.patchValue({ + balanceSwapOption: !!response.balanceSwapOption, + msme: !!response.msme, + autoDebitFacility: !!response.autoDebitFacility + }); this.cdr.detectChanges(); if (!this.isFieldOfficerPatched && this.recurringDepositsAccountTemplate.fieldOfficerId) { this.recurringDepositAccountDetailsForm @@ -184,7 +196,15 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { /** * Returns recurring deposits account details form value. */ + onIntroducerChange(event: { clientId: number; displayName: string } | null): void { + this.introducerId = event?.clientId ?? null; + } + get recurringDepositAccountDetails() { - return this.recurringDepositAccountDetailsForm.value; + const value = this.recurringDepositAccountDetailsForm.value; + if (this.introducerId) { + value.introducerId = this.introducerId; + } + return value; } } diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component.html b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component.html index c38bb2cc0..28268e478 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component.html +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component.html @@ -207,14 +207,3 @@
- -
- - -
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.html b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.html index 7b64f8833..e8058af74 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.html +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.html @@ -249,316 +249,6 @@
- -
-
- {{ 'labels.heading.Terms' | translate }} -
-
-
-
- {{ 'labels.inputs.Nominal Annual Interest' | translate }} - - {{ recurringDepositsAccountProductTemplate?.nominalAnnualInterestRate ?? '—' }} - @if (recurringDepositsAccountProductTemplate?.nominalAnnualInterestRate != null) { - % - } - -
- - @if (recurringDepositsAccountProductTemplate?.effectiveDate) { -
- Effective Date - {{ recurringDepositsAccountProductTemplate?.effectiveDate | dateFormat }} -
- } - -
- {{ 'labels.inputs.Interest Compounding Period' | translate }} - {{ - recurringDepositAccountData?.interestCompoundingPeriodType - | find: recurringDepositsAccountProductTemplate?.interestCompoundingPeriodTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
- -
- {{ 'labels.inputs.Interest Posting Period' | translate }} - {{ - recurringDepositAccountData?.interestPostingPeriodType - | find: recurringDepositsAccountProductTemplate?.interestPostingPeriodTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
- - @if (recurringDepositsAccountProductTemplate?.averagePeriod) { -
- Average Period - {{ recurringDepositsAccountProductTemplate?.averagePeriod }} -
- } - -
- {{ 'labels.inputs.Interest Calculated using' | translate }} - {{ - recurringDepositAccountData?.interestCalculationType - | find: recurringDepositsAccountProductTemplate?.interestCalculationTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
- -
- {{ 'labels.inputs.Days in Year' | translate }} - {{ - recurringDepositAccountData?.interestCalculationDaysInYearType - | find: recurringDepositsAccountProductTemplate?.interestCalculationDaysInYearTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
- - @if ( - recurringDepositsAccountProductTemplate?.autoPost !== undefined && - recurringDepositsAccountProductTemplate?.autoPost !== null - ) { -
- Auto Interest Post - - {{ recurringDepositsAccountProductTemplate?.autoPost | yesNo }} - -
- } - - @if ( - recurringDepositsAccountProductTemplate?.frequencyFixDay !== undefined && - recurringDepositsAccountProductTemplate?.frequencyFixDay !== null - ) { -
- Frequency (Fix Day) - {{ recurringDepositsAccountProductTemplate?.frequencyFixDay }} -
- } - - @if (recurringDepositsAccountProductTemplate?.lastInterestDate) { -
- Last Interest Date - {{ - recurringDepositsAccountProductTemplate?.lastInterestDate | dateFormat - }} -
- } - - @if (recurringDepositsAccountProductTemplate?.interestCalculationTypeName) { -
- Interest Calculation Type - {{ - recurringDepositsAccountProductTemplate?.interestCalculationTypeName - }} -
- } -
-
-
- - -
-
- {{ 'labels.heading.Settings' | translate }} -
-
- -
-
Premature Option
-
-
- Loan Facility - - {{ recurringDepositAccountData?.loanFacility | yesNo }} - -
-
- Premature Withdrawal Option - - {{ recurringDepositAccountData?.prematureWithdrawalOption | yesNo }} - -
-
- Tenure Flexibility - - {{ recurringDepositAccountData?.tenureFlexibility | yesNo }} - -
-
- Lien Marked - - {{ recurringDepositAccountData?.lienMarked | yesNo }} - -
-
- Withdrawal Fee for Transfers - - {{ recurringDepositAccountData?.withdrawalFeeForTransfers | yesNo }} - -
-
-
- - - - -
-
{{ 'labels.inputs.Lock-in Period' | translate }}
-
-
- Enable Lock-in Period - - {{ recurringDepositAccountData?.enableLockinPeriod | yesNo }} - -
- @if (recurringDepositAccountData?.enableLockinPeriod) { -
- Lock-in Frequency - - {{ recurringDepositAccountData?.lockinPeriodFrequency }} - {{ - recurringDepositAccountData?.lockinPeriodFrequencyType - | find: recurringDepositsAccountProductTemplate?.lockinPeriodFrequencyTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} - -
- } -
-
- - - - -
-
Maturity Instructions
-
-
- Maturity Instructions - - {{ recurringDepositAccountData?.maturityInstructions | yesNo }} - -
-
- Credit Maturity Proceeds - - {{ recurringDepositAccountData?.creditMaturityProceeds | yesNo }} - -
-
- Penalty / Grace Period for Missed Installments - - {{ recurringDepositAccountData?.penaltyGracePeriodMissedInstallments | yesNo }} - -
-
- Panel Request Applicable - - {{ recurringDepositAccountData?.panelRequestApplicable | yesNo }} - -
-
- No Penalty for Nominee / Legal Heir Closure - - {{ recurringDepositAccountData?.noPenaltyNomineeHeirClosure | yesNo }} - -
-
- Deceased Settlement - - {{ recurringDepositAccountData?.deceasedSettlement | yesNo }} - -
-
- Allow Debit Txn after Due Date - - {{ recurringDepositAccountData?.allowDebitTxnAfterDueDate | yesNo }} - -
-
-
- - - - -
-
Additional Rules
-
-
- Account Closed with Zero Balance in Day Closing - - {{ recurringDepositAccountData?.accountClosedZeroBalanceInDayClosing | yesNo }} - -
- @if (recurringDepositAccountData?.minBalanceForInterestCalculation) { -
- Min. Balance for Interest Calculation - {{ - recurringDepositAccountData?.minBalanceForInterestCalculation | formatNumber - }} -
- } - @if (recurringDepositAccountData?.mandatoryRecommendedDepositAmount) { -
- {{ 'labels.inputs.Recurring Deposit Amount' | translate }} - {{ - recurringDepositAccountData?.mandatoryRecommendedDepositAmount | formatNumber - }} -
- } -
-
-
-
- @if (_activeRdVersion || recurringDepositsAccountProductTemplate?.accountChart) {
@@ -770,82 +460,6 @@
} - - - @if (recurringDepositAccountData?.charges?.length) { -
-
- {{ 'labels.heading.Charges' | translate }} -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ 'labels.inputs.name' | translate }}{{ charge.name + ', ' + charge.currency?.displaySymbol }}{{ 'labels.inputs.Type' | translate }} - {{ charge.chargeCalculationType?.value | translateKey: 'catalogs' }} - {{ 'labels.inputs.Amount' | translate }}{{ charge.amount | formatNumber }}{{ 'labels.inputs.Collected On' | translate }}{{ charge.chargeTimeType?.value }}{{ 'labels.inputs.Date' | translate }} - - {{ (charge.dueDate | dateFormat) || 'Unassigned' }} - - - {{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }} - - - {{ 'labels.inputs.N/A' | translate }} - - {{ 'labels.inputs.Repayments Every' | translate }}{{ charge.feeInterval || 'Not Provided' }}
-
-
- }
diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.ts b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.ts index 7beb38296..25e2a9c41 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.ts +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.ts @@ -51,6 +51,10 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit { this.recurringDepositProductsTemplate.currency?.decimalPlaces ?? 2, dormancyAlert: this.recurringDepositProductsTemplate.dormancyAlert ?? false, + isDormancyTrackingActive: this.recurringDepositProductsTemplate.isDormancyTrackingActive ?? false, + daysToInactive: this.recurringDepositProductsTemplate.daysToInactive ?? 30, + daysToDormancy: this.recurringDepositProductsTemplate.daysToDormancy ?? 60, + daysToEscheat: this.recurringDepositProductsTemplate.daysToEscheat ?? 90, balanceSwapOption: this.recurringDepositProductsTemplate.balanceSwapOption ?? false, msme: this.recurringDepositProductsTemplate.msme ?? false, nominationFacility: this.recurringDepositProductsTemplate.nominationFacility ?? false, @@ -72,6 +76,10 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit { ] ], dormancyAlert: [false], + isDormancyTrackingActive: [false], + daysToInactive: [30], + daysToDormancy: [60], + daysToEscheat: [90], balanceSwapOption: [false], msme: [false], nominationFacility: [false], @@ -81,7 +89,7 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit { get recurringDepositProductCurrency() { const formValue = this.recurringDepositProductCurrencyForm.value; - return { + const result: any = { currencyCode: formValue.currencyCode, digitsAfterDecimal: formValue.digitsAfterDecimal, dormancyAlert: formValue.dormancyAlert, @@ -90,5 +98,11 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit { nominationFacility: formValue.nominationFacility, autoDebitFacility: formValue.autoDebitFacility }; + if (formValue.dormancyAlert) { + result.daysToInactive = formValue.daysToInactive; + result.daysToDormancy = formValue.daysToDormancy; + result.daysToEscheat = formValue.daysToEscheat; + } + return result; } } diff --git a/src/app/savings/create-savings-account/create-savings-account.component.html b/src/app/savings/create-savings-account/create-savings-account.component.html index ffe983360..23f9f93aa 100644 --- a/src/app/savings/create-savings-account/create-savings-account.component.html +++ b/src/app/savings/create-savings-account/create-savings-account.component.html @@ -35,7 +35,7 @@ - + Product Classification - - - Currency + @if (savingsAccountProductTemplate) { + + + Currency - - + + -
- - -
-
+
+ + +
+
- - - Term + + + Digital - - + + -
- - -
-
+
+ + +
+
- - - {{ 'labels.inputs.SETTINGS' | translate }} - - - - -
- - -
-
- - - - Digital - - - - -
- - -
-
- - - - {{ 'labels.inputs.CHARGES' | translate }} - - - - - - - @if (savingsAccountFormValid) { + {{ 'labels.inputs.PREVIEW' | translate }} { this.savingsAccountTemplate = data.savingsAccountTemplate; @@ -88,28 +83,14 @@ export class CreateSavingsAccountComponent { return this.savingsAccountDetailsStep.savingsAccountDetailsForm; } - get savingsAccountTermsForm() { - return this.savingsAccountTermStep.savingsAccountTermForm; - } - - get savingsAccountSettingsForm() { - return this.savingsAccountSettingsStep.savingsAccountSettingsForm; - } - - get savingsAccountFormValid() { - return ( - this.savingsAccountDetailsForm.valid && - this.savingsAccountTermsForm.valid && - this.savingsAccountSettingsForm.valid - ); + /** Stub — no term step in this flow */ + get savingsAccountTermsForm(): FormGroup { + return new FormGroup({}); } get savingsAccount() { return { - ...this.savingsAccountDetailsStep.savingsAccountDetails, - ...this.savingsAccountTermStep.savingsAccountTerms, - ...this.savingsAccountSettingsStep.savingsAccountSettings, - ...this.savingsAccountChargesStep.savingsAccountCharges + ...this.savingsAccountDetailsStep.savingsAccountDetails }; } @@ -119,15 +100,7 @@ export class CreateSavingsAccountComponent { const monthDayFormat = 'dd MMMM'; const savingsAccount = { ...this.savingsAccount, - charges: this.savingsAccount.charges.map((charge: any) => ({ - chargeId: charge.id, - amount: charge.amount, - dueDate: charge.dueDate ? this.dateUtils.formatDate(charge.dueDate, dateFormat) : charge.dueDate, - feeOnMonthDay: charge.feeOnMonthDay - ? this.dateUtils.formatDate(charge.feeOnMonthDay, monthDayFormat) - : charge.feeOnMonthDay, - feeInterval: charge.feeInterval - })), + charges: [], submittedOnDate: this.dateUtils.formatDate(this.savingsAccount.submittedOnDate, dateFormat), dateFormat, monthDayFormat, diff --git a/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.html b/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.html index 10c787094..9dd007828 100644 --- a/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.html @@ -36,6 +36,8 @@
+ +

Nomination Facility

@@ -45,5 +47,9 @@ {{ savingsAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
+ + @if (savingsAccountProductTemplate?.nominationFacility) { + + }
diff --git a/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.ts index 553256ce0..31e029241 100644 --- a/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.ts +++ b/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.ts @@ -8,13 +8,25 @@ import { Component, Input } from '@angular/core'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { NomineeFormComponent } from 'app/shared/nominee-form/nominee-form.component'; +import { DormancyAlertPanelComponent } from 'app/shared/dormancy-alert-panel/dormancy-alert-panel.component'; @Component({ selector: 'mifosx-savings-account-currency-step', templateUrl: './savings-account-currency-step.component.html', styleUrls: ['./savings-account-currency-step.component.scss'], - imports: [...STANDALONE_SHARED_IMPORTS] + imports: [ + ...STANDALONE_SHARED_IMPORTS, + NomineeFormComponent, + DormancyAlertPanelComponent + ] }) export class SavingsAccountCurrencyStepComponent { @Input() savingsAccountProductTemplate: any; + + nomineeData: any = null; + + onNomineeChange(event: any): void { + this.nomineeData = event; + } } diff --git a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html index df5db2234..60304a70d 100644 --- a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html @@ -67,7 +67,7 @@ - + @if (savingsProductSelected) {
@@ -118,16 +118,9 @@
- } -
- - -
+ @if (productTemplate?.introducerRequired) { + + } + } diff --git a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.scss b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.scss index 7c9847ae8..5744c0823 100644 --- a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.scss +++ b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.scss @@ -15,7 +15,7 @@ $gray-900: #202124; .sad-section { border: 1px solid $border; border-radius: 10px; - overflow: hidden; + overflow: visible; background: #fff; &--mt { diff --git a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts index e30469624..8585f3dac 100644 --- a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts +++ b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts @@ -17,6 +17,7 @@ import { SavingsService } from 'app/savings/savings.service'; import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { IntroducerSearchComponent } from 'app/shared/introducer-search/introducer-search.component'; /** * Savings Account Details Step @@ -29,7 +30,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; ...STANDALONE_SHARED_IMPORTS, MatStepperPrevious, FaIconComponent, - MatStepperNext + MatStepperNext, + IntroducerSearchComponent ] }) export class SavingsAccountDetailsStepComponent implements OnInit { @@ -56,6 +58,7 @@ export class SavingsAccountDetailsStepComponent implements OnInit { savingsAccountDetailsForm: UntypedFormGroup; savingsProductSelected = false; + introducerId: number | null = null; /** Savings Account Template with product data */ @Output() savingsAccountProductTemplate = new EventEmitter(); @@ -165,7 +168,15 @@ export class SavingsAccountDetailsStepComponent implements OnInit { /** * Returns savings account form value. */ + onIntroducerChange(event: { clientId: number; displayName: string } | null): void { + this.introducerId = event?.clientId ?? null; + } + get savingsAccountDetails() { - return this.savingsAccountDetailsForm.getRawValue(); + const value = this.savingsAccountDetailsForm.getRawValue(); + if (this.introducerId) { + value.introducerId = this.introducerId; + } + return value; } } diff --git a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.html b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.html index c8bddd8d6..5b0970996 100644 --- a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.html @@ -6,229 +6,209 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
-
- - - -
Transaction & Product Limits
-
FROM PRODUCT
+
+
+
+ +
+
+

Digital & Account Features

+

Toggle the facilities available for this account (as per RBI guidelines)

+
+ FROM PRODUCT
-
-
-
Account Features (as per RBI guidelines)
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+
+
+
+ ATM Facility + {{ facilityConfig.atmFacility ? 'Enabled' : 'Disabled' }}
+
- @if (accountFacilityLimits.length > 0) { -
- - - - - - - - - - - - - - - - - @for (row of accountFacilityLimits; track row.limitType) { - - - - - - - } - -
Limit TypeGeneral Limit (Daily)Minor Account LimitSmall Saving Limit
{{ row.limitType }}
+
+
+
+ ATM Withdrawal + {{ facilityConfig.atmWithdrawalFacility ? 'Enabled' : 'Disabled' }}
- } + +
- @if (depositLimitKyc?.general) { -
-

Deposit Limit (KYC)

- - - - - - - - - - - - - - - - - - - - - - - - - -
Account TypeKYC TypeLimit
General - -
Minor - -
Small Saving - -
+
+
+
+ UPI Facility + {{ facilityConfig.upiFacility ? 'Enabled' : 'Disabled' }}
- } + +
-
- Note: - These facilities are defined at product level and applied to this account. Small Saving Account limits are - strictly capped at Rs.1,00,000 maximum balance per RBI Master Circular. +
+
+
+ POS Transactions + {{ facilityConfig.posTransactionsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Cheque Book + {{ facilityConfig.chequeBook ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ IMPS Facility + {{ facilityConfig.impsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ SMS Alerts + {{ facilityConfig.smsAlerts ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ BBPS Facility + {{ facilityConfig.bbpsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Insurance Cover + {{ facilityConfig.insuranceCover ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ BEPG Facility + {{ facilityConfig.bepgFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ NEFT Facility + {{ facilityConfig.neftFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ RTGS Facility + {{ facilityConfig.rtgsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Inter-Bank Total Cap + {{ facilityConfig.interBankTotalCapFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Withdrawal Facility + {{ facilityConfig.withdrawalFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Deposit Facility (KYC) + {{ facilityConfig.depositFacilityKyc ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Virtual Debit Card + {{ facilityConfig.virtualDebitCard ? 'Enabled' : 'Disabled' }} +
+
diff --git a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.scss b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.scss index 7b58a1abb..038aa7fc7 100644 --- a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.scss +++ b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.scss @@ -6,194 +6,205 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -$border: #d9e2ec; -$border-strong: #c7d2de; -$text: #24364b; +$blue: #2563eb; +$blue-light: #eff6ff; +$green: #16a34a; +$green-light: #f0fdf4; +$green-border: #bbf7d0; +$border: #e2e8f0; +$text: #1e293b; $muted: #64748b; -$header-bg: #f8fafc; +$gray-bg: #f8fafc; -.spl-section { +// ── Wrapper ────────────────────────────────────────────── +.dig-wrapper { border: 1px solid $border; - border-radius: 12px; + border-radius: 14px; overflow: hidden; background: #fff; + box-shadow: 0 1px 4px rgb(0 0 0 / 6%); } -.spl-section__header { +// ── Header ─────────────────────────────────────────────── +.dig-header { display: flex; align-items: center; - gap: 12px; - padding: 16px 20px; - background: $header-bg; + gap: 14px; + padding: 18px 24px; + background: $gray-bg; border-bottom: 1px solid $border; -} -.spl-section__icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - border-radius: 8px; - background: #eff6ff; - color: #2563eb; - font-size: 14px; - flex: 0 0 auto; -} - -.spl-section__title { - flex: 1 1 auto; - font-size: 16px; - font-weight: 600; - color: $text; -} - -.spl-section__badge { - flex: 0 0 auto; - padding: 5px 10px; - border-radius: 999px; - background: #dbeafe; - color: #1d4ed8; - font-size: 10px; - font-weight: 700; - letter-spacing: 0.05em; - white-space: nowrap; -} - -.spl-section__body { - padding: 18px 20px 20px; -} - -.spl-table-wrap { - overflow-x: auto; - margin-top: 16px; -} - -.spl-table { - width: 100%; - min-width: 0; - table-layout: fixed; - border-collapse: collapse; - border: 1px solid $border; - - col.spl-table__col--label { - width: 22%; + &__icon { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 10px; + background: $blue-light; + color: $blue; + font-size: 16px; + flex: 0 0 auto; } - col.spl-table__col--value { - width: 26%; + &__text { + flex: 1; } - th, - td { - border: 1px solid $border; - padding: 6px 8px; - vertical-align: middle; - } - - th { - background: #f8fafc; - color: #64748b; - text-align: center; - font-size: 11px; + &__title { + margin: 0 0 2px; + font-size: 15px; font-weight: 700; - } - - td:first-child { - background: #f8fafc; - font-weight: 600; color: $text; - white-space: nowrap; } - td { - font-size: 13px; - overflow: hidden; + &__sub { + margin: 0; + font-size: 12px; + color: $muted; + } + + &__badge { + flex: 0 0 auto; + padding: 4px 12px; + border-radius: 999px; + background: #dbeafe; + color: $blue; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.06em; } } -.spl-input, -.spl-select { - width: 100%; - box-sizing: border-box; - font: inherit; -} - -.spl-input { - height: 30px; - border: 1px solid $border-strong; - border-radius: 2px; - padding: 0 8px; - background: #f8fafc; - color: $muted; -} - -.spl-select { - height: 30px; - border: 1px solid $border-strong; - border-radius: 2px; - padding: 0 6px; - background: #f8fafc; - color: $muted; -} - -.spl-features { - margin-bottom: 16px; -} - -.spl-features__header { - font-size: 13px; - font-weight: 600; - color: $muted; - margin-bottom: 12px; - text-transform: uppercase; - letter-spacing: 0.04em; -} - -.spl-features__grid { +// ── Grid ───────────────────────────────────────────────── +.dig-grid { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 12px 16px; + grid-template-columns: repeat(3, 1fr); + gap: 0; - @media (width <= 900px) { - grid-template-columns: repeat(2, minmax(0, 1fr)); + @media (width <= 960px) { + grid-template-columns: repeat(2, 1fr); } - @media (width <= 640px) { + @media (width <= 580px) { grid-template-columns: 1fr; } } -.spl-feature { +// ── Card ───────────────────────────────────────────────── +.dig-card { display: flex; - flex-direction: column; - gap: 6px; - font-size: 13px; - color: $text; -} - -.spl-feature__label { - display: inline-flex; align-items: center; - gap: 8px; - font-weight: 600; - color: #334155; + gap: 12px; + padding: 14px 18px; + border-right: 1px solid $border; + border-bottom: 1px solid $border; + background: #fff; + transition: background 0.18s ease; + cursor: pointer; - i { - color: #2563eb; - font-size: 12px; - width: 14px; - text-align: center; + &:nth-child(3n) { + border-right: none; + } + + &--on { + background: $green-light; + + .dig-card__icon { + background: $green-border; + color: $green; + } + + .dig-card__status { + color: $green; + font-weight: 600; + } + } + + &__icon { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 9px; + background: $blue-light; + color: $blue; + font-size: 14px; + flex: 0 0 auto; + transition: + background 0.18s, + color 0.18s; + } + + &__body { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 2px; + } + + &__label { + font-size: 13px; + font-weight: 600; + color: $text; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &__status { + font-size: 11px; + color: $muted; + transition: color 0.18s; } } -.spl-note { - margin-top: 16px; - padding: 12px 14px; - border: 1px solid $border; - border-radius: 8px; - background: #f8fafc; - color: $muted; - font-size: 11px; - line-height: 1.6; +// ── Toggle Switch ───────────────────────────────────────── +.dig-toggle { + position: relative; + flex: 0 0 auto; + cursor: pointer; + + input { + position: absolute; + opacity: 0; + width: 0; + height: 0; + + &:checked + .dig-toggle__track { + background: $green; + border-color: $green; + } + + &:checked + .dig-toggle__track .dig-toggle__thumb { + transform: translateX(18px); + } + } + + &__track { + display: block; + width: 40px; + height: 22px; + border-radius: 999px; + background: #cbd5e1; + border: 1.5px solid #94a3b8; + position: relative; + transition: + background 0.2s, + border-color 0.2s; + } + + &__thumb { + position: absolute; + top: 2px; + left: 2px; + width: 16px; + height: 16px; + border-radius: 50%; + background: #fff; + box-shadow: 0 1px 3px rgb(0 0 0 / 20%); + transition: transform 0.2s ease; + } } diff --git a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.ts index ae9f82d8e..5c747b548 100644 --- a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.ts +++ b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.ts @@ -39,31 +39,16 @@ const DEFAULT_FACILITY_CONFIG = { ] }) export class SavingsAccountDigitalStepComponent { - private _savingsAccountProductTemplate: any; + facilityConfig = { ...DEFAULT_FACILITY_CONFIG }; @Input() set savingsAccountProductTemplate(value: any) { - this._savingsAccountProductTemplate = value; - } - get savingsAccountProductTemplate(): any { - return this._savingsAccountProductTemplate; + if (value?.accountFacilityConfig) { + this.facilityConfig = { ...DEFAULT_FACILITY_CONFIG, ...value.accountFacilityConfig }; + } } - get accountFacilityConfig() { - return this._savingsAccountProductTemplate?.accountFacilityConfig || DEFAULT_FACILITY_CONFIG; - } - - get accountFacilityLimits(): any[] { - return this._savingsAccountProductTemplate?.accountFacilityLimits || []; - } - - get depositLimitKyc() { - return ( - this._savingsAccountProductTemplate?.depositLimitKyc || { - general: { kycType: '', limit: '' }, - minor: { kycType: '', limit: '' }, - smallSaving: { kycType: '', limit: '' } - } - ); + get savingsAccountDigital() { + return { accountFacilityConfig: { ...this.facilityConfig } }; } } diff --git a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.html b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.html index 0a595f67f..70337bb64 100644 --- a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.html @@ -213,198 +213,6 @@
-
-
- {{ 'labels.heading.Terms' | translate }} -
-
-
-
- {{ 'labels.inputs.Nominal Annual Interest' | translate }} - {{ savingsAccount?.nominalAnnualInterestRate | formatNumber }} % -
- @if (savingsAccountProductTemplate?.interestCalculationTypeName) { -
- Interest Calculation Mode - {{ savingsAccountProductTemplate?.interestCalculationTypeName }} -
- } -
- {{ 'labels.inputs.Interest Compounding Period' | translate }} - {{ - savingsAccount?.interestCompoundingPeriodType - | find: savingsAccountProductTemplate?.interestCompoundingPeriodTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
-
- {{ 'labels.inputs.Interest Posting Period' | translate }} - {{ - savingsAccount?.interestPostingPeriodType - | find: savingsAccountProductTemplate?.interestPostingPeriodTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
-
- {{ 'labels.inputs.Interest Calculated using' | translate }} - {{ - savingsAccount?.interestCalculationType - | find: savingsAccountProductTemplate?.interestCalculationTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
-
- {{ 'labels.inputs.Days in Year' | translate }} - {{ - savingsAccount?.interestCalculationDaysInYearType - | find: savingsAccountProductTemplate?.interestCalculationDaysInYearTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
- -
- Interest Calculation Selector - {{ - savingsAccount?.interestCalculateSelector - | find: savingsAccountProductTemplate?.interestCalculationTypeOption : 'id' : 'value' - | translateKey: 'catalogs' - }} -
- @if (savingsAccount?.averagePeriod) { -
- Average Period - {{ savingsAccount?.averagePeriod }} -
- } -
- Auto Post - - {{ savingsAccount?.autoPost | yesNo }} - -
- @if (savingsAccountProductTemplate?.lastInterestDate) { -
- Last Interest Date - {{ savingsAccountProductTemplate?.lastInterestDate | dateFormat }} -
- } - @if (savingsAccount?.frequencyFixDay !== undefined && savingsAccount?.frequencyFixDay !== null) { -
- Frequency Fix Day - {{ savingsAccount?.frequencyFixDay }} -
- } -
-
-
- -
-
- {{ 'labels.heading.Settings' | translate }} -
-
-
- @if (savingsAccount?.minRequiredOpeningBalance) { -
- {{ 'labels.inputs.Minimum Opening Balance' | translate }} - {{ savingsAccount?.minRequiredOpeningBalance | formatNumber }} -
- } -
- {{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }} - - {{ savingsAccount?.withdrawalFeeForTransfers | yesNo }} - -
- @if (savingsAccount?.lockinPeriodFrequency) { -
- {{ 'labels.inputs.Lock-in Period' | translate }} - - {{ savingsAccount?.lockinPeriodFrequency }} - {{ - savingsAccount?.lockinPeriodFrequencyType - | find: savingsAccountProductTemplate?.lockinPeriodFrequencyTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} - -
- } -
- {{ 'labels.inputs.Is Overdraft Allowed' | translate }} - - {{ savingsAccount?.allowOverdraft | yesNo }} - -
- @if (savingsAccount?.minOverdraftForInterestCalculation) { -
- {{ - 'labels.inputs.Minimum Overdraft Required for Interest Calculation' | translate - }} - {{ savingsAccount?.minOverdraftForInterestCalculation | formatNumber }} -
- } - @if (savingsAccount?.nominalAnnualInterestRateOverdraft) { -
- {{ 'labels.inputs.Nominal Annual Interest for Overdraft' | translate }} - {{ savingsAccount?.nominalAnnualInterestRateOverdraft | formatNumber }} % -
- } - @if (savingsAccount?.overdraftLimit) { -
- {{ 'labels.inputs.Maximum Overdraft Amount Limit' | translate }} - {{ savingsAccount?.overdraftLimit | formatNumber }} -
- } -
- {{ 'labels.inputs.Enforce Minimum Balance' | translate }} - - {{ savingsAccount?.enforceMinRequiredBalance | yesNo }} - -
- @if (savingsAccountProductTemplate?.enableLockinPeriod !== undefined) { -
- Enable Lock-in Period - - {{ savingsAccountProductTemplate?.enableLockinPeriod | yesNo }} - -
- } - @if (savingsAccountProductTemplate?.balanceFrequency) { -
- Balance Frequency - {{ savingsAccountProductTemplate?.balanceFrequency }} -
- } - @if (savingsAccountProductTemplate?.accountClosedZeroBalanceInDayClosing !== undefined) { -
- Account Closed Zero Balance In Day Closing - - {{ savingsAccountProductTemplate?.accountClosedZeroBalanceInDayClosing | yesNo }} - -
- } - @if (savingsAccount?.minRequiredBalance) { -
- {{ 'labels.inputs.Minimum Balance' | translate }} - {{ savingsAccount?.minRequiredBalance }} -
- } -
-
-
- @if (hasDigitalSection) {
@@ -424,58 +232,6 @@
}
- - @if (digitalPreview?.accountFacilityLimits?.length) { - - - - - - - - - - - @for (row of digitalPreview.accountFacilityLimits; track row.limitType) { - - - - - - - } - -
Limit TypeGeneral LimitMinor Account LimitSmall Saving Limit
{{ row.limitType }}{{ row.generalLimit || 'Unassigned' }}{{ row.minorAccountLimit || 'Unassigned' }}{{ row.smallSavingLimit || 'Unassigned' }}
- } - - @if (digitalPreview?.depositLimitKyc?.general) { - - - - - - - - - - - - - - - - - - - - - - - - - -
Account TypeKYC TypeLimit
General{{ digitalPreview.depositLimitKyc.general.kycType || 'Unassigned' }}{{ digitalPreview.depositLimitKyc.general.limit || 'Unassigned' }}
Minor{{ digitalPreview.depositLimitKyc.minor?.kycType || 'Unassigned' }}{{ digitalPreview.depositLimitKyc.minor?.limit || 'Unassigned' }}
Small Saving{{ digitalPreview.depositLimitKyc.smallSaving?.kycType || 'Unassigned' }}{{ digitalPreview.depositLimitKyc.smallSaving?.limit || 'Unassigned' }}
- }
} @@ -501,65 +257,6 @@
} - - @if (savingsAccount?.charges?.length) { -
-
- {{ 'labels.inputs.Charges' | translate }} -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ 'labels.inputs.Name' | translate }}{{ charge.name + ', ' + charge.currency.displaySymbol }}{{ 'labels.inputs.Type' | translate }} - {{ charge.chargeCalculationType.value | translateKey: 'catalogs' }} - {{ 'labels.inputs.Amount' | translate }}{{ charge.amount | formatNumber }}{{ 'labels.inputs.Date' | translate }} - @if ( - charge.chargeTimeType.value === 'Specified due date' || charge.chargeTimeType.value === 'Weekly Fee' - ) { - {{ (charge.dueDate | dateFormat) || 'Unassigned' }} - } - @if (charge.chargeTimeType.value === 'Monthly Fee' || charge.chargeTimeType.value === 'Annual Fee') { - {{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }} - } - @if ( - !( - charge.chargeTimeType.value === 'Monthly Fee' || - charge.chargeTimeType.value === 'Annual Fee' || - charge.chargeTimeType.value === 'Specified due date' || - charge.chargeTimeType.value === 'Weekly Fee' - ) - ) { - N/A - } - {{ 'labels.inputs.Repayments Every' | translate }}{{ charge.feeInterval || 'Not Provided' }}{{ 'labels.inputs.Collected On' | translate }}{{ charge.chargeTimeType.value }}
-
-
- }
diff --git a/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.html b/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.html index a90dfeba3..b7592f93b 100644 --- a/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.html @@ -144,13 +144,7 @@ Last Interest Date - + diff --git a/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.html b/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.html new file mode 100644 index 000000000..5aba5ae30 --- /dev/null +++ b/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.html @@ -0,0 +1,308 @@ + + +@if (shouldShow) { +
+ +
+
+
+ +
+
+
+ {{ isProductMode ? 'Dormancy Monitoring Configuration' : 'Dormancy Risk Monitor' }} +
+
+ @if (isProductMode) { + Product-level dormancy thresholds applied to this account + } @else { + Real-time dormancy tracking · Based on last customer transaction + } +
+
+
+ + +
+ + + @if (isProductMode) { + Monitoring: {{ riskSeverity }} + } @else { + Dormancy Risk: {{ riskSeverity }} + } + + @if (!isProductMode && isMonitoringActive) { + Monitoring Active + } +
+
+ + +
+ @if (isProductMode) { + + + +
+ Account Status + New Account + No transactions yet +
+ + +
+ + Dormancy Monitoring + + + + + {{ isMonitoringActive ? 'Enabled' : 'Disabled' }} + + {{ + isMonitoringActive ? 'Transaction activity tracked' : 'Not configured' + }} +
+ + +
+ + Alert Notifications + + + + + {{ isAlertEnabled ? 'Enabled' : 'Disabled' }} + + {{ isAlertEnabled ? 'Branch will be notified' : 'No alerts configured' }} +
+ + +
+ + Inactive Threshold + + + @if (daysToInactive !== null) { + {{ daysToInactive }}days + No transaction for {{ daysToInactive }} days + } @else { + Not configured + } +
+ + +
+ + Dormancy Threshold + + + @if (daysToDormancy !== null) { + {{ daysToDormancy }}days + {{ daysToDormancy }} days after inactive + } @else { + Not configured + } +
+ + +
+ + Escheat Threshold + + + @if (daysToEscheat !== null) { + {{ daysToEscheat }}days + {{ daysToEscheat }} days after dormant + } @else { + Not configured + } +
+ } @else { + + + +
+ Account Status + + {{ statusLabel }} + + + @if (statusId === 0) { + Currently active + } + @if (statusId === 100) { + No recent transactions + } + @if (statusId === 200) { + Classified as dormant + } + @if (statusId === 300) { + Pending regulatory transfer + } + +
+ + +
+ Last Transaction + @if (lastTxnDate) { + {{ lastTxnDate }} + Most recent customer activity + } @else if (accountOpenedDate) { + {{ accountOpenedDate }} + Account opened · No transactions yet + } @else { + No transactions yet + } +
+ + +
+ + Days to Inactive + + + @if (statusId >= 100) { + Threshold Reached + Account is inactive + } @else if (daysToInactive !== null) { + + {{ daysToInactive }}days + + {{ inactiveSubtext() }} + } @else { + — + } +
+ + +
+ + Days to Dormant + + + @if (statusId >= 200) { + Threshold Reached + Account is dormant + } @else if (daysToDormancy !== null) { + + {{ daysToDormancy }}days + + {{ dormancySubtext() }} + } @else { + — + } +
+ + +
+ + Days to Escheat + + + @if (statusId >= 300) { + Threshold Reached + Pending regulatory transfer + } @else if (daysToEscheat !== null) { + + {{ daysToEscheat }}days + + {{ escheatSubtext() }} + } @else { + — + } +
+ } +
+ + +
+
+ Dormancy Progression + @if (!isProductMode) { + Timeline based on last customer transaction + } +
+ +
+ +
+
+ +
+ Active + Normal +
+ + @for (step of timelineSteps; track step.label) { +
+
+
+ @if (step.reached && !step.current) { + + } @else if (step.current) { + + } @else { + + } +
+ + {{ step.label }} + + + @if (!step.reached && step.days !== null) { + {{ step.days }}d + } @else if (step.reached) { + Reached + } +
+ } +
+
+
+} diff --git a/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.scss b/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.scss new file mode 100644 index 000000000..4cb26e9bc --- /dev/null +++ b/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.scss @@ -0,0 +1,684 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +@use 'sass:color'; + +// ── Design Tokens ────────────────────────────────────────────────────────── +// Risk colours +$c-safe: #16a34a; +$bg-safe: #f0fdf4; +$bd-safe: #bbf7d0; +$c-low: #0284c7; +$bg-low: #f0f9ff; +$bd-low: #bae6fd; +$c-medium: #d97706; +$bg-medium: #fffbeb; +$bd-medium: #fde68a; +$c-high: #dc2626; +$bg-high: #fff5f5; +$bd-high: #fecaca; +$c-critical: #b91c1c; +$bg-critical: #fef2f2; +$bd-critical: #fca5a5; +$c-escheat: #7c3aed; +$bg-escheat: #f5f3ff; +$bd-escheat: #ddd6fe; + +// Neutral +$text: #1e293b; +$muted: #64748b; +$subtle: #94a3b8; +$border: #e2e8f0; +$surface: #f8fafc; + +// ── Host ─────────────────────────────────────────────────────────────────── +:host { + display: block; + margin: 14px 0 18px; +} + +// ── Root wrapper ─────────────────────────────────────────────────────────── +.dap { + border-radius: 14px; + border: 1.5px solid $border; + background: #fff; + overflow: hidden; + box-shadow: 0 1px 4px rgb(0 0 0 / 6%); + transition: box-shadow 0.2s; + + &:hover { + box-shadow: 0 3px 12px rgb(0 0 0 / 10%); + } + + // Risk-tinted border + &--safe { + border-color: $bd-safe; + } + + &--low { + border-color: $bd-low; + } + + &--medium { + border-color: $bd-medium; + } + + &--high { + border-color: $bd-high; + } + + &--critical { + border-color: $bd-critical; + } + + &--escheat { + border-color: $bd-escheat; + } + + // ── HEADER ─────────────────────────────────────────────────────── + &__header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 12px; + padding: 14px 18px; + background: $surface; + border-bottom: 1px solid $border; + } + + &__header-left { + display: flex; + align-items: center; + gap: 12px; + } + + &__icon-wrap { + width: 40px; + height: 40px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + border: 1px solid $border; + + i { + font-size: 17px; + } + + .dap--safe & { + background: $bg-safe; + border-color: $bd-safe; + + i { + color: $c-safe; + } + } + + .dap--low & { + background: $bg-low; + border-color: $bd-low; + + i { + color: $c-low; + } + } + + .dap--medium & { + background: $bg-medium; + border-color: $bd-medium; + + i { + color: $c-medium; + } + } + + .dap--high & { + background: $bg-high; + border-color: $bd-high; + + i { + color: $c-high; + } + } + + .dap--critical & { + background: $bg-critical; + border-color: $bd-critical; + + i { + color: $c-critical; + } + } + + .dap--escheat & { + background: $bg-escheat; + border-color: $bd-escheat; + + i { + color: $c-escheat; + } + } + } + + &__title { + font-size: 13.5px; + font-weight: 700; + color: $text; + letter-spacing: -0.01em; + line-height: 1.3; + } + + &__subtitle { + font-size: 11.5px; + color: $muted; + margin-top: 2px; + line-height: 1.4; + } + + // ── Badge group ────────────────────────────────────────────────── + &__badges { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + } + + &__risk-badge { + display: inline-flex; + align-items: center; + gap: 7px; + padding: 5px 13px; + border-radius: 999px; + font-size: 11.5px; + font-weight: 700; + letter-spacing: 0.01em; + cursor: default; + + .dap--safe & { + background: $bg-safe; + color: $c-safe; + border: 1.5px solid $bd-safe; + } + + .dap--low & { + background: $bg-low; + color: $c-low; + border: 1.5px solid $bd-low; + } + + .dap--medium & { + background: $bg-medium; + color: $c-medium; + border: 1.5px solid $bd-medium; + } + + .dap--high & { + background: $bg-high; + color: $c-high; + border: 1.5px solid $bd-high; + } + + .dap--critical & { + background: $bg-critical; + color: $c-critical; + border: 1.5px solid $bd-critical; + } + + .dap--escheat & { + background: $bg-escheat; + color: $c-escheat; + border: 1.5px solid $bd-escheat; + } + } + + &__risk-dot { + width: 7px; + height: 7px; + border-radius: 50%; + display: inline-block; + animation: pulse-dot 2s ease-in-out infinite; + + .dap--safe & { + background: $c-safe; + } + + .dap--low & { + background: $c-low; + } + + .dap--medium & { + background: $c-medium; + } + + .dap--high & { + background: $c-high; + } + + .dap--critical & { + background: $c-critical; + } + + .dap--escheat & { + background: $c-escheat; + } + } + + &__monitor-badge { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 5px 11px; + border-radius: 999px; + font-size: 11px; + font-weight: 600; + background: $bg-low; + color: $c-low; + border: 1.5px solid $bd-low; + + i { + font-size: 10px; + } + } + + // ── Metric Cards ───────────────────────────────────────────────── + &__cards { + display: grid; + grid-template-columns: repeat(5, 1fr); + border-bottom: 1px solid $border; + + &--product { + grid-template-columns: repeat(6, 1fr); + } + + @media (width <= 1024px) { + grid-template-columns: repeat(3, 1fr) !important; + + .dap__card:nth-child(3n) { + border-right: none; + } + } + + @media (width <= 600px) { + grid-template-columns: repeat(2, 1fr) !important; + + .dap__card:nth-child(2n) { + border-right: none; + } + } + } + + &__card { + padding: 14px 16px; + border-right: 1px solid $border; + display: flex; + flex-direction: column; + gap: 5px; + transition: background 0.15s; + + &:last-child { + border-right: none; + } + + &:hover { + background: $surface; + } + } + + &__card-label { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: $muted; + display: flex; + align-items: center; + gap: 4px; + } + + &__card-hint { + font-size: 10.5px; + color: $subtle; + margin-top: 2px; + line-height: 1.3; + } + + &__help { + font-size: 9px; + color: $subtle; + cursor: help; + + &:hover { + color: $c-low; + } + } + + // Status pill + &__pill { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 4px 11px; + border-radius: 999px; + font-size: 12px; + font-weight: 600; + width: fit-content; + + i { + font-size: 11px; + } + + &--green { + background: $bg-safe; + color: $c-safe; + border: 1px solid $bd-safe; + } + + &--gray { + background: #f1f5f9; + color: $muted; + border: 1px solid #e2e8f0; + } + + &--safe { + background: $bg-safe; + color: $c-safe; + border: 1px solid $bd-safe; + } + + &--low { + background: $bg-low; + color: $c-low; + border: 1px solid $bd-low; + } + + &--medium { + background: $bg-medium; + color: $c-medium; + border: 1px solid $bd-medium; + } + + &--high { + background: $bg-high; + color: $c-high; + border: 1px solid $bd-high; + } + + &--critical { + background: $bg-critical; + color: $c-critical; + border: 1px solid $bd-critical; + } + + &--escheat { + background: $bg-escheat; + color: $c-escheat; + border: 1px solid $bd-escheat; + } + } + + // Number display + &__number { + font-size: 22px; + font-weight: 800; + color: $text; + display: flex; + align-items: baseline; + gap: 4px; + font-variant-numeric: tabular-nums; + + small { + font-size: 11px; + font-weight: 500; + color: $muted; + } + + &--warn { + color: $c-high; + + small { + color: color.adjust($c-high, $lightness: 10%); + } + } + } + + &__date { + font-size: 12.5px; + font-weight: 600; + color: $text; + display: flex; + align-items: center; + gap: 6px; + + i { + color: $muted; + font-size: 12px; + } + + &--muted { + color: $muted; + } + } + + &__reached { + font-size: 12px; + font-weight: 600; + color: $c-high; + display: flex; + align-items: center; + gap: 6px; + + i { + font-size: 11px; + } + } + + &__na { + font-size: 12px; + color: $subtle; + font-style: italic; + } + + // ── Timeline ───────────────────────────────────────────────────── + &__timeline { + padding: 16px 18px 18px; + background: $surface; + border-top: 1px solid $border; + } + + &__tl-header { + display: flex; + align-items: baseline; + justify-content: space-between; + flex-wrap: wrap; + gap: 4px; + margin-bottom: 16px; + } + + &__tl-label { + font-size: 10px; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.07em; + color: $muted; + } + + &__tl-note { + font-size: 10px; + color: $subtle; + font-style: italic; + } + + &__tl-track { + display: flex; + align-items: flex-start; + overflow-x: auto; + padding-bottom: 4px; + } + + &__tl-node { + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; + flex: 0 0 auto; + min-width: 68px; + } + + &__tl-circle { + width: 30px; + height: 30px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: 2px solid $border; + background: #fff; + transition: all 0.2s; + + i { + font-size: 12px; + color: #cbd5e1; + } + + &--done { + background: $c-safe; + border-color: $c-safe; + box-shadow: 0 0 0 3px rgba($c-safe, 0.15); + + i { + color: #fff; + } + } + + &--current { + background: #fff; + box-shadow: 0 0 0 3px rgb(0 0 0 / 8%); + + .dap--safe &, + .dap--low & { + border-color: $c-safe; + box-shadow: 0 0 0 3px rgba($c-safe, 0.15); + + i { + color: $c-safe; + } + } + + .dap--medium & { + border-color: $c-medium; + box-shadow: 0 0 0 3px rgba($c-medium, 0.15); + + i { + color: $c-medium; + } + } + + .dap--high &, + .dap--critical & { + border-color: $c-high; + box-shadow: 0 0 0 3px rgba($c-high, 0.15); + + i { + color: $c-high; + } + } + + .dap--escheat & { + border-color: $c-escheat; + box-shadow: 0 0 0 3px rgba($c-escheat, 0.15); + + i { + color: $c-escheat; + } + } + } + + &--pending { + background: $surface; + + i { + color: #cbd5e1; + } + } + } + + &__tl-name { + font-size: 10px; + font-weight: 700; + color: $muted; + text-transform: uppercase; + letter-spacing: 0.04em; + text-align: center; + white-space: nowrap; + cursor: help; + display: flex; + align-items: center; + gap: 3px; + } + + &__tl-info { + font-size: 8px; + color: $subtle; + text-transform: none; + } + + &__tl-sub { + font-size: 9px; + color: $subtle; + text-align: center; + } + + &__tl-days { + font-size: 10px; + color: $muted; + background: #e9eef5; + border-radius: 999px; + padding: 2px 7px; + font-weight: 600; + white-space: nowrap; + } + + &__tl-reached-tag { + font-size: 9px; + color: $c-high; + background: $bg-high; + border-radius: 999px; + padding: 2px 7px; + font-weight: 700; + border: 1px solid $bd-high; + white-space: nowrap; + } + + &__tl-line { + flex: 1; + height: 2px; + background: $border; + margin: 14px 2px 0; + min-width: 20px; + border-radius: 2px; + transition: background 0.3s; + + &--done { + background: $c-safe; + } + } +} + +// ── Pulse animation for risk dot ─────────────────────────────────────────── +@keyframes pulse-dot { + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.45; + } +} diff --git a/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.ts b/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.ts new file mode 100644 index 000000000..2797494ad --- /dev/null +++ b/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.ts @@ -0,0 +1,195 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, Input, OnInit } from '@angular/core'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +export type DormancyRisk = 'safe' | 'low' | 'medium' | 'high' | 'critical' | 'escheat'; + +@Component({ + selector: 'mifosx-dormancy-alert-panel', + templateUrl: './dormancy-alert-panel.component.html', + styleUrls: ['./dormancy-alert-panel.component.scss'], + imports: [...STANDALONE_SHARED_IMPORTS] +}) +export class DormancyAlertPanelComponent implements OnInit { + /** Pass full account data — for ACCOUNT VIEW (general tab) */ + @Input() accountData: any; + /** Pass product template — for ACCOUNT CREATION (currency step) */ + @Input() productTemplate: any; + + risk: DormancyRisk = 'safe'; + riskLabel = ''; + riskSeverity = ''; // e.g. "Low", "Medium", "High" + + statusLabel = ''; + statusId = 0; + + lastTxnDate: string | null = null; + accountOpenedDate: string | null = null; + + daysToInactive: number | null = null; + daysToDormancy: number | null = null; + daysToEscheat: number | null = null; + + isMonitoringActive = false; // isDormancyTrackingActive + isAlertEnabled = false; // dormancyAlert + + shouldShow = false; + + /** true = product template mode (creation stepper), false = account view mode */ + get isProductMode(): boolean { + return !!this.productTemplate && !this.accountData; + } + + ngOnInit(): void { + if (this.productTemplate && !this.accountData) { + this.initFromProductTemplate(); + } else if (this.accountData) { + this.initFromAccountData(); + } + } + + // ── Product Template Mode (Currency Step) ───────────────────── + private initFromProductTemplate(): void { + const pt = this.productTemplate; + + // Savings: fields come with "FromProduct" suffix + // RD/Current: fields come directly (isDormancyTrackingActive, daysToInactive, etc.) + this.isMonitoringActive = !!(pt.isDormancyTrackingActiveFromProduct ?? pt.isDormancyTrackingActive ?? false); + this.isAlertEnabled = !!pt.dormancyAlert; + + this.daysToInactive = pt.daysToInactiveFromProduct ?? pt.daysToInactive ?? null; + this.daysToDormancy = pt.daysToDormancyFromProduct ?? pt.daysToDormancy ?? null; + this.daysToEscheat = pt.daysToEscheatFromProduct ?? pt.daysToEscheat ?? null; + + this.statusId = 0; + this.statusLabel = 'New Account'; + + this.computeRiskFromProduct(); + this.shouldShow = this.isMonitoringActive || this.isAlertEnabled; + } + + private computeRiskFromProduct(): void { + // Product mode — always show safe/neutral, no risk colouring + this.risk = 'safe'; + this.riskLabel = 'Configured'; + this.riskSeverity = this.isMonitoringActive ? 'Active' : 'Inactive'; + } + + // ── Account Data Mode (General Tab) ─────────────────────────── + private initFromAccountData(): void { + const a = this.accountData; + + this.statusId = a.subStatus?.id ?? 0; + this.statusLabel = a.subStatus?.value ?? 'Active'; + this.isAlertEnabled = !!a.dormancyAlert; + this.isMonitoringActive = !!a.isDormancyTrackingActive; + + this.daysToInactive = a.daysToInactive ?? null; + this.daysToDormancy = a.daysToDormancy ?? null; + this.daysToEscheat = a.daysToEscheat ?? null; + + // Last active transaction date + const raw = a.lastActiveTransactionDate; + if (raw) { + const d = Array.isArray(raw) && raw.length >= 3 ? new Date(raw[0], raw[1] - 1, raw[2]) : new Date(raw); + this.lastTxnDate = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); + } + + // Account opened / activated date + const tl = a.timeline; + if (tl?.activatedOnDate) { + const ad = + Array.isArray(tl.activatedOnDate) && tl.activatedOnDate.length >= 3 + ? new Date(tl.activatedOnDate[0], tl.activatedOnDate[1] - 1, tl.activatedOnDate[2]) + : new Date(tl.activatedOnDate); + this.accountOpenedDate = ad.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); + } + + this.computeRiskFromAccount(); + this.shouldShow = + this.isAlertEnabled || + this.isMonitoringActive || + this.statusId > 0 || + (this.daysToInactive !== null && this.daysToInactive <= 90) || + !!this.lastTxnDate; + } + + private computeRiskFromAccount(): void { + if (this.statusId === 300) { + this.risk = 'escheat'; + this.riskLabel = 'Escheat'; + this.riskSeverity = 'Critical'; + } else if (this.statusId === 200) { + this.risk = 'critical'; + this.riskLabel = 'Dormant'; + this.riskSeverity = 'Critical'; + } else if (this.statusId === 100) { + this.risk = 'high'; + this.riskLabel = 'Inactive'; + this.riskSeverity = 'High'; + } else if (this.daysToInactive !== null && this.daysToInactive <= 30) { + this.risk = 'high'; + this.riskLabel = 'High'; + this.riskSeverity = 'High'; + } else if (this.daysToInactive !== null && this.daysToInactive <= 60) { + this.risk = 'medium'; + this.riskLabel = 'Medium'; + this.riskSeverity = 'Medium'; + } else if (this.daysToInactive !== null && this.daysToInactive <= 90) { + this.risk = 'low'; + this.riskLabel = 'Low'; + this.riskSeverity = 'Low'; + } else { + this.risk = 'safe'; + this.riskLabel = 'Active'; + this.riskSeverity = 'Safe'; + } + } + + get timelineSteps() { + return [ + { + label: 'Inactive', + tooltip: 'Account flagged as inactive after no customer-initiated transaction', + days: this.daysToInactive, + reached: this.statusId >= 100, + current: this.statusId === 100 + }, + { + label: 'Dormant', + tooltip: 'Account classified as dormant per RBI/regulatory guidelines', + days: this.daysToDormancy, + reached: this.statusId >= 200, + current: this.statusId === 200 + }, + { + label: 'Escheat', + tooltip: 'Unclaimed funds transferred to government/regulator as per law', + days: this.daysToEscheat, + reached: this.statusId >= 300, + current: this.statusId === 300 + } + ]; + } + + /** e.g. "Inactive after 30 days of no transaction" */ + inactiveSubtext(): string { + return this.daysToInactive !== null ? `No transaction for ${this.daysToInactive} days` : 'Threshold not configured'; + } + + dormancySubtext(): string { + const total = (this.daysToInactive ?? 0) + (this.daysToDormancy ?? 0); + return this.daysToDormancy !== null ? `${this.daysToDormancy} days after inactive` : 'Threshold not configured'; + } + + escheatSubtext(): string { + return this.daysToEscheat !== null ? `${this.daysToEscheat} days after dormant` : 'Threshold not configured'; + } +} diff --git a/src/app/shared/introducer-search/introducer-search.component.html b/src/app/shared/introducer-search/introducer-search.component.html new file mode 100644 index 000000000..d7663ef85 --- /dev/null +++ b/src/app/shared/introducer-search/introducer-search.component.html @@ -0,0 +1,130 @@ + + +
+
+ + Introducer + Required +
+ +
+ @if (!selectedClient) { + +
+ + Search by Name or Client ID + + search + + + @if (searching) { +
+
+ + Searching... +
+
+ } + + @if (!searching && searchResults.length > 0) { +
+ @for (r of searchResults; track r.id) { +
+ person +
+ {{ r.displayName }} + + ID: {{ r.id }} + @if (r.officeName) { + · {{ r.officeName }} + } + @if (r.mobileNo) { + · {{ r.mobileNo }} + } + +
+
+ } +
+ } + + @if (!searching && searchText.length >= 2 && searchResults.length === 0) { +
No clients found
+ } +
+ } + + @if (selectedClient) { + +
+
+ @if (linkedProfileImage) { + Profile + } @else { + person + } +
+
+ {{ selectedClient.displayName }} + + Client ID: {{ selectedClient.id }} + @if (selectedClient.accountNumber) { + · A/C: {{ selectedClient.accountNumber }} + } + +
+ +
+ + @if (fetchingLinkedData) { +
+ + Fetching client details... +
+ } + + @if (!fetchingLinkedData) { +
+
+ FULL NAME + {{ selectedClient.displayName }} +
+
+ DATE OF BIRTH + {{ clientDob || '—' }} +
+ @if (clientAadhaar) { +
+ AADHAAR + + {{ clientAadhaar }} + @if (linkedKycStatus?.aadhaarVerified) { + verified Verified + } + +
+ } + @if (clientPan) { +
+ PAN + + {{ clientPan }} + @if (linkedKycStatus?.panVerified) { + verified Verified + } + +
+ } +
+ } + } +
+
diff --git a/src/app/shared/introducer-search/introducer-search.component.scss b/src/app/shared/introducer-search/introducer-search.component.scss new file mode 100644 index 000000000..4b882d3cd --- /dev/null +++ b/src/app/shared/introducer-search/introducer-search.component.scss @@ -0,0 +1,316 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$blue: #2563eb; +$blue-light: #eff6ff; +$green: #16a34a; +$green-light: #dcfce7; +$border: #e2e8f0; +$text: #1e293b; +$muted: #64748b; +$gray-bg: #f8fafc; + +:host { + overflow: visible; +} + +.intro-section { + border: 1px solid $border; + border-radius: 12px; + overflow: visible; + background: #fff; + margin-top: 16px; + position: relative; + z-index: 10; + + &__header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 20px; + background: $gray-bg; + border-bottom: 1px solid $border; + border-radius: 12px 12px 0 0; + + i { + color: $blue; + font-size: 14px; + } + + span:nth-child(2) { + font-size: 14px; + font-weight: 600; + color: $text; + flex: 1; + } + } +} + +.intro-badge { + display: inline-flex; + align-items: center; + padding: 2px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 600; + + &--required { + background: #fef3c7; + color: #92400e; + border: 1px solid #fde68a; + } +} + +.intro-body { + padding: 16px 20px; + overflow: visible; + position: relative; + z-index: 1000; +} + +// Search +.intro-search-wrap { + position: relative; + z-index: 1000; + overflow: visible !important; +} + +.intro-search-field { + width: 100%; +} + +.intro-results-inline { + margin-top: 4px; + border: 1px solid $border; + border-radius: 8px; + background: #fff; + box-shadow: 0 4px 16px rgb(0 0 0 / 8%); + overflow: hidden; + max-height: 260px; + overflow-y: auto; +} + +.intro-results { + position: absolute !important; + z-index: 99999 !important; + top: calc(100% + 4px); + left: 0; + right: 0; + background: #fff; + border: 1px solid $border; + border-radius: 8px; + box-shadow: 0 4px 16px rgb(0 0 0 / 10%); + max-height: 260px; + overflow: hidden auto; + + &__loading { + display: flex; + align-items: center; + gap: 10px; + padding: 14px 16px; + font-size: 13px; + color: $muted; + } +} + +.intro-result-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + cursor: pointer; + transition: background 0.15s; + border-bottom: 1px solid $border; + min-width: 0; + + &:last-child { + border-bottom: none; + } + + &:hover { + background: $blue-light; + } + + &__icon { + color: $muted; + font-size: 20px; + width: 20px; + height: 20px; + flex: 0 0 auto; + } + + &__info { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + flex: 1; + } + + &__name { + font-size: 13px; + font-weight: 600; + color: $text; + white-space: normal; + word-break: break-word; + overflow-wrap: break-word; + } + + &__meta { + font-size: 11px; + color: $muted; + white-space: normal; + word-break: break-word; + overflow-wrap: break-word; + } +} + +.intro-no-results { + padding: 10px 0; + font-size: 13px; + color: $muted; + text-align: center; +} + +// Selected client card +.intro-card { + display: flex; + align-items: center; + gap: 14px; + padding: 14px 16px; + border: 1px solid $border; + border-radius: 10px; + background: $gray-bg; + margin-bottom: 14px; + min-width: 0; + + &__avatar { + width: 44px; + height: 44px; + border-radius: 50%; + background: $blue-light; + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + overflow: hidden; + } + + &__photo { + width: 100%; + height: 100%; + object-fit: cover; + } + + &__avatar-icon { + color: $blue; + font-size: 24px; + width: 24px; + height: 24px; + } + + &__info { + flex: 1; + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; + } + + &__name { + font-size: 14px; + font-weight: 600; + color: $text; + white-space: normal; + word-break: break-word; + overflow-wrap: break-word; + } + + &__meta { + font-size: 12px; + color: $muted; + white-space: normal; + word-break: break-word; + overflow-wrap: break-word; + } + + &__remove { + flex: 0 0 auto; + color: $muted; + } +} + +.intro-fetching { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: $muted; + padding: 6px 0; +} + +// Details grid +.intro-details-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 14px 24px; + padding: 14px 16px; + border: 1px solid $border; + border-radius: 10px; + background: #fff; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} + +.intro-detail { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; + + &__label { + font-size: 10px; + font-weight: 700; + letter-spacing: 0.06em; + color: $muted; + text-transform: uppercase; + } + + &__value { + font-size: 13px; + font-weight: 600; + color: $text; + display: flex; + align-items: center; + gap: 6px; + white-space: normal; + word-break: break-word; + overflow-wrap: break-word; + flex-wrap: wrap; + } +} + +.intro-verified { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 11px; + font-weight: 600; + color: $green; + background: $green-light; + border-radius: 999px; + padding: 2px 8px; + + mat-icon { + font-size: 12px; + width: 12px; + height: 12px; + } +} diff --git a/src/app/shared/introducer-search/introducer-search.component.ts b/src/app/shared/introducer-search/introducer-search.component.ts new file mode 100644 index 000000000..fa0410d78 --- /dev/null +++ b/src/app/shared/introducer-search/introducer-search.component.ts @@ -0,0 +1,138 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, Output, EventEmitter, inject } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { ClientsService } from 'app/clients/clients.service'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { MatIconButton } from '@angular/material/button'; +import { MatIcon } from '@angular/material/icon'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'mifosx-introducer-search', + templateUrl: './introducer-search.component.html', + styleUrls: ['./introducer-search.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + FormsModule, + MatProgressSpinner, + MatIconButton, + MatIcon + ] +}) +export class IntroducerSearchComponent { + private clientsService = inject(ClientsService); + private sanitizer = inject(DomSanitizer); + + @Output() introducerChange = new EventEmitter<{ clientId: number; displayName: string } | null>(); + + entryMode: 'linked' = 'linked'; + searchText = ''; + searching = false; + searchResults: any[] = []; + selectedClient: any = null; + fetchingLinkedData = false; + linkedProfileImage: any = null; + linkedKycStatus: any = null; + + // Fetched client details + clientDob = ''; + clientAadhaar = ''; + clientPan = ''; + + private searchTimer: any; + + onSearch(text: string): void { + this.searchText = text; + clearTimeout(this.searchTimer); + if (!text || text.length < 2) { + this.searchResults = []; + return; + } + this.searching = true; + this.searchTimer = setTimeout(() => { + this.clientsService.searchByText(text, 0, 8).subscribe({ + next: (data: any) => { + this.searchResults = data?.content || []; + this.searching = false; + }, + error: () => { + this.searchResults = []; + this.searching = false; + } + }); + }, 400); + } + + selectClient(client: any): void { + this.selectedClient = client; + this.searchResults = []; + this.searchText = ''; + this.clientDob = ''; + this.clientAadhaar = ''; + this.clientPan = ''; + this.linkedProfileImage = null; + this.linkedKycStatus = null; + this.fetchLinkedClientData(client.id); + this.introducerChange.emit({ clientId: client.id, displayName: client.displayName }); + } + + removeClient(): void { + this.selectedClient = null; + this.linkedProfileImage = null; + this.linkedKycStatus = null; + this.clientDob = ''; + this.clientAadhaar = ''; + this.clientPan = ''; + this.introducerChange.emit(null); + } + + private fetchLinkedClientData(clientId: string): void { + this.fetchingLinkedData = true; + + this.clientsService.getClientData(clientId).subscribe({ + next: (data: any) => { + if (data.dateOfBirth) { + const d = Array.isArray(data.dateOfBirth) + ? new Date(data.dateOfBirth[0], data.dateOfBirth[1] - 1, data.dateOfBirth[2]) + : new Date(data.dateOfBirth); + this.clientDob = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); + } + this.fetchingLinkedData = false; + }, + error: () => { + this.fetchingLinkedData = false; + } + }); + + this.clientsService.getClientKyc(clientId).subscribe({ + next: (kyc: any) => { + if (kyc) { + this.clientAadhaar = kyc.aadhaarNumberMasked || ''; + this.clientPan = kyc.panNumber || ''; + this.linkedKycStatus = { + panVerified: kyc.panVerified || false, + aadhaarVerified: kyc.aadhaarVerified || false + }; + } + }, + error: () => {} + }); + + this.clientsService.getClientProfileImage(clientId).subscribe({ + next: (imageData: any) => { + if (imageData) { + this.linkedProfileImage = this.sanitizer.bypassSecurityTrustResourceUrl(imageData); + } + }, + error: () => {} + }); + } +} diff --git a/src/app/shared/nominee-form/nominee-form.component.html b/src/app/shared/nominee-form/nominee-form.component.html new file mode 100644 index 000000000..49bd32d8a --- /dev/null +++ b/src/app/shared/nominee-form/nominee-form.component.html @@ -0,0 +1,282 @@ + + +
+ +
+
+ + Nomination Facility +
+
+ + +
+
+ +
+ + @if (mode === 'manual') { +
+ +
+ + Full Name * + + Full name is required + + + + Relationship * + + @for (rel of relationships; track rel) { + {{ rel }} + } + + Relationship is required + +
+ +
+ + Date of Birth + + + + + + + Nationality + + Indian + NRI + Foreign National + + +
+ + + + +
+ + Aadhaar Number + + + + + PAN Number + + +
+ +
+ + Mobile Number + + + + + Nominee Share (%) + + +
+ + + + +
+ + Guardian Name + + + + + Guardian Relationship + + @for (rel of relationships; track rel) { + {{ rel }} + } + + +
+ + +
+ Is Alive + Is Dependent +
+ + + + Remarks + + +
+ } + + + @if (mode === 'linked') { + @if (!selectedClient) { + +
+ + Search by Name or Client ID + + search + + + @if (searching) { +
+
+ + Searching... +
+
+ } + + @if (!searching && searchResults.length > 0) { +
+ @for (r of searchResults; track r.id) { +
+ person +
+ {{ r.displayName }} + + ID: {{ r.id }} + @if (r.officeName) { + · {{ r.officeName }} + } + @if (r.mobileNo) { + · {{ r.mobileNo }} + } + +
+
+ } +
+ } + + @if (!searching && searchText.length >= 2 && searchResults.length === 0) { +
No clients found
+ } +
+ } + + @if (selectedClient) { + +
+
+ @if (linkedProfileImage) { + Profile + } @else { + person + } +
+
+ {{ selectedClient.displayName }} + + Client ID: {{ selectedClient.id }} + @if (selectedClient.accountNumber) { + · A/C: {{ selectedClient.accountNumber }} + } + +
+ +
+ + @if (fetchingLinkedData) { +
+ + Fetching nominee details... +
+ } + + @if (!fetchingLinkedData) { +
+
+ Full Name + {{ selectedClient.displayName }} +
+
+ Date of Birth + {{ clientDob || '—' }} +
+ @if (clientAadhaar) { +
+ Aadhaar + + {{ clientAadhaar }} + @if (linkedKycStatus?.aadhaarVerified) { + verified Verified + } + +
+ } + @if (clientPan) { +
+ PAN + + {{ clientPan }} + @if (linkedKycStatus?.panVerified) { + verified Verified + } + +
+ } +
+ + +
+ + Relationship * + + @for (rel of relationships; track rel) { + {{ rel }} + } + + + + + Nominee Share (%) + + +
+ } + } + } +
+
diff --git a/src/app/shared/nominee-form/nominee-form.component.scss b/src/app/shared/nominee-form/nominee-form.component.scss new file mode 100644 index 000000000..edfa9a96f --- /dev/null +++ b/src/app/shared/nominee-form/nominee-form.component.scss @@ -0,0 +1,387 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +@use 'sass:color'; + +$blue: #2563eb; +$blue-light: #eff6ff; +$green: #16a34a; +$green-light: #dcfce7; +$border: #e2e8f0; +$text: #1e293b; +$muted: #64748b; +$gray-bg: #f8fafc; +$purple: #7c3aed; +$purple-light: #f5f3ff; + +:host { + display: block; +} + +// ─── Wrapper ─────────────────────────────────────────────────── +.nom-section { + border: 1px solid $border; + border-radius: 14px; + background: #fff; + margin-top: 20px; + overflow: visible; + position: relative; + z-index: 10; +} + +// ─── Header ──────────────────────────────────────────────────── +.nom-header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 12px; + padding: 14px 20px; + background: $gray-bg; + border-bottom: 1px solid $border; + border-radius: 14px 14px 0 0; + + &__left { + display: flex; + align-items: center; + gap: 10px; + + i { + color: $purple; + font-size: 15px; + } + } + + &__title { + font-size: 14px; + font-weight: 700; + color: $text; + } +} + +// ─── Mode toggle tabs ────────────────────────────────────────── +.nom-mode-tabs { + display: flex; + gap: 8px; +} + +.nom-tab { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 14px; + border-radius: 8px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + border: 1.5px solid $border; + background: #fff; + color: $muted; + transition: all 0.18s; + + i { + font-size: 11px; + } + + &:hover { + border-color: $blue; + color: $blue; + background: $blue-light; + } + + &--active { + border-color: $blue; + background: $blue; + color: #fff; + + &:hover { + background: color.adjust(#2563eb, $lightness: -5%); + } + } +} + +// ─── Body ────────────────────────────────────────────────────── +.nom-body { + padding: 20px; + overflow: visible; + position: relative; + z-index: 10; +} + +// ─── Manual form grid ────────────────────────────────────────── +.nom-form { + display: flex; + flex-direction: column; + gap: 0; +} + +.nom-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} + +.nom-field { + width: 100%; + + &--full { + width: 100%; + display: block; + } +} + +.nom-section-label { + display: flex; + align-items: center; + gap: 8px; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.07em; + text-transform: uppercase; + color: $blue; + margin: 8px 0 4px; + + i { + font-size: 12px; + } +} + +.nom-hint { + font-size: 10px; + font-weight: 500; + text-transform: none; + letter-spacing: 0; + color: $muted; + margin-left: 2px; +} + +.nom-flags { + display: flex; + align-items: center; + gap: 24px; + margin: 8px 0 12px; +} + +// ─── Search (link mode) ──────────────────────────────────────── +.nom-search-wrap { + position: relative; + z-index: 1000; + overflow: visible; +} + +.nom-results { + margin-top: 4px; + border: 1px solid $border; + border-radius: 8px; + background: #fff; + box-shadow: 0 4px 20px rgb(0 0 0 / 10%); + overflow: hidden; + max-height: 260px; + overflow-y: auto; + + &__loading { + display: flex; + align-items: center; + gap: 10px; + padding: 14px 16px; + font-size: 13px; + color: $muted; + } +} + +.nom-result-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + cursor: pointer; + transition: background 0.15s; + border-bottom: 1px solid $border; + + &:last-child { + border-bottom: none; + } + + &:hover { + background: $blue-light; + } + + &__icon { + color: $muted; + font-size: 20px; + width: 20px; + height: 20px; + flex: 0 0 auto; + } + + &__info { + display: flex; + flex-direction: column; + gap: 2px; + } + + &__name { + font-size: 13px; + font-weight: 600; + color: $text; + } + + &__meta { + font-size: 11px; + color: $muted; + } +} + +.nom-no-results { + padding: 12px 0; + font-size: 13px; + color: $muted; + text-align: center; +} + +// ─── Selected client card ────────────────────────────────────── +.nom-client-card { + display: flex; + align-items: center; + gap: 14px; + padding: 14px 16px; + border: 1px solid $border; + border-radius: 10px; + background: $gray-bg; + margin-bottom: 14px; + + &__avatar { + width: 46px; + height: 46px; + border-radius: 50%; + background: $blue-light; + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + overflow: hidden; + } + + &__photo { + width: 100%; + height: 100%; + object-fit: cover; + } + + &__avatar-icon { + color: $blue; + font-size: 26px; + width: 26px; + height: 26px; + } + + &__info { + flex: 1; + display: flex; + flex-direction: column; + gap: 3px; + } + + &__name { + font-size: 14px; + font-weight: 700; + color: $text; + } + + &__meta { + font-size: 12px; + color: $muted; + } + + &__remove { + flex: 0 0 auto; + color: $muted; + } +} + +.nom-fetching { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: $muted; + padding: 6px 0; +} + +// ─── Linked client details grid ──────────────────────────────── +.nom-details-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 14px 24px; + padding: 14px 16px; + border: 1px solid $border; + border-radius: 10px; + background: #fff; + margin-bottom: 14px; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} + +.nom-detail { + display: flex; + flex-direction: column; + gap: 4px; + + &__label { + font-size: 10px; + font-weight: 700; + letter-spacing: 0.06em; + color: $muted; + text-transform: uppercase; + } + + &__value { + font-size: 13px; + font-weight: 600; + color: $text; + display: flex; + align-items: center; + gap: 6px; + } +} + +.nom-verified { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 11px; + font-weight: 600; + color: $green; + background: $green-light; + border-radius: 999px; + padding: 2px 8px; + + mat-icon { + font-size: 12px; + width: 12px; + height: 12px; + } +} + +// ─── Extras for linked (relationship + share) ────────────────── +.nom-linked-extras { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin-top: 4px; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} diff --git a/src/app/shared/nominee-form/nominee-form.component.ts b/src/app/shared/nominee-form/nominee-form.component.ts new file mode 100644 index 000000000..594692183 --- /dev/null +++ b/src/app/shared/nominee-form/nominee-form.component.ts @@ -0,0 +1,254 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, Output, EventEmitter, inject, OnInit } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators, FormsModule } from '@angular/forms'; +import { DomSanitizer } from '@angular/platform-browser'; +import { ClientsService } from 'app/clients/clients.service'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { MatIconButton } from '@angular/material/button'; +import { MatIcon } from '@angular/material/icon'; +import { MatCheckbox } from '@angular/material/checkbox'; +import { MatDatepickerModule } from '@angular/material/datepicker'; + +export interface NomineeData { + mode: 'manual' | 'linked'; + // Manual fields + fullName?: string; + relationship?: string; + dateOfBirth?: string; + nationality?: string; + aadhaarNumber?: string; + panNumber?: string; + mobileNo?: string; + nomineeShare?: number; + isAlive?: boolean; + isDependent?: boolean; + guardianName?: string; + guardianRelationship?: string; + remarks?: string; + // Linked client fields + clientId?: number; + displayName?: string; +} + +const RELATIONSHIPS = [ + 'Spouse', + 'Son', + 'Daughter', + 'Father', + 'Mother', + 'Brother', + 'Sister', + 'Grandfather', + 'Grandmother', + 'Grandson', + 'Granddaughter', + 'Uncle', + 'Aunt', + 'Nephew', + 'Niece', + 'Friend', + 'Other' +]; + +@Component({ + selector: 'mifosx-nominee-form', + templateUrl: './nominee-form.component.html', + styleUrls: ['./nominee-form.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + FormsModule, + MatProgressSpinner, + MatIconButton, + MatIcon, + MatCheckbox, + MatDatepickerModule + ] +}) +export class NomineeFormComponent implements OnInit { + private fb = inject(UntypedFormBuilder); + private clientsService = inject(ClientsService); + private sanitizer = inject(DomSanitizer); + + @Output() nomineeChange = new EventEmitter(); + + mode: 'manual' | 'linked' = 'manual'; + relationships = RELATIONSHIPS; + + // Manual form + manualForm!: UntypedFormGroup; + minDate = new Date(1900, 0, 1); + maxDate = new Date(); + + // Link mode + searchText = ''; + searching = false; + searchResults: any[] = []; + selectedClient: any = null; + fetchingLinkedData = false; + linkedProfileImage: any = null; + linkedKycStatus: any = null; + clientDob = ''; + clientAadhaar = ''; + clientPan = ''; + linkedRelationship = ''; + linkedShare = 100; + + private searchTimer: any; + + ngOnInit(): void { + this.manualForm = this.fb.group({ + fullName: [ + '', + Validators.required + ], + relationship: [ + '', + Validators.required + ], + dateOfBirth: [''], + nationality: ['Indian'], + aadhaarNumber: [''], + panNumber: [''], + mobileNo: [''], + nomineeShare: [100], + isAlive: [true], + isDependent: [false], + guardianName: [''], + guardianRelationship: [''], + remarks: [''] + }); + + this.manualForm.valueChanges.subscribe(() => { + if (this.mode === 'manual' && this.manualForm.valid) { + this.emitManual(); + } + }); + } + + switchMode(m: 'manual' | 'linked'): void { + this.mode = m; + if (m === 'manual') { + this.clearLinked(); + } else { + this.manualForm.reset({ nationality: 'Indian', isAlive: true, isDependent: false, nomineeShare: 100 }); + this.nomineeChange.emit(null); + } + } + + // ─── Manual ────────────────────────────────────── + private emitManual(): void { + const v = this.manualForm.value; + this.nomineeChange.emit({ mode: 'manual', ...v }); + } + + // ─── Link existing client ───────────────────────── + onSearch(text: string): void { + this.searchText = text; + clearTimeout(this.searchTimer); + if (!text || text.length < 2) { + this.searchResults = []; + return; + } + this.searching = true; + this.searchTimer = setTimeout(() => { + this.clientsService.searchByText(text, 0, 8).subscribe({ + next: (data: any) => { + this.searchResults = data?.content || []; + this.searching = false; + }, + error: () => { + this.searchResults = []; + this.searching = false; + } + }); + }, 400); + } + + selectClient(client: any): void { + this.selectedClient = client; + this.searchResults = []; + this.searchText = ''; + this.fetchLinkedClientData(client.id); + this.nomineeChange.emit({ mode: 'linked', clientId: client.id, displayName: client.displayName }); + } + + removeLinked(): void { + this.clearLinked(); + this.nomineeChange.emit(null); + } + + onLinkedRelationshipChange(rel: string): void { + if (this.selectedClient) { + this.nomineeChange.emit({ + mode: 'linked', + clientId: this.selectedClient.id, + displayName: this.selectedClient.displayName, + relationship: rel, + nomineeShare: this.linkedShare + }); + } + } + + private clearLinked(): void { + this.selectedClient = null; + this.linkedProfileImage = null; + this.linkedKycStatus = null; + this.clientDob = ''; + this.clientAadhaar = ''; + this.clientPan = ''; + this.searchText = ''; + this.searchResults = []; + this.linkedRelationship = ''; + this.linkedShare = 100; + } + + private fetchLinkedClientData(clientId: string): void { + this.fetchingLinkedData = true; + + this.clientsService.getClientData(clientId).subscribe({ + next: (data: any) => { + if (data.dateOfBirth) { + const d = Array.isArray(data.dateOfBirth) + ? new Date(data.dateOfBirth[0], data.dateOfBirth[1] - 1, data.dateOfBirth[2]) + : new Date(data.dateOfBirth); + this.clientDob = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); + } + this.fetchingLinkedData = false; + }, + error: () => { + this.fetchingLinkedData = false; + } + }); + + this.clientsService.getClientKyc(clientId).subscribe({ + next: (kyc: any) => { + if (kyc) { + this.clientAadhaar = kyc.aadhaarNumberMasked || ''; + this.clientPan = kyc.panNumber || ''; + this.linkedKycStatus = { + panVerified: kyc.panVerified || false, + aadhaarVerified: kyc.aadhaarVerified || false + }; + } + }, + error: () => {} + }); + + this.clientsService.getClientProfileImage(clientId).subscribe({ + next: (imageData: any) => { + if (imageData) { + this.linkedProfileImage = this.sanitizer.bypassSecurityTrustResourceUrl(imageData); + } + }, + error: () => {} + }); + } +} diff --git a/src/environments/.env.ts b/src/environments/.env.ts index c31d3d90c..9feeaaf07 100644 --- a/src/environments/.env.ts +++ b/src/environments/.env.ts @@ -5,13 +5,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - // IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN! /* tslint:disable */ export default { 'mifos_x': { - 'version': '260422', - 'hash': 'e9a93771' + 'version': '260424', + 'hash': 'fc1c5126' }, 'allow_switching_backend_instance': true }; From 5fb25edca5294da71e922b96d50580b4cbd98793 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Sun, 26 Apr 2026 02:11:28 +0530 Subject: [PATCH 2/3] add gl mapping in the saving product,current product,rd product and also add genrate account no all three --- .../current-account-details-step.component.html | 8 ++++++-- ...ng-deposits-account-details-step.component.html | 10 ++++++++-- .../current-product-details-step.component.html | 8 ++++++-- .../current-product-details-step.component.ts | 14 +++++++++++--- .../current-product-preview-step.component.html | 8 ++++---- .../current-product-preview-step.component.ts | 7 +++++++ .../current-product-general-tab.component.html | 8 ++++---- ...ing-deposit-product-details-step.component.html | 8 ++++++-- ...rring-deposit-product-details-step.component.ts | 14 +++++++++++--- ...ing-deposit-product-preview-step.component.html | 8 ++++---- ...rring-deposit-product-preview-step.component.ts | 7 +++++++ .../recurring-deposit-general-tab.component.html | 6 +++--- .../saving-product-details-step.component.html | 8 ++++++-- .../saving-product-details-step.component.ts | 14 +++++++++++--- .../saving-product-preview-step.component.html | 8 ++++---- .../saving-product-preview-step.component.ts | 7 +++++++ .../saving-product-general-tab.component.html | 8 ++++---- .../savings-account-details-step.component.html | 8 ++++++-- src/environments/.env.ts | 4 ++-- 19 files changed, 117 insertions(+), 46 deletions(-) diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html index 440bf7b4f..46882eb22 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html @@ -83,8 +83,12 @@ - Product Code - + Account No + @if (productTemplate.effectiveDate) { diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html index 1b01888e6..186f2f076 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html @@ -83,8 +83,14 @@ - Product Code - + Account No + @if (productTemplate.effectiveDate) { diff --git a/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.html b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.html index 7b619793d..65c350380 100644 --- a/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.html +++ b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.html @@ -82,8 +82,12 @@ - Product Code - + GL Account (Liability) + + @for (account of liabilityAccountOptions; track account.id) { + {{ account.name }} ({{ account.glCode }}) + } + diff --git a/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.ts b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.ts index 1aba2fbac..0c7125ca5 100644 --- a/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.ts +++ b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.ts @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core'; +import { Component, OnInit, OnChanges, Input, SimpleChanges, inject, ChangeDetectorRef } from '@angular/core'; import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; import { MatTooltip } from '@angular/material/tooltip'; import { CdkTextareaAutosize } from '@angular/cdk/text-field'; @@ -30,12 +30,14 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges { private formBuilder = inject(UntypedFormBuilder); private dateUtils = inject(Dates); private settingsService = inject(SettingsService); + private cdr = inject(ChangeDetectorRef); @Input() CurrentProductsTemplate: any; CurrentProductDetailsForm: UntypedFormGroup; accountTypeData: string[] = []; customerSegmentData: string[] = []; + liabilityAccountOptions: any[] = []; yesNoOptions: Array<{ value: boolean; label: string; @@ -82,7 +84,7 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges { Validators.required ], customerSegment: [null], - productCode: [''], + glAccountId: [null], effectiveTo: [''], endDate: [''], introducerRequired: [false], @@ -124,6 +126,8 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges { this.accountTypeData = accountTypeOptions; this.customerSegmentData = customerSegmentOptions; + this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? []; + this.cdr.detectChanges(); if (yesNoOptions.length) { this.yesNoOptions = yesNoOptions; } @@ -136,7 +140,7 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges { description: template.description, accountType: template.accountType ?? this.accountTypeData[0] ?? null, customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null, - productCode: template.productCode ?? '', + glAccountId: template.glAccountId ?? null, effectiveTo: this.parseDate(template.effectiveTo), endDate: this.parseDate(template.endDate), introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes', @@ -173,6 +177,10 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges { .filter((value) => !!value); } + compareById(o1: any, o2: any): boolean { + return o1 != null && o2 != null && Number(o1) === Number(o2); + } + get CurrentProductDetails() { const formValue = { ...this.CurrentProductDetailsForm.value }; const dateFormat = this.settingsService.dateFormat; diff --git a/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.html b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.html index 68e5bdde8..c74008227 100644 --- a/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.html +++ b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.html @@ -28,7 +28,7 @@ @if ( CurrentProduct.accountType || CurrentProduct.customerSegment || - CurrentProduct.productCode || + CurrentProduct.glAccountId || CurrentProduct.effectiveTo || CurrentProduct.introducerRequired !== undefined || CurrentProduct.shareLinkingEnabled !== undefined @@ -51,10 +51,10 @@
} - @if (CurrentProduct.productCode) { + @if (CurrentProduct.glAccountId) {
- Product Code: - {{ CurrentProduct.productCode }} + GL Account (Liability): + {{ getGlAccountName(CurrentProduct.glAccountId) }}
} diff --git a/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.ts b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.ts index 2ff2bd27c..8800b5b92 100644 --- a/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.ts +++ b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.ts @@ -205,6 +205,13 @@ export class CurrentProductPreviewStepComponent implements OnInit, OnChanges { return value === true || value === 'Yes' ? 'Yes' : 'No'; } + getGlAccountName(id: any): string { + if (!id) return ''; + const accounts = this.CurrentProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions ?? []; + const found = accounts.find((a: any) => a.id === id || a.id === +id); + return found ? `${found.name} (${found.glCode})` : String(id); + } + getLockinPeriodFrequencyTypeLabel(value: any): string { switch (value) { case 'DAYS': diff --git a/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.html b/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.html index c316d2003..0a68dd9d7 100644 --- a/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.html +++ b/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.html @@ -40,7 +40,7 @@ @if ( currentProduct.accountType || currentProduct.customerSegment || - currentProduct.productCode || + currentProduct.glAccountId || currentProduct.effectiveDate || currentProduct.effectiveTo || currentProduct.endDate || @@ -65,10 +65,10 @@
} - @if (currentProduct.productCode) { + @if (currentProduct.glAccountId) {
- Product Code: - {{ currentProduct.productCode }} + GL Account (Liability): + {{ currentProduct.glAccountName || currentProduct.glAccountId }}
} diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.html index 7e5f20a46..35a3129ac 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.html @@ -88,8 +88,12 @@ - Product Code - + GL Account (Liability) + + @for (account of liabilityAccountOptions; track account.id) { + {{ account.name }} ({{ account.glCode }}) + } + diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.ts b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.ts index 3f2cd558c..749ac8a3b 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.ts +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.ts @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core'; +import { Component, OnInit, OnChanges, Input, SimpleChanges, inject, ChangeDetectorRef } from '@angular/core'; import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; import { MatTooltip } from '@angular/material/tooltip'; import { CdkTextareaAutosize } from '@angular/cdk/text-field'; @@ -28,12 +28,14 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh private formBuilder = inject(UntypedFormBuilder); private dateUtils = inject(Dates); private settingsService = inject(SettingsService); + private cdr = inject(ChangeDetectorRef); @Input() recurringDepositProductsTemplate: any; recurringDepositProductDetailsForm: UntypedFormGroup; accountTypeData: string[] = []; customerSegmentData: string[] = []; + liabilityAccountOptions: any[] = []; yesNoOptions: Array<{ value: boolean; label: string }> = [ { value: true, label: 'Yes' }, { value: false, label: 'No' } @@ -71,7 +73,7 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh Validators.required ], customerSegment: [null], - productCode: [''], + glAccountId: [null], effectiveTo: [''], endDate: [''], introducerRequired: [false], @@ -99,9 +101,15 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh return new Date(dateValue); } + compareById(o1: any, o2: any): boolean { + return o1 != null && o2 != null && Number(o1) === Number(o2); + } + private applyTemplate(template: any) { this.accountTypeData = this.getOptionNames(template.accountTypeOptions); this.customerSegmentData = this.getOptionNames(template.customerSegmentOptions); + this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? []; + this.cdr.detectChanges(); const isEditing = !!template.id; this.recurringDepositProductDetailsForm.patchValue({ @@ -110,7 +118,7 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh description: template.description || '', accountType: template.accountType ?? this.accountTypeData[0] ?? null, customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null, - productCode: template.productCode ?? '', + glAccountId: template.glAccountId ?? null, effectiveTo: this.parseDate(template.effectiveTo), endDate: this.parseDate(template.endDate), introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes', diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.html index a421e9a2e..c32f8fbd1 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.html @@ -31,7 +31,7 @@ @if ( recurringDepositProduct.accountType || recurringDepositProduct.customerSegment || - recurringDepositProduct.productCode || + recurringDepositProduct.glAccountId || recurringDepositProduct.effectiveTo || recurringDepositProduct.introducerRequired !== undefined || recurringDepositProduct.shareLinkingEnabled !== undefined @@ -53,10 +53,10 @@ } - @if (recurringDepositProduct.productCode) { + @if (recurringDepositProduct.glAccountId) {
- Product Code: - {{ recurringDepositProduct.productCode }} + GL Account (Liability): + {{ getGlAccountName(recurringDepositProduct.glAccountId) }}
} diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.ts b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.ts index dadf5d011..9aa3f0276 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.ts +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.ts @@ -121,6 +121,13 @@ export class RecurringDepositProductPreviewStepComponent implements OnInit, OnCh return found?.value || String(value); } + getGlAccountName(id: any): string { + if (!id) return ''; + const accounts = this.recurringDepositProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions ?? []; + const found = accounts.find((a: any) => a.id === id || a.id === +id); + return found ? `${found.name} (${found.glCode})` : String(id); + } + formatGlAccountById(glAccountId: number, options: any[]): string { if (!glAccountId || !Array.isArray(options)) return '-'; const account = this.accounting.glAccountLookUp(glAccountId, options as any); diff --git a/src/app/products/recurring-deposit-products/view-recurring-deposit-product/recurring-deposit-general-tab/recurring-deposit-general-tab.component.html b/src/app/products/recurring-deposit-products/view-recurring-deposit-product/recurring-deposit-general-tab/recurring-deposit-general-tab.component.html index 09319e1dc..f673989be 100644 --- a/src/app/products/recurring-deposit-products/view-recurring-deposit-product/recurring-deposit-general-tab/recurring-deposit-general-tab.component.html +++ b/src/app/products/recurring-deposit-products/view-recurring-deposit-product/recurring-deposit-general-tab/recurring-deposit-general-tab.component.html @@ -54,10 +54,10 @@ {{ recurringDepositProduct.customerSegment }} } - @if (recurringDepositProduct.productCode) { + @if (recurringDepositProduct.glAccountId) {
- Product Code: - {{ recurringDepositProduct.productCode }} + GL Account (Liability): + {{ recurringDepositProduct.glAccountName || recurringDepositProduct.glAccountId }}
} @if (recurringDepositProduct.effectiveTo) { diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html index 93ae5be7c..7a8c5d3dc 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html @@ -82,8 +82,12 @@
- Product Code - + GL Account (Liability) + + @for (account of liabilityAccountOptions; track account.id) { + {{ account.name }} ({{ account.glCode }}) + } + diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts index 2805dc866..c9d2ba566 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core'; +import { Component, OnInit, OnChanges, Input, SimpleChanges, inject, ChangeDetectorRef } from '@angular/core'; import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; import { MatTooltip } from '@angular/material/tooltip'; import { CdkTextareaAutosize } from '@angular/cdk/text-field'; @@ -30,12 +30,14 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges { private formBuilder = inject(UntypedFormBuilder); private dateUtils = inject(Dates); private settingsService = inject(SettingsService); + private cdr = inject(ChangeDetectorRef); @Input() savingProductsTemplate: any; savingProductDetailsForm: UntypedFormGroup; accountTypeData: string[] = []; customerSegmentData: string[] = []; + liabilityAccountOptions: any[] = []; yesNoOptions: Array<{ value: boolean; label: string; @@ -82,7 +84,7 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges { Validators.required ], customerSegment: [null], - productCode: [''], + glAccountId: [null], effectiveDate: [''], effectiveTo: [''], endDate: [''], @@ -110,6 +112,8 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges { this.accountTypeData = accountTypeOptions; this.customerSegmentData = customerSegmentOptions; + this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? []; + this.cdr.detectChanges(); if (yesNoOptions.length) { this.yesNoOptions = yesNoOptions; } @@ -122,7 +126,7 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges { description: template.description, accountType: template.accountType ?? this.accountTypeData[0] ?? null, customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null, - productCode: template.productCode ?? '', + glAccountId: template.glAccountId ?? null, effectiveDate: template.effectiveDate ? new Date(template.effectiveDate) : '', effectiveTo: template.effectiveTo ? new Date(template.effectiveTo) : '', endDate: template.endDate ? new Date(template.endDate) : '', @@ -162,6 +166,10 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges { .filter((value) => !!value); } + compareById(o1: any, o2: any): boolean { + return o1 != null && o2 != null && Number(o1) === Number(o2); + } + get savingProductDetails() { const formValue = { ...this.savingProductDetailsForm.value }; const dateFormat = this.settingsService.dateFormat; diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.html b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.html index 1fbae2866..8710a144e 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.html +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.html @@ -28,7 +28,7 @@ @if ( savingProduct.accountType || savingProduct.customerSegment || - savingProduct.productCode || + savingProduct.glAccountId || savingProduct.effectiveTo || savingProduct.introducerRequired !== undefined || savingProduct.shareLinkingEnabled !== undefined @@ -51,10 +51,10 @@ } - @if (savingProduct.productCode) { + @if (savingProduct.glAccountId) {
- Product Code: - {{ savingProduct.productCode }} + GL Account (Liability): + {{ getGlAccountName(savingProduct.glAccountId) }}
} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.ts index e1b544103..decfc645d 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.ts +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.ts @@ -201,6 +201,13 @@ export class SavingProductPreviewStepComponent implements OnInit, OnChanges { return this.accounting.isAccrualAccounting(this.accountingRule); } + getGlAccountName(id: any): string { + if (!id) return ''; + const accounts = this.savingProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions ?? []; + const found = accounts.find((a: any) => a.id === id || a.id === +id); + return found ? `${found.name} (${found.glCode})` : String(id); + } + getBooleanLabel(value: any): string { return value === true || value === 'Yes' ? 'Yes' : 'No'; } diff --git a/src/app/products/saving-products/view-saving-product/saving-product-general-tab/saving-product-general-tab.component.html b/src/app/products/saving-products/view-saving-product/saving-product-general-tab/saving-product-general-tab.component.html index cde61d82f..a36665ab5 100644 --- a/src/app/products/saving-products/view-saving-product/saving-product-general-tab/saving-product-general-tab.component.html +++ b/src/app/products/saving-products/view-saving-product/saving-product-general-tab/saving-product-general-tab.component.html @@ -40,7 +40,7 @@ @if ( savingProduct.accountType || savingProduct.customerSegment || - savingProduct.productCode || + savingProduct.glAccountId || savingProduct.effectiveDate || savingProduct.effectiveTo || savingProduct.endDate || @@ -65,10 +65,10 @@ } - @if (savingProduct.productCode) { + @if (savingProduct.glAccountId) {
- Product Code: - {{ savingProduct.productCode }} + GL Account (Liability): + {{ savingProduct.glAccountName || savingProduct.glAccountId }}
} diff --git a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html index 60304a70d..4bce9f33d 100644 --- a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html @@ -87,8 +87,12 @@
- Product Code - + Account No + @if (productTemplate.effectiveDate) { diff --git a/src/environments/.env.ts b/src/environments/.env.ts index 9feeaaf07..d1086eaaa 100644 --- a/src/environments/.env.ts +++ b/src/environments/.env.ts @@ -9,8 +9,8 @@ /* tslint:disable */ export default { 'mifos_x': { - 'version': '260424', - 'hash': 'fc1c5126' + 'version': '260425', + 'hash': 'fbaec185' }, 'allow_switching_backend_instance': true }; From e4516934a7d3f4eb7485e4a91d98f681cf2d0b00 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Mon, 27 Apr 2026 10:55:03 +0530 Subject: [PATCH 3/3] saving account,current account and rd account modify based on new update --- .../create-current-account.component.html | 3 + .../create-current-account.component.ts | 26 +- ...rrent-account-currency-step.component.html | 6 +- .../add-beneficiary-dialog.component.html | 190 ++++++ .../add-beneficiary-dialog.component.scss | 415 ++++++++++++ .../add-beneficiary-dialog.component.ts | 167 +++++ .../add-related-client-dialog.component.html | 135 ++++ .../add-related-client-dialog.component.scss | 359 +++++++++++ .../add-related-client-dialog.component.ts | 104 +++ ...urrent-account-details-step.component.html | 202 ++++++ ...urrent-account-details-step.component.scss | 608 ++++++++++++++++++ .../current-account-details-step.component.ts | 132 +++- ...urrent-account-preview-step.component.html | 451 +++++++++---- ...urrent-account-preview-step.component.scss | 324 +++++++++- .../current-account-preview-step.component.ts | 37 +- ...-recurring-deposits-account.component.html | 4 + ...te-recurring-deposits-account.component.ts | 25 +- ...osits-account-currency-step.component.html | 10 +- ...deposits-account-details-step.component.ts | 8 +- ...nt-interest-rate-chart-step.component.html | 235 ++----- ...nt-interest-rate-chart-step.component.scss | 99 +++ ...ount-interest-rate-chart-step.component.ts | 20 +- ...posits-account-preview-step.component.html | 402 ++++++++---- ...posits-account-preview-step.component.scss | 193 ++++++ ...deposits-account-preview-step.component.ts | 16 + .../create-savings-account.component.html | 2 + .../create-savings-account.component.ts | 15 + ...vings-account-currency-step.component.html | 6 +- .../savings-account-details-step.component.ts | 8 +- ...avings-account-preview-step.component.html | 386 +++++++---- ...avings-account-preview-step.component.scss | 271 +++++++- .../savings-account-preview-step.component.ts | 34 +- .../introducer-search.component.ts | 8 +- .../nominee-form/nominee-form.component.ts | 11 +- src/environments/.env.ts | 5 +- src/main.scss | 48 ++ 36 files changed, 4302 insertions(+), 663 deletions(-) create mode 100644 src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.html create mode 100644 src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.scss create mode 100644 src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.ts create mode 100644 src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.html create mode 100644 src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.scss create mode 100644 src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.ts diff --git a/src/app/current-accounts/create-current-account/create-current-account.component.html b/src/app/current-accounts/create-current-account/create-current-account.component.html index 1f07c596b..6a81d16b6 100644 --- a/src/app/current-accounts/create-current-account/create-current-account.component.html +++ b/src/app/current-accounts/create-current-account/create-current-account.component.html @@ -96,6 +96,9 @@ [currentAccountSettingsForm]="currentAccountSettingsForm" [currentAccountTermsForm]="currentAccountTermsForm" [currentAccount]="currentAccount" + [currentAccountDigital]="currentAccountDigital" + [currentAccountNominee]="currentAccountNominee" + [currentAccountCurrencyFlags]="currentAccountCurrencyFlags" (submitEvent)="submit()" >
diff --git a/src/app/current-accounts/create-current-account/create-current-account.component.ts b/src/app/current-accounts/create-current-account/create-current-account.component.ts index 9e0cad657..1329e5312 100644 --- a/src/app/current-accounts/create-current-account/create-current-account.component.ts +++ b/src/app/current-accounts/create-current-account/create-current-account.component.ts @@ -66,14 +66,15 @@ export class CreateCurrentAccountComponent { /** Current Account Product Template */ currentAccountProductTemplate: any; - /** Current Account Details Step */ @ViewChild(CurrentAccountDetailsStepComponent, { static: true }) currentAccountDetailsStep: CurrentAccountDetailsStepComponent; - /** Current Account Currency Step */ - @ViewChild(CurrentAccountCurrencyStepComponent, { static: true }) + @ViewChild(CurrentAccountCurrencyStepComponent) currentAccountCurrencyStep: CurrentAccountCurrencyStepComponent; + @ViewChild(CurrentAccountDigitalStepComponent) + currentAccountDigitalStep: CurrentAccountDigitalStepComponent; + constructor() { this.route.data.subscribe((data: { currentAccountTemplate: any }) => { this.currentAccountTemplate = data.currentAccountTemplate; @@ -107,12 +108,23 @@ export class CreateCurrentAccountComponent { return new FormGroup({}); } - /** - * Retrieves current account object. - */ get currentAccount() { + return { ...this.currentAccountDetailsStep.currentAccountDetails }; + } + + get currentAccountDigital() { + return this.currentAccountDigitalStep?.currentAccountDigital ?? null; + } + + get currentAccountNominee() { + return this.currentAccountCurrencyStep?.nomineeData ?? null; + } + + get currentAccountCurrencyFlags() { return { - ...this.currentAccountDetailsStep.currentAccountDetails + balanceSwapOption: this.currentAccountCurrencyStep?.balanceSwapOption ?? false, + msme: this.currentAccountCurrencyStep?.msme ?? false, + autoDebitFacility: this.currentAccountCurrencyStep?.autoDebitFacility ?? false }; } diff --git a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.html index e790fd695..935e312dd 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.html @@ -59,8 +59,9 @@ {{ currentAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }} - - + @if (currentAccountProductTemplate?.dormancyAlert) { + + }
@@ -71,7 +72,6 @@ {{ currentAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
- @if (currentAccountProductTemplate?.nominationFacility) { } diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.html b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.html new file mode 100644 index 000000000..1b0c61fc7 --- /dev/null +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.html @@ -0,0 +1,190 @@ + + + +
+
+ person_pin +
+
+

Add Beneficiary / Nominee

+

Register a nominee for this account as per Indian banking norms

+
+ +
+ + +
+ +
+ + +
+ + + @if (mode === 'search') { +
+ + Search Client by Name / CIF + search + + @if (isSearching) { + + } + @if (selectedClient) { + + } + + + @if (searchResults.length > 0) { +
+ @for (client of searchResults; track client) { +
+
{{ (client.displayName || '?')[0].toUpperCase() }}
+
+ {{ client.displayName }} + + @if (client.cifNumber) { + {{ client.cifNumber }} + } + @if (client.officeName) { + {{ client.officeName }} + } + +
+ chevron_right +
+ } +
+ } + + @if (selectedClient) { +
+
{{ (selectedClient.displayName || '?')[0].toUpperCase() }}
+
+ {{ selectedClient.displayName }} + {{ selectedClient.cifNumber || selectedClient.accountNumber }} +
+ Linked +
+ } +
+ } + + + + Nominee / Beneficiary Name * + + Name is required + + + +
+ + Relationship * + + @for (r of relationships; track r) { + {{ r }} + } + + Relationship is required + + + + Date of Birth + + + + +
+ + +
+ + Share % * + + % + Enter 1–100 + +
+ @for (p of [25, 33, 50, 100]; track p) { + + } +
+
+ + +
+ + Nominee is a Minor (below 18 years) + +
+ + + @if (isMinor) { +
+
+ supervisor_account + Guardian Details +
+
+ + Guardian Name + + + + Guardian Relationship + + @for (r of guardianRelationships; track r) { + {{ r }} + } + + +
+
+ } +
+
+ + + + + diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.scss b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.scss new file mode 100644 index 000000000..4c9e71b0c --- /dev/null +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.scss @@ -0,0 +1,415 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$primary: #1a56db; +$primary-light: #eff6ff; +$primary-border: #bfdbfe; +$success: #057a55; +$success-light: #f3faf7; +$success-border: #84e1bc; +$orange: #d03801; +$orange-light: #fff8f1; +$orange-border: #fcd9bd; +$border: #e5e7eb; +$text: #111827; +$muted: #6b7280; +$radius: 10px; + +mifosx-add-beneficiary-dialog { + display: block; +} + +// Header +.abd-header { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 18px 20px 14px; + border-bottom: 1px solid $border; + background: linear-gradient(135deg, #f0f7ff 0%, #fff 60%); + + &__icon { + width: 42px; + height: 42px; + border-radius: 10px; + background: $primary; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + mat-icon { + color: #fff; + font-size: 22px; + width: 22px; + height: 22px; + } + } + + &__text { + flex: 1; + } + + &__title { + margin: 0; + font-size: 15px; + font-weight: 700; + color: $text; + } + + &__sub { + margin: 3px 0 0; + font-size: 11.5px; + color: $muted; + } + + &__close { + color: $muted; + margin: -4px -8px 0 0; + } +} + +// Form +.abd-form { + display: flex; + flex-direction: column; + gap: 0; + width: 500px; + max-width: 100%; + padding: 4px 0; +} + +.abd-full-width { + width: 100%; +} + +.abd-mt { + margin-top: 8px; +} + +// Mode toggle +.abd-mode-toggle { + display: flex; + gap: 8px; + margin: 14px 0 10px; + background: #f9fafb; + border: 1px solid $border; + border-radius: 10px; + padding: 5px; +} + +.abd-mode-btn { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 8px 14px; + border-radius: 8px; + border: 1.5px solid transparent; + background: transparent; + font-size: 13px; + font-weight: 600; + color: $muted; + cursor: pointer; + transition: all 0.15s; + + mat-icon { + font-size: 16px; + width: 16px; + height: 16px; + } + + &--active { + background: #fff; + border-color: $primary-border; + color: $primary; + box-shadow: 0 1px 4px rgb(0 0 0 / 8%); + } +} + +// Search +.abd-search-section { + position: relative; +} + +.abd-prefix-icon { + font-size: 18px; + width: 18px; + height: 18px; + color: $muted; + margin-right: 4px; +} + +.abd-search-results { + position: absolute; + z-index: 200; + top: calc(100% - 8px); + left: 0; + right: 0; + background: #fff; + border: 1px solid $border; + border-radius: $radius; + box-shadow: 0 8px 24px rgb(0 0 0 / 12%); + max-height: 220px; + overflow-y: auto; +} + +.abd-result-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + cursor: pointer; + border-bottom: 1px solid #f3f4f6; + transition: background 0.1s; + + &:last-child { + border-bottom: none; + } + + &:hover { + background: $primary-light; + } +} + +.abd-result-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: linear-gradient(135deg, #6366f1, $primary); + color: #fff; + font-size: 13px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.abd-result-body { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} + +.abd-result-name { + font-size: 13px; + font-weight: 600; + color: $text; +} + +.abd-result-meta { + display: flex; + gap: 5px; + flex-wrap: wrap; +} + +.abd-chip { + font-size: 10px; + padding: 1px 7px; + border-radius: 999px; + background: #f3f4f6; + color: $muted; + font-weight: 500; +} + +.abd-result-arrow { + font-size: 18px; + width: 18px; + height: 18px; + color: $primary; + opacity: 0.4; + transition: all 0.1s; + + .abd-result-item:hover & { + opacity: 1; + transform: translateX(2px); + } +} + +// Selected card +.abd-selected-card { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + background: $success-light; + border: 1.5px solid $success-border; + border-radius: $radius; + margin-top: 6px; +} + +.abd-selected-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: linear-gradient(135deg, #10b981, $success); + color: #fff; + font-size: 14px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.abd-selected-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} + +.abd-selected-name { + font-size: 13px; + font-weight: 700; + color: #065f46; +} + +.abd-selected-sub { + font-size: 11px; + color: #047857; +} + +.abd-badge { + font-size: 10px; + font-weight: 700; + padding: 3px 10px; + border-radius: 999px; + text-transform: uppercase; + letter-spacing: 0.04em; + + &--linked { + background: $success; + color: #fff; + } +} + +// Row layout +.abd-row { + display: flex; + gap: 12px; + margin-top: 8px; + + @media (width <= 500px) { + flex-direction: column; + gap: 0; + } +} + +.abd-flex { + flex: 1; +} + +// Share % +.abd-share-row { + display: flex; + align-items: flex-start; + gap: 12px; + margin-top: 8px; +} + +.abd-share-field { + flex: 0 0 150px; +} + +.abd-pct { + font-size: 15px; + font-weight: 600; + color: $muted; + margin-right: 4px; +} + +.abd-quick-pct { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding-top: 10px; +} + +.abd-pct-chip { + padding: 5px 12px; + border-radius: 8px; + border: 1.5px solid $border; + background: #fff; + font-size: 12px; + font-weight: 600; + color: $muted; + cursor: pointer; + transition: all 0.12s; + + &:hover { + border-color: $primary; + background: $primary-light; + color: $primary; + } +} + +// Required star +.abd-required { + color: #e02424; +} + +// Minor toggle +.abd-minor-toggle { + margin-top: 14px; + padding: 12px 14px; + background: #fffbeb; + border: 1px solid #fde68a; + border-radius: 8px; +} + +// Guardian box +.abd-guardian-box { + margin-top: 10px; + padding: 14px; + background: $orange-light; + border: 1.5px solid $orange-border; + border-radius: $radius; +} + +.abd-guardian-label { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + font-weight: 700; + color: $orange; + text-transform: uppercase; + letter-spacing: 0.04em; + margin-bottom: 10px; + + mat-icon { + font-size: 16px; + width: 16px; + height: 16px; + color: $orange; + } +} + +// Actions +.abd-actions { + padding: 12px 20px 16px !important; + border-top: 1px solid $border; + gap: 10px; + justify-content: flex-end !important; +} + +.abd-btn-add { + border-radius: 8px !important; + padding: 0 18px !important; + gap: 4px; + + mat-icon { + font-size: 18px; + width: 18px; + height: 18px; + } +} diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.ts b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.ts new file mode 100644 index 000000000..bd691bbdf --- /dev/null +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.ts @@ -0,0 +1,167 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, inject, OnDestroy, ViewEncapsulation } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { MatDialogRef, MatDialogContent, MatDialogActions } from '@angular/material/dialog'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { MatIcon } from '@angular/material/icon'; +import { MatSlideToggle } from '@angular/material/slide-toggle'; +import { ClientsService } from 'app/clients/clients.service'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-add-beneficiary-dialog', + templateUrl: './add-beneficiary-dialog.component.html', + styleUrl: './add-beneficiary-dialog.component.scss', + encapsulation: ViewEncapsulation.None, + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatDialogContent, + MatDialogActions, + MatProgressSpinner, + MatIcon, + MatSlideToggle, + ReactiveFormsModule + ] +}) +export class AddBeneficiaryDialogComponent implements OnDestroy { + private fb = inject(UntypedFormBuilder); + private clientsService = inject(ClientsService); + dialogRef = inject(MatDialogRef); + + /** Toggle between linked client and manual entry */ + mode: 'search' | 'manual' = 'search'; + + form: UntypedFormGroup = this.fb.group({ + searchText: [''], + name: [ + '', + Validators.required + ], + relationship: [ + '', + Validators.required + ], + dateOfBirth: [null], + sharePercentage: [ + null, + [ + Validators.required, + Validators.min(1), + Validators.max(100) + ] + ], + isMinor: [false], + guardianName: [''], + guardianRelationship: [''] + }); + + searchResults: any[] = []; + selectedClient: any = null; + isSearching = false; + private searchTimer: any = null; + + readonly relationships = [ + 'Spouse', + 'Son', + 'Daughter', + 'Father', + 'Mother', + 'Brother', + 'Sister', + 'Grandson', + 'Granddaughter', + 'Father-in-law', + 'Mother-in-law', + 'Other' + ]; + + readonly guardianRelationships = [ + 'Father', + 'Mother', + 'Guardian', + 'Natural Guardian' + ]; + + get isMinor(): boolean { + return this.form.get('isMinor').value; + } + + switchMode(m: 'search' | 'manual'): void { + this.mode = m; + this.selectedClient = null; + this.searchResults = []; + this.form.patchValue({ searchText: '', name: '' }); + } + + onSearchInput(value: string): void { + if (this.searchTimer) clearTimeout(this.searchTimer); + this.selectedClient = null; + if (!value || value.trim().length < 2) { + this.searchResults = []; + return; + } + this.isSearching = true; + this.searchTimer = setTimeout(() => { + this.clientsService.searchByText(value.trim(), 0, 10).subscribe({ + next: (res: any) => { + this.searchResults = res?.content || []; + this.isSearching = false; + }, + error: () => { + this.searchResults = []; + this.isSearching = false; + } + }); + }, 400); + } + + selectClient(client: any): void { + this.selectedClient = client; + this.form.patchValue({ + searchText: client.displayName, + name: client.displayName + }); + this.searchResults = []; + } + + clearSelection(): void { + this.selectedClient = null; + this.form.patchValue({ searchText: '', name: '' }); + this.searchResults = []; + } + + confirm(): void { + if ( + this.form.get('name').invalid || + this.form.get('relationship').invalid || + this.form.get('sharePercentage').invalid + ) + return; + const v = this.form.getRawValue(); + this.dialogRef.close({ + clientId: this.selectedClient?.id || null, + name: v.name, + relationship: v.relationship, + dateOfBirth: v.dateOfBirth, + sharePercentage: v.sharePercentage, + isMinor: v.isMinor, + guardianName: v.isMinor ? v.guardianName : null, + guardianRelationship: v.isMinor ? v.guardianRelationship : null + }); + } + + cancel(): void { + this.dialogRef.close(null); + } + + ngOnDestroy(): void { + if (this.searchTimer) clearTimeout(this.searchTimer); + } +} diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.html b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.html new file mode 100644 index 000000000..29ffc269e --- /dev/null +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.html @@ -0,0 +1,135 @@ + + + +
+
+ person_add +
+
+

Add Related Client / Partner

+

Search and link an existing client with their ownership share

+
+ +
+ + +
+ +
+ 1 + Select Client +
+ +
+ + Search by Name / CIF / Account No + search + + @if (isSearching) { + + } + @if (selectedClient) { + + } + + + + @if (searchResults.length > 0) { +
+ @for (client of searchResults; track client) { +
+
+ {{ (client.displayName || '?')[0].toUpperCase() }} +
+
+ {{ client.displayName }} + + @if (client.cifNumber) { + {{ client.cifNumber }} + } + @if (client.officeName) { + {{ client.officeName }} + } + @if (client.status?.value) { + + {{ client.status.value }} + + } + +
+ chevron_right +
+ } +
+ } +
+ + + @if (selectedClient) { +
+
+ {{ (selectedClient.displayName || '?')[0].toUpperCase() }} +
+
+ {{ selectedClient.displayName }} + {{ selectedClient.cifNumber || selectedClient.accountNumber || 'N/A' }} +
+ Selected +
+ } + + +
+ 2 + Ownership Share +
+ + +
+
+ + + + + diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.scss b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.scss new file mode 100644 index 000000000..3e9fcdc7e --- /dev/null +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.scss @@ -0,0 +1,359 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +// ── Tokens ─────────────────────────────────────────────────────────────────── +$primary: #2563eb; +$primary-light: #eff6ff; +$primary-border: #bfdbfe; +$success: #16a34a; +$success-light: #f0fdf4; +$success-border: #bbf7d0; +$border: #e5e7eb; +$text: #111827; +$text-muted: #6b7280; +$radius: 10px; + +// ── Dialog wrapper ──────────────────────────────────────────────────────────── +mifosx-add-related-client-dialog { + display: block; +} + +// ── Header ──────────────────────────────────────────────────────────────────── +.arc-header { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 20px 20px 14px; + border-bottom: 1px solid $border; + + &__icon { + width: 40px; + height: 40px; + border-radius: 10px; + background: $primary-light; + border: 1px solid $primary-border; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + mat-icon { + color: $primary; + font-size: 20px; + width: 20px; + height: 20px; + } + } + + &__text { + flex: 1; + } + + &__title { + margin: 0; + font-size: 15px; + font-weight: 700; + color: $text; + line-height: 1.3; + } + + &__sub { + margin: 3px 0 0; + font-size: 12px; + color: $text-muted; + } + + &__close { + margin-top: -4px; + margin-right: -8px; + color: $text-muted; + } +} + +// ── Dialog content ──────────────────────────────────────────────────────────── +.arc-dialog-form { + display: flex; + flex-direction: column; + gap: 0; + width: 440px; + max-width: 100%; + padding-top: 4px; +} + +// ── Step labels ─────────────────────────────────────────────────────────────── +.arc-step-label { + display: flex; + align-items: center; + gap: 8px; + margin: 16px 0 10px; + font-size: 12px; + font-weight: 700; + color: $text-muted; + text-transform: uppercase; + letter-spacing: 0.04em; + + &--mt { + margin-top: 20px; + } +} + +.arc-step-badge { + width: 20px; + height: 20px; + border-radius: 50%; + background: $primary; + color: #fff; + font-size: 11px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +// ── Search ──────────────────────────────────────────────────────────────────── +.arc-full-width { + width: 100%; +} + +.arc-search-section { + position: relative; +} + +.arc-search-icon { + font-size: 18px; + width: 18px; + height: 18px; + color: $text-muted; + margin-right: 4px; +} + +.arc-search-results { + position: absolute; + z-index: 200; + top: calc(100% - 8px); + left: 0; + right: 0; + background: #fff; + border: 1px solid $border; + border-radius: $radius; + box-shadow: 0 8px 24px rgb(0 0 0 / 12%); + max-height: 240px; + overflow-y: auto; +} + +.arc-search-result-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + cursor: pointer; + border-bottom: 1px solid #f3f4f6; + transition: background 0.12s; + + &:last-child { + border-bottom: none; + } + + &:hover { + background: $primary-light; + + .arc-result-arrow { + opacity: 1; + transform: translateX(2px); + } + } +} + +.arc-result-avatar { + width: 34px; + height: 34px; + border-radius: 50%; + background: linear-gradient(135deg, #6366f1, #2563eb); + color: #fff; + font-size: 13px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.arc-result-body { + flex: 1; + display: flex; + flex-direction: column; + gap: 3px; +} + +.arc-result-name { + font-size: 13px; + font-weight: 600; + color: $text; +} + +.arc-result-meta { + display: flex; + gap: 6px; + flex-wrap: wrap; +} + +.arc-chip { + font-size: 10px; + padding: 1px 7px; + border-radius: 999px; + background: #f3f4f6; + color: $text-muted; + font-weight: 500; + + &--status { + background: #fef3c7; + color: #92400e; + } + + &--active { + background: #dcfce7; + color: #166534; + } +} + +.arc-result-arrow { + font-size: 18px; + width: 18px; + height: 18px; + color: $primary; + opacity: 0; + transition: + opacity 0.12s, + transform 0.12s; +} + +// ── Selected client ─────────────────────────────────────────────────────────── +.arc-selected-client { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 14px; + background: $success-light; + border: 1.5px solid $success-border; + border-radius: $radius; + margin-top: 6px; +} + +.arc-selected-avatar { + width: 38px; + height: 38px; + border-radius: 50%; + background: linear-gradient(135deg, #22c55e, #16a34a); + color: #fff; + font-size: 14px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.arc-client-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} + +.arc-client-name { + font-size: 13px; + font-weight: 700; + color: #166534; +} + +.arc-client-sub { + font-size: 11px; + color: #15803d; +} + +.arc-selected-badge { + font-size: 10px; + font-weight: 700; + padding: 3px 10px; + border-radius: 999px; + background: $success; + color: #fff; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +// ── Share percentage ────────────────────────────────────────────────────────── +.arc-share-row { + display: flex; + align-items: flex-start; + gap: 12px; +} + +.arc-share-field { + flex: 0 0 160px; +} + +.arc-percent-suffix { + font-size: 15px; + font-weight: 600; + color: $text-muted; + margin-right: 4px; +} + +.arc-share-hints { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding-top: 10px; +} + +.arc-hint-chip { + padding: 5px 12px; + border-radius: 8px; + border: 1.5px solid $border; + background: #fff; + font-size: 12px; + font-weight: 600; + color: $text-muted; + cursor: pointer; + transition: + border-color 0.12s, + background 0.12s, + color 0.12s; + + &:hover { + border-color: $primary; + background: $primary-light; + color: $primary; + } +} + +// ── Actions ─────────────────────────────────────────────────────────────────── +.arc-actions { + padding: 12px 20px 16px !important; + border-top: 1px solid $border; + gap: 10px; + justify-content: flex-end !important; +} + +.arc-btn-cancel { + border-radius: 8px !important; +} + +.arc-btn-add { + border-radius: 8px !important; + padding: 0 18px !important; + gap: 4px; + + mat-icon { + font-size: 18px; + width: 18px; + height: 18px; + } +} diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.ts b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.ts new file mode 100644 index 000000000..60c96d69a --- /dev/null +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.ts @@ -0,0 +1,104 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, inject, OnDestroy, ViewEncapsulation } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { MatDialogRef, MatDialogContent, MatDialogActions } from '@angular/material/dialog'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { MatIcon } from '@angular/material/icon'; +import { ClientsService } from 'app/clients/clients.service'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-add-related-client-dialog', + templateUrl: './add-related-client-dialog.component.html', + styleUrl: './add-related-client-dialog.component.scss', + encapsulation: ViewEncapsulation.None, + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatDialogContent, + MatDialogActions, + MatProgressSpinner, + MatIcon, + ReactiveFormsModule + ] +}) +export class AddRelatedClientDialogComponent implements OnDestroy { + private fb = inject(UntypedFormBuilder); + private clientsService = inject(ClientsService); + dialogRef = inject(MatDialogRef); + + form: UntypedFormGroup = this.fb.group({ + searchText: [''], + sharePercentage: [ + null, + [ + Validators.required, + Validators.min(1), + Validators.max(100) + ] + ] + }); + + searchResults: any[] = []; + selectedClient: any = null; + isSearching = false; + private searchTimer: any = null; + + onSearchInput(value: string): void { + if (this.searchTimer) clearTimeout(this.searchTimer); + this.selectedClient = null; + if (!value || value.trim().length < 2) { + this.searchResults = []; + return; + } + this.isSearching = true; + this.searchTimer = setTimeout(() => { + this.clientsService.searchByText(value.trim(), 0, 10).subscribe({ + next: (res: any) => { + this.searchResults = res?.content || []; + this.isSearching = false; + }, + error: () => { + this.searchResults = []; + this.isSearching = false; + } + }); + }, 400); + } + + selectClient(client: any): void { + this.selectedClient = client; + this.form.patchValue({ searchText: client.displayName }); + this.searchResults = []; + } + + clearSelection(): void { + this.selectedClient = null; + this.form.patchValue({ searchText: '' }); + this.searchResults = []; + } + + confirm(): void { + if (!this.selectedClient || this.form.get('sharePercentage').invalid) return; + this.dialogRef.close({ + clientId: this.selectedClient.id, + clientName: this.selectedClient.displayName, + clientCif: this.selectedClient.cifNumber || this.selectedClient.accountNumber || '', + sharePercentage: this.form.get('sharePercentage').value + }); + } + + cancel(): void { + this.dialogRef.close(null); + } + + ngOnDestroy(): void { + if (this.searchTimer) clearTimeout(this.searchTimer); + } +} diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html index 46882eb22..68adf30a0 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html @@ -131,6 +131,208 @@ } } + + @if (isNonIndividualClient && currentAccountProductSelected) { +
+ +
+
+
+ +
+
+

Non-Individual Account Details

+ As per RBI / Indian banking norms for non-individual entities +
+
+ Entity Account +
+ + +
+
+
+ A +
+ Partners / Members + Authorised persons linked to this entity account +
+
+ +
+ + @if (relatedClients.length > 0) { +
+ + + + + + + + + + + + + @for (c of relatedClients; track c; let i = $index) { + + + + + + + + + } + + + + + + + + + +
Sr.Partner NameCIF No.Ownership %Signing AuthorityAction
{{ i + 1 }} +
+
+ {{ c.clientName[0].toUpperCase() }} +
+
+ {{ c.clientName }} + @if (c.isMandatory) { + Mandatory Signatory + } +
+
+
+ {{ c.clientCif || '—' }} + + + {{ c.sharePercentage }}% + + + + + +
Total Ownership + + {{ totalSharePercentage }}% + + + + lock {{ mandatoryCount }} Mandatory + +
+
+ @if (totalSharePercentage !== 100) { +
+ warning_amber + Total ownership is {{ totalSharePercentage }}%. It must equal 100%. +
+ } @else { +
+ check_circle + Ownership allocation is complete (100%). +
+ } + } @else { +
+ group_add + No partners added yet. Click Add Partner to begin. +
+ } +
+ + +
+
+
+ B +
+ Mode of Operation / Signatory Rights + Auto-computed from Mandatory partners — you can override manually +
+
+ @if (signatoryRights) { + auto_awesome Auto + } +
+ + @if (relatedClients.length === 0) { +
+ how_to_vote + Add partners in Section A and set Mandatory/Optional to auto-compute the mode. +
+ } @else { +
+ @for (opt of signatoryRightsOptions; track opt.value) { + + } +
+ +
+ @if (signatoryRights) { + how_to_vote + Mode of Operation: {{ selectedSignatoryLabel }} + + ({{ mandatoryCount }} Mandatory + {{ relatedClients.length - mandatoryCount }} Optional partner(s)) + + } @else { + info + Set Mandatory/Optional for partners above to auto-select mode. + } +
+ } +
+
+ } +
- - v.isActive) ?? this._rdInterestRates[0] ?? null; @@ -80,6 +84,18 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges { /** Output the submit action */ @Output() submitEvent = new EventEmitter(); + get currencyName(): string { + return this.recurringDepositsAccountProductTemplate?.currency?.name || '—'; + } + + get decimalPlaces(): string | number { + return this.recurringDepositsAccountProductTemplate?.currency?.decimalPlaces ?? '—'; + } + + get inMultiplesOf(): string | number { + return this.recurringDepositsAccountProductTemplate?.currency?.inMultiplesOf ?? '—'; + } + _rdInterestRates: any[] = []; _activeRdVersion: any = null; diff --git a/src/app/savings/create-savings-account/create-savings-account.component.html b/src/app/savings/create-savings-account/create-savings-account.component.html index 23f9f93aa..edd2aea77 100644 --- a/src/app/savings/create-savings-account/create-savings-account.component.html +++ b/src/app/savings/create-savings-account/create-savings-account.component.html @@ -92,6 +92,8 @@ [savingsAccountTemplate]="savingsAccountTemplate" [savingsAccountTermsForm]="savingsAccountTermsForm" [savingsAccount]="savingsAccount" + [savingsAccountDigital]="savingsAccountDigital" + [savingsAccountNominee]="savingsAccountNominee" (submitEvent)="submit()" > diff --git a/src/app/savings/create-savings-account/create-savings-account.component.ts b/src/app/savings/create-savings-account/create-savings-account.component.ts index fd733bbae..a84dd5dcb 100644 --- a/src/app/savings/create-savings-account/create-savings-account.component.ts +++ b/src/app/savings/create-savings-account/create-savings-account.component.ts @@ -15,6 +15,7 @@ import { FormGroup } from '@angular/forms'; import { SavingsAccountDetailsStepComponent } from '../savings-account-stepper/savings-account-details-step/savings-account-details-step.component'; import { SavingsAccountCurrencyStepComponent } from '../savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component'; import { SavingsAccountDigitalStepComponent } from '../savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component'; +import { SavingsAccountSettingsStepComponent } from '../savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component'; /** Custom Services */ import { SavingsService } from '../savings.service'; @@ -69,6 +70,12 @@ export class CreateSavingsAccountComponent { @ViewChild(SavingsAccountDetailsStepComponent, { static: true }) savingsAccountDetailsStep: SavingsAccountDetailsStepComponent; + @ViewChild(SavingsAccountDigitalStepComponent) + savingsAccountDigitalStep: SavingsAccountDigitalStepComponent; + + @ViewChild(SavingsAccountCurrencyStepComponent) + savingsAccountCurrencyStep: SavingsAccountCurrencyStepComponent; + constructor() { this.route.data.subscribe((data: { savingsAccountTemplate: any }) => { this.savingsAccountTemplate = data.savingsAccountTemplate; @@ -94,6 +101,14 @@ export class CreateSavingsAccountComponent { }; } + get savingsAccountDigital() { + return this.savingsAccountDigitalStep?.savingsAccountDigital ?? null; + } + + get savingsAccountNominee() { + return this.savingsAccountCurrencyStep?.nomineeData ?? null; + } + submit() { const locale = this.settingsService.language.code; const dateFormat = this.settingsService.dateFormat; diff --git a/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.html b/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.html index 9dd007828..29231e681 100644 --- a/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.html @@ -35,8 +35,9 @@ {{ savingsAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }}
- - + @if (savingsAccountProductTemplate?.dormancyAlert) { + + }
@@ -47,7 +48,6 @@ {{ savingsAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
- @if (savingsAccountProductTemplate?.nominationFacility) { } diff --git a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts index 8585f3dac..7bdaefa68 100644 --- a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts +++ b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts @@ -59,6 +59,8 @@ export class SavingsAccountDetailsStepComponent implements OnInit { savingsProductSelected = false; introducerId: number | null = null; + introducerName: string | null = null; + introducerCif: string | null = null; /** Savings Account Template with product data */ @Output() savingsAccountProductTemplate = new EventEmitter(); @@ -168,14 +170,18 @@ export class SavingsAccountDetailsStepComponent implements OnInit { /** * Returns savings account form value. */ - onIntroducerChange(event: { clientId: number; displayName: string } | null): void { + onIntroducerChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void { this.introducerId = event?.clientId ?? null; + this.introducerName = event?.displayName ?? null; + this.introducerCif = event?.cifNumber ?? null; } get savingsAccountDetails() { const value = this.savingsAccountDetailsForm.getRawValue(); if (this.introducerId) { value.introducerId = this.introducerId; + value.introducerName = this.introducerName; + value.introducerCif = this.introducerCif; } return value; } diff --git a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.html b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.html index 70337bb64..f523e4bd4 100644 --- a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.html @@ -7,9 +7,11 @@ -->
+
- {{ 'labels.heading.Details' | translate }} + + Account Details
@@ -37,25 +39,27 @@ } @else { - {{ 'labels.inputs.Unassigned' | translate }} + Unassigned }
+ @if ( savingsAccountProductTemplate?.accountType || savingsAccountProductTemplate?.customerSegment || savingsAccountProductTemplate?.productCode || + savingsAccountProductTemplate?.previewAccountNo || savingsAccountProductTemplate?.effectiveDate || - savingsAccountProductTemplate?.endDate || - savingsAccountProductTemplate?.introducerRequired !== undefined || - savingsAccountProductTemplate?.shareLinkingEnabled !== undefined + savingsAccountProductTemplate?.endDate ) {
+ Product Classification + From Product
@@ -74,9 +78,15 @@ @if (savingsAccountProductTemplate?.productCode) {
Product Code - {{ savingsAccountProductTemplate?.productCode }} + {{ savingsAccountProductTemplate?.productCode }}
} +
+ Account No + {{ + savingsAccountProductTemplate?.previewAccountNo || savingsAccountTemplate?.accountNo || 'Auto-generated' + }} +
@if (savingsAccountProductTemplate?.effectiveDate) {
Effective Date @@ -89,145 +99,285 @@ {{ savingsAccountProductTemplate?.endDate | dateFormat }}
} - @if ( - savingsAccountProductTemplate?.introducerRequired !== undefined && - savingsAccountProductTemplate?.introducerRequired !== null - ) { -
- Introducer Required - - {{ savingsAccountProductTemplate?.introducerRequired | yesNo }} - -
- } - @if ( - savingsAccountProductTemplate?.shareLinkingEnabled !== undefined && - savingsAccountProductTemplate?.shareLinkingEnabled !== null - ) { -
- Share Linking Enabled - - {{ savingsAccountProductTemplate?.shareLinkingEnabled | yesNo }} - -
- } +
+ Introducer Required + + {{ savingsAccountProductTemplate?.introducerRequired | yesNo }} + +
+
+ Share Linking Enabled + + {{ savingsAccountProductTemplate?.shareLinkingEnabled | yesNo }} + +
} + + @if (savingsAccount?.introducerId) { +
+
+ + Introducer Details +
+
+
+
+ Name + {{ savingsAccount?.introducerName || '—' }} +
+ @if (savingsAccount?.introducerCif) { +
+ CIF No + {{ savingsAccount.introducerCif }} +
+ } +
+ Client ID + {{ savingsAccount.introducerId }} +
+
+
+
+ } + +
- {{ 'labels.heading.Currency' | translate }} + + Currency & Account Flags
- {{ 'labels.inputs.Currency' | translate }} - {{ savingsAccountTermsForm?.get('currencyCode')?.value }} + Currency + {{ currencyName }}
- {{ 'labels.inputs.Decimal Places' | translate }} - {{ savingsAccountProductTemplate?.currency?.decimalPlaces }} + Decimal Places + {{ decimalPlaces }}
- {{ 'labels.inputs.Currency In Multiples Of' | translate }} - {{ savingsAccountProductTemplate?.currency?.inMultiplesOf }} + Currency In Multiples Of + {{ inMultiplesOf }} +
+
+ Dormancy Alert + + {{ savingsAccountProductTemplate?.dormancyAlert | yesNo }} + +
+
+ Nomination Facility + + {{ savingsAccountProductTemplate?.nominationFacility | yesNo }} +
- - @if ( - savingsAccountProductTemplate?.dormancyAlert !== undefined && - savingsAccountProductTemplate?.dormancyAlert !== null - ) { -
- Dormancy Alert - - {{ savingsAccountProductTemplate?.dormancyAlert | yesNo }} - -
- } - - @if ( - savingsAccountProductTemplate?.balanceSwapOption !== undefined && - savingsAccountProductTemplate?.balanceSwapOption !== null - ) { -
- Balance Swap Option - - {{ savingsAccountProductTemplate?.balanceSwapOption | yesNo }} - -
- } - - @if (savingsAccountProductTemplate?.msme !== undefined && savingsAccountProductTemplate?.msme !== null) { -
- MSME - - {{ savingsAccountProductTemplate?.msme | yesNo }} - -
- } - - @if ( - savingsAccountProductTemplate?.nominationFacility !== undefined && - savingsAccountProductTemplate?.nominationFacility !== null - ) { -
- Nomination Facility - - {{ savingsAccountProductTemplate?.nominationFacility | yesNo }} - -
- } - - @if ( - savingsAccountProductTemplate?.autoDebitFacility !== undefined && - savingsAccountProductTemplate?.autoDebitFacility !== null - ) { -
- Auto Debit Facility - - {{ savingsAccountProductTemplate?.autoDebitFacility | yesNo }} - -
- }
- @if (hasDigitalSection) { + + @if ( + savingsAccountProductTemplate?.dormancyAlert || savingsAccountProductTemplate?.isDormancyTrackingActiveFromProduct + ) {
- Digital + + Dormancy Monitoring + + {{ savingsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }} + +
+
+
+
+
+
+ Inactive + + @if (savingsAccountProductTemplate?.daysToInactiveFromProduct != null) { + After {{ savingsAccountProductTemplate.daysToInactiveFromProduct }} days of no transaction + } @else { + Not configured + } + +
+
+
+
+
+
+ Dormant + + @if (savingsAccountProductTemplate?.daysToDormancyFromProduct != null) { + {{ savingsAccountProductTemplate.daysToDormancyFromProduct }} days after inactive + } @else { + Not configured + } + +
+
+
+
+
+
+ Escheat + + @if (savingsAccountProductTemplate?.daysToEscheatFromProduct != null) { + {{ savingsAccountProductTemplate.daysToEscheatFromProduct }} days after dormant + } @else { + Not configured + } + +
+
+
+
+ + Alert + {{ savingsAccountProductTemplate?.dormancyAlert ? 'Enabled' : 'Disabled' }} + + + Tracking + {{ savingsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }} + +
+
+
+ } + + + @if (savingsAccountNominee) { +
+
+ + Nominee Details + + {{ savingsAccountNominee?.mode === 'linked' ? 'Linked Client' : 'Manual Entry' }} +
- @for (facility of digitalFacilityLabels; track facility.key) { + +
+ Name + {{ + savingsAccountNominee?.fullName || savingsAccountNominee?.displayName || '—' + }} +
+ @if (savingsAccountNominee?.cifNumber) {
- {{ facility.label }} + CIF No + {{ savingsAccountNominee.cifNumber }} +
+ } +
+ Relationship + {{ savingsAccountNominee?.relationship || '—' }} +
+
+ Share % + {{ savingsAccountNominee?.nomineeShare ?? '—' }}% +
+ @if (savingsAccountNominee?.dateOfBirth) { +
+ Date of Birth + {{ savingsAccountNominee?.dateOfBirth | dateFormat }} +
+ } + @if (savingsAccountNominee?.nationality) { +
+ Nationality + {{ savingsAccountNominee?.nationality }} +
+ } + @if (savingsAccountNominee?.mobileNo) { +
+ Mobile No + {{ savingsAccountNominee?.mobileNo }} +
+ } + @if (savingsAccountNominee?.aadhaarNumber) { +
+ Aadhaar No + {{ savingsAccountNominee?.aadhaarNumber }} +
+ } + @if (savingsAccountNominee?.panNumber) { +
+ PAN No + {{ savingsAccountNominee?.panNumber }} +
+ } + @if (savingsAccountNominee?.isDependent) { +
+ Dependent + Yes +
+ } + @if (savingsAccountNominee?.guardianName) { +
+ Guardian Name + {{ savingsAccountNominee?.guardianName }} +
+ } + @if (savingsAccountNominee?.guardianRelationship) { +
+ Guardian Relationship + {{ savingsAccountNominee?.guardianRelationship }} +
+ } + @if (savingsAccountNominee?.remarks) { +
+ Remarks + {{ savingsAccountNominee?.remarks }} +
+ } +
+
+
+ } + + + @if (hasDigitalSection) { +
+
+ + Digital Facilities + User Configured +
+
+
+ @for (facility of digitalFacilityLabels; track facility.key) { +
+ {{ facility.label }} - {{ digitalPreview?.accountFacilityConfig?.[facility.key] | yesNo }} + {{ digitalFacilityConfig?.[facility.key] ? 'Enabled' : 'Disabled' }}
} @@ -236,9 +386,11 @@
} + @if (activeClientMembers) {
+ {{ 'labels.heading.Client Members' | translate }}
diff --git a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.scss b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.scss index bec5645fb..25e0a4e09 100644 --- a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.scss +++ b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.scss @@ -9,29 +9,59 @@ .preview-wrapper { display: flex; flex-direction: column; - gap: 1.25rem; + gap: 1rem; padding: 0.5rem 0; } .preview-section { - border: 1px solid #e0e0e0; - border-radius: 8px; + border: 1px solid #e2e8f0; + border-radius: 10px; overflow: hidden; background: #fff; - box-shadow: 0 1px 3px rgb(0 0 0 / 6%); + box-shadow: 0 1px 4px rgb(0 0 0 / 5%); &__header { - background: #f5f5f5; - border-bottom: 1px solid #e0e0e0; + display: flex; + align-items: center; + gap: 8px; + background: #f8fafc; + border-bottom: 1px solid #e2e8f0; padding: 0.6rem 1rem; } + &__icon { + font-size: 13px; + color: #0ea5e9; + width: 16px; + text-align: center; + } + &__title { - font-size: 0.95rem; - font-weight: 600; - color: #333; + font-size: 0.82rem; + font-weight: 700; + color: #1e293b; text-transform: uppercase; - letter-spacing: 0.04em; + letter-spacing: 0.06em; + flex: 1; + } + + &__badge { + display: inline-flex; + align-items: center; + padding: 2px 10px; + border-radius: 999px; + font-size: 10px; + font-weight: 700; + background: #dbeafe; + color: #1d4ed8; + border: 1px solid #bfdbfe; + text-transform: uppercase; + + &--digital { + background: #fef3c7; + color: #92400e; + border-color: #fde68a; + } } } @@ -41,7 +71,7 @@ .preview-grid { display: grid; - gap: 0.75rem 1.5rem; + gap: 0.85rem 1.5rem; &--three { grid-template-columns: repeat(3, 1fr); @@ -59,36 +89,229 @@ .preview-field { display: flex; flex-direction: column; - gap: 0.15rem; + gap: 0.2rem; } .preview-label { - font-size: 0.72rem; - color: #888; - font-weight: 500; + font-size: 0.68rem; + color: #94a3b8; + font-weight: 600; text-transform: uppercase; - letter-spacing: 0.04em; + letter-spacing: 0.05em; } .preview-value { - font-size: 0.92rem; - color: #222; + font-size: 0.9rem; + color: #1e293b; font-weight: 500; } +.preview-muted { + color: #94a3b8; + font-style: italic; +} + +.preview-mono { + font-family: monospace; + font-size: 0.88rem; + color: #334155; +} + .preview-badge { display: inline-block; - padding: 0.1rem 0.55rem; + padding: 0.15rem 0.6rem; border-radius: 12px; - font-size: 0.78rem; + font-size: 0.75rem; font-weight: 600; - background: #ffebee; - color: #c62828; + background: #fef2f2; + color: #dc2626; width: fit-content; &--yes { - background: #e8f5e9; - color: #2e7d32; + background: #f0fdf4; + color: #15803d; + } + + &--warn { + background: #fef3c7; + color: #92400e; + } +} + +// Digital facilities grid +.preview-digital-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; + + @media (width <= 900px) { + grid-template-columns: repeat(3, 1fr); + } + + @media (width <= 600px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.preview-digital-card { + display: flex; + flex-direction: column; + gap: 4px; + padding: 10px 12px; + border: 1.5px solid #e2e8f0; + border-radius: 8px; + background: #f8fafc; + transition: all 0.12s; + + &--on { + border-color: #86efac; + background: #f0fdf4; + } +} + +.preview-digital-label { + font-size: 11px; + font-weight: 600; + color: #475569; +} + +.preview-digital-status { + font-size: 11px; + font-weight: 700; + color: #dc2626; + + &--on { + color: #15803d; + } +} + +// Section badge variants +.preview-section__badge--active { + background: #dcfce7 !important; + color: #15803d !important; + border-color: #bbf7d0 !important; +} + +// ── Dormancy Monitoring ─────────────────────────────────────────── +.preview-dormancy-timeline { + display: flex; + align-items: flex-start; + gap: 0; + margin-bottom: 14px; +} + +.preview-dorm-step { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + flex: 1; + + &__dot { + width: 36px; + height: 36px; + border-radius: 50%; + border: 3px solid; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + &--inactive { + border-color: #f59e0b; + background: #fffbeb; + + &::after { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: #f59e0b; + } + } + + &--dormant { + border-color: #ef4444; + background: #fef2f2; + + &::after { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: #ef4444; + } + } + + &--escheat { + border-color: #7c3aed; + background: #f5f3ff; + + &::after { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: #7c3aed; + } + } + } + + &__body { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 2px; + } + + &__label { + font-size: 12px; + font-weight: 700; + color: #1e293b; + } + + &__days { + font-size: 10.5px; + color: #64748b; + line-height: 1.3; + } +} + +.preview-dorm-connector { + flex: 0 0 40px; + height: 3px; + background: linear-gradient(90deg, #e2e8f0, #cbd5e1); + margin-top: 16px; + 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; } } diff --git a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.ts index aae881819..f29663102 100644 --- a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.ts +++ b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.ts @@ -88,6 +88,10 @@ export class SavingsAccountPreviewStepComponent implements OnChanges { @Input() savingsAccountTermsForm: any; /** Savings Account */ @Input() savingsAccount: any; + /** Digital facilities — user-modified values from digital step */ + @Input() savingsAccountDigital: any; + /** Nominee data from currency step */ + @Input() savingsAccountNominee: any; /** active Client Members in case of GSIM Account */ @Input() activeClientMembers?: any; @@ -118,15 +122,33 @@ export class SavingsAccountPreviewStepComponent implements OnChanges { } } - get digitalPreview(): any { - return this.savingsAccountProductTemplate || {}; + /** Returns user-modified digital config (from digital step), falling back to product template */ + get digitalFacilityConfig(): any { + return ( + this.savingsAccountDigital?.accountFacilityConfig ?? + this.savingsAccountProductTemplate?.accountFacilityConfig ?? + null + ); } get hasDigitalSection(): boolean { - return !!( - this.digitalPreview?.accountFacilityConfig || - this.digitalPreview?.accountFacilityLimits?.length || - this.digitalPreview?.depositLimitKyc + return !!this.digitalFacilityConfig; + } + + /** Currency info from product template */ + get currencyName(): string { + return ( + this.savingsAccountProductTemplate?.currency?.name || + this.savingsAccountTermsForm?.get?.('currencyCode')?.value || + '—' ); } + + get decimalPlaces(): string | number { + return this.savingsAccountProductTemplate?.currency?.decimalPlaces ?? '—'; + } + + get inMultiplesOf(): string | number { + return this.savingsAccountProductTemplate?.currency?.inMultiplesOf ?? '—'; + } } diff --git a/src/app/shared/introducer-search/introducer-search.component.ts b/src/app/shared/introducer-search/introducer-search.component.ts index fa0410d78..703b691fa 100644 --- a/src/app/shared/introducer-search/introducer-search.component.ts +++ b/src/app/shared/introducer-search/introducer-search.component.ts @@ -31,7 +31,7 @@ export class IntroducerSearchComponent { private clientsService = inject(ClientsService); private sanitizer = inject(DomSanitizer); - @Output() introducerChange = new EventEmitter<{ clientId: number; displayName: string } | null>(); + @Output() introducerChange = new EventEmitter<{ clientId: number; displayName: string; cifNumber?: string } | null>(); entryMode: 'linked' = 'linked'; searchText = ''; @@ -81,7 +81,11 @@ export class IntroducerSearchComponent { this.linkedProfileImage = null; this.linkedKycStatus = null; this.fetchLinkedClientData(client.id); - this.introducerChange.emit({ clientId: client.id, displayName: client.displayName }); + this.introducerChange.emit({ + clientId: client.id, + displayName: client.displayName, + cifNumber: client.cifNumber || client.accountNumber || '' + }); } removeClient(): void { diff --git a/src/app/shared/nominee-form/nominee-form.component.ts b/src/app/shared/nominee-form/nominee-form.component.ts index 594692183..d016d8a6b 100644 --- a/src/app/shared/nominee-form/nominee-form.component.ts +++ b/src/app/shared/nominee-form/nominee-form.component.ts @@ -36,6 +36,7 @@ export interface NomineeData { // Linked client fields clientId?: number; displayName?: string; + cifNumber?: string; } const RELATIONSHIPS = [ @@ -98,6 +99,7 @@ export class NomineeFormComponent implements OnInit { clientDob = ''; clientAadhaar = ''; clientPan = ''; + clientCifNumber = ''; linkedRelationship = ''; linkedShare = 100; @@ -177,7 +179,12 @@ export class NomineeFormComponent implements OnInit { this.searchResults = []; this.searchText = ''; this.fetchLinkedClientData(client.id); - this.nomineeChange.emit({ mode: 'linked', clientId: client.id, displayName: client.displayName }); + this.nomineeChange.emit({ + mode: 'linked', + clientId: client.id, + displayName: client.displayName, + cifNumber: client.accountNo || client.cifNumber || '' + }); } removeLinked(): void { @@ -191,6 +198,7 @@ export class NomineeFormComponent implements OnInit { mode: 'linked', clientId: this.selectedClient.id, displayName: this.selectedClient.displayName, + cifNumber: this.selectedClient.accountNo || this.selectedClient.cifNumber || '', relationship: rel, nomineeShare: this.linkedShare }); @@ -204,6 +212,7 @@ export class NomineeFormComponent implements OnInit { this.clientDob = ''; this.clientAadhaar = ''; this.clientPan = ''; + this.clientCifNumber = ''; this.searchText = ''; this.searchResults = []; this.linkedRelationship = ''; diff --git a/src/environments/.env.ts b/src/environments/.env.ts index d1086eaaa..77fd662d8 100644 --- a/src/environments/.env.ts +++ b/src/environments/.env.ts @@ -5,12 +5,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + // IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN! /* tslint:disable */ export default { 'mifos_x': { - 'version': '260425', - 'hash': 'fbaec185' + 'version': '260427', + 'hash': '5fb25edc' }, 'allow_switching_backend_instance': true }; diff --git a/src/main.scss b/src/main.scss index 9fda56e50..c10b82bd2 100644 --- a/src/main.scss +++ b/src/main.scss @@ -957,3 +957,51 @@ mat-card { border-radius: 16px !important; } } + +/* ─── Add Beneficiary Dialog Panel ─── */ +.abd-dialog-panel { + .mat-mdc-dialog-container { + padding: 0; + border-radius: 14px; + overflow: hidden; + } + + .mat-mdc-dialog-surface { + padding: 0 !important; + border-radius: 14px !important; + } + + .mat-mdc-dialog-content { + padding: 0 20px !important; + max-height: 72vh; + } + + .mat-mdc-dialog-actions { + padding: 0 !important; + margin: 0 !important; + } +} + +/* ─── Add Related Client Dialog Panel ─── */ +.arc-dialog-panel { + .mat-mdc-dialog-container { + padding: 0; + border-radius: 14px; + overflow: hidden; + } + + .mat-mdc-dialog-surface { + padding: 0 !important; + border-radius: 14px !important; + } + + .mat-mdc-dialog-content { + padding: 0 20px !important; + max-height: 70vh; + } + + .mat-mdc-dialog-actions { + padding: 0 !important; + margin: 0 !important; + } +}