Add float & monetary custom field types

This commit is contained in:
shamoon
2023-11-04 09:47:03 -07:00
parent 4152166693
commit fd8ba7c41c
9 changed files with 75 additions and 2 deletions

View File

@@ -10,7 +10,7 @@
</div>
<div [class.col-md-9]="horizontal">
<div class="input-group" [class.is-invalid]="error">
<input #inputField type="number" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error" [disabled]="disabled">
<input #inputField type="number" class="form-control" [step]="step" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error" [disabled]="disabled">
<button *ngIf="showAdd" class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="disabled">+1</button>
</div>
<div class="invalid-feedback">

View File

@@ -46,4 +46,18 @@ describe('NumberComponent', () => {
component.nextAsn()
expect(component.value).toEqual(1002)
})
it('should support float & monetary values', () => {
component.writeValue(11.13)
expect(component.value).toEqual(11)
component.step = 0.01
component.writeValue(11.1)
expect(component.value).toEqual('11.10')
component.step = 0.1
component.writeValue(12.3456)
expect(component.value).toEqual(12.3456)
// float (step = .1) doesnt force 2 decimals
component.writeValue(11.1)
expect(component.value).toEqual(11.1)
})
})

View File

@@ -19,6 +19,9 @@ export class NumberComponent extends AbstractInputComponent<number> {
@Input()
showAdd: boolean = true
@Input()
step: number = 1
constructor(private documentService: DocumentService) {
super()
}
@@ -32,4 +35,10 @@ export class NumberComponent extends AbstractInputComponent<number> {
this.onChange(this.value)
})
}
writeValue(newValue: any): void {
if (this.step === 1) newValue = parseInt(newValue, 10)
if (this.step === 0.01) newValue = parseFloat(newValue).toFixed(2)
super.writeValue(newValue)
}
}

View File

@@ -117,6 +117,8 @@
<pngx-input-text formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.String" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-text>
<pngx-input-date formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Date" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-date>
<pngx-input-number formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Integer" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false"></pngx-input-number>
<pngx-input-number formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Float" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false" [step]=".1"></pngx-input-number>
<pngx-input-number formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Monetary" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false" [step]=".01"></pngx-input-number>
<pngx-input-check formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Boolean" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-check>
<pngx-input-url formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Url" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-url>
</div>

View File

@@ -6,6 +6,8 @@ export enum PaperlessCustomFieldDataType {
Date = 'date',
Boolean = 'boolean',
Integer = 'integer',
Float = 'float',
Monetary = 'monetary',
}
export const DATA_TYPE_LABELS = [
@@ -19,8 +21,16 @@ export const DATA_TYPE_LABELS = [
},
{
id: PaperlessCustomFieldDataType.Integer,
name: $localize`Integer`,
},
{
id: PaperlessCustomFieldDataType.Float,
name: $localize`Number`,
},
{
id: PaperlessCustomFieldDataType.Monetary,
name: $localize`Monetary`,
},
{
id: PaperlessCustomFieldDataType.String,
name: $localize`String`,

View File

@@ -43,6 +43,8 @@ class Migration(migrations.Migration):
("date", "Date"),
("boolean", "Boolean"),
("integer", "Integer"),
("float", "Float"),
("monetary", "Monetary"),
],
editable=False,
max_length=50,
@@ -82,6 +84,11 @@ class Migration(migrations.Migration):
("value_url", models.URLField(null=True)),
("value_date", models.DateField(null=True)),
("value_int", models.IntegerField(null=True)),
("value_float", models.FloatField(null=True)),
(
"value_monetary",
models.DecimalField(decimal_places=2, max_digits=12, null=True),
),
(
"document",
models.ForeignKey(

View File

@@ -888,6 +888,8 @@ class CustomField(models.Model):
DATE = ("date", _("Date"))
BOOL = ("boolean"), _("Boolean")
INT = ("integer", _("Integer"))
FLOAT = ("float", _("Float"))
MONETARY = ("monetary", _("Monetary"))
created = models.DateTimeField(
_("created"),
@@ -962,6 +964,10 @@ class CustomFieldInstance(models.Model):
value_int = models.IntegerField(null=True)
value_float = models.FloatField(null=True)
value_monetary = models.DecimalField(null=True, decimal_places=2, max_digits=12)
class Meta:
ordering = ("created",)
verbose_name = _("custom field instance")
@@ -992,6 +998,10 @@ class CustomFieldInstance(models.Model):
return self.value_bool
elif self.field.data_type == CustomField.FieldDataType.INT:
return self.value_int
elif self.field.data_type == CustomField.FieldDataType.FLOAT:
return self.value_float
elif self.field.data_type == CustomField.FieldDataType.MONETARY:
return self.value_monetary
raise NotImplementedError(self.field.data_type)

View File

@@ -437,6 +437,8 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer):
CustomField.FieldDataType.DATE: "value_date",
CustomField.FieldDataType.BOOL: "value_bool",
CustomField.FieldDataType.INT: "value_int",
CustomField.FieldDataType.FLOAT: "value_float",
CustomField.FieldDataType.MONETARY: "value_monetary",
}
# An instance is attached to a document
document: Document = validated_data["document"]

View File

@@ -34,6 +34,7 @@ class TestCustomField(DirectoriesMixin, APITestCase):
("date", "Invoiced Date"),
("integer", "Invoice #"),
("boolean", "Is Active"),
("float", "Total Paid"),
]:
resp = self.client.post(
self.ENDPOINT,
@@ -87,6 +88,14 @@ class TestCustomField(DirectoriesMixin, APITestCase):
name="Test Custom Field Url",
data_type=CustomField.FieldDataType.URL,
)
custom_field_float = CustomField.objects.create(
name="Test Custom Field Float",
data_type=CustomField.FieldDataType.FLOAT,
)
custom_field_monetary = CustomField.objects.create(
name="Test Custom Field Monetary",
data_type=CustomField.FieldDataType.MONETARY,
)
date_value = date.today()
@@ -114,6 +123,14 @@ class TestCustomField(DirectoriesMixin, APITestCase):
"field": custom_field_url.id,
"value": "https://example.com",
},
{
"field": custom_field_float.id,
"value": 12.3456,
},
{
"field": custom_field_monetary.id,
"value": 11.10,
},
],
},
format="json",
@@ -131,11 +148,13 @@ class TestCustomField(DirectoriesMixin, APITestCase):
{"field": custom_field_int.id, "value": 3},
{"field": custom_field_boolean.id, "value": True},
{"field": custom_field_url.id, "value": "https://example.com"},
{"field": custom_field_float.id, "value": 12.3456},
{"field": custom_field_monetary.id, "value": 11.10},
],
)
doc.refresh_from_db()
self.assertEqual(len(doc.custom_fields.all()), 5)
self.assertEqual(len(doc.custom_fields.all()), 7)
def test_change_custom_field_instance_value(self):
"""