mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Starting to implement BOM management
- Each part can be made of other parts - Disable tracking and project apps for now - Project will change (eventually) to work order - Part parameters have been disabled (for now)
This commit is contained in:
parent
47e99d5f35
commit
ed61ebe5b7
@ -46,10 +46,11 @@ INSTALLED_APPS = [
|
||||
|
||||
# InvenTree apps
|
||||
'part.apps.PartConfig',
|
||||
'project.apps.ProjectConfig',
|
||||
'stock.apps.StockConfig',
|
||||
'bom.apps.BomConfig',
|
||||
'supplier.apps.SupplierConfig',
|
||||
'track.apps.TrackConfig'
|
||||
'stock.apps.StockConfig',
|
||||
#'project.apps.ProjectConfig',
|
||||
#'track.apps.TrackConfig',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -3,11 +3,14 @@ from django.contrib import admin
|
||||
|
||||
from rest_framework.documentation import include_docs_urls
|
||||
|
||||
from part.urls import part_urls, part_cat_urls, part_param_urls, part_param_template_urls
|
||||
from part.urls import part_urls, part_cat_urls
|
||||
from bom.urls import bom_urls
|
||||
from stock.urls import stock_urls, stock_loc_urls
|
||||
from project.urls import prj_urls, prj_part_urls, prj_cat_urls, prj_run_urls
|
||||
from supplier.urls import cust_urls, manu_urls, supplier_part_urls, price_break_urls, supplier_urls
|
||||
from track.urls import unique_urls, part_track_urls
|
||||
|
||||
#from project.urls import prj_urls, prj_part_urls, prj_cat_urls, prj_run_urls
|
||||
#from track.urls import unique_urls, part_track_urls
|
||||
|
||||
from users.urls import user_urls
|
||||
|
||||
admin.site.site_header = "InvenTree Admin"
|
||||
@ -21,8 +24,11 @@ apipatterns = [
|
||||
# Part URLs
|
||||
url(r'^part/', include(part_urls)),
|
||||
url(r'^part-category/', include(part_cat_urls)),
|
||||
url(r'^part-param/', include(part_param_urls)),
|
||||
url(r'^part-param-template/', include(part_param_template_urls)),
|
||||
#url(r'^part-param/', include(part_param_urls)),
|
||||
#url(r'^part-param-template/', include(part_param_template_urls)),
|
||||
|
||||
# Part BOM URLs
|
||||
url(r'^bom/', include(bom_urls)),
|
||||
|
||||
# Supplier URLs
|
||||
url(r'^supplier/', include(supplier_urls)),
|
||||
@ -32,14 +38,14 @@ apipatterns = [
|
||||
url(r'^customer/', include(cust_urls)),
|
||||
|
||||
# Tracking URLs
|
||||
url(r'^track/', include(part_track_urls)),
|
||||
url(r'^unique-part/', include(unique_urls)),
|
||||
#url(r'^track/', include(part_track_urls)),
|
||||
#url(r'^unique-part/', include(unique_urls)),
|
||||
|
||||
# Project URLs
|
||||
url(r'^project/', include(prj_urls)),
|
||||
url(r'^project-category/', include(prj_cat_urls)),
|
||||
url(r'^project-part/', include(prj_part_urls)),
|
||||
url(r'^project-run/', include(prj_run_urls)),
|
||||
#url(r'^project/', include(prj_urls)),
|
||||
#url(r'^project-category/', include(prj_cat_urls)),
|
||||
#url(r'^project-part/', include(prj_part_urls)),
|
||||
#url(r'^project-run/', include(prj_run_urls)),
|
||||
|
||||
# User URLs
|
||||
url(r'^user/', include(user_urls)),
|
||||
|
0
InvenTree/bom/__init__.py
Normal file
0
InvenTree/bom/__init__.py
Normal file
8
InvenTree/bom/admin.py
Normal file
8
InvenTree/bom/admin.py
Normal file
@ -0,0 +1,8 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import BomItem
|
||||
|
||||
class BomItemAdmin(admin.ModelAdmin):
|
||||
list_display=('part', 'sub_part', 'quantity')
|
||||
|
||||
admin.site.register(BomItem, BomItemAdmin)
|
6
InvenTree/bom/apps.py
Normal file
6
InvenTree/bom/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
class BomConfig(AppConfig):
|
||||
name = 'bom'
|
37
InvenTree/bom/models.py
Normal file
37
InvenTree/bom/models.py
Normal file
@ -0,0 +1,37 @@
|
||||
from __future__ import unicode_literals
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.db import models
|
||||
from django.db.models import Sum
|
||||
from django.core.validators import MinValueValidator
|
||||
|
||||
from part.models import Part
|
||||
|
||||
class BomItem(models.Model):
|
||||
""" A BomItem links a part to its component items.
|
||||
A part can have a BOM (bill of materials) which defines
|
||||
which parts are required (and in what quatity) to make it
|
||||
"""
|
||||
|
||||
# A link to the parent part
|
||||
# Each part will get a reverse lookup field 'bom_items'
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='bom_items')
|
||||
|
||||
# A link to the child item (sub-part)
|
||||
# Each part will get a reverse lookup field 'used_in'
|
||||
sub_part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='used_in')
|
||||
|
||||
# Quantity required
|
||||
quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)])
|
||||
|
||||
|
||||
class Meta:
|
||||
verbose_name = "BOM Item"
|
||||
|
||||
# Prevent duplication of parent/child rows
|
||||
unique_together = ('part', 'sub_part')
|
||||
|
||||
def __str__(self):
|
||||
return "{par} -> {child} ({n})".format(
|
||||
par=self.part.name,
|
||||
child=self.sub_part.name,
|
||||
n=self.quantity)
|
13
InvenTree/bom/serializers.py
Normal file
13
InvenTree/bom/serializers.py
Normal file
@ -0,0 +1,13 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import BomItem
|
||||
|
||||
|
||||
class BomItemSerializer(serializers.HyperlinkedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = BomItem
|
||||
fields = ('url',
|
||||
'part',
|
||||
'sub_part',
|
||||
'quantity')
|
6
InvenTree/bom/tests.py
Normal file
6
InvenTree/bom/tests.py
Normal file
@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
12
InvenTree/bom/urls.py
Normal file
12
InvenTree/bom/urls.py
Normal file
@ -0,0 +1,12 @@
|
||||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
bom_urls = [
|
||||
# Bom Item detail
|
||||
url(r'^(?P<pk>[0-9]+)/?$', views.BomItemDetail.as_view(), name='bomitem-detail'),
|
||||
|
||||
# List of top-level categories
|
||||
url(r'^\?*.*/?$', views.BomItemList.as_view()),
|
||||
url(r'^$', views.BomItemList.as_view())
|
||||
]
|
35
InvenTree/bom/views.py
Normal file
35
InvenTree/bom/views.py
Normal file
@ -0,0 +1,35 @@
|
||||
from django_filters.rest_framework import FilterSet, DjangoFilterBackend
|
||||
|
||||
from rest_framework import generics, permissions
|
||||
|
||||
from InvenTree.models import FilterChildren
|
||||
|
||||
from .models import BomItem
|
||||
|
||||
from .serializers import BomItemSerializer
|
||||
|
||||
|
||||
class BomItemDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
||||
queryset = BomItem.objects.all()
|
||||
serializer_class = BomItemSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class BomItemFilter(FilterSet):
|
||||
|
||||
class Meta:
|
||||
model = BomItem
|
||||
fields = ['part', 'sub_part']
|
||||
|
||||
|
||||
class BomItemList(generics.ListCreateAPIView):
|
||||
|
||||
#def get_queryset(self):
|
||||
# params = self.request.
|
||||
|
||||
queryset = BomItem.objects.all()
|
||||
serializer_class = BomItemSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
filter_backends = (DjangoFilterBackend,)
|
||||
filter_class = BomItemFilter
|
@ -1,7 +1,6 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import PartCategory, Part, PartParameter, PartParameterTemplate, CategoryParameterLink
|
||||
|
||||
from .models import PartCategory, Part
|
||||
|
||||
class PartAdmin(admin.ModelAdmin):
|
||||
|
||||
@ -12,18 +11,18 @@ class PartCategoryAdmin(admin.ModelAdmin):
|
||||
|
||||
list_display = ('name', 'path', 'description')
|
||||
|
||||
|
||||
"""
|
||||
class ParameterTemplateAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'units', 'format')
|
||||
|
||||
|
||||
class ParameterAdmin(admin.ModelAdmin):
|
||||
list_display = ('part', 'template', 'value')
|
||||
|
||||
"""
|
||||
|
||||
admin.site.register(Part, PartAdmin)
|
||||
admin.site.register(PartCategory, PartCategoryAdmin)
|
||||
|
||||
admin.site.register(PartParameter, ParameterAdmin)
|
||||
admin.site.register(PartParameterTemplate, ParameterTemplateAdmin)
|
||||
admin.site.register(CategoryParameterLink)
|
||||
#admin.site.register(PartParameter, ParameterAdmin)
|
||||
#admin.site.register(PartParameterTemplate, ParameterTemplateAdmin)
|
||||
#admin.site.register(CategoryParameterLink)
|
||||
|
@ -21,7 +21,10 @@ class PartCategory(InvenTreeTree):
|
||||
|
||||
|
||||
class Part(models.Model):
|
||||
""" Represents a """
|
||||
""" Represents an abstract part
|
||||
Parts can be "stocked" in multiple warehouses,
|
||||
and can be combined to form other parts
|
||||
"""
|
||||
|
||||
# Short name of the part
|
||||
name = models.CharField(max_length=100)
|
||||
@ -92,84 +95,4 @@ class Part(models.Model):
|
||||
return projects
|
||||
|
||||
|
||||
class PartParameterTemplate(models.Model):
|
||||
""" A PartParameterTemplate pre-defines a parameter field,
|
||||
ready to be copied for use with a given Part.
|
||||
A PartParameterTemplate can be optionally associated with a PartCategory
|
||||
"""
|
||||
name = models.CharField(max_length=20, unique=True)
|
||||
units = models.CharField(max_length=10, blank=True)
|
||||
|
||||
# Parameter format
|
||||
PARAM_NUMERIC = 10
|
||||
PARAM_TEXT = 20
|
||||
PARAM_BOOL = 30
|
||||
|
||||
PARAM_TYPE_CODES = {
|
||||
PARAM_NUMERIC: _("Numeric"),
|
||||
PARAM_TEXT: _("Text"),
|
||||
PARAM_BOOL: _("Bool")
|
||||
}
|
||||
|
||||
format = models.PositiveIntegerField(
|
||||
default=PARAM_NUMERIC,
|
||||
choices=PARAM_TYPE_CODES.items(),
|
||||
validators=[MinValueValidator(0)])
|
||||
|
||||
def __str__(self):
|
||||
return "{name} ({units})".format(
|
||||
name=self.name,
|
||||
units=self.units)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Parameter Template"
|
||||
verbose_name_plural = "Parameter Templates"
|
||||
|
||||
|
||||
class CategoryParameterLink(models.Model):
|
||||
""" Links a PartParameterTemplate to a PartCategory
|
||||
"""
|
||||
category = models.ForeignKey(PartCategory, on_delete=models.CASCADE)
|
||||
template = models.ForeignKey(PartParameterTemplate, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return "{name} - {cat}".format(
|
||||
name=self.template.name,
|
||||
cat=self.category)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Category Parameter"
|
||||
verbose_name_plural = "Category Parameters"
|
||||
unique_together = ('category', 'template')
|
||||
|
||||
|
||||
class PartParameter(models.Model):
|
||||
""" PartParameter is associated with a single part
|
||||
"""
|
||||
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='parameters')
|
||||
template = models.ForeignKey(PartParameterTemplate)
|
||||
|
||||
# Value data
|
||||
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)
|
||||
|
||||
def __str__(self):
|
||||
return "{name} : {val}{units}".format(
|
||||
name=self.template.name,
|
||||
val=self.value,
|
||||
units=self.template.units)
|
||||
|
||||
@property
|
||||
def units(self):
|
||||
return self.template.units
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.template.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Part Parameter"
|
||||
verbose_name_plural = "Part Parameters"
|
||||
unique_together = ('part', 'template')
|
||||
|
89
InvenTree/part/param_todo.py
Normal file
89
InvenTree/part/param_todo.py
Normal file
@ -0,0 +1,89 @@
|
||||
"""
|
||||
TODO - Implement part parameters, and templates
|
||||
|
||||
See code below
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class PartParameterTemplate(models.Model):
|
||||
""" A PartParameterTemplate pre-defines a parameter field,
|
||||
ready to be copied for use with a given Part.
|
||||
A PartParameterTemplate can be optionally associated with a PartCategory
|
||||
"""
|
||||
name = models.CharField(max_length=20, unique=True)
|
||||
units = models.CharField(max_length=10, blank=True)
|
||||
|
||||
# Parameter format
|
||||
PARAM_NUMERIC = 10
|
||||
PARAM_TEXT = 20
|
||||
PARAM_BOOL = 30
|
||||
|
||||
PARAM_TYPE_CODES = {
|
||||
PARAM_NUMERIC: _("Numeric"),
|
||||
PARAM_TEXT: _("Text"),
|
||||
PARAM_BOOL: _("Bool")
|
||||
}
|
||||
|
||||
format = models.PositiveIntegerField(
|
||||
default=PARAM_NUMERIC,
|
||||
choices=PARAM_TYPE_CODES.items(),
|
||||
validators=[MinValueValidator(0)])
|
||||
|
||||
def __str__(self):
|
||||
return "{name} ({units})".format(
|
||||
name=self.name,
|
||||
units=self.units)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Parameter Template"
|
||||
verbose_name_plural = "Parameter Templates"
|
||||
|
||||
|
||||
class CategoryParameterLink(models.Model):
|
||||
""" Links a PartParameterTemplate to a PartCategory
|
||||
"""
|
||||
category = models.ForeignKey(PartCategory, on_delete=models.CASCADE)
|
||||
template = models.ForeignKey(PartParameterTemplate, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return "{name} - {cat}".format(
|
||||
name=self.template.name,
|
||||
cat=self.category)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Category Parameter"
|
||||
verbose_name_plural = "Category Parameters"
|
||||
unique_together = ('category', 'template')
|
||||
|
||||
|
||||
class PartParameter(models.Model):
|
||||
""" PartParameter is associated with a single part
|
||||
"""
|
||||
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='parameters')
|
||||
template = models.ForeignKey(PartParameterTemplate)
|
||||
|
||||
# Value data
|
||||
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)
|
||||
|
||||
def __str__(self):
|
||||
return "{name} : {val}{units}".format(
|
||||
name=self.template.name,
|
||||
val=self.value,
|
||||
units=self.template.units)
|
||||
|
||||
@property
|
||||
def units(self):
|
||||
return self.template.units
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.template.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Part Parameter"
|
||||
verbose_name_plural = "Part Parameters"
|
||||
unique_together = ('part', 'template')
|
@ -1,11 +1,11 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import Part, PartCategory, PartParameter, PartParameterTemplate
|
||||
|
||||
from .models import Part, PartCategory
|
||||
|
||||
"""
|
||||
class PartParameterSerializer(serializers.HyperlinkedModelSerializer):
|
||||
""" Serializer for a PartParameter
|
||||
"""
|
||||
" Serializer for a PartParameter
|
||||
"
|
||||
|
||||
class Meta:
|
||||
model = PartParameter
|
||||
@ -15,7 +15,7 @@ class PartParameterSerializer(serializers.HyperlinkedModelSerializer):
|
||||
'name',
|
||||
'value',
|
||||
'units')
|
||||
|
||||
"""
|
||||
|
||||
class PartSerializer(serializers.HyperlinkedModelSerializer):
|
||||
""" Serializer for complete detail information of a part.
|
||||
@ -44,7 +44,7 @@ class PartCategorySerializer(serializers.HyperlinkedModelSerializer):
|
||||
'parent',
|
||||
'path')
|
||||
|
||||
|
||||
"""
|
||||
class PartTemplateSerializer(serializers.HyperlinkedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
@ -53,3 +53,4 @@ class PartTemplateSerializer(serializers.HyperlinkedModelSerializer):
|
||||
'name',
|
||||
'units',
|
||||
'format')
|
||||
"""
|
@ -12,6 +12,17 @@ part_cat_urls = [
|
||||
url(r'^$', views.PartCategoryList.as_view())
|
||||
]
|
||||
|
||||
part_urls = [
|
||||
|
||||
# Individual part
|
||||
url(r'^(?P<pk>[0-9]+)/?$', views.PartDetail.as_view(), name='part-detail'),
|
||||
|
||||
# List parts with optional filters
|
||||
url(r'^\?.*/?$', views.PartList.as_view()),
|
||||
url(r'^$', views.PartList.as_view()),
|
||||
]
|
||||
|
||||
"""
|
||||
part_param_urls = [
|
||||
# Detail of a single part parameter
|
||||
url(r'^(?P<pk>[0-9]+)/?$', views.PartParamDetail.as_view(), name='partparameter-detail'),
|
||||
@ -29,13 +40,6 @@ part_param_template_urls = [
|
||||
url(r'^\?.*/?$', views.PartTemplateList.as_view()),
|
||||
url(r'^$', views.PartTemplateList.as_view())
|
||||
]
|
||||
"""
|
||||
|
||||
part_urls = [
|
||||
|
||||
# Individual part
|
||||
url(r'^(?P<pk>[0-9]+)/?$', views.PartDetail.as_view(), name='part-detail'),
|
||||
|
||||
# List parts with optional filters
|
||||
url(r'^\?.*/?$', views.PartList.as_view()),
|
||||
url(r'^$', views.PartList.as_view()),
|
||||
]
|
||||
|
@ -3,11 +3,12 @@ from django_filters.rest_framework import FilterSet, DjangoFilterBackend
|
||||
from rest_framework import generics, permissions
|
||||
|
||||
from InvenTree.models import FilterChildren
|
||||
from .models import PartCategory, Part, PartParameter, PartParameterTemplate
|
||||
from .models import PartCategory, Part
|
||||
|
||||
from .serializers import PartSerializer
|
||||
from .serializers import PartCategorySerializer
|
||||
from .serializers import PartParameterSerializer
|
||||
from .serializers import PartTemplateSerializer
|
||||
#from .serializers import PartParameterSerializer
|
||||
#from .serializers import PartTemplateSerializer
|
||||
|
||||
|
||||
class PartDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
@ -28,22 +29,22 @@ class PartDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
"""
|
||||
class PartParamFilter(FilterSet):
|
||||
|
||||
class Meta:
|
||||
model = PartParameter
|
||||
fields = ['part']
|
||||
|
||||
|
||||
class PartParamList(generics.ListCreateAPIView):
|
||||
"""
|
||||
"
|
||||
|
||||
get:
|
||||
Return a list of all part parameters (with optional filters)
|
||||
|
||||
post:
|
||||
Create a new part parameter
|
||||
"""
|
||||
""
|
||||
|
||||
queryset = PartParameter.objects.all()
|
||||
serializer_class = PartParameterSerializer
|
||||
@ -53,7 +54,7 @@ class PartParamList(generics.ListCreateAPIView):
|
||||
|
||||
|
||||
class PartParamDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
""
|
||||
|
||||
get:
|
||||
Detail view of a single PartParameter
|
||||
@ -64,12 +65,12 @@ class PartParamDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
delete:
|
||||
Remove a PartParameter from the database
|
||||
|
||||
"""
|
||||
"
|
||||
|
||||
queryset = PartParameter.objects.all()
|
||||
serializer_class = PartParameterSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
"""
|
||||
|
||||
class PartFilter(FilterSet):
|
||||
|
||||
@ -138,8 +139,9 @@ class PartCategoryList(generics.ListCreateAPIView):
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
"""
|
||||
class PartTemplateDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
""
|
||||
|
||||
get:
|
||||
Return detail on a single PartParameterTemplate object
|
||||
@ -150,7 +152,7 @@ class PartTemplateDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
delete:
|
||||
Remove a PartParameterTemplate object
|
||||
|
||||
"""
|
||||
""
|
||||
|
||||
queryset = PartParameterTemplate.objects.all()
|
||||
serializer_class = PartTemplateSerializer
|
||||
@ -158,7 +160,7 @@ class PartTemplateDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
||||
|
||||
class PartTemplateList(generics.ListCreateAPIView):
|
||||
"""
|
||||
""
|
||||
|
||||
get:
|
||||
Return a list of all PartParameterTemplate objects
|
||||
@ -167,8 +169,10 @@ class PartTemplateList(generics.ListCreateAPIView):
|
||||
post:
|
||||
Create a new PartParameterTemplate object
|
||||
|
||||
"""
|
||||
""
|
||||
|
||||
queryset = PartParameterTemplate.objects.all()
|
||||
serializer_class = PartTemplateSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
"""
|
||||
|
@ -14,6 +14,8 @@ from datetime import datetime
|
||||
|
||||
class StockLocation(InvenTreeTree):
|
||||
""" Organization tree for StockItem objects
|
||||
A "StockLocation" can be considered a warehouse, or storage location
|
||||
Stock locations can be heirarchical as required
|
||||
"""
|
||||
|
||||
@property
|
||||
@ -36,33 +38,27 @@ class StockItem(models.Model):
|
||||
review_needed = models.BooleanField(default=False)
|
||||
|
||||
# Stock status types
|
||||
ITEM_IN_STOCK = 10
|
||||
ITEM_INCOMING = 15
|
||||
ITEM_IN_PROGRESS = 20
|
||||
ITEM_COMPLETE = 25
|
||||
ITEM_OK = 10
|
||||
ITEM_ATTENTION = 50
|
||||
ITEM_DAMAGED = 55
|
||||
ITEM_DESTROYED = 60
|
||||
|
||||
ITEM_STATUS_CODES = {
|
||||
ITEM_IN_STOCK: _("In stock"),
|
||||
ITEM_INCOMING: _("Incoming"),
|
||||
ITEM_IN_PROGRESS: _("In progress"),
|
||||
ITEM_COMPLETE: _("Complete"),
|
||||
ITEM_OK: _("OK"),
|
||||
ITEM_ATTENTION: _("Attention needed"),
|
||||
ITEM_DAMAGED: _("Damaged"),
|
||||
ITEM_DESTROYED: _("Destroyed")
|
||||
}
|
||||
|
||||
status = models.PositiveIntegerField(
|
||||
default=ITEM_IN_STOCK,
|
||||
default=ITEM_OK,
|
||||
choices=ITEM_STATUS_CODES.items(),
|
||||
validators=[MinValueValidator(0)])
|
||||
|
||||
notes = models.CharField(max_length=100, blank=True)
|
||||
|
||||
# If stock item is incoming, an (optional) ETA field
|
||||
expected_arrival = models.DateField(null=True, blank=True)
|
||||
# expected_arrival = models.DateField(null=True, blank=True)
|
||||
|
||||
infinite = models.BooleanField(default=False)
|
||||
|
||||
|
@ -35,7 +35,11 @@ class SupplierPart(models.Model):
|
||||
class Meta:
|
||||
unique_together = ('part', 'supplier', 'SKU')
|
||||
|
||||
part = models.ForeignKey(Part, null=True, blank=True, on_delete=models.CASCADE)
|
||||
# Link to an actual part
|
||||
# The part will have a field 'supplier_parts' which links to the supplier part options
|
||||
part = models.ForeignKey(Part, null=True, blank=True, on_delete=models.CASCADE,
|
||||
related_name='supplier_parts')
|
||||
|
||||
supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE)
|
||||
SKU = models.CharField(max_length=100)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user