From f00eabb99087d2ecfd8865b947ab5b081d03ac42 Mon Sep 17 00:00:00 2001 From: Muskan Khedia <36980003+muskankhedia@users.noreply.github.com> Date: Wed, 5 Aug 2020 14:11:31 +0530 Subject: [PATCH] feat: add create charge component (#1175) Fixes: #191 Refer: #187 Co-authored-by: punwai --- .../charges/charges-template-and-resolver.ts | 25 ++ .../charges/charges-template.resolver.ts | 15 +- .../products/charges/charges.component.html | 2 +- .../create-charge.component.html | 197 +++++++++++++++ .../create-charge.component.scss | 11 + .../create-charge.component.spec.ts | 25 ++ .../create-charge/create-charge.component.ts | 230 ++++++++++++++++++ .../edit-charge/edit-charge.component.html | 2 +- .../view-charge/view-charge.component.html | 4 +- .../create-product-mix.component.html | 2 +- .../edit-product-mix.component.html | 2 +- .../products-mix/products-mix.component.html | 2 +- .../view-product-mix.component.html | 4 +- src/app/products/products-routing.module.ts | 15 +- src/app/products/products.module.ts | 5 +- src/app/products/products.service.ts | 8 + 16 files changed, 532 insertions(+), 17 deletions(-) create mode 100644 src/app/products/charges/charges-template-and-resolver.ts create mode 100644 src/app/products/charges/create-charge/create-charge.component.html create mode 100644 src/app/products/charges/create-charge/create-charge.component.scss create mode 100644 src/app/products/charges/create-charge/create-charge.component.spec.ts create mode 100644 src/app/products/charges/create-charge/create-charge.component.ts diff --git a/src/app/products/charges/charges-template-and-resolver.ts b/src/app/products/charges/charges-template-and-resolver.ts new file mode 100644 index 000000000..f4e39d528 --- /dev/null +++ b/src/app/products/charges/charges-template-and-resolver.ts @@ -0,0 +1,25 @@ +/** Angular Imports */ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; + +/** rxjs Imports */ +import { Observable } from 'rxjs'; + +/** Custom Services */ +import { ProductsService } from '../products.service'; + +@Injectable() +export class ChargesTemplateAndResolver implements Resolve { + + constructor(private productsService: ProductsService) { } + + /** + * Returns the changes template and data. + * @returns {Observable} + */ + resolve(route: ActivatedRouteSnapshot): Observable { + const savingProductId = route.paramMap.get('id'); + return this.productsService.getCharge(savingProductId, true); + } + +} diff --git a/src/app/products/charges/charges-template.resolver.ts b/src/app/products/charges/charges-template.resolver.ts index f8e8f769c..50b4a093c 100644 --- a/src/app/products/charges/charges-template.resolver.ts +++ b/src/app/products/charges/charges-template.resolver.ts @@ -1,6 +1,6 @@ /** Angular Imports */ import { Injectable } from '@angular/core'; -import { Resolve, ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; +import { Resolve } from '@angular/router'; /** rxjs Imports */ import { Observable } from 'rxjs'; @@ -8,18 +8,23 @@ import { Observable } from 'rxjs'; /** Custom Services */ import { ProductsService } from '../products.service'; +/** + * Charges template data resolver. + */ @Injectable() export class ChargesTemplateResolver implements Resolve { + /** + * @param {ProductsService} productsService Products service. + */ constructor(private productsService: ProductsService) {} /** - * Returns the share products template data. + * Returns the charges template data. * @returns {Observable} */ - resolve(route: ActivatedRouteSnapshot): Observable { - const savingProductId = route.paramMap.get('id'); - return this.productsService.getCharge(savingProductId, true); + resolve(): Observable { + return this.productsService.getChargesTemplate(); } } diff --git a/src/app/products/charges/charges.component.html b/src/app/products/charges/charges.component.html index 4d956e081..99511e34e 100644 --- a/src/app/products/charges/charges.component.html +++ b/src/app/products/charges/charges.component.html @@ -1,5 +1,5 @@
- diff --git a/src/app/products/charges/create-charge/create-charge.component.html b/src/app/products/charges/create-charge/create-charge.component.html new file mode 100644 index 000000000..275793f13 --- /dev/null +++ b/src/app/products/charges/create-charge/create-charge.component.html @@ -0,0 +1,197 @@ +
+ + + +
+ + + +
+ + + Charge Applies To + + + {{ chargeAppliesTo.value }} + + + + Charge Applies To is required + + + + + +
+ + + Charge Name + + + Charge Name is required + + + + + Currency + + + {{ currency.name }} + + + + Currency is required + + + + + Charge Time Type + + + {{ chargeTimeType.value }} + + + + Charge Time Type is required + + + + + Charge Calculation Type + + + {{ chargeCalculationType.value }} + + + + Charge Calculation Type is required + + + + + Charge Payment Mode + + + {{ chargePaymentMode.value }} + + + + Charge Payment Mode is required + + + +
+ + Add Fee Frequency + +
+ + + Charge Frequency + + + {{ feeFrequency.value }} + + + + Charge Frequency is required + + + + + Frequency Interval + + + Frequency Interval is required + + + Frequency Interval must be a positive integer + + + + + Due Date + + + + + Due Date is required + + + + + Repeat Every ({{ repeatEveryLabel }}) + + + Repeat Every ({{ repeatEveryLabel }}) is required + + + Repeat Every ({{ repeatEveryLabel }}) must be between 1 and 12 + + + Repeat Every ({{ repeatEveryLabel }}) must be a positive integer + + + + + Amount + + + Amount is required + + + Amount must be a positive number with {{ currencyDecimalPlaces ? 'up to ' + currencyDecimalPlaces : 'no' }} decimal places + + + Amount must be a positive number + + + +
+
+ + Active + +
+ +
+ + Is Penalty + +
+
+ + + Income from Charge + + + {{ incomeOrLiabilityAccount.name }} + + + + + + Tax Group + + + {{ taxGroup.name }} + + + + +
+ +
+ +
+ + + + + + +
+ +
+ +
diff --git a/src/app/products/charges/create-charge/create-charge.component.scss b/src/app/products/charges/create-charge/create-charge.component.scss new file mode 100644 index 000000000..95138373e --- /dev/null +++ b/src/app/products/charges/create-charge/create-charge.component.scss @@ -0,0 +1,11 @@ +.penalty-wrapper { + padding: 17.5px 0 17.5px 0; +} + +.active-wrapper { + padding: 17.5px 0 17.5px 0; +} + +.add-fee-frequency-wrapper { + padding: 17.5px 0 17.5px 0; +} diff --git a/src/app/products/charges/create-charge/create-charge.component.spec.ts b/src/app/products/charges/create-charge/create-charge.component.spec.ts new file mode 100644 index 000000000..b3f670b77 --- /dev/null +++ b/src/app/products/charges/create-charge/create-charge.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CreateChargeComponent } from './create-charge.component'; + +describe('CreateChargeComponent', () => { + let component: CreateChargeComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CreateChargeComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateChargeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/products/charges/create-charge/create-charge.component.ts b/src/app/products/charges/create-charge/create-charge.component.ts new file mode 100644 index 000000000..c0363006c --- /dev/null +++ b/src/app/products/charges/create-charge/create-charge.component.ts @@ -0,0 +1,230 @@ +/** Angular Imports */ +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +import { DatePipe } from '@angular/common'; + +/** Custom Services */ +import { ProductsService } from '../../products.service'; + +/** + * Create charge component. + */ +@Component({ + selector: 'mifosx-create-charge', + templateUrl: './create-charge.component.html', + styleUrls: ['./create-charge.component.scss'] +}) +export class CreateChargeComponent implements OnInit { + + /** Charge form. */ + chargeForm: FormGroup; + /** Charges template data. */ + chargesTemplateData: any; + /** Charge time type data. */ + chargeTimeTypeData: any; + /** Charge calculation type data. */ + chargeCalculationTypeData: any = ''; + /** Income and liability account data */ + incomeAndLiabilityAccountData: any; + /** Minimum due date allowed. */ + minDate = new Date(2000, 0, 1); + /** Maximum due date allowed. */ + maxDate = new Date(new Date().setFullYear(new Date().getFullYear() + 1)); + /** Repeat every label */ + repeatEveryLabel: string; + /** Currency decimal places */ + currencyDecimalPlaces: number; + + /** + * Retrieves the charges template data and income and liability account data from `resolve`. + * @param {FormBuilder} formBuilder Form Builder. + * @param {ProductsService} productsService Products Service. + * @param {ActivatedRoute} route Activated Route. + * @param {Router} router Router for navigation. + * @param {DatePipe} datePipe Date Pipe to format date. + */ + constructor(private formBuilder: FormBuilder, + private productsService: ProductsService, + private route: ActivatedRoute, + private router: Router, + private datePipe: DatePipe) { + this.route.data.subscribe((data: { chargesTemplate: any }) => { + this.chargesTemplateData = data.chargesTemplate; + this.incomeAndLiabilityAccountData = data.chargesTemplate.incomeOrLiabilityAccountOptions.incomeAccountOptions + .concat(data.chargesTemplate.incomeOrLiabilityAccountOptions.liabilityAccountOptions); + }); + } + + /** + * Creates and sets the charge form. + */ + ngOnInit() { + this.createChargeForm(); + this.setChargeForm(); + this.setConditionalControls(); + } + + /** + * Creates the charge form. + */ + createChargeForm() { + this.chargeForm = this.formBuilder.group({ + 'chargeAppliesTo': ['', Validators.required], + 'name': ['', Validators.required], + 'currencyCode': ['', Validators.required], + 'chargeTimeType': ['', Validators.required], + 'chargeCalculationType': ['', Validators.required], + 'amount': ['', [Validators.required, Validators.pattern('^\\s*(?=.*[1-9])\\d*(?:\\.\\d+)?\\s*$')]], + 'active': [false], + 'penalty': [false], + 'taxGroupId': [''] + }); + } + + /** + * Sets the charge calculation type and charge time type data + */ + setChargeForm() { + this.chargeForm.get('chargeAppliesTo').valueChanges.subscribe((chargeAppliesTo) => { + switch (chargeAppliesTo) { + case 1: + this.chargeCalculationTypeData = this.chargesTemplateData.loanChargeCalculationTypeOptions; + this.chargeTimeTypeData = this.chargesTemplateData.loanChargeTimeTypeOptions; + break; + case 2: + this.chargeCalculationTypeData = this.chargesTemplateData.savingsChargeCalculationTypeOptions; + this.chargeTimeTypeData = this.chargesTemplateData.savingsChargeTimeTypeOptions; + break; + case 3: + this.chargeCalculationTypeData = this.chargesTemplateData.clientChargeCalculationTypeOptions; + this.chargeTimeTypeData = this.chargesTemplateData.clientChargeTimeTypeOptions; + break; + case 4: + this.chargeCalculationTypeData = this.chargesTemplateData.shareChargeCalculationTypeOptions; + this.chargeTimeTypeData = this.chargesTemplateData.shareChargeTimeTypeOptions; + break; + } + }); + } + + /** + * @returns {any} Filtered charge calculation type data. + */ + filteredChargeCalculationType(): any { + return this.chargeCalculationTypeData.filter((chargeCalculationType: any) => { + if (this.chargeForm.get('chargeTimeType').value === 12 && (chargeCalculationType.id === 3 || chargeCalculationType.id === 4)) { + return false; + } + if (this.chargeForm.get('chargeTimeType').value !== 12 && chargeCalculationType.id === 5) { + return false; + } + if (this.chargeForm.get('chargeAppliesTo').value === 2) { + if (!(this.chargeForm.get('chargeTimeType').value === 5 || this.chargeForm.get('chargeTimeType').value === 16) && chargeCalculationType.id === 2) { + return false; + } + } + return true; + }); + } + + /** + * Sets the conditional controls of the user form + */ + setConditionalControls() { + this.chargeForm.get('chargeAppliesTo').valueChanges.subscribe((chargeAppliesTo) => { + this.chargeForm.get('penalty').enable(); + switch (chargeAppliesTo) { + case 1: // Loan + this.chargeForm.addControl('chargePaymentMode', new FormControl('', Validators.required)); + this.chargeForm.removeControl('incomeAccountId'); + break; + case 2: // Savings + this.chargeForm.removeControl('chargePaymentMode'); + this.chargeForm.removeControl('incomeAccountId'); + break; + case 3: // Client + this.chargeForm.removeControl('chargePaymentMode'); + this.chargeForm.addControl('incomeAccountId', new FormControl('')); + break; + case 4: // Shares + this.chargeForm.removeControl('chargePaymentMode'); + this.chargeForm.removeControl('incomeAccountId'); + this.chargeForm.get('penalty').setValue(false); + this.chargeForm.get('penalty').disable(); + break; + } + this.chargeForm.get('chargeCalculationType').reset(); + this.chargeForm.get('chargeTimeType').reset(); + }); + this.chargeForm.get('chargeTimeType').valueChanges.subscribe((chargeTimeType) => { + this.chargeForm.removeControl('feeFrequency'); + this.chargeForm.removeControl('feeInterval'); + this.chargeForm.removeControl('feeOnMonthDay'); + this.chargeForm.removeControl('addFeeFrequency'); + if (this.chargeForm.get('chargeAppliesTo').value !== 4) { + this.chargeForm.get('penalty').enable(); + } + switch (chargeTimeType) { + case 6: // Annual Fee + this.chargeForm.addControl('feeOnMonthDay', new FormControl('', Validators.required)); + break; + case 7: // Monthly Fee + this.chargeForm.addControl('feeOnMonthDay', new FormControl('')); + this.chargeForm.addControl('feeInterval', new FormControl('', [Validators.required, Validators.min(1), Validators.max(12), Validators.pattern('^[1-9]\\d*$')])); + this.repeatEveryLabel = 'Months'; + break; + case 9: // Overdue Fee + this.chargeForm.get('penalty').setValue(true); + this.chargeForm.get('penalty').disable(); + this.chargeForm.addControl('addFeeFrequency', new FormControl(false)); + this.chargeForm.get('addFeeFrequency').valueChanges.subscribe((addFeeFrequency) => { + if (addFeeFrequency) { + this.chargeForm.addControl('feeFrequency', new FormControl('', Validators.required)); + this.chargeForm.addControl('feeInterval', new FormControl('', [Validators.required, Validators.pattern('^[1-9]\\d*$')])); + } else { + this.chargeForm.removeControl('feeFrequency'); + this.chargeForm.removeControl('feeInterval'); + } + }); + break; + case 11: // Weekly Fee + this.chargeForm.addControl('feeInterval', new FormControl('', [Validators.required, Validators.pattern('^[1-9]\\d*$')])); + this.repeatEveryLabel = 'Weeks'; + break; + } + }); + this.chargeForm.get('currencyCode').valueChanges.subscribe((currencyCode) => { + this.currencyDecimalPlaces = this.chargesTemplateData.currencyOptions.find((currency: any) => currency.code === currencyCode).decimalPlaces; + if (this.currencyDecimalPlaces === 0) { + this.chargeForm.get('amount').setValidators([Validators.required, Validators.pattern('^[1-9]\\d*$')]); + } else { + this.chargeForm.get('amount').setValidators([Validators.required, Validators.pattern(`^\\s*(?=.*[1-9])\\d*(\\.\\d{1,${this.currencyDecimalPlaces}})?\\s*$`)]); + } + }); + } + + /** + * Submits the charge form and creates charge, + * if successful redirects to charges. + */ + submit() { + const prevFeeOnMonthDay: Date = this.chargeForm.value.feeOnMonthDay; + const monthDayFormat = 'dd MMM'; + this.chargeForm.patchValue({ + feeOnMonthDay: this.datePipe.transform(prevFeeOnMonthDay, monthDayFormat) + }); + const charge = this.chargeForm.getRawValue(); + // TODO: Update once language and date settings are setup + charge.locale = 'en'; + charge.monthDayFormat = monthDayFormat; + delete charge.addFeeFrequency; + if (!charge.taxGroupId) { + delete charge.taxGroupId; + } + this.productsService.createCharge(charge).subscribe((response: any) => { + this.router.navigate(['../'], { relativeTo: this.route }); + }); + } + +} diff --git a/src/app/products/charges/edit-charge/edit-charge.component.html b/src/app/products/charges/edit-charge/edit-charge.component.html index 61563f02b..2098b5e8d 100644 --- a/src/app/products/charges/edit-charge/edit-charge.component.html +++ b/src/app/products/charges/edit-charge/edit-charge.component.html @@ -162,7 +162,7 @@ - + diff --git a/src/app/products/charges/view-charge/view-charge.component.html b/src/app/products/charges/view-charge/view-charge.component.html index 7cd64b74b..170a73a97 100644 --- a/src/app/products/charges/view-charge/view-charge.component.html +++ b/src/app/products/charges/view-charge/view-charge.component.html @@ -1,9 +1,9 @@
- - diff --git a/src/app/products/products-mix/create-product-mix/create-product-mix.component.html b/src/app/products/products-mix/create-product-mix/create-product-mix.component.html index 74db66535..74174b6d3 100644 --- a/src/app/products/products-mix/create-product-mix/create-product-mix.component.html +++ b/src/app/products/products-mix/create-product-mix/create-product-mix.component.html @@ -37,7 +37,7 @@ - + diff --git a/src/app/products/products-mix/edit-product-mix/edit-product-mix.component.html b/src/app/products/products-mix/edit-product-mix/edit-product-mix.component.html index 171aff0f5..f86c3838f 100644 --- a/src/app/products/products-mix/edit-product-mix/edit-product-mix.component.html +++ b/src/app/products/products-mix/edit-product-mix/edit-product-mix.component.html @@ -30,7 +30,7 @@ - + diff --git a/src/app/products/products-mix/products-mix.component.html b/src/app/products/products-mix/products-mix.component.html index 71819e44d..19e3dca1a 100644 --- a/src/app/products/products-mix/products-mix.component.html +++ b/src/app/products/products-mix/products-mix.component.html @@ -1,5 +1,5 @@
-
diff --git a/src/app/products/products-mix/view-product-mix/view-product-mix.component.html b/src/app/products/products-mix/view-product-mix/view-product-mix.component.html index f9e3a56bb..3e6a537b1 100644 --- a/src/app/products/products-mix/view-product-mix/view-product-mix.component.html +++ b/src/app/products/products-mix/view-product-mix/view-product-mix.component.html @@ -1,9 +1,9 @@
- - diff --git a/src/app/products/products-routing.module.ts b/src/app/products/products-routing.module.ts index 2618a56ad..3fec7ada5 100644 --- a/src/app/products/products-routing.module.ts +++ b/src/app/products/products-routing.module.ts @@ -27,6 +27,7 @@ import { RecurringDepositProductsComponent } from './recurring-deposit-products/ import { ViewRecurringDepositProductComponent } from './recurring-deposit-products/view-recurring-deposit-product/view-recurring-deposit-product.component'; import { ChargesComponent } from './charges/charges.component'; import { ViewChargeComponent } from './charges/view-charge/view-charge.component'; +import { CreateChargeComponent } from './charges/create-charge/create-charge.component'; import { FixedDepositProductsComponent } from './fixed-deposit-products/fixed-deposit-products.component'; import { CreateFixedDepositProductComponent } from './fixed-deposit-products/create-fixed-deposit-product/create-fixed-deposit-product.component'; import { ProductsMixComponent } from './products-mix/products-mix.component'; @@ -67,6 +68,7 @@ import { RecurringDepositProductsResolver } from './recurring-deposit-products/r import { RecurringDepositProductResolver } from './recurring-deposit-products/recurring-deposit-product.resolver'; import { ChargesResolver } from './charges/charges.resolver'; import { ChargeResolver } from './charges/charge.resolver'; +import { ChargesTemplateResolver } from './charges/charges-template.resolver'; import { FixedDepositProductsResolver } from './fixed-deposit-products/fixed-deposit-products.resolver'; import { FixedDepositProductsTemplateResolver } from './fixed-deposit-products/fixed-deposit-products-template.resolver'; import { ProductsMixResolver } from './products-mix/products-mix.resolver'; @@ -78,7 +80,6 @@ import { ManageTaxGroupsResolver } from './manage-tax-groups/manage-tax-groups.r import { TaxComponentResolver } from './manage-tax-components/tax-component.resolver'; import { TaxComponentTemplateResolver } from './manage-tax-components/tax-component-template.resolver'; import { EditChargeComponent } from './charges/edit-charge/edit-charge.component'; -import { ChargesTemplateResolver } from './charges/charges-template.resolver'; import { TaxGroupResolver } from './manage-tax-groups/tax-group.resolver'; import { DividendsResolver } from './share-products/dividends-share-product/dividends.resolver'; import { RecurringDepositProductsTemplateResolver } from './recurring-deposit-products/recurring-deposit-products-template.resolver'; @@ -90,6 +91,7 @@ import { ManageTaxGroupTemplateResolver } from './manage-tax-groups/create-tax-g import { EditTaxGroupResolver } from './manage-tax-groups/edit-tax-group/edit-tax-group.resolver'; import { ProductsMixTemplateResolver } from './products-mix/products-mix-template.resolver'; import { EditProductMixComponent } from './products-mix/edit-product-mix/edit-product-mix.component'; +import { ChargesTemplateAndResolver } from './charges/charges-template-and-resolver'; /** Products Routes */ const routes: Routes = [ @@ -514,6 +516,14 @@ const routes: Routes = [ path: 'charges', data: { title: extract('Charges'), breadcrumb: 'Charges' }, children: [ + { + path: 'create', + component: CreateChargeComponent, + data: { title: extract('Create Charge'), breadcrumb: 'Create Charge' }, + resolve: { + chargesTemplate: ChargesTemplateResolver + } + }, { path: '', component: ChargesComponent, @@ -537,7 +547,7 @@ const routes: Routes = [ component: EditChargeComponent, data: { title: extract('Edit Charge'), breadcrumb: 'Edit', routeParamBreadcrumb: false }, resolve: { - chargesTemplate: ChargesTemplateResolver + chargesTemplate: ChargesTemplateAndResolver } }, ] @@ -574,6 +584,7 @@ const routes: Routes = [ RecurringDepositProductResolver, ChargesResolver, ChargeResolver, + ChargesTemplateAndResolver, ChargesTemplateResolver, FixedDepositProductsResolver, FixedDepositProductsTemplateResolver, diff --git a/src/app/products/products.module.ts b/src/app/products/products.module.ts index 639fa77ae..89b89b745 100644 --- a/src/app/products/products.module.ts +++ b/src/app/products/products.module.ts @@ -94,6 +94,7 @@ import { CreateTaxGroupComponent } from './manage-tax-groups/create-tax-group/cr import { EditTaxGroupComponent } from './manage-tax-groups/edit-tax-group/edit-tax-group.component'; import { CreateProductMixComponent } from './products-mix/create-product-mix/create-product-mix.component'; import { EditProductMixComponent } from './products-mix/edit-product-mix/edit-product-mix.component'; +import { CreateChargeComponent } from './charges/create-charge/create-charge.component'; /** * Products Module @@ -191,7 +192,9 @@ import { EditProductMixComponent } from './products-mix/edit-product-mix/edit-pr CreateTaxGroupComponent, EditTaxGroupComponent, CreateProductMixComponent, - EditProductMixComponent + EditProductMixComponent, + ManageTaxGroupsComponent, + CreateChargeComponent ], entryComponents: [ DepositProductIncentiveFormDialogComponent, diff --git a/src/app/products/products.service.ts b/src/app/products/products.service.ts index 1ec70c00f..e11f64048 100644 --- a/src/app/products/products.service.ts +++ b/src/app/products/products.service.ts @@ -175,6 +175,14 @@ export class ProductsService { return this.http.delete(`/charges/${chargeId}`); } + /** + * @param {any} charge Charge to be created. + * @returns {Observable} + */ + createCharge(charge: any): Observable { + return this.http.post('/charges', charge); + } + /** * @returns {Observable} Fixed deposit products data */