diff --git a/src/app/asset-calculator/asset-calculator.component.ts b/src/app/asset-calculator/asset-calculator.component.ts index 72b5bf1..8b8fb00 100755 --- a/src/app/asset-calculator/asset-calculator.component.ts +++ b/src/app/asset-calculator/asset-calculator.component.ts @@ -7,233 +7,172 @@ import { FormsModule } from '@angular/forms'; import { MatDatepicker, MatDatepickerModule } from '@angular/material/datepicker'; import { MatNativeDateModule } from '@angular/material/core'; import { MatInputModule } from '@angular/material/input'; -import { format, parse, addMonths, isAfter } from 'date-fns'; +import { format, parse } from 'date-fns'; +import { createHash } from 'crypto'; -interface FormValues { - initialValueAsset:number; - rate: number; - startDepreciation: Date; - typeDepreciation: TypeDepreciation; - factor: number; -} -class AssetLifeChangeWrapper{ +class AssetLifeChangeWrapper { + when = signal(YearMonth.todayTxt()); + initial = signal(0); - when = signal ( YearMonth.todayTxt()); - initial = signal( 0 ); - - constructor( assetLifeChange : AssetLifeChange ){ + constructor(assetLifeChange: AssetLifeChange) { this.set(assetLifeChange); - } + } get(): AssetLifeChange { - const ym = this.when(); - const when = YearMonth.from( ym ); - return new AssetLifeChange( when, this.initial() , 0, 0 ); + return new AssetLifeChange(YearMonth.from(this.when()), this.initial(), 0, 0); } + set(assetLifeChange: AssetLifeChange) { this.when.set(YearMonth.toTxt(assetLifeChange.when)); this.initial.set(assetLifeChange.initial); } - } -const NAME_IN_STORAGE = 'assetForCalculator'; + +const STORAGE_KEY = 'assetForCalculator'; +const CACHE_KEY = 'cachedResults'; +const HASH_KEY = 'assetHash'; @Component({ selector: 'app-asset-calculator', standalone: true, - imports: [DecimalPipe, TranslateModule, FormsModule, MatDatepickerModule, MatNativeDateModule, MatInputModule], - providers: [ - MatDatepickerModule, - MatNativeDateModule - ], + imports: [DecimalPipe, TranslateModule, FormsModule, MatDatepickerModule, MatNativeDateModule, MatInputModule], + providers: [MatDatepickerModule, MatNativeDateModule], 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(new Date()); + typeDepreciation = signal(TypeDepreciation.linear); + factor = signal(2); + lifeChangesSignal = signal([]); + amortizationsSignal = signal([]); - initialValueAsset = signal ( 6000 ); - rate = signal ( 20 ); - startDepreciation = signal ( new Date() ); - typeDepreciation = signal ( TypeDepreciation.linear ); - factor = signal ( 2 ); + constructor(private assetService: AssetService, private translate: TranslateService) {} - 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( ) ) - - } - - - private storage = { - load: (key: string) => { - try { - const data = localStorage.getItem(key); - return data ? JSON.parse(data) : null; - } catch { - console.error(`Failed to load ${key} from localStorage`); - return null; - } - }, - save: (key: string, data: any) => { - try { - localStorage.setItem(key, JSON.stringify(data)); - } catch { - console.error(`Failed to save ${key} to localStorage`); - } - } - }; - - ngOnInit(): void{ + ngOnInit(): void { this.restoreState(); } ngOnDestroy(): void { - const asset = this.controlsToAsset( ); - if( null != asset){ - this.saveState( asset ); - } + this.saveState(this.controlsToAsset()); } private restoreState() { - const savedAsset = this.storage.load(NAME_IN_STORAGE); - if (savedAsset) this.assetToControls(savedAsset); + const savedAsset = localStorage.getItem(STORAGE_KEY); + const savedResults = localStorage.getItem(CACHE_KEY); + const savedHash = localStorage.getItem(HASH_KEY); + + if (savedAsset) { + const asset: Asset = JSON.parse(savedAsset); + this.assetToControls(asset); + + // Generujemy nowy hash + const newHash = this.generateHash(asset); + + // Jeśli hash jest taki sam, ładujemy wyniki z cache + if (savedHash === newHash && savedResults) { + this.amortizationsSignal.set(JSON.parse(savedResults)); + console.log("✅ Załadowano wyniki z cache – brak przeliczeń."); + return; + } + + console.log("🔄 Hash zmieniony – wykonuję przeliczenie."); + this.reCalculate(); + } } private saveState(asset: Asset) { - this.storage.save(NAME_IN_STORAGE, asset); + localStorage.setItem(STORAGE_KEY, JSON.stringify(asset)); + localStorage.setItem(HASH_KEY, this.generateHash(asset)); // Zapisujemy nowy hash } - private assetToControls( asset : Asset ) { - if( asset.life.length > 0 ){ - this.updateBatch(asset); - } -} + private assetToControls(asset: Asset) { + if (!asset.life.length) return; + const [firstLife, ...restLives] = asset.life; + const method = asset.depreciationMethods[0]; -private updateBatch(asset: Asset) { - const [firstLife, ...restLives] = asset.life; - const method0 = asset.depreciationMethods[0]; + this.initialValueAsset.set(firstLife.initial); + this.startDepreciation.set(YearMonth.toDate(firstLife.when)); + this.rate.set(method.rate); + this.typeDepreciation.set(method.type); + this.factor.set(method.factor); + this.lifeChangesSignal.set(restLives.map(life => new AssetLifeChangeWrapper(life))); + } - this.initialValueAsset.set(firstLife.initial); - this.startDepreciation.set(YearMonth.toDate(firstLife.when)); - this.rate.set(method0.rate); - this.typeDepreciation.set(method0.type); - this.factor.set(method0.factor); + private controlsToAsset(): Asset { + const when = YearMonth.fromDate(this.startDepreciation()); + const asset = new Asset(when); + const method = new AssetDepreciationMethod(when.year, this.rate(), this.typeDepreciation(), this.factor()); - const lifeChanges = restLives.map((lifeChange) => - new AssetLifeChangeWrapper(lifeChange) - ); - this.lifeChangesSignal.set(lifeChanges); -} + asset.addMethod(method); + asset.addChange(new AssetLifeChange(when, this.initialValueAsset(), 0, 0)); + this.lifeChangesSignal().forEach(lc => asset.addChange(lc.get())); -private controlsToAsset(): Asset { - - const formValues = this.formValues(); - const lifeChanges = this.lifeChangesSignal(); + return asset; + } - const when = YearMonth.fromDate( formValues.startDepreciation ); - const asset = new Asset( when ); + private calculateToValues(positions: AssetPlanPosition[]) { + let sum = 0, sumThisYear = 0; - const method = new AssetDepreciationMethod( when.year, formValues.rate, formValues.typeDepreciation, formValues.factor ); - asset.addMethod( method ); + positions.forEach(pos => { + pos.calculatedDepreciation *= 0.01; + sum += pos.calculatedDepreciation; + pos.sum = sum; + sumThisYear = pos.when.month === 1 ? pos.calculatedDepreciation : sumThisYear + pos.calculatedDepreciation; + pos.sumThisYear = sumThisYear; + }); - 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; - }); - } + // Cache wyników + localStorage.setItem(CACHE_KEY, JSON.stringify(positions)); + } private reCalculate() { - - const asset = this.controlsToAsset( ); - this.calculateAmortizationsForAsset(asset); - this.saveState(asset); - + this.calculateAmortizationsForAsset(this.controlsToAsset()); } private calculateAmortizationsForAsset(asset: Asset) { - this.assetService.calculate(asset).subscribe((positions) => { + 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"; + 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.fromDate( this.formValues().startDepreciation ), 1000, 0, 0 ); - return this.addLifeChange2( assetLifeChange2 ); - + addLifeChange(amount = 1000) { + const change = new AssetLifeChange(YearMonth.fromDate(this.startDepreciation()), amount, 0, 0); + this.lifeChangesSignal.update(changes => [...changes, new AssetLifeChangeWrapper(change)]); } - 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) - ); + this.lifeChangesSignal.update(changes => changes.filter((_, i) => i !== index)); } updateDateFromInput(inputValue: string) { - // Konwersja stringa 'YYYY-MM' na Date - const parsedDate = parse(inputValue, 'yyyy-MM', new Date()); - this.startDepreciation.set(parsedDate); + this.startDepreciation.set(parse(inputValue, 'yyyy-MM', new Date())); } - onMonthSelected(event: Date, datepicker: MatDatepicker) { - this.startDepreciation.set(event); // Aktualizacja pola w formularzu + + onMonthSelected(event: Date, datepicker: any) { + this.startDepreciation.set(event); datepicker.close(); } -} + private generateHash(asset: Asset): string { + const data = JSON.stringify({ + initialValue: asset.life[0]?.initial, + rate: asset.depreciationMethods[0]?.rate, + type: asset.depreciationMethods[0]?.type, + factor: asset.depreciationMethods[0]?.factor, + lifeChanges: asset.life.map(lc => ({ when: lc.when, initial: lc.initial })) + }); + return createHash('sha256').update(data).digest('hex'); + } +} \ No newline at end of file diff --git a/src/app/fixed-asset/fixed-asset.component.ts b/src/app/fixed-asset/fixed-asset.component.ts index 612c664..9a6b2f4 100755 --- a/src/app/fixed-asset/fixed-asset.component.ts +++ b/src/app/fixed-asset/fixed-asset.component.ts @@ -93,11 +93,12 @@ export class FixedAssetComponent { private controlsToAssets(): [string, Asset][] { const assets = new Map(); + const today = YearMonth.today(); this.assetFormArray.controls.forEach( control => { const nrInv = control.get('nrInv')?.value; const initialValue = control.get('initialValue')?.value; - const yearMonth = YearMonth.today(); + const yearMonth = today; const asset = new Asset( yearMonth ); const assetLifeChange = new AssetLifeChange( yearMonth, initialValue, 0, 0 ); asset.addChange( assetLifeChange ); @@ -117,11 +118,11 @@ export class FixedAssetComponent { this.addRow( "NrInv" + FixedAssetComponent.indexNr++, 1000 ); } - addRow( nrInv_:string, initial_: number ):void{ + addRow( nrInv:string, initial: number ):void{ const newRow = this.fb.group({ - nrInv : [ nrInv_ ], - initialValue : [ initial_ ], + nrInv : [ nrInv ], + initialValue : [ initial ], }); // typeDepreciation : [ TypeDepreciation.linear ],