mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
ef83480f65
@ -97,6 +97,10 @@ class Build(models.Model):
|
||||
|
||||
notes = MarkdownxField(blank=True, help_text=_('Extra build notes'))
|
||||
|
||||
@property
|
||||
def output_count(self):
|
||||
return self.build_outputs.count()
|
||||
|
||||
@transaction.atomic
|
||||
def cancelBuild(self, user):
|
||||
""" Mark the Build as CANCELLED
|
||||
@ -235,7 +239,7 @@ class Build(models.Model):
|
||||
now=str(datetime.now().date())
|
||||
)
|
||||
|
||||
if self.part.trackable:
|
||||
if self.part.trackable and serial_numbers:
|
||||
# Add new serial numbers
|
||||
for serial in serial_numbers:
|
||||
item = StockItem.objects.create(
|
||||
|
@ -90,6 +90,10 @@ InvenTree | Build - {{ build }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_load %}
|
||||
<script type='text/javascript' src="{% static 'script/inventree/stock.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
|
||||
$("#build-edit").click(function () {
|
||||
|
32
InvenTree/build/templates/build/build_output.html
Normal file
32
InvenTree/build/templates/build/build_output.html
Normal file
@ -0,0 +1,32 @@
|
||||
{% extends "build/build_base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% include "build/tabs.html" with tab='output' %}
|
||||
|
||||
<h4>{% trans "Build Outputs" %}</h4>
|
||||
<hr>
|
||||
|
||||
{% include "stock_table.html" %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
loadStockTable($("#stock-table"), {
|
||||
params: {
|
||||
location_detail: true,
|
||||
part_details: true,
|
||||
build: {{ build.id }},
|
||||
},
|
||||
groupByField: 'location',
|
||||
buttons: [
|
||||
'#stock-options',
|
||||
],
|
||||
url: "{% url 'api-stock-list' %}",
|
||||
});
|
||||
|
||||
{% endblock %}
|
@ -4,6 +4,9 @@
|
||||
<li{% if tab == 'details' %} class='active'{% endif %}>
|
||||
<a href="{% url 'build-detail' build.id %}">{% trans "Details" %}</a>
|
||||
</li>
|
||||
<li{% if tab == 'output' %} class='active'{% endif %}>
|
||||
<a href="{% url 'build-output' build.id %}">{% trans "Outputs" %}{% if build.output_count > 0%}<span class='badge'>{{ build.output_count }}</span>{% endif %}</a>
|
||||
</li>
|
||||
<li{% if tab == 'notes' %} class='active'{% endif %}>
|
||||
<a href="{% url 'build-notes' build.id %}">{% trans "Notes" %}{% if build.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
||||
</li>
|
||||
|
@ -26,6 +26,9 @@ build_detail_urls = [
|
||||
url(r'^unallocate/', views.BuildUnallocate.as_view(), name='build-unallocate'),
|
||||
|
||||
url(r'^notes/', views.BuildNotes.as_view(), name='build-notes'),
|
||||
|
||||
url(r'^output/', views.BuildDetail.as_view(template_name='build/build_output.html'), name='build-output'),
|
||||
|
||||
url(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),
|
||||
]
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
from django.db.utils import OperationalError
|
||||
from django.db.utils import OperationalError, ProgrammingError
|
||||
|
||||
import os
|
||||
|
||||
@ -43,6 +43,6 @@ class CommonConfig(AppConfig):
|
||||
setting.save()
|
||||
|
||||
print("Creating new key: '{k}' = '{v}'".format(k=key, v=default))
|
||||
except OperationalError:
|
||||
except (OperationalError, ProgrammingError):
|
||||
# Migrations have not yet been applied - table does not exist
|
||||
break
|
||||
|
@ -85,6 +85,13 @@ class StockItemResource(ModelResource):
|
||||
|
||||
stocktake_date = Field(attribute='stocktake_date', widget=widgets.DateWidget())
|
||||
|
||||
def after_import(self, dataset, result, using_transactions, dry_run, **kwargs):
|
||||
|
||||
super().after_import(dataset, result, using_transactions, dry_run, **kwargs)
|
||||
|
||||
# Rebuild the StockItem tree(s)
|
||||
StockItem.objects.rebuild()
|
||||
|
||||
class Meta:
|
||||
model = StockItem
|
||||
skip_unchanged = True
|
||||
|
@ -257,8 +257,12 @@ class StockList(generics.ListCreateAPIView):
|
||||
- location: Filter stock by location
|
||||
- category: Filter by parts belonging to a certain category
|
||||
- supplier: Filter by supplier
|
||||
- ancestor: Filter by an 'ancestor' StockItem
|
||||
- status: Filter by the StockItem status
|
||||
"""
|
||||
|
||||
queryset = StockItem.objects.all()
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
|
||||
try:
|
||||
@ -284,6 +288,7 @@ class StockList(generics.ListCreateAPIView):
|
||||
|
||||
data = queryset.values(
|
||||
'pk',
|
||||
'parent',
|
||||
'quantity',
|
||||
'serial',
|
||||
'batch',
|
||||
@ -332,7 +337,9 @@ class StockList(generics.ListCreateAPIView):
|
||||
"""
|
||||
|
||||
# Start with all objects
|
||||
stock_list = StockItem.objects.filter(customer=None, belongs_to=None)
|
||||
stock_list = super(StockList, self).get_queryset()
|
||||
|
||||
stock_list = stock_list.filter(customer=None, belongs_to=None)
|
||||
|
||||
# Does the client wish to filter by the Part ID?
|
||||
part_id = self.request.query_params.get('part', None)
|
||||
@ -347,7 +354,20 @@ class StockList(generics.ListCreateAPIView):
|
||||
else:
|
||||
stock_list = stock_list.filter(part=part_id)
|
||||
|
||||
except Part.DoesNotExist:
|
||||
except (ValueError, Part.DoesNotExist):
|
||||
pass
|
||||
|
||||
# Does the client wish to filter by the 'ancestor'?
|
||||
anc_id = self.request.query_params.get('ancestor', None)
|
||||
|
||||
if anc_id:
|
||||
try:
|
||||
ancestor = StockItem.objects.get(pk=anc_id)
|
||||
|
||||
# Only allow items which are descendants of the specified StockItem
|
||||
stock_list = stock_list.filter(id__in=[item.pk for item in ancestor.children.all()])
|
||||
|
||||
except (ValueError, Part.DoesNotExist):
|
||||
pass
|
||||
|
||||
# Does the client wish to filter by stock location?
|
||||
@ -358,7 +378,7 @@ class StockList(generics.ListCreateAPIView):
|
||||
location = StockLocation.objects.get(pk=loc_id)
|
||||
stock_list = stock_list.filter(location__in=location.getUniqueChildren())
|
||||
|
||||
except StockLocation.DoesNotExist:
|
||||
except (ValueError, StockLocation.DoesNotExist):
|
||||
pass
|
||||
|
||||
# Does the client wish to filter by part category?
|
||||
@ -369,9 +389,15 @@ class StockList(generics.ListCreateAPIView):
|
||||
category = PartCategory.objects.get(pk=cat_id)
|
||||
stock_list = stock_list.filter(part__category__in=category.getUniqueChildren())
|
||||
|
||||
except PartCategory.DoesNotExist:
|
||||
except (ValueError, PartCategory.DoesNotExist):
|
||||
pass
|
||||
|
||||
# Filter by StockItem status
|
||||
status = self.request.query_params.get('status', None)
|
||||
|
||||
if status:
|
||||
stock_list = stock_list.filter(status=status)
|
||||
|
||||
# Filter by supplier_part ID
|
||||
supplier_part_id = self.request.query_params.get('supplier_part', None)
|
||||
|
||||
@ -411,7 +437,7 @@ class StockList(generics.ListCreateAPIView):
|
||||
'supplier_part',
|
||||
'customer',
|
||||
'belongs_to',
|
||||
# 'status' TODO - There are some issues filtering based on an enumeration field
|
||||
'build'
|
||||
]
|
||||
|
||||
|
||||
|
@ -7,6 +7,10 @@
|
||||
location: 3
|
||||
batch: 'B123'
|
||||
quantity: 4000
|
||||
level: 0
|
||||
tree_id: 0
|
||||
lft: 0
|
||||
rght: 0
|
||||
|
||||
# 5,000 screws in the bathroom
|
||||
- model: stock.stockitem
|
||||
@ -14,6 +18,10 @@
|
||||
part: 1
|
||||
location: 2
|
||||
quantity: 5000
|
||||
level: 0
|
||||
tree_id: 0
|
||||
lft: 0
|
||||
rght: 0
|
||||
|
||||
# 1234 2K2 resistors in 'Drawer_1'
|
||||
- model: stock.stockitem
|
||||
@ -22,6 +30,10 @@
|
||||
part: 3
|
||||
location: 5
|
||||
quantity: 1234
|
||||
level: 0
|
||||
tree_id: 0
|
||||
lft: 0
|
||||
rght: 0
|
||||
|
||||
# Some widgets in drawer 3
|
||||
- model: stock.stockitem
|
||||
@ -31,6 +43,10 @@
|
||||
location: 7
|
||||
quantity: 10
|
||||
delete_on_deplete: False
|
||||
level: 0
|
||||
tree_id: 0
|
||||
lft: 0
|
||||
rght: 0
|
||||
|
||||
- model: stock.stockitem
|
||||
pk: 101
|
||||
@ -38,6 +54,10 @@
|
||||
part: 25
|
||||
location: 7
|
||||
quantity: 5
|
||||
level: 0
|
||||
tree_id: 0
|
||||
lft: 0
|
||||
rght: 0
|
||||
|
||||
- model: stock.stockitem
|
||||
pk: 102
|
||||
@ -45,3 +65,7 @@
|
||||
part: 25
|
||||
location: 7
|
||||
quantity: 3
|
||||
level: 0
|
||||
tree_id: 0
|
||||
lft: 0
|
||||
rght: 0
|
44
InvenTree/stock/migrations/0021_auto_20200215_2232.py
Normal file
44
InvenTree/stock/migrations/0021_auto_20200215_2232.py
Normal file
@ -0,0 +1,44 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-15 22:32
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0020_auto_20200206_1213'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='stockitem',
|
||||
name='level',
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='stockitem',
|
||||
name='lft',
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
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'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='stockitem',
|
||||
name='rght',
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='stockitem',
|
||||
name='tree_id',
|
||||
field=models.PositiveIntegerField(db_index=True, default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
21
InvenTree/stock/migrations/0022_auto_20200217_1109.py
Normal file
21
InvenTree/stock/migrations/0022_auto_20200217_1109.py
Normal file
@ -0,0 +1,21 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-17 11:09
|
||||
|
||||
from django.db import migrations
|
||||
from stock import models
|
||||
|
||||
|
||||
def update_stock_item_tree(apps, schema_editor):
|
||||
# Update the StockItem MPTT model
|
||||
|
||||
models.StockItem.objects.rebuild()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0021_auto_20200215_2232'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(update_stock_item_tree)
|
||||
]
|
@ -18,7 +18,7 @@ from django.dispatch import receiver
|
||||
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
from mptt.models import TreeForeignKey
|
||||
from mptt.models import MPTTModel, TreeForeignKey
|
||||
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from datetime import datetime
|
||||
@ -102,11 +102,12 @@ def before_delete_stock_location(sender, instance, using, **kwargs):
|
||||
child.save()
|
||||
|
||||
|
||||
class StockItem(models.Model):
|
||||
class StockItem(MPTTModel):
|
||||
"""
|
||||
A StockItem object represents a quantity of physical instances of a part.
|
||||
|
||||
Attributes:
|
||||
parent: Link to another StockItem from which this StockItem was created
|
||||
part: Link to the master abstract part that this StockItem is an instance of
|
||||
supplier_part: Link to a specific SupplierPart (optional)
|
||||
location: Where this StockItem is located
|
||||
@ -296,6 +297,11 @@ class StockItem(models.Model):
|
||||
}
|
||||
)
|
||||
|
||||
parent = TreeForeignKey('self',
|
||||
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={
|
||||
@ -370,15 +376,31 @@ class StockItem(models.Model):
|
||||
def can_delete(self):
|
||||
""" Can this stock item be deleted? It can NOT be deleted under the following circumstances:
|
||||
|
||||
- Has child StockItems
|
||||
- Has a serial number and is tracked
|
||||
- Is installed inside another StockItem
|
||||
"""
|
||||
|
||||
if self.child_count > 0:
|
||||
return False
|
||||
|
||||
if self.part.trackable and self.serial is not None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
""" Return a list of the child items which have been split from this stock item """
|
||||
return self.get_descendants(include_self=False)
|
||||
|
||||
@property
|
||||
def child_count(self):
|
||||
""" Return the number of 'child' items associated with this StockItem.
|
||||
A child item is one which has been split from this one.
|
||||
"""
|
||||
return self.children.count()
|
||||
|
||||
@property
|
||||
def in_stock(self):
|
||||
|
||||
@ -469,6 +491,7 @@ class StockItem(models.Model):
|
||||
new_item.quantity = 1
|
||||
new_item.serial = serial
|
||||
new_item.pk = None
|
||||
new_item.parent = self
|
||||
|
||||
if location:
|
||||
new_item.location = location
|
||||
@ -496,13 +519,14 @@ class StockItem(models.Model):
|
||||
item.save()
|
||||
|
||||
@transaction.atomic
|
||||
def splitStock(self, quantity, user):
|
||||
def splitStock(self, quantity, location, user):
|
||||
""" Split this stock item into two items, in the same location.
|
||||
Stock tracking notes for this StockItem will be duplicated,
|
||||
and added to the new StockItem.
|
||||
|
||||
Args:
|
||||
quantity: Number of stock items to remove from this entity, and pass to the next
|
||||
location: Where to move the new StockItem to
|
||||
|
||||
Notes:
|
||||
The provided quantity will be subtracted from this item and given to the new one.
|
||||
@ -530,7 +554,15 @@ class StockItem(models.Model):
|
||||
# Nullify the PK so a new record is created
|
||||
new_stock = StockItem.objects.get(pk=self.pk)
|
||||
new_stock.pk = None
|
||||
new_stock.parent = self
|
||||
new_stock.quantity = quantity
|
||||
|
||||
# Move to the new location if specified, otherwise use current location
|
||||
if location:
|
||||
new_stock.location = location
|
||||
else:
|
||||
new_stock.location = self.location
|
||||
|
||||
new_stock.save()
|
||||
|
||||
# Copy the transaction history of this part into the new one
|
||||
@ -549,6 +581,11 @@ class StockItem(models.Model):
|
||||
def move(self, location, notes, user, **kwargs):
|
||||
""" Move part to a new location.
|
||||
|
||||
If less than the available quantity is to be moved,
|
||||
a new StockItem is created, with the defined quantity,
|
||||
and that new StockItem is moved.
|
||||
The quantity is also subtracted from the existing StockItem.
|
||||
|
||||
Args:
|
||||
location: Destination location (cannot be null)
|
||||
notes: User notes
|
||||
@ -576,8 +613,10 @@ class StockItem(models.Model):
|
||||
if quantity < self.quantity:
|
||||
# We need to split the stock!
|
||||
|
||||
# Leave behind certain quantity
|
||||
self.splitStock(self.quantity - quantity, user)
|
||||
# Split the existing StockItem in two
|
||||
self.splitStock(quantity, location, user)
|
||||
|
||||
return True
|
||||
|
||||
msg = "Moved to {loc}".format(loc=str(location))
|
||||
|
||||
@ -586,10 +625,11 @@ class StockItem(models.Model):
|
||||
|
||||
self.location = location
|
||||
|
||||
self.addTransactionNote(msg,
|
||||
user,
|
||||
notes=notes,
|
||||
system=True)
|
||||
self.addTransactionNote(
|
||||
msg,
|
||||
user,
|
||||
notes=notes,
|
||||
system=True)
|
||||
|
||||
self.save()
|
||||
|
||||
@ -727,6 +767,23 @@ class StockItem(models.Model):
|
||||
return s
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=StockItem, dispatch_uid='stock_item_pre_delete_log')
|
||||
def before_delete_stock_item(sender, instance, using, **kwargs):
|
||||
""" Receives pre_delete signal from StockItem object.
|
||||
|
||||
Before a StockItem is deleted, ensure that each child object is updated,
|
||||
to point to the new parent item.
|
||||
"""
|
||||
|
||||
# Update each StockItem parent field
|
||||
for child in instance.children.all():
|
||||
child.parent = instance.parent
|
||||
child.save()
|
||||
|
||||
# Rebuild the MPTT tree
|
||||
StockItem.objects.rebuild()
|
||||
|
||||
|
||||
class StockItemTracking(models.Model):
|
||||
""" Stock tracking entry - breacrumb for keeping track of automated stock transactions
|
||||
|
||||
|
@ -43,21 +43,32 @@
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-edit' title='Edit stock item'>
|
||||
<span class='glyphicon glyphicon-edit'/>
|
||||
</button>
|
||||
{% if item.can_delete %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-delete' title='Edit stock item'>
|
||||
<span class='glyphicon glyphicon-trash'/>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</p>
|
||||
{% if item.serialized %}
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %}
|
||||
</div>
|
||||
{% elif item.child_count > 0 %}
|
||||
<div class='alert alert-block alert-warning'>
|
||||
{% trans "This stock item cannot be deleted as it has child items" %}
|
||||
</div>
|
||||
{% elif item.delete_on_deplete %}
|
||||
<div class='alert alert-block alert-warning'>
|
||||
{% trans "This stock item will be automatically deleted when all stock is depleted." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% 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 %}
|
||||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
|
42
InvenTree/stock/templates/stock/item_childs.html
Normal file
42
InvenTree/stock/templates/stock/item_childs.html
Normal file
@ -0,0 +1,42 @@
|
||||
{% extends "stock/item_base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% include "stock/tabs.html" with tab='children' %}
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>{% trans "Child Stock Items" %}</h4>
|
||||
|
||||
{% if item.child_count > 0 %}
|
||||
{% include "stock_table.html" %}
|
||||
{% else %}
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% trans "This stock item does not have any child items" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
{% if item.child_count > 0 %}
|
||||
loadStockTable($("#stock-table"), {
|
||||
params: {
|
||||
location_detail: true,
|
||||
part_details: true,
|
||||
ancestor: {{ item.id }},
|
||||
},
|
||||
groupByField: 'location',
|
||||
buttons: [
|
||||
'#stock-options',
|
||||
],
|
||||
url: "{% url 'api-stock-list' %}",
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -4,6 +4,9 @@
|
||||
<li{% ifequal tab 'tracking' %} class='active'{% endifequal %}>
|
||||
<a href="{% url 'stock-item-detail' item.id %}">{% trans "Tracking" %}</a>
|
||||
</li>
|
||||
<li{% ifequal tab 'children' %} class='active'{% endifequal %}>
|
||||
<a href="{% url 'stock-item-children' item.id %}">{% trans "Children" %}{% if item.child_count > 0 %}<span class='badge'>{{ item.child_count }}</span>{% endif %}</a>
|
||||
</li>
|
||||
{% if 0 %}
|
||||
<!-- These tabs are to be implemented in the future -->
|
||||
<li{% ifequal tab 'builds' %} class='active'{% endifequal %}>
|
||||
|
@ -156,7 +156,9 @@ class StockTest(TestCase):
|
||||
|
||||
# Move 6 of the units
|
||||
self.assertTrue(w1.move(self.diningroom, 'Moved', None, quantity=6))
|
||||
self.assertEqual(w1.quantity, 6)
|
||||
|
||||
# There should be 4 remaining
|
||||
self.assertEqual(w1.quantity, 4)
|
||||
|
||||
# There should also be a new object still in drawer3
|
||||
self.assertEqual(StockItem.objects.filter(part=25).count(), 4)
|
||||
@ -175,17 +177,17 @@ class StockTest(TestCase):
|
||||
N = StockItem.objects.filter(part=3).count()
|
||||
|
||||
stock = StockItem.objects.get(id=1234)
|
||||
stock.splitStock(1000, None)
|
||||
stock.splitStock(1000, None, self.user)
|
||||
self.assertEqual(stock.quantity, 234)
|
||||
|
||||
# There should be a new stock item too!
|
||||
self.assertEqual(StockItem.objects.filter(part=3).count(), N + 1)
|
||||
|
||||
# Try to split a negative quantity
|
||||
stock.splitStock(-10, None)
|
||||
stock.splitStock(-10, None, self.user)
|
||||
self.assertEqual(StockItem.objects.filter(part=3).count(), N + 1)
|
||||
|
||||
stock.splitStock(stock.quantity, None)
|
||||
stock.splitStock(stock.quantity, None, self.user)
|
||||
self.assertEqual(StockItem.objects.filter(part=3).count(), N + 1)
|
||||
|
||||
def test_stocktake(self):
|
||||
@ -325,6 +327,3 @@ class StockTest(TestCase):
|
||||
|
||||
# Serialize the remainder of the stock
|
||||
item.serializeStock(2, [99, 100], self.user)
|
||||
|
||||
# Two more items but the original has been deleted
|
||||
self.assertEqual(StockItem.objects.filter(part=25).count(), n + 9)
|
||||
|
@ -24,6 +24,7 @@ stock_item_detail_urls = [
|
||||
|
||||
url(r'^add_tracking/', views.StockItemTrackingCreate.as_view(), name='stock-tracking-create'),
|
||||
|
||||
url(r'^children/', views.StockItemDetail.as_view(template_name='stock/item_childs.html'), name='stock-item-children'),
|
||||
url(r'^notes/', views.StockItemNotes.as_view(), name='stock-item-notes'),
|
||||
|
||||
url('^.*$', views.StockItemDetail.as_view(), name='stock-item-detail'),
|
||||
|
@ -35,7 +35,7 @@ To configure Inventree inside a virtual environment, ``cd`` into the inventree b
|
||||
|
||||
``source inventree-env/bin/activate``
|
||||
|
||||
This will place the current shell session inside a virtual environment - the terminal should display the ``(inventree)`` prefix.
|
||||
This will place the current shell session inside a virtual environment - the terminal should display the ``(inventree-env)`` prefix.
|
||||
|
||||
.. note::
|
||||
Remember to run ``source inventree-env/bin/activate`` when starting each shell session, before running Inventree commands. This will ensure that the correct environment is being used.
|
||||
|
@ -1,4 +1,4 @@
|
||||
Django==2.2.10 # Django package
|
||||
Django==2.2.9 # Django package
|
||||
pillow==6.2.0 # Image manipulation
|
||||
djangorestframework==3.10.3 # DRF framework
|
||||
django-cors-headers==3.2.0 # CORS headers extension for DRF
|
||||
|
Loading…
Reference in New Issue
Block a user