From 8951f15e6fc0d214f9d69ea2cd101ff653803f92 Mon Sep 17 00:00:00 2001 From: Abhay Chawla Date: Sun, 28 Jul 2019 17:40:16 +0530 Subject: [PATCH] feat: add create saving product component (#455) - Add saving product stepper components - Add create saving product component Fixes: #189 --- src/app/products/products-routing.module.ts | 13 +- src/app/products/products.module.ts | 18 +- src/app/products/products.service.ts | 8 + .../create-saving-product.component.html | 99 +++++ .../create-saving-product.component.scss | 4 + .../create-saving-product.component.spec.ts | 25 ++ .../create-saving-product.component.ts | 96 +++++ ...ing-product-accounting-step.component.html | 301 +++++++++++++++ ...ing-product-accounting-step.component.scss | 23 ++ ...-product-accounting-step.component.spec.ts | 25 ++ ...aving-product-accounting-step.component.ts | 236 ++++++++++++ ...saving-product-charges-step.component.html | 74 ++++ ...saving-product-charges-step.component.scss | 11 + ...ing-product-charges-step.component.spec.ts | 25 ++ .../saving-product-charges-step.component.ts | 56 +++ ...aving-product-currency-step.component.html | 46 +++ ...aving-product-currency-step.component.scss | 3 + ...ng-product-currency-step.component.spec.ts | 25 ++ .../saving-product-currency-step.component.ts | 42 +++ ...saving-product-details-step.component.html | 39 ++ ...saving-product-details-step.component.scss | 3 + ...ing-product-details-step.component.spec.ts | 25 ++ .../saving-product-details-step.component.ts | 32 ++ ...saving-product-preview-step.component.html | 347 ++++++++++++++++++ ...saving-product-preview-step.component.scss | 24 ++ ...ing-product-preview-step.component.spec.ts | 25 ++ .../saving-product-preview-step.component.ts | 24 ++ ...aving-product-settings-step.component.html | 135 +++++++ ...aving-product-settings-step.component.scss | 19 + ...ng-product-settings-step.component.spec.ts | 25 ++ .../saving-product-settings-step.component.ts | 84 +++++ .../saving-product-terms-step.component.html | 76 ++++ .../saving-product-terms-step.component.scss | 3 + ...aving-product-terms-step.component.spec.ts | 25 ++ .../saving-product-terms-step.component.ts | 52 +++ .../saving-products-template.resolver.ts | 24 ++ .../saving-products.component.html | 2 +- 37 files changed, 2091 insertions(+), 3 deletions(-) create mode 100644 src/app/products/saving-products/create-saving-product/create-saving-product.component.html create mode 100644 src/app/products/saving-products/create-saving-product/create-saving-product.component.scss create mode 100644 src/app/products/saving-products/create-saving-product/create-saving-product.component.spec.ts create mode 100644 src/app/products/saving-products/create-saving-product/create-saving-product.component.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.html create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.scss create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.spec.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.html create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.scss create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.spec.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.html create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.scss create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.spec.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.scss create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.spec.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.html create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.scss create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.spec.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.html create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.scss create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.spec.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.html create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.scss create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.spec.ts create mode 100644 src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.ts create mode 100644 src/app/products/saving-products/saving-products-template.resolver.ts diff --git a/src/app/products/products-routing.module.ts b/src/app/products/products-routing.module.ts index 89a00d66f..c764456b0 100644 --- a/src/app/products/products-routing.module.ts +++ b/src/app/products/products-routing.module.ts @@ -15,6 +15,7 @@ import { CreateLoanProductComponent } from './loan-products/create-loan-product/ import { ViewLoanProductComponent } from './loan-products/view-loan-product/view-loan-product.component'; import { EditLoanProductComponent } from './loan-products/edit-loan-product/edit-loan-product.component'; import { SavingProductsComponent } from './saving-products/saving-products.component'; +import { CreateSavingProductComponent } from './saving-products/create-saving-product/create-saving-product.component'; import { ManageTaxConfigurationsComponent } from './manage-tax-configurations/manage-tax-configurations.component'; import { RecurringDepositProductsComponent } from './recurring-deposit-products/recurring-deposit-products.component'; import { ChargesComponent } from './charges/charges.component'; @@ -28,6 +29,7 @@ import { LoanProductsTemplateResolver } from './loan-products/loan-products-temp import { LoanProductResolver } from './loan-products/loan-product.resolver'; import { LoanProductAndTemplateResolver } from './loan-products/edit-loan-product/loan-product-and-template.resolver'; import { SavingProductsResolver } from './saving-products/saving-products.resolver'; +import { SavingProductsTemplateResolver } from './saving-products/saving-products-template.resolver'; import { RecurringDepositProductsResolver } from './recurring-deposit-products/recurring-deposit-products.resolver'; import { ChargesResolver } from './charges/charges.resolver'; import { FixedDepositProductsResolver } from './fixed-deposit-products/fixed-deposit-products.resolver'; @@ -97,7 +99,15 @@ const routes: Routes = [ resolve: { savingProducts: SavingProductsResolver } - } + }, + { + path: 'create', + component: CreateSavingProductComponent, + data: { title: extract('Create Saving Product'), breadcrumb: 'Create' }, + resolve: { + savingProductsTemplate: SavingProductsTemplateResolver + } + }, ] }, { @@ -169,6 +179,7 @@ const routes: Routes = [ LoanProductResolver, LoanProductAndTemplateResolver, SavingProductsResolver, + SavingProductsTemplateResolver, RecurringDepositProductsResolver, ChargesResolver, FixedDepositProductsResolver, diff --git a/src/app/products/products.module.ts b/src/app/products/products.module.ts index c3724f22f..9970ac079 100644 --- a/src/app/products/products.module.ts +++ b/src/app/products/products.module.ts @@ -27,6 +27,14 @@ import { ManageTaxComponentsComponent } from './manage-tax-components/manage-tax import { ViewLoanProductComponent } from './loan-products/view-loan-product/view-loan-product.component'; import { EditLoanProductComponent } from './loan-products/edit-loan-product/edit-loan-product.component'; import { SavingProductsComponent } from './saving-products/saving-products.component'; +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 { CreateSavingProductComponent } from './saving-products/create-saving-product/create-saving-product.component'; /** * Products Module @@ -58,7 +66,15 @@ import { SavingProductsComponent } from './saving-products/saving-products.compo ManageTaxComponentsComponent, ViewLoanProductComponent, EditLoanProductComponent, - SavingProductsComponent + SavingProductsComponent, + SavingProductDetailsStepComponent, + SavingProductCurrencyStepComponent, + SavingProductTermsStepComponent, + SavingProductSettingsStepComponent, + SavingProductChargesStepComponent, + SavingProductAccountingStepComponent, + SavingProductPreviewStepComponent, + CreateSavingProductComponent ], entryComponents: [ ], diff --git a/src/app/products/products.service.ts b/src/app/products/products.service.ts index 1de7b31e3..40263d40a 100644 --- a/src/app/products/products.service.ts +++ b/src/app/products/products.service.ts @@ -49,6 +49,14 @@ export class ProductsService { return this.http.get('/savingsproducts'); } + createSavingProduct(savingProduct: string): Observable { + return this.http.post('/savingsproducts', savingProduct); + } + + getSavingProductsTemplate(): Observable { + return this.http.get('/savingsproducts/template'); + } + /** * @returns {Observable} Recurring deposit products data */ diff --git a/src/app/products/saving-products/create-saving-product/create-saving-product.component.html b/src/app/products/saving-products/create-saving-product/create-saving-product.component.html new file mode 100644 index 000000000..9ca121207 --- /dev/null +++ b/src/app/products/saving-products/create-saving-product/create-saving-product.component.html @@ -0,0 +1,99 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + DETAILS + + + + + + + + CURRENCY + + + + + + + + TERMS + + + + + + + + SETTINGS + + + + + + + + CHARGES + + + + + + + + + ACCOUNTING + + + + + + + + + PREVIEW + + + + + + + + +
diff --git a/src/app/products/saving-products/create-saving-product/create-saving-product.component.scss b/src/app/products/saving-products/create-saving-product/create-saving-product.component.scss new file mode 100644 index 000000000..fbc6592ec --- /dev/null +++ b/src/app/products/saving-products/create-saving-product/create-saving-product.component.scss @@ -0,0 +1,4 @@ +fa-icon { + position: relative; + left: 5%; +} diff --git a/src/app/products/saving-products/create-saving-product/create-saving-product.component.spec.ts b/src/app/products/saving-products/create-saving-product/create-saving-product.component.spec.ts new file mode 100644 index 000000000..ec15c3fcb --- /dev/null +++ b/src/app/products/saving-products/create-saving-product/create-saving-product.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CreateSavingProductComponent } from './create-saving-product.component'; + +describe('CreateSavingProductComponent', () => { + let component: CreateSavingProductComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CreateSavingProductComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateSavingProductComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/products/saving-products/create-saving-product/create-saving-product.component.ts b/src/app/products/saving-products/create-saving-product/create-saving-product.component.ts new file mode 100644 index 000000000..801ab0ec8 --- /dev/null +++ b/src/app/products/saving-products/create-saving-product/create-saving-product.component.ts @@ -0,0 +1,96 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { SavingProductDetailsStepComponent } from '../saving-product-stepper/saving-product-details-step/saving-product-details-step.component'; +import { SavingProductCurrencyStepComponent } from '../saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component'; +import { SavingProductTermsStepComponent } from '../saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component'; +import { SavingProductSettingsStepComponent } from '../saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component'; +import { SavingProductChargesStepComponent } from '../saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component'; +import { SavingProductAccountingStepComponent } from '../saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component'; + +import { ProductsService } from 'app/products/products.service'; + +@Component({ + selector: 'mifosx-create-saving-product', + templateUrl: './create-saving-product.component.html', + styleUrls: ['./create-saving-product.component.scss'] +}) +export class CreateSavingProductComponent implements OnInit { + + @ViewChild(SavingProductDetailsStepComponent) savingProductDetailsStep: SavingProductDetailsStepComponent; + @ViewChild(SavingProductCurrencyStepComponent) savingProductCurrencyStep: SavingProductCurrencyStepComponent; + @ViewChild(SavingProductTermsStepComponent) savingProductTermsStep: SavingProductTermsStepComponent; + @ViewChild(SavingProductSettingsStepComponent) savingProductSettingsStep: SavingProductSettingsStepComponent; + @ViewChild(SavingProductChargesStepComponent) savingProductChargesStep: SavingProductChargesStepComponent; + @ViewChild(SavingProductAccountingStepComponent) savingProductAccountingStep: SavingProductAccountingStepComponent; + + savingProductsTemplate: any; + accountingRuleData = ['None', 'Cash']; + + constructor(private route: ActivatedRoute, + private productsService: ProductsService, + private router: Router) { + this.route.data.subscribe((data: { savingProductsTemplate: any }) => { + this.savingProductsTemplate = data.savingProductsTemplate; + }); + } + + ngOnInit() { + } + + get savingProductDetailsForm() { + return this.savingProductDetailsStep.savingProductDetailsForm; + } + + get savingProductCurrencyForm() { + return this.savingProductCurrencyStep.savingProductCurrencyForm; + } + + get savingProductTermsForm() { + return this.savingProductTermsStep.savingProductTermsForm; + } + + get savingProductSettingsForm() { + return this.savingProductSettingsStep.savingProductSettingsForm; + } + + get savingProductAccountingForm() { + return this.savingProductAccountingStep.savingProductAccountingForm; + } + + get savingProductFormValid() { + return ( + this.savingProductDetailsForm.valid && + this.savingProductCurrencyForm.valid && + this.savingProductTermsForm.valid && + this.savingProductSettingsForm.valid && + this.savingProductAccountingForm.valid + ); + } + + get savingProduct() { + return { + ...this.savingProductDetailsStep.savingProductDetails, + ...this.savingProductCurrencyStep.savingProductCurrency, + ...this.savingProductTermsStep.savingProductTerms, + ...this.savingProductSettingsStep.savingProductSettings, + ...this.savingProductChargesStep.savingProductCharges, + ...this.savingProductAccountingStep.savingProductAccounting + }; + } + + submit() { + // TODO: Update once language and date settings are setup + const savingProduct = { + ...this.savingProduct, + charges: this.savingProduct.charges.map((charge: any) => ({ id: charge.id })), + locale: 'en' // locale required for nominalAnnualInterestRate + }; + delete savingProduct.advancedAccountingRules; + this.productsService.createSavingProduct(savingProduct) + .subscribe((response: any) => { + this.router.navigate(['../', response.resourceId], { relativeTo: this.route }); + }); + } + +} 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 new file mode 100644 index 000000000..94ca1f957 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.html @@ -0,0 +1,301 @@ +
+ +
+ + + + {{ accountingRule }} + + + + + +
+ +

Assets

+ + + Saving reference + + + {{ assetAccount.name }} + + + + Saving reference is required + + + + + Overdraft portfolio + + + {{ assetAccount.name }} + + + + Overdraft portfolio is required + + + + + +

Liabilities

+ + + Saving control + + + {{ liabilityAccount.name }} + + + + Saving control is required + + + + + Savings transfers in suspense + + + {{ liabilityAccount.name }} + + + + Savings transfers in suspense is required + + + + + Escheat liability + + + {{ liabilityAccount.name }} + + + + Escheat liability is required + + + + + +

Expenses

+ + + Interest on savings + + + {{ expenseAccount.name }} + + + + Interest on savings is required + + + + + Write-off + + + {{ expenseAccount.name }} + + + + Write-off is required + + + + + +

Income

+ + + Income from fees + + + {{ incomeAccount.name }} + + + + Income from fees Repayments is required + + + + + Income from penalties + + + {{ incomeAccount.name }} + + + + Income from penalties is required + + + + + Overdraft Interest Income + + + {{ incomeAccount.name }} + + + + Overdraft Interest Income is required + + + + + +

Advanced Accounting Rules

+ + + +
+ +

Configure Fund Sources for Payment Channels

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
Payment Type + {{ paymentFundSource.paymentTypeId | find:paymentTypeData:'id':'name' }} + Fund Source + {{ paymentFundSource.fundSourceAccountId | find:assetAccountData:'id':'name' }} + Actions + + +
+ +

Map Fees to Specific Income Accounts

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
Fees + {{ feesIncome.chargeId | find:chargeData:'id':'name' }} + Income Account + {{ feesIncome.incomeAccountId | find:incomeAccountData:'id':'name' }} + Actions + + +
+ +

Map Penalties to Specific Income Accounts

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
Penalty + {{ penaltyIncome.chargeId | find:penaltyData:'id':'name' }} + Income Account + {{ penaltyIncome.incomeAccountId | find:incomeAccountData:'id':'name' }} + Actions + + +
+ +
+ +
+ +
+ +
+ + +
+ +
diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.scss new file mode 100644 index 000000000..e9e5641d0 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.scss @@ -0,0 +1,23 @@ +table { + width: 100%; +} + +.mat-elevation-z1 { + margin: 1em 0 1.5em 0; +} + +h3, h4 { + font-weight: 500; +} + +mat-radio-group, h4 { + margin: 1em 0; +} + +mat-divider { + margin: 1em 0 2em 0; +} + +.margin-t { + margin-top: 1em; +} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.spec.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.spec.ts new file mode 100644 index 000000000..a66239f20 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SavingProductAccountingStepComponent } from './saving-product-accounting-step.component'; + +describe('SavingProductAccountingStepComponent', () => { + let component: SavingProductAccountingStepComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SavingProductAccountingStepComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SavingProductAccountingStepComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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 new file mode 100644 index 000000000..293bc30a4 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component.ts @@ -0,0 +1,236 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { FormGroup, FormBuilder, FormArray, Validators, FormControl } from '@angular/forms'; +import { MatDialog } from '@angular/material'; + +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'; + +@Component({ + selector: 'mifosx-saving-product-accounting-step', + templateUrl: './saving-product-accounting-step.component.html', + styleUrls: ['./saving-product-accounting-step.component.scss'] +}) +export class SavingProductAccountingStepComponent implements OnInit { + + @Input() savingProductsTemplate: any; + @Input() accountingRuleData: any; + @Input() isDormancyTrackingActive: FormControl; + @Input() savingProductFormValid: boolean; + + savingProductAccountingForm: FormGroup; + + chargeData: any; + penaltyData: any; + paymentTypeData: any; + assetAccountData: any; + incomeAccountData: any; + expenseAccountData: any; + liabilityAccountData: any; + incomeAndLiabilityAccountData: any; + + paymentFundSourceDisplayedColumns: string[] = ['paymentTypeId', 'fundSourceAccountId', 'actions']; + feesPenaltyIncomeDisplayedColumns: string[] = ['chargeId', 'incomeAccountId', 'actions']; + + constructor(private formBuilder: FormBuilder, + public dialog: MatDialog) { + this.createsavingProductAccountingForm(); + this.setConditionalControls(); + } + + ngOnInit() { + this.chargeData = this.savingProductsTemplate.chargeOptions || []; + this.penaltyData = this.savingProductsTemplate.penaltyOptions || []; + this.paymentTypeData = this.savingProductsTemplate.paymentTypeOptions || []; + this.assetAccountData = this.savingProductsTemplate.accountingMappingOptions.assetAccountOptions || []; + this.incomeAccountData = this.savingProductsTemplate.accountingMappingOptions.incomeAccountOptions || []; + this.expenseAccountData = this.savingProductsTemplate.accountingMappingOptions.expenseAccountOptions || []; + this.liabilityAccountData = this.savingProductsTemplate.accountingMappingOptions.liabilityAccountOptions || []; + } + + createsavingProductAccountingForm() { + this.savingProductAccountingForm = this.formBuilder.group({ + 'accountingRule': [1] + }); + } + + setConditionalControls() { + this.savingProductAccountingForm.get('accountingRule').valueChanges + .subscribe((accountingRule: any) => { + if (accountingRule === 2) { + this.savingProductAccountingForm.addControl('savingsReferenceAccountId', new FormControl('', Validators.required)); + this.savingProductAccountingForm.addControl('overdraftPortfolioControlId', new FormControl('', Validators.required)); + this.savingProductAccountingForm.addControl('savingsControlAccountId', new FormControl('', Validators.required)); + this.savingProductAccountingForm.addControl('transfersInSuspenseAccountId', new FormControl('', Validators.required)); + this.savingProductAccountingForm.addControl('interestOnSavingsAccountId', new FormControl('', Validators.required)); + this.savingProductAccountingForm.addControl('writeOffAccountId', new FormControl('', Validators.required)); + this.savingProductAccountingForm.addControl('incomeFromFeeAccountId', new FormControl('', Validators.required)); + this.savingProductAccountingForm.addControl('incomeFromPenaltyAccountId', new FormControl('', Validators.required)); + this.savingProductAccountingForm.addControl('incomeFromInterestId', new FormControl('', Validators.required)); + this.savingProductAccountingForm.addControl('advancedAccountingRules', new FormControl(false)); + + if (this.isDormancyTrackingActive.value) { + this.savingProductAccountingForm.addControl('escheatLiabilityId', new FormControl('', Validators.required)); + } + + this.isDormancyTrackingActive.valueChanges + .subscribe((isDormancyTrackingActive: boolean) => { + if (isDormancyTrackingActive) { + this.savingProductAccountingForm.addControl('escheatLiabilityId', new FormControl('', Validators.required)); + } else { + this.savingProductAccountingForm.removeControl('escheatLiabilityId'); + } + }); + + this.savingProductAccountingForm.get('advancedAccountingRules').valueChanges + .subscribe((advancedAccountingRules: boolean) => { + if (advancedAccountingRules) { + this.savingProductAccountingForm.addControl('paymentChannelToFundSourceMappings', this.formBuilder.array([])); + this.savingProductAccountingForm.addControl('feeToIncomeAccountMappings', this.formBuilder.array([])); + this.savingProductAccountingForm.addControl('penaltyToIncomeAccountMappings', this.formBuilder.array([])); + } else { + this.savingProductAccountingForm.removeControl('paymentChannelToFundSourceMappings'); + this.savingProductAccountingForm.removeControl('feeToIncomeAccountMappings'); + this.savingProductAccountingForm.removeControl('penaltyToIncomeAccountMappings'); + } + }); + } else { + this.savingProductAccountingForm.removeControl('savingsReferenceAccountId'); + this.savingProductAccountingForm.removeControl('overdraftPortfolioControlId'); + this.savingProductAccountingForm.removeControl('savingsControlAccountId'); + this.savingProductAccountingForm.removeControl('transfersInSuspenseAccountId'); + this.savingProductAccountingForm.removeControl('interestOnSavingsAccountId'); + this.savingProductAccountingForm.removeControl('writeOffAccountId'); + this.savingProductAccountingForm.removeControl('incomeFromFeeAccountId'); + this.savingProductAccountingForm.removeControl('incomeFromPenaltyAccountId'); + this.savingProductAccountingForm.removeControl('incomeFromInterestId'); + this.savingProductAccountingForm.removeControl('advancedAccountingRules'); + this.savingProductAccountingForm.removeControl('escheatLiabilityId'); + } + }); + } + + get paymentChannelToFundSourceMappings(): FormArray { + return this.savingProductAccountingForm.get('paymentChannelToFundSourceMappings') as FormArray; + } + + get feeToIncomeAccountMappings(): FormArray { + return this.savingProductAccountingForm.get('feeToIncomeAccountMappings') as FormArray; + } + + get penaltyToIncomeAccountMappings(): FormArray { + return this.savingProductAccountingForm.get('penaltyToIncomeAccountMappings') as FormArray; + } + + add(formType: string, formArray: FormArray) { + 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); + } + }); + } + + edit(formType: string, formArray: FormArray, 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: FormArray, 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: 'Configure Fund Sources for Payment Channels', formfields: this.getPaymentFundSourceFormfields(values) }; + case 'FeesIncome': return { title: 'Map Fees to Income Accounts', formfields: this.getFeesIncomeFormfields(values) }; + case 'PenaltyIncome': return { title: 'Map Penalties to Specific Income Accounts', formfields: this.getPenaltyIncomeFormfields(values) }; + } + } + + getPaymentFundSourceFormfields(values?: any) { + const formfields: FormfieldBase[] = [ + new SelectBase({ + controlName: 'paymentTypeId', + label: 'Payment Type', + value: values ? values.paymentTypeId : this.paymentTypeData[0].id, + options: { label: 'name', value: 'id', data: this.paymentTypeData }, + required: true, + order: 1 + }), + new SelectBase({ + controlName: 'fundSourceAccountId', + label: 'Fund Source', + value: values ? values.fundSourceAccountId : this.assetAccountData[0].id, + options: { label: 'name', value: 'id', data: this.assetAccountData }, + required: true, + order: 2 + }) + ]; + return formfields; + } + + getFeesIncomeFormfields(values?: any) { + const formfields: FormfieldBase[] = [ + new SelectBase({ + controlName: 'chargeId', + label: 'Fees', + value: values ? values.chargeId : this.chargeData[0].id, + options: { label: 'name', value: 'id', data: this.chargeData }, + required: true, + order: 1 + }), + new SelectBase({ + controlName: 'incomeAccountId', + label: 'Income Account', + value: values ? values.incomeAccountId : this.incomeAccountData[0].id, + options: { label: 'name', value: 'id', data: this.incomeAccountData }, + required: true, + order: 2 + }) + ]; + return formfields; + } + + 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 + }) + ]; + return formfields; + } + + get savingProductAccounting() { + return this.savingProductAccountingForm.value; + } + +} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.html b/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.html new file mode 100644 index 000000000..4a0d5881e --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.html @@ -0,0 +1,74 @@ +
+ + + Charge + + + {{ charge.name }} + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name + {{ charge.name + ', ' + charge.currency.displaySymbol }} + Type + {{ charge.chargeCalculationType.value }} + Amount + {{ charge.amount }} + Collected On + {{ charge.chargeTimeType.value }} + Actions + +
+ +
+ +
+ + +
diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.scss new file mode 100644 index 000000000..dd420676a --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.scss @@ -0,0 +1,11 @@ +table { + width: 100%; +} + +.mat-elevation-z1 { + margin: 1em 0 1.5em 0; +} + +.margin-t { + margin-top: 1em; +} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.spec.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.spec.ts new file mode 100644 index 000000000..6df886f77 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SavingProductChargesStepComponent } from './saving-product-charges-step.component'; + +describe('SavingProductChargesStepComponent', () => { + let component: SavingProductChargesStepComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SavingProductChargesStepComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SavingProductChargesStepComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.ts new file mode 100644 index 000000000..6e8f75657 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component.ts @@ -0,0 +1,56 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { MatDialog } from '@angular/material'; +import { FormControl } from '@angular/forms'; + +import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component'; + +@Component({ + selector: 'mifosx-saving-product-charges-step', + templateUrl: './saving-product-charges-step.component.html', + styleUrls: ['./saving-product-charges-step.component.scss'] +}) +export class SavingProductChargesStepComponent implements OnInit { + + @Input() savingProductsTemplate: any; + @Input() currencyCode: FormControl; + + chargeData: any; + + chargesDataSource: {}[]; + displayedColumns: string[] = ['name', 'chargeCalculationType', 'amount', 'chargeTimeType', 'action']; + + constructor(public dialog: MatDialog) { + } + + ngOnInit() { + this.chargeData = this.savingProductsTemplate.chargeOptions; + + this.chargesDataSource = []; + + this.currencyCode.valueChanges.subscribe(() => this.chargesDataSource = []); + } + + addCharge(charge: any) { + this.chargesDataSource = this.chargesDataSource.concat([charge.value]); + charge.value = ''; + } + + deleteCharge(charge: any) { + const deleteChargeDialogRef = this.dialog.open(DeleteDialogComponent, { + data: { deleteContext: `charge ${charge.name}` } + }); + deleteChargeDialogRef.afterClosed().subscribe((response: any) => { + if (response.delete) { + this.chargesDataSource.splice(this.chargesDataSource.indexOf(charge), 1); + this.chargesDataSource = this.chargesDataSource.concat([]); + } + }); + } + + get savingProductCharges() { + return { + charges: this.chargesDataSource + }; + } + +} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.html b/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.html new file mode 100644 index 000000000..c94f34255 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.html @@ -0,0 +1,46 @@ +
+ +
+ + + Currency + + + {{ currency.name }} + + + + Currency is required + + + + + Decimal Places + + + Decimal Places is required + + + + + Currency in multiples of + + + Currency in multiples of is required + + + +
+ +
+ + +
+ +
diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.scss new file mode 100644 index 000000000..aa8d23226 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.scss @@ -0,0 +1,3 @@ +.margin-t { + margin-top: 1em; +} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.spec.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.spec.ts new file mode 100644 index 000000000..c6bffa51f --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SavingProductCurrencyStepComponent } from './saving-product-currency-step.component'; + +describe('SavingProductCurrencyStepComponent', () => { + let component: SavingProductCurrencyStepComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SavingProductCurrencyStepComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SavingProductCurrencyStepComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.ts new file mode 100644 index 000000000..dc33e44b8 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; + +@Component({ + selector: 'mifosx-saving-product-currency-step', + templateUrl: './saving-product-currency-step.component.html', + styleUrls: ['./saving-product-currency-step.component.scss'] +}) +export class SavingProductCurrencyStepComponent implements OnInit { + + @Input() savingProductsTemplate: any; + + savingProductCurrencyForm: FormGroup; + + currencyData: any; + + constructor(private formBuilder: FormBuilder) { + this.createSavingProductCurrencyForm(); + } + + ngOnInit() { + this.currencyData = this.savingProductsTemplate.currencyOptions; + + this.savingProductCurrencyForm.patchValue({ + 'currencyCode': this.currencyData[0].code, + 'digitsAfterDecimal': 2 + }); + } + + createSavingProductCurrencyForm() { + this.savingProductCurrencyForm = this.formBuilder.group({ + 'currencyCode': ['', Validators.required], + 'digitsAfterDecimal': ['', Validators.required], + 'inMultiplesOf': ['', Validators.required] + }); + } + + get savingProductCurrency() { + return this.savingProductCurrencyForm.value; + } + +} 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 new file mode 100644 index 000000000..27e2bbd42 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html @@ -0,0 +1,39 @@ +
+ +
+ + + Product Name + + + Product Name is required + + + + + Short Name + + + Short Name is required + + + + + Description + + + +
+ +
+ + +
+ +
diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.scss new file mode 100644 index 000000000..aa8d23226 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.scss @@ -0,0 +1,3 @@ +.margin-t { + margin-top: 1em; +} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.spec.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.spec.ts new file mode 100644 index 000000000..db50d1881 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SavingProductDetailsStepComponent } from './saving-product-details-step.component'; + +describe('SavingProductDetailsStepComponent', () => { + let component: SavingProductDetailsStepComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SavingProductDetailsStepComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SavingProductDetailsStepComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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 new file mode 100644 index 000000000..74f527052 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts @@ -0,0 +1,32 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; + +@Component({ + selector: 'mifosx-saving-product-details-step', + templateUrl: './saving-product-details-step.component.html', + styleUrls: ['./saving-product-details-step.component.scss'] +}) +export class SavingProductDetailsStepComponent implements OnInit { + + savingProductDetailsForm: FormGroup; + + constructor(private formBuilder: FormBuilder) { + this.createSavingProductDetailsForm(); + } + + ngOnInit() { + } + + createSavingProductDetailsForm() { + this.savingProductDetailsForm = this.formBuilder.group({ + 'name': ['', Validators.required], + 'shortName': ['', Validators.required], + 'description': [''] + }); + } + + get savingProductDetails() { + return this.savingProductDetailsForm.value; + } + +} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.html b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.html new file mode 100644 index 000000000..f8669680d --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.html @@ -0,0 +1,347 @@ +
+ +

{{ savingProduct.name }}

+ + + +

Details

+ + + +
+ Short Name: + {{ savingProduct.shortName }} +
+ +
+ Description: + {{ savingProduct.description }} +
+ +

Currency

+ + + +
+ Currency: + {{ savingProduct.currencyCode | find:savingProductsTemplate.currencyOptions:'code':'name' }} +
+ +
+ Decimal Places: + {{ savingProduct.digitsAfterDecimal }} +
+ +
+ Currency in multiples of: + {{ savingProduct.inMultiplesOf }} +
+ +

Terms

+ + + +
+ Nominal Annual Interest Rate: + {{ savingProduct.nominalAnnualInterestRate }} +
+ +
+ Interest Compounding Period: + {{ savingProduct.interestCompoundingPeriodType | find:savingProductsTemplate.interestCompoundingPeriodTypeOptions:'id':'value' }} +
+ +
+ Interest Posting Period: + {{ savingProduct.interestPostingPeriodType | find:savingProductsTemplate.interestPostingPeriodTypeOptions:'id':'value' }} +
+ +
+ Interest Calculated using: + {{ savingProduct.interestCalculationType | find:savingProductsTemplate.interestCalculationTypeOptions:'id':'value' }} +
+ +
+ Days in Year: + {{ savingProduct.interestCalculationDaysInYearType | find:savingProductsTemplate.interestCalculationDaysInYearTypeOptions:'id':'value' }} +
+ +

Settings

+ + + +
+ Minimum Opening Balance: + {{ savingProduct.minRequiredOpeningBalance }} +
+ +
+ Lock-in Period: + {{ savingProduct.lockinPeriodFrequency + ' ' + (savingProduct.lockinPeriodFrequencyType | find:savingProductsTemplate.lockinPeriodFrequencyTypeOptions:'id':'value') }} +
+ +
+ Apply Withdrawal Fee for Transfers: + {{ savingProduct.withdrawalFeeForTransfers ? 'Yes' : 'No' }} +
+ +
+ Balance Required for Interest Calculation: + {{ savingProduct.minBalanceForInterestCalculation }} +
+ +
+ Enforce Minimum Balance: + {{ savingProduct.enforceMinRequiredBalance ? 'Yes' : 'No' }} +
+ +
+ Minimum Balance: + {{ savingProduct.minRequiredBalance }} +
+ +
+ Withhold Tax is Applicable: + {{ savingProduct.withHoldTax ? 'Yes' : 'No' }} +
+ +
+ Tax Group: + {{ savingProduct.taxGroupId | find:savingProductsTemplate.taxGroupOptions:'id':'name' }} +
+ +
+ Is Overdraft Allowed: + {{ savingProduct.allowOverdraft ? 'Yes' : 'No' }} +
+ +
+ Minimum Overdraft Required for Interest Calculation: + {{ savingProduct.minOverdraftForInterestCalculation }} +
+ +
+ Nominal Annual Interest for Overdraft: + {{ savingProduct.nominalAnnualInterestRateOverdraft }} +
+ +
+ Maximum Overdraft Amount Limit: + {{ savingProduct.overdraftLimit }} +
+ +
+ Enable Dormancy Tracking: + {{ savingProduct.isDormancyTrackingActive ? 'Yes' : 'No' }} +
+ +
+ Number of Days to Inactive sub-status: + {{ savingProduct.daysToInactive }} + Number of Days to Dormant sub-status: + {{ savingProduct.daysToDormancy }} + Number of Days to Escheat: + {{ savingProduct.daysToEscheat }} +
+ +
+ +

Charges

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name + {{ charge.name + ', ' + charge.currency.displaySymbol }} + Type + {{ charge.chargeCalculationType.value }} + Amount + {{ charge.amount }} + Collected On + {{ charge.chargeTimeType.value }} +
+ +
+ +

Accounting

+ + + +
+ Type: + {{ accountingRuleData[savingProduct.accountingRule-1] }} +
+ +
+ +

Assets

+ +
+ Saving reference: + {{ savingProduct.savingsReferenceAccountId | find:savingProductsTemplate.accountingMappingOptions.assetAccountOptions:'id':'name' }} + Overdraft portfolio: + {{ savingProduct.overdraftPortfolioControlId | find:savingProductsTemplate.accountingMappingOptions.assetAccountOptions:'id':'name' }} +
+ +

Liabilities

+ +
+ Saving control: + {{ savingProduct.savingsControlAccountId | find:savingProductsTemplate.accountingMappingOptions.liabilityAccountOptions:'id':'name' }} + Savings transfers in suspense: + {{ savingProduct.transfersInSuspenseAccountId | find:savingProductsTemplate.accountingMappingOptions.liabilityAccountOptions:'id':'name' }} +
+ Escheat liability: + {{ savingProduct.escheatLiabilityId | find:savingProductsTemplate.accountingMappingOptions.liabilityAccountOptions:'id':'name' }} +
+
+ +

Expenses

+ +
+ Interest on savings: + {{ savingProduct.interestOnSavingsAccountId | find:savingProductsTemplate.accountingMappingOptions.expenseAccountOptions:'id':'name' }} + Write-off: + {{ savingProduct.writeOffAccountId | find:savingProductsTemplate.accountingMappingOptions.expenseAccountOptions:'id':'name' }} +
+ +

Income

+ +
+ Income from fees: + {{ savingProduct.incomeFromFeeAccountId | find:savingProductsTemplate.accountingMappingOptions.incomeAccountOptions:'id':'name' }} + Income from penalties: + {{ savingProduct.incomeFromPenaltyAccountId | find:savingProductsTemplate.accountingMappingOptions.incomeAccountOptions:'id':'name' }} + Overdraft Interest Income: + {{ savingProduct.incomeFromInterestId | find:savingProductsTemplate.accountingMappingOptions.incomeAccountOptions:'id':'name' }} +
+ +
+ +

Advanced Accounting Rules

+ + + +
+ +

Fund Sources for Payment Channels

+ + + + + + + + + + + + + + + + +
Payment Type + {{ paymentFundSource.paymentTypeId | find:savingProductsTemplate.paymentTypeOptions:'id':'name' }} + Fund Source + {{ paymentFundSource.fundSourceAccountId | find:savingProductsTemplate.accountingMappingOptions.assetAccountOptions:'id':'name' }} +
+ +
+ +
+ +

Fees to Specific Income Accounts

+ + + + + + + + + + + + + + + + +
Fees + {{ feesIncome.chargeId | find:savingProductsTemplate.chargeOptions:'id':'name' }} + Income Account + {{ feesIncome.incomeAccountId | find:savingProductsTemplate.accountingMappingOptions.incomeAccountOptions:'id':'name' }} +
+ +
+ +
+ +

Penalties to Specific Income Accounts

+ + + + + + + + + + + + + + + + +
Penalty + {{ penaltyIncome.chargeId | find:savingProductsTemplate.penaltyOptions:'id':'name' }} + Income Account + {{ penaltyIncome.incomeAccountId | find:savingProductsTemplate.accountingMappingOptions.incomeAccountOptions:'id':'name' }} +
+ +
+ +
+ +
+ +
+ +
+ + + +
diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.scss new file mode 100644 index 000000000..87258dacb --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.scss @@ -0,0 +1,24 @@ +table { + width: 100%; +} + +.mat-elevation-z1 { + margin: 1em 0 1.5em 0; +} + +h2, h3, h4 { + margin: 0; + font-weight: 500; +} + +span { + margin: 0.5em 0; +} + +mat-divider { + margin: 0 0 0.5em 0; +} + +.margin-t { + margin-top: 1em; +} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.spec.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.spec.ts new file mode 100644 index 000000000..df6107a94 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SavingProductPreviewStepComponent } from './saving-product-preview-step.component'; + +describe('SavingProductPreviewStepComponent', () => { + let component: SavingProductPreviewStepComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SavingProductPreviewStepComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SavingProductPreviewStepComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.ts new file mode 100644 index 000000000..931a35e2e --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.ts @@ -0,0 +1,24 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'mifosx-saving-product-preview-step', + templateUrl: './saving-product-preview-step.component.html', + styleUrls: ['./saving-product-preview-step.component.scss'] +}) +export class SavingProductPreviewStepComponent implements OnInit { + + @Input() savingProductsTemplate: any; + @Input() accountingRuleData: any; + @Input() savingProduct: any; + @Output() submit = new EventEmitter(); + + chargesDisplayedColumns: string[] = ['name', 'chargeCalculationType', 'amount', 'chargeTimeType']; + paymentFundSourceDisplayedColumns: string[] = ['paymentTypeId', 'fundSourceAccountId']; + feesPenaltyIncomeDisplayedColumns: string[] = ['chargeId', 'incomeAccountId']; + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.html b/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.html new file mode 100644 index 000000000..e79ba7e21 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.html @@ -0,0 +1,135 @@ +
+ +
+ + + Minimum Opening Balance + + + + + + + Lock-in Period + + + + + + + {{ lockinPeriodFrequencyType.value }} + + + + + + Apply Withdrawal Fee for Transfers + + + + Balance Required for Interest Calculation + + + + + Enforce Minimum Balance + + + + Minimum Balance + + + + + Is Withhold Tax Applicable? + + + + Tax Group + + + {{ taxGroup.name }} + + + + Tax Group is required + + + + + +

Overdraft

+ + + Is Overdraft Allowed? + + +
+ + + Minimum Overdraft Required for Interest Calculation + + + + + Nominal Annual Interest for Overdraft + + + + + Maximum Overdraft Amount Limit + + + +
+ + + +

Dormancy Tracking

+ + + Enable Dormancy Tracking + + +
+ + + Number of Days to Inactive sub-status + + + Number of Days to Inactive sub-status is required + + + + + Number of Days to Dormant sub-status + + + Number of Days to Dormant sub-status is required + + + + + Number of Days to Escheat + + + Number of Days to Escheat is required + + + +
+ +
+ +
+ + +
+ +
diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.scss new file mode 100644 index 000000000..10bdddeb1 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.scss @@ -0,0 +1,19 @@ +h3 { + font-weight: 500; +} + +mat-divider { + margin: 1em 0 2em 0; +} + +.margin-v { + margin: 1em 0; +} + +.margin-b { + margin: 0 0 1em 0; +} + +.margin-t { + margin-top: 1em; +} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.spec.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.spec.ts new file mode 100644 index 000000000..f0a0099b6 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SavingProductSettingsStepComponent } from './saving-product-settings-step.component'; + +describe('SavingProductSettingsStepComponent', () => { + let component: SavingProductSettingsStepComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SavingProductSettingsStepComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SavingProductSettingsStepComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.ts new file mode 100644 index 000000000..670fcb9ec --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component.ts @@ -0,0 +1,84 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms'; + +@Component({ + selector: 'mifosx-saving-product-settings-step', + templateUrl: './saving-product-settings-step.component.html', + styleUrls: ['./saving-product-settings-step.component.scss'] +}) +export class SavingProductSettingsStepComponent implements OnInit { + + @Input() savingProductsTemplate: any; + + savingProductSettingsForm: FormGroup; + + lockinPeriodFrequencyTypeData: any; + taxGroupData: any; + + constructor(private formBuilder: FormBuilder) { + this.createSavingProductSettingsForm(); + this.setConditionalControls(); + } + + ngOnInit() { + this.lockinPeriodFrequencyTypeData = this.savingProductsTemplate.lockinPeriodFrequencyTypeOptions; + this.taxGroupData = this.savingProductsTemplate.taxGroupOptions; + } + + createSavingProductSettingsForm() { + this.savingProductSettingsForm = this.formBuilder.group({ + 'minRequiredOpeningBalance': [''], + 'lockinPeriodFrequency': [''], + 'lockinPeriodFrequencyType': [''], + 'withdrawalFeeForTransfers': [false], + 'minBalanceForInterestCalculation': [''], + 'enforceMinRequiredBalance': [false], + 'minRequiredBalance': [''], + 'allowOverdraft': [false], + 'withHoldTax': [false], + 'isDormancyTrackingActive': [false] + }); + } + + setConditionalControls() { + this.savingProductSettingsForm.get('allowOverdraft').valueChanges + .subscribe((allowOverdraft: any) => { + if (allowOverdraft) { + this.savingProductSettingsForm.addControl('minOverdraftForInterestCalculation', new FormControl('')); + this.savingProductSettingsForm.addControl('nominalAnnualInterestRateOverdraft', new FormControl('')); + this.savingProductSettingsForm.addControl('overdraftLimit', new FormControl('')); + } else { + this.savingProductSettingsForm.removeControl('minOverdraftForInterestCalculation'); + this.savingProductSettingsForm.removeControl('nominalAnnualInterestRateOverdraft'); + this.savingProductSettingsForm.removeControl('overdraftLimit'); + } + }); + + this.savingProductSettingsForm.get('withHoldTax').valueChanges + .subscribe((withHoldTax: any) => { + if (withHoldTax) { + this.savingProductSettingsForm.addControl('taxGroupId', new FormControl('', Validators.required)); + } else { + this.savingProductSettingsForm.removeControl('taxGroupId'); + } + }); + + this.savingProductSettingsForm.get('isDormancyTrackingActive').valueChanges + .subscribe((isDormancyTrackingActive: any) => { + if (isDormancyTrackingActive) { + this.savingProductSettingsForm.addControl('daysToInactive', new FormControl('', Validators.required)); + this.savingProductSettingsForm.addControl('daysToDormancy', new FormControl('', Validators.required)); + this.savingProductSettingsForm.addControl('daysToEscheat', new FormControl('', Validators.required)); + } else { + this.savingProductSettingsForm.removeControl('daysToInactive'); + this.savingProductSettingsForm.removeControl('daysToDormancy'); + this.savingProductSettingsForm.removeControl('daysToEscheat'); + } + }); + } + + get savingProductSettings() { + return this.savingProductSettingsForm.value; + } + +} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.html b/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.html new file mode 100644 index 000000000..a7127951c --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.html @@ -0,0 +1,76 @@ +
+ +
+ + + Nominal Annual Interest Rate + + + Nominal Annual Interest is required + + + + + + + Interest Compounding Period + + + {{ interestCompoundingPeriodType.value }} + + + + Interest Compounding Period is required + + + + + Interest Posting Period + + + {{ interestPostingPeriodType.value }} + + + + Interest Posting Period is required + + + + + Interest Calculated using + + + {{ interestCalculationType.value }} + + + + Interest Calculated using is required + + + + + Days in Year + + + {{ interestCalculationDaysInYearType.value }} + + + + Days in Year is required + + + +
+ +
+ + +
+ +
diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.scss b/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.scss new file mode 100644 index 000000000..aa8d23226 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.scss @@ -0,0 +1,3 @@ +.margin-t { + margin-top: 1em; +} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.spec.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.spec.ts new file mode 100644 index 000000000..3976d5507 --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SavingProductTermsStepComponent } from './saving-product-terms-step.component'; + +describe('SavingProductTermsStepComponent', () => { + let component: SavingProductTermsStepComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SavingProductTermsStepComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SavingProductTermsStepComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.ts new file mode 100644 index 000000000..39b0757ba --- /dev/null +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component.ts @@ -0,0 +1,52 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; + +@Component({ + selector: 'mifosx-saving-product-terms-step', + templateUrl: './saving-product-terms-step.component.html', + styleUrls: ['./saving-product-terms-step.component.scss'] +}) +export class SavingProductTermsStepComponent implements OnInit { + + @Input() savingProductsTemplate: any; + + savingProductTermsForm: FormGroup; + + interestCompoundingPeriodTypeData: any; + interestPostingPeriodTypeData: any; + interestCalculationTypeData: any; + interestCalculationDaysInYearTypeData: any; + + constructor(private formBuilder: FormBuilder) { + this.createSavingProductTermsForm(); + } + + ngOnInit() { + this.interestCompoundingPeriodTypeData = this.savingProductsTemplate.interestCompoundingPeriodTypeOptions; + this.interestPostingPeriodTypeData = this.savingProductsTemplate.interestPostingPeriodTypeOptions; + this.interestCalculationTypeData = this.savingProductsTemplate.interestCalculationTypeOptions; + this.interestCalculationDaysInYearTypeData = this.savingProductsTemplate.interestCalculationDaysInYearTypeOptions; + + this.savingProductTermsForm.patchValue({ + 'interestCompoundingPeriodType': this.interestCompoundingPeriodTypeData[0].id, + 'interestPostingPeriodType': this.interestPostingPeriodTypeData[0].id, + 'interestCalculationType': this.interestCalculationTypeData[0].id, + 'interestCalculationDaysInYearType': this.interestCalculationDaysInYearTypeData[0].id + }); + } + + createSavingProductTermsForm() { + this.savingProductTermsForm = this.formBuilder.group({ + 'nominalAnnualInterestRate': ['', Validators.required], + 'interestCompoundingPeriodType': ['', Validators.required], + 'interestPostingPeriodType': ['', Validators.required], + 'interestCalculationType': ['', Validators.required], + 'interestCalculationDaysInYearType': ['', Validators.required] + }); + } + + get savingProductTerms() { + return this.savingProductTermsForm.value; + } + +} diff --git a/src/app/products/saving-products/saving-products-template.resolver.ts b/src/app/products/saving-products/saving-products-template.resolver.ts new file mode 100644 index 000000000..e7e937f12 --- /dev/null +++ b/src/app/products/saving-products/saving-products-template.resolver.ts @@ -0,0 +1,24 @@ +/** Angular Imports */ +import { Injectable } from '@angular/core'; +import { Resolve } from '@angular/router'; + +/** rxjs Imports */ +import { Observable } from 'rxjs'; + +/** Custom Services */ +import { ProductsService } from '../products.service'; + +@Injectable() +export class SavingProductsTemplateResolver implements Resolve { + + constructor(private productsService: ProductsService) {} + + /** + * Returns the saving products template data. + * @returns {Observable} + */ + resolve(): Observable { + return this.productsService.getSavingProductsTemplate(); + } + +} diff --git a/src/app/products/saving-products/saving-products.component.html b/src/app/products/saving-products/saving-products.component.html index c60985ecd..4d1256b92 100644 --- a/src/app/products/saving-products/saving-products.component.html +++ b/src/app/products/saving-products/saving-products.component.html @@ -1,5 +1,5 @@
-