Update company_site/financial/models.py for Stripe invoices (#23)
This commit is contained in:
@@ -302,6 +302,108 @@ class TimeCardCell(IdMixin, TimeMixin):
|
|||||||
charge_number = models.ForeignKey(ChargeNumber, on_delete=models.CASCADE, null=True, blank=True)
|
charge_number = models.ForeignKey(ChargeNumber, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
class BillingCustomer(IdMixin, TimeMixin):
|
||||||
|
"""Customer who pays us (one-off invoices or subscriptions)."""
|
||||||
|
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
email = models.EmailField()
|
||||||
|
company = models.CharField(max_length=200, blank=True)
|
||||||
|
stripe_customer_id = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
notes = models.TextField(blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["name"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} <{self.email}>"
|
||||||
|
|
||||||
|
|
||||||
|
class Invoice(IdMixin, TimeMixin):
|
||||||
|
"""One-off invoice — customer pays us via Stripe hosted invoice URL."""
|
||||||
|
|
||||||
|
class Status(models.TextChoices):
|
||||||
|
DRAFT = "draft", "Draft"
|
||||||
|
OPEN = "open", "Open"
|
||||||
|
PAID = "paid", "Paid"
|
||||||
|
VOID = "void", "Void"
|
||||||
|
UNCOLLECTIBLE = "uncollectible", "Uncollectible"
|
||||||
|
FAILED = "failed", "Failed"
|
||||||
|
|
||||||
|
customer = models.ForeignKey(
|
||||||
|
BillingCustomer, on_delete=models.PROTECT, related_name="invoices"
|
||||||
|
)
|
||||||
|
description = models.CharField(max_length=500)
|
||||||
|
amount_cents = models.PositiveIntegerField(help_text="Amount in cents (USD)")
|
||||||
|
currency = models.CharField(max_length=3, default="usd")
|
||||||
|
status = models.CharField(
|
||||||
|
max_length=20, choices=Status.choices, default=Status.DRAFT
|
||||||
|
)
|
||||||
|
due_date = models.DateField(null=True, blank=True)
|
||||||
|
notes = models.TextField(blank=True)
|
||||||
|
stripe_invoice_id = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
hosted_invoice_url = models.URLField(max_length=500, blank=True, default="")
|
||||||
|
invoice_pdf_url = models.URLField(max_length=500, blank=True, default="")
|
||||||
|
pay_link_emailed_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["-created"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Invoice {self.pk} — {self.customer} — {self.status}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amount_dollars(self):
|
||||||
|
return self.amount_cents / 100.0
|
||||||
|
|
||||||
|
|
||||||
|
class RecurringSubscription(IdMixin, TimeMixin):
|
||||||
|
"""Monthly (or custom) recurring charge — customer pays us via Stripe Subscription."""
|
||||||
|
|
||||||
|
class Status(models.TextChoices):
|
||||||
|
INCOMPLETE = "incomplete", "Incomplete"
|
||||||
|
ACTIVE = "active", "Active"
|
||||||
|
PAST_DUE = "past_due", "Past due"
|
||||||
|
CANCELED = "canceled", "Canceled"
|
||||||
|
UNPAID = "unpaid", "Unpaid"
|
||||||
|
TRIALING = "trialing", "Trialing"
|
||||||
|
PAUSED = "paused", "Paused"
|
||||||
|
|
||||||
|
class Interval(models.TextChoices):
|
||||||
|
MONTH = "month", "Monthly"
|
||||||
|
YEAR = "year", "Yearly"
|
||||||
|
WEEK = "week", "Weekly"
|
||||||
|
|
||||||
|
customer = models.ForeignKey(
|
||||||
|
BillingCustomer, on_delete=models.PROTECT, related_name="subscriptions"
|
||||||
|
)
|
||||||
|
description = models.CharField(max_length=500)
|
||||||
|
amount_cents = models.PositiveIntegerField(help_text="Amount per period in cents (USD)")
|
||||||
|
currency = models.CharField(max_length=3, default="usd")
|
||||||
|
interval = models.CharField(
|
||||||
|
max_length=10, choices=Interval.choices, default=Interval.MONTH
|
||||||
|
)
|
||||||
|
status = models.CharField(
|
||||||
|
max_length=20, choices=Status.choices, default=Status.INCOMPLETE
|
||||||
|
)
|
||||||
|
notes = models.TextField(blank=True)
|
||||||
|
stripe_product_id = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
stripe_price_id = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
stripe_subscription_id = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
checkout_session_id = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
checkout_url = models.URLField(max_length=500, blank=True, default="")
|
||||||
|
pay_link_emailed_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["-created"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Subscription {self.pk} — {self.customer} — {self.status}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amount_dollars(self):
|
||||||
|
return self.amount_cents / 100.0
|
||||||
|
|
||||||
|
|
||||||
def set_user_type(user, user_type):
|
def set_user_type(user, user_type):
|
||||||
"""Set user type and sync the Employee record (mutually exclusive types)."""
|
"""Set user type and sync the Employee record (mutually exclusive types)."""
|
||||||
user.__dict__.pop("profile", None)
|
user.__dict__.pop("profile", None)
|
||||||
|
|||||||
Reference in New Issue
Block a user