From bb04702426ec46b64a4f9e6838398952f9ae9e42 Mon Sep 17 00:00:00 2001 From: Matthew McKenzie Date: Thu, 23 Jan 2025 16:33:18 +0000 Subject: [PATCH 1/3] Duty sentence parser return Decimal --- measures/duty_sentence_parser.py | 3 ++- measures/tests/test_lark_parser.py | 15 ++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/measures/duty_sentence_parser.py b/measures/duty_sentence_parser.py index d3d29a0ac..dc2845447 100644 --- a/measures/duty_sentence_parser.py +++ b/measures/duty_sentence_parser.py @@ -1,4 +1,5 @@ from datetime import datetime +from decimal import Decimal from typing import Sequence from django.core.exceptions import ObjectDoesNotExist @@ -460,7 +461,7 @@ def expr_amount_permitted(self, value): def duty_amount(self, value): (value,) = value - return ("duty_amount", float(value)) + return ("duty_amount", Decimal(value)) def monetary_unit(self, value): (value,) = value diff --git a/measures/tests/test_lark_parser.py b/measures/tests/test_lark_parser.py index b5dd28d1e..f0ccc8ef7 100644 --- a/measures/tests/test_lark_parser.py +++ b/measures/tests/test_lark_parser.py @@ -1,3 +1,4 @@ +from decimal import Decimal from typing import Dict from typing import List from typing import Tuple @@ -45,7 +46,7 @@ ["1.230", "EUR", "kg"], [ { - "duty_amount": 1.23, + "duty_amount": Decimal("1.23"), "duty_expression": PERCENT_OR_AMOUNT_FIXTURE_NAME, "monetary_unit": EURO_FIXTURE_NAME, "measurement_unit": KILOGRAM_FIXTURE_NAME, @@ -57,16 +58,16 @@ ["9.10", "%", "MAX", "1.00", "%", "+", "0.90", "GBP", "100 kg"], [ { - "duty_amount": 9.1, + "duty_amount": Decimal("9.1"), "duty_expression": PERCENT_OR_AMOUNT_FIXTURE_NAME, }, { "duty_expression_sid": MAXIMUM_CLAUSE_SID[0], - "duty_amount": 1.0, + "duty_amount": Decimal("1.0"), }, { "duty_expression_sid": PLUS_PERCENT_OR_AMOUNT_SID[1], - "duty_amount": 0.9, + "duty_amount": Decimal("0.9"), "monetary_unit": BRITISH_POUND_FIXTURE_NAME, "measurement_unit": HECTOKILOGRAM_FIXTURE_NAME, }, @@ -77,7 +78,7 @@ ["0.300", "XEM", "100 kg", "lactic."], [ { - "duty_amount": 0.3, + "duty_amount": Decimal("0.3"), "duty_expression": PERCENT_OR_AMOUNT_FIXTURE_NAME, "measurement_unit": HECTOKILOGRAM_FIXTURE_NAME, "measurement_unit_qualifier": LACTIC_MATTER_FIXTURE_NAME, @@ -91,10 +92,10 @@ [ { "duty_expression": PERCENT_OR_AMOUNT_FIXTURE_NAME, - "duty_amount": 12.9, + "duty_amount": Decimal("12.9"), }, { - "duty_amount": 20.0, + "duty_amount": Decimal("20.0"), "duty_expression_sid": PLUS_PERCENT_OR_AMOUNT_SID[0], "measurement_unit": KILOGRAM_FIXTURE_NAME, "monetary_unit": EURO_FIXTURE_NAME, From 4b14d4aa14bef4f00f5ba6a619fcb3132a561f29 Mon Sep 17 00:00:00 2001 From: Matthew McKenzie Date: Thu, 23 Jan 2025 17:00:43 +0000 Subject: [PATCH 2/3] Validate decimal places and return error --- measures/forms/mixins.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/measures/forms/mixins.py b/measures/forms/mixins.py index 6bf3a2acc..2494ae996 100644 --- a/measures/forms/mixins.py +++ b/measures/forms/mixins.py @@ -252,6 +252,13 @@ def conditions_clean( try: components = parser.transform(price) cleaned_data["duty_amount"] = components[0].get("duty_amount") + if ( + cleaned_data["duty_amount"] + and len(str(cleaned_data["duty_amount"]).split(".")[-1]) > 3 + ): + raise ValidationError( + f"The reference price cannot have more than 3 decimal places.", + ) cleaned_data["monetary_unit"] = components[0].get("monetary_unit") cleaned_data["condition_measurement"] = ( models.Measurement.objects.as_at(date) From d850d29e3ccd23a3902074727ead87c783675a5e Mon Sep 17 00:00:00 2001 From: Matthew McKenzie Date: Thu, 23 Jan 2025 17:51:13 +0000 Subject: [PATCH 3/3] Add test for new error message --- measures/tests/test_forms.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/measures/tests/test_forms.py b/measures/tests/test_forms.py index 294fe9ea5..82ad6a7a7 100644 --- a/measures/tests/test_forms.py +++ b/measures/tests/test_forms.py @@ -1220,6 +1220,37 @@ def test_measure_forms_conditions_wizard_clears_unneeded_certificate(date_ranges assert form_expects_no_certificate.cleaned_data["required_certificate"] is None +def test_measure_forms_conditions_wizard_form_invalid_duty(date_ranges): + """Tests that MeasureConditionsWizardStepForm is invalid when the duty has + more than 3 decimal places.""" + condition_code = factories.MeasureConditionCodeFactory.create() + monetary_unit = factories.MonetaryUnitFactory.create() + factories.MeasurementUnitFactory.create() + factories.MeasurementUnitQualifierFactory.create() + factories.MeasureConditionComponentFactory.create() + factories.DutyExpressionFactory.create(sid=99) + action = factories.MeasureActionFactory.create() + + data = { + "reference_price": f"1.2345 {monetary_unit.code}", + "action": action.pk, + "condition_code": condition_code.pk, + } + # MeasureConditionsForm.__init__ expects prefix kwarg for instantiating crispy forms `Layout` object + form = forms.MeasureConditionsWizardStepForm( + data, + prefix="", + measure_start_date=date_ranges.normal, + ) + + with override_current_transaction(action.transaction): + assert not form.is_valid() + assert ( + "The reference price cannot have more than 3 decimal places." + in form.errors["reference_price"] + ) + + def test_measure_form_valid_data(erga_omnes, session_request_with_workbasket): """Test that MeasureForm.is_valid returns True when passed required fields and geographical_area and sid fields in cleaned data.""" @@ -1970,8 +2001,8 @@ def test_simple_measure_edit_forms_serialize_deserialize( request, duty_sentence_parser, ): - """Test that the EditMeasure simple forms that use the - SerializableFormMixin behave correctly and as expected.""" + """Test that the EditMeasure simple forms that use the SerializableFormMixin + behave correctly and as expected.""" # Create some measures to apply this data to, for the kwargs quota_order_number = factories.QuotaOrderNumberFactory()