コード例 #1
0
ファイル: rent.py プロジェクト: igordavydsson/mvj
    def get_index_for_date(self, the_date):
        if self.cycle == RentCycle.APRIL_TO_MARCH and is_date_on_first_quarter(
                the_date):
            return Index.objects.get_latest_for_year(the_date.year - 1)

        return Index.objects.get_latest_for_date(the_date)
コード例 #2
0
def test_is_date_on_first_quarter(the_day, expected):
    if inspect.isclass(expected) and issubclass(expected, Exception):
        with pytest.raises(expected):
            is_date_on_first_quarter(the_day)
    else:
        assert is_date_on_first_quarter(the_day) == expected
コード例 #3
0
ファイル: rent.py プロジェクト: igordavydsson/mvj
    def get_amount_for_date_range(self,
                                  date_range_start,
                                  date_range_end,
                                  explain=False):  # noqa: C901 TODO
        assert date_range_start <= date_range_end, 'date_range_start cannot be after date_range_end.'

        explanation = Explanation()

        range_filtering = Q(
            Q(Q(end_date=None) | Q(end_date__gte=date_range_start))
            & Q(Q(start_date=None) | Q(start_date__lte=date_range_end)))

        fixed_initial_year_rents = self.fixed_initial_year_rents.filter(
            range_filtering)
        contract_rents = self.contract_rents.filter(range_filtering)
        rent_adjustments = self.rent_adjustments.filter(range_filtering)

        total = Decimal('0.00')
        fixed_applied = False
        remaining_ranges = []

        # TODO: seasonal spanning multiple years
        if self.is_seasonal():
            seasonal_period_start = datetime.date(
                year=date_range_start.year,
                month=self.seasonal_start_month,
                day=self.seasonal_start_day)
            seasonal_period_end = datetime.date(year=date_range_start.year,
                                                month=self.seasonal_end_month,
                                                day=self.seasonal_end_day)

            if date_range_start < seasonal_period_start and date_range_start < seasonal_period_end:
                date_range_start = seasonal_period_start

            if date_range_end > seasonal_period_end and date_range_end > seasonal_period_start:
                date_range_end = seasonal_period_end
        else:
            if ((self.start_date and date_range_start < self.start_date) and
                (not self.end_date or date_range_start < self.end_date)):
                date_range_start = self.start_date

            if ((self.end_date and date_range_end > self.end_date) and
                (self.start_date and date_range_end > self.start_date)):
                date_range_end = self.end_date

        for fixed_initial_year_rent in fixed_initial_year_rents:
            (fixed_overlap,
             fixed_remainders) = get_range_overlap_and_remainder(
                 date_range_start, date_range_end,
                 *fixed_initial_year_rent.date_range)

            if not fixed_overlap:
                continue

            if fixed_remainders:
                remaining_ranges.extend(fixed_remainders)

            fixed_applied = True

            fixed_amount = fixed_initial_year_rent.get_amount_for_date_range(
                *fixed_overlap)

            fixed_explanation_item = explanation.add(
                subject=fixed_initial_year_rent,
                date_ranges=[fixed_overlap],
                amount=fixed_amount)

            for rent_adjustment in rent_adjustments:
                if fixed_initial_year_rent.intended_use and \
                        rent_adjustment.intended_use != fixed_initial_year_rent.intended_use:
                    continue

                (adjustment_overlap,
                 adjustment_remainders) = get_range_overlap_and_remainder(
                     fixed_overlap[0], fixed_overlap[1],
                     *rent_adjustment.date_range)

                if not adjustment_overlap:
                    continue

                tmp_amount = fix_amount_for_overlap(fixed_amount,
                                                    adjustment_overlap,
                                                    adjustment_remainders)
                adjustment_amount = rent_adjustment.get_amount_for_date_range(
                    tmp_amount, *adjustment_overlap)
                fixed_amount += adjustment_amount

                explanation.add(subject=rent_adjustment,
                                date_ranges=[adjustment_overlap],
                                amount=adjustment_amount,
                                related_item=fixed_explanation_item)

            total += fixed_amount

        if fixed_applied:
            if not remaining_ranges:
                if explain:
                    explanation.add(subject=self,
                                    date_ranges=[(date_range_start,
                                                  date_range_end)],
                                    amount=total)

                    return total, explanation
                else:
                    return total
            else:
                date_ranges = remaining_ranges
        else:
            date_ranges = [(date_range_start, date_range_end)]

        # We may need to calculate multiple separate ranges if the rent
        # type is index or manual because the index number could be different
        # in different years.
        if self.type in [RentType.INDEX, RentType.MANUAL]:
            date_ranges = self.split_ranges_by_cycle(date_ranges)

        for (range_start, range_end) in date_ranges:
            if self.type == RentType.ONE_TIME:
                total += self.amount
                continue

            for contract_rent in contract_rents:
                (contract_overlap,
                 _remainder) = get_range_overlap_and_remainder(
                     range_start, range_end, *contract_rent.date_range)

                if not contract_overlap:
                    continue

                if self.type == RentType.FIXED:
                    contract_amount = contract_rent.get_amount_for_date_range(
                        *contract_overlap)
                    contract_rent_explanation_item = explanation.add(
                        subject=contract_rent,
                        date_ranges=[contract_overlap],
                        amount=contract_amount)
                elif self.type == RentType.MANUAL:
                    contract_amount = contract_rent.get_amount_for_date_range(
                        *contract_overlap)
                    explanation.add(subject=contract_rent,
                                    date_ranges=[contract_overlap],
                                    amount=contract_amount)

                    manual_ratio = self.manual_ratio

                    if self.cycle == RentCycle.APRIL_TO_MARCH and is_date_on_first_quarter(
                            contract_overlap[0]):
                        manual_ratio = self.manual_ratio_previous

                    contract_amount *= manual_ratio

                    contract_rent_explanation_item = explanation.add(
                        subject={
                            "subject_type":
                            "ratio",
                            "description":
                            _("Manual ratio {ratio}").format(
                                ratio=manual_ratio),
                        },
                        date_ranges=[contract_overlap],
                        amount=contract_amount)
                elif self.type == RentType.INDEX:
                    original_rent_amount = contract_rent.get_base_amount_for_date_range(
                        *contract_overlap)

                    index = self.get_index_for_date(contract_overlap[0])

                    index_calculation = IndexCalculation(
                        amount=original_rent_amount,
                        index=index,
                        index_type=self.index_type,
                        precision=self.index_rounding,
                        x_value=self.x_value,
                        y_value=self.y_value)

                    contract_amount = index_calculation.calculate()

                    contract_rent_explanation_item = explanation.add(
                        subject=contract_rent,
                        date_ranges=[contract_overlap],
                        amount=original_rent_amount)

                    index_explanation_item = explanation.add(
                        subject=index,
                        date_ranges=[contract_overlap],
                        amount=contract_amount,
                        related_item=contract_rent_explanation_item)

                    for item in index_calculation.explanation_items:
                        explanation.add_item(
                            item, related_item=index_explanation_item)

                elif self.type == RentType.FREE:
                    continue
                else:
                    raise NotImplementedError(
                        'RentType {} not implemented'.format(self.type))

                for rent_adjustment in rent_adjustments:
                    if rent_adjustment.intended_use != contract_rent.intended_use:
                        continue

                    (adjustment_overlap,
                     adjustment_remainders) = get_range_overlap_and_remainder(
                         contract_overlap[0], contract_overlap[1],
                         *rent_adjustment.date_range)

                    if not adjustment_overlap:
                        continue

                    tmp_amount = fix_amount_for_overlap(
                        contract_amount, adjustment_overlap,
                        adjustment_remainders)
                    adjustment_amount = rent_adjustment.get_amount_for_date_range(
                        tmp_amount, *adjustment_overlap)
                    contract_amount += adjustment_amount

                    explanation.add(
                        subject=rent_adjustment,
                        date_ranges=[adjustment_overlap],
                        amount=adjustment_amount,
                        related_item=contract_rent_explanation_item)

                total += max(Decimal(0), contract_amount)

        explanation.add(subject=self,
                        date_ranges=[(date_range_start, date_range_end)],
                        amount=total)

        if explain:
            return total, explanation
        else:
            return total