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) {
+
+
+
+
+ @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
+
+
+
+
+
+
+
+
+
+ | Sr. |
+ Holder Name |
+ CIF No. |
+ Ownership % |
+ Signing Authority |
+ Role |
+ Action |
+
+
+
+
+
+ | 1 |
+
+
+
+ {{ primaryClientName ? primaryClientName[0].toUpperCase() : '?' }}
+
+
+ {{ primaryClientName || 'Primary Holder' }}
+ Primary Account Holder
+
+
+ |
+ {{ primaryClientCif || '—' }} |
+
+
+ %
+ |
+
+
+ lock Mandatory
+
+ |
+ Primary Account Holder |
+ — |
+
+
+
+ @for (h of jointHolders; track h; let i = $index) {
+
+ | {{ 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 }}
+ }
+
+
+
+
+
+
+ manage_accounts
+ Search & Link Guardian (existing client)
+
+
+
+ @if (guardianName) {
+
+
verified_user
+
+
{{ guardianName }}
+
CIF: {{ guardianCif || 'N/A' }}
+
+
Guardian Linked
+
+ }
+
+
+
+
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 (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
+
+
+
+
+
+
+
+
+
+ | Sr. |
+ Holder Name |
+ CIF No. |
+ Ownership % |
+ Signing Authority |
+ Role |
+ Action |
+
+
+
+
+ | 1 |
+
+
+
+ {{ primaryClientName ? primaryClientName[0].toUpperCase() : '?' }}
+
+
+ {{ primaryClientName || 'Primary Holder' }}
+ Primary Account Holder
+
+
+ |
+ {{ primaryClientCif || '—' }} |
+
+
+ %
+ |
+
+
+ lock Mandatory
+
+ |
+ Primary Account Holder |
+ — |
+
+
+ @for (h of jointHolders; track h; let i = $index) {
+
+ | {{ 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 }}
+ }
+
+
+
+
+
+
+ manage_accounts
+ Search & Link Guardian (existing client)
+
+
+
+ @if (guardianName) {
+
+
verified_user
+
+
{{ guardianName }}
+
CIF: {{ guardianCif || 'N/A' }}
+
+
Guardian Linked
+
+ }
+
+
+
+
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
) {
+
+
+
+ @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
+
+
+
+
+
+
+
+
+
+ | Sr. |
+ Holder Name |
+ CIF No. |
+ Ownership % |
+ Signing Authority |
+ Role |
+ Action |
+
+
+
+
+
+ | 1 |
+
+
+
+ {{ primaryClientName ? primaryClientName[0].toUpperCase() : '?' }}
+
+
+ {{ primaryClientName || 'Primary Holder' }}
+ Primary Account Holder
+
+
+ |
+
+ {{ primaryClientCif || '—' }}
+ |
+
+
+ %
+ |
+
+
+ lock Mandatory
+
+ |
+ Primary Account Holder |
+
+ —
+ |
+
+
+
+ @for (h of jointHolders; track h; let i = $index) {
+
+ | {{ 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 }}
+ }
+
+
+
+
+
+
+ manage_accounts
+ Search & Link Guardian (existing client)
+
+
+
+ @if (guardianName) {
+
+
verified_user
+
+
{{ guardianName }}
+
CIF: {{ guardianCif || 'N/A' }}
+
+
Guardian Linked
+
+ }
+
+
+
+
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) {
+
+
+
+
+
+
+
+ @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) {
+
+
+
+
+ | Sr. |
+ Holder Name |
+ CIF No. |
+ Ownership % |
+ Signing |
+ Role |
+
+
+
+ @for (holder of savingsAccount.jointHolders; track holder.clientId; let i = $index) {
+
+ | {{ 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 }}
+
+ }
+
+ }
+
+
+
+ }
+
+