Example #1
0
def make_end_of_cycle_updates_if_needed(plan: CustomerPlan,
                                        event_time: datetime) -> Optional[LicenseLedger]:
    last_ledger_entry = LicenseLedger.objects.filter(plan=plan).order_by('-id').first()
    last_renewal = LicenseLedger.objects.filter(plan=plan, is_renewal=True) \
                                        .order_by('-id').first().event_time
    next_billing_cycle = start_of_next_billing_cycle(plan, last_renewal)
    if next_billing_cycle <= event_time:
        if plan.status == CustomerPlan.ACTIVE:
            return LicenseLedger.objects.create(
                plan=plan, is_renewal=True, event_time=next_billing_cycle,
                licenses=last_ledger_entry.licenses_at_next_renewal,
                licenses_at_next_renewal=last_ledger_entry.licenses_at_next_renewal)
        if plan.status == CustomerPlan.FREE_TRIAL:
            plan.invoiced_through = last_ledger_entry
            assert(plan.next_invoice_date is not None)
            plan.billing_cycle_anchor = plan.next_invoice_date.replace(microsecond=0)
            plan.status = CustomerPlan.ACTIVE
            plan.save(update_fields=["invoiced_through", "billing_cycle_anchor", "status"])
            return LicenseLedger.objects.create(
                plan=plan, is_renewal=True, event_time=next_billing_cycle,
                licenses=last_ledger_entry.licenses_at_next_renewal,
                licenses_at_next_renewal=last_ledger_entry.licenses_at_next_renewal)
        if plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE:
            process_downgrade(plan)
        return None
    return last_ledger_entry
Example #2
0
def make_end_of_cycle_updates_if_needed(
    plan: CustomerPlan, event_time: datetime
) -> Tuple[Optional[CustomerPlan], Optional[LicenseLedger]]:
    last_ledger_entry = LicenseLedger.objects.filter(
        plan=plan).order_by('-id').first()
    last_renewal = LicenseLedger.objects.filter(plan=plan, is_renewal=True) \
                                        .order_by('-id').first().event_time
    next_billing_cycle = start_of_next_billing_cycle(plan, last_renewal)
    if next_billing_cycle <= event_time:
        if plan.status == CustomerPlan.ACTIVE:
            return None, LicenseLedger.objects.create(
                plan=plan,
                is_renewal=True,
                event_time=next_billing_cycle,
                licenses=last_ledger_entry.licenses_at_next_renewal,
                licenses_at_next_renewal=last_ledger_entry.
                licenses_at_next_renewal)
        if plan.status == CustomerPlan.FREE_TRIAL:
            plan.invoiced_through = last_ledger_entry
            assert (plan.next_invoice_date is not None)
            plan.billing_cycle_anchor = plan.next_invoice_date.replace(
                microsecond=0)
            plan.status = CustomerPlan.ACTIVE
            plan.save(update_fields=[
                "invoiced_through", "billing_cycle_anchor", "status"
            ])
            return None, LicenseLedger.objects.create(
                plan=plan,
                is_renewal=True,
                event_time=next_billing_cycle,
                licenses=last_ledger_entry.licenses_at_next_renewal,
                licenses_at_next_renewal=last_ledger_entry.
                licenses_at_next_renewal)

        if plan.status == CustomerPlan.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE:
            if plan.fixed_price is not None:  # nocoverage
                raise NotImplementedError(
                    "Can't switch fixed priced monthly plan to annual.")

            plan.status = CustomerPlan.ENDED
            plan.save(update_fields=["status"])

            discount = plan.customer.default_discount or plan.discount
            _, _, _, price_per_license = compute_plan_parameters(
                automanage_licenses=plan.automanage_licenses,
                billing_schedule=CustomerPlan.ANNUAL,
                discount=plan.discount)

            new_plan = CustomerPlan.objects.create(
                customer=plan.customer,
                billing_schedule=CustomerPlan.ANNUAL,
                automanage_licenses=plan.automanage_licenses,
                charge_automatically=plan.charge_automatically,
                price_per_license=price_per_license,
                discount=discount,
                billing_cycle_anchor=next_billing_cycle,
                tier=plan.tier,
                status=CustomerPlan.ACTIVE,
                next_invoice_date=next_billing_cycle,
                invoiced_through=None,
                invoicing_status=CustomerPlan.INITIAL_INVOICE_TO_BE_SENT,
            )

            new_plan_ledger_entry = LicenseLedger.objects.create(
                plan=new_plan,
                is_renewal=True,
                event_time=next_billing_cycle,
                licenses=last_ledger_entry.licenses_at_next_renewal,
                licenses_at_next_renewal=last_ledger_entry.
                licenses_at_next_renewal)

            RealmAuditLog.objects.create(
                realm=new_plan.customer.realm,
                event_time=event_time,
                event_type=RealmAuditLog.
                CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN,
                extra_data=orjson.dumps({
                    "monthly_plan_id": plan.id,
                    "annual_plan_id": new_plan.id,
                }).decode())
            return new_plan, new_plan_ledger_entry

        if plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE:
            process_downgrade(plan)
        return None, None
    return None, last_ledger_entry
Example #3
0
def make_end_of_cycle_updates_if_needed(
    plan: CustomerPlan, event_time: datetime
) -> Tuple[Optional[CustomerPlan], Optional[LicenseLedger]]:
    last_ledger_entry = LicenseLedger.objects.filter(plan=plan).order_by("-id").first()
    last_ledger_renewal = (
        LicenseLedger.objects.filter(plan=plan, is_renewal=True).order_by("-id").first()
    )
    assert last_ledger_renewal is not None
    last_renewal = last_ledger_renewal.event_time

    if plan.is_free_trial() or plan.status == CustomerPlan.SWITCH_NOW_FROM_STANDARD_TO_PLUS:
        assert plan.next_invoice_date is not None
        next_billing_cycle = plan.next_invoice_date
    else:
        next_billing_cycle = start_of_next_billing_cycle(plan, last_renewal)
    if next_billing_cycle <= event_time and last_ledger_entry is not None:
        licenses_at_next_renewal = last_ledger_entry.licenses_at_next_renewal
        assert licenses_at_next_renewal is not None
        if plan.status == CustomerPlan.ACTIVE:
            return None, LicenseLedger.objects.create(
                plan=plan,
                is_renewal=True,
                event_time=next_billing_cycle,
                licenses=licenses_at_next_renewal,
                licenses_at_next_renewal=licenses_at_next_renewal,
            )
        if plan.is_free_trial():
            plan.invoiced_through = last_ledger_entry
            plan.billing_cycle_anchor = next_billing_cycle.replace(microsecond=0)
            plan.status = CustomerPlan.ACTIVE
            plan.save(update_fields=["invoiced_through", "billing_cycle_anchor", "status"])
            return None, LicenseLedger.objects.create(
                plan=plan,
                is_renewal=True,
                event_time=next_billing_cycle,
                licenses=licenses_at_next_renewal,
                licenses_at_next_renewal=licenses_at_next_renewal,
            )

        if plan.status == CustomerPlan.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE:
            if plan.fixed_price is not None:  # nocoverage
                raise NotImplementedError("Can't switch fixed priced monthly plan to annual.")

            plan.status = CustomerPlan.ENDED
            plan.save(update_fields=["status"])

            discount = plan.customer.default_discount or plan.discount
            _, _, _, price_per_license = compute_plan_parameters(
                tier=plan.tier,
                automanage_licenses=plan.automanage_licenses,
                billing_schedule=CustomerPlan.ANNUAL,
                discount=plan.discount,
            )

            new_plan = CustomerPlan.objects.create(
                customer=plan.customer,
                billing_schedule=CustomerPlan.ANNUAL,
                automanage_licenses=plan.automanage_licenses,
                charge_automatically=plan.charge_automatically,
                price_per_license=price_per_license,
                discount=discount,
                billing_cycle_anchor=next_billing_cycle,
                tier=plan.tier,
                status=CustomerPlan.ACTIVE,
                next_invoice_date=next_billing_cycle,
                invoiced_through=None,
                invoicing_status=CustomerPlan.INITIAL_INVOICE_TO_BE_SENT,
            )

            new_plan_ledger_entry = LicenseLedger.objects.create(
                plan=new_plan,
                is_renewal=True,
                event_time=next_billing_cycle,
                licenses=licenses_at_next_renewal,
                licenses_at_next_renewal=licenses_at_next_renewal,
            )

            realm = new_plan.customer.realm
            assert realm is not None

            RealmAuditLog.objects.create(
                realm=realm,
                event_time=event_time,
                event_type=RealmAuditLog.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN,
                extra_data=orjson.dumps(
                    {
                        "monthly_plan_id": plan.id,
                        "annual_plan_id": new_plan.id,
                    }
                ).decode(),
            )
            return new_plan, new_plan_ledger_entry

        if plan.status == CustomerPlan.SWITCH_NOW_FROM_STANDARD_TO_PLUS:
            standard_plan = plan
            standard_plan.end_date = next_billing_cycle
            standard_plan.status = CustomerPlan.ENDED
            standard_plan.save(update_fields=["status", "end_date"])

            (_, _, _, plus_plan_price_per_license) = compute_plan_parameters(
                CustomerPlan.PLUS,
                standard_plan.automanage_licenses,
                standard_plan.billing_schedule,
                standard_plan.customer.default_discount,
            )
            plus_plan_billing_cycle_anchor = standard_plan.end_date.replace(microsecond=0)

            plus_plan = CustomerPlan.objects.create(
                customer=standard_plan.customer,
                status=CustomerPlan.ACTIVE,
                automanage_licenses=standard_plan.automanage_licenses,
                charge_automatically=standard_plan.charge_automatically,
                price_per_license=plus_plan_price_per_license,
                discount=standard_plan.customer.default_discount,
                billing_schedule=standard_plan.billing_schedule,
                tier=CustomerPlan.PLUS,
                billing_cycle_anchor=plus_plan_billing_cycle_anchor,
                invoicing_status=CustomerPlan.INITIAL_INVOICE_TO_BE_SENT,
                next_invoice_date=plus_plan_billing_cycle_anchor,
            )

            standard_plan_last_ledger = (
                LicenseLedger.objects.filter(plan=standard_plan).order_by("id").last()
            )
            licenses_for_plus_plan = standard_plan_last_ledger.licenses_at_next_renewal
            plus_plan_ledger_entry = LicenseLedger.objects.create(
                plan=plus_plan,
                is_renewal=True,
                event_time=plus_plan_billing_cycle_anchor,
                licenses=licenses_for_plus_plan,
                licenses_at_next_renewal=licenses_for_plus_plan,
            )
            return plus_plan, plus_plan_ledger_entry

        if plan.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE:
            process_downgrade(plan)
        return None, None
    return None, last_ledger_entry