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)