mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Updates
- 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:
parent
84bfffd5a7
commit
03a231bffb
@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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),
|
||||
)
|
||||
|
@ -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 %}",
|
||||
{
|
||||
|
@ -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'),
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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 %}
|
||||
|
@ -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>`;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user