mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-01 19:17:15 -04:00 
			
		
		
		
	Enhancement: refactor monetary field (#6370)
This commit is contained in:
		
							parent
							
								
									78f338484f
								
							
						
					
					
						commit
						95fd1ae879
					
				| @ -12,9 +12,9 @@ | ||||
|     </div> | ||||
|     <div class="position-relative" [class.col-md-9]="horizontal"> | ||||
|       <div class="input-group" [class.is-invalid]="error"> | ||||
|         <span class="input-group-text fw-bold bg-light">{{monetaryValue | currency: currencyCode }}</span> | ||||
|         <input #currencyField class="form-control text-muted mw-60" tabindex="0" [(ngModel)]="currencyCode" maxlength="3" [class.is-invalid]="error" (change)="onChange(value)" [disabled]="disabled"> | ||||
|         <input #inputField type="number" tabindex="0" class="form-control text-muted" step=".01" [id]="inputId" [(ngModel)]="monetaryValue" (change)="onChange(value)" [class.is-invalid]="error" [disabled]="disabled"> | ||||
|         <span class="input-group-text fw-bold bg-light">{{ monetaryValue | currency: currency }}</span> | ||||
|         <input #currencyField class="form-control text-muted mw-60" [(ngModel)]="currency" (input)="currencyChange()" maxlength="3" [class.is-invalid]="error" [disabled]="disabled"> | ||||
|         <input #monetaryValueField type="number" class="form-control text-muted" step=".01" [(ngModel)]="monetaryValue" (input)="monetaryValueChange()" (change)="monetaryValueChange(true)" [class.is-invalid]="error" [disabled]="disabled"> | ||||
|       </div> | ||||
|       <div class="invalid-feedback position-absolute top-100"> | ||||
|         {{error}} | ||||
|  | ||||
| @ -11,7 +11,6 @@ import { MonetaryComponent } from './monetary.component' | ||||
| describe('MonetaryComponent', () => { | ||||
|   let component: MonetaryComponent | ||||
|   let fixture: ComponentFixture<MonetaryComponent> | ||||
|   let input: HTMLInputElement | ||||
| 
 | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
| @ -24,37 +23,22 @@ describe('MonetaryComponent', () => { | ||||
|     fixture.debugElement.injector.get(NG_VALUE_ACCESSOR) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|     input = component.inputField.nativeElement | ||||
|   }) | ||||
| 
 | ||||
|   it('should set the currency code correctly', () => { | ||||
|     expect(component.currencyCode).toEqual('USD') // default
 | ||||
|     component.currencyCode = 'EUR' | ||||
|     expect(component.currencyCode).toEqual('EUR') | ||||
|   it('should set the currency code and monetary value correctly', () => { | ||||
|     expect(component.currency).toEqual('USD') // default
 | ||||
|     component.writeValue('G123.4') | ||||
|     expect(component.currency).toEqual('G') | ||||
| 
 | ||||
|     component.value = 'G123.4' | ||||
|     jest | ||||
|       .spyOn(document, 'activeElement', 'get') | ||||
|       .mockReturnValue(component.currencyField.nativeElement) | ||||
|     expect(component.currencyCode).toEqual('G') | ||||
|     component.writeValue('EUR123.4') | ||||
|     expect(component.currency).toEqual('EUR') | ||||
|     expect(component.monetaryValue).toEqual('123.40') | ||||
|   }) | ||||
| 
 | ||||
|   it('should parse monetary value only when out of focus', () => { | ||||
|     component.monetaryValue = 10.5 | ||||
|     jest.spyOn(document, 'activeElement', 'get').mockReturnValue(null) | ||||
|   it('should set monetary value to fixed decimals', () => { | ||||
|     component.monetaryValue = '10.5' | ||||
|     component.monetaryValueChange(true) | ||||
|     expect(component.monetaryValue).toEqual('10.50') | ||||
| 
 | ||||
|     component.value = 'GBP123.4' | ||||
|     jest | ||||
|       .spyOn(document, 'activeElement', 'get') | ||||
|       .mockReturnValue(component.inputField.nativeElement) | ||||
|     expect(component.monetaryValue).toEqual('123.4') | ||||
|   }) | ||||
| 
 | ||||
|   it('should report value including currency code and monetary value', () => { | ||||
|     component.currencyCode = 'EUR' | ||||
|     component.monetaryValue = 10.5 | ||||
|     expect(component.value).toEqual('EUR10.50') | ||||
|   }) | ||||
| 
 | ||||
|   it('should set the default currency code based on LOCALE_ID', () => { | ||||
|  | ||||
| @ -1,11 +1,4 @@ | ||||
| import { | ||||
|   Component, | ||||
|   ElementRef, | ||||
|   forwardRef, | ||||
|   Inject, | ||||
|   LOCALE_ID, | ||||
|   ViewChild, | ||||
| } from '@angular/core' | ||||
| import { Component, forwardRef, Inject, LOCALE_ID } from '@angular/core' | ||||
| import { NG_VALUE_ACCESSOR } from '@angular/forms' | ||||
| import { AbstractInputComponent } from '../abstract-input' | ||||
| import { getLocaleCurrencyCode } from '@angular/common' | ||||
| @ -23,40 +16,47 @@ import { getLocaleCurrencyCode } from '@angular/common' | ||||
|   styleUrls: ['./monetary.component.scss'], | ||||
| }) | ||||
| export class MonetaryComponent extends AbstractInputComponent<string> { | ||||
|   @ViewChild('currencyField') | ||||
|   currencyField: ElementRef | ||||
|   public currency: string = '' | ||||
|   public monetaryValue: string = '' | ||||
|   defaultCurrencyCode: string | ||||
| 
 | ||||
|   constructor(@Inject(LOCALE_ID) currentLocale: string) { | ||||
|     super() | ||||
| 
 | ||||
|     this.defaultCurrencyCode = getLocaleCurrencyCode(currentLocale) | ||||
|     this.currency = this.defaultCurrencyCode = | ||||
|       getLocaleCurrencyCode(currentLocale) | ||||
|   } | ||||
| 
 | ||||
|   get currencyCode(): string { | ||||
|     const focused = document.activeElement === this.currencyField?.nativeElement | ||||
|     if (focused && this.value) | ||||
|       return this.value.toUpperCase().match(/^([A-Z]{0,3})/)?.[0] | ||||
|   writeValue(newValue: any): void { | ||||
|     this.currency = this.parseCurrencyCode(newValue) | ||||
|     this.monetaryValue = this.parseMonetaryValue(newValue, true) | ||||
| 
 | ||||
|     this.value = this.currency + this.monetaryValue | ||||
|   } | ||||
| 
 | ||||
|   public monetaryValueChange(fixed: boolean = false): void { | ||||
|     this.monetaryValue = this.parseMonetaryValue(this.monetaryValue, fixed) | ||||
|     this.onChange(this.currency + this.monetaryValue) | ||||
|   } | ||||
| 
 | ||||
|   public currencyChange(): void { | ||||
|     if (this.currency.length) { | ||||
|       this.currency = this.parseCurrencyCode(this.currency) | ||||
|       this.onChange(this.currency + this.monetaryValue?.toString()) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private parseCurrencyCode(value: string): string { | ||||
|     return ( | ||||
|       this.value | ||||
|       value | ||||
|         ?.toString() | ||||
|         .toUpperCase() | ||||
|         .match(/^([A-Z]{1,3})/)?.[0] ?? this.defaultCurrencyCode | ||||
|     ) | ||||
|   } | ||||
| 
 | ||||
|   set currencyCode(value: string) { | ||||
|     this.value = value.toUpperCase() + this.monetaryValue?.toString() | ||||
|   } | ||||
| 
 | ||||
|   get monetaryValue(): string { | ||||
|     if (!this.value) return null | ||||
|     const focused = document.activeElement === this.inputField?.nativeElement | ||||
|     const val = parseFloat(this.value.toString().replace(/[^0-9.,-]+/g, '')) | ||||
|     return focused ? val.toString() : val.toFixed(2) | ||||
|   } | ||||
| 
 | ||||
|   set monetaryValue(value: number) { | ||||
|     this.value = this.currencyCode + value.toFixed(2) | ||||
|   private parseMonetaryValue(value: string, fixed: boolean = false): string { | ||||
|     const val: number = parseFloat(value.toString().replace(/[^0-9.,-]+/g, '')) | ||||
|     return fixed ? val.toFixed(2) : val.toString() | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -112,7 +112,7 @@ | ||||
|               <pngx-input-select [items]="storagePaths" i18n-title title="Storage path" formControlName="storage_path" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" | ||||
|               (createNew)="createStoragePath($event)" [suggestions]="suggestions?.storage_paths" i18n-placeholder placeholder="Default" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }"></pngx-input-select> | ||||
|               <pngx-input-tags formControlName="tags" [suggestions]="suggestions?.tags" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }"></pngx-input-tags> | ||||
|               @for (fieldInstance of document?.custom_fields; track fieldInstance; let i = $index) { | ||||
|               @for (fieldInstance of document?.custom_fields; track fieldInstance.field; let i = $index) { | ||||
|                 <div [formGroup]="customFieldFormFields.controls[i]"> | ||||
|                   @switch (getCustomFieldFromInstance(fieldInstance)?.data_type) { | ||||
|                     @case (CustomFieldDataType.String) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user