Merge pull request #43 from SchrodingersGat/master

Added StockTake endpoint
This commit is contained in:
Oliver 2017-04-20 22:09:12 +10:00 committed by GitHub
commit c716b2d6c1
9 changed files with 197 additions and 24 deletions

View File

@ -5,7 +5,7 @@ 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, stock_track_urls
from project.urls import prj_urls, prj_part_urls, prj_cat_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
@ -39,6 +39,7 @@ apipatterns = [
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)),
]
urlpatterns = [

View File

@ -93,4 +93,4 @@ class ProjectRun(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)])
run_date = models.DateField(auto_now_add=True)
run_date = models.DateField(blank=True, null=True)

View File

@ -1,6 +1,6 @@
from rest_framework import serializers
from .models import ProjectCategory, Project, ProjectPart
from .models import ProjectCategory, Project, ProjectPart, ProjectRun
class ProjectPartSerializer(serializers.HyperlinkedModelSerializer):
@ -33,3 +33,15 @@ class ProjectCategorySerializer(serializers.HyperlinkedModelSerializer):
'description',
'parent',
'path')
class ProjectRunSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ProjectRun
fields = ('url',
'project',
'quantity',
'run_date')
read_only_fields = ('run_date',)

View File

@ -28,3 +28,12 @@ prj_urls = [
url(r'^\?.*/?$', views.ProjectList.as_view()),
url(r'^$', views.ProjectList.as_view())
]
prj_run_urls = [
# Individual project URL
url(r'^(?P<pk>[0-9]+)/?$', views.ProjectRunDetail.as_view(), name='projectrun-detail'),
# List of all projects
url(r'^\?.*/?$', views.ProjectRunList.as_view()),
url(r'^$', views.ProjectRunList.as_view())
]

View File

@ -2,10 +2,11 @@ from django_filters.rest_framework import FilterSet, DjangoFilterBackend
from rest_framework import generics, permissions
from InvenTree.models import FilterChildren
from .models import ProjectCategory, Project, ProjectPart
from .models import ProjectCategory, Project, ProjectPart, ProjectRun
from .serializers import ProjectSerializer
from .serializers import ProjectCategorySerializer
from .serializers import ProjectPartSerializer
from .serializers import ProjectRunSerializer
class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
@ -96,6 +97,13 @@ class ProjectCategoryList(generics.ListCreateAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class ProjectPartFilter(FilterSet):
class Meta:
model = ProjectPart
fields = ['project', 'part']
class ProjectPartsList(generics.ListCreateAPIView):
"""
@ -109,20 +117,9 @@ class ProjectPartsList(generics.ListCreateAPIView):
serializer_class = ProjectPartSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get_queryset(self):
parts = ProjectPart.objects.all()
params = self.request.query_params
project_id = params.get('project', None)
if project_id:
parts = parts.filter(project=project_id)
part_id = params.get('part', None)
if part_id:
parts = parts.filter(part=part_id)
return parts
queryset = ProjectPart.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_class = ProjectPartFilter
class ProjectPartDetail(generics.RetrieveUpdateDestroyAPIView):
@ -142,3 +139,45 @@ class ProjectPartDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = ProjectPart.objects.all()
serializer_class = ProjectPartSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class ProjectRunDetail(generics.RetrieveUpdateDestroyAPIView):
"""
get:
Return a single ProjectRun
post:
Update a ProjectRun
delete:
Remove a ProjectRun
"""
queryset = ProjectRun.objects.all()
serializer_class = ProjectRunSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class ProjectRunFilter(FilterSet):
class Meta:
model = ProjectRun
fields = ['project']
class ProjectRunList(generics.ListCreateAPIView):
"""
get:
Return a list of all ProjectRun objects
post:
Create a new ProjectRun object
"""
queryset = ProjectRun.objects.all()
serializer_class = ProjectRunSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
filter_backends = (DjangoFilterBackend,)
filter_class = ProjectRunFilter

View File

@ -7,6 +7,8 @@ from supplier.models import SupplierPart
from part.models import Part
from InvenTree.models import InvenTreeTree
from datetime import datetime
class StockLocation(InvenTreeTree):
""" Organization tree for StockItem objects
@ -26,7 +28,7 @@ class StockItem(models.Model):
updated = models.DateField(auto_now=True)
# last time the stock was checked / counted
last_checked = models.DateField(blank=True, null=True)
stocktake_date = models.DateField(blank=True, null=True)
review_needed = models.BooleanField(default=False)
@ -59,6 +61,65 @@ class StockItem(models.Model):
# If stock item is incoming, an (optional) ETA field
expected_arrival = models.DateField(null=True, blank=True)
infinite = models.BooleanField(default=False)
def stocktake(self, count):
""" Perform item stocktake.
When the quantity of an item is counted,
record the date of stocktake
"""
count = int(count)
if count < 0 or self.infinite:
return
self.quantity = count
self.stocktake_date = datetime.now().date()
self.save()
def take_stock(self, amount):
""" Take items from stock
This function can be called by initiating a ProjectRun,
or by manually taking the items from the stock location
"""
if self.infinite:
return
amount = int(amount)
if amount < 0:
raise ValueError("Stock amount must be positive")
q = self.quantity - amount
if q < 0:
q = 0
self.quantity = q
self.save()
def add_stock(self, amount):
""" Add items to stock
This function can be called by initiating a ProjectRun,
or by manually adding the items to the stock location
"""
amount = int(amount)
if self.infinite or amount == 0:
return
amount = int(amount)
q = self.quantity + amount
if q < 0:
q = 0
self.quantity = q
self.save()
def __str__(self):
return "{n} x {part} @ {loc}".format(
n=self.quantity,

View File

@ -17,10 +17,22 @@ class StockItemSerializer(serializers.HyperlinkedModelSerializer):
'status',
'notes',
'updated',
'last_checked',
'stocktake_date',
'review_needed',
'expected_arrival')
""" These fields are read-only in this context.
They can be updated by accessing the appropriate API endpoints
"""
read_only_fields = ('stocktake_date', 'quantity',)
class StockQuantitySerializer(serializers.ModelSerializer):
class Meta:
model = StockItem
fields = ('quantity',)
class LocationSerializer(serializers.HyperlinkedModelSerializer):
""" Detailed information about a stock location

View File

@ -1,10 +1,18 @@
from django.conf.urls import url
from django.conf.urls import url, include
from . import views
stock_endpoints = [
url(r'^$', views.StockDetail.as_view(), name='stockitem-detail'),
url(r'^stocktake/?$', views.StockStocktakeEndpoint.as_view(), name='stockitem-stocktake'),
url(r'^add-stock/?$', views.AddStockEndpoint.as_view(), name='stockitem-add-stock'),
]
stock_urls = [
# Detail for a single stock item
url(r'^(?P<pk>[0-9]+)/?$', views.StockDetail.as_view(), name='stockitem-detail'),
url(r'^(?P<pk>[0-9]+)/', include(stock_endpoints)),
# List all stock items, with optional filters
url(r'^\?.*/?$', views.StockList.as_view()),

View File

@ -1,11 +1,12 @@
from django_filters.rest_framework import FilterSet, DjangoFilterBackend
from django_filters import NumberFilter
from rest_framework import generics, permissions
from rest_framework import generics, permissions, response
# from InvenTree.models import FilterChildren
from .models import StockLocation, StockItem, StockTracking
from .serializers import StockItemSerializer, LocationSerializer, StockTrackingSerializer
from .serializers import StockItemSerializer, StockQuantitySerializer
from .serializers import LocationSerializer, StockTrackingSerializer
class StockDetail(generics.RetrieveUpdateDestroyAPIView):
@ -53,6 +54,36 @@ class StockList(generics.ListCreateAPIView):
filter_class = StockFilter
class StockStocktakeEndpoint(generics.UpdateAPIView):
queryset = StockItem.objects.all()
serializer_class = StockQuantitySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def update(self, request, *args, **kwargs):
object = self.get_object()
object.stocktake(request.data['quantity'])
serializer = self.get_serializer(object)
return response.Response(serializer.data)
class AddStockEndpoint(generics.UpdateAPIView):
queryset = StockItem.objects.all()
serializer_class = StockQuantitySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def update(self, request, *args, **kwargs):
object = self.get_object()
object.add_stock(request.data['quantity'])
serializer = self.get_serializer(object)
return response.Response(serializer.data)
class LocationDetail(generics.RetrieveUpdateDestroyAPIView):
"""