Template
Extract always-on public/portal/UTM plus optional email_sms, directmail, blog, payments, social, and social_ai so new client sites can be bootstrapped from this seed. Refs #1 Refs #2 Co-authored-by: Cursor <cursoragent@cursor.com>
53 lines
1.7 KiB
Python
53 lines
1.7 KiB
Python
from decimal import Decimal
|
|
|
|
from django.conf import settings
|
|
from django.db import models
|
|
|
|
from contacts.models import Contact
|
|
from core.models import TimeStampedModel, UUIDPrimaryKeyModel
|
|
|
|
|
|
class Invoice(UUIDPrimaryKeyModel, TimeStampedModel):
|
|
class Status(models.TextChoices):
|
|
DRAFT = "draft", "Draft"
|
|
OPEN = "open", "Open"
|
|
PAID = "paid", "Paid"
|
|
VOID = "void", "Void"
|
|
UNCOLLECTIBLE = "uncollectible", "Uncollectible"
|
|
|
|
number = models.CharField(max_length=32, unique=True)
|
|
contact = models.ForeignKey(
|
|
Contact,
|
|
on_delete=models.PROTECT,
|
|
related_name="invoices",
|
|
)
|
|
description = models.CharField(max_length=255)
|
|
amount = models.DecimalField(max_digits=10, decimal_places=2)
|
|
currency = models.CharField(max_length=8, default="usd")
|
|
status = models.CharField(
|
|
max_length=16, choices=Status.choices, default=Status.DRAFT
|
|
)
|
|
due_date = models.DateField(null=True, blank=True)
|
|
stripe_invoice_id = models.CharField(max_length=255, blank=True)
|
|
stripe_checkout_session_id = models.CharField(max_length=255, blank=True)
|
|
hosted_invoice_url = models.URLField(blank=True)
|
|
paid_at = models.DateTimeField(null=True, blank=True)
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="created_invoices",
|
|
)
|
|
notes = models.TextField(blank=True)
|
|
|
|
class Meta:
|
|
ordering = ["-created_at"]
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.number} · {self.contact} · {self.amount} {self.currency}"
|
|
|
|
@property
|
|
def amount_cents(self) -> int:
|
|
return int((self.amount * Decimal("100")).quantize(Decimal("1")))
|