feat(savings,shares): bidirectional savings/share account linking, new Migrate Cash Denomination screen
- Savings account creation shows a required "Default Share Account" picker once Share Linking is enabled, populated from the client's active share accounts; submits shareAccountId alongside it. - Fixes a real data-loss bug on Share Account creation: the Charges step re-seeded its table from the product's default charges on every ngOnChanges firing (e.g. stepping back to Details and forward again), silently discarding a charge the clerk had already added, edited, or removed - a pristine guard existed for exactly this but wasn't checked in ngOnChanges. Now it is. - Removes the hard "required" on Share Account creation's Default Savings Account field, matching the backend now accepting a share account with no linked savings account. - New /accounting/migrate-cash-denomination screen: counts a branch's opening cash into notes and coins, checks it against (or defines, if unset) the Cash GL account's balance, and opens the branch's first business day with that same count - the vault-side counterpart to Migrate Opening Balances, kept under Accounting rather than Branch Ops/Cashier so it isn't reachable by mistake. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -146,6 +146,13 @@ export class CreateSavingsAccountComponent {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (detailsStep.shareLinkingEnabled && !detailsStep.shareAccountId) {
|
||||
this.alertService.alert({
|
||||
type: 'Error',
|
||||
message: 'Share Linking is enabled — please select the share account to link before submitting.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
const locale = this.settingsService.language.code;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const monthDayFormat = 'dd MMMM';
|
||||
@@ -208,6 +215,14 @@ export class CreateSavingsAccountComponent {
|
||||
type: 'Error',
|
||||
message: 'An account with this External ID already exists. Please use a unique External ID.'
|
||||
});
|
||||
} else if (
|
||||
errorCode === 'error.msg.savingsaccount.share.account.required' ||
|
||||
errorCode === 'error.msg.savingsaccount.share.account.not.active.for.client'
|
||||
) {
|
||||
this.alertService.alert({
|
||||
type: 'Error',
|
||||
message: 'Please select an active share account belonging to this client before submitting.'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+22
@@ -126,6 +126,28 @@
|
||||
<mat-option [value]="false">No</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
@if (shareLinkingEnabled) {
|
||||
<mat-form-field appearance="outline" class="sad-field">
|
||||
<mat-label>Default Share Account</mat-label>
|
||||
<mat-select
|
||||
required
|
||||
[(ngModel)]="shareAccountId"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[disabled]="readOnly"
|
||||
>
|
||||
@for (account of activeShareAccounts; track account.id) {
|
||||
<mat-option [value]="account.id"> {{ account.accountNo }} - {{ account.productName }} </mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
@if (!shareAccountId) {
|
||||
<mat-error>Default Share Account is <strong>required</strong></mat-error>
|
||||
}
|
||||
@if (activeShareAccounts.length === 0) {
|
||||
<mat-hint>This client has no active share account. Open one first, or turn off Share Linking.</mat-hint>
|
||||
}
|
||||
</mat-form-field>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+15
@@ -78,6 +78,11 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
|
||||
productTemplate: any = null;
|
||||
shareLinkingEnabled = false;
|
||||
/** The client's active share accounts, offered as the "Default Share Account" once Share Linking
|
||||
* is enabled — the reverse of the "Default Savings Account" picker Share Account creation
|
||||
* already shows. Fetched once for the client this form is for, since it never changes mid-form. */
|
||||
activeShareAccounts: any[] = [];
|
||||
shareAccountId: number | null = null;
|
||||
accountTypeOptions: string[] = [];
|
||||
customerSegmentOptions: string[] = [];
|
||||
selectedAccountType: string | null = null;
|
||||
@@ -209,6 +214,10 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
const clientId = this.savingsAccountTemplate?.clientId;
|
||||
if (clientId) {
|
||||
this.primaryClientId = clientId;
|
||||
this.savingsService.getClientActiveShareAccounts(clientId).subscribe({
|
||||
next: (accounts) => (this.activeShareAccounts = accounts || []),
|
||||
error: () => (this.activeShareAccounts = [])
|
||||
});
|
||||
this.clientsService.getClientData(String(clientId)).subscribe((client: any) => {
|
||||
this.primaryClientName = client?.displayName ?? client?.fullname ?? '';
|
||||
this.primaryClientCif = client?.cifNumber ?? client?.cifNo ?? '';
|
||||
@@ -450,6 +459,9 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
if (ea.shareLinkingEnabled !== undefined) {
|
||||
this.shareLinkingEnabled = !!ea.shareLinkingEnabled;
|
||||
}
|
||||
if (ea.shareAccountId) {
|
||||
this.shareAccountId = ea.shareAccountId;
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
@@ -669,6 +681,9 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
}
|
||||
|
||||
value.shareLinkingEnabled = this.shareLinkingEnabled;
|
||||
if (this.shareLinkingEnabled && this.shareAccountId) {
|
||||
value.shareAccountId = this.shareAccountId;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -181,6 +181,12 @@ export class SavingsService {
|
||||
return this.http.get('/savingsaccounts/template', { params: httpParams });
|
||||
}
|
||||
|
||||
/** A client's active share accounts — for the "Default Share Account" picker shown once Share
|
||||
* Linking is enabled on a savings account being created. */
|
||||
getClientActiveShareAccounts(clientId: number): Observable<any[]> {
|
||||
return this.http.get<any[]>(`/clients/${clientId}/activeshareaccounts`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} savingsAccount Savings Account
|
||||
* @returns {Observable<any>}
|
||||
|
||||
Reference in New Issue
Block a user