mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-02 05:26:45 +00:00
Merge pull request #43 from SchrodingersGat/master
Added StockTake endpoint
This commit is contained in:
commit
c716b2d6c1
@ -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 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 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 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 track.urls import unique_urls, part_track_urls
|
||||||
|
|
||||||
@ -39,6 +39,7 @@ apipatterns = [
|
|||||||
url(r'^project/', include(prj_urls)),
|
url(r'^project/', include(prj_urls)),
|
||||||
url(r'^project-category/', include(prj_cat_urls)),
|
url(r'^project-category/', include(prj_cat_urls)),
|
||||||
url(r'^project-part/', include(prj_part_urls)),
|
url(r'^project-part/', include(prj_part_urls)),
|
||||||
|
url(r'^project-run/', include(prj_run_urls)),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
@ -93,4 +93,4 @@ class ProjectRun(models.Model):
|
|||||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
||||||
quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)])
|
quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)])
|
||||||
|
|
||||||
run_date = models.DateField(auto_now_add=True)
|
run_date = models.DateField(blank=True, null=True)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import ProjectCategory, Project, ProjectPart
|
from .models import ProjectCategory, Project, ProjectPart, ProjectRun
|
||||||
|
|
||||||
|
|
||||||
class ProjectPartSerializer(serializers.HyperlinkedModelSerializer):
|
class ProjectPartSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
@ -33,3 +33,15 @@ class ProjectCategorySerializer(serializers.HyperlinkedModelSerializer):
|
|||||||
'description',
|
'description',
|
||||||
'parent',
|
'parent',
|
||||||
'path')
|
'path')
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectRunSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ProjectRun
|
||||||
|
fields = ('url',
|
||||||
|
'project',
|
||||||
|
'quantity',
|
||||||
|
'run_date')
|
||||||
|
|
||||||
|
read_only_fields = ('run_date',)
|
||||||
|
@ -28,3 +28,12 @@ prj_urls = [
|
|||||||
url(r'^\?.*/?$', views.ProjectList.as_view()),
|
url(r'^\?.*/?$', views.ProjectList.as_view()),
|
||||||
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())
|
||||||
|
]
|
||||||
|
@ -2,10 +2,11 @@ from django_filters.rest_framework import FilterSet, DjangoFilterBackend
|
|||||||
|
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions
|
||||||
from InvenTree.models import FilterChildren
|
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 ProjectSerializer
|
||||||
from .serializers import ProjectCategorySerializer
|
from .serializers import ProjectCategorySerializer
|
||||||
from .serializers import ProjectPartSerializer
|
from .serializers import ProjectPartSerializer
|
||||||
|
from .serializers import ProjectRunSerializer
|
||||||
|
|
||||||
|
|
||||||
class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
|
class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
@ -96,6 +97,13 @@ class ProjectCategoryList(generics.ListCreateAPIView):
|
|||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectPartFilter(FilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ProjectPart
|
||||||
|
fields = ['project', 'part']
|
||||||
|
|
||||||
|
|
||||||
class ProjectPartsList(generics.ListCreateAPIView):
|
class ProjectPartsList(generics.ListCreateAPIView):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -109,20 +117,9 @@ class ProjectPartsList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
serializer_class = ProjectPartSerializer
|
serializer_class = ProjectPartSerializer
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
queryset = ProjectPart.objects.all()
|
||||||
def get_queryset(self):
|
filter_backends = (DjangoFilterBackend,)
|
||||||
parts = ProjectPart.objects.all()
|
filter_class = ProjectPartFilter
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectPartDetail(generics.RetrieveUpdateDestroyAPIView):
|
class ProjectPartDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
@ -142,3 +139,45 @@ class ProjectPartDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
queryset = ProjectPart.objects.all()
|
queryset = ProjectPart.objects.all()
|
||||||
serializer_class = ProjectPartSerializer
|
serializer_class = ProjectPartSerializer
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
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
|
||||||
|
@ -7,6 +7,8 @@ from supplier.models import SupplierPart
|
|||||||
from part.models import Part
|
from part.models import Part
|
||||||
from InvenTree.models import InvenTreeTree
|
from InvenTree.models import InvenTreeTree
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class StockLocation(InvenTreeTree):
|
class StockLocation(InvenTreeTree):
|
||||||
""" Organization tree for StockItem objects
|
""" Organization tree for StockItem objects
|
||||||
@ -26,7 +28,7 @@ class StockItem(models.Model):
|
|||||||
updated = models.DateField(auto_now=True)
|
updated = models.DateField(auto_now=True)
|
||||||
|
|
||||||
# last time the stock was checked / counted
|
# 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)
|
review_needed = models.BooleanField(default=False)
|
||||||
|
|
||||||
@ -59,6 +61,65 @@ class StockItem(models.Model):
|
|||||||
# If stock item is incoming, an (optional) ETA field
|
# 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)
|
||||||
|
|
||||||
|
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):
|
def __str__(self):
|
||||||
return "{n} x {part} @ {loc}".format(
|
return "{n} x {part} @ {loc}".format(
|
||||||
n=self.quantity,
|
n=self.quantity,
|
||||||
|
@ -17,10 +17,22 @@ class StockItemSerializer(serializers.HyperlinkedModelSerializer):
|
|||||||
'status',
|
'status',
|
||||||
'notes',
|
'notes',
|
||||||
'updated',
|
'updated',
|
||||||
'last_checked',
|
'stocktake_date',
|
||||||
'review_needed',
|
'review_needed',
|
||||||
'expected_arrival')
|
'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):
|
class LocationSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
""" Detailed information about a stock location
|
""" Detailed information about a stock location
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
from django.conf.urls import url
|
from django.conf.urls import url, include
|
||||||
|
|
||||||
from . import views
|
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 = [
|
stock_urls = [
|
||||||
# Detail for a single stock item
|
# 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
|
# List all stock items, with optional filters
|
||||||
url(r'^\?.*/?$', views.StockList.as_view()),
|
url(r'^\?.*/?$', views.StockList.as_view()),
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
from django_filters.rest_framework import FilterSet, DjangoFilterBackend
|
from django_filters.rest_framework import FilterSet, DjangoFilterBackend
|
||||||
from django_filters import NumberFilter
|
from django_filters import NumberFilter
|
||||||
|
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions, response
|
||||||
|
|
||||||
# from InvenTree.models import FilterChildren
|
# from InvenTree.models import FilterChildren
|
||||||
from .models import StockLocation, StockItem, StockTracking
|
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):
|
class StockDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
@ -53,6 +54,36 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
filter_class = StockFilter
|
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):
|
class LocationDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user