Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove _ensure_full_coverage step from invoicing #35503

Merged
merged 8 commits into from
Jan 2, 2025
Merged
71 changes: 0 additions & 71 deletions corehq/apps/accounting/invoicing.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
CreditLine,
CustomerBillingRecord,
CustomerInvoice,
DefaultProductPlan,
DomainUserHistory,
EntryPoint,
FeatureType,
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
77 changes: 3 additions & 74 deletions corehq/apps/accounting/tests/test_invoice_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
33 changes: 19 additions & 14 deletions corehq/apps/accounting/tests/test_invoicing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -23,6 +23,7 @@
Invoice,
SoftwarePlanEdition,
Subscriber,
Subscription,
SubscriptionType,
)
from corehq.apps.accounting.tasks import calculate_users_in_all_domains
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -144,16 +145,19 @@ 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)
generator.create_excess_community_users(domain)
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()
Expand All @@ -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"""
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down
Loading