2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 12:06:44 +00:00

Merge pull request #15 from SchrodingersGat/master

updates
This commit is contained in:
Oliver 2017-03-29 23:39:04 +11:00 committed by GitHub
commit 574e0d4f62
5 changed files with 134 additions and 86 deletions

View File

@ -1,5 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.db import models from django.db import models
from django.db.models import Sum from django.db.models import Sum
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
@ -10,15 +10,15 @@ from InvenTree.models import InvenTreeTree
class PartCategory(InvenTreeTree): class PartCategory(InvenTreeTree):
""" PartCategory provides hierarchical organization of Part objects. """ PartCategory provides hierarchical organization of Part objects.
""" """
class Meta: class Meta:
verbose_name = "Part Category" verbose_name = "Part Category"
verbose_name_plural = "Part Categories" verbose_name_plural = "Part Categories"
class Part(models.Model): class Part(models.Model):
""" Represents a """ """ Represents a """
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
description = models.CharField(max_length=250, blank=True) description = models.CharField(max_length=250, blank=True)
IPN = models.CharField(max_length=100, blank=True) IPN = models.CharField(max_length=100, blank=True)
@ -26,7 +26,7 @@ class Part(models.Model):
minimum_stock = models.IntegerField(default=0) minimum_stock = models.IntegerField(default=0)
units = models.CharField(max_length=20, default="pcs", blank=True) units = models.CharField(max_length=20, default="pcs", blank=True)
trackable = models.BooleanField(default=False) trackable = models.BooleanField(default=False)
def __str__(self): def __str__(self):
if self.IPN: if self.IPN:
return "{name} ({ipn})".format( return "{name} ({ipn})".format(
@ -34,39 +34,39 @@ class Part(models.Model):
name=self.name) name=self.name)
else: else:
return self.name return self.name
class Meta: class Meta:
verbose_name = "Part" verbose_name = "Part"
verbose_name_plural = "Parts" verbose_name_plural = "Parts"
@property @property
def stock(self): def stock(self):
""" Return the total stock quantity for this part. """ Return the total stock quantity for this part.
Part may be stored in multiple locations Part may be stored in multiple locations
""" """
stocks = self.locations.all() stocks = self.locations.all()
if len(stocks) == 0: if len(stocks) == 0:
return 0 return 0
result = stocks.aggregate(total=Sum('quantity')) result = stocks.aggregate(total=Sum('quantity'))
return result['total'] return result['total']
@property @property
def projects(self): def projects(self):
""" Return a list of unique projects that this part is associated with """ Return a list of unique projects that this part is associated with
""" """
project_ids = set() project_ids = set()
project_parts = self.projectpart_set.all() project_parts = self.projectpart_set.all()
projects = [] projects = []
for pp in project_parts: for pp in project_parts:
if pp.project.id not in project_ids: if pp.project.id not in project_ids:
project_ids.add(pp.project.id) project_ids.add(pp.project.id)
projects.append(pp.project) projects.append(pp.project)
return projects return projects
@ -78,28 +78,31 @@ class PartParameterTemplate(models.Model):
name = models.CharField(max_length=20) name = models.CharField(max_length=20)
description = models.CharField(max_length=100, blank=True) description = models.CharField(max_length=100, blank=True)
units = models.CharField(max_length=10, blank=True) units = models.CharField(max_length=10, blank=True)
default_value = models.CharField(max_length=50, blank=True) default_value = models.CharField(max_length=50, blank=True)
default_min = models.CharField(max_length=50, blank=True) default_min = models.CharField(max_length=50, blank=True)
default_max = models.CharField(max_length=50, blank=True) default_max = models.CharField(max_length=50, blank=True)
# Parameter format # Parameter format
PARAM_NUMERIC = 10 PARAM_NUMERIC = 10
PARAM_TEXT = 20 PARAM_TEXT = 20
PARAM_BOOL = 30 PARAM_BOOL = 30
PARAM_TYPE_CODES = {
PARAM_NUMERIC: _("Numeric"),
PARAM_TEXT: _("Text"),
PARAM_BOOL: _("Bool")
}
format = models.IntegerField( format = models.IntegerField(
default=PARAM_NUMERIC, default=PARAM_NUMERIC,
choices=[ choices=PARAM_TYPE_CODES.items())
(PARAM_NUMERIC, "Numeric"),
(PARAM_TEXT, "Text"),
(PARAM_BOOL, "Boolean")])
def __str__(self): def __str__(self):
return "{name} ({units})".format( return "{name} ({units})".format(
name=self.name, name=self.name,
units=self.units) units=self.units)
class Meta: class Meta:
verbose_name = "Parameter Template" verbose_name = "Parameter Template"
verbose_name_plural = "Parameter Templates" verbose_name_plural = "Parameter Templates"
@ -110,7 +113,7 @@ class CategoryParameterLink(models.Model):
""" """
category = models.ForeignKey(PartCategory, on_delete=models.CASCADE) category = models.ForeignKey(PartCategory, on_delete=models.CASCADE)
template = models.ForeignKey(PartParameterTemplate, on_delete=models.CASCADE) template = models.ForeignKey(PartParameterTemplate, on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return "{name} - {cat}".format( return "{name} - {cat}".format(
name=self.template.name, name=self.template.name,
@ -119,20 +122,20 @@ class CategoryParameterLink(models.Model):
class Meta: class Meta:
verbose_name = "Category Parameter" verbose_name = "Category Parameter"
verbose_name_plural = "Category Parameters" verbose_name_plural = "Category Parameters"
class PartParameter(models.Model): class PartParameter(models.Model):
""" PartParameter is associated with a single part """ PartParameter is associated with a single part
""" """
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='parameters') part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='parameters')
template = models.ForeignKey(PartParameterTemplate) template = models.ForeignKey(PartParameterTemplate)
# Value data # Value data
value = models.CharField(max_length=50, blank=True) value = models.CharField(max_length=50, blank=True)
min_value = models.CharField(max_length=50, blank=True) min_value = models.CharField(max_length=50, blank=True)
max_value = models.CharField(max_length=50, blank=True) max_value = models.CharField(max_length=50, blank=True)
# Prevent multiple parameters of the same template # Prevent multiple parameters of the same template
# from being added to the same part # from being added to the same part
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -141,27 +144,27 @@ class PartParameter(models.Model):
return return
if len(params) == 1 and params[0].id != self.id: if len(params) == 1 and params[0].id != self.id:
return return
super(PartParameter, self).save(*args, **kwargs) super(PartParameter, self).save(*args, **kwargs)
def __str__(self): def __str__(self):
return "{name} : {val}{units}".format( return "{name} : {val}{units}".format(
name=self.template.name, name=self.template.name,
val=self.value, val=self.value,
units=self.template.units) units=self.template.units)
@property @property
def units(self): def units(self):
return self.template.units return self.template.units
@property @property
def name(self): def name(self):
return self.template.name return self.template.name
class Meta: class Meta:
verbose_name = "Part Parameter" verbose_name = "Part Parameter"
verbose_name_plural = "Part Parameters" verbose_name_plural = "Part Parameters"
class PartRevision(models.Model): class PartRevision(models.Model):
""" A PartRevision represents a change-notification to a Part """ A PartRevision represents a change-notification to a Part
@ -169,12 +172,12 @@ class PartRevision(models.Model):
which should be tracked. which should be tracked.
UniqueParts can have a single associated PartRevision UniqueParts can have a single associated PartRevision
""" """
part = models.ForeignKey(Part, on_delete=models.CASCADE) part = models.ForeignKey(Part, on_delete=models.CASCADE)
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
description = models.CharField(max_length=500) description = models.CharField(max_length=500)
revision_date = models.DateField(auto_now_add=True) revision_date = models.DateField(auto_now_add=True)
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -1,6 +1,6 @@
from django.contrib import admin from django.contrib import admin
from .models import ProjectCategory, Project, ProjectPart from .models import ProjectCategory, Project, ProjectPart, ProjectRun
class ProjectCategoryAdmin(admin.ModelAdmin): class ProjectCategoryAdmin(admin.ModelAdmin):
@ -14,6 +14,11 @@ class ProjectAdmin(admin.ModelAdmin):
class ProjectPartAdmin(admin.ModelAdmin): class ProjectPartAdmin(admin.ModelAdmin):
list_display = ('part', 'project', 'quantity') list_display = ('part', 'project', 'quantity')
class ProjectRunAdmin(admin.ModelAdmin):
list_display = ('project', 'quantity', 'run_date')
admin.site.register(ProjectCategory, ProjectCategoryAdmin) admin.site.register(ProjectCategory, ProjectCategoryAdmin)
admin.site.register(Project, ProjectAdmin) admin.site.register(Project, ProjectAdmin)
admin.site.register(ProjectPart, ProjectPartAdmin) admin.site.register(ProjectPart, ProjectPartAdmin)
admin.site.register(ProjectRun, ProjectRunAdmin)

View File

@ -1,4 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.db import models from django.db import models
@ -11,7 +12,7 @@ class ProjectCategory(InvenTreeTree):
Each ProjectCategory can contain zero-or-more child categories, Each ProjectCategory can contain zero-or-more child categories,
and in turn can have zero-or-one parent category. and in turn can have zero-or-one parent category.
""" """
class Meta: class Meta:
verbose_name = "Project Category" verbose_name = "Project Category"
verbose_name_plural = "Project Categories" verbose_name_plural = "Project Categories"
@ -21,14 +22,14 @@ class Project(models.Model):
""" A Project takes multiple Part objects. """ A Project takes multiple Part objects.
A project can output zero-or-more Part objects A project can output zero-or-more Part objects
""" """
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
description = models.CharField(max_length=500, blank=True) description = models.CharField(max_length=500, blank=True)
category = models.ForeignKey(ProjectCategory, on_delete=models.CASCADE) category = models.ForeignKey(ProjectCategory, on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return self.name return self.name
@property @property
def projectParts(self): def projectParts(self):
""" Return a list of all project parts associated with this project """ Return a list of all project parts associated with this project
@ -41,23 +42,40 @@ class ProjectPart(models.Model):
The quantity of parts required for a single-run of that project is stored. The quantity of parts required for a single-run of that project is stored.
The overage is the number of extra parts that are generally used for a single run. The overage is the number of extra parts that are generally used for a single run.
""" """
# Overage types # Overage types
OVERAGE_PERCENT = 0 OVERAGE_PERCENT = 0
OVERAGE_ABSOLUTE = 1 OVERAGE_ABSOLUTE = 1
OVARAGE_CODES = {
OVERAGE_PERCENT: _("Percent"),
OVERAGE_ABSOLUTE: _("Absolute")
}
part = models.ForeignKey(Part, on_delete=models.CASCADE) part = models.ForeignKey(Part, on_delete=models.CASCADE)
project = models.ForeignKey(Project, on_delete=models.CASCADE) project = models.ForeignKey(Project, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1) quantity = models.PositiveIntegerField(default=1)
overage = models.FloatField(default=0) overage = models.FloatField(default=0)
overage_type = models.IntegerField( overage_type = models.PositiveIntegerField(
default=1, default=1,
choices=[ choices=OVARAGE_CODES.items())
(OVERAGE_PERCENT, "Percent"),
(OVERAGE_ABSOLUTE, "Absolute")
])
def __str__(self): def __str__(self):
return "{quan} x {name}".format( return "{quan} x {name}".format(
name=self.part.name, name=self.part.name,
quan=self.quantity) quan=self.quantity)
class ProjectRun(models.Model):
""" A single run of a particular project.
Tracks the number of 'units' made in the project.
Provides functionality to update stock,
based on both:
a) Parts used (project inputs)
b) Parts produced (project outputs)
"""
project = models.ForeignKey(Project, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(default=1)
run_date = models.DateField(auto_now_add=True)

View File

@ -1,5 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.db import models from django.db import models
from part.models import Part from part.models import Part
@ -8,32 +8,47 @@ from InvenTree.models import InvenTreeTree
class Warehouse(InvenTreeTree): class Warehouse(InvenTreeTree):
pass pass
class StockItem(models.Model): class StockItem(models.Model):
part = models.ForeignKey(Part, part = models.ForeignKey(Part,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='locations') related_name='locations')
location = models.ForeignKey(Warehouse, on_delete=models.CASCADE) location = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
quantity = models.IntegerField() quantity = models.PositiveIntegerField()
updated = models.DateField(auto_now=True) updated = models.DateField(auto_now=True)
# last time the stock was checked / counted
last_checked = models.DateField(blank=True, null=True)
review_needed = models.BooleanField(default=False)
# Stock status types # Stock status types
ITEM_IN_PROGRESS = 0 ITEM_IN_STOCK = 10
ITEM_INCOMING = 5 ITEM_INCOMING = 15
ITEM_DAMAGED = 10 ITEM_IN_PROGRESS = 20
ITEM_ATTENTION = 20 ITEM_COMPLETE = 25
ITEM_COMPLETE = 50 ITEM_ATTENTION = 50
ITEM_DAMAGED = 55
status = models.IntegerField(default=ITEM_IN_PROGRESS, ITEM_DESTROYED = 60
choices=[
(ITEM_IN_PROGRESS, "In progress"), ITEM_STATUS_CODES = {
(ITEM_INCOMING, "Incoming"), ITEM_IN_STOCK: _("In stock"),
(ITEM_DAMAGED, "Damaged"), ITEM_INCOMING: _("Incoming"),
(ITEM_ATTENTION, "Requires attention"), ITEM_IN_PROGRESS: _("In progress"),
(ITEM_COMPLETE, "Complete") ITEM_COMPLETE: _("Complete"),
]) ITEM_ATTENTION: _("Attention needed"),
ITEM_DAMAGED: _("Damaged"),
ITEM_DESTROYED: _("Destroyed")
}
status = models.PositiveIntegerField(
default=ITEM_IN_STOCK,
choices=ITEM_STATUS_CODES.items())
# If stock item is incoming, an (optional) ETA field
expected_arrival = models.DateField(null=True, blank=True)
def __str__(self): def __str__(self):
return "{n} x {part} @ {loc}".format( return "{n} x {part} @ {loc}".format(
n=self.quantity, n=self.quantity,

View File

@ -9,9 +9,8 @@ from part.models import Part
class Supplier(Company): class Supplier(Company):
""" Represents a manufacturer or supplier """ Represents a manufacturer or supplier
""" """
pass pass
class Manufacturer(Company): class Manufacturer(Company):
""" Represents a manfufacturer """ Represents a manfufacturer
@ -32,21 +31,32 @@ class SupplierPart(models.Model):
- A Part may be available from multiple suppliers - A Part may be available from multiple suppliers
""" """
part = models.ForeignKey(Part, part = models.ForeignKey(Part, null=True, blank=True, on_delete=models.CASCADE, related_name='supplier_parts')
on_delete=models.CASCADE) supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE)
supplier = models.ForeignKey(Supplier,
on_delete=models.CASCADE)
SKU = models.CharField(max_length=100) SKU = models.CharField(max_length=100)
manufacturer = models.ForeignKey(Manufacturer, blank=True, null=True, on_delete=models.CASCADE) manufacturer = models.ForeignKey(Manufacturer, blank=True, null=True, on_delete=models.CASCADE)
MPN = models.CharField(max_length=100, blank=True) MPN = models.CharField(max_length=100, blank=True)
URL = models.URLField(blank=True) URL = models.URLField(blank=True)
description = models.CharField(max_length=250, blank=True) description = models.CharField(max_length=250, blank=True)
single_price = models.DecimalField(max_digits=10,
decimal_places=3,
default=0)
# packaging that the part is supplied in, e.g. "Reel"
packaging = models.CharField(max_length=50, blank=True)
# multiple that the part is provided in
multiple = models.PositiveIntegerField(default=1)
# lead time for parts that cannot be delivered immediately
lead_time = models.DurationField(blank=True, null=True)
def __str__(self): def __str__(self):
return "{mpn} - {supplier}".format( return "{sku} - {supplier}".format(
mpn=self.MPN, sku=self.SKU,
supplier=self.supplier.name) supplier=self.supplier.name)
@ -56,12 +66,9 @@ class SupplierPriceBreak(models.Model):
- SupplierPart(s) may have zero-or-more associated SupplierPriceBreak(s) - SupplierPart(s) may have zero-or-more associated SupplierPriceBreak(s)
""" """
part = models.ForeignKey(SupplierPart, part = models.ForeignKey(SupplierPart, on_delete=models.CASCADE, related_name='price_breaks')
on_delete=models.CASCADE) quantity = models.PositiveIntegerField()
quantity = models.IntegerField()
cost = models.DecimalField(max_digits=10, decimal_places=3) cost = models.DecimalField(max_digits=10, decimal_places=3)
currency = models.CharField(max_length=10,
blank=True)
def __str__(self): def __str__(self):
return "{mpn} - {cost}{currency} @ {quan}".format( return "{mpn} - {cost}{currency} @ {quan}".format(