fix: nominee share & joint ownership validation across all account types
- Block 0% nominee share in all steppers (Savings, FD, RD, Current) — Add button disabled via isValid, addNominee() guards < 1, nominee-form error condition fixed for value=0 - Joint ownership: default primary share 50%, enforce min 1% on blur, max 100% on input with DOM clamp via $event, auto-redistribute co-holders proportionally when primary share changes, isJointOwnershipValid guards each holder >= 1 - Pending/Rejected account tabs: normalize date format to 22 May 2026 for both array and string date values from API Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
df96be9a0e
commit
ad6f629c3f
+6
-1
@@ -123,7 +123,12 @@
|
||||
<button type="button" class="nom-cancel-btn" (click)="showNomineeForm = false; nomineeError = ''">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" class="nom-confirm-btn" (click)="addNominee()">
|
||||
<button
|
||||
type="button"
|
||||
class="nom-confirm-btn"
|
||||
[disabled]="!nomineeFormComp?.isValid"
|
||||
(click)="addNominee()"
|
||||
>
|
||||
<i class="fas fa-plus"></i> Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+4
@@ -83,6 +83,10 @@ export class CurrentAccountCurrencyStepComponent implements OnChanges {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!data.percentageShare || data.percentageShare < 1) {
|
||||
this.nomineeError = 'Share must be at least 1%.';
|
||||
return;
|
||||
}
|
||||
const newTotal = this.totalShare + (+data.percentageShare || 0);
|
||||
if (newTotal > 100) {
|
||||
this.nomineeError = `Total share cannot exceed 100%. Remaining: ${this.remainingShare}%`;
|
||||
|
||||
+14
-5
@@ -301,13 +301,18 @@
|
||||
<input
|
||||
class="soa-pct-input"
|
||||
type="number"
|
||||
min="0"
|
||||
min="1"
|
||||
max="100"
|
||||
[(ngModel)]="primaryHolderShare"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
placeholder="0"
|
||||
(input)="onPrimaryShareInput($event)"
|
||||
(blur)="onPrimaryShareBlur($event)"
|
||||
/>
|
||||
<span class="soa-pct-unit">%</span>
|
||||
@if (primaryHolderShare !== null && primaryHolderShare !== undefined && primaryHolderShare < 1) {
|
||||
<div class="soa-pct-error">Min 1%</div>
|
||||
}
|
||||
</td>
|
||||
<td class="soa-td soa-td--center">
|
||||
<span class="soa-sign-toggle soa-sign-toggle--mandatory">
|
||||
@@ -370,8 +375,8 @@
|
||||
<td class="soa-td soa-td--center">
|
||||
<span
|
||||
class="soa-pct-badge"
|
||||
[class.soa-pct-badge--ok]="totalJointSharePercentage === 100"
|
||||
[class.soa-pct-badge--warn]="totalJointSharePercentage !== 100"
|
||||
[class.soa-pct-badge--ok]="isJointOwnershipValid"
|
||||
[class.soa-pct-badge--warn]="!isJointOwnershipValid"
|
||||
>
|
||||
{{ totalJointSharePercentage }}%
|
||||
</span>
|
||||
@@ -395,10 +400,14 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (totalJointSharePercentage !== 100) {
|
||||
@if (!isJointOwnershipValid) {
|
||||
<div class="soa-alert soa-alert--warn" style="margin-top: 8px">
|
||||
<mat-icon>warning_amber</mat-icon>
|
||||
Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%.
|
||||
@if (totalJointSharePercentage !== 100) {
|
||||
Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%.
|
||||
} @else {
|
||||
Each holder must have at least 1% ownership share.
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="soa-alert soa-alert--ok" style="margin-top: 8px">
|
||||
|
||||
+6
@@ -1118,6 +1118,12 @@ $ni-bg: #f9fafb;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.soa-pct-error {
|
||||
font-size: 11px;
|
||||
color: #ef4444;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.soa-no-action {
|
||||
color: #cbd5e1;
|
||||
font-size: 16px;
|
||||
|
||||
+48
-1
@@ -99,7 +99,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
||||
primaryClientName = '';
|
||||
primaryClientCif = '';
|
||||
primaryClientId: number | null = null;
|
||||
primaryHolderShare = 0;
|
||||
primaryHolderShare = 50;
|
||||
|
||||
// ── Status of Account (RBI) ────────────────────────────────────────────
|
||||
readonly accountStatusOptions = [
|
||||
@@ -412,6 +412,53 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
||||
return (this.primaryHolderShare || 0) + this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0);
|
||||
}
|
||||
|
||||
onPrimaryShareInput(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
let val = +input.value;
|
||||
if (val > 100) {
|
||||
val = 100;
|
||||
input.value = '100';
|
||||
}
|
||||
this.primaryHolderShare = val;
|
||||
if (val >= 1) this.redistributeCoHolders();
|
||||
}
|
||||
|
||||
onPrimaryShareBlur(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
let val = +input.value;
|
||||
if (!val || val < 1) {
|
||||
val = 1;
|
||||
input.value = '1';
|
||||
this.primaryHolderShare = 1;
|
||||
this.redistributeCoHolders();
|
||||
}
|
||||
}
|
||||
|
||||
private redistributeCoHolders(): void {
|
||||
if (this.jointHolders.length === 0) return;
|
||||
const remaining = 100 - this.primaryHolderShare;
|
||||
const totalCo = this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0);
|
||||
if (totalCo === 0) return;
|
||||
let allocated = 0;
|
||||
this.jointHolders.forEach((h, i) => {
|
||||
if (i === this.jointHolders.length - 1) {
|
||||
h.sharePercentage = Math.max(0, remaining - allocated);
|
||||
} else {
|
||||
const share = Math.round((h.sharePercentage / totalCo) * remaining);
|
||||
h.sharePercentage = Math.max(0, share);
|
||||
allocated += h.sharePercentage;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get isJointOwnershipValid(): boolean {
|
||||
return (
|
||||
this.totalJointSharePercentage === 100 &&
|
||||
this.primaryHolderShare >= 1 &&
|
||||
this.jointHolders.every((h) => h.sharePercentage >= 1)
|
||||
);
|
||||
}
|
||||
|
||||
get mandatoryJointCount(): number {
|
||||
return this.jointHolders.filter((h) => h.isMandatory).length;
|
||||
}
|
||||
|
||||
+6
-1
@@ -108,7 +108,12 @@
|
||||
<button type="button" class="nom-cancel-btn" (click)="showNomineeForm = false; nomineeError = ''">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" class="nom-confirm-btn" (click)="addNominee()">
|
||||
<button
|
||||
type="button"
|
||||
class="nom-confirm-btn"
|
||||
[disabled]="!nomineeFormComp?.isValid"
|
||||
(click)="addNominee()"
|
||||
>
|
||||
<i class="fas fa-plus"></i> Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+4
@@ -80,6 +80,10 @@ export class FixedDepositAccountCurrencyStepComponent implements OnChanges {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!data.percentageShare || data.percentageShare < 1) {
|
||||
this.nomineeError = 'Share must be at least 1%.';
|
||||
return;
|
||||
}
|
||||
const newTotal = this.totalShare + (+data.percentageShare || 0);
|
||||
if (newTotal > 100) {
|
||||
this.nomineeError = `Total share cannot exceed 100%. Remaining: ${this.remainingShare}%`;
|
||||
|
||||
+14
-5
@@ -411,13 +411,18 @@
|
||||
<input
|
||||
class="soa-pct-input"
|
||||
type="number"
|
||||
min="0"
|
||||
min="1"
|
||||
max="100"
|
||||
[(ngModel)]="primaryHolderShare"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
placeholder="0"
|
||||
(input)="onPrimaryShareInput($event)"
|
||||
(blur)="onPrimaryShareBlur($event)"
|
||||
/>
|
||||
<span class="soa-pct-unit">%</span>
|
||||
@if (primaryHolderShare !== null && primaryHolderShare !== undefined && primaryHolderShare < 1) {
|
||||
<div class="soa-pct-error">Min 1%</div>
|
||||
}
|
||||
</td>
|
||||
<td class="soa-td soa-td--center">
|
||||
<span class="soa-sign-toggle soa-sign-toggle--mandatory">
|
||||
@@ -479,8 +484,8 @@
|
||||
<td class="soa-td soa-td--center">
|
||||
<span
|
||||
class="soa-pct-badge"
|
||||
[class.soa-pct-badge--ok]="totalJointSharePercentage === 100"
|
||||
[class.soa-pct-badge--warn]="totalJointSharePercentage !== 100"
|
||||
[class.soa-pct-badge--ok]="isJointOwnershipValid"
|
||||
[class.soa-pct-badge--warn]="!isJointOwnershipValid"
|
||||
>
|
||||
{{ totalJointSharePercentage }}%
|
||||
</span>
|
||||
@@ -504,10 +509,14 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (totalJointSharePercentage !== 100) {
|
||||
@if (!isJointOwnershipValid) {
|
||||
<div class="soa-alert soa-alert--warn" style="margin-top: 8px">
|
||||
<mat-icon>warning_amber</mat-icon>
|
||||
Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%.
|
||||
@if (totalJointSharePercentage !== 100) {
|
||||
Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%.
|
||||
} @else {
|
||||
Each holder must have at least 1% ownership share.
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="soa-alert soa-alert--ok" style="margin-top: 8px">
|
||||
|
||||
+6
@@ -1033,6 +1033,12 @@ $gray-900: #202124;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.soa-pct-error {
|
||||
font-size: 11px;
|
||||
color: #ef4444;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.soa-pct-badge {
|
||||
display: inline-flex;
|
||||
padding: 2px 8px;
|
||||
|
||||
+48
-1
@@ -99,7 +99,7 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
||||
primaryClientName = '';
|
||||
primaryClientCif = '';
|
||||
primaryClientId: number | null = null;
|
||||
primaryHolderShare = 0;
|
||||
primaryHolderShare = 50;
|
||||
|
||||
// ── Status of Account (RBI) ────────────────────────────────────────────
|
||||
readonly accountStatusOptions = [
|
||||
@@ -528,6 +528,53 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
||||
return (this.primaryHolderShare || 0) + this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0);
|
||||
}
|
||||
|
||||
onPrimaryShareInput(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
let val = +input.value;
|
||||
if (val > 100) {
|
||||
val = 100;
|
||||
input.value = '100';
|
||||
}
|
||||
this.primaryHolderShare = val;
|
||||
if (val >= 1) this.redistributeCoHolders();
|
||||
}
|
||||
|
||||
onPrimaryShareBlur(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
let val = +input.value;
|
||||
if (!val || val < 1) {
|
||||
val = 1;
|
||||
input.value = '1';
|
||||
this.primaryHolderShare = 1;
|
||||
this.redistributeCoHolders();
|
||||
}
|
||||
}
|
||||
|
||||
private redistributeCoHolders(): void {
|
||||
if (this.jointHolders.length === 0) return;
|
||||
const remaining = 100 - this.primaryHolderShare;
|
||||
const totalCo = this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0);
|
||||
if (totalCo === 0) return;
|
||||
let allocated = 0;
|
||||
this.jointHolders.forEach((h, i) => {
|
||||
if (i === this.jointHolders.length - 1) {
|
||||
h.sharePercentage = Math.max(0, remaining - allocated);
|
||||
} else {
|
||||
const share = Math.round((h.sharePercentage / totalCo) * remaining);
|
||||
h.sharePercentage = Math.max(0, share);
|
||||
allocated += h.sharePercentage;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get isJointOwnershipValid(): boolean {
|
||||
return (
|
||||
this.totalJointSharePercentage === 100 &&
|
||||
this.primaryHolderShare >= 1 &&
|
||||
this.jointHolders.every((h) => h.sharePercentage >= 1)
|
||||
);
|
||||
}
|
||||
|
||||
get mandatoryJointCount(): number {
|
||||
return this.jointHolders.filter((h) => h.isMandatory).length;
|
||||
}
|
||||
|
||||
+6
-1
@@ -110,7 +110,12 @@
|
||||
<button type="button" class="nom-cancel-btn" (click)="showNomineeForm = false; nomineeError = ''">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" class="nom-confirm-btn" (click)="addNominee()">
|
||||
<button
|
||||
type="button"
|
||||
class="nom-confirm-btn"
|
||||
[disabled]="!nomineeFormComp?.isValid"
|
||||
(click)="addNominee()"
|
||||
>
|
||||
<i class="fas fa-plus"></i> Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+4
@@ -78,6 +78,10 @@ export class RecurringDepositsAccountCurrencyStepComponent implements OnChanges
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!data.percentageShare || data.percentageShare < 1) {
|
||||
this.nomineeError = 'Share must be at least 1%.';
|
||||
return;
|
||||
}
|
||||
const newTotal = this.totalShare + (+data.percentageShare || 0);
|
||||
if (newTotal > 100) {
|
||||
this.nomineeError = `Total share cannot exceed 100%. Remaining: ${this.remainingShare}%`;
|
||||
|
||||
+14
-5
@@ -406,13 +406,18 @@
|
||||
<input
|
||||
class="soa-pct-input"
|
||||
type="number"
|
||||
min="0"
|
||||
min="1"
|
||||
max="100"
|
||||
[(ngModel)]="primaryHolderShare"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
placeholder="0"
|
||||
(input)="onPrimaryShareInput($event)"
|
||||
(blur)="onPrimaryShareBlur($event)"
|
||||
/>
|
||||
<span class="soa-pct-unit">%</span>
|
||||
@if (primaryHolderShare !== null && primaryHolderShare !== undefined && primaryHolderShare < 1) {
|
||||
<div class="soa-pct-error">Min 1%</div>
|
||||
}
|
||||
</td>
|
||||
<td class="soa-td soa-td--center">
|
||||
<span class="soa-sign-toggle soa-sign-toggle--mandatory">
|
||||
@@ -474,8 +479,8 @@
|
||||
<td class="soa-td soa-td--center">
|
||||
<span
|
||||
class="soa-pct-badge"
|
||||
[class.soa-pct-badge--ok]="totalJointSharePercentage === 100"
|
||||
[class.soa-pct-badge--warn]="totalJointSharePercentage !== 100"
|
||||
[class.soa-pct-badge--ok]="isJointOwnershipValid"
|
||||
[class.soa-pct-badge--warn]="!isJointOwnershipValid"
|
||||
>
|
||||
{{ totalJointSharePercentage }}%
|
||||
</span>
|
||||
@@ -499,10 +504,14 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (totalJointSharePercentage !== 100) {
|
||||
@if (!isJointOwnershipValid) {
|
||||
<div class="soa-alert soa-alert--warn" style="margin-top: 8px">
|
||||
<mat-icon>warning_amber</mat-icon>
|
||||
Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%.
|
||||
@if (totalJointSharePercentage !== 100) {
|
||||
Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%.
|
||||
} @else {
|
||||
Each holder must have at least 1% ownership share.
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="soa-alert soa-alert--ok" style="margin-top: 8px">
|
||||
|
||||
+6
@@ -985,6 +985,12 @@ $gray-900: #202124;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.soa-pct-error {
|
||||
font-size: 11px;
|
||||
color: #ef4444;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.soa-no-action {
|
||||
color: #cbd5e1;
|
||||
font-size: 16px;
|
||||
|
||||
+48
-1
@@ -88,7 +88,7 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
||||
primaryClientName = '';
|
||||
primaryClientCif = '';
|
||||
primaryClientId: number | null = null;
|
||||
primaryHolderShare = 0;
|
||||
primaryHolderShare = 50;
|
||||
|
||||
// ── Status of Account (RBI) ────────────────────────────────────────────
|
||||
readonly accountStatusOptions = [
|
||||
@@ -485,6 +485,53 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
||||
return (this.primaryHolderShare || 0) + this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0);
|
||||
}
|
||||
|
||||
onPrimaryShareInput(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
let val = +input.value;
|
||||
if (val > 100) {
|
||||
val = 100;
|
||||
input.value = '100';
|
||||
}
|
||||
this.primaryHolderShare = val;
|
||||
if (val >= 1) this.redistributeCoHolders();
|
||||
}
|
||||
|
||||
onPrimaryShareBlur(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
let val = +input.value;
|
||||
if (!val || val < 1) {
|
||||
val = 1;
|
||||
input.value = '1';
|
||||
this.primaryHolderShare = 1;
|
||||
this.redistributeCoHolders();
|
||||
}
|
||||
}
|
||||
|
||||
private redistributeCoHolders(): void {
|
||||
if (this.jointHolders.length === 0) return;
|
||||
const remaining = 100 - this.primaryHolderShare;
|
||||
const totalCo = this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0);
|
||||
if (totalCo === 0) return;
|
||||
let allocated = 0;
|
||||
this.jointHolders.forEach((h, i) => {
|
||||
if (i === this.jointHolders.length - 1) {
|
||||
h.sharePercentage = Math.max(0, remaining - allocated);
|
||||
} else {
|
||||
const share = Math.round((h.sharePercentage / totalCo) * remaining);
|
||||
h.sharePercentage = Math.max(0, share);
|
||||
allocated += h.sharePercentage;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get isJointOwnershipValid(): boolean {
|
||||
return (
|
||||
this.totalJointSharePercentage === 100 &&
|
||||
this.primaryHolderShare >= 1 &&
|
||||
this.jointHolders.every((h) => h.sharePercentage >= 1)
|
||||
);
|
||||
}
|
||||
|
||||
get mandatoryJointCount(): number {
|
||||
return this.jointHolders.filter((h) => h.isMandatory).length;
|
||||
}
|
||||
|
||||
+6
-1
@@ -104,7 +104,12 @@
|
||||
<button type="button" class="nom-cancel-btn" (click)="showNomineeForm = false; nomineeError = ''">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" class="nom-confirm-btn" (click)="addNominee()">
|
||||
<button
|
||||
type="button"
|
||||
class="nom-confirm-btn"
|
||||
[disabled]="!nomineeFormComp?.isValid"
|
||||
(click)="addNominee()"
|
||||
>
|
||||
<i class="fas fa-plus"></i> Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+4
@@ -65,6 +65,10 @@ export class SavingsAccountCurrencyStepComponent {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!data.percentageShare || data.percentageShare < 1) {
|
||||
this.nomineeError = 'Share must be at least 1%.';
|
||||
return;
|
||||
}
|
||||
const newTotal = this.totalShare + (+data.percentageShare || 0);
|
||||
if (newTotal > 100) {
|
||||
this.nomineeError = `Total share cannot exceed 100%. Remaining: ${this.remainingShare}%`;
|
||||
|
||||
+14
-5
@@ -261,13 +261,18 @@
|
||||
<input
|
||||
class="soa-pct-input"
|
||||
type="number"
|
||||
min="0"
|
||||
min="1"
|
||||
max="100"
|
||||
[(ngModel)]="primaryHolderShare"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
placeholder="0"
|
||||
(input)="onPrimaryShareInput($event)"
|
||||
(blur)="onPrimaryShareBlur($event)"
|
||||
/>
|
||||
<span class="soa-pct-unit">%</span>
|
||||
@if (primaryHolderShare !== null && primaryHolderShare !== undefined && primaryHolderShare < 1) {
|
||||
<div class="soa-pct-error">Min 1%</div>
|
||||
}
|
||||
</td>
|
||||
<td class="soa-td soa-td--center">
|
||||
<span class="soa-sign-toggle soa-sign-toggle--mandatory">
|
||||
@@ -332,8 +337,8 @@
|
||||
<td class="soa-td soa-td--center">
|
||||
<span
|
||||
class="soa-pct-badge"
|
||||
[class.soa-pct-badge--ok]="totalJointSharePercentage === 100"
|
||||
[class.soa-pct-badge--warn]="totalJointSharePercentage !== 100"
|
||||
[class.soa-pct-badge--ok]="isJointOwnershipValid"
|
||||
[class.soa-pct-badge--warn]="!isJointOwnershipValid"
|
||||
>
|
||||
{{ totalJointSharePercentage }}%
|
||||
</span>
|
||||
@@ -357,10 +362,14 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (totalJointSharePercentage !== 100) {
|
||||
@if (!isJointOwnershipValid) {
|
||||
<div class="soa-alert soa-alert--warn" style="margin-top: 8px">
|
||||
<mat-icon>warning_amber</mat-icon>
|
||||
Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%.
|
||||
@if (totalJointSharePercentage !== 100) {
|
||||
Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%.
|
||||
} @else {
|
||||
Each holder must have at least 1% ownership share.
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="soa-alert soa-alert--ok" style="margin-top: 8px">
|
||||
|
||||
+6
@@ -514,6 +514,12 @@ $gray-900: #202124;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.soa-pct-error {
|
||||
font-size: 11px;
|
||||
color: #ef4444;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.soa-no-action {
|
||||
color: #cbd5e1;
|
||||
font-size: 16px;
|
||||
|
||||
+48
-1
@@ -81,7 +81,7 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
primaryClientName = '';
|
||||
primaryClientCif = '';
|
||||
primaryClientId: number | null = null;
|
||||
primaryHolderShare = 0;
|
||||
primaryHolderShare = 50;
|
||||
/** Best-effort string label of the client's entity type
|
||||
* (e.g. "Individual", "Corporate", "MSME") — used to auto-fill the
|
||||
* Customer Segment dropdown when the product template lands. */
|
||||
@@ -428,6 +428,53 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
return (this.primaryHolderShare || 0) + this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0);
|
||||
}
|
||||
|
||||
onPrimaryShareInput(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
let val = +input.value;
|
||||
if (val > 100) {
|
||||
val = 100;
|
||||
input.value = '100';
|
||||
}
|
||||
this.primaryHolderShare = val;
|
||||
if (val >= 1) this.redistributeCoHolders();
|
||||
}
|
||||
|
||||
onPrimaryShareBlur(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
let val = +input.value;
|
||||
if (!val || val < 1) {
|
||||
val = 1;
|
||||
input.value = '1';
|
||||
this.primaryHolderShare = 1;
|
||||
this.redistributeCoHolders();
|
||||
}
|
||||
}
|
||||
|
||||
private redistributeCoHolders(): void {
|
||||
if (this.jointHolders.length === 0) return;
|
||||
const remaining = 100 - this.primaryHolderShare;
|
||||
const totalCo = this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0);
|
||||
if (totalCo === 0) return;
|
||||
let allocated = 0;
|
||||
this.jointHolders.forEach((h, i) => {
|
||||
if (i === this.jointHolders.length - 1) {
|
||||
h.sharePercentage = Math.max(0, remaining - allocated);
|
||||
} else {
|
||||
const share = Math.round((h.sharePercentage / totalCo) * remaining);
|
||||
h.sharePercentage = Math.max(0, share);
|
||||
allocated += h.sharePercentage;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get isJointOwnershipValid(): boolean {
|
||||
return (
|
||||
this.totalJointSharePercentage === 100 &&
|
||||
this.primaryHolderShare >= 1 &&
|
||||
this.jointHolders.every((h) => h.sharePercentage >= 1)
|
||||
);
|
||||
}
|
||||
|
||||
get mandatoryJointCount(): number {
|
||||
return this.jointHolders.filter((h) => h.isMandatory).length;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,9 @@
|
||||
<mat-form-field appearance="outline" class="nom-edit-field nom-edit-field--sm">
|
||||
<mat-label>Share %</mat-label>
|
||||
<input matInput type="number" [(ngModel)]="editShare" min="1" [max]="maxShareForEdit(i)" />
|
||||
@if (editShare && editShare > maxShareForEdit(i)) {
|
||||
@if (editShare !== null && editShare !== undefined && editShare < 1) {
|
||||
<mat-error>Share must be at least 1%</mat-error>
|
||||
} @else if (editShare && editShare > maxShareForEdit(i)) {
|
||||
<mat-error>Max {{ maxShareForEdit(i) }}%</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
@@ -152,7 +154,7 @@
|
||||
color="primary"
|
||||
type="button"
|
||||
class="nom-edit-save"
|
||||
[disabled]="!editRelId || !editShare || editShare > maxShareForEdit(i)"
|
||||
[disabled]="!editRelId || !editShare || editShare < 1 || editShare > maxShareForEdit(i)"
|
||||
(click)="saveEdit(nominee, i)"
|
||||
>
|
||||
<mat-icon>check</mat-icon>
|
||||
@@ -317,7 +319,9 @@
|
||||
<mat-form-field appearance="outline" class="nom-field-item nom-field-item--sm">
|
||||
<mat-label>Share %</mat-label>
|
||||
<input matInput type="number" [(ngModel)]="nomineeShare" min="1" [max]="maxAllowedShare" />
|
||||
@if (nomineeShare && nomineeShare > maxAllowedShare) {
|
||||
@if (nomineeShare !== null && nomineeShare !== undefined && nomineeShare < 1) {
|
||||
<mat-error>Share must be at least 1%</mat-error>
|
||||
} @else if (nomineeShare && nomineeShare > maxAllowedShare) {
|
||||
<mat-error>Max {{ maxAllowedShare }}%</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
@@ -398,6 +402,7 @@
|
||||
[disabled]="
|
||||
!nomineeRelationshipId ||
|
||||
!nomineeShare ||
|
||||
nomineeShare < 1 ||
|
||||
nomineeSaving ||
|
||||
nomineeShare > maxAllowedShare ||
|
||||
(nomineeIsMinor && !selectedGuardian) ||
|
||||
|
||||
@@ -351,7 +351,7 @@ export class AccountNomineesTabComponent implements OnInit {
|
||||
}
|
||||
|
||||
saveNominee(): void {
|
||||
if (!this.selectedClient || !this.nomineeRelationshipId || !this.nomineeShare) return;
|
||||
if (!this.selectedClient || !this.nomineeRelationshipId || !this.nomineeShare || this.nomineeShare < 1) return;
|
||||
if (this.totalShare + this.nomineeShare > 100) return;
|
||||
if (this.guardianSameAsNominee) return;
|
||||
|
||||
@@ -429,7 +429,8 @@ export class AccountNomineesTabComponent implements OnInit {
|
||||
}
|
||||
|
||||
saveEdit(nominee: any, index: number): void {
|
||||
if (!this.editRelId || !this.editShare || this.editShare > this.maxShareForEdit(index)) return;
|
||||
if (!this.editRelId || !this.editShare || this.editShare < 1 || this.editShare > this.maxShareForEdit(index))
|
||||
return;
|
||||
|
||||
const payload: any = {
|
||||
relationshipId: this.editRelId,
|
||||
|
||||
@@ -162,8 +162,12 @@
|
||||
<mat-form-field appearance="outline" class="nom-field-item nom-field-item--sm">
|
||||
<mat-label>Share %</mat-label>
|
||||
<input matInput type="number" [(ngModel)]="percentageShare" min="1" max="100" />
|
||||
@if (percentageShare && (percentageShare < 1 || percentageShare > 100)) {
|
||||
<mat-error>1–100</mat-error>
|
||||
@if (
|
||||
percentageShare !== null &&
|
||||
percentageShare !== undefined &&
|
||||
(percentageShare < 1 || percentageShare > 100)
|
||||
) {
|
||||
<mat-error>Share must be 1–100%</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
+4
@@ -166,6 +166,10 @@ export class PendingAccountComponent {
|
||||
const d = new Date(date[0], date[1] - 1, date[2]);
|
||||
return d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
const d = new Date(date);
|
||||
if (!isNaN(d.getTime())) {
|
||||
return d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -82,6 +82,10 @@ export class RejectedAccountComponent {
|
||||
const d = new Date(date[0], date[1] - 1, date[2]);
|
||||
return d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
const d = new Date(date);
|
||||
if (!isNaN(d.getTime())) {
|
||||
return d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user