- Add StockHistoryCode to custom context
- Add simple form for editing stock item history
- Add tracking entry when stock status is changed
This commit is contained in:
Oliver Walters 2021-05-11 23:38:26 +10:00
parent 84bfffd5a7
commit 03a231bffb
11 changed files with 119 additions and 16 deletions

View File

@ -6,6 +6,7 @@ Provides extra global data to all templates.
from InvenTree.status_codes import SalesOrderStatus, PurchaseOrderStatus
from InvenTree.status_codes import BuildStatus, StockStatus
from InvenTree.status_codes import StockHistoryCode
import InvenTree.status
@ -65,6 +66,7 @@ def status_codes(request):
'PurchaseOrderStatus': PurchaseOrderStatus,
'BuildStatus': BuildStatus,
'StockStatus': StockStatus,
'StockHistoryCode': StockHistoryCode,
}

View File

@ -7,6 +7,8 @@ class StatusCode:
This is used to map a set of integer values to text.
"""
colors = {}
@classmethod
def render(cls, key, large=False):
"""

View File

@ -21,7 +21,7 @@ from .models import StockItemTestResult
from part.models import Part, PartCategory
from part.serializers import PartBriefSerializer
from company.models import SupplierPart
from company.models import Company, SupplierPart
from company.serializers import CompanySerializer, SupplierPartSerializer
from order.models import PurchaseOrder
@ -100,6 +100,16 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
return self.serializer_class(*args, **kwargs)
def update(self, request, *args, **kwargs):
"""
Record the user who updated the item
"""
# TODO: Record the user!
# user = request.user
return super().update(request, *args, **kwargs)
class StockFilter(FilterSet):
""" FilterSet for advanced stock filtering.
@ -374,25 +384,25 @@ class StockList(generics.ListCreateAPIView):
we can pre-fill the location automatically.
"""
user = request.user
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
item = serializer.save()
item = serializer.save(user=user, commit=False)
# A location was *not* specified - try to infer it
if 'location' not in request.data:
location = item.part.get_default_location()
if location is not None:
item.location = location
item.save()
item.location = item.part.get_default_location()
# An expiry date was *not* specified - try to infer it!
if 'expiry_date' not in request.data:
if item.part.default_expiry > 0:
item.expiry_date = datetime.now().date() + timedelta(days=item.part.default_expiry)
item.save()
# Finally, save the item
item.save(user=user)
# Return a response
headers = self.get_success_headers(serializer.data)
@ -1029,7 +1039,7 @@ class StockTrackingList(generics.ListAPIView):
if 'customer' in deltas:
try:
customer = Company.objects.get(pk=deltas['customer'])
serializer = CompanySerializer(location)
serializer = CompanySerializer(customer)
deltas['customer_detail'] = serializer.data
except:
pass

View File

@ -393,6 +393,18 @@ class AdjustStockForm(forms.ModelForm):
]
class EditStockItemStatusForm(HelperForm):
"""
Simple form for editing StockItem status field
"""
class Meta:
model = StockItem
fields = [
'status',
]
class EditStockItemForm(HelperForm):
""" Form for editing a StockItem object.
Note that not all fields can be edited here (even if they can be specified during creation.

View File

@ -183,20 +183,46 @@ class StockItem(MPTTModel):
self.validate_unique()
self.clean()
user = kwargs.pop('user', None)
# If 'add_note = False' specified, then no tracking note will be added for item creation
add_note = kwargs.pop('add_note', True)
notes = kwargs.pop('notes', '')
if not self.pk:
# StockItem has not yet been saved
add_note = add_note and True
else:
# StockItem has already been saved
# Check if "interesting" fields have been changed
# (we wish to record these as historical records)
try:
old = StockItem.objects.get(pk=self.pk)
deltas = {}
# Status changed?
if not old.status == self.status:
deltas['status'] = self.status
# TODO - Other interesting changes we are interested in...
if add_note and len(deltas) > 0:
self.add_tracking_entry(
StockHistoryCode.EDITED,
user,
deltas=deltas,
notes=notes,
)
except (ValueError, StockItem.DoesNotExist):
pass
add_note = False
user = kwargs.pop('user', None)
add_note = add_note and kwargs.pop('note', True)
super(StockItem, self).save(*args, **kwargs)
if add_note:
@ -209,6 +235,7 @@ class StockItem(MPTTModel):
StockHistoryCode.CREATED,
user,
deltas=tracking_info,
notes=notes,
location=self.location,
quantity=float(self.quantity),
)

View File

@ -94,7 +94,13 @@
{% if item.is_expired %}
<span class='label label-large label-large-red'>{% trans "Expired" %}</span>
{% else %}
{% if roles.stock.change %}
<a href='#' id='stock-edit-status'>
{% endif %}
{% stock_status_label item.status large=True %}
{% if roles.stock.change %}
</a>
{% endif %}
{% if item.is_stale %}
<span class='label label-large label-large-yellow'>{% trans "Stale" %}</span>
{% endif %}
@ -453,6 +459,7 @@ $("#print-label").click(function() {
printStockItemLabels([{{ item.pk }}]);
});
{% if roles.stock.change %}
$("#stock-duplicate").click(function() {
createNewStockItem({
follow: true,
@ -472,6 +479,18 @@ $("#stock-edit").click(function () {
);
});
$('#stock-edit-status').click(function () {
launchModalForm(
"{% url 'stock-item-edit-status' item.id %}",
{
reload: true,
submit_text: '{% trans "Save" %}',
}
);
});
{% endif %}
$("#show-qr-code").click(function() {
launchModalForm("{% url 'stock-item-qr' item.id %}",
{

View File

@ -4,7 +4,7 @@ URL lookup for Stock app
from django.conf.urls import url, include
from . import views
from stock import views
location_urls = [
@ -24,6 +24,7 @@ location_urls = [
]
stock_item_detail_urls = [
url(r'^edit_status/', views.StockItemEditStatus.as_view(), name='stock-item-edit-status'),
url(r'^edit/', views.StockItemEdit.as_view(), name='stock-item-edit'),
url(r'^convert/', views.StockItemConvert.as_view(), name='stock-item-convert'),
url(r'^serialize/', views.StockItemSerialize.as_view(), name='stock-item-serialize'),

View File

@ -1212,6 +1212,16 @@ class StockAdjust(AjaxView, FormMixin):
return _("Deleted {n} stock items").format(n=count)
class StockItemEditStatus(AjaxUpdateView):
"""
View for editing stock item status field
"""
model = StockItem
form_class = StockForms.EditStockItemStatusForm
ajax_form_title = _('Edit Stock Item Status')
class StockItemEdit(AjaxUpdateView):
"""
View for editing details of a single StockItem

View File

@ -1097,6 +1097,16 @@ function loadStockTrackingTable(table, options) {
// Status information
if (details.status) {
html += `<tr><th>{% trans "Status" %}</td>`;
html += '<td>';
html += stockStatusDisplay(
details.status,
{
classes: 'float-right',
}
);
html += '</td></tr>';
}
@ -1147,6 +1157,8 @@ function loadStockTrackingTable(table, options) {
}
});
/*
// 2021-05-11 - Ability to edit or delete StockItemTracking entries is now removed
cols.push({
sortable: false,
formatter: function(value, row, index, field) {
@ -1161,6 +1173,7 @@ function loadStockTrackingTable(table, options) {
}
}
});
*/
table.inventreeTable({
method: 'get',

View File

@ -3,6 +3,7 @@
{% load inventree_extras %}
{% include "status_codes.html" with label='stock' options=StockStatus.list %}
{% include "status_codes.html" with label='stockHistory' options=StockHistoryCode.list %}
{% include "status_codes.html" with label='build' options=BuildStatus.list %}
{% include "status_codes.html" with label='purchaseOrder' options=PurchaseOrderStatus.list %}
{% include "status_codes.html" with label='salesOrder' options=SalesOrderStatus.list %}

View File

@ -14,7 +14,7 @@ var {{ label }}Codes = {
* Uses the values specified in "status_codes.py"
* This function is generated by the "status_codes.html" template
*/
function {{ label }}StatusDisplay(key) {
function {{ label }}StatusDisplay(key, options={}) {
key = String(key);
@ -31,5 +31,11 @@ function {{ label }}StatusDisplay(key) {
label = '';
}
return `<span class='label ${label}'>${value}</span>`;
var classes = `label ${label}`;
if (options.classes) {
classes += ' ' + options.classes;
}
return `<span class='${classes}'>${value}</span>`;
}