diff --git a/src/accounting/models.py b/src/accounting/models.py index c5355f2..3d0fb2d 100644 --- a/src/accounting/models.py +++ b/src/accounting/models.py @@ -67,9 +67,8 @@ class Transaction(CreatedModifiedAbstract): class Order(CreatedModifiedAbstract): """An order. - Scoped out: Contents of invoices will have to be tracked either here or in - a separate Invoice model. This is undecided because we are not generating - invoices at the moment. + We assemble the order from a number of products. Once an order is paid, the contents should be + considered locked. """ user = models.ForeignKey("auth.User", on_delete=models.PROTECT) @@ -114,6 +113,32 @@ class Order(CreatedModifiedAbstract): return x.hexdigest() +class Product(CreatedModifiedAbstract): + """A generic product, for instance a membership or a service fee.""" + + name = models.CharField(max_length=512) + price = MoneyField(max_digits=16, decimal_places=2) + vat = MoneyField(max_digits=16, decimal_places=2) + + def __str__(self) -> str: + return self.name + + +class OrderProduct(CreatedModifiedAbstract): + """When a product is ordered, we store the product on the order. + + This includes pricing information. + """ + + order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="ordered_products") + product = models.ForeignKey(Product, related_name="ordered_products", on_delete=models.PROTECT) + price = MoneyField(max_digits=16, decimal_places=2) + vat = MoneyField(max_digits=16, decimal_places=2) + + def __str__(self) -> str: + return f"{self.product.name}" + + class Payment(CreatedModifiedAbstract): """A payment is a transaction that is made to pay for an order.""" diff --git a/src/membership/models.py b/src/membership/models.py index 502aa66..7e71393 100644 --- a/src/membership/models.py +++ b/src/membership/models.py @@ -133,9 +133,23 @@ class MembershipType(CreatedModifiedAbstract): name = models.CharField(verbose_name=_("name"), max_length=64) + product = models.ForeignKey("accounting.Product", on_delete=models.PROTECT) + + current = models.BooleanField(default=False) + class Meta: verbose_name = _("membership type") verbose_name_plural = _("membership types") def __str__(self) -> str: return self.name + + def create_membership(self, user: User) -> Membership: + """Create a current membership for this type.""" + from .selectors import get_current_subscription_period + + return Membership.objects.create( + membership_type=self, + user=user, + period=get_current_subscription_period(), + )