diff --git a/corehq/apps/accounting/invoicing.py b/corehq/apps/accounting/invoicing.py index ca254cf0f624..52aecea41173 100644 --- a/corehq/apps/accounting/invoicing.py +++ b/corehq/apps/accounting/invoicing.py @@ -28,7 +28,6 @@ CreditLine, CustomerBillingRecord, CustomerInvoice, - DefaultProductPlan, DomainUserHistory, EntryPoint, FeatureType, @@ -84,7 +83,6 @@ def __init__(self, date_start, date_end, domain, recipients=None): def create_invoices(self): all_subscriptions = self._get_subscriptions() - self._ensure_full_coverage(all_subscriptions) chargeable_subscriptions = [sub for sub in all_subscriptions if sub.plan_version.plan.edition != SoftwarePlanEdition.PAUSED] for subscription in chargeable_subscriptions: @@ -111,47 +109,6 @@ def _get_subscriptions(self): ).order_by('date_start', 'date_end').all() return list(subscriptions) - @transaction.atomic - def _ensure_full_coverage(self, subscriptions): - plan_version = DefaultProductPlan.get_default_plan_version() - if not plan_version.feature_charges_exist_for_domain(self.domain): - return - - community_ranges = self._get_community_ranges(subscriptions) - if not community_ranges: - return - - # First check to make sure none of the existing subscriptions is set - # to do not invoice. Let's be on the safe side and not send a - # community invoice out, if that's the case. - do_not_invoice = any([s.do_not_invoice for s in subscriptions]) - - account = BillingAccount.get_or_create_account_by_domain( - self.domain.name, - created_by=self.__class__.__name__, - entry_point=EntryPoint.SELF_STARTED, - )[0] - if account.date_confirmed_extra_charges is None: - log_accounting_info( - "Did not generate invoice because date_confirmed_extra_charges " - "was null for domain %s" % self.domain.name - ) - do_not_invoice = True - - for start_date, end_date in community_ranges: - # create a new community subscription for each - # date range that the domain did not have a subscription - community_subscription = Subscription( - account=account, - plan_version=plan_version, - subscriber=self.subscriber, - date_start=start_date, - date_end=end_date, - do_not_invoice=do_not_invoice, - ) - community_subscription.save() - subscriptions.append(community_subscription) - def _create_invoice_for_subscription(self, subscription): def _get_invoice_start(sub, date_start): return max(sub.date_start, date_start) @@ -194,34 +151,6 @@ def _get_invoice_end(sub, date_end): return invoice - def _get_community_ranges(self, subscriptions): - community_ranges = [] - if len(subscriptions) == 0: - return [(self.date_start, self.date_end + datetime.timedelta(days=1))] - else: - prev_sub_end = self.date_end - for ind, sub in enumerate(subscriptions): - if ind == 0 and sub.date_start > self.date_start: - # the first subscription started AFTER the beginning - # of the invoicing period - community_ranges.append((self.date_start, sub.date_start)) - - if prev_sub_end < self.date_end and sub.date_start > prev_sub_end: - community_ranges.append((prev_sub_end, sub.date_start)) - prev_sub_end = sub.date_end - - if ( - ind == len(subscriptions) - 1 - and sub.date_end is not None - and sub.date_end <= self.date_end - ): - # the last subscription ended BEFORE the end of - # the invoicing period - community_ranges.append( - (sub.date_end, self.date_end + datetime.timedelta(days=1)) - ) - return community_ranges - def _generate_invoice(self, subscription, invoice_start, invoice_end): # use create_or_get when is_hidden_to_ops is False to utilize unique index on Invoice # so our test will make sure the unique index prevent race condition diff --git a/corehq/apps/accounting/tests/test_invoice_factory.py b/corehq/apps/accounting/tests/test_invoice_factory.py index 8ad47c26078a..80899d9847c1 100644 --- a/corehq/apps/accounting/tests/test_invoice_factory.py +++ b/corehq/apps/accounting/tests/test_invoice_factory.py @@ -57,84 +57,13 @@ def test_feature_charges(self): self.assertFalse(self.community.feature_charges_exist_for_domain(domain_under_limits)) domain_under_limits.delete() - def test_incomplete_starting_coverage(self): - some_plan = generator.subscribable_plan_version() - subscription = Subscription.new_domain_subscription( - self.account, self.domain.name, some_plan, - date_start=self.invoice_start + datetime.timedelta(days=3) - ) - subscriptions = self.invoice_factory._get_subscriptions() - community_ranges = self.invoice_factory._get_community_ranges(subscriptions) - self.assertEqual(len(community_ranges), 1) - self.assertEqual(community_ranges[0][0], self.invoice_start) - self.assertEqual(community_ranges[0][1], subscription.date_start) - - def test_incomplete_ending_coverage(self): - some_plan = generator.subscribable_plan_version() - subscription = Subscription.new_domain_subscription( - self.account, self.domain.name, some_plan, - date_start=self.invoice_start, - date_end=self.invoice_end - datetime.timedelta(days=3) - ) - subscriptions = self.invoice_factory._get_subscriptions() - community_ranges = self.invoice_factory._get_community_ranges(subscriptions) - self.assertEqual(len(community_ranges), 1) - self.assertEqual(community_ranges[0][0], subscription.date_end) - self.assertEqual(community_ranges[0][1], - self.invoice_end + datetime.timedelta(days=1)) - - def test_patchy_coverage(self): - some_plan = generator.subscribable_plan_version() - middle_date = self.invoice_end - datetime.timedelta(days=15) - Subscription.new_domain_subscription( - self.account, self.domain.name, some_plan, - date_start=self.invoice_start + datetime.timedelta(days=1), - date_end=middle_date - ) - next_start = middle_date + datetime.timedelta(days=2) - next_end = next_start + datetime.timedelta(days=2) - Subscription.new_domain_subscription( - self.account, self.domain.name, some_plan, - date_start=next_start, - date_end=next_end, - ) - final_start = next_end + datetime.timedelta(days=2) - Subscription.new_domain_subscription( - self.account, self.domain.name, some_plan, - date_start=final_start, - date_end=self.invoice_end - datetime.timedelta(days=1), - ) - subscriptions = self.invoice_factory._get_subscriptions() - self.assertEqual(len(subscriptions), 3) - community_ranges = self.invoice_factory._get_community_ranges(subscriptions) - self.assertEqual(len(community_ranges), 4) - - def test_full_coverage(self): - some_plan = generator.subscribable_plan_version() - Subscription.new_domain_subscription( - self.account, self.domain.name, some_plan, - date_start=self.invoice_start, - date_end=self.invoice_end + datetime.timedelta(days=1), - ) - subscriptions = self.invoice_factory._get_subscriptions() - community_ranges = self.invoice_factory._get_community_ranges(subscriptions) - self.assertEqual(len(community_ranges), 0) - - def test_no_coverage(self): - subscriptions = self.invoice_factory._get_subscriptions() - self.assertEqual(len(subscriptions), 0) - community_ranges = self.invoice_factory._get_community_ranges(subscriptions) - self.assertEqual(community_ranges, [(self.invoice_start, self.invoice_end + datetime.timedelta(days=1))]) - def test_community_plan_generates_invoice(self): """ Ensure that Community plans can generate invoices. """ - community_plan = DefaultProductPlan.get_default_plan_version() - subscription = Subscription.new_domain_subscription( - self.account, self.domain.name, community_plan, - date_start=self.invoice_start, - date_end=self.invoice_end + datetime.timedelta(days=1), + subscription = generator.generate_domain_subscription( + self.account, self.domain, self.invoice_start, None, + plan_version=generator.subscribable_plan_version(edition=SoftwarePlanEdition.COMMUNITY) ) DomainUserHistory.objects.create( domain=self.domain.name, record_date=self.invoice_end, num_users=10) diff --git a/corehq/apps/accounting/tests/test_invoicing.py b/corehq/apps/accounting/tests/test_invoicing.py index 83fd1f1162d1..b138c3578c2c 100644 --- a/corehq/apps/accounting/tests/test_invoicing.py +++ b/corehq/apps/accounting/tests/test_invoicing.py @@ -2,12 +2,12 @@ import random from decimal import Decimal +from dateutil.relativedelta import relativedelta + from django.conf import settings from django.core import mail from django.test import override_settings -from dimagi.utils.dates import add_months_to_date - from corehq.apps.accounting import tasks, utils from corehq.apps.accounting.invoicing import DomainInvoiceFactory from corehq.apps.accounting.models import ( @@ -23,6 +23,7 @@ Invoice, SoftwarePlanEdition, Subscriber, + Subscription, SubscriptionType, ) from corehq.apps.accounting.tasks import calculate_users_in_all_domains @@ -68,7 +69,7 @@ def setUpClass(cls): if cls.is_testing_web_user_feature: # make sure the subscription is still active when we count web users cls.subscription_is_active = True - cls.subscription_end_date = add_months_to_date(cls.subscription_start_date, cls.subscription_length) + cls.subscription_end_date = cls.subscription_start_date + relativedelta(months=cls.subscription_length) cls.subscription = generator.generate_domain_subscription( cls.account, cls.domain, @@ -131,7 +132,7 @@ def test_no_invoice_after_end(self): tasks.generate_invoices_based_on_date(invoice_date) self.assertEqual(self.subscription.invoice_set.count(), 0) - def test_community_no_charges_no_invoice(self): + def test_no_subscription_no_charges_no_invoice(self): """ No invoices should be generated for domains that are not on a subscription and do not have any per_excess charges on users or SMS messages @@ -144,9 +145,8 @@ def test_community_no_charges_no_invoice(self): def test_community_invoice(self): """ - For an unsubscribed domain with any charges over the community limit for the month of invoicing, - make sure that an invoice is generated in addition to a subscription for that month to - the community plan. + For community-subscribed domain with any charges over the community limit for the month of invoicing, + make sure that an invoice is generated. """ domain = generator.arbitrary_domain() self.addCleanup(domain.delete) @@ -154,6 +154,10 @@ def test_community_invoice(self): account = BillingAccount.get_or_create_account_by_domain( domain, created_by=self.dimagi_user)[0] generator.arbitrary_contact_info(account, self.dimagi_user) + generator.generate_domain_subscription( + account, domain, self.subscription_start_date, None, + plan_version=generator.subscribable_plan_version(edition=SoftwarePlanEdition.COMMUNITY) + ) account.date_confirmed_extra_charges = datetime.date.today() account.save() today = datetime.date.today() @@ -164,11 +168,6 @@ def test_community_invoice(self): self.assertEqual(invoices.count(), 1) invoice = invoices.get() self.assertEqual(invoice.subscription.subscriber.domain, domain.name) - self.assertEqual(invoice.subscription.date_start, invoice.date_start) - self.assertEqual( - invoice.subscription.date_end - datetime.timedelta(days=1), - invoice.date_end - ) def test_date_due_not_set_small_invoice(self): """Date Due doesn't get set if the invoice is small""" @@ -417,7 +416,7 @@ def num_users(): def test_community_over_limit(self): """ - For a domain under community (no subscription) with users over the community limit, make sure that: + For a domain under community with users over the community limit, make sure that: - base_description is None - base_cost is 0.0 - unit_description is not None @@ -436,8 +435,14 @@ def test_community_over_limit(self): account.date_confirmed_extra_charges = today account.save() + community_plan = DefaultProductPlan.get_default_plan_version() + Subscription.new_domain_subscription( + account, domain.name, community_plan, + date_start=datetime.date(today.year, today.month, 1) - relativedelta(months=1), + ) + calculate_users_in_all_domains(datetime.date(today.year, today.month, 1)) - tasks.generate_invoices_based_on_date(datetime.date.today()) + tasks.generate_invoices_based_on_date(today) subscriber = Subscriber.objects.get(domain=domain.name) invoice = Invoice.objects.filter(subscription__subscriber=subscriber).get() user_line_item = invoice.lineitem_set.get_feature_by_type(FeatureType.USER).get()