From 31b2490f09553b3fd7906508e68495bedddd5c57 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Tue, 28 Apr 2026 13:16:07 +0530 Subject: [PATCH] feat: update savings/current/rd account UI flows --- .../add-related-client-dialog.component.html | 1 + .../add-related-client-dialog.component.ts | 22 +- ...urrent-account-details-step.component.html | 546 +++++++ ...urrent-account-details-step.component.scss | 935 ++++++++++++ .../current-account-details-step.component.ts | 312 +++- .../current-accounts.service.ts | 5 + ...posits-account-details-step.component.html | 655 ++++++++- ...posits-account-details-step.component.scss | 1283 +++++++++++++++++ ...deposits-account-details-step.component.ts | 384 ++++- ...posits-account-preview-step.component.html | 2 +- ...eposit-product-details-step.component.html | 2 +- ...eposit-product-preview-step.component.html | 2 +- ...curring-deposit-general-tab.component.html | 2 +- .../create-savings-account.component.ts | 13 +- ...avings-account-details-step.component.html | 557 +++++++ ...avings-account-details-step.component.scss | 1043 ++++++++++++++ .../savings-account-details-step.component.ts | 322 ++++- ...avings-account-preview-step.component.html | 162 ++- ...avings-account-preview-step.component.scss | 72 + src/app/savings/savings.service.ts | 5 + .../introducer-search.component.ts | 2 +- .../nominee-form/nominee-form.component.ts | 2 +- 22 files changed, 6150 insertions(+), 179 deletions(-) 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 index 29ffc269e..7325a2cca 100644 --- 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 @@ -116,6 +116,7 @@ } + 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 index 60c96d69a..bba5fd60d 100644 --- 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 @@ -42,9 +42,23 @@ export class AddRelatedClientDialogComponent implements OnDestroy { Validators.min(1), Validators.max(100) ] - ] + ], + signingAuthority: [null], + role: [null] }); + readonly signingAuthorityOptions = [ + { value: 'SINGLE', label: 'Single' }, + { value: 'JOINTLY', label: 'Jointly' }, + { value: 'EITHER_OR_SURVIVOR', label: 'Either or Survivor' } + ]; + + readonly roleOptions = [ + { value: 'PRIMARY', label: 'Primary' }, + { value: 'JOINT', label: 'Joint' }, + { value: 'NOMINEE', label: 'Nominee' } + ]; + searchResults: any[] = []; selectedClient: any = null; isSearching = false; @@ -89,8 +103,10 @@ export class AddRelatedClientDialogComponent implements OnDestroy { this.dialogRef.close({ clientId: this.selectedClient.id, clientName: this.selectedClient.displayName, - clientCif: this.selectedClient.cifNumber || this.selectedClient.accountNumber || '', - sharePercentage: this.form.get('sharePercentage').value + clientCif: this.selectedClient.cifNumber || this.selectedClient.cifNo || '', + sharePercentage: this.form.get('sharePercentage').value, + signingAuthority: this.form.get('signingAuthority').value || null, + role: this.form.get('role').value || null }); } 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 cc480140b..e2bd70e26 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 @@ -59,6 +59,22 @@ {{ 'labels.inputs.External ID' | translate }} + + + Establishment Date + + + + Date of entity / business establishment + + + + Start of Operation Date + + + + Date when operations commenced + } @@ -139,6 +155,536 @@ } } + + @if (currentAccountProductSelected) { +
+
+
+
+ +
+
+

Status of Account

+ As per RBI guidelines — select account holder type +
+
+ RBI Compliant +
+ + + @if (clientDob) { +
+
+ cake + Date of Birth: {{ formatTemplateDate(clientDob) }} +
+
+ person_pin + Age: {{ clientAge }} years +
+ @if (clientAge !== null && clientAge < 18) { + + child_care Auto-detected: Minor + + } @else if (clientAge !== null && clientAge >= 60) { + + elderly Auto-detected: Senior Citizen + + } @else { + check_circle Age Verified + } +
+ } + + +
+ @for (opt of accountStatusOptions; track opt.value) { + + } +
+ + + @if (selectedAccountStatus === 'JOINT') { +
+
+
+ A +
+ Joint Account Holders + Add all co-applicants linked to this joint current account +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + @for (h of jointHolders; track h; let i = $index) { + + + + + + + + + + } + + + + + + + + + + +
Sr.Holder NameCIF No.Ownership %Signing AuthorityRoleAction
1 +
+
+ {{ primaryClientName ? primaryClientName[0].toUpperCase() : '?' }} +
+
+ {{ primaryClientName || 'Primary Holder' }} + Primary Account Holder +
+
+
{{ primaryClientCif || '—' }} + + % + + + lock Mandatory + + Primary Account Holder—
{{ i + 2 }} +
+
+ {{ h.clientName[0].toUpperCase() }} +
+
+ {{ h.clientName }} + @if (h.isMandatory) { + Mandatory Signatory + } +
+
+
{{ h.clientCif || '—' }} + + {{ h.sharePercentage }}% + + + + Co-Holder + +
Total Ownership + + {{ totalJointSharePercentage }}% + + + + lock {{ mandatoryJointCount + 1 }} Mandatory + +
+
+ + @if (jointHolders.length === 0) { +
+ group_add + Click Add Holder to add joint account holders. +
+ } + + @if (totalJointSharePercentage !== 100) { +
+ warning_amber + Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%. +
+ } @else { +
+ check_circle + Ownership allocation is complete (100%). +
+ } +
+ + +
+
+
+ B +
+ Mode of Operation + Auto-set from mandatory holders — override manually if required +
+
+ @if (modeOfOperation) { + auto_awesome Auto + } +
+ + @if (jointHolders.length === 0) { +
+ how_to_vote + Add joint holders in Section A first. +
+ } @else { +
+ @for (opt of modeOfOperationOptions; track opt.value) { + + } +
+ +
+ @if (modeOfOperation) { + how_to_vote + Mode of Operation: {{ selectedModeLabel }} + + ({{ mandatoryJointCount }} Mandatory + {{ jointHolders.length - mandatoryJointCount }} Optional) + + } @else { + info + Set Mandatory/Optional for holders above to auto-select mode. + } +
+ } +
+ } + + + @if (selectedAccountStatus === 'MINOR') { +
+
+
+ + child_care + +
+ Minor Account Details + As per RBI — guardian must be an existing registered client +
+
+ Guardian Required +
+ +
+ + Minor's Date of Birth + + + + @if (clientDob) { + Auto-filled from client profile + } + + + + Age + + Must be below 18 years + + + + Date of Majority (Auto) + + Auto-calculated: DOB + 18 years + + + + Guardian Relationship + + @for (rel of guardianRelationshipOptions; track rel) { + {{ rel }} + } + + +
+ + + +
+ info +
    +
  • Minor below 10 years — Guardian must operate the account.
  • +
  • Minor above 10 years — Can operate independently (bank discretion).
  • +
  • Account must be converted to regular savings on attaining majority.
  • +
  • Fresh KYC of the minor must be done on majority.
  • +
+
+
+ } + + + @if (selectedAccountStatus === 'SENIOR_CITIZEN') { +
+
+
+ + elderly + +
+ Senior Citizen Account + Age ≥ 60 years — eligible for preferential banking services +
+
+ Age ≥ 60 +
+ +
+ + Date of Birth + + From client profile + + + + Age + + Age ≥ 60 required for Senior Citizen + +
+ +
+
+ priority_high +
+ Priority Service + Dedicated counters / assisted banking at branches +
+
+
+ percent +
+ Preferential Interest Rate + Additional 0.25%–0.50% on deposits (as per product) +
+
+
+ health_and_safety +
+ Doorstep Banking + Eligible for home banking services (RBI circular) +
+
+
+ gavel +
+ Nomination Mandatory + RBI mandate — ensure nomination is registered +
+
+
+ +
+ info +
    +
  • Age proof (DOB document) mandatory at time of account opening.
  • +
  • Nomination registration is compulsory (Form DA-1).
  • +
  • Bank must offer dedicated helpline and priority grievance redressal.
  • +
  • Door-step banking services must be offered (RBI Master Direction 2023).
  • +
+
+
+ } + + + @if (selectedAccountStatus === 'INDIVIDUAL_MALE') { +
+
+
+ + person + +
+ Individual Account (Male) + Select special category if applicable — governs applicable schemes & benefits +
+
+ Special Category +
+ +
+ @for (opt of specialCategoryOptions; track opt.value) { + + } +
+ +
+ info +
    +
  • Valid KYC documents required (Aadhaar / PAN / Passport).
  • +
  • Nomination registration is mandatory (Form DA-1).
  • +
  • Defence / Ex-Serviceman — eligible for special savings schemes (DSSP).
  • +
  • NRI — FCNR / NRE / NRO account rules apply as per FEMA guidelines.
  • +
+
+
+ } + + + @if (selectedAccountStatus === 'INDIVIDUAL_FEMALE') { +
+
+
+ + person_outline + +
+ Individual Account (Female) + Select weaker section category if applicable — governs PMJDY & priority schemes +
+
+ Weaker Section +
+ +
+ @for (opt of weakerSectionOptions; track opt.value) { + + } +
+ +
+ info +
    +
  • Valid KYC documents required (Aadhaar / PAN / Passport).
  • +
  • Nomination registration is mandatory (Form DA-1).
  • +
  • BPL / SC-ST / Minority — eligible for zero-balance accounts (RBI Priority Sector norms).
  • +
  • PMJDY beneficiary — ₹10,000 overdraft facility & accident insurance cover applicable.
  • +
  • SHG members — group linkage lending & special deposit rates applicable.
  • +
+
+
+ } +
+ } + @if (isNonIndividualClient && currentAccountProductSelected) {
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 bcad57a8c..859832e06 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 @@ -712,6 +712,941 @@ $ni-bg: #f9fafb; } } +// ── Status of Account (RBI) ─────────────────────────────────────────────── + +.soa-card { + margin-top: 16px; + border: 1px solid #e2e8f0; + border-radius: 10px; + background: #fff; + overflow: visible; + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 24px; + background: #f8f9fa; + border-bottom: 1px solid #e8eaed; + border-radius: 10px 10px 0 0; + } + + &__header-left { + display: flex; + align-items: center; + gap: 12px; + } + + &__icon { + width: 36px; + height: 36px; + border-radius: 8px; + background: #ede9fe; + display: flex; + align-items: center; + justify-content: center; + + i { + font-size: 16px; + color: #7c3aed; + } + } + + &__title { + margin: 0; + font-size: 14px; + font-weight: 600; + color: #202124; + } + + &__sub { + font-size: 11px; + color: #6b7280; + display: block; + } +} + +.soa-pill { + display: inline-flex; + align-items: center; + padding: 3px 12px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + background: #ede9fe; + color: #7c3aed; + border: 1px solid #ddd6fe; + + &--minor { + background: #fef3c7; + color: #b45309; + border-color: #fde68a; + } + + &--senior { + background: #d1fae5; + color: #065f46; + border-color: #a7f3d0; + } + + &--ok { + background: #dcfce7; + color: #15803d; + border-color: #bbf7d0; + } +} + +.soa-client-info-bar { + display: flex; + align-items: center; + gap: 20px; + padding: 10px 24px; + background: #f0f9ff; + border-bottom: 1px solid #bae6fd; + flex-wrap: wrap; + + &__item { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + color: #0369a1; + + mat-icon { + font-size: 16px; + height: 16px; + width: 16px; + } + + strong { + font-weight: 700; + } + } +} + +.soa-auto-pill { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + margin-left: auto; + + mat-icon { + font-size: 13px; + height: 13px; + width: 13px; + } + + &--minor { + background: #fef3c7; + color: #b45309; + border: 1px solid #fde68a; + } + + &--senior { + background: #d1fae5; + color: #065f46; + border: 1px solid #a7f3d0; + } + + &--ok { + background: #dcfce7; + color: #15803d; + border: 1px solid #bbf7d0; + } +} + +.soa-tiles { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 12px; + padding: 16px 24px; + border-bottom: 1px solid #f1f5f9; + + @media (width <= 900px) { + grid-template-columns: repeat(3, 1fr); + } + + @media (width <= 600px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.soa-tile { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 14px 10px; + border: 2px solid #e2e8f0; + border-radius: 10px; + background: #fff; + cursor: pointer; + transition: + border-color 0.15s, + box-shadow 0.15s; + position: relative; + + &:hover { + border-color: #a5b4fc; + box-shadow: 0 2px 8px rgb(99 102 241 / 12%); + } + + &--selected { + border-color: #6366f1; + background: #eef2ff; + box-shadow: 0 2px 12px rgb(99 102 241 / 20%); + } + + &__icon { + font-size: 28px; + height: 28px; + width: 28px; + } + + &__label { + font-size: 12px; + font-weight: 600; + color: #374151; + text-align: center; + } + + &__check { + position: absolute; + top: 6px; + right: 6px; + font-size: 16px; + height: 16px; + width: 16px; + color: #6366f1; + } + + &__lock { + position: absolute; + top: 4px; + right: 4px; + display: flex; + align-items: center; + + mat-icon { + font-size: 13px; + height: 13px; + width: 13px; + color: #94a3b8; + } + } + + &--disabled { + opacity: 0.45; + cursor: not-allowed; + background: #f8fafc; + border-color: #e2e8f0; + box-shadow: none; + + &:hover { + border-color: #e2e8f0; + box-shadow: none; + } + } +} + +.soa-block { + padding: 16px 24px; + + &--sep { + border-top: 1px dashed #e2e8f0; + } + + &__head { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: 14px; + } + + &__head-left { + display: flex; + align-items: flex-start; + gap: 12px; + } + + &__letter { + width: 28px; + height: 28px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; + font-weight: 700; + color: #fff; + flex-shrink: 0; + margin-top: 2px; + } + + &__title { + font-size: 13px; + font-weight: 600; + color: #1f2937; + display: block; + } + + &__hint { + font-size: 11px; + color: #6b7280; + display: block; + } +} + +.soa-table-wrap { + overflow-x: auto; + border-radius: 8px; + border: 1px solid #e2e8f0; +} + +.soa-tbl { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +.soa-th { + padding: 10px 14px; + background: #f8fafc; + text-align: left; + font-weight: 600; + font-size: 11px; + color: #64748b; + text-transform: uppercase; + letter-spacing: 0.04em; + border-bottom: 1px solid #e2e8f0; + + &--sr { + width: 44px; + } + + &--center { + text-align: center; + } +} + +.soa-td { + padding: 10px 14px; + border-bottom: 1px solid #f1f5f9; + vertical-align: middle; + + &--sr { + text-align: center; + font-size: 12px; + color: #94a3b8; + font-weight: 600; + } + + &--center { + text-align: center; + } + + &--total-label { + font-size: 12px; + font-weight: 600; + color: #475569; + text-align: right; + } +} + +.soa-tr { + transition: background 0.1s; + + &:hover { + background: #f8fafc; + } + + &--mandatory { + background: #fdf4ff; + } + + &--primary { + background: #f0f9ff; + border-left: 3px solid #0891b2; + } +} + +.soa-avatar--primary { + background: #cffafe; + color: #0e7490; +} + +.soa-primary-tag { + font-size: 10px; + color: #0e7490; + font-weight: 600; + background: #cffafe; + padding: 1px 7px; + border-radius: 999px; + display: inline-block; +} + +.soa-pct-input { + width: 56px; + padding: 4px 6px; + border: 1px solid #cbd5e1; + border-radius: 6px; + font-size: 13px; + font-weight: 600; + text-align: center; + color: #1f2937; + + &:focus { + outline: none; + border-color: #0891b2; + box-shadow: 0 0 0 2px rgb(8 145 178 / 15%); + } + + &::-webkit-inner-spin-button, + &::-webkit-outer-spin-button { + opacity: 1; + } +} + +.soa-pct-unit { + font-size: 13px; + font-weight: 600; + color: #64748b; + margin-left: 2px; +} + +.soa-no-action { + color: #cbd5e1; + font-size: 16px; +} + +.soa-tr-total { + background: #f8fafc; +} + +.soa-client-cell { + display: flex; + align-items: center; + gap: 10px; +} + +.soa-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: #dbeafe; + color: #1d4ed8; + font-weight: 700; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + &--mandatory { + background: #ede9fe; + color: #7c3aed; + } +} + +.soa-client-name { + font-weight: 600; + font-size: 13px; + color: #1f2937; + display: block; +} + +.soa-mandatory-tag { + font-size: 10px; + color: #7c3aed; + font-weight: 600; + background: #ede9fe; + padding: 1px 7px; + border-radius: 999px; +} + +.soa-mono { + font-family: 'Roboto Mono', monospace; + font-size: 12px; + color: #64748b; +} + +.soa-pct-badge { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 2px 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 700; + background: #f1f5f9; + color: #475569; + + &--full, + &--ok { + background: #dcfce7; + color: #15803d; + } + + &--warn { + background: #fef3c7; + color: #b45309; + } +} + +.soa-sign-toggle { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + border: 1px solid #e2e8f0; + cursor: pointer; + transition: all 0.15s; + + mat-icon { + font-size: 14px; + height: 14px; + width: 14px; + } + + &--mandatory { + background: #ede9fe; + color: #7c3aed; + border-color: #ddd6fe; + } + + &--optional { + background: #f8fafc; + color: #94a3b8; + } +} + +.soa-mandatory-count { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + font-weight: 600; + color: #7c3aed; + + mat-icon { + font-size: 14px; + height: 14px; + width: 14px; + } +} + +.soa-alert { + display: flex; + align-items: center; + gap: 8px; + margin-top: 10px; + padding: 8px 14px; + border-radius: 8px; + font-size: 13px; + font-weight: 500; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + } + + &--warn { + background: #fef3c7; + color: #b45309; + } + + &--ok { + background: #dcfce7; + color: #15803d; + } +} + +.soa-empty { + display: flex; + align-items: center; + gap: 10px; + padding: 20px; + background: #f8fafc; + border-radius: 8px; + font-size: 13px; + color: #64748b; + + mat-icon { + color: #94a3b8; + font-size: 22px; + height: 22px; + width: 22px; + } +} + +.soa-add-btn { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; +} + +.soa-mode-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; + + @media (width <= 700px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.soa-mode-card { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 12px 8px; + border: 2px solid #e2e8f0; + border-radius: 10px; + background: #fff; + cursor: pointer; + transition: all 0.15s; + + &:hover { + border-color: #a5b4fc; + } + + &--selected { + border-color: #7c3aed; + background: #faf5ff; + } + + &__icon { + font-size: 24px; + height: 24px; + width: 24px; + color: #7c3aed; + } + + &__code { + font-size: 13px; + font-weight: 700; + color: #1f2937; + } + + &__desc { + font-size: 11px; + color: #6b7280; + text-align: center; + } + + &__check { + position: absolute; + top: 6px; + right: 6px; + font-size: 16px; + height: 16px; + width: 16px; + color: #7c3aed; + } +} + +.soa-mode-summary { + display: flex; + align-items: center; + gap: 8px; + margin-top: 12px; + padding: 8px 14px; + border-radius: 8px; + font-size: 13px; + background: #f8fafc; + color: #64748b; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + } + + &--set { + background: #faf5ff; + color: #7c3aed; + } + + &__from { + font-size: 11px; + color: #94a3b8; + } +} + +.soa-auto-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + background: #fef9c3; + color: #854d0e; + border: 1px solid #fde68a; + + mat-icon { + font-size: 13px; + height: 13px; + width: 13px; + } +} + +.soa-minor-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 4px 16px; + margin-bottom: 16px; + + @media (width <= 700px) { + grid-template-columns: 1fr; + } +} + +.soa-field { + width: 100%; +} + +.soa-guardian-search { + margin-bottom: 12px; + + &__label { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + font-weight: 600; + color: #374151; + margin-bottom: 8px; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + color: #7c3aed; + } + } +} + +.soa-guardian-selected { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 8px; + margin-top: 10px; + + &__icon { + font-size: 24px; + height: 24px; + width: 24px; + color: #16a34a; + } + + &__name { + font-size: 13px; + font-weight: 600; + color: #1f2937; + } + + &__cif { + font-size: 11px; + color: #6b7280; + } +} + +.soa-sc-benefits { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 10px; + margin-bottom: 14px; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} + +.soa-sc-benefit { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 8px; + + mat-icon { + color: #16a34a; + font-size: 20px; + height: 20px; + width: 20px; + flex-shrink: 0; + margin-top: 2px; + } + + strong { + display: block; + font-size: 13px; + color: #1f2937; + font-weight: 600; + } + + span { + font-size: 11px; + color: #6b7280; + } +} + +.soa-category-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + margin-bottom: 16px; + + @media (width <= 700px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.soa-cat-tile { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 14px 10px; + border: 2px solid #e2e8f0; + border-radius: 10px; + background: #fff; + cursor: pointer; + transition: all 0.15s; + + &:hover { + border-color: #a5b4fc; + box-shadow: 0 2px 8px rgb(99 102 241 / 12%); + } + + &--selected { + &.soa-cat-tile--male { + border-color: #3b82f6; + background: #eff6ff; + box-shadow: 0 2px 12px rgb(59 130 246 / 20%); + } + + &.soa-cat-tile--female { + border-color: #ec4899; + background: #fdf2f8; + box-shadow: 0 2px 12px rgb(236 72 153 / 20%); + } + } + + &__icon { + font-size: 26px; + height: 26px; + width: 26px; + color: #64748b; + } + + &--selected.soa-cat-tile--male &__icon { + color: #3b82f6; + } + + &--selected.soa-cat-tile--female &__icon { + color: #ec4899; + } + + &__label { + font-size: 11px; + font-weight: 600; + color: #374151; + text-align: center; + line-height: 1.3; + } + + &__check { + position: absolute; + top: 6px; + right: 6px; + font-size: 16px; + height: 16px; + width: 16px; + + .soa-cat-tile--male & { + color: #3b82f6; + } + + .soa-cat-tile--female & { + color: #ec4899; + } + } +} + +.soa-info-banner { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px 14px; + border-radius: 8px; + font-size: 12px; + background: #eff6ff; + border: 1px solid #bfdbfe; + color: #1e40af; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + flex-shrink: 0; + margin-top: 2px; + } + + ul { + margin: 4px 0 0; + padding-left: 18px; + + li { + margin-bottom: 3px; + } + } + + &--minor { + background: #fefce8; + border-color: #fde68a; + color: #92400e; + } + + &--senior { + background: #f0fdf4; + border-color: #bbf7d0; + color: #065f46; + } + + &--male { + background: #eff6ff; + border-color: #bfdbfe; + color: #1e40af; + } + + &--female { + background: #fdf2f8; + border-color: #f9a8d4; + color: #9d174d; + } +} + // ── Account Feature Toggles ──────────────────────────────────────────────── .sad-toggles { display: flex; 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 205a267cc..3cc5083de 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 @@ -8,7 +8,7 @@ /** Angular Imports */ import { Component, OnInit, Input, Output, EventEmitter, inject, ChangeDetectorRef } from '@angular/core'; -import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; +import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms'; import { Dates } from 'app/core/utils/dates'; /** Custom Services */ @@ -17,20 +17,7 @@ import { SettingsService } from 'app/settings/settings.service'; import { ClientsService } from 'app/clients/clients.service'; import { MatDialog } from '@angular/material/dialog'; import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper'; -import { - MatTable, - MatColumnDef, - MatHeaderCellDef, - MatHeaderCell, - MatCellDef, - MatCell, - MatHeaderRowDef, - MatHeaderRow, - MatRowDef, - MatRow -} from '@angular/material/table'; import { MatIcon } from '@angular/material/icon'; -import { FormsModule } from '@angular/forms'; 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'; @@ -49,16 +36,6 @@ import { AddRelatedClientDialogComponent } from './add-related-client-dialog/add FaIconComponent, MatStepperNext, IntroducerSearchComponent, - MatTable, - MatColumnDef, - MatHeaderCellDef, - MatHeaderCell, - MatCellDef, - MatCell, - MatHeaderRowDef, - MatHeaderRow, - MatRowDef, - MatRow, MatIcon, FormsModule ] @@ -72,20 +49,13 @@ export class CurrentAccountDetailsStepComponent implements OnInit { private dateUtils = inject(Dates); private cdr = inject(ChangeDetectorRef); - /** Current Account Template */ @Input() currentAccountTemplate: any; - /** Minimum date allowed. */ minDate = new Date(2000, 0, 1); - /** Maximum date allowed. */ maxDate = new Date(); - /** Product Data */ productData: any; - /** Field Officer Data */ fieldOfficerData: any; - /** For edit current account form */ isFieldOfficerPatched = false; - /** Current Account Details Form */ currentAccountDetailsForm: UntypedFormGroup; currentAccountProductSelected = false; @@ -93,7 +63,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit { introducerName: string | null = null; introducerCif: string | null = null; - /** Non-Individual section */ + // ── Non-Individual (existing) ────────────────────────────────────────── isNonIndividualClient = false; relatedClients: Array<{ clientId: number; @@ -111,16 +81,86 @@ export class CurrentAccountDetailsStepComponent implements OnInit { { value: 'ONLY_ONE', label: 'Single', sublabel: 'Only 1 Person', icon: 'person' } ]; - /** Current Account Template with product data */ @Output() currentAccountProductTemplate = new EventEmitter(); - /** Local copy of product template for displaying classification info */ productTemplate: any = null; accountTypeOptions: string[] = []; customerSegmentOptions: string[] = []; selectedAccountType: string | null = null; selectedCustomerSegment: string | null = null; + // ── Client info (auto-fetched) ───────────────────────────────────────── + clientDob: Date | null = null; + clientAge: number | null = null; + clientGender: string | null = null; + primaryClientName = ''; + primaryClientCif = ''; + primaryClientId: number | null = null; + primaryHolderShare = 0; + + // ── Status of Account (RBI) ──────────────────────────────────────────── + readonly accountStatusOptions = [ + { value: 'INDIVIDUAL_MALE', label: 'Individual (Male)', icon: 'person', color: '#3b82f6' }, + { value: 'INDIVIDUAL_FEMALE', label: 'Individual (Female)', icon: 'person_outline', color: '#ec4899' }, + { value: 'SINGLE', label: 'Single Account', icon: 'account_circle', color: '#0891b2' }, + { value: 'JOINT', label: 'Joint Account', icon: 'group', color: '#8b5cf6' }, + { value: 'MINOR', label: 'Minor Account', icon: 'child_care', color: '#f59e0b' }, + { value: 'SENIOR_CITIZEN', label: 'Senior Citizen', icon: 'elderly', color: '#10b981' } + ]; + + selectedAccountStatus = ''; + + // ── Joint Account (Status of Account) ───────────────────────────────── + jointHolders: Array<{ + clientId: number; + clientName: string; + clientCif: string; + sharePercentage: number; + isMandatory: boolean; + }> = []; + + modeOfOperation = ''; + + readonly modeOfOperationOptions = [ + { value: 'EITHER_OR_SURVIVOR', label: 'E or S', sublabel: 'Either or Survivor', icon: 'how_to_vote' }, + { value: 'FORMER_OR_SURVIVOR', label: 'F or S', sublabel: 'Former or Survivor', icon: 'person' }, + { value: 'JOINTLY', label: 'Jointly', sublabel: 'All Must Sign', icon: 'groups' }, + { value: 'ANYONE_OR_SURVIVOR', label: 'Anyone', sublabel: 'Anyone or Survivor', icon: 'group' } + ]; + + // ── Individual Male — Special Category ──────────────────────────────── + selectedSpecialCategory = ''; + + readonly specialCategoryOptions = [ + { value: 'DEFENCE', label: 'Defence Personnel', icon: 'military_tech' }, + { value: 'EX_SERVICEMAN', label: 'Ex-Serviceman', icon: 'shield' }, + { value: 'GOVT_EMPLOYEE', label: 'Government Employee', icon: 'account_balance' }, + { value: 'PHYSICALLY_CHALLENGED', label: 'Physically Challenged', icon: 'accessible' }, + { value: 'NRI', label: 'NRI (Non-Resident Indian)', icon: 'flight' }, + { value: 'NONE', label: 'None / General', icon: 'person' } + ]; + + // ── Individual Female — Weaker Section ──────────────────────────────── + selectedWeakerSection = ''; + + readonly weakerSectionOptions = [ + { value: 'BPL', label: 'BPL (Below Poverty Line)', icon: 'volunteer_activism' }, + { value: 'SC_ST', label: 'SC / ST Category', icon: 'groups' }, + { value: 'MINORITY', label: 'Minority Community', icon: 'diversity_3' }, + { value: 'SHG', label: 'Self Help Group Member', icon: 'handshake' }, + { value: 'PMJDY', label: 'PMJDY Beneficiary', icon: 'savings' }, + { value: 'NONE', label: 'None / General', icon: 'person_outline' } + ]; + + // ── Minor Account ────────────────────────────────────────────────────── + guardianClientId: number | null = null; + guardianName = ''; + guardianCif = ''; + guardianRelationship = ''; + minorDob: Date | null = null; + + readonly guardianRelationshipOptions = ['Father', 'Mother', 'Legal Guardian', 'Court Appointed Guardian']; + constructor() { this.createCurrentAccountDetailsForm(); } @@ -128,9 +168,23 @@ export class CurrentAccountDetailsStepComponent implements OnInit { ngOnInit() { this.maxDate = this.settingsService.businessDate; this.buildDependencies(); - if (this.currentAccountTemplate?.clientId) { - this.clientsService.getClientData(String(this.currentAccountTemplate.clientId)).subscribe((client: any) => { + + const clientId = this.currentAccountTemplate?.clientId; + if (clientId) { + this.primaryClientId = clientId; + this.clientsService.getClientData(String(clientId)).subscribe((client: any) => { this.isNonIndividualClient = client?.legalForm?.id === 2; + this.primaryClientName = client?.displayName ?? client?.fullname ?? ''; + this.primaryClientCif = client?.cifNumber ?? client?.cifNo ?? ''; + if (client?.dateOfBirth) { + const dob = this.parseClientDob(client.dateOfBirth); + if (dob) { + this.clientDob = dob; + this.clientAge = this.calculateAge(dob); + this.clientGender = client.gender?.name?.toLowerCase() ?? null; + this.autoDetectAccountStatus(); + } + } this.cdr.detectChanges(); }); } @@ -153,39 +207,27 @@ export class CurrentAccountDetailsStepComponent implements OnInit { } } - /** - * Creates current account details form. - */ createCurrentAccountDetailsForm() { this.currentAccountDetailsForm = this.formBuilder.group({ - productId: [ - '', - Validators.required - ], - submittedOnDate: [ - '', - Validators.required - ], + productId: ['', Validators.required], + submittedOnDate: ['', Validators.required], fieldOfficerId: [''], externalId: [''], balanceSwapOption: [false], msme: [false], - autoDebitFacility: [false] + autoDebitFacility: [false], + establishmentDate: [''], + startOperationDate: [''] }); } - /** - * Fetches current account product template on productId value changes - */ buildDependencies() { const entityId = this.currentAccountTemplate.groupId || this.currentAccountTemplate.clientId; this.currentAccountDetailsForm.get('productId').valueChanges.subscribe((productId: string) => { this.currentAccountsService .getCurrentAccountTemplate(entityId, productId, this.currentAccountTemplate.groupId ? true : false) .subscribe((response: any) => { - // Ensure currency is included in the product template if (!response.currency) { - // Try to get currency from the selected product const selectedProduct = this.currentAccountTemplate.productOptions?.find( (product: any) => product.id == productId ); @@ -203,7 +245,6 @@ 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, @@ -220,9 +261,6 @@ export class CurrentAccountDetailsStepComponent implements OnInit { }); } - /** - * Formats template dates into the current CBS date format. - */ private extractOptions(options: any[], fallback?: string): string[] { if (Array.isArray(options) && options.length > 0) { return options.map((o: any) => o.value ?? o.name ?? o); @@ -231,9 +269,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit { } formatTemplateDate(value: any): string { - if (!value) { - return ''; - } + if (!value) return ''; let dateValue: Date | null = null; if (value instanceof Date) { dateValue = value; @@ -248,15 +284,123 @@ export class CurrentAccountDetailsStepComponent implements OnInit { return this.dateUtils.formatDate(dateValue, 'M/d/yyyy') || ''; } - /** - * Returns current account form value. - */ + // ── Client DOB helpers ───────────────────────────────────────────────── + private parseClientDob(dob: any): Date | null { + if (!dob) return null; + if (Array.isArray(dob) && dob.length >= 3) return new Date(dob[0], dob[1] - 1, dob[2]); + const d = new Date(dob); + return isNaN(d.getTime()) ? null : d; + } + + private calculateAge(dob: Date): number { + const today = this.settingsService.businessDate ?? new Date(); + let age = today.getFullYear() - dob.getFullYear(); + const m = today.getMonth() - dob.getMonth(); + if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) age--; + return age; + } + + private autoDetectAccountStatus(): void { + if (this.clientAge === null) return; + if (this.clientAge < 18) { + this.selectedAccountStatus = 'MINOR'; + this.minorDob = this.clientDob; + } else if (this.clientAge >= 60) { + this.selectedAccountStatus = 'SENIOR_CITIZEN'; + } else if (this.clientGender === 'female') { + this.selectedAccountStatus = 'INDIVIDUAL_FEMALE'; + } else { + this.selectedAccountStatus = 'INDIVIDUAL_MALE'; + } + } + + isTileDisabled(value: string): boolean { + if (value === 'MINOR') return this.clientAge === null || this.clientAge >= 18; + if (value === 'SENIOR_CITIZEN') return this.clientAge === null || this.clientAge < 60; + return false; + } + + getTileTooltip(value: string): string { + if (value === 'MINOR' && this.clientAge !== null && this.clientAge >= 18) + return `Age ${this.clientAge} yrs — Minor only allowed below 18 years`; + if (value === 'SENIOR_CITIZEN' && this.clientAge !== null && this.clientAge < 60) + return `Age ${this.clientAge} yrs — Senior Citizen only allowed at 60+ years`; + if (value === 'MINOR' && this.clientAge === null) return 'DOB not available — cannot verify age'; + if (value === 'SENIOR_CITIZEN' && this.clientAge === null) return 'DOB not available — cannot verify age'; + return ''; + } + + // ── Introducer ───────────────────────────────────────────────────────── 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; } + onGuardianChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void { + this.guardianClientId = event?.clientId ?? null; + this.guardianName = event?.displayName ?? ''; + this.guardianCif = event?.cifNumber ?? ''; + } + + // ── Joint Account (Status of Account) ───────────────────────────────── + openAddJointHolderDialog(): void { + const ref = this.dialog.open(AddRelatedClientDialogComponent, { + width: '500px', + panelClass: 'arc-dialog-panel', + disableClose: true + }); + ref.afterClosed().subscribe((result) => { + if (result) { + const duplicate = this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif); + if (duplicate) { + alert(`Account holder with CIF ${result.clientCif} is already added.`); + return; + } + this.jointHolders = [...this.jointHolders, { ...result, isMandatory: false }]; + this.autoSetModeOfOperation(); + } + }); + } + + removeJointHolder(index: number): void { + this.jointHolders = this.jointHolders.filter((_, i) => i !== index); + this.autoSetModeOfOperation(); + } + + toggleJointHolderMandatory(holder: any): void { + holder.isMandatory = !holder.isMandatory; + this.autoSetModeOfOperation(); + } + + autoSetModeOfOperation(): void { + const mandatory = this.jointHolders.filter((h) => h.isMandatory).length; + if (mandatory === 0) this.modeOfOperation = 'EITHER_OR_SURVIVOR'; + else if (mandatory === 1) this.modeOfOperation = 'FORMER_OR_SURVIVOR'; + else this.modeOfOperation = 'JOINTLY'; + } + + get totalJointSharePercentage(): number { + return (this.primaryHolderShare || 0) + this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0); + } + + get mandatoryJointCount(): number { + return this.jointHolders.filter((h) => h.isMandatory).length; + } + + get selectedModeLabel(): string { + return this.modeOfOperationOptions.find((o) => o.value === this.modeOfOperation)?.sublabel ?? ''; + } + + get majorityDate(): string { + const dob = this.minorDob ?? this.clientDob; + if (!dob) return ''; + const d = new Date(dob); + d.setFullYear(d.getFullYear() + 18); + return this.dateUtils.formatDate(d, 'M/d/yyyy') || ''; + } + + // ── Non-Individual (existing) helpers ────────────────────────────────── openAddRelatedClientDialog(): void { const ref = this.dialog.open(AddRelatedClientDialogComponent, { width: '500px', @@ -270,9 +414,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit { alert(`Partner with CIF ${result.clientCif} is already added.`); return; } - this.relatedClients = [ - ...this.relatedClients, - { ...result, isMandatory: false }]; + this.relatedClients = [...this.relatedClients, { ...result, isMandatory: false }]; this.autoSetSignatoryRights(); } }); @@ -288,16 +430,11 @@ export class CurrentAccountDetailsStepComponent implements OnInit { this.autoSetSignatoryRights(); } - /** Auto-compute Mode of Operation based on mandatory count */ autoSetSignatoryRights(): void { const mandatory = this.relatedClients.filter((c) => c.isMandatory).length; - if (mandatory === 0) { - this.signatoryRights = 'ANYONE'; - } else if (mandatory === 1) { - this.signatoryRights = 'ONE_MANDATORY'; - } else { - this.signatoryRights = 'TWO_MANDATORY'; - } + if (mandatory === 0) this.signatoryRights = 'ANYONE'; + else if (mandatory === 1) this.signatoryRights = 'ONE_MANDATORY'; + else this.signatoryRights = 'TWO_MANDATORY'; } get totalSharePercentage(): number { @@ -329,6 +466,33 @@ export class CurrentAccountDetailsStepComponent implements OnInit { sharePercentage: c.sharePercentage, isMandatory: c.isMandatory })); + } else { + if (this.selectedAccountStatus) value.accountStatus = this.selectedAccountStatus; + if (this.selectedAccountStatus === 'INDIVIDUAL_MALE' && this.selectedSpecialCategory) + value.specialCategory = this.selectedSpecialCategory; + if (this.selectedAccountStatus === 'INDIVIDUAL_FEMALE' && this.selectedWeakerSection) + value.weakerSection = this.selectedWeakerSection; + if (this.selectedAccountStatus === 'JOINT') { + value.modeOfOperation = this.modeOfOperation || null; + value.jointHolders = [ + { + clientId: this.primaryClientId, + clientName: this.primaryClientName, + clientCif: this.primaryClientCif, + sharePercentage: this.primaryHolderShare, + isMandatory: true, + isPrimary: true + }, + ...this.jointHolders.map((h) => ({ ...h, isPrimary: false })) + ]; + } + if (this.selectedAccountStatus === 'MINOR') { + value.guardianClientId = this.guardianClientId; + value.guardianName = this.guardianName; + value.guardianCif = this.guardianCif; + value.guardianRelationship = this.guardianRelationship; + value.minorDob = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'yyyy-MM-dd') : null; + } } return value; } diff --git a/src/app/current-accounts/current-accounts.service.ts b/src/app/current-accounts/current-accounts.service.ts index 6267da6b3..b9721be8f 100644 --- a/src/app/current-accounts/current-accounts.service.ts +++ b/src/app/current-accounts/current-accounts.service.ts @@ -68,6 +68,11 @@ export class CurrentAccountsService { return this.http.get(`/currentaccounts/${accountId}`, { params: httpParams }); } + getClientCurrentAccounts(clientId: string): Observable { + const httpParams = new HttpParams().set('clientId', clientId); + return this.http.get('/currentaccounts', { params: httpParams }); + } + /** * @param accountId Current Account Id of account to get data for. * @returns {Observable} Current account and template. 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 30b71d413..57e6ae181 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 @@ -115,13 +115,8 @@ } - - Introducer Required - - -
- Share Linking Enabled + Deposit Account Link {{ productTemplate.shareLinkingEnabled ? 'Yes' : 'No' }} @@ -129,8 +124,652 @@
- @if (productTemplate?.introducerRequired) { - + + @if (productTemplate?.shareLinkingEnabled) { +
+
+
+
+ link +
+
+

Deposit Account Link

+ Link an existing Savings or Current account to this RD +
+
+
+ +
+ + @if (dalLoading) { +
+ hourglass_top + Loading accounts… +
+ } @else if (selectedLinkedAccount) { + + +
+
+ + {{ selectedLinkedAccount.accountType === 'SAVINGS' ? 'savings' : 'account_balance' }} + {{ selectedLinkedAccount.accountType === 'SAVINGS' ? 'Savings' : 'Current' }} + +
+ {{ selectedLinkedAccount.accountNo }} + {{ selectedLinkedAccount.productName }} +
+
+ Balance + {{ selectedLinkedAccount.currencyCode }} {{ selectedLinkedAccount.balance | number:'1.2-2' }} +
+
+ +
+ + } @else { + + + @if (!dalSelectedType) { +
+

Select account type to link:

+
+ + +
+
+ } @else { + + +
+
+ + + {{ dalSelectedType === 'SAVINGS' ? 'savings' : 'account_balance' }} + {{ dalSelectedType === 'SAVINGS' ? 'Savings Accounts' : 'Current Accounts' }} + +
+ + + Search by Account No. or Product + search + + @if (dalSearch) { + + } + + + @if (dalFilteredAccounts.length === 0) { +
+ search_off + No {{ dalSelectedType === 'SAVINGS' ? 'Savings' : 'Current' }} accounts found. +
+ } @else { +
+ @for (account of dalFilteredAccounts; track account.id) { +
+
+ {{ account.accountNo }} + {{ account.productName }} +
+
+ {{ account.status }} + {{ account.currencyCode }} {{ account.balance | number:'1.2-2' }} +
+ chevron_right +
+ } +
+ } +
+ } + } +
+
} } + + + @if (isProductSelected) { +
+
+
+
+ +
+
+

Status of Account

+ As per RBI guidelines — select account holder type +
+
+ RBI Compliant +
+ + + @if (clientDob) { +
+
+ cake + Date of Birth: {{ formatTemplateDate(clientDob) }} +
+
+ person_pin + Age: {{ clientAge }} years +
+ @if (clientAge !== null && clientAge < 18) { + + child_care Auto-detected: Minor + + } @else if (clientAge !== null && clientAge >= 60) { + + elderly Auto-detected: Senior Citizen + + } @else { + check_circle Age Verified + } +
+ } + + +
+ @for (opt of accountStatusOptions; track opt.value) { + + } +
+ + + @if (selectedAccountStatus === 'JOINT') { +
+
+
+ A +
+ Joint Account Holders + Add all co-applicants linked to this joint RD account +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + @for (h of jointHolders; track h; let i = $index) { + + + + + + + + + + } + + + + + + + + + + +
Sr.Holder NameCIF No.Ownership %Signing AuthorityRoleAction
1 +
+
+ {{ primaryClientName ? primaryClientName[0].toUpperCase() : '?' }} +
+
+ {{ primaryClientName || 'Primary Holder' }} + Primary Account Holder +
+
+
{{ primaryClientCif || '—' }} + + % + + + lock Mandatory + + Primary Account Holder—
{{ i + 2 }} +
+
+ {{ h.clientName[0].toUpperCase() }} +
+
+ {{ h.clientName }} + @if (h.isMandatory) { + Mandatory Signatory + } +
+
+
{{ h.clientCif || '—' }} + + {{ h.sharePercentage }}% + + + + Co-Holder + +
Total Ownership + + {{ totalJointSharePercentage }}% + + + + lock {{ mandatoryJointCount + 1 }} Mandatory + +
+
+ + @if (jointHolders.length === 0) { +
+ group_add + Click Add Holder to add joint account holders. +
+ } + + @if (totalJointSharePercentage !== 100) { +
+ warning_amber + Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%. +
+ } @else { +
+ check_circle + Ownership allocation is complete (100%). +
+ } +
+ +
+
+
+ B +
+ Mode of Operation + Auto-set from mandatory holders — override manually if required +
+
+ @if (modeOfOperation) { + auto_awesome Auto + } +
+ + @if (jointHolders.length === 0) { +
+ how_to_vote + Add joint holders in Section A first. +
+ } @else { +
+ @for (opt of modeOfOperationOptions; track opt.value) { + + } +
+ +
+ @if (modeOfOperation) { + how_to_vote + Mode of Operation: {{ selectedModeLabel }} + + ({{ mandatoryJointCount }} Mandatory + {{ jointHolders.length - mandatoryJointCount }} Optional) + + } @else { + info + Set Mandatory/Optional for holders above to auto-select mode. + } +
+ } +
+ } + + + @if (selectedAccountStatus === 'MINOR') { +
+
+
+ + child_care + +
+ Minor Account Details + As per RBI — guardian must be an existing registered client +
+
+ Guardian Required +
+ +
+ + Minor's Date of Birth + + + + @if (clientDob) { + Auto-filled from client profile + } + + + + Age + + Must be below 18 years + + + + Date of Majority (Auto) + + Auto-calculated: DOB + 18 years + + + + Guardian Relationship + + @for (rel of guardianRelationshipOptions; track rel) { + {{ rel }} + } + + +
+ + + +
+ info +
    +
  • Minor below 10 years — Guardian must operate the account.
  • +
  • Minor above 10 years — Can operate independently (bank discretion).
  • +
  • Account must be converted to regular savings on attaining majority.
  • +
  • Fresh KYC of the minor must be done on majority.
  • +
+
+
+ } + + + @if (selectedAccountStatus === 'SENIOR_CITIZEN') { +
+
+
+ + elderly + +
+ Senior Citizen Account + Age ≥ 60 years — eligible for preferential banking services +
+
+ Age ≥ 60 +
+ +
+ + Date of Birth + + From client profile + + + + Age + + Age ≥ 60 required for Senior Citizen + +
+ +
+
+ priority_high +
+ Priority Service + Dedicated counters / assisted banking at branches +
+
+
+ percent +
+ Preferential Interest Rate + Additional 0.25%–0.50% on deposits (as per product) +
+
+
+ health_and_safety +
+ Doorstep Banking + Eligible for home banking services (RBI circular) +
+
+
+ gavel +
+ Nomination Mandatory + RBI mandate — ensure nomination is registered +
+
+
+ +
+ info +
    +
  • Age proof (DOB document) mandatory at time of account opening.
  • +
  • Nomination registration is compulsory (Form DA-1).
  • +
  • Bank must offer dedicated helpline and priority grievance redressal.
  • +
  • Door-step banking services must be offered (RBI Master Direction 2023).
  • +
+
+
+ } + + + @if (selectedAccountStatus === 'INDIVIDUAL_MALE') { +
+
+
+ + person + +
+ Individual Account (Male) + Select special category if applicable — governs applicable schemes & benefits +
+
+ Special Category +
+ +
+ @for (opt of specialCategoryOptions; track opt.value) { + + } +
+ +
+ info +
    +
  • Valid KYC documents required (Aadhaar / PAN / Passport).
  • +
  • Nomination registration is mandatory (Form DA-1).
  • +
  • Defence / Ex-Serviceman — eligible for special savings schemes (DSSP).
  • +
  • NRI — FCNR / NRE / NRO account rules apply as per FEMA guidelines.
  • +
+
+
+ } + + + @if (selectedAccountStatus === 'INDIVIDUAL_FEMALE') { +
+
+
+ + person_outline + +
+ Individual Account (Female) + Select weaker section category if applicable — governs PMJDY & priority schemes +
+
+ Weaker Section +
+ +
+ @for (opt of weakerSectionOptions; track opt.value) { + + } +
+ +
+ info +
    +
  • Valid KYC documents required (Aadhaar / PAN / Passport).
  • +
  • Nomination registration is mandatory (Form DA-1).
  • +
  • BPL / SC-ST / Minority — eligible for zero-balance accounts (RBI Priority Sector norms).
  • +
  • PMJDY beneficiary — ₹10,000 overdraft facility & accident insurance cover applicable.
  • +
  • SHG members — group linkage lending & special deposit rates applicable.
  • +
+
+
+ } +
+ } + + 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 cd6bfa982..6afa07d9b 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 @@ -184,6 +184,1289 @@ $gray-900: #202124; } } +// ── Deposit Account Link ────────────────────────────────────────────────── + +.dal-card { + margin-top: 16px; + border: 1px solid #e2e8f0; + border-radius: 12px; + background: #fff; + overflow: hidden; + + &__header { + display: flex; + align-items: center; + gap: 14px; + padding: 16px 24px; + border-bottom: 1px solid #f1f5f9; + background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); + } + + &__header-left { + display: flex; + align-items: center; + gap: 14px; + } + + &__icon { + width: 44px; + height: 44px; + border-radius: 10px; + background: #0ea5e9; + display: flex; + align-items: center; + justify-content: center; + color: #fff; + } + + &__title { + margin: 0; + font-size: 15px; + font-weight: 700; + color: #0c4a6e; + } + + &__sub { + display: block; + font-size: 12px; + color: #64748b; + margin-top: 2px; + } + + &__body { + padding: 20px 24px; + } +} + +// ── Loading ─────────────────────────────────────────────────────────────── +.dal-loading { + display: flex; + align-items: center; + gap: 10px; + padding: 28px; + color: #64748b; + font-size: 14px; + justify-content: center; + + &__icon { + animation: dal-spin 1.2s linear infinite; + color: #0ea5e9; + } +} + +@keyframes dal-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +// ── Step 1 — Type selection ─────────────────────────────────────────────── +.dal-type-step { + &__label { + font-size: 13px; + color: #64748b; + margin: 0 0 14px; + } +} + +.dal-type-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 14px; +} + +.dal-type-btn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 24px 16px; + border-radius: 12px; + border: 2px solid #e2e8f0; + background: #f8fafc; + cursor: pointer; + transition: all 0.18s; + + mat-icon { + font-size: 32px; + width: 32px; + height: 32px; + } + + &__name { + font-size: 14px; + font-weight: 700; + color: #0f172a; + } + + &__count { + font-size: 11px; + color: #94a3b8; + } + + &--savings { + &:hover { + border-color: #3b82f6; + background: #eff6ff; + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15); + mat-icon { color: #2563eb; } + .dal-type-btn__name { color: #1d4ed8; } + } + } + + &--current { + &:hover { + border-color: #16a34a; + background: #f0fdf4; + box-shadow: 0 4px 12px rgba(22, 163, 74, 0.15); + mat-icon { color: #15803d; } + .dal-type-btn__name { color: #15803d; } + } + } +} + +// ── Step 2 — Picker ─────────────────────────────────────────────────────── +.dal-picker { + &__topbar { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 14px; + } + + &__title { + display: flex; + align-items: center; + gap: 6px; + font-size: 14px; + font-weight: 700; + color: #0f172a; + + mat-icon { font-size: 18px; width: 18px; height: 18px; color: #64748b; } + } +} + +.dal-back-btn { + color: #64748b; +} + +.dal-search-field { + width: 100%; + margin-bottom: 8px; +} + +.dal-empty { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 32px 16px; + color: #94a3b8; + font-size: 13px; + text-align: center; + + mat-icon { font-size: 36px; width: 36px; height: 36px; } +} + +.dal-list { + display: flex; + flex-direction: column; + gap: 8px; + max-height: 300px; + overflow-y: auto; +} + +.dal-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + border: 1px solid #e2e8f0; + border-radius: 10px; + cursor: pointer; + transition: border-color 0.15s, background 0.15s, box-shadow 0.15s; + + &:hover { + border-color: #0ea5e9; + background: #f0f9ff; + box-shadow: 0 2px 8px rgba(14, 165, 233, 0.12); + } + + &__details { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; + } + + &__accno { + font-size: 14px; + font-weight: 600; + color: #0f172a; + font-family: monospace; + } + + &__product { + font-size: 12px; + color: #64748b; + } + + &__right { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 3px; + } + + &__status { + font-size: 11px; + font-weight: 600; + color: #94a3b8; + + &--active { color: #16a34a; } + } + + &__balance { + font-size: 13px; + font-weight: 600; + color: #0f172a; + } + + &__arrow { + color: #cbd5e1; + font-size: 20px; + width: 20px; + height: 20px; + } +} + +// ── Selected Account ────────────────────────────────────────────────────── +.dal-selected { + display: flex; + align-items: center; + gap: 16px; + padding: 16px 20px; + border: 2px solid #0ea5e9; + border-radius: 12px; + background: #f0f9ff; + + &__info { + display: flex; + align-items: center; + gap: 14px; + flex: 1; + flex-wrap: wrap; + } + + &__details { + display: flex; + flex-direction: column; + gap: 2px; + flex: 1; + } + + &__accno { + font-size: 15px; + font-weight: 700; + color: #0c4a6e; + font-family: monospace; + } + + &__product { + font-size: 12px; + color: #64748b; + } + + &__balance { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 2px; + } + + &__bal-label { + font-size: 10px; + color: #94a3b8; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + &__bal-amount { + font-size: 15px; + font-weight: 700; + color: #0c4a6e; + } +} + +.dal-change-btn { + white-space: nowrap; +} + +.dal-type-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 12px; + border-radius: 999px; + font-size: 12px; + font-weight: 700; + white-space: nowrap; + background: #f1f5f9; + color: #475569; + border: 1px solid #e2e8f0; + + mat-icon { font-size: 14px; width: 14px; height: 14px; } + + &--savings { + background: #eff6ff; + color: #1d4ed8; + border-color: #bfdbfe; + mat-icon { color: #2563eb; } + } + + &--current { + background: #f0fdf4; + color: #15803d; + border-color: #bbf7d0; + mat-icon { color: #16a34a; } + } +} + +// ── Status of Account (RBI) ─────────────────────────────────────────────── + +.soa-card { + margin-top: 16px; + border: 1px solid #e2e8f0; + border-radius: 10px; + background: #fff; + overflow: visible; + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 24px; + background: #f8f9fa; + border-bottom: 1px solid #e8eaed; + border-radius: 10px 10px 0 0; + } + + &__header-left { + display: flex; + align-items: center; + gap: 12px; + } + + &__icon { + width: 36px; + height: 36px; + border-radius: 8px; + background: #ede9fe; + display: flex; + align-items: center; + justify-content: center; + + i { + font-size: 16px; + color: #7c3aed; + } + } + + &__title { + margin: 0; + font-size: 14px; + font-weight: 600; + color: #202124; + } + + &__sub { + font-size: 11px; + color: #6b7280; + display: block; + } +} + +.soa-pill { + display: inline-flex; + align-items: center; + padding: 3px 12px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + background: #ede9fe; + color: #7c3aed; + border: 1px solid #ddd6fe; + + &--minor { + background: #fef3c7; + color: #b45309; + border-color: #fde68a; + } + + &--senior { + background: #d1fae5; + color: #065f46; + border-color: #a7f3d0; + } + + &--ok { + background: #dcfce7; + color: #15803d; + border-color: #bbf7d0; + } +} + +.soa-client-info-bar { + display: flex; + align-items: center; + gap: 20px; + padding: 10px 24px; + background: #f0f9ff; + border-bottom: 1px solid #bae6fd; + flex-wrap: wrap; + + &__item { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + color: #0369a1; + + mat-icon { + font-size: 16px; + height: 16px; + width: 16px; + } + + strong { + font-weight: 700; + } + } +} + +.soa-auto-pill { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + margin-left: auto; + + mat-icon { + font-size: 13px; + height: 13px; + width: 13px; + } + + &--minor { + background: #fef3c7; + color: #b45309; + border: 1px solid #fde68a; + } + + &--senior { + background: #d1fae5; + color: #065f46; + border: 1px solid #a7f3d0; + } + + &--ok { + background: #dcfce7; + color: #15803d; + border: 1px solid #bbf7d0; + } +} + +.soa-tiles { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 12px; + padding: 16px 24px; + border-bottom: 1px solid #f1f5f9; + + @media (width <= 900px) { + grid-template-columns: repeat(3, 1fr); + } + + @media (width <= 600px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.soa-tile { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 14px 10px; + border: 2px solid #e2e8f0; + border-radius: 10px; + background: #fff; + cursor: pointer; + transition: + border-color 0.15s, + box-shadow 0.15s; + position: relative; + + &:hover { + border-color: #a5b4fc; + box-shadow: 0 2px 8px rgb(99 102 241 / 12%); + } + + &--selected { + border-color: #6366f1; + background: #eef2ff; + box-shadow: 0 2px 12px rgb(99 102 241 / 20%); + } + + &__icon { + font-size: 28px; + height: 28px; + width: 28px; + } + + &__label { + font-size: 12px; + font-weight: 600; + color: #374151; + text-align: center; + } + + &__check { + position: absolute; + top: 6px; + right: 6px; + font-size: 16px; + height: 16px; + width: 16px; + color: #6366f1; + } + + &__lock { + position: absolute; + top: 4px; + right: 4px; + display: flex; + align-items: center; + + mat-icon { + font-size: 13px; + height: 13px; + width: 13px; + color: #94a3b8; + } + } + + &--disabled { + opacity: 0.45; + cursor: not-allowed; + background: #f8fafc; + border-color: #e2e8f0; + box-shadow: none; + + &:hover { + border-color: #e2e8f0; + box-shadow: none; + } + } +} + +.soa-block { + padding: 16px 24px; + + &--sep { + border-top: 1px dashed #e2e8f0; + } + + &__head { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: 14px; + } + + &__head-left { + display: flex; + align-items: flex-start; + gap: 12px; + } + + &__letter { + width: 28px; + height: 28px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; + font-weight: 700; + color: #fff; + flex-shrink: 0; + margin-top: 2px; + } + + &__title { + font-size: 13px; + font-weight: 600; + color: #1f2937; + display: block; + } + + &__hint { + font-size: 11px; + color: #6b7280; + display: block; + } +} + +.soa-table-wrap { + overflow-x: auto; + border-radius: 8px; + border: 1px solid #e2e8f0; +} + +.soa-tbl { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +.soa-th { + padding: 10px 14px; + background: #f8fafc; + text-align: left; + font-weight: 600; + font-size: 11px; + color: #64748b; + text-transform: uppercase; + letter-spacing: 0.04em; + border-bottom: 1px solid #e2e8f0; + + &--sr { + width: 44px; + } + + &--center { + text-align: center; + } +} + +.soa-td { + padding: 10px 14px; + border-bottom: 1px solid #f1f5f9; + vertical-align: middle; + + &--sr { + text-align: center; + font-size: 12px; + color: #94a3b8; + font-weight: 600; + } + + &--center { + text-align: center; + } + + &--total-label { + font-size: 12px; + font-weight: 600; + color: #475569; + text-align: right; + } +} + +.soa-tr { + transition: background 0.1s; + + &:hover { + background: #f8fafc; + } + + &--mandatory { + background: #fdf4ff; + } + + &--primary { + background: #f0f9ff; + border-left: 3px solid #0891b2; + } +} + +.soa-avatar--primary { + background: #cffafe; + color: #0e7490; +} + +.soa-primary-tag { + font-size: 10px; + color: #0e7490; + font-weight: 600; + background: #cffafe; + padding: 1px 7px; + border-radius: 999px; + display: inline-block; +} + +.soa-pct-input { + width: 56px; + padding: 4px 6px; + border: 1px solid #cbd5e1; + border-radius: 6px; + font-size: 13px; + font-weight: 600; + text-align: center; + color: #1f2937; + + &:focus { + outline: none; + border-color: #0891b2; + box-shadow: 0 0 0 2px rgb(8 145 178 / 15%); + } + + &::-webkit-inner-spin-button, + &::-webkit-outer-spin-button { + opacity: 1; + } +} + +.soa-pct-unit { + font-size: 13px; + font-weight: 600; + color: #64748b; + margin-left: 2px; +} + +.soa-no-action { + color: #cbd5e1; + font-size: 16px; +} + +.soa-tr-total { + background: #f8fafc; +} + +.soa-client-cell { + display: flex; + align-items: center; + gap: 10px; +} + +.soa-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: #dbeafe; + color: #1d4ed8; + font-weight: 700; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + &--mandatory { + background: #ede9fe; + color: #7c3aed; + } +} + +.soa-client-name { + font-weight: 600; + font-size: 13px; + color: #1f2937; + display: block; +} + +.soa-mandatory-tag { + font-size: 10px; + color: #7c3aed; + font-weight: 600; + background: #ede9fe; + padding: 1px 7px; + border-radius: 999px; +} + +.soa-mono { + font-family: 'Roboto Mono', monospace; + font-size: 12px; + color: #64748b; +} + +.soa-pct-badge { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 2px 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 700; + background: #f1f5f9; + color: #475569; + + &--full, + &--ok { + background: #dcfce7; + color: #15803d; + } + + &--warn { + background: #fef3c7; + color: #b45309; + } +} + +.soa-sign-toggle { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + border: 1px solid #e2e8f0; + cursor: pointer; + transition: all 0.15s; + + mat-icon { + font-size: 14px; + height: 14px; + width: 14px; + } + + &--mandatory { + background: #ede9fe; + color: #7c3aed; + border-color: #ddd6fe; + } + + &--optional { + background: #f8fafc; + color: #94a3b8; + } +} + +.soa-mandatory-count { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + font-weight: 600; + color: #7c3aed; + + mat-icon { + font-size: 14px; + height: 14px; + width: 14px; + } +} + +.soa-alert { + display: flex; + align-items: center; + gap: 8px; + margin-top: 10px; + padding: 8px 14px; + border-radius: 8px; + font-size: 13px; + font-weight: 500; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + } + + &--warn { + background: #fef3c7; + color: #b45309; + } + + &--ok { + background: #dcfce7; + color: #15803d; + } +} + +.soa-empty { + display: flex; + align-items: center; + gap: 10px; + padding: 20px; + background: #f8fafc; + border-radius: 8px; + font-size: 13px; + color: #64748b; + + mat-icon { + color: #94a3b8; + font-size: 22px; + height: 22px; + width: 22px; + } +} + +.soa-add-btn { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; +} + +.soa-mode-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; + + @media (width <= 700px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.soa-mode-card { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 12px 8px; + border: 2px solid #e2e8f0; + border-radius: 10px; + background: #fff; + cursor: pointer; + transition: all 0.15s; + + &:hover { + border-color: #a5b4fc; + } + + &--selected { + border-color: #7c3aed; + background: #faf5ff; + } + + &__icon { + font-size: 24px; + height: 24px; + width: 24px; + color: #7c3aed; + } + + &__code { + font-size: 13px; + font-weight: 700; + color: #1f2937; + } + + &__desc { + font-size: 11px; + color: #6b7280; + text-align: center; + } + + &__check { + position: absolute; + top: 6px; + right: 6px; + font-size: 16px; + height: 16px; + width: 16px; + color: #7c3aed; + } +} + +.soa-mode-summary { + display: flex; + align-items: center; + gap: 8px; + margin-top: 12px; + padding: 8px 14px; + border-radius: 8px; + font-size: 13px; + background: #f8fafc; + color: #64748b; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + } + + &--set { + background: #faf5ff; + color: #7c3aed; + } + + &__from { + font-size: 11px; + color: #94a3b8; + } +} + +.soa-auto-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + background: #fef9c3; + color: #854d0e; + border: 1px solid #fde68a; + + mat-icon { + font-size: 13px; + height: 13px; + width: 13px; + } +} + +.soa-minor-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 4px 16px; + margin-bottom: 16px; + + @media (width <= 700px) { + grid-template-columns: 1fr; + } +} + +.soa-field { + width: 100%; +} + +.soa-guardian-search { + margin-bottom: 12px; + + &__label { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + font-weight: 600; + color: #374151; + margin-bottom: 8px; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + color: #7c3aed; + } + } +} + +.soa-guardian-selected { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 8px; + margin-top: 10px; + + &__icon { + font-size: 24px; + height: 24px; + width: 24px; + color: #16a34a; + } + + &__name { + font-size: 13px; + font-weight: 600; + color: #1f2937; + } + + &__cif { + font-size: 11px; + color: #6b7280; + } +} + +.soa-sc-benefits { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 10px; + margin-bottom: 14px; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} + +.soa-sc-benefit { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 8px; + + mat-icon { + color: #16a34a; + font-size: 20px; + height: 20px; + width: 20px; + flex-shrink: 0; + margin-top: 2px; + } + + strong { + display: block; + font-size: 13px; + color: #1f2937; + font-weight: 600; + } + + span { + font-size: 11px; + color: #6b7280; + } +} + +.soa-category-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + margin-bottom: 16px; + + @media (width <= 700px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.soa-cat-tile { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 14px 10px; + border: 2px solid #e2e8f0; + border-radius: 10px; + background: #fff; + cursor: pointer; + transition: all 0.15s; + + &:hover { + border-color: #a5b4fc; + box-shadow: 0 2px 8px rgb(99 102 241 / 12%); + } + + &--selected { + &.soa-cat-tile--male { + border-color: #3b82f6; + background: #eff6ff; + box-shadow: 0 2px 12px rgb(59 130 246 / 20%); + } + + &.soa-cat-tile--female { + border-color: #ec4899; + background: #fdf2f8; + box-shadow: 0 2px 12px rgb(236 72 153 / 20%); + } + } + + &__icon { + font-size: 26px; + height: 26px; + width: 26px; + color: #64748b; + } + + &--selected.soa-cat-tile--male &__icon { + color: #3b82f6; + } + + &--selected.soa-cat-tile--female &__icon { + color: #ec4899; + } + + &__label { + font-size: 11px; + font-weight: 600; + color: #374151; + text-align: center; + line-height: 1.3; + } + + &__check { + position: absolute; + top: 6px; + right: 6px; + font-size: 16px; + height: 16px; + width: 16px; + + .soa-cat-tile--male & { + color: #3b82f6; + } + + .soa-cat-tile--female & { + color: #ec4899; + } + } +} + +.soa-info-banner { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px 14px; + border-radius: 8px; + font-size: 12px; + background: #eff6ff; + border: 1px solid #bfdbfe; + color: #1e40af; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + flex-shrink: 0; + margin-top: 2px; + } + + ul { + margin: 4px 0 0; + padding-left: 18px; + + li { + margin-bottom: 3px; + } + } + + &--minor { + background: #fefce8; + border-color: #fde68a; + color: #92400e; + } + + &--senior { + background: #f0fdf4; + border-color: #bbf7d0; + color: #065f46; + } + + &--male { + background: #eff6ff; + border-color: #bfdbfe; + color: #1e40af; + } + + &--female { + background: #fdf2f8; + border-color: #f9a8d4; + color: #9d174d; + } +} + // ── Account Feature Toggles ──────────────────────────────────────────────── .rad-toggles { display: flex; 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 6c484b099..5f8e0063b 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 @@ -11,14 +11,17 @@ import { Component, OnInit, Input, Output, EventEmitter, inject, ChangeDetectorR import { UntypedFormGroup, UntypedFormBuilder, Validators, FormsModule } from '@angular/forms'; import { SettingsService } from 'app/settings/settings.service'; import { Dates } from 'app/core/utils/dates'; +import { MatDialog } from '@angular/material/dialog'; /** Custom Services */ import { RecurringDepositsService } from '../../recurring-deposits.service'; +import { ClientsService } from 'app/clients/clients.service'; 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'; +import { MatIcon } from '@angular/material/icon'; +import { AddRelatedClientDialogComponent } from 'app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component'; /** * Recurring Deposits Account Details Step @@ -33,8 +36,8 @@ import { IntroducerSearchComponent } from 'app/shared/introducer-search/introduc MatStepperPrevious, FaIconComponent, MatStepperNext, - IntroducerSearchComponent, - FormsModule + FormsModule, + MatIcon ] }) export class RecurringDepositsAccountDetailsStepComponent implements OnInit { @@ -43,6 +46,8 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { private settingsService = inject(SettingsService); private dateUtils = inject(Dates); private cdr = inject(ChangeDetectorRef); + private clientsService = inject(ClientsService); + private dialog = inject(MatDialog); /** Recurring Deposits Account Template */ @Input() recurringDepositsAccountTemplate: any; @@ -61,9 +66,6 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { recurringDepositAccountDetailsForm!: UntypedFormGroup; isProductSelected = false; - introducerId: number | null = null; - introducerName: string | null = null; - introducerCif: string | null = null; /** Recurring Deposits Account Template with product data */ @Output() recurringDepositsAccountProductTemplate = new EventEmitter(); @@ -75,6 +77,145 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { selectedAccountType: string | null = null; selectedCustomerSegment: string | null = null; + // ── Client info (auto-fetched) ───────────────────────────────────────── + clientDob: Date | null = null; + clientAge: number | null = null; + clientGender: string | null = null; + primaryClientName = ''; + primaryClientCif = ''; + primaryClientId: number | null = null; + primaryHolderShare = 0; + + // ── Status of Account (RBI) ──────────────────────────────────────────── + readonly accountStatusOptions = [ + { value: 'INDIVIDUAL_MALE', label: 'Individual (Male)', icon: 'person', color: '#3b82f6' }, + { value: 'INDIVIDUAL_FEMALE', label: 'Individual (Female)', icon: 'person_outline', color: '#ec4899' }, + { value: 'SINGLE', label: 'Single Account', icon: 'account_circle', color: '#0891b2' }, + { value: 'JOINT', label: 'Joint Account', icon: 'group', color: '#8b5cf6' }, + { value: 'MINOR', label: 'Minor Account', icon: 'child_care', color: '#f59e0b' }, + { value: 'SENIOR_CITIZEN', label: 'Senior Citizen', icon: 'elderly', color: '#10b981' } + ]; + + selectedAccountStatus = ''; + + // ── Joint Account ────────────────────────────────────────────────────── + jointHolders: Array<{ + clientId: number; + clientName: string; + clientCif: string; + sharePercentage: number; + isMandatory: boolean; + }> = []; + + modeOfOperation = ''; + + readonly modeOfOperationOptions = [ + { value: 'EITHER_OR_SURVIVOR', label: 'E or S', sublabel: 'Either or Survivor', icon: 'how_to_vote' }, + { value: 'FORMER_OR_SURVIVOR', label: 'F or S', sublabel: 'Former or Survivor', icon: 'person' }, + { value: 'JOINTLY', label: 'Jointly', sublabel: 'All Must Sign', icon: 'groups' }, + { value: 'ANYONE_OR_SURVIVOR', label: 'Anyone', sublabel: 'Anyone or Survivor', icon: 'group' } + ]; + + // ── Individual Male — Special Category ──────────────────────────────── + selectedSpecialCategory = ''; + + readonly specialCategoryOptions = [ + { value: 'DEFENCE', label: 'Defence Personnel', icon: 'military_tech' }, + { value: 'EX_SERVICEMAN', label: 'Ex-Serviceman', icon: 'shield' }, + { value: 'GOVT_EMPLOYEE', label: 'Government Employee', icon: 'account_balance' }, + { value: 'PHYSICALLY_CHALLENGED', label: 'Physically Challenged', icon: 'accessible' }, + { value: 'NRI', label: 'NRI (Non-Resident Indian)', icon: 'flight' }, + { value: 'NONE', label: 'None / General', icon: 'person' } + ]; + + // ── Individual Female — Weaker Section ──────────────────────────────── + selectedWeakerSection = ''; + + readonly weakerSectionOptions = [ + { value: 'BPL', label: 'BPL (Below Poverty Line)', icon: 'volunteer_activism' }, + { value: 'SC_ST', label: 'SC / ST Category', icon: 'groups' }, + { value: 'MINORITY', label: 'Minority Community', icon: 'diversity_3' }, + { value: 'SHG', label: 'Self Help Group Member', icon: 'handshake' }, + { value: 'PMJDY', label: 'PMJDY Beneficiary', icon: 'savings' }, + { value: 'NONE', label: 'None / General', icon: 'person_outline' } + ]; + + // ── Minor Account ────────────────────────────────────────────────────── + guardianClientId: number | null = null; + guardianName = ''; + guardianCif = ''; + guardianRelationship = ''; + minorDob: Date | null = null; + + readonly guardianRelationshipOptions = ['Father', 'Mother', 'Legal Guardian', 'Court Appointed Guardian']; + + // ── Deposit Account Link ─────────────────────────────────────────────── + dalSelectedType: 'SAVINGS' | 'CURRENT' | null = null; + dalAllSavings: any[] = []; + dalAllCurrent: any[] = []; + dalSearch = ''; + dalLoading = false; + selectedLinkedAccount: any = null; + + get dalAccounts(): any[] { + return this.dalSelectedType === 'SAVINGS' ? this.dalAllSavings : this.dalAllCurrent; + } + + get dalFilteredAccounts(): any[] { + const q = this.dalSearch.toLowerCase().trim(); + if (!q) return this.dalAccounts; + return this.dalAccounts.filter( + (a) => a.accountNo.toLowerCase().includes(q) || a.productName.toLowerCase().includes(q) + ); + } + + selectDalType(type: 'SAVINGS' | 'CURRENT'): void { + this.dalSelectedType = type; + this.dalSearch = ''; + this.selectedLinkedAccount = null; + } + + selectLinkedAccount(account: any): void { + this.selectedLinkedAccount = account; + } + + clearLinkedAccount(): void { + this.selectedLinkedAccount = null; + this.dalSelectedType = null; + this.dalSearch = ''; + } + + private loadClientAccounts(): void { + const clientId = String(this.recurringDepositsAccountTemplate?.clientId); + if (!clientId) return; + this.dalLoading = true; + this.clientsService.getClientAccountData(clientId).subscribe({ + next: (data: any) => { + this.dalAllSavings = (data?.savingsAccounts ?? []).map((a: any) => ({ + id: a.id, + accountNo: a.accountNo, + productName: a.productName ?? '', + accountType: 'SAVINGS' as const, + status: a.status?.value ?? '', + balance: a.accountBalance ?? 0, + currencyCode: a.currency?.code ?? '' + })); + this.dalAllCurrent = (data?.currentAccounts ?? []).map((a: any) => ({ + id: a.id, + accountNo: a.accountNo, + productName: a.productName ?? '', + accountType: 'CURRENT' as const, + status: a.status?.value ?? '', + balance: a.accountBalance ?? 0, + currencyCode: a.currency?.code ?? '' + })); + this.dalLoading = false; + this.cdr.detectChanges(); + }, + error: () => { this.dalLoading = false; } + }); + } + constructor() { this.createRecurringDepositsAccountDetailsForm(); } @@ -85,7 +226,6 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { if (this.recurringDepositsAccountTemplate) { this.productData = this.recurringDepositsAccountTemplate.productOptions; if (this.recurringDepositsAccountTemplate.depositProductId) { - // Edit mode: patch from saved data this.recurringDepositAccountDetailsForm.patchValue({ productId: this.recurringDepositsAccountTemplate.depositProductId, submittedOnDate: @@ -94,12 +234,84 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { externalId: this.recurringDepositsAccountTemplate.externalId }); } else { - // New account: default submittedOnDate to today's business date this.recurringDepositAccountDetailsForm.patchValue({ submittedOnDate: this.settingsService.businessDate }); } } + + // Auto-detect account status from client DOB + capture primary holder info + const clientId = this.recurringDepositsAccountTemplate?.clientId; + if (clientId) { + this.primaryClientId = clientId; + this.clientsService.getClientData(String(clientId)).subscribe((client: any) => { + this.primaryClientName = client?.displayName ?? client?.fullname ?? ''; + this.primaryClientCif = client?.cifNumber ?? client?.cifNo ?? ''; + if (client?.dateOfBirth) { + const dob = this.parseClientDob(client.dateOfBirth); + if (dob) { + this.clientDob = dob; + this.clientAge = this.calculateAge(dob); + this.clientGender = client.gender?.name?.toLowerCase() ?? null; + this.autoDetectAccountStatus(); + } + } + this.cdr.detectChanges(); + }); + } + } + + /** Parse DOB from backend — handles array [yyyy, mm, dd] or string */ + private parseClientDob(dob: any): Date | null { + if (!dob) return null; + if (Array.isArray(dob) && dob.length >= 3) { + return new Date(dob[0], dob[1] - 1, dob[2]); + } + const d = new Date(dob); + return isNaN(d.getTime()) ? null : d; + } + + /** Calculate full years from a date to today */ + private calculateAge(dob: Date): number { + const today = this.settingsService.businessDate ?? new Date(); + let age = today.getFullYear() - dob.getFullYear(); + const monthDiff = today.getMonth() - dob.getMonth(); + if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dob.getDate())) { + age--; + } + return age; + } + + isTileDisabled(value: string): boolean { + if (value === 'MINOR') return this.clientAge === null || this.clientAge >= 18; + if (value === 'SENIOR_CITIZEN') return this.clientAge === null || this.clientAge < 60; + return false; + } + + getTileTooltip(value: string): string { + if (value === 'MINOR' && this.clientAge !== null && this.clientAge >= 18) { + return `Age ${this.clientAge} yrs — Minor only allowed below 18 years`; + } + if (value === 'SENIOR_CITIZEN' && this.clientAge !== null && this.clientAge < 60) { + return `Age ${this.clientAge} yrs — Senior Citizen only allowed at 60+ years`; + } + if (value === 'MINOR' && this.clientAge === null) return 'DOB not available — cannot verify age'; + if (value === 'SENIOR_CITIZEN' && this.clientAge === null) return 'DOB not available — cannot verify age'; + return ''; + } + + private autoDetectAccountStatus(): void { + if (this.clientAge === null) return; + if (this.clientAge < 18) { + this.selectedAccountStatus = 'MINOR'; + this.minorDob = this.clientDob; + } else if (this.clientAge >= 60) { + this.selectedAccountStatus = 'SENIOR_CITIZEN'; + } else if (this.clientGender === 'female') { + this.selectedAccountStatus = 'INDIVIDUAL_FEMALE'; + } else { + this.selectedAccountStatus = 'INDIVIDUAL_MALE'; + } } /** @@ -107,14 +319,8 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { */ createRecurringDepositsAccountDetailsForm() { this.recurringDepositAccountDetailsForm = this.formBuilder.group({ - productId: [ - '', - Validators.required - ], - submittedOnDate: [ - '', - Validators.required - ], + productId: ['', Validators.required], + submittedOnDate: ['', Validators.required], fieldOfficerId: [''], externalId: [''], balanceSwapOption: [false], @@ -132,9 +338,7 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { this.recurringDepositsService .getRecurringDepositsAccountTemplate(clientId, productId) .subscribe((response: any) => { - // Ensure currency is included in the product template if (!response.currency) { - // Try to get currency from the selected product const selectedProduct = this.recurringDepositsAccountTemplate.productOptions?.find( (product: any) => product.id == productId ); @@ -152,7 +356,9 @@ 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) + if (response.shareLinkingEnabled) { + this.loadClientAccounts(); + } this.recurringDepositAccountDetailsForm.patchValue({ balanceSwapOption: !!response.balanceSwapOption, msme: !!response.msme, @@ -171,17 +377,10 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { }); } - /** - * Returns true if a value is not null, undefined, or empty string. - */ hasValue(val: any): boolean { return val !== null && val !== undefined && val !== ''; } - /** - * Formats template dates into the current CBS date format. - * Handles backend date arrays like [2026, 4, 20] as well as strings and Date objects. - */ private extractOptions(options: any[], fallback?: string): string[] { if (Array.isArray(options) && options.length > 0) { return options.map((o: any) => o.value ?? o.name ?? o); @@ -190,12 +389,8 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { } formatTemplateDate(value: any): string { - if (!value) { - return ''; - } - + if (!value) return ''; let dateValue: Date | null = null; - if (value instanceof Date) { dateValue = value; } else if (Array.isArray(value) && value.length >= 3) { @@ -203,30 +398,133 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { } else { dateValue = new Date(value); } - if (!dateValue || Number.isNaN(dateValue.getTime())) { return Array.isArray(value) ? value.join('/') : String(value); } - return this.dateUtils.formatDate(dateValue, 'M/d/yyyy') || ''; } + onGuardianChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void { + this.guardianClientId = event?.clientId ?? null; + this.guardianName = event?.displayName ?? ''; + this.guardianCif = event?.cifNumber ?? ''; + } + + // ── Joint Account helpers ────────────────────────────────────────────── + openAddJointHolderDialog(): void { + const ref = this.dialog.open(AddRelatedClientDialogComponent, { + width: '500px', + panelClass: 'arc-dialog-panel', + disableClose: true + }); + ref.afterClosed().subscribe((result) => { + if (result) { + const duplicate = this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif); + if (duplicate) { + alert(`Account holder with CIF ${result.clientCif} is already added.`); + return; + } + this.jointHolders = [...this.jointHolders, { ...result, isMandatory: false }]; + this.autoSetModeOfOperation(); + } + }); + } + + removeJointHolder(index: number): void { + this.jointHolders = this.jointHolders.filter((_, i) => i !== index); + this.autoSetModeOfOperation(); + } + + toggleJointHolderMandatory(holder: any): void { + holder.isMandatory = !holder.isMandatory; + this.autoSetModeOfOperation(); + } + + autoSetModeOfOperation(): void { + const mandatory = this.jointHolders.filter((h) => h.isMandatory).length; + if (mandatory === 0) { + this.modeOfOperation = 'EITHER_OR_SURVIVOR'; + } else if (mandatory === 1) { + this.modeOfOperation = 'FORMER_OR_SURVIVOR'; + } else { + this.modeOfOperation = 'JOINTLY'; + } + } + + get totalJointSharePercentage(): number { + return (this.primaryHolderShare || 0) + this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0); + } + + get mandatoryJointCount(): number { + return this.jointHolders.filter((h) => h.isMandatory).length; + } + + get selectedModeLabel(): string { + return this.modeOfOperationOptions.find((o) => o.value === this.modeOfOperation)?.sublabel ?? ''; + } + + get majorityDate(): string { + const dob = this.minorDob ?? this.clientDob; + if (!dob) return ''; + const d = new Date(dob); + d.setFullYear(d.getFullYear() + 18); + return this.dateUtils.formatDate(d, 'M/d/yyyy') || ''; + } + /** * Returns recurring deposits account details form value. */ - 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 recurringDepositAccountDetails() { const value = this.recurringDepositAccountDetailsForm.value; - if (this.introducerId) { - value.introducerId = this.introducerId; - value.introducerName = this.introducerName; - value.introducerCif = this.introducerCif; + + if (this.selectedAccountStatus) { + value.accountStatus = this.selectedAccountStatus; } + + if (this.selectedAccountStatus === 'JOINT') { + value.modeOfOperation = this.modeOfOperation || null; + value.jointHolders = [ + { + clientId: this.primaryClientId, + clientName: this.primaryClientName, + clientCif: this.primaryClientCif, + sharePercentage: this.primaryHolderShare, + isMandatory: true, + isPrimary: true + }, + ...this.jointHolders.map((h) => ({ + clientId: h.clientId, + clientName: h.clientName, + clientCif: h.clientCif, + sharePercentage: h.sharePercentage, + isMandatory: h.isMandatory, + isPrimary: false + })) + ]; + } + + if (this.selectedAccountStatus === 'INDIVIDUAL_MALE' && this.selectedSpecialCategory) { + value.specialCategory = this.selectedSpecialCategory; + } + + if (this.selectedAccountStatus === 'INDIVIDUAL_FEMALE' && this.selectedWeakerSection) { + value.weakerSection = this.selectedWeakerSection; + } + + if (this.selectedAccountStatus === 'MINOR') { + value.guardianClientId = this.guardianClientId; + value.guardianName = this.guardianName; + value.guardianCif = this.guardianCif; + value.guardianRelationship = this.guardianRelationship; + value.minorDob = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'yyyy-MM-dd') : null; + } + + if (this.selectedLinkedAccount) { + value.linkedAccountId = this.selectedLinkedAccount.id; + value.linkedAccountType = this.selectedLinkedAccount.accountType; + value.linkedAccountNo = this.selectedLinkedAccount.accountNo; + } + return value; } } 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 60ca14d64..d3699f0a1 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 @@ -112,7 +112,7 @@ recurringDepositsAccountProductTemplate?.shareLinkingEnabled !== null ) {
- Share Linking Enabled + Deposit Account Link
- Share Linking Enabled + Deposit Account Link
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 c32f8fbd1..a946f557e 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 @@ -87,7 +87,7 @@ @if (recurringDepositProduct.shareLinkingEnabled !== undefined) {
- Share Linking Enabled: + Deposit Account Link: {{ recurringDepositProduct.shareLinkingEnabled | yesNo }}
} 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 f673989be..1105b7f68 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 @@ -77,7 +77,7 @@ {{ recurringDepositProduct.introducerRequired | yesNo }}
- Share Linking Enabled: + Deposit Account Link: {{ recurringDepositProduct.shareLinkingEnabled | yesNo }}
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 a84dd5dcb..8cefe0623 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 @@ -113,7 +113,7 @@ export class CreateSavingsAccountComponent { const locale = this.settingsService.language.code; const dateFormat = this.settingsService.dateFormat; const monthDayFormat = 'dd MMMM'; - const savingsAccount = { + const savingsAccount: any = { ...this.savingsAccount, charges: [], submittedOnDate: this.dateUtils.formatDate(this.savingsAccount.submittedOnDate, dateFormat), @@ -121,6 +121,17 @@ export class CreateSavingsAccountComponent { monthDayFormat, locale }; + + // Digital Facilities + if (this.savingsAccountDigital?.accountFacilityConfig) { + savingsAccount.accountFacilityConfig = this.savingsAccountDigital.accountFacilityConfig; + } + + // Nominee + if (this.savingsAccountNominee) { + savingsAccount.nominee = this.savingsAccountNominee; + } + if (this.savingsAccountTemplate.clientId) { savingsAccount.clientId = this.savingsAccountTemplate.clientId; } else { 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 8bf0e988a..1fd029dc5 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 @@ -135,4 +135,561 @@ } } + + + @if (savingsProductSelected) { +
+
+
+
+ +
+
+

Status of Account

+ As per RBI guidelines — select account holder type +
+
+ RBI Compliant +
+ + + @if (clientDob) { +
+
+ cake + Date of Birth: {{ formatTemplateDate(clientDob) }} +
+
+ person_pin + Age: {{ clientAge }} years +
+ @if (clientAge !== null && clientAge < 18) { + + child_care Auto-detected: Minor + + } @else if (clientAge !== null && clientAge >= 60) { + + elderly Auto-detected: Senior Citizen + + } @else { + check_circle Age Verified + } +
+ } + + +
+ @for (opt of accountStatusOptions; track opt.value) { + + } +
+ + + @if (selectedAccountStatus === 'JOINT') { +
+
+
+ A +
+ Joint Account Holders + Add all co-applicants linked to this joint savings account +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + @for (h of jointHolders; track h; let i = $index) { + + + + + + + + + + } + + + + + + + + + + +
Sr.Holder NameCIF No.Ownership %Signing AuthorityRoleAction
1 +
+
+ {{ primaryClientName ? primaryClientName[0].toUpperCase() : '?' }} +
+
+ {{ primaryClientName || 'Primary Holder' }} + Primary Account Holder +
+
+
+ {{ primaryClientCif || '—' }} + + + % + + + lock Mandatory + + Primary Account Holder + — +
{{ i + 2 }} +
+
+ {{ h.clientName[0].toUpperCase() }} +
+
+ {{ h.clientName }} + @if (h.isMandatory) { + Mandatory Signatory + } +
+
+
+ {{ h.clientCif || '—' }} + + + {{ h.sharePercentage }}% + + + + Co-Holder + +
Total Ownership + + {{ totalJointSharePercentage }}% + + + + lock {{ mandatoryJointCount + 1 }} Mandatory + +
+
+ + @if (jointHolders.length === 0) { +
+ group_add + Click Add Holder to add joint account holders. +
+ } + + @if (totalJointSharePercentage !== 100) { +
+ warning_amber + Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%. +
+ } @else { +
+ check_circle + Ownership allocation is complete (100%). +
+ } +
+ + +
+
+
+ B +
+ Mode of Operation + Auto-set from mandatory holders — override manually if required +
+
+ @if (modeOfOperation) { + auto_awesome Auto + } +
+ + @if (jointHolders.length === 0) { +
+ how_to_vote + Add joint holders in Section A first. +
+ } @else { +
+ @for (opt of modeOfOperationOptions; track opt.value) { + + } +
+ +
+ @if (modeOfOperation) { + how_to_vote + Mode of Operation: {{ selectedModeLabel }} + + ({{ mandatoryJointCount }} Mandatory + {{ jointHolders.length - mandatoryJointCount }} Optional) + + } @else { + info + Set Mandatory/Optional for holders above to auto-select mode. + } +
+ } +
+ } + + + @if (selectedAccountStatus === 'MINOR') { +
+
+
+ + child_care + +
+ Minor Account Details + As per RBI — guardian must be an existing registered client +
+
+ Guardian Required +
+ +
+ + Minor's Date of Birth + + + + @if (clientDob) { + Auto-filled from client profile + } + + + + Age + + Must be below 18 years + + + + Date of Majority (Auto) + + Auto-calculated: DOB + 18 years + + + + Guardian Relationship + + @for (rel of guardianRelationshipOptions; track rel) { + {{ rel }} + } + + +
+ + + +
+ info +
    +
  • Minor below 10 years — Guardian must operate the account.
  • +
  • Minor above 10 years — Can operate independently (bank discretion).
  • +
  • Account must be converted to regular savings on attaining majority.
  • +
  • Fresh KYC of the minor must be done on majority.
  • +
+
+
+ } + + + @if (selectedAccountStatus === 'SENIOR_CITIZEN') { +
+
+
+ + elderly + +
+ Senior Citizen Account + Age ≥ 60 years — eligible for preferential banking services +
+
+ Age ≥ 60 +
+ + +
+ + Date of Birth + + From client profile + + + + Age + + Age ≥ 60 required for Senior Citizen + +
+ +
+
+ priority_high +
+ Priority Service + Dedicated counters / assisted banking at branches +
+
+
+ percent +
+ Preferential Interest Rate + Additional 0.25%–0.50% on deposits (as per product) +
+
+
+ health_and_safety +
+ Doorstep Banking + Eligible for home banking services (RBI circular) +
+
+
+ gavel +
+ Nomination Mandatory + RBI mandate — ensure nomination is registered +
+
+
+ +
+ info +
    +
  • Age proof (DOB document) mandatory at time of account opening.
  • +
  • Nomination registration is compulsory (Form DA-1).
  • +
  • Bank must offer dedicated helpline and priority grievance redressal.
  • +
  • Door-step banking services must be offered (RBI Master Direction 2023).
  • +
+
+
+ } + + + @if (selectedAccountStatus === 'INDIVIDUAL_MALE') { +
+
+
+ + person + +
+ Individual Account (Male) + Select special category if applicable — governs applicable schemes & benefits +
+
+ + Special Category + +
+ +
+ @for (opt of specialCategoryOptions; track opt.value) { + + } +
+ +
+ info +
    +
  • Valid KYC documents required (Aadhaar / PAN / Passport).
  • +
  • Nomination registration is mandatory (Form DA-1).
  • +
  • Defence / Ex-Serviceman — eligible for special savings schemes (DSSP).
  • +
  • NRI — FCNR / NRE / NRO account rules apply as per FEMA guidelines.
  • +
+
+
+ } + + + @if (selectedAccountStatus === 'INDIVIDUAL_FEMALE') { +
+
+
+ + person_outline + +
+ Individual Account (Female) + Select weaker section category if applicable — governs PMJDY & priority schemes +
+
+ + Weaker Section + +
+ +
+ @for (opt of weakerSectionOptions; track opt.value) { + + } +
+ +
+ info +
    +
  • Valid KYC documents required (Aadhaar / PAN / Passport).
  • +
  • Nomination registration is mandatory (Form DA-1).
  • +
  • BPL / SC-ST / Minority — eligible for zero-balance accounts (RBI Priority Sector norms).
  • +
  • PMJDY beneficiary — ₹10,000 overdraft facility & accident insurance cover applicable.
  • +
  • SHG members — group linkage lending & special deposit rates applicable.
  • +
+
+
+ } +
+ } 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 5744c0823..ff42ba281 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 @@ -103,3 +103,1046 @@ $gray-900: #202124; border-color: #bbf7d0; } } + +// ── Status of Account card ──────────────────────────────────────────── + +.soa-card { + margin-top: 16px; + border: 1px solid #e2e8f0; + border-radius: 10px; + background: #fff; + overflow: visible; + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 24px; + background: #f8f9fa; + border-bottom: 1px solid #e8eaed; + border-radius: 10px 10px 0 0; + } + + &__header-left { + display: flex; + align-items: center; + gap: 12px; + } + + &__icon { + width: 36px; + height: 36px; + border-radius: 8px; + background: #ede9fe; + display: flex; + align-items: center; + justify-content: center; + + i { + font-size: 16px; + color: #7c3aed; + } + } + + &__title { + margin: 0; + font-size: 14px; + font-weight: 600; + color: #202124; + } + + &__sub { + font-size: 11px; + color: #6b7280; + display: block; + } +} + +.soa-pill { + display: inline-flex; + align-items: center; + padding: 3px 12px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + background: #ede9fe; + color: #7c3aed; + border: 1px solid #ddd6fe; + + &--minor { + background: #fef3c7; + color: #b45309; + border-color: #fde68a; + } + + &--senior { + background: #d1fae5; + color: #065f46; + border-color: #a7f3d0; + } + + &--ok { + background: #dcfce7; + color: #15803d; + border-color: #bbf7d0; + } +} + +// Client info bar (auto-detected DOB + Age) +.soa-client-info-bar { + display: flex; + align-items: center; + gap: 20px; + padding: 10px 24px; + background: #f0f9ff; + border-bottom: 1px solid #bae6fd; + flex-wrap: wrap; + + &__item { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + color: #0369a1; + + mat-icon { + font-size: 16px; + height: 16px; + width: 16px; + } + + strong { + font-weight: 700; + } + } +} + +.soa-auto-pill { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + margin-left: auto; + + mat-icon { + font-size: 13px; + height: 13px; + width: 13px; + } + + &--minor { + background: #fef3c7; + color: #b45309; + border: 1px solid #fde68a; + } + + &--senior { + background: #d1fae5; + color: #065f46; + border: 1px solid #a7f3d0; + } + + &--ok { + background: #dcfce7; + color: #15803d; + border: 1px solid #bbf7d0; + } +} + +// Status tiles +.soa-tiles { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 12px; + padding: 16px 24px; + border-bottom: 1px solid #f1f5f9; + + @media (width <= 900px) { + grid-template-columns: repeat(3, 1fr); + } + + @media (width <= 600px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.soa-tile { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 14px 10px; + border: 2px solid #e2e8f0; + border-radius: 10px; + background: #fff; + cursor: pointer; + transition: + border-color 0.15s, + box-shadow 0.15s; + position: relative; + + &:hover { + border-color: #a5b4fc; + box-shadow: 0 2px 8px rgb(99 102 241 / 12%); + } + + &--selected { + border-color: #6366f1; + background: #eef2ff; + box-shadow: 0 2px 12px rgb(99 102 241 / 20%); + } + + &__icon { + font-size: 28px; + height: 28px; + width: 28px; + } + + &__label { + font-size: 12px; + font-weight: 600; + color: #374151; + text-align: center; + } + + &__check { + position: absolute; + top: 6px; + right: 6px; + font-size: 16px; + height: 16px; + width: 16px; + color: #6366f1; + } + + &__lock { + position: absolute; + top: 4px; + right: 4px; + display: flex; + align-items: center; + + mat-icon { + font-size: 13px; + height: 13px; + width: 13px; + color: #94a3b8; + } + } + + &--disabled { + opacity: 0.45; + cursor: not-allowed; + background: #f8fafc; + border-color: #e2e8f0; + box-shadow: none; + + &:hover { + border-color: #e2e8f0; + box-shadow: none; + } + } +} + +// Block inside SOA card +.soa-block { + padding: 16px 24px; + + &--sep { + border-top: 1px dashed #e2e8f0; + } + + &__head { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: 14px; + } + + &__head-left { + display: flex; + align-items: flex-start; + gap: 12px; + } + + &__letter { + width: 28px; + height: 28px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; + font-weight: 700; + color: #fff; + flex-shrink: 0; + margin-top: 2px; + } + + &__title { + font-size: 13px; + font-weight: 600; + color: #1f2937; + display: block; + } + + &__hint { + font-size: 11px; + color: #6b7280; + display: block; + } +} + +// Joint holders table +.soa-table-wrap { + overflow-x: auto; + border-radius: 8px; + border: 1px solid #e2e8f0; +} + +.soa-tbl { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +.soa-th { + padding: 10px 14px; + background: #f8fafc; + text-align: left; + font-weight: 600; + font-size: 11px; + color: #64748b; + text-transform: uppercase; + letter-spacing: 0.04em; + border-bottom: 1px solid #e2e8f0; + + &--sr { + width: 44px; + } + + &--center { + text-align: center; + } +} + +.soa-td { + padding: 10px 14px; + border-bottom: 1px solid #f1f5f9; + vertical-align: middle; + + &--sr { + text-align: center; + font-size: 12px; + color: #94a3b8; + font-weight: 600; + } + + &--center { + text-align: center; + } + + &--total-label { + font-size: 12px; + font-weight: 600; + color: #475569; + text-align: right; + } +} + +.soa-tr { + transition: background 0.1s; + + &:hover { + background: #f8fafc; + } + + &--mandatory { + background: #fdf4ff; + } + + &--primary { + background: #f0f9ff; + border-left: 3px solid #0891b2; + } +} + +.soa-avatar--primary { + background: #cffafe; + color: #0e7490; +} + +.soa-primary-tag { + font-size: 10px; + color: #0e7490; + font-weight: 600; + background: #cffafe; + padding: 1px 7px; + border-radius: 999px; + display: inline-block; +} + +.soa-pct-input { + width: 56px; + padding: 4px 6px; + border: 1px solid #cbd5e1; + border-radius: 6px; + font-size: 13px; + font-weight: 600; + text-align: center; + color: #1f2937; + + &:focus { + outline: none; + border-color: #0891b2; + box-shadow: 0 0 0 2px rgb(8 145 178 / 15%); + } + + &::-webkit-inner-spin-button, + &::-webkit-outer-spin-button { + opacity: 1; + } +} + +.soa-pct-unit { + font-size: 13px; + font-weight: 600; + color: #64748b; + margin-left: 2px; +} + +.soa-no-action { + color: #cbd5e1; + font-size: 16px; +} + +.soa-tr-total { + background: #f8fafc; +} + +.soa-client-cell { + display: flex; + align-items: center; + gap: 10px; +} + +.soa-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: #dbeafe; + color: #1d4ed8; + font-weight: 700; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + &--mandatory { + background: #ede9fe; + color: #7c3aed; + } +} + +.soa-client-name { + font-weight: 600; + font-size: 13px; + color: #1f2937; + display: block; +} + +.soa-mandatory-tag { + font-size: 10px; + color: #7c3aed; + font-weight: 600; + background: #ede9fe; + padding: 1px 7px; + border-radius: 999px; +} + +.soa-mono { + font-family: 'Roboto Mono', monospace; + font-size: 12px; + color: #64748b; +} + +.soa-pct-badge { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 2px 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 700; + background: #f1f5f9; + color: #475569; + + &--full, + &--ok { + background: #dcfce7; + color: #15803d; + } + + &--warn { + background: #fef3c7; + color: #b45309; + } +} + +.soa-sign-toggle { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + border: 1px solid #e2e8f0; + cursor: pointer; + transition: all 0.15s; + + mat-icon { + font-size: 14px; + height: 14px; + width: 14px; + } + + &--mandatory { + background: #ede9fe; + color: #7c3aed; + border-color: #ddd6fe; + } + + &--optional { + background: #f8fafc; + color: #94a3b8; + } +} + +.soa-mandatory-count { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + font-weight: 600; + color: #7c3aed; + + mat-icon { + font-size: 14px; + height: 14px; + width: 14px; + } +} + +.soa-alert { + display: flex; + align-items: center; + gap: 8px; + margin-top: 10px; + padding: 8px 14px; + border-radius: 8px; + font-size: 13px; + font-weight: 500; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + } + + &--warn { + background: #fef3c7; + color: #b45309; + } + + &--ok { + background: #dcfce7; + color: #15803d; + } +} + +.soa-empty { + display: flex; + align-items: center; + gap: 10px; + padding: 20px; + background: #f8fafc; + border-radius: 8px; + font-size: 13px; + color: #64748b; + + mat-icon { + color: #94a3b8; + font-size: 22px; + height: 22px; + width: 22px; + } +} + +.soa-add-btn { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; +} + +// Mode of operation grid +.soa-mode-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; + + @media (width <= 700px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.soa-mode-card { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 12px 8px; + border: 2px solid #e2e8f0; + border-radius: 10px; + background: #fff; + cursor: pointer; + transition: all 0.15s; + + &:hover { + border-color: #a5b4fc; + } + + &--selected { + border-color: #7c3aed; + background: #faf5ff; + } + + &__icon { + font-size: 24px; + height: 24px; + width: 24px; + color: #7c3aed; + } + + &__code { + font-size: 13px; + font-weight: 700; + color: #1f2937; + } + + &__desc { + font-size: 11px; + color: #6b7280; + text-align: center; + } + + &__check { + position: absolute; + top: 6px; + right: 6px; + font-size: 16px; + height: 16px; + width: 16px; + color: #7c3aed; + } +} + +.soa-mode-summary { + display: flex; + align-items: center; + gap: 8px; + margin-top: 12px; + padding: 8px 14px; + border-radius: 8px; + font-size: 13px; + background: #f8fafc; + color: #64748b; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + } + + &--set { + background: #faf5ff; + color: #7c3aed; + } + + &__from { + font-size: 11px; + color: #94a3b8; + } +} + +.soa-auto-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + background: #fef9c3; + color: #854d0e; + border: 1px solid #fde68a; + + mat-icon { + font-size: 13px; + height: 13px; + width: 13px; + } +} + +// Minor account +.soa-minor-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 4px 16px; + margin-bottom: 16px; + + @media (width <= 700px) { + grid-template-columns: 1fr; + } +} + +.soa-field { + width: 100%; +} + +.soa-guardian-search { + margin-bottom: 12px; + + &__label { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + font-weight: 600; + color: #374151; + margin-bottom: 8px; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + color: #7c3aed; + } + } +} + +.soa-guardian-selected { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 8px; + margin-top: 10px; + + &__icon { + font-size: 24px; + height: 24px; + width: 24px; + color: #16a34a; + } + + &__name { + font-size: 13px; + font-weight: 600; + color: #1f2937; + } + + &__cif { + font-size: 11px; + color: #6b7280; + } +} + +// Senior citizen benefits +.soa-sc-benefits { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 10px; + margin-bottom: 14px; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} + +.soa-sc-benefit { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 8px; + + mat-icon { + color: #16a34a; + font-size: 20px; + height: 20px; + width: 20px; + flex-shrink: 0; + margin-top: 2px; + } + + strong { + display: block; + font-size: 13px; + color: #1f2937; + font-weight: 600; + } + + span { + font-size: 11px; + color: #6b7280; + } +} + +// Individual Male / Female — Category grid +.soa-category-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + margin-bottom: 16px; + + @media (width <= 700px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.soa-cat-tile { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 14px 10px; + border: 2px solid #e2e8f0; + border-radius: 10px; + background: #fff; + cursor: pointer; + transition: all 0.15s; + + &:hover { + border-color: #a5b4fc; + box-shadow: 0 2px 8px rgb(99 102 241 / 12%); + } + + &--selected { + &.soa-cat-tile--male { + border-color: #3b82f6; + background: #eff6ff; + box-shadow: 0 2px 12px rgb(59 130 246 / 20%); + } + + &.soa-cat-tile--female { + border-color: #ec4899; + background: #fdf2f8; + box-shadow: 0 2px 12px rgb(236 72 153 / 20%); + } + } + + &__icon { + font-size: 26px; + height: 26px; + width: 26px; + color: #64748b; + } + + &--selected.soa-cat-tile--male &__icon { + color: #3b82f6; + } + + &--selected.soa-cat-tile--female &__icon { + color: #ec4899; + } + + &__label { + font-size: 11px; + font-weight: 600; + color: #374151; + text-align: center; + line-height: 1.3; + } + + &__check { + position: absolute; + top: 6px; + right: 6px; + font-size: 16px; + height: 16px; + width: 16px; + + .soa-cat-tile--male & { + color: #3b82f6; + } + + .soa-cat-tile--female & { + color: #ec4899; + } + } +} + +// Single Account — Nominee section +.soa-nominee-section { + margin-bottom: 16px; + + &__header { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + font-weight: 600; + color: #0e7490; + margin-bottom: 12px; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + } + } +} + +.soa-field--full { + grid-column: 1 / -1; +} + +// PoA section +.soa-poa-section { + margin-bottom: 14px; + padding: 12px 14px; + background: #faf5ff; + border: 1px dashed #ddd6fe; + border-radius: 8px; + + &__header { + display: flex; + align-items: center; + gap: 10px; + font-size: 13px; + font-weight: 600; + color: #6d28d9; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + } + } +} + +// Toggle switch +.soa-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + flex-shrink: 0; + + input { + opacity: 0; + width: 0; + height: 0; + } + + &__slider { + position: absolute; + cursor: pointer; + inset: 0; + background: #cbd5e1; + border-radius: 22px; + transition: background 0.2s; + + &::before { + content: ''; + position: absolute; + height: 16px; + width: 16px; + left: 3px; + bottom: 3px; + background: #fff; + border-radius: 50%; + transition: transform 0.2s; + } + } + + input:checked + &__slider { + background: #7c3aed; + + &::before { + transform: translateX(18px); + } + } + + &__label { + font-size: 12px; + color: #6b7280; + font-weight: 500; + } +} + +// Info banners +.soa-info-banner { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px 14px; + border-radius: 8px; + font-size: 12px; + background: #eff6ff; + border: 1px solid #bfdbfe; + color: #1e40af; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + flex-shrink: 0; + margin-top: 2px; + } + + ul { + margin: 4px 0 0; + padding-left: 18px; + + li { + margin-bottom: 3px; + } + } + + &--minor { + background: #fefce8; + border-color: #fde68a; + color: #92400e; + } + + &--senior { + background: #f0fdf4; + border-color: #bbf7d0; + color: #065f46; + } + + &--male { + background: #eff6ff; + border-color: #bfdbfe; + color: #1e40af; + } + + &--female { + background: #fdf2f8; + border-color: #f9a8d4; + color: #9d174d; + } +} 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 f4af5679d..d9d9558a3 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 @@ -1,4 +1,4 @@ -/** +/** * Copyright since 2025 Mifos Initiative * * This Source Code Form is subject to the terms of the Mozilla Public @@ -11,13 +11,17 @@ import { Component, OnInit, Input, Output, EventEmitter, inject, ChangeDetectorR import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms'; import { Dates } from 'app/core/utils/dates'; import { SettingsService } from 'app/settings/settings.service'; +import { MatDialog } from '@angular/material/dialog'; /** Custom Services */ import { SavingsService } from 'app/savings/savings.service'; +import { ClientsService } from 'app/clients/clients.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'; +import { MatIcon } from '@angular/material/icon'; +import { AddRelatedClientDialogComponent } from 'app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component'; /** * Savings Account Details Step @@ -32,30 +36,27 @@ import { IntroducerSearchComponent } from 'app/shared/introducer-search/introduc FaIconComponent, MatStepperNext, IntroducerSearchComponent, - FormsModule + FormsModule, + MatIcon ] }) export class SavingsAccountDetailsStepComponent implements OnInit { private formBuilder = inject(UntypedFormBuilder); private savingsService = inject(SavingsService); + private clientsService = inject(ClientsService); private settingsService = inject(SettingsService); private dateUtils = inject(Dates); private cdr = inject(ChangeDetectorRef); + private dialog = inject(MatDialog); /** Savings Account Template */ @Input() savingsAccountTemplate: any; - /** Minimum date allowed. */ minDate = new Date(2000, 0, 1); - /** Maximum date allowed. */ maxDate = new Date(); - /** Product Data */ productData: any; - /** Field Officer Data */ fieldOfficerData: any; - /** For edit savings form */ isFieldOfficerPatched = false; - /** Savings Account Details Form */ savingsAccountDetailsForm: UntypedFormGroup; savingsProductSelected = false; @@ -63,22 +64,93 @@ export class SavingsAccountDetailsStepComponent implements OnInit { introducerName: string | null = null; introducerCif: string | null = null; - /** Savings Account Template with product data */ @Output() savingsAccountProductTemplate = new EventEmitter(); - /** Local copy of product template for displaying classification info */ productTemplate: any = null; accountTypeOptions: string[] = []; customerSegmentOptions: string[] = []; selectedAccountType: string | null = null; selectedCustomerSegment: string | null = null; - /** - * Sets share account details form. - * @param {FormBuilder} formBuilder Form Builder. - * @param {SavingsService} savingsService Savings Service. - * @param {SettingsService} settingsService Setting service - */ + // ── Client info (auto-fetched) ───────────────────────────────────────── + clientDob: Date | null = null; + clientAge: number | null = null; + clientGender: string | null = null; + primaryClientName = ''; + primaryClientCif = ''; + primaryClientId: number | null = null; + primaryHolderShare = 0; + + // ── Status of Account (RBI) ──────────────────────────────────────────── + readonly accountStatusOptions = [ + { value: 'INDIVIDUAL_MALE', label: 'Individual (Male)', icon: 'person', color: '#3b82f6' }, + { value: 'INDIVIDUAL_FEMALE', label: 'Individual (Female)', icon: 'person_outline', color: '#ec4899' }, + { value: 'SINGLE', label: 'Single Account', icon: 'account_circle', color: '#0891b2' }, + { value: 'JOINT', label: 'Joint Account', icon: 'group', color: '#8b5cf6' }, + { value: 'MINOR', label: 'Minor Account', icon: 'child_care', color: '#f59e0b' }, + { value: 'SENIOR_CITIZEN', label: 'Senior Citizen', icon: 'elderly', color: '#10b981' } + ]; + + selectedAccountStatus = ''; + + // ── Joint Account ────────────────────────────────────────────────────── + jointHolders: Array<{ + clientId: number; + clientName: string; + clientCif: string; + sharePercentage: number; + isMandatory: boolean; + signingAuthority?: string; + role?: string; + }> = []; + + modeOfOperation = ''; + + readonly modeOfOperationOptions = [ + { value: 'EITHER_OR_SURVIVOR', label: 'E or S', sublabel: 'Either or Survivor', icon: 'how_to_vote' }, + { value: 'FORMER_OR_SURVIVOR', label: 'F or S', sublabel: 'Former or Survivor', icon: 'person' }, + { value: 'JOINTLY', label: 'Jointly', sublabel: 'All Must Sign', icon: 'groups' }, + { value: 'ANYONE_OR_SURVIVOR', label: 'Anyone', sublabel: 'Anyone or Survivor', icon: 'group' } + ]; + + // ── Individual Male — Special Category ──────────────────────────────── + selectedSpecialCategory = ''; + + readonly specialCategoryOptions = [ + { value: 'DEFENCE', label: 'Defence Personnel', icon: 'military_tech' }, + { value: 'EX_SERVICEMAN', label: 'Ex-Serviceman', icon: 'shield' }, + { value: 'GOVT_EMPLOYEE', label: 'Government Employee', icon: 'account_balance' }, + { value: 'PHYSICALLY_CHALLENGED', label: 'Physically Challenged', icon: 'accessible' }, + { value: 'NRI', label: 'NRI (Non-Resident Indian)', icon: 'flight' }, + { value: 'NONE', label: 'None / General', icon: 'person' } + ]; + + // ── Individual Female — Weaker Section ──────────────────────────────── + selectedWeakerSection = ''; + + readonly weakerSectionOptions = [ + { value: 'BPL', label: 'BPL (Below Poverty Line)', icon: 'volunteer_activism' }, + { value: 'SC_ST', label: 'SC / ST Category', icon: 'groups' }, + { value: 'MINORITY', label: 'Minority Community', icon: 'diversity_3' }, + { value: 'SHG', label: 'Self Help Group Member', icon: 'handshake' }, + { value: 'PMJDY', label: 'PMJDY Beneficiary', icon: 'savings' }, + { value: 'NONE', label: 'None / General', icon: 'person_outline' } + ]; + + // ── Minor Account ────────────────────────────────────────────────────── + guardianClientId: number | null = null; + guardianName = ''; + guardianCif = ''; + guardianRelationship = ''; + minorDob: Date | null = null; + + readonly guardianRelationshipOptions = [ + 'Father', + 'Mother', + 'Legal Guardian', + 'Court Appointed Guardian' + ]; + constructor() { this.createSavingsAccountDetailsForm(); } @@ -102,11 +174,83 @@ export class SavingsAccountDetailsStepComponent implements OnInit { }); } } + + // Auto-detect account status from client DOB + capture primary holder info + const clientId = this.savingsAccountTemplate?.clientId; + if (clientId) { + this.primaryClientId = clientId; + this.clientsService.getClientData(String(clientId)).subscribe((client: any) => { + this.primaryClientName = client?.displayName ?? client?.fullname ?? ''; + this.primaryClientCif = client?.cifNumber ?? client?.cifNo ?? ''; + if (client?.dateOfBirth) { + const dob = this.parseClientDob(client.dateOfBirth); + if (dob) { + this.clientDob = dob; + this.clientAge = this.calculateAge(dob); + this.clientGender = client.gender?.name?.toLowerCase() ?? null; + this.autoDetectAccountStatus(); + } + } + this.cdr.detectChanges(); + }); + } + } + + /** Parse DOB from backend — handles array [yyyy, mm, dd] or string */ + private parseClientDob(dob: any): Date | null { + if (!dob) return null; + if (Array.isArray(dob) && dob.length >= 3) { + return new Date(dob[0], dob[1] - 1, dob[2]); + } + const d = new Date(dob); + return isNaN(d.getTime()) ? null : d; + } + + /** Calculate full years from a date to today */ + private calculateAge(dob: Date): number { + const today = this.settingsService.businessDate ?? new Date(); + let age = today.getFullYear() - dob.getFullYear(); + const monthDiff = today.getMonth() - dob.getMonth(); + if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dob.getDate())) { + age--; + } + return age; + } + + isTileDisabled(value: string): boolean { + if (value === 'MINOR') return this.clientAge === null || this.clientAge >= 18; + if (value === 'SENIOR_CITIZEN') return this.clientAge === null || this.clientAge < 60; + return false; + } + + getTileTooltip(value: string): string { + if (value === 'MINOR' && this.clientAge !== null && this.clientAge >= 18) { + return `Age ${this.clientAge} yrs — Minor only allowed below 18 years`; + } + if (value === 'SENIOR_CITIZEN' && this.clientAge !== null && this.clientAge < 60) { + return `Age ${this.clientAge} yrs — Senior Citizen only allowed at 60+ years`; + } + if (value === 'MINOR' && this.clientAge === null) return 'DOB not available — cannot verify age'; + if (value === 'SENIOR_CITIZEN' && this.clientAge === null) return 'DOB not available — cannot verify age'; + return ''; + } + + /** Auto-set status tile based on age; pre-fill minorDob if minor */ + private autoDetectAccountStatus(): void { + if (this.clientAge === null) return; + + if (this.clientAge < 18) { + this.selectedAccountStatus = 'MINOR'; + this.minorDob = this.clientDob; + } else if (this.clientAge >= 60) { + this.selectedAccountStatus = 'SENIOR_CITIZEN'; + } else if (this.clientGender === 'female') { + this.selectedAccountStatus = 'INDIVIDUAL_FEMALE'; + } else { + this.selectedAccountStatus = 'INDIVIDUAL_MALE'; + } } - /** - * Creates savings account details form. - */ createSavingsAccountDetailsForm() { this.savingsAccountDetailsForm = this.formBuilder.group({ productId: [ @@ -122,9 +266,6 @@ export class SavingsAccountDetailsStepComponent implements OnInit { }); } - /** - * Fetches savings account product template on productId value changes - */ buildDependencies() { const entityId = this.savingsAccountTemplate.groupId || this.savingsAccountTemplate.clientId; this.savingsAccountDetailsForm.get('productId').valueChanges.subscribe((productId: string) => { @@ -150,10 +291,6 @@ export class SavingsAccountDetailsStepComponent implements OnInit { }); } - /** - * Formats template dates into the current CBS date format. - * Handles backend date arrays like [2026, 4, 20] as well as strings and Date objects. - */ private extractOptions(options: any[], fallback?: string): string[] { if (Array.isArray(options) && options.length > 0) { return options.map((o: any) => o.value ?? o.name ?? o); @@ -162,12 +299,8 @@ export class SavingsAccountDetailsStepComponent implements OnInit { } formatTemplateDate(value: any): string { - if (!value) { - return ''; - } - + if (!value) return ''; let dateValue: Date | null = null; - if (value instanceof Date) { dateValue = value; } else if (Array.isArray(value) && value.length >= 3) { @@ -175,30 +308,145 @@ export class SavingsAccountDetailsStepComponent implements OnInit { } else { dateValue = new Date(value); } - if (!dateValue || Number.isNaN(dateValue.getTime())) { return Array.isArray(value) ? value.join('/') : String(value); } - return this.dateUtils.formatDate(dateValue, 'M/d/yyyy') || ''; } - /** - * Returns savings account form value. - */ 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; } + onGuardianChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void { + this.guardianClientId = event?.clientId ?? null; + this.guardianName = event?.displayName ?? ''; + this.guardianCif = event?.cifNumber ?? ''; + } + + // ── Joint Account helpers ────────────────────────────────────────────── + openAddJointHolderDialog(): void { + const ref = this.dialog.open(AddRelatedClientDialogComponent, { + width: '500px', + panelClass: 'arc-dialog-panel', + disableClose: true + }); + ref.afterClosed().subscribe((result) => { + if (result) { + const duplicate = this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif); + if (duplicate) { + alert(`Account holder with CIF ${result.clientCif} is already added.`); + return; + } + this.jointHolders = [ + ...this.jointHolders, + { ...result, isMandatory: false }]; + this.autoSetModeOfOperation(); + } + }); + } + + removeJointHolder(index: number): void { + this.jointHolders = this.jointHolders.filter((_, i) => i !== index); + this.autoSetModeOfOperation(); + } + + toggleJointHolderMandatory(holder: any): void { + holder.isMandatory = !holder.isMandatory; + this.autoSetModeOfOperation(); + } + + autoSetModeOfOperation(): void { + const mandatory = this.jointHolders.filter((h) => h.isMandatory).length; + if (mandatory === 0) { + this.modeOfOperation = 'EITHER_OR_SURVIVOR'; + } else if (mandatory === 1) { + this.modeOfOperation = 'FORMER_OR_SURVIVOR'; + } else { + this.modeOfOperation = 'JOINTLY'; + } + } + + get totalJointSharePercentage(): number { + return (this.primaryHolderShare || 0) + this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0); + } + + get mandatoryJointCount(): number { + return this.jointHolders.filter((h) => h.isMandatory).length; + } + + get selectedModeLabel(): string { + return this.modeOfOperationOptions.find((o) => o.value === this.modeOfOperation)?.sublabel ?? ''; + } + + // ── Minor Account helpers ────────────────────────────────────────────── + get majorityDate(): string { + const dob = this.minorDob ?? this.clientDob; + if (!dob) return ''; + const d = new Date(dob); + d.setFullYear(d.getFullYear() + 18); + return this.dateUtils.formatDate(d, 'M/d/yyyy') || ''; + } + get savingsAccountDetails() { const value = this.savingsAccountDetailsForm.getRawValue(); + + // Product Classification — user-selected values + if (this.selectedAccountType) value.accountType = this.selectedAccountType; + if (this.selectedCustomerSegment) value.customerSegment = this.selectedCustomerSegment; + if (this.introducerId) { value.introducerId = this.introducerId; value.introducerName = this.introducerName; - value.introducerCif = this.introducerCif; + value.introducerCifNumber = this.introducerCif; } + + if (this.selectedAccountStatus) { + value.accountStatus = this.selectedAccountStatus; + } + + if (this.selectedAccountStatus === 'JOINT') { + value.modeOfOperation = this.modeOfOperation || null; + value.jointHolders = [ + { + clientId: this.primaryClientId, + displayName: this.primaryClientName, + cifNumber: this.primaryClientCif, + sharePercentage: this.primaryHolderShare, + isMandatory: true, + isPrimary: true + }, + ...this.jointHolders.map((h) => ({ + clientId: h.clientId, + displayName: h.clientName, + cifNumber: h.clientCif, + sharePercentage: h.sharePercentage, + isPrimary: false, + isMandatory: h.isMandatory, + signingAuthority: h.signingAuthority || null, + role: h.role || null + })) + ]; + } + + if (this.selectedAccountStatus === 'INDIVIDUAL_MALE' && this.selectedSpecialCategory) { + value.specialCategory = this.selectedSpecialCategory; + } + + if (this.selectedAccountStatus === 'INDIVIDUAL_FEMALE' && this.selectedWeakerSection) { + value.weakerSection = this.selectedWeakerSection; + } + + if (this.selectedAccountStatus === 'MINOR') { + value.guardianClientId = this.guardianClientId; + value.guardianName = this.guardianName; + value.guardianCifNumber = this.guardianCif; + value.guardianRelationship = this.guardianRelationship; + value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'yyyy-MM-dd') : null; + } + 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 f523e4bd4..0b42b59a5 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 @@ -63,16 +63,16 @@
- @if (savingsAccountProductTemplate?.accountType) { + @if (savingsAccount?.accountType || savingsAccountProductTemplate?.accountType) {
Account Type - {{ savingsAccountProductTemplate?.accountType }} + {{ savingsAccount?.accountType || savingsAccountProductTemplate?.accountType }}
} - @if (savingsAccountProductTemplate?.customerSegment) { + @if (savingsAccount?.customerSegment || savingsAccountProductTemplate?.customerSegment) {
Customer Segment - {{ savingsAccountProductTemplate?.customerSegment }} + {{ savingsAccount?.customerSegment || savingsAccountProductTemplate?.customerSegment }}
} @if (savingsAccountProductTemplate?.productCode) { @@ -135,10 +135,10 @@ Name {{ savingsAccount?.introducerName || '—' }}
- @if (savingsAccount?.introducerCif) { + @if (savingsAccount?.introducerCifNumber) {
CIF No - {{ savingsAccount.introducerCif }} + {{ savingsAccount.introducerCifNumber }}
}
@@ -150,7 +150,155 @@
} - + + @if (savingsAccount?.accountStatus) { +
+
+ + Status of Account + RBI Guidelines +
+
+ + +
+ + @switch (savingsAccount.accountStatus) { + @case ('INDIVIDUAL_MALE') { Individual (Male) } + @case ('INDIVIDUAL_FEMALE') { Individual (Female) } + @case ('SINGLE') { Single Account } + @case ('JOINT') { Joint Account } + @case ('MINOR') { Minor Account } + @case ('SENIOR_CITIZEN') { Senior Citizen } + @default { {{ savingsAccount.accountStatus }} } + } + +
+ + + @if (savingsAccount.accountStatus === 'JOINT') { +
+
+ Mode of Operation + + @switch (savingsAccount.modeOfOperation) { + @case ('EITHER_OR_SURVIVOR') { Either or Survivor } + @case ('FORMER_OR_SURVIVOR') { Former or Survivor } + @case ('JOINTLY') { Jointly } + @case ('ANYONE_OR_SURVIVOR') { Anyone or Survivor } + @default { {{ savingsAccount.modeOfOperation || '—' }} } + } + +
+
+ @if (savingsAccount.jointHolders?.length) { +
+ + + + + + + + + + + + + @for (holder of savingsAccount.jointHolders; track holder.clientId; let i = $index) { + + + + + + + + + } + +
Sr.Holder NameCIF No.Ownership %SigningRole
{{ i + 1 }} + {{ holder.displayName || '—' }} + @if (holder.isPrimary) { Primary } + {{ holder.cifNumber || '—' }}{{ holder.sharePercentage ?? '—' }}%{{ holder.isMandatory ? 'Mandatory' : 'Optional' }}{{ holder.isPrimary ? 'Primary Account Holder' : 'Co-Holder' }}
+
+ } + } + + + @if (savingsAccount.accountStatus === 'INDIVIDUAL_MALE' && savingsAccount.specialCategory) { +
+
+ Special Category + + @switch (savingsAccount.specialCategory) { + @case ('DEFENCE') { Defence Personnel } + @case ('EX_SERVICEMAN') { Ex-Serviceman } + @case ('GOVT_EMPLOYEE') { Government Employee } + @case ('PHYSICALLY_CHALLENGED') { Physically Challenged } + @case ('NRI') { NRI (Non-Resident Indian) } + @case ('NONE') { None / General } + @default { {{ savingsAccount.specialCategory }} } + } + +
+
+ } + + + @if (savingsAccount.accountStatus === 'INDIVIDUAL_FEMALE' && savingsAccount.weakerSection) { +
+
+ Weaker Section + + @switch (savingsAccount.weakerSection) { + @case ('BPL') { BPL (Below Poverty Line) } + @case ('SC_ST') { SC / ST Category } + @case ('MINORITY') { Minority Community } + @case ('SHG') { Self Help Group Member } + @case ('PMJDY') { PMJDY Beneficiary } + @case ('NONE') { None / General } + @default { {{ savingsAccount.weakerSection }} } + } + +
+
+ } + + + @if (savingsAccount.accountStatus === 'MINOR') { +
+ @if (savingsAccount.guardianName) { +
+ Guardian Name + {{ savingsAccount.guardianName }} +
+ } + @if (savingsAccount.guardianCifNumber) { +
+ Guardian CIF + {{ savingsAccount.guardianCifNumber }} +
+ } + @if (savingsAccount.guardianRelationship) { +
+ Relationship + {{ savingsAccount.guardianRelationship }} +
+ } + @if (savingsAccount.minorDateOfBirth) { +
+ Minor DOB + {{ savingsAccount.minorDateOfBirth }} +
+ } +
+ } + +
+
+ } + +
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 25e0a4e09..f97eaaa4a 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 @@ -323,3 +323,75 @@ .margin-t { margin-top: 1em; } + +// ── Status of Account preview ───────────────────────────────────────────── + +.preview-soa-status-row { + display: flex; + align-items: center; + gap: 10px; +} + +.preview-soa-tile { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + border-radius: 999px; + font-size: 14px; + font-weight: 700; + background: #eff6ff; + color: #1d4ed8; + border: 2px solid #bfdbfe; + + i { font-size: 16px; } +} + +.preview-soa-table-wrap { + margin-top: 14px; + overflow-x: auto; + border-radius: 8px; + border: 1px solid #e2e8f0; +} + +.preview-soa-tbl { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +.preview-soa-th { + background: #f8fafc; + padding: 8px 12px; + text-align: left; + font-weight: 600; + color: #475569; + border-bottom: 1px solid #e2e8f0; + white-space: nowrap; +} + +.preview-soa-td { + padding: 10px 12px; + border-bottom: 1px solid #f1f5f9; + color: #0f172a; + vertical-align: middle; +} + +.preview-soa-tr--primary { + background: #f0f9ff; + + .preview-soa-td { font-weight: 600; } +} + +.preview-soa-primary-tag { + display: inline-block; + margin-left: 6px; + padding: 1px 8px; + border-radius: 999px; + font-size: 10px; + font-weight: 700; + background: #dbeafe; + color: #1d4ed8; + border: 1px solid #bfdbfe; + vertical-align: middle; +} diff --git a/src/app/savings/savings.service.ts b/src/app/savings/savings.service.ts index bd5078871..52480fc4c 100644 --- a/src/app/savings/savings.service.ts +++ b/src/app/savings/savings.service.ts @@ -64,6 +64,11 @@ export class SavingsService { return this.http.get(`/savingsaccounts/${accountId}`, { params: httpParams }); } + getClientSavingsAccounts(clientId: string): Observable { + const httpParams = new HttpParams().set('clientId', clientId); + return this.http.get('/savingsaccounts', { params: httpParams }); + } + /** * @param accountId Savings Account Id of account to get data for. * @returns {Observable} Savings account and template. diff --git a/src/app/shared/introducer-search/introducer-search.component.ts b/src/app/shared/introducer-search/introducer-search.component.ts index 703b691fa..adf14cc46 100644 --- a/src/app/shared/introducer-search/introducer-search.component.ts +++ b/src/app/shared/introducer-search/introducer-search.component.ts @@ -84,7 +84,7 @@ export class IntroducerSearchComponent { this.introducerChange.emit({ clientId: client.id, displayName: client.displayName, - cifNumber: client.cifNumber || client.accountNumber || '' + cifNumber: client.cifNumber || client.cifNo || '' }); } diff --git a/src/app/shared/nominee-form/nominee-form.component.ts b/src/app/shared/nominee-form/nominee-form.component.ts index d016d8a6b..8d0e05652 100644 --- a/src/app/shared/nominee-form/nominee-form.component.ts +++ b/src/app/shared/nominee-form/nominee-form.component.ts @@ -183,7 +183,7 @@ export class NomineeFormComponent implements OnInit { mode: 'linked', clientId: client.id, displayName: client.displayName, - cifNumber: client.accountNo || client.cifNumber || '' + cifNumber: client.cifNumber || client.cifNo || '' }); }