Source code for squaresdb.money.models

import secrets

from django.contrib.auth import get_user_model
from django.db import models
from django.utils import timezone

import reversion

# This is envisioned as generic money/payment models that could be used outside
# SquaresDB

[docs] def default_nonce(): return secrets.token_hex(8)
[docs] @reversion.register class Transaction(models.Model):
[docs] class Stage(models.IntegerChoices): # pylint:disable=too-many-ancestors CART = 10 REVIEW = 40 PAID = 50 CANCEL = 60
nonce = models.CharField(default=default_nonce, max_length=16) time = models.DateTimeField(default=timezone.now) user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT, blank=True, null=True, ) person_name = models.CharField(max_length=50) email = models.EmailField() notes = models.TextField(blank=True) admin_notes = models.TextField(blank=True) stage = models.IntegerField(choices=Stage)
[docs] def net_amount(self, ): # Note: Any Transaction with stage=paid should total to 0 return sum(lineitem.amount for lineitem in self.lineitem_set.all())
[docs] def first_name(self, ): names = self.person_name.rsplit(maxsplit=1) return names[0] if len(names) > 1 else ""
[docs] def last_name(self, ): names = self.person_name.rsplit(maxsplit=1) return names[-1]
[docs] def stage_label(self, ): return self.Stage(self.stage).label
# Any Product.high greater than this is treated as infinite # This should align with the Product.high help text MAX_AMOUNT = 10**4
[docs] @reversion.register class LineItem(models.Model): transaction = models.ForeignKey(Transaction, on_delete=models.PROTECT) # We should never need a line item over $10M... amount = models.DecimalField(max_digits=9, decimal_places=2) account_name = models.CharField(max_length=255) label = models.CharField(max_length=255) notes = models.TextField(blank=True)
# https://docs.djangoproject.com/en/5.2/topics/db/models/#model-inheritance # Not abstract, because it's useful to do operations like "add up all the line items"
[docs] @reversion.register class CybersourceLineItem(LineItem): # Note that payment line items should generally have a negative amount receipt_post = models.JSONField() decision = models.CharField(max_length=50, blank=True) ref_number = models.CharField(max_length=50, blank=True) card_number = models.CharField(max_length=50, blank=True) card_type = models.CharField(max_length=50, blank=True)
[docs] @reversion.register class ProductCategory(models.Model): slug = models.SlugField(primary_key=True) name = models.CharField(max_length=50) order = models.IntegerField(db_index=True) def __str__(self): return self.name class Meta: verbose_name_plural = "product categories"
[docs] @reversion.register class Product(models.Model): slug = models.SlugField(primary_key=True) label = models.CharField(max_length=255) category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE) order = models.IntegerField(db_index=True) active = models.BooleanField(default=True) account_name = models.CharField(max_length=255) description = models.TextField(blank=True, help_text="displayed to users") admin_notes = models.TextField(blank=True, help_text="internal item notes") low = models.DecimalField(max_digits=9, decimal_places=2) high = models.DecimalField(max_digits=9, decimal_places=2, help_text="Use 9999 for unlimited")
[docs] def price(self, ): """Returns the price if low==high, else None""" return self.low if self.low == self.high else None
[docs] @reversion.register class ProductLineItem(LineItem): product = models.ForeignKey(Product, on_delete=models.PROTECT) count = models.IntegerField() price_each = models.DecimalField(max_digits=9, decimal_places=2)