mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-17 04:25:42 +00:00
Build consume stock (#4817)
* Adds "consumed_by" field to the StockItem model. - Points to a BuildOrder instance which "consumed" this stock - Marks item as unavailable - Allows filtering against build order * Allow API filtering * Adds table of "consumed stock items" to build order page * Update stock table to show "consumed by" stock status * Add "consumed_by" link to stock item detail * Optionally add 'buildorder' details to installStockItem method * Update methodology for completing a build item - Instead of deleting stock, mark as "consumed by" * Fix history entry for splitting stock * Bug fix * track "consumed_by" field for tracked items also * Update build docs * Update allocation documentation * Update terminology.md * Unit test updates * Fix conflicting migrations * revert change
This commit is contained in:
@ -1349,39 +1349,48 @@ class BuildItem(InvenTree.models.MetadataMixin, models.Model):
|
||||
"""Complete the allocation of this BuildItem into the output stock item.
|
||||
|
||||
- If the referenced part is trackable, the stock item will be *installed* into the build output
|
||||
- If the referenced part is *not* trackable, the stock item will be removed from stock
|
||||
- If the referenced part is *not* trackable, the stock item will be *consumed* by the build order
|
||||
"""
|
||||
item = self.stock_item
|
||||
|
||||
# Split the allocated stock if there are more available than allocated
|
||||
if item.quantity > self.quantity:
|
||||
item = item.splitStock(
|
||||
self.quantity,
|
||||
None,
|
||||
user,
|
||||
notes=notes,
|
||||
)
|
||||
|
||||
# For a trackable part, special consideration needed!
|
||||
if item.part.trackable:
|
||||
# Split the allocated stock if there are more available than allocated
|
||||
if item.quantity > self.quantity:
|
||||
item = item.splitStock(
|
||||
self.quantity,
|
||||
None,
|
||||
user,
|
||||
code=StockHistoryCode.BUILD_CONSUMED,
|
||||
)
|
||||
|
||||
# Make sure we are pointing to the new item
|
||||
self.stock_item = item
|
||||
self.save()
|
||||
# Make sure we are pointing to the new item
|
||||
self.stock_item = item
|
||||
self.save()
|
||||
|
||||
# Install the stock item into the output
|
||||
self.install_into.installStockItem(
|
||||
item,
|
||||
self.quantity,
|
||||
user,
|
||||
notes
|
||||
notes,
|
||||
build=self.build,
|
||||
)
|
||||
|
||||
else:
|
||||
# Simply remove the items from stock
|
||||
item.take_stock(
|
||||
self.quantity,
|
||||
# Mark the item as "consumed" by the build order
|
||||
item.consumed_by = self.build
|
||||
item.save(add_note=False)
|
||||
|
||||
item.add_tracking_entry(
|
||||
StockHistoryCode.BUILD_CONSUMED,
|
||||
user,
|
||||
code=StockHistoryCode.BUILD_CONSUMED
|
||||
notes=notes,
|
||||
deltas={
|
||||
'buildorder': self.build.pk,
|
||||
'quantity': float(item.quantity),
|
||||
}
|
||||
)
|
||||
|
||||
def getStockItemThumbnail(self):
|
||||
|
@ -282,6 +282,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='panel panel-hidden' id='panel-consumed'>
|
||||
<div class='panel-heading'>
|
||||
<h4>
|
||||
{% trans "Consumed Stock" %}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div class='panel-content'>
|
||||
{% include "stock_table.html" with read_only=True prefix="consumed-" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='panel panel-hidden' id='panel-completed'>
|
||||
<div class='panel-heading'>
|
||||
<h4>
|
||||
@ -329,6 +341,17 @@
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
onPanelLoad('consumed', function() {
|
||||
loadStockTable($('#consumed-stock-table'), {
|
||||
params: {
|
||||
location_detail: true,
|
||||
part_detail: true,
|
||||
consumed_by: {{ build.pk }},
|
||||
in_stock: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
onPanelLoad('completed', function() {
|
||||
loadStockTable($("#build-stock-table"), {
|
||||
params: {
|
||||
@ -337,11 +360,9 @@ onPanelLoad('completed', function() {
|
||||
build: {{ build.id }},
|
||||
is_building: false,
|
||||
},
|
||||
groupByField: 'location',
|
||||
buttons: [
|
||||
'#stock-options',
|
||||
],
|
||||
url: "{% url 'api-stock-list' %}",
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -8,7 +8,9 @@
|
||||
{% trans "Allocate Stock" as text %}
|
||||
{% include "sidebar_item.html" with label='allocate' text=text icon="fa-tasks" %}
|
||||
{% endif %}
|
||||
{% if not build.is_complete %}
|
||||
{% trans "Consumed Stock" as text %}
|
||||
{% include "sidebar_item.html" with label='consumed' text=text icon="fa-list" %}
|
||||
{% if build.is_active %}
|
||||
{% trans "Incomplete Outputs" as text %}
|
||||
{% include "sidebar_item.html" with label='outputs' text=text icon="fa-tools" %}
|
||||
{% endif %}
|
||||
|
@ -424,6 +424,7 @@ class BuildTest(BuildTestBase):
|
||||
extra_2_2: 4, # 35
|
||||
}
|
||||
)
|
||||
|
||||
self.assertTrue(self.build.has_overallocated_parts(None))
|
||||
|
||||
self.build.trim_allocated_stock()
|
||||
@ -433,15 +434,30 @@ class BuildTest(BuildTestBase):
|
||||
self.build.complete_build_output(self.output_2, None)
|
||||
self.assertTrue(self.build.can_complete)
|
||||
|
||||
n = StockItem.objects.filter(consumed_by=self.build).count()
|
||||
|
||||
self.build.complete_build(None)
|
||||
|
||||
self.assertEqual(self.build.status, status.BuildStatus.COMPLETE)
|
||||
|
||||
# Check stock items are in expected state.
|
||||
self.assertEqual(StockItem.objects.get(pk=self.stock_1_2.pk).quantity, 53)
|
||||
self.assertEqual(StockItem.objects.filter(part=self.sub_part_2).aggregate(Sum('quantity'))['quantity__sum'], 5)
|
||||
|
||||
# Total stock quantity has not been decreased
|
||||
items = StockItem.objects.filter(part=self.sub_part_2)
|
||||
self.assertEqual(items.aggregate(Sum('quantity'))['quantity__sum'], 35)
|
||||
|
||||
# However, the "available" stock quantity has been decreased
|
||||
self.assertEqual(items.filter(consumed_by=None).aggregate(Sum('quantity'))['quantity__sum'], 5)
|
||||
|
||||
# And the "consumed_by" quantity has been increased
|
||||
self.assertEqual(items.filter(consumed_by=self.build).aggregate(Sum('quantity'))['quantity__sum'], 30)
|
||||
|
||||
self.assertEqual(StockItem.objects.get(pk=self.stock_3_1.pk).quantity, 980)
|
||||
|
||||
# Check that the "consumed_by" item count has increased
|
||||
self.assertEqual(StockItem.objects.filter(consumed_by=self.build).count(), n + 8)
|
||||
|
||||
def test_cancel(self):
|
||||
"""Test cancellation of the build"""
|
||||
|
||||
@ -510,15 +526,12 @@ class BuildTest(BuildTestBase):
|
||||
self.assertEqual(BuildItem.objects.count(), 0)
|
||||
|
||||
# New stock items should have been created!
|
||||
self.assertEqual(StockItem.objects.count(), 10)
|
||||
self.assertEqual(StockItem.objects.count(), 13)
|
||||
|
||||
# This stock item has been depleted!
|
||||
with self.assertRaises(StockItem.DoesNotExist):
|
||||
StockItem.objects.get(pk=self.stock_1_1.pk)
|
||||
|
||||
# This stock item has also been depleted
|
||||
with self.assertRaises(StockItem.DoesNotExist):
|
||||
StockItem.objects.get(pk=self.stock_2_1.pk)
|
||||
# This stock item has been marked as "consumed"
|
||||
item = StockItem.objects.get(pk=self.stock_1_1.pk)
|
||||
self.assertIsNotNone(item.consumed_by)
|
||||
self.assertFalse(item.in_stock)
|
||||
|
||||
# And 10 new stock items created for the build output
|
||||
outputs = StockItem.objects.filter(build=self.build)
|
||||
|
Reference in New Issue
Block a user