Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2022-04-19 17:45:21 +10:00
commit 1519a4f882
14 changed files with 7973 additions and 28 deletions

View File

@ -667,6 +667,7 @@ LANGUAGES = [
('en', _('English')), ('en', _('English')),
('es', _('Spanish')), ('es', _('Spanish')),
('es-mx', _('Spanish (Mexican)')), ('es-mx', _('Spanish (Mexican)')),
('fa', _('Farsi / Persian')),
('fr', _('French')), ('fr', _('French')),
('he', _('Hebrew')), ('he', _('Hebrew')),
('hu', _('Hungarian')), ('hu', _('Hungarian')),

View File

@ -255,6 +255,9 @@ class StockHistoryCode(StatusCode):
# Stock merging operations # Stock merging operations
MERGED_STOCK_ITEMS = 45 MERGED_STOCK_ITEMS = 45
# Convert stock item to variant
CONVERTED_TO_VARIANT = 48
# Build order codes # Build order codes
BUILD_OUTPUT_CREATED = 50 BUILD_OUTPUT_CREATED = 50
BUILD_OUTPUT_COMPLETED = 55 BUILD_OUTPUT_COMPLETED = 55
@ -294,6 +297,8 @@ class StockHistoryCode(StatusCode):
MERGED_STOCK_ITEMS: _('Merged stock items'), MERGED_STOCK_ITEMS: _('Merged stock items'),
CONVERTED_TO_VARIANT: _('Converted to variant'),
SENT_TO_CUSTOMER: _('Sent to customer'), SENT_TO_CUSTOMER: _('Sent to customer'),
RETURNED_FROM_CUSTOMER: _('Returned from customer'), RETURNED_FROM_CUSTOMER: _('Returned from customer'),

View File

@ -12,11 +12,14 @@ import common.models
INVENTREE_SW_VERSION = "0.7.0 dev" INVENTREE_SW_VERSION = "0.7.0 dev"
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 38 INVENTREE_API_VERSION = 39
""" """
Increment this API version number whenever there is a significant change to the API that any clients need to know about Increment this API version number whenever there is a significant change to the API that any clients need to know about
v39 -> 2022-04-18
- Adds ability to filter StockItem list by "has_batch" parameter
v38 -> 2022-04-14 : https://github.com/inventree/InvenTree/pull/2828 v38 -> 2022-04-14 : https://github.com/inventree/InvenTree/pull/2828
- Adds the ability to include stock test results for "installed items" - Adds the ability to include stock test results for "installed items"

View File

@ -238,7 +238,7 @@
}); });
{% if company.is_customer %} {% if company.is_customer %}
onPanelLoad('panel-sales-orders', function() { onPanelLoad('sales-orders', function() {
loadSalesOrderTable("#sales-order-table", { loadSalesOrderTable("#sales-order-table", {
url: "{% url 'api-so-list' %}", url: "{% url 'api-so-list' %}",
params: { params: {

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -410,6 +410,20 @@ class StockFilter(rest_filters.FilterSet):
return queryset return queryset
has_batch = rest_filters.BooleanFilter(label='Has batch code', method='filter_has_batch')
def filter_has_batch(self, queryset, name, value):
"""
Filter by whether the StockItem has a batch code (or not)
"""
if str2bool(value):
queryset = queryset.exclude(batch=None)
else:
queryset = queryset.filter(batch=None)
return queryset
installed = rest_filters.BooleanFilter(label='Installed in other stock item', method='filter_installed') installed = rest_filters.BooleanFilter(label='Installed in other stock item', method='filter_installed')
def filter_installed(self, queryset, name, value): def filter_installed(self, queryset, name, value):
@ -1220,6 +1234,15 @@ class StockTrackingList(generics.ListAPIView):
if not deltas: if not deltas:
deltas = {} deltas = {}
# Add part detail
if 'part' in deltas:
try:
part = Part.objects.get(pk=deltas['part'])
serializer = PartBriefSerializer(part)
deltas['part_detail'] = serializer.data
except:
pass
# Add location detail # Add location detail
if 'location' in deltas: if 'location' in deltas:
try: try:

View File

@ -718,6 +718,33 @@ class StockItem(MPTTModel):
help_text=_('Select Owner'), help_text=_('Select Owner'),
related_name='stock_items') related_name='stock_items')
@transaction.atomic
def convert_to_variant(self, variant, user, notes=None):
"""
Convert this StockItem instance to a "variant",
i.e. change the "part" reference field
"""
if not variant:
# Ignore null values
return
if variant == self.part:
# Variant is the same as the current part
return
self.part = variant
self.save()
self.add_tracking_entry(
StockHistoryCode.CONVERTED_TO_VARIANT,
user,
deltas={
'part': variant.pk,
},
notes=_('Converted to part') + ': ' + variant.full_name,
)
def get_item_owner(self): def get_item_owner(self):
""" """
Return the closest "owner" for this StockItem. Return the closest "owner" for this StockItem.

View File

@ -26,11 +26,12 @@
</div> </div>
</div> </div>
<div class='panel-content'> <div class='panel-content'>
<div id='table-toolbar'> <div id='tracking-table-toolbar'>
<div class='btn-group'> <div class='btn-group'>
{% include "filter_list.html" with id="stocktracking" %}
</div> </div>
</div> </div>
<table class='table table-condensed table-striped' id='track-table' data-toolbar='#table-toolbar'> <table class='table table-condensed table-striped' id='track-table' data-toolbar='#tracking-table-toolbar'>
</table> </table>
</div> </div>
</div> </div>
@ -342,7 +343,6 @@
); );
}); });
loadStockTrackingTable($("#track-table"), { loadStockTrackingTable($("#track-table"), {
params: { params: {
ordering: '-date', ordering: '-date',

View File

@ -644,6 +644,16 @@ class StockItemConvert(AjaxUpdateView):
return form return form
def save(self, obj, form):
stock_item = self.get_object()
variant = form.cleaned_data.get('part', None)
stock_item.convert_to_variant(variant, user=self.request.user)
return stock_item
class StockLocationCreate(AjaxCreateView): class StockLocationCreate(AjaxCreateView):
""" """

View File

@ -99,14 +99,22 @@ function renderStockItem(name, data, parameters={}, options={}) {
var stock_detail = ''; var stock_detail = '';
if (data.serial && data.quantity == 1) { if (data.quantity == 0) {
stock_detail = `{% trans "Serial Number" %}: ${data.serial}`;
} else if (data.quantity == 0) {
stock_detail = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock"% }</span>`; stock_detail = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock"% }</span>`;
} else { } else {
stock_detail = `{% trans "Quantity" %}: ${data.quantity}`; if (data.serial && data.quantity == 1) {
stock_detail = `{% trans "Serial Number" %}: ${data.serial}`;
} else {
stock_detail = `{% trans "Quantity" %}: ${data.quantity}`;
}
if (data.batch != null) {
stock_detail += ` - <small>{% trans "Batch" %}: ${data.batch}</small>`;
}
} }
var html = ` var html = `
<span> <span>
${part_detail} ${part_detail}

View File

@ -293,6 +293,7 @@ function categoryFields() {
return { return {
parent: { parent: {
help_text: '{% trans "Parent part category" %}', help_text: '{% trans "Parent part category" %}',
required: false,
}, },
name: {}, name: {},
description: {}, description: {},

View File

@ -107,6 +107,7 @@ function stockLocationFields(options={}) {
var fields = { var fields = {
parent: { parent: {
help_text: '{% trans "Parent stock location" %}', help_text: '{% trans "Parent stock location" %}',
required: false,
}, },
name: {}, name: {},
description: {}, description: {},
@ -963,6 +964,10 @@ function adjustStock(action, items, options={}) {
quantity = `#${item.serial}`; quantity = `#${item.serial}`;
} }
if (item.batch != null) {
quantity += ` - <small>{% trans "Batch" %}: ${item.batch}</small>`;
}
var actionInput = ''; var actionInput = '';
if (actionTitle != null) { if (actionTitle != null) {
@ -2314,6 +2319,23 @@ function loadStockTrackingTable(table, options) {
var cols = []; var cols = [];
var filterTarget = '#filter-list-stocktracking';
var filterKey = 'stocktracking';
var filters = loadTableFilters(filterKey);
var params = options.params;
var original = {};
for (var k in params) {
original[k] = params[k];
filters[k] = params[k];
}
setupFilterList(filterKey, table, filterTarget);
// Date // Date
cols.push({ cols.push({
field: 'date', field: 'date',
@ -2351,6 +2373,19 @@ function loadStockTrackingTable(table, options) {
return html; return html;
} }
// Part information
if (details.part) {
html += `<tr><th>{% trans "Part" %}</th><td>`;
if (details.part_detail) {
html += renderLink(details.part_detail.full_name, `/part/${details.part}/`);
} else {
html += `{% trans "Part information unavailable" %}`;
}
html += `</td></tr>`;
}
// Location information // Location information
if (details.location) { if (details.location) {
@ -2488,27 +2523,10 @@ 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) {
// Manually created entries can be edited or deleted
if (false && !row.system) {
var bEdit = "<button title='{% trans 'Edit tracking entry' %}' class='btn btn-entry-edit btn-outline-secondary' type='button' url='/stock/track/" + row.pk + "/edit/'><span class='fas fa-edit'/></button>";
var bDel = "<button title='{% trans 'Delete tracking entry' %}' class='btn btn-entry-delete btn-outline-secondary' type='button' url='/stock/track/" + row.pk + "/delete/'><span class='fas fa-trash-alt icon-red'/></button>";
return "<div class='btn-group' role='group'>" + bEdit + bDel + "</div>";
} else {
return "";
}
}
});
*/
table.inventreeTable({ table.inventreeTable({
method: 'get', method: 'get',
queryParams: options.params, queryParams: filters,
original: original,
columns: cols, columns: cols,
url: options.url, url: options.url,
}); });

View File

@ -234,6 +234,10 @@ function getAvailableTableFilters(tableKey) {
title: '{% trans "Stock status" %}', title: '{% trans "Stock status" %}',
description: '{% trans "Stock status" %}', description: '{% trans "Stock status" %}',
}, },
has_batch: {
title: '{% trans "Has batch code" %}',
type: 'bool',
},
batch: { batch: {
title: '{% trans "Batch" %}', title: '{% trans "Batch" %}',
description: '{% trans "Batch code" %}', description: '{% trans "Batch code" %}',