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
6ffb6db248
@ -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):
|
class CompleteBuildForm(HelperForm):
|
||||||
""" Form for marking a Build as complete """
|
""" 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)
|
- If there are multiple StockItems available, ignore (leave up to the user)
|
||||||
|
|
||||||
Returns:
|
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():
|
for item in self.part.bom_items.all():
|
||||||
|
|
||||||
@ -151,19 +151,37 @@ class Build(models.Model):
|
|||||||
|
|
||||||
# Are there any parts available?
|
# Are there any parts available?
|
||||||
if stock_item.quantity > 0:
|
if stock_item.quantity > 0:
|
||||||
|
|
||||||
# Only take as many as are available
|
# Only take as many as are available
|
||||||
if stock_item.quantity < q_required:
|
if stock_item.quantity < q_required:
|
||||||
q_required = stock_item.quantity
|
q_required = stock_item.quantity
|
||||||
|
|
||||||
# Add the item to the allocations list
|
allocation = {
|
||||||
allocations[stock_item] = q_required
|
'stock_item': stock_item,
|
||||||
|
'quantity': q_required,
|
||||||
|
}
|
||||||
|
|
||||||
|
allocations.append(allocation)
|
||||||
|
|
||||||
return allocations
|
return allocations
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def unallocateStock(self):
|
||||||
|
""" Deletes all stock allocations for this build. """
|
||||||
|
|
||||||
|
BuildItem.objects.filter(build=self.id).delete()
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def autoAllocate(self):
|
def autoAllocate(self):
|
||||||
""" Run auto-allocation routine to allocate StockItems to this Build.
|
""" 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()
|
See: getAutoAllocations()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -173,8 +191,8 @@ class Build(models.Model):
|
|||||||
# Create a new allocation
|
# Create a new allocation
|
||||||
build_item = BuildItem(
|
build_item = BuildItem(
|
||||||
build=self,
|
build=self,
|
||||||
stock_item=item,
|
stock_item=item['stock_item'],
|
||||||
quantity=allocations[item])
|
quantity=item['quantity'])
|
||||||
|
|
||||||
build_item.save()
|
build_item.save()
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ InvenTree | Allocate Parts
|
|||||||
</div>
|
</div>
|
||||||
<div class='col-sm-6'>
|
<div class='col-sm-6'>
|
||||||
<div class='btn-group' style='float: right;'>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -54,12 +55,20 @@ InvenTree | Allocate Parts
|
|||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
$("#complete-build").on('click', function() {
|
$("#auto-allocate-build").on('click', function() {
|
||||||
launchModalForm(
|
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,
|
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>
|
<hr>
|
||||||
{% if taking %}
|
{% if taking %}
|
||||||
The following items will be removed from stock:
|
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 %}
|
{% 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 %}
|
{% endfor %}
|
||||||
</ul>
|
</table>
|
||||||
{% else %}
|
{% else %}
|
||||||
No parts have been allocated to this build.
|
No parts have been allocated to this build.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<hr>
|
<hr>
|
||||||
The following items will be created:
|
The following items will be created:
|
||||||
<ul>
|
<div class='panel panel-default'>
|
||||||
<li>{{ build.quantity }} x {{ build.part.name }}</li>
|
<a class='hover-icon'>
|
||||||
</ul>
|
<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 %}
|
{% endblock %}
|
@ -17,12 +17,15 @@ InvenTree | Build - {{ build }}
|
|||||||
<h3>
|
<h3>
|
||||||
<div style='float: right;'>
|
<div style='float: right;'>
|
||||||
<div class="dropdown" 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
|
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Options
|
||||||
<span class="caret"></span></button>
|
<span class="caret"></span></button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a href='#' id='build-edit' title='Edit build'>Edit build</a></li>
|
<li><a href='#' id='build-edit' title='Edit build'>Edit build</a></li>
|
||||||
{% if build.is_active %}
|
{% 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>
|
<li><a href='#' id='build-cancel' title='Cancel build'>Cancel build</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@ -127,4 +130,15 @@ InvenTree | Build - {{ build }}
|
|||||||
submit_text: "Cancel Build",
|
submit_text: "Cancel Build",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#build-complete").on('click', function() {
|
||||||
|
launchModalForm(
|
||||||
|
"{% url 'build-complete' build.id %}",
|
||||||
|
{
|
||||||
|
reload: true,
|
||||||
|
submit_text: "Complete Build",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
{% endblock %}
|
{% 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'^allocate/?', views.BuildAllocate.as_view(), name='build-allocate'),
|
||||||
url(r'^cancel/?', views.BuildCancel.as_view(), name='build-cancel'),
|
url(r'^cancel/?', views.BuildCancel.as_view(), name='build-cancel'),
|
||||||
url(r'^complete/?', views.BuildComplete.as_view(), name='build-complete'),
|
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'),
|
url(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),
|
||||||
]
|
]
|
||||||
|
@ -12,7 +12,7 @@ from django.forms import HiddenInput
|
|||||||
|
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from .models import Build, BuildItem
|
from .models import Build, BuildItem
|
||||||
from .forms import EditBuildForm, EditBuildItemForm, CompleteBuildForm
|
from . import forms
|
||||||
from stock.models import StockLocation, StockItem
|
from stock.models import StockLocation, StockItem
|
||||||
|
|
||||||
from InvenTree.views import AjaxView, AjaxUpdateView, AjaxCreateView, AjaxDeleteView
|
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):
|
class BuildComplete(AjaxUpdateView):
|
||||||
""" View to mark a build as Complete.
|
""" View to mark a build as Complete.
|
||||||
|
|
||||||
@ -76,7 +165,7 @@ class BuildComplete(AjaxUpdateView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
model = Build
|
model = Build
|
||||||
form_class = CompleteBuildForm
|
form_class = forms.CompleteBuildForm
|
||||||
context_object_name = "build"
|
context_object_name = "build"
|
||||||
ajax_form_title = "Complete Build"
|
ajax_form_title = "Complete Build"
|
||||||
ajax_template_name = "build/complete.html"
|
ajax_template_name = "build/complete.html"
|
||||||
@ -193,7 +282,7 @@ class BuildCreate(AjaxCreateView):
|
|||||||
""" View to create a new Build object """
|
""" View to create a new Build object """
|
||||||
model = Build
|
model = Build
|
||||||
context_object_name = 'build'
|
context_object_name = 'build'
|
||||||
form_class = EditBuildForm
|
form_class = forms.EditBuildForm
|
||||||
ajax_form_title = 'Start new Build'
|
ajax_form_title = 'Start new Build'
|
||||||
ajax_template_name = 'modal_form.html'
|
ajax_template_name = 'modal_form.html'
|
||||||
|
|
||||||
@ -225,7 +314,7 @@ class BuildUpdate(AjaxUpdateView):
|
|||||||
""" View for editing a Build object """
|
""" View for editing a Build object """
|
||||||
|
|
||||||
model = Build
|
model = Build
|
||||||
form_class = EditBuildForm
|
form_class = forms.EditBuildForm
|
||||||
context_object_name = 'build'
|
context_object_name = 'build'
|
||||||
ajax_form_title = 'Edit Build Details'
|
ajax_form_title = 'Edit Build Details'
|
||||||
ajax_template_name = 'modal_form.html'
|
ajax_template_name = 'modal_form.html'
|
||||||
@ -256,7 +345,7 @@ class BuildItemCreate(AjaxCreateView):
|
|||||||
""" View for allocating a new part to a build """
|
""" View for allocating a new part to a build """
|
||||||
|
|
||||||
model = BuildItem
|
model = BuildItem
|
||||||
form_class = EditBuildItemForm
|
form_class = forms.EditBuildItemForm
|
||||||
ajax_template_name = 'modal_form.html'
|
ajax_template_name = 'modal_form.html'
|
||||||
ajax_form_title = 'Allocate new Part'
|
ajax_form_title = 'Allocate new Part'
|
||||||
|
|
||||||
@ -342,7 +431,7 @@ class BuildItemEdit(AjaxUpdateView):
|
|||||||
|
|
||||||
model = BuildItem
|
model = BuildItem
|
||||||
ajax_template_name = 'modal_form.html'
|
ajax_template_name = 'modal_form.html'
|
||||||
form_class = EditBuildItemForm
|
form_class = forms.EditBuildItemForm
|
||||||
ajax_form_title = 'Edit Stock Allocation'
|
ajax_form_title = 'Edit Stock Allocation'
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
|
@ -30,8 +30,8 @@
|
|||||||
<td>{{ attachment.comment }}</td>
|
<td>{{ attachment.comment }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class='btn-group' style='float: right;'>
|
<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-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 }})'>Delete</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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -129,7 +129,7 @@ function loadBomTable(table, options) {
|
|||||||
if (options.editable) {
|
if (options.editable) {
|
||||||
cols.push({
|
cols.push({
|
||||||
formatter: function(value, row, index, field) {
|
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>";
|
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>";
|
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) {
|
formatter: function(value, row, index, field) {
|
||||||
var html = value;
|
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 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' url='/build/item/" + row.pk + "/delete/'>Delete</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>";
|
html += "<div class='btn-group' style='float: right;'>" + bEdit + bDel + "</div>";
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ class CreateStockItemForm(HelperForm):
|
|||||||
'location',
|
'location',
|
||||||
'batch',
|
'batch',
|
||||||
'quantity',
|
'quantity',
|
||||||
|
'delete_on_deplete',
|
||||||
'status',
|
'status',
|
||||||
'notes',
|
'notes',
|
||||||
'URL',
|
'URL',
|
||||||
@ -78,6 +79,7 @@ class EditStockItemForm(HelperForm):
|
|||||||
fields = [
|
fields = [
|
||||||
'supplier_part',
|
'supplier_part',
|
||||||
'batch',
|
'batch',
|
||||||
|
'delete_on_deplete',
|
||||||
'status',
|
'status',
|
||||||
'notes',
|
'notes',
|
||||||
'URL',
|
'URL',
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.2 on 2019-05-09 12:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('stock', '0014_auto_20190508_2332'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='stockitem',
|
||||||
|
name='delete_on_deplete',
|
||||||
|
field=models.BooleanField(default=True, help_text='Delete this Stock Item when stock is depleted'),
|
||||||
|
),
|
||||||
|
]
|
@ -93,7 +93,7 @@ class StockItem(models.Model):
|
|||||||
|
|
||||||
if add_note:
|
if add_note:
|
||||||
# This StockItem is being saved for the first time
|
# This StockItem is being saved for the first time
|
||||||
self.add_transaction_note(
|
self.addTransactionNote(
|
||||||
'Created stock item',
|
'Created stock item',
|
||||||
None,
|
None,
|
||||||
notes="Created new stock item for part '{p}'".format(p=str(self.part)),
|
notes="Created new stock item for part '{p}'".format(p=str(self.part)),
|
||||||
@ -221,6 +221,8 @@ class StockItem(models.Model):
|
|||||||
|
|
||||||
review_needed = models.BooleanField(default=False)
|
review_needed = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
delete_on_deplete = models.BooleanField(default=True, help_text='Delete this Stock Item when stock is depleted')
|
||||||
|
|
||||||
ITEM_OK = 10
|
ITEM_OK = 10
|
||||||
ITEM_ATTENTION = 50
|
ITEM_ATTENTION = 50
|
||||||
ITEM_DAMAGED = 55
|
ITEM_DAMAGED = 55
|
||||||
@ -261,7 +263,11 @@ class StockItem(models.Model):
|
|||||||
def has_tracking_info(self):
|
def has_tracking_info(self):
|
||||||
return self.tracking_info.count() > 0
|
return self.tracking_info.count() > 0
|
||||||
|
|
||||||
def add_transaction_note(self, title, user, notes='', system=True):
|
def addTransactionNote(self, title, user, notes='', system=True):
|
||||||
|
""" Generation a stock transaction note for this item.
|
||||||
|
|
||||||
|
Brief automated note detailing a movement or quantity change.
|
||||||
|
"""
|
||||||
track = StockItemTracking.objects.create(
|
track = StockItemTracking.objects.create(
|
||||||
item=self,
|
item=self,
|
||||||
title=title,
|
title=title,
|
||||||
@ -290,15 +296,39 @@ class StockItem(models.Model):
|
|||||||
msg += " (from {loc})".format(loc=str(self.location))
|
msg += " (from {loc})".format(loc=str(self.location))
|
||||||
|
|
||||||
self.location = location
|
self.location = location
|
||||||
|
|
||||||
|
self.addTransactionNote(msg,
|
||||||
|
user,
|
||||||
|
notes=notes,
|
||||||
|
system=True)
|
||||||
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
self.add_transaction_note(msg,
|
|
||||||
user,
|
|
||||||
notes=notes,
|
|
||||||
system=True)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def updateQuantity(self, quantity):
|
||||||
|
""" Update stock quantity for this item.
|
||||||
|
|
||||||
|
If the quantity has reached zero, this StockItem will be deleted.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- True if the quantity was saved
|
||||||
|
- False if the StockItem was deleted
|
||||||
|
"""
|
||||||
|
|
||||||
|
if quantity < 0:
|
||||||
|
quantity = 0
|
||||||
|
|
||||||
|
self.quantity = quantity
|
||||||
|
|
||||||
|
if quantity <= 0 and self.delete_on_deplete:
|
||||||
|
self.delete()
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.save()
|
||||||
|
return True
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def stocktake(self, count, user, notes=''):
|
def stocktake(self, count, user, notes=''):
|
||||||
""" Perform item stocktake.
|
""" Perform item stocktake.
|
||||||
@ -311,15 +341,15 @@ class StockItem(models.Model):
|
|||||||
if count < 0 or self.infinite:
|
if count < 0 or self.infinite:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.quantity = count
|
|
||||||
self.stocktake_date = datetime.now().date()
|
self.stocktake_date = datetime.now().date()
|
||||||
self.stocktake_user = user
|
self.stocktake_user = user
|
||||||
self.save()
|
|
||||||
|
|
||||||
self.add_transaction_note('Stocktake - counted {n} items'.format(n=count),
|
if self.updateQuantity(count):
|
||||||
user,
|
|
||||||
notes=notes,
|
self.addTransactionNote('Stocktake - counted {n} items'.format(n=count),
|
||||||
system=True)
|
user,
|
||||||
|
notes=notes,
|
||||||
|
system=True)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -336,14 +366,12 @@ class StockItem(models.Model):
|
|||||||
if quantity <= 0 or self.infinite:
|
if quantity <= 0 or self.infinite:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.quantity += quantity
|
if self.updateQuantity(self.quantity + quantity):
|
||||||
|
|
||||||
self.save()
|
self.addTransactionNote('Added {n} items to stock'.format(n=quantity),
|
||||||
|
user,
|
||||||
self.add_transaction_note('Added {n} items to stock'.format(n=quantity),
|
notes=notes,
|
||||||
user,
|
system=True)
|
||||||
notes=notes,
|
|
||||||
system=True)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -360,17 +388,12 @@ class StockItem(models.Model):
|
|||||||
if quantity <= 0 or self.infinite:
|
if quantity <= 0 or self.infinite:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.quantity -= quantity
|
if self.updateQuantity(self.quantity - quantity):
|
||||||
|
|
||||||
if self.quantity < 0:
|
self.addTransactionNote('Removed {n} items from stock'.format(n=quantity),
|
||||||
self.quantity = 0
|
user,
|
||||||
|
notes=notes,
|
||||||
self.save()
|
system=True)
|
||||||
|
|
||||||
self.add_transaction_note('Removed {n} items from stock'.format(n=quantity),
|
|
||||||
user,
|
|
||||||
notes=notes,
|
|
||||||
system=True)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
# InvenTree
|
# InvenTree
|
||||||
InvenTree is an open-source Inventory Management System which provides powerful low-level stock control and part tracking. The core of the InvenTree system is a Python/Django database backend which provides an admin interface (web-based) and a JSON API for interaction with external interfaces and applications.
|
InvenTree is an open-source Inventory Management System which provides powerful low-level stock control and part tracking. The core of the InvenTree system is a Python/Django database backend which provides an admin interface (web-based) and a JSON API for interaction with external interfaces and applications.
|
||||||
|
|
||||||
InvenTree is designed to be lightweight and easy to use for SME or hobbyist applications, where many existing stock management solutions are bloated and cumbersome to use. Updating stock is a single-action procses and does not require a complex system of work orders or stock transactions.
|
InvenTree is designed to be lightweight and easy to use for SME or hobbyist applications, where many existing stock management solutions are bloated and cumbersome to use. Updating stock is a single-action process and does not require a complex system of work orders or stock transactions.
|
||||||
|
|
||||||
However, complex business logic works in the background to ensure that stock tracking history is maintained, and users have ready access to stock level information.
|
However, powerful business logic works in the background to ensure that stock tracking history is maintained, and users have ready access to stock level information.
|
||||||
|
|
||||||
## User Documentation
|
## User Documentation
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user