212 lines
7.1 KiB
Python
212 lines
7.1 KiB
Python
from django.db import models, transaction
|
|
from django.db.models import Sum
|
|
|
|
class PartModel(models.Model):
|
|
creation = models.DateTimeField(auto_now_add=True)
|
|
modification = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
|
|
class Brand(PartModel):
|
|
name = models.CharField(max_length=256, db_index=True)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class Product(PartModel):
|
|
name = models.CharField(max_length=256, db_index=True)
|
|
brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
|
|
|
|
def __str__(self):
|
|
return self.name + " (" + self.brand.name + ")"
|
|
|
|
class Version(PartModel):
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE)
|
|
name = models.CharField(max_length=256)
|
|
replaced_by = models.ForeignKey('self', blank=True, null=True, related_name='replaces', on_delete=models.SET_NULL)
|
|
start = models.DateField(blank=True, null=True)
|
|
end = models.DateField(blank=True, null=True)
|
|
|
|
def __str__(self):
|
|
return "Version: " + self.name + " (" + str(self.product) + ")"
|
|
|
|
@staticmethod
|
|
@transaction.atomic
|
|
def merge(target, rip):
|
|
self = Version.objects.filter(name=target)
|
|
other = Version.objects.filter(name=rip)
|
|
if not (self and other):
|
|
raise ValueError("invalid arguments! {} {}".format(self, other))
|
|
self = self.first()
|
|
other = other.first()
|
|
for x in other.introduces.all():
|
|
x.used_until = self
|
|
x.save()
|
|
for x in other.dissmisses.all():
|
|
x.used_since = self
|
|
x.save()
|
|
other.delete()
|
|
|
|
|
|
class Sketch(PartModel):
|
|
class Meta:
|
|
verbose_name_plural = "sketches"
|
|
|
|
name = models.CharField(max_length=256)
|
|
brand = models.ForeignKey(Brand, blank=True, null=True, on_delete=models.CASCADE, db_index=True)
|
|
product = models.ForeignKey(Product, blank=True, null=True, on_delete=models.CASCADE)
|
|
image = models.FileField(upload_to="sketches/", blank=True, null=True)
|
|
used_until = models.ForeignKey(Version, null=True, blank=True, related_name='dismisses_sketch', on_delete=models.SET_NULL)
|
|
used_since = models.ForeignKey(Version, null=True, blank=True, related_name='introduces_sketch', on_delete=models.SET_NULL)
|
|
option = models.CharField(max_length=1024, null=True, blank=True)
|
|
|
|
def __str__(self):
|
|
return "Sketch: " + self.name + " (" + str(self.product) + ")"
|
|
|
|
def get_product(self):
|
|
if self.product:
|
|
return [self.product] + list(self.usage_set.values('productusage__product').distinct())
|
|
return self.usage_set.values('productusage__product').distinct()
|
|
|
|
def get_brand(self):
|
|
if self.brand:
|
|
return [self.brand] + list(self.usage_set.values('productusage__product__brand').distinct())
|
|
return self.usage_set.values('productusage__product__brand').distinct()
|
|
|
|
|
|
|
|
class Part(PartModel):
|
|
name = models.CharField(max_length=256, db_index=True)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class BrandedPart(PartModel):
|
|
parts = models.ManyToManyField(Part, db_index=True, related_name="branded_parts")
|
|
brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
|
|
number = models.CharField(max_length=64, unique=True, blank=True, null=True, db_index=True)
|
|
|
|
@transaction.atomic
|
|
def _merge(self, other):
|
|
"""add other part's usages and delete it"""
|
|
for p in other.parts.all():
|
|
if not p in self.parts.all():
|
|
self.parts.add(p)
|
|
self.save()
|
|
for u in other.usage_set.all():
|
|
u.part = self
|
|
u.save()
|
|
other.delete()
|
|
|
|
@staticmethod
|
|
@transaction.atomic
|
|
def merge(number1, number2):
|
|
self = BrandedPart.objects.filter(number=number1)
|
|
other = BrandedPart.objects.filter(number=number2)
|
|
if not (self and other):
|
|
raise ValueError("invalid arguments! {} {}".format(self, other))
|
|
self[0]._merge(other[0])
|
|
|
|
def get_part_name(self):
|
|
return self.parts.first().name if self.parts.exists() else ""
|
|
|
|
def get_name(self, full=False, first=False):
|
|
if full:
|
|
return ", ".join([i.name for i in self.parts.all()])
|
|
if first:
|
|
return self.parts.first().name if self.parts else "--(parts)--"
|
|
return "{} ({})".format(self.parts.first().name, self.parts.count()) if self.parts else "--(parts)--"
|
|
|
|
def __str__(self):
|
|
na = self.get_name() if self.parts else "--(part)--"
|
|
return str(self.number) + ": " + na + " @ " + self.brand.name
|
|
|
|
|
|
class Usage(PartModel):
|
|
part = models.ForeignKey(BrandedPart, on_delete=models.CASCADE, db_index=True)
|
|
sketch = models.ForeignKey(Sketch, on_delete=models.CASCADE, db_index=True)
|
|
sketch_number = models.CharField(max_length=32)
|
|
exact_sketch = models.BooleanField(default=True)
|
|
|
|
# quantity = models.ManyToManyField(ProductUsage)
|
|
|
|
def __str__(self):
|
|
no = self.part.number if self.part else "--(part.number)--"
|
|
na = self.part.get_name() if self.part else "--(part.part.name)--"
|
|
return self.sketch_number + "; " + str(no) + "; " + na + "; " + str(self.sketch)
|
|
|
|
|
|
class ProductUsage(PartModel):
|
|
usage = models.ForeignKey(Usage, on_delete=models.CASCADE, db_index=True)
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE, db_index=True)
|
|
quantity = models.IntegerField(null=True)
|
|
on_demand = models.BooleanField(default=False)
|
|
obsolete = models.BooleanField(default=False)
|
|
used_until = models.ForeignKey(Version, null=True, blank=True, related_name='introduces', on_delete=models.SET_NULL) # TODO: switch 'introduces' and 'dismisses'
|
|
used_since = models.ForeignKey(Version, null=True, blank=True, related_name='dissmisses', on_delete=models.SET_NULL) # TODO: switch 'introduces' and 'dismisses'
|
|
# replaced_by = models.ForeignKey('self', null=True, blank=True, related_name='replaces', on_delete=models.SET_NULL)
|
|
replaced_by = models.CharField(max_length=512, null=True, blank=True)
|
|
# alternative = models.ForeignKey('self', null=True, blank=True, related_name='alternatives', on_delete=models.SET_NULL)
|
|
note = models.CharField(max_length=1024, blank=True)
|
|
internal_note = models.CharField(max_length=2, blank=True)
|
|
|
|
def __str__(self):
|
|
no = self.usage.part.number if self.usage else "--(part.number)--"
|
|
return "ProductUsage: " + self.product.name + " @ " + str(self.quantity) + "; " + str(no)
|
|
|
|
|
|
|
|
class Shop(PartModel):
|
|
name = models.CharField(max_length=512)
|
|
url = models.URLField(null=True, blank=True)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class Cart(PartModel):
|
|
name = models.CharField(max_length=512)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def add(self, product_usage):
|
|
part = product_usage.usage.part
|
|
entry = None
|
|
for i in self.entries.all():
|
|
if part == i.part:
|
|
entry = i
|
|
break
|
|
if not entry:
|
|
entry = CartEntry.objects.create(cart=self)
|
|
entry.origins.add(product_usage)
|
|
entry.save()
|
|
|
|
def shop_entries(self, shop):
|
|
return sorted(self.entries.filter(shop=shop), key=lambda x: x.part.number)
|
|
|
|
|
|
class CartEntry(PartModel):
|
|
cart = models.ForeignKey(Cart, on_delete=models.CASCADE, related_name="entries")
|
|
origins = models.ManyToManyField(ProductUsage)
|
|
shop = models.ForeignKey(Shop, on_delete=models.CASCADE, null=True, blank=True)
|
|
in_cart = models.BooleanField(default=False, blank=True)
|
|
ordered = models.BooleanField(default=False, blank=True)
|
|
|
|
@property
|
|
def part(self):
|
|
return self.origins.first().usage.part
|
|
|
|
@property
|
|
def quantity(self):
|
|
return self.origins.aggregate(sum=Sum('quantity'))["sum"]
|
|
|
|
def get_quantity(self):
|
|
return self.quantity
|
|
|
|
def __str__(self):
|
|
return self.part.get_name()
|