mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
More work
- Consolidated "in_stock" filter to single code location - Improve 'limit_choices_to' for BuildItem and SalesOrderAllocation - Various template improvements etc
This commit is contained in:
parent
4147163418
commit
e768ada83b
@ -48,7 +48,6 @@ function loadStockTable(table, options) {
|
|||||||
|
|
||||||
options.params['part_detail'] = true;
|
options.params['part_detail'] = true;
|
||||||
options.params['location_detail'] = true;
|
options.params['location_detail'] = true;
|
||||||
options.params['in_stock'] = true;
|
|
||||||
|
|
||||||
var params = options.params || {};
|
var params = options.params || {};
|
||||||
|
|
||||||
|
@ -53,6 +53,8 @@ class BuildList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
def filter_queryset(self, queryset):
|
def filter_queryset(self, queryset):
|
||||||
|
|
||||||
|
queryset = super().filter_queryset(queryset)
|
||||||
|
|
||||||
# Filter by build status?
|
# Filter by build status?
|
||||||
status = self.request.query_params.get('status', None)
|
status = self.request.query_params.get('status', None)
|
||||||
|
|
||||||
|
20
InvenTree/build/migrations/0016_auto_20200426_0551.py
Normal file
20
InvenTree/build/migrations/0016_auto_20200426_0551.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
20
InvenTree/build/migrations/0017_auto_20200426_0612.py
Normal file
20
InvenTree/build/migrations/0017_auto_20200426_0612.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -261,8 +261,6 @@ class Build(MPTTModel):
|
|||||||
- Delete pending BuildItem objects
|
- Delete pending BuildItem objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print("Complete build...")
|
|
||||||
|
|
||||||
# Complete the build allocation for each BuildItem
|
# Complete the build allocation for each BuildItem
|
||||||
for build_item in self.allocated_stock.all().prefetch_related('stock_item'):
|
for build_item in self.allocated_stock.all().prefetch_related('stock_item'):
|
||||||
build_item.complete_allocation(user)
|
build_item.complete_allocation(user)
|
||||||
@ -495,6 +493,11 @@ class BuildItem(models.Model):
|
|||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='allocations',
|
related_name='allocations',
|
||||||
help_text=_('Stock Item to allocate to build'),
|
help_text=_('Stock Item to allocate to build'),
|
||||||
|
limit_choices_to={
|
||||||
|
'build_order': None,
|
||||||
|
'sales_order': None,
|
||||||
|
'belongs_to': None,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
quantity = models.DecimalField(
|
quantity = models.DecimalField(
|
||||||
|
@ -82,7 +82,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
</tr>
|
</tr>
|
||||||
{% if build.parent %}
|
{% if build.parent %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-tools'></span></td>
|
<td><span class='fas fa-sitemap'></span></td>
|
||||||
<td>{% trans "Parent Build" %}</td>
|
<td>{% trans "Parent Build" %}</td>
|
||||||
<td><a href="{% url 'build-detail' build.parent.id %}">{{ build.parent }}</a></td>
|
<td><a href="{% url 'build-detail' build.parent.id %}">{{ build.parent }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
20
InvenTree/order/migrations/0030_auto_20200426_0551.py
Normal file
20
InvenTree/order/migrations/0030_auto_20200426_0551.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
20
InvenTree/order/migrations/0031_auto_20200426_0612.py
Normal file
20
InvenTree/order/migrations/0031_auto_20200426_0612.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -19,7 +19,7 @@ import os
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from part.models import Part
|
from part import models as PartModels
|
||||||
from stock import models as stock_models
|
from stock import models as stock_models
|
||||||
from company.models import Company, SupplierPart
|
from company.models import Company, SupplierPart
|
||||||
|
|
||||||
@ -511,7 +511,7 @@ class SalesOrderAllocation(models.Model):
|
|||||||
try:
|
try:
|
||||||
if not self.line.part == self.item.part:
|
if not self.line.part == self.item.part:
|
||||||
errors['item'] = _('Cannot allocate stock item to a line with a different 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')
|
errors['line'] = _('Cannot allocate stock to a line without a part')
|
||||||
|
|
||||||
if self.quantity > self.item.quantity:
|
if self.quantity > self.item.quantity:
|
||||||
@ -535,7 +535,12 @@ class SalesOrderAllocation(models.Model):
|
|||||||
'stock.StockItem',
|
'stock.StockItem',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='sales_order_allocations',
|
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')
|
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)
|
- Mark the StockItem as belonging to the Customer (this will remove it from stock)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
order = self.line.order
|
||||||
|
|
||||||
item = self.item
|
item = self.item
|
||||||
|
|
||||||
# If the allocated quantity is less than the amount available,
|
# If the allocated quantity is less than the amount available,
|
||||||
@ -579,7 +586,7 @@ class SalesOrderAllocation(models.Model):
|
|||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
# Assign the StockItem to the SalesOrder customer
|
# Assign the StockItem to the SalesOrder customer
|
||||||
item.customer = self.line.order.customer
|
item.sales_order = order
|
||||||
|
|
||||||
# Clear the location
|
# Clear the location
|
||||||
item.location = None
|
item.location = None
|
||||||
|
@ -42,6 +42,7 @@ from InvenTree.helpers import decimal2string, normalize
|
|||||||
from InvenTree.status_codes import BuildStatus, StockStatus, PurchaseOrderStatus
|
from InvenTree.status_codes import BuildStatus, StockStatus, PurchaseOrderStatus
|
||||||
|
|
||||||
from company.models import SupplierPart
|
from company.models import SupplierPart
|
||||||
|
from stock import models as StockModels
|
||||||
|
|
||||||
|
|
||||||
class PartCategory(InvenTreeTree):
|
class PartCategory(InvenTreeTree):
|
||||||
@ -639,11 +640,12 @@ class Part(models.Model):
|
|||||||
def stock_entries(self):
|
def stock_entries(self):
|
||||||
""" Return all 'in stock' items. To be in stock:
|
""" Return all 'in stock' items. To be in stock:
|
||||||
|
|
||||||
- customer is None
|
- build_order is None
|
||||||
|
- sales_order is None
|
||||||
- belongs_to 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
|
@property
|
||||||
def total_stock(self):
|
def total_stock(self):
|
||||||
|
@ -6,11 +6,6 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% if part.active == False %}
|
|
||||||
<div class='alert alert-danger alert-block'>
|
|
||||||
{% trans "This part is not active" %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if part.is_template %}
|
{% if part.is_template %}
|
||||||
<div class='alert alert-info alert-block'>
|
<div class='alert alert-info alert-block'>
|
||||||
{% trans "This part is a template part." %}
|
{% trans "This part is a template part." %}
|
||||||
@ -28,9 +23,14 @@
|
|||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
{% include "part/part_thumb.html" %}
|
{% include "part/part_thumb.html" %}
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
<h4>
|
<h3>
|
||||||
{{ part.full_name }}
|
{{ part.full_name }}
|
||||||
</h4>
|
{% if not part.active %}
|
||||||
|
<div class='label label-large label-large-red'>
|
||||||
|
{% trans 'Inactive' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</h3>
|
||||||
<p><i>{{ part.description }}</i></p>
|
<p><i>{{ part.description }}</i></p>
|
||||||
<p>
|
<p>
|
||||||
<div class='btn-row'>
|
<div class='btn-row'>
|
||||||
|
@ -13,7 +13,7 @@ from .models import StockItemTracking
|
|||||||
|
|
||||||
from build.models import Build
|
from build.models import Build
|
||||||
from company.models import Company, SupplierPart
|
from company.models import Company, SupplierPart
|
||||||
from order.models import PurchaseOrder
|
from order.models import PurchaseOrder, SalesOrder
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
|
|
||||||
|
|
||||||
@ -74,10 +74,12 @@ class StockItemResource(ModelResource):
|
|||||||
|
|
||||||
belongs_to = Field(attribute='belongs_to', widget=widgets.ForeignKeyWidget(StockItem))
|
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))
|
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))
|
purchase_order = Field(attribute='purchase_order', widget=widgets.ForeignKeyWidget(PurchaseOrder))
|
||||||
|
|
||||||
# Date management
|
# Date management
|
||||||
|
@ -369,10 +369,10 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
if in_stock:
|
if in_stock:
|
||||||
# Filter out parts which are not actually "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:
|
else:
|
||||||
# Only show parts which are not in stock
|
# 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?
|
# Filter by 'allocated' patrs?
|
||||||
allocated = self.request.query_params.get('allocated', None)
|
allocated = self.request.query_params.get('allocated', None)
|
||||||
@ -511,9 +511,9 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
filter_fields = [
|
filter_fields = [
|
||||||
'supplier_part',
|
'supplier_part',
|
||||||
'customer',
|
|
||||||
'belongs_to',
|
'belongs_to',
|
||||||
'build',
|
'build',
|
||||||
|
'build_order',
|
||||||
'sales_order',
|
'sales_order',
|
||||||
'build_order',
|
'build_order',
|
||||||
]
|
]
|
||||||
|
19
InvenTree/stock/migrations/0033_auto_20200426_0539.py
Normal file
19
InvenTree/stock/migrations/0033_auto_20200426_0539.py
Normal file
@ -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)]),
|
||||||
|
),
|
||||||
|
]
|
96
InvenTree/stock/migrations/0034_auto_20200426_0602.py
Normal file
96
InvenTree/stock/migrations/0034_auto_20200426_0602.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -11,7 +11,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from django.db import models, transaction
|
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.db.models.functions import Coalesce
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
@ -30,7 +30,7 @@ from InvenTree.status_codes import StockStatus
|
|||||||
from InvenTree.models import InvenTreeTree
|
from InvenTree.models import InvenTreeTree
|
||||||
from InvenTree.fields import InvenTreeURLField
|
from InvenTree.fields import InvenTreeURLField
|
||||||
|
|
||||||
from part.models import Part
|
from part import models as PartModels
|
||||||
from order.models import PurchaseOrder, SalesOrder
|
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)
|
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):
|
def save(self, *args, **kwargs):
|
||||||
if not self.pk:
|
if not self.pk:
|
||||||
add_note = True
|
add_note = True
|
||||||
@ -215,7 +218,7 @@ class StockItem(MPTTModel):
|
|||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'serial': _('A stock item with this serial number already exists')
|
'serial': _('A stock item with this serial number already exists')
|
||||||
})
|
})
|
||||||
except Part.DoesNotExist:
|
except PartModels.Part.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
@ -228,6 +231,18 @@ class StockItem(MPTTModel):
|
|||||||
- Quantity must be 1 if the StockItem has a serial number
|
- 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!
|
# The 'supplier_part' field must point to the same part!
|
||||||
try:
|
try:
|
||||||
if self.supplier_part is not None:
|
if self.supplier_part is not None:
|
||||||
@ -261,7 +276,7 @@ class StockItem(MPTTModel):
|
|||||||
if self.part.is_template:
|
if self.part.is_template:
|
||||||
raise ValidationError({'part': _('Stock item cannot be created for a template Part')})
|
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
|
# This gets thrown if self.supplier_part is null
|
||||||
# TODO - Find a test than can be perfomed...
|
# TODO - Find a test than can be perfomed...
|
||||||
pass
|
pass
|
||||||
@ -303,12 +318,17 @@ class StockItem(MPTTModel):
|
|||||||
|
|
||||||
uid = models.CharField(blank=True, max_length=128, help_text=("Unique identifier field"))
|
uid = models.CharField(blank=True, max_length=128, help_text=("Unique identifier field"))
|
||||||
|
|
||||||
parent = TreeForeignKey('self',
|
parent = TreeForeignKey(
|
||||||
|
'self',
|
||||||
|
verbose_name=_('Parent Stock Item'),
|
||||||
on_delete=models.DO_NOTHING,
|
on_delete=models.DO_NOTHING,
|
||||||
blank=True, null=True,
|
blank=True, null=True,
|
||||||
related_name='children')
|
related_name='children'
|
||||||
|
)
|
||||||
|
|
||||||
part = models.ForeignKey('part.Part', on_delete=models.CASCADE,
|
part = models.ForeignKey(
|
||||||
|
'part.Part', on_delete=models.CASCADE,
|
||||||
|
verbose_name=_('Base Part'),
|
||||||
related_name='stock_items', help_text=_('Base part'),
|
related_name='stock_items', help_text=_('Base part'),
|
||||||
limit_choices_to={
|
limit_choices_to={
|
||||||
'is_template': False,
|
'is_template': False,
|
||||||
@ -316,35 +336,57 @@ class StockItem(MPTTModel):
|
|||||||
'virtual': False
|
'virtual': False
|
||||||
})
|
})
|
||||||
|
|
||||||
supplier_part = models.ForeignKey('company.SupplierPart', blank=True, null=True, on_delete=models.SET_NULL,
|
supplier_part = models.ForeignKey(
|
||||||
help_text=_('Select a matching supplier part for this stock item'))
|
'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,
|
location = TreeForeignKey(
|
||||||
related_name='stock_items', blank=True, null=True,
|
StockLocation, on_delete=models.DO_NOTHING,
|
||||||
help_text=_('Where is this stock item located?'))
|
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,
|
belongs_to = models.ForeignKey(
|
||||||
|
'self',
|
||||||
|
verbose_name=_('Installed In'),
|
||||||
|
on_delete=models.DO_NOTHING,
|
||||||
related_name='owned_parts', blank=True, null=True,
|
related_name='owned_parts', blank=True, null=True,
|
||||||
help_text=_('Is this item installed in another item?'))
|
help_text=_('Is this item installed in another item?')
|
||||||
|
)
|
||||||
|
|
||||||
customer = models.ForeignKey('company.Company', on_delete=models.SET_NULL,
|
serial = models.PositiveIntegerField(
|
||||||
related_name='stockitems', blank=True, null=True,
|
verbose_name=_('Serial Number'),
|
||||||
help_text=_('Item assigned to customer?'))
|
blank=True, null=True,
|
||||||
|
help_text=_('Serial number for this item')
|
||||||
|
)
|
||||||
|
|
||||||
serial = models.PositiveIntegerField(blank=True, null=True,
|
link = InvenTreeURLField(
|
||||||
help_text=_('Serial number for this item'))
|
verbose_name=_('External Link'),
|
||||||
|
max_length=125, blank=True,
|
||||||
|
help_text=_("Link to external URL")
|
||||||
|
)
|
||||||
|
|
||||||
link = InvenTreeURLField(max_length=125, blank=True, help_text=_("Link to external URL"))
|
batch = models.CharField(
|
||||||
|
verbose_name=_('Batch Code'),
|
||||||
|
max_length=100, blank=True, null=True,
|
||||||
|
help_text=_('Batch code for this stock item')
|
||||||
|
)
|
||||||
|
|
||||||
batch = models.CharField(max_length=100, blank=True, null=True,
|
quantity = models.DecimalField(
|
||||||
help_text=_('Batch code for this stock item'))
|
verbose_name=_("Stock Quantity"),
|
||||||
|
max_digits=15, decimal_places=5, validators=[MinValueValidator(0)],
|
||||||
quantity = models.DecimalField(max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], default=1)
|
default=1
|
||||||
|
)
|
||||||
|
|
||||||
updated = models.DateField(auto_now=True, null=True)
|
updated = models.DateField(auto_now=True, null=True)
|
||||||
|
|
||||||
build = models.ForeignKey(
|
build = models.ForeignKey(
|
||||||
'build.Build', on_delete=models.SET_NULL,
|
'build.Build', on_delete=models.SET_NULL,
|
||||||
|
verbose_name=_('Source Build'),
|
||||||
blank=True, null=True,
|
blank=True, null=True,
|
||||||
help_text=_('Build for this stock item'),
|
help_text=_('Build for this stock item'),
|
||||||
related_name='build_outputs',
|
related_name='build_outputs',
|
||||||
@ -353,6 +395,7 @@ class StockItem(MPTTModel):
|
|||||||
purchase_order = models.ForeignKey(
|
purchase_order = models.ForeignKey(
|
||||||
PurchaseOrder,
|
PurchaseOrder,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
|
verbose_name=_('Source Purchase Order'),
|
||||||
related_name='stock_items',
|
related_name='stock_items',
|
||||||
blank=True, null=True,
|
blank=True, null=True,
|
||||||
help_text=_('Purchase order for this stock item')
|
help_text=_('Purchase order for this stock item')
|
||||||
@ -361,12 +404,14 @@ class StockItem(MPTTModel):
|
|||||||
sales_order = models.ForeignKey(
|
sales_order = models.ForeignKey(
|
||||||
SalesOrder,
|
SalesOrder,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
|
verbose_name=_("Destination Sales Order"),
|
||||||
related_name='stock_items',
|
related_name='stock_items',
|
||||||
null=True, blank=True)
|
null=True, blank=True)
|
||||||
|
|
||||||
build_order = models.ForeignKey(
|
build_order = models.ForeignKey(
|
||||||
'build.Build',
|
'build.Build',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
|
verbose_name=_("Destination Build Order"),
|
||||||
related_name='stock_items',
|
related_name='stock_items',
|
||||||
null=True, blank=True
|
null=True, blank=True
|
||||||
)
|
)
|
||||||
@ -386,7 +431,11 @@ class StockItem(MPTTModel):
|
|||||||
choices=StockStatus.items(),
|
choices=StockStatus.items(),
|
||||||
validators=[MinValueValidator(0)])
|
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
|
# 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)
|
||||||
@ -447,7 +496,7 @@ class StockItem(MPTTModel):
|
|||||||
- Has child StockItems
|
- Has child StockItems
|
||||||
- Has a serial number and is tracked
|
- Has a serial number and is tracked
|
||||||
- Is installed inside another StockItem
|
- 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
|
- It has been assigned to a BuildOrder
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -457,7 +506,7 @@ class StockItem(MPTTModel):
|
|||||||
if self.part.trackable and self.serial is not None:
|
if self.part.trackable and self.serial is not None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.customer is not None:
|
if self.sales_order is not None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.build_order is not None:
|
if self.build_order is not None:
|
||||||
@ -485,7 +534,7 @@ class StockItem(MPTTModel):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Not 'in stock' if it has been sent to a customer
|
# 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
|
return False
|
||||||
|
|
||||||
# Not 'in stock' if it has been allocated to a BuildOrder
|
# Not 'in stock' if it has been allocated to a BuildOrder
|
||||||
|
@ -118,6 +118,8 @@ class StockItemSerializer(InvenTreeModelSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'allocated',
|
'allocated',
|
||||||
'batch',
|
'batch',
|
||||||
|
'build_order',
|
||||||
|
'belongs_to',
|
||||||
'in_stock',
|
'in_stock',
|
||||||
'link',
|
'link',
|
||||||
'location',
|
'location',
|
||||||
@ -127,6 +129,7 @@ class StockItemSerializer(InvenTreeModelSerializer):
|
|||||||
'part_detail',
|
'part_detail',
|
||||||
'pk',
|
'pk',
|
||||||
'quantity',
|
'quantity',
|
||||||
|
'sales_order',
|
||||||
'serial',
|
'serial',
|
||||||
'supplier_part',
|
'supplier_part',
|
||||||
'supplier_part_detail',
|
'supplier_part_detail',
|
||||||
|
@ -15,11 +15,15 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
|
|||||||
{% block pre_content %}
|
{% block pre_content %}
|
||||||
{% include 'stock/loc_link.html' with location=item.location %}
|
{% include 'stock/loc_link.html' with location=item.location %}
|
||||||
|
|
||||||
{% if item.customer %}
|
{% if item.sales_order %}
|
||||||
<div class='alert alert-block alert-info'>
|
<div class='alert alert-block alert-info'>
|
||||||
{% trans "This stock item has been sent to" %} <b><a href="{% url 'company-detail-sales-orders' item.customer.id %}">{{ item.customer.name }}</a></b>
|
{% trans "This stock item was assigned to" %} <b><a href="{% url 'so-detail' item.sales_order.id %}"> {% trans "Sales Order" %} {{ item.sales_order.id }}</a></b>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% elif item.build_order %}
|
||||||
|
<div class='alert alert-block alert-info'>
|
||||||
|
{% trans "This stock item was assigned to" %}<b><a href="{% url 'build-detail' item.build_order.id %}"> {% trans "Build Order" %} #{{ item.build_order.id }}</a></b>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
{% for allocation in item.sales_order_allocations.all %}
|
{% for allocation in item.sales_order_allocations.all %}
|
||||||
<div class='alert alert-block alert-info'>
|
<div class='alert alert-block alert-info'>
|
||||||
@ -32,6 +36,7 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
|
|||||||
{% trans "This stock item is allocated to Build" %} <a href="{% url 'build-detail' allocation.build.id %}"><b>#{{ allocation.build.id }}</b></a> ({% trans "Quantity" %}: {% decimal allocation.quantity %})
|
{% trans "This stock item is allocated to Build" %} <a href="{% url 'build-detail' allocation.build.id %}"><b>#{{ allocation.build.id }}</b></a> ({% trans "Quantity" %}: {% decimal allocation.quantity %})
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if item.serialized %}
|
{% if item.serialized %}
|
||||||
<div class='alert alert-block alert-warning'>
|
<div class='alert alert-block alert-warning'>
|
||||||
@ -46,11 +51,6 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
|
|||||||
{% trans "This stock item will be automatically deleted when all stock is depleted." %}
|
{% trans "This stock item will be automatically deleted when all stock is depleted." %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if item.parent %}
|
|
||||||
<div class='alert alert-block alert-info'>
|
|
||||||
{% trans "This stock item was split from " %}<a href="{% url 'stock-item-detail' item.parent.id %}">{{ item.parent }}</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -59,7 +59,19 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block page_data %}
|
{% block page_data %}
|
||||||
<h3>{% trans "Stock Item" %}{% if item.status in StockStatus.UNAVAILABLE_CODES %}{% stock_status_label item.status large=True %}{% endif %}</h3>
|
<h3>
|
||||||
|
{% trans "Stock Item" %}
|
||||||
|
{% if item.sales_order %}
|
||||||
|
<div class='label label-large label-large-blue'>
|
||||||
|
{% trans "Sold" $}
|
||||||
|
</div>
|
||||||
|
{% elif item.build_order %}
|
||||||
|
<div class='label label-large label-large-blue'>
|
||||||
|
{% trans "Used in Build" %}
|
||||||
|
</div>
|
||||||
|
{% elif item.status in StockStatus.UNAVAILABLE_CODES %}{% stock_status_label item.status large=True %}
|
||||||
|
{% endif %}
|
||||||
|
</h3>
|
||||||
<hr>
|
<hr>
|
||||||
<h4>
|
<h4>
|
||||||
{% if item.serialized %}
|
{% if item.serialized %}
|
||||||
@ -74,10 +86,10 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
|
|||||||
{% if item.in_stock %}
|
{% if item.in_stock %}
|
||||||
{% if not item.serialized %}
|
{% if not item.serialized %}
|
||||||
<button type='button' class='btn btn-default' id='stock-add' title='Add to stock'>
|
<button type='button' class='btn btn-default' id='stock-add' title='Add to stock'>
|
||||||
<span class='fas fa-plus-circle' style='color: #1a1;'/>
|
<span class='fas fa-plus-circle icon-green'/>
|
||||||
</button>
|
</button>
|
||||||
<button type='button' class='btn btn-default' id='stock-remove' title='Take from stock'>
|
<button type='button' class='btn btn-default' id='stock-remove' title='Take from stock'>
|
||||||
<span class='fas fa-minus-circle' style='color: #a11;'/>
|
<span class='fas fa-minus-circle icon-red''/>
|
||||||
</button>
|
</button>
|
||||||
<button type='button' class='btn btn-default' id='stock-count' title='Count stock'>
|
<button type='button' class='btn btn-default' id='stock-count' title='Count stock'>
|
||||||
<span class='fas fa-clipboard-list'/>
|
<span class='fas fa-clipboard-list'/>
|
||||||
@ -125,11 +137,17 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
|
|||||||
<td>{% trans "Belongs To" %}</td>
|
<td>{% trans "Belongs To" %}</td>
|
||||||
<td><a href="{% url 'stock-item-detail' item.belongs_to.id %}">{{ item.belongs_to }}</a></td>
|
<td><a href="{% url 'stock-item-detail' item.belongs_to.id %}">{{ item.belongs_to }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% elif item.customer %}
|
{% elif item.sales_order %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-user-tie'></span></td>
|
<td><span class='fas fa-user-tie'></span></td>
|
||||||
<td>{% trans "Customer" %}</td>
|
<td>{% trans "Sales Order" %}</td>
|
||||||
<td><a href="{% url 'company-detail' item.customer.id %}">{{ item.customer.name }}</a></td>
|
<td><a href="{% url 'so-detail' item.sales_order.id %}">{{ item.sales_order.reference }}</a> - <a href="{% url 'company-detail' item.sales_order.customer.id %}">{{ item.sales_order.customer.name }}</a></td>
|
||||||
|
</tr>
|
||||||
|
{% elif item.build_order %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-tools'></span></td>
|
||||||
|
<td>{% trans "Build Order" %}</td>
|
||||||
|
<td><a href="{% url 'build-detail' item.build_order.id %}">{{ item.build_order }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% elif item.location %}
|
{% elif item.location %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -183,7 +201,7 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
|
|||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-sitemap'></span></td>
|
<td><span class='fas fa-sitemap'></span></td>
|
||||||
<td>{% trans "Parent Item" %}</td>
|
<td>{% trans "Parent Item" %}</td>
|
||||||
<td><a href="{% url 'stock-item-detail' item.parent.id %}">{{ item.parent }}</a></td>
|
<td><a href="{% url 'stock-item-detail' item.parent.id %}">{% trans "Stock Item" %} #{{ item.parent.id }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if item.link %}
|
{% if item.link %}
|
||||||
|
@ -258,9 +258,7 @@ class StockExport(AjaxView):
|
|||||||
stock_items = stock_items.filter(supplier_part=supplier_part)
|
stock_items = stock_items.filter(supplier_part=supplier_part)
|
||||||
|
|
||||||
# Filter out stock items that are not 'in stock'
|
# Filter out stock items that are not 'in stock'
|
||||||
# TODO - This might need some more thought in the future...
|
stock_items = stock_items.filter(StockItem.IN_STOCK_FILTER)
|
||||||
stock_items = stock_items.filter(customer=None)
|
|
||||||
stock_items = stock_items.filter(belongs_to=None)
|
|
||||||
|
|
||||||
# Pre-fetch related fields to reduce DB queries
|
# Pre-fetch related fields to reduce DB queries
|
||||||
stock_items = stock_items.prefetch_related('part', 'supplier_part__supplier', 'location', 'purchase_order', 'build')
|
stock_items = stock_items.prefetch_related('part', 'supplier_part__supplier', 'location', 'purchase_order', 'build')
|
||||||
@ -314,7 +312,7 @@ class StockAdjust(AjaxView, FormMixin):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Start with all 'in stock' items
|
# Start with all 'in stock' items
|
||||||
items = StockItem.objects.filter(customer=None, belongs_to=None)
|
items = StockItem.objects.filter(StockItem.IN_STOCK_FILTER)
|
||||||
|
|
||||||
# Client provides a list of individual stock items
|
# Client provides a list of individual stock items
|
||||||
if 'stock[]' in self.request.GET:
|
if 'stock[]' in self.request.GET:
|
||||||
|
@ -21,6 +21,11 @@ function getAvailableTableFilters(tableKey) {
|
|||||||
title: '{% trans "Include sublocations" %}',
|
title: '{% trans "Include sublocations" %}',
|
||||||
description: '{% trans "Include stock in sublocations" %}',
|
description: '{% trans "Include stock in sublocations" %}',
|
||||||
},
|
},
|
||||||
|
in_stock: {
|
||||||
|
type: 'bool',
|
||||||
|
title: '{% trans "In stock" %}',
|
||||||
|
description: '{% trans "Item is in stock" %}',
|
||||||
|
},
|
||||||
active: {
|
active: {
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
title: '{% trans "Active parts" %}',
|
title: '{% trans "Active parts" %}',
|
||||||
|
Loading…
Reference in New Issue
Block a user