From 8f86c6ed538850292aa4d8c4b3b2a1f2e42d8c81 Mon Sep 17 00:00:00 2001 From: Artur Date: Thu, 21 Nov 2024 19:14:22 +0100 Subject: [PATCH] New version of asset calculator on signals --- src/app/app.routes.ts | 2 +- .../asset-calculator.component.html | 59 +-- .../asset-calculator.component.ts | 371 ++++++++++-------- src/app/assets/asset.ts | 43 +- src/app/fixed-asset/fixed-asset.component.ts | 4 +- 5 files changed, 268 insertions(+), 211 deletions(-) diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 5fcd765..ab08253 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -10,7 +10,7 @@ import { FriendlyPagesComponent } from './friendly-pages/friendly-pages.componen export const routes: Routes = [ { path: "", - title: "Sygnały", + title: "Kalkulator amortyzacyjny", component:AssetCalculatorComponent }, { diff --git a/src/app/asset-calculator/asset-calculator.component.html b/src/app/asset-calculator/asset-calculator.component.html index afc1ce0..7a28b78 100644 --- a/src/app/asset-calculator/asset-calculator.component.html +++ b/src/app/asset-calculator/asset-calculator.component.html @@ -4,37 +4,39 @@
-
+
{{ 'asset-calculator.depreciationCalculator' | translate }}
-
{{ 'asset-calculator.fixedAsset' | translate }}
-
+
{{ 'asset-calculator.fixedAsset' | translate }}
-
- + - @if(assetsDepreciationFormGroup.get('initialValueSet')?.invalid && assetsDepreciationFormGroup.get('initialValueSet')?.touched){ +
- +
- +
@@ -46,7 +48,7 @@
- +
@@ -57,54 +59,53 @@
-
- - @if( TypeDepreciation.digressive === assetsDepreciationFormGroup.get( 'typeDepreciation' )?.value ) { + + @if( TypeDepreciation.digressive === typeDepreciation() ) {
- +
- } - + }
+ (click)=addLifeChange() type="button" value="{{ 'asset-calculator.addChangeValue' | translate }}">
- @if( lifeFormArray.controls.length > 0 ){ + @if( lifeChangesSignal().length > 0 ){
-
{{ 'asset-calculator.valueChanges' | translate }}
+
{{ 'asset-calculator.valueChanges' | translate }}
- - - + + + - @for( changeGroup of lifeFormArray.controls; track $index ) { - + @for( life of lifeChangesSignal(); track $index ) { + - - - + + } @@ -130,7 +131,7 @@ - @for (position of amortizations; track $index) { + @for (position of amortizationsSignal(); track $index) { @@ -138,7 +139,7 @@ - @if( 12 === position.when.month || $index === amortizations.length-1){ + @if( 12 === position.when.month || $index === amortizationsSignal.length-1){ diff --git a/src/app/asset-calculator/asset-calculator.component.ts b/src/app/asset-calculator/asset-calculator.component.ts index 70257d9..031899f 100644 --- a/src/app/asset-calculator/asset-calculator.component.ts +++ b/src/app/asset-calculator/asset-calculator.component.ts @@ -1,178 +1,219 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; -import { ReactiveFormsModule, FormGroup, Validators, FormControl, FormBuilder, AbstractControl } from '@angular/forms'; -import { DecimalPipe } from '@angular/common'; - -import {Asset, AssetPlanPosition, TypeDepreciation, YearMonth, AssetLifeChange, AssetDepreciationMethod, YearMonthUtil } from '../assets/asset'; -import {AssetService} from '../assets/service/asset.service' -import {TranslateModule} from "@ngx-translate/core"; - +import { Component, signal, effect, computed } from '@angular/core'; +import { DecimalPipe} from '@angular/common'; +import { Asset, AssetPlanPosition, TypeDepreciation, YearMonth, AssetLifeChange, AssetDepreciationMethod } from '../assets/asset'; +import { AssetService } from '../assets/service/asset.service'; +import { TranslateModule, TranslateService } from "@ngx-translate/core"; +import { FormsModule } from '@angular/forms'; interface FormValues { - initialValueAsset: number; + initialValueAsset:number; rate: number; startDepreciation: string; typeDepreciation: TypeDepreciation; factor: number; } -@Component({ - selector: 'app-asset-calculator', - standalone: true, - imports: [ DecimalPipe, ReactiveFormsModule, TranslateModule ] , - templateUrl: "asset-calculator.component.html", - styleUrl: 'asset-calculator.component.css' -}) -export class AssetCalculatorComponent implements OnInit, OnDestroy{ - - TypeDepreciation = TypeDepreciation; +class AssetLifeChangeWrapper{ - lifeFormArray ; - assetsDepreciationFormGroup : FormGroup; - amortizations : AssetPlanPosition[]=[]; + when = signal ( YearMonth.todayTxt()); + initial = signal( 0 ); - constructor(private fb: FormBuilder, - private assetService : AssetService){ + get(): AssetLifeChange { + const ym = this.when(); + const when = YearMonth.from( ym ); + return new AssetLifeChange( when, this.initial() , 0, 0 ); + } - this.assetsDepreciationFormGroup = new FormGroup({ - initialValueAsset : new FormControl( 6000, [ Validators.required, this.currencyValidator ]), - rate : new FormControl( 20 ) , - startDepreciation : new FormControl( new Date().toISOString().slice(0, 7) ), - typeDepreciation : new FormControl( TypeDepreciation.linear ), - factor : new FormControl( 2/*, [Validators.required,Validators.max(2)]*/ ), - - } as { - [key in keyof FormValues]: FormControl - }); - this.lifeFormArray = this.fb.array([]); + constructor( assetLifeChange : AssetLifeChange ){ - this.assetsDepreciationFormGroup.valueChanges.subscribe( () => this.calculate() ); - this.lifeFormArray.valueChanges.subscribe( () => this.calculate() ); + this.when.set( YearMonth.toTxt( assetLifeChange.when ) ); + this.initial.set( assetLifeChange.initial ); + } - } - - - ngOnInit(): void{ - const savedAsset = localStorage.getItem('assetForCalculator'); - if (savedAsset) this.assetToControls(JSON.parse(savedAsset)); - this.calculate(); - } - - ngOnDestroy(): void { - const asset = this.controlsToAsset(); - // Zapisywanie danych przed zniszczeniem komponentu - this.saveInStorage( asset ); - } - - private saveInStorage( asset : Asset ) { - localStorage.setItem('assetForCalculator', JSON.stringify(asset)); - } - - private assetToControls( asset : Asset ) { - if( asset.life.length > 0 ){ - const year_month = YearMonthUtil.toTxt( asset.life[0].when ); - this.assetsDepreciationFormGroup.patchValue({ - year_month : year_month, - initialValueAsset : asset.life[0].initial, - rate : asset.depreciationMethods[0].rate, - typeDepreciation : asset.depreciationMethods[0].type, - factor : asset.depreciationMethods[0].factor, - }) - asset.life.slice(1).forEach( lifeChange => { - this.addChangeValue(); - const row = this.lifeFormArray.at( this.lifeFormArray.length - 1 ) as FormGroup; - row.patchValue( { - initialValueAsset: lifeChange.initial, - year_month: YearMonthUtil.toTxt( lifeChange.when ) - } ); - }); - - } - - } - - private controlsToAsset() : Asset { - - const formValues = this.assetsDepreciationFormGroup.getRawValue() as FormValues; - - const when = new YearMonth( formValues.startDepreciation); - const asset = new Asset( when ); - - const method = new AssetDepreciationMethod( when.year, formValues.rate, formValues.typeDepreciation, formValues.factor ); - asset.addMethod( method ); - - const creationlifeChange = new AssetLifeChange( when, formValues.initialValueAsset, 0, 0 ); - asset.addChange( creationlifeChange ); - - this.lifeFormArray.controls.forEach(control => { - const initialValue = control.get('initialValueAsset')?.value; - const yearMonth = control.get('year_month')?.value; - asset.addChange( new AssetLifeChange( new YearMonth(yearMonth), initialValue, 0, 0 ) ); - }) - - return asset; - } - - calculate(){ - const asset = this.controlsToAsset(); - this.saveInStorage( asset ); - this.assetService.calculate(asset).subscribe(positions => { - this.calculateToValues(positions); - this.amortizations = positions; - - }); - - } - - calculateForAsset( asset : Asset ) { - this.assetService.calculate( asset ).subscribe( - positions => { - this.calculateToValues( positions ); - this.amortizations = positions }); - - } - - calculateToValues( positions:AssetPlanPosition[] ) { - let sum = 0; - let sumThisYear = 0; - positions.forEach(position => { - position.calculatedDepreciation *= 0.01; - sum += position.calculatedDepreciation; - position.sum = sum; - - if (position.when.month === 1) { - sumThisYear = position.calculatedDepreciation; - } else { - sumThisYear += position.calculatedDepreciation; - } - position.sumThisYear = sumThisYear; - }); - } - - clazz(pos : AssetPlanPosition ){ - - return pos.when.year % 2 === 0 ? "table-light" : "table-dark"; - } - - // Walidator dla kwoty - currencyValidator(control: AbstractControl) { - const value = control.value; - const regex = /^\d+(\.\d{1,2})?$/; // Akceptuje liczby z maksymalnie dwoma miejscami po przecinku - return regex.test(value) ? null : { invalidCurrency: true }; - } - - addChangeValue() { - const formValues = this.assetsDepreciationFormGroup.value as FormValues; - - const change = new AssetLifeChange(new YearMonth( formValues.startDepreciation ), 1000, 0, 0 ); - - const newFormGroup = new FormGroup( { initialValueAsset: new FormControl( change.initial ), - year_month : new FormControl( formValues.startDepreciation )}) - - this.lifeFormArray.push(newFormGroup); - - } - - removeChange(index: number) { - - this.lifeFormArray.removeAt(index); - } } +const NAME_IN_STORAGE = 'assetForCalculator'; +@Component({ + selector: 'app-asset-calculator', + standalone: true, + imports: [DecimalPipe, TranslateModule, FormsModule], + templateUrl: "asset-calculator.component.html", + styleUrls: ['asset-calculator.component.css'] +}) +export class AssetCalculatorComponent { + + TypeDepreciation = TypeDepreciation; + + // Signals for form state and amortizations + + initialValueAsset = signal ( 6000 ); + rate = signal ( 20 ); + startDepreciation = signal ( YearMonth.today().toString() ); + typeDepreciation = signal ( TypeDepreciation.linear ); + factor = signal ( 2 ); + + formValues = computed(() => { + return { + initialValueAsset: this.initialValueAsset(), + rate: this.rate(), + startDepreciation: this.startDepreciation(), + typeDepreciation: this.typeDepreciation(), + factor: this.factor(), + }; + }); + + lifeChangesSignal = signal([]); // Replaces `lifeFormArray` + + amortizationsSignal =signal([]); + + constructor( private assetService: AssetService, + private translate : TranslateService ) { + // Effect to recalculate when form values change + effect( () => this.reCalculate( ) ) + + } + + ngOnInit(): void{ + this.restoreState(); + } + + + ngOnDestroy(): void { + const asset = this.controlsToAsset( ); + if( null != asset){ + this.saveState( asset ); + } + } + + private restoreState() { + const savedAsset = localStorage.getItem(NAME_IN_STORAGE); + if( savedAsset ) { + try { + const parsedAsset : Asset = JSON.parse(savedAsset); + this.assetToControls( parsedAsset ); + } catch (error) { + console.error('Failed to parse saved asset:', error); + localStorage.removeItem(NAME_IN_STORAGE); + } + } + } + + private saveState(asset:Asset) { + + // Zapisywanie danych przed zniszczeniem komponentu + try { + // Serialize the asset to a JSON string + localStorage.setItem(NAME_IN_STORAGE, JSON.stringify(asset) ); + + } catch (error) { + console.error('Failed to save asset:', error); + } + } + + + private assetToControls( asset : Asset ) { + if( asset.life.length > 0 ){ + this.updateBatch(asset); + } +} + + private updateBatch(asset: Asset) { + const life0 = asset.life[0]; + const method0 = asset.depreciationMethods[0]; + this.initialValueAsset.set(life0.initial); + const ymText =YearMonth.toTxt( life0.when ); + this.startDepreciation.set(ymText); + this.rate.set(method0.rate); + this.typeDepreciation.set(method0.type); + this.factor.set(method0.rate); + + console.log( asset.life ); + for( let i = 1; i < asset.life.length ; i++ ){ + const assetLifeChange = asset.life[i]; + const lifeChangeWrapper = this.addLifeChange2(assetLifeChange); + lifeChangeWrapper.initial.set( assetLifeChange.initial ); + lifeChangeWrapper.when.set(YearMonth.toTxt( assetLifeChange.when )); + }; + } + +private controlsToAsset(): Asset { + + const formValues = this.formValues(); + const lifeChanges = this.lifeChangesSignal(); + + const when = YearMonth.from( formValues.startDepreciation ); + const asset = new Asset( when ); + + const method = new AssetDepreciationMethod( when.year, formValues.rate, formValues.typeDepreciation, formValues.factor ); + asset.addMethod( method ); + + const creationLifeChange = new AssetLifeChange( when, formValues.initialValueAsset, 0, 0 ); + asset.addChange( creationLifeChange ); + + lifeChanges.forEach((lifeChange) => { + asset.addChange( lifeChange.get() ); + }); + + return asset; +} + + private calculateToValues( positions:AssetPlanPosition[] ) { + let sum = 0; + let sumThisYear = 0; + positions.forEach( position => { + // From gr to zł or from cents to dollars it depends from currency + position.calculatedDepreciation *= 0.01; + sum += position.calculatedDepreciation; + position.sum = sum; + + if( position.when.month === 1 ) { + sumThisYear = position.calculatedDepreciation; + } else { + sumThisYear += position.calculatedDepreciation; + } + position.sumThisYear = sumThisYear; + }); + } + + private reCalculate() { + + const asset = this.controlsToAsset( ); + this.calculateAmortizationsForAsset(asset); + this.saveState(asset); + + } + + private calculateAmortizationsForAsset(asset: Asset) { + this.assetService.calculate(asset).subscribe((positions) => { + this.calculateToValues(positions); + this.amortizationsSignal.set(positions); + }); + } + + + clazz(pos : AssetPlanPosition ){ + return pos.when.year % 2 === 0 ? "table-light" : "table-dark"; + } + + // Method to add a life change + addLifeChange( ) { + const assetLifeChange2 : AssetLifeChange + = new AssetLifeChange( YearMonth.from( this.formValues().startDepreciation ), 1000, 0, 0 ); + return this.addLifeChange2( assetLifeChange2 ); + + } + + addLifeChange2( assetLifeChange : AssetLifeChange ) { + const newChangeWrapper = new AssetLifeChangeWrapper( assetLifeChange ); + this.lifeChangesSignal.update((changes) => [...changes, newChangeWrapper]); + return newChangeWrapper; + } + + // Method to remove a life change + removeLifeChange(index: number) { + this.lifeChangesSignal.update((changes) => + changes.filter((_, i) => i !== index) + ); + } + +} + + diff --git a/src/app/assets/asset.ts b/src/app/assets/asset.ts index c729fc8..01d633c 100644 --- a/src/app/assets/asset.ts +++ b/src/app/assets/asset.ts @@ -5,29 +5,44 @@ export enum TypeDepreciation{ export class YearMonth{ + static from( year_month : string) { + const [year, month] = year_month.split('-').map(Number); + return new YearMonth( year, month ); + } + readonly year : number ; readonly month : number ; - constructor( year_month:string ){ - const [year, month] = year_month.split('-').map(Number); + + constructor( year:number, month : number ){ this.year = year; this.month = month; + } - } - -} - -export class YearMonthUtil{ - - static toTxt( ym : YearMonth ):string{ - return ym.year + '-' + ym.month ; - } static today( ):YearMonth{ const today = new Date(); - return new YearMonth( today.getFullYear() + '-' +today.getMonth()+1 ); + return new YearMonth( today.getFullYear() , today.getMonth()+1 ); } -} + static todayTxt( ):string{ + const today = new Date(); + return YearMonth.toTxt ( YearMonth.today()); + } + static toTxt( ym : YearMonth ):string{ + return ym.year + '-' + String(ym.month).padStart(2, '0');; + } + + toJSON() { + return { year: this.year, month: this.month }; + } + + static fromJSON(json: { year: number; month: number }): YearMonth { + return new YearMonth(json.year, json.month); + } +} + + + export class AssetLifeChange{ @@ -113,7 +128,7 @@ export class AssetsContainer{ } export class AssetPlanPosition{ - when : YearMonth = new YearMonth('0-0'); + when : YearMonth = new YearMonth(0,0); calculatedDepreciation = 0; sum = 0; sumThisYear = 0; diff --git a/src/app/fixed-asset/fixed-asset.component.ts b/src/app/fixed-asset/fixed-asset.component.ts index 59a7137..67ac633 100644 --- a/src/app/fixed-asset/fixed-asset.component.ts +++ b/src/app/fixed-asset/fixed-asset.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; -import { Asset, TypeDepreciation, YearMonthUtil, AssetLifeChange } from '../assets/asset'; +import { Asset, TypeDepreciation, YearMonth, AssetLifeChange } from '../assets/asset'; import { ReactiveFormsModule, FormBuilder, FormGroup, FormArray, ValidatorFn, ValidationErrors, AbstractControl } from '@angular/forms'; @@ -96,7 +96,7 @@ export class FixedAssetComponent { const nrInv = control.get('nrInv')?.value; const initialValue = control.get('initialValue')?.value; - const yearMonth = YearMonthUtil.today(); + const yearMonth = YearMonth.today(); const asset = new Asset( yearMonth ); const assetLifeChange = new AssetLifeChange( yearMonth, initialValue, 0, 0 ); asset.addChange( assetLifeChange );
{{ 'asset-calculator.lp' | translate }}{{ 'asset-calculator.month' | translate }}{{ 'asset-calculator.change' | translate }} {{ 'asset-calculator.lp' | translate }} {{ 'asset-calculator.month' | translate }} {{ 'asset-calculator.change' | translate }}
{{$index+1}} +
{{$index+1}} {{ position.when.year }}{{ position.calculatedDepreciation | number:'1.2-2' }} {{ position.sum | number:'1.2-2' }}