mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-04 03:27:12 -05:00 
			
		
		
		
	Enhancement: refactor monetary field (#6370)
This commit is contained in:
		
							parent
							
								
									78f338484f
								
							
						
					
					
						commit
						95fd1ae879
					
				@ -12,9 +12,9 @@
 | 
				
			|||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div class="position-relative" [class.col-md-9]="horizontal">
 | 
					    <div class="position-relative" [class.col-md-9]="horizontal">
 | 
				
			||||||
      <div class="input-group" [class.is-invalid]="error">
 | 
					      <div class="input-group" [class.is-invalid]="error">
 | 
				
			||||||
        <span class="input-group-text fw-bold bg-light">{{monetaryValue | currency: currencyCode }}</span>
 | 
					        <span class="input-group-text fw-bold bg-light">{{ monetaryValue | currency: currency }}</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 #currencyField class="form-control text-muted mw-60" [(ngModel)]="currency" (input)="currencyChange()" maxlength="3" [class.is-invalid]="error" [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">
 | 
					        <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>
 | 
				
			||||||
      <div class="invalid-feedback position-absolute top-100">
 | 
					      <div class="invalid-feedback position-absolute top-100">
 | 
				
			||||||
        {{error}}
 | 
					        {{error}}
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,6 @@ import { MonetaryComponent } from './monetary.component'
 | 
				
			|||||||
describe('MonetaryComponent', () => {
 | 
					describe('MonetaryComponent', () => {
 | 
				
			||||||
  let component: MonetaryComponent
 | 
					  let component: MonetaryComponent
 | 
				
			||||||
  let fixture: ComponentFixture<MonetaryComponent>
 | 
					  let fixture: ComponentFixture<MonetaryComponent>
 | 
				
			||||||
  let input: HTMLInputElement
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(async () => {
 | 
					  beforeEach(async () => {
 | 
				
			||||||
    await TestBed.configureTestingModule({
 | 
					    await TestBed.configureTestingModule({
 | 
				
			||||||
@ -24,37 +23,22 @@ describe('MonetaryComponent', () => {
 | 
				
			|||||||
    fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
 | 
					    fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
 | 
				
			||||||
    component = fixture.componentInstance
 | 
					    component = fixture.componentInstance
 | 
				
			||||||
    fixture.detectChanges()
 | 
					    fixture.detectChanges()
 | 
				
			||||||
    input = component.inputField.nativeElement
 | 
					 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should set the currency code correctly', () => {
 | 
					  it('should set the currency code and monetary value correctly', () => {
 | 
				
			||||||
    expect(component.currencyCode).toEqual('USD') // default
 | 
					    expect(component.currency).toEqual('USD') // default
 | 
				
			||||||
    component.currencyCode = 'EUR'
 | 
					    component.writeValue('G123.4')
 | 
				
			||||||
    expect(component.currencyCode).toEqual('EUR')
 | 
					    expect(component.currency).toEqual('G')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    component.value = 'G123.4'
 | 
					    component.writeValue('EUR123.4')
 | 
				
			||||||
    jest
 | 
					    expect(component.currency).toEqual('EUR')
 | 
				
			||||||
      .spyOn(document, 'activeElement', 'get')
 | 
					    expect(component.monetaryValue).toEqual('123.40')
 | 
				
			||||||
      .mockReturnValue(component.currencyField.nativeElement)
 | 
					 | 
				
			||||||
    expect(component.currencyCode).toEqual('G')
 | 
					 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should parse monetary value only when out of focus', () => {
 | 
					  it('should set monetary value to fixed decimals', () => {
 | 
				
			||||||
    component.monetaryValue = 10.5
 | 
					    component.monetaryValue = '10.5'
 | 
				
			||||||
    jest.spyOn(document, 'activeElement', 'get').mockReturnValue(null)
 | 
					    component.monetaryValueChange(true)
 | 
				
			||||||
    expect(component.monetaryValue).toEqual('10.50')
 | 
					    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', () => {
 | 
					  it('should set the default currency code based on LOCALE_ID', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,4 @@
 | 
				
			|||||||
import {
 | 
					import { Component, forwardRef, Inject, LOCALE_ID } from '@angular/core'
 | 
				
			||||||
  Component,
 | 
					 | 
				
			||||||
  ElementRef,
 | 
					 | 
				
			||||||
  forwardRef,
 | 
					 | 
				
			||||||
  Inject,
 | 
					 | 
				
			||||||
  LOCALE_ID,
 | 
					 | 
				
			||||||
  ViewChild,
 | 
					 | 
				
			||||||
} from '@angular/core'
 | 
					 | 
				
			||||||
import { NG_VALUE_ACCESSOR } from '@angular/forms'
 | 
					import { NG_VALUE_ACCESSOR } from '@angular/forms'
 | 
				
			||||||
import { AbstractInputComponent } from '../abstract-input'
 | 
					import { AbstractInputComponent } from '../abstract-input'
 | 
				
			||||||
import { getLocaleCurrencyCode } from '@angular/common'
 | 
					import { getLocaleCurrencyCode } from '@angular/common'
 | 
				
			||||||
@ -23,40 +16,47 @@ import { getLocaleCurrencyCode } from '@angular/common'
 | 
				
			|||||||
  styleUrls: ['./monetary.component.scss'],
 | 
					  styleUrls: ['./monetary.component.scss'],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class MonetaryComponent extends AbstractInputComponent<string> {
 | 
					export class MonetaryComponent extends AbstractInputComponent<string> {
 | 
				
			||||||
  @ViewChild('currencyField')
 | 
					  public currency: string = ''
 | 
				
			||||||
  currencyField: ElementRef
 | 
					  public monetaryValue: string = ''
 | 
				
			||||||
  defaultCurrencyCode: string
 | 
					  defaultCurrencyCode: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(@Inject(LOCALE_ID) currentLocale: string) {
 | 
					  constructor(@Inject(LOCALE_ID) currentLocale: string) {
 | 
				
			||||||
    super()
 | 
					    super()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.defaultCurrencyCode = getLocaleCurrencyCode(currentLocale)
 | 
					    this.currency = this.defaultCurrencyCode =
 | 
				
			||||||
 | 
					      getLocaleCurrencyCode(currentLocale)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get currencyCode(): string {
 | 
					  writeValue(newValue: any): void {
 | 
				
			||||||
    const focused = document.activeElement === this.currencyField?.nativeElement
 | 
					    this.currency = this.parseCurrencyCode(newValue)
 | 
				
			||||||
    if (focused && this.value)
 | 
					    this.monetaryValue = this.parseMonetaryValue(newValue, true)
 | 
				
			||||||
      return this.value.toUpperCase().match(/^([A-Z]{0,3})/)?.[0]
 | 
					
 | 
				
			||||||
 | 
					    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 (
 | 
					    return (
 | 
				
			||||||
      this.value
 | 
					      value
 | 
				
			||||||
        ?.toString()
 | 
					        ?.toString()
 | 
				
			||||||
        .toUpperCase()
 | 
					        .toUpperCase()
 | 
				
			||||||
        .match(/^([A-Z]{1,3})/)?.[0] ?? this.defaultCurrencyCode
 | 
					        .match(/^([A-Z]{1,3})/)?.[0] ?? this.defaultCurrencyCode
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  set currencyCode(value: string) {
 | 
					  private parseMonetaryValue(value: string, fixed: boolean = false): string {
 | 
				
			||||||
    this.value = value.toUpperCase() + this.monetaryValue?.toString()
 | 
					    const val: number = parseFloat(value.toString().replace(/[^0-9.,-]+/g, ''))
 | 
				
			||||||
  }
 | 
					    return fixed ? val.toFixed(2) : val.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)
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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)"
 | 
					              <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>
 | 
					              (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>
 | 
					              <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]">
 | 
					                <div [formGroup]="customFieldFormFields.controls[i]">
 | 
				
			||||||
                  @switch (getCustomFieldFromInstance(fieldInstance)?.data_type) {
 | 
					                  @switch (getCustomFieldFromInstance(fieldInstance)?.data_type) {
 | 
				
			||||||
                    @case (CustomFieldDataType.String) {
 | 
					                    @case (CustomFieldDataType.String) {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user