diff --git a/InvenTree/InvenTree/static/script/inventree/stock.js b/InvenTree/InvenTree/static/script/inventree/stock.js index 3f1331dd2a..e21971bb0f 100644 --- a/InvenTree/InvenTree/static/script/inventree/stock.js +++ b/InvenTree/InvenTree/static/script/inventree/stock.js @@ -48,7 +48,6 @@ function loadStockTable(table, options) { options.params['part_detail'] = true; options.params['location_detail'] = true; - options.params['in_stock'] = true; var params = options.params || {}; diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index 1c0f91570e..22514ee7b7 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -53,6 +53,8 @@ class BuildList(generics.ListCreateAPIView): def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + # Filter by build status? status = self.request.query_params.get('status', None) diff --git a/InvenTree/build/migrations/0016_auto_20200426_0551.py b/InvenTree/build/migrations/0016_auto_20200426_0551.py new file mode 100644 index 0000000000..f44a37712c --- /dev/null +++ b/InvenTree/build/migrations/0016_auto_20200426_0551.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.5 on 2020-04-26 05:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0033_auto_20200426_0539'), + ('build', '0015_auto_20200425_1350'), + ] + + operations = [ + migrations.AlterField( + model_name='builditem', + name='stock_item', + field=models.ForeignKey(help_text='Stock Item to allocate to build', limit_choices_to={'belongs_to': None, 'build_order': None, 'customer': None}, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='stock.StockItem'), + ), + ] diff --git a/InvenTree/build/migrations/0017_auto_20200426_0612.py b/InvenTree/build/migrations/0017_auto_20200426_0612.py new file mode 100644 index 0000000000..83eb02ce35 --- /dev/null +++ b/InvenTree/build/migrations/0017_auto_20200426_0612.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.5 on 2020-04-26 06:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0034_auto_20200426_0602'), + ('build', '0016_auto_20200426_0551'), + ] + + operations = [ + migrations.AlterField( + model_name='builditem', + name='stock_item', + field=models.ForeignKey(help_text='Stock Item to allocate to build', limit_choices_to={'belongs_to': None, 'build_order': None, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='stock.StockItem'), + ), + ] diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 28bcc6e62e..893f9ed439 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -261,8 +261,6 @@ class Build(MPTTModel): - Delete pending BuildItem objects """ - print("Complete build...") - # Complete the build allocation for each BuildItem for build_item in self.allocated_stock.all().prefetch_related('stock_item'): build_item.complete_allocation(user) @@ -495,6 +493,11 @@ class BuildItem(models.Model): on_delete=models.CASCADE, related_name='allocations', help_text=_('Stock Item to allocate to build'), + limit_choices_to={ + 'build_order': None, + 'sales_order': None, + 'belongs_to': None, + } ) quantity = models.DecimalField( diff --git a/InvenTree/build/templates/build/build_base.html b/InvenTree/build/templates/build/build_base.html index 9ea112911f..8f5ec62663 100644 --- a/InvenTree/build/templates/build/build_base.html +++ b/InvenTree/build/templates/build/build_base.html @@ -82,7 +82,7 @@ src="{% static 'img/blank_image.png' %}" {% if build.parent %} - + {% trans "Parent Build" %} {{ build.parent }} diff --git a/InvenTree/order/migrations/0030_auto_20200426_0551.py b/InvenTree/order/migrations/0030_auto_20200426_0551.py new file mode 100644 index 0000000000..7236088be3 --- /dev/null +++ b/InvenTree/order/migrations/0030_auto_20200426_0551.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.5 on 2020-04-26 05:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0033_auto_20200426_0539'), + ('order', '0029_auto_20200423_1042'), + ] + + operations = [ + migrations.AlterField( + model_name='salesorderallocation', + name='item', + field=models.ForeignKey(help_text='Select stock item to allocate', limit_choices_to={'belongs_to': None, 'build_order': None, 'customer': None, 'part__salable': True}, on_delete=django.db.models.deletion.CASCADE, related_name='sales_order_allocations', to='stock.StockItem'), + ), + ] diff --git a/InvenTree/order/migrations/0031_auto_20200426_0612.py b/InvenTree/order/migrations/0031_auto_20200426_0612.py new file mode 100644 index 0000000000..aa1cd055ec --- /dev/null +++ b/InvenTree/order/migrations/0031_auto_20200426_0612.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.5 on 2020-04-26 06:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0034_auto_20200426_0602'), + ('order', '0030_auto_20200426_0551'), + ] + + operations = [ + migrations.AlterField( + model_name='salesorderallocation', + name='item', + field=models.ForeignKey(help_text='Select stock item to allocate', limit_choices_to={'belongs_to': None, 'build_order': None, 'part__salable': True, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='sales_order_allocations', to='stock.StockItem'), + ), + ] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 0ca5b33a4b..2869b227a1 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -19,7 +19,7 @@ import os from datetime import datetime from decimal import Decimal -from part.models import Part +from part import models as PartModels from stock import models as stock_models from company.models import Company, SupplierPart @@ -511,7 +511,7 @@ class SalesOrderAllocation(models.Model): try: if not self.line.part == self.item.part: errors['item'] = _('Cannot allocate stock item to a line with a different part') - except Part.DoesNotExist: + except PartModels.Part.DoesNotExist: errors['line'] = _('Cannot allocate stock to a line without a part') if self.quantity > self.item.quantity: @@ -535,7 +535,12 @@ class SalesOrderAllocation(models.Model): 'stock.StockItem', on_delete=models.CASCADE, related_name='sales_order_allocations', - limit_choices_to={'part__salable': True}, + limit_choices_to={ + 'part__salable': True, + 'belongs_to': None, + 'sales_order': None, + 'build_order': None, + }, help_text=_('Select stock item to allocate') ) @@ -565,6 +570,8 @@ class SalesOrderAllocation(models.Model): - Mark the StockItem as belonging to the Customer (this will remove it from stock) """ + order = self.line.order + item = self.item # If the allocated quantity is less than the amount available, @@ -579,7 +586,7 @@ class SalesOrderAllocation(models.Model): self.save() # Assign the StockItem to the SalesOrder customer - item.customer = self.line.order.customer + item.sales_order = order # Clear the location item.location = None diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 597c9a277c..a03f11cbfa 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -42,6 +42,7 @@ from InvenTree.helpers import decimal2string, normalize from InvenTree.status_codes import BuildStatus, StockStatus, PurchaseOrderStatus from company.models import SupplierPart +from stock import models as StockModels class PartCategory(InvenTreeTree): @@ -639,11 +640,12 @@ class Part(models.Model): def stock_entries(self): """ Return all 'in stock' items. To be in stock: - - customer is None + - build_order is None + - sales_order is None - belongs_to is None """ - return self.stock_items.filter(customer=None, belongs_to=None) + return self.stock_items.filter(StockModels.StockItem.IN_STOCK_FILTER).exclude(status__in=StockStatus.UNAVAILABLE_CODES) @property def total_stock(self): diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index 0c329c72be..4da8ec7ffe 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -6,11 +6,6 @@ {% block content %} -{% if part.active == False %} -
- {% trans "This part is not active" %} -
-{% endif %} {% if part.is_template %}
{% trans "This part is a template part." %} @@ -28,9 +23,14 @@
{% include "part/part_thumb.html" %}
-

+

{{ part.full_name }} -

+ {% if not part.active %} +
+ {% trans 'Inactive' %} +
+ {% endif %} +

{{ part.description }}

diff --git a/InvenTree/stock/admin.py b/InvenTree/stock/admin.py index 8c91518de0..193e807b7f 100644 --- a/InvenTree/stock/admin.py +++ b/InvenTree/stock/admin.py @@ -13,7 +13,7 @@ from .models import StockItemTracking from build.models import Build from company.models import Company, SupplierPart -from order.models import PurchaseOrder +from order.models import PurchaseOrder, SalesOrder from part.models import Part @@ -74,10 +74,12 @@ class StockItemResource(ModelResource): belongs_to = Field(attribute='belongs_to', widget=widgets.ForeignKeyWidget(StockItem)) - customer = Field(attribute='customer', widget=widgets.ForeignKeyWidget(Company)) - build = Field(attribute='build', widget=widgets.ForeignKeyWidget(Build)) + sales_order = Field(attribute='sales_order', widget=widgets.ForeignKeyWidget(SalesOrder)) + + build_order = Field(attribute='build_order', widget=widgets.ForeignKeyWidget(Build)) + purchase_order = Field(attribute='purchase_order', widget=widgets.ForeignKeyWidget(PurchaseOrder)) # Date management diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 75deff6bd3..c31c1b8993 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -369,10 +369,10 @@ class StockList(generics.ListCreateAPIView): if in_stock: # Filter out parts which are not actually "in stock" - stock_list = stock_list.filter(customer=None, belongs_to=None, build_order=None) + stock_list = stock_list.filter(StockItem.IN_STOCK_FILTER) else: # Only show parts which are not in stock - stock_list = stock_list.exclude(customer=None, belongs_to=None, build_order=None) + stock_list = stock_list.exclude(StockItem.IN_STOCK_FILTER) # Filter by 'allocated' patrs? allocated = self.request.query_params.get('allocated', None) @@ -511,9 +511,9 @@ class StockList(generics.ListCreateAPIView): filter_fields = [ 'supplier_part', - 'customer', 'belongs_to', 'build', + 'build_order', 'sales_order', 'build_order', ] diff --git a/InvenTree/stock/migrations/0033_auto_20200426_0539.py b/InvenTree/stock/migrations/0033_auto_20200426_0539.py new file mode 100644 index 0000000000..214a66feeb --- /dev/null +++ b/InvenTree/stock/migrations/0033_auto_20200426_0539.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.5 on 2020-04-26 05:39 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0032_stockitem_build_order'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='status', + field=models.PositiveIntegerField(choices=[(10, 'OK'), (50, 'Attention needed'), (55, 'Damaged'), (60, 'Destroyed'), (70, 'Lost'), (85, 'Returned'), (110, 'Shipped'), (120, 'Used for Build'), (130, 'Installed in Stock Item')], default=10, validators=[django.core.validators.MinValueValidator(0)]), + ), + ] diff --git a/InvenTree/stock/migrations/0034_auto_20200426_0602.py b/InvenTree/stock/migrations/0034_auto_20200426_0602.py new file mode 100644 index 0000000000..4bf3171aa2 --- /dev/null +++ b/InvenTree/stock/migrations/0034_auto_20200426_0602.py @@ -0,0 +1,96 @@ +# Generated by Django 3.0.5 on 2020-04-26 06:02 + +import InvenTree.fields +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import markdownx.models +import mptt.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0030_auto_20200426_0551'), + ('build', '0016_auto_20200426_0551'), + ('part', '0035_auto_20200406_0045'), + ('company', '0021_remove_supplierpart_manufacturer_name'), + ('stock', '0033_auto_20200426_0539'), + ] + + operations = [ + migrations.RemoveField( + model_name='stockitem', + name='customer', + ), + migrations.AlterField( + model_name='stockitem', + name='batch', + field=models.CharField(blank=True, help_text='Batch code for this stock item', max_length=100, null=True, verbose_name='Batch Code'), + ), + migrations.AlterField( + model_name='stockitem', + name='belongs_to', + field=models.ForeignKey(blank=True, help_text='Is this item installed in another item?', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='owned_parts', to='stock.StockItem', verbose_name='Installed In'), + ), + migrations.AlterField( + model_name='stockitem', + name='build', + field=models.ForeignKey(blank=True, help_text='Build for this stock item', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='build_outputs', to='build.Build', verbose_name='Source Build'), + ), + migrations.AlterField( + model_name='stockitem', + name='build_order', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stock_items', to='build.Build', verbose_name='Destination Build Order'), + ), + migrations.AlterField( + model_name='stockitem', + name='link', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', max_length=125, verbose_name='External Link'), + ), + migrations.AlterField( + model_name='stockitem', + name='location', + field=mptt.fields.TreeForeignKey(blank=True, help_text='Where is this stock item located?', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='stock_items', to='stock.StockLocation', verbose_name='Stock Location'), + ), + migrations.AlterField( + model_name='stockitem', + name='notes', + field=markdownx.models.MarkdownxField(blank=True, help_text='Stock Item Notes', null=True, verbose_name='Notes'), + ), + migrations.AlterField( + model_name='stockitem', + name='parent', + field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='stock.StockItem', verbose_name='Parent Stock Item'), + ), + migrations.AlterField( + model_name='stockitem', + name='part', + field=models.ForeignKey(help_text='Base part', limit_choices_to={'active': True, 'is_template': False, 'virtual': False}, on_delete=django.db.models.deletion.CASCADE, related_name='stock_items', to='part.Part', verbose_name='Base Part'), + ), + migrations.AlterField( + model_name='stockitem', + name='purchase_order', + field=models.ForeignKey(blank=True, help_text='Purchase order for this stock item', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stock_items', to='order.PurchaseOrder', verbose_name='Source Purchase Order'), + ), + migrations.AlterField( + model_name='stockitem', + name='quantity', + field=models.DecimalField(decimal_places=5, default=1, max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Stock Quantity'), + ), + migrations.AlterField( + model_name='stockitem', + name='sales_order', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stock_items', to='order.SalesOrder', verbose_name='Destination Sales Order'), + ), + migrations.AlterField( + model_name='stockitem', + name='serial', + field=models.PositiveIntegerField(blank=True, help_text='Serial number for this item', null=True, verbose_name='Serial Number'), + ), + migrations.AlterField( + model_name='stockitem', + name='supplier_part', + field=models.ForeignKey(blank=True, help_text='Select a matching supplier part for this stock item', null=True, on_delete=django.db.models.deletion.SET_NULL, to='company.SupplierPart', verbose_name='Supplier Part'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 89103703c6..25c00ca65a 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -11,7 +11,7 @@ from django.core.exceptions import ValidationError from django.urls import reverse from django.db import models, transaction -from django.db.models import Sum +from django.db.models import Sum, Q from django.db.models.functions import Coalesce from django.core.validators import MinValueValidator from django.contrib.auth.models import User @@ -30,7 +30,7 @@ from InvenTree.status_codes import StockStatus from InvenTree.models import InvenTreeTree from InvenTree.fields import InvenTreeURLField -from part.models import Part +from part import models as PartModels from order.models import PurchaseOrder, SalesOrder @@ -133,6 +133,9 @@ class StockItem(MPTTModel): build_order: Link to a BuildOrder object (if the StockItem has been assigned to a BuildOrder) """ + # A Query filter which will be re-used in multiple places to determine if a StockItem is actually "in stock" + IN_STOCK_FILTER = Q(sales_order=None, build_order=None, belongs_to=None) + def save(self, *args, **kwargs): if not self.pk: add_note = True @@ -215,7 +218,7 @@ class StockItem(MPTTModel): raise ValidationError({ 'serial': _('A stock item with this serial number already exists') }) - except Part.DoesNotExist: + except PartModels.Part.DoesNotExist: pass def clean(self): @@ -228,6 +231,18 @@ class StockItem(MPTTModel): - Quantity must be 1 if the StockItem has a serial number """ + if self.status == StockStatus.SHIPPED and self.sales_order is None: + raise ValidationError({ + 'sales_order': "SalesOrder must be specified as status is marked as SHIPPED", + 'status': "Status cannot be marked as SHIPPED if the Customer is not set", + }) + + if self.status == StockStatus.ASSIGNED_TO_OTHER_ITEM and self.belongs_to is None: + raise ValidationError({ + 'belongs_to': "Belongs_to field must be specified as statis is marked as ASSIGNED_TO_OTHER_ITEM", + 'status': 'Status cannot be marked as ASSIGNED_TO_OTHER_ITEM if the belongs_to field is not set', + }) + # The 'supplier_part' field must point to the same part! try: if self.supplier_part is not None: @@ -261,7 +276,7 @@ class StockItem(MPTTModel): if self.part.is_template: raise ValidationError({'part': _('Stock item cannot be created for a template Part')}) - except Part.DoesNotExist: + except PartModels.Part.DoesNotExist: # This gets thrown if self.supplier_part is null # TODO - Find a test than can be perfomed... pass @@ -303,48 +318,75 @@ class StockItem(MPTTModel): uid = models.CharField(blank=True, max_length=128, help_text=("Unique identifier field")) - parent = TreeForeignKey('self', - on_delete=models.DO_NOTHING, - blank=True, null=True, - related_name='children') + parent = TreeForeignKey( + 'self', + verbose_name=_('Parent Stock Item'), + on_delete=models.DO_NOTHING, + blank=True, null=True, + related_name='children' + ) - part = models.ForeignKey('part.Part', on_delete=models.CASCADE, - related_name='stock_items', help_text=_('Base part'), - limit_choices_to={ - 'is_template': False, - 'active': True, - 'virtual': False - }) + part = models.ForeignKey( + 'part.Part', on_delete=models.CASCADE, + verbose_name=_('Base Part'), + related_name='stock_items', help_text=_('Base part'), + limit_choices_to={ + 'is_template': False, + 'active': True, + 'virtual': False + }) - supplier_part = models.ForeignKey('company.SupplierPart', blank=True, null=True, on_delete=models.SET_NULL, - help_text=_('Select a matching supplier part for this stock item')) + supplier_part = models.ForeignKey( + 'company.SupplierPart', blank=True, null=True, on_delete=models.SET_NULL, + verbose_name=_('Supplier Part'), + help_text=_('Select a matching supplier part for this stock item') + ) - location = TreeForeignKey(StockLocation, on_delete=models.DO_NOTHING, - related_name='stock_items', blank=True, null=True, - help_text=_('Where is this stock item located?')) + location = TreeForeignKey( + StockLocation, on_delete=models.DO_NOTHING, + verbose_name=_('Stock Location'), + related_name='stock_items', + blank=True, null=True, + help_text=_('Where is this stock item located?') + ) - belongs_to = models.ForeignKey('self', on_delete=models.DO_NOTHING, - related_name='owned_parts', blank=True, null=True, - help_text=_('Is this item installed in another item?')) + belongs_to = models.ForeignKey( + 'self', + verbose_name=_('Installed In'), + on_delete=models.DO_NOTHING, + related_name='owned_parts', blank=True, null=True, + help_text=_('Is this item installed in another item?') + ) - customer = models.ForeignKey('company.Company', on_delete=models.SET_NULL, - related_name='stockitems', blank=True, null=True, - help_text=_('Item assigned to customer?')) - - serial = models.PositiveIntegerField(blank=True, null=True, - help_text=_('Serial number for this item')) + serial = models.PositiveIntegerField( + verbose_name=_('Serial Number'), + blank=True, null=True, + help_text=_('Serial number for this item') + ) - link = InvenTreeURLField(max_length=125, blank=True, help_text=_("Link to external URL")) + link = InvenTreeURLField( + verbose_name=_('External Link'), + max_length=125, blank=True, + help_text=_("Link to external URL") + ) - batch = models.CharField(max_length=100, blank=True, null=True, - help_text=_('Batch code for this stock item')) + batch = models.CharField( + verbose_name=_('Batch Code'), + max_length=100, blank=True, null=True, + help_text=_('Batch code for this stock item') + ) - quantity = models.DecimalField(max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], default=1) + quantity = models.DecimalField( + verbose_name=_("Stock Quantity"), + max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], + default=1 + ) updated = models.DateField(auto_now=True, null=True) build = models.ForeignKey( 'build.Build', on_delete=models.SET_NULL, + verbose_name=_('Source Build'), blank=True, null=True, help_text=_('Build for this stock item'), related_name='build_outputs', @@ -353,6 +395,7 @@ class StockItem(MPTTModel): purchase_order = models.ForeignKey( PurchaseOrder, on_delete=models.SET_NULL, + verbose_name=_('Source Purchase Order'), related_name='stock_items', blank=True, null=True, help_text=_('Purchase order for this stock item') @@ -361,12 +404,14 @@ class StockItem(MPTTModel): sales_order = models.ForeignKey( SalesOrder, on_delete=models.SET_NULL, + verbose_name=_("Destination Sales Order"), related_name='stock_items', null=True, blank=True) build_order = models.ForeignKey( 'build.Build', on_delete=models.SET_NULL, + verbose_name=_("Destination Build Order"), related_name='stock_items', null=True, blank=True ) @@ -386,7 +431,11 @@ class StockItem(MPTTModel): choices=StockStatus.items(), validators=[MinValueValidator(0)]) - notes = MarkdownxField(blank=True, null=True, help_text=_('Stock Item Notes')) + notes = MarkdownxField( + blank=True, null=True, + verbose_name=_("Notes"), + help_text=_('Stock Item Notes') + ) # If stock item is incoming, an (optional) ETA field # expected_arrival = models.DateField(null=True, blank=True) @@ -447,7 +496,7 @@ class StockItem(MPTTModel): - Has child StockItems - Has a serial number and is tracked - Is installed inside another StockItem - - It has been delivered to a customer + - It has been assigned to a SalesOrder - It has been assigned to a BuildOrder """ @@ -457,7 +506,7 @@ class StockItem(MPTTModel): if self.part.trackable and self.serial is not None: return False - if self.customer is not None: + if self.sales_order is not None: return False if self.build_order is not None: @@ -485,7 +534,7 @@ class StockItem(MPTTModel): return False # Not 'in stock' if it has been sent to a customer - if self.customer is not None: + if self.sales_order is not None: return False # Not 'in stock' if it has been allocated to a BuildOrder diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 0c7674a088..4e586b789e 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -118,6 +118,8 @@ class StockItemSerializer(InvenTreeModelSerializer): fields = [ 'allocated', 'batch', + 'build_order', + 'belongs_to', 'in_stock', 'link', 'location', @@ -127,6 +129,7 @@ class StockItemSerializer(InvenTreeModelSerializer): 'part_detail', 'pk', 'quantity', + 'sales_order', 'serial', 'supplier_part', 'supplier_part_detail', diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index ae38263001..bb209f309c 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -15,11 +15,15 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% block pre_content %} {% include 'stock/loc_link.html' with location=item.location %} -{% if item.customer %} +{% if item.sales_order %}
- {% trans "This stock item has been sent to" %} {{ item.customer.name }} + {% trans "This stock item was assigned to" %} {% trans "Sales Order" %} {{ item.sales_order.id }}
-{% endif %} +{% elif item.build_order %} +
+ {% trans "This stock item was assigned to" %} {% trans "Build Order" %} #{{ item.build_order.id }} +
+{% else %} {% for allocation in item.sales_order_allocations.all %}
@@ -32,6 +36,7 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% trans "This stock item is allocated to Build" %} #{{ allocation.build.id }} ({% trans "Quantity" %}: {% decimal allocation.quantity %})
{% endfor %} +{% endif %} {% if item.serialized %}
@@ -46,11 +51,6 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% trans "This stock item will be automatically deleted when all stock is depleted." %}
{% endif %} -{% if item.parent %} -
- {% trans "This stock item was split from " %}{{ item.parent }} -
-{% endif %} {% endblock %} @@ -59,7 +59,19 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% endblock %} {% block page_data %} -

{% trans "Stock Item" %}{% if item.status in StockStatus.UNAVAILABLE_CODES %}{% stock_status_label item.status large=True %}{% endif %}

+

+ {% trans "Stock Item" %} + {% if item.sales_order %} +
+ {% trans "Sold" $} +
+ {% elif item.build_order %} +
+ {% trans "Used in Build" %} +
+ {% elif item.status in StockStatus.UNAVAILABLE_CODES %}{% stock_status_label item.status large=True %} + {% endif %} +


{% if item.serialized %} @@ -74,10 +86,10 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% if item.in_stock %} {% if not item.serialized %}