From fe61c925ae72513e459d59966b4e41ebc5ab91e2 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Wed, 15 Apr 2026 20:46:15 +0530 Subject: [PATCH] update current product code and RD code --- .ai/mcp/mcp.json | 0 .husky/{pre-commit.bak => pre-commit} | 0 .qwen/settings.json | 8 +- .../create-charge.component.scss | 4 +- .../edit-charge/edit-charge.component.scss | 4 +- .../view-charge/view-charge.component.scss | 4 +- .../create-collateral.component.scss | 4 +- .../edit-collateral.component.scss | 4 +- .../view-collateral.component.scss | 2 +- .../create-current-product.component.html | 69 +- .../create-current-product.component.scss | 8 +- .../create-current-product.component.ts | 109 ++- ...ent-product-accounting-step.component.html | 121 +++ ...ent-product-accounting-step.component.scss | 314 +++++++ ...rrent-product-accounting-step.component.ts | 495 +++++++++++ ...urrent-product-charges-step.component.html | 93 ++ ...urrent-product-charges-step.component.scss | 103 +++ .../current-product-charges-step.component.ts | 108 +++ ...rrent-product-currency-step.component.html | 111 +++ ...rrent-product-currency-step.component.scss | 114 +++ ...current-product-currency-step.component.ts | 94 +++ ...urrent-product-details-step.component.html | 117 +++ ...urrent-product-details-step.component.scss | 70 ++ .../current-product-details-step.component.ts | 190 +++++ ...current-product-limits-step.component.html | 351 ++++++++ ...current-product-limits-step.component.scss | 257 ++++++ .../current-product-limits-step.component.ts | 250 ++++++ ...urrent-product-preview-step.component.html | 799 ++++++++++++++++++ ...urrent-product-preview-step.component.scss | 165 ++++ .../current-product-preview-step.component.ts | 247 ++++++ ...rrent-product-settings-step.component.html | 189 +++++ ...rrent-product-settings-step.component.scss | 263 ++++++ ...current-product-settings-step.component.ts | 172 ++++ .../current-product-terms-step.component.html | 68 ++ .../current-product-terms-step.component.scss | 148 ++++ .../current-product-terms-step.component.ts | 112 +++ .../edit-current-product.component.html | 203 +++-- .../edit-current-product.component.scss | 92 +- .../edit-current-product.component.ts | 143 ++-- ...current-product-general-tab.component.html | 698 +++++++++++++-- .../current-product-general-tab.component.ts | 35 + ...reate-fixed-deposit-product.component.scss | 9 +- ...sit-product-accounting-step.component.scss | 4 +- ...eposit-product-charges-step.component.scss | 2 +- ...posit-product-currency-step.component.scss | 2 +- ...eposit-product-details-step.component.scss | 2 +- ...ct-interest-rate-chart-step.component.scss | 2 +- ...eposit-product-preview-step.component.scss | 2 +- ...posit-product-settings-step.component.scss | 2 +- ...-deposit-product-terms-step.component.scss | 2 +- .../create-floating-rate.component.scss | 2 +- .../edit-floating-rate.component.scss | 2 +- .../view-floating-rate.component.scss | 2 +- .../create-loan-product.component.scss | 8 +- ...oan-product-accounting-step.component.scss | 2 +- ...red-income-recognition-step.component.scss | 2 +- .../loan-product-charges-step.component.scss | 2 +- .../loan-product-currency-step.component.scss | 2 +- .../loan-product-details-step.component.scss | 2 +- ...roduct-interest-refund-step.component.scss | 2 +- .../loan-product-settings-step.component.scss | 4 +- .../loan-product-terms-step.component.scss | 8 +- .../create-bucket.component.scss | 2 +- .../edit-bucket/edit-bucket.component.scss | 2 +- .../create-range/create-range.component.scss | 4 +- .../edit-range/edit-range.component.scss | 4 +- .../manage-delinquency-buckets.component.scss | 2 +- .../create-tax-component.component.scss | 2 +- .../edit-tax-component.component.scss | 2 +- .../view-tax-component.component.scss | 6 +- .../manage-tax-configurations.component.scss | 4 +- .../create-tax-group.component.scss | 2 +- .../edit-tax-group.component.scss | 2 +- .../view-tax-group.component.scss | 6 +- .../create-product-mix.component.scss | 2 +- .../edit-product-mix.component.scss | 2 +- .../view-product-mix.component.scss | 2 +- src/app/products/products.component.scss | 28 +- ...e-recurring-deposit-product.component.html | 13 +- ...e-recurring-deposit-product.component.scss | 93 +- ...sit-product-accounting-step.component.html | 356 +++----- ...sit-product-accounting-step.component.scss | 307 ++++++- ...posit-product-accounting-step.component.ts | 714 ++++++++-------- ...eposit-product-charges-step.component.html | 9 +- ...eposit-product-charges-step.component.scss | 22 +- ...posit-product-currency-step.component.html | 150 ++-- ...posit-product-currency-step.component.scss | 69 +- ...deposit-product-currency-step.component.ts | 83 +- ...eposit-product-details-step.component.html | 68 +- ...eposit-product-details-step.component.scss | 40 +- ...-deposit-product-details-step.component.ts | 92 +- ...ct-interest-rate-chart-step.component.html | 50 +- ...ct-interest-rate-chart-step.component.scss | 2 +- ...eposit-product-preview-step.component.html | 30 +- ...eposit-product-preview-step.component.scss | 38 +- ...posit-product-settings-step.component.html | 292 +++---- ...posit-product-settings-step.component.scss | 129 ++- ...deposit-product-settings-step.component.ts | 130 +-- ...-deposit-product-terms-step.component.html | 223 +++-- ...-deposit-product-terms-step.component.scss | 108 ++- ...ng-deposit-product-terms-step.component.ts | 121 ++- ...ing-product-accounting-step.component.html | 17 +- ...aving-product-accounting-step.component.ts | 35 +- ...saving-product-details-step.component.html | 2 +- .../saving-product-details-step.component.ts | 4 +- .../saving-product-general-tab.component.html | 71 ++ .../create-share-product.component.scss | 8 +- ...are-product-accounting-step.component.scss | 2 +- .../share-product-charges-step.component.scss | 4 +- ...share-product-currency-step.component.scss | 3 +- .../share-product-details-step.component.scss | 3 +- .../share-product-preview-step.component.scss | 2 +- ...share-product-settings-step.component.scss | 2 +- .../share-product-terms-step.component.scss | 3 +- src/environments/.env.ts | 4 +- 115 files changed, 8057 insertions(+), 1717 deletions(-) create mode 100644 .ai/mcp/mcp.json rename .husky/{pre-commit.bak => pre-commit} (100%) create mode 100644 src/app/products/current-products/current-product-stepper/current-product-accounting-step/current-product-accounting-step.component.html create mode 100644 src/app/products/current-products/current-product-stepper/current-product-accounting-step/current-product-accounting-step.component.scss create mode 100644 src/app/products/current-products/current-product-stepper/current-product-accounting-step/current-product-accounting-step.component.ts create mode 100644 src/app/products/current-products/current-product-stepper/current-product-charges-step/current-product-charges-step.component.html create mode 100644 src/app/products/current-products/current-product-stepper/current-product-charges-step/current-product-charges-step.component.scss create mode 100644 src/app/products/current-products/current-product-stepper/current-product-charges-step/current-product-charges-step.component.ts create mode 100644 src/app/products/current-products/current-product-stepper/current-product-currency-step/current-product-currency-step.component.html create mode 100644 src/app/products/current-products/current-product-stepper/current-product-currency-step/current-product-currency-step.component.scss create mode 100644 src/app/products/current-products/current-product-stepper/current-product-currency-step/current-product-currency-step.component.ts create mode 100644 src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.html create mode 100644 src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.scss create mode 100644 src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.ts create mode 100644 src/app/products/current-products/current-product-stepper/current-product-limits-step/current-product-limits-step.component.html create mode 100644 src/app/products/current-products/current-product-stepper/current-product-limits-step/current-product-limits-step.component.scss create mode 100644 src/app/products/current-products/current-product-stepper/current-product-limits-step/current-product-limits-step.component.ts create mode 100644 src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.html create mode 100644 src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.scss create mode 100644 src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.ts create mode 100644 src/app/products/current-products/current-product-stepper/current-product-settings-step/current-product-settings-step.component.html create mode 100644 src/app/products/current-products/current-product-stepper/current-product-settings-step/current-product-settings-step.component.scss create mode 100644 src/app/products/current-products/current-product-stepper/current-product-settings-step/current-product-settings-step.component.ts create mode 100644 src/app/products/current-products/current-product-stepper/current-product-terms-step/current-product-terms-step.component.html create mode 100644 src/app/products/current-products/current-product-stepper/current-product-terms-step/current-product-terms-step.component.scss create mode 100644 src/app/products/current-products/current-product-stepper/current-product-terms-step/current-product-terms-step.component.ts diff --git a/.ai/mcp/mcp.json b/.ai/mcp/mcp.json new file mode 100644 index 000000000..e69de29bb diff --git a/.husky/pre-commit.bak b/.husky/pre-commit similarity index 100% rename from .husky/pre-commit.bak rename to .husky/pre-commit diff --git a/.qwen/settings.json b/.qwen/settings.json index e16bd106b..d2edcaf7c 100644 --- a/.qwen/settings.json +++ b/.qwen/settings.json @@ -6,8 +6,12 @@ "Bash(git commit *)", "Bash(select-object *)", "Bash(git config *)", - "Bash(set *)" + "Bash(set *)", + "Bash(git push)", + "Bash(xcopy *)", + "Bash(powershell *)", + "mcp__idea__build_project" ] }, "$version": 3 -} \ No newline at end of file +} diff --git a/src/app/products/charges/create-charge/create-charge.component.scss b/src/app/products/charges/create-charge/create-charge.component.scss index cc1d898a4..d48d0ed63 100644 --- a/src/app/products/charges/create-charge/create-charge.component.scss +++ b/src/app/products/charges/create-charge/create-charge.component.scss @@ -98,10 +98,10 @@ gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .cc-grid { grid-template-columns: 1fr; } diff --git a/src/app/products/charges/edit-charge/edit-charge.component.scss b/src/app/products/charges/edit-charge/edit-charge.component.scss index 8263ebc96..3126303d6 100644 --- a/src/app/products/charges/edit-charge/edit-charge.component.scss +++ b/src/app/products/charges/edit-charge/edit-charge.component.scss @@ -98,10 +98,10 @@ gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .ec-grid { grid-template-columns: 1fr; } diff --git a/src/app/products/charges/view-charge/view-charge.component.scss b/src/app/products/charges/view-charge/view-charge.component.scss index 07e5ecc49..fe770b1db 100644 --- a/src/app/products/charges/view-charge/view-charge.component.scss +++ b/src/app/products/charges/view-charge/view-charge.component.scss @@ -109,10 +109,10 @@ gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .vc-top-bar { flex-direction: column; align-items: flex-start; diff --git a/src/app/products/collaterals/create-collateral/create-collateral.component.scss b/src/app/products/collaterals/create-collateral/create-collateral.component.scss index 8943f0478..627a547d4 100644 --- a/src/app/products/collaterals/create-collateral/create-collateral.component.scss +++ b/src/app/products/collaterals/create-collateral/create-collateral.component.scss @@ -72,10 +72,10 @@ gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .ccl-section__body { grid-template-columns: 1fr; } diff --git a/src/app/products/collaterals/edit-collateral/edit-collateral.component.scss b/src/app/products/collaterals/edit-collateral/edit-collateral.component.scss index cfd828fc7..e46a033ad 100644 --- a/src/app/products/collaterals/edit-collateral/edit-collateral.component.scss +++ b/src/app/products/collaterals/edit-collateral/edit-collateral.component.scss @@ -72,10 +72,10 @@ gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .ecl-section__body { grid-template-columns: 1fr; } diff --git a/src/app/products/collaterals/view-collateral/view-collateral.component.scss b/src/app/products/collaterals/view-collateral/view-collateral.component.scss index b99904792..2ea21f890 100644 --- a/src/app/products/collaterals/view-collateral/view-collateral.component.scss +++ b/src/app/products/collaterals/view-collateral/view-collateral.component.scss @@ -99,7 +99,7 @@ } } -@media (max-width: 600px) { +@media (width <= 600px) { .vcl-top-bar { flex-direction: column; align-items: flex-start; diff --git a/src/app/products/current-products/create-current-product/create-current-product.component.html b/src/app/products/current-products/create-current-product/create-current-product.component.html index 4114aa60a..6662df7d6 100644 --- a/src/app/products/current-products/create-current-product/create-current-product.component.html +++ b/src/app/products/current-products/create-current-product/create-current-product.component.html @@ -12,14 +12,21 @@

{{ 'labels.heading.Create Current Product' | translate }}

- + {{ 'labels.inputs.DETAILS' | translate }}
- +
@@ -29,9 +36,9 @@ {{ 'labels.inputs.CURRENCY' | translate }}
- +
@@ -41,9 +48,9 @@ {{ 'labels.inputs.TERMS' | translate }}
- +
@@ -53,9 +60,9 @@ {{ 'labels.inputs.SETTINGS' | translate }}
- +
@@ -65,10 +72,22 @@ {{ 'labels.inputs.CHARGES' | translate }}
- + > +
+ + + + + + Digital + +
+
@@ -78,13 +97,13 @@ {{ 'labels.inputs.ACCOUNTING' | translate }}
- + >
@@ -95,13 +114,13 @@ {{ 'labels.inputs.PREVIEW' | translate }}
- + >
} @@ -114,19 +133,19 @@
@if (selectedTabIndex > 0) { } @if (selectedTabIndex < totalTabs - 1) { } @if (formValid && selectedTabIndex === totalTabs - 1) { } diff --git a/src/app/products/current-products/create-current-product/create-current-product.component.scss b/src/app/products/current-products/create-current-product/create-current-product.component.scss index 946837366..98f8a7bee 100644 --- a/src/app/products/current-products/create-current-product/create-current-product.component.scss +++ b/src/app/products/current-products/create-current-product/create-current-product.component.scss @@ -35,11 +35,11 @@ $primary: #1353a0; .cp-tabs { background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); ::ng-deep { .mat-mdc-tab-header { - border-bottom: 1px solid rgba(0, 0, 0, 0.08); + border-bottom: 1px solid rgb(0 0 0 / 8%); } .mat-mdc-tab { @@ -77,10 +77,10 @@ $primary: #1353a0; align-items: center; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); &__cancel { - color: rgba(0, 0, 0, 0.6); + color: rgb(0 0 0 / 60%); } &__right { diff --git a/src/app/products/current-products/create-current-product/create-current-product.component.ts b/src/app/products/current-products/create-current-product/create-current-product.component.ts index da403853c..f0b8477d5 100644 --- a/src/app/products/current-products/create-current-product/create-current-product.component.ts +++ b/src/app/products/current-products/create-current-product/create-current-product.component.ts @@ -8,13 +8,14 @@ import { Component, ViewChild, inject } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { SavingProductDetailsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component'; -import { SavingProductCurrencyStepComponent } from '../../saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component'; -import { SavingProductTermsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component'; -import { SavingProductSettingsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component'; -import { SavingProductChargesStepComponent } from '../../saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component'; -import { SavingProductAccountingStepComponent } from '../../saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component'; -import { SavingProductPreviewStepComponent } from '../../saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component'; +import { CurrentProductDetailsStepComponent } from '../current-product-stepper/current-product-details-step/current-product-details-step.component'; +import { CurrentProductCurrencyStepComponent } from '../current-product-stepper/current-product-currency-step/current-product-currency-step.component'; +import { CurrentProductTermsStepComponent } from '../current-product-stepper/current-product-terms-step/current-product-terms-step.component'; +import { CurrentProductSettingsStepComponent } from '../current-product-stepper/current-product-settings-step/current-product-settings-step.component'; +import { CurrentProductChargesStepComponent } from '../current-product-stepper/current-product-charges-step/current-product-charges-step.component'; +import { CurrentProductLimitsStepComponent } from '../current-product-stepper/current-product-limits-step/current-product-limits-step.component'; +import { CurrentProductAccountingStepComponent } from '../current-product-stepper/current-product-accounting-step/current-product-accounting-step.component'; +import { CurrentProductPreviewStepComponent } from '../current-product-stepper/current-product-preview-step/current-product-preview-step.component'; import { ProductsService } from 'app/products/products.service'; import { SettingsService } from 'app/settings/settings.service'; import { Accounting } from 'app/core/utils/accounting'; @@ -28,13 +29,14 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; imports: [ ...STANDALONE_SHARED_IMPORTS, MatTabsModule, - SavingProductDetailsStepComponent, - SavingProductCurrencyStepComponent, - SavingProductTermsStepComponent, - SavingProductSettingsStepComponent, - SavingProductChargesStepComponent, - SavingProductAccountingStepComponent, - SavingProductPreviewStepComponent + CurrentProductDetailsStepComponent, + CurrentProductCurrencyStepComponent, + CurrentProductTermsStepComponent, + CurrentProductSettingsStepComponent, + CurrentProductChargesStepComponent, + CurrentProductLimitsStepComponent, + CurrentProductAccountingStepComponent, + CurrentProductPreviewStepComponent ] }) export class CreateCurrentProductComponent { @@ -44,13 +46,14 @@ export class CreateCurrentProductComponent { private settingsService = inject(SettingsService); private accounting = inject(Accounting); - @ViewChild(SavingProductDetailsStepComponent, { static: true }) detailsStep: SavingProductDetailsStepComponent; - @ViewChild(SavingProductCurrencyStepComponent, { static: true }) currencyStep: SavingProductCurrencyStepComponent; - @ViewChild(SavingProductTermsStepComponent, { static: true }) termsStep: SavingProductTermsStepComponent; - @ViewChild(SavingProductSettingsStepComponent, { static: true }) settingsStep: SavingProductSettingsStepComponent; - @ViewChild(SavingProductChargesStepComponent, { static: true }) chargesStep: SavingProductChargesStepComponent; - @ViewChild(SavingProductAccountingStepComponent, { static: true }) - accountingStep: SavingProductAccountingStepComponent; + @ViewChild(CurrentProductDetailsStepComponent, { static: true }) detailsStep: CurrentProductDetailsStepComponent; + @ViewChild(CurrentProductCurrencyStepComponent, { static: true }) currencyStep: CurrentProductCurrencyStepComponent; + @ViewChild(CurrentProductTermsStepComponent, { static: true }) termsStep: CurrentProductTermsStepComponent; + @ViewChild(CurrentProductSettingsStepComponent, { static: true }) settingsStep: CurrentProductSettingsStepComponent; + @ViewChild(CurrentProductChargesStepComponent, { static: true }) chargesStep: CurrentProductChargesStepComponent; + @ViewChild(CurrentProductLimitsStepComponent, { static: true }) limitsStep: CurrentProductLimitsStepComponent; + @ViewChild(CurrentProductAccountingStepComponent, { static: true }) + accountingStep: CurrentProductAccountingStepComponent; currentProductsTemplate: any; accountingRuleData: string[] = []; @@ -64,23 +67,26 @@ export class CreateCurrentProductComponent { } get detailsForm() { - return this.detailsStep.savingProductDetailsForm; + return this.detailsStep.CurrentProductDetailsForm; } get currencyForm() { - return this.currencyStep.savingProductCurrencyForm; + return this.currencyStep.CurrentProductCurrencyForm; } get termsForm() { - return this.termsStep.savingProductTermsForm; + return this.termsStep.CurrentProductTermsForm; } get settingsForm() { - return this.settingsStep.savingProductSettingsForm; + return this.settingsStep.CurrentProductSettingsForm; + } + get limitsForm(): void { + return; // Limits step doesn't have a form, it's data-driven } get accountingForm() { - return this.accountingStep.savingProductAccountingForm; + return this.accountingStep.CurrentProductAccountingForm; } get totalTabs(): number { - return this.formValid ? 7 : 6; + return this.formValid ? 8 : 7; } get formValid() { @@ -95,23 +101,50 @@ export class CreateCurrentProductComponent { get currentProduct() { return { - ...this.detailsStep.savingProductDetails, - ...this.currencyStep.savingProductCurrency, - ...this.termsStep.savingProductTerms, - ...this.settingsStep.savingProductSettings, - ...this.chargesStep.savingProductCharges, - ...this.accountingStep.savingProductAccounting + ...this.detailsStep.CurrentProductDetails, + ...this.currencyStep.CurrentProductCurrency, + ...this.termsStep.CurrentProductTerms, + ...this.settingsStep.CurrentProductSettings, + ...this.chargesStep.CurrentProductCharges, + ...this.limitsStep.CurrentProductLimits, + ...this.accountingStep.CurrentProductAccounting }; } - submit() { - const product = { + private prepareCurrentProductPayload() { + const currentProduct = { ...this.currentProduct, charges: this.currentProduct.charges.map((charge: any) => ({ id: charge.id })), - locale: this.settingsService.language.code + locale: this.settingsService.language.code, + dateFormat: 'dd MMMM yyyy' }; - delete product.advancedAccountingRules; - this.productsService.createCurrentProduct(product).subscribe((response: any) => { + delete currentProduct.advancedAccountingRules; + + // Convert lockinPeriodFrequencyType from string to integer + // Backend expects: 0=DAYS, 1=WEEKS, 2=MONTHS, 3=YEARS + if (currentProduct.lockinPeriodFrequencyType) { + const frequencyTypeMapping: { [key: string]: number } = { + DAYS: 0, + WEEKS: 1, + MONTHS: 2, + YEARS: 3 + }; + currentProduct.lockinPeriodFrequencyType = frequencyTypeMapping[currentProduct.lockinPeriodFrequencyType]; + } + + Object.keys(currentProduct).forEach((key) => { + if (currentProduct[key] === '' || currentProduct[key] === null || currentProduct[key] === undefined) { + delete currentProduct[key]; + } + }); + + return currentProduct; + } + + submit() { + const currentProduct = this.prepareCurrentProductPayload(); + console.log('Current product submit payload:', currentProduct); + this.productsService.createCurrentProduct(currentProduct).subscribe((response: any) => { this.router.navigate( [ '../', diff --git a/src/app/products/current-products/current-product-stepper/current-product-accounting-step/current-product-accounting-step.component.html b/src/app/products/current-products/current-product-stepper/current-product-accounting-step/current-product-accounting-step.component.html new file mode 100644 index 000000000..b22ff2e6e --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-accounting-step/current-product-accounting-step.component.html @@ -0,0 +1,121 @@ + + +
+
+
+ + + +

Accounting

+
+ +
+
+
+

Accounting Rule

+
+
+ Cash +
+
+ + @if (isCashOrAccrualAccounting()) { + @for (section of accountingSectionConfigs; track section.key) { +
+
+

{{ section.title }}

+ +
+
+ @for (row of section.rows; track row.key) { + @if ( + (!row.accrualOnly || isAccrualAccounting()) && (!row.dormancyOnly || isDormancyTrackingEnabled()) + ) { +
+
+ + {{ section.pillLabel }} + + {{ row.label }} +
+ +
+ } + } +
+ +
+ @for ( + otherRow of getOtherGlMappingsArray(section.otherMappingsControlName).controls; + track $index; + let i = $index + ) { +
+
+
+ + {{ section.pillLabel }} + + +
+ +
+ + GL Code + + @for (glAccount of section.otherAccountOptions; track glAccount.id) { + + {{ '(' + glAccount.glCode + ') ' + glAccount.name }} + + } + + +
+
+ +
+ } +
+
+ } + } +
+
+
diff --git a/src/app/products/current-products/current-product-stepper/current-product-accounting-step/current-product-accounting-step.component.scss b/src/app/products/current-products/current-product-stepper/current-product-accounting-step/current-product-accounting-step.component.scss new file mode 100644 index 000000000..30125e801 --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-accounting-step/current-product-accounting-step.component.scss @@ -0,0 +1,314 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$border: #e2e8f0; +$gray-50: #f8f9fa; +$gray-200: #e8eaed; +$gray-500: #6b7280; +$gray-700: #334155; +$gray-900: #202124; +$primary: #1353a0; + +.spa-section { + border: 1px solid $border; + border-radius: 10px; + overflow: hidden; + background: #fff; +} + +.spa-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; + } +} + +.spa-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; +} + +.spa-section__body { + padding: 16px 24px 20px; +} + +.spa-group { + & + & { + margin-top: 20px; + } + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin: 0 0 12px; + } + + h4 { + margin: 0 0 12px; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #94a3b8; + } +} + +.spa-add-btn { + border: 0; + background: transparent; + color: #2563eb; + font-size: 12px; + font-weight: 700; + padding: 0; + cursor: pointer; +} + +.spa-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.spa-list + .spa-list { + margin-top: 12px; +} + +.spa-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 10px 12px; + border: 1px solid #e2e8f0; + border-radius: 8px; + background: #fff; + + @media (width <= 900px) { + flex-direction: column; + align-items: stretch; + } +} + +.spa-row:has(mifosx-gl-account-selector) { + align-items: stretch; +} + +.spa-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; + } +} + +.spa-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; + } +} + +.spa-row__meta { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; + flex: 1 1 auto; +} + +.spa-row__meta--other { + min-width: 0; + flex: 1 1 auto; + align-self: stretch; +} + +.spa-row__label { + font-size: 14px; + font-weight: 600; + color: $gray-700; +} + +.spa-row__input { + flex: 0 0 22rem; + max-width: 22rem; + min-width: 18rem; + + @media (width <= 900px) { + width: 100%; + max-width: 100%; + min-width: 0; + } +} + +.spa-row__input--other { + flex: 1 1 auto; + max-width: 100%; + min-width: 0; +} + +.spa-row__input :is(mifosx-gl-account-selector) { + width: 100%; + display: block; +} + +.spa-row__input :is(mifosx-gl-account-selector) :is(.layout-column) { + width: 100%; + margin-right: 0 !important; +} + +.spa-row__input :is(mifosx-gl-account-selector) :is(mat-form-field) { + width: 100%; +} + +.spa-row__actions { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; +} + +.spa-row__actions--other { + justify-content: flex-end; + align-self: stretch; + width: 100%; + + @media (width <= 1100px) { + justify-content: flex-start; + } +} + +.spa-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%; + } +} + +.spa-row__name--code { + max-width: 20rem; +} + +.spa-row__name--other-account { + max-width: 100%; + flex: 1 1 auto; + height: 48px; +} + +.spa-row__other-field { + width: 100%; + min-width: 0; +} + +.spa-row__other-field :is(mat-form-field) { + width: 100%; +} + +.spa-row__other-field :is(.mat-mdc-form-field-required-marker) { + display: none; +} + +.spa-remove-btn { + border: 0; + background: transparent; + color: #ef4444; + font-size: 18px; + line-height: 1; + cursor: pointer; + padding: 0 4px; +} + +.spa-remove-btn--side { + place-self: center end; + margin-right: 4px; + + @media (width <= 1100px) { + justify-self: start; + margin-right: 0; + } +} + +.spa-pill { + display: inline-flex; + align-items: center; + 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; + + &--green { + background: #dcfce7; + color: #16a34a; + } + + &--amber { + background: #fef3c7; + color: #d97706; + } + + &--blue { + background: #dbeafe; + color: #2563eb; + } +} + +datalist { + display: none; +} + +@media (width <= 900px) { + .spa-row__meta { + margin-bottom: 4px; + } + + .spa-row__input { + flex: 1 1 auto; + max-width: 100%; + } +} diff --git a/src/app/products/current-products/current-product-stepper/current-product-accounting-step/current-product-accounting-step.component.ts b/src/app/products/current-products/current-product-stepper/current-product-accounting-step/current-product-accounting-step.component.ts new file mode 100644 index 000000000..252629bc5 --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-accounting-step/current-product-accounting-step.component.ts @@ -0,0 +1,495 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, Input, OnInit, inject } from '@angular/core'; +import { + FormsModule, + ReactiveFormsModule, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validators +} from '@angular/forms'; +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 { 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; + dormancyOnly?: 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-current-product-accounting-step', + templateUrl: './current-product-accounting-step.component.html', + styleUrls: ['./current-product-accounting-step.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + FormsModule, + ReactiveFormsModule, + MatRadioGroup, + MatRadioButton, + GlAccountSelectorComponent + ] +}) +export class CurrentProductAccountingStepComponent implements OnInit { + private formBuilder = inject(UntypedFormBuilder); + private accounting = inject(Accounting); + + @Input() CurrentProductsTemplate: any; + @Input() accountingRuleData: any; + @Input() isDormancyTrackingActive: any; + @Input() CurrentProductFormValid: boolean; + @Input() allowOverdraft: any; + + CurrentProductAccountingForm: UntypedFormGroup; + liabilityAccountOptions: GLAccount[] = []; + expenseAccountOptions: GLAccount[] = []; + incomeAccountOptions: GLAccount[] = []; + assetAccountOptions: GLAccount[] = []; + accountingSectionConfigs: AccountingSectionConfig[] = []; + + constructor() { + this.createCurrentProductAccountingForm(); + this.setConditionalControls(); + } + + ngOnInit() { + const accountingMappings = this.CurrentProductsTemplate?.accountingMappings || {}; + // otherGlMappingsMap is at the root of the template, not inside accountingMappings + const mappings = { + ...accountingMappings, + otherGlMappingsMap: this.CurrentProductsTemplate?.otherGlMappingsMap + }; + const accountingMappingOptions = this.CurrentProductsTemplate?.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.CurrentProductAccountingForm.patchValue({ + accountingRule: accountingRuleId + }); + + this.syncAccountingControls(accountingRuleId); + this.patchAccountingValues(mappings); + this.setOverdraftControl(); + this.setDormancyControl(); + } + + createCurrentProductAccountingForm() { + this.CurrentProductAccountingForm = this.formBuilder.group({ + accountingRule: [2], + assetsOtherGlMappings: this.formBuilder.array([]), + liabilitiesOtherGlMappings: this.formBuilder.array([]), + expensesOtherGlMappings: this.formBuilder.array([]), + incomeOtherGlMappings: this.formBuilder.array([]) + }); + } + + private setConditionalControls() { + this.CurrentProductAccountingForm.get('accountingRule')?.valueChanges.subscribe((accountingRule: number) => { + this.syncAccountingControls(accountingRule); + }); + } + + private patchAccountingValues(mappings: any) { + this.CurrentProductAccountingForm.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 || '', + incomeFromInterestId: mappings.incomeFromInterest?.id || '', + overdraftPortfolioControlId: mappings.overdraftPortfolioControl?.id || '', + writeOffAccountId: mappings.writeOffAccount?.id || '', + feesReceivableAccountId: mappings.feeReceivableAccount?.id || '', + penaltiesReceivableAccountId: mappings.penaltyReceivableAccount?.id || '', + interestPayableAccountId: mappings.interestPayableAccount?.id || '', + interestReceivableAccountId: mappings.interestReceivableAccount?.id || '', + escheatLiabilityId: mappings.escheatLiabilityAccount?.id || '' + }); + + if (this.isCashOrAccrualAccounting()) { + // Backend serializes as 'otherGlMappingsMap'; fall back to 'otherGlMappings' for compatibility + 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); + } + } + + private createOtherGlMappingGroup(otherGlMapping: Partial = {}) { + return this.formBuilder.group({ + accountName: [ + otherGlMapping.accountName || '', + Validators.required + ], + glAccountId: [ + otherGlMapping.glAccountId || '', + Validators.required + ] + }); + } + + getOtherGlMappingsArray(controlName: string): UntypedFormArray { + return this.CurrentProductAccountingForm.get(controlName) as UntypedFormArray; + } + + private setOtherGlMappings(controlName: string, otherGlMappings: any[], options: GLAccount[]) { + const otherGlMappingsArray = this.getOtherGlMappingsArray(controlName); + otherGlMappingsArray.clear(); + (otherGlMappings || []).forEach((otherGlMapping) => { + otherGlMappingsArray.push( + this.createOtherGlMappingGroup({ + accountName: otherGlMapping.accountName, + glAccountId: + otherGlMapping.glAccountId || options.find((option) => option.glCode === otherGlMapping.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((controlName) => this.getOtherGlMappingsArray(controlName).clear()); + } + + private setOverdraftControl() { + this.allowOverdraft?.valueChanges?.subscribe(() => { + this.syncAccountingControls(this.CurrentProductAccountingForm.value.accountingRule); + }); + } + + private setDormancyControl() { + this.isDormancyTrackingActive?.valueChanges?.subscribe(() => { + this.syncAccountingControls(this.CurrentProductAccountingForm.value.accountingRule); + }); + } + + private getOtherGlMappings() { + const mapOtherGlMappings = (controlName: string, options: GLAccount[]) => + this.getOtherGlMappingsArray(controlName) + .value.filter((item: OtherGlMappingValue) => !!item.accountName || !!item.glAccountId) + .map((item: OtherGlMappingValue) => ({ + accountName: item.accountName, + glCode: options.find((option) => option.id === item.glAccountId)?.glCode || '' + })) + .filter((item: { accountName: string; glCode: string }) => !!item.accountName || !!item.glCode); + + const otherGlMappings = { + assets: mapOtherGlMappings('assetsOtherGlMappings', this.assetAccountOptions), + liabilities: mapOtherGlMappings('liabilitiesOtherGlMappings', this.liabilityAccountOptions), + expenses: mapOtherGlMappings('expensesOtherGlMappings', this.expenseAccountOptions), + income: mapOtherGlMappings('incomeOtherGlMappings', this.incomeAccountOptions) + }; + + return Object.values(otherGlMappings).some((value) => value.length) ? otherGlMappings : null; + } + + isDormancyTrackingEnabled(): boolean { + return this.isDormancyTrackingActive?.value ?? this.isDormancyTrackingActive ?? false; + } + + private syncAccountingControls(accountingRule: number) { + const isDormancyActive = this.isDormancyTrackingEnabled(); + + const addControl = (controlName: string, required = false) => { + if (!this.CurrentProductAccountingForm.contains(controlName)) { + this.CurrentProductAccountingForm.addControl( + controlName, + new UntypedFormControl('', required ? Validators.required : undefined) + ); + } else if (required) { + this.CurrentProductAccountingForm.get(controlName)?.setValidators(Validators.required); + this.CurrentProductAccountingForm.get(controlName)?.updateValueAndValidity({ emitEvent: false }); + } + }; + + const removeControl = (controlName: string) => { + if (this.CurrentProductAccountingForm.contains(controlName)) { + this.CurrentProductAccountingForm.removeControl(controlName); + } + }; + + if (this.accounting.isCashOrAccrualAccountingRuleId(accountingRule)) { + addControl('savingsReferenceAccountId', true); + addControl('savingsControlAccountId', true); + addControl('transfersInSuspenseAccountId', true); + addControl('interestOnSavingsAccountId', true); + addControl('incomeFromFeeAccountId', true); + addControl('incomeFromPenaltyAccountId', true); + addControl('incomeFromInterestId', true); + addControl('writeOffAccountId', true); + addControl('overdraftPortfolioControlId', false); + + if (this.accounting.isAccrualAccountingRuleId(accountingRule)) { + addControl('feesReceivableAccountId', true); + addControl('penaltiesReceivableAccountId', true); + addControl('interestPayableAccountId', true); + addControl('interestReceivableAccountId', false); + } else { + removeControl('feesReceivableAccountId'); + removeControl('penaltiesReceivableAccountId'); + removeControl('interestPayableAccountId'); + removeControl('interestReceivableAccountId'); + } + + if (isDormancyActive) { + addControl('escheatLiabilityId', false); + } else { + removeControl('escheatLiabilityId'); + } + } else { + removeControl('savingsReferenceAccountId'); + removeControl('savingsControlAccountId'); + removeControl('transfersInSuspenseAccountId'); + removeControl('interestOnSavingsAccountId'); + removeControl('incomeFromFeeAccountId'); + removeControl('incomeFromPenaltyAccountId'); + removeControl('incomeFromInterestId'); + removeControl('writeOffAccountId'); + removeControl('overdraftPortfolioControlId'); + removeControl('feesReceivableAccountId'); + removeControl('penaltiesReceivableAccountId'); + removeControl('interestPayableAccountId'); + removeControl('interestReceivableAccountId'); + removeControl('escheatLiabilityId'); + this.clearOtherGlMappings(); + } + } + + get CurrentProductAccounting() { + const { + assetsOtherGlMappings, + liabilitiesOtherGlMappings, + expensesOtherGlMappings, + incomeOtherGlMappings, + ...CurrentProductAccounting + } = this.CurrentProductAccountingForm.value; + const otherGlMappings = this.getOtherGlMappings(); + + return { + ...CurrentProductAccounting, + ...(otherGlMappings ? { otherGlMappings } : {}) + }; + } + + isCashOrAccrualAccounting(): boolean { + return this.accounting.isCashOrAccrualAccountingRuleId(this.CurrentProductAccountingForm.value.accountingRule); + } + + isAccrualAccounting(): boolean { + return this.accounting.isAccrualAccountingRuleId(this.CurrentProductAccountingForm.value.accountingRule); + } + + 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: 'overdraftPortfolioControlId', + label: 'Overdraft Portfolio Control', + inputLabel: 'Overdraft portfolio control', + controlName: 'overdraftPortfolioControlId', + options: this.assetAccountOptions + }, + { + key: 'interestReceivableAccountId', + label: 'Interest Receivable Account', + inputLabel: 'Interest receivable account', + controlName: 'interestReceivableAccountId', + options: this.assetAccountOptions, + accrualOnly: 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 + }, + { + key: 'escheatLiabilityId', + label: 'Escheat Liability Account', + inputLabel: 'Escheat liability account', + controlName: 'escheatLiabilityId', + options: this.liabilityAccountOptions, + dormancyOnly: 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 + }, + { + key: 'writeOffAccountId', + label: 'Write Off Account', + inputLabel: 'Write off account', + controlName: 'writeOffAccountId', + 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 + }, + { + key: 'incomeFromInterestId', + label: 'Income From Interest', + inputLabel: 'Income from interest', + controlName: 'incomeFromInterestId', + options: this.incomeAccountOptions, + required: true + } + ], + otherMappingsControlName: 'incomeOtherGlMappings', + otherAccountOptions: this.incomeAccountOptions + } + ]; + } +} diff --git a/src/app/products/current-products/current-product-stepper/current-product-charges-step/current-product-charges-step.component.html b/src/app/products/current-products/current-product-stepper/current-product-charges-step/current-product-charges-step.component.html new file mode 100644 index 000000000..c72d119fe --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-charges-step/current-product-charges-step.component.html @@ -0,0 +1,93 @@ + + +
+
+ +

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

+
+
+
+ + {{ '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 | translateKey: 'catalogs' }} + {{ 'labels.inputs.Amount' | translate }} + {{ charge.amount | formatNumber }} + {{ 'labels.inputs.Collected On' | translate }} + {{ charge.chargeTimeType.value | translateKey: 'catalogs' }} + {{ 'labels.inputs.Actions' | translate }} + +
+
+ } + + @if (chargesDataSource.length === 0) { +
+ + {{ 'labels.text.No charges added yet' | translate }} +
+ } +
+
diff --git a/src/app/products/current-products/current-product-stepper/current-product-charges-step/current-product-charges-step.component.scss b/src/app/products/current-products/current-product-stepper/current-product-charges-step/current-product-charges-step.component.scss new file mode 100644 index 000000000..f272b71f6 --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-charges-step/current-product-charges-step.component.scss @@ -0,0 +1,103 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$primary: #1353a0; +$accent: #0ea5e9; +$border: #e2e8f0; +$gray-50: #f8f9fa; +$gray-200: #e8eaed; +$gray-500: #9aa0a6; +$gray-900: #202124; + +.spch-section { + border: 1px solid $border; + border-radius: 10px; + overflow: hidden; + background: #fff; + + &__header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + background: $gray-50; + border-bottom: 1px solid $gray-200; + + i { + font-size: 14px; + color: $accent; + } + + h3 { + margin: 0; + font-size: 14px; + font-weight: 600; + color: $gray-900; + } + } +} + +.spch-body { + padding: 16px 24px; +} + +.spch-add-row { + display: flex; + align-items: flex-start; + gap: 12px; + + &__select { + flex: 1; + max-width: 400px; + } + + &__btn { + margin-top: 4px; + height: 36px !important; + border-radius: 8px !important; + font-size: 13px !important; + font-weight: 600 !important; + box-shadow: 0 1px 3px rgba($primary, 0.3) !important; + } + + @media (width <= 600px) { + flex-direction: column; + + &__select { + max-width: 100%; + width: 100%; + } + } +} + +.spch-table-wrap { + margin-top: 8px; + border: 1px solid $border; + border-radius: 8px; + overflow: hidden; +} + +.spch-table { + width: 100%; +} + +.spch-empty { + display: flex; + align-items: center; + gap: 8px; + padding: 16px; + margin-top: 8px; + background: $gray-50; + border-radius: 8px; + color: $gray-500; + font-size: 13px; + + i { + font-size: 14px; + } +} diff --git a/src/app/products/current-products/current-product-stepper/current-product-charges-step/current-product-charges-step.component.ts b/src/app/products/current-products/current-product-stepper/current-product-charges-step/current-product-charges-step.component.ts new file mode 100644 index 000000000..d6cafa12d --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-charges-step/current-product-charges-step.component.ts @@ -0,0 +1,108 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, Input, OnInit, inject } from '@angular/core'; +import { UntypedFormControl } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; + +import { TranslateService } from '@ngx-translate/core'; +import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component'; +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 { ChargesFilterPipe } from '../../../../pipes/charges-filter.pipe'; +import { FormatNumberPipe } from '../../../../pipes/format-number.pipe'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-current-product-charges-step', + templateUrl: './current-product-charges-step.component.html', + styleUrls: ['./current-product-charges-step.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + FaIconComponent, + MatTable, + MatColumnDef, + MatHeaderCellDef, + MatHeaderCell, + MatCellDef, + MatCell, + MatIconButton, + MatHeaderRowDef, + MatHeaderRow, + MatRowDef, + MatRow, + ChargesFilterPipe, + FormatNumberPipe + ] +}) +export class CurrentProductChargesStepComponent implements OnInit { + dialog = inject(MatDialog); + private translateService = inject(TranslateService); + + @Input() CurrentProductsTemplate: any; + @Input() currencyCode: UntypedFormControl; + + chargeData: any; + + chargesDataSource: {}[]; + displayedColumns: string[] = [ + 'name', + 'chargeCalculationType', + 'amount', + 'chargeTimeType', + 'action' + ]; + + pristine = true; + + ngOnInit() { + this.chargeData = this.CurrentProductsTemplate.chargeOptions; + + this.chargesDataSource = this.CurrentProductsTemplate.charges || []; + this.pristine = true; + + this.currencyCode.valueChanges.subscribe(() => (this.chargesDataSource = [])); + } + + addCharge(charge: any) { + this.chargesDataSource = this.chargesDataSource.concat([charge.value]); + charge.value = ''; + this.pristine = false; + } + + deleteCharge(charge: any) { + const deleteChargeDialogRef = this.dialog.open(DeleteDialogComponent, { + data: { deleteContext: this.translateService.instant('labels.inputs.Charge') + ' ' + charge.name } + }); + deleteChargeDialogRef.afterClosed().subscribe((response: any) => { + if (response.delete) { + this.chargesDataSource.splice(this.chargesDataSource.indexOf(charge), 1); + this.chargesDataSource = this.chargesDataSource.concat([]); + this.pristine = false; + } + }); + } + + get CurrentProductCharges() { + return { + charges: this.chargesDataSource + }; + } +} diff --git a/src/app/products/current-products/current-product-stepper/current-product-currency-step/current-product-currency-step.component.html b/src/app/products/current-products/current-product-stepper/current-product-currency-step/current-product-currency-step.component.html new file mode 100644 index 000000000..1fb165c2d --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-currency-step/current-product-currency-step.component.html @@ -0,0 +1,111 @@ + + +
+
+
+ + Currency & Account Status Configuration +
+
+
+ + {{ 'labels.inputs.Currency' | translate }} + + @for (currency of currencyData; track currency) { + + {{ currency.name }} + + } + + Default: Indian Rupee - auto-set for domestic products + + {{ '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/current-products/current-product-stepper/current-product-currency-step/current-product-currency-step.component.scss b/src/app/products/current-products/current-product-stepper/current-product-currency-step/current-product-currency-step.component.scss new file mode 100644 index 000000000..a287d691e --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-currency-step/current-product-currency-step.component.scss @@ -0,0 +1,114 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$accent: #0ea5e9; +$border: #e2e8f0; +$gray-50: #f8f9fa; +$gray-200: #e8eaed; +$gray-900: #202124; + +.spc-section { + border: 1px solid $border; + border-radius: 10px; + overflow: hidden; + background: #fff; + + & + & { + margin-top: 16px; + } + + &__header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + background: $gray-50; + border-bottom: 1px solid $gray-200; + + i { + font-size: 14px; + color: $accent; + } + + h3 { + margin: 0; + font-size: 14px; + font-weight: 600; + color: $gray-900; + } + } +} + +.spc-section__badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 6px; + background: #eff6ff; + font-size: 14px; +} + +.spc-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 4px 16px; + padding: 16px 24px; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} + +.spc-body { + background: #fff; +} + +.spc-field { + width: 100%; +} + +.spc-alert { + display: flex; + align-items: center; + 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/current-products/current-product-stepper/current-product-currency-step/current-product-currency-step.component.ts b/src/app/products/current-products/current-product-stepper/current-product-currency-step/current-product-currency-step.component.ts new file mode 100644 index 000000000..37c7b4c56 --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-currency-step/current-product-currency-step.component.ts @@ -0,0 +1,94 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, OnInit, Input, inject } from '@angular/core'; +import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; +import { MatTooltip } from '@angular/material/tooltip'; +import { MatRadioGroup, MatRadioButton } from '@angular/material/radio'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-current-product-currency-step', + templateUrl: './current-product-currency-step.component.html', + styleUrls: ['./current-product-currency-step.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatTooltip, + MatRadioGroup, + MatRadioButton, + FaIconComponent + ] +}) +export class CurrentProductCurrencyStepComponent implements OnInit { + private formBuilder = inject(UntypedFormBuilder); + + @Input() CurrentProductsTemplate: any; + + CurrentProductCurrencyForm: UntypedFormGroup; + + currencyData: any; + decimalPlacesData = [ + 2, + 0, + 3 + ]; + + constructor() { + this.createCurrentProductCurrencyForm(); + } + + ngOnInit() { + this.currencyData = this.CurrentProductsTemplate.currencyOptions; + + this.CurrentProductCurrencyForm.patchValue({ + currencyCode: this.CurrentProductsTemplate.currency?.code || this.currencyData?.[0]?.code || '', + digitsAfterDecimal: + this.CurrentProductsTemplate.digitsAfterDecimal ?? this.CurrentProductsTemplate.currency?.decimalPlaces ?? 2, + dormancyAlert: this.CurrentProductsTemplate.dormancyAlert ?? false, + balanceSwapOption: this.CurrentProductsTemplate.balanceSwapOption ?? false, + msme: this.CurrentProductsTemplate.msme ?? false, + nominationFacility: this.CurrentProductsTemplate.nominationFacility ?? false, + autoDebitFacility: this.CurrentProductsTemplate.autoDebitFacility ?? false + }); + } + + createCurrentProductCurrencyForm() { + this.CurrentProductCurrencyForm = this.formBuilder.group({ + currencyCode: [ + '', + Validators.required + ], + digitsAfterDecimal: [ + 2, + [ + Validators.required, + Validators.min(0) + ] + ], + dormancyAlert: [false], + balanceSwapOption: [false], + msme: [false], + nominationFacility: [false], + autoDebitFacility: [false] + }); + } + + get CurrentProductCurrency() { + const formValue = this.CurrentProductCurrencyForm.value; + return { + currencyCode: formValue.currencyCode, + digitsAfterDecimal: formValue.digitsAfterDecimal, + dormancyAlert: formValue.dormancyAlert, + balanceSwapOption: formValue.balanceSwapOption, + msme: formValue.msme, + nominationFacility: formValue.nominationFacility, + autoDebitFacility: formValue.autoDebitFacility + }; + } +} diff --git a/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.html b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.html new file mode 100644 index 000000000..7b619793d --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.html @@ -0,0 +1,117 @@ + + +
+
+
+ +

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

+
+
+ + {{ 'labels.inputs.Product Name' | translate }} + + + {{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + + + + {{ 'labels.inputs.Short Name' | translate }} + + + {{ 'labels.inputs.Short Name' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + + + + {{ 'labels.inputs.Description' | 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 }} + } + + + + + Product Code + + + + + Effective To + + + + + + + End Date + + + + + + + Introducer Required + + @for (option of yesNoOptions; track option.value) { + {{ option.label }} + } + + + +
+ Share Linking Enabled +
+
+
+
diff --git a/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.scss b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.scss new file mode 100644 index 000000000..7b5c920ef --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.scss @@ -0,0 +1,70 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$accent: #0ea5e9; +$border: #e2e8f0; +$gray-50: #f8f9fa; +$gray-200: #e8eaed; +$gray-900: #202124; + +.spd-section { + border: 1px solid $border; + border-radius: 10px; + overflow: hidden; + background: #fff; + + &__header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + background: $gray-50; + border-bottom: 1px solid $gray-200; + + i { + font-size: 14px; + color: $accent; + } + + h3 { + margin: 0; + font-size: 14px; + font-weight: 600; + color: $gray-900; + } + } +} + +.spd-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 4px 16px; + padding: 16px 24px; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} + +.spd-grid--classification { + grid-template-columns: repeat(3, 1fr); +} + +.spd-field { + width: 100%; + + &--full { + grid-column: 1 / -1; + } +} + +.spd-checkbox-row { + display: flex; + align-items: center; + min-height: 56px; +} diff --git a/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.ts b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.ts new file mode 100644 index 000000000..1aba2fbac --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.ts @@ -0,0 +1,190 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core'; +import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; +import { MatTooltip } from '@angular/material/tooltip'; +import { CdkTextareaAutosize } from '@angular/cdk/text-field'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { Dates } from 'app/core/utils/dates'; +import { SettingsService } from 'app/settings/settings.service'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-current-product-details-step', + templateUrl: './current-product-details-step.component.html', + styleUrls: ['./current-product-details-step.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatTooltip, + CdkTextareaAutosize, + FaIconComponent + ] +}) +export class CurrentProductDetailsStepComponent implements OnInit, OnChanges { + private formBuilder = inject(UntypedFormBuilder); + private dateUtils = inject(Dates); + private settingsService = inject(SettingsService); + + @Input() CurrentProductsTemplate: any; + + CurrentProductDetailsForm: UntypedFormGroup; + accountTypeData: string[] = []; + customerSegmentData: string[] = []; + yesNoOptions: Array<{ + value: boolean; + label: string; + }> = [ + { + value: true, + label: 'Yes' + }, + { + value: false, + label: 'No' + } + ]; + + constructor() { + this.createCurrentProductDetailsForm(); + } + + ngOnInit() { + if (this.CurrentProductsTemplate) { + this.applyTemplate(this.CurrentProductsTemplate); + } + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.CurrentProductsTemplate?.currentValue) { + this.applyTemplate(changes.CurrentProductsTemplate.currentValue); + } + } + + createCurrentProductDetailsForm() { + this.CurrentProductDetailsForm = this.formBuilder.group({ + name: [ + '', + Validators.required + ], + shortName: [ + '', + Validators.required + ], + description: [''], + accountType: [ + null, + Validators.required + ], + customerSegment: [null], + productCode: [''], + effectiveTo: [''], + endDate: [''], + introducerRequired: [false], + shareLinkingEnabled: [false] + }); + } + + private getOptionNames(options: any[]): string[] { + if (!Array.isArray(options)) { + return []; + } + + return options + .map((option) => option?.name ?? option?.value ?? option) + .filter((value) => typeof value === 'string' && value.trim().length > 0); + } + + private parseDate(dateValue: any): Date | null { + if (!dateValue) return null; + + // Handle array format [year, month, day] + if (Array.isArray(dateValue)) { + return new Date(dateValue[0], dateValue[1] - 1, dateValue[2]); + } + + // Handle Date object + if (dateValue instanceof Date) { + return dateValue; + } + + // Handle string format + return new Date(dateValue); + } + + private applyTemplate(template: any) { + const accountTypeOptions = this.getOptionNames(template.accountTypeOptions); + const customerSegmentOptions = this.getOptionNames(template.customerSegmentOptions); + const yesNoOptions = this.getYesNoOptions(template.yesNoOptions); + + this.accountTypeData = accountTypeOptions; + this.customerSegmentData = customerSegmentOptions; + if (yesNoOptions.length) { + this.yesNoOptions = yesNoOptions; + } + + // Use existing product data if available (when editing), otherwise use template defaults + const isEditing = !!template.id; + this.CurrentProductDetailsForm.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, + productCode: template.productCode ?? '', + effectiveTo: this.parseDate(template.effectiveTo), + endDate: this.parseDate(template.endDate), + introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes', + shareLinkingEnabled: !!template.shareLinkingEnabled + }); + } + + private getYesNoOptions(options: any[]): Array<{ value: boolean; label: string }> { + if (!Array.isArray(options)) { + return []; + } + + return options + .map((option) => { + const rawValue = option?.name ?? option?.value ?? option; + const normalized = typeof rawValue === 'string' ? rawValue.trim().toUpperCase() : rawValue; + + if (normalized === 'YES') { + return { + value: true, + label: 'Yes' + }; + } + + if (normalized === 'NO') { + return { + value: false, + label: 'No' + }; + } + + return null; + }) + .filter((value) => !!value); + } + + get CurrentProductDetails() { + const formValue = { ...this.CurrentProductDetailsForm.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/current-products/current-product-stepper/current-product-limits-step/current-product-limits-step.component.html b/src/app/products/current-products/current-product-stepper/current-product-limits-step/current-product-limits-step.component.html new file mode 100644 index 000000000..0a78185a6 --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-limits-step/current-product-limits-step.component.html @@ -0,0 +1,351 @@ + +
+
+ + + +
Transaction & Product Limits
+
R&D BASED DEFAULTS
+
+ +
+
+
Account Features (as per RBI guidelines)
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + + + + + + + + + + + + + @for (row of accountFacilityLimits; track row.limitType) { + + + + + + + } + +
Limit TypeGeneral Limit (Daily)Minor Account LimitSmall Saving Limit
{{ row.limitType }} + + + + + +
+
+ +
+

Deposit Limit (KYC)

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Account TypeKYC TypeLimit
General + + + +
Minor + + + +
Small Saving + + + +
+
+ +
+ Note: + Small Saving Account limits are strictly capped at Rs.1,00,000 maximum balance and Rs.10,000 monthly withdrawal + per RBI Master Circular. Minor accounts are restricted from RTGS and high-risk digital products (BEPG/BBPS) by + default. +
+
+
diff --git a/src/app/products/current-products/current-product-stepper/current-product-limits-step/current-product-limits-step.component.scss b/src/app/products/current-products/current-product-stepper/current-product-limits-step/current-product-limits-step.component.scss new file mode 100644 index 000000000..f0f7cad32 --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-limits-step/current-product-limits-step.component.scss @@ -0,0 +1,257 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$border: #d9e2ec; +$border-strong: #c7d2de; +$text: #24364b; +$muted: #64748b; +$header-bg: #f8fafc; +$highlight-bg: #eff6ff; + +.spl-section { + border: 1px solid $border; + border-radius: 12px; + overflow: hidden; + background: #fff; +} + +.spl-section__header { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + background: $header-bg; + border-bottom: 1px solid $border; +} + +.spl-section__icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 8px; + background: #eff6ff; + color: #2563eb; + font-size: 14px; + flex: 0 0 auto; +} + +.spl-section__title { + flex: 1 1 auto; + font-size: 16px; + font-weight: 600; + color: $text; +} + +.spl-section__badge { + flex: 0 0 auto; + padding: 5px 10px; + border-radius: 999px; + background: #dbeafe; + color: #1d4ed8; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.05em; + white-space: nowrap; +} + +.spl-section__body { + padding: 18px 20px 20px; +} + +.spl-subsection { + border: 1px solid $border; + border-radius: 12px; + overflow: hidden; + background: #fff; + + & + & { + margin-top: 16px; + } +} + +.spl-subsection__header { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 16px; + background: $header-bg; + border-bottom: 1px solid $border; + color: $text; + font-size: 14px; + font-weight: 600; +} + +.spl-subsection__icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + border-radius: 8px; + background: #eff6ff; + color: #2563eb; + font-size: 13px; + flex: 0 0 auto; +} + +.spl-subsection__body { + padding: 16px 16px 18px; +} + +.spl-table-wrap { + overflow-x: auto; +} + +.spl-table { + width: 100%; + min-width: 0; + table-layout: fixed; + border-collapse: collapse; + border: 1px solid $border; + + col.spl-table__col--label { + width: 22%; + } + + col.spl-table__col--value { + width: 26%; + } + + th, + td { + border: 1px solid $border; + padding: 6px 8px; + vertical-align: middle; + } + + th { + background: #f8fafc; + color: #64748b; + text-align: center; + font-size: 11px; + font-weight: 700; + line-height: 1.3; + white-space: normal; + word-break: break-word; + } + + td:first-child { + background: #f8fafc; + font-weight: 600; + color: $text; + white-space: nowrap; + } + + td { + font-size: 13px; + overflow: hidden; + } +} + +.spl-table__highlight td:first-child { + color: #1d4ed8; +} + +.spl-input, +.spl-select { + width: 100%; + box-sizing: border-box; + font: inherit; +} + +.spl-input { + height: 30px; + border: 1px solid $border-strong; + border-radius: 2px; + padding: 0 8px; + background: #fff; +} + +.spl-input:disabled, +.spl-select:disabled { + background: #f8fafc; + color: $muted; +} + +.spl-select { + height: 30px; + border: 1px solid $border-strong; + border-radius: 2px; + padding: 0 6px; + background: #fff; +} + +.spl-input--stacked { + margin-top: 6px; +} + +.spl-input--disabled { + background: #f8fafc; + color: $muted; +} + +.spl-table__muted { + font-size: 11px; + color: #94a3b8; + letter-spacing: 0.03em; +} + +.spl-note { + margin-top: 16px; + padding: 12px 14px; + border: 1px solid $border; + border-radius: 8px; + background: #f8fafc; + color: $muted; + font-size: 11px; + line-height: 1.6; +} + +.spl-features { + margin-bottom: 16px; +} + +.spl-features__grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px 16px; + + @media (width <= 900px) { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + @media (width <= 640px) { + grid-template-columns: 1fr; + } +} + +.spl-feature { + display: flex; + flex-direction: column; + gap: 6px; + font-size: 13px; + color: $text; +} + +.spl-feature__label { + display: inline-flex; + align-items: center; + gap: 8px; + min-height: 18px; + font-weight: 600; + color: #334155; + + i { + color: #2563eb; + font-size: 12px; + width: 14px; + text-align: center; + } +} diff --git a/src/app/products/current-products/current-product-stepper/current-product-limits-step/current-product-limits-step.component.ts b/src/app/products/current-products/current-product-stepper/current-product-limits-step/current-product-limits-step.component.ts new file mode 100644 index 000000000..a210e2ae2 --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-limits-step/current-product-limits-step.component.ts @@ -0,0 +1,250 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +type BooleanFeatureKey = + | 'atmFacility' + | 'atmWithdrawalFacility' + | 'upiFacility' + | 'posTransactionsFacility' + | 'chequeBook' + | 'impsFacility' + | 'smsAlerts' + | 'bbpsFacility' + | 'insuranceCover' + | 'bepgFacility' + | 'neftFacility' + | 'rtgsFacility' + | 'interBankTotalCapFacility' + | 'withdrawalFacility' + | 'depositFacilityKyc' + | 'virtualDebitCard'; + +interface FacilityLimitRow { + limitType: string; + generalLimit: string | number | null; + minorAccountLimit: string | number | null; + smallSavingLimit: string | number | null; +} + +@Component({ + selector: 'mifosx-current-product-limits-step', + templateUrl: './current-product-limits-step.component.html', + styleUrls: ['./current-product-limits-step.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + FormsModule + ] +}) +export class CurrentProductLimitsStepComponent implements OnChanges { + @Input() CurrentProductsTemplate: any; + pristine = true; + + yesNoOptions: Array<{ + value: boolean; + label: string; + }> = [ + { + value: true, + label: 'Yes' + }, + { + value: false, + label: 'No' + } + ]; + + accountFacilityConfig: Record = { + atmFacility: true, + atmWithdrawalFacility: true, + upiFacility: true, + posTransactionsFacility: true, + chequeBook: true, + impsFacility: true, + smsAlerts: true, + bbpsFacility: true, + insuranceCover: false, + bepgFacility: true, + neftFacility: true, + rtgsFacility: true, + interBankTotalCapFacility: true, + withdrawalFacility: true, + depositFacilityKyc: true, + virtualDebitCard: false + }; + + accountFacilityLimits: FacilityLimitRow[] = [ + { + limitType: 'ATM Withdrawal', + generalLimit: 40000, + minorAccountLimit: 5000, + smallSavingLimit: 10000 + }, + { + limitType: 'POS Transactions', + generalLimit: 100000, + minorAccountLimit: 2000, + smallSavingLimit: 10000 + }, + { + limitType: 'UPI Limit', + generalLimit: 50000, + minorAccountLimit: 1000, + smallSavingLimit: 5000 + }, + { + limitType: 'IMPS Limit', + generalLimit: 50000, + minorAccountLimit: 100, + smallSavingLimit: 5000 + }, + { + limitType: 'BBPS', + generalLimit: 25000, + minorAccountLimit: 0, + smallSavingLimit: 2000 + }, + { + limitType: 'BEPG', + generalLimit: 25000, + minorAccountLimit: 0, + smallSavingLimit: 2000 + }, + { + limitType: 'NEFT Limit', + generalLimit: 1000000, + minorAccountLimit: 25000, + smallSavingLimit: 10000 + }, + { + limitType: 'RTGS Limit (Min 2L)', + generalLimit: 5000000, + minorAccountLimit: 'N/A', + smallSavingLimit: 'N/A' + }, + { + limitType: 'Inter-Bank Total Cap', + generalLimit: 5000000, + minorAccountLimit: 25000, + smallSavingLimit: 10000 + }, + { + limitType: 'Withdrawal Limit', + generalLimit: 'UNLIMITED', + minorAccountLimit: 10000, + smallSavingLimit: 10000 + } + ]; + + depositLimitKyc = { + general: { + kycType: 'Full KYC', + limit: 'No Limit' + }, + minor: { + kycType: 'Guardian KYC', + limit: 100000 + }, + smallSaving: { + kycType: 'Min KYC', + limit: 50000 + } + }; + + ngOnChanges(changes: SimpleChanges) { + if (changes.CurrentProductsTemplate?.currentValue) { + const template = changes.CurrentProductsTemplate.currentValue; + this.setYesNoOptions(template); + this.populateFromTemplate(template); + } + } + + markAsDirty() { + this.pristine = false; + } + + private populateFromTemplate(template: any) { + const isEditing = !!template.id; + + // Populate accountFacilityConfig from existing product + if (isEditing && template.accountFacilityConfig) { + this.accountFacilityConfig = { ...this.accountFacilityConfig, ...template.accountFacilityConfig }; + } + + // Populate accountFacilityLimits from existing product + if (isEditing && template.accountFacilityLimits && template.accountFacilityLimits.length > 0) { + this.accountFacilityLimits = template.accountFacilityLimits.map((limit: any) => ({ + limitType: limit.limitType || '', + generalLimit: limit.generalLimit ?? null, + minorAccountLimit: limit.minorAccountLimit ?? null, + smallSavingLimit: limit.smallSavingLimit ?? null + })); + } + + // Populate depositLimitKyc from existing product + if (isEditing && template.depositLimitKyc) { + this.depositLimitKyc = { + general: { + kycType: template.depositLimitKyc.general?.kycType || 'Full KYC', + limit: template.depositLimitKyc.general?.limit || 'No Limit' + }, + minor: { + kycType: template.depositLimitKyc.minor?.kycType || 'Guardian KYC', + limit: template.depositLimitKyc.minor?.limit ?? 100000 + }, + smallSaving: { + kycType: template.depositLimitKyc.smallSaving?.kycType || 'Min KYC', + limit: template.depositLimitKyc.smallSaving?.limit ?? 50000 + } + }; + } + } + + private setYesNoOptions(template: any) { + const options = Array.isArray(template?.yesNoOptions) ? template.yesNoOptions : []; + const mappedOptions = options + .map((option: any) => { + const rawValue = option?.name ?? option?.value ?? option; + const normalized = typeof rawValue === 'string' ? rawValue.trim().toUpperCase() : rawValue; + + if (normalized === 'YES') { + return { + value: true, + label: 'Yes' + }; + } + + if (normalized === 'NO') { + return { + value: false, + label: 'No' + }; + } + + return null; + }) + .filter((value: any) => !!value); + + if (mappedOptions.length) { + this.yesNoOptions = mappedOptions; + } + } + + get CurrentProductLimits() { + return { + accountFacilityConfig: { ...this.accountFacilityConfig }, + accountFacilityLimits: this.accountFacilityLimits.map((row) => ({ ...row })), + depositLimitKyc: { + ...this.depositLimitKyc + } + }; + } +} diff --git a/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.html b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.html new file mode 100644 index 000000000..7e179aef5 --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.html @@ -0,0 +1,799 @@ + + +
+

{{ CurrentProduct.name }}

+ + +

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

+ + +
+ {{ 'labels.inputs.Short Name' | translate }}: + {{ CurrentProduct.shortName }} +
+ + @if (CurrentProduct.description) { +
+ {{ 'labels.inputs.Description' | translate }}: + {{ CurrentProduct.description }} +
+ } + + @if ( + CurrentProduct.accountType || + CurrentProduct.customerSegment || + CurrentProduct.productCode || + CurrentProduct.effectiveTo || + CurrentProduct.introducerRequired !== undefined || + CurrentProduct.shareLinkingEnabled !== undefined + ) { +

Product Classification

+ + + + @if (CurrentProduct.accountType) { +
+ Account Type: + {{ CurrentProduct.accountType }} +
+ } + + @if (CurrentProduct.customerSegment) { +
+ Customer Segment: + {{ CurrentProduct.customerSegment }} +
+ } + + @if (CurrentProduct.productCode) { +
+ Product Code: + {{ CurrentProduct.productCode }} +
+ } + + @if (CurrentProduct.effectiveTo) { +
+ Effective To: + {{ CurrentProduct.effectiveTo }} +
+ } + + @if (CurrentProduct.endDate) { +
+ End Date: + {{ CurrentProduct.endDate }} +
+ } + + @if (CurrentProduct.introducerRequired !== undefined) { +
+ Introducer Required: + {{ + CurrentProduct.introducerRequired === true || CurrentProduct.introducerRequired === 'Yes' ? 'Yes' : 'No' + }} +
+ } + + @if (CurrentProduct.shareLinkingEnabled !== undefined) { +
+ Share Linking Enabled: + {{ CurrentProduct.shareLinkingEnabled | yesNo }} +
+ } + } + +

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

+ + +
+ {{ 'labels.inputs.Currency' | translate }}: + {{ + CurrentProduct.currency?.name || + CurrentProduct.currency?.displayLabel || + (CurrentProduct.currencyCode | find: CurrentProductsTemplate?.currencyOptions : 'code' : 'name') || + CurrentProduct.currencyCode || + '-' + }} +
+ +
+ {{ 'labels.inputs.Decimal Places' | translate }}: + {{ CurrentProduct.digitsAfterDecimal ?? CurrentProduct.currency?.decimalPlaces ?? 2 }} +
+ + @if (CurrentProduct.currency?.inMultiplesOf !== undefined && CurrentProduct.currency?.inMultiplesOf !== null) { +
+ {{ 'labels.inputs.Currency in multiples of' | translate }}: + {{ CurrentProduct.currency.inMultiplesOf }} +
+ } + + @if (CurrentProduct.dormancyAlert !== undefined) { +
+ Dormancy Alert: + {{ CurrentProduct.dormancyAlert | yesNo }} +
+ } + + @if (CurrentProduct.balanceSwapOption !== undefined) { +
+ Balance Swap Option: + {{ CurrentProduct.balanceSwapOption | yesNo }} +
+ } + + @if (CurrentProduct.msme !== undefined) { +
+ MSME: + {{ CurrentProduct.msme | yesNo }} +
+ } + + @if (CurrentProduct.nominationFacility !== undefined) { +
+ Nomination Facility: + {{ CurrentProduct.nominationFacility | yesNo }} +
+ } + + @if (CurrentProduct.autoDebitFacility !== undefined) { +
+ Auto Debit Facility: + {{ CurrentProduct.autoDebitFacility | yesNo }} +
+ } + +

Interest On OD

+ + +
+ Interest Posting Frequency: + {{ + CurrentProduct.interestPostingPeriodType + | find: CurrentProductsTemplate?.interestPostingPeriodTypeOptions : 'id' : 'value' + | translateKey: 'catalogs' + }} +
+ +
+ Auto Interest Post: + {{ CurrentProduct.autoPost | yesNo }} +
+ + @if ( + CurrentProduct.frequencyFixDay !== undefined && + CurrentProduct.frequencyFixDay !== null && + CurrentProduct.frequencyFixDay !== '' + ) { +
+ Frequency (Fix Day): + {{ CurrentProduct.frequencyFixDay }} Days +
+ } + + @if (CurrentProduct.lastInterestDate) { +
+ Last Interest Date: + {{ CurrentProduct.lastInterestDate }} +
+ } + +

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

+ + + @if (CurrentProduct.minRequiredOpeningBalance) { +
+ {{ 'labels.inputs.Minimum Opening Balance' | translate }}: + {{ CurrentProduct.minRequiredOpeningBalance | formatNumber }} +
+ } + + @if (CurrentProduct.lockinPeriodFrequency) { +
+ {{ 'labels.inputs.Lock-in Period' | translate }}: + {{ + CurrentProduct.lockinPeriodFrequency + + ' ' + + (CurrentProduct.lockinPeriodFrequencyType + | find: CurrentProductsTemplate?.lockinPeriodFrequencyTypeOptions : 'id' : 'value') + | translateKey: 'catalogs' + }} +
+ } + +
+ {{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }}: + {{ CurrentProduct.withdrawalFeeForTransfers | yesNo }} +
+ + @if (CurrentProduct.balanceFrequency) { +
+ Balance Frequency: + {{ CurrentProduct.balanceFrequency }} +
+ } + +
+ Enable Lock-in Period: + {{ CurrentProduct.enableLockinPeriod | yesNo }} +
+ + @if (CurrentProduct.enableLockinPeriod && CurrentProduct.lockinPeriodFrequency) { +
+ Lock-in Period: + + {{ CurrentProduct.lockinPeriodFrequency }} + {{ getLockinPeriodFrequencyTypeLabel(CurrentProduct.lockinPeriodFrequencyType) }} + +
+ } + + @if (CurrentProduct.minBalanceForInterestCalculation) { +
+ {{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}: + {{ CurrentProduct.minBalanceForInterestCalculation | formatNumber }} +
+ } + +
+ {{ 'labels.inputs.Enforce Minimum Balance' | translate }}: + {{ CurrentProduct.enforceMinRequiredBalance | yesNo }} +
+ + @if (CurrentProduct.minRequiredBalance) { +
+ {{ 'labels.inputs.Minimum Balance' | translate }}: + {{ CurrentProduct.minRequiredBalance | formatNumber }} +
+ } + +
+ {{ 'labels.inputs.Withhold Tax is Applicable' | translate }}: + {{ CurrentProduct.withHoldTax | yesNo }} +
+ + @if (CurrentProduct.withHoldTax) { +
+ {{ 'labels.inputs.Tax Group' | translate }}: + {{ + CurrentProduct.taxGroupId | find: CurrentProductsTemplate?.taxGroupOptions : 'id' : 'name' + }} +
+ } + +
+ Account Closed with Zero Balance in Day Closing: + {{ CurrentProduct.accountClosedZeroBalanceInDayClosing | yesNo }} +
+ +

Overdraft (OD) Facility

+ + +
+ Is Overdraft Allowed: + {{ CurrentProduct.allowOverdraft | yesNo }} +
+ + @if (CurrentProduct.allowOverdraft) { + @if (CurrentProduct.avgBalanceCalcPeriod) { +
+ Avg Balance Calculation Period: + {{ CurrentProduct.avgBalanceCalcPeriod }} +
+ } + + @if (CurrentProduct.validForPeriod) { +
+ Valid for Period: + {{ CurrentProduct.validForPeriod }} +
+ } + + @if (CurrentProduct.overdraftLimit !== undefined && CurrentProduct.overdraftLimit !== '') { +
+ OD Limit %: + {{ CurrentProduct.overdraftLimit }} +
+ } + + @if ( + CurrentProduct.nominalAnnualInterestRateOverdraft !== undefined && + CurrentProduct.nominalAnnualInterestRateOverdraft !== '' + ) { +
+ Interest on OD %: + {{ CurrentProduct.nominalAnnualInterestRateOverdraft }} +
+ } + +
+ OD Account Closed with Zero Balance in Day Closing: + {{ CurrentProduct.odAccountClosedZeroBalanceInDayClosing | yesNo }} +
+ } + +
+ {{ 'labels.inputs.Enable Dormancy Tracking' | translate }}: + {{ CurrentProduct.isDormancyTrackingActive | yesNo }} +
+ + @if (CurrentProduct.isDormancyTrackingActive) { +
+ {{ 'labels.inputs.Number of Days to Inactive sub-status' | translate }}: + {{ CurrentProduct.daysToInactive }} + {{ 'labels.inputs.Number of Days to Dormant sub-status' | translate }}: + {{ CurrentProduct.daysToDormancy }} + {{ 'labels.inputs.Number of Days to Escheat' | translate }}: + {{ CurrentProduct.daysToEscheat }} +
+ } + +

Account Facilities

+ + + @if (CurrentProduct.accountFacilityConfig) { +
+ @for (feature of accountFacilityFeatureItems; track feature.key) { +
+ {{ feature.label }}: + {{ CurrentProduct.accountFacilityConfig[feature.key] | yesNo }} +
+ } +
+ } + + @if (CurrentProduct.accountFacilityLimits?.length) { +
+

Account Facility Limits

+ + + + + + + + + + + + + + + + + + + +
Limit Type{{ row.limitType }}General Limit{{ row.generalLimit }}Minor Account Limit{{ row.minorAccountLimit }}Small Saving Limit{{ row.smallSavingLimit }}
+
+ } + + @if (CurrentProduct.depositLimitKyc) { +
+

Deposit Limit (KYC)

+
+ General: + {{ CurrentProduct.depositLimitKyc.general.kycType }} - + {{ CurrentProduct.depositLimitKyc.general.limit }} +
+
+ Minor: + {{ CurrentProduct.depositLimitKyc.minor.kycType }} - {{ CurrentProduct.depositLimitKyc.minor.limit }} +
+
+ Small Saving: + + {{ CurrentProduct.depositLimitKyc.smallSaving.kycType }} - + {{ CurrentProduct.depositLimitKyc.smallSaving.limit }} + +
+
+ } + + @if (CurrentProduct.charges.length) { +
+

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

+ + + + + + + + + + + + + + + + + + + + +
{{ 'labels.inputs.Name' | translate }} + {{ charge.name + ', ' + charge.currency.displaySymbol }} + {{ 'labels.inputs.Type' | translate }} + {{ charge.chargeCalculationType.value | translateKey: 'catalogs' }} + {{ 'labels.inputs.Amount' | translate }} + {{ charge.amount | formatNumber }} + {{ 'labels.inputs.Collected On' | translate }} + {{ charge.chargeTimeType.value | translateKey: 'catalogs' }} +
+
+ } + +

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

+ + + @if (isCashOrAccrualAccounting()) { +
+
+

GL Mapping

+
+ +
+
Assets
+
+ Savings reference account + {{ + formatGlAccountById( + CurrentProduct.savingsReferenceAccountId, + CurrentProductsTemplate?.accountingMappingOptions.assetAccountOptions + ) + }} +
+
+ Overdraft portfolio control + {{ + formatGlAccountById( + CurrentProduct.overdraftPortfolioControlId, + CurrentProductsTemplate?.accountingMappingOptions.assetAccountOptions + ) + }} +
+
+ Interest receivable account + {{ + formatGlAccountById( + CurrentProduct.interestReceivableAccountId, + CurrentProductsTemplate?.accountingMappingOptions.assetAccountOptions + ) + }} +
+
+ Fees receivable account + {{ + formatGlAccountById( + CurrentProduct.feesReceivableAccountId, + CurrentProductsTemplate?.accountingMappingOptions.assetAccountOptions + ) + }} +
+
+ Penalties receivable account + {{ + formatGlAccountById( + CurrentProduct.penaltiesReceivableAccountId, + CurrentProductsTemplate?.accountingMappingOptions.assetAccountOptions + ) + }} +
+
+ +
+
Liabilities
+
+ Savings control account + {{ + formatGlAccountById( + CurrentProduct.savingsControlAccountId, + CurrentProductsTemplate?.accountingMappingOptions.liabilityAccountOptions + ) + }} +
+
+ Transfers in suspense account + {{ + formatGlAccountById( + CurrentProduct.transfersInSuspenseAccountId, + CurrentProductsTemplate?.accountingMappingOptions.liabilityAccountOptions + ) + }} +
+
+ Interest payable account + {{ + formatGlAccountById( + CurrentProduct.interestPayableAccountId, + CurrentProductsTemplate?.accountingMappingOptions.liabilityAccountOptions + ) + }} +
+ @if (CurrentProduct.isDormancyTrackingActive) { +
+ Escheat liability account + {{ + formatGlAccountById( + CurrentProduct.escheatLiabilityId, + CurrentProductsTemplate?.accountingMappingOptions.liabilityAccountOptions + ) + }} +
+ } +
+ +
+
Expenses
+
+ Interest on savings account + {{ + formatGlAccountById( + CurrentProduct.interestOnSavingsAccountId, + CurrentProductsTemplate?.accountingMappingOptions.expenseAccountOptions + ) + }} +
+
+ Write off account + {{ + formatGlAccountById( + CurrentProduct.writeOffAccountId, + CurrentProductsTemplate?.accountingMappingOptions.expenseAccountOptions + ) + }} +
+
+ +
+
Income
+
+ Income from fees + {{ + formatGlAccountById( + CurrentProduct.incomeFromFeeAccountId, + CurrentProductsTemplate?.accountingMappingOptions.incomeAccountOptions + ) + }} +
+
+ Income from penalties + {{ + formatGlAccountById( + CurrentProduct.incomeFromPenaltyAccountId, + CurrentProductsTemplate?.accountingMappingOptions.incomeAccountOptions + ) + }} +
+
+ Income from interest + {{ + formatGlAccountById( + CurrentProduct.incomeFromInterestId, + CurrentProductsTemplate?.accountingMappingOptions.incomeAccountOptions + ) + }} +
+
+
+ } + @if (CurrentProduct.otherGlMappings) { +
+
+

Other GL Mappings

+
+ + @if (CurrentProduct.otherGlMappings.assets?.length) { + + + + + + + + + + + +
Asset Account{{ row.accountName }}GL Account + {{ + formatGlAccountByCode(row.glCode, CurrentProductsTemplate?.accountingMappingOptions.assetAccountOptions) + }} +
+ } + + @if (CurrentProduct.otherGlMappings.liabilities?.length) { + + + + + + + + + + + +
Liability Account{{ row.accountName }}GL Account + {{ + formatGlAccountByCode( + row.glCode, + CurrentProductsTemplate?.accountingMappingOptions.liabilityAccountOptions + ) + }} +
+ } + + @if (CurrentProduct.otherGlMappings.expenses?.length) { + + + + + + + + + + + +
Expense Account{{ row.accountName }}GL Account + {{ + formatGlAccountByCode( + row.glCode, + CurrentProductsTemplate?.accountingMappingOptions.expenseAccountOptions + ) + }} +
+ } + + @if (CurrentProduct.otherGlMappings.income?.length) { + + + + + + + + + + + +
Income Account{{ row.accountName }}GL Account + {{ + formatGlAccountByCode( + row.glCode, + CurrentProductsTemplate?.accountingMappingOptions.incomeAccountOptions + ) + }} +
+ } +
+ } + + @if ( + CurrentProduct.advancedAccountingRules && + (CurrentProduct.paymentChannelToFundSourceMappings?.length || + CurrentProduct.feeToIncomeAccountMappings?.length || + CurrentProduct.penaltyToIncomeAccountMappings?.length) + ) { +
+

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

+ + @if (CurrentProduct.paymentChannelToFundSourceMappings?.length) { +
+

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

+ + + + + + + + + + + +
{{ 'labels.inputs.Payment Type' | translate }} + {{ paymentFundSource.paymentTypeId | find: CurrentProductsTemplate.paymentTypeOptions : 'id' : 'name' }} + {{ 'labels.inputs.Fund Source' | translate }} + {{ + paymentFundSource.fundSourceAccountId + | find: CurrentProductsTemplate?.accountingMappingOptions.assetAccountOptions : 'id' : 'name' + }} +
+
+ } + @if (CurrentProduct.feeToIncomeAccountMappings?.length) { +
+

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

+ + + + + + + + + + + +
{{ 'labels.inputs.Fees' | translate }} + {{ feesIncome.chargeId | find: CurrentProductsTemplate.chargeOptions : 'id' : 'name' }} + {{ 'labels.inputs.Income Account' | translate }} + {{ + feesIncome.incomeAccountId + | find: CurrentProductsTemplate?.accountingMappingOptions.incomeAccountOptions : 'id' : 'name' + }} +
+
+ } + @if (CurrentProduct.penaltyToIncomeAccountMappings?.length) { +
+

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

+ + + + + + + + + + + +
{{ 'labels.inputs.Penalty' | translate }} + {{ penaltyIncome.chargeId | find: CurrentProductsTemplate.penaltyOptions : 'id' : 'name' }} + {{ 'labels.inputs.Income Account' | translate }} + {{ + penaltyIncome.incomeAccountId + | find: CurrentProductsTemplate?.accountingMappingOptions.incomeAccountOptions : 'id' : 'name' + }} +
+
+ } +
+ } +
diff --git a/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.scss b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.scss new file mode 100644 index 000000000..f69c8fea0 --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.scss @@ -0,0 +1,165 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +@use 'assets/styles/helper'; +@use 'assets/styles/colours' as *; + +.tab-container { + padding: 1%; + margin: 1%; +} + +.mat-h3 { + margin-top: 10px !important; + font-weight: 300; + color: $status-approved; +} + +.mat-elevation-z1 { + margin: 1em 0 1.5em; +} + +.interestCarts { + float: right; +} + +h2, +h3, +h4 { + margin: 0; + font-weight: 500; +} + +table { + width: 100%; + + tr.incentives-row { + height: 0; + + > td { + padding: 0; + } + + .incentives { + overflow: hidden; + } + + .mat-card { + border-radius: 0; + + > h4 { + font-weight: 500; + margin: 0.5em 0 0; + } + } + } +} + +span { + margin: 0.5em 0; +} + +mat-divider { + margin: 0 0 0.5em; +} + +.margin-t { + margin-top: 1em; +} + +.spa-gl-summary, +.spa-other-gl-summary { + display: flex; + flex-direction: column; + gap: 0.9rem; + margin-top: 1.25rem; +} + +.spa-gl-summary__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.25rem 0 0.5rem; +} + +.spa-gl-section { + display: flex; + flex-direction: column; + gap: 0.75rem; + padding: 1rem 1.15rem; + border: 1px solid #e3e7f2; + border-radius: 12px; + background: #fff; +} + +.spa-gl-section h5 { + font-size: 0.9rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #91a0bc; +} + +.spa-gl-row { + display: grid; + grid-template-columns: minmax(220px, 40%) 1fr; + gap: 1rem; + align-items: baseline; + padding: 0.2rem 0; +} + +.spa-gl-row__label { + color: #1f2f4a; + font-weight: 500; +} + +.spa-gl-row__value { + color: #101828; + font-weight: 600; + word-break: break-word; +} + +.spa-other-gl-table { + margin-top: 0.5rem; + margin-bottom: 0.75rem; + table-layout: fixed; + width: 100%; +} + +.spa-other-gl-table:first-of-type { + margin-top: 0.25rem; +} + +.spa-other-gl-summary .mat-elevation-z1 { + border-radius: 10px; + overflow: hidden; +} + +.spa-other-gl-table th.mat-mdc-header-cell, +.spa-other-gl-table td.mat-mdc-cell { + padding-left: 1.25rem; + padding-right: 1.25rem; +} + +.spa-other-gl-table th.mat-mdc-header-cell:first-child, +.spa-other-gl-table td.mat-mdc-cell:first-child { + width: 38%; +} + +.spa-other-gl-table th.mat-mdc-header-cell:last-child, +.spa-other-gl-table td.mat-mdc-cell:last-child { + width: 62%; + word-break: break-word; +} + +@media (width <= 768px) { + .spa-gl-row { + grid-template-columns: 1fr; + gap: 0.35rem; + } +} diff --git a/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.ts b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.ts new file mode 100644 index 000000000..5ca6b51dc --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.ts @@ -0,0 +1,247 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges, inject } from '@angular/core'; +import { Accounting } from 'app/core/utils/accounting'; +import { OptionData } from 'app/shared/models/option-data.model'; +import { MatDivider } from '@angular/material/divider'; +import { + MatTable, + MatColumnDef, + MatHeaderCellDef, + MatHeaderCell, + MatCellDef, + MatCell, + MatHeaderRowDef, + MatHeaderRow, + MatRowDef, + MatRow +} from '@angular/material/table'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { FindPipe } from '../../../../pipes/find.pipe'; +import { FormatNumberPipe } from '../../../../pipes/format-number.pipe'; +import { YesnoPipe } from '../../../../pipes/yesno.pipe'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-current-product-preview-step', + templateUrl: './current-product-preview-step.component.html', + styleUrls: ['./current-product-preview-step.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatDivider, + MatTable, + MatColumnDef, + MatHeaderCellDef, + MatHeaderCell, + MatCellDef, + MatCell, + MatHeaderRowDef, + MatHeaderRow, + MatRowDef, + MatRow, + FaIconComponent, + FindPipe, + FormatNumberPipe, + YesnoPipe + ] +}) +export class CurrentProductPreviewStepComponent implements OnInit, OnChanges { + private accounting = inject(Accounting); + + @Input() CurrentProductsTemplate: any; + @Input() accountingRuleData: any; + @Input() CurrentProduct: any; + @Input() taskPermission: string; + @Output() submitEvent = new EventEmitter(); + + chargesDisplayedColumns: string[] = [ + 'name', + 'chargeCalculationType', + 'amount', + 'chargeTimeType' + ]; + paymentFundSourceDisplayedColumns: string[] = [ + 'paymentTypeId', + 'fundSourceAccountId' + ]; + feesPenaltyIncomeDisplayedColumns: string[] = [ + 'chargeId', + 'incomeAccountId' + ]; + + accountingMappings: any = {}; + accountingRule: OptionData; + interestCalculationTypeOptionData: any[] = []; + accountFacilityFeatureItems = [ + { key: 'atmFacility', label: 'ATM Facility' }, + { key: 'atmWithdrawalFacility', label: 'ATM Withdrawal Facility' }, + { key: 'upiFacility', label: 'UPI Facility' }, + { key: 'posTransactionsFacility', label: 'POS Transactions Facility' }, + { key: 'chequeBook', label: 'Cheque Book' }, + { key: 'impsFacility', label: 'IMPS Facility' }, + { key: 'smsAlerts', label: 'SMS Alerts' }, + { key: 'bbpsFacility', label: 'BBPS Facility' }, + { key: 'insuranceCover', label: 'Insurance Cover' }, + { key: 'bepgFacility', label: 'BEPG Facility' }, + { key: 'neftFacility', label: 'NEFT Facility' }, + { key: 'rtgsFacility', label: 'RTGS Facility' }, + { key: 'interBankTotalCapFacility', label: 'Inter-Bank Total Cap Facility' }, + { key: 'withdrawalFacility', label: 'Withdrawal Facility' }, + { key: 'depositFacilityKyc', label: 'Deposit Facility (KYC)' }, + { key: 'virtualDebitCard', label: 'Virtual Debit Card' } + ]; + + ngOnInit() { + this.setCurrentValues(); + } + + ngOnChanges(changes: SimpleChanges): void { + this.setCurrentValues(); + } + + setCurrentValues(): void { + if (!this.CurrentProduct || !this.CurrentProductsTemplate) { + return; + } + + this.interestCalculationTypeOptionData = + this.CurrentProductsTemplate.interestCalculationTypeOption || + this.CurrentProductsTemplate.interestCalculationTypeOptions || + []; + this.accountingRule = this.accounting.getAccountingRuleFrom(this.CurrentProduct.accountingRule); + + if (!this.isCashOrAccrualAccounting()) { + return; + } + + const assetAccountData = this.CurrentProductsTemplate?.accountingMappingOptions?.assetAccountOptions || []; + const incomeAccountData = this.CurrentProductsTemplate?.accountingMappingOptions?.incomeAccountOptions || []; + const expenseAccountData = this.CurrentProductsTemplate?.accountingMappingOptions?.expenseAccountOptions || []; + const liabilityAccountData = this.CurrentProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions || []; + + this.accountingMappings = { + savingsReferenceAccount: this.accounting.glAccountLookUp( + this.CurrentProduct.savingsReferenceAccountId, + assetAccountData + ), + overdraftPortfolioControl: this.accounting.glAccountLookUp( + this.CurrentProduct.overdraftPortfolioControlId, + assetAccountData + ), + savingsControlAccount: this.accounting.glAccountLookUp( + this.CurrentProduct.savingsControlAccountId, + liabilityAccountData + ), + transfersInSuspenseAccount: this.accounting.glAccountLookUp( + this.CurrentProduct.transfersInSuspenseAccountId, + liabilityAccountData + ), + interestOnSavingsAccount: this.accounting.glAccountLookUp( + this.CurrentProduct.interestOnSavingsAccountId, + expenseAccountData + ), + writeOffAccount: this.accounting.glAccountLookUp(this.CurrentProduct.writeOffAccountId, expenseAccountData), + incomeFromFeeAccount: this.accounting.glAccountLookUp( + this.CurrentProduct.incomeFromFeeAccountId, + incomeAccountData + ), + incomeFromPenaltyAccount: this.accounting.glAccountLookUp( + this.CurrentProduct.incomeFromPenaltyAccountId, + incomeAccountData + ), + incomeFromInterest: this.accounting.glAccountLookUp(this.CurrentProduct.incomeFromInterestId, incomeAccountData) + }; + + if (this.isAccrualAccounting()) { + this.accountingMappings = { + ...this.accountingMappings, + feeReceivableAccount: this.accounting.glAccountLookUp( + this.CurrentProduct.feesReceivableAccountId, + assetAccountData + ), + penaltyReceivableAccount: this.accounting.glAccountLookUp( + this.CurrentProduct.penaltiesReceivableAccountId, + assetAccountData + ), + interestPayableAccount: this.accounting.glAccountLookUp( + this.CurrentProduct.interestPayableAccountId, + liabilityAccountData + ), + interestReceivableAccount: this.accounting.glAccountLookUp( + this.CurrentProduct.interestReceivableAccountId, + assetAccountData + ) + }; + } + + if (this.CurrentProduct.isDormancyTrackingActive) { + this.accountingMappings = { + ...this.accountingMappings, + escheatLiabilityAccount: this.accounting.glAccountLookUp( + this.CurrentProduct.escheatLiabilityId, + liabilityAccountData + ) + }; + } + } + + isCashOrAccrualAccounting(): boolean { + return this.accounting.isCashOrAccrualAccounting(this.accountingRule); + } + + isAccrualAccounting(): boolean { + return this.accounting.isAccrualAccounting(this.accountingRule); + } + + getBooleanLabel(value: any): string { + return value === true || value === 'Yes' ? 'Yes' : 'No'; + } + + getLockinPeriodFrequencyTypeLabel(value: any): string { + switch (value) { + case 'DAYS': + return 'Days'; + case 'WEEKS': + return 'Weeks'; + case 'MONTHS': + return 'Months'; + case 'YEARS': + return 'Years'; + default: + return value ?? ''; + } + } + + getOptionLabel(value: any, options: any[], key: string, property: string): string { + if (value === null || value === undefined || !Array.isArray(options)) { + return '-'; + } + + const match = options.find((option) => option?.[key] === value); + return match?.[property] || '-'; + } + + formatGlAccountById(glAccountId: number, options: any[]): string { + if (!glAccountId || !Array.isArray(options)) { + return '-'; + } + + const account = this.accounting.glAccountLookUp(glAccountId, options as any); + return account ? `(${account.glCode}) ${account.name}` : '-'; + } + + formatGlAccountByCode(glCode: string, options: any[]): string { + if (!glCode || !Array.isArray(options)) { + return '-'; + } + + const account = options.find((option) => option?.glCode === glCode); + return account ? `(${account.glCode}) ${account.name}` : glCode; + } +} diff --git a/src/app/products/current-products/current-product-stepper/current-product-settings-step/current-product-settings-step.component.html b/src/app/products/current-products/current-product-stepper/current-product-settings-step/current-product-settings-step.component.html new file mode 100644 index 000000000..f79d6f32e --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-settings-step/current-product-settings-step.component.html @@ -0,0 +1,189 @@ + + +
+ +
+
+ + Balance & Minimum Balance +
+
+
+
+ + Minimum Opening Balance + + + +
+ + Enforce Minimum Balance + +
+ + + Minimum Balance (₹) * + + PENAL CHARGE APPLIES ON BREACH + +
+ +
+ + Balance Frequency + + @for (balanceFrequency of periodFrequencyData; track balanceFrequency.id) { + {{ balanceFrequency.value }} + } + + + +
+ + Apply Withdrawal Fee for Transfers + +
+
+
+
+
+ + +
+
+ + Lock-in Period +
+
+
+ Enable Lock-in Period + +
+ + @if (CurrentProductSettingsForm.value.enableLockinPeriod) { +
+ + Frequency * + + + + + Type * + + @for (type of lockinPeriodFrequencyTypeData; track type) { + + {{ type.value | translateKey: 'catalogs' }} + + } + + +
+ } +
+
+ + +
+
+ + Additional Rules +
+
+
+ + Account Closed with Zero Balance in Day Closing + +
+
+ + Withhold Tax is Applicable + +
+
+
+ + +
+
+ + Overdraft (OD) Facility +
+
+
+ +
+
+ + Is Overdraft Allowed + +
+ @if (CurrentProductSettingsForm.value.allowOverdraft) { +
+ + OD Account Closed with Zero Balance in Day Closing + +

+ Automatically closes OD accounts when balance reaches zero during day-end closing. +

+
+ } +
+ + + @if (CurrentProductSettingsForm.value.allowOverdraft) { +
+
+ Average Balance Calculation in Period + + 6M + 1Y + 3Y + +
+ +
+ Valid for Period + + 3M + 1Y + 3Y + +
+ +
+ + OD Limit % + + + + + Interest on OD % + + +
+
+ } +
+
+
+
diff --git a/src/app/products/current-products/current-product-stepper/current-product-settings-step/current-product-settings-step.component.scss b/src/app/products/current-products/current-product-stepper/current-product-settings-step/current-product-settings-step.component.scss new file mode 100644 index 000000000..0e3cd524f --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-settings-step/current-product-settings-step.component.scss @@ -0,0 +1,263 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$accent: #0ea5e9; +$border: #e2e8f0; +$gray-50: #f8f9fa; +$gray-200: #e8eaed; +$gray-900: #202124; + +.sps-section { + border: 1px solid $border; + border-radius: 10px; + overflow: hidden; + background: #fff; + + & + & { + margin-top: 16px; + } + + &__header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + background: $gray-50; + border-bottom: 1px solid $gray-200; + + i { + font-size: 14px; + color: $accent; + } + + h3 { + margin: 0; + font-size: 14px; + font-weight: 600; + color: $gray-900; + } + } +} + +.sps-section__badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 6px; + background: #eff6ff; + font-size: 14px; +} + +.sps-body { + background: #fff; + padding: 18px 24px 24px; +} + +.sps-field { + width: 100%; + + &--full { + grid-column: 1 / -1; + } + + &--compact { + width: 100%; + } +} + +.sps-layout { + display: grid; + gap: 16px; +} + +.sps-layout--balance { + gap: 22px; +} + +.sps-layout--lockin { + grid-template-columns: repeat(2, minmax(0, 1fr)); + + @media (width <= 640px) { + grid-template-columns: 1fr; + } +} + +.sps-layout--additional { + grid-template-columns: repeat(2, minmax(0, 1fr)); + + @media (width <= 1100px) { + grid-template-columns: 1fr; + } +} + +.sps-row { + display: grid; + gap: 18px; + align-items: start; +} + +.sps-row--three { + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + align-items: center; + + .sps-field--compact:first-child { + min-width: 0; + } + + .sps-toggle--inline { + align-self: center; + min-height: 56px; + } +} + +.sps-row--two { + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + + .sps-field--compact { + min-width: 0; + } +} + +.sps-toggle { + display: flex; + align-items: center; + min-height: 56px; + + mat-checkbox { + font-size: 14px; + color: $gray-900; + } + + &--full { + min-height: auto; + margin-bottom: 8px; + } + + &--inline { + padding-top: 0; + } + + &--right { + justify-content: flex-start; + } +} + +.sps-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; +} + +.sps-lockin-row mat-checkbox { + margin-right: 2px; +} + +.sps-lockin-fields { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; + + @media (width <= 640px) { + grid-template-columns: 1fr; + } +} + +// ── Overdraft (OD) Facility ─────────────────────────────────────────────── + +.sps-od-layout { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 24px; + align-items: start; + + @media (width <= 900px) { + grid-template-columns: 1fr; + } +} + +.sps-od-checks { + display: flex; + flex-direction: column; + gap: 12px; +} + +.sps-od-check-row { + padding: 14px 16px; + border: 1px solid $border; + border-radius: 8px; + background: $gray-50; + + mat-checkbox { + font-size: 14px; + font-weight: 500; + color: $gray-900; + } +} + +.sps-od-check-hint { + margin: 6px 0 0 28px; + font-size: 11.5px; + color: #64748b; + line-height: 1.5; +} + +.sps-od-panel { + background: #f0f7ff; + border: 1px solid #bfdbfe; + border-radius: 10px; + padding: 20px 24px; + display: flex; + flex-direction: column; + gap: 20px; +} + +.sps-od-radio-group { + display: flex; + flex-direction: column; + gap: 8px; + padding-bottom: 16px; + border-bottom: 1px solid #dbeafe; + + &:last-of-type { + border-bottom: none; + padding-bottom: 0; + } +} + +.sps-od-radio-label { + font-size: 10.5px; + font-weight: 700; + letter-spacing: 0.07em; + color: #3b82f6; + text-transform: uppercase; +} + +.sps-od-radio-choices { + display: flex; + gap: 20px; + flex-wrap: wrap; +} + +.sps-od-inputs { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + + @media (width <= 640px) { + grid-template-columns: 1fr; + } +} diff --git a/src/app/products/current-products/current-product-stepper/current-product-settings-step/current-product-settings-step.component.ts b/src/app/products/current-products/current-product-stepper/current-product-settings-step/current-product-settings-step.component.ts new file mode 100644 index 000000000..4c464aeae --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-settings-step/current-product-settings-step.component.ts @@ -0,0 +1,172 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, OnInit, Input, inject } from '@angular/core'; +import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; +import { MatCheckbox } from '@angular/material/checkbox'; +import { MatRadioGroup, MatRadioButton } from '@angular/material/radio'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-current-product-settings-step', + templateUrl: './current-product-settings-step.component.html', + styleUrls: ['./current-product-settings-step.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatCheckbox, + MatRadioGroup, + MatRadioButton + ] +}) +export class CurrentProductSettingsStepComponent implements OnInit { + private formBuilder = inject(UntypedFormBuilder); + + @Input() CurrentProductsTemplate: any; + + CurrentProductSettingsForm: UntypedFormGroup; + + periodFrequencyData: any[] = []; + + lockinPeriodFrequencyTypeData = [ + { id: 'DAYS', value: 'value.days' }, + { id: 'WEEKS', value: 'value.weeks' }, + { id: 'MONTHS', value: 'value.months' }, + { id: 'YEARS', value: 'value.years' } + ]; + + constructor() { + this.createCurrentProductSettingsForm(); + this.setConditionalControls(); + } + + ngOnInit() { + this.periodFrequencyData = this.CurrentProductsTemplate.periodFrequencyOptions || []; + const defaultPeriod = this.periodFrequencyData[0]?.value || 'Monthly'; + const isEditing = !!this.CurrentProductsTemplate.id; + + this.CurrentProductSettingsForm.patchValue({ + minRequiredOpeningBalance: this.CurrentProductsTemplate.minRequiredOpeningBalance, + withdrawalFeeForTransfers: this.CurrentProductsTemplate.withdrawalFeeForTransfers, + enforceMinRequiredBalance: this.CurrentProductsTemplate.enforceMinRequiredBalance, + minRequiredBalance: this.CurrentProductsTemplate.minRequiredBalance, + withHoldTax: this.CurrentProductsTemplate.withHoldTax ?? false, + balanceFrequency: isEditing ? this.CurrentProductsTemplate.balanceFrequency || defaultPeriod : defaultPeriod, + enableLockinPeriod: !!( + this.CurrentProductsTemplate.lockinPeriodFrequency && this.CurrentProductsTemplate.lockinPeriodFrequency > 0 + ), + accountClosedZeroBalanceInDayClosing: isEditing + ? (this.CurrentProductsTemplate.accountClosedZeroBalanceInDayClosing ?? false) + : false, + isDormancyTrackingActive: isEditing ? (this.CurrentProductsTemplate.isDormancyTrackingActive ?? true) : true, + daysToInactive: isEditing ? this.CurrentProductsTemplate.daysToInactive || 30 : 30, + daysToDormancy: isEditing ? this.CurrentProductsTemplate.daysToDormancy || 60 : 60, + daysToEscheat: isEditing ? this.CurrentProductsTemplate.daysToEscheat || 90 : 90, + // Overdraft fields + allowOverdraft: isEditing ? (this.CurrentProductsTemplate.allowOverdraft ?? false) : false, + overdraftLimit: isEditing ? this.CurrentProductsTemplate.overdraftLimit : '', + nominalAnnualInterestRateOverdraft: isEditing + ? this.CurrentProductsTemplate.nominalAnnualInterestRateOverdraft + : '', + avgBalanceCalcPeriod: isEditing ? this.CurrentProductsTemplate.avgBalanceCalcPeriod || '1Y' : '1Y', + validForPeriod: isEditing ? this.CurrentProductsTemplate.validForPeriod || '1Y' : '1Y', + odAccountClosedZeroBalanceInDayClosing: isEditing + ? (this.CurrentProductsTemplate.odAccountClosedZeroBalanceInDayClosing ?? false) + : false + }); + + if ( + isEditing && + this.CurrentProductsTemplate.lockinPeriodFrequency && + this.CurrentProductsTemplate.lockinPeriodFrequency > 0 + ) { + const lockinTypeIdToKey: { [key: number]: string } = { 0: 'DAYS', 1: 'WEEKS', 2: 'MONTHS', 3: 'YEARS' }; + const rawLockinType = this.CurrentProductsTemplate.lockinPeriodFrequencyType; + // Backend may return an EnumOptionData {id, code, value} or a plain integer + const lockinTypeId = typeof rawLockinType === 'object' ? rawLockinType?.id : rawLockinType; + this.CurrentProductSettingsForm.patchValue({ + lockinPeriodFrequency: this.CurrentProductsTemplate.lockinPeriodFrequency, + lockinPeriodFrequencyType: lockinTypeIdToKey[lockinTypeId] || '' + }); + } + } + + createCurrentProductSettingsForm() { + this.CurrentProductSettingsForm = this.formBuilder.group({ + minRequiredOpeningBalance: [ + '', + [Validators.min(0)] + ], + withdrawalFeeForTransfers: [false], + enforceMinRequiredBalance: [false], + minRequiredBalance: [ + '', + [Validators.min(0)] + ], + withHoldTax: [false], + balanceFrequency: ['Monthly'], + enableLockinPeriod: [false], + lockinPeriodFrequency: [''], + lockinPeriodFrequencyType: [''], + accountClosedZeroBalanceInDayClosing: [false], + // Dormancy (used internally / by accounting step) + isDormancyTrackingActive: [true], + daysToInactive: [30], + daysToDormancy: [60], + daysToEscheat: [90], + // Overdraft (OD) Facility + allowOverdraft: [false], + overdraftLimit: [ + '', + [Validators.min(0)] + ], + nominalAnnualInterestRateOverdraft: [ + '', + [Validators.min(0)] + ], + avgBalanceCalcPeriod: ['1Y'], + validForPeriod: ['1Y'], + odAccountClosedZeroBalanceInDayClosing: [false] + }); + } + + setConditionalControls() { + this.CurrentProductSettingsForm.get('enableLockinPeriod').valueChanges.subscribe((enabled: boolean) => { + const freq = this.CurrentProductSettingsForm.get('lockinPeriodFrequency'); + const type = this.CurrentProductSettingsForm.get('lockinPeriodFrequencyType'); + if (enabled) { + freq.setValidators([ + Validators.required, + Validators.min(1), + Validators.pattern('^[1-9]\\d*$') + ]); + type.setValidators([Validators.required]); + } else { + freq.setValue(''); + type.setValue(''); + freq.clearValidators(); + type.clearValidators(); + } + freq.updateValueAndValidity(); + type.updateValueAndValidity(); + }); + } + + get CurrentProductSettings() { + const formValue = { ...this.CurrentProductSettingsForm.value }; + + if (!formValue.allowOverdraft) { + delete formValue.overdraftLimit; + delete formValue.nominalAnnualInterestRateOverdraft; + delete formValue.avgBalanceCalcPeriod; + delete formValue.validForPeriod; + delete formValue.odAccountClosedZeroBalanceInDayClosing; + } + + return formValue; + } +} diff --git a/src/app/products/current-products/current-product-stepper/current-product-terms-step/current-product-terms-step.component.html b/src/app/products/current-products/current-product-stepper/current-product-terms-step/current-product-terms-step.component.html new file mode 100644 index 000000000..f2350d95a --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-terms-step/current-product-terms-step.component.html @@ -0,0 +1,68 @@ + + +
+
+
+ + Interest On OD +
+
+ + Interest Posting Frequency * + + @for (item of interestPostingPeriodTypeData; track item) { + + {{ item.value | translateKey: 'catalogs' }} + + } + + + Interest Posting Frequency {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + + + +
+
+
diff --git a/src/app/products/current-products/current-product-stepper/current-product-terms-step/current-product-terms-step.component.scss b/src/app/products/current-products/current-product-stepper/current-product-terms-step/current-product-terms-step.component.scss new file mode 100644 index 000000000..807262b37 --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-terms-step/current-product-terms-step.component.scss @@ -0,0 +1,148 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$accent: #0ea5e9; +$border: #e2e8f0; +$gray-50: #f8f9fa; +$gray-200: #e8eaed; +$gray-900: #202124; + +.spt-section { + border: 1px solid $border; + border-radius: 10px; + overflow: hidden; + background: #fff; + + & + & { + margin-top: 16px; + } + + &__header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + background: $gray-50; + border-bottom: 1px solid $gray-200; + + i { + font-size: 14px; + color: $accent; + } + + h3 { + margin: 0; + font-size: 14px; + font-weight: 600; + color: $gray-900; + } + } +} + +.spt-section__badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 6px; + background: #eff6ff; + font-size: 14px; +} + +.spt-body { + background: #fff; + padding: 16px 24px; +} + +.spt-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 4px 16px; + padding: 16px 24px; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} + +.spt-grid--three { + grid-template-columns: repeat(3, 1fr); +} + +.spt-field { + width: 100%; + + &--full { + grid-column: 1 / -1; + } +} + +.spt-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; + } +} + +.spt-footer { + padding: 0 24px 24px; + border-top: 1px solid #f1f5f9; +} + +.spt-footer__grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + padding-top: 18px; + + @media (width <= 900px) { + grid-template-columns: 1fr; + } +} + +.spt-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; + } +} + +.spt-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/current-products/current-product-stepper/current-product-terms-step/current-product-terms-step.component.ts b/src/app/products/current-products/current-product-stepper/current-product-terms-step/current-product-terms-step.component.ts new file mode 100644 index 000000000..aaab2e0aa --- /dev/null +++ b/src/app/products/current-products/current-product-stepper/current-product-terms-step/current-product-terms-step.component.ts @@ -0,0 +1,112 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, OnInit, Input, inject } from '@angular/core'; +import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { Dates } from 'app/core/utils/dates'; +import { SettingsService } from 'app/settings/settings.service'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-current-product-terms-step', + templateUrl: './current-product-terms-step.component.html', + styleUrls: ['./current-product-terms-step.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + FaIconComponent + ] +}) +export class CurrentProductTermsStepComponent implements OnInit { + private formBuilder = inject(UntypedFormBuilder); + private dateUtils = inject(Dates); + private settingsService = inject(SettingsService); + + @Input() CurrentProductsTemplate: any; + + CurrentProductTermsForm: UntypedFormGroup; + + interestPostingPeriodTypeData: any[] = []; + yesNoOptions: Array<{ value: boolean; label: string }> = [ + { value: true, label: 'Yes' }, + { value: false, label: 'No' } + ]; + + constructor() { + this.createCurrentProductTermsForm(); + } + + ngOnInit() { + this.interestPostingPeriodTypeData = this.CurrentProductsTemplate.interestPostingPeriodTypeOptions || []; + this.setYesNoOptions(this.CurrentProductsTemplate?.yesNoOptions); + + const isEditing = !!this.CurrentProductsTemplate.id; + + this.CurrentProductTermsForm.patchValue({ + interestPostingPeriodType: this.CurrentProductsTemplate.interestPostingPeriodType?.id || '', + autoPost: isEditing ? (this.CurrentProductsTemplate.autoPost ?? true) : true, + frequencyFixDay: isEditing ? (this.CurrentProductsTemplate.frequencyFixDay ?? 30) : 30, + lastInterestDate: this.parseDate(this.CurrentProductsTemplate.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); + } + + private setYesNoOptions(options: any[]) { + if (!Array.isArray(options)) return; + + const mappedOptions = options + .map((option: any) => { + const rawValue = option?.name ?? option?.value ?? option; + const normalized = typeof rawValue === 'string' ? rawValue.trim().toUpperCase() : rawValue; + if (normalized === 'YES') return { value: true, label: 'Yes' }; + if (normalized === 'NO') return { value: false, label: 'No' }; + return null; + }) + .filter((v: any) => !!v); + + if (mappedOptions.length) { + this.yesNoOptions = mappedOptions; + } + } + + createCurrentProductTermsForm() { + this.CurrentProductTermsForm = this.formBuilder.group({ + interestPostingPeriodType: [ + '', + Validators.required + ], + autoPost: [true], + frequencyFixDay: [ + 30, + [Validators.min(0)] + ], + lastInterestDate: [''] + }); + } + + get CurrentProductTerms() { + const formValue = { ...this.CurrentProductTermsForm.value }; + const dateFormat = this.settingsService.dateFormat; + + if (formValue.lastInterestDate instanceof Date) { + formValue.lastInterestDate = this.dateUtils.formatDate(formValue.lastInterestDate, dateFormat) || ''; + } + + return { ...formValue }; + } +} diff --git a/src/app/products/current-products/edit-current-product/edit-current-product.component.html b/src/app/products/current-products/edit-current-product/edit-current-product.component.html index a8ab8c762..699f0bfce 100644 --- a/src/app/products/current-products/edit-current-product/edit-current-product.component.html +++ b/src/app/products/current-products/edit-current-product/edit-current-product.component.html @@ -1,69 +1,154 @@ -
- - - - - - + - - {{ 'labels.inputs.CURRENCY' | translate }} - - +
+
+ +

{{ 'labels.heading.Edit Current Product' | translate }}

+
- - {{ 'labels.inputs.TERMS' | translate }} - - + + + + + {{ 'labels.inputs.DETAILS' | translate }} + +
+ +
+
- - {{ 'labels.inputs.SETTINGS' | translate }} - - + + + + {{ 'labels.inputs.CURRENCY' | translate }} + +
+ +
+
- - {{ 'labels.inputs.CHARGES' | translate }} - - + + + + {{ 'labels.inputs.TERMS' | translate }} + +
+ +
+
- - {{ 'labels.inputs.ACCOUNTING' | translate }} - - + + + + {{ 'labels.inputs.SETTINGS' | translate }} + +
+ +
+
+ + + + + {{ 'labels.inputs.CHARGES' | translate }} + +
+ +
+
+ + + + + Digital + +
+ +
+
+ + + + + {{ 'labels.inputs.ACCOUNTING' | translate }} + +
+ +
+
@if (formValidAndNotPristine) { - - {{ 'labels.inputs.PREVIEW' | translate }} - - + + + + {{ 'labels.inputs.PREVIEW' | translate }} + +
+ +
+
} - +
+ +
+ +
+ @if (selectedTabIndex > 0) { + + } + @if (selectedTabIndex < totalTabs - 1) { + + } + @if (formValidAndNotPristine && selectedTabIndex === totalTabs - 1) { + + } +
+
diff --git a/src/app/products/current-products/edit-current-product/edit-current-product.component.scss b/src/app/products/current-products/edit-current-product/edit-current-product.component.scss index fbc6592ec..98f8a7bee 100644 --- a/src/app/products/current-products/edit-current-product/edit-current-product.component.scss +++ b/src/app/products/current-products/edit-current-product/edit-current-product.component.scss @@ -1,4 +1,90 @@ -fa-icon { - position: relative; - left: 5%; +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$primary: #1353a0; + +.cp-container { + max-width: 960px; + margin: 0 auto; + padding: 24px 16px; +} + +.cp-header { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 20px; + + &__icon { + font-size: 20px; + color: $primary; + } + + &__title { + margin: 0; + font-size: 22px; + font-weight: 500; + } +} + +.cp-tabs { + background: var(--md-sys-color-surface, #fff); + border-radius: 8px; + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); + + ::ng-deep { + .mat-mdc-tab-header { + border-bottom: 1px solid rgb(0 0 0 / 8%); + } + + .mat-mdc-tab { + min-width: 120px; + padding: 0 16px; + height: 52px; + } + + .mat-mdc-tab-body-wrapper { + min-height: 200px; + } + + .mat-mdc-tab-body { + overflow: visible !important; + } + + .mat-mdc-tab-body-content { + overflow: visible !important; + } + } +} + +.cp-tab-icon { + margin-right: 8px; + font-size: 14px; +} + +.cp-tab-content { + padding: 24px 16px; +} + +.cp-action-bar { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 20px; + padding: 16px 0; + border-top: 1px solid rgb(0 0 0 / 8%); + + &__cancel { + color: rgb(0 0 0 / 60%); + } + + &__right { + display: flex; + gap: 12px; + } } diff --git a/src/app/products/current-products/edit-current-product/edit-current-product.component.ts b/src/app/products/current-products/edit-current-product/edit-current-product.component.ts index 0083121a2..5d6d2441b 100644 --- a/src/app/products/current-products/edit-current-product/edit-current-product.component.ts +++ b/src/app/products/current-products/edit-current-product/edit-current-product.component.ts @@ -1,17 +1,24 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ import { Component, ViewChild, inject } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { SavingProductDetailsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component'; -import { SavingProductCurrencyStepComponent } from '../../saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component'; -import { SavingProductTermsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component'; -import { SavingProductSettingsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component'; -import { SavingProductChargesStepComponent } from '../../saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component'; -import { SavingProductAccountingStepComponent } from '../../saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component'; -import { SavingProductPreviewStepComponent } from '../../saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component'; +import { CurrentProductDetailsStepComponent } from '../current-product-stepper/current-product-details-step/current-product-details-step.component'; +import { CurrentProductCurrencyStepComponent } from '../current-product-stepper/current-product-currency-step/current-product-currency-step.component'; +import { CurrentProductTermsStepComponent } from '../current-product-stepper/current-product-terms-step/current-product-terms-step.component'; +import { CurrentProductSettingsStepComponent } from '../current-product-stepper/current-product-settings-step/current-product-settings-step.component'; +import { CurrentProductChargesStepComponent } from '../current-product-stepper/current-product-charges-step/current-product-charges-step.component'; +import { CurrentProductLimitsStepComponent } from '../current-product-stepper/current-product-limits-step/current-product-limits-step.component'; +import { CurrentProductAccountingStepComponent } from '../current-product-stepper/current-product-accounting-step/current-product-accounting-step.component'; +import { CurrentProductPreviewStepComponent } from '../current-product-stepper/current-product-preview-step/current-product-preview-step.component'; import { ProductsService } from 'app/products/products.service'; import { SettingsService } from 'app/settings/settings.service'; import { Accounting } from 'app/core/utils/accounting'; -import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper'; -import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { MatTabsModule } from '@angular/material/tabs'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @Component({ @@ -20,18 +27,15 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; styleUrls: ['./edit-current-product.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, - MatStepper, - MatStepperIcon, - FaIconComponent, - MatStep, - MatStepLabel, - SavingProductDetailsStepComponent, - SavingProductCurrencyStepComponent, - SavingProductTermsStepComponent, - SavingProductSettingsStepComponent, - SavingProductChargesStepComponent, - SavingProductAccountingStepComponent, - SavingProductPreviewStepComponent + MatTabsModule, + CurrentProductDetailsStepComponent, + CurrentProductCurrencyStepComponent, + CurrentProductTermsStepComponent, + CurrentProductSettingsStepComponent, + CurrentProductChargesStepComponent, + CurrentProductLimitsStepComponent, + CurrentProductAccountingStepComponent, + CurrentProductPreviewStepComponent ] }) export class EditCurrentProductComponent { @@ -41,16 +45,18 @@ export class EditCurrentProductComponent { private settingsService = inject(SettingsService); private accounting = inject(Accounting); - @ViewChild(SavingProductDetailsStepComponent, { static: true }) detailsStep: SavingProductDetailsStepComponent; - @ViewChild(SavingProductCurrencyStepComponent, { static: true }) currencyStep: SavingProductCurrencyStepComponent; - @ViewChild(SavingProductTermsStepComponent, { static: true }) termsStep: SavingProductTermsStepComponent; - @ViewChild(SavingProductSettingsStepComponent, { static: true }) settingsStep: SavingProductSettingsStepComponent; - @ViewChild(SavingProductChargesStepComponent, { static: true }) chargesStep: SavingProductChargesStepComponent; - @ViewChild(SavingProductAccountingStepComponent, { static: true }) - accountingStep: SavingProductAccountingStepComponent; + @ViewChild(CurrentProductDetailsStepComponent, { static: true }) detailsStep: CurrentProductDetailsStepComponent; + @ViewChild(CurrentProductCurrencyStepComponent, { static: true }) currencyStep: CurrentProductCurrencyStepComponent; + @ViewChild(CurrentProductTermsStepComponent, { static: true }) termsStep: CurrentProductTermsStepComponent; + @ViewChild(CurrentProductSettingsStepComponent, { static: true }) settingsStep: CurrentProductSettingsStepComponent; + @ViewChild(CurrentProductChargesStepComponent, { static: true }) chargesStep: CurrentProductChargesStepComponent; + @ViewChild(CurrentProductLimitsStepComponent, { static: true }) limitsStep: CurrentProductLimitsStepComponent; + @ViewChild(CurrentProductAccountingStepComponent, { static: true }) + accountingStep: CurrentProductAccountingStepComponent; currentProductAndTemplate: any; accountingRuleData: string[] = []; + selectedTabIndex = 0; constructor() { this.route.data.subscribe((data: { currentProductAndTemplate: any }) => { @@ -60,19 +66,26 @@ export class EditCurrentProductComponent { } get detailsForm() { - return this.detailsStep.savingProductDetailsForm; + return this.detailsStep.CurrentProductDetailsForm; } get currencyForm() { - return this.currencyStep.savingProductCurrencyForm; + return this.currencyStep.CurrentProductCurrencyForm; } get termsForm() { - return this.termsStep.savingProductTermsForm; + return this.termsStep.CurrentProductTermsForm; } get settingsForm() { - return this.settingsStep.savingProductSettingsForm; + return this.settingsStep.CurrentProductSettingsForm; + } + get limitsForm(): void { + return; // Limits step doesn't have a form, it's data-driven } get accountingForm() { - return this.accountingStep.savingProductAccountingForm; + return this.accountingStep.CurrentProductAccountingForm; + } + + get totalTabs(): number { + return this.formValidAndNotPristine ? 8 : 7; } get formValidAndNotPristine() { @@ -87,36 +100,64 @@ export class EditCurrentProductComponent { !this.termsForm.pristine || !this.settingsForm.pristine || !this.chargesStep.pristine || + !this.limitsStep.pristine || !this.accountingForm.pristine) ); } get currentProduct() { return { - ...this.detailsStep.savingProductDetails, - ...this.currencyStep.savingProductCurrency, - ...this.termsStep.savingProductTerms, - ...this.settingsStep.savingProductSettings, - ...this.chargesStep.savingProductCharges, - ...this.accountingStep.savingProductAccounting + ...this.detailsStep.CurrentProductDetails, + ...this.currencyStep.CurrentProductCurrency, + ...this.termsStep.CurrentProductTerms, + ...this.settingsStep.CurrentProductSettings, + ...this.chargesStep.CurrentProductCharges, + ...this.limitsStep.CurrentProductLimits, + ...this.accountingStep.CurrentProductAccounting }; } - submit() { - const product = { + private prepareCurrentProductPayload() { + const currentProduct = { ...this.currentProduct, charges: this.currentProduct.charges.map((charge: any) => ({ id: charge.id })), - locale: this.settingsService.language.code + locale: this.settingsService.language.code, + dateFormat: 'dd MMMM yyyy' }; - delete product.advancedAccountingRules; - this.productsService.updateCurrentProduct(this.currentProductAndTemplate.id, product).subscribe((response: any) => { - this.router.navigate( - [ - '../../', - response.resourceId - ], - { relativeTo: this.route } - ); + delete currentProduct.advancedAccountingRules; + + if (currentProduct.lockinPeriodFrequencyType) { + const frequencyTypeMapping: { [key: string]: number } = { + DAYS: 0, + WEEKS: 1, + MONTHS: 2, + YEARS: 3 + }; + currentProduct.lockinPeriodFrequencyType = frequencyTypeMapping[currentProduct.lockinPeriodFrequencyType]; + } + + Object.keys(currentProduct).forEach((key) => { + if (currentProduct[key] === '' || currentProduct[key] === null || currentProduct[key] === undefined) { + delete currentProduct[key]; + } }); + + return currentProduct; + } + + submit() { + const currentProduct = this.prepareCurrentProductPayload(); + console.log('Current product update payload:', currentProduct); + this.productsService + .updateCurrentProduct(this.currentProductAndTemplate.id, currentProduct) + .subscribe((response: any) => { + this.router.navigate( + [ + '../../', + response.resourceId + ], + { relativeTo: this.route } + ); + }); } } diff --git a/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.html b/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.html index 18491561c..6dc6b1343 100644 --- a/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.html +++ b/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.html @@ -1,3 +1,11 @@ + +
-
-
-

{{ currentProduct.name }}

- +
+

{{ currentProduct.name }}

-

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

- + +

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

-
- {{ 'labels.inputs.Short Name' | translate }}: - {{ currentProduct.shortName }} -
- - @if (currentProduct.description) { -
- {{ 'labels.inputs.Description' | translate }}: - {{ currentProduct.description }} -
- } - -

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

- - -
- {{ 'labels.inputs.Currency' | translate }}: - {{ currentProduct.currency.name }} -
- -
- {{ 'labels.inputs.Decimal Places' | translate }}: - {{ currentProduct.currency.decimalPlaces }} -
- -

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

- - -
- {{ 'labels.inputs.Nominal Annual Interest' | translate }}: - {{ currentProduct.nominalAnnualInterestRate | formatNumber }} % -
- -
- {{ 'labels.inputs.Interest Compounding Period' | translate }}: - {{ currentProduct.interestCompoundingPeriodType?.value | translateKey: 'catalogs' }} -
- -
- {{ 'labels.inputs.Interest Posting Period' | translate }}: - {{ currentProduct.interestPostingPeriodType?.value | translateKey: 'catalogs' }} -
- - @if (currentProduct.charges?.length) { -
-

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

- - - - - - - - - - - - - - - - - - - - -
{{ 'labels.inputs.Name' | translate }}{{ charge.name + ', ' + charge.currency.displaySymbol }}{{ 'labels.inputs.Type' | translate }} - {{ charge.chargeCalculationType.value | translateKey: 'catalogs' }} - {{ 'labels.inputs.Amount' | translate }}{{ charge.amount | formatNumber }}{{ 'labels.inputs.Collected On' | translate }}{{ charge.chargeTimeType.value | translateKey: 'catalogs' }}
-
- } - -

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

- - - + +
+ {{ 'labels.inputs.Short Name' | translate }}: + {{ currentProduct.shortName }}
+ + @if (currentProduct.description) { +
+ {{ 'labels.inputs.Description' | translate }}: + {{ currentProduct.description }} +
+ } + + @if ( + currentProduct.accountType || + currentProduct.customerSegment || + currentProduct.productCode || + currentProduct.effectiveDate || + currentProduct.effectiveTo || + currentProduct.endDate || + currentProduct.introducerRequired !== undefined || + currentProduct.shareLinkingEnabled !== undefined + ) { +

Product Classification

+ + + + @if (currentProduct.accountType) { +
+ Account Type: + {{ currentProduct.accountType }} +
+ } + + @if (currentProduct.customerSegment) { +
+ Customer Segment: + {{ currentProduct.customerSegment }} +
+ } + + @if (currentProduct.productCode) { +
+ Product Code: + {{ currentProduct.productCode }} +
+ } + + @if (currentProduct.effectiveDate) { +
+ Effective Date: + {{ currentProduct.effectiveDate }} +
+ } + + @if (currentProduct.effectiveTo) { +
+ Effective To: + {{ currentProduct.effectiveTo }} +
+ } + + @if (currentProduct.endDate) { +
+ End Date: + {{ currentProduct.endDate }} +
+ } + + @if (currentProduct.introducerRequired !== undefined) { +
+ Introducer Required: + {{ + currentProduct.introducerRequired === true || currentProduct.introducerRequired === 'Yes' ? 'Yes' : 'No' + }} +
+ } + + @if (currentProduct.shareLinkingEnabled !== undefined) { +
+ Share Linking Enabled: + {{ currentProduct.shareLinkingEnabled | yesNo }} +
+ } + } + +

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

+ + + +
+ {{ 'labels.inputs.Currency' | translate }}: + {{ currentProduct.currency.name }} +
+ +
+ {{ 'labels.inputs.Decimal Places' | translate }}: + {{ currentProduct.currency.decimalPlaces }} +
+ + @if (currentProduct.currency.inMultiplesOf !== undefined && currentProduct.currency.inMultiplesOf !== null) { +
+ {{ 'labels.inputs.Currency in multiples of' | translate }}: + {{ currentProduct.currency.inMultiplesOf }} +
+ } + + @if (currentProduct.dormancyAlert !== undefined) { +
+ Dormancy Alert: + {{ currentProduct.dormancyAlert | yesNo }} +
+ } + + @if (currentProduct.nominationFacility !== undefined) { +
+ Nomination Facility: + {{ currentProduct.nominationFacility | yesNo }} +
+ } + + @if (currentProduct.balanceSwapOption !== undefined) { +
+ Balance Swap Option: + {{ currentProduct.balanceSwapOption | yesNo }} +
+ } + + @if (currentProduct.msme !== undefined) { +
+ MSME: + {{ currentProduct.msme | yesNo }} +
+ } + + @if (currentProduct.autoDebitFacility !== undefined) { +
+ Auto Debit Facility: + {{ currentProduct.autoDebitFacility | yesNo }} +
+ } + +

Interest On OD

+ + + + @if (currentProduct.interestPostingPeriodType?.value) { +
+ {{ 'labels.inputs.Interest Posting Period' | translate }}: + {{ currentProduct.interestPostingPeriodType.value | translateKey: 'catalogs' }} +
+ } + + @if (currentProduct.interestCalculateSelector) { +
+ Interest Calculate: + {{ currentProduct.interestCalculateSelector }} +
+ } + + @if (currentProduct.averagePeriod) { +
+ Average Period: + {{ currentProduct.averagePeriod }} +
+ } + + @if (currentProduct.autoPost !== undefined) { +
+ Auto Post: + {{ currentProduct.autoPost | yesNo }} +
+ } + + @if (currentProduct.frequencyFixDay) { +
+ Frequency Fix Day: + {{ currentProduct.frequencyFixDay }} +
+ } + + @if (currentProduct.lastInterestDate) { +
+ Last Interest Date: + {{ currentProduct.lastInterestDate }} +
+ } + +

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

+ + + + @if (currentProduct.minRequiredOpeningBalance) { +
+ {{ 'labels.inputs.Minimum Opening Balance' | translate }}: + {{ currentProduct.minRequiredOpeningBalance | formatNumber }} +
+ } + + @if (currentProduct.lockinPeriodFrequency) { +
+ {{ 'labels.inputs.Lock-in Period' | translate }}: + {{ + currentProduct.lockinPeriodFrequency + + ' ' + + (currentProduct.lockinPeriodFrequencyType?.value || currentProduct.lockinPeriodFrequencyType?.code || '') + }} +
+ } + +
+ {{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }}: + {{ currentProduct.withdrawalFeeForTransfers | yesNo }} +
+ + @if (currentProduct.balanceFrequency) { +
+ Balance Frequency: + {{ currentProduct.balanceFrequency }} +
+ } + +
+ Enable Lock-in Period: + {{ currentProduct.enableLockinPeriod | yesNo }} +
+ + @if (currentProduct.minBalanceForInterestCalculation) { +
+ {{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}: + {{ currentProduct.minBalanceForInterestCalculation | formatNumber }} +
+ } + +
+ {{ 'labels.inputs.Enforce Minimum Balance' | translate }}: + {{ currentProduct.enforceMinRequiredBalance | yesNo }} +
+ + @if (currentProduct.minRequiredBalance) { +
+ {{ 'labels.inputs.Minimum Balance' | translate }}: + {{ currentProduct.minRequiredBalance | formatNumber }} +
+ } + +
+ {{ 'labels.inputs.Withhold Tax is Applicable' | translate }}: + {{ currentProduct.withHoldTax | yesNo }} +
+ + @if (currentProduct.withHoldTax) { +
+ {{ 'labels.inputs.Tax Group' | translate }}: + {{ currentProduct.taxGroup.name }} +
+ } + +
+ {{ 'labels.inputs.Is Overdraft Allowed' | translate }}: + {{ currentProduct.allowOverdraft | yesNo }} +
+ +
+ Account Closed Zero Balance in Day Closing: + {{ currentProduct.accountClosedZeroBalanceInDayClosing | yesNo }} +
+ + @if (currentProduct.minOverdraftForInterestCalculation) { +
+ {{ 'labels.inputs.Minimum Overdraft Required for Interest Calculation' | translate }}: + {{ currentProduct.minOverdraftForInterestCalculation | formatNumber }} +
+ } + + @if (currentProduct.nominalAnnualInterestRateOverdraft) { +
+ {{ 'labels.inputs.Nominal Annual Interest for Overdraft' | translate }}: + {{ currentProduct.nominalAnnualInterestRateOverdraft | formatNumber }} % +
+ } + + @if (currentProduct.overdraftLimit) { +
+ {{ 'labels.inputs.Maximum Overdraft Amount Limit' | translate }}: + {{ currentProduct.overdraftLimit | formatNumber }} +
+ } + + @if (currentProduct.allowOverdraft) { + @if (currentProduct.avgBalanceCalcPeriod) { +
+ Average Balance Calculation Period: + {{ currentProduct.avgBalanceCalcPeriod }} +
+ } + + @if (currentProduct.validForPeriod) { +
+ Valid for Period: + {{ currentProduct.validForPeriod }} +
+ } + + @if (currentProduct.odAccountClosedZeroBalanceInDayClosing !== undefined) { +
+ OD Account Closed with Zero Balance in Day Closing: + {{ currentProduct.odAccountClosedZeroBalanceInDayClosing | yesNo }} +
+ } + } + +
+ {{ 'labels.inputs.Enable Dormancy Tracking' | translate }}: + {{ currentProduct.isDormancyTrackingActive | yesNo }} +
+ + @if (currentProduct.isDormancyTrackingActive) { +
+ {{ 'labels.inputs.Number of Days to Inactive sub-status' | translate }}: + {{ currentProduct.daysToInactive }} + {{ 'labels.inputs.Number of Days to Dormant sub-status' | translate }}: + {{ currentProduct.daysToDormancy }} + {{ 'labels.inputs.Number of Days to Escheat' | translate }}: + {{ currentProduct.daysToEscheat }} +
+ } + +

Account Facilities

+ + + @if (currentProduct.accountFacilityConfig) { +
+ @for (feature of accountFacilityFeatureItems; track feature.key) { + @if (currentProduct.accountFacilityConfig[feature.key] !== undefined) { +
+ {{ feature.label }}: + {{ currentProduct.accountFacilityConfig[feature.key] | yesNo }} +
+ } + } +
+ } + + @if (currentProduct.accountFacilityLimits?.length) { +
+

Account Facility Limits

+ + + + + + + + + + + + + + + + + + + + +
Limit Type{{ row.limitType }}General Limit{{ row.generalLimit }}Minor Account Limit{{ row.minorAccountLimit }}Small Saving Limit{{ row.smallSavingLimit }}
+
+ } + + @if (currentProduct.depositLimitKyc) { +
+

Deposit Limit (KYC)

+
+ General: + {{ currentProduct.depositLimitKyc.general.kycType }} - + {{ currentProduct.depositLimitKyc.general.limit }} +
+
+ Minor: + {{ currentProduct.depositLimitKyc.minor.kycType }} - {{ currentProduct.depositLimitKyc.minor.limit }} +
+
+ Small Saving: + {{ currentProduct.depositLimitKyc.smallSaving.kycType }} - + {{ currentProduct.depositLimitKyc.smallSaving.limit }} +
+
+ } + + @if (currentProduct.charges.length) { +
+

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

+ + + + + + + + + + + + + + + + + + + + +
{{ 'labels.inputs.Name' | translate }} + {{ charge.name + ', ' + charge.currency.displaySymbol }} + {{ 'labels.inputs.Type' | translate }} + {{ charge.chargeCalculationType.value | translateKey: 'catalogs' }} + {{ 'labels.inputs.Amount' | translate }} + {{ charge.amount | formatNumber }} + {{ 'labels.inputs.Collected On' | translate }} + {{ charge.chargeTimeType.value | translateKey: 'catalogs' }} +
+
+ } + +

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

+ + + + + + + @if (currentProduct.otherGlMappingsMap) { +
+

Other GL Mappings

+ + + @if (currentProduct.otherGlMappingsMap.assets?.length) { +

Assets

+ + + + + + + + + + + +
Account Name{{ row.accountName }}GL Account{{ row.glCode || row.glAccountId }}
+ } + + @if (currentProduct.otherGlMappingsMap.liabilities?.length) { +

Liabilities

+ + + + + + + + + + + +
Account Name{{ row.accountName }}GL Account{{ row.glCode || row.glAccountId }}
+ } + + @if (currentProduct.otherGlMappingsMap.expenses?.length) { +

Expenses

+ + + + + + + + + + + +
Account Name{{ row.accountName }}GL Account{{ row.glCode || row.glAccountId }}
+ } + + @if (currentProduct.otherGlMappingsMap.income?.length) { +

Income

+ + + + + + + + + + + +
Account Name{{ row.accountName }}GL Account{{ row.glCode || row.glAccountId }}
+ } +
+ } + + @if ( + currentProduct.paymentChannelToFundSourceMappings?.length || + currentProduct.feeToIncomeAccountMappings?.length || + currentProduct.penaltyToIncomeAccountMappings?.length + ) { +
+

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

+ + @if (currentProduct.paymentChannelToFundSourceMappings?.length) { +
+

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

+ + + + + + + + + + + +
{{ 'labels.inputs.Payment Type' | translate }} + {{ paymentFundSource.paymentType.name }} + {{ 'labels.inputs.Fund Source' | translate }} + ({{ paymentFundSource.fundSourceAccount.glCode }}) {{ paymentFundSource.fundSourceAccount.name }} +
+
+ } + @if (currentProduct.feeToIncomeAccountMappings?.length) { +
+

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

+ + + + + + + + + + + +
{{ 'labels.inputs.Fee/Charge' | translate }} + {{ fee.charge.name }} + {{ 'labels.inputs.Income Account' | translate }}({{ fee.incomeAccount.glCode }}) {{ fee.incomeAccount.name }}
+
+ } + @if (currentProduct.penaltyToIncomeAccountMappings?.length) { +
+

{{ 'labels.inputs.Penalties to Specific Income Accounts' | translate }}

+ + + + + + + + + + + +
{{ 'labels.inputs.Penalty' | translate }} + {{ penalty.charge.name }} + {{ 'labels.inputs.Income Account' | translate }} + ({{ penalty.incomeAccount.glCode }}) {{ penalty.incomeAccount.name }} +
+
+ } +
+ }
diff --git a/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.ts b/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.ts index 54b58f02c..289028614 100644 --- a/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.ts +++ b/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.ts @@ -1,3 +1,10 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ import { Component, inject } from '@angular/core'; import { ActivatedRoute, RouterLink } from '@angular/router'; import { Accounting } from 'app/core/utils/accounting'; @@ -56,6 +63,34 @@ export class CurrentProductGeneralTabComponent { 'chargeTimeType' ]; + paymentFundSourceDisplayedColumns: string[] = [ + 'paymentTypeId', + 'fundSourceAccountId' + ]; + feesPenaltyIncomeDisplayedColumns: string[] = [ + 'chargeId', + 'incomeAccountId' + ]; + + accountFacilityFeatureItems = [ + { key: 'atmFacility', label: 'ATM Facility' }, + { key: 'atmWithdrawalFacility', label: 'ATM Withdrawal Facility' }, + { key: 'upiFacility', label: 'UPI Facility' }, + { key: 'posTransactionsFacility', label: 'POS Transactions Facility' }, + { key: 'chequeBook', label: 'Cheque Book' }, + { key: 'impsFacility', label: 'IMPS Facility' }, + { key: 'smsAlerts', label: 'SMS Alerts' }, + { key: 'bbpsFacility', label: 'BBPS Facility' }, + { key: 'insuranceCover', label: 'Insurance Cover' }, + { key: 'bepgFacility', label: 'BEPG Facility' }, + { key: 'neftFacility', label: 'NEFT Facility' }, + { key: 'rtgsFacility', label: 'RTGS Facility' }, + { key: 'interBankTotalCapFacility', label: 'Inter-Bank Total Cap Facility' }, + { key: 'withdrawalFacility', label: 'Withdrawal Facility' }, + { key: 'depositFacilityKyc', label: 'Deposit Facility KYC' }, + { key: 'virtualDebitCard', label: 'Virtual Debit Card' } + ]; + constructor() { this.route.data.subscribe((data: { currentProduct: any }) => { this.currentProduct = data.currentProduct; diff --git a/src/app/products/fixed-deposit-products/create-fixed-deposit-product/create-fixed-deposit-product.component.scss b/src/app/products/fixed-deposit-products/create-fixed-deposit-product/create-fixed-deposit-product.component.scss index 38cce13dd..8e2ab2292 100644 --- a/src/app/products/fixed-deposit-products/create-fixed-deposit-product/create-fixed-deposit-product.component.scss +++ b/src/app/products/fixed-deposit-products/create-fixed-deposit-product/create-fixed-deposit-product.component.scss @@ -18,9 +18,10 @@ $primary: #1353a0; .fdp-tabs { background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); + ::ng-deep { - .mat-mdc-tab-header { border-bottom: 1px solid rgba(0, 0, 0, 0.08); } + .mat-mdc-tab-header { border-bottom: 1px solid rgb(0 0 0 / 8%); } .mat-mdc-tab { min-width: 100px; padding: 0 12px; height: 52px; } .mat-mdc-tab-body-wrapper { min-height: 200px; } .mat-mdc-tab-body { overflow: visible !important; } @@ -33,7 +34,7 @@ $primary: #1353a0; .fdp-action-bar { display: flex; justify-content: space-between; align-items: center; - margin-top: 20px; padding: 16px 0; border-top: 1px solid rgba(0, 0, 0, 0.08); - &__cancel { color: rgba(0, 0, 0, 0.6); } + margin-top: 20px; padding: 16px 0; border-top: 1px solid rgb(0 0 0 / 8%); + &__cancel { color: rgb(0 0 0 / 60%); } &__right { display: flex; gap: 12px; } } 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 2f9db0ace..74f18ffcf 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 @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } @@ -62,7 +62,7 @@ gap: 24px; flex-wrap: wrap; - @media (max-width: 600px) { + @media (width <= 600px) { flex-direction: column; } } 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 1e57eef92..ccfe94921 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 @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } 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 a6baa2b82..fe1c47dc5 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 @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } 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 c561e87f5..7747019fc 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 @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-interest-rate-chart-step/fixed-deposit-product-interest-rate-chart-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-interest-rate-chart-step/fixed-deposit-product-interest-rate-chart-step.component.scss index 161bdd25c..e74afdae2 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-interest-rate-chart-step/fixed-deposit-product-interest-rate-chart-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-interest-rate-chart-step/fixed-deposit-product-interest-rate-chart-step.component.scss @@ -51,7 +51,7 @@ grid-template-columns: repeat(2, 1fr); gap: 4px 16px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-preview-step/fixed-deposit-product-preview-step.component.scss b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-preview-step/fixed-deposit-product-preview-step.component.scss index 5e4dbc4ec..4ae6c504a 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-preview-step/fixed-deposit-product-preview-step.component.scss +++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-preview-step/fixed-deposit-product-preview-step.component.scss @@ -57,7 +57,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } 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 41cc7b11f..62b1187f8 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 @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } 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 49fd6b91b..ac8a2ddd5 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 @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/floating-rates/create-floating-rate/create-floating-rate.component.scss b/src/app/products/floating-rates/create-floating-rate/create-floating-rate.component.scss index e5da63228..9a9eda976 100644 --- a/src/app/products/floating-rates/create-floating-rate/create-floating-rate.component.scss +++ b/src/app/products/floating-rates/create-floating-rate/create-floating-rate.component.scss @@ -152,7 +152,7 @@ padding-top: 8px; } -@media (max-width: 600px) { +@media (width <= 600px) { .cfr-container { padding: 16px 10px; } diff --git a/src/app/products/floating-rates/edit-floating-rate/edit-floating-rate.component.scss b/src/app/products/floating-rates/edit-floating-rate/edit-floating-rate.component.scss index 4cf972d91..2c1864327 100644 --- a/src/app/products/floating-rates/edit-floating-rate/edit-floating-rate.component.scss +++ b/src/app/products/floating-rates/edit-floating-rate/edit-floating-rate.component.scss @@ -152,7 +152,7 @@ padding-top: 8px; } -@media (max-width: 600px) { +@media (width <= 600px) { .efr-container { padding: 16px 10px; } diff --git a/src/app/products/floating-rates/view-floating-rate/view-floating-rate.component.scss b/src/app/products/floating-rates/view-floating-rate/view-floating-rate.component.scss index 3c5fe96f7..9fc868d02 100644 --- a/src/app/products/floating-rates/view-floating-rate/view-floating-rate.component.scss +++ b/src/app/products/floating-rates/view-floating-rate/view-floating-rate.component.scss @@ -154,7 +154,7 @@ margin-right: 10px; } -@media (max-width: 600px) { +@media (width <= 600px) { .vfr-container { padding: 16px 10px; } diff --git a/src/app/products/loan-products/create-loan-product/create-loan-product.component.scss b/src/app/products/loan-products/create-loan-product/create-loan-product.component.scss index 65cff69e8..d05cedb1a 100644 --- a/src/app/products/loan-products/create-loan-product/create-loan-product.component.scss +++ b/src/app/products/loan-products/create-loan-product/create-loan-product.component.scss @@ -35,11 +35,11 @@ $primary: #1353a0; .lp-tabs { background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); ::ng-deep { .mat-mdc-tab-header { - border-bottom: 1px solid rgba(0, 0, 0, 0.08); + border-bottom: 1px solid rgb(0 0 0 / 8%); } .mat-mdc-tab { @@ -77,10 +77,10 @@ $primary: #1353a0; align-items: center; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); &__cancel { - color: rgba(0, 0, 0, 0.6); + color: rgb(0 0 0 / 60%); } &__right { diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-accounting-step/loan-product-accounting-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-accounting-step/loan-product-accounting-step.component.scss index de058a346..687d9456d 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-accounting-step/loan-product-accounting-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-accounting-step/loan-product-accounting-step.component.scss @@ -112,7 +112,7 @@ } // Responsive -@media (max-width: 600px) { +@media (width <= 600px) { .lpa-grid { grid-template-columns: 1fr; } diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-capitalized-income-step/loan-product-deferred-income-recognition-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-capitalized-income-step/loan-product-deferred-income-recognition-step.component.scss index 1c455c6f9..3b4fc2e89 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-capitalized-income-step/loan-product-deferred-income-recognition-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-capitalized-income-step/loan-product-deferred-income-recognition-step.component.scss @@ -39,7 +39,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .lpdi-field { diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-charges-step/loan-product-charges-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-charges-step/loan-product-charges-step.component.scss index e9667a432..a9cfa9a1f 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-charges-step/loan-product-charges-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-charges-step/loan-product-charges-step.component.scss @@ -57,7 +57,7 @@ $gray-900: #202124; box-shadow: 0 1px 3px rgba($primary, 0.3) !important; } - @media (max-width: 600px) { + @media (width <= 600px) { flex-direction: column; &__select { max-width: 100%; width: 100%; } } diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-currency-step/loan-product-currency-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-currency-step/loan-product-currency-step.component.scss index eb806e2d3..5dbbf06b8 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-currency-step/loan-product-currency-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-currency-step/loan-product-currency-step.component.scss @@ -39,7 +39,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .lpc-field { diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.scss index f50bafc78..01e69962d 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.scss @@ -39,7 +39,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .lpd-field { diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-interest-refund-step/loan-product-interest-refund-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-interest-refund-step/loan-product-interest-refund-step.component.scss index 5d6f7a21b..c8dc9d8b9 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-interest-refund-step/loan-product-interest-refund-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-interest-refund-step/loan-product-interest-refund-step.component.scss @@ -37,7 +37,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .lpir-field { diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.scss index 9fbe0d1ec..ef42e0e74 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.scss @@ -53,7 +53,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } @@ -80,7 +80,7 @@ grid-template-columns: repeat(2, 1fr); gap: 4px 16px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.scss index 883ed9f1c..5f40cb5b5 100644 --- a/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.scss +++ b/src/app/products/loan-products/loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component.scss @@ -50,7 +50,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } @@ -58,7 +58,7 @@ .lpt-grid-3 { grid-template-columns: repeat(3, 1fr); - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } @@ -66,11 +66,11 @@ .lpt-grid-4 { grid-template-columns: repeat(4, 1fr); - @media (max-width: 900px) { + @media (width <= 900px) { grid-template-columns: repeat(2, 1fr); } - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/manage-delinquency-buckets/delinquency-bucket/create-bucket/create-bucket.component.scss b/src/app/products/manage-delinquency-buckets/delinquency-bucket/create-bucket/create-bucket.component.scss index d043d9455..4ba22656a 100644 --- a/src/app/products/manage-delinquency-buckets/delinquency-bucket/create-bucket/create-bucket.component.scss +++ b/src/app/products/manage-delinquency-buckets/delinquency-bucket/create-bucket/create-bucket.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .db-field { diff --git a/src/app/products/manage-delinquency-buckets/delinquency-bucket/edit-bucket/edit-bucket.component.scss b/src/app/products/manage-delinquency-buckets/delinquency-bucket/edit-bucket/edit-bucket.component.scss index d043d9455..4ba22656a 100644 --- a/src/app/products/manage-delinquency-buckets/delinquency-bucket/edit-bucket/edit-bucket.component.scss +++ b/src/app/products/manage-delinquency-buckets/delinquency-bucket/edit-bucket/edit-bucket.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .db-field { diff --git a/src/app/products/manage-delinquency-buckets/delinquency-range/create-range/create-range.component.scss b/src/app/products/manage-delinquency-buckets/delinquency-range/create-range/create-range.component.scss index baee4a424..c191f95f5 100644 --- a/src/app/products/manage-delinquency-buckets/delinquency-range/create-range/create-range.component.scss +++ b/src/app/products/manage-delinquency-buckets/delinquency-range/create-range/create-range.component.scss @@ -31,9 +31,9 @@ .cr-action-bar { display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .cr-grid { grid-template-columns: 1fr; } } diff --git a/src/app/products/manage-delinquency-buckets/delinquency-range/edit-range/edit-range.component.scss b/src/app/products/manage-delinquency-buckets/delinquency-range/edit-range/edit-range.component.scss index e5a3364e0..53d95280c 100644 --- a/src/app/products/manage-delinquency-buckets/delinquency-range/edit-range/edit-range.component.scss +++ b/src/app/products/manage-delinquency-buckets/delinquency-range/edit-range/edit-range.component.scss @@ -31,9 +31,9 @@ .er-action-bar { display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); } -@media (max-width: 600px) { +@media (width <= 600px) { .er-grid { grid-template-columns: 1fr; } } diff --git a/src/app/products/manage-delinquency-buckets/manage-delinquency-buckets.component.scss b/src/app/products/manage-delinquency-buckets/manage-delinquency-buckets.component.scss index 8d62191ed..cc835be50 100644 --- a/src/app/products/manage-delinquency-buckets/manage-delinquency-buckets.component.scss +++ b/src/app/products/manage-delinquency-buckets/manage-delinquency-buckets.component.scss @@ -23,7 +23,7 @@ &:hover { border-color: #0ea5e9; - box-shadow: 0 2px 8px rgba(14, 165, 233, 0.12); + box-shadow: 0 2px 8px rgb(14 165 233 / 12%); } &__icon-wrap { diff --git a/src/app/products/manage-tax-components/create-tax-component/create-tax-component.component.scss b/src/app/products/manage-tax-components/create-tax-component/create-tax-component.component.scss index 54e7899d4..ddf54f2c6 100644 --- a/src/app/products/manage-tax-components/create-tax-component/create-tax-component.component.scss +++ b/src/app/products/manage-tax-components/create-tax-component/create-tax-component.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ctc-field { diff --git a/src/app/products/manage-tax-components/edit-tax-component/edit-tax-component.component.scss b/src/app/products/manage-tax-components/edit-tax-component/edit-tax-component.component.scss index 57173690c..4eddbd8f5 100644 --- a/src/app/products/manage-tax-components/edit-tax-component/edit-tax-component.component.scss +++ b/src/app/products/manage-tax-components/edit-tax-component/edit-tax-component.component.scss @@ -92,7 +92,7 @@ $gray-900: #202124; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .etc-field { diff --git a/src/app/products/manage-tax-components/view-tax-component/view-tax-component.component.scss b/src/app/products/manage-tax-components/view-tax-component/view-tax-component.component.scss index 83d5d9403..5d3396259 100644 --- a/src/app/products/manage-tax-components/view-tax-component/view-tax-component.component.scss +++ b/src/app/products/manage-tax-components/view-tax-component/view-tax-component.component.scss @@ -81,14 +81,14 @@ $gray-900: #202124; padding: 20px 28px 24px; background: #fff; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .vtc-cell { padding: 14px 16px; background: #f8fafc; border-radius: 8px; - border: 1px solid rgba(0, 0, 0, 0.04); + border: 1px solid rgb(0 0 0 / 4%); margin: 3px; transition: background 0.12s; @@ -114,7 +114,7 @@ $gray-900: #202124; } // Responsive -@media (max-width: 600px) { +@media (width <= 600px) { .vtc-header { flex-direction: column; align-items: flex-start; diff --git a/src/app/products/manage-tax-configurations/manage-tax-configurations.component.scss b/src/app/products/manage-tax-configurations/manage-tax-configurations.component.scss index bc087f023..b107dad99 100644 --- a/src/app/products/manage-tax-configurations/manage-tax-configurations.component.scss +++ b/src/app/products/manage-tax-configurations/manage-tax-configurations.component.scss @@ -62,7 +62,7 @@ $gray-900: #0f172a; grid-template-columns: repeat(2, 1fr); gap: 12px; - @media (max-width: 768px) { + @media (width <= 768px) { grid-template-columns: 1fr; } } @@ -82,7 +82,7 @@ $gray-900: #0f172a; &:hover { border-color: #cbd5e1; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + box-shadow: 0 2px 8px rgb(0 0 0 / 6%); transform: translateY(-1px); } diff --git a/src/app/products/manage-tax-groups/create-tax-group/create-tax-group.component.scss b/src/app/products/manage-tax-groups/create-tax-group/create-tax-group.component.scss index 5fc7c9d76..38a8f6a0a 100644 --- a/src/app/products/manage-tax-groups/create-tax-group/create-tax-group.component.scss +++ b/src/app/products/manage-tax-groups/create-tax-group/create-tax-group.component.scss @@ -102,7 +102,7 @@ $text-muted: #64748b; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .ctg-field { diff --git a/src/app/products/manage-tax-groups/edit-tax-group/edit-tax-group.component.scss b/src/app/products/manage-tax-groups/edit-tax-group/edit-tax-group.component.scss index 508e65c38..5558ddbaa 100644 --- a/src/app/products/manage-tax-groups/edit-tax-group/edit-tax-group.component.scss +++ b/src/app/products/manage-tax-groups/edit-tax-group/edit-tax-group.component.scss @@ -102,7 +102,7 @@ $text-muted: #64748b; gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .etg-field { diff --git a/src/app/products/manage-tax-groups/view-tax-group/view-tax-group.component.scss b/src/app/products/manage-tax-groups/view-tax-group/view-tax-group.component.scss index e17847cf5..107152abd 100644 --- a/src/app/products/manage-tax-groups/view-tax-group/view-tax-group.component.scss +++ b/src/app/products/manage-tax-groups/view-tax-group/view-tax-group.component.scss @@ -109,14 +109,14 @@ $text-muted: #64748b; gap: 1px; padding: 20px 28px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + @media (width <= 600px) { grid-template-columns: 1fr; } } .vtg-cell { padding: 14px 16px; background: #f8fafc; border-radius: 8px; - border: 1px solid rgba(0, 0, 0, 0.04); + border: 1px solid rgb(0 0 0 / 4%); margin: 3px; transition: background 0.12s; @@ -195,7 +195,7 @@ $text-muted: #64748b; } // Responsive -@media (max-width: 600px) { +@media (width <= 600px) { .vtg-header { flex-direction: column; align-items: flex-start; diff --git a/src/app/products/products-mix/create-product-mix/create-product-mix.component.scss b/src/app/products/products-mix/create-product-mix/create-product-mix.component.scss index 621e88022..a7e748e41 100644 --- a/src/app/products/products-mix/create-product-mix/create-product-mix.component.scss +++ b/src/app/products/products-mix/create-product-mix/create-product-mix.component.scss @@ -81,7 +81,7 @@ padding-top: 8px; } -@media (max-width: 600px) { +@media (width <= 600px) { .cpm-container { padding: 16px 12px; } diff --git a/src/app/products/products-mix/edit-product-mix/edit-product-mix.component.scss b/src/app/products/products-mix/edit-product-mix/edit-product-mix.component.scss index 2a0403cdf..c8f348e5d 100644 --- a/src/app/products/products-mix/edit-product-mix/edit-product-mix.component.scss +++ b/src/app/products/products-mix/edit-product-mix/edit-product-mix.component.scss @@ -81,7 +81,7 @@ padding-top: 8px; } -@media (max-width: 600px) { +@media (width <= 600px) { .epm-container { padding: 16px 12px; } diff --git a/src/app/products/products-mix/view-product-mix/view-product-mix.component.scss b/src/app/products/products-mix/view-product-mix/view-product-mix.component.scss index 069352a61..7ed4e4fb4 100644 --- a/src/app/products/products-mix/view-product-mix/view-product-mix.component.scss +++ b/src/app/products/products-mix/view-product-mix/view-product-mix.component.scss @@ -156,7 +156,7 @@ background: transparent; } -@media (max-width: 600px) { +@media (width <= 600px) { .vpm-container { padding: 16px 12px; } diff --git a/src/app/products/products.component.scss b/src/app/products/products.component.scss index 985812ad5..698497b5f 100644 --- a/src/app/products/products.component.scss +++ b/src/app/products/products.component.scss @@ -61,7 +61,7 @@ $gray-900: #0f172a; grid-template-columns: repeat(2, 1fr); gap: 12px; - @media (max-width: 768px) { + @media (width <= 768px) { grid-template-columns: 1fr; } } @@ -82,7 +82,7 @@ $gray-900: #0f172a; &:hover { border-color: #cbd5e1; background: $gray-50; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + box-shadow: 0 2px 8px rgb(0 0 0 / 4%); .pd-card__arrow { color: $gray-700; @@ -101,62 +101,62 @@ $gray-900: #0f172a; flex-shrink: 0; &--blue { - background: rgba(59, 130, 246, 0.1); + background: rgb(59 130 246 / 10%); color: #2563eb; } &--green { - background: rgba(34, 197, 94, 0.1); + background: rgb(34 197 94 / 10%); color: #16a34a; } &--cyan { - background: rgba(6, 182, 212, 0.1); + background: rgb(6 182 212 / 10%); color: #0891b2; } &--purple { - background: rgba(139, 92, 246, 0.1); + background: rgb(139 92 246 / 10%); color: #7c3aed; } &--amber { - background: rgba(245, 158, 11, 0.1); + background: rgb(245 158 11 / 10%); color: #d97706; } &--teal { - background: rgba(20, 184, 166, 0.1); + background: rgb(20 184 166 / 10%); color: #0d9488; } &--indigo { - background: rgba(99, 102, 241, 0.1); + background: rgb(99 102 241 / 10%); color: #4f46e5; } &--orange { - background: rgba(249, 115, 22, 0.1); + background: rgb(249 115 22 / 10%); color: #ea580c; } &--slate { - background: rgba(100, 116, 139, 0.1); + background: rgb(100 116 139 / 10%); color: #475569; } &--rose { - background: rgba(244, 63, 94, 0.1); + background: rgb(244 63 94 / 10%); color: #e11d48; } &--emerald { - background: rgba(16, 185, 129, 0.1); + background: rgb(16 185 129 / 10%); color: #059669; } &--red { - background: rgba(239, 68, 68, 0.1); + background: rgb(239 68 68 / 10%); color: #dc2626; } } diff --git a/src/app/products/recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component.html b/src/app/products/recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component.html index 9395a61dd..b0882ab54 100644 --- a/src/app/products/recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component.html +++ b/src/app/products/recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component.html @@ -12,7 +12,12 @@

{{ 'labels.heading.Create Recurring Deposit Product' | translate }}

- + @@ -126,19 +131,19 @@
@if (selectedTabIndex > 0) { } @if (selectedTabIndex < totalTabs - 1) { } @if (recurringDepositProductFormValid && selectedTabIndex === totalTabs - 1) { } diff --git a/src/app/products/recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component.scss b/src/app/products/recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component.scss index 6d2fa4996..9891e8ba1 100644 --- a/src/app/products/recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component.scss +++ b/src/app/products/recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component.scss @@ -8,32 +8,83 @@ $primary: #1353a0; -.rdp-container { max-width: 960px; margin: 0 auto; padding: 24px 16px; } - -.rdp-header { - display: flex; align-items: center; gap: 10px; margin-bottom: 20px; - &__icon { font-size: 20px; color: $primary; } - &__title { margin: 0; font-size: 22px; font-weight: 500; } +.rdp-container { + max-width: 960px; + margin: 0 auto; + padding: 24px 16px; } -.rdp-tabs { - background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); - ::ng-deep { - .mat-mdc-tab-header { border-bottom: 1px solid rgba(0, 0, 0, 0.08); } - .mat-mdc-tab { min-width: 100px; padding: 0 12px; height: 52px; } - .mat-mdc-tab-body-wrapper { min-height: 200px; } - .mat-mdc-tab-body { overflow: visible !important; } - .mat-mdc-tab-body-content { overflow: visible !important; } +.rdp-header { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 20px; + + &__icon { + font-size: 20px; + color: $primary; + } + + &__title { + margin: 0; + font-size: 22px; + font-weight: 500; } } -.rdp-tab-icon { margin-right: 8px; font-size: 14px; } -.rdp-tab-content { padding: 24px 16px; } +.rdp-tabs { + background: var(--md-sys-color-surface, #fff); + border-radius: 8px; + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); + + ::ng-deep { + .mat-mdc-tab-header { + border-bottom: 1px solid rgb(0 0 0 / 8%); + } + + .mat-mdc-tab { + min-width: 100px; + padding: 0 12px; + height: 52px; + } + + .mat-mdc-tab-body-wrapper { + min-height: 200px; + } + + .mat-mdc-tab-body { + overflow: visible !important; + } + + .mat-mdc-tab-body-content { + overflow: visible !important; + } + } +} + +.rdp-tab-icon { + margin-right: 8px; + font-size: 14px; +} + +.rdp-tab-content { + padding: 24px 16px; +} .rdp-action-bar { - display: flex; justify-content: space-between; align-items: center; - margin-top: 20px; padding: 16px 0; border-top: 1px solid rgba(0, 0, 0, 0.08); - &__cancel { color: rgba(0, 0, 0, 0.6); } - &__right { display: flex; gap: 12px; } + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 20px; + padding: 16px 0; + border-top: 1px solid rgb(0 0 0 / 8%); + + &__cancel { + color: rgb(0 0 0 / 60%); + } + + &__right { + display: flex; + gap: 12px; + } } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.html index 10024514c..6d8eba561 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.html @@ -7,273 +7,113 @@ -->
- -
- -

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

+ + + +

Accounting

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

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

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

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

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

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

-
-
- -
-
- - -
-
- -

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

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

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

- +
+
+

Accounting Rule

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

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

-
- @if (paymentChannelToFundSourceMappings.value.length !== 0) { -
- - - - - - - - - - - - - - - -
{{ 'labels.inputs.Payment Type' | translate }} - {{ paymentFundSource.paymentTypeId | find: paymentTypeData : 'id' : 'name' }} - {{ 'labels.inputs.Fund Source' | translate }} - {{ paymentFundSource.fundSourceAccountId | find: assetAccountData : 'id' : 'name' }} - {{ 'labels.inputs.Actions' | translate }} - - -
-
- } +
+ @for (row of section.rows; track row.key) { + @if (!row.accrualOnly || isAccrualAccounting()) { +
+
+ + {{ section.pillLabel }} + + {{ row.label }} +
+ +
+ } + } +
+ +
+ @for ( + otherRow of getOtherGlMappingsArray(section.otherMappingsControlName).controls; + track $index; + let i = $index + ) { +
+
+
+ + {{ section.pillLabel }} + + +
+ +
+ + GL Code + + @for (glAccount of section.otherAccountOptions; track glAccount.id) { + + {{ '(' + glAccount.glCode + ') ' + glAccount.name }} + + } + + +
+
+ +
+ } +
- - - @if (existCharges()) { -
-
-

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

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

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

- -
- @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.inputs.Actions' | translate }} - - -
-
- } -
- } - -
+ } }
- } +
diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.scss index 250653495..fe6ce3f2b 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.scss @@ -6,33 +6,256 @@ * 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; + .rdpa-section { - border: 1px solid #e2e8f0; + border: 1px solid $border; border-radius: 10px; overflow: hidden; background: #fff; +} - & + & { margin-top: 16px; } +.rdpa-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; + } +} + +.rdpa-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; +} + +.rdpa-section__body { + padding: 16px 24px 20px; +} + +.rdpa-group { + & + & { + margin-top: 20px; + } &__header { display: flex; align-items: center; - gap: 10px; - padding: 12px 24px; - background: #f8f9fa; - border-bottom: 1px solid #e8eaed; - - i { font-size: 14px; color: #0ea5e9; } - h3 { margin: 0; font-size: 14px; font-weight: 600; color: #202124; } + justify-content: space-between; + gap: 16px; + margin: 0 0 12px; } - &__body { padding: 16px 24px; } + h4 { + margin: 0 0 12px; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #94a3b8; + } } -.rdpa-grid { +.rdpa-add-btn { + border: 0; + background: transparent; + color: #2563eb; + font-size: 12px; + font-weight: 700; + padding: 0; + cursor: pointer; +} + +.rdpa-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.rdpa-list + .rdpa-list { + margin-top: 12px; +} + +.rdpa-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; + } +} + +.rdpa-row:has(mifosx-gl-account-selector) { + align-items: stretch; +} + +.rdpa-row--other { + border-style: dashed; + border-color: #cbd5e1; + background: #f8fafc; display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 4px 16px; + grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr); + align-items: center; + padding: 12px 16px; + + @media (width <= 1100px) { + grid-template-columns: 1fr; + } +} + +.rdpa-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; + } +} + +.rdpa-row__meta { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; + flex: 1 1 auto; +} + +.rdpa-row__meta--other { + min-width: 0; + flex: 1 1 auto; + align-self: stretch; +} + +.rdpa-row__label { + font-size: 14px; + font-weight: 600; + color: $gray-700; +} + +.rdpa-row__input { + flex: 0 0 22rem; + max-width: 22rem; + min-width: 18rem; + + @media (width <= 900px) { + width: 100%; + max-width: 100%; + min-width: 0; + } +} + +.rdpa-row__input :is(mifosx-gl-account-selector) { + width: 100%; + display: block; +} + +.rdpa-row__input :is(mifosx-gl-account-selector) :is(.layout-column) { + width: 100%; + margin-right: 0 !important; +} + +.rdpa-row__input :is(mifosx-gl-account-selector) :is(mat-form-field) { + width: 100%; +} + +.rdpa-row__actions { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; +} + +.rdpa-row__actions--other { + justify-content: flex-end; + align-self: stretch; + width: 100%; + + @media (width <= 1100px) { + justify-content: flex-start; + } +} + +.rdpa-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%; + } +} + +.rdpa-row__name--other-account { + max-width: 100%; + flex: 1 1 auto; + height: 48px; +} + +.rdpa-row__other-field { + width: 100%; + min-width: 0; +} + +.rdpa-row__other-field :is(mat-form-field) { + width: 100%; +} + +.rdpa-row__other-field :is(.mat-mdc-form-field-required-marker) { + display: none; +} + +.rdpa-remove-btn { + border: 0; + background: transparent; + color: #ef4444; + font-size: 18px; + line-height: 1; + cursor: pointer; + padding: 0 4px; +} + +.rdpa-remove-btn--side { + place-self: center end; + margin-right: 4px; + + @media (width <= 1100px) { + justify-self: start; + margin-right: 0; + } } .rdpa-radio-group { @@ -41,39 +264,41 @@ flex-wrap: wrap; } -.rdpa-header-checkbox { margin-left: auto; } +.rdpa-pill { + display: inline-flex; + align-items: center; + 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; -.rdpa-subsection { - & + & { margin-top: 16px; } + &--green { + background: #dcfce7; + color: #16a34a; + } - &__header { - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - gap: 8px; - margin-bottom: 8px; + &--amber { + background: #fef3c7; + color: #d97706; + } - h4 { margin: 0; font-size: 13px; font-weight: 600; color: #202124; } + &--blue { + background: #dbeafe; + color: #2563eb; } } -.rdpa-add-btn { - height: 36px; - border-radius: 8px; - font-size: 13px; - font-weight: 600; -} +@media (width <= 900px) { + .rdpa-row__meta { + margin-bottom: 4px; + } -.rdpa-table-wrapper { - border: 1px solid #e2e8f0; - border-radius: 8px; - overflow: hidden; - - table { width: 100%; } -} - -@media (max-width: 600px) { - .rdpa-grid { grid-template-columns: 1fr; } - .rdpa-radio-group { flex-direction: column; } + .rdpa-row__input { + flex: 1 1 auto; + max-width: 100%; + } } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.ts b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.ts index 6dfca1628..9d8843dda 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.ts +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.ts @@ -6,441 +6,393 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component, OnInit, Input, inject } from '@angular/core'; +import { Component, Input, OnInit, inject } from '@angular/core'; import { - UntypedFormGroup, - UntypedFormBuilder, + FormsModule, + ReactiveFormsModule, UntypedFormArray, - Validators, + UntypedFormBuilder, UntypedFormControl, - ReactiveFormsModule + UntypedFormGroup, + Validators } from '@angular/forms'; -import { MatDialog } from '@angular/material/dialog'; - -import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component'; -import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component'; - -import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base'; -import { SelectBase } from 'app/shared/form-dialog/formfield/model/select-base'; +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 { TranslateService } from '@ngx-translate/core'; -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-recurring-deposit-product-accounting-step', templateUrl: './recurring-deposit-product-accounting-step.component.html', styleUrls: ['./recurring-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 RecurringDepositProductAccountingStepComponent implements OnInit { private formBuilder = inject(UntypedFormBuilder); - private dialog = inject(MatDialog); private accounting = inject(Accounting); - private translateService = inject(TranslateService); @Input() recurringDepositProductsTemplate: any; @Input() accountingRuleData: any; @Input() recurringDepositProductFormValid: boolean; recurringDepositProductAccountingForm: 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.createrecurringDepositProductAccountingForm(); + this.createRecurringDepositProductAccountingForm(); this.setConditionalControls(); } ngOnInit() { - this.chargeData = this.recurringDepositProductsTemplate.chargeOptions || []; - this.penaltyData = this.recurringDepositProductsTemplate.penaltyOptions || []; - this.paymentTypeData = this.recurringDepositProductsTemplate.paymentTypeOptions || []; - this.assetAccountData = this.recurringDepositProductsTemplate.accountingMappingOptions.assetAccountOptions || []; - this.incomeAccountData = this.recurringDepositProductsTemplate.accountingMappingOptions.incomeAccountOptions || []; - this.expenseAccountData = - this.recurringDepositProductsTemplate.accountingMappingOptions.expenseAccountOptions || []; - this.liabilityAccountData = - this.recurringDepositProductsTemplate.accountingMappingOptions.liabilityAccountOptions || []; - if (!(this.recurringDepositProductsTemplate === undefined) && this.recurringDepositProductsTemplate.id) { - this.assignAccountingStepData(); - } + const accountingMappings = this.recurringDepositProductsTemplate?.accountingMappings || {}; + const mappings = { + ...accountingMappings, + otherGlMappingsMap: this.recurringDepositProductsTemplate?.otherGlMappingsMap + }; + const accountingMappingOptions = this.recurringDepositProductsTemplate?.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.recurringDepositProductAccountingForm.patchValue({ accountingRule: accountingRuleId }); + this.syncAccountingControls(accountingRuleId); + this.patchAccountingValues(mappings); } - assignAccountingStepData() { - this.recurringDepositProductAccountingForm.patchValue({ - accountingRule: this.recurringDepositProductsTemplate.accountingRule.id - }); - if (this.isCashOrAccrualAccounting()) { - this.recurringDepositProductAccountingForm.patchValue({ - savingsReferenceAccountId: this.recurringDepositProductsTemplate.accountingMappings.savingsReferenceAccount.id, - savingsControlAccountId: this.recurringDepositProductsTemplate.accountingMappings.savingsControlAccount.id, - transfersInSuspenseAccountId: - this.recurringDepositProductsTemplate.accountingMappings.transfersInSuspenseAccount.id, - incomeFromFeeAccountId: this.recurringDepositProductsTemplate.accountingMappings.incomeFromFeeAccount.id, - incomeFromPenaltyAccountId: - this.recurringDepositProductsTemplate.accountingMappings.incomeFromPenaltyAccount.id, - interestOnSavingsAccountId: this.recurringDepositProductsTemplate.accountingMappings.interestOnSavingsAccount.id - }); - - if (this.isAccrualAccounting()) { - this.recurringDepositProductAccountingForm.patchValue({ - feesReceivableAccountId: this.recurringDepositProductsTemplate.accountingMappings.feeReceivableAccount.id, - penaltiesReceivableAccountId: - this.recurringDepositProductsTemplate.accountingMappings.penaltyReceivableAccount.id, - interestPayableAccountId: this.recurringDepositProductsTemplate.accountingMappings.interestPayableAccount.id - }); - } - - if ( - this.recurringDepositProductsTemplate.paymentChannelToFundSourceMappings || - this.recurringDepositProductsTemplate.feeToIncomeAccountMappings || - this.recurringDepositProductsTemplate.penaltyToIncomeAccountMappings - ) { - this.recurringDepositProductAccountingForm.patchValue({ - advancedAccountingRules: true - }); - } - if (this.recurringDepositProductsTemplate.paymentChannelToFundSourceMappings) { - this.recurringDepositProductsTemplate.paymentChannelToFundSourceMappings.forEach( - (paymentChannelToFundSourceMapping: any) => { - const paymentChannelToFundSourceMappingData = this.formBuilder.group({ - paymentTypeId: [ - paymentChannelToFundSourceMapping.paymentType.id, - Validators.required - ], - fundSourceAccountId: [ - paymentChannelToFundSourceMapping.fundSourceAccount.id, - Validators.required - ] - }); - const formArray = this.recurringDepositProductAccountingForm.controls[ - 'paymentChannelToFundSourceMappings' - ] as UntypedFormArray; - formArray.push(paymentChannelToFundSourceMappingData); - } - ); - } - if (this.recurringDepositProductsTemplate.feeToIncomeAccountMappings) { - this.recurringDepositProductsTemplate.feeToIncomeAccountMappings.forEach((feeToIncomeAccountMapping: any) => { - const feeToIncomeAccountMappingData = this.formBuilder.group({ - chargeId: [ - feeToIncomeAccountMapping.charge.id, - Validators.required - ], - incomeAccountId: [ - feeToIncomeAccountMapping.incomeAccount.id, - Validators.required - ] - }); - const formArray = this.recurringDepositProductAccountingForm.controls[ - 'feeToIncomeAccountMappings' - ] as UntypedFormArray; - formArray.push(feeToIncomeAccountMappingData); - }); - } - if (this.recurringDepositProductsTemplate.penaltyToIncomeAccountMappings) { - this.recurringDepositProductsTemplate.penaltyToIncomeAccountMappings.forEach( - (penaltyToIncomeAccountMapping: any) => { - const penaltyToIncomeAccountMappingData = this.formBuilder.group({ - chargeId: [ - penaltyToIncomeAccountMapping.charge.id, - Validators.required - ], - incomeAccountId: [ - penaltyToIncomeAccountMapping.incomeAccount.id, - Validators.required - ] - }); - const formArray = this.recurringDepositProductAccountingForm.controls[ - 'penaltyToIncomeAccountMappings' - ] as UntypedFormArray; - formArray.push(penaltyToIncomeAccountMappingData); - } - ); - } - } - } - - createrecurringDepositProductAccountingForm() { + createRecurringDepositProductAccountingForm() { this.recurringDepositProductAccountingForm = 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.recurringDepositProductAccountingForm.get('accountingRule').valueChanges.subscribe((accountingRule: any) => { - if (accountingRule === 2 || accountingRule === 3) { - this.recurringDepositProductAccountingForm.addControl( - 'savingsReferenceAccountId', - new UntypedFormControl('', Validators.required) - ); - this.recurringDepositProductAccountingForm.addControl( - 'savingsControlAccountId', - new UntypedFormControl('', Validators.required) - ); - this.recurringDepositProductAccountingForm.addControl( - 'transfersInSuspenseAccountId', - new UntypedFormControl('', Validators.required) - ); - this.recurringDepositProductAccountingForm.addControl( - 'interestOnSavingsAccountId', - new UntypedFormControl('', Validators.required) - ); - this.recurringDepositProductAccountingForm.addControl( - 'incomeFromFeeAccountId', - new UntypedFormControl('', Validators.required) - ); - this.recurringDepositProductAccountingForm.addControl( - 'incomeFromPenaltyAccountId', - new UntypedFormControl('', Validators.required) - ); - this.recurringDepositProductAccountingForm.addControl('advancedAccountingRules', new UntypedFormControl(false)); - - if (accountingRule === 3) { - this.recurringDepositProductAccountingForm.addControl( - 'feesReceivableAccountId', - new UntypedFormControl('', Validators.required) - ); - this.recurringDepositProductAccountingForm.addControl( - 'penaltiesReceivableAccountId', - new UntypedFormControl('', Validators.required) - ); - this.recurringDepositProductAccountingForm.addControl( - 'interestPayableAccountId', - new UntypedFormControl('', Validators.required) - ); - } - - this.recurringDepositProductAccountingForm - .get('advancedAccountingRules') - .valueChanges.subscribe((advancedAccountingRules: boolean) => { - if (advancedAccountingRules) { - this.recurringDepositProductAccountingForm.addControl( - 'paymentChannelToFundSourceMappings', - this.formBuilder.array([]) - ); - this.recurringDepositProductAccountingForm.addControl( - 'feeToIncomeAccountMappings', - this.formBuilder.array([]) - ); - this.recurringDepositProductAccountingForm.addControl( - 'penaltyToIncomeAccountMappings', - this.formBuilder.array([]) - ); - } else { - this.recurringDepositProductAccountingForm.removeControl('paymentChannelToFundSourceMappings'); - this.recurringDepositProductAccountingForm.removeControl('feeToIncomeAccountMappings'); - this.recurringDepositProductAccountingForm.removeControl('penaltyToIncomeAccountMappings'); - } - }); - } else { - this.recurringDepositProductAccountingForm.removeControl('savingsReferenceAccountId'); - this.recurringDepositProductAccountingForm.removeControl('overdraftPortfolioControlId'); - this.recurringDepositProductAccountingForm.removeControl('savingsControlAccountId'); - this.recurringDepositProductAccountingForm.removeControl('transfersInSuspenseAccountId'); - this.recurringDepositProductAccountingForm.removeControl('interestOnSavingsAccountId'); - this.recurringDepositProductAccountingForm.removeControl('writeOffAccountId'); - this.recurringDepositProductAccountingForm.removeControl('incomeFromFeeAccountId'); - this.recurringDepositProductAccountingForm.removeControl('incomeFromPenaltyAccountId'); - this.recurringDepositProductAccountingForm.removeControl('incomeFromInterestId'); - this.recurringDepositProductAccountingForm.removeControl('advancedAccountingRules'); - this.recurringDepositProductAccountingForm.removeControl('escheatLiabilityId'); - this.recurringDepositProductAccountingForm.removeControl('feesReceivableAccountId'); - this.recurringDepositProductAccountingForm.removeControl('penaltiesReceivableAccountId'); - this.recurringDepositProductAccountingForm.removeControl('interestPayableAccountId'); - } + private setConditionalControls() { + this.recurringDepositProductAccountingForm.get('accountingRule')?.valueChanges.subscribe((rule: number) => { + this.syncAccountingControls(rule); }); } - get paymentChannelToFundSourceMappings(): UntypedFormArray { - return this.recurringDepositProductAccountingForm.get('paymentChannelToFundSourceMappings') as UntypedFormArray; - } - - get feeToIncomeAccountMappings(): UntypedFormArray { - return this.recurringDepositProductAccountingForm.get('feeToIncomeAccountMappings') as UntypedFormArray; - } - - get penaltyToIncomeAccountMappings(): UntypedFormArray { - return this.recurringDepositProductAccountingForm.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.recurringDepositProductAccountingForm.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` } - }); - dialogRef.afterClosed().subscribe((response: any) => { - if (response.delete) { - formArray.removeAt(index); - } - }); - } - - getData(formType: string, values?: any) { - switch (formType) { - case 'PaymentFundSource': - return { - title: this.translateService.instant('labels.heading.Configure Fund Sources for Payment Channels'), - formfields: this.getPaymentFundSourceFormfields(values) - }; - case 'FeesIncome': - return { - title: this.translateService.instant('labels.heading.Map Fees to Specific Income Accounts'), - formfields: this.getFeesIncomeFormfields(values) - }; - case 'PenaltyIncome': - return { - title: this.translateService.instant('labels.heading.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: this.translateService.instant('labels.inputs.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: this.translateService.instant('labels.inputs.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: this.translateService.instant('labels.inputs.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: this.translateService.instant('labels.inputs.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.recurringDepositProductAccountingForm.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.recurringDepositProductAccountingForm.contains(name)) { + this.recurringDepositProductAccountingForm.addControl( + name, + new UntypedFormControl('', required ? Validators.required : undefined) + ); + } else if (required) { + this.recurringDepositProductAccountingForm.get(name)?.setValidators(Validators.required); + this.recurringDepositProductAccountingForm.get(name)?.updateValueAndValidity({ emitEvent: false }); + } + }; + const removeControl = (name: string) => { + if (this.recurringDepositProductAccountingForm.contains(name)) { + this.recurringDepositProductAccountingForm.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 recurringDepositProductAccounting() { - return this.recurringDepositProductAccountingForm.value; + const { + assetsOtherGlMappings, + liabilitiesOtherGlMappings, + expensesOtherGlMappings, + incomeOtherGlMappings, + ...accounting + } = this.recurringDepositProductAccountingForm.value; + const otherGlMappings = this.getOtherGlMappings(); + return { ...accounting, ...(otherGlMappings ? { otherGlMappings } : {}) }; } isCashOrAccrualAccounting(): boolean { diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-charges-step/recurring-deposit-product-charges-step.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-charges-step/recurring-deposit-product-charges-step.component.html index dd7563ee5..b2c701878 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-charges-step/recurring-deposit-product-charges-step.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-charges-step/recurring-deposit-product-charges-step.component.html @@ -23,7 +23,14 @@ } - diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-charges-step/recurring-deposit-product-charges-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-charges-step/recurring-deposit-product-charges-step.component.scss index 84a9cf676..661815e4c 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-charges-step/recurring-deposit-product-charges-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-charges-step/recurring-deposit-product-charges-step.component.scss @@ -20,8 +20,17 @@ background: #f8f9fa; border-bottom: 1px solid #e8eaed; - i { font-size: 14px; color: #0ea5e9; } - h3 { margin: 0; font-size: 14px; font-weight: 600; color: #202124; } + i { + font-size: 14px; + color: #0ea5e9; + } + + h3 { + margin: 0; + font-size: 14px; + font-weight: 600; + color: #202124; + } } &__body { @@ -35,7 +44,10 @@ gap: 16px; flex-wrap: wrap; - &__select { flex: 1; min-width: 200px; } + &__select { + flex: 1; + min-width: 200px; + } } .rdpch-add-btn { @@ -51,5 +63,7 @@ overflow: hidden; margin-top: 12px; - table { width: 100%; } + table { + width: 100%; + } } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.html index baeea3de9..d21e17094 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.html @@ -9,71 +9,103 @@
- -

{{ '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 recurring deposit installment in multiples of' | translate }} - -
- - @if (recurringDepositProductCurrencyForm.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/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.scss index c0b63cb17..c2bcdef6b 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-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; + .rdpc-section { - border: 1px solid #e2e8f0; + border: 1px solid $border; border-radius: 10px; overflow: hidden; background: #fff; @@ -21,46 +27,77 @@ 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; } } } +.rdpc-body { + background: #fff; +} + .rdpc-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 16px; padding: 16px 24px; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } } .rdpc-field { width: 100%; - - &--full { - grid-column: 1 / -1; - } } -.rdpc-checkbox-row { +.rdpc-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 (max-width: 600px) { - .rdpc-grid { - grid-template-columns: 1fr; + @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/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.ts b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.ts index 50412b965..7beb38296 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.ts +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.ts @@ -6,12 +6,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component, OnInit, Input, inject, DestroyRef } from '@angular/core'; +import { Component, OnInit, Input, inject } from '@angular/core'; import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; import { MatTooltip } from '@angular/material/tooltip'; -import { MatCheckbox } from '@angular/material/checkbox'; +import { MatRadioGroup, MatRadioButton } from '@angular/material/radio'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'mifosx-recurring-deposit-product-currency-step', @@ -20,18 +19,23 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; imports: [ ...STANDALONE_SHARED_IMPORTS, MatTooltip, - MatCheckbox + MatRadioGroup, + MatRadioButton ] }) export class RecurringDepositProductCurrencyStepComponent implements OnInit { private formBuilder = inject(UntypedFormBuilder); - private destroyRef = inject(DestroyRef); @Input() recurringDepositProductsTemplate: any; recurringDepositProductCurrencyForm: UntypedFormGroup; currencyData: any; + decimalPlacesData = [ + 2, + 0, + 3 + ]; constructor() { this.createrecurringDepositProductCurrencyForm(); @@ -41,24 +45,17 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit { this.currencyData = this.recurringDepositProductsTemplate.currencyOptions; this.recurringDepositProductCurrencyForm.patchValue({ - currencyCode: this.recurringDepositProductsTemplate.currency?.code || this.currencyData[0].code, - digitsAfterDecimal: this.recurringDepositProductsTemplate.digitsAfterDecimal ?? '', - setMultiples: !!this.recurringDepositProductsTemplate.inMultiplesOf, - inMultiplesOf: this.recurringDepositProductsTemplate.inMultiplesOf ?? '' + currencyCode: this.recurringDepositProductsTemplate.currency?.code || this.currencyData?.[0]?.code || '', + digitsAfterDecimal: + this.recurringDepositProductsTemplate.digitsAfterDecimal ?? + this.recurringDepositProductsTemplate.currency?.decimalPlaces ?? + 2, + dormancyAlert: this.recurringDepositProductsTemplate.dormancyAlert ?? false, + balanceSwapOption: this.recurringDepositProductsTemplate.balanceSwapOption ?? false, + msme: this.recurringDepositProductsTemplate.msme ?? false, + nominationFacility: this.recurringDepositProductsTemplate.nominationFacility ?? false, + autoDebitFacility: this.recurringDepositProductsTemplate.autoDebitFacility ?? false }); - - this.setupConditionalValidation(); - - // Apply initial validators based on the patched setMultiples value - const inMultiplesOfControl = this.recurringDepositProductCurrencyForm.get('inMultiplesOf'); - const setMultiplesControl = this.recurringDepositProductCurrencyForm.get('setMultiples'); - if (setMultiplesControl?.value) { - inMultiplesOfControl?.setValidators([ - Validators.required, - Validators.min(1) - ]); - inMultiplesOfControl?.updateValueAndValidity(); - } } createrecurringDepositProductCurrencyForm() { @@ -68,46 +65,30 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit { Validators.required ], digitsAfterDecimal: [ - '', + 2, [ Validators.required, Validators.min(0) ] ], - setMultiples: [false], - inMultiplesOf: [''] - }); - } - - setupConditionalValidation() { - const inMultiplesOfControl = this.recurringDepositProductCurrencyForm.get('inMultiplesOf'); - const setMultiplesControl = this.recurringDepositProductCurrencyForm.get('setMultiples'); - - setMultiplesControl?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((checked) => { - if (checked) { - inMultiplesOfControl?.setValidators([ - Validators.required, - Validators.min(1) - ]); - } else { - inMultiplesOfControl?.clearValidators(); - inMultiplesOfControl?.setValue(''); - } - inMultiplesOfControl?.updateValueAndValidity(); + dormancyAlert: [false], + balanceSwapOption: [false], + msme: [false], + nominationFacility: [false], + autoDebitFacility: [false] }); } get recurringDepositProductCurrency() { const formValue = this.recurringDepositProductCurrencyForm.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/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.html index d2d651a6e..7e5f20a46 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.html @@ -10,7 +10,7 @@
-

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

+

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

@@ -57,13 +57,67 @@ cdkTextareaAutosize cdkAutosizeMinRows="2" > - @if (recurringDepositProductDetailsForm.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 }} + } + + + + + Product Code + + + + + Effective To + + + + + + + End Date + + + + + + + Introducer Required + + @for (option of yesNoOptions; track option.value) { + {{ option.label }} + } + + + +
+ Share Linking Enabled +
+
+
diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.scss index 543151363..fd6cd1cb3 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-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; + .rdpd-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; } } } @@ -43,6 +49,22 @@ grid-template-columns: repeat(2, 1fr); gap: 4px 16px; padding: 16px 24px; + + @media (width <= 600px) { + grid-template-columns: 1fr; + } +} + +.rdpd-grid--classification { + grid-template-columns: repeat(3, 1fr); + + @media (width <= 900px) { + grid-template-columns: repeat(2, 1fr); + } + + @media (width <= 600px) { + grid-template-columns: 1fr; + } } .rdpd-field { @@ -53,8 +75,8 @@ } } -@media (max-width: 600px) { - .rdpd-grid { - grid-template-columns: 1fr; - } +.rdpd-checkbox-row { + display: flex; + align-items: center; + min-height: 56px; } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.ts b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.ts index 1aaa3dac7..3f2cd558c 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.ts +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.ts @@ -6,10 +6,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component, OnInit, Input, inject } from '@angular/core'; +import { Component, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core'; import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; import { MatTooltip } from '@angular/material/tooltip'; import { CdkTextareaAutosize } from '@angular/cdk/text-field'; +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,24 +24,34 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; CdkTextareaAutosize ] }) -export class RecurringDepositProductDetailsStepComponent implements OnInit { +export class RecurringDepositProductDetailsStepComponent implements OnInit, OnChanges { private formBuilder = inject(UntypedFormBuilder); + private dateUtils = inject(Dates); + private settingsService = inject(SettingsService); @Input() recurringDepositProductsTemplate: any; recurringDepositProductDetailsForm: UntypedFormGroup; + accountTypeData: string[] = []; + customerSegmentData: string[] = []; + yesNoOptions: Array<{ value: boolean; label: string }> = [ + { value: true, label: 'Yes' }, + { value: false, label: 'No' } + ]; constructor() { this.createrecurringDepositProductDetailsForm(); } ngOnInit() { - if (!(this.recurringDepositProductsTemplate === undefined) && this.recurringDepositProductsTemplate.id) { - this.recurringDepositProductDetailsForm.patchValue({ - name: this.recurringDepositProductsTemplate.name, - shortName: this.recurringDepositProductsTemplate.shortName, - description: this.recurringDepositProductsTemplate.description - }); + if (this.recurringDepositProductsTemplate) { + this.applyTemplate(this.recurringDepositProductsTemplate); + } + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.recurringDepositProductsTemplate?.currentValue) { + this.applyTemplate(changes.recurringDepositProductsTemplate.currentValue); } } @@ -53,14 +65,70 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit { '', Validators.required ], - description: [ - '', + description: [''], + accountType: [ + null, Validators.required - ] + ], + customerSegment: [null], + productCode: [''], + effectiveTo: [''], + endDate: [''], + introducerRequired: [false], + shareLinkingEnabled: [false] + }); + } + + private getOptionNames(options: any[]): string[] { + if (!Array.isArray(options)) { + return []; + } + return options + .map((option) => option?.name ?? option?.value ?? option) + .filter((value) => typeof value === 'string' && value.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); + } + + private applyTemplate(template: any) { + this.accountTypeData = this.getOptionNames(template.accountTypeOptions); + this.customerSegmentData = this.getOptionNames(template.customerSegmentOptions); + + const isEditing = !!template.id; + this.recurringDepositProductDetailsForm.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, + productCode: template.productCode ?? '', + effectiveTo: this.parseDate(template.effectiveTo), + endDate: this.parseDate(template.endDate), + introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes', + shareLinkingEnabled: !!template.shareLinkingEnabled }); } get recurringDepositProductDetails() { - return this.recurringDepositProductDetailsForm.value; + const formValue = { ...this.recurringDepositProductDetailsForm.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/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component.html index 1f75f427d..df813724b 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component.html @@ -78,7 +78,13 @@ {{ 'labels.inputs.End Date' | translate }} - + @@ -91,7 +97,13 @@
- @@ -105,11 +117,7 @@ @if (chart.value.chartSlabs.length !== 0) {
- +
{{ 'labels.inputs.Period' | translate }} @@ -184,7 +192,9 @@ >

@@ -205,25 +215,33 @@
- + - + - + - + - + diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component.scss index 09b447b0c..e72e64e10 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component.scss @@ -143,7 +143,7 @@ margin-bottom: 12px; } -@media (max-width: 600px) { +@media (width <= 600px) { .rdpi-grid { grid-template-columns: 1fr; } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.html index b8d5f706e..3c9a72fa5 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.html @@ -300,7 +300,9 @@ >

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

@@ -319,7 +321,9 @@ -
+ - + - + - +
{{ 'labels.inputs.Entity Type' | translate }} + {{ 'labels.inputs.Entity Type' | translate }} + {{ incentive.entityType | find: entityTypeData : 'id' : 'value' }} {{ 'labels.inputs.Attribute Name' | translate }} + {{ 'labels.inputs.Attribute Name' | translate }} + {{ incentive.attributeName | find: attributeNameData : 'id' : 'value' }} {{ 'labels.inputs.Condition Type' | translate }} + {{ 'labels.inputs.Condition Type' | translate }} + {{ incentive.conditionType | find: conditionTypeData : 'id' : 'value' }} {{ 'labels.inputs.Attribute Value' | translate }} + {{ 'labels.inputs.Attribute Value' | translate }} + @switch (incentive.attributeName) { @case (2) { @@ -233,7 +251,9 @@ {{ incentive.attributeValue }} } @case (4) { - {{ incentive.attributeValue | find: clientTypeData : 'id' : 'name' }} + {{ + incentive.attributeValue | find: clientTypeData : 'id' : 'name' + }} } @case (5) { {{ @@ -244,7 +264,9 @@ {{ 'labels.inputs.Incentive Type' | translate }} + {{ 'labels.inputs.Incentive Type' | translate }} + {{ incentive.incentiveType | find: incentiveTypeData : 'id' : 'value' }} {{ 'labels.inputs.Attribute Name' | translate }} + {{ 'labels.inputs.Attribute Name' | translate }} + {{ incentive.attributeName @@ -331,7 +335,9 @@ {{ 'labels.inputs.Condition Type' | translate }} + {{ 'labels.inputs.Condition Type' | translate }} + {{ incentive.conditionType @@ -343,13 +349,18 @@ {{ 'labels.inputs.Attribute Value' | translate }} + {{ 'labels.inputs.Attribute Value' | translate }} + @switch (incentive.attributeName) { @case (2) { {{ incentive.attributeValue - | find: recurringDepositProductsTemplate.chartTemplate.genderOptions : 'id' : 'name' + | find + : recurringDepositProductsTemplate.chartTemplate.genderOptions + : 'id' + : 'name' }} } @case (3) { @@ -377,7 +388,9 @@ {{ 'labels.inputs.Incentive Type' | translate }} + {{ 'labels.inputs.Incentive Type' | translate }} + {{ incentive.incentiveType @@ -468,7 +481,10 @@
@if (!isNoneAccounting()) { - + } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.scss index 375a82861..b00fc9c5c 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.scss @@ -12,7 +12,9 @@ overflow: hidden; background: #fff; - & + & { margin-top: 16px; } + & + & { + margin-top: 16px; + } &__header { display: flex; @@ -22,11 +24,22 @@ background: #f8f9fa; border-bottom: 1px solid #e8eaed; - i { font-size: 14px; color: #0ea5e9; } - h3 { margin: 0; font-size: 14px; font-weight: 600; color: #202124; } + i { + font-size: 14px; + color: #0ea5e9; + } + + h3 { + margin: 0; + font-size: 14px; + font-weight: 600; + color: #202124; + } } - &__body { padding: 16px 24px; } + &__body { + padding: 16px 24px; + } } .rdpp-product-name { @@ -41,7 +54,9 @@ padding: 6px 0; border-bottom: 1px solid #f1f3f4; - &:last-child { border-bottom: none; } + &:last-child { + border-bottom: none; + } } .rdpp-label { @@ -63,9 +78,13 @@ overflow: hidden; margin-top: 8px; - table { width: 100%; } + table { + width: 100%; + } - & + & { margin-top: 12px; } + & + & { + margin-top: 12px; + } } .rdpp-sub-heading { @@ -108,6 +127,9 @@ table { tr.incentives-row { height: 0; - > td { padding: 0; } + + > td { + padding: 0; + } } } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.html index ff0f2d5d5..ca817dbd2 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.html @@ -7,215 +7,117 @@ -->
+
- -

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

+ + Premature option
-
-
- - {{ 'labels.inputs.Is Mandatory Deposit' | translate }} - -
- -
- - {{ 'labels.inputs.Adjust advance payments toward future installments' | translate }} - -
- -
- - {{ 'labels.inputs.Allow Withdrawals' | translate }} - +
+
+ Loan Facility + Premature Withdrawal option + Tenure Flexibility + Lien Marked + Apply Withdrawal Fee for Transfers
+
- -

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

+ + {{ 'labels.inputs.Lock-in Period' | translate }}
-
- - {{ 'labels.inputs.Frequency' | translate }} - - - - - {{ 'labels.inputs.Type' | translate }} - - @for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) { - - {{ lockinPeriodFrequencyType.value }} - - } - - -
-
- -
-
- -

{{ 'labels.inputs.Minimum Deposit Term' | 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 (recurringDepositProductSettingsForm.value.enableLockinPeriod) { +
+ + Frequency* + + - - {{ 'labels.inputs.Period' | translate }} - - @for (preClosurePenalInterestOnType of preClosurePenalInterestOnTypeData; track preClosurePenalInterestOnType) { - - {{ preClosurePenalInterestOnType.value }} - - } - - -
-
- -
-
- -

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

-
-
-
- - {{ 'labels.inputs.Is Withhold Tax Applicable' | translate }} - -
- - @if (recurringDepositProductSettingsForm.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 }} - - + + Type* + + @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/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.scss index d94100dfe..53e82e31e 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-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; + .rdps-section { - border: 1px solid #e2e8f0; + border: 1px solid $border; border-radius: 10px; overflow: hidden; background: #fff; @@ -21,28 +27,37 @@ 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(.rdps-section__badge) { margin: 0; font-size: 14px; font-weight: 600; - color: #202124; + color: $gray-900; } } } -.rdps-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 4px 16px; - padding: 16px 24px; +.rdps-section__badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 6px; + background: #eff6ff; + font-size: 14px; +} + +.rdps-body { + background: #fff; + padding: 18px 24px 24px; } .rdps-field { @@ -53,14 +68,98 @@ } } -.rdps-checkbox-row { +.rdps-premature-row { display: flex; align-items: center; - padding: 4px 0; + gap: 24px; + flex-wrap: wrap; + + mat-checkbox { + font-size: 14px; + color: $gray-900; + } } -@media (max-width: 600px) { - .rdps-grid { +.rdps-layout { + display: grid; + gap: 16px; +} + +.rdps-layout--additional { + grid-template-columns: repeat(2, minmax(0, 1fr)); + + @media (width <= 1100px) { grid-template-columns: 1fr; } } + +.rdps-toggle { + display: flex; + align-items: center; + min-height: 40px; + + mat-checkbox { + font-size: 14px; + color: $gray-900; + } + + &--full { + min-height: auto; + margin-bottom: 16px; + } +} + +.rdps-body--list { + padding: 0 24px; +} + +.rdps-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; + } +} + +.rdps-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; +} + +.rdps-lockin-fields { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; + + &--single { + grid-template-columns: minmax(0, 1fr); + max-width: 50%; + } + + @media (width <= 640px) { + grid-template-columns: 1fr; + + &--single { + max-width: 100%; + } + } +} diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.ts b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.ts index a9b559218..74c8ca844 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.ts +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-settings-step/recurring-deposit-product-settings-step.component.ts @@ -7,15 +7,8 @@ */ import { Component, OnInit, Input, inject } from '@angular/core'; -import { - UntypedFormGroup, - UntypedFormBuilder, - UntypedFormControl, - Validators, - ReactiveFormsModule -} from '@angular/forms'; +import { UntypedFormGroup, UntypedFormBuilder, ReactiveFormsModule } from '@angular/forms'; import { MatCheckbox } from '@angular/material/checkbox'; -import { MatTooltip } from '@angular/material/tooltip'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @Component({ @@ -24,8 +17,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; styleUrls: ['./recurring-deposit-product-settings-step.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, - MatCheckbox, - MatTooltip + MatCheckbox ] }) export class RecurringDepositProductSettingsStepComponent implements OnInit { @@ -36,106 +28,68 @@ export class RecurringDepositProductSettingsStepComponent implements OnInit { recurringDepositProductSettingsForm: UntypedFormGroup; lockinPeriodFrequencyTypeData: any; - periodFrequencyTypeData: any; - preClosurePenalInterestOnTypeData: any; - taxGroupData: any; constructor() { this.createrecurringDepositProductSettingsForm(); - this.setConditionalControls(); } ngOnInit() { this.lockinPeriodFrequencyTypeData = this.recurringDepositProductsTemplate.lockinPeriodFrequencyTypeOptions; - this.periodFrequencyTypeData = this.recurringDepositProductsTemplate.periodFrequencyTypeOptions.slice(0, -1); - this.preClosurePenalInterestOnTypeData = this.recurringDepositProductsTemplate.preClosurePenalInterestOnTypeOptions; - this.taxGroupData = this.recurringDepositProductsTemplate.taxGroupOptions; - if (!(this.recurringDepositProductsTemplate === undefined) && this.recurringDepositProductsTemplate.id) { + if (this.recurringDepositProductsTemplate?.id) { this.recurringDepositProductSettingsForm.patchValue({ - isMandatoryDeposit: this.recurringDepositProductsTemplate.isMandatoryDeposit, - adjustAdvanceTowardsFuturePayments: this.recurringDepositProductsTemplate.adjustAdvanceTowardsFuturePayments, - allowWithdrawal: this.recurringDepositProductsTemplate.allowWithdrawal, - lockinPeriodFrequency: this.recurringDepositProductsTemplate.lockinPeriodFrequency, - lockinPeriodFrequencyType: this.recurringDepositProductsTemplate.lockinPeriodFrequencyType - ? this.recurringDepositProductsTemplate.lockinPeriodFrequencyType.id - : '', - minDepositTerm: this.recurringDepositProductsTemplate.minDepositTerm, - minDepositTermTypeId: this.recurringDepositProductsTemplate.minDepositTermType - ? this.recurringDepositProductsTemplate.minDepositTermType.id - : '', - inMultiplesOfDepositTerm: this.recurringDepositProductsTemplate.inMultiplesOfDepositTerm, - inMultiplesOfDepositTermTypeId: this.recurringDepositProductsTemplate.inMultiplesOfDepositTermType - ? this.recurringDepositProductsTemplate.inMultiplesOfDepositTerm.id - : '', - maxDepositTerm: this.recurringDepositProductsTemplate.maxDepositTerm, - maxDepositTermTypeId: this.recurringDepositProductsTemplate.maxDepositTermType - ? this.recurringDepositProductsTemplate.minDepositTermType.id - : '', - preClosurePenalApplicable: this.recurringDepositProductsTemplate.preClosurePenalApplicable, - preClosurePenalInterest: this.recurringDepositProductsTemplate.preClosurePenalInterest, - preClosurePenalInterestOnTypeId: this.recurringDepositProductsTemplate.preClosurePenalInterestOnType - ? this.recurringDepositProductsTemplate.preClosurePenalInterestOnType.id - : '', - withHoldTax: this.recurringDepositProductsTemplate.withHoldTax - }); - } - - if (this.recurringDepositProductsTemplate.withHoldTax) { - this.recurringDepositProductSettingsForm.patchValue({ - taxGroupId: this.recurringDepositProductsTemplate.taxGroup - ? this.recurringDepositProductsTemplate.taxGroup.id - : '' + loanFacility: this.recurringDepositProductsTemplate.loanFacility ?? false, + prematureWithdrawalOption: this.recurringDepositProductsTemplate.prematureWithdrawalOption ?? false, + tenureFlexibility: this.recurringDepositProductsTemplate.tenureFlexibility ?? false, + lienMarked: this.recurringDepositProductsTemplate.lienMarked ?? false, + withdrawalFeeForTransfers: this.recurringDepositProductsTemplate.withdrawalFeeForTransfers ?? false, + enableLockinPeriod: this.recurringDepositProductsTemplate.enableLockinPeriod ?? false, + lockinPeriodFrequency: this.recurringDepositProductsTemplate.lockinPeriodFrequency ?? '', + lockinPeriodFrequencyType: this.recurringDepositProductsTemplate.lockinPeriodFrequencyType?.id ?? '', + maturityInstructions: this.recurringDepositProductsTemplate.maturityInstructions ?? false, + creditMaturityProceeds: this.recurringDepositProductsTemplate.creditMaturityProceeds ?? false, + penaltyGracePeriodMissedInstallments: + this.recurringDepositProductsTemplate.penaltyGracePeriodMissedInstallments ?? false, + panelRequestApplicable: this.recurringDepositProductsTemplate.panelRequestApplicable ?? false, + noPenaltyNomineeHeirClosure: this.recurringDepositProductsTemplate.noPenaltyNomineeHeirClosure ?? false, + deceasedSettlement: this.recurringDepositProductsTemplate.deceasedSettlement ?? false, + allowDebitTxnAfterDueDate: this.recurringDepositProductsTemplate.allowDebitTxnAfterDueDate ?? false, + accountClosedZeroBalanceInDayClosing: + this.recurringDepositProductsTemplate.accountClosedZeroBalanceInDayClosing ?? false, + minBalanceForInterestCalculation: this.recurringDepositProductsTemplate.minBalanceForInterestCalculation ?? '' }); } } createrecurringDepositProductSettingsForm() { this.recurringDepositProductSettingsForm = this.formBuilder.group({ - isMandatoryDeposit: [false], - adjustAdvanceTowardsFuturePayments: [false], - allowWithdrawal: [false], + loanFacility: [false], + prematureWithdrawalOption: [false], + tenureFlexibility: [false], + lienMarked: [false], + withdrawalFeeForTransfers: [false], + enableLockinPeriod: [false], lockinPeriodFrequency: [''], lockinPeriodFrequencyType: [''], - minDepositTerm: [ - '', - Validators.required - ], - minDepositTermTypeId: [ - '', - Validators.required - ], - inMultiplesOfDepositTerm: [''], - inMultiplesOfDepositTermTypeId: [''], - maxDepositTerm: [''], - maxDepositTermTypeId: [''], - preClosurePenalApplicable: [false], - preClosurePenalInterest: [''], - preClosurePenalInterestOnTypeId: [''], - withHoldTax: [false] - }); - } - - setConditionalControls() { - this.recurringDepositProductSettingsForm.get('withHoldTax').valueChanges.subscribe((withHoldTax: any) => { - if (withHoldTax) { - this.recurringDepositProductSettingsForm.addControl( - 'taxGroupId', - new UntypedFormControl('', Validators.required) - ); - } else { - this.recurringDepositProductSettingsForm.removeControl('taxGroupId'); - } + maturityInstructions: [false], + creditMaturityProceeds: [false], + penaltyGracePeriodMissedInstallments: [false], + panelRequestApplicable: [false], + noPenaltyNomineeHeirClosure: [false], + deceasedSettlement: [false], + allowDebitTxnAfterDueDate: [false], + accountClosedZeroBalanceInDayClosing: [false], + minBalanceForInterestCalculation: [''] }); } get recurringDepositProductSettings() { - const recurringDepositProductSettings = this.recurringDepositProductSettingsForm.value; - for (const key in recurringDepositProductSettings) { - if (recurringDepositProductSettings[key] === '') { - delete recurringDepositProductSettings[key]; + const settings = { ...this.recurringDepositProductSettingsForm.value }; + for (const key in settings) { + if (settings[key] === '') { + delete settings[key]; } } - return recurringDepositProductSettings; + return settings; } } diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.html index b5a55f78e..65358d314 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.html @@ -9,99 +9,148 @@
- -

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

+ + Interest Rate & Posting Terms
-
- - {{ 'labels.inputs.Minimum' | translate }} - - +
+
+ ℹ️ + + Recurring Deposit base rate: + {{ recurringDepositProductTermsForm.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 (recurringDepositProductTermsForm.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 (recurringDepositProductTermsForm.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 (interestCalculationType of interestCalculationTypeOptionData; track interestCalculationType.id) { + + {{ interestCalculationType.value | translateKey: 'catalogs' }} + + } + + + {{ 'labels.inputs.Interest Calculation Type' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + +
+ +
+ + Interest Calculate + + @for (mode of interestCalculationTypeData; track mode.id) { + {{ mode.value }} + } + + + + @if (recurringDepositProductTermsForm.value.interestCalculateSelector === 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/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.scss b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.scss index a520130f8..e5e2608bc 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.scss +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-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; + .rdpt-section { - border: 1px solid #e2e8f0; + border: 1px solid $border; border-radius: 10px; overflow: hidden; background: #fff; @@ -21,23 +27,39 @@ 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 { + h3, + span:not(.rdpt-section__badge) { margin: 0; font-size: 14px; font-weight: 600; - color: #202124; + color: $gray-900; } } } +.rdpt-section__badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 6px; + background: #eff6ff; + font-size: 14px; +} + +.rdpt-body { + background: #fff; +} + .rdpt-grid { display: grid; grid-template-columns: repeat(2, 1fr); @@ -47,6 +69,14 @@ &--three { grid-template-columns: repeat(3, 1fr); } + + @media (width <= 600px) { + grid-template-columns: 1fr; + + &--three { + grid-template-columns: 1fr; + } + } } .rdpt-field { @@ -57,12 +87,66 @@ } } -@media (max-width: 600px) { - .rdpt-grid { - grid-template-columns: 1fr; +.rdpt-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; - &--three { - grid-template-columns: 1fr; - } + &__icon { + font-size: 14px; } } + +.rdpt-footer { + padding: 0 24px 24px; + border-top: 1px solid #f1f5f9; +} + +.rdpt-footer__grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + padding-top: 18px; + + @media (width <= 900px) { + grid-template-columns: 1fr; + } +} + +.rdpt-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; + } +} + +.rdpt-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/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.ts b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.ts index 81b8af313..ccef15fff 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.ts +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-terms-step/recurring-deposit-product-terms-step.component.ts @@ -7,7 +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({ @@ -15,11 +18,14 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; templateUrl: './recurring-deposit-product-terms-step.component.html', styleUrls: ['./recurring-deposit-product-terms-step.component.scss'], imports: [ - ...STANDALONE_SHARED_IMPORTS + ...STANDALONE_SHARED_IMPORTS, + MatTooltip ] }) export class RecurringDepositProductTermsStepComponent implements OnInit { private formBuilder = inject(UntypedFormBuilder); + private dateUtils = inject(Dates); + private settingsService = inject(SettingsService); @Input() recurringDepositProductsTemplate: any; @@ -28,7 +34,13 @@ export class RecurringDepositProductTermsStepComponent 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.createrecurringDepositProductTermsForm(); @@ -38,40 +50,72 @@ export class RecurringDepositProductTermsStepComponent implements OnInit { this.interestCompoundingPeriodTypeData = this.recurringDepositProductsTemplate.interestCompoundingPeriodTypeOptions; this.interestPostingPeriodTypeData = this.recurringDepositProductsTemplate.interestPostingPeriodTypeOptions; this.interestCalculationTypeData = this.recurringDepositProductsTemplate.interestCalculationTypeOptions; + this.interestCalculationTypeOptionData = + this.recurringDepositProductsTemplate.interestCalculationTypeOption || + this.recurringDepositProductsTemplate.interestCalculationTypeOptions; this.interestCalculationDaysInYearTypeData = this.recurringDepositProductsTemplate.interestCalculationDaysInYearTypeOptions; - if (!(this.recurringDepositProductsTemplate === undefined) && this.recurringDepositProductsTemplate.id) { - this.recurringDepositProductTermsForm.patchValue({ - minDepositAmount: this.recurringDepositProductsTemplate.minDepositAmount, - depositAmount: this.recurringDepositProductsTemplate.depositAmount, - maxDepositAmount: this.recurringDepositProductsTemplate.maxDepositAmount - }); - } + this.periodFrequencyData = this.recurringDepositProductsTemplate.periodFrequencyOptions || []; + this.setYesNoOptions(this.recurringDepositProductsTemplate?.yesNoOptions); + + const isEditing = !!this.recurringDepositProductsTemplate.id; + const defaultPeriod = this.periodFrequencyData[0]?.value || 'Monthly'; + this.recurringDepositProductTermsForm.patchValue({ - interestCompoundingPeriodType: this.recurringDepositProductsTemplate.interestCompoundingPeriodType.id, - interestPostingPeriodType: this.recurringDepositProductsTemplate.interestPostingPeriodType.id, - interestCalculationType: this.recurringDepositProductsTemplate.interestCalculationType.id, - interestCalculationDaysInYearType: this.recurringDepositProductsTemplate.interestCalculationDaysInYearType.id + nominalAnnualInterestRate: this.recurringDepositProductsTemplate.nominalAnnualInterestRate ?? 0, + interestCompoundingPeriodType: this.recurringDepositProductsTemplate.interestCompoundingPeriodType?.id, + interestPostingPeriodType: this.recurringDepositProductsTemplate.interestPostingPeriodType?.id, + interestCalculationType: isEditing + ? this.recurringDepositProductsTemplate.interestCalculationTypeOption?.id || + this.interestCalculationTypeOptionData?.[0]?.id || + 1 + : this.interestCalculationTypeOptionData?.[0]?.id || 1, + interestCalculationDaysInYearType: this.recurringDepositProductsTemplate.interestCalculationDaysInYearType?.id, + effectiveDate: this.recurringDepositProductsTemplate.effectiveDate + ? new Date(this.recurringDepositProductsTemplate.effectiveDate) + : '', + interestCalculateSelector: isEditing + ? this.recurringDepositProductsTemplate.interestCalculateSelector || 1 + : this.recurringDepositProductsTemplate.interestCalculationType?.id || + this.interestCalculationTypeData?.[0]?.id || + 1, + averagePeriod: isEditing ? this.recurringDepositProductsTemplate.averagePeriod || defaultPeriod : defaultPeriod, + autoPost: isEditing ? (this.recurringDepositProductsTemplate.autoPost ?? true) : true, + frequencyFixDay: isEditing ? this.recurringDepositProductsTemplate.frequencyFixDay || 30 : 30, + lastInterestDate: this.recurringDepositProductsTemplate.lastInterestDate + ? new Date(this.recurringDepositProductsTemplate.lastInterestDate) + : '' }); + + const currentInterestCalcType = this.recurringDepositProductTermsForm.get('interestCalculationType').value; + if (currentInterestCalcType && currentInterestCalcType > 2) { + this.recurringDepositProductTermsForm.patchValue({ interestCalculationType: 1 }); + } + } + + private setYesNoOptions(options: any[]) { + if (!Array.isArray(options)) return; + const mapped = options + .map((opt: any) => { + const raw = opt?.name ?? opt?.value ?? opt; + const norm = typeof raw === 'string' ? raw.trim().toUpperCase() : raw; + if (norm === 'YES') return { value: true, label: 'Yes' }; + if (norm === 'NO') return { value: false, label: 'No' }; + return null; + }) + .filter(Boolean); + if (mapped.length) this.yesNoOptions = mapped; } createrecurringDepositProductTermsForm() { this.recurringDepositProductTermsForm = this.formBuilder.group({ - minDepositAmount: [ - '', - Validators.min(0) - ], - depositAmount: [ + nominalAnnualInterestRate: [ '', [ Validators.required, Validators.min(0) ] ], - maxDepositAmount: [ - '', - Validators.min(0) - ], interestCompoundingPeriodType: [ '', Validators.required @@ -87,17 +131,38 @@ export class RecurringDepositProductTermsStepComponent implements OnInit { interestCalculationDaysInYearType: [ '', Validators.required - ] + ], + effectiveDate: [''], + interestCalculateSelector: [1], + averagePeriod: ['Monthly'], + autoPost: [true], + frequencyFixDay: [ + 30, + [Validators.min(0)] + ], + lastInterestDate: [''] }); } get recurringDepositProductTerms() { - const recurringDepositProductTerms = this.recurringDepositProductTermsForm.value; - for (const key in recurringDepositProductTerms) { - if (recurringDepositProductTerms[key] === '') { - delete recurringDepositProductTerms[key]; + const formValue = { ...this.recurringDepositProductTermsForm.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) || ''; + } + if (formValue.interestCalculationType && formValue.interestCalculationType > 2) { + formValue.interestCalculationType = 1; + } + + for (const key in formValue) { + if (formValue[key] === '') { + delete formValue[key]; } } - return recurringDepositProductTerms; + return formValue; } } diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.html b/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.html index 64b0a162f..d2db28c86 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.html +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.html @@ -20,21 +20,10 @@

Accounting Rule

- - @for (accountingRule of accountingRuleData; track accountingRule; let i = $index) { - - {{ 'labels.accounting.' + accountingRule | translate }} - - } - -
- - @if (!isCashOrAccrualAccounting()) { -
- Select a cash or accrual accounting rule to configure GL mappings. The default "None" rule does not require - any accounting mappings. +
+ Cash
- } +
@if (isCashOrAccrualAccounting()) { @for (section of accountingSectionConfigs; track section.key) { diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.ts index d148f29c3..88e88a51d 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.ts +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.ts @@ -86,9 +86,14 @@ export class SavingProductAccountingStepComponent implements OnInit { } ngOnInit() { - const mappings = this.savingProductsTemplate?.accountingMappings || {}; + const accountingMappings = this.savingProductsTemplate?.accountingMappings || {}; + // otherGlMappingsMap is at the root of the template, not inside accountingMappings + const mappings = { + ...accountingMappings, + otherGlMappingsMap: this.savingProductsTemplate?.otherGlMappingsMap + }; const accountingMappingOptions = this.savingProductsTemplate?.accountingMappingOptions || {}; - const accountingRuleId = this.savingProductsTemplate?.accountingRule?.id || 1; + const accountingRuleId = 2; // Always Cash this.assetAccountOptions = accountingMappingOptions.assetAccountOptions || []; this.liabilityAccountOptions = accountingMappingOptions.liabilityAccountOptions || []; this.expenseAccountOptions = accountingMappingOptions.expenseAccountOptions || []; @@ -106,7 +111,7 @@ export class SavingProductAccountingStepComponent implements OnInit { createSavingProductAccountingForm() { this.savingProductAccountingForm = this.formBuilder.group({ - accountingRule: [1], + accountingRule: [2], assetsOtherGlMappings: this.formBuilder.array([]), liabilitiesOtherGlMappings: this.formBuilder.array([]), expensesOtherGlMappings: this.formBuilder.array([]), @@ -135,30 +140,20 @@ export class SavingProductAccountingStepComponent implements OnInit { penaltiesReceivableAccountId: mappings.penaltyReceivableAccount?.id || '', interestPayableAccountId: mappings.interestPayableAccount?.id || '', interestReceivableAccountId: mappings.interestReceivableAccount?.id || '', - escheatLiabilityId: mappings.escheatLiabilityAccount?.id || this.liabilityAccountOptions[0]?.id || '' + escheatLiabilityId: mappings.escheatLiabilityAccount?.id || '' }); if (this.isCashOrAccrualAccounting()) { - this.setOtherGlMappings( - 'assetsOtherGlMappings', - mappings.otherGlMappings?.assets || [], - this.assetAccountOptions - ); + // Backend serializes as 'otherGlMappingsMap'; fall back to 'otherGlMappings' for compatibility + const glMappings = mappings.otherGlMappingsMap || mappings.otherGlMappings; + this.setOtherGlMappings('assetsOtherGlMappings', glMappings?.assets || [], this.assetAccountOptions); this.setOtherGlMappings( 'liabilitiesOtherGlMappings', - mappings.otherGlMappings?.liabilities || [], + glMappings?.liabilities || [], this.liabilityAccountOptions ); - this.setOtherGlMappings( - 'expensesOtherGlMappings', - mappings.otherGlMappings?.expenses || [], - this.expenseAccountOptions - ); - this.setOtherGlMappings( - 'incomeOtherGlMappings', - mappings.otherGlMappings?.income || [], - this.incomeAccountOptions - ); + this.setOtherGlMappings('expensesOtherGlMappings', glMappings?.expenses || [], this.expenseAccountOptions); + this.setOtherGlMappings('incomeOtherGlMappings', glMappings?.income || [], this.incomeAccountOptions); } } diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html index 8b101463b..93ae5be7c 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html @@ -83,7 +83,7 @@ Product Code - + diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts index 01a118a8a..2805dc866 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts @@ -82,7 +82,7 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges { Validators.required ], customerSegment: [null], - productCode: ['SB-IND-001'], + productCode: [''], effectiveDate: [''], effectiveTo: [''], endDate: [''], @@ -122,7 +122,7 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges { description: template.description, accountType: template.accountType ?? this.accountTypeData[0] ?? null, customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null, - productCode: template.productCode ?? 'SB-IND-001', + productCode: template.productCode ?? '', effectiveDate: template.effectiveDate ? new Date(template.effectiveDate) : '', effectiveTo: template.effectiveTo ? new Date(template.effectiveTo) : '', endDate: template.endDate ? new Date(template.endDate) : '', diff --git a/src/app/products/saving-products/view-saving-product/saving-product-general-tab/saving-product-general-tab.component.html b/src/app/products/saving-products/view-saving-product/saving-product-general-tab/saving-product-general-tab.component.html index c1a5138fa..ad48a1b1d 100644 --- a/src/app/products/saving-products/view-saving-product/saving-product-general-tab/saving-product-general-tab.component.html +++ b/src/app/products/saving-products/view-saving-product/saving-product-general-tab/saving-product-general-tab.component.html @@ -449,6 +449,77 @@ > + @if (savingProduct.otherGlMappingsMap) { +
+

Other GL Mappings

+ + + @if (savingProduct.otherGlMappingsMap.assets?.length) { +

Assets

+ + + + + + + + + + + +
Account Name{{ row.accountName }}GL Account{{ row.glCode || row.glAccountId }}
+ } + + @if (savingProduct.otherGlMappingsMap.liabilities?.length) { +

Liabilities

+ + + + + + + + + + + +
Account Name{{ row.accountName }}GL Account{{ row.glCode || row.glAccountId }}
+ } + + @if (savingProduct.otherGlMappingsMap.expenses?.length) { +

Expenses

+ + + + + + + + + + + +
Account Name{{ row.accountName }}GL Account{{ row.glCode || row.glAccountId }}
+ } + + @if (savingProduct.otherGlMappingsMap.income?.length) { +

Income

+ + + + + + + + + + + +
Account Name{{ row.accountName }}GL Account{{ row.glCode || row.glAccountId }}
+ } +
+ } + @if ( savingProduct.paymentChannelToFundSourceMappings?.length || savingProduct.feeToIncomeAccountMappings?.length || diff --git a/src/app/products/share-products/create-share-product/create-share-product.component.scss b/src/app/products/share-products/create-share-product/create-share-product.component.scss index 95c13fd11..6f7b6c969 100644 --- a/src/app/products/share-products/create-share-product/create-share-product.component.scss +++ b/src/app/products/share-products/create-share-product/create-share-product.component.scss @@ -27,10 +27,10 @@ $primary: #1353a0; .shp-tabs { background: var(--md-sys-color-surface, #fff); border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); ::ng-deep { - .mat-mdc-tab-header { border-bottom: 1px solid rgba(0, 0, 0, 0.08); } + .mat-mdc-tab-header { border-bottom: 1px solid rgb(0 0 0 / 8%); } .mat-mdc-tab { min-width: 100px; padding: 0 12px; height: 52px; } .mat-mdc-tab-body-wrapper { min-height: 200px; } .mat-mdc-tab-body { overflow: visible !important; } @@ -47,8 +47,8 @@ $primary: #1353a0; align-items: center; margin-top: 20px; padding: 16px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); + border-top: 1px solid rgb(0 0 0 / 8%); - &__cancel { color: rgba(0, 0, 0, 0.6); } + &__cancel { color: rgb(0 0 0 / 60%); } &__right { display: flex; gap: 12px; } } diff --git a/src/app/products/share-products/share-product-stepper/share-product-accounting-step/share-product-accounting-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-accounting-step/share-product-accounting-step.component.scss index 17f1d4ab4..fa8e0ab44 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-accounting-step/share-product-accounting-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-accounting-step/share-product-accounting-step.component.scss @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/share-products/share-product-stepper/share-product-charges-step/share-product-charges-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-charges-step/share-product-charges-step.component.scss index e055abfed..b1b3a5d6c 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-charges-step/share-product-charges-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-charges-step/share-product-charges-step.component.scss @@ -44,7 +44,7 @@ gap: 16px; margin-bottom: 16px; - @media (max-width: 600px) { + @media (width <= 600px) { flex-direction: column; align-items: stretch; } @@ -54,7 +54,7 @@ flex: 1; max-width: 400px; - @media (max-width: 600px) { + @media (width <= 600px) { max-width: 100%; } } diff --git a/src/app/products/share-products/share-product-stepper/share-product-currency-step/share-product-currency-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-currency-step/share-product-currency-step.component.scss index 4418949a8..2b4edfb6d 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-currency-step/share-product-currency-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-currency-step/share-product-currency-step.component.scss @@ -31,7 +31,8 @@ $gray-900: #202124; .shpcr-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + + @media (width <= 600px) { grid-template-columns: 1fr; } } .shpcr-field { diff --git a/src/app/products/share-products/share-product-stepper/share-product-details-step/share-product-details-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-details-step/share-product-details-step.component.scss index b521d4162..a8e05a519 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-details-step/share-product-details-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-details-step/share-product-details-step.component.scss @@ -29,7 +29,8 @@ $gray-900: #202124; .shpd-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + + @media (width <= 600px) { grid-template-columns: 1fr; } } .shpd-field { diff --git a/src/app/products/share-products/share-product-stepper/share-product-preview-step/share-product-preview-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-preview-step/share-product-preview-step.component.scss index e0921d1c9..427ce59ca 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-preview-step/share-product-preview-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-preview-step/share-product-preview-step.component.scss @@ -61,7 +61,7 @@ font-size: 13px; line-height: 1.5; - @media (max-width: 600px) { + @media (width <= 600px) { flex-direction: column; } } diff --git a/src/app/products/share-products/share-product-stepper/share-product-settings-step/share-product-settings-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-settings-step/share-product-settings-step.component.scss index 5122b92b7..5b661cd01 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-settings-step/share-product-settings-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-settings-step/share-product-settings-step.component.scss @@ -44,7 +44,7 @@ gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { + @media (width <= 600px) { grid-template-columns: 1fr; } } diff --git a/src/app/products/share-products/share-product-stepper/share-product-terms-step/share-product-terms-step.component.scss b/src/app/products/share-products/share-product-stepper/share-product-terms-step/share-product-terms-step.component.scss index f5c638d7c..34b0f20d8 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-terms-step/share-product-terms-step.component.scss +++ b/src/app/products/share-products/share-product-stepper/share-product-terms-step/share-product-terms-step.component.scss @@ -29,7 +29,8 @@ $gray-900: #202124; .shpt-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 16px; padding: 16px 24px; - @media (max-width: 600px) { grid-template-columns: 1fr; } + + @media (width <= 600px) { grid-template-columns: 1fr; } } .shpt-field { diff --git a/src/environments/.env.ts b/src/environments/.env.ts index a46de5b84..acdf1e8dc 100644 --- a/src/environments/.env.ts +++ b/src/environments/.env.ts @@ -9,8 +9,8 @@ /* tslint:disable */ export default { 'mifos_x': { - 'version': '260413', - 'hash': '79e455c4' + 'version': '260415', + 'hash': '4679c6f7' }, 'allow_switching_backend_instance': true };