Files
cbsnexora/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts
T

321 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/** Angular Imports */
import { Component, OnInit, Input, Output, EventEmitter, inject, ChangeDetectorRef } from '@angular/core';
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import { Dates } from 'app/core/utils/dates';
/** Custom Services */
import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service';
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';
import { AddRelatedClientDialogComponent } from './add-related-client-dialog/add-related-client-dialog.component';
/**
* Current Account Details Step
*/
@Component({
selector: 'mifosx-current-account-details-step',
templateUrl: './current-account-details-step.component.html',
styleUrls: ['./current-account-details-step.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatStepperPrevious,
FaIconComponent,
MatStepperNext,
IntroducerSearchComponent,
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow,
MatIcon,
FormsModule
]
})
export class CurrentAccountDetailsStepComponent implements OnInit {
private formBuilder = inject(UntypedFormBuilder);
private currentAccountsService = inject(CurrentAccountsService);
private settingsService = inject(SettingsService);
private clientsService = inject(ClientsService);
private dialog = inject(MatDialog);
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;
introducerId: number | null = null;
introducerName: string | null = null;
introducerCif: string | null = null;
/** Non-Individual section */
isNonIndividualClient = false;
relatedClients: Array<{
clientId: number;
clientName: string;
clientCif: string;
sharePercentage: number;
isMandatory: boolean;
}> = [];
signatoryRights = '';
readonly signatoryRightsOptions = [
{ value: 'ANYONE', label: 'E or S', sublabel: 'Either or Survivor', icon: 'how_to_vote' },
{ value: 'ONE_MANDATORY', label: 'J/One', sublabel: 'Joint – One Mandatory', icon: 'group' },
{ value: 'TWO_MANDATORY', label: 'J/Two', sublabel: 'Joint – Two Mandatory', icon: 'groups' },
{ 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;
constructor() {
this.createCurrentAccountDetailsForm();
}
ngOnInit() {
this.maxDate = this.settingsService.businessDate;
this.buildDependencies();
if (this.currentAccountTemplate?.clientId) {
this.clientsService.getClientData(String(this.currentAccountTemplate.clientId)).subscribe((client: any) => {
this.isNonIndividualClient = client?.legalForm?.id === 2;
this.cdr.detectChanges();
});
}
if (this.currentAccountTemplate) {
this.productData = this.currentAccountTemplate.productOptions;
if (this.currentAccountTemplate.currentAccountProductId) {
this.currentAccountDetailsForm.patchValue({
productId: this.currentAccountTemplate.currentAccountProductId,
submittedOnDate:
this.currentAccountTemplate.timeline.submittedOnDate &&
new Date(this.currentAccountTemplate.timeline.submittedOnDate),
externalId: this.currentAccountTemplate.externalId
});
} else {
this.currentAccountDetailsForm.patchValue({
submittedOnDate: this.settingsService.businessDate
});
}
}
}
/**
* Creates current account details form.
*/
createCurrentAccountDetailsForm() {
this.currentAccountDetailsForm = this.formBuilder.group({
productId: [
'',
Validators.required
],
submittedOnDate: [
'',
Validators.required
],
fieldOfficerId: [''],
externalId: [''],
balanceSwapOption: [false],
msme: [false],
autoDebitFacility: [false]
});
}
/**
* 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
);
if (selectedProduct?.currency) {
response.currency = selectedProduct.currency;
} else if (this.currentAccountTemplate.currency) {
response.currency = this.currentAccountTemplate.currency;
}
}
this.productTemplate = response;
this.currentAccountProductTemplate.emit(response);
this.fieldOfficerData = response.fieldOfficerOptions;
this.currentAccountProductSelected = true;
// Pre-populate toggles from product defaults (user can override)
this.currentAccountDetailsForm.patchValue({
balanceSwapOption: !!response.balanceSwapOption,
msme: !!response.msme,
autoDebitFacility: !!response.autoDebitFacility
});
this.cdr.detectChanges();
if (!this.isFieldOfficerPatched && this.currentAccountTemplate.fieldOfficerId) {
this.currentAccountDetailsForm.get('fieldOfficerId').patchValue(this.currentAccountTemplate.fieldOfficerId);
this.isFieldOfficerPatched = true;
} else {
this.currentAccountDetailsForm.get('fieldOfficerId').patchValue('');
}
});
});
}
/**
* Formats template dates into the current CBS date format.
*/
formatTemplateDate(value: any): string {
if (!value) {
return '';
}
let dateValue: Date | null = null;
if (value instanceof Date) {
dateValue = value;
} else if (Array.isArray(value) && value.length >= 3) {
dateValue = new Date(value[0], value[1] - 1, value[2]);
} 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 current 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;
}
openAddRelatedClientDialog(): void {
const ref = this.dialog.open(AddRelatedClientDialogComponent, {
width: '500px',
panelClass: 'arc-dialog-panel',
disableClose: true
});
ref.afterClosed().subscribe((result) => {
if (result) {
const duplicate = this.relatedClients.some((c) => c.clientCif && c.clientCif === result.clientCif);
if (duplicate) {
alert(`Partner with CIF ${result.clientCif} is already added.`);
return;
}
this.relatedClients = [
...this.relatedClients,
{ ...result, isMandatory: false }];
this.autoSetSignatoryRights();
}
});
}
removeRelatedClient(index: number): void {
this.relatedClients = this.relatedClients.filter((_, i) => i !== index);
this.autoSetSignatoryRights();
}
toggleMandatory(client: any): void {
client.isMandatory = !client.isMandatory;
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';
}
}
get totalSharePercentage(): number {
return this.relatedClients.reduce((sum, c) => sum + (c.sharePercentage || 0), 0);
}
get mandatoryCount(): number {
return this.relatedClients.filter((c) => c.isMandatory).length;
}
get selectedSignatoryLabel(): string {
const opt = this.signatoryRightsOptions.find((x) => x.value === this.signatoryRights);
return opt ? opt.sublabel : '';
}
get currentAccountDetails() {
const value = this.currentAccountDetailsForm.getRawValue();
if (this.introducerId) {
value.introducerId = this.introducerId;
value.introducerName = this.introducerName;
value.introducerCif = this.introducerCif;
}
if (this.isNonIndividualClient) {
value.signatoryRights = this.signatoryRights || null;
value.partners = this.relatedClients.map((c) => ({
clientId: c.clientId,
clientName: c.clientName,
clientCif: c.clientCif,
sharePercentage: c.sharePercentage,
isMandatory: c.isMandatory
}));
}
return value;
}
}