2018-09-25 14:02:43 +02:00
|
|
|
import datetime
|
2018-12-12 19:41:03 +01:00
|
|
|
from decimal import Decimal
|
|
|
|
from typing import Optional
|
2018-09-25 14:02:43 +02:00
|
|
|
|
|
|
|
from django.db import models
|
2018-12-30 03:45:23 +01:00
|
|
|
from django.db.models import CASCADE
|
2018-09-25 14:02:43 +02:00
|
|
|
|
2019-02-02 23:53:19 +01:00
|
|
|
from zerver.models import Realm
|
2018-09-25 14:02:43 +02:00
|
|
|
|
|
|
|
class Customer(models.Model):
|
2018-12-30 03:45:23 +01:00
|
|
|
realm = models.OneToOneField(Realm, on_delete=CASCADE) # type: Realm
|
2019-01-29 06:34:31 +01:00
|
|
|
stripe_customer_id = models.CharField(max_length=255, null=True, unique=True) # type: str
|
2019-01-25 02:14:07 +01:00
|
|
|
# A percentage, like 85.
|
2018-12-12 19:41:03 +01:00
|
|
|
default_discount = models.DecimalField(decimal_places=4, max_digits=7, null=True) # type: Optional[Decimal]
|
2018-09-25 14:02:43 +02:00
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
return "<Customer %s %s>" % (self.realm, self.stripe_customer_id)
|
|
|
|
|
2020-03-23 13:35:04 +01:00
|
|
|
def get_customer_by_realm(realm: Realm) -> Optional[Customer]:
|
|
|
|
return Customer.objects.filter(realm=realm).first()
|
|
|
|
|
2018-12-15 09:33:25 +01:00
|
|
|
class CustomerPlan(models.Model):
|
2018-12-30 03:45:23 +01:00
|
|
|
customer = models.ForeignKey(Customer, on_delete=CASCADE) # type: Customer
|
2018-12-15 09:33:25 +01:00
|
|
|
automanage_licenses = models.BooleanField(default=False) # type: bool
|
|
|
|
charge_automatically = models.BooleanField(default=False) # type: bool
|
|
|
|
|
|
|
|
# Both of these are in cents. Exactly one of price_per_license or
|
|
|
|
# fixed_price should be set. fixed_price is only for manual deals, and
|
|
|
|
# can't be set via the self-serve billing system.
|
|
|
|
price_per_license = models.IntegerField(null=True) # type: Optional[int]
|
|
|
|
fixed_price = models.IntegerField(null=True) # type: Optional[int]
|
|
|
|
|
2019-01-25 02:14:07 +01:00
|
|
|
# Discount that was applied. For display purposes only.
|
2018-12-15 09:33:25 +01:00
|
|
|
discount = models.DecimalField(decimal_places=4, max_digits=6, null=True) # type: Optional[Decimal]
|
|
|
|
|
|
|
|
billing_cycle_anchor = models.DateTimeField() # type: datetime.datetime
|
2018-12-12 23:23:15 +01:00
|
|
|
ANNUAL = 1
|
|
|
|
MONTHLY = 2
|
2018-12-15 09:33:25 +01:00
|
|
|
billing_schedule = models.SmallIntegerField() # type: int
|
|
|
|
|
2019-04-08 05:16:35 +02:00
|
|
|
next_invoice_date = models.DateTimeField(db_index=True, null=True) # type: Optional[datetime.datetime]
|
2019-01-28 14:18:21 +01:00
|
|
|
invoiced_through = models.ForeignKey(
|
|
|
|
'LicenseLedger', null=True, on_delete=CASCADE, related_name='+') # type: Optional[LicenseLedger]
|
|
|
|
DONE = 1
|
|
|
|
STARTED = 2
|
|
|
|
invoicing_status = models.SmallIntegerField(default=DONE) # type: int
|
2018-12-15 09:33:25 +01:00
|
|
|
|
|
|
|
STANDARD = 1
|
|
|
|
PLUS = 2 # not available through self-serve signup
|
|
|
|
ENTERPRISE = 10
|
|
|
|
tier = models.SmallIntegerField() # type: int
|
|
|
|
|
|
|
|
ACTIVE = 1
|
2019-04-10 19:43:16 +02:00
|
|
|
DOWNGRADE_AT_END_OF_CYCLE = 2
|
|
|
|
# "Live" plans should have a value < LIVE_STATUS_THRESHOLD.
|
|
|
|
# There should be at most one live plan per customer.
|
|
|
|
LIVE_STATUS_THRESHOLD = 10
|
|
|
|
ENDED = 11
|
|
|
|
NEVER_STARTED = 12
|
2018-12-15 09:33:25 +01:00
|
|
|
status = models.SmallIntegerField(default=ACTIVE) # type: int
|
|
|
|
|
|
|
|
# TODO maybe override setattr to ensure billing_cycle_anchor, etc are immutable
|
|
|
|
|
2019-04-10 19:43:16 +02:00
|
|
|
def get_current_plan(customer: Customer) -> Optional[CustomerPlan]:
|
|
|
|
return CustomerPlan.objects.filter(
|
|
|
|
customer=customer, status__lt=CustomerPlan.LIVE_STATUS_THRESHOLD).first()
|
2018-12-12 23:23:15 +01:00
|
|
|
|
2018-12-28 07:20:30 +01:00
|
|
|
class LicenseLedger(models.Model):
|
|
|
|
plan = models.ForeignKey(CustomerPlan, on_delete=CASCADE) # type: CustomerPlan
|
|
|
|
# Also True for the initial upgrade.
|
|
|
|
is_renewal = models.BooleanField(default=False) # type: bool
|
|
|
|
event_time = models.DateTimeField() # type: datetime.datetime
|
|
|
|
licenses = models.IntegerField() # type: int
|
|
|
|
# None means the plan does not automatically renew.
|
|
|
|
# This cannot be None if plan.automanage_licenses.
|
|
|
|
licenses_at_next_renewal = models.IntegerField(null=True) # type: Optional[int]
|