def test_package_creation_does_not_increase_price_for_cheaper_1_day_long_old_package_in_the_end_of_the_month( self): old_component_price = 5 new_component_price = old_component_price + 5 start_date = timezone.datetime(2014, 2, 27, tzinfo=pytz.UTC) package_change_date = timezone.datetime(2014, 2, 28, tzinfo=pytz.UTC) end_of_the_month = core_utils.month_end(package_change_date) with freeze_time(start_date): old_package = invoices_fixtures.create_package( component_price=old_component_price) tenant = old_package.tenant with freeze_time(package_change_date): old_package.delete() new_template = invoices_fixtures.create_package_template( component_price=new_component_price) new_package = packages_factories.OpenStackPackageFactory( template=new_template, tenant=tenant, ) old_components_price = old_package.template.price * ( package_change_date - start_date).days second_component_usage_days = invoices_utils.get_full_days( package_change_date, end_of_the_month) new_components_price = new_package.template.price * second_component_usage_days expected_price = old_components_price + new_components_price # assert self.assertEqual(invoices_models.Invoice.objects.count(), 1) self.assertEqual(Decimal(expected_price), invoices_models.Invoice.objects.first().price)
def send_invoice_report(): """ Sends aggregate accounting data as CSV """ date = get_previous_month() subject = render_to_string( 'invoices/report_subject.txt', {'month': date.month, 'year': date.year,} ).strip() body = render_to_string( 'invoices/report_body.txt', {'month': date.month, 'year': date.year,} ).strip() filename = '3M%02d%dWaldur.txt' % (date.month, date.year) invoices = models.Invoice.objects.filter(year=date.year, month=date.month) # Report should include only organizations that had accounting running during the invoice period. if settings.WALDUR_CORE['ENABLE_ACCOUNTING_START_DATE']: invoices = invoices.filter( customer__accounting_start_date__lte=core_utils.month_end(date) ) # Report should not include customers with 0 invoice sum. invoices = [invoice for invoice in invoices if invoice.total > 0] text_message = format_invoice_csv(invoices) # Please note that email body could be empty if there are no valid invoices emails = [settings.WALDUR_INVOICES['INVOICE_REPORTING']['EMAIL']] logger.debug('About to send accounting report to {emails}'.format(emails=emails)) core_utils.send_mail_with_attachment( subject=subject, body=body, to=emails, attachment=text_message, filename=filename, )
def component_usage_register(component_usage): from waldur_mastermind.slurm_invoices.registrators import AllocationRegistrator plan_period = component_usage.plan_period if not plan_period: logger.warning( 'Skipping processing of component usage with ID %s because ' 'plan period is not defined.', component_usage.id, ) return try: plan = plan_period.plan plan_component = plan.components.get(component=component_usage.component) allocation = component_usage.resource.scope customer = allocation.project.customer invoice, created = registrators.RegistrationManager.get_or_create_invoice( customer, component_usage.date ) details = AllocationRegistrator().get_component_details( allocation, plan_component ) details['plan_period_id'] = plan_period.id offering_component = plan_component.component month_start = core_utils.month_start(component_usage.date) month_end = core_utils.month_end(component_usage.date) start = ( month_start if not component_usage.plan_period.start else max(component_usage.plan_period.start, month_start) ) end = ( month_end if not component_usage.plan_period.end else min(component_usage.plan_period.end, month_end) ) invoice_models.InvoiceItem.objects.create( content_type=ContentType.objects.get_for_model(allocation), object_id=allocation.id, project=allocation.project, invoice=invoice, start=start, end=end, details=details, unit_price=plan_component.price, quantity=component_usage.usage, unit=common_mixins.UnitPriceMixin.Units.QUANTITY, product_code=offering_component.product_code or plan.product_code, article_code=offering_component.article_code or plan.article_code, ) except marketplace_models.PlanComponent.DoesNotExist: logger.warning( 'Plan component for usage component %s is not found.', component_usage.id )
def test_switch_plan_resource(self): self.order_item_process(self.order_item) resource = self.order_item.resource resource.plan = self.fixture.new_plan resource.save() new_start = datetime.datetime.now() end = month_end(new_start) old_items = invoices_models.InvoiceItem.objects.filter( project=resource.project, end=new_start, ) unit_price = 0 self.assertEqual(old_items.count(), 2) for i in old_items: self.assertTrue(self.fixture.plan.name in i.name) unit_price += i.unit_price self.assertEqual(unit_price, self.fixture.plan.unit_price) new_items = invoices_models.InvoiceItem.objects.filter( project=resource.project, start=new_start, end=end, ) unit_price = 0 self.assertEqual(new_items.count(), 2) for i in new_items: self.assertTrue(self.fixture.new_plan.name in i.name) unit_price += i.unit_price self.assertEqual(unit_price, self.fixture.new_plan.unit_price)
def get_invoice_item_for_component_usage(component_usage): if not component_usage.plan_period: # Field plan_period is optional if component_usage is not connected with billing return else: if component_usage.plan_period.end: plan_period_end = component_usage.plan_period.end else: plan_period_end = core_utils.month_end( component_usage.billing_period) if component_usage.plan_period.start: plan_period_start = component_usage.plan_period.start else: plan_period_start = component_usage.billing_period try: item = invoice_models.InvoiceItem.objects.get( invoice__year=component_usage.billing_period.year, invoice__month=component_usage.billing_period.month, resource=component_usage.resource, start__gte=plan_period_start, end__lte=plan_period_end, details__offering_component_type=component_usage.component.type, ) return item except invoice_models.InvoiceItem.DoesNotExist: pass
def test_package_creation_increases_price_from_old_package_if_it_is_more_expensive_in_the_end_of_the_month( self): old_component_price = 15 new_component_price = old_component_price - 5 start_date = timezone.datetime(2014, 2, 20, tzinfo=pytz.UTC) package_change_date = timezone.datetime(2014, 2, 28, tzinfo=pytz.UTC) end_of_the_month = core_utils.month_end(package_change_date) with freeze_time(start_date): old_package = fixtures.create_package( component_price=old_component_price) customer = old_package.tenant.service_project_link.project.customer tenant = old_package.tenant with freeze_time(package_change_date): old_package.delete() new_template = fixtures.create_package_template( component_price=new_component_price) new_package = packages_factories.OpenStackPackageFactory( template=new_template, tenant=tenant, ) old_components_price = old_package.template.price * ( (package_change_date - start_date).days + 1) second_component_usage_days = utils.get_full_days( package_change_date, end_of_the_month) - 1 new_components_price = new_package.template.price * second_component_usage_days expected_price = old_components_price + new_components_price # assert self.assertEqual(models.Invoice.objects.count(), 1) self.assertEqual(Decimal(expected_price), models.Invoice.objects.first().price)
def test_invoice_item_modification(self): new_quantity = 200 new_month_end = month_end(timezone.now() + datetime.timedelta(weeks=5)) new_item_data = self.get_common_data(quantity=new_quantity, end=new_month_end) old_item_data = self.get_common_data() self.client_mock().get_invoice_for_customer.return_value = { 'items': [{ 'resource_uuid': self.resource.backend_id, **new_item_data, }] } invoice = InvoiceFactory(customer=self.customer) item = InvoiceItemFactory( invoice=invoice, resource=self.resource, **old_item_data, ) self.assertNotEqual(new_month_end, item.end) ResourceInvoicePullTask().run(serialize_instance(self.resource)) item.refresh_from_db() self.assertEqual(new_quantity, item.quantity) self.assertEqual(new_month_end, item.end)
def test_package_creation_does_not_increase_price_for_cheaper_1_day_long_old_package_in_the_same_day( self): old_component_price = 10 new_component_price = old_component_price + 5 start_date = timezone.datetime(2014, 2, 26, tzinfo=pytz.UTC) package_change_date = start_date end_of_the_month = core_utils.month_end(package_change_date) with freeze_time(start_date): old_package = fixtures.create_package( component_price=old_component_price) customer = old_package.tenant.service_project_link.project.customer with freeze_time(package_change_date): old_package.delete() new_template = fixtures.create_package_template( component_price=new_component_price) new_package = packages_factories.OpenStackPackageFactory( template=new_template, tenant__service_project_link__project__customer=customer, ) old_components_price = old_package.template.price * ( package_change_date - start_date).days second_component_usage_days = utils.get_full_days( package_change_date, end_of_the_month) new_components_price = new_package.template.price * second_component_usage_days expected_price = old_components_price + new_components_price # assert self.assertEqual(models.Invoice.objects.count(), 1) self.assertEqual(Decimal(expected_price), models.Invoice.objects.first().price)
def component_usage_register(component_usage): from waldur_mastermind.support_invoices.registrators import OfferingRegistrator plan_period = component_usage.plan_period if not plan_period: logger.warning('Skipping processing of component usage with ID %s because ' 'plan period is not defined.', component_usage.id) return plan = plan_period.plan try: plan_component = plan.components.get(component=component_usage.component) item = invoice_models.InvoiceItem.objects.get(scope=component_usage.resource.scope, details__plan_period_id=plan_period.id, details__plan_component_id=plan_component.id) item.quantity = component_usage.usage item.unit_price = plan_component.price item.save() except invoice_models.InvoiceItem.DoesNotExist: offering = component_usage.resource.scope customer = offering.project.customer invoice, created = registrators.RegistrationManager.get_or_create_invoice(customer, component_usage.date) details = OfferingRegistrator().get_component_details(offering, plan_component) details['plan_period_id'] = plan_period.id offering_component = plan_component.component month_start = core_utils.month_start(component_usage.date) month_end = core_utils.month_end(component_usage.date) start = month_start if not component_usage.plan_period.start else \ max(component_usage.plan_period.start, month_start) end = month_end if not component_usage.plan_period.end else \ min(component_usage.plan_period.end, month_end) invoice_models.InvoiceItem.objects.create( content_type=ContentType.objects.get_for_model(offering), object_id=offering.id, project=offering.project, invoice=invoice, start=start, end=end, details=details, unit_price=plan_component.price, quantity=component_usage.usage, unit=common_mixins.UnitPriceMixin.Units.QUANTITY, product_code=offering_component.product_code or plan.product_code, article_code=offering_component.article_code or plan.article_code, ) except marketplace_models.PlanComponent.DoesNotExist: logger.warning('Plan component for usage component %s is not found.', component_usage.id) except invoice_models.InvoiceItem.MultipleObjectsReturned: logger.warning('Skipping the invoice item unit price update ' 'because multiple GenericInvoiceItem objects found. Scope: %s %s, date: %s.', component_usage.resource.content_type, component_usage.resource.object_id, component_usage.date)
def test_new_invoice_is_created_in_new_month_after_half_month_of_usage(self): """ Tests that invoices are created and updated according to the current state of customer's package. Steps: - Test that invoice has been created; - Check price of it in the end of the month; - Ensure that a new invoice has been generated in the new month; - Assert that end date of newly created openstack item set to the date of package deletion. :return: """ self.client.force_authenticate(user=self.fixture.staff) middle_of_the_month = datetime(2017, 1, 15, tzinfo=pytz.UTC) with freeze_time(middle_of_the_month): payload = self.get_package_create_payload() response = self.client.post(self.url, data=payload) self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.data) self.assertEqual(models.Invoice.objects.count(), 1) template = package_models.PackageTemplate.objects.first() price_per_day = template.price end_of_the_month = datetime(2017, 1, 31, 23, 59, 59, tzinfo=pytz.UTC) expected_price = utils.get_full_days(middle_of_the_month, end_of_the_month) * price_per_day with freeze_time(end_of_the_month): invoice = models.Invoice.objects.first() self.assertEqual(invoice.price, expected_price) beginning_of_the_new_month = datetime(2017, 2, 1, tzinfo=pytz.UTC) task_triggering_date = datetime(2017, 2, 2, 23, 59, 59, tzinfo=pytz.UTC) end_of_the_new_month = core_utils.month_end(beginning_of_the_new_month) expected_price = utils.get_full_days(beginning_of_the_new_month, end_of_the_new_month) * price_per_day with freeze_time(task_triggering_date): tasks.create_monthly_invoices() self.assertEqual(models.Invoice.objects.count(), 2) invoice.refresh_from_db() second_invoice = models.Invoice.objects.exclude(pk=invoice.pk).first() self.assertEqual(second_invoice.price, expected_price) self.assertEqual(invoice.state, models.Invoice.States.CREATED) self.assertEqual(invoice.invoice_date, datetime.now().date()) self.assertEqual(second_invoice.state, models.Invoice.States.PENDING) self.assertIsNone(second_invoice.invoice_date) package_deletion_date = datetime(2017, 2, 20, tzinfo=pytz.UTC) expected_price = (package_deletion_date - beginning_of_the_new_month).days * price_per_day with freeze_time(package_deletion_date): package = package_models.OpenStackPackage.objects.first() package.delete() week_after_deletion = datetime(2017, 2, 27, tzinfo=pytz.UTC) with freeze_time(week_after_deletion): second_invoice.refresh_from_db() self.assertEqual(expected_price, second_invoice.price) openstack_item = second_invoice.generic_items.first() self.assertEqual(openstack_item.end.date(), package_deletion_date.date())
def _find_item(self, source, now): resource = marketplace_models.Resource.objects.get(scope=source) return list( invoices_models.InvoiceItem.objects.filter( resource=resource, invoice__customer=self.get_customer(source), invoice__state=invoices_models.Invoice.States.PENDING, invoice__year=now.year, invoice__month=now.month, end=core_utils.month_end(now), ))
def shift_forward(self): """ Adjust old invoice item end field to the end of current unit. Adjust new invoice item start field to the start of next unit. """ end = self.old_item.end if self.old_item.unit != self.unit and self.unit == Units.PER_MONTH: end = core_utils.month_end(end) elif self.old_item.unit != self.unit and self.unit == Units.PER_HALF_MONTH: if end.day > 15: end = core_utils.month_end(end) else: end = end.replace(day=15) elif self.unit == Units.PER_HOUR: end = end.replace(minute=59, second=59) else: end = end.replace(hour=23, minute=59, second=59) start = end + timedelta(seconds=1) return start, end
def register_offering(self, offering, start=None): if start is None: start = timezone.now() end = core_utils.month_end(start) OfferingItem.objects.create( offering=offering, unit_price=offering.unit_price, unit=offering.unit, invoice=self, start=start, end=end, )
def test_existing_invoice_is_updated_on_offering_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) with freeze_time(start_date): invoice = factories.InvoiceFactory(customer=self.fixture.customer) offering = self.fixture.offering offering.state = offering.States.OK offering.save() self.assertEqual(models.Invoice.objects.count(), 1) self.assertTrue(invoice.generic_items.filter(scope=offering).exists()) expected_price = offering.unit_price * usage_days self.assertEqual(invoice.price, Decimal(expected_price))
def test_invoice_price_is_not_changed_after_a_while_if_offering_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) with freeze_time(start_date): offering = self.fixture.offering offering.state = offering.States.OK offering.save() self.assertEqual(models.Invoice.objects.count(), 1) invoice = models.Invoice.objects.first() with freeze_time(end_date): offering.delete() expected_price = offering.unit_price * usage_days self.assertEqual(invoice.price, Decimal(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 get_common_data(self, start=None, end=None, quantity=100): now = timezone.now() if start is None: start = month_start(now) if end is None: end = month_end(now) return { 'unit': 'sample-unit', 'name': 'Fake invoice item', 'measured_unit': 'sample-m-unit', 'article_code': '', 'unit_price': 2.0, 'details': {}, 'quantity': quantity, 'start': start.isoformat(), 'end': end.isoformat(), }
def _find_item(self, source, now): """ Find an item or some items by source and date. :param source: object that was bought by customer. :param now: date of invoice with invoice items. :return: invoice item, item's list (or another iterable object, f.e. tuple or queryset) or None """ return list( invoice_models.InvoiceItem.objects.filter( resource=source, invoice__customer=self.get_customer(source), invoice__state=invoice_models.Invoice.States.PENDING, invoice__year=now.year, invoice__month=now.month, end=core_utils.month_end(now), ) )
def test_switch_invoice_item_if_plan_switched(self): self.get_order_item(self.success_issue_status) new_start = datetime.datetime.now() end = month_end(new_start) self.assertTrue(invoices_models.InvoiceItem.objects.filter( scope=self.request, project=self.project, unit_price=Decimal(10), start=self.start, end=new_start, ).exists()) self.assertTrue(invoices_models.InvoiceItem.objects.filter( scope=self.request, project=self.project, unit_price=Decimal(50), start=new_start, end=end, ).exists())
def _find_item(self, source, now): """ Find a list of items by source and date. :param source: object that was bought by customer. :param now: date of invoice with invoice items. :return: list of invoice items related to allocation (source) """ model_type = ContentType.objects.get_for_model(source) result = invoice_models.InvoiceItem.objects.filter( content_type=model_type, object_id=source.id, invoice__customer=self.get_customer(source), invoice__state=invoice_models.Invoice.States.PENDING, invoice__year=now.year, invoice__month=now.month, end=core_utils.month_end(now), ) return result
def update_invoice_item_on_allocation_usage_update( sender, instance, created=False, **kwargs ): allocation_usage = instance allocation = allocation_usage.allocation package = utils.get_package(allocation) if package: start = timezone.now() end = core_utils.month_end(start) registrator = slurm_registrators.AllocationRegistrator() customer = registrator.get_customer(allocation) invoice = invoice_models.Invoice.objects.get( customer=customer, month=start.month, year=start.year, ) registrator.create_or_update_items( allocation, allocation_usage, package, invoice, start, end )
def _find_item(self, source, now): """ Find an item or some items by source and date. :param source: object that was bought by customer. :param now: date of invoice with invoice items. :return: invoice item, item's list (or another iterable object, f.e. tuple or queryset) or None """ model_type = ContentType.objects.get_for_model(source) result = invoices_models.InvoiceItem.objects.filter( content_type=model_type, object_id=source.id, invoice__customer=self.get_customer(source), invoice__state=invoices_models.Invoice.States.PENDING, invoice__year=now.year, invoice__month=now.month, end=core_utils.month_end(now), ).first() return result
def test_existing_invoice_is_update_on_offering_creation_if_it_has_package_item_for_same_customer(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) with freeze_time(start_date): packages_factories.OpenStackPackageFactory( tenant__service_project_link__project__customer=self.fixture.customer) self.assertEqual(models.Invoice.objects.count(), 1) invoice = models.Invoice.objects.first() components_price = invoice.price offering = self.fixture.offering offering.state = offering.States.OK offering.save() self.assertEqual(models.Invoice.objects.count(), 1) self.assertTrue(invoice.offering_items.filter(offering=offering).exists()) expected_price = offering.unit_price * usage_days + components_price self.assertEqual(invoice.price, Decimal(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 test_switch_plan_resource(self): resource = self.order_item.resource resource.plan = self.fixture.new_plan resource.save() new_start = datetime.datetime.now() end = month_end(new_start) old_item = invoices_models.GenericInvoiceItem.objects.get( project=resource.project, unit_price=Decimal(10), end=new_start, ) self.assertTrue(self.fixture.plan.name in old_item.details['name']) new_item = invoices_models.GenericInvoiceItem.objects.get( project=resource.project, unit_price=Decimal(5), start=new_start, end=end, ) self.assertTrue(self.fixture.new_plan.name in new_item.details['name'])
def register(self, sources, invoice, start, **kwargs): """ For each source create invoice item and register it in invoice. """ end = core_utils.month_end(start) for source in sources: self._create_item(source, invoice, start=start, end=end, **kwargs)
def consumed_in_month(self): """ How many resources were (or will be) consumed until end of the month """ month_end = core_utils.month_end( datetime.date(self.price_estimate.year, self.price_estimate.month, 1)) return self._get_consumed(month_end)
def get_current_month_end(): return core_utils.month_end(timezone.now())
def get_last_day_of_month(self, invoice_item): first_day = self.get_first_day(invoice_item) last_day = core_utils.month_end(first_day) return self.format_date(last_day)
def get_covered_period(self, invoice_item): first_day = self.get_first_day(invoice_item) last_day = core_utils.month_end(first_day) return '%s-%s' % (self.format_date(first_day), self.format_date(last_day))