Merge pull request #41 from SchrodingersGat/master

Added StockTracking model
This commit is contained in:
Oliver 2017-04-16 01:44:18 +10:00 committed by GitHub
commit 4b3226e117
10 changed files with 107 additions and 58 deletions

View File

@ -4,7 +4,7 @@ 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 stock.urls import stock_urls, stock_loc_urls
from stock.urls import stock_urls, stock_loc_urls, stock_track_urls
from project.urls import prj_urls, prj_part_urls, prj_cat_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
@ -16,6 +16,7 @@ apipatterns = [
# Stock URLs
url(r'^stock/', include(stock_urls)),
url(r'^stock-location/', include(stock_loc_urls)),
url(r'^stock-track/', include(stock_track_urls)),
# Part URLs
url(r'^part/', include(part_urls)),

View File

@ -56,6 +56,8 @@ class Part(models.Model):
class Meta:
verbose_name = "Part"
verbose_name_plural = "Parts"
unique_together = (("name", "category"),
("IPN", "category"))
@property
def stock(self):
@ -94,7 +96,7 @@ class PartParameterTemplate(models.Model):
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)
name = models.CharField(max_length=20, unique=True)
units = models.CharField(max_length=10, blank=True)
# Parameter format
@ -136,32 +138,13 @@ class CategoryParameterLink(models.Model):
class Meta:
verbose_name = "Category Parameter"
verbose_name_plural = "Category Parameters"
class PartParameterManager(models.Manager):
""" Manager for handling PartParameter objects
"""
def create(self, *args, **kwargs):
""" Prevent creation of duplicate PartParameter
"""
part_id = kwargs['part']
template_id = kwargs['template']
params = self.filter(part=part_id, template=template_id)
if len(params) > 0:
return params[0]
return super(PartParameterManager, self).create(*args, **kwargs)
unique_together = ('category', 'template')
class PartParameter(models.Model):
""" PartParameter is associated with a single part
"""
objects = PartParameterManager()
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='parameters')
template = models.ForeignKey(PartParameterTemplate)
@ -187,3 +170,4 @@ class PartParameter(models.Model):
class Meta:
verbose_name = "Part Parameter"
verbose_name_plural = "Part Parameters"
unique_together = ('part', 'template')

View File

@ -3,13 +3,13 @@ from rest_framework import serializers
from .models import Part, PartCategory, PartParameter, PartParameterTemplate
class PartParameterSerializer(serializers.ModelSerializer):
class PartParameterSerializer(serializers.HyperlinkedModelSerializer):
""" Serializer for a PartParameter
"""
class Meta:
model = PartParameter
fields = ('pk',
fields = ('url',
'part',
'template',
'name',
@ -45,11 +45,11 @@ class PartCategorySerializer(serializers.HyperlinkedModelSerializer):
'path')
class PartTemplateSerializer(serializers.ModelSerializer):
class PartTemplateSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PartParameterTemplate
fields = ('pk',
fields = ('url',
'name',
'units',
'format')

View File

@ -31,44 +31,26 @@ class Project(models.Model):
description = models.CharField(max_length=500, blank=True)
category = models.ForeignKey(ProjectCategory, on_delete=models.CASCADE, related_name='projects')
class Meta:
unique_together = ('name', 'category')
def __str__(self):
return self.name
class ProjectPartManager(models.Manager):
""" Manager for handling ProjectParts
"""
def create(self, *args, **kwargs):
""" Test for validity of new ProjectPart before actually creating it.
If a ProjectPart already exists that references the same:
a) Part
b) Project
then return THAT project instead.
"""
project_id = kwargs['project']
part_id = kwargs['part']
project_parts = self.filter(project=project_id, part=part_id)
if len(project_parts) > 0:
return project_parts[0]
return super(ProjectPartManager, self).create(*args, **kwargs)
class ProjectPart(models.Model):
""" A project part associates a single part with a project
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.
"""
objects = ProjectPartManager()
part = models.ForeignKey(Part, on_delete=models.CASCADE)
project = models.ForeignKey(Project, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(default=1)
class Meta:
unique_together = ('part', 'project')
"""
# TODO - Add overage model fields

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.db import models
from supplier.models import SupplierPart
from part.models import Part
from InvenTree.models import InvenTreeTree
@ -17,9 +18,8 @@ class StockLocation(InvenTreeTree):
class StockItem(models.Model):
part = models.ForeignKey(Part,
on_delete=models.CASCADE,
related_name='locations')
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='locations')
supplier_part = models.ForeignKey(SupplierPart, blank=True, null=True, on_delete=models.SET_NULL)
location = models.ForeignKey(StockLocation, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField()
updated = models.DateField(auto_now=True)
@ -52,6 +52,8 @@ class StockItem(models.Model):
default=ITEM_IN_STOCK,
choices=ITEM_STATUS_CODES.items())
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)
@ -60,3 +62,16 @@ class StockItem(models.Model):
n=self.quantity,
part=self.part.name,
loc=self.location.name)
class StockTracking(models.Model):
""" Tracks a single movement of stock
- Used to track stock being taken from a location
- Used to track stock being added to a location
- "Pending" flag shows that stock WILL be taken / added
"""
item = models.ForeignKey(StockItem, on_delete=models.CASCADE, related_name='tracking')
quantity = models.IntegerField()
pending = models.BooleanField(default=False)
when = models.DateTimeField(auto_now=True)

View File

@ -1,6 +1,6 @@
from rest_framework import serializers
from .models import StockItem, StockLocation
from .models import StockItem, StockLocation, StockTracking
class StockItemSerializer(serializers.HyperlinkedModelSerializer):
@ -14,6 +14,7 @@ class StockItemSerializer(serializers.HyperlinkedModelSerializer):
'location',
'quantity',
'status',
'notes',
'updated',
'last_checked',
'review_needed',
@ -31,3 +32,14 @@ class LocationSerializer(serializers.HyperlinkedModelSerializer):
'description',
'parent',
'path')
class StockTrackingSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = StockTracking
fields = ('url',
'item',
'quantity',
'pending',
'when')

View File

@ -18,3 +18,11 @@ stock_loc_urls = [
url(r'^$', views.LocationList.as_view())
]
stock_track_urls = [
url(r'^(?P<pk>[0-9]+)/?$', views.StockTrackingDetail.as_view(), name='stocktracking-detail'),
url(r'^\?.*/?$', views.StockTrackingList.as_view()),
url(r'^$', views.StockTrackingList.as_view())
]

View File

@ -4,8 +4,8 @@ from django_filters import NumberFilter
from rest_framework import generics, permissions
# from InvenTree.models import FilterChildren
from .models import StockLocation, StockItem
from .serializers import StockItemSerializer, LocationSerializer
from .models import StockLocation, StockItem, StockTracking
from .serializers import StockItemSerializer, LocationSerializer, StockTrackingSerializer
class StockDetail(generics.RetrieveUpdateDestroyAPIView):
@ -100,3 +100,49 @@ class LocationList(generics.ListCreateAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
filter_backends = (DjangoFilterBackend,)
filter_class = StockLocationFilter
class StockTrackingDetail(generics.RetrieveUpdateDestroyAPIView):
"""
get:
Return a single StockTracking object
post:
Update a StockTracking object
delete:
Remove a StockTracking object
"""
queryset = StockTracking.objects.all()
serializer_class = StockTrackingSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class StockTrackingFilter(FilterSet):
item = NumberFilter(name='item', lookup_expr='exact')
class Meta:
model = StockTracking
fields = ['item']
class StockTrackingList(generics.ListCreateAPIView):
"""
get:
Return a list of all StockTracking items
post:
Create a new StockTracking item
"""
queryset = StockTracking.objects.all()
serializer_class = StockTrackingSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
filter_backends = (DjangoFilterBackend,)
filter_class = StockTrackingFilter

View File

@ -78,6 +78,9 @@ class SupplierPriceBreak(models.Model):
quantity = models.PositiveIntegerField()
cost = models.DecimalField(max_digits=10, decimal_places=3)
class Meta:
unique_together = ("part", "quantity")
def __str__(self):
return "{mpn} - {cost}{currency} @ {quan}".format(
mpn=self.part.MPN,

View File

@ -12,8 +12,6 @@ class UniquePartManager(models.Manager):
def create(self, *args, **kwargs):
print(kwargs)
part = kwargs.get('part', None)
if not part.trackable: