コード例 #1
0
class CertificateDescription(DescriptionMixin, ValidityMixin, TrackedModel):
    record_code = "205"
    subrecord_code = "10"

    period_record_code = "205"
    period_subrecord_code = "05"

    sid = SignedIntSID(db_index=True)

    description = ShortDescription()
    described_certificate = models.ForeignKey(
        Certificate,
        related_name="descriptions",
        on_delete=models.PROTECT,
    )

    indirect_business_rules = (business_rules.CE6,)
    business_rules = (
        business_rules.NoOverlappingDescriptions,
        business_rules.ContiguousDescriptions,
    )

    def __str__(self):
        return self.identifying_fields_to_string(
            identifying_fields=("described_certificate", "valid_between"),
        )

    class Meta:
        ordering = ("valid_between",)
コード例 #2
0
class FootnoteDescription(DescriptionMixin, ValidityMixin, TrackedModel):
    """
    The footnote description contains the text associated with a footnote, for a
    given language and for a particular period.

    Description period(s) associated with footnote text. The description of a
    footnote may change independently of the footnote id. The footnote
    description period contains the validity start date of the footnote
    description.
    """

    record_code = "200"
    subrecord_code = "10"

    period_record_code = "200"
    period_subrecord_code = "05"

    described_footnote = models.ForeignKey(
        Footnote,
        on_delete=models.CASCADE,
        related_name="descriptions",
    )
    description = models.TextField()
    sid = SignedIntSID(db_index=True)

    indirect_business_rules = (business_rules.FO4,)

    def __str__(self):
        return f"for Footnote {self.described_footnote}"

    class Meta:
        ordering = ("valid_between",)
コード例 #3
0
class AdditionalCodeDescription(DescriptionMixin, ValidityMixin, TrackedModel):
    """
    The additional code description contains the description of the additional
    code for a particular period.

    This model combines the additional code description and the additional code
    description period domain objects, because we only care about 1 language.
    """

    record_code = "245"
    subrecord_code = "10"
    period_record_code = "245"
    period_subrecord_code = "05"

    # Store the additional code description period sid so that we can send it in TARIC3
    # updates to systems that expect it.
    sid = SignedIntSID(db_index=True)

    described_additionalcode = models.ForeignKey(
        AdditionalCode,
        on_delete=models.PROTECT,
        related_name="descriptions",
    )
    description = models.TextField()

    indirect_business_rules = (business_rules.ACN5, )

    def __str__(self):
        return self.identifying_fields_to_string(
            identifying_fields=("described_additionalcode", "valid_between"), )

    class Meta:
        ordering = ("valid_between", )
コード例 #4
0
ファイル: models.py プロジェクト: uktrade/tamato
class CertificateDescription(DescriptionMixin, TrackedModel):
    record_code = "205"
    subrecord_code = "10"

    period_record_code = "205"
    period_subrecord_code = "05"

    identifying_fields = ("sid", )

    sid = SignedIntSID(db_index=True)

    description = ShortDescription()
    described_certificate = models.ForeignKey(
        Certificate,
        related_name="descriptions",
        on_delete=models.PROTECT,
    )

    indirect_business_rules = (business_rules.CE6, )

    def save(self, *args, **kwargs):
        if getattr(self, "sid") is None:
            highest_sid = CertificateDescription.objects.aggregate(
                Max("sid"))["sid__max"]
            self.sid = highest_sid + 1

        return super().save(*args, **kwargs)

    class Meta:
        ordering = ("validity_start", )
コード例 #5
0
ファイル: models.py プロジェクト: uktrade/tamato
class GeographicalAreaDescription(DescriptionMixin, TrackedModel):
    record_code = "250"
    subrecord_code = "10"

    period_record_code = "250"
    period_subrecord_code = "05"

    identifying_fields = ("sid", )

    described_geographicalarea = models.ForeignKey(
        GeographicalArea,
        on_delete=models.CASCADE,
        related_name="descriptions",
    )
    description = ShortDescription()
    sid = SignedIntSID(db_index=True)

    def save(self, *args, **kwargs):
        if getattr(self, "sid") is None:
            highest_sid = GeographicalAreaDescription.objects.aggregate(
                Max("sid"))["sid__max"]
            self.sid = highest_sid + 1

        return super().save(*args, **kwargs)

    url_pattern_name_prefix = "geo_area_description"

    class Meta:
        ordering = ("validity_start", )
コード例 #6
0
class QuotaOrderNumber(TrackedModel, ValidityMixin):
    """
    The order number is the identification of a quota.

    It is defined for tariff quotas and surveillances. If an operator wants to
    benefit from a tariff quota, they must refer to it via the order number in
    the customs declaration. An order number may have multiple associated quota
    definitions, for example to divide a quota over several time periods.
    """

    record_code = "360"
    subrecord_code = "00"

    sid = SignedIntSID(db_index=True)
    order_number = models.CharField(
        max_length=6,
        validators=[validators.quota_order_number_validator],
        db_index=True,
    )
    mechanism = models.PositiveSmallIntegerField(
        choices=validators.AdministrationMechanism.choices, )
    category = models.PositiveSmallIntegerField(
        choices=validators.QuotaCategory.choices, )

    origins = models.ManyToManyField(
        "geo_areas.GeographicalArea",
        through="QuotaOrderNumberOrigin",
        related_name="quotas",
    )

    indirect_business_rules = (
        business_rules.ON7,
        business_rules.ON8,
        business_rules.QBP2,
        business_rules.QD1,
        business_rules.QD7,
    )
    business_rules = (
        business_rules.ON1,
        business_rules.ON2,
        business_rules.ON9,
        business_rules.ON11,
    )

    def __str__(self):
        return self.order_number

    def in_use(self):
        return (self.measure_set.model.objects.filter(
            order_number__sid=self.sid, ).approved_up_to_transaction(
                self.transaction).exists())

    class Meta:
        verbose_name = "quota"
コード例 #7
0
class QuotaSuspension(TrackedModel, ValidityMixin):
    """Defines a suspension period for a quota."""

    record_code = "370"
    subrecord_code = "15"

    sid = SignedIntSID(db_index=True)
    quota_definition = models.ForeignKey(QuotaDefinition,
                                         on_delete=models.PROTECT)
    description = ShortDescription()

    business_rules = (business_rules.QSP2, )
コード例 #8
0
class QuotaBlocking(TrackedModel, ValidityMixin):
    """Defines a blocking period for a (sub-)quota."""

    record_code = "370"
    subrecord_code = "10"

    sid = SignedIntSID(db_index=True)
    quota_definition = models.ForeignKey(QuotaDefinition,
                                         on_delete=models.PROTECT)
    blocking_period_type = models.PositiveSmallIntegerField(
        choices=validators.BlockingPeriodType.choices, )
    description = ShortDescription()

    business_rules = (business_rules.QBP2, )
コード例 #9
0
ファイル: models.py プロジェクト: kintisheff/tamato
class GeographicalAreaDescription(TrackedModel, ValidityMixin):
    record_code = "250"
    subrecord_code = "10"

    period_record_code = "250"
    period_subrecord_code = "05"

    area = models.ForeignKey(
        GeographicalArea,
        on_delete=models.CASCADE,
        related_name="descriptions",
    )
    description = ShortDescription()
    sid = SignedIntSID(db_index=True)

    def __str__(self):
        return f'description ({self.sid}) - "{self.description}" for {self.area}'

    class Meta:
        ordering = ("valid_between", )
コード例 #10
0
class AdditionalCode(TrackedModel, ValidityMixin):
    """
    The additional code identifies a piece of text associated with a goods
    nomenclature code within a measure.

    An additional code can be re-used over time.
    """

    record_code = "245"
    subrecord_code = "00"

    sid = SignedIntSID(db_index=True)
    type = models.ForeignKey(AdditionalCodeType, on_delete=models.PROTECT)
    code = models.CharField(
        max_length=3,
        validators=[validators.additional_code_validator],
    )

    indirect_business_rules = (
        footnotes_business_rules.FO15,
        footnotes_business_rules.FO9,
        measures_business_rules.ME1,
    )
    business_rules = (
        business_rules.ACN1,
        business_rules.ACN2,
        business_rules.ACN4,
        business_rules.ACN5,
        business_rules.ACN13,
        business_rules.ACN14,
        business_rules.ACN17,
    )

    def __str__(self):
        return f"{self.type.sid}{self.code}"

    def in_use(self):
        return (self.measure_set.model.objects.filter(
            additional_code__sid=self.sid, ).approved_up_to_transaction(
                self.transaction).exists())
コード例 #11
0
class FootnoteDescription(DescriptionMixin, TrackedModel):
    """
    The footnote description contains the text associated with a footnote, for a
    given language and for a particular period.

    Description period(s) associated with footnote text. The description of a
    footnote may change independently of the footnote id. The footnote
    description period contains the validity start date of the footnote
    description.
    """

    record_code = "200"
    subrecord_code = "10"

    period_record_code = "200"
    period_subrecord_code = "05"

    identifying_fields = ("sid", )

    described_footnote = models.ForeignKey(
        Footnote,
        on_delete=models.CASCADE,
        related_name="descriptions",
    )
    description = LongDescription()
    sid = SignedIntSID(db_index=True)

    indirect_business_rules = (business_rules.FO4, )

    def save(self, *args, **kwargs):
        if getattr(self, "sid") is None:
            highest_sid = FootnoteDescription.objects.aggregate(
                Max("sid"))["sid__max"]
            self.sid = highest_sid + 1

        return super().save(*args, **kwargs)

    class Meta:
        ordering = ("validity_start", )
コード例 #12
0
class QuotaOrderNumberOrigin(TrackedModel, ValidityMixin):
    """The order number origin defines a quota as being available only to
    imports from a specific origin, usually a country or group of countries."""

    record_code = "360"
    subrecord_code = "10"

    identifying_fields = ("sid", )

    sid = SignedIntSID(db_index=True)
    order_number = models.ForeignKey(QuotaOrderNumber,
                                     on_delete=models.PROTECT)
    geographical_area = models.ForeignKey(
        "geo_areas.GeographicalArea",
        on_delete=models.PROTECT,
    )

    excluded_areas = models.ManyToManyField(
        "geo_areas.GeographicalArea",
        through="QuotaOrderNumberOriginExclusion",
        related_name="+",
    )

    indirect_business_rules = (
        business_rules.ON13,
        business_rules.ON14,
    )
    business_rules = (
        business_rules.ON5,
        business_rules.ON6,
        business_rules.ON7,
        business_rules.ON10,
        business_rules.ON12,
        UniqueIdentifyingFields,
        UpdateValidity,
    )

    def order_number_in_use(self, transaction):
        return self.order_number.in_use(transaction)
コード例 #13
0
class AdditionalCode(TrackedModel, ValidityMixin, DescribedMixin):
    """
    The additional code identifies a piece of text associated with a goods
    nomenclature code within a measure.

    An additional code can be re-used over time.
    """

    record_code = "245"
    subrecord_code = "00"

    identifying_fields = ("sid", )

    sid = SignedIntSID(db_index=True)
    type = models.ForeignKey(AdditionalCodeType, on_delete=models.PROTECT)
    code = models.CharField(
        max_length=3,
        validators=[validators.additional_code_validator],
    )

    indirect_business_rules = (
        footnotes_business_rules.FO15,
        footnotes_business_rules.FO9,
        measures_business_rules.ME1,
    )
    business_rules = (
        business_rules.ACN1,
        business_rules.ACN2,
        business_rules.ACN4,
        business_rules.ACN5,
        business_rules.ACN13,
        business_rules.ACN14,
        business_rules.ACN17,
        UpdateValidity,
        UniqueIdentifyingFields,
    )

    def __str__(self):
        return f"{self.type.sid}{self.code}"
コード例 #14
0
class AdditionalCodeDescription(DescriptionMixin, TrackedModel):
    """
    The additional code description contains the description of the additional
    code for a particular period.

    This model combines the additional code description and the additional code
    description period domain objects, because we only care about 1 language.
    """

    record_code = "245"
    subrecord_code = "10"
    period_record_code = "245"
    period_subrecord_code = "05"

    identifying_fields = ("sid", )

    # Store the additional code description period sid so that we can send it in TARIC3
    # updates to systems that expect it.
    sid = SignedIntSID(db_index=True)

    described_additionalcode = models.ForeignKey(
        AdditionalCode,
        on_delete=models.PROTECT,
        related_name="descriptions",
    )
    description = LongDescription()

    indirect_business_rules = (business_rules.ACN5, )

    def save(self, *args, **kwargs):
        if getattr(self, "sid") is None:
            highest_sid = AdditionalCodeDescription.objects.aggregate(
                Max("sid"))["sid__max"]
            self.sid = highest_sid + 1

        return super().save(*args, **kwargs)

    class Meta:
        ordering = ("validity_start", )
コード例 #15
0
class QuotaOrderNumberOrigin(TrackedModel, ValidityMixin):
    """The order number origin defines a quota as being available only to
    imports from a specific origin, usually a country or group of countries."""

    record_code = "360"
    subrecord_code = "10"

    sid = SignedIntSID(db_index=True)
    order_number = models.ForeignKey(QuotaOrderNumber,
                                     on_delete=models.PROTECT)
    geographical_area = models.ForeignKey(
        "geo_areas.GeographicalArea",
        on_delete=models.PROTECT,
    )

    excluded_areas = models.ManyToManyField(
        "geo_areas.GeographicalArea",
        through="QuotaOrderNumberOriginExclusion",
        related_name="+",
    )

    indirect_business_rules = (
        business_rules.ON13,
        business_rules.ON14,
    )
    business_rules = (
        business_rules.ON5,
        business_rules.ON6,
        business_rules.ON7,
        business_rules.ON10,
        business_rules.ON12,
    )

    def in_use(self):
        return (self.order_number.measure_set.model.objects.filter(
            order_number__sid=self.order_number.sid,
        ).approved_up_to_transaction(self.transaction).exists())
コード例 #16
0
class Measure(TrackedModel, ValidityMixin):
    """
    Defines the validity period in which a particular measure type is applicable
    to particular nomenclature for a particular geographical area.

    Measures in the TARIC database are stored against the nomenclature code
    which is at the highest level appropriate in the hierarchy. Thus, measures
    which apply to all the declarable codes in a complete chapter are stored
    against the nomenclature code for the chapter (i.e. at the 2-digit level
    only); those which apply to all sub-divisions of an HS code are stored
    against that HS code (i.e. at the 6-digit level only). The advantage of this
    system is that it reduces the number of measures stored in the database; the
    data capture workload (thus diminishing the possibility of introducing
    errors) and the transmission volumes.
    """

    record_code = "430"
    subrecord_code = "00"

    sid = SignedIntSID(db_index=True)
    measure_type = models.ForeignKey(MeasureType, on_delete=models.PROTECT)
    geographical_area = models.ForeignKey(
        "geo_areas.GeographicalArea",
        on_delete=models.PROTECT,
        related_name="measures",
    )
    goods_nomenclature = models.ForeignKey(
        "commodities.GoodsNomenclature",
        on_delete=models.PROTECT,
        related_name="measures",
        null=True,
        blank=True,
    )
    additional_code = models.ForeignKey(
        "additional_codes.AdditionalCode",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )
    dead_additional_code = models.CharField(
        max_length=16,
        null=True,
        blank=True,
        db_index=True,
    )
    order_number = models.ForeignKey(
        "quotas.QuotaOrderNumber",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )
    dead_order_number = models.CharField(
        max_length=6,
        validators=[quota_order_number_validator],
        null=True,
        blank=True,
        db_index=True,
    )
    reduction = models.PositiveSmallIntegerField(
        validators=[validators.validate_reduction_indicator],
        null=True,
        blank=True,
        db_index=True,
    )
    generating_regulation = models.ForeignKey(
        "regulations.Regulation",
        on_delete=models.PROTECT,
    )
    terminating_regulation = models.ForeignKey(
        "regulations.Regulation",
        on_delete=models.PROTECT,
        related_name="terminated_measures",
        null=True,
        blank=True,
    )
    stopped = models.BooleanField(default=False)
    export_refund_nomenclature_sid = SignedIntSID(null=True, blank=True, default=None)

    footnotes = models.ManyToManyField(
        "footnotes.Footnote",
        through="FootnoteAssociationMeasure",
    )

    identifying_fields = ("sid",)

    indirect_business_rules = (
        business_rules.MA4,
        business_rules.MC3,
        business_rules.ME42,
        business_rules.ME49,
        business_rules.ME61,
        business_rules.ME65,
        business_rules.ME66,
        business_rules.ME67,
        business_rules.ME71,
        business_rules.ME73,
    )
    business_rules = (
        business_rules.ME1,
        business_rules.ME2,
        business_rules.ME3,
        business_rules.ME4,
        business_rules.ME5,
        business_rules.ME6,
        business_rules.ME7,
        business_rules.ME8,
        business_rules.ME88,
        business_rules.ME16,
        business_rules.ME115,
        business_rules.ME25,
        business_rules.ME32,
        business_rules.ME10,
        business_rules.ME116,
        business_rules.ME119,
        business_rules.ME9,
        business_rules.ME12,
        business_rules.ME17,
        business_rules.ME24,
        business_rules.ME87,
        business_rules.ME33,
        business_rules.ME34,
        business_rules.ME40,
        business_rules.ME45,
        business_rules.ME46,
        business_rules.ME47,
        business_rules.ME109,
        business_rules.ME110,
        business_rules.ME111,
        business_rules.ME104,
    )

    objects = PolymorphicManager.from_queryset(MeasuresQuerySet)()

    validity_field_name = "db_effective_valid_between"

    @property
    def effective_end_date(self):
        """Measure end dates may be overridden by regulations."""

        # UK measures will have explicit end dates only
        # if self.national:
        #     return self.valid_between.upper

        reg = self.generating_regulation
        effective_end_date = (
            date(
                reg.effective_end_date.year,
                reg.effective_end_date.month,
                reg.effective_end_date.day,
            )
            if reg.effective_end_date
            else None
        )

        if self.valid_between.upper and reg and effective_end_date:
            if self.valid_between.upper > effective_end_date:
                return effective_end_date
            return self.valid_between.upper

        if self.valid_between.upper and self.terminating_regulation:
            return self.valid_between.upper

        if reg:
            return effective_end_date

        return self.valid_between.upper

    @property
    def effective_valid_between(self):
        return TaricDateRange(self.valid_between.lower, self.effective_end_date)

    @classmethod
    def objects_with_validity_field(cls):
        return super().objects_with_validity_field().with_effective_valid_between()

    def has_components(self):
        return (
            MeasureComponent.objects.approved_up_to_transaction(
                transaction=self.transaction,
            )
            .filter(component_measure__sid=self.sid)
            .exists()
        )

    def has_condition_components(self):
        return (
            MeasureConditionComponent.objects.approved_up_to_transaction(
                transaction=self.transaction,
            )
            .filter(condition__dependent_measure__sid=self.sid)
            .exists()
        )

    def get_conditions(self):
        return MeasureCondition.objects.filter(
            dependent_measure__sid=self.sid,
        ).latest_approved()

    def terminate(self, workbasket, when: date):
        """
        Returns a new version of the measure updated to end on the specified
        date.

        If the measure would not have started on that date, the measure is
        deleted instead. If the measure will already have ended by this date,
        then does nothing.
        """
        starts_after_date = self.valid_between.lower >= when
        ends_before_date = (
            not self.valid_between.upper_inf and self.valid_between.upper < when
        )

        if ends_before_date:
            return self

        update_params = {}
        if starts_after_date:
            update_params["update_type"] = UpdateType.DELETE
        else:
            update_params["update_type"] = UpdateType.UPDATE
            update_params["valid_between"] = TaricDateRange(
                lower=self.valid_between.lower,
                upper=when,
            )
            if not self.terminating_regulation:
                update_params["terminating_regulation"] = self.generating_regulation

        return self.new_draft(workbasket, **update_params)
コード例 #17
0
class QuotaDefinition(TrackedModel, ValidityMixin):
    """
    Defines the validity period and quantity for which a quota is applicable.
    This model also represents sub-quotas, via a parent-child recursive relation
    through QuotaAssociation.

    The monetary unit code and the measurement unit code (with its optional unit
    qualifier code) are mutually exclusive – each quota definition must have one
    and only one of monetary or measurement unit.

    The pair of measurement and measurement unit qualifier must appear as a
    valid measurement in the measurements table.
    """

    record_code = "370"
    subrecord_code = "00"

    identifying_fields = ("sid", )

    sid = SignedIntSID(db_index=True)
    order_number = models.ForeignKey(QuotaOrderNumber,
                                     on_delete=models.PROTECT)
    volume = models.DecimalField(max_digits=14, decimal_places=3)
    initial_volume = models.DecimalField(max_digits=14, decimal_places=3)
    monetary_unit = models.ForeignKey(
        "measures.MonetaryUnit",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )
    measurement_unit = models.ForeignKey(
        "measures.MeasurementUnit",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )
    measurement_unit_qualifier = models.ForeignKey(
        "measures.MeasurementUnitQualifier",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )
    maximum_precision = models.PositiveSmallIntegerField(
        validators=[validators.validate_max_precision], )
    quota_critical = models.BooleanField(default=False)
    # the percentage at which the quota becomes critical
    quota_critical_threshold = models.PositiveSmallIntegerField(
        validators=[validators.validate_percentage], )
    description = ShortDescription()

    sub_quotas = models.ManyToManyField(
        "self",
        through="QuotaAssociation",
        through_fields=("main_quota", "sub_quota"),
    )

    indirect_business_rules = (
        business_rules.QA2,
        business_rules.QA3,
        business_rules.QA5,
        business_rules.QSP2,
    )
    business_rules = (
        business_rules.ON8,
        business_rules.QD1,
        business_rules.QD7,
        business_rules.QD8,
        business_rules.QD10,
        business_rules.QD11,
        business_rules.PreventQuotaDefinitionDeletion,
        business_rules.QuotaAssociationMustReferToANonDeletedSubQuota,
        business_rules.QuotaSuspensionMustReferToANonDeletedQuotaDefinition,
        business_rules.
        QuotaBlockingPeriodMustReferToANonDeletedQuotaDefinition,
        business_rules.OverlappingQuotaDefinition,
        business_rules.VolumeAndInitialVolumeMustMatch,
        UniqueIdentifyingFields,
        UpdateValidity,
    )

    class Meta:
        constraints = [
            models.CheckConstraint(
                check=(models.Q(
                    monetary_unit__isnull=False,
                    measurement_unit__isnull=True,
                )
                       | models.Q(
                           monetary_unit__isnull=True,
                           measurement_unit__isnull=False,
                       )),
                name="quota_definition_must_have_one_unit",
            ),
        ]

    def __str__(self):
        return str(self.sid)
コード例 #18
0
class QuotaOrderNumber(TrackedModel, ValidityMixin):
    """
    The order number is the identification of a quota.

    It is defined for tariff quotas and surveillances. If an operator wants to
    benefit from a tariff quota, they must refer to it via the order number in
    the customs declaration. An order number may have multiple associated quota
    definitions, for example to divide a quota over several time periods.
    """

    record_code = "360"
    subrecord_code = "00"

    identifying_fields = ("sid", )

    sid = SignedIntSID(db_index=True)
    order_number = models.CharField(
        max_length=6,
        validators=[validators.quota_order_number_validator],
        db_index=True,
    )
    mechanism = models.PositiveSmallIntegerField(
        choices=validators.AdministrationMechanism.choices, )
    category = models.PositiveSmallIntegerField(
        choices=validators.QuotaCategory.choices, )

    origins = models.ManyToManyField(
        "geo_areas.GeographicalArea",
        through="QuotaOrderNumberOrigin",
        related_name="quotas",
    )

    required_certificates = models.ManyToManyField(
        "certificates.Certificate",
        related_name="quotas",
    )

    indirect_business_rules = (
        business_rules.ON7,
        business_rules.ON8,
        business_rules.QBP2,
        business_rules.QD1,
        business_rules.QD7,
        business_rules.CertificateValidityPeriodMustSpanQuotaOrderNumber,
        business_rules.CertificatesMustExist,
    )
    business_rules = (
        business_rules.ON1,
        business_rules.ON2,
        business_rules.ON9,
        business_rules.ON11,
        UniqueIdentifyingFields,
        UpdateValidity,
    )

    objects = TrackedModelManager.from_queryset(
        querysets.QuotaOrderNumberQuerySet)()

    def __str__(self):
        return self.order_number

    @property
    def autocomplete_label(self):
        return str(self)

    @property
    def is_origin_quota(self):
        return any(self.required_certificates.all())

    class Meta:
        verbose_name = "quota"
コード例 #19
0
ファイル: models.py プロジェクト: uktrade/tamato
class Measure(TrackedModel, ValidityMixin):
    """
    Defines the validity period in which a particular measure type is applicable
    to particular nomenclature for a particular geographical area.

    Measures in the TARIC database are stored against the nomenclature code
    which is at the highest level appropriate in the hierarchy. Thus, measures
    which apply to all the declarable codes in a complete chapter are stored
    against the nomenclature code for the chapter (i.e. at the 2-digit level
    only); those which apply to all sub-divisions of an HS code are stored
    against that HS code (i.e. at the 6-digit level only). The advantage of this
    system is that it reduces the number of measures stored in the database; the
    data capture workload (thus diminishing the possibility of introducing
    errors) and the transmission volumes.
    """

    record_code = "430"
    subrecord_code = "00"

    sid = SignedIntSID(db_index=True)
    measure_type = models.ForeignKey(MeasureType, on_delete=models.PROTECT)
    geographical_area = models.ForeignKey(
        "geo_areas.GeographicalArea",
        on_delete=models.PROTECT,
        related_name="measures",
    )
    goods_nomenclature = models.ForeignKey(
        "commodities.GoodsNomenclature",
        on_delete=models.PROTECT,
        related_name="measures",
        null=True,
        blank=True,
    )
    additional_code = models.ForeignKey(
        "additional_codes.AdditionalCode",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )
    dead_additional_code = models.CharField(
        max_length=16,
        null=True,
        blank=True,
        db_index=True,
    )
    order_number = models.ForeignKey(
        "quotas.QuotaOrderNumber",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )
    dead_order_number = models.CharField(
        max_length=6,
        validators=[quota_order_number_validator],
        null=True,
        blank=True,
        db_index=True,
    )
    reduction = models.PositiveSmallIntegerField(
        validators=[validators.validate_reduction_indicator],
        null=True,
        blank=True,
        db_index=True,
    )
    generating_regulation = models.ForeignKey(
        "regulations.Regulation",
        on_delete=models.PROTECT,
    )
    terminating_regulation = models.ForeignKey(
        "regulations.Regulation",
        on_delete=models.PROTECT,
        related_name="terminated_measures",
        null=True,
        blank=True,
    )
    stopped = models.BooleanField(default=False)
    export_refund_nomenclature_sid = SignedIntSID(null=True,
                                                  blank=True,
                                                  default=None)

    footnotes = models.ManyToManyField(
        "footnotes.Footnote",
        through="FootnoteAssociationMeasure",
    )

    identifying_fields = ("sid", )

    indirect_business_rules = (
        business_rules.MA4,
        business_rules.MC3,
        business_rules.ME42,
        business_rules.ME49,
        business_rules.ME61,
        business_rules.ME65,
        business_rules.ME66,
        business_rules.ME67,
        business_rules.ME71,
        business_rules.ME73,
    )
    business_rules = (
        business_rules.ME1,
        business_rules.ME2,
        business_rules.ME3,
        business_rules.ME4,
        business_rules.ME5,
        business_rules.ME6,
        business_rules.ME7,
        business_rules.ME8,
        business_rules.ME88,
        business_rules.ME16,
        business_rules.ME115,
        business_rules.ME25,
        business_rules.ME32,
        business_rules.ME10,
        business_rules.ME116,
        business_rules.ME119,
        business_rules.ME9,
        business_rules.ME12,
        business_rules.ME17,
        business_rules.ME24,
        business_rules.ME27,
        business_rules.ME87,
        business_rules.ME33,
        business_rules.ME34,
        business_rules.ME40,
        business_rules.ME45,
        business_rules.ME46,
        business_rules.ME47,
        business_rules.ME109,
        business_rules.ME110,
        business_rules.ME111,
        business_rules.ME104,
        UniqueIdentifyingFields,
        UpdateValidity,
    )

    objects = TrackedModelManager.from_queryset(MeasuresQuerySet)()

    @property
    def footnote_application_codes(
            self) -> Set[footnote_validators.ApplicationCode]:
        codes = {footnote_validators.ApplicationCode.DYNAMIC_FOOTNOTE}
        if self.goods_nomenclature:
            codes.add(footnote_validators.ApplicationCode.OTHER_MEASURES)
        if not self.goods_nomenclature.is_taric_code:
            codes.add(footnote_validators.ApplicationCode.CN_MEASURES)
        return codes

    validity_field_name = "db_effective_valid_between"

    @property
    def effective_end_date(self) -> date:
        """Measure end dates may be overridden by regulations."""
        if not hasattr(self, self.validity_field_name):
            effective_valid_between = (
                type(self).objects.with_validity_field().filter(
                    pk=self.pk).get().db_effective_valid_between)
            setattr(self, self.validity_field_name, effective_valid_between)

        return getattr(self, self.validity_field_name).upper

    def __str__(self):
        return str(self.sid)

    @property
    def effective_valid_between(self) -> TaricDateRange:
        if hasattr(self, self.validity_field_name):
            return getattr(self, self.validity_field_name)

        return TaricDateRange(self.valid_between.lower,
                              self.effective_end_date)

    @property
    def duty_sentence(self) -> str:
        return MeasureComponent.objects.duty_sentence(self)

    @classproperty
    def auto_value_fields(cls):
        """Remove export refund SID because we don't want to auto-increment it –
        it should really be a foreign key to an ExportRefundNomenclature model
        but as we don't use them in the UK Tariff we don't store them."""
        counters = super().auto_value_fields
        counters.remove(cls._meta.get_field("export_refund_nomenclature_sid"))
        return counters

    def has_components(self, transaction):
        return (MeasureComponent.objects.approved_up_to_transaction(
            transaction).filter(component_measure__sid=self.sid).exists())

    def has_condition_components(self, transaction):
        return (MeasureConditionComponent.objects.approved_up_to_transaction(
            transaction).filter(
                condition__dependent_measure__sid=self.sid).exists())
コード例 #20
0
ファイル: models.py プロジェクト: uktrade/tamato
class GeographicalArea(TrackedModel, ValidityMixin, DescribedMixin):
    """
    A Geographical Area covers three distinct types of object:

        1) A Country
        2) A Region (a trading area which is not recognised as a country)
        3) A Grouping of the above

    These objects are generally used when linked to data structures such as measures.
    As a measure does not care to distinguish between a country, region or group,
    the 3 types are stored as one for relational purposes.

    As a country or region can belong to a group there is a self-referential many-to-many
    field which is restricted. Yet groups can also have parent groups - in which case
    measures must of the parent must also apply to a child. To accomodate this there is a
    separate foreign key for group to group relations.
    """

    record_code = "250"
    subrecord_code = "00"

    identifying_fields = ("sid", )

    url_pattern_name_prefix = "geo_area"

    sid = SignedIntSID(db_index=True)
    area_id = models.CharField(max_length=4, validators=[area_id_validator])
    area_code = models.PositiveSmallIntegerField(choices=AreaCode.choices)

    # This deals with countries and regions belonging to area groups
    memberships = models.ManyToManyField("self",
                                         through="GeographicalMembership")

    # This deals with subgroups of other groups
    parent = models.ForeignKey("self",
                               on_delete=models.PROTECT,
                               null=True,
                               blank=True)

    objects = PolymorphicManager.from_queryset(GeographicalAreaQuerySet)()

    indirect_business_rules = (
        business_rules.GA14,
        business_rules.GA16,
        business_rules.GA17,
        measures_business_rules.ME1,
        measures_business_rules.ME65,
        measures_business_rules.ME66,
        measures_business_rules.ME67,
        quotas_business_rules.ON13,
        quotas_business_rules.ON14,
        quotas_business_rules.ON6,
    )
    business_rules = (
        business_rules.GA1,
        business_rules.GA3,
        business_rules.GA4,
        business_rules.GA5,
        business_rules.GA6,
        business_rules.GA7,
        business_rules.GA10,
        business_rules.GA11,
        business_rules.GA21,
        business_rules.GA22,
        UniqueIdentifyingFields,
        UpdateValidity,
    )

    def get_current_memberships(self):
        return (GeographicalMembership.objects.filter(
            Q(geo_group__sid=self.sid)
            | Q(member__sid=self.sid), ).current().select_related(
                "member", "geo_group"))

    def is_single_region_or_country(self):
        return self.area_code == AreaCode.COUNTRY or self.area_code == AreaCode.REGION

    def is_all_countries(self):
        return self.area_code == AreaCode.GROUP and self.area_id == "1011"

    def is_group(self):
        return self.area_code == AreaCode.GROUP

    def __str__(self):
        return f"{self.get_area_code_display()} {self.area_id}"

    class Meta:
        constraints = (CheckConstraint(
            name="only_groups_have_parents",
            check=Q(area_code=1) | Q(parent__isnull=True),
        ), )
コード例 #21
0
ファイル: models.py プロジェクト: uktrade/tamato
class MeasureCondition(TrackedModel):
    """
    A measure may be dependent on conditions.

    These are expressed in a series of conditions, each having zero or more
    components. Conditions for the same condition type will have sequence
    numbers. Conditions of different types may be combined.
    """

    record_code = "430"
    subrecord_code = "10"
    url_pattern_name_prefix = "measure"
    url_suffix = "#conditions"

    identifying_fields = ("sid", )

    sid = SignedIntSID(db_index=True)
    dependent_measure = models.ForeignKey(
        Measure,
        on_delete=models.PROTECT,
        related_name="conditions",
    )
    condition_code = models.ForeignKey(
        MeasureConditionCode,
        on_delete=models.PROTECT,
        related_name="conditions",
    )
    component_sequence_number = models.PositiveSmallIntegerField(
        validators=[validators.validate_component_sequence_number], )
    duty_amount = models.DecimalField(
        max_digits=10,
        decimal_places=3,
        null=True,
        blank=True,
    )
    monetary_unit = models.ForeignKey(
        MonetaryUnit,
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )
    condition_measurement = models.ForeignKey(
        Measurement,
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )
    action = models.ForeignKey(
        MeasureAction,
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )
    required_certificate = models.ForeignKey(
        "certificates.Certificate",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )

    objects = TrackedModelManager.from_queryset(MeasureConditionQuerySet)()

    indirect_business_rules = (
        business_rules.MA2,
        business_rules.MC4,
        business_rules.ME53,
    )
    business_rules = (
        business_rules.MC3,
        business_rules.MA4,
        business_rules.ME56,
        business_rules.ME57,
        business_rules.ME58,
        business_rules.ME59,
        business_rules.ME60,
        business_rules.ME61,
        business_rules.ME62,
        business_rules.ME63,
        business_rules.ME64,
        business_rules.ActionRequiresDuty,
        business_rules.ConditionCodeAcceptance,
        UniqueIdentifyingFields,
        UpdateValidity,
    )

    class Meta:
        ordering = [
            "dependent_measure__sid",
            "condition_code__code",
            "component_sequence_number",
        ]

    def is_certificate_required(self):
        return self.condition_code.code in ("A", "B", "C", "H", "Q", "Y", "Z")

    @property
    def description(self) -> str:
        out: list[str] = []

        out.append(
            f"Condition of type {self.condition_code.code} - {self.condition_code.description}",
        )

        if self.required_certificate:
            out.append(
                f"On presentation of certificate {self.required_certificate.code},",
            )
        elif self.is_certificate_required():
            out.append("On presentation of no certificate,")

        if hasattr(self,
                   "reference_price_string") and self.reference_price_string:
            out.append(f"If reference price > {self.reference_price_string},")

        out.append(
            f"perform action {self.action.code} - {self.action.description}")

        if self.condition_string:
            out.append(f"\n\nApplicable duty is {self.condition_string}")

        return " ".join(out)

    @property
    def condition_string(self) -> str:
        out: list[str] = []

        components = self.components.latest_approved()
        measures: set[str] = set()
        measure_types: set[str] = set()
        additional_codes: set[str] = set()

        for mcc in components:
            measures.add(mcc.condition.dependent_measure.sid)
            measure_types.add(mcc.condition.dependent_measure.measure_type.sid)
            if mcc.condition.dependent_measure.additional_code:
                additional_codes.add(
                    mcc.condition.dependent_measure.additional_code.sid, )

        if (len(measures) == len(measure_types) == len(additional_codes) == 1
                or len(measure_types) > 1 or len(additional_codes) > 1):
            out.append(self.duty_sentence)

        return "".join(out)

    @property
    def duty_sentence(self) -> str:
        return MeasureConditionComponent.objects.duty_sentence(self)