def get_factor(self, current=False): month_days = monthrange(self.start.year, self.start.month)[1] if self.unit == self.Units.QUANTITY: return self.quantity elif self.unit == self.Units.PER_HOUR: if current: return utils.get_full_hours(self.start, min(self.end, timezone.now())) else: return utils.get_full_hours(self.start, self.end) elif self.unit == self.Units.PER_DAY: if current: return utils.get_full_days(self.start, min(self.end, timezone.now())) else: return self.usage_days elif self.unit == self.Units.PER_HALF_MONTH: if (self.start.day == 1 and self.end.day == 15) or (self.start.day == 16 and self.end.day == month_days): return 1 elif (self.start.day == 1 and self.end.day == month_days): return 2 elif (self.start.day == 1 and self.end.day > 15): return quantize_price(1 + (self.end.day - 15) / decimal.Decimal(month_days / 2)) elif (self.start.day < 16 and self.end.day == month_days): return quantize_price(1 + (16 - self.start.day) / decimal.Decimal(month_days / 2)) else: return quantize_price((self.end.day - self.start.day + 1) / decimal.Decimal(month_days / 2.0)) # By default PER_MONTH else: if self.start.day == 1 and self.end.day == month_days: return 1 use_days = (self.end - self.start).days + 1 return quantize_price(decimal.Decimal(use_days) / month_days)
def get_factor(item): month_days = monthrange(item.start.year, item.start.month)[1] if item.unit == Units.QUANTITY: return item.quantity elif item.unit == Units.PER_HOUR: return utils.get_full_hours(item.start, item.end) elif item.unit == Units.PER_DAY: return usage_days(item) elif item.unit == Units.PER_HALF_MONTH: if (item.start.day == 1 and item.end.day == 15) or ( item.start.day == 16 and item.end.day == month_days): return 1 elif item.start.day == 1 and item.end.day == month_days: return 2 elif item.start.day == 1 and item.end.day > 15: return quantize_price(1 + (item.end.day - 15) / decimal.Decimal(month_days / 2)) elif item.start.day < 16 and item.end.day == month_days: return quantize_price(1 + (16 - item.start.day) / decimal.Decimal(month_days / 2)) else: return quantize_price((item.end.day - item.start.day + 1) / decimal.Decimal(month_days / 2.0)) # By default PER_MONTH else: if item.start.day == 1 and item.end.day == month_days: return 1 use_days = (item.end - item.start).days + 1 return quantize_price(decimal.Decimal(use_days) / month_days)
def get_quantity(unit, start, end): """ For fixed components this method computes number of billing periods resource was used from the time it was purchased or from the start of current month till the time it was terminated or billing plan has been switched or end of current month. """ month_days = monthrange(start.year, start.month)[1] if unit == Units.PER_HOUR: return utils.get_full_hours(start, end) elif unit == Units.PER_DAY: return utils.get_full_days(start, end) elif unit == Units.PER_HALF_MONTH: if (start.day == 1 and end.day == 15) or (start.day == 16 and end.day == month_days): return 1 elif start.day == 1 and end.day == month_days: return 2 elif start.day == 1 and end.day > 15: return quantize_price(1 + (end.day - 15) / decimal.Decimal(month_days / 2)) elif start.day < 16 and end.day == month_days: return quantize_price(1 + (16 - start.day) / decimal.Decimal(month_days / 2)) else: return quantize_price( (end.day - start.day + 1) / decimal.Decimal(month_days / 2.0)) # By default PER_MONTH else: if start.day == 1 and end.day == month_days: return 1 use_days = (end - start).days + 1 return quantize_price(decimal.Decimal(use_days) / month_days)
def test_case_when_usage_is_reported_for_switched_plan(self): self.assertEqual(self.invoice.price, self.fixture.plan.unit_price) self._switch_plan() fixed_price = (Decimal(self.fixture.plan.unit_price) * quantize_price(Decimal(15 / 31.0))) + \ (Decimal(self.fixture.new_plan.unit_price) * quantize_price(Decimal(17 / 31.0))) self.assertEqual(self.invoice.price, fixed_price) self._create_usage(datetime.date(2018, 1, 10), usage=10) self.invoice.refresh_from_db() self.assertEqual( self.invoice.price, fixed_price + self.fixture.plan_component_cpu.price * 10)
def test_case_when_usage_is_reported_for_new_plan(self): self.assertEqual(self.invoice.items.count(), 1) self.assertEqual(self.invoice.price, self.fixture.plan.unit_price) self._switch_plan() self.assertEqual(self.invoice.items.count(), 2) fixed_price = (Decimal(self.fixture.plan.unit_price) * quantize_price( Decimal(15 / 31.0))) + (Decimal(self.fixture.new_plan.unit_price) * quantize_price(Decimal(17 / 31.0))) self.assertEqual(self.invoice.price, fixed_price) self._create_usage(usage=10) self.invoice.refresh_from_db() self.assertEqual( self.invoice.price, fixed_price + self.fixture.new_plan_component_cpu.price * 10, )
def test_factor_for_month_is_equal_to_fraction_of_days(self): item = factories.InvoiceItemFactory( start=parse_datetime('2016-11-1 14:00:00'), end=parse_datetime('2016-11-8 14:00:00'), unit=models.InvoiceItem.Units.PER_MONTH, ) self.assertEqual(item.get_factor(), quantize_price(decimal.Decimal(8.0 / 30)))
def price(self): """ Price for whole template for one day. :rtype: Decimal """ price = self.components.aggregate(total=models.Sum( models.F('price') * models.F('amount'), output_field=models.DecimalField( max_digits=22, decimal_places=10)))['total'] or Decimal('0') return quantize_price(price)
def test_invoice_item_with_start_in_first_day_and_end_in_second_half(self): start_date = timezone.datetime(2017, 7, 1) month_days = monthrange(2017, 7)[1] end_date = timezone.datetime(2017, 7, 20) usage_days = 5 offering, offering_item = self._start_end_offering( start_date, end_date) expected_price = offering.unit_price * quantize_price( 1 + (usage_days / decimal.Decimal(month_days / 2))) self.assertEqual(offering_item.price, expected_price)
def monthly_price(self): """ Return price for one month in case of monthly billing plan or price for 30 days otherwise. :rtype: Decimal """ if self.unit == common_mixins.UnitPriceMixin.Units.PER_MONTH: price = self.components.aggregate( total=models.Sum(models.F('price') * models.F('amount'), output_field=models.DecimalField( max_digits=22, decimal_places=10) ))['total'] or Decimal('0') return quantize_price(price) elif self.unit == common_mixins.UnitPriceMixin.Units.PER_DAY: return self.price * 30
def test_invoice_item_with_monthly_price(self): start_date = timezone.datetime(2017, 7, 20) month_days = monthrange(2017, 7)[1] usage_days = month_days - start_date.day + 1 offering = support_factories.OfferingFactory( unit=common_mixins.UnitPriceMixin.Units.PER_MONTH) with freeze_time(start_date): offering.state = support_models.Offering.States.OK offering.save(update_fields=['state']) expected_price = offering.unit_price * quantize_price( decimal.Decimal((usage_days / month_days))) offering_item = models.InvoiceItem.objects.get(scope=offering) self.assertEqual(offering_item.price, expected_price)
def _price(self, current=False): """ For components billed daily and hourly this method returns estimated price if `current` is True. Otherwise, it returns total price calculated using `quantity` field. It is assumed that value of `quantity` field is updated automatically when invoice item is terminated. """ quantity = self.quantity if current: if self.unit == self.Units.PER_HOUR: quantity = utils.get_full_hours(self.start, min(self.end, timezone.now())) if self.unit == self.Units.PER_DAY: quantity = utils.get_full_days(self.start, min(self.end, timezone.now())) return quantize_price(self.unit_price * decimal.Decimal(quantity))
def test_invoice_item_with_monthly_price(self): start_date = timezone.datetime(2017, 7, 20) end_date = timezone.datetime(2017, 7, 31) resource = self.fixture.resource with freeze_time(start_date): resource.set_state_ok() resource.save() use_days = (end_date - start_date).days + 1 month_days = monthrange(start_date.year, start_date.month)[1] factor = quantize_price(decimal.Decimal(use_days) / month_days) expected_price = self.fixture.plan_component.price * factor invoice_item = models.InvoiceItem.objects.get(resource=resource) self.assertEqual(invoice_item.price, expected_price)
def test_existing_invoice_is_updated_on_resource_creation(self): start_date = timezone.datetime(2014, 2, 27, tzinfo=pytz.UTC) end_date = core_utils.month_end(start_date) usage_days = utils.get_full_days(start_date, end_date) month_days = monthrange(start_date.year, start_date.month)[1] factor = quantize_price(decimal.Decimal(usage_days) / month_days) with freeze_time(start_date): invoice = factories.InvoiceFactory(customer=self.fixture.customer) self.resource.set_state_ok() self.resource.save() self.assertEqual(models.Invoice.objects.count(), 1) self.assertTrue(invoice.items.filter(resource=self.resource).exists()) expected_price = self.plan_component.price * factor self.assertEqual(invoice.price, Decimal(expected_price))
def test_invoice_item_with_half_monthly_price_with_start_in_first_half( self): start_date = timezone.datetime(2017, 7, 14) resource = self.fixture.resource self.fixture.plan.unit = common_mixins.UnitPriceMixin.Units.PER_HALF_MONTH self.fixture.plan.save() with freeze_time(start_date): resource.set_state_ok() resource.save() month_days = monthrange(2017, 7)[1] factor = quantize_price(1 + (16 - start_date.day) / decimal.Decimal(month_days / 2)) expected_price = self.fixture.plan_component.price * factor invoice_item = models.InvoiceItem.objects.get(resource=resource) self.assertEqual(invoice_item.price, expected_price)
def stats(self, request, uuid=None): invoice = self.get_object() offerings = {} for item in invoice.items.all(): if not item.scope: continue if isinstance(item.scope, marketplace_models.Resource): resource = item.scope offering = resource.offering customer = offering.customer service_category_title = offering.category.title service_provider_name = customer.name service_provider_uuid = customer.serviceprovider.uuid.hex elif isinstance(item.scope, support_models.Offering): offering = item.scope.template service_category_title = 'Request' service_provider_name = '' service_provider_uuid = '' else: continue if offering.uuid.hex not in offerings.keys(): offerings[offering.uuid.hex] = { 'offering_name': offering.name, 'aggregated_cost': item.total, 'service_category_title': service_category_title, 'service_provider_name': service_provider_name, 'service_provider_uuid': service_provider_uuid, } else: offerings[offering.uuid.hex]['aggregated_cost'] += item.total queryset = [dict(uuid=key, **details) for (key, details) in offerings.items()] for item in queryset: item['aggregated_cost'] = quantize_price( decimal.Decimal(item['aggregated_cost']) ) page = self.paginate_queryset(queryset) return self.get_paginated_response(page)
def _start_end_offering(self, start_date, end_date): resource = self.fixture.resource use_days = (end_date - start_date).days + 1 month_days = monthrange(start_date.year, start_date.month)[1] factor = quantize_price(decimal.Decimal(use_days) / month_days) expected_price = self.fixture.plan_component.price * factor with freeze_time(start_date): resource.set_state_ok() resource.save() invoice_item = models.InvoiceItem.objects.get(resource=resource) with freeze_time(end_date): resource.set_state_terminating() resource.save() resource.set_state_terminated() resource.save() invoice_item.refresh_from_db() return resource, invoice_item, expected_price
def test_invoice_price_is_not_changed_after_a_while_if_resource_is_deleted( self): start_date = timezone.datetime(2014, 2, 27, tzinfo=pytz.UTC) end_date = core_utils.month_end(start_date) usage_days = utils.get_full_days(start_date, end_date) month_days = monthrange(start_date.year, start_date.month)[1] factor = quantize_price(decimal.Decimal(usage_days) / month_days) with freeze_time(start_date): self.resource.set_state_ok() self.resource.save() self.assertEqual(models.Invoice.objects.count(), 1) invoice = models.Invoice.objects.first() with freeze_time(end_date): self.resource.set_state_terminating() self.resource.save() self.resource.set_state_terminated() self.resource.save() expected_price = self.plan_component.price * factor self.assertEqual(invoice.price, Decimal(expected_price))
def create_file(self): parser = HTMLParser() context = { 'contract': self, 'client': { 'name': self.team.customer.name, 'laws': self.team.customer.country, 'number': self.team.customer.registration_code, 'representative': self.team.customer.get_owners()[0].full_name }, 'expert': { 'name': self.request.project.customer.name, 'laws': self.request.project.customer.country, 'number': self.request.project.customer.registration_code, 'representative': self.request.project.customer.get_owners()[0].full_name }, 'service': { 'description': parser.unescape(self.request.description), 'date_end': '' }, 'status': ExpertRequest.States.COMPLETED, 'remuneration': { 'type': 'regular payment' if self.request.recurring_billing else 'one-time payment', 'value': common_utils.quantize_price(self.price), 'currency': settings.WALDUR_EXPERTS['CURRENCY_NAME'] }, 'period': '' } contract_text = render_to_string('experts/contract_template.html', context) pdf = pdfkit.from_string(contract_text, False) self._file = base64.b64encode(pdf) self.save()
def get_factor(self, start_date, usage_days): month_days = monthrange(start_date.year, start_date.month)[1] return quantize_price(decimal.Decimal(usage_days) / month_days)
def get_tax(self, invoice_item): return quantize_price(invoice_item.tax)
def get_total(self, invoice_item): return quantize_price(invoice_item.price)
def price(self): return quantize_price( decimal.Decimal(sum((item.price for item in self.items))))
def _price(self, current=False): return quantize_price(self.unit_price * decimal.Decimal(self.get_factor(current)))
def quantize_prices(apps, schema_editor): OpenStackItem = apps.get_model('invoices', 'OpenStackItem') for item in OpenStackItem.objects.iterator(): item.daily_price = quantize_price(item.daily_price) item.save(update_fields=['daily_price'])
def price(item): return quantize_price(item.unit_price * decimal.Decimal(get_factor(item)))