Example #1
0
    def conditions_clean(self, cleaned_data, measure_start_date):
        """
        We get the reference_price from cleaned_data and the measure_start_date
        from the form's initial data.

        If both are present, we call validate_duties with measure_start_date.
        Then, if reference_price is provided, we use DutySentenceParser with
        measure_start_date, if present, or the current_date, to check that we
        are dealing with a simple duty (i.e. only one component). We then update
        cleaned_data with key-value pairs created from this single, unsaved
        component.
        """
        price = cleaned_data.get("reference_price")

        if price and measure_start_date is not None:
            validate_duties(price, measure_start_date)

        if price:
            parser = DutySentenceParser.get(measure_start_date)
            components = parser.parse(price)
            if len(components) > 1:
                raise ValidationError(
                    "A MeasureCondition cannot be created with a compound reference price (e.g. 3.5% + 11 GBP / 100 kg)",
                )
            cleaned_data["duty_amount"] = components[0].duty_amount
            cleaned_data["monetary_unit"] = components[0].monetary_unit
            cleaned_data["condition_measurement"] = components[0].component_measurement

        # The JS autocomplete does not allow for clearing unnecessary certificates
        # In case of a user changing data, the information is cleared here.
        condition_code = cleaned_data.get("condition_code")
        if condition_code and not condition_code.accepts_certificate:
            cleaned_data["required_certificate"] = None

        return cleaned_data
Example #2
0
def duty_sentence_parser_test(
    duty_sentence_parser: DutySentenceParser,
    duty_sentence_data: Tuple[str, List[Dict]],
):
    duty_sentence, expected_results = duty_sentence_data
    components = list(duty_sentence_parser.parse(duty_sentence))
    assert len(expected_results) == len(components)
    for expected, actual in zip(expected_results, components):
        assert_attributes(expected, actual)
Example #3
0
def duty_sentence_parser(
    duty_expressions: Dict[int, DutyExpression],
    monetary_units: Dict[str, MonetaryUnit],
    measurements: Dict[Tuple[str, Optional[str]], Measurement],
) -> DutySentenceParser:
    return DutySentenceParser(
        duty_expressions.values(),
        monetary_units.values(),
        measurements.values(),
    )
Example #4
0
def validate_duties(duties, measure_start_date):
    """Validate duty sentence by parsing it."""
    from measures.parsers import DutySentenceParser

    duty_sentence_parser = DutySentenceParser.get(measure_start_date, )

    try:
        duty_sentence_parser.parse(duties)
    except ParseError as e:
        # More helpful errors could be emitted here -
        # for example if an amount or currency is missing
        # it may be possible to highlight that.
        logger.error("Error parse duty sentence %s", e)
        raise ValidationError("Enter a valid duty sentence.")
Example #5
0
 def __init__(
     self,
     workbasket: WorkBasket,
     base_date: date,
     defaults: Dict[str, Any] = {},
     duty_sentence_parser: DutySentenceParser = None,
     condition_sentence_parser: ConditionSentenceParser = None,
 ) -> None:
     self.workbasket = workbasket
     self.defaults = defaults
     self.duty_sentence_parser = duty_sentence_parser or DutySentenceParser.get(
         base_date, )
     self.condition_sentence_parser = (condition_sentence_parser
                                       or ConditionSentenceParser.get(
                                           base_date, ))
Example #6
0
    def create_conditions(self, obj):
        """
        Gets condition formset from context data, loops over these forms and
        validates the data, checking for the condition_sid field in the data to
        indicate whether an existing condition is being updated or a new one
        created from scratch.

        Then deletes any existing conditions that are not being updated,
        before calling the MeasureCreationPattern.create_condition_and_components with the appropriate parser and condition data.
        """
        formset = self.get_context_data()["conditions_formset"]
        excluded_sids = []
        conditions_data = []
        workbasket = WorkBasket.current(self.request)
        existing_conditions = obj.conditions.approved_up_to_transaction(
            workbasket.get_current_transaction(self.request), )

        for f in formset.forms:
            f.is_valid()
            condition_data = f.cleaned_data
            # If the form has changed and "condition_sid" is in the changed data,
            # this means that the condition is preexisting and needs to updated
            # so that its dependent_measure points to the latest version of measure
            if f.has_changed() and "condition_sid" in f.changed_data:
                excluded_sids.append(f.initial["condition_sid"])
                update_type = UpdateType.UPDATE
                condition_data["version_group"] = existing_conditions.get(
                    sid=f.initial["condition_sid"], ).version_group
                condition_data["sid"] = f.initial["condition_sid"]
            # If changed and condition_sid not in changed_data, then this is a newly created condition
            elif f.has_changed() and "condition_sid" not in f.changed_data:
                update_type = UpdateType.CREATE

            condition_data["update_type"] = update_type
            conditions_data.append(condition_data)

        workbasket = WorkBasket.current(self.request)

        # Delete all existing conditions from the measure instance, except those that need to be updated
        for condition in existing_conditions.exclude(sid__in=excluded_sids):
            condition.new_version(
                workbasket=workbasket,
                update_type=UpdateType.DELETE,
                transaction=obj.transaction,
            )

        if conditions_data:
            measure_creation_pattern = MeasureCreationPattern(
                workbasket=workbasket,
                base_date=obj.valid_between.lower,
            )
            parser = DutySentenceParser.get(
                obj.valid_between.lower,
                component_output=MeasureConditionComponent,
            )

            # Loop over conditions_data, starting at 1 because component_sequence_number has to start at 1
            for component_sequence_number, condition_data in enumerate(
                    conditions_data,
                    start=1,
            ):
                # Create conditions and measure condition components, using instance as `dependent_measure`
                measure_creation_pattern.create_condition_and_components(
                    condition_data,
                    component_sequence_number,
                    obj,
                    parser,
                    workbasket,
                )
Example #7
0
    def create_measures(self, data):
        """Returns a list of the created measures."""
        measure_start_date = data["valid_between"].lower
        workbasket = WorkBasket.current(self.request)
        measure_creation_pattern = MeasureCreationPattern(
            workbasket=workbasket,
            base_date=measure_start_date,
            defaults={
                "generating_regulation": data["generating_regulation"],
            },
        )

        measures_data = []

        for commodity_data in data.get("formset-commodities", []):
            if not commodity_data.get("DELETE"):
                for geo_area in data["geo_area_list"]:

                    measure_data = {
                        "measure_type":
                        data["measure_type"],
                        "geographical_area":
                        geo_area,
                        "exclusions":
                        data.get("geo_area_exclusions", None) or [],
                        "goods_nomenclature":
                        commodity_data["commodity"],
                        "additional_code":
                        data["additional_code"],
                        "order_number":
                        data["order_number"],
                        "validity_start":
                        measure_start_date,
                        "validity_end":
                        data["valid_between"].upper,
                        "footnotes": [
                            item["footnote"]
                            for item in data.get("formset-footnotes", [])
                            if not item.get("DELETE")
                        ],
                        # condition_sentence here, or handle separately and duty_sentence after?
                        "duty_sentence":
                        commodity_data["duties"],
                    }

                    measures_data.append(measure_data)

        created_measures = []

        for measure_data in measures_data:
            measure = measure_creation_pattern.create(**measure_data)
            parser = DutySentenceParser.get(
                measure.valid_between.lower,
                component_output=MeasureConditionComponent,
            )
            for component_sequence_number, condition_data in enumerate(
                    data.get("formset-conditions", []),
                    start=1,
            ):
                if not condition_data.get("DELETE"):

                    measure_creation_pattern.create_condition_and_components(
                        condition_data,
                        component_sequence_number,
                        measure,
                        parser,
                        workbasket,
                    )

            created_measures.append(measure)

        return created_measures
Example #8
0
def diff_components(
    instance,
    duty_sentence: str,
    start_date: date,
    workbasket: WorkBasket,
    transaction: Type[Transaction],
    component_output: Type[TrackedModel] = MeasureComponent,
    reverse_attribute: str = "component_measure",
):
    """
    Takes a start_date and component_output (MeasureComponent is the default)
    and creates an instance of DutySentenceParser.

    Expects a duty_sentence string and passes this to parser to generate a list
    of new components. Then compares this list with existing components on the
    model instance (either a Measure or a MeasureCondition) and determines
    whether existing components are to be updated, created, or deleted.
    Optionally accepts a Transaction, which should be passed when the method is
    called during the creation of a measure or condition, to minimise the number
    of transactions and avoid business rule violations (e.g.
    ActionRequiresDuty).
    """
    from measures.parsers import DutySentenceParser

    parser = DutySentenceParser.get(
        start_date,
        component_output=component_output,
    )

    new_components = parser.parse(duty_sentence)
    old_components = instance.components.approved_up_to_transaction(
        workbasket.current_transaction,
    )
    new_by_id = {c.duty_expression.id: c for c in new_components}
    old_by_id = {c.duty_expression.id: c for c in old_components}
    all_ids = set(new_by_id.keys()) | set(old_by_id.keys())
    update_transaction = transaction if transaction else None
    for id in all_ids:
        new = new_by_id.get(id)
        old = old_by_id.get(id)
        if new and old:
            # Component is having amount/unit changed – UPDATE it
            new.update_type = UpdateType.UPDATE
            new.version_group = old.version_group
            setattr(new, reverse_attribute, instance)
            if not update_transaction:
                update_transaction = workbasket.new_transaction()
            new.transaction = update_transaction
            new.save()

        elif new:
            # Component exists only in new set - CREATE it
            new.update_type = UpdateType.CREATE
            setattr(new, reverse_attribute, instance)
            new.transaction = (
                transaction if transaction else workbasket.new_transaction()
            )
            new.save()

        elif old:
            # Component exists only in old set – DELETE it
            old = old.new_version(
                workbasket,
                update_type=UpdateType.DELETE,
                transaction=workbasket.new_transaction(),
            )