mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #290 from SchrodingersGat/auto-allocation
Auto allocation
This commit is contained in:
commit
7ba5f7869a
@ -27,6 +27,18 @@ class EditBuildForm(HelperForm):
|
||||
]
|
||||
|
||||
|
||||
class ConfirmBuildForm(HelperForm):
|
||||
""" Form for auto-allocation of stock to a build """
|
||||
|
||||
confirm = forms.BooleanField(required=False, help_text='Confirm')
|
||||
|
||||
class Meta:
|
||||
model = Build
|
||||
fields = [
|
||||
'confirm'
|
||||
]
|
||||
|
||||
|
||||
class CompleteBuildForm(HelperForm):
|
||||
""" Form for marking a Build as complete """
|
||||
|
||||
|
@ -127,10 +127,10 @@ class Build(models.Model):
|
||||
- If there are multiple StockItems available, ignore (leave up to the user)
|
||||
|
||||
Returns:
|
||||
A dict object containing the StockItem objects to be allocated (and the quantities)
|
||||
A list object containing the StockItem objects to be allocated (and the quantities)
|
||||
"""
|
||||
|
||||
allocations = {}
|
||||
allocations = []
|
||||
|
||||
for item in self.part.bom_items.all():
|
||||
|
||||
@ -151,19 +151,37 @@ class Build(models.Model):
|
||||
|
||||
# Are there any parts available?
|
||||
if stock_item.quantity > 0:
|
||||
|
||||
# Only take as many as are available
|
||||
if stock_item.quantity < q_required:
|
||||
q_required = stock_item.quantity
|
||||
|
||||
# Add the item to the allocations list
|
||||
allocations[stock_item] = q_required
|
||||
allocation = {
|
||||
'stock_item': stock_item,
|
||||
'quantity': q_required,
|
||||
}
|
||||
|
||||
allocations.append(allocation)
|
||||
|
||||
return allocations
|
||||
|
||||
@transaction.atomic
|
||||
def unallocateStock(self):
|
||||
""" Deletes all stock allocations for this build. """
|
||||
|
||||
BuildItem.objects.filter(build=self.id).delete()
|
||||
|
||||
@transaction.atomic
|
||||
def autoAllocate(self):
|
||||
""" Run auto-allocation routine to allocate StockItems to this Build.
|
||||
|
||||
Returns a list of dict objects with keys like:
|
||||
|
||||
{
|
||||
'stock_item': item,
|
||||
'quantity': quantity,
|
||||
}
|
||||
|
||||
See: getAutoAllocations()
|
||||
"""
|
||||
|
||||
@ -173,8 +191,8 @@ class Build(models.Model):
|
||||
# Create a new allocation
|
||||
build_item = BuildItem(
|
||||
build=self,
|
||||
stock_item=item,
|
||||
quantity=allocations[item])
|
||||
stock_item=item['stock_item'],
|
||||
quantity=item['quantity'])
|
||||
|
||||
build_item.save()
|
||||
|
||||
|
@ -18,7 +18,8 @@ InvenTree | Allocate Parts
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
<div class='btn-group' style='float: right;'>
|
||||
<button class='btn btn-warning' type='button' id='complete-build'>Complete Build</button>
|
||||
<button class='btn btn-primary' type='button' title='Automatic allocation' id='auto-allocate-build'>Auto Allocate</button>
|
||||
<button class='btn btn-warning' type='button' title='Unallocate build stock' id='unallocate-build'>Unallocate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -54,12 +55,20 @@ InvenTree | Allocate Parts
|
||||
|
||||
{% endfor %}
|
||||
|
||||
$("#complete-build").on('click', function() {
|
||||
$("#auto-allocate-build").on('click', function() {
|
||||
launchModalForm(
|
||||
"{% url 'build-complete' build.id %}",
|
||||
"{% url 'build-auto-allocate' build.id %}",
|
||||
{
|
||||
reload: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$('#unallocate-build').on('click', function() {
|
||||
launchModalForm(
|
||||
"{% url 'build-unallocate' build.id %}",
|
||||
{
|
||||
reload: true,
|
||||
submit_text: "Complete Build",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
43
InvenTree/build/templates/build/auto_allocate.html
Normal file
43
InvenTree/build/templates/build/auto_allocate.html
Normal file
@ -0,0 +1,43 @@
|
||||
{% extends "modal_form.html" %}
|
||||
|
||||
{% block pre_form_content %}
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
<b>Build: {{ build.title }}</b> - {{ build.quantity }} x {{ build.part.name }}
|
||||
<br><br>
|
||||
Automatically allocate stock to this build?
|
||||
<hr>
|
||||
|
||||
{% if allocations %}
|
||||
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Part</th>
|
||||
<th>Quantity</th>
|
||||
<th>Location</th>
|
||||
</tr>
|
||||
{% for item in allocations %}
|
||||
<tr>
|
||||
<td>
|
||||
<a class='hover-icon'>
|
||||
<img class='hover-img-thumb' src='{{ item.stock_item.part.image.url }}'>
|
||||
<img class='hover-img-large' src='{{ item.stock_item.part.image.url }}'>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ item.stock_item.part.name }}<br>
|
||||
<i>{{ item.stock_item.part.description }}</i>
|
||||
</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td>{{ item.stock_item.location }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% else %}
|
||||
<i>No stock could be selected for automatic build allocation.</i>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -7,18 +7,42 @@ Are you sure you want to mark this build as complete?
|
||||
<hr>
|
||||
{% if taking %}
|
||||
The following items will be removed from stock:
|
||||
<ul>
|
||||
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Part</th>
|
||||
<th>Quantity</th>
|
||||
<th>Location</th>
|
||||
</tr>
|
||||
{% for item in taking %}
|
||||
<li>{{ item.quantity }} x {{ item.stock_item.part.name }} from {{ item.stock_item.location }}</li>
|
||||
<tr>
|
||||
<td>
|
||||
<a class='hover-icon'>
|
||||
<img class='hover-img-thumb' src='{{ item.stock_item.part.image.url }}'>
|
||||
<img class='hover-img-large' src='{{ item.stock_item.part.image.url }}'>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ item.stock_item.part.name }}<br>
|
||||
<i>{{ item.stock_item.part.description }}</i>
|
||||
</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td>{{ item.stock_item.location }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</table>
|
||||
{% else %}
|
||||
No parts have been allocated to this build.
|
||||
{% endif %}
|
||||
<hr>
|
||||
The following items will be created:
|
||||
<ul>
|
||||
<li>{{ build.quantity }} x {{ build.part.name }}</li>
|
||||
</ul>
|
||||
<div class='panel panel-default'>
|
||||
<a class='hover-icon'>
|
||||
<img class='hover-img-thumb' src='{{ build.part.image.url }}'>
|
||||
<img class='hover-img-large' src='{{ build.part.image.url }}'>
|
||||
</a>
|
||||
{{ build.quantity }} x {{ build.part.name }}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -17,12 +17,15 @@ InvenTree | Build - {{ build }}
|
||||
<h3>
|
||||
<div style='float: right;'>
|
||||
<div class="dropdown" style="float: right;">
|
||||
<a href="{% url 'build-allocate' build.id %}">
|
||||
<button class='btn btn-info' type='button' title='Allocate Parts' id='build-allocate'>Allocate Parts</button>
|
||||
</a>
|
||||
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Options
|
||||
<span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href='#' id='build-edit' title='Edit build'>Edit build</a></li>
|
||||
{% if build.is_active %}
|
||||
<li><a href="{% url 'build-allocate' build.id %}" title='Allocate parts'>Allocate Parts</a></li>
|
||||
<li><a href='#' id='build-complete' title='Complete Build'>Complete Build</a></li>
|
||||
<li><a href='#' id='build-cancel' title='Cancel build'>Cancel build</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
@ -127,4 +130,15 @@ InvenTree | Build - {{ build }}
|
||||
submit_text: "Cancel Build",
|
||||
});
|
||||
});
|
||||
|
||||
$("#build-complete").on('click', function() {
|
||||
launchModalForm(
|
||||
"{% url 'build-complete' build.id %}",
|
||||
{
|
||||
reload: true,
|
||||
submit_text: "Complete Build",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
{% endblock %}
|
||||
|
9
InvenTree/build/templates/build/unallocate.html
Normal file
9
InvenTree/build/templates/build/unallocate.html
Normal file
@ -0,0 +1,9 @@
|
||||
{% extends "modal_form.html" %}
|
||||
|
||||
{% block pre_form_content %}
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
Are you sure you wish to unallocate all stock for this build?
|
||||
|
||||
{% endblock %}
|
@ -21,6 +21,8 @@ build_detail_urls = [
|
||||
url(r'^allocate/?', views.BuildAllocate.as_view(), name='build-allocate'),
|
||||
url(r'^cancel/?', views.BuildCancel.as_view(), name='build-cancel'),
|
||||
url(r'^complete/?', views.BuildComplete.as_view(), name='build-complete'),
|
||||
url(r'^auto-allocate/?', views.BuildAutoAllocate.as_view(), name='build-auto-allocate'),
|
||||
url(r'^unallocate/', views.BuildUnallocate.as_view(), name='build-unallocate'),
|
||||
|
||||
url(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),
|
||||
]
|
||||
|
@ -12,7 +12,7 @@ from django.forms import HiddenInput
|
||||
|
||||
from part.models import Part
|
||||
from .models import Build, BuildItem
|
||||
from .forms import EditBuildForm, EditBuildItemForm, CompleteBuildForm
|
||||
from . import forms
|
||||
from stock.models import StockLocation, StockItem
|
||||
|
||||
from InvenTree.views import AjaxView, AjaxUpdateView, AjaxCreateView, AjaxDeleteView
|
||||
@ -67,6 +67,95 @@ class BuildCancel(AjaxView):
|
||||
}
|
||||
|
||||
|
||||
class BuildAutoAllocate(AjaxUpdateView):
|
||||
""" View to auto-allocate parts for a build.
|
||||
Follows a simple set of rules to automatically allocate StockItem objects.
|
||||
|
||||
Ref: build.models.Build.getAutoAllocations()
|
||||
"""
|
||||
|
||||
model = Build
|
||||
form_class = forms.ConfirmBuildForm
|
||||
context_object_name = 'build'
|
||||
ajax_form_title = 'Allocate Stock'
|
||||
ajax_template_name = 'build/auto_allocate.html'
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
""" Get the context data for form rendering. """
|
||||
|
||||
context = {}
|
||||
|
||||
try:
|
||||
build = Build.objects.get(id=self.kwargs['pk'])
|
||||
context['build'] = build
|
||||
context['allocations'] = build.getAutoAllocations()
|
||||
except Build.DoesNotExist:
|
||||
context['error'] = 'No matching buidl found'
|
||||
|
||||
return context
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
""" Handle POST request. Perform auto allocations.
|
||||
|
||||
- If the form validation passes, perform allocations
|
||||
- Otherwise, the form is passed back to the client
|
||||
"""
|
||||
|
||||
build = self.get_object()
|
||||
form = self.get_form()
|
||||
|
||||
confirm = request.POST.get('confirm', False)
|
||||
|
||||
valid = False
|
||||
|
||||
if confirm is False:
|
||||
form.errors['confirm'] = ['Confirm stock allocation']
|
||||
form.non_field_errors = 'Check the confirmation box at the bottom of the list'
|
||||
else:
|
||||
build.autoAllocate()
|
||||
valid = True
|
||||
|
||||
data = {
|
||||
'form_valid': valid,
|
||||
}
|
||||
|
||||
return self.renderJsonResponse(request, form, data, context=self.get_context_data())
|
||||
|
||||
|
||||
class BuildUnallocate(AjaxUpdateView):
|
||||
""" View to un-allocate all parts from a build.
|
||||
|
||||
Provides a simple confirmation dialog with a BooleanField checkbox.
|
||||
"""
|
||||
|
||||
model = Build
|
||||
form_class = forms.ConfirmBuildForm
|
||||
ajax_form_title = "Unallocate Stock"
|
||||
ajax_template_name = "build/unallocate.html"
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
build = self.get_object()
|
||||
form = self.get_form()
|
||||
|
||||
confirm = request.POST.get('confirm', False)
|
||||
|
||||
valid = False
|
||||
|
||||
if confirm is False:
|
||||
form.errors['confirm'] = ['Confirm unallocation of build stock']
|
||||
form.non_field_errors = 'Check the confirmation box'
|
||||
else:
|
||||
build.unallocateStock()
|
||||
valid = True
|
||||
|
||||
data = {
|
||||
'form_valid': valid,
|
||||
}
|
||||
|
||||
return self.renderJsonResponse(request, form, data)
|
||||
|
||||
|
||||
class BuildComplete(AjaxUpdateView):
|
||||
""" View to mark a build as Complete.
|
||||
|
||||
@ -76,7 +165,7 @@ class BuildComplete(AjaxUpdateView):
|
||||
"""
|
||||
|
||||
model = Build
|
||||
form_class = CompleteBuildForm
|
||||
form_class = forms.CompleteBuildForm
|
||||
context_object_name = "build"
|
||||
ajax_form_title = "Complete Build"
|
||||
ajax_template_name = "build/complete.html"
|
||||
@ -193,7 +282,7 @@ class BuildCreate(AjaxCreateView):
|
||||
""" View to create a new Build object """
|
||||
model = Build
|
||||
context_object_name = 'build'
|
||||
form_class = EditBuildForm
|
||||
form_class = forms.EditBuildForm
|
||||
ajax_form_title = 'Start new Build'
|
||||
ajax_template_name = 'modal_form.html'
|
||||
|
||||
@ -225,7 +314,7 @@ class BuildUpdate(AjaxUpdateView):
|
||||
""" View for editing a Build object """
|
||||
|
||||
model = Build
|
||||
form_class = EditBuildForm
|
||||
form_class = forms.EditBuildForm
|
||||
context_object_name = 'build'
|
||||
ajax_form_title = 'Edit Build Details'
|
||||
ajax_template_name = 'modal_form.html'
|
||||
@ -256,7 +345,7 @@ class BuildItemCreate(AjaxCreateView):
|
||||
""" View for allocating a new part to a build """
|
||||
|
||||
model = BuildItem
|
||||
form_class = EditBuildItemForm
|
||||
form_class = forms.EditBuildItemForm
|
||||
ajax_template_name = 'modal_form.html'
|
||||
ajax_form_title = 'Allocate new Part'
|
||||
|
||||
@ -342,7 +431,7 @@ class BuildItemEdit(AjaxUpdateView):
|
||||
|
||||
model = BuildItem
|
||||
ajax_template_name = 'modal_form.html'
|
||||
form_class = EditBuildItemForm
|
||||
form_class = forms.EditBuildItemForm
|
||||
ajax_form_title = 'Edit Stock Allocation'
|
||||
|
||||
def get_data(self):
|
||||
|
@ -30,8 +30,8 @@
|
||||
<td>{{ attachment.comment }}</td>
|
||||
<td>
|
||||
<div class='btn-group' style='float: right;'>
|
||||
<button type='button' class='btn btn-primary attachment-edit-button' url="{% url 'part-attachment-edit' attachment.id %}" data-toggle='tooltip' title='Edit attachment ({{ attachment.basename }})'>Edit</button>
|
||||
<button type='button' class='btn btn-danger attachment-delete-button' url="{% url 'part-attachment-delete' attachment.id %}" data-toggle='tooltip' title='Delete attachment ({{ attachment.basename }})'>Delete</button>
|
||||
<button type='button' class='btn btn-primary attachment-edit-button' url="{% url 'part-attachment-edit' attachment.id %}" data-toggle='tooltip' title='Edit attachment ({{ attachment.basename }})'><span class='glyphicon glyphicon-small glyphicon-pencil'></span></button>
|
||||
<button type='button' class='btn btn-danger attachment-delete-button' url="{% url 'part-attachment-delete' attachment.id %}" data-toggle='tooltip' title='Delete attachment ({{ attachment.basename }})'><span class='glyphicon glyphicon-small glyphicon-trash'></span></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -129,7 +129,7 @@ function loadBomTable(table, options) {
|
||||
if (options.editable) {
|
||||
cols.push({
|
||||
formatter: function(value, row, index, field) {
|
||||
var bEdit = "<button title='Edit BOM Item' class='btn btn-success bom-edit-button btn-sm' type='button' url='/part/bom/" + row.pk + "/edit'><span class='glyphicon glyphicon-small glyphicon-pencil'></span></button>";
|
||||
var bEdit = "<button title='Edit BOM Item' class='btn btn-primary bom-edit-button btn-sm' type='button' url='/part/bom/" + row.pk + "/edit'><span class='glyphicon glyphicon-small glyphicon-pencil'></span></button>";
|
||||
var bDelt = "<button title='Delete BOM Item' class='btn btn-danger bom-delete-button btn-sm' type='button' url='/part/bom/" + row.pk + "/delete'><span class='glyphicon glyphicon-small glyphicon-trash'></span></button>";
|
||||
|
||||
return "<div class='btn-group'>" + bEdit + bDelt + "</div>";
|
||||
|
@ -40,8 +40,8 @@ function loadAllocationTable(table, part_id, part, url, required, button) {
|
||||
formatter: function(value, row, index, field) {
|
||||
var html = value;
|
||||
|
||||
var bEdit = "<button class='btn btn-success item-edit-button btn-sm' type='button' url='/build/item/" + row.pk + "/edit/'>Edit</button>";
|
||||
var bDel = "<button class='btn btn-danger item-del-button btn-sm' type='button' url='/build/item/" + row.pk + "/delete/'>Delete</button>";
|
||||
var bEdit = "<button class='btn btn-primary item-edit-button btn-sm' type='button' title='Edit stock allocation' url='/build/item/" + row.pk + "/edit/'><span class='glyphicon glyphicon-small glyphicon-pencil'></span></button>";
|
||||
var bDel = "<button class='btn btn-danger item-del-button btn-sm' type='button' title='Delete stock allocation' url='/build/item/" + row.pk + "/delete/'><span class='glyphicon glyphicon-small glyphicon-trash'></span></button>";
|
||||
|
||||
html += "<div class='btn-group' style='float: right;'>" + bEdit + bDel + "</div>";
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user