corporate: Fix invoicing of plans on free-trial with changed schedule.

Earlier, the 'next_invoice_date', 'invoiced_through', and
'invoicing_status' fields in 'do_change_schedule_after_free_trial'
were not set correctly.

It resulted in invoicing the ENDED plan and the ledger we create
in this function.

Multiple invoices were created depending on the number of times the
billing frequency was changed for customers who started free-trial
& changed their billing frequency.

This commit sets those fields correctly leading to create only one
invoice.
This commit is contained in:
Prakhar Pratyush 2024-03-01 18:57:06 +05:30 committed by Tim Abbott
parent 3480d204de
commit a513489f38
3 changed files with 66 additions and 12 deletions

View File

@ -1903,7 +1903,8 @@ class BillingSession(ABC):
raise BillingError("Customer is already on monthly fixed plan.")
plan.status = CustomerPlan.ENDED
plan.save(update_fields=["status"])
plan.next_invoice_date = None
plan.save(update_fields=["status", "next_invoice_date"])
discount_for_current_plan = plan.discount
_, _, _, price_per_license = compute_plan_parameters(
@ -1924,11 +1925,9 @@ class BillingSession(ABC):
tier=plan.tier,
status=CustomerPlan.FREE_TRIAL,
next_invoice_date=next_billing_cycle,
invoiced_through=None,
invoicing_status=CustomerPlan.INVOICING_STATUS_INITIAL_INVOICE_TO_BE_SENT,
)
LicenseLedger.objects.create(
ledger_entry = LicenseLedger.objects.create(
plan=new_plan,
is_renewal=True,
event_time=plan.billing_cycle_anchor,
@ -1936,6 +1935,9 @@ class BillingSession(ABC):
licenses_at_next_renewal=licenses_at_next_renewal,
)
new_plan.invoiced_through = ledger_entry
new_plan.save(update_fields=["invoiced_through"])
if schedule == CustomerPlan.BILLING_SCHEDULE_ANNUAL:
self.write_to_audit_log(
event_type=AuditLogEventType.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN,

View File

@ -0,0 +1,50 @@
# Generated by Django 4.2.10 on 2024-03-01 14:05
from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps
def fix_plans_on_free_trial_with_changes_in_schedule(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
CustomerPlan = apps.get_model("corporate", "CustomerPlan")
LicenseLedger = apps.get_model("corporate", "LicenseLedger")
CustomerPlan.FREE_TRIAL = 3
CustomerPlan.ENDED = 11
CustomerPlan.INVOICING_STATUS_DONE = 1
plans_on_free_trial_with_changed_schedule = CustomerPlan.objects.filter(
status=CustomerPlan.FREE_TRIAL, invoiced_through=None
)
for plan in plans_on_free_trial_with_changed_schedule:
assert plan.customer is not None
assert plan.billing_cycle_anchor is not None
ledger_entries = LicenseLedger.objects.filter(
plan=plan,
is_renewal=True,
event_time=plan.billing_cycle_anchor,
)
assert ledger_entries.count() == 1
plan.invoiced_through = ledger_entries.first()
plan.invoicing_status = CustomerPlan.INVOICING_STATUS_DONE
plan.save(update_fields=["invoiced_through", "invoicing_status"])
CustomerPlan.objects.filter(
customer=plan.customer,
status=CustomerPlan.ENDED,
billing_cycle_anchor=plan.billing_cycle_anchor,
).update(next_invoice_date=None)
class Migration(migrations.Migration):
dependencies = [
("corporate", "0040_customerplan_reminder_to_review_plan_email_sent"),
]
operations = [
migrations.RunPython(
fix_plans_on_free_trial_with_changes_in_schedule,
),
]

View File

@ -3023,8 +3023,9 @@ class StripeTest(StripeTestCase):
plan.refresh_from_db()
self.assertEqual(plan.status, CustomerPlan.ENDED)
self.assertIsNone(plan.next_invoice_date)
plan = CustomerPlan.objects.get(
new_plan = CustomerPlan.objects.get(
customer=customer,
automanage_licenses=True,
price_per_license=8000,
@ -3032,19 +3033,19 @@ class StripeTest(StripeTestCase):
discount=None,
billing_cycle_anchor=self.now,
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
invoiced_through=None,
next_invoice_date=free_trial_end_date,
tier=CustomerPlan.TIER_CLOUD_STANDARD,
status=CustomerPlan.FREE_TRIAL,
charge_automatically=True,
)
LicenseLedger.objects.get(
plan=plan,
ledger_entry = LicenseLedger.objects.get(
plan=new_plan,
is_renewal=True,
event_time=self.now,
licenses=self.seat_count,
licenses_at_next_renewal=self.seat_count,
)
self.assertEqual(new_plan.invoiced_through, ledger_entry)
realm_audit_log = RealmAuditLog.objects.filter(
event_type=RealmAuditLog.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN
@ -3077,8 +3078,9 @@ class StripeTest(StripeTestCase):
self.assert_json_success(result)
plan.refresh_from_db()
self.assertEqual(plan.status, CustomerPlan.ENDED)
self.assertIsNone(plan.next_invoice_date)
plan = CustomerPlan.objects.get(
new_plan = CustomerPlan.objects.get(
customer=customer,
automanage_licenses=True,
price_per_license=800,
@ -3086,19 +3088,19 @@ class StripeTest(StripeTestCase):
discount=None,
billing_cycle_anchor=self.now,
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
invoiced_through=None,
next_invoice_date=free_trial_end_date,
tier=CustomerPlan.TIER_CLOUD_STANDARD,
status=CustomerPlan.FREE_TRIAL,
charge_automatically=True,
)
LicenseLedger.objects.get(
plan=plan,
ledger_entry = LicenseLedger.objects.get(
plan=new_plan,
is_renewal=True,
event_time=self.now,
licenses=self.seat_count,
licenses_at_next_renewal=self.seat_count,
)
self.assertEqual(new_plan.invoiced_through, ledger_entry)
realm_audit_log = RealmAuditLog.objects.filter(
event_type=RealmAuditLog.CUSTOMER_SWITCHED_FROM_ANNUAL_TO_MONTHLY_PLAN