diff --git a/src/app/deposits/fixed-deposits/create-fixed-deposit-account/create-fixed-deposit-account.component.html b/src/app/deposits/fixed-deposits/create-fixed-deposit-account/create-fixed-deposit-account.component.html index 25e5d2c38..66a5f262e 100644 --- a/src/app/deposits/fixed-deposits/create-fixed-deposit-account/create-fixed-deposit-account.component.html +++ b/src/app/deposits/fixed-deposits/create-fixed-deposit-account/create-fixed-deposit-account.component.html @@ -34,7 +34,8 @@ - + + {{ 'labels.inputs.DETAILS' | translate }} - - {{ 'labels.inputs.TERMS' | translate }} + @if (fixedDepositsAccountProductTemplate) { + + + {{ 'labels.inputs.TERMS' | translate }} - - - + + - - {{ 'labels.inputs.SETTINGS' | translate }} +
+ + +
+
- - -
+ + + {{ 'labels.inputs.SETTINGS' | translate }} - - {{ 'labels.inputs.INTEREST RATE CHART' | translate }} + + - - - +
+ + +
+
- - {{ 'labels.inputs.CHARGES' | translate }} + + + {{ 'labels.inputs.INTEREST RATE CHART' | translate }} - - - + + - @if (fixedDepositAccountFormValid) { +
+ + +
+
+ + + + {{ 'labels.inputs.CHARGES' | translate }} + + + + +
+ + +
+
+ + {{ 'labels.inputs.PREVIEW' | translate }} - {{ 'labels.inputs.INTEREST RATE CHART' | translate }} + CBS Base Chart
- + @if (activeBaseVersion) { +
+
+
+

{{ activeBaseVersion.versionLabel }}

+

+ Active CBS base chart + Effective from {{ activeBaseVersion.effectiveFromDate }} +

+
+ Active +
+ + @if (activeBaseVersion.remarks) { +

{{ activeBaseVersion.remarks }}

+ } + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Sr.{{ slab.srNo }}Particular{{ slab.description }}From{{ slab.fromPeriod }}To{{ slab.toPeriod ?? 'N/A' }}Period Type{{ slab.periodTypeLabel }}Annual Rate (%) + {{ slab.annualInterestRate | number: '1.2-2' }} +
+
+ } @else { +

Loading CBS base chart...

+ }
@@ -112,7 +160,7 @@
{ this.fixedDepositProductsTemplate = data.fixedDepositProductsTemplate; }); this.accountingRuleData = this.accounting.getAccountingRulesForSavings(); + this.productsService.getRdBaseInterestRates().subscribe({ + next: (rates: any[]) => { + this.fdInterestRates = rates || []; + }, + error: () => { + this.fdInterestRates = []; + } + }); } get fixedDepositProductDetailsForm() { @@ -102,10 +130,6 @@ export class CreateFixedDepositProductComponent { return this.fixedDepositProductSettingsStep.fixedDepositProductSettingsForm; } - get fixedDepositProductInterestRateChartForm() { - return this.fixedDepositProductInterestRateChartStep.fixedDepositProductInterestRateChartForm; - } - get fixedDepositProductAccountingForm() { return this.fixedDepositProductAccountingStep.fixedDepositProductAccountingForm; } @@ -120,29 +144,35 @@ export class CreateFixedDepositProductComponent { this.fixedDepositProductCurrencyForm.valid && this.fixedDepositProductTermsForm.valid && this.fixedDepositProductSettingsForm.valid && - this.fixedDepositProductInterestRateChartForm.valid && this.fixedDepositProductAccountingForm.valid ); } + get activeBaseVersion() { + return this.fdInterestRates.find((v: any) => v.isActive) ?? this.fdInterestRates[0] ?? null; + } + + get activeBaseSlabs() { + return this.activeBaseVersion?.slabs || []; + } + get fixedDepositProduct() { return { ...this.fixedDepositProductDetailsStep.fixedDepositProductDetails, ...this.fixedDepositProductCurrencyStep.fixedDepositProductCurrency, ...this.fixedDepositProductTermsStep.fixedDepositProductTerms, ...this.fixedDepositProductSettingsStep.fixedDepositProductSettings, - ...this.fixedDepositProductInterestRateChartStep.fixedDepositProductInterestRateChart, ...this.fixedDepositProductChargesStep.fixedDepositProductCharges, - ...this.fixedDepositProductAccountingStep.fixedDepositProductAccounting + ...this.fixedDepositProductAccountingStep.fixedDepositProductAccounting, + fdSlabs: this.activeBaseSlabs }; } submit() { - // TODO: Update once language and date settings are setup const fixedDepositProduct = { ...this.fixedDepositProduct, charges: this.fixedDepositProduct.charges.map((charge: any) => ({ id: charge.id })), - locale: this.settingsService.language.code // locale required for depositAmount + locale: this.settingsService.language.code }; delete fixedDepositProduct.advancedAccountingRules; this.productsService.createFixedDepositProduct(fixedDepositProduct).subscribe((response: any) => { diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.html b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.html index 86939918d..2df0b809d 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.html +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.html @@ -9,310 +9,111 @@
- -

{{ 'labels.heading.Accounting' | translate }}

-
-
-
- - @for (accountingRule of accountingRuleData; track accountingRule; let i = $index) { - - {{ 'labels.accounting.' + accountingRule | translate }} - - } - -
-
-
- - @if (isCashOrAccrualAccounting()) { -
-
- -

{{ 'labels.heading.Assets' | translate }}

-
-
- - - @if (isAccrualAccounting()) { - - - } - @if (isAccrualAccounting()) { - - - } -
+ + + +

Accounting

-
-
- -

{{ 'labels.heading.Liabilities' | translate }}

-
-
- - - - - @if (isAccrualAccounting()) { - - - } -
-
- -
-
- -

{{ 'labels.heading.Expenses' | translate }}

-
-
- - -
-
- -
-
- -

{{ 'labels.heading.Income' | translate }}

-
-
- - - - -
-
- -
-
- -

{{ 'labels.heading.Advanced Accounting Rules' | translate }}

-
-
-
- +
+
+
+

Accounting Rule

+
+
+ Cash
- @if (fixedDepositProductAccountingForm.value.advancedAccountingRules) { - -
-
-

{{ 'labels.heading.Configure Fund Sources for Payment Channels' | translate }}

- -
- @if (paymentChannelToFundSourceMappings.value.length !== 0) { -
- - - - - - - - - - - - - - - -
{{ 'labels.heading.Payment Type' | translate }} - {{ paymentFundSource.paymentTypeId | find: paymentTypeData : 'id' : 'name' }} - {{ 'labels.inputs.Fund Source' | translate }} - {{ paymentFundSource.fundSourceAccountId | find: assetAccountData : 'id' : 'name' }} - {{ 'labels.buttons.Actions' | translate }} - - -
-
- } -
- - @if (existCharges()) { - -
-
-

{{ 'labels.heading.Map Fees to Specific Income Accounts' | translate }}

-
- @if (feeToIncomeAccountMappings.value.length !== 0) { -
- - - - - - - - - - - - - - - -
{{ 'labels.catalogs.Fees' | translate }} - {{ feesIncome.chargeId | find: chargeData : 'id' : 'name' }} - {{ 'labels.inputs.Income Account' | translate }} - {{ feesIncome.incomeAccountId | find: incomeAccountData : 'id' : 'name' }} - {{ 'labels.buttons.Actions' | translate }} - - -
-
- } -
- - -
-
-

- {{ 'labels.catalogs.Penalties' | translate }} - {{ 'labels.heading.Map Penalties to Specific Income Accounts' | translate }} -

- + {{ section.pillLabel }} + + {{ row.label }} +
+ +
+ } + }
- @if (penaltyToIncomeAccountMappings.value.length !== 0) { -
- - - - - - - - - - - - - - - -
{{ 'labels.inputs.Penalty' | translate }} - {{ penaltyIncome.chargeId | find: penaltyData : 'id' : 'name' }} - {{ 'labels.inputs.Income Account' | translate }} - {{ penaltyIncome.incomeAccountId | find: incomeAccountData : 'id' : 'name' }} - {{ 'labels.buttons.Actions' | translate }} - - -
-
- } + {{ section.pillLabel }} + + +
+ +
+ + GL Code + + @for (glAccount of section.otherAccountOptions; track glAccount.id) { + + {{ '(' + glAccount.glCode + ') ' + glAccount.name }} + + } + + +
+
+ +
+ } +
} } - } + diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.scss index 74f18ffcf..bf75de1b0 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.scss @@ -6,8 +6,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +$border: #e2e8f0; +$gray-50: #f8f9fa; +$gray-200: #e8eaed; +$gray-700: #334155; +$gray-900: #202124; + .fdpa-section { - border: 1px solid #e2e8f0; + border: 1px solid $border; border-radius: 10px; overflow: hidden; background: #fff; @@ -15,45 +21,244 @@ & + & { margin-top: 16px; } +} + +.fdpa-section__header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + background: $gray-50; + border-bottom: 1px solid $gray-200; + + h3 { + margin: 0; + font-size: 14px; + font-weight: 600; + color: $gray-900; + } +} + +.fdpa-section__icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 6px; + background: #eff6ff; + color: #2563eb; + font-size: 13px; +} + +.fdpa-section__body { + padding: 16px 24px 20px; +} + +.fdpa-group { + & + & { + margin-top: 20px; + } &__header { display: flex; align-items: center; - gap: 10px; - padding: 12px 24px; - background: #f8f9fa; - border-bottom: 1px solid #e8eaed; + justify-content: space-between; + gap: 16px; + margin: 0 0 12px; + } - i { - font-size: 14px; - color: #0ea5e9; - } - - h3 { - margin: 0; - font-size: 14px; - font-weight: 600; - color: #202124; - } + h4 { + margin: 0 0 12px; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #94a3b8; } } -.fdpa-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 4px 16px; - padding: 16px 24px; +.fdpa-add-btn { + border: 0; + background: transparent; + color: #2563eb; + font-size: 12px; + font-weight: 700; + padding: 0; + cursor: pointer; +} - @media (width <= 600px) { +.fdpa-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.fdpa-list + .fdpa-list { + margin-top: 12px; +} + +.fdpa-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 10px 12px; + border: 1px solid $border; + border-radius: 8px; + background: #fff; + + @media (width <= 900px) { + flex-direction: column; + align-items: stretch; + } +} + +.fdpa-row:has(mifosx-gl-account-selector) { + align-items: stretch; +} + +.fdpa-row--other { + border-style: dashed; + border-color: #cbd5e1; + background: #f8fafc; + display: grid; + grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr); + align-items: center; + padding: 12px 16px; + + @media (width <= 1100px) { grid-template-columns: 1fr; } } -.fdpa-field { +.fdpa-other-row-wrap { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 12px; + + @media (width <= 1100px) { + grid-template-columns: 1fr; + gap: 8px; + } +} + +.fdpa-row__meta { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; + flex: 1 1 auto; +} + +.fdpa-row__meta--other { + min-width: 0; + flex: 1 1 auto; + align-self: stretch; +} + +.fdpa-row__label { + font-size: 14px; + font-weight: 600; + color: $gray-700; +} + +.fdpa-row__input { + flex: 0 0 22rem; + max-width: 22rem; + min-width: 18rem; + + @media (width <= 900px) { + width: 100%; + max-width: 100%; + min-width: 0; + } +} + +.fdpa-row__input :is(mifosx-gl-account-selector) { + width: 100%; + display: block; +} + +.fdpa-row__input :is(mifosx-gl-account-selector) :is(.layout-column) { + width: 100%; + margin-right: 0 !important; +} + +.fdpa-row__input :is(mifosx-gl-account-selector) :is(mat-form-field) { + width: 100%; +} + +.fdpa-row__actions { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; +} + +.fdpa-row__actions--other { + justify-content: flex-end; + align-self: stretch; width: 100%; - &--full { - grid-column: 1 / -1; + @media (width <= 1100px) { + justify-content: flex-start; + } +} + +.fdpa-row__name { + width: 100%; + min-width: 0; + max-width: 24rem; + height: 40px; + border: 1px solid #cbd5e1; + border-radius: 6px; + padding: 0 12px; + font-size: 14px; + background: #fff; + + @media (width <= 900px) { + max-width: 100%; + } +} + +.fdpa-row__name--other-account { + max-width: 100%; + flex: 1 1 auto; + height: 48px; +} + +.fdpa-row__other-field { + width: 100%; + min-width: 0; +} + +.fdpa-row__other-field :is(mat-form-field) { + width: 100%; +} + +.fdpa-row__other-field :is(.mat-mdc-form-field-required-marker) { + display: none; +} + +.fdpa-remove-btn { + border: 0; + background: transparent; + color: #ef4444; + font-size: 18px; + line-height: 1; + cursor: pointer; + padding: 0 4px; +} + +.fdpa-remove-btn--side { + place-self: center end; + margin-right: 4px; + + @media (width <= 1100px) { + justify-self: start; + margin-right: 0; } } @@ -61,23 +266,34 @@ display: flex; gap: 24px; flex-wrap: wrap; - - @media (width <= 600px) { - flex-direction: column; - } } -.fdpa-checkbox-row { - display: flex; +.fdpa-pill { + display: inline-flex; align-items: center; - padding: 4px 0; -} + justify-content: center; + padding: 2px 8px; + border-radius: 999px; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + white-space: nowrap; -.fdpa-add-btn { - height: 36px; - border-radius: 8px; - font-size: 13px; - font-weight: 600; + &--green { + background: #dcfce7; + color: #16a34a; + } + + &--amber { + background: #fef3c7; + color: #d97706; + } + + &--blue { + background: #dbeafe; + color: #2563eb; + } } .fdpa-subsection { @@ -95,13 +311,13 @@ margin: 0; font-size: 14px; font-weight: 600; - color: #202124; + color: $gray-900; } } } .fdpa-table-wrapper { - border: 1px solid #e2e8f0; + border: 1px solid $border; border-radius: 8px; overflow: hidden; @@ -109,3 +325,14 @@ width: 100%; } } + +@media (width <= 900px) { + .fdpa-row__meta { + margin-bottom: 4px; + } + + .fdpa-row__input { + flex: 1 1 auto; + max-width: 100%; + } +} diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.ts b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.ts index 6fa7c4100..34d17ab66 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.ts +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component.ts @@ -8,434 +8,391 @@ import { Component, Input, OnInit, inject } from '@angular/core'; import { + FormsModule, + ReactiveFormsModule, UntypedFormArray, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, - Validators, - ReactiveFormsModule + Validators } from '@angular/forms'; -import { MatDialog } from '@angular/material/dialog'; - -import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component'; -import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component'; - -import { TranslateService } from '@ngx-translate/core'; +import { MatRadioButton, MatRadioGroup } from '@angular/material/radio'; +import { GlAccountSelectorComponent } from 'app/shared/accounting/gl-account-selector/gl-account-selector.component'; import { Accounting } from 'app/core/utils/accounting'; -import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base'; -import { SelectBase } from 'app/shared/form-dialog/formfield/model/select-base'; -import { MatRadioGroup, MatRadioButton } from '@angular/material/radio'; -import { GlAccountSelectorComponent } from '../../../../shared/accounting/gl-account-selector/gl-account-selector.component'; -import { MatCheckbox } from '@angular/material/checkbox'; -import { MatButton, MatIconButton } from '@angular/material/button'; -import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { - MatTable, - MatColumnDef, - MatHeaderCellDef, - MatHeaderCell, - MatCellDef, - MatCell, - MatHeaderRowDef, - MatHeaderRow, - MatRowDef, - MatRow -} from '@angular/material/table'; -import { FindPipe } from '../../../../pipes/find.pipe'; +import { GLAccount } from 'app/shared/models/general.model'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +type AccountingSectionKey = 'assets' | 'liabilities' | 'expenses' | 'income'; + +type AccountingRowConfig = { + key: string; + label: string; + inputLabel: string; + controlName: string; + options: GLAccount[]; + required?: boolean; + accrualOnly?: boolean; +}; + +type AccountingSectionConfig = { + key: AccountingSectionKey; + title: string; + pillLabel: string; + pillClass: 'green' | 'amber' | 'blue'; + rows: AccountingRowConfig[]; + otherMappingsControlName: string; + otherAccountOptions: GLAccount[]; +}; + +type OtherGlMappingValue = { + accountName: string; + glAccountId: number | null; +}; + @Component({ selector: 'mifosx-fixed-deposit-product-accounting-step', templateUrl: './fixed-deposit-product-accounting-step.component.html', styleUrls: ['./fixed-deposit-product-accounting-step.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, + FormsModule, + ReactiveFormsModule, MatRadioGroup, MatRadioButton, - GlAccountSelectorComponent, - MatCheckbox, - FaIconComponent, - MatTable, - MatColumnDef, - MatHeaderCellDef, - MatHeaderCell, - MatCellDef, - MatCell, - MatIconButton, - MatHeaderRowDef, - MatHeaderRow, - MatRowDef, - MatRow, - FindPipe + GlAccountSelectorComponent ] }) export class FixedDepositProductAccountingStepComponent implements OnInit { private formBuilder = inject(UntypedFormBuilder); - dialog = inject(MatDialog); private accounting = inject(Accounting); - private translateService = inject(TranslateService); @Input() fixedDepositProductsTemplate: any; @Input() accountingRuleData: any; @Input() fixedDepositProductFormValid: boolean; fixedDepositProductAccountingForm: UntypedFormGroup; - - chargeData: any; - penaltyData: any; - paymentTypeData: any; - assetAccountData: any; - incomeAccountData: any; - expenseAccountData: any; - liabilityAccountData: any; - - paymentFundSourceDisplayedColumns: string[] = [ - 'paymentTypeId', - 'fundSourceAccountId', - 'actions' - ]; - feesPenaltyIncomeDisplayedColumns: string[] = [ - 'chargeId', - 'incomeAccountId', - 'actions' - ]; + assetAccountOptions: GLAccount[] = []; + liabilityAccountOptions: GLAccount[] = []; + expenseAccountOptions: GLAccount[] = []; + incomeAccountOptions: GLAccount[] = []; + accountingSectionConfigs: AccountingSectionConfig[] = []; constructor() { - this.createfixedDepositProductAccountingForm(); + this.createFixedDepositProductAccountingForm(); this.setConditionalControls(); } ngOnInit() { - this.chargeData = this.fixedDepositProductsTemplate.chargeOptions || []; - this.penaltyData = this.fixedDepositProductsTemplate.penaltyOptions || []; - this.paymentTypeData = this.fixedDepositProductsTemplate.paymentTypeOptions || []; - this.assetAccountData = this.fixedDepositProductsTemplate.accountingMappingOptions.assetAccountOptions || []; - this.incomeAccountData = this.fixedDepositProductsTemplate.accountingMappingOptions.incomeAccountOptions || []; - this.expenseAccountData = this.fixedDepositProductsTemplate.accountingMappingOptions.expenseAccountOptions || []; - this.liabilityAccountData = - this.fixedDepositProductsTemplate.accountingMappingOptions.liabilityAccountOptions || []; - if (!(this.fixedDepositProductsTemplate === undefined) && this.fixedDepositProductsTemplate.id) { - this.assignAccountingStepData(); - } + const accountingMappings = this.fixedDepositProductsTemplate?.accountingMappings || {}; + const mappings = { + ...accountingMappings, + otherGlMappingsMap: this.fixedDepositProductsTemplate?.otherGlMappingsMap + }; + const accountingMappingOptions = this.fixedDepositProductsTemplate?.accountingMappingOptions || {}; + const accountingRuleId = 2; // Always Cash + + this.assetAccountOptions = accountingMappingOptions.assetAccountOptions || []; + this.liabilityAccountOptions = accountingMappingOptions.liabilityAccountOptions || []; + this.expenseAccountOptions = accountingMappingOptions.expenseAccountOptions || []; + this.incomeAccountOptions = accountingMappingOptions.incomeAccountOptions || []; + + this.buildAccountingSectionConfigs(); + + this.fixedDepositProductAccountingForm.patchValue({ accountingRule: accountingRuleId }); + this.syncAccountingControls(accountingRuleId); + this.patchAccountingValues(mappings); } - assignAccountingStepData() { - this.fixedDepositProductAccountingForm.patchValue({ - accountingRule: this.fixedDepositProductsTemplate.accountingRule.id - }); - if (this.isCashOrAccrualAccounting()) { - this.fixedDepositProductAccountingForm.patchValue({ - savingsReferenceAccountId: this.fixedDepositProductsTemplate.accountingMappings.savingsReferenceAccount.id, - savingsControlAccountId: this.fixedDepositProductsTemplate.accountingMappings.savingsControlAccount.id, - transfersInSuspenseAccountId: - this.fixedDepositProductsTemplate.accountingMappings.transfersInSuspenseAccount.id, - incomeFromFeeAccountId: this.fixedDepositProductsTemplate.accountingMappings.incomeFromFeeAccount.id, - incomeFromPenaltyAccountId: this.fixedDepositProductsTemplate.accountingMappings.incomeFromPenaltyAccount.id, - interestOnSavingsAccountId: this.fixedDepositProductsTemplate.accountingMappings.interestOnSavingsAccount.id - }); - - if (this.isAccrualAccounting()) { - this.fixedDepositProductAccountingForm.patchValue({ - feesReceivableAccountId: this.fixedDepositProductsTemplate.accountingMappings.feeReceivableAccount.id, - penaltiesReceivableAccountId: - this.fixedDepositProductsTemplate.accountingMappings.penaltyReceivableAccount.id, - interestPayableAccountId: this.fixedDepositProductsTemplate.accountingMappings.interestPayableAccount.id - }); - } - - if ( - this.fixedDepositProductsTemplate.paymentChannelToFundSourceMappings || - this.fixedDepositProductsTemplate.feeToIncomeAccountMappings || - this.fixedDepositProductsTemplate.penaltyToIncomeAccountMappings - ) { - this.fixedDepositProductAccountingForm.patchValue({ - advancedAccountingRules: true - }); - } - if (this.fixedDepositProductsTemplate.paymentChannelToFundSourceMappings) { - this.fixedDepositProductsTemplate.paymentChannelToFundSourceMappings.forEach( - (paymentChannelToFundSourceMapping: any) => { - const paymentChannelToFundSourceMappingData = this.formBuilder.group({ - paymentTypeId: [ - paymentChannelToFundSourceMapping.paymentType.id, - Validators.required - ], - fundSourceAccountId: [ - paymentChannelToFundSourceMapping.fundSourceAccount.id, - Validators.required - ] - }); - const formArray = this.fixedDepositProductAccountingForm.controls[ - 'paymentChannelToFundSourceMappings' - ] as UntypedFormArray; - formArray.push(paymentChannelToFundSourceMappingData); - } - ); - } - if (this.fixedDepositProductsTemplate.feeToIncomeAccountMappings) { - this.fixedDepositProductsTemplate.feeToIncomeAccountMappings.forEach((feeToIncomeAccountMapping: any) => { - const feeToIncomeAccountMappingData = this.formBuilder.group({ - chargeId: [ - feeToIncomeAccountMapping.charge.id, - Validators.required - ], - incomeAccountId: [ - feeToIncomeAccountMapping.incomeAccount.id, - Validators.required - ] - }); - const formArray = this.fixedDepositProductAccountingForm.controls[ - 'feeToIncomeAccountMappings' - ] as UntypedFormArray; - formArray.push(feeToIncomeAccountMappingData); - }); - } - if (this.fixedDepositProductsTemplate.penaltyToIncomeAccountMappings) { - this.fixedDepositProductsTemplate.penaltyToIncomeAccountMappings.forEach( - (penaltyToIncomeAccountMapping: any) => { - const penaltyToIncomeAccountMappingData = this.formBuilder.group({ - chargeId: [ - penaltyToIncomeAccountMapping.charge.id, - Validators.required - ], - incomeAccountId: [ - penaltyToIncomeAccountMapping.incomeAccount.id, - Validators.required - ] - }); - const formArray = this.fixedDepositProductAccountingForm.controls[ - 'penaltyToIncomeAccountMappings' - ] as UntypedFormArray; - formArray.push(penaltyToIncomeAccountMappingData); - } - ); - } - } - } - - createfixedDepositProductAccountingForm() { + createFixedDepositProductAccountingForm() { this.fixedDepositProductAccountingForm = this.formBuilder.group({ - accountingRule: [1] + accountingRule: [2], + assetsOtherGlMappings: this.formBuilder.array([]), + liabilitiesOtherGlMappings: this.formBuilder.array([]), + expensesOtherGlMappings: this.formBuilder.array([]), + incomeOtherGlMappings: this.formBuilder.array([]) }); } - existCharges(): boolean { - return this.chargeData.length > 0; - } - - setConditionalControls() { - this.fixedDepositProductAccountingForm.get('accountingRule').valueChanges.subscribe((accountingRule: any) => { - if (accountingRule === 2 || accountingRule === 3) { - this.fixedDepositProductAccountingForm.addControl( - 'savingsReferenceAccountId', - new UntypedFormControl('', Validators.required) - ); - this.fixedDepositProductAccountingForm.addControl( - 'savingsControlAccountId', - new UntypedFormControl('', Validators.required) - ); - this.fixedDepositProductAccountingForm.addControl( - 'transfersInSuspenseAccountId', - new UntypedFormControl('', Validators.required) - ); - this.fixedDepositProductAccountingForm.addControl( - 'interestOnSavingsAccountId', - new UntypedFormControl('', Validators.required) - ); - this.fixedDepositProductAccountingForm.addControl( - 'incomeFromFeeAccountId', - new UntypedFormControl('', Validators.required) - ); - this.fixedDepositProductAccountingForm.addControl( - 'incomeFromPenaltyAccountId', - new UntypedFormControl('', Validators.required) - ); - this.fixedDepositProductAccountingForm.addControl('advancedAccountingRules', new UntypedFormControl(false)); - - if (accountingRule === 3) { - this.fixedDepositProductAccountingForm.addControl( - 'feesReceivableAccountId', - new UntypedFormControl('', Validators.required) - ); - this.fixedDepositProductAccountingForm.addControl( - 'penaltiesReceivableAccountId', - new UntypedFormControl('', Validators.required) - ); - this.fixedDepositProductAccountingForm.addControl( - 'interestPayableAccountId', - new UntypedFormControl('', Validators.required) - ); - } - - this.fixedDepositProductAccountingForm - .get('advancedAccountingRules') - .valueChanges.subscribe((advancedAccountingRules: boolean) => { - if (advancedAccountingRules) { - this.fixedDepositProductAccountingForm.addControl( - 'paymentChannelToFundSourceMappings', - this.formBuilder.array([]) - ); - this.fixedDepositProductAccountingForm.addControl( - 'feeToIncomeAccountMappings', - this.formBuilder.array([]) - ); - this.fixedDepositProductAccountingForm.addControl( - 'penaltyToIncomeAccountMappings', - this.formBuilder.array([]) - ); - } else { - this.fixedDepositProductAccountingForm.removeControl('paymentChannelToFundSourceMappings'); - this.fixedDepositProductAccountingForm.removeControl('feeToIncomeAccountMappings'); - this.fixedDepositProductAccountingForm.removeControl('penaltyToIncomeAccountMappings'); - } - }); - } else { - this.fixedDepositProductAccountingForm.removeControl('savingsReferenceAccountId'); - this.fixedDepositProductAccountingForm.removeControl('overdraftPortfolioControlId'); - this.fixedDepositProductAccountingForm.removeControl('savingsControlAccountId'); - this.fixedDepositProductAccountingForm.removeControl('transfersInSuspenseAccountId'); - this.fixedDepositProductAccountingForm.removeControl('interestOnSavingsAccountId'); - this.fixedDepositProductAccountingForm.removeControl('writeOffAccountId'); - this.fixedDepositProductAccountingForm.removeControl('incomeFromFeeAccountId'); - this.fixedDepositProductAccountingForm.removeControl('incomeFromPenaltyAccountId'); - this.fixedDepositProductAccountingForm.removeControl('incomeFromInterestId'); - this.fixedDepositProductAccountingForm.removeControl('advancedAccountingRules'); - this.fixedDepositProductAccountingForm.removeControl('escheatLiabilityId'); - this.fixedDepositProductAccountingForm.removeControl('feesReceivableAccountId'); - this.fixedDepositProductAccountingForm.removeControl('penaltiesReceivableAccountId'); - this.fixedDepositProductAccountingForm.removeControl('interestPayableAccountId'); - } + private setConditionalControls() { + this.fixedDepositProductAccountingForm.get('accountingRule')?.valueChanges.subscribe((rule: number) => { + this.syncAccountingControls(rule); }); } - get paymentChannelToFundSourceMappings(): UntypedFormArray { - return this.fixedDepositProductAccountingForm.get('paymentChannelToFundSourceMappings') as UntypedFormArray; - } - - get feeToIncomeAccountMappings(): UntypedFormArray { - return this.fixedDepositProductAccountingForm.get('feeToIncomeAccountMappings') as UntypedFormArray; - } - - get penaltyToIncomeAccountMappings(): UntypedFormArray { - return this.fixedDepositProductAccountingForm.get('penaltyToIncomeAccountMappings') as UntypedFormArray; - } - - add(formType: string, formArray: UntypedFormArray) { - const data = { ...this.getData(formType), pristine: false }; - const dialogRef = this.dialog.open(FormDialogComponent, { data }); - dialogRef.afterClosed().subscribe((response: any) => { - if (response.data) { - formArray.push(response.data); - } + private patchAccountingValues(mappings: any) { + this.fixedDepositProductAccountingForm.patchValue({ + savingsReferenceAccountId: mappings.savingsReferenceAccount?.id || '', + savingsControlAccountId: mappings.savingsControlAccount?.id || '', + transfersInSuspenseAccountId: mappings.transfersInSuspenseAccount?.id || '', + interestOnSavingsAccountId: mappings.interestOnSavingsAccount?.id || '', + incomeFromFeeAccountId: mappings.incomeFromFeeAccount?.id || '', + incomeFromPenaltyAccountId: mappings.incomeFromPenaltyAccount?.id || '', + feesReceivableAccountId: mappings.feeReceivableAccount?.id || '', + penaltiesReceivableAccountId: mappings.penaltyReceivableAccount?.id || '', + interestPayableAccountId: mappings.interestPayableAccount?.id || '' }); - } - edit(formType: string, formArray: UntypedFormArray, index: number) { - const data = { ...this.getData(formType, formArray.at(index).value), layout: { addButtonText: 'Edit' } }; - const dialogRef = this.dialog.open(FormDialogComponent, { data }); - dialogRef.afterClosed().subscribe((response: any) => { - if (response.data) { - formArray.at(index).patchValue(response.data.value); - } - }); - } - - delete(formArray: UntypedFormArray, index: number) { - const dialogRef = this.dialog.open(DeleteDialogComponent, { - data: { deleteContext: this.translateService.instant('labels.text.this') } - }); - dialogRef.afterClosed().subscribe((response: any) => { - if (response.delete) { - formArray.removeAt(index); - } - }); - } - - getData(formType: string, values?: any) { - switch (formType) { - case 'PaymentFundSource': - return { - title: 'Configure Fund Sources for Payment Channels', - formfields: this.getPaymentFundSourceFormfields(values) - }; - case 'FeesIncome': - return { title: 'Map Fees to Income Accounts', formfields: this.getFeesIncomeFormfields(values) }; - case 'PenaltyIncome': - return { - title: 'Map Penalties to Specific Income Accounts', - formfields: this.getPenaltyIncomeFormfields(values) - }; + if (this.isCashOrAccrualAccounting()) { + const glMappings = mappings.otherGlMappingsMap || mappings.otherGlMappings; + this.setOtherGlMappings('assetsOtherGlMappings', glMappings?.assets || [], this.assetAccountOptions); + this.setOtherGlMappings( + 'liabilitiesOtherGlMappings', + glMappings?.liabilities || [], + this.liabilityAccountOptions + ); + this.setOtherGlMappings('expensesOtherGlMappings', glMappings?.expenses || [], this.expenseAccountOptions); + this.setOtherGlMappings('incomeOtherGlMappings', glMappings?.income || [], this.incomeAccountOptions); } } - getPaymentFundSourceFormfields(values?: any) { - const formfields: FormfieldBase[] = [ - new SelectBase({ - controlName: 'paymentTypeId', - label: 'Payment Type', - value: values ? values.paymentTypeId : this.paymentTypeData[0].id, - options: { label: 'name', value: 'id', data: this.paymentTypeData }, - required: true, - order: 1 - }), - new SelectBase({ - controlName: 'fundSourceAccountId', - label: 'Fund Source', - value: values ? values.fundSourceAccountId : this.assetAccountData[0].id, - options: { label: 'name', value: 'id', data: this.assetAccountData }, - required: true, - order: 2 - }) - ]; - return formfields; + private createOtherGlMappingGroup(otherGlMapping: Partial = {}) { + return this.formBuilder.group({ + accountName: [ + otherGlMapping.accountName || '', + Validators.required + ], + glAccountId: [ + otherGlMapping.glAccountId || '', + Validators.required + ] + }); } - getFeesIncomeFormfields(values?: any) { - const formfields: FormfieldBase[] = [ - new SelectBase({ - controlName: 'chargeId', - label: 'Fees', - value: values ? values.chargeId : this.chargeData[0].id, - options: { label: 'name', value: 'id', data: this.chargeData }, - required: true, - order: 1 - }), - new SelectBase({ - controlName: 'incomeAccountId', - label: 'Income Account', - value: values ? values.incomeAccountId : this.incomeAccountData[0].id, - options: { label: 'name', value: 'id', data: this.incomeAccountData }, - required: true, - order: 2 - }) - ]; - return formfields; + getOtherGlMappingsArray(controlName: string): UntypedFormArray { + return this.fixedDepositProductAccountingForm.get(controlName) as UntypedFormArray; } - getPenaltyIncomeFormfields(values?: any) { - const formfields: FormfieldBase[] = [ - new SelectBase({ - controlName: 'chargeId', - label: 'Penalty', - value: values ? values.chargeId : this.penaltyData[0].id, - options: { label: 'name', value: 'id', data: this.penaltyData }, - required: true, - order: 1 - }), - new SelectBase({ - controlName: 'incomeAccountId', - label: 'Income Account', - value: values ? values.incomeAccountId : this.incomeAccountData[0].id, - options: { label: 'name', value: 'id', data: this.incomeAccountData }, - required: true, - order: 2 - }) + private setOtherGlMappings(controlName: string, otherGlMappings: any[], options: GLAccount[]) { + const arr = this.getOtherGlMappingsArray(controlName); + arr.clear(); + (otherGlMappings || []).forEach((m) => { + arr.push( + this.createOtherGlMappingGroup({ + accountName: m.accountName, + glAccountId: m.glAccountId || options.find((o) => o.glCode === m.glCode)?.id || null + }) + ); + }); + } + + addOtherGlMapping(controlName: string) { + this.getOtherGlMappingsArray(controlName).push(this.createOtherGlMappingGroup()); + } + + removeOtherGlMapping(controlName: string, index: number) { + this.getOtherGlMappingsArray(controlName).removeAt(index); + } + + getOtherGlMappingControl(controlName: string, index: number, fieldName: string): UntypedFormControl { + return this.getOtherGlMappingsArray(controlName).at(index).get(fieldName) as UntypedFormControl; + } + + private clearOtherGlMappings() { + [ + 'assetsOtherGlMappings', + 'liabilitiesOtherGlMappings', + 'expensesOtherGlMappings', + 'incomeOtherGlMappings' + ].forEach((c) => this.getOtherGlMappingsArray(c).clear()); + } + + private getOtherGlMappings() { + const map = (controlName: string, options: GLAccount[]) => + this.getOtherGlMappingsArray(controlName) + .value.filter((item: OtherGlMappingValue) => !!item.accountName || !!item.glAccountId) + .map((item: OtherGlMappingValue) => ({ + accountName: item.accountName, + glCode: options.find((o) => o.id === item.glAccountId)?.glCode || '' + })) + .filter((item: { accountName: string; glCode: string }) => !!item.accountName || !!item.glCode); + + const otherGlMappings = { + assets: map('assetsOtherGlMappings', this.assetAccountOptions), + liabilities: map('liabilitiesOtherGlMappings', this.liabilityAccountOptions), + expenses: map('expensesOtherGlMappings', this.expenseAccountOptions), + income: map('incomeOtherGlMappings', this.incomeAccountOptions) + }; + return Object.values(otherGlMappings).some((v) => v.length) ? otherGlMappings : null; + } + + private syncAccountingControls(accountingRule: number) { + const addControl = (name: string, required = false) => { + if (!this.fixedDepositProductAccountingForm.contains(name)) { + this.fixedDepositProductAccountingForm.addControl( + name, + new UntypedFormControl('', required ? Validators.required : undefined) + ); + } else if (required) { + this.fixedDepositProductAccountingForm.get(name)?.setValidators(Validators.required); + this.fixedDepositProductAccountingForm.get(name)?.updateValueAndValidity({ emitEvent: false }); + } + }; + const removeControl = (name: string) => { + if (this.fixedDepositProductAccountingForm.contains(name)) { + this.fixedDepositProductAccountingForm.removeControl(name); + } + }; + + if (this.accounting.isCashOrAccrualAccountingRuleId(accountingRule)) { + addControl('savingsReferenceAccountId', true); + addControl('savingsControlAccountId', true); + addControl('transfersInSuspenseAccountId', true); + addControl('interestOnSavingsAccountId', true); + addControl('incomeFromFeeAccountId', true); + addControl('incomeFromPenaltyAccountId', true); + + if (this.accounting.isAccrualAccountingRuleId(accountingRule)) { + addControl('feesReceivableAccountId', true); + addControl('penaltiesReceivableAccountId', true); + addControl('interestPayableAccountId', true); + } else { + removeControl('feesReceivableAccountId'); + removeControl('penaltiesReceivableAccountId'); + removeControl('interestPayableAccountId'); + } + } else { + [ + 'savingsReferenceAccountId', + 'savingsControlAccountId', + 'transfersInSuspenseAccountId', + 'interestOnSavingsAccountId', + 'incomeFromFeeAccountId', + 'incomeFromPenaltyAccountId', + 'feesReceivableAccountId', + 'penaltiesReceivableAccountId', + 'interestPayableAccountId' + ].forEach(removeControl); + this.clearOtherGlMappings(); + } + } + + buildAccountingSectionConfigs() { + this.accountingSectionConfigs = [ + { + key: 'assets', + title: 'Assets', + pillLabel: 'Asset', + pillClass: 'green', + rows: [ + { + key: 'savingsReferenceAccountId', + label: 'Savings Reference Account', + inputLabel: 'Savings reference account', + controlName: 'savingsReferenceAccountId', + options: this.assetAccountOptions, + required: true + }, + { + key: 'feesReceivableAccountId', + label: 'Fees Receivable Account', + inputLabel: 'Fees receivable account', + controlName: 'feesReceivableAccountId', + options: this.assetAccountOptions, + accrualOnly: true + }, + { + key: 'penaltiesReceivableAccountId', + label: 'Penalties Receivable Account', + inputLabel: 'Penalties receivable account', + controlName: 'penaltiesReceivableAccountId', + options: this.assetAccountOptions, + accrualOnly: true + } + ], + otherMappingsControlName: 'assetsOtherGlMappings', + otherAccountOptions: this.assetAccountOptions + }, + { + key: 'liabilities', + title: 'Liabilities', + pillLabel: 'Liability', + pillClass: 'green', + rows: [ + { + key: 'savingsControlAccountId', + label: 'Savings Control Account', + inputLabel: 'Savings control account', + controlName: 'savingsControlAccountId', + options: this.liabilityAccountOptions, + required: true + }, + { + key: 'transfersInSuspenseAccountId', + label: 'Transfers In Suspense Account', + inputLabel: 'Transfers in suspense account', + controlName: 'transfersInSuspenseAccountId', + options: this.liabilityAccountOptions, + required: true + }, + { + key: 'interestPayableAccountId', + label: 'Interest Payable Account', + inputLabel: 'Interest payable account', + controlName: 'interestPayableAccountId', + options: this.liabilityAccountOptions, + accrualOnly: true + } + ], + otherMappingsControlName: 'liabilitiesOtherGlMappings', + otherAccountOptions: this.liabilityAccountOptions + }, + { + key: 'expenses', + title: 'Expenses', + pillLabel: 'Expense', + pillClass: 'amber', + rows: [ + { + key: 'interestOnSavingsAccountId', + label: 'Interest On Savings Account', + inputLabel: 'Interest on savings account', + controlName: 'interestOnSavingsAccountId', + options: this.expenseAccountOptions, + required: true + } + ], + otherMappingsControlName: 'expensesOtherGlMappings', + otherAccountOptions: this.expenseAccountOptions + }, + { + key: 'income', + title: 'Income', + pillLabel: 'Income', + pillClass: 'blue', + rows: [ + { + key: 'incomeFromFeeAccountId', + label: 'Income From Fees', + inputLabel: 'Income from fees', + controlName: 'incomeFromFeeAccountId', + options: this.incomeAccountOptions, + required: true + }, + { + key: 'incomeFromPenaltyAccountId', + label: 'Income From Penalties', + inputLabel: 'Income from penalties', + controlName: 'incomeFromPenaltyAccountId', + options: this.incomeAccountOptions, + required: true + } + ], + otherMappingsControlName: 'incomeOtherGlMappings', + otherAccountOptions: this.incomeAccountOptions + } ]; - return formfields; } get fixedDepositProductAccounting() { - return this.fixedDepositProductAccountingForm.value; + const { + assetsOtherGlMappings, + liabilitiesOtherGlMappings, + expensesOtherGlMappings, + incomeOtherGlMappings, + ...accounting + } = this.fixedDepositProductAccountingForm.value; + const otherGlMappings = this.getOtherGlMappings(); + return { ...accounting, ...(otherGlMappings ? { otherGlMappings } : {}) }; } isCashOrAccrualAccounting(): boolean { diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.html b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.html index a8de6dfc4..2a806a267 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.html +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.html @@ -8,22 +8,21 @@
- -

{{ 'labels.inputs.Charge' | translate }}

+ +

{{ 'labels.heading.Charges' | translate }}

-
- - {{ 'labels.inputs.Charge' | translate }} - - @for (charge of chargeData | chargesFilter: chargesDataSource : currencyCode.value; track charge) { - - {{ charge.name }} - - } - - - -
+
+
+ + {{ 'labels.inputs.Charge' | translate }} + + @for (charge of chargeData | chargesFilter: chargesDataSource : currencyCode.value; track charge) { + + {{ charge.name }} + + } + +
+ + @if (chargesDataSource.length > 0) { +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'labels.inputs.Name' | translate }} + {{ charge.name + ', ' + charge.currency.displaySymbol }} + {{ 'labels.inputs.Type' | translate }} + {{ charge.chargeCalculationType.value }} + {{ 'labels.inputs.Amount' | translate }} + {{ charge.amount | formatNumber }} + {{ 'labels.inputs.Collected On' | translate }} + {{ charge.chargeTimeType.value }} + {{ 'labels.inputs.Actions' | translate }} + +
+
+ }
- - @if (chargesDataSource.length > 0) { -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ 'labels.inputs.Name' | translate }} - {{ charge.name + ', ' + charge.currency.displaySymbol }} - {{ 'labels.inputs.Type' | translate }} - {{ charge.chargeCalculationType.value }} - {{ 'labels.inputs.Amount' | translate }} - {{ charge.amount | formatNumber }} - {{ 'labels.inputs.Collected On' | translate }} - {{ charge.chargeTimeType.value }} - {{ 'labels.inputs.Actions' | translate }} - -
-
- }
diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.scss index ccfe94921..21e98cf16 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component.scss @@ -12,10 +12,6 @@ overflow: hidden; background: #fff; - & + & { - margin-top: 16px; - } - &__header { display: flex; align-items: center; @@ -36,30 +32,22 @@ color: #202124; } } -} -.fdpch-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 4px 16px; - padding: 16px 24px; - - @media (width <= 600px) { - grid-template-columns: 1fr; + &__body { + padding: 16px 24px; } } -.fdpch-field { - width: 100%; - - &--full { - grid-column: 1 / -1; - } -} - -.fdpch-btn-wrap { +.fdpch-add-row { display: flex; align-items: center; + gap: 16px; + flex-wrap: wrap; + + &__select { + flex: 1; + min-width: 200px; + } } .fdpch-add-btn { @@ -73,7 +61,7 @@ border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; - margin: 0 24px 16px; + margin-top: 12px; table { width: 100%; diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.html b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.html index 68ed1a8f3..df8a5251d 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.html +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.html @@ -10,60 +10,102 @@
-

{{ 'labels.inputs.Currency' | translate }}

+ Currency & Account Status Configuration
-
- - {{ 'labels.inputs.Currency' | translate }} - - @for (currency of currencyData; track currency) { - - {{ currency.name }} - - } - - - {{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - - - - {{ 'labels.inputs.Decimal Places' | translate }} - - - {{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - - {{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.Minimum Value must be' | translate }} - 0 - - - -
- - {{ 'labels.inputs.Set the fixed deposit installment in multiples of' | translate }} - -
- - @if (fixedDepositProductCurrencyForm.value.setMultiples) { +
+
- {{ 'labels.inputs.Currency in multiples of' | translate }} - + {{ 'labels.inputs.Currency' | translate }} + + @for (currency of currencyData; track currency) { + + {{ currency.name }} + + } + + Default: Indian Rupee - auto-set for domestic products - {{ 'labels.inputs.Currency in multiples of' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }} {{ 'labels.commons.required' | translate }} - } + + + {{ 'labels.inputs.Decimal Places' | translate }} + + @for (places of decimalPlacesData; track places) { + {{ places }} + } + + + {{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + +
+ +
+
+

Dormancy Alert

+

Enable automated notifications when an account becomes inactive based on product rules.

+
+ + yes + no + +
+ +
+
+

Balance Swap Option

+

Enable automated transfer of excess funds to linked deposits or vice-versa to maintain liquidity.

+
+ + yes + no + +
+ +
+
+

MSME

+

Micro, Small and Medium Enterprises

+
+ + yes + no + +
+ +
+
+

Nomination Facility

+

Allow account holders to designate individuals to receive funds in the event of account holder's death.

+
+ + yes + no + +
+ +
+
+

Auto Debit Facility

+

Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.

+
+ + yes + no + +
diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.scss index fe1c47dc5..f32a534a6 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.scss @@ -6,8 +6,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +$accent: #0ea5e9; +$border: #e2e8f0; +$gray-50: #f8f9fa; +$gray-200: #e8eaed; +$gray-900: #202124; + .fdpc-section { - border: 1px solid #e2e8f0; + border: 1px solid $border; border-radius: 10px; overflow: hidden; background: #fff; @@ -21,23 +27,27 @@ align-items: center; gap: 10px; padding: 12px 24px; - background: #f8f9fa; - border-bottom: 1px solid #e8eaed; + background: $gray-50; + border-bottom: 1px solid $gray-200; i { font-size: 14px; - color: #0ea5e9; + color: $accent; } - h3 { + span { margin: 0; font-size: 14px; font-weight: 600; - color: #202124; + color: $gray-900; } } } +.fdpc-body { + background: #fff; +} + .fdpc-grid { display: grid; grid-template-columns: repeat(2, 1fr); @@ -51,14 +61,43 @@ .fdpc-field { width: 100%; - - &--full { - grid-column: 1 / -1; - } } -.fdpc-checkbox-row { +.fdpc-alert { display: flex; align-items: center; - padding: 4px 0; + justify-content: space-between; + gap: 24px; + padding: 18px 24px 24px; + border-top: 1px solid $border; + background: #f8fafc; + + @media (width <= 760px) { + flex-direction: column; + align-items: flex-start; + } + + &__content { + h4 { + margin: 0 0 6px; + font-size: 14px; + font-weight: 600; + color: $gray-900; + } + + p { + margin: 0; + font-size: 12px; + line-height: 1.5; + color: #64748b; + max-width: 540px; + } + } + + &__choices { + display: flex; + align-items: center; + gap: 18px; + flex-wrap: wrap; + } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.ts b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.ts index 3fb6c6f2d..6e0466dfa 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.ts +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component.ts @@ -6,12 +6,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component, OnInit, Input, inject, DestroyRef } from '@angular/core'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { startWith } from 'rxjs/operators'; -import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; -import { MatCheckbox } from '@angular/material/checkbox'; +import { Component, OnInit, Input, inject } from '@angular/core'; +import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; import { MatTooltip } from '@angular/material/tooltip'; +import { MatRadioGroup, MatRadioButton } from '@angular/material/radio'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @Component({ @@ -21,18 +19,23 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; imports: [ ...STANDALONE_SHARED_IMPORTS, MatTooltip, - MatCheckbox + MatRadioGroup, + MatRadioButton ] }) export class FixedDepositProductCurrencyStepComponent implements OnInit { private formBuilder = inject(UntypedFormBuilder); - private destroyRef = inject(DestroyRef); @Input() fixedDepositProductsTemplate: any; fixedDepositProductCurrencyForm: UntypedFormGroup; currencyData: any; + decimalPlacesData = [ + 2, + 0, + 3 + ]; constructor() { this.createFixedDepositProductCurrencyForm(); @@ -42,13 +45,17 @@ export class FixedDepositProductCurrencyStepComponent implements OnInit { this.currencyData = this.fixedDepositProductsTemplate.currencyOptions; this.fixedDepositProductCurrencyForm.patchValue({ - currencyCode: this.fixedDepositProductsTemplate.currency?.code || this.currencyData[0].code, - digitsAfterDecimal: this.fixedDepositProductsTemplate.digitsAfterDecimal ?? '', - setMultiples: !!this.fixedDepositProductsTemplate.inMultiplesOf, - inMultiplesOf: this.fixedDepositProductsTemplate.inMultiplesOf ?? '' + currencyCode: this.fixedDepositProductsTemplate.currency?.code || this.currencyData?.[0]?.code || '', + digitsAfterDecimal: + this.fixedDepositProductsTemplate.digitsAfterDecimal ?? + this.fixedDepositProductsTemplate.currency?.decimalPlaces ?? + 2, + dormancyAlert: this.fixedDepositProductsTemplate.dormancyAlert ?? false, + balanceSwapOption: this.fixedDepositProductsTemplate.balanceSwapOption ?? false, + msme: this.fixedDepositProductsTemplate.msme ?? false, + nominationFacility: this.fixedDepositProductsTemplate.nominationFacility ?? false, + autoDebitFacility: this.fixedDepositProductsTemplate.autoDebitFacility ?? false }); - - this.setupConditionalValidation(); } createFixedDepositProductCurrencyForm() { @@ -58,48 +65,30 @@ export class FixedDepositProductCurrencyStepComponent implements OnInit { Validators.required ], digitsAfterDecimal: [ - '', + 2, [ Validators.required, Validators.min(0) ] ], - setMultiples: [false], - inMultiplesOf: [''] + dormancyAlert: [false], + balanceSwapOption: [false], + msme: [false], + nominationFacility: [false], + autoDebitFacility: [false] }); } - setupConditionalValidation() { - const inMultiplesOfControl = this.fixedDepositProductCurrencyForm.get('inMultiplesOf'); - const setMultiplesControl = this.fixedDepositProductCurrencyForm.get('setMultiples'); - - setMultiplesControl?.valueChanges - .pipe(startWith(setMultiplesControl.value), takeUntilDestroyed(this.destroyRef)) - .subscribe((checked) => { - if (checked) { - inMultiplesOfControl?.setValidators([ - Validators.required, - Validators.min(1) - ]); - } else { - inMultiplesOfControl?.clearValidators(); - inMultiplesOfControl?.setValue(''); - } - inMultiplesOfControl?.updateValueAndValidity(); - }); - } - get fixedDepositProductCurrency() { const formValue = this.fixedDepositProductCurrencyForm.value; - const result: any = { + return { currencyCode: formValue.currencyCode, - digitsAfterDecimal: formValue.digitsAfterDecimal + digitsAfterDecimal: formValue.digitsAfterDecimal, + dormancyAlert: formValue.dormancyAlert, + balanceSwapOption: formValue.balanceSwapOption, + msme: formValue.msme, + nominationFacility: formValue.nominationFacility, + autoDebitFacility: formValue.autoDebitFacility }; - - if (formValue.inMultiplesOf !== '' && formValue.inMultiplesOf !== null && formValue.inMultiplesOf !== undefined) { - result.inMultiplesOf = formValue.inMultiplesOf; - } - - return result; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.html b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.html index 92f540da7..98add2dc8 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.html +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.html @@ -9,13 +9,18 @@
- +

{{ 'labels.inputs.Product Name' | translate }} & {{ 'labels.inputs.Description' | translate }}

{{ 'labels.inputs.Product Name' | translate }} - + @if (fixedDepositProductDetailsForm.controls.name.hasError('required')) { {{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }} @@ -26,7 +31,13 @@ {{ 'labels.inputs.Short Name' | translate }} - + @if (fixedDepositProductDetailsForm.controls.shortName.hasError('required')) { {{ 'labels.inputs.Short Name' | translate }} {{ 'labels.commons.is' | translate }} @@ -37,14 +48,80 @@ {{ 'labels.inputs.Description' | translate }} - - @if (fixedDepositProductDetailsForm.controls.description.hasError('required')) { - - {{ 'labels.inputs.Description' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - } +
+ +
+
+ +

Product Classification

+
+ +
+ + Account Type + + @for (accountType of accountTypeData; track accountType) { + {{ accountType }} + } + + Account Type is required + + + + Customer Segment + + @for (customerSegment of customerSegmentData; track customerSegment) { + {{ customerSegment }} + } + + + + + GL Account (Liability) + + @for (account of liabilityAccountOptions; track account.id) { + {{ account.name }} ({{ account.glCode }}) + } + + + + + Effective To + + + + + + + End Date + + + + + + + Introducer Required + + @for (option of yesNoOptions; track option.value) { + {{ option.label }} + } + + + +
+ Deposit Account Link +
+
+
diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.scss index 7747019fc..59c20ba02 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.scss @@ -6,8 +6,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +$accent: #0ea5e9; +$border: #e2e8f0; +$gray-50: #f8f9fa; +$gray-200: #e8eaed; +$gray-900: #202124; + .fdpd-section { - border: 1px solid #e2e8f0; + border: 1px solid $border; border-radius: 10px; overflow: hidden; background: #fff; @@ -21,19 +27,19 @@ align-items: center; gap: 10px; padding: 12px 24px; - background: #f8f9fa; - border-bottom: 1px solid #e8eaed; + background: $gray-50; + border-bottom: 1px solid $gray-200; i { font-size: 14px; - color: #0ea5e9; + color: $accent; } h3 { margin: 0; font-size: 14px; font-weight: 600; - color: #202124; + color: $gray-900; } } } @@ -49,6 +55,18 @@ } } +.fdpd-grid--classification { + grid-template-columns: repeat(3, 1fr); + + @media (width <= 900px) { + grid-template-columns: repeat(2, 1fr); + } + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} + .fdpd-field { width: 100%; @@ -56,3 +74,9 @@ grid-column: 1 / -1; } } + +.fdpd-checkbox-row { + display: flex; + align-items: center; + min-height: 56px; +} diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.ts b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.ts index bce5e5a57..30b91dffb 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.ts +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-details-step/fixed-deposit-product-details-step.component.ts @@ -6,8 +6,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component, OnInit, Input, inject } from '@angular/core'; -import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; +import { Component, OnInit, OnChanges, Input, SimpleChanges, inject, ChangeDetectorRef } from '@angular/core'; +import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; +import { CdkTextareaAutosize } from '@angular/cdk/text-field'; +import { MatTooltip } from '@angular/material/tooltip'; +import { Dates } from 'app/core/utils/dates'; +import { SettingsService } from 'app/settings/settings.service'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @Component({ @@ -15,27 +19,41 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; templateUrl: './fixed-deposit-product-details-step.component.html', styleUrls: ['./fixed-deposit-product-details-step.component.scss'], imports: [ - ...STANDALONE_SHARED_IMPORTS + ...STANDALONE_SHARED_IMPORTS, + CdkTextareaAutosize, + MatTooltip ] }) -export class FixedDepositProductDetailsStepComponent implements OnInit { +export class FixedDepositProductDetailsStepComponent implements OnInit, OnChanges { private formBuilder = inject(UntypedFormBuilder); + private dateUtils = inject(Dates); + private settingsService = inject(SettingsService); + private cdr = inject(ChangeDetectorRef); @Input() fixedDepositProductsTemplate: any; fixedDepositProductDetailsForm: UntypedFormGroup; + accountTypeData: string[] = []; + customerSegmentData: string[] = []; + liabilityAccountOptions: any[] = []; + yesNoOptions: Array<{ value: boolean; label: string }> = [ + { value: true, label: 'Yes' }, + { value: false, label: 'No' } + ]; constructor() { this.createFixedDepositProductDetailsForm(); } ngOnInit() { - if (!(this.fixedDepositProductsTemplate === undefined) && this.fixedDepositProductsTemplate.id) { - this.fixedDepositProductDetailsForm.patchValue({ - name: this.fixedDepositProductsTemplate.name, - shortName: this.fixedDepositProductsTemplate.shortName, - description: this.fixedDepositProductsTemplate.description - }); + if (this.fixedDepositProductsTemplate) { + this.applyTemplate(this.fixedDepositProductsTemplate); + } + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.fixedDepositProductsTemplate?.currentValue) { + this.applyTemplate(changes.fixedDepositProductsTemplate.currentValue); } } @@ -49,14 +67,66 @@ export class FixedDepositProductDetailsStepComponent implements OnInit { '', Validators.required ], - description: [ - '', + description: [''], + accountType: [ + null, Validators.required - ] + ], + customerSegment: [null], + glAccountId: [null], + effectiveTo: [''], + endDate: [''], + introducerRequired: [false], + shareLinkingEnabled: [false] + }); + } + + private getOptionNames(options: any[]): string[] { + if (!Array.isArray(options)) return []; + return options.map((o) => o?.name ?? o?.value ?? o).filter((v) => typeof v === 'string' && v.trim().length > 0); + } + + private parseDate(dateValue: any): Date | null { + if (!dateValue) return null; + if (Array.isArray(dateValue)) return new Date(dateValue[0], dateValue[1] - 1, dateValue[2]); + if (dateValue instanceof Date) return dateValue; + return new Date(dateValue); + } + + compareById(o1: any, o2: any): boolean { + return o1 != null && o2 != null && Number(o1) === Number(o2); + } + + private applyTemplate(template: any) { + this.accountTypeData = this.getOptionNames(template.accountTypeOptions); + this.customerSegmentData = this.getOptionNames(template.customerSegmentOptions); + this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? []; + this.cdr.detectChanges(); + + const isEditing = !!template.id; + this.fixedDepositProductDetailsForm.patchValue({ + name: isEditing ? template.name : template.name || '', + shortName: isEditing ? template.shortName : template.shortName || '', + description: template.description || '', + accountType: template.accountType ?? this.accountTypeData[0] ?? null, + customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null, + glAccountId: template.glAccountId ?? null, + effectiveTo: this.parseDate(template.effectiveTo), + endDate: this.parseDate(template.endDate), + introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes', + shareLinkingEnabled: !!template.shareLinkingEnabled }); } get fixedDepositProductDetails() { - return this.fixedDepositProductDetailsForm.value; + const formValue = { ...this.fixedDepositProductDetailsForm.value }; + const dateFormat = this.settingsService.dateFormat; + if (formValue.effectiveTo instanceof Date) { + formValue.effectiveTo = this.dateUtils.formatDate(formValue.effectiveTo, dateFormat) || ''; + } + if (formValue.endDate instanceof Date) { + formValue.endDate = this.dateUtils.formatDate(formValue.endDate, dateFormat) || ''; + } + return formValue; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.html b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.html index 5c8f66550..7843f2654 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.html +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.html @@ -7,204 +7,124 @@ -->
+
- -

{{ 'labels.inputs.Lock-in Period' | translate }}

+ + Premature option
-
- - {{ 'labels.inputs.Frequency' | translate }} - - - - - {{ 'labels.inputs.Type' | translate }} - - @for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) { - - {{ lockinPeriodFrequencyType.value }} - - } - - +
+
+ Loan Facility + Premature Withdrawal option + Tenure Flexibility + Lien Marked + Apply Withdrawal Fee for Transfers +
+
- -

{{ 'labels.inputs.Minimum Deposit Term' | translate }}

+ + {{ 'labels.inputs.Lock-in Period' | translate }}
-
- - {{ 'labels.inputs.Frequency' | translate }} - - - {{ 'labels.inputs.Minimum Deposit Term Frequency' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - - - - {{ 'labels.inputs.Frequency Type' | translate }} - - @for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) { - - {{ periodFrequencyType.value }} - - } - - - {{ 'labels.inputs.Minimum Deposit Term Frequency Type' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - -
-
- -
-
- -

{{ 'labels.heading.And thereafter, in Multiples of' | translate }}

-
-
- - {{ 'labels.inputs.Frequency' | translate }} - - - - - {{ 'labels.inputs.Type' | translate }} - - @for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) { - - {{ periodFrequencyType.value }} - - } - - -
-
- -
-
- -

{{ 'labels.inputs.Maximum Deposit Term' | translate }}

-
-
- - {{ 'labels.inputs.Frequency' | translate }} - - - - - {{ 'labels.inputs.Type' | translate }} - - @for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) { - - {{ periodFrequencyType.value }} - - } - - -
-
- -
-
- -

{{ 'labels.heading.For Pre-mature closure' | translate }}

-
-
-
- - {{ 'labels.inputs.Apply Penal Interest (less)' | translate }} - +
+
+ Enable Lock-in Period +
- - {{ 'labels.inputs.Penal Interest' | translate }} (%) - - + @if (fixedDepositProductSettingsForm.value.enableLockinPeriod) { +
+ + {{ 'labels.inputs.Frequency' | translate }} + + - - {{ 'labels.inputs.Period' | translate }} - - @for ( - preClosurePenalInterestOnType of preClosurePenalInterestOnTypeData; - track preClosurePenalInterestOnType - ) { - - {{ preClosurePenalInterestOnType.value }} - - } - - -
-
- -
-
- -

{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}

-
-
-
- - {{ 'labels.inputs.Is Withhold Tax Applicable' | translate }} - -
- - @if (fixedDepositProductSettingsForm.value.withHoldTax) { - - {{ 'labels.inputs.Tax Group' | translate }} - - @for (taxGroup of taxGroupData; track taxGroup) { - - {{ taxGroup.name }} - - } - - - {{ 'labels.inputs.Tax Group' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - + + {{ 'labels.inputs.Type' | translate }} + + @for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) { + + {{ lockinPeriodFrequencyType.value | translateKey: 'catalogs' }} + + } + + +
}
+ + +
+
+ + Maturity Instructions +
+
+
+ Maturity Instructions + +
+
+ Option to credit maturity proceeds directly to the linked account or reinvest in another product (like + FD). + +
+
+ Penalty / Grace Period for Missed Installments + +
+
+ Panel Request Applicable + +
+
+ No penalty for nominee / legal heir closure + +
+
+ Deceased Settlement + +
+
+ Allow Debit Txn after due date + +
+
+
+ + +
+
+ + Additional Rules +
+
+
+
+ + Account Closed with Zero balance in Day Closing + +
+ + + Balance Required for Interest Calculation + + +
+
+
diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.scss index 62b1187f8..6017718c9 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.scss @@ -6,8 +6,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +$accent: #0ea5e9; +$border: #e2e8f0; +$gray-50: #f8f9fa; +$gray-200: #e8eaed; +$gray-900: #202124; + .fdps-section { - border: 1px solid #e2e8f0; + border: 1px solid $border; border-radius: 10px; overflow: hidden; background: #fff; @@ -21,32 +27,41 @@ align-items: center; gap: 10px; padding: 12px 24px; - background: #f8f9fa; - border-bottom: 1px solid #e8eaed; + background: $gray-50; + border-bottom: 1px solid $gray-200; i { font-size: 14px; - color: #0ea5e9; + color: $accent; } - h3 { + span:not(.fdps-section__badge) { margin: 0; font-size: 14px; font-weight: 600; - color: #202124; + color: $gray-900; } } } -.fdps-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 4px 16px; - padding: 16px 24px; +.fdps-section__badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 6px; + background: #eff6ff; + font-size: 14px; +} - @media (width <= 600px) { - grid-template-columns: 1fr; - } +.fdps-body { + background: #fff; + padding: 18px 24px 24px; +} + +.fdps-body--list { + padding: 0 24px; } .fdps-field { @@ -55,10 +70,91 @@ &--full { grid-column: 1 / -1; } + + &--compact { + width: 200px; + min-width: 150px; + } } -.fdps-checkbox-row { +.fdps-premature-row { display: flex; align-items: center; - padding: 4px 0; + gap: 24px; + flex-wrap: wrap; + + mat-checkbox { + font-size: 14px; + color: $gray-900; + } +} + +.fdps-list-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 14px 0; + border-bottom: 1px solid $gray-200; + font-size: 14px; + color: $gray-900; + + &:last-child { + border-bottom: none; + } + + span { + max-width: 85%; + line-height: 1.4; + } +} + +.fdps-lockin-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 16px; + padding-bottom: 16px; + border-bottom: 1px solid $gray-200; + font-size: 14px; + color: $gray-900; +} + +.fdps-lockin-fields { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; + + @media (width <= 640px) { + grid-template-columns: 1fr; + } +} + +.fdps-layout { + display: grid; + gap: 16px; +} + +.fdps-layout--additional { + grid-template-columns: repeat(2, minmax(0, 1fr)); + + @media (width <= 1100px) { + grid-template-columns: 1fr; + } +} + +.fdps-toggle { + display: flex; + align-items: center; + min-height: 40px; + + mat-checkbox { + font-size: 14px; + color: $gray-900; + } + + &--inline { + min-height: auto; + } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.ts b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.ts index 4c1bad874..33933731c 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.ts +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component.ts @@ -7,13 +7,7 @@ */ import { Component, OnInit, Input, inject } from '@angular/core'; -import { - UntypedFormGroup, - UntypedFormBuilder, - UntypedFormControl, - Validators, - ReactiveFormsModule -} from '@angular/forms'; +import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; import { MatTooltip } from '@angular/material/tooltip'; import { MatCheckbox } from '@angular/material/checkbox'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @@ -36,113 +30,71 @@ export class FixedDepositProductSettingsStepComponent implements OnInit { fixedDepositProductSettingsForm: UntypedFormGroup; lockinPeriodFrequencyTypeData: any; - periodFrequencyTypeData: any; - preClosurePenalInterestOnTypeData: any; - taxGroupData: any; constructor() { this.createFixedDepositProductSettingsForm(); - this.setConditionalControls(); } ngOnInit() { this.lockinPeriodFrequencyTypeData = this.fixedDepositProductsTemplate.lockinPeriodFrequencyTypeOptions; - this.periodFrequencyTypeData = this.fixedDepositProductsTemplate.periodFrequencyTypeOptions.slice(0, -1); - this.preClosurePenalInterestOnTypeData = this.fixedDepositProductsTemplate.preClosurePenalInterestOnTypeOptions; - this.taxGroupData = this.fixedDepositProductsTemplate.taxGroupOptions; - if (!(this.fixedDepositProductsTemplate === undefined) && this.fixedDepositProductsTemplate.id) { + if (this.fixedDepositProductsTemplate?.id) { this.fixedDepositProductSettingsForm.patchValue({ - isMandatoryDeposit: this.fixedDepositProductsTemplate.isMandatoryDeposit, - adjustAdvanceTowardsFuturePayments: this.fixedDepositProductsTemplate.adjustAdvanceTowardsFuturePayments, - allowWithdrawal: this.fixedDepositProductsTemplate.allowWithdrawal, - lockinPeriodFrequency: this.fixedDepositProductsTemplate.lockinPeriodFrequency, - lockinPeriodFrequencyType: this.fixedDepositProductsTemplate.lockinPeriodFrequencyType - ? this.fixedDepositProductsTemplate.lockinPeriodFrequencyType.id - : '', - minDepositTerm: this.fixedDepositProductsTemplate.minDepositTerm, - minDepositTermTypeId: this.fixedDepositProductsTemplate.minDepositTermType - ? this.fixedDepositProductsTemplate.minDepositTermType.id - : '', - inMultiplesOfDepositTerm: this.fixedDepositProductsTemplate.inMultiplesOfDepositTerm, - inMultiplesOfDepositTermTypeId: this.fixedDepositProductsTemplate.inMultiplesOfDepositTermType - ? this.fixedDepositProductsTemplate.inMultiplesOfDepositTerm.id - : '', - maxDepositTerm: this.fixedDepositProductsTemplate.maxDepositTerm, - maxDepositTermTypeId: this.fixedDepositProductsTemplate.maxDepositTermType - ? this.fixedDepositProductsTemplate.minDepositTermType.id - : '', - preClosurePenalApplicable: this.fixedDepositProductsTemplate.preClosurePenalApplicable, - preClosurePenalInterest: this.fixedDepositProductsTemplate.preClosurePenalInterest, - preClosurePenalInterestOnTypeId: this.fixedDepositProductsTemplate.preClosurePenalInterestOnType - ? this.fixedDepositProductsTemplate.preClosurePenalInterestOnType.id - : '', - withHoldTax: this.fixedDepositProductsTemplate.withHoldTax - }); - } - - if (this.fixedDepositProductsTemplate.withHoldTax) { - this.fixedDepositProductSettingsForm.patchValue({ - taxGroupId: this.fixedDepositProductsTemplate.taxGroup ? this.fixedDepositProductsTemplate.taxGroup.id : '' + loanFacility: this.fixedDepositProductsTemplate.loanFacility ?? false, + prematureWithdrawalOption: this.fixedDepositProductsTemplate.prematureWithdrawalOption ?? false, + tenureFlexibility: this.fixedDepositProductsTemplate.tenureFlexibility ?? false, + lienMarked: this.fixedDepositProductsTemplate.lienMarked ?? false, + withdrawalFeeForTransfers: this.fixedDepositProductsTemplate.withdrawalFeeForTransfers ?? false, + enableLockinPeriod: this.fixedDepositProductsTemplate.enableLockinPeriod ?? false, + lockinPeriodFrequency: this.fixedDepositProductsTemplate.lockinPeriodFrequency ?? '', + lockinPeriodFrequencyType: this.fixedDepositProductsTemplate.lockinPeriodFrequencyType?.id ?? '', + maturityInstructions: this.fixedDepositProductsTemplate.maturityInstructions ?? false, + creditMaturityProceeds: this.fixedDepositProductsTemplate.creditMaturityProceeds ?? false, + penaltyGracePeriodMissedInstallments: + this.fixedDepositProductsTemplate.penaltyGracePeriodMissedInstallments ?? false, + panelRequestApplicable: this.fixedDepositProductsTemplate.panelRequestApplicable ?? false, + noPenaltyNomineeHeirClosure: this.fixedDepositProductsTemplate.noPenaltyNomineeHeirClosure ?? false, + deceasedSettlement: this.fixedDepositProductsTemplate.deceasedSettlement ?? false, + allowDebitTxnAfterDueDate: this.fixedDepositProductsTemplate.allowDebitTxnAfterDueDate ?? false, + accountClosedZeroBalanceInDayClosing: + this.fixedDepositProductsTemplate.accountClosedZeroBalanceInDayClosing ?? false, + minBalanceForInterestCalculation: this.fixedDepositProductsTemplate.minBalanceForInterestCalculation ?? '' }); } } createFixedDepositProductSettingsForm() { this.fixedDepositProductSettingsForm = this.formBuilder.group({ + loanFacility: [false], + prematureWithdrawalOption: [false], + tenureFlexibility: [false], + lienMarked: [false], + withdrawalFeeForTransfers: [false], + enableLockinPeriod: [false], lockinPeriodFrequency: [ '', Validators.min(0) ], lockinPeriodFrequencyType: [''], - minDepositTerm: [ - '', - [ - Validators.required, - Validators.min(0) - ] - ], - minDepositTermTypeId: [ - '', - Validators.required - ], - inMultiplesOfDepositTerm: [ - '', - Validators.min(0) - ], - inMultiplesOfDepositTermTypeId: [''], - maxDepositTerm: [ - '', - Validators.min(0) - ], - maxDepositTermTypeId: [''], - preClosurePenalApplicable: [false], - preClosurePenalInterest: [ - '', - Validators.min(0) - ], - preClosurePenalInterestOnTypeId: [''], - withHoldTax: [false] - }); - } - - setConditionalControls() { - this.fixedDepositProductSettingsForm.get('withHoldTax').valueChanges.subscribe((withHoldTax: any) => { - if (withHoldTax) { - this.fixedDepositProductSettingsForm.addControl('taxGroupId', new UntypedFormControl('', Validators.required)); - } else { - this.fixedDepositProductSettingsForm.removeControl('taxGroupId'); - } + maturityInstructions: [false], + creditMaturityProceeds: [false], + penaltyGracePeriodMissedInstallments: [false], + panelRequestApplicable: [false], + noPenaltyNomineeHeirClosure: [false], + deceasedSettlement: [false], + allowDebitTxnAfterDueDate: [false], + accountClosedZeroBalanceInDayClosing: [false], + minBalanceForInterestCalculation: [''] }); } get fixedDepositProductSettings() { - const fixedDepositProductSettings = this.fixedDepositProductSettingsForm.value; - for (const key in fixedDepositProductSettings) { - if (fixedDepositProductSettings[key] === '') { - delete fixedDepositProductSettings[key]; + const settings = { ...this.fixedDepositProductSettingsForm.value }; + for (const key in settings) { + if (settings[key] === '') { + delete settings[key]; } } - return fixedDepositProductSettings; + return settings; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.html b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.html index c2228368b..ab0346c21 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.html +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.html @@ -7,139 +7,148 @@ -->
+
- -

{{ 'labels.inputs.Deposit Amount' | translate }}

+ + Interest Rate & Posting Terms
-
- - {{ 'labels.inputs.Minimum' | translate }} - - +
+
+ ℹ️ + + Fixed Deposit base rate: + {{ fixedDepositProductTermsForm.get('nominalAnnualInterestRate').value }}% + +
- - {{ 'labels.inputs.Default' | translate }} - - - {{ 'labels.inputs.Default' | translate }} {{ 'labels.inputs.Deposit Amount' | translate }} - {{ 'labels.commons.is' | translate }} {{ 'labels.commons.required' | translate }} - - - - - {{ 'labels.inputs.Maximum' | translate }} - - -
-
- -
-
- -

{{ 'labels.inputs.Interest Compounding Period' | translate }}

-
-
- - {{ 'labels.inputs.Interest Compounding Period' | translate }} - - @for ( - interestCompoundingPeriodType of interestCompoundingPeriodTypeData; - track interestCompoundingPeriodType - ) { - - {{ interestCompoundingPeriodType.value }} - +
+ + {{ 'labels.inputs.Nominal Annual Interest' | translate }} + + @if (fixedDepositProductTermsForm.get('nominalAnnualInterestRate').hasError('required')) { + + {{ 'labels.inputs.Nominal Annual Interest' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + } - - - {{ 'labels.inputs.Interest Compounding Period' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - - - - {{ 'labels.inputs.Interest Posting Period' | translate }} - - @for (interestPostingPeriodType of interestPostingPeriodTypeData; track interestPostingPeriodType) { - - {{ interestPostingPeriodType.value }} - + @if (fixedDepositProductTermsForm.get('nominalAnnualInterestRate').hasError('min')) { + {{ 'labels.inputs.Nominal Annual Interest' | translate }} must be zero or greater } - - - {{ 'labels.inputs.Interest Posting Period' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - + - - {{ 'labels.inputs.Interest Calculated using' | translate }} - - @for (interestCalculationType of interestCalculationTypeData; track interestCalculationType) { - - {{ interestCalculationType.value }} - - } - - - {{ 'labels.inputs.Interest Calculated using' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - + + Effective Date + + + + - - {{ 'labels.inputs.Days in Year' | translate }} - - @for ( - interestCalculationDaysInYearType of interestCalculationDaysInYearTypeData; - track interestCalculationDaysInYearType - ) { - - {{ interestCalculationDaysInYearType.value }} - - } - - - {{ 'labels.inputs.Days in Year' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - + + {{ 'labels.inputs.Interest Calculation Type' | translate }} + + @for (opt of interestCalculationTypeOptionData; track opt.id) { + {{ opt.value }} + } + + +
+ +
+ + Interest Calculate + + @for (mode of interestCalculationTypeData; track mode.id) { + {{ mode.value | translateKey: 'catalogs' }} + } + + + {{ 'labels.inputs.Interest Calculation Type' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + + + @if (fixedDepositProductTermsForm.value.interestCalculationType === 2) { + + Average Period + + @for (period of periodFrequencyData; track period.id) { + {{ period.value }} + } + + + } + + + {{ 'labels.inputs.Interest Posting Period' | translate }} + + @for (interestPostingPeriodType of interestPostingPeriodTypeData; track interestPostingPeriodType) { + + {{ interestPostingPeriodType.value | translateKey: 'catalogs' }} + + } + + + {{ 'labels.inputs.Interest Posting Period' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + +
+ +
diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.scss index ac8a2ddd5..8b4b08ace 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.scss @@ -6,8 +6,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +$accent: #0ea5e9; +$border: #e2e8f0; +$gray-50: #f8f9fa; +$gray-200: #e8eaed; +$gray-900: #202124; + .fdpt-section { - border: 1px solid #e2e8f0; + border: 1px solid $border; border-radius: 10px; overflow: hidden; background: #fff; @@ -21,31 +27,54 @@ align-items: center; gap: 10px; padding: 12px 24px; - background: #f8f9fa; - border-bottom: 1px solid #e8eaed; + background: $gray-50; + border-bottom: 1px solid $gray-200; i { font-size: 14px; - color: #0ea5e9; + color: $accent; } - h3 { + span:not(.fdpt-section__badge) { margin: 0; font-size: 14px; font-weight: 600; - color: #202124; + color: $gray-900; } } } +.fdpt-section__badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 6px; + background: #eff6ff; + font-size: 14px; +} + +.fdpt-body { + background: #fff; +} + .fdpt-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 16px; padding: 16px 24px; + &--three { + grid-template-columns: repeat(3, 1fr); + } + @media (width <= 600px) { grid-template-columns: 1fr; + + &--three { + grid-template-columns: 1fr; + } } } @@ -56,3 +85,67 @@ grid-column: 1 / -1; } } + +.fdpt-footer { + padding: 0 24px 24px; + border-top: 1px solid #f1f5f9; +} + +.fdpt-footer__grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + padding-top: 18px; + + @media (width <= 900px) { + grid-template-columns: 1fr; + } +} + +.fdpt-banner { + display: flex; + align-items: center; + gap: 10px; + margin: 16px 24px 0; + padding: 12px 14px; + background: #eff6ff; + border: 1px solid #dbeafe; + border-radius: 8px; + color: #1d4ed8; + font-size: 13px; + + &__icon { + font-size: 14px; + } +} + +.fdpt-toggle-card { + padding: 12px 0; + + h4 { + margin: 0 0 8px; + font-size: 14px; + font-weight: 600; + color: $gray-900; + } + + &__choices { + display: flex; + align-items: center; + gap: 16px; + flex-wrap: wrap; + } +} + +.fdpt-note { + display: flex; + align-items: center; + gap: 10px; + margin-top: 18px; + padding: 12px 14px; + border: 1px solid #fde68a; + background: #fffbeb; + color: #b45309; + border-radius: 8px; + font-size: 12px; +} diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.ts b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.ts index c58163ebf..29dc4defa 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.ts +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.ts @@ -7,8 +7,10 @@ */ import { Component, OnInit, Input, inject } from '@angular/core'; -import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; +import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; import { MatTooltip } from '@angular/material/tooltip'; +import { Dates } from 'app/core/utils/dates'; +import { SettingsService } from 'app/settings/settings.service'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @Component({ @@ -22,6 +24,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; }) export class FixedDepositProductTermsStepComponent implements OnInit { private formBuilder = inject(UntypedFormBuilder); + private dateUtils = inject(Dates); + private settingsService = inject(SettingsService); @Input() fixedDepositProductsTemplate: any; @@ -30,7 +34,13 @@ export class FixedDepositProductTermsStepComponent implements OnInit { interestCompoundingPeriodTypeData: any; interestPostingPeriodTypeData: any; interestCalculationTypeData: any; + interestCalculationTypeOptionData: any; interestCalculationDaysInYearTypeData: any; + periodFrequencyData: any[] = []; + yesNoOptions: Array<{ value: boolean; label: string }> = [ + { value: true, label: 'Yes' }, + { value: false, label: 'No' } + ]; constructor() { this.createFixedDepositProductTermsForm(); @@ -40,10 +50,22 @@ export class FixedDepositProductTermsStepComponent implements OnInit { this.interestCompoundingPeriodTypeData = this.fixedDepositProductsTemplate.interestCompoundingPeriodTypeOptions; this.interestPostingPeriodTypeData = this.fixedDepositProductsTemplate.interestPostingPeriodTypeOptions; this.interestCalculationTypeData = this.fixedDepositProductsTemplate.interestCalculationTypeOptions; + this.interestCalculationTypeOptionData = this.fixedDepositProductsTemplate.interestCalculationTypeOption || []; this.interestCalculationDaysInYearTypeData = this.fixedDepositProductsTemplate.interestCalculationDaysInYearTypeOptions; + this.periodFrequencyData = this.fixedDepositProductsTemplate.periodFrequencyOptions || []; - if (!(this.fixedDepositProductsTemplate === undefined) && this.fixedDepositProductsTemplate.id) { + const isEditing = !!this.fixedDepositProductsTemplate.id; + const defaultPeriod = this.periodFrequencyData[0]?.value || 'Monthly'; + const savedEnumTypeId = + this.fixedDepositProductsTemplate.interestCalculationType?.id || this.interestCalculationTypeData?.[0]?.id || 1; + const savedSelectorId = isEditing + ? (this.fixedDepositProductsTemplate.interestCalculateSelector ?? + this.interestCalculationTypeOptionData?.[0]?.id ?? + null) + : (this.interestCalculationTypeOptionData?.[0]?.id ?? null); + + if (isEditing) { this.fixedDepositProductTermsForm.patchValue({ minDepositAmount: this.fixedDepositProductsTemplate.minDepositAmount, depositAmount: this.fixedDepositProductsTemplate.depositAmount, @@ -52,13 +74,31 @@ export class FixedDepositProductTermsStepComponent implements OnInit { } this.fixedDepositProductTermsForm.patchValue({ - interestCompoundingPeriodType: this.fixedDepositProductsTemplate.interestCompoundingPeriodType.id, - interestPostingPeriodType: this.fixedDepositProductsTemplate.interestPostingPeriodType.id, - interestCalculationType: this.fixedDepositProductsTemplate.interestCalculationType.id, - interestCalculationDaysInYearType: this.fixedDepositProductsTemplate.interestCalculationDaysInYearType.id + nominalAnnualInterestRate: this.fixedDepositProductsTemplate.nominalAnnualInterestRate ?? 0, + interestCompoundingPeriodType: this.fixedDepositProductsTemplate.interestCompoundingPeriodType?.id, + interestPostingPeriodType: this.fixedDepositProductsTemplate.interestPostingPeriodType?.id, + interestCalculationType: savedEnumTypeId, + interestCalculationDaysInYearType: this.fixedDepositProductsTemplate.interestCalculationDaysInYearType?.id, + effectiveDate: this.fixedDepositProductsTemplate.effectiveDate + ? this.parseDate(this.fixedDepositProductsTemplate.effectiveDate) + : '', + interestCalculateSelector: savedSelectorId, + averagePeriod: isEditing ? this.fixedDepositProductsTemplate.averagePeriod || defaultPeriod : defaultPeriod, + autoPost: isEditing ? (this.fixedDepositProductsTemplate.autoPost ?? true) : true, + frequencyFixDay: isEditing ? (this.fixedDepositProductsTemplate.frequencyFixDay ?? 30) : 30, + lastInterestDate: this.fixedDepositProductsTemplate.lastInterestDate + ? this.parseDate(this.fixedDepositProductsTemplate.lastInterestDate) + : '' }); } + private parseDate(dateValue: any): Date | null { + if (!dateValue) return null; + if (Array.isArray(dateValue)) return new Date(dateValue[0], dateValue[1] - 1, dateValue[2]); + if (dateValue instanceof Date) return dateValue; + return new Date(dateValue); + } + createFixedDepositProductTermsForm() { this.fixedDepositProductTermsForm = this.formBuilder.group({ minDepositAmount: [ @@ -76,6 +116,13 @@ export class FixedDepositProductTermsStepComponent implements OnInit { '', Validators.min(0) ], + nominalAnnualInterestRate: [ + '', + [ + Validators.required, + Validators.min(0) + ] + ], interestCompoundingPeriodType: [ '', Validators.required @@ -91,17 +138,34 @@ export class FixedDepositProductTermsStepComponent implements OnInit { interestCalculationDaysInYearType: [ '', Validators.required - ] + ], + effectiveDate: [''], + interestCalculateSelector: [1], + averagePeriod: ['Monthly'], + autoPost: [true], + frequencyFixDay: [ + 30, + [Validators.min(0)] + ], + lastInterestDate: [''] }); } get fixedDepositProductTerms() { - const fixedDepositProductTerms = this.fixedDepositProductTermsForm.value; - for (const key in fixedDepositProductTerms) { - if (fixedDepositProductTerms[key] === '') { - delete fixedDepositProductTerms[key]; + const formValue = { ...this.fixedDepositProductTermsForm.value }; + const dateFormat = this.settingsService.dateFormat; + + if (formValue.effectiveDate instanceof Date) { + formValue.effectiveDate = this.dateUtils.formatDate(formValue.effectiveDate, dateFormat) || ''; + } + if (formValue.lastInterestDate instanceof Date) { + formValue.lastInterestDate = this.dateUtils.formatDate(formValue.lastInterestDate, dateFormat) || ''; + } + for (const key in formValue) { + if (formValue[key] === '') { + delete formValue[key]; } } - return fixedDepositProductTerms; + return formValue; } }