This commit is contained in:
2026-05-18 14:08:13 -04:00
commit 377326ec2c
36 changed files with 1473 additions and 0 deletions

107
inventory/models.py Normal file
View File

@@ -0,0 +1,107 @@
from django.conf import settings
from django.core.validators import MinValueValidator
from django.db import models
from django.utils import timezone
class ItemTemplate(models.Model):
name = models.CharField(max_length=120, unique=True)
description = models.TextField(blank=True)
category = models.CharField(max_length=80)
# field_definitions is a list of objects describing extra fields for items of this template.
# Example: [{"name": "rewind", "label": "Rewind?", "type": "boolean"}, {"name":"output_count","label":"Outputs","type":"number"}]
field_definitions = models.JSONField(default=list, blank=True)
default_purchase_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
default_estimated_resale_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
default_notes = models.TextField(blank=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self) -> str:
return self.name
class Item(models.Model):
class Status(models.TextChoices):
IN_STOCK = "in_stock", "In stock"
LISTED = "listed", "Listed"
SOLD = "sold", "Sold"
DONATED = "donated", "Donated"
template = models.ForeignKey(ItemTemplate, null=True, blank=True, on_delete=models.SET_NULL, related_name="items")
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="items_created")
title = models.CharField(max_length=200)
brand = models.CharField(max_length=120, blank=True)
category = models.CharField(max_length=80)
condition = models.CharField(max_length=80, blank=True)
size = models.CharField(max_length=50, blank=True)
color = models.CharField(max_length=80, blank=True)
purchase_price = models.DecimalField(max_digits=10, decimal_places=2, validators=[MinValueValidator(0)])
estimated_resale_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
sold_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
ebay_fee = models.DecimalField(max_digits=10, decimal_places=2, default=0)
shipping_cost = models.DecimalField(max_digits=10, decimal_places=2, default=0)
status = models.CharField(max_length=20, choices=Status.choices, default=Status.IN_STOCK)
notes = models.TextField(blank=True)
# properties stores template-specific values keyed by field name
properties = models.JSONField(default=dict, blank=True)
acquisition_date = models.DateField(auto_now_add=True)
sold_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@property
def total_cost(self):
return self.purchase_price + self.ebay_fee + self.shipping_cost
@property
def profit(self):
if self.sold_price is None:
return None
return self.sold_price - self.total_cost
@property
def margin(self):
if self.profit is None or self.sold_price in (None, 0):
return None
return self.profit / self.sold_price
def mark_sold(self, sold_price, sold_at=None):
self.sold_price = sold_price
self.status = self.Status.SOLD
self.sold_at = sold_at or timezone.now()
self.save(update_fields=["sold_price", "status", "sold_at", "updated_at"])
def get_absolute_url(self):
from django.urls import reverse
return reverse("item-detail", kwargs={"pk": self.pk})
def __str__(self) -> str:
return self.title
class PriceEstimate(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name="price_estimates")
source = models.CharField(max_length=80)
source_url = models.URLField(blank=True)
estimated_price = models.DecimalField(max_digits=10, decimal_places=2)
notes = models.TextField(blank=True)
retrieved_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-retrieved_at"]
class ItemNote(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name="notes_history")
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class ItemPhoto(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name="photos")
file = models.FileField(upload_to="item-photos/")
uploaded_at = models.DateTimeField(auto_now_add=True)