Merge pull request #711 from SchrodingersGat/tweakers

Table Filtering
This commit is contained in:
Oliver 2020-04-12 01:05:03 +10:00 committed by GitHub
commit a921b3fcee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1934 additions and 735 deletions

View File

@ -11,6 +11,8 @@ from django.core import validators
from django import forms from django import forms
from decimal import Decimal from decimal import Decimal
from InvenTree.helpers import normalize
class InvenTreeURLFormField(FormURLField): class InvenTreeURLFormField(FormURLField):
""" Custom URL form field with custom scheme validators """ """ Custom URL form field with custom scheme validators """
@ -53,7 +55,7 @@ class RoundingDecimalFormField(forms.DecimalField):
""" """
if type(value) == Decimal: if type(value) == Decimal:
return value.normalize() return normalize(value)
else: else:
return value return value

View File

@ -8,6 +8,8 @@ import json
import os.path import os.path
from PIL import Image from PIL import Image
from decimal import Decimal
from wsgiref.util import FileWrapper from wsgiref.util import FileWrapper
from django.http import StreamingHttpResponse from django.http import StreamingHttpResponse
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -104,6 +106,20 @@ def isNull(text):
return str(text).strip().lower() in ['top', 'null', 'none', 'empty', 'false', '-1', ''] return str(text).strip().lower() in ['top', 'null', 'none', 'empty', 'false', '-1', '']
def normalize(d):
"""
Normalize a decimal number, and remove exponential formatting.
"""
if type(d) is not Decimal:
d = Decimal(d)
d = d.normalize()
# Ref: https://docs.python.org/3/library/decimal.html
return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
def decimal2string(d): def decimal2string(d):
""" """
Format a Decimal number as a string, Format a Decimal number as a string,
@ -117,6 +133,9 @@ def decimal2string(d):
A string representation of the input number A string representation of the input number
""" """
if type(d) is Decimal:
d = normalize(d)
try: try:
# Ensure that the provided string can actually be converted to a float # Ensure that the provided string can actually be converted to a float
float(d) float(d)

View File

@ -125,6 +125,8 @@
.label-right { .label-right {
float: right; float: right;
margin-left: 3px;
margin-right: 3px;
} }
/* Bootstrap table overrides */ /* Bootstrap table overrides */
@ -157,6 +159,66 @@
font-style: italic; font-style: italic;
} }
.dropdown {
padding-left: 1px;
margin-left: 1px;
}
/* Styles for table buttons and filtering */
.button-toolbar .btn {
margin-left: 1px;
margin-right: 1px;
}
.filter-list {
display: inline-block;
*display: inline;
margin-bottom: 1px;
margin-top: 1px;
vertical-align: middle;
margin: 1px;
padding: 2px;
background: #eee;
border: 1px solid #eee;
border-radius: 3px;
}
.filter-list .close {
cursor: pointer;
right: 0%;
padding-right: 2px;
padding-left: 2px;
transform: translate(0%, -25%);
}
.filter-list .close:hover {background: #bbb;}
.filter-tag {
display: inline-block;
*display: inline;
zoom: 1;
padding-left: 3px;
padding-right: 3px;
padding-top: 2px;
padding-bottom: 2px;
border: 1px solid #aaa;
border-radius: 3px;
background: #eee;
margin: 1px;
margin-left: 5px;
margin-right: 5px;
}
.filter-input {
display: inline-block;
*display: inline;
zoom: 1;
}
.filter-tag:hover {
background: #ddd;
}
/* Part image icons with full-display on mouse hover */ /* Part image icons with full-display on mouse hover */
.hover-img-thumb { .hover-img-thumb {

View File

@ -3272,10 +3272,7 @@
}, { }, {
key: 'getOptions', key: 'getOptions',
value: function getOptions() { value: function getOptions() {
// deep copy and remove data return this.options;
var options = JSON.parse(JSON.stringify(this.options));
delete options.data;
return options;
} }
}, { }, {
key: 'getSelections', key: 'getSelections',

View File

@ -133,11 +133,11 @@ function loadBomTable(table, options) {
title: 'Part', title: 'Part',
sortable: true, sortable: true,
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
var html = imageHoverIcon(row.sub_part_detail.image_url) + renderLink(row.sub_part_detail.full_name, row.sub_part_detail.url); var html = imageHoverIcon(row.sub_part_detail.thumbnail) + renderLink(row.sub_part_detail.full_name, row.sub_part_detail.url);
// Display an extra icon if this part is an assembly // Display an extra icon if this part is an assembly
if (row.sub_part_detail.assembly) { if (row.sub_part_detail.assembly) {
html += "<a href='" + row.sub_part_detail.url + "bom'><span class='glyphicon-right glyphicon glyphicon-th-list'></span></a>"; html += "<a href='" + row.sub_part_detail.url + "bom'><span title='Open subassembly' class='fas fa-stream label-right'></span></a>";
} }
return html; return html;

View File

@ -1,3 +1,77 @@
function loadBuildTable(table, options) {
var params = options.params || {};
var filters = loadTableFilters("build");
for (var key in params) {
filters[key] = params[key];
}
setupFilterList("build", table);
table.inventreeTable({
method: 'get',
formatNoMatches: function() {
return "No builds matching query";
},
url: options.url,
queryParams: filters,
groupBy: false,
original: params,
columns: [
{
field: 'pk',
title: 'ID',
visible: false,
},
{
field: 'title',
title: 'Build',
sortable: true,
formatter: function(value, row, index, field) {
return renderLink(value, '/build/' + row.pk + '/');
}
},
{
field: 'part',
title: 'Part',
sortable: true,
formatter: function(value, row, index, field) {
var name = row.part_detail.full_name;
return imageHoverIcon(row.part_detail.thumbnail) + renderLink(name, '/part/' + row.part + '/');
}
},
{
field: 'quantity',
title: 'Quantity',
sortable: true,
},
{
field: 'status',
title: 'Status',
sortable: true,
formatter: function(value, row, index, field) {
return buildStatusDisplay(value);
},
},
{
field: 'creation_date',
title: 'Created',
sortable: true,
},
{
field: 'completion_date',
title: 'Completed',
sortable: true,
},
],
});
}
function updateAllocationTotal(id, count, required) { function updateAllocationTotal(id, count, required) {
count = parseFloat(count); count = parseFloat(count);

View File

@ -0,0 +1,410 @@
/**
* Code for managing query filters / table options.
*
* Optional query filters are available to the user for various
* tables display in the web interface.
* These filters are saved to the web session, and should be
* persistent for a given table type.
*
* This makes use of the 'inventreeSave' and 'inventreeLoad' functions
* for writing to and reading from session storage.
*
*/
function defaultFilters() {
return {
stock: "cascade=1",
build: "",
parts: "cascade=1",
};
}
/**
* Load table filters for the given table from session storage
*
* @param tableKey - String key for the particular table
* @param defaults - Default filters for this table e.g. 'cascade=1&location=5'
*/
function loadTableFilters(tableKey) {
var lookup = "table-filters-" + tableKey.toLowerCase();
var defaults = defaultFilters()[tableKey] || '';
var filterstring = inventreeLoad(lookup, defaults);
var filters = {};
filterstring.split("&").forEach(function(item, index) {
item = item.trim();
if (item.length > 0) {
var f = item.split('=');
if (f.length == 2) {
filters[f[0]] = f[1];
} else {
console.log(`Improperly formatted filter: ${item}`);
}
}
});
return filters;
}
/**
* Save table filters to session storage
*
* @param {*} tableKey - string key for the given table
* @param {*} filters - object of string:string pairs
*/
function saveTableFilters(tableKey, filters) {
var lookup = "table-filters-" + tableKey.toLowerCase();
var strings = [];
for (var key in filters) {
strings.push(`${key.trim()}=${String(filters[key]).trim()}`);
}
var filterstring = strings.join('&');
console.log(`Saving filters for table '${tableKey}' - ${filterstring}`);
inventreeSave(lookup, filterstring);
}
/*
* Remove a named filter parameter
*/
function removeTableFilter(tableKey, filterKey) {
var filters = loadTableFilters(tableKey);
delete filters[filterKey];
saveTableFilters(tableKey, filters);
// Return a copy of the updated filters
return filters;
}
function addTableFilter(tableKey, filterKey, filterValue) {
var filters = loadTableFilters(tableKey);
filters[filterKey] = filterValue;
saveTableFilters(tableKey, filters);
// Return a copy of the updated filters
return filters;
}
/*
* Clear all the custom filters for a given table
*/
function clearTableFilters(tableKey) {
saveTableFilters(tableKey, {});
return {};
}
/*
* Return a list of the "available" filters for a given table key.
* A filter is "available" if it is not already being used to filter the table.
* Once a filter is selected, it will not be returned here.
*/
function getRemainingTableFilters(tableKey) {
var filters = loadTableFilters(tableKey);
var remaining = getAvailableTableFilters(tableKey);
for (var key in filters) {
// Delete the filter if it is already in use
delete remaining[key];
}
return remaining;
}
/*
* Return the filter settings for a given table and key combination.
* Return empty object if the combination does not exist.
*/
function getFilterSettings(tableKey, filterKey) {
return getAvailableTableFilters(tableKey)[filterKey] || {};
}
/*
* Return a set of key:value options for the given filter.
* If no options are specified (e.g. for a number field),
* then a null object is returned.
*/
function getFilterOptionList(tableKey, filterKey) {
var settings = getFilterSettings(tableKey, filterKey);
if (settings.type == 'bool') {
return {
'1': {
key: '1',
value: 'true',
},
'0': {
key: '0',
value: 'false',
},
};
} else if ('options' in settings) {
return settings.options;
}
return null;
}
/*
* Generate a list of <option> tags for the given table.
*/
function generateAvailableFilterList(tableKey) {
var remaining = getRemainingTableFilters(tableKey);
var id = 'filter-tag-' + tableKey.toLowerCase();
var html = `<select class='form-control filter-input' id='${id}' name='tag'>`;
html += `<option value=''>Select filter</option>`;
for (var opt in remaining) {
var title = getFilterTitle(tableKey, opt);
html += `<option value='${opt}'>${title}</option>`;
}
html += `</select>`;
return html;
}
/*
* Generate an input for setting the value of a given filter.
*/
function generateFilterInput(tableKey, filterKey) {
var id = 'filter-value-' + tableKey.toLowerCase();
if (filterKey == null || filterKey.length == 0) {
// Return an 'empty' element
return `<div class='filter-input' id='${id}'></div>`;
}
var options = getFilterOptionList(tableKey, filterKey);
var html = '';
// A 'null' options list means that a simple text-input dialog should be used
if (options == null) {
html = `<input class='form-control filter-input' id='${id}' name='value'></input>`;
} else {
// Return a 'select' input with the available values
html = `<select class='form-control filter-input' id='${id}' name='value'>`;
for (var key in options) {
option = options[key];
html += `<option value='${key}'>${option.value}</option>`;
}
html += `</select>`;
}
return html;
}
/**
* Configure a filter list for a given table
*
* @param {*} tableKey - string lookup key for filter settings
* @param {*} table - bootstrapTable element to update
* @param {*} target - name of target element on page
*/
function setupFilterList(tableKey, table, target) {
var addClicked = false;
if (target == null || target.length == 0) {
target = `#filter-list-${tableKey}`;
}
var tag = `filter-tag-${tableKey}`;
var add = `filter-add-${tableKey}`;
var clear = `filter-clear-${tableKey}`;
var make = `filter-make-${tableKey}`;
console.log(`Generating filter list: ${tableKey}`);
var filters = loadTableFilters(tableKey);
console.log("Filters: " + filters.count);
var element = $(target);
// One blank slate, please
element.empty();
element.append(`<button id='${add}' title='Add new filter' class='btn btn-default filter-tag'><span class='fas fa-filter'></span></button>`);
if (Object.keys(filters).length > 0) {
element.append(`<button id='${clear}' title='Clear all filters' class='btn btn-default filter-tag'><span class='fas fa-trash-alt'></span></button>`);
}
for (var key in filters) {
var value = getFilterOptionValue(tableKey, key, filters[key]);
var title = getFilterTitle(tableKey, key);
element.append(`<div class='filter-tag'>${title} = ${value}<span ${tag}='${key}' class='close'>x</span></div>`);
}
// Add a callback for adding a new filter
element.find(`#${add}`).click(function clicked() {
if (!addClicked) {
addClicked = true;
var html = '';
//`<div class='filter-input'>`;
html += generateAvailableFilterList(tableKey);
html += generateFilterInput(tableKey);
html += `<button title='Create filter' class='btn btn-default filter-tag' id='${make}'><span class='fas fa-plus'></span></button>`;
//html += '</div>';
element.append(html);
// Add a callback for when the filter tag selection is changed
element.find(`#filter-tag-${tableKey}`).on('change', function() {
var list = element.find(`#filter-value-${tableKey}`);
list.replaceWith(generateFilterInput(tableKey, this.value));
});
// Add a callback for when the new filter is created
element.find(`#filter-make-${tableKey}`).click(function() {
var tag = element.find(`#filter-tag-${tableKey}`).val();
var val = element.find(`#filter-value-${tableKey}`).val();
// Only add the new filter if it is not empty!
if (tag && tag.length > 0) {
var filters = addTableFilter(tableKey, tag, val);
reloadTable(table, filters);
// Run this function again
setupFilterList(tableKey, table, target);
}
});
} else {
addClicked = false;
setupFilterList(tableKey, table, target);
}
});
// Add a callback for clearing all the filters
element.find(`#${clear}`).click(function() {
var filters = clearTableFilters(tableKey);
reloadTable(table, filters);
setupFilterList(tableKey, table, target);
});
// Add callback for deleting each filter
element.find(".close").click(function(event) {
var me = $(this);
var filter = me.attr(`filter-tag-${tableKey}`);
var filters = removeTableFilter(tableKey, filter);
reloadTable(table, filters);
// Run this function again!
setupFilterList(tableKey, table, target);
});
}
/**
* Return the pretty title for the given table and filter selection.
* If no title is provided, default to the key value.
*
*/
function getFilterTitle(tableKey, filterKey) {
var settings = getFilterSettings(tableKey, filterKey);
return settings.title || filterKey;
}
/*
* Return a description for the given table and filter selection.
*/
function getFilterDescription(tableKey, filterKey) {
var settings = getFilterSettings(tableKey, filterKey);
return settings.description || filterKey;
}
/*
* Return the display value for a particular option
*/
function getFilterOptionValue(tableKey, filterKey, valueKey) {
var filter = getFilterSettings(tableKey, filterKey);
var value = String(valueKey);
// Lookup for boolean options
if (filter.type == 'bool') {
if (value == '1') return 'true';
if (value == '0') return 'false';
return value;
}
// Iterate through a list of options
if ('options' in filter) {
for (var key in filter.options) {
if (key == valueKey) {
return filter.options[key].value;
}
}
// Could not find a match
return value;
}
// Cannot map to a display string - return the original text
return value;
}

View File

@ -104,8 +104,21 @@ function removePurchaseOrderLineItem(e) {
function loadPurchaseOrderTable(table, options) { function loadPurchaseOrderTable(table, options) {
/* Create a purchase-order table */ /* Create a purchase-order table */
var params = options.params || {};
var filters = loadTableFilters("order");
for (var key in params) {
filters[key] = params[key];
}
setupFilterList("order", table);
table.inventreeTable({ table.inventreeTable({
url: options.url, url: options.url,
queryParams: filters,
groupBy: false,
original: params,
formatNoMatches: function() { return "No purchase orders found"; }, formatNoMatches: function() { return "No purchase orders found"; },
columns: [ columns: [
{ {
@ -144,7 +157,7 @@ function loadPurchaseOrderTable(table, options) {
field: 'status', field: 'status',
title: 'Status', title: 'Status',
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
return orderStatusLabel(row.status, row.status_text); return orderStatusDisplay(row.status, row.status_text);
} }
}, },
{ {
@ -155,39 +168,3 @@ function loadPurchaseOrderTable(table, options) {
], ],
}); });
} }
function orderStatusLabel(code, label) {
/* Render a purchase-order status label. */
var html = "<span class='label";
switch (code) {
case 10: // pending
html += " label-info";
break;
case 20: // placed
html += " label-primary";
break;
case 30: // complete
html += " label-success";
break;
case 40: // cancelled
case 50: // lost
html += " label-warning";
break;
case 60: // returned
html += " label-danger";
break;
default:
break;
}
html += "'>";
html += label;
html += "</span>";
console.log(html);
return html;
}

View File

@ -87,17 +87,15 @@ function loadPartTable(table, url, options={}) {
* buttons: If provided, link buttons to selection status of this table * buttons: If provided, link buttons to selection status of this table
*/ */
// Default query params var params = options.parms || {};
query = options.query;
var filters = loadTableFilters("parts");
if (!options.allowInactive) {
// Only display active parts for (var key in params) {
query.active = true; filters[key] = params[key];
} }
// Include sub-category search setupFilterList("parts", $(table));
// TODO - Make this user-configurable!
query.cascade = true;
var columns = [ var columns = [
{ {
@ -142,11 +140,21 @@ function loadPartTable(table, url, options={}) {
var display = imageHoverIcon(row.thumbnail) + renderLink(name, '/part/' + row.pk + '/'); var display = imageHoverIcon(row.thumbnail) + renderLink(name, '/part/' + row.pk + '/');
if (row.is_template) { if (row.is_template) {
display = display + "<span class='label label-info' style='float: right;'>TEMPLATE</span>"; display += `<span class='fas fa-clone label-right' title='Template part'></span>`;
}
if (row.assembly) {
display += `<span class='fas fa-tools label-right' title='Assembled part'></span>`;
} }
/*
if (row.component) {
display = display + `<span class='fas fa-cogs label-right' title='Component part'></span>`;
}
*/
if (!row.active) { if (!row.active) {
display = display + "<span class='label label-warning' style='float: right;'>INACTIVE</span>"; display += `<span class='label label-warning label-right'>INACTIVE</span>`;
} }
return display; return display;
} }
@ -175,7 +183,7 @@ function loadPartTable(table, url, options={}) {
return renderLink(row.category__name, "/part/category/" + row.category + "/"); return renderLink(row.category__name, "/part/category/" + row.category + "/");
} }
else { else {
return ''; return 'No category';
} }
} }
}); });
@ -217,10 +225,10 @@ function loadPartTable(table, url, options={}) {
url: url, url: url,
sortName: 'name', sortName: 'name',
method: 'get', method: 'get',
queryParams: filters,
groupBy: false,
original: params,
formatNoMatches: function() { return "No parts found"; }, formatNoMatches: function() { return "No parts found"; },
queryParams: function(p) {
return query;
},
columns: columns, columns: columns,
}); });

View File

@ -14,6 +14,7 @@ function getStockLocations(filters={}, options={}) {
return inventreeGet('/api/stock/location/', filters, options) return inventreeGet('/api/stock/location/', filters, options)
} }
/* Functions for interacting with stock management forms /* Functions for interacting with stock management forms
*/ */
@ -28,6 +29,7 @@ function removeStockRow(e) {
$('#' + row).remove(); $('#' + row).remove();
} }
function loadStockTable(table, options) { function loadStockTable(table, options) {
/* Load data into a stock table with adjustable options. /* Load data into a stock table with adjustable options.
* Fetches data (via AJAX) and loads into a bootstrap table. * Fetches data (via AJAX) and loads into a bootstrap table.
@ -38,23 +40,39 @@ function loadStockTable(table, options) {
* params - query params for augmenting stock data request * params - query params for augmenting stock data request
* groupByField - Column for grouping stock items * groupByField - Column for grouping stock items
* buttons - Which buttons to link to stock selection callbacks * buttons - Which buttons to link to stock selection callbacks
* filterList - <ul> element where filters are displayed
*/ */
// List of user-params which override the default filters
var params = options.params || {}; var params = options.params || {};
// Enforce 'cascade' option var filterListElement = options.filterList || "#filter-list-stock";
// TODO - Make this user-configurable?
params.cascade = true;
console.log('load stock table'); var filters = loadTableFilters("stock");
var original = {};
for (var key in params) {
original[key] = params[key];
}
setupFilterList("stock", table, filterListElement);
// Override the default values, or add new ones
for (var key in params) {
filters[key] = params[key];
}
table.inventreeTable({ table.inventreeTable({
method: 'get', method: 'get',
formatNoMatches: function() { formatNoMatches: function() {
return 'No stock items matching query'; return 'No stock items matching query';
}, },
url: options.url,
queryParams: filters,
customSort: customGroupSorter, customSort: customGroupSorter,
groupBy: true, groupBy: true,
original: original,
groupByField: options.groupByField || 'part', groupByField: options.groupByField || 'part',
groupByFormatter: function(field, id, data) { groupByFormatter: function(field, id, data) {
@ -87,6 +105,29 @@ function loadStockTable(table, options) {
stock = +stock.toFixed(5); stock = +stock.toFixed(5);
return stock + " (" + items + " items)"; return stock + " (" + items + " items)";
} else if (field == 'status') {
var statii = [];
data.forEach(function(item) {
var status = String(item.status);
if (!status || status == '') {
status = '-';
}
if (!statii.includes(status)) {
statii.push(status);
}
});
// Multiple status codes
if (statii.length > 1) {
return "-";
} else if (statii.length == 1) {
return stockStatusDisplay(statii[0]);
} else {
return "-";
}
} else if (field == 'batch') { } else if (field == 'batch') {
var batches = []; var batches = [];
@ -211,13 +252,17 @@ function loadStockTable(table, options) {
var text = renderLink(val, '/stock/item/' + row.pk + '/'); var text = renderLink(val, '/stock/item/' + row.pk + '/');
if (row.status_text != 'OK') {
text = text + "<span class='badge'>" + row.status_text + "</span>";
}
return text; return text;
} }
}, },
{
field: 'status',
title: 'Status',
sortable: 'true',
formatter: function(value, row, index, field) {
return stockStatusDisplay(value);
},
},
{ {
field: 'batch', field: 'batch',
title: 'Batch', title: 'Batch',
@ -241,8 +286,6 @@ function loadStockTable(table, options) {
title: 'Notes', title: 'Notes',
} }
], ],
url: options.url,
queryParams: params,
}); });
if (options.buttons) { if (options.buttons) {

View File

@ -44,6 +44,42 @@ function isNumeric(n) {
} }
/*
* Reload a table which has already been made into a bootstrap table.
* New filters can be optionally provided, to change the query params.
*/
function reloadTable(table, filters) {
// Simply perform a refresh
if (filters == null) {
table.bootstrapTable('refresh');
return;
}
// More complex refresh with new filters supplied
var options = table.bootstrapTable('getOptions');
// Construct a new list of filters to use for the query
var params = {};
for (var key in filters) {
params[key] = filters[key];
}
// Original query params will override
if (options.original != null) {
for (var key in options.original) {
params[key] = options.original[key];
}
}
options.queryParams = params;
table.bootstrapTable('refreshOptions', options);
table.bootstrapTable('refresh');
}
/* Wrapper function for bootstrapTable. /* Wrapper function for bootstrapTable.
* Sets some useful defaults, and manage persistent settings. * Sets some useful defaults, and manage persistent settings.
*/ */

View File

@ -2,6 +2,56 @@ from django.utils.translation import ugettext as _
class StatusCode: class StatusCode:
"""
Base class for representing a set of StatusCodes.
This is used to map a set of integer values to text.
"""
labels = {}
@classmethod
def render(cls, key):
"""
Render the value as a label.
"""
print("Rendering:", key, cls.options)
# If the key cannot be found, pass it back
if key not in cls.options.keys():
return key
value = cls.options.get(key, key)
label = cls.labels.get(key, None)
if label:
return "<span class='label label-{label}'>{value}</span>".format(label=label, value=value)
else:
return value
@classmethod
def list(cls):
"""
Return the StatusCode options as a list of mapped key / value items
"""
codes = []
for key in cls.options.keys():
opt = {
'key': key,
'value': cls.options[key]
}
label = cls.labels.get(key)
if label:
opt['label'] = label
codes.append(opt)
return codes
@classmethod @classmethod
def items(cls): def items(cls):
@ -41,6 +91,15 @@ class OrderStatus(StatusCode):
RETURNED: _("Returned"), RETURNED: _("Returned"),
} }
labels = {
PENDING: "primary",
PLACED: "primary",
COMPLETE: "success",
CANCELLED: "danger",
LOST: "warning",
RETURNED: "warning",
}
# Open orders # Open orders
OPEN = [ OPEN = [
PENDING, PENDING,
@ -71,6 +130,12 @@ class StockStatus(StatusCode):
LOST: _("Lost"), LOST: _("Lost"),
} }
labels = {
OK: 'success',
ATTENTION: 'warning',
DAMAGED: 'danger',
}
# The following codes correspond to parts that are 'available' or 'in stock' # The following codes correspond to parts that are 'available' or 'in stock'
AVAILABLE_CODES = [ AVAILABLE_CODES = [
OK, OK,
@ -100,6 +165,13 @@ class BuildStatus(StatusCode):
COMPLETE: _("Complete"), COMPLETE: _("Complete"),
} }
labels = {
PENDING: 'primary',
ALLOCATED: 'info',
COMPLETE: 'success',
CANCELLED: 'danger',
}
ACTIVE_CODES = [ ACTIVE_CODES = [
PENDING, PENDING,
ALLOCATED ALLOCATED

View File

@ -11,6 +11,8 @@ from rest_framework import generics, permissions
from django.conf.urls import url, include from django.conf.urls import url, include
from InvenTree.helpers import str2bool
from .models import Build, BuildItem from .models import Build, BuildItem
from .serializers import BuildSerializer, BuildItemSerializer from .serializers import BuildSerializer, BuildItemSerializer
@ -36,9 +38,41 @@ class BuildList(generics.ListCreateAPIView):
] ]
filter_fields = [ filter_fields = [
'part',
] ]
def get_queryset(self):
"""
Override the queryset filtering,
as some of the fields don't natively play nicely with DRF
"""
build_list = super().get_queryset()
# Filter by part
part = self.request.query_params.get('part', None)
if part is not None:
build_list = build_list.filter(part=part)
# Filter by build status?
status = self.request.query_params.get('status', None)
if status is not None:
build_list = build_list.filter(status=status)
return build_list
def get_serializer(self, *args, **kwargs):
try:
part_detail = str2bool(self.request.GET.get('part_detail', None))
except AttributeError:
part_detail = None
kwargs['part_detail'] = part_detail
return self.serializer_class(*args, **kwargs)
class BuildDetail(generics.RetrieveUpdateAPIView): class BuildDetail(generics.RetrieveUpdateAPIView):
""" API endpoint for detail view of a Build object """ """ API endpoint for detail view of a Build object """

View File

@ -10,6 +10,7 @@ from InvenTree.serializers import InvenTreeModelSerializer
from stock.serializers import StockItemSerializerBrief from stock.serializers import StockItemSerializerBrief
from .models import Build, BuildItem from .models import Build, BuildItem
from part.serializers import PartBriefSerializer
class BuildSerializer(InvenTreeModelSerializer): class BuildSerializer(InvenTreeModelSerializer):
@ -18,6 +19,16 @@ class BuildSerializer(InvenTreeModelSerializer):
url = serializers.CharField(source='get_absolute_url', read_only=True) url = serializers.CharField(source='get_absolute_url', read_only=True)
status_text = serializers.CharField(source='get_status_display', read_only=True) status_text = serializers.CharField(source='get_status_display', read_only=True)
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
def __init__(self, *args, **kwargs):
part_detail = kwargs.pop('part_detail', False)
super().__init__(*args, **kwargs)
if part_detail is not True:
self.fields.pop('part_detail')
class Meta: class Meta:
model = Build model = Build
fields = [ fields = [
@ -27,6 +38,7 @@ class BuildSerializer(InvenTreeModelSerializer):
'creation_date', 'creation_date',
'completion_date', 'completion_date',
'part', 'part',
'part_detail',
'quantity', 'quantity',
'status', 'status',
'status_text', 'status_text',

View File

@ -1,6 +1,8 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% load i18n %}
{% load status_codes %}
{% block page_title %} {% block page_title %}
InvenTree | Build - {{ build }} InvenTree | Build - {{ build }}
@ -22,61 +24,70 @@ InvenTree | Build - {{ build }}
</div> </div>
</div> </div>
<div class='media-body'> <div class='media-body'>
<h4>Build Details</h4> <h4>{% trans "Build" %}</h4>
<div class='btn-row'>
<p> <div class='btn-group'>
<div class='btn-row'> <button type='button' class='btn btn-default btn-glyph' id='build-edit' title='Edit Build'>
<div class='btn-group'> <span class='glyphicon glyphicon-edit'/>
<button type='button' class='btn btn-default btn-glyph' id='build-edit' title='Edit Build'> </button>
<span class='glyphicon glyphicon-edit'/> {% if build.is_active %}
</button> <button type='button' class='btn btn-default btn-glyph' id='build-complete' title="Complete Build">
{% if build.is_active %} <span class='glyphicon glyphicon-send'/>
<button type='button' class='btn btn-default btn-glyph' id='build-complete' title="Complete Build"> </button>
<span class='glyphicon glyphicon-send'/> <button type='button' class='btn btn-default btn-glyph' id='build-cancel' title='Cancel Build'>
</button> <span class='glyphicon glyphicon-remove'/>
<button type='button' class='btn btn-default btn-glyph' id='build-cancel' title='Cancel Build'> </button>
<span class='glyphicon glyphicon-remove'/> {% endif %}
</button> {% if build.status == BuildStatus.CANCELLED %}
{% endif %} <button type='button' class='btn btn-default btn-glyph' id='build-delete' title='Delete Build'>
{% if build.status == BuildStatus.CANCELLED %} <span class='glyphicon glyphicon-trash'/>
<button type='button' class='btn btn-default btn-glyph' id='build-delete' title='Delete Build'> </button>
<span class='glyphicon glyphicon-trash'/> {% endif %}
</button>
{% endif %}
</div>
</div> </div>
</p> </div>
<table class='table table-striped table-condensed'>
<tr>
<td>{{ build.title }}</td>
<td>{% include "build_status.html" with build=build %}</td>
</tr>
<tr>
<td>Part</td>
<td><a href="{% url 'part-detail' build.part.id %}">{{ build.part.full_name }}</a></td>
</tr>
<tr>
<td>Quantity</td>
<td>{{ build.quantity }}</td>
</tr>
<tr>
<td>BOM Price</td>
<td>
{% if bom_price %}
{{ bom_price }}
{% if build.part.has_complete_bom_pricing == False %}
<br><span class='warning-msg'><i>BOM pricing is incomplete</i></span>
{% endif %}
{% else %}
<span class='warning-msg'><i>No pricing information</i></span>
{% endif %}
</td>
</tr>
</table>
</div> </div>
</div> </div>
</div> </div>
<div class='col-sm-6'>
<h4>{% trans "Build Details" %}</h4>
<table class='table table-striped table-condensed'>
<tr>
<td></td>
<td>{% trans "Build Title" %}</td>
<td>{{ build.title }}</td>
</tr>
<tr>
<td><span class='fas fa-shapes'></span></td>
<td>Part</td>
<td><a href="{% url 'part-detail' build.part.id %}">{{ build.part.full_name }}</a></td>
</tr>
<tr>
<td></td>
<td>{% trans "Quantity" %}</td>
<td>{{ build.quantity }}</td>
</tr>
<tr>
<td><span class='fas fa-info'></span></td>
<td>{% trans "Status" %}</td>
<td>{% build_status build.status %}</td>
</tr>
<tr>
<td><span class='fas fa-dollar-sign'></span></td>
<td>{% trans "BOM Price" %}</td>
<td>
{% if bom_price %}
{{ bom_price }}
{% if build.part.has_complete_bom_pricing == False %}
<br><span class='warning-msg'><i>{% trans "BOM pricing is incomplete" %}</i></span>
{% endif %}
{% else %}
<span class='warning-msg'><i>{% trans "No pricing information" %}</i></span>
{% endif %}
</td>
</tr>
</table>
</div>
</div>
</div> </div>
<hr> <hr>

View File

@ -1,38 +0,0 @@
{% extends "collapse.html" %}
{% block collapse_title %}
<b>{{ title }}</b> - {{ builds | length }}
{% endblock %}
{% block collapse_content %}
<table class='table table-striped table-condensed build-table' id='build-table-{{collapse_id}}' data-toolbar='#button-toolbar'>
<thead>
<tr>
<th>Build</th>
<th>Part</th>
<th>Quantity</th>
<th>Status</th>
{% if completed %}
<th>Completed</th>
{% else %}
<th>Created</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for build in builds %}
<tr>
<td><a href="{% url 'build-detail' build.id %}">{{ build.title }}</a></td>
<td><a href="{% url 'part-build' build.part.id %}">{{ build.part.full_name }}</a></td>
<td>{{ build.quantity }}</td>
<td>{% include "build_status.html" with build=build %}
{% if completed %}
<td>{{ build.completion_date }}<span class='badge'>{{ build.completed_by.username }}</span></td>
{% else %}
<td>{{ build.creation_date }}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -2,6 +2,7 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% block details %} {% block details %}
{% load status_codes %}
{% include "build/tabs.html" with tab='details' %} {% include "build/tabs.html" with tab='details' %}
@ -39,7 +40,7 @@
<tr> <tr>
<td><span class='fas fa-info'></span></td> <td><span class='fas fa-info'></span></td>
<td>{% trans "Status" %}</td> <td>{% trans "Status" %}</td>
<td>{% include "build_status.html" with build=build %}</td> <td>{% build_status build.status %}</td>
</tr> </tr>
{% if build.batch %} {% if build.batch %}
<tr> <tr>

View File

@ -13,21 +13,23 @@ InvenTree | Build List
<h3>Part Builds</h3> <h3>Part Builds</h3>
</div> </div>
<div class='col-sm-6'> <div class='col-sm-6'>
<div class='container' id='active-build-toolbar' style='float: right;'>
<div class='btn-group' style='float: right;'> </div>
<button type='button' class="btn btn-success" id='new-build'>Start New Build</button>
<hr>
<div id='button-toolbar'>
<div class='button-toolbar container-fluid' style='float: right;'>
<button type='button' class="btn btn-success" id='new-build'>Start New Build</button>
<div class='filter-list' id='filter-list-build'>
<!-- An empty div in which the filter list will be constructed -->
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<hr> <table class='table table-striped table-condensed' id='build-table' data-toolbar='#button-toolbar'>
</table>
{% include "build/build_list.html" with builds=active title="Active Builds" completed=False collapse_id='active' %}
{% include "build/build_list.html" with builds=completed completed=True title="Completed Builds" collapse_id="complete" %}
{% include "build/build_list.html" with builds=cancelled title="Cancelled Builds" completed=False collapse_id="cancelled" %}
{% endblock %} {% endblock %}
@ -38,38 +40,18 @@ InvenTree | Build List
$("#new-build").click(function() { $("#new-build").click(function() {
launchModalForm( launchModalForm(
"{% url 'build-create' %}", "{% url 'build-create' %}",
{ {
follow: true follow: true
}); }
);
}); });
$(".build-table").inventreeTable({ loadBuildTable($("#build-table"), {
formatNoMatches: function() { return 'No builds found'; }, url: "{% url 'api-build-list' %}",
columns: [ params: {
{ part_detail: "true",
field: 'name', },
title: 'Build',
sortable: true,
},
{
field: 'part',
title: 'Part',
sortable: true,
},
{
title: 'Quantity',
sortable: true,
searchable: false
},
{
title: 'Status',
sortable: true,
},
{
sortable: true,
},
]
}); });
{% endblock %} {% endblock %}

View File

@ -143,9 +143,7 @@ class TestBuildViews(TestCase):
content = str(response.content) content = str(response.content)
# Content should contain build titles self.assertIn("Part Builds", content)
for build in Build.objects.all():
self.assertIn(build.title, content)
def test_build_detail(self): def test_build_detail(self):
""" Test the detail view for a Build object """ """ Test the detail view for a Build object """

View File

@ -30,21 +30,21 @@
$("#part-create").click(function () { $("#part-create").click(function () {
launchModalForm( launchModalForm(
"{% url 'supplier-part-create' %}", "{% url 'supplier-part-create' %}",
{ {
data: { data: {
supplier: {{ company.id }} supplier: {{ company.id }}
}, },
reload: true, reload: true,
secondary: [ secondary: [
{ {
field: 'part', field: 'part',
label: 'New Part', label: 'New Part',
title: 'Create New Part', title: 'Create New Part',
url: "{% url 'part-create' %}" url: "{% url 'part-create' %}"
}, },
] ]
}); });
}); });
$("#part-table").inventreeTable({ $("#part-table").inventreeTable({
@ -64,7 +64,22 @@
field: 'part_detail.full_name', field: 'part_detail.full_name',
title: '{% trans "Part" %}', title: '{% trans "Part" %}',
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
return imageHoverIcon(row.part_detail.image_url) + renderLink(value, '/part/' + row.part + '/suppliers/');
var html = imageHoverIcon(row.part_detail.thumbnail) + renderLink(value, '/part/' + row.part + '/suppliers/');
if (row.part_detail.is_template) {
html += `<span class='fas fa-clone label-right' title='Template part'></span>`;
}
if (row.part_detail.assembly) {
html += `<span class='fas fa-tools label-right' title='Assembled part'></span>`;
}
if (!row.part_detail.active) {
html += `<span class='label label-warning label-right'>INACTIVE</span>`;
}
return html;
} }
}, },
{ {

View File

@ -9,8 +9,11 @@
<hr> <hr>
<div id='button-bar'> <div id='button-bar'>
<div class='btn-group'> <div class='button-toolbar container-fluid' style='float: right;'>
<button class='btn btn-primary' type='button' id='company-order2' title='Create new purchase order'>{% trans "New Purchase Order" %}</button> <button class='btn btn-primary' type='button' id='company-order2' title='Create new purchase order'>{% trans "New Purchase Order" %}</button>
<div class='filter-list' id='filter-list-order'>
<!-- Empty div -->
</div>
</div> </div>
</div> </div>

View File

@ -1,7 +0,0 @@
{% for order in orders %}
<tr>
<td>{{ order }}</td>
<td>{{ order.description }}</td>
<td>{% include "order/order_status.html" with order=order %}</td>
</tr>
{% endfor %}

View File

@ -96,8 +96,8 @@ class CompanySimpleTest(TestCase):
def test_part_pricing(self): def test_part_pricing(self):
m2x4 = Part.objects.get(name='M2x4 LPHS') m2x4 = Part.objects.get(name='M2x4 LPHS')
self.assertEqual(m2x4.get_price_info(10), "70.00000 - 75.00000") self.assertEqual(m2x4.get_price_info(10), "70 - 75")
self.assertEqual(m2x4.get_price_info(100), "125.00000 - 350.00000") self.assertEqual(m2x4.get_price_info(100), "125 - 350")
pmin, pmax = m2x4.get_price_range(5) pmin, pmax = m2x4.get_price_range(5)
self.assertEqual(pmin, 35) self.assertEqual(pmin, 35)

View File

@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-09 15:04+0000\n" "POT-Creation-Date: 2020-04-11 15:00+0000\n"
"PO-Revision-Date: 2020-02-02 08:07+0100\n" "PO-Revision-Date: 2020-02-02 08:07+0100\n"
"Last-Translator: Christian Schlüter <chschlue@gmail.com>\n" "Last-Translator: Christian Schlüter <chschlue@gmail.com>\n"
"Language-Team: C <kde-i18n-doc@kde.org>\n" "Language-Team: C <kde-i18n-doc@kde.org>\n"
@ -17,30 +17,30 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 19.12.0\n" "X-Generator: Lokalize 19.12.0\n"
#: InvenTree/helpers.py:240 order/models.py:164 order/models.py:215 #: InvenTree/helpers.py:259 order/models.py:164 order/models.py:215
msgid "Invalid quantity provided" msgid "Invalid quantity provided"
msgstr "Keine gültige Menge" msgstr "Keine gültige Menge"
#: InvenTree/helpers.py:243 #: InvenTree/helpers.py:262
msgid "Empty serial number string" msgid "Empty serial number string"
msgstr "Keine Seriennummer angegeben" msgstr "Keine Seriennummer angegeben"
#: InvenTree/helpers.py:264 InvenTree/helpers.py:281 #: InvenTree/helpers.py:283 InvenTree/helpers.py:300
#, python-brace-format #, python-brace-format
msgid "Duplicate serial: {n}" msgid "Duplicate serial: {n}"
msgstr "Doppelte Seriennummer: {n}" msgstr "Doppelte Seriennummer: {n}"
#: InvenTree/helpers.py:268 InvenTree/helpers.py:271 InvenTree/helpers.py:274 #: InvenTree/helpers.py:287 InvenTree/helpers.py:290 InvenTree/helpers.py:293
#: InvenTree/helpers.py:285 #: InvenTree/helpers.py:304
#, python-brace-format #, python-brace-format
msgid "Invalid group: {g}" msgid "Invalid group: {g}"
msgstr "Ungültige Gruppe: {g}" msgstr "Ungültige Gruppe: {g}"
#: InvenTree/helpers.py:291 #: InvenTree/helpers.py:310
msgid "No serial numbers found" msgid "No serial numbers found"
msgstr "Keine Seriennummern gefunden" msgstr "Keine Seriennummern gefunden"
#: InvenTree/helpers.py:295 #: InvenTree/helpers.py:314
#, python-brace-format #, python-brace-format
msgid "Number of unique serial number ({s}) must match quantity ({q})" msgid "Number of unique serial number ({s}) must match quantity ({q})"
msgstr "" msgstr ""
@ -71,49 +71,49 @@ msgstr "Französisch"
msgid "Polish" msgid "Polish"
msgstr "Polnisch" msgstr "Polnisch"
#: InvenTree/status_codes.py:36 InvenTree/status_codes.py:97 #: InvenTree/status_codes.py:86 InvenTree/status_codes.py:162
msgid "Pending" msgid "Pending"
msgstr "Ausstehend" msgstr "Ausstehend"
#: InvenTree/status_codes.py:37 #: InvenTree/status_codes.py:87
msgid "Placed" msgid "Placed"
msgstr "Platziert" msgstr "Platziert"
#: InvenTree/status_codes.py:38 InvenTree/status_codes.py:100 #: InvenTree/status_codes.py:88 InvenTree/status_codes.py:165
msgid "Complete" msgid "Complete"
msgstr "Fertig" msgstr "Fertig"
#: InvenTree/status_codes.py:39 InvenTree/status_codes.py:99 #: InvenTree/status_codes.py:89 InvenTree/status_codes.py:164
msgid "Cancelled" msgid "Cancelled"
msgstr "Storniert" msgstr "Storniert"
#: InvenTree/status_codes.py:40 InvenTree/status_codes.py:71 #: InvenTree/status_codes.py:90 InvenTree/status_codes.py:130
msgid "Lost" msgid "Lost"
msgstr "Verloren" msgstr "Verloren"
#: InvenTree/status_codes.py:41 #: InvenTree/status_codes.py:91
msgid "Returned" msgid "Returned"
msgstr "Zurückgegeben" msgstr "Zurückgegeben"
#: InvenTree/status_codes.py:67 #: InvenTree/status_codes.py:126
msgid "OK" msgid "OK"
msgstr "OK" msgstr "OK"
#: InvenTree/status_codes.py:68 #: InvenTree/status_codes.py:127
msgid "Attention needed" msgid "Attention needed"
msgstr "erfordert Eingriff" msgstr "erfordert Eingriff"
#: InvenTree/status_codes.py:69 #: InvenTree/status_codes.py:128
msgid "Damaged" msgid "Damaged"
msgstr "Beschädigt" msgstr "Beschädigt"
#: InvenTree/status_codes.py:70 #: InvenTree/status_codes.py:129
msgid "Destroyed" msgid "Destroyed"
msgstr "Zerstört" msgstr "Zerstört"
#: InvenTree/status_codes.py:98 build/templates/build/allocate_edit.html:28 #: InvenTree/status_codes.py:163 build/templates/build/allocate_edit.html:28
#: build/templates/build/allocate_view.html:21 #: build/templates/build/allocate_view.html:21
#: part/templates/part/part_base.html:109 part/templates/part/tabs.html:21 #: part/templates/part/part_base.html:114 part/templates/part/tabs.html:21
msgid "Allocated" msgid "Allocated"
msgstr "Zugeordnet" msgstr "Zugeordnet"
@ -178,7 +178,7 @@ msgstr ""
msgid "Number of parts to build" msgid "Number of parts to build"
msgstr "Anzahl der zu bauenden Teile" msgstr "Anzahl der zu bauenden Teile"
#: build/models.py:82 #: build/models.py:82 templates/table_filters.html:42
msgid "Build status" msgid "Build status"
msgstr "Bau-Status" msgstr "Bau-Status"
@ -231,10 +231,10 @@ msgstr "Zuweisung aufheben"
#: build/templates/build/allocate_edit.html:19 #: build/templates/build/allocate_edit.html:19
#: build/templates/build/allocate_view.html:17 #: build/templates/build/allocate_view.html:17
#: build/templates/build/detail.html:21 #: build/templates/build/detail.html:22
#: company/templates/company/detail_part.html:65 #: company/templates/company/detail_part.html:65
#: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/order_wizard/select_parts.html:30
#: order/templates/order/purchase_order_detail.html:25 #: order/templates/order/purchase_order_detail.html:26
#: part/templates/part/part_app_base.html:7 #: part/templates/part/part_app_base.html:7
msgid "Part" msgid "Part"
msgstr "Teil" msgstr "Teil"
@ -266,16 +266,62 @@ msgstr "Teile bestellen"
#: company/templates/company/index.html:54 #: company/templates/company/index.html:54
#: company/templates/company/supplier_part_base.html:50 #: company/templates/company/supplier_part_base.html:50
#: company/templates/company/supplier_part_detail.html:27 #: company/templates/company/supplier_part_detail.html:27
#: order/templates/order/purchase_order_detail.html:26 #: order/templates/order/purchase_order_detail.html:27
#: part/templates/part/detail.html:38 #: part/templates/part/detail.html:38
msgid "Description" msgid "Description"
msgstr "Beschreibung" msgstr "Beschreibung"
#: build/templates/build/allocate_view.html:22 #: build/templates/build/allocate_view.html:22
#: part/templates/part/part_base.html:115 #: part/templates/part/part_base.html:121
msgid "On Order" msgid "On Order"
msgstr "bestellt" msgstr "bestellt"
#: build/templates/build/build_base.html:27 part/templates/part/tabs.html:28
#: stock/templates/stock/item_base.html:122 templates/navbar.html:12
msgid "Build"
msgstr "Bau"
#: build/templates/build/build_base.html:52 build/templates/build/detail.html:9
msgid "Build Details"
msgstr "Bau-Status"
#: build/templates/build/build_base.html:56
#, fuzzy
#| msgid "Build Notes"
msgid "Build Title"
msgstr "Bau-Bemerkungen"
#: build/templates/build/build_base.html:66
#: build/templates/build/detail.html:27
#: company/templates/company/supplier_part_pricing.html:27
#: order/templates/order/order_wizard/select_parts.html:32
#: order/templates/order/purchase_order_detail.html:30
#: stock/templates/stock/item_base.html:108
#: stock/templates/stock/stock_adjust.html:18
msgid "Quantity"
msgstr "Anzahl"
#: build/templates/build/build_base.html:71
#: build/templates/build/detail.html:42
#: order/templates/order/order_base.html:72
#: stock/templates/stock/item_base.html:175
msgid "Status"
msgstr "Status"
#: build/templates/build/build_base.html:76
msgid "BOM Price"
msgstr ""
#: build/templates/build/build_base.html:81
msgid "BOM pricing is incomplete"
msgstr ""
#: build/templates/build/build_base.html:84
#, fuzzy
#| msgid "Show pricing information"
msgid "No pricing information"
msgstr "Kosteninformationen ansehen"
#: build/templates/build/build_output.html:9 #: build/templates/build/build_output.html:9
#, fuzzy #, fuzzy
#| msgid "Build status" #| msgid "Build status"
@ -289,68 +335,49 @@ msgid "Are you sure you want to unallocate these parts?"
msgstr "" msgstr ""
"Sind Sie sicher, dass sie die folgenden Zulieferer-Teile löschen möchten?" "Sind Sie sicher, dass sie die folgenden Zulieferer-Teile löschen möchten?"
#: build/templates/build/detail.html:8 #: build/templates/build/detail.html:17
msgid "Build Details"
msgstr "Bau-Status"
#: build/templates/build/detail.html:16
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
#: build/templates/build/detail.html:26 #: build/templates/build/detail.html:31
#: company/templates/company/supplier_part_pricing.html:27
#: order/templates/order/order_wizard/select_parts.html:32
#: order/templates/order/purchase_order_detail.html:29
#: stock/templates/stock/item_base.html:107
#: stock/templates/stock/stock_adjust.html:18
msgid "Quantity"
msgstr "Anzahl"
#: build/templates/build/detail.html:30
msgid "Stock Source" msgid "Stock Source"
msgstr "Lagerobjekt" msgstr "Lagerobjekt"
#: build/templates/build/detail.html:35 #: build/templates/build/detail.html:36
msgid "Stock can be taken from any available location." msgid "Stock can be taken from any available location."
msgstr "Bestand kann jedem verfügbaren Lagerort entnommen werden." msgstr "Bestand kann jedem verfügbaren Lagerort entnommen werden."
#: build/templates/build/detail.html:41 #: build/templates/build/detail.html:48
#: order/templates/order/order_base.html:71 #: stock/templates/stock/item_base.html:115
#: stock/templates/stock/item_base.html:174
msgid "Status"
msgstr "Status"
#: build/templates/build/detail.html:47
#: stock/templates/stock/item_base.html:114
msgid "Batch" msgid "Batch"
msgstr "Los" msgstr "Los"
#: build/templates/build/detail.html:54 #: build/templates/build/detail.html:55
#: company/templates/company/supplier_part_base.html:47 #: company/templates/company/supplier_part_base.html:47
#: company/templates/company/supplier_part_detail.html:24 #: company/templates/company/supplier_part_detail.html:24
#: part/templates/part/detail.html:67 part/templates/part/part_base.html:84 #: part/templates/part/detail.html:67 part/templates/part/part_base.html:85
#: stock/templates/stock/item_base.html:142 #: stock/templates/stock/item_base.html:143
msgid "External Link" msgid "External Link"
msgstr "" msgstr ""
#: build/templates/build/detail.html:60 #: build/templates/build/detail.html:61
#: order/templates/order/order_base.html:83 #: order/templates/order/order_base.html:84
msgid "Created" msgid "Created"
msgstr "Erstellt" msgstr "Erstellt"
#: build/templates/build/detail.html:66 #: build/templates/build/detail.html:67
msgid "Enough Parts?" msgid "Enough Parts?"
msgstr "Genügend Teile?" msgstr "Genügend Teile?"
#: build/templates/build/detail.html:69 #: build/templates/build/detail.html:70
msgid "Yes" msgid "Yes"
msgstr "Ja" msgstr "Ja"
#: build/templates/build/detail.html:71 #: build/templates/build/detail.html:72
msgid "No" msgid "No"
msgstr "Nein" msgstr "Nein"
#: build/templates/build/detail.html:79 #: build/templates/build/detail.html:80
msgid "Completed" msgid "Completed"
msgstr "Fertig" msgstr "Fertig"
@ -622,7 +649,7 @@ msgstr "Zulieferer auswählen"
msgid "Supplier stock keeping unit" msgid "Supplier stock keeping unit"
msgstr "Stock Keeping Units (SKU) des Zulieferers" msgstr "Stock Keeping Units (SKU) des Zulieferers"
#: company/models.py:256 company/templates/company/detail_part.html:81 #: company/models.py:256 company/templates/company/detail_part.html:96
#: company/templates/company/supplier_part_base.html:53 #: company/templates/company/supplier_part_base.html:53
#: company/templates/company/supplier_part_detail.html:30 #: company/templates/company/supplier_part_detail.html:30
msgid "Manufacturer" msgid "Manufacturer"
@ -680,7 +707,7 @@ msgid "Company Details"
msgstr "Firmenbemerkungen" msgstr "Firmenbemerkungen"
#: company/templates/company/detail.html:16 #: company/templates/company/detail.html:16
#: stock/templates/stock/item_base.html:135 #: stock/templates/stock/item_base.html:136
msgid "Customer" msgid "Customer"
msgstr "Kunde" msgstr "Kunde"
@ -688,9 +715,9 @@ msgstr "Kunde"
#: company/templates/company/index.html:46 #: company/templates/company/index.html:46
#: company/templates/company/supplier_part_base.html:44 #: company/templates/company/supplier_part_base.html:44
#: company/templates/company/supplier_part_detail.html:21 #: company/templates/company/supplier_part_detail.html:21
#: order/templates/order/order_base.html:66 #: order/templates/order/order_base.html:67
#: order/templates/order/order_wizard/select_pos.html:30 #: order/templates/order/order_wizard/select_pos.html:30
#: stock/templates/stock/item_base.html:149 #: stock/templates/stock/item_base.html:150
msgid "Supplier" msgid "Supplier"
msgstr "Zulieferer" msgstr "Zulieferer"
@ -716,13 +743,13 @@ msgstr ""
msgid "Delete Parts" msgid "Delete Parts"
msgstr "Anhang löschen" msgstr "Anhang löschen"
#: company/templates/company/detail_part.html:73 #: company/templates/company/detail_part.html:88
#: company/templates/company/supplier_part_base.html:45 #: company/templates/company/supplier_part_base.html:45
#: company/templates/company/supplier_part_detail.html:22 #: company/templates/company/supplier_part_detail.html:22
msgid "SKU" msgid "SKU"
msgstr "" msgstr ""
#: company/templates/company/detail_part.html:90 #: company/templates/company/detail_part.html:105
msgid "Link" msgid "Link"
msgstr "" msgstr ""
@ -783,7 +810,7 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:6 #: company/templates/company/supplier_part_base.html:6
#: company/templates/company/supplier_part_base.html:13 #: company/templates/company/supplier_part_base.html:13
#: stock/templates/stock/item_base.html:154 #: stock/templates/stock/item_base.html:155
msgid "Supplier Part" msgid "Supplier Part"
msgstr "Zulieferer-Teil" msgstr "Zulieferer-Teil"
@ -810,7 +837,7 @@ msgstr "IPN (Interne Produktnummer)"
#: company/templates/company/supplier_part_base.html:57 #: company/templates/company/supplier_part_base.html:57
#: company/templates/company/supplier_part_detail.html:34 #: company/templates/company/supplier_part_detail.html:34
#: order/templates/order/purchase_order_detail.html:33 #: order/templates/order/purchase_order_detail.html:34
msgid "Note" msgid "Note"
msgstr "Notiz" msgstr "Notiz"
@ -1030,7 +1057,7 @@ msgstr "Position - Referenz"
msgid "Line item notes" msgid "Line item notes"
msgstr "Position - Notizen" msgstr "Position - Notizen"
#: order/models.py:298 stock/templates/stock/item_base.html:128 #: order/models.py:298 stock/templates/stock/item_base.html:129
msgid "Purchase Order" msgid "Purchase Order"
msgstr "Kaufvertrag" msgstr "Kaufvertrag"
@ -1042,16 +1069,16 @@ msgstr "Zulieferer-Teil"
msgid "Number of items received" msgid "Number of items received"
msgstr "Empfangene Objekt-Anzahl" msgstr "Empfangene Objekt-Anzahl"
#: order/templates/order/order_base.html:61 #: order/templates/order/order_base.html:62
msgid "Purchase Order Details" msgid "Purchase Order Details"
msgstr "Bestelldetails" msgstr "Bestelldetails"
#: order/templates/order/order_base.html:89 #: order/templates/order/order_base.html:90
msgid "Issued" msgid "Issued"
msgstr "Aufgegeben" msgstr "Aufgegeben"
#: order/templates/order/order_base.html:96 #: order/templates/order/order_base.html:97
#: order/templates/order/purchase_order_detail.html:31 #: order/templates/order/purchase_order_detail.html:32
msgid "Received" msgid "Received"
msgstr "Empfangen" msgstr "Empfangen"
@ -1152,23 +1179,23 @@ msgid "Are you sure you want to delete this attachment?"
msgstr "" msgstr ""
"Sind Sie sicher, dass sie die folgenden Zulieferer-Teile löschen möchten?" "Sind Sie sicher, dass sie die folgenden Zulieferer-Teile löschen möchten?"
#: order/templates/order/purchase_order_detail.html:15 order/views.py:825 #: order/templates/order/purchase_order_detail.html:16 order/views.py:825
msgid "Add Line Item" msgid "Add Line Item"
msgstr "Position hinzufügen" msgstr "Position hinzufügen"
#: order/templates/order/purchase_order_detail.html:19 #: order/templates/order/purchase_order_detail.html:20
msgid "Order Items" msgid "Order Items"
msgstr "Bestellungspositionen" msgstr "Bestellungspositionen"
#: order/templates/order/purchase_order_detail.html:24 #: order/templates/order/purchase_order_detail.html:25
msgid "Line" msgid "Line"
msgstr "Position" msgstr "Position"
#: order/templates/order/purchase_order_detail.html:27 #: order/templates/order/purchase_order_detail.html:28
msgid "Order Code" msgid "Order Code"
msgstr "Bestellnummer" msgstr "Bestellnummer"
#: order/templates/order/purchase_order_detail.html:28 #: order/templates/order/purchase_order_detail.html:29
msgid "Reference" msgid "Reference"
msgstr "Referenz" msgstr "Referenz"
@ -1475,63 +1502,63 @@ msgstr "Bemerkungen - unterstüzt Markdown-Formatierung"
msgid "Stored BOM checksum" msgid "Stored BOM checksum"
msgstr "Prüfsumme der Stückliste gespeichert" msgstr "Prüfsumme der Stückliste gespeichert"
#: part/models.py:1040 #: part/models.py:1049
msgid "Parameter template name must be unique" msgid "Parameter template name must be unique"
msgstr "Vorlagen-Name des Parameters muss eindeutig sein" msgstr "Vorlagen-Name des Parameters muss eindeutig sein"
#: part/models.py:1045 #: part/models.py:1054
msgid "Parameter Name" msgid "Parameter Name"
msgstr "Name des Parameters" msgstr "Name des Parameters"
#: part/models.py:1047 #: part/models.py:1056
msgid "Parameter Units" msgid "Parameter Units"
msgstr "Parameter Einheit" msgstr "Parameter Einheit"
#: part/models.py:1073 #: part/models.py:1082
msgid "Parent Part" msgid "Parent Part"
msgstr "Ausgangsteil" msgstr "Ausgangsteil"
#: part/models.py:1075 #: part/models.py:1084
msgid "Parameter Template" msgid "Parameter Template"
msgstr "Parameter Vorlage" msgstr "Parameter Vorlage"
#: part/models.py:1077 #: part/models.py:1086
msgid "Parameter Value" msgid "Parameter Value"
msgstr "Parameter Wert" msgstr "Parameter Wert"
#: part/models.py:1101 #: part/models.py:1110
msgid "Select parent part" msgid "Select parent part"
msgstr "Ausgangsteil auswählen" msgstr "Ausgangsteil auswählen"
#: part/models.py:1110 #: part/models.py:1119
msgid "Select part to be used in BOM" msgid "Select part to be used in BOM"
msgstr "Teil für die Nutzung in der Stückliste auswählen" msgstr "Teil für die Nutzung in der Stückliste auswählen"
#: part/models.py:1117 #: part/models.py:1126
msgid "BOM quantity for this BOM item" msgid "BOM quantity for this BOM item"
msgstr "Stücklisten-Anzahl für dieses Stücklisten-Teil" msgstr "Stücklisten-Anzahl für dieses Stücklisten-Teil"
#: part/models.py:1120 #: part/models.py:1129
msgid "Estimated build wastage quantity (absolute or percentage)" msgid "Estimated build wastage quantity (absolute or percentage)"
msgstr "Geschätzter Ausschuss (absolut oder prozentual)" msgstr "Geschätzter Ausschuss (absolut oder prozentual)"
#: part/models.py:1123 #: part/models.py:1132
msgid "BOM item reference" msgid "BOM item reference"
msgstr "Referenz des Objekts auf der Stückliste" msgstr "Referenz des Objekts auf der Stückliste"
#: part/models.py:1126 #: part/models.py:1135
msgid "BOM item notes" msgid "BOM item notes"
msgstr "Notizen zum Stücklisten-Objekt" msgstr "Notizen zum Stücklisten-Objekt"
#: part/models.py:1128 #: part/models.py:1137
msgid "BOM line checksum" msgid "BOM line checksum"
msgstr "Prüfsumme der Stückliste" msgstr "Prüfsumme der Stückliste"
#: part/models.py:1191 #: part/models.py:1200
msgid "Part cannot be added to its own Bill of Materials" msgid "Part cannot be added to its own Bill of Materials"
msgstr "Teil kann nicht zu seiner eigenen Stückliste hinzugefügt werden" msgstr "Teil kann nicht zu seiner eigenen Stückliste hinzugefügt werden"
#: part/models.py:1198 #: part/models.py:1207
#, python-brace-format #, python-brace-format
msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)"
msgstr "Teil '{p1}' wird in Stückliste für Teil '{p2}' benutzt (rekursiv)" msgstr "Teil '{p1}' wird in Stückliste für Teil '{p2}' benutzt (rekursiv)"
@ -1581,7 +1608,7 @@ msgstr "Teile (inklusive Unter-Kategorien)"
msgid "Part Details" msgid "Part Details"
msgstr "Teile-Details" msgstr "Teile-Details"
#: part/templates/part/detail.html:25 part/templates/part/part_base.html:77 #: part/templates/part/detail.html:25 part/templates/part/part_base.html:78
msgid "IPN" msgid "IPN"
msgstr "IPN (Interne Produktnummer)" msgstr "IPN (Interne Produktnummer)"
@ -1637,7 +1664,7 @@ msgstr "Teil ist virtuell (kein physisches Teil)"
msgid "Part is not a virtual part" msgid "Part is not a virtual part"
msgstr "Teil ist nicht virtuell" msgstr "Teil ist nicht virtuell"
#: part/templates/part/detail.html:132 #: part/templates/part/detail.html:132 templates/table_filters.html:86
msgid "Assembly" msgid "Assembly"
msgstr "Baugruppe" msgstr "Baugruppe"
@ -1649,7 +1676,7 @@ msgstr "Teil kann aus anderen Teilen angefertigt werden"
msgid "Part cannot be assembled from other parts" msgid "Part cannot be assembled from other parts"
msgstr "Teil kann nicht aus anderen Teilen angefertigt werden" msgstr "Teil kann nicht aus anderen Teilen angefertigt werden"
#: part/templates/part/detail.html:141 #: part/templates/part/detail.html:141 templates/table_filters.html:90
msgid "Component" msgid "Component"
msgstr "Komponente" msgstr "Komponente"
@ -1713,31 +1740,47 @@ msgstr "Teile"
msgid "This part is not active" msgid "This part is not active"
msgstr "Dieses Teil ist nicht aktiv" msgstr "Dieses Teil ist nicht aktiv"
#: part/templates/part/part_base.html:37 #: part/templates/part/part_base.html:16
#, fuzzy
#| msgid "Is this part a template part?"
msgid "This part is a template part."
msgstr "Ist dieses Teil eine Vorlage?"
#: part/templates/part/part_base.html:18
msgid "It is not a real part, but real parts can be based on this template."
msgstr ""
#: part/templates/part/part_base.html:23
#, fuzzy
#| msgid "This part is not active"
msgid "This part is a variant of"
msgstr "Dieses Teil ist nicht aktiv"
#: part/templates/part/part_base.html:38
msgid "Star this part" msgid "Star this part"
msgstr "Teil favorisieren" msgstr "Teil favorisieren"
#: part/templates/part/part_base.html:43 #: part/templates/part/part_base.html:44
msgid "Show pricing information" msgid "Show pricing information"
msgstr "Kosteninformationen ansehen" msgstr "Kosteninformationen ansehen"
#: part/templates/part/part_base.html:98 #: part/templates/part/part_base.html:101
msgid "Available Stock" msgid "Available Stock"
msgstr "Verfügbarer Lagerbestand" msgstr "Verfügbarer Lagerbestand"
#: part/templates/part/part_base.html:103 #: part/templates/part/part_base.html:107
msgid "In Stock" msgid "In Stock"
msgstr "Auf Lager" msgstr "Auf Lager"
#: part/templates/part/part_base.html:124 #: part/templates/part/part_base.html:131
msgid "Build Status" msgid "Build Status"
msgstr "Bau-Status" msgstr "Bau-Status"
#: part/templates/part/part_base.html:128 #: part/templates/part/part_base.html:136
msgid "Can Build" msgid "Can Build"
msgstr "Herstellbar?" msgstr "Herstellbar?"
#: part/templates/part/part_base.html:133 #: part/templates/part/part_base.html:142
msgid "Underway" msgid "Underway"
msgstr "unterwegs" msgstr "unterwegs"
@ -1787,11 +1830,6 @@ msgstr "Varianten"
msgid "BOM" msgid "BOM"
msgstr "Stückliste" msgstr "Stückliste"
#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:121
#: templates/navbar.html:12
msgid "Build"
msgstr "Bau"
#: part/templates/part/tabs.html:32 #: part/templates/part/tabs.html:32
msgid "Used In" msgid "Used In"
msgstr "Benutzt in" msgstr "Benutzt in"
@ -2164,11 +2202,11 @@ msgstr "Link auf externe Seite für weitere Informationen"
msgid "Stock Tracking Information" msgid "Stock Tracking Information"
msgstr "Informationen zum Lagerbestands-Tracking" msgstr "Informationen zum Lagerbestands-Tracking"
#: stock/templates/stock/item_base.html:10 #: stock/templates/stock/item_base.html:11
msgid "Stock Item Details" msgid "Stock Item Details"
msgstr "Lagerbestands-Details" msgstr "Lagerbestands-Details"
#: stock/templates/stock/item_base.html:55 #: stock/templates/stock/item_base.html:56
msgid "" msgid ""
"This stock item is serialized - it has a unique serial number and the " "This stock item is serialized - it has a unique serial number and the "
"quantity cannot be adjusted." "quantity cannot be adjusted."
@ -2176,45 +2214,45 @@ msgstr ""
"Dieses Lagerobjekt ist serialisiert. Es hat eine eindeutige Seriennummer und " "Dieses Lagerobjekt ist serialisiert. Es hat eine eindeutige Seriennummer und "
"die Anzahl kann nicht angepasst werden." "die Anzahl kann nicht angepasst werden."
#: stock/templates/stock/item_base.html:59 #: stock/templates/stock/item_base.html:60
#, fuzzy #, fuzzy
#| msgid "Stock item cannot be created for a template Part" #| msgid "Stock item cannot be created for a template Part"
msgid "This stock item cannot be deleted as it has child items" msgid "This stock item cannot be deleted as it has child items"
msgstr "Lagerobjekt kann nicht für Vorlagen-Teile angelegt werden" msgstr "Lagerobjekt kann nicht für Vorlagen-Teile angelegt werden"
#: stock/templates/stock/item_base.html:63 #: stock/templates/stock/item_base.html:64
msgid "" msgid ""
"This stock item will be automatically deleted when all stock is depleted." "This stock item will be automatically deleted when all stock is depleted."
msgstr "" msgstr ""
"Dieses Lagerobjekt wird automatisch gelöscht wenn der Lagerbestand " "Dieses Lagerobjekt wird automatisch gelöscht wenn der Lagerbestand "
"aufgebraucht ist." "aufgebraucht ist."
#: stock/templates/stock/item_base.html:68 #: stock/templates/stock/item_base.html:69
msgid "This stock item was split from " msgid "This stock item was split from "
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:88 #: stock/templates/stock/item_base.html:89
msgid "Belongs To" msgid "Belongs To"
msgstr "Gehört zu" msgstr "Gehört zu"
#: stock/templates/stock/item_base.html:94 #: stock/templates/stock/item_base.html:95
#: stock/templates/stock/stock_adjust.html:17 #: stock/templates/stock/stock_adjust.html:17
msgid "Location" msgid "Location"
msgstr "Standort" msgstr "Standort"
#: stock/templates/stock/item_base.html:101 #: stock/templates/stock/item_base.html:102
msgid "Serial Number" msgid "Serial Number"
msgstr "Seriennummer" msgstr "Seriennummer"
#: stock/templates/stock/item_base.html:160 #: stock/templates/stock/item_base.html:161
msgid "Last Updated" msgid "Last Updated"
msgstr "Zuletzt aktualisiert" msgstr "Zuletzt aktualisiert"
#: stock/templates/stock/item_base.html:165 #: stock/templates/stock/item_base.html:166
msgid "Last Stocktake" msgid "Last Stocktake"
msgstr "Letzte Inventur" msgstr "Letzte Inventur"
#: stock/templates/stock/item_base.html:169 #: stock/templates/stock/item_base.html:170
msgid "No stocktake performed" msgid "No stocktake performed"
msgstr "Keine Inventur ausgeführt" msgstr "Keine Inventur ausgeführt"
@ -2540,6 +2578,80 @@ msgstr "bestellt"
msgid "Delete Stock" msgid "Delete Stock"
msgstr "Lagerobjekt löschen" msgstr "Lagerobjekt löschen"
#: templates/table_filters.html:22
#, fuzzy
#| msgid "Include stock items in sub locations"
msgid "Include sublocations"
msgstr "Lagerobjekte in untergeordneten Lagerorten einschließen"
#: templates/table_filters.html:23
#, fuzzy
#| msgid "Include stock items in sub locations"
msgid "Include stock in sublocations"
msgstr "Lagerobjekte in untergeordneten Lagerorten einschließen"
#: templates/table_filters.html:27
#, fuzzy
#| msgid "Required Parts"
msgid "Active parts"
msgstr "benötigte Teile"
#: templates/table_filters.html:28
msgid "Show stock for active parts"
msgstr ""
#: templates/table_filters.html:32 templates/table_filters.html:33
#, fuzzy
#| msgid "Stock Details"
msgid "Stock status"
msgstr "Objekt-Details"
#: templates/table_filters.html:53
#, fuzzy
#| msgid "Order Parts"
msgid "Order status"
msgstr "Teile bestellen"
#: templates/table_filters.html:64
#, fuzzy
#| msgid "Parts (Including subcategories)"
msgid "Include subcategories"
msgstr "Teile (inklusive Unter-Kategorien)"
#: templates/table_filters.html:65
#, fuzzy
#| msgid "Parts (Including subcategories)"
msgid "Include parts in subcategories"
msgstr "Teile (inklusive Unter-Kategorien)"
#: templates/table_filters.html:69
msgid "Active"
msgstr ""
#: templates/table_filters.html:70
#, fuzzy
#| msgid "Build to allocate parts"
msgid "Show active parts"
msgstr "Bau starten um Teile zuzuweisen"
#: templates/table_filters.html:74
#, fuzzy
#| msgid "Parameter Template"
msgid "Template"
msgstr "Parameter Vorlage"
#: templates/table_filters.html:78
#, fuzzy
#| msgid "Available"
msgid "Stock available"
msgstr "verfügbar"
#: templates/table_filters.html:82
#, fuzzy
#| msgid "Stock"
msgid "Low stock"
msgstr "Lagerbestand"
#~ msgid "URL" #~ msgid "URL"
#~ msgstr "URL" #~ msgstr "URL"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-09 15:04+0000\n" "POT-Creation-Date: 2020-04-11 15:00+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,30 +18,30 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: InvenTree/helpers.py:240 order/models.py:164 order/models.py:215 #: InvenTree/helpers.py:259 order/models.py:164 order/models.py:215
msgid "Invalid quantity provided" msgid "Invalid quantity provided"
msgstr "" msgstr ""
#: InvenTree/helpers.py:243 #: InvenTree/helpers.py:262
msgid "Empty serial number string" msgid "Empty serial number string"
msgstr "" msgstr ""
#: InvenTree/helpers.py:264 InvenTree/helpers.py:281 #: InvenTree/helpers.py:283 InvenTree/helpers.py:300
#, python-brace-format #, python-brace-format
msgid "Duplicate serial: {n}" msgid "Duplicate serial: {n}"
msgstr "" msgstr ""
#: InvenTree/helpers.py:268 InvenTree/helpers.py:271 InvenTree/helpers.py:274 #: InvenTree/helpers.py:287 InvenTree/helpers.py:290 InvenTree/helpers.py:293
#: InvenTree/helpers.py:285 #: InvenTree/helpers.py:304
#, python-brace-format #, python-brace-format
msgid "Invalid group: {g}" msgid "Invalid group: {g}"
msgstr "" msgstr ""
#: InvenTree/helpers.py:291 #: InvenTree/helpers.py:310
msgid "No serial numbers found" msgid "No serial numbers found"
msgstr "" msgstr ""
#: InvenTree/helpers.py:295 #: InvenTree/helpers.py:314
#, python-brace-format #, python-brace-format
msgid "Number of unique serial number ({s}) must match quantity ({q})" msgid "Number of unique serial number ({s}) must match quantity ({q})"
msgstr "" msgstr ""
@ -70,49 +70,49 @@ msgstr ""
msgid "Polish" msgid "Polish"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:36 InvenTree/status_codes.py:97 #: InvenTree/status_codes.py:86 InvenTree/status_codes.py:162
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:37 #: InvenTree/status_codes.py:87
msgid "Placed" msgid "Placed"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:38 InvenTree/status_codes.py:100 #: InvenTree/status_codes.py:88 InvenTree/status_codes.py:165
msgid "Complete" msgid "Complete"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:39 InvenTree/status_codes.py:99 #: InvenTree/status_codes.py:89 InvenTree/status_codes.py:164
msgid "Cancelled" msgid "Cancelled"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:40 InvenTree/status_codes.py:71 #: InvenTree/status_codes.py:90 InvenTree/status_codes.py:130
msgid "Lost" msgid "Lost"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:41 #: InvenTree/status_codes.py:91
msgid "Returned" msgid "Returned"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:67 #: InvenTree/status_codes.py:126
msgid "OK" msgid "OK"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:68 #: InvenTree/status_codes.py:127
msgid "Attention needed" msgid "Attention needed"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:69 #: InvenTree/status_codes.py:128
msgid "Damaged" msgid "Damaged"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:70 #: InvenTree/status_codes.py:129
msgid "Destroyed" msgid "Destroyed"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:98 build/templates/build/allocate_edit.html:28 #: InvenTree/status_codes.py:163 build/templates/build/allocate_edit.html:28
#: build/templates/build/allocate_view.html:21 #: build/templates/build/allocate_view.html:21
#: part/templates/part/part_base.html:109 part/templates/part/tabs.html:21 #: part/templates/part/part_base.html:114 part/templates/part/tabs.html:21
msgid "Allocated" msgid "Allocated"
msgstr "" msgstr ""
@ -175,7 +175,7 @@ msgstr ""
msgid "Number of parts to build" msgid "Number of parts to build"
msgstr "" msgstr ""
#: build/models.py:82 #: build/models.py:82 templates/table_filters.html:42
msgid "Build status" msgid "Build status"
msgstr "" msgstr ""
@ -227,10 +227,10 @@ msgstr ""
#: build/templates/build/allocate_edit.html:19 #: build/templates/build/allocate_edit.html:19
#: build/templates/build/allocate_view.html:17 #: build/templates/build/allocate_view.html:17
#: build/templates/build/detail.html:21 #: build/templates/build/detail.html:22
#: company/templates/company/detail_part.html:65 #: company/templates/company/detail_part.html:65
#: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/order_wizard/select_parts.html:30
#: order/templates/order/purchase_order_detail.html:25 #: order/templates/order/purchase_order_detail.html:26
#: part/templates/part/part_app_base.html:7 #: part/templates/part/part_app_base.html:7
msgid "Part" msgid "Part"
msgstr "" msgstr ""
@ -262,16 +262,58 @@ msgstr ""
#: company/templates/company/index.html:54 #: company/templates/company/index.html:54
#: company/templates/company/supplier_part_base.html:50 #: company/templates/company/supplier_part_base.html:50
#: company/templates/company/supplier_part_detail.html:27 #: company/templates/company/supplier_part_detail.html:27
#: order/templates/order/purchase_order_detail.html:26 #: order/templates/order/purchase_order_detail.html:27
#: part/templates/part/detail.html:38 #: part/templates/part/detail.html:38
msgid "Description" msgid "Description"
msgstr "" msgstr ""
#: build/templates/build/allocate_view.html:22 #: build/templates/build/allocate_view.html:22
#: part/templates/part/part_base.html:115 #: part/templates/part/part_base.html:121
msgid "On Order" msgid "On Order"
msgstr "" msgstr ""
#: build/templates/build/build_base.html:27 part/templates/part/tabs.html:28
#: stock/templates/stock/item_base.html:122 templates/navbar.html:12
msgid "Build"
msgstr ""
#: build/templates/build/build_base.html:52 build/templates/build/detail.html:9
msgid "Build Details"
msgstr ""
#: build/templates/build/build_base.html:56
msgid "Build Title"
msgstr ""
#: build/templates/build/build_base.html:66
#: build/templates/build/detail.html:27
#: company/templates/company/supplier_part_pricing.html:27
#: order/templates/order/order_wizard/select_parts.html:32
#: order/templates/order/purchase_order_detail.html:30
#: stock/templates/stock/item_base.html:108
#: stock/templates/stock/stock_adjust.html:18
msgid "Quantity"
msgstr ""
#: build/templates/build/build_base.html:71
#: build/templates/build/detail.html:42
#: order/templates/order/order_base.html:72
#: stock/templates/stock/item_base.html:175
msgid "Status"
msgstr ""
#: build/templates/build/build_base.html:76
msgid "BOM Price"
msgstr ""
#: build/templates/build/build_base.html:81
msgid "BOM pricing is incomplete"
msgstr ""
#: build/templates/build/build_base.html:84
msgid "No pricing information"
msgstr ""
#: build/templates/build/build_output.html:9 #: build/templates/build/build_output.html:9
msgid "Build Outputs" msgid "Build Outputs"
msgstr "" msgstr ""
@ -280,68 +322,49 @@ msgstr ""
msgid "Are you sure you want to unallocate these parts?" msgid "Are you sure you want to unallocate these parts?"
msgstr "" msgstr ""
#: build/templates/build/detail.html:8 #: build/templates/build/detail.html:17
msgid "Build Details"
msgstr ""
#: build/templates/build/detail.html:16
msgid "Title" msgid "Title"
msgstr "" msgstr ""
#: build/templates/build/detail.html:26 #: build/templates/build/detail.html:31
#: company/templates/company/supplier_part_pricing.html:27
#: order/templates/order/order_wizard/select_parts.html:32
#: order/templates/order/purchase_order_detail.html:29
#: stock/templates/stock/item_base.html:107
#: stock/templates/stock/stock_adjust.html:18
msgid "Quantity"
msgstr ""
#: build/templates/build/detail.html:30
msgid "Stock Source" msgid "Stock Source"
msgstr "" msgstr ""
#: build/templates/build/detail.html:35 #: build/templates/build/detail.html:36
msgid "Stock can be taken from any available location." msgid "Stock can be taken from any available location."
msgstr "" msgstr ""
#: build/templates/build/detail.html:41 #: build/templates/build/detail.html:48
#: order/templates/order/order_base.html:71 #: stock/templates/stock/item_base.html:115
#: stock/templates/stock/item_base.html:174
msgid "Status"
msgstr ""
#: build/templates/build/detail.html:47
#: stock/templates/stock/item_base.html:114
msgid "Batch" msgid "Batch"
msgstr "" msgstr ""
#: build/templates/build/detail.html:54 #: build/templates/build/detail.html:55
#: company/templates/company/supplier_part_base.html:47 #: company/templates/company/supplier_part_base.html:47
#: company/templates/company/supplier_part_detail.html:24 #: company/templates/company/supplier_part_detail.html:24
#: part/templates/part/detail.html:67 part/templates/part/part_base.html:84 #: part/templates/part/detail.html:67 part/templates/part/part_base.html:85
#: stock/templates/stock/item_base.html:142 #: stock/templates/stock/item_base.html:143
msgid "External Link" msgid "External Link"
msgstr "" msgstr ""
#: build/templates/build/detail.html:60 #: build/templates/build/detail.html:61
#: order/templates/order/order_base.html:83 #: order/templates/order/order_base.html:84
msgid "Created" msgid "Created"
msgstr "" msgstr ""
#: build/templates/build/detail.html:66 #: build/templates/build/detail.html:67
msgid "Enough Parts?" msgid "Enough Parts?"
msgstr "" msgstr ""
#: build/templates/build/detail.html:69 #: build/templates/build/detail.html:70
msgid "Yes" msgid "Yes"
msgstr "" msgstr ""
#: build/templates/build/detail.html:71 #: build/templates/build/detail.html:72
msgid "No" msgid "No"
msgstr "" msgstr ""
#: build/templates/build/detail.html:79 #: build/templates/build/detail.html:80
msgid "Completed" msgid "Completed"
msgstr "" msgstr ""
@ -581,7 +604,7 @@ msgstr ""
msgid "Supplier stock keeping unit" msgid "Supplier stock keeping unit"
msgstr "" msgstr ""
#: company/models.py:256 company/templates/company/detail_part.html:81 #: company/models.py:256 company/templates/company/detail_part.html:96
#: company/templates/company/supplier_part_base.html:53 #: company/templates/company/supplier_part_base.html:53
#: company/templates/company/supplier_part_detail.html:30 #: company/templates/company/supplier_part_detail.html:30
msgid "Manufacturer" msgid "Manufacturer"
@ -637,7 +660,7 @@ msgid "Company Details"
msgstr "" msgstr ""
#: company/templates/company/detail.html:16 #: company/templates/company/detail.html:16
#: stock/templates/stock/item_base.html:135 #: stock/templates/stock/item_base.html:136
msgid "Customer" msgid "Customer"
msgstr "" msgstr ""
@ -645,9 +668,9 @@ msgstr ""
#: company/templates/company/index.html:46 #: company/templates/company/index.html:46
#: company/templates/company/supplier_part_base.html:44 #: company/templates/company/supplier_part_base.html:44
#: company/templates/company/supplier_part_detail.html:21 #: company/templates/company/supplier_part_detail.html:21
#: order/templates/order/order_base.html:66 #: order/templates/order/order_base.html:67
#: order/templates/order/order_wizard/select_pos.html:30 #: order/templates/order/order_wizard/select_pos.html:30
#: stock/templates/stock/item_base.html:149 #: stock/templates/stock/item_base.html:150
msgid "Supplier" msgid "Supplier"
msgstr "" msgstr ""
@ -669,13 +692,13 @@ msgstr ""
msgid "Delete Parts" msgid "Delete Parts"
msgstr "" msgstr ""
#: company/templates/company/detail_part.html:73 #: company/templates/company/detail_part.html:88
#: company/templates/company/supplier_part_base.html:45 #: company/templates/company/supplier_part_base.html:45
#: company/templates/company/supplier_part_detail.html:22 #: company/templates/company/supplier_part_detail.html:22
msgid "SKU" msgid "SKU"
msgstr "" msgstr ""
#: company/templates/company/detail_part.html:90 #: company/templates/company/detail_part.html:105
msgid "Link" msgid "Link"
msgstr "" msgstr ""
@ -727,7 +750,7 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:6 #: company/templates/company/supplier_part_base.html:6
#: company/templates/company/supplier_part_base.html:13 #: company/templates/company/supplier_part_base.html:13
#: stock/templates/stock/item_base.html:154 #: stock/templates/stock/item_base.html:155
msgid "Supplier Part" msgid "Supplier Part"
msgstr "" msgstr ""
@ -748,7 +771,7 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:57 #: company/templates/company/supplier_part_base.html:57
#: company/templates/company/supplier_part_detail.html:34 #: company/templates/company/supplier_part_detail.html:34
#: order/templates/order/purchase_order_detail.html:33 #: order/templates/order/purchase_order_detail.html:34
msgid "Note" msgid "Note"
msgstr "" msgstr ""
@ -930,7 +953,7 @@ msgstr ""
msgid "Line item notes" msgid "Line item notes"
msgstr "" msgstr ""
#: order/models.py:298 stock/templates/stock/item_base.html:128 #: order/models.py:298 stock/templates/stock/item_base.html:129
msgid "Purchase Order" msgid "Purchase Order"
msgstr "" msgstr ""
@ -942,16 +965,16 @@ msgstr ""
msgid "Number of items received" msgid "Number of items received"
msgstr "" msgstr ""
#: order/templates/order/order_base.html:61 #: order/templates/order/order_base.html:62
msgid "Purchase Order Details" msgid "Purchase Order Details"
msgstr "" msgstr ""
#: order/templates/order/order_base.html:89 #: order/templates/order/order_base.html:90
msgid "Issued" msgid "Issued"
msgstr "" msgstr ""
#: order/templates/order/order_base.html:96 #: order/templates/order/order_base.html:97
#: order/templates/order/purchase_order_detail.html:31 #: order/templates/order/purchase_order_detail.html:32
msgid "Received" msgid "Received"
msgstr "" msgstr ""
@ -1035,23 +1058,23 @@ msgstr ""
msgid "Are you sure you want to delete this attachment?" msgid "Are you sure you want to delete this attachment?"
msgstr "" msgstr ""
#: order/templates/order/purchase_order_detail.html:15 order/views.py:825 #: order/templates/order/purchase_order_detail.html:16 order/views.py:825
msgid "Add Line Item" msgid "Add Line Item"
msgstr "" msgstr ""
#: order/templates/order/purchase_order_detail.html:19 #: order/templates/order/purchase_order_detail.html:20
msgid "Order Items" msgid "Order Items"
msgstr "" msgstr ""
#: order/templates/order/purchase_order_detail.html:24 #: order/templates/order/purchase_order_detail.html:25
msgid "Line" msgid "Line"
msgstr "" msgstr ""
#: order/templates/order/purchase_order_detail.html:27 #: order/templates/order/purchase_order_detail.html:28
msgid "Order Code" msgid "Order Code"
msgstr "" msgstr ""
#: order/templates/order/purchase_order_detail.html:28 #: order/templates/order/purchase_order_detail.html:29
msgid "Reference" msgid "Reference"
msgstr "" msgstr ""
@ -1324,63 +1347,63 @@ msgstr ""
msgid "Stored BOM checksum" msgid "Stored BOM checksum"
msgstr "" msgstr ""
#: part/models.py:1040 #: part/models.py:1049
msgid "Parameter template name must be unique" msgid "Parameter template name must be unique"
msgstr "" msgstr ""
#: part/models.py:1045 #: part/models.py:1054
msgid "Parameter Name" msgid "Parameter Name"
msgstr "" msgstr ""
#: part/models.py:1047 #: part/models.py:1056
msgid "Parameter Units" msgid "Parameter Units"
msgstr "" msgstr ""
#: part/models.py:1073 #: part/models.py:1082
msgid "Parent Part" msgid "Parent Part"
msgstr "" msgstr ""
#: part/models.py:1075 #: part/models.py:1084
msgid "Parameter Template" msgid "Parameter Template"
msgstr "" msgstr ""
#: part/models.py:1077 #: part/models.py:1086
msgid "Parameter Value" msgid "Parameter Value"
msgstr "" msgstr ""
#: part/models.py:1101 #: part/models.py:1110
msgid "Select parent part" msgid "Select parent part"
msgstr "" msgstr ""
#: part/models.py:1110 #: part/models.py:1119
msgid "Select part to be used in BOM" msgid "Select part to be used in BOM"
msgstr "" msgstr ""
#: part/models.py:1117 #: part/models.py:1126
msgid "BOM quantity for this BOM item" msgid "BOM quantity for this BOM item"
msgstr "" msgstr ""
#: part/models.py:1120 #: part/models.py:1129
msgid "Estimated build wastage quantity (absolute or percentage)" msgid "Estimated build wastage quantity (absolute or percentage)"
msgstr "" msgstr ""
#: part/models.py:1123 #: part/models.py:1132
msgid "BOM item reference" msgid "BOM item reference"
msgstr "" msgstr ""
#: part/models.py:1126 #: part/models.py:1135
msgid "BOM item notes" msgid "BOM item notes"
msgstr "" msgstr ""
#: part/models.py:1128 #: part/models.py:1137
msgid "BOM line checksum" msgid "BOM line checksum"
msgstr "" msgstr ""
#: part/models.py:1191 #: part/models.py:1200
msgid "Part cannot be added to its own Bill of Materials" msgid "Part cannot be added to its own Bill of Materials"
msgstr "" msgstr ""
#: part/models.py:1198 #: part/models.py:1207
#, python-brace-format #, python-brace-format
msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)"
msgstr "" msgstr ""
@ -1430,7 +1453,7 @@ msgstr ""
msgid "Part Details" msgid "Part Details"
msgstr "" msgstr ""
#: part/templates/part/detail.html:25 part/templates/part/part_base.html:77 #: part/templates/part/detail.html:25 part/templates/part/part_base.html:78
msgid "IPN" msgid "IPN"
msgstr "" msgstr ""
@ -1482,7 +1505,7 @@ msgstr ""
msgid "Part is not a virtual part" msgid "Part is not a virtual part"
msgstr "" msgstr ""
#: part/templates/part/detail.html:132 #: part/templates/part/detail.html:132 templates/table_filters.html:86
msgid "Assembly" msgid "Assembly"
msgstr "" msgstr ""
@ -1494,7 +1517,7 @@ msgstr ""
msgid "Part cannot be assembled from other parts" msgid "Part cannot be assembled from other parts"
msgstr "" msgstr ""
#: part/templates/part/detail.html:141 #: part/templates/part/detail.html:141 templates/table_filters.html:90
msgid "Component" msgid "Component"
msgstr "" msgstr ""
@ -1554,31 +1577,43 @@ msgstr ""
msgid "This part is not active" msgid "This part is not active"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:37 #: part/templates/part/part_base.html:16
msgid "This part is a template part."
msgstr ""
#: part/templates/part/part_base.html:18
msgid "It is not a real part, but real parts can be based on this template."
msgstr ""
#: part/templates/part/part_base.html:23
msgid "This part is a variant of"
msgstr ""
#: part/templates/part/part_base.html:38
msgid "Star this part" msgid "Star this part"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:43 #: part/templates/part/part_base.html:44
msgid "Show pricing information" msgid "Show pricing information"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:98 #: part/templates/part/part_base.html:101
msgid "Available Stock" msgid "Available Stock"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:103 #: part/templates/part/part_base.html:107
msgid "In Stock" msgid "In Stock"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:124 #: part/templates/part/part_base.html:131
msgid "Build Status" msgid "Build Status"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:128 #: part/templates/part/part_base.html:136
msgid "Can Build" msgid "Can Build"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:133 #: part/templates/part/part_base.html:142
msgid "Underway" msgid "Underway"
msgstr "" msgstr ""
@ -1618,11 +1653,6 @@ msgstr ""
msgid "BOM" msgid "BOM"
msgstr "" msgstr ""
#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:121
#: templates/navbar.html:12
msgid "Build"
msgstr ""
#: part/templates/part/tabs.html:32 #: part/templates/part/tabs.html:32
msgid "Used In" msgid "Used In"
msgstr "" msgstr ""
@ -1938,51 +1968,51 @@ msgstr ""
msgid "Stock Tracking Information" msgid "Stock Tracking Information"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:10 #: stock/templates/stock/item_base.html:11
msgid "Stock Item Details" msgid "Stock Item Details"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:55 #: stock/templates/stock/item_base.html:56
msgid "" msgid ""
"This stock item is serialized - it has a unique serial number and the " "This stock item is serialized - it has a unique serial number and the "
"quantity cannot be adjusted." "quantity cannot be adjusted."
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:59 #: stock/templates/stock/item_base.html:60
msgid "This stock item cannot be deleted as it has child items" msgid "This stock item cannot be deleted as it has child items"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:63 #: stock/templates/stock/item_base.html:64
msgid "" msgid ""
"This stock item will be automatically deleted when all stock is depleted." "This stock item will be automatically deleted when all stock is depleted."
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:68 #: stock/templates/stock/item_base.html:69
msgid "This stock item was split from " msgid "This stock item was split from "
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:88 #: stock/templates/stock/item_base.html:89
msgid "Belongs To" msgid "Belongs To"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:94 #: stock/templates/stock/item_base.html:95
#: stock/templates/stock/stock_adjust.html:17 #: stock/templates/stock/stock_adjust.html:17
msgid "Location" msgid "Location"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:101 #: stock/templates/stock/item_base.html:102
msgid "Serial Number" msgid "Serial Number"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:160 #: stock/templates/stock/item_base.html:161
msgid "Last Updated" msgid "Last Updated"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:165 #: stock/templates/stock/item_base.html:166
msgid "Last Stocktake" msgid "Last Stocktake"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:169 #: stock/templates/stock/item_base.html:170
msgid "No stocktake performed" msgid "No stocktake performed"
msgstr "" msgstr ""
@ -2280,3 +2310,55 @@ msgstr ""
#: templates/stock_table.html:17 #: templates/stock_table.html:17
msgid "Delete Stock" msgid "Delete Stock"
msgstr "" msgstr ""
#: templates/table_filters.html:22
msgid "Include sublocations"
msgstr ""
#: templates/table_filters.html:23
msgid "Include stock in sublocations"
msgstr ""
#: templates/table_filters.html:27
msgid "Active parts"
msgstr ""
#: templates/table_filters.html:28
msgid "Show stock for active parts"
msgstr ""
#: templates/table_filters.html:32 templates/table_filters.html:33
msgid "Stock status"
msgstr ""
#: templates/table_filters.html:53
msgid "Order status"
msgstr ""
#: templates/table_filters.html:64
msgid "Include subcategories"
msgstr ""
#: templates/table_filters.html:65
msgid "Include parts in subcategories"
msgstr ""
#: templates/table_filters.html:69
msgid "Active"
msgstr ""
#: templates/table_filters.html:70
msgid "Show active parts"
msgstr ""
#: templates/table_filters.html:74
msgid "Template"
msgstr ""
#: templates/table_filters.html:78
msgid "Stock available"
msgstr ""
#: templates/table_filters.html:82
msgid "Low stock"
msgstr ""

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-09 15:04+0000\n" "POT-Creation-Date: 2020-04-11 15:00+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,30 +18,30 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: InvenTree/helpers.py:240 order/models.py:164 order/models.py:215 #: InvenTree/helpers.py:259 order/models.py:164 order/models.py:215
msgid "Invalid quantity provided" msgid "Invalid quantity provided"
msgstr "" msgstr ""
#: InvenTree/helpers.py:243 #: InvenTree/helpers.py:262
msgid "Empty serial number string" msgid "Empty serial number string"
msgstr "" msgstr ""
#: InvenTree/helpers.py:264 InvenTree/helpers.py:281 #: InvenTree/helpers.py:283 InvenTree/helpers.py:300
#, python-brace-format #, python-brace-format
msgid "Duplicate serial: {n}" msgid "Duplicate serial: {n}"
msgstr "" msgstr ""
#: InvenTree/helpers.py:268 InvenTree/helpers.py:271 InvenTree/helpers.py:274 #: InvenTree/helpers.py:287 InvenTree/helpers.py:290 InvenTree/helpers.py:293
#: InvenTree/helpers.py:285 #: InvenTree/helpers.py:304
#, python-brace-format #, python-brace-format
msgid "Invalid group: {g}" msgid "Invalid group: {g}"
msgstr "" msgstr ""
#: InvenTree/helpers.py:291 #: InvenTree/helpers.py:310
msgid "No serial numbers found" msgid "No serial numbers found"
msgstr "" msgstr ""
#: InvenTree/helpers.py:295 #: InvenTree/helpers.py:314
#, python-brace-format #, python-brace-format
msgid "Number of unique serial number ({s}) must match quantity ({q})" msgid "Number of unique serial number ({s}) must match quantity ({q})"
msgstr "" msgstr ""
@ -70,49 +70,49 @@ msgstr ""
msgid "Polish" msgid "Polish"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:36 InvenTree/status_codes.py:97 #: InvenTree/status_codes.py:86 InvenTree/status_codes.py:162
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:37 #: InvenTree/status_codes.py:87
msgid "Placed" msgid "Placed"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:38 InvenTree/status_codes.py:100 #: InvenTree/status_codes.py:88 InvenTree/status_codes.py:165
msgid "Complete" msgid "Complete"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:39 InvenTree/status_codes.py:99 #: InvenTree/status_codes.py:89 InvenTree/status_codes.py:164
msgid "Cancelled" msgid "Cancelled"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:40 InvenTree/status_codes.py:71 #: InvenTree/status_codes.py:90 InvenTree/status_codes.py:130
msgid "Lost" msgid "Lost"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:41 #: InvenTree/status_codes.py:91
msgid "Returned" msgid "Returned"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:67 #: InvenTree/status_codes.py:126
msgid "OK" msgid "OK"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:68 #: InvenTree/status_codes.py:127
msgid "Attention needed" msgid "Attention needed"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:69 #: InvenTree/status_codes.py:128
msgid "Damaged" msgid "Damaged"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:70 #: InvenTree/status_codes.py:129
msgid "Destroyed" msgid "Destroyed"
msgstr "" msgstr ""
#: InvenTree/status_codes.py:98 build/templates/build/allocate_edit.html:28 #: InvenTree/status_codes.py:163 build/templates/build/allocate_edit.html:28
#: build/templates/build/allocate_view.html:21 #: build/templates/build/allocate_view.html:21
#: part/templates/part/part_base.html:109 part/templates/part/tabs.html:21 #: part/templates/part/part_base.html:114 part/templates/part/tabs.html:21
msgid "Allocated" msgid "Allocated"
msgstr "" msgstr ""
@ -175,7 +175,7 @@ msgstr ""
msgid "Number of parts to build" msgid "Number of parts to build"
msgstr "" msgstr ""
#: build/models.py:82 #: build/models.py:82 templates/table_filters.html:42
msgid "Build status" msgid "Build status"
msgstr "" msgstr ""
@ -227,10 +227,10 @@ msgstr ""
#: build/templates/build/allocate_edit.html:19 #: build/templates/build/allocate_edit.html:19
#: build/templates/build/allocate_view.html:17 #: build/templates/build/allocate_view.html:17
#: build/templates/build/detail.html:21 #: build/templates/build/detail.html:22
#: company/templates/company/detail_part.html:65 #: company/templates/company/detail_part.html:65
#: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/order_wizard/select_parts.html:30
#: order/templates/order/purchase_order_detail.html:25 #: order/templates/order/purchase_order_detail.html:26
#: part/templates/part/part_app_base.html:7 #: part/templates/part/part_app_base.html:7
msgid "Part" msgid "Part"
msgstr "" msgstr ""
@ -262,16 +262,58 @@ msgstr ""
#: company/templates/company/index.html:54 #: company/templates/company/index.html:54
#: company/templates/company/supplier_part_base.html:50 #: company/templates/company/supplier_part_base.html:50
#: company/templates/company/supplier_part_detail.html:27 #: company/templates/company/supplier_part_detail.html:27
#: order/templates/order/purchase_order_detail.html:26 #: order/templates/order/purchase_order_detail.html:27
#: part/templates/part/detail.html:38 #: part/templates/part/detail.html:38
msgid "Description" msgid "Description"
msgstr "" msgstr ""
#: build/templates/build/allocate_view.html:22 #: build/templates/build/allocate_view.html:22
#: part/templates/part/part_base.html:115 #: part/templates/part/part_base.html:121
msgid "On Order" msgid "On Order"
msgstr "" msgstr ""
#: build/templates/build/build_base.html:27 part/templates/part/tabs.html:28
#: stock/templates/stock/item_base.html:122 templates/navbar.html:12
msgid "Build"
msgstr ""
#: build/templates/build/build_base.html:52 build/templates/build/detail.html:9
msgid "Build Details"
msgstr ""
#: build/templates/build/build_base.html:56
msgid "Build Title"
msgstr ""
#: build/templates/build/build_base.html:66
#: build/templates/build/detail.html:27
#: company/templates/company/supplier_part_pricing.html:27
#: order/templates/order/order_wizard/select_parts.html:32
#: order/templates/order/purchase_order_detail.html:30
#: stock/templates/stock/item_base.html:108
#: stock/templates/stock/stock_adjust.html:18
msgid "Quantity"
msgstr ""
#: build/templates/build/build_base.html:71
#: build/templates/build/detail.html:42
#: order/templates/order/order_base.html:72
#: stock/templates/stock/item_base.html:175
msgid "Status"
msgstr ""
#: build/templates/build/build_base.html:76
msgid "BOM Price"
msgstr ""
#: build/templates/build/build_base.html:81
msgid "BOM pricing is incomplete"
msgstr ""
#: build/templates/build/build_base.html:84
msgid "No pricing information"
msgstr ""
#: build/templates/build/build_output.html:9 #: build/templates/build/build_output.html:9
msgid "Build Outputs" msgid "Build Outputs"
msgstr "" msgstr ""
@ -280,68 +322,49 @@ msgstr ""
msgid "Are you sure you want to unallocate these parts?" msgid "Are you sure you want to unallocate these parts?"
msgstr "" msgstr ""
#: build/templates/build/detail.html:8 #: build/templates/build/detail.html:17
msgid "Build Details"
msgstr ""
#: build/templates/build/detail.html:16
msgid "Title" msgid "Title"
msgstr "" msgstr ""
#: build/templates/build/detail.html:26 #: build/templates/build/detail.html:31
#: company/templates/company/supplier_part_pricing.html:27
#: order/templates/order/order_wizard/select_parts.html:32
#: order/templates/order/purchase_order_detail.html:29
#: stock/templates/stock/item_base.html:107
#: stock/templates/stock/stock_adjust.html:18
msgid "Quantity"
msgstr ""
#: build/templates/build/detail.html:30
msgid "Stock Source" msgid "Stock Source"
msgstr "" msgstr ""
#: build/templates/build/detail.html:35 #: build/templates/build/detail.html:36
msgid "Stock can be taken from any available location." msgid "Stock can be taken from any available location."
msgstr "" msgstr ""
#: build/templates/build/detail.html:41 #: build/templates/build/detail.html:48
#: order/templates/order/order_base.html:71 #: stock/templates/stock/item_base.html:115
#: stock/templates/stock/item_base.html:174
msgid "Status"
msgstr ""
#: build/templates/build/detail.html:47
#: stock/templates/stock/item_base.html:114
msgid "Batch" msgid "Batch"
msgstr "" msgstr ""
#: build/templates/build/detail.html:54 #: build/templates/build/detail.html:55
#: company/templates/company/supplier_part_base.html:47 #: company/templates/company/supplier_part_base.html:47
#: company/templates/company/supplier_part_detail.html:24 #: company/templates/company/supplier_part_detail.html:24
#: part/templates/part/detail.html:67 part/templates/part/part_base.html:84 #: part/templates/part/detail.html:67 part/templates/part/part_base.html:85
#: stock/templates/stock/item_base.html:142 #: stock/templates/stock/item_base.html:143
msgid "External Link" msgid "External Link"
msgstr "" msgstr ""
#: build/templates/build/detail.html:60 #: build/templates/build/detail.html:61
#: order/templates/order/order_base.html:83 #: order/templates/order/order_base.html:84
msgid "Created" msgid "Created"
msgstr "" msgstr ""
#: build/templates/build/detail.html:66 #: build/templates/build/detail.html:67
msgid "Enough Parts?" msgid "Enough Parts?"
msgstr "" msgstr ""
#: build/templates/build/detail.html:69 #: build/templates/build/detail.html:70
msgid "Yes" msgid "Yes"
msgstr "" msgstr ""
#: build/templates/build/detail.html:71 #: build/templates/build/detail.html:72
msgid "No" msgid "No"
msgstr "" msgstr ""
#: build/templates/build/detail.html:79 #: build/templates/build/detail.html:80
msgid "Completed" msgid "Completed"
msgstr "" msgstr ""
@ -581,7 +604,7 @@ msgstr ""
msgid "Supplier stock keeping unit" msgid "Supplier stock keeping unit"
msgstr "" msgstr ""
#: company/models.py:256 company/templates/company/detail_part.html:81 #: company/models.py:256 company/templates/company/detail_part.html:96
#: company/templates/company/supplier_part_base.html:53 #: company/templates/company/supplier_part_base.html:53
#: company/templates/company/supplier_part_detail.html:30 #: company/templates/company/supplier_part_detail.html:30
msgid "Manufacturer" msgid "Manufacturer"
@ -637,7 +660,7 @@ msgid "Company Details"
msgstr "" msgstr ""
#: company/templates/company/detail.html:16 #: company/templates/company/detail.html:16
#: stock/templates/stock/item_base.html:135 #: stock/templates/stock/item_base.html:136
msgid "Customer" msgid "Customer"
msgstr "" msgstr ""
@ -645,9 +668,9 @@ msgstr ""
#: company/templates/company/index.html:46 #: company/templates/company/index.html:46
#: company/templates/company/supplier_part_base.html:44 #: company/templates/company/supplier_part_base.html:44
#: company/templates/company/supplier_part_detail.html:21 #: company/templates/company/supplier_part_detail.html:21
#: order/templates/order/order_base.html:66 #: order/templates/order/order_base.html:67
#: order/templates/order/order_wizard/select_pos.html:30 #: order/templates/order/order_wizard/select_pos.html:30
#: stock/templates/stock/item_base.html:149 #: stock/templates/stock/item_base.html:150
msgid "Supplier" msgid "Supplier"
msgstr "" msgstr ""
@ -669,13 +692,13 @@ msgstr ""
msgid "Delete Parts" msgid "Delete Parts"
msgstr "" msgstr ""
#: company/templates/company/detail_part.html:73 #: company/templates/company/detail_part.html:88
#: company/templates/company/supplier_part_base.html:45 #: company/templates/company/supplier_part_base.html:45
#: company/templates/company/supplier_part_detail.html:22 #: company/templates/company/supplier_part_detail.html:22
msgid "SKU" msgid "SKU"
msgstr "" msgstr ""
#: company/templates/company/detail_part.html:90 #: company/templates/company/detail_part.html:105
msgid "Link" msgid "Link"
msgstr "" msgstr ""
@ -727,7 +750,7 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:6 #: company/templates/company/supplier_part_base.html:6
#: company/templates/company/supplier_part_base.html:13 #: company/templates/company/supplier_part_base.html:13
#: stock/templates/stock/item_base.html:154 #: stock/templates/stock/item_base.html:155
msgid "Supplier Part" msgid "Supplier Part"
msgstr "" msgstr ""
@ -748,7 +771,7 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:57 #: company/templates/company/supplier_part_base.html:57
#: company/templates/company/supplier_part_detail.html:34 #: company/templates/company/supplier_part_detail.html:34
#: order/templates/order/purchase_order_detail.html:33 #: order/templates/order/purchase_order_detail.html:34
msgid "Note" msgid "Note"
msgstr "" msgstr ""
@ -930,7 +953,7 @@ msgstr ""
msgid "Line item notes" msgid "Line item notes"
msgstr "" msgstr ""
#: order/models.py:298 stock/templates/stock/item_base.html:128 #: order/models.py:298 stock/templates/stock/item_base.html:129
msgid "Purchase Order" msgid "Purchase Order"
msgstr "" msgstr ""
@ -942,16 +965,16 @@ msgstr ""
msgid "Number of items received" msgid "Number of items received"
msgstr "" msgstr ""
#: order/templates/order/order_base.html:61 #: order/templates/order/order_base.html:62
msgid "Purchase Order Details" msgid "Purchase Order Details"
msgstr "" msgstr ""
#: order/templates/order/order_base.html:89 #: order/templates/order/order_base.html:90
msgid "Issued" msgid "Issued"
msgstr "" msgstr ""
#: order/templates/order/order_base.html:96 #: order/templates/order/order_base.html:97
#: order/templates/order/purchase_order_detail.html:31 #: order/templates/order/purchase_order_detail.html:32
msgid "Received" msgid "Received"
msgstr "" msgstr ""
@ -1035,23 +1058,23 @@ msgstr ""
msgid "Are you sure you want to delete this attachment?" msgid "Are you sure you want to delete this attachment?"
msgstr "" msgstr ""
#: order/templates/order/purchase_order_detail.html:15 order/views.py:825 #: order/templates/order/purchase_order_detail.html:16 order/views.py:825
msgid "Add Line Item" msgid "Add Line Item"
msgstr "" msgstr ""
#: order/templates/order/purchase_order_detail.html:19 #: order/templates/order/purchase_order_detail.html:20
msgid "Order Items" msgid "Order Items"
msgstr "" msgstr ""
#: order/templates/order/purchase_order_detail.html:24 #: order/templates/order/purchase_order_detail.html:25
msgid "Line" msgid "Line"
msgstr "" msgstr ""
#: order/templates/order/purchase_order_detail.html:27 #: order/templates/order/purchase_order_detail.html:28
msgid "Order Code" msgid "Order Code"
msgstr "" msgstr ""
#: order/templates/order/purchase_order_detail.html:28 #: order/templates/order/purchase_order_detail.html:29
msgid "Reference" msgid "Reference"
msgstr "" msgstr ""
@ -1324,63 +1347,63 @@ msgstr ""
msgid "Stored BOM checksum" msgid "Stored BOM checksum"
msgstr "" msgstr ""
#: part/models.py:1040 #: part/models.py:1049
msgid "Parameter template name must be unique" msgid "Parameter template name must be unique"
msgstr "" msgstr ""
#: part/models.py:1045 #: part/models.py:1054
msgid "Parameter Name" msgid "Parameter Name"
msgstr "" msgstr ""
#: part/models.py:1047 #: part/models.py:1056
msgid "Parameter Units" msgid "Parameter Units"
msgstr "" msgstr ""
#: part/models.py:1073 #: part/models.py:1082
msgid "Parent Part" msgid "Parent Part"
msgstr "" msgstr ""
#: part/models.py:1075 #: part/models.py:1084
msgid "Parameter Template" msgid "Parameter Template"
msgstr "" msgstr ""
#: part/models.py:1077 #: part/models.py:1086
msgid "Parameter Value" msgid "Parameter Value"
msgstr "" msgstr ""
#: part/models.py:1101 #: part/models.py:1110
msgid "Select parent part" msgid "Select parent part"
msgstr "" msgstr ""
#: part/models.py:1110 #: part/models.py:1119
msgid "Select part to be used in BOM" msgid "Select part to be used in BOM"
msgstr "" msgstr ""
#: part/models.py:1117 #: part/models.py:1126
msgid "BOM quantity for this BOM item" msgid "BOM quantity for this BOM item"
msgstr "" msgstr ""
#: part/models.py:1120 #: part/models.py:1129
msgid "Estimated build wastage quantity (absolute or percentage)" msgid "Estimated build wastage quantity (absolute or percentage)"
msgstr "" msgstr ""
#: part/models.py:1123 #: part/models.py:1132
msgid "BOM item reference" msgid "BOM item reference"
msgstr "" msgstr ""
#: part/models.py:1126 #: part/models.py:1135
msgid "BOM item notes" msgid "BOM item notes"
msgstr "" msgstr ""
#: part/models.py:1128 #: part/models.py:1137
msgid "BOM line checksum" msgid "BOM line checksum"
msgstr "" msgstr ""
#: part/models.py:1191 #: part/models.py:1200
msgid "Part cannot be added to its own Bill of Materials" msgid "Part cannot be added to its own Bill of Materials"
msgstr "" msgstr ""
#: part/models.py:1198 #: part/models.py:1207
#, python-brace-format #, python-brace-format
msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)"
msgstr "" msgstr ""
@ -1430,7 +1453,7 @@ msgstr ""
msgid "Part Details" msgid "Part Details"
msgstr "" msgstr ""
#: part/templates/part/detail.html:25 part/templates/part/part_base.html:77 #: part/templates/part/detail.html:25 part/templates/part/part_base.html:78
msgid "IPN" msgid "IPN"
msgstr "" msgstr ""
@ -1482,7 +1505,7 @@ msgstr ""
msgid "Part is not a virtual part" msgid "Part is not a virtual part"
msgstr "" msgstr ""
#: part/templates/part/detail.html:132 #: part/templates/part/detail.html:132 templates/table_filters.html:86
msgid "Assembly" msgid "Assembly"
msgstr "" msgstr ""
@ -1494,7 +1517,7 @@ msgstr ""
msgid "Part cannot be assembled from other parts" msgid "Part cannot be assembled from other parts"
msgstr "" msgstr ""
#: part/templates/part/detail.html:141 #: part/templates/part/detail.html:141 templates/table_filters.html:90
msgid "Component" msgid "Component"
msgstr "" msgstr ""
@ -1554,31 +1577,43 @@ msgstr ""
msgid "This part is not active" msgid "This part is not active"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:37 #: part/templates/part/part_base.html:16
msgid "This part is a template part."
msgstr ""
#: part/templates/part/part_base.html:18
msgid "It is not a real part, but real parts can be based on this template."
msgstr ""
#: part/templates/part/part_base.html:23
msgid "This part is a variant of"
msgstr ""
#: part/templates/part/part_base.html:38
msgid "Star this part" msgid "Star this part"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:43 #: part/templates/part/part_base.html:44
msgid "Show pricing information" msgid "Show pricing information"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:98 #: part/templates/part/part_base.html:101
msgid "Available Stock" msgid "Available Stock"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:103 #: part/templates/part/part_base.html:107
msgid "In Stock" msgid "In Stock"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:124 #: part/templates/part/part_base.html:131
msgid "Build Status" msgid "Build Status"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:128 #: part/templates/part/part_base.html:136
msgid "Can Build" msgid "Can Build"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:133 #: part/templates/part/part_base.html:142
msgid "Underway" msgid "Underway"
msgstr "" msgstr ""
@ -1618,11 +1653,6 @@ msgstr ""
msgid "BOM" msgid "BOM"
msgstr "" msgstr ""
#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:121
#: templates/navbar.html:12
msgid "Build"
msgstr ""
#: part/templates/part/tabs.html:32 #: part/templates/part/tabs.html:32
msgid "Used In" msgid "Used In"
msgstr "" msgstr ""
@ -1938,51 +1968,51 @@ msgstr ""
msgid "Stock Tracking Information" msgid "Stock Tracking Information"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:10 #: stock/templates/stock/item_base.html:11
msgid "Stock Item Details" msgid "Stock Item Details"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:55 #: stock/templates/stock/item_base.html:56
msgid "" msgid ""
"This stock item is serialized - it has a unique serial number and the " "This stock item is serialized - it has a unique serial number and the "
"quantity cannot be adjusted." "quantity cannot be adjusted."
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:59 #: stock/templates/stock/item_base.html:60
msgid "This stock item cannot be deleted as it has child items" msgid "This stock item cannot be deleted as it has child items"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:63 #: stock/templates/stock/item_base.html:64
msgid "" msgid ""
"This stock item will be automatically deleted when all stock is depleted." "This stock item will be automatically deleted when all stock is depleted."
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:68 #: stock/templates/stock/item_base.html:69
msgid "This stock item was split from " msgid "This stock item was split from "
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:88 #: stock/templates/stock/item_base.html:89
msgid "Belongs To" msgid "Belongs To"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:94 #: stock/templates/stock/item_base.html:95
#: stock/templates/stock/stock_adjust.html:17 #: stock/templates/stock/stock_adjust.html:17
msgid "Location" msgid "Location"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:101 #: stock/templates/stock/item_base.html:102
msgid "Serial Number" msgid "Serial Number"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:160 #: stock/templates/stock/item_base.html:161
msgid "Last Updated" msgid "Last Updated"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:165 #: stock/templates/stock/item_base.html:166
msgid "Last Stocktake" msgid "Last Stocktake"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:169 #: stock/templates/stock/item_base.html:170
msgid "No stocktake performed" msgid "No stocktake performed"
msgstr "" msgstr ""
@ -2280,3 +2310,55 @@ msgstr ""
#: templates/stock_table.html:17 #: templates/stock_table.html:17
msgid "Delete Stock" msgid "Delete Stock"
msgstr "" msgstr ""
#: templates/table_filters.html:22
msgid "Include sublocations"
msgstr ""
#: templates/table_filters.html:23
msgid "Include stock in sublocations"
msgstr ""
#: templates/table_filters.html:27
msgid "Active parts"
msgstr ""
#: templates/table_filters.html:28
msgid "Show stock for active parts"
msgstr ""
#: templates/table_filters.html:32 templates/table_filters.html:33
msgid "Stock status"
msgstr ""
#: templates/table_filters.html:53
msgid "Order status"
msgstr ""
#: templates/table_filters.html:64
msgid "Include subcategories"
msgstr ""
#: templates/table_filters.html:65
msgid "Include parts in subcategories"
msgstr ""
#: templates/table_filters.html:69
msgid "Active"
msgstr ""
#: templates/table_filters.html:70
msgid "Show active parts"
msgstr ""
#: templates/table_filters.html:74
msgid "Template"
msgstr ""
#: templates/table_filters.html:78
msgid "Stock available"
msgstr ""
#: templates/table_filters.html:82
msgid "Low stock"
msgstr ""

View File

@ -3,6 +3,7 @@
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% load inventree_extras %} {% load inventree_extras %}
{% load status_codes %}
{% block page_title %} {% block page_title %}
InvenTree | {{ order }} InvenTree | {{ order }}
@ -69,7 +70,7 @@ InvenTree | {{ order }}
<tr> <tr>
<td><span class='fas fa-info'></span></td> <td><span class='fas fa-info'></span></td>
<td>{% trans "Status" %}</td> <td>{% trans "Status" %}</td>
<td>{% include "order/order_status.html" %}</td> <td>{% order_status order.status %}</td>
</tr> </tr>
{% if order.link %} {% if order.link %}
<tr> <tr>

View File

@ -1,13 +0,0 @@
{% if order.status == OrderStatus.PENDING %}
<span class='label label-large label-info'>
{% elif order.status == OrderStatus.PLACED %}
<span class='label label-large label-primary'>
{% elif order.status == OrderStatus.COMPLETE %}
<span class='label label-large label-success'>
{% elif order.status == OrderStatus.CANCELLED or order.status == OrderStatus.RETURNED %}
<span class='label label-large label-warning'>
{% else %}
<span class='label label-large label-danger'>
{% endif %}
{{ order.get_status_display }}
</span>

View File

@ -1,22 +0,0 @@
<table class='table table-striped table-condensed po-table' id='po-table' {% if toolbar %}data-toolbar='{{ toolbar }}'{% endif %}>
<thead>
<tr>
<th data-field='company' data-sortable='true' data-searchable='true'>Company</th>
<th data-field='reference' data-sortable='true' data-searchable='true'>Order Reference</th>
<th data-field='description' data-sortable='true' data-searchable='true'>Description</th>
<th data-field='status' data-sortable='true'>Status</th>
<th data-field='items' data-sortable='true'>Items</th>
</tr>
</thead>
<tbody>
{% for order in orders %}
<tr>
<td>{% include "hover_image.html" with image=order.supplier.image hover=True %}<a href="{{ order.supplier.get_absolute_url }}purchase-orders/">{{ order.supplier.name }}</a></td>
<td><a href="{% url 'po-detail' order.id %}">{{ order }}</a></td>
<td>{{ order.description }}</td>
<td>{% include "order/order_status.html" %}</td>
<td>{{ order.lines.count }}</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -1,6 +1,7 @@
{% extends "order/order_base.html" %} {% extends "order/order_base.html" %}
{% load inventree_extras %} {% load inventree_extras %}
{% load status_codes %}
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}

View File

@ -13,8 +13,11 @@ InvenTree | Purchase Orders
<hr> <hr>
<div id='table-buttons'> <div id='table-buttons'>
<div class='btn-group' style='float: right;'> <div class='button-toolbar container-fluid' style='float: right;'>
<button class='btn btn-primary' type='button' id='po-create' title='Create new purchase order'>New Purchase Order</button> <button class='btn btn-primary' type='button' id='po-create' title='Create new purchase order'>New Purchase Order</button>
<div class='filter-list' id='filter-list-order'>
<!-- An empty div in which the filter list will be constructed -->
</div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,44 @@
"""
Tests for the Order API
"""
from rest_framework.test import APITestCase
from rest_framework import status
from django.urls import reverse
from django.contrib.auth import get_user_model
class OrderTest(APITestCase):
fixtures = [
'category',
'part',
'company',
'location',
'supplier_part',
'stock',
]
def setUp(self):
# Create a user for auth
User = get_user_model()
User.objects.create_user('testuser', 'test@testing.com', 'password')
self.client.login(username='testuser', password='password')
def doGet(self, url, options=''):
return self.client.get(url + "?" + options, format='json')
def test_po_list(self,):
url = reverse('api-po-list')
# List all order items
response = self.doGet(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Filter by stuff
response = self.doGet(url, 'status=10&part=1&supplier_part=1')
self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@ -8,7 +8,8 @@ from __future__ import unicode_literals
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from django.conf import settings from django.conf import settings
from django.db.models import Q, Sum, Count from django.db.models import Q, F, Sum, Count
from django.db.models.functions import Coalesce
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
@ -19,6 +20,7 @@ from django.conf.urls import url, include
from django.urls import reverse from django.urls import reverse
import os import os
from decimal import Decimal
from .models import Part, PartCategory, BomItem, PartStar from .models import Part, PartCategory, BomItem, PartStar
from .models import PartParameter, PartParameterTemplate from .models import PartParameter, PartParameterTemplate
@ -147,6 +149,18 @@ class PartList(generics.ListCreateAPIView):
- GET: Return list of objects - GET: Return list of objects
- POST: Create a new Part object - POST: Create a new Part object
The Part object list can be filtered by:
- category: Filter by PartCategory reference
- cascade: If true, include parts from sub-categories
- is_template: Is the part a template part?
- variant_of: Filter by variant_of Part reference
- assembly: Filter by assembly field
- component: Filter by component field
- trackable: Filter by trackable field
- purchaseable: Filter by purcahseable field
- salable: Filter by salable field
- active: Filter by active field
""" """
serializer_class = part_serializers.PartSerializer serializer_class = part_serializers.PartSerializer
@ -210,11 +224,39 @@ class PartList(generics.ListCreateAPIView):
'active', 'active',
).annotate( ).annotate(
# Quantity of items which are "in stock" # Quantity of items which are "in stock"
in_stock=Sum('stock_items__quantity', filter=stock_filter), in_stock=Coalesce(Sum('stock_items__quantity', filter=stock_filter), Decimal(0)),
on_order=Sum('supplier_parts__purchase_order_line_items__quantity', filter=order_filter), on_order=Coalesce(Sum('supplier_parts__purchase_order_line_items__quantity', filter=order_filter), Decimal(0)),
building=Sum('builds__quantity', filter=build_filter), building=Coalesce(Sum('builds__quantity', filter=build_filter), Decimal(0)),
) )
# If we are filtering by 'has_stock' status
has_stock = self.request.query_params.get('has_stock', None)
if has_stock is not None:
has_stock = str2bool(has_stock)
if has_stock:
# Filter items which have a non-null 'in_stock' quantity above zero
data = data.filter(in_stock__gt=0)
else:
# Filter items which a null or zero 'in_stock' quantity
data = data.filter(Q(in_stock__lte=0))
# If we are filtering by 'low_stock' status
low_stock = self.request.query_params.get('low_stock', None)
if low_stock is not None:
low_stock = str2bool(low_stock)
if low_stock:
# Ignore any parts which do not have a specified 'minimum_stock' level
data = data.exclude(minimum_stock=0)
# Filter items which have an 'in_stock' level lower than 'minimum_stock'
data = data.filter(Q(in_stock__lt=F('minimum_stock')))
else:
# Filter items which have an 'in_stock' level higher than 'minimum_stock'
data = data.filter(Q(in_stock__gte=F('minimum_stock')))
# Reduce the number of lookups we need to do for the part categories # Reduce the number of lookups we need to do for the part categories
categories = {} categories = {}
@ -261,23 +303,23 @@ class PartList(generics.ListCreateAPIView):
cascade = str2bool(self.request.query_params.get('cascade', False)) cascade = str2bool(self.request.query_params.get('cascade', False))
if cat_id is not None: if cat_id is None:
# Top-level parts
if isNull(cat_id): if not cascade:
parts_list = parts_list.filter(category=None) parts_list = parts_list.filter(category=None)
else:
try:
cat_id = int(cat_id)
category = PartCategory.objects.get(pk=cat_id)
# If '?cascade=true' then include parts which exist in sub-categories else:
if cascade: try:
parts_list = parts_list.filter(category__in=category.getUniqueChildren()) category = PartCategory.objects.get(pk=cat_id)
# Just return parts directly in the requested category
else: # If '?cascade=true' then include parts which exist in sub-categories
parts_list = parts_list.filter(category=cat_id) if cascade:
except (ValueError, PartCategory.DoesNotExist): parts_list = parts_list.filter(category__in=category.getUniqueChildren())
pass # Just return parts directly in the requested category
else:
parts_list = parts_list.filter(category=cat_id)
except (ValueError, PartCategory.DoesNotExist):
pass
# Ensure that related models are pre-loaded to reduce DB trips # Ensure that related models are pre-loaded to reduce DB trips
parts_list = self.get_serializer_class().setup_eager_loading(parts_list) parts_list = self.get_serializer_class().setup_eager_loading(parts_list)
@ -443,6 +485,19 @@ class BomList(generics.ListCreateAPIView):
def get_queryset(self): def get_queryset(self):
queryset = BomItem.objects.all() queryset = BomItem.objects.all()
queryset = self.get_serializer_class().setup_eager_loading(queryset) queryset = self.get_serializer_class().setup_eager_loading(queryset)
# Filter by part?
part = self.request.query_params.get('part', None)
if part is not None:
queryset = queryset.filter(part=part)
# Filter by sub-part?
sub_part = self.request.query_params.get('sub_part', None)
if sub_part is not None:
queryset = queryset.filter(sub_part=sub_part)
return queryset return queryset
permission_classes = [ permission_classes = [
@ -456,8 +511,6 @@ class BomList(generics.ListCreateAPIView):
] ]
filter_fields = [ filter_fields = [
'part',
'sub_part',
] ]

View File

@ -37,7 +37,7 @@ from InvenTree import helpers
from InvenTree import validators from InvenTree import validators
from InvenTree.models import InvenTreeTree, InvenTreeAttachment from InvenTree.models import InvenTreeTree, InvenTreeAttachment
from InvenTree.fields import InvenTreeURLField from InvenTree.fields import InvenTreeURLField
from InvenTree.helpers import decimal2string from InvenTree.helpers import decimal2string, normalize
from InvenTree.status_codes import BuildStatus, StockStatus, OrderStatus from InvenTree.status_codes import BuildStatus, StockStatus, OrderStatus
@ -659,7 +659,7 @@ class Part(models.Model):
if total: if total:
return total return total
else: else:
return 0 return Decimal(0)
@property @property
def has_bom(self): def has_bom(self):
@ -781,6 +781,9 @@ class Part(models.Model):
if min_price == max_price: if min_price == max_price:
return min_price return min_price
min_price = normalize(min_price)
max_price = normalize(max_price)
return "{a} - {b}".format(a=min_price, b=max_price) return "{a} - {b}".format(a=min_price, b=max_price)
def get_supplier_price_range(self, quantity=1): def get_supplier_price_range(self, quantity=1):
@ -804,6 +807,9 @@ class Part(models.Model):
if min_price is None or max_price is None: if min_price is None or max_price is None:
return None return None
min_price = normalize(min_price)
max_price = normalize(max_price)
return (min_price, max_price) return (min_price, max_price)
def get_bom_price_range(self, quantity=1): def get_bom_price_range(self, quantity=1):
@ -837,6 +843,9 @@ class Part(models.Model):
if min_price is None or max_price is None: if min_price is None or max_price is None:
return None return None
min_price = normalize(min_price)
max_price = normalize(max_price)
return (min_price, max_price) return (min_price, max_price)
def get_price_range(self, quantity=1, buy=True, bom=True): def get_price_range(self, quantity=1, buy=True, bom=True):

View File

@ -1,5 +1,6 @@
{% extends "part/part_base.html" %} {% extends "part/part_base.html" %}
{% block details %} {% block details %}
{% load status_codes %}
{% include "part/tabs.html" with tab="allocation" %} {% include "part/tabs.html" with tab="allocation" %}
@ -17,7 +18,7 @@
<td><a href="{% url 'build-detail' allocation.build.id %}">{{ allocation.build.title }}</a></td> <td><a href="{% url 'build-detail' allocation.build.id %}">{{ allocation.build.title }}</a></td>
<td>{{ allocation.build.quantity }} &times <a href="{% url 'part-detail' allocation.build.part.id %}">{{ allocation.build.part.full_name }}</a></td> <td>{{ allocation.build.quantity }} &times <a href="{% url 'part-detail' allocation.build.part.id %}">{{ allocation.build.part.full_name }}</a></td>
<td>{{ allocation.quantity }}</td> <td>{{ allocation.quantity }}</td>
<td>{% include "build_status.html" with build=allocation.build %}</td> <td>{% build_status allocation.build.status %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

View File

@ -7,9 +7,14 @@
<h3>Part Builds</h3> <h3>Part Builds</h3>
<div id='button-toolbar'> <div id='button-toolbar'>
{% if part.active %} <div class='button-toolbar container-flui' style='float: right';>
<button class="btn btn-success" id='start-build'>Start New Build</button> {% if part.active %}
{% endif %} <button class="btn btn-success" id='start-build'>Start New Build</button>
{% endif %}
<div class='filter-list' id='filter-list-build'>
<!-- Empty div for filters -->
</div>
</div>
</div> </div>
<table class='table table-striped table-condensed' data-toolbar='#button-toolbar' id='build-table'> <table class='table table-striped table-condensed' data-toolbar='#button-toolbar' id='build-table'>
@ -31,64 +36,12 @@
}); });
}); });
$("#build-table").inventreeTable({ loadBuildTable($("#build-table"), {
queryParams: function(p) { url: "{% url 'api-build-list' %}",
return { params: {
part: {{ part.id }}, part_detail: "true",
} part: {{ part.id }},
}, }
columns: [
{
field: 'pk',
title: 'ID',
visible: false,
},
{
field: 'title',
title: 'Title',
formatter: function(value, row, index, field) {
return renderLink(value, row.url);
}
},
{
field: 'quantity',
title: 'Quantity',
},
{
field: 'status',
title: 'Status',
formatter: function(value, row, index, field) {
var color = '';
switch (value) {
case 10: // Pending
color = 'label-info';
break;
case 20: // Allocated
color = 'label-primary';
break;
case 30: // Cancelled
color = 'label-danger';
break;
case 40: // Complete
color = 'label-success';
break;
default:
break;
}
var html = "<span class='label " + color + " label-large'>" + row.status_text + "</span>";
return html;
}
},
{
field: 'completion_date',
title: 'Completed'
}
],
url: "{% url 'api-build-list' %}",
}); });
{% endblock %} {% endblock %}

View File

@ -1,10 +0,0 @@
{% for build in builds %}
<tr>
<td><a href="{% url 'build-detail' build.id %}">{{ build.title }}</a></td>
<td>{{ build.quantity }}</td>
<td>
{% include "build_status.html" with build=build %}
</td>
<td>{% if build.completion_date %}{{ build.completion_date }}{% endif %}</td>
</tr>
{% endfor %}

View File

@ -99,7 +99,7 @@
<div class='button-toolbar container-fluid' style="float: right;"> <div class='button-toolbar container-fluid' style="float: right;">
<button class='btn btn-default' id='part-export' title='Export Part Data'>Export</button> <button class='btn btn-default' id='part-export' title='Export Part Data'>Export</button>
<button class='btn btn-success' id='part-create'>New Part</button> <button class='btn btn-success' id='part-create'>New Part</button>
<div class='dropdown' style='float: right;'> <div class='btn dropdown'>
<button id='part-options' class='btn btn-primary dropdown-toggle' type='button' data-toggle="dropdown">Options<span class='caret'></span></button> <button id='part-options' class='btn btn-primary dropdown-toggle' type='button' data-toggle="dropdown">Options<span class='caret'></span></button>
<ul class='dropdown-menu'> <ul class='dropdown-menu'>
<li><a href='#' id='multi-part-category' title='Set category'>Set Category</a></li> <li><a href='#' id='multi-part-category' title='Set category'>Set Category</a></li>
@ -107,6 +107,9 @@
<li><a href='#' id='multi-part-export' title='Export'>Export Data</a></li> <li><a href='#' id='multi-part-export' title='Export'>Export Data</a></li>
</ul> </ul>
</div> </div>
<div class='filter-list' id='filter-list-parts'>
<!-- Empty div -->
</div>
</div> </div>
</div> </div>

View File

@ -8,18 +8,19 @@
{% if part.active == False %} {% if part.active == False %}
<div class='alert alert-danger alert-block'> <div class='alert alert-danger alert-block'>
{% trans "This part is not active" %}" {% trans "This part is not active" %}
</div> </div>
{% endif %} {% endif %}
{% if part.is_template %} {% if part.is_template %}
<div class='alert alert-info alert-block'> <div class='alert alert-info alert-block'>
This part is a <i>template part</i>.<br> {% trans "This part is a template part." %}
It is not a <i>real</i> part, but real parts can be based on this template. <br>
{% trans "It is not a real part, but real parts can be based on this template." %}
</div> </div>
{% endif %} {% endif %}
{% if part.variant_of %} {% if part.variant_of %}
<div class='alert alert-info alert-block'> <div class='alert alert-info alert-block'>
This part is a variant of <b><a href="{% url 'part-detail' part.variant_of.id %}">{{ part.variant_of.full_name }}</a></b> {% trans "This part is a variant of" %} <b><a href="{% url 'part-detail' part.variant_of.id %}">{{ part.variant_of.full_name }}</a></b>
</div> </div>
{% endif %} {% endif %}
@ -93,25 +94,30 @@
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<table class="table table-striped"> <table class="table table-striped">
<col width='25'>
<tr> <tr>
<td><span class='fas fa-boxes'></span></td>
<td> <td>
<h4>{% trans "Available Stock" %}</h4> <h4>{% trans "Available Stock" %}</h4>
</td> </td>
<td><h4>{% decimal part.available_stock %} {{ part.units }}</h4></td> <td><h4>{% decimal part.available_stock %} {{ part.units }}</h4></td>
</tr> </tr>
<tr> <tr>
<td><span class='fas fa-map-marker-alt'></span></td>
<td>{% trans "In Stock" %}</td> <td>{% trans "In Stock" %}</td>
<td>{% include "part/stock_count.html" %}</td> <td>{% include "part/stock_count.html" %}</td>
</tr> </tr>
{% if not part.is_template %} {% if not part.is_template %}
{% if part.allocation_count > 0 %} {% if part.allocation_count > 0 %}
<tr> <tr>
<td><span class='fas fa-dolly'></span></td>
<td>{% trans "Allocated" %}</td> <td>{% trans "Allocated" %}</td>
<td>{% decimal part.allocation_count %}</td> <td>{% decimal part.allocation_count %}</td>
</tr> </tr>
{% endif %} {% endif %}
{% if part.on_order > 0 %} {% if part.on_order > 0 %}
<tr> <tr>
<td><span class='fas fa-shopping-cart'></span></td>
<td>{% trans "On Order" %}</td> <td>{% trans "On Order" %}</td>
<td>{% decimal part.on_order %}</td> <td>{% decimal part.on_order %}</td>
</tr> </tr>
@ -120,16 +126,19 @@
{% if not part.is_template %} {% if not part.is_template %}
{% if part.assembly %} {% if part.assembly %}
<tr> <tr>
<td><span class='fas fa-tools'></span></td>
<td colspan='2'> <td colspan='2'>
<b>{% trans "Build Status" %}</b> <b>{% trans "Build Status" %}</b>
</td> </td>
</tr> </tr>
<tr> <tr>
<td></td>
<td>{% trans "Can Build" %}</td> <td>{% trans "Can Build" %}</td>
<td>{% decimal part.can_build %}</td> <td>{% decimal part.can_build %}</td>
</tr> </tr>
{% if part.quantity_being_built > 0 %} {% if part.quantity_being_built > 0 %}
<tr> <tr>
<td></td>
<td>{% trans "Underway" %}</td> <td>{% trans "Underway" %}</td>
<td>{% decimal part.quantity_being_built %}</td> <td>{% decimal part.quantity_being_built %}</td>
</tr> </tr>

View File

@ -4,7 +4,7 @@
{% decimal part.total_stock %} {% decimal part.total_stock %}
{% if part.total_stock == 0 %} {% if part.total_stock == 0 %}
<span class='label label-danger'>{% trans "No Stock" %}</span> <span class='label label-danger label-right'>{% trans "No Stock" %}</span>
{% elif part.total_stock < part.minimum_stock %} {% elif part.total_stock < part.minimum_stock %}
<span class='label label-warning'>{% trans "Low Stock" %}</span> <span class='label label-warning label-right'>{% trans "Low Stock" %}</span>
{% endif %} {% endif %}

View File

@ -25,7 +25,7 @@
<li{% ifequal tab 'bom' %} class="active"{% endifequal %}> <li{% ifequal tab 'bom' %} class="active"{% endifequal %}>
<a href="{% url 'part-bom' part.id %}">{% trans "BOM" %}<span class="badge{% if part.is_bom_valid == False %} badge-alert{% endif %}">{{ part.bom_count }}</span></a></li> <a href="{% url 'part-bom' part.id %}">{% trans "BOM" %}<span class="badge{% if part.is_bom_valid == False %} badge-alert{% endif %}">{{ part.bom_count }}</span></a></li>
<li{% ifequal tab 'build' %} class="active"{% endifequal %}> <li{% ifequal tab 'build' %} class="active"{% endifequal %}>
<a href="{% url 'part-build' part.id %}">{% trans "Build" %}<span class='badge'>{{ part.active_builds|length }}</span></a></li> <a href="{% url 'part-build' part.id %}">{% trans "Build" %}<span class='badge'>{{ part.builds|length }}</span></a></li>
{% endif %} {% endif %}
{% if part.component or part.used_in_count > 0 %} {% if part.component or part.used_in_count > 0 %}
<li{% ifequal tab 'used' %} class="active"{% endifequal %}> <li{% ifequal tab 'used' %} class="active"{% endifequal %}>

View File

@ -35,7 +35,7 @@
title: 'Part', title: 'Part',
sortable: true, sortable: true,
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
var html = imageHoverIcon(row.part_detail.image_url) + renderLink(value.full_name, value.url + 'bom/'); var html = imageHoverIcon(row.part_detail.thumbnail) + renderLink(value.full_name, value.url + 'bom/');
if (!row.part_detail.active) { if (!row.part_detail.active) {
html += "<span class='label label-warning' style='float: right;'>INACTIVE</span>"; html += "<span class='label label-warning' style='float: right;'>INACTIVE</span>";

View File

@ -0,0 +1,38 @@
"""
Provide templates for the various model status codes.
"""
from django import template
from django.utils.safestring import mark_safe
from InvenTree.status_codes import OrderStatus, StockStatus, BuildStatus
register = template.Library()
@register.simple_tag
def order_status(key, *args, **kwargs):
return mark_safe(OrderStatus.render(key))
@register.simple_tag
def stock_status(key, *args, **kwargs):
return mark_safe(StockStatus.render(key))
@register.simple_tag
def build_status(key, *args, **kwargs):
return mark_safe(BuildStatus.render(key))
@register.simple_tag(takes_context=True)
def load_status_codes(context):
"""
Make the various StatusCodes available to the page context
"""
context['order_status_codes'] = OrderStatus.list()
context['stock_status_codes'] = StockStatus.list()
context['build_status_codes'] = BuildStatus.list()
# Need to return something as the result is rendered to the page
return ''

View File

@ -82,7 +82,8 @@ class PartAPITest(APITestCase):
def test_get_all_parts(self): def test_get_all_parts(self):
url = reverse('api-part-list') url = reverse('api-part-list')
response = self.client.get(url, format='json') data = {'cascade': True}
response = self.client.get(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 8) self.assertEqual(len(response.data), 8)

View File

@ -410,8 +410,16 @@ class StockList(generics.ListCreateAPIView):
# Start with all objects # Start with all objects
stock_list = super(StockList, self).get_queryset() stock_list = super(StockList, self).get_queryset()
# Filter out parts which are not actually "in stock"
stock_list = stock_list.filter(customer=None, belongs_to=None) stock_list = stock_list.filter(customer=None, belongs_to=None)
# Do we wish to filter by "active parts"
active = self.request.query_params.get('active', None)
if active is not None:
active = str2bool(active)
stock_list = stock_list.filter(part__active=active)
# Does the client wish to filter by the Part ID? # Does the client wish to filter by the Part ID?
part_id = self.request.query_params.get('part', None) part_id = self.request.query_params.get('part', None)

View File

@ -1,6 +1,7 @@
{% extends "stock/stock_app_base.html" %} {% extends "stock/stock_app_base.html" %}
{% load static %} {% load static %}
{% load inventree_extras %} {% load inventree_extras %}
{% load status_codes %}
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
@ -172,7 +173,7 @@
<tr> <tr>
<td><span class='fas fa-info'></span></td> <td><span class='fas fa-info'></span></td>
<td>{% trans "Status" %}</td> <td>{% trans "Status" %}</td>
<td>{{ item.get_status_display }}</td> <td>{% stock_status item.status %}</td>
</tr> </tr>
</table> </table>
</div> </div>

View File

@ -1,4 +1,5 @@
{% load static %} {% load static %}
{% load i18n %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -102,7 +103,9 @@ InvenTree
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script> <script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script> <script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/bom.js' %}"></script> <script type='text/javascript' src="{% static 'script/inventree/bom.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/filters.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/tables.js' %}"></script> <script type='text/javascript' src="{% static 'script/inventree/tables.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/build.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/modals.js' %}"></script> <script type='text/javascript' src="{% static 'script/inventree/modals.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/order.js' %}"></script> <script type='text/javascript' src="{% static 'script/inventree/order.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script> <script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script>
@ -115,7 +118,10 @@ InvenTree
{% block js_load %} {% block js_load %}
{% endblock %} {% endblock %}
{% include "table_filters.html" %}
<script type='text/javascript'> <script type='text/javascript'>
$(document).ready(function () { $(document).ready(function () {
{% block js_ready %} {% block js_ready %}
{% endblock %} {% endblock %}
@ -124,6 +130,7 @@ $(document).ready(function () {
showCachedAlerts(); showCachedAlerts();
}); });
</script> </script>
{% block js %} {% block js %}

View File

@ -1,11 +0,0 @@
{% if build.status == BuildStatus.PENDING %}
<span class='label label-large label-info'>
{% elif build.status == BuildStatus.ALLOCATED %}
<span class='label label-large label-primary'>
{% elif build.status == BuildStatus.CANCELLED %}
<span class='label label-large label-danger'>
{% elif build.status == BuildStatus.COMPLETE %}
<span class='label label-large label-success'>
{% endif %}
{{ build.get_status_display }}
</span>

View File

@ -0,0 +1,35 @@
/*
* Status codes for the {{ label }} model.
*/
var {{ label }}Codes = {
{% for opt in options %}'{{ opt.key }}': {
key: '{{ opt.key }}',
value: '{{ opt.value }}',{% if opt.label %}
label: '{{ opt.label }}',{% endif %}
},{% endfor %}
};
/*
* Render the status for a {{ label }} object.
* Uses the values specified in "status_codes.py"
* This function is generated by the "status_codes.html" template
*/
function {{ label }}StatusDisplay(key) {
key = String(key);
var label = {{ label }}Codes[key].label;
var value = {{ label }}Codes[key].value;
if (value == null || value.length == 0) {
value = key;
}
// Label not found, return the original string
if (label == null || label.length == 0) {
return value;
}
return `<span class='label label-${label}'>${value}</span>`;
}

View File

@ -6,7 +6,7 @@
{% if read_only %} {% if read_only %}
{% else %} {% else %}
<button class="btn btn-success" id='item-create'>{% trans "New Stock Item" %}</button> <button class="btn btn-success" id='item-create'>{% trans "New Stock Item" %}</button>
<div class="dropdown" style='float: right;'> <div class="btn dropdown">
<button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}<span class="caret"></span></button> <button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}<span class="caret"></span></button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="#" id='multi-item-add' title='Add to selected stock items'>{% trans "Add stock" %}</a></li> <li><a href="#" id='multi-item-add' title='Add to selected stock items'>{% trans "Add stock" %}</a></li>
@ -18,6 +18,9 @@
</ul> </ul>
</div> </div>
{% endif %} {% endif %}
<div class='filter-list' id='filter-list-stock'>
<!-- An empty div in which the filter list will be constructed -->
</div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,98 @@
{% load i18n %}
{% load status_codes %}
{% load_status_codes %}
<script type='text/javascript'>
{% include "status_codes.html" with label='stock' options=stock_status_codes %}
{% include "status_codes.html" with label='build' options=build_status_codes %}
{% include "status_codes.html" with label='order' options=order_status_codes %}
function getAvailableTableFilters(tableKey) {
tableKey = tableKey.toLowerCase();
// Filters for the "Stock" table
if (tableKey == 'stock') {
return {
cascade: {
type: 'bool',
title: '{% trans "Include sublocations" %}',
description: '{% trans "Include stock in sublocations" %}',
},
active: {
type: 'bool',
title: '{% trans "Active parts" %}',
description: '{% trans "Show stock for active parts" %}',
},
'status': {
options: stockCodes,
title: '{% trans "Stock status" %}',
description: '{% trans "Stock status" %}',
},
};
}
// Filters for the "Build" table
if (tableKey == 'build') {
return {
status: {
title: '{% trans "Build status" %}',
options: buildCodes,
},
};
}
// Filters for the "Order" table
if (tableKey == "order") {
return {
status: {
title: '{% trans "Order status" %}',
options: orderCodes,
},
};
}
// Filters for the "Parts" table
if (tableKey == "parts") {
return {
cascade: {
type: 'bool',
title: '{% trans "Include subcategories" %}',
description: '{% trans "Include parts in subcategories" %}',
},
active: {
type: 'bool',
title: '{% trans "Active" %}',
description: '{% trans "Show active parts" %}',
},
is_template: {
type: 'bool',
title: '{% trans "Template" %}',
},
has_stock: {
type: 'bool',
title: '{% trans "Stock available" %}'
},
low_stock: {
type: 'bool',
title: '{% trans "Low stock" %}',
},
assembly: {
type: 'bool',
title: '{% trans "Assembly" %}',
},
component: {
type: 'bool',
title: '{% trans "Component" %}',
},
};
}
// Finally, no matching key
return {};
}
</script>