mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	Merge remote-tracking branch 'inventree/master'
This commit is contained in:
		@@ -11,6 +11,8 @@ from django.core import validators
 | 
			
		||||
from django import forms
 | 
			
		||||
from decimal import Decimal
 | 
			
		||||
 | 
			
		||||
from InvenTree.helpers import normalize
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InvenTreeURLFormField(FormURLField):
 | 
			
		||||
    """ Custom URL form field with custom scheme validators """
 | 
			
		||||
@@ -53,7 +55,7 @@ class RoundingDecimalFormField(forms.DecimalField):
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if type(value) == Decimal:
 | 
			
		||||
            return value.normalize()
 | 
			
		||||
            return normalize(value)
 | 
			
		||||
        else:
 | 
			
		||||
            return value
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,8 @@ import json
 | 
			
		||||
import os.path
 | 
			
		||||
from PIL import Image
 | 
			
		||||
 | 
			
		||||
from decimal import Decimal
 | 
			
		||||
 | 
			
		||||
from wsgiref.util import FileWrapper
 | 
			
		||||
from django.http import StreamingHttpResponse
 | 
			
		||||
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', '']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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):
 | 
			
		||||
    """
 | 
			
		||||
    Format a Decimal number as a string,
 | 
			
		||||
@@ -117,6 +133,9 @@ def decimal2string(d):
 | 
			
		||||
        A string representation of the input number
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    if type(d) is Decimal:
 | 
			
		||||
        d = normalize(d)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        # Ensure that the provided string can actually be converted to a float
 | 
			
		||||
        float(d)
 | 
			
		||||
 
 | 
			
		||||
@@ -125,6 +125,8 @@
 | 
			
		||||
 | 
			
		||||
.label-right {
 | 
			
		||||
    float: right;
 | 
			
		||||
    margin-left: 3px;
 | 
			
		||||
    margin-right: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Bootstrap table overrides */
 | 
			
		||||
@@ -157,6 +159,66 @@
 | 
			
		||||
    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 */
 | 
			
		||||
 | 
			
		||||
.hover-img-thumb {
 | 
			
		||||
 
 | 
			
		||||
@@ -3272,10 +3272,7 @@
 | 
			
		||||
      }, {
 | 
			
		||||
        key: 'getOptions',
 | 
			
		||||
        value: function getOptions() {
 | 
			
		||||
          // deep copy and remove data
 | 
			
		||||
          var options = JSON.parse(JSON.stringify(this.options));
 | 
			
		||||
          delete options.data;
 | 
			
		||||
          return options;
 | 
			
		||||
         return this.options;
 | 
			
		||||
        }
 | 
			
		||||
      }, {
 | 
			
		||||
        key: 'getSelections',
 | 
			
		||||
 
 | 
			
		||||
@@ -133,11 +133,11 @@ function loadBomTable(table, options) {
 | 
			
		||||
            title: 'Part',
 | 
			
		||||
            sortable: true,
 | 
			
		||||
            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
 | 
			
		||||
                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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
    
 | 
			
		||||
    count = parseFloat(count);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										410
									
								
								InvenTree/InvenTree/static/script/inventree/filters.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										410
									
								
								InvenTree/InvenTree/static/script/inventree/filters.js
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -104,8 +104,21 @@ function removePurchaseOrderLineItem(e) {
 | 
			
		||||
function loadPurchaseOrderTable(table, options) {
 | 
			
		||||
    /* 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({
 | 
			
		||||
        url: options.url,
 | 
			
		||||
        queryParams: filters,
 | 
			
		||||
        groupBy: false,
 | 
			
		||||
        original: params,
 | 
			
		||||
        formatNoMatches: function() { return "No purchase orders found"; },
 | 
			
		||||
        columns: [
 | 
			
		||||
            {
 | 
			
		||||
@@ -144,7 +157,7 @@ function loadPurchaseOrderTable(table, options) {
 | 
			
		||||
                field: 'status',
 | 
			
		||||
                title: 'Status',
 | 
			
		||||
                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;
 | 
			
		||||
}
 | 
			
		||||
@@ -87,17 +87,15 @@ function loadPartTable(table, url, options={}) {
 | 
			
		||||
     *      buttons: If provided, link buttons to selection status of this table
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    // Default query params
 | 
			
		||||
    query = options.query;
 | 
			
		||||
    
 | 
			
		||||
    if (!options.allowInactive) {
 | 
			
		||||
        // Only display active parts
 | 
			
		||||
        query.active = true;
 | 
			
		||||
    var params = options.parms || {};
 | 
			
		||||
 | 
			
		||||
    var filters = loadTableFilters("parts");
 | 
			
		||||
 | 
			
		||||
    for (var key in params) {
 | 
			
		||||
        filters[key] = params[key];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Include sub-category search
 | 
			
		||||
    // TODO - Make this user-configurable!
 | 
			
		||||
    query.cascade = true;
 | 
			
		||||
    setupFilterList("parts", $(table));
 | 
			
		||||
 | 
			
		||||
    var columns = [
 | 
			
		||||
        {
 | 
			
		||||
@@ -142,11 +140,21 @@ function loadPartTable(table, url, options={}) {
 | 
			
		||||
            var display = imageHoverIcon(row.thumbnail) + renderLink(name, '/part/' + row.pk + '/');
 | 
			
		||||
            
 | 
			
		||||
            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) {
 | 
			
		||||
                display = display + "<span class='label label-warning' style='float: right;'>INACTIVE</span>";
 | 
			
		||||
                display += `<span class='label label-warning label-right'>INACTIVE</span>`; 
 | 
			
		||||
            }
 | 
			
		||||
            return display; 
 | 
			
		||||
        }
 | 
			
		||||
@@ -175,7 +183,7 @@ function loadPartTable(table, url, options={}) {
 | 
			
		||||
                return renderLink(row.category__name, "/part/category/" + row.category + "/");
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                return '';
 | 
			
		||||
                return 'No category';
 | 
			
		||||
            }
 | 
			
		||||
        }   
 | 
			
		||||
    });
 | 
			
		||||
@@ -217,10 +225,10 @@ function loadPartTable(table, url, options={}) {
 | 
			
		||||
        url: url,
 | 
			
		||||
        sortName: 'name',
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        queryParams: filters,
 | 
			
		||||
        groupBy: false,
 | 
			
		||||
        original: params,
 | 
			
		||||
        formatNoMatches: function() { return "No parts found"; },
 | 
			
		||||
        queryParams: function(p) {
 | 
			
		||||
            return  query;
 | 
			
		||||
        },
 | 
			
		||||
        columns: columns,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ function getStockLocations(filters={}, options={}) {
 | 
			
		||||
    return inventreeGet('/api/stock/location/', filters, options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Functions for interacting with stock management forms
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -28,6 +29,7 @@ function removeStockRow(e) {
 | 
			
		||||
    $('#' + row).remove();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function loadStockTable(table, options) {
 | 
			
		||||
    /* Load data into a stock table with adjustable options.
 | 
			
		||||
     * 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
 | 
			
		||||
     *  groupByField - Column for grouping stock items
 | 
			
		||||
     *  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 || {};
 | 
			
		||||
 | 
			
		||||
    // Enforce 'cascade' option
 | 
			
		||||
    // TODO - Make this user-configurable?
 | 
			
		||||
    params.cascade = true;
 | 
			
		||||
    var filterListElement = options.filterList || "#filter-list-stock";
 | 
			
		||||
 | 
			
		||||
    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({
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        formatNoMatches: function() {
 | 
			
		||||
            return 'No stock items matching query';
 | 
			
		||||
        },
 | 
			
		||||
        url: options.url,
 | 
			
		||||
        queryParams: filters,
 | 
			
		||||
        customSort: customGroupSorter,
 | 
			
		||||
        groupBy: true,
 | 
			
		||||
        original: original,
 | 
			
		||||
        groupByField: options.groupByField || 'part',
 | 
			
		||||
        groupByFormatter: function(field, id, data) {
 | 
			
		||||
 | 
			
		||||
@@ -87,6 +105,29 @@ function loadStockTable(table, options) {
 | 
			
		||||
                stock = +stock.toFixed(5);
 | 
			
		||||
 | 
			
		||||
                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') {
 | 
			
		||||
                var batches = [];
 | 
			
		||||
 | 
			
		||||
@@ -211,13 +252,17 @@ function loadStockTable(table, options) {
 | 
			
		||||
 | 
			
		||||
                    var text = renderLink(val, '/stock/item/' + row.pk + '/');
 | 
			
		||||
                    
 | 
			
		||||
                    if (row.status_text != 'OK') {
 | 
			
		||||
                        text = text + "<span class='badge'>" + row.status_text + "</span>";
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    return text;
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                field: 'status',
 | 
			
		||||
                title: 'Status',
 | 
			
		||||
                sortable: 'true',
 | 
			
		||||
                formatter: function(value, row, index, field) {
 | 
			
		||||
                    return stockStatusDisplay(value);
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                field: 'batch',
 | 
			
		||||
                title: 'Batch',
 | 
			
		||||
@@ -241,8 +286,6 @@ function loadStockTable(table, options) {
 | 
			
		||||
                title: 'Notes',
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        url: options.url,
 | 
			
		||||
        queryParams: params,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (options.buttons) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 * Sets some useful defaults, and manage persistent settings.
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,56 @@ from django.utils.translation import ugettext as _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    def items(cls):
 | 
			
		||||
@@ -41,6 +91,15 @@ class OrderStatus(StatusCode):
 | 
			
		||||
        RETURNED: _("Returned"),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    labels = {
 | 
			
		||||
        PENDING: "primary",
 | 
			
		||||
        PLACED: "primary",
 | 
			
		||||
        COMPLETE: "success",
 | 
			
		||||
        CANCELLED: "danger",
 | 
			
		||||
        LOST: "warning",
 | 
			
		||||
        RETURNED: "warning",
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # Open orders
 | 
			
		||||
    OPEN = [
 | 
			
		||||
        PENDING,
 | 
			
		||||
@@ -71,6 +130,12 @@ class StockStatus(StatusCode):
 | 
			
		||||
        LOST: _("Lost"),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    labels = {
 | 
			
		||||
        OK: 'success',
 | 
			
		||||
        ATTENTION: 'warning',
 | 
			
		||||
        DAMAGED: 'danger',
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # The following codes correspond to parts that are 'available' or 'in stock'
 | 
			
		||||
    AVAILABLE_CODES = [
 | 
			
		||||
        OK,
 | 
			
		||||
@@ -100,6 +165,13 @@ class BuildStatus(StatusCode):
 | 
			
		||||
        COMPLETE: _("Complete"),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    labels = {
 | 
			
		||||
        PENDING: 'primary',
 | 
			
		||||
        ALLOCATED: 'info',
 | 
			
		||||
        COMPLETE: 'success',
 | 
			
		||||
        CANCELLED: 'danger',
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ACTIVE_CODES = [
 | 
			
		||||
        PENDING,
 | 
			
		||||
        ALLOCATED
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,8 @@ from rest_framework import generics, permissions
 | 
			
		||||
 | 
			
		||||
from django.conf.urls import url, include
 | 
			
		||||
 | 
			
		||||
from InvenTree.helpers import str2bool
 | 
			
		||||
 | 
			
		||||
from .models import Build, BuildItem
 | 
			
		||||
from .serializers import BuildSerializer, BuildItemSerializer
 | 
			
		||||
 | 
			
		||||
@@ -36,9 +38,41 @@ class BuildList(generics.ListCreateAPIView):
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    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):
 | 
			
		||||
    """ API endpoint for detail view of a Build object """
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ from InvenTree.serializers import InvenTreeModelSerializer
 | 
			
		||||
from stock.serializers import StockItemSerializerBrief
 | 
			
		||||
 | 
			
		||||
from .models import Build, BuildItem
 | 
			
		||||
from part.serializers import PartBriefSerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BuildSerializer(InvenTreeModelSerializer):
 | 
			
		||||
@@ -18,6 +19,16 @@ class BuildSerializer(InvenTreeModelSerializer):
 | 
			
		||||
    url = serializers.CharField(source='get_absolute_url', 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:
 | 
			
		||||
        model = Build
 | 
			
		||||
        fields = [
 | 
			
		||||
@@ -27,6 +38,7 @@ class BuildSerializer(InvenTreeModelSerializer):
 | 
			
		||||
            'creation_date',
 | 
			
		||||
            'completion_date',
 | 
			
		||||
            'part',
 | 
			
		||||
            'part_detail',
 | 
			
		||||
            'quantity',
 | 
			
		||||
            'status',
 | 
			
		||||
            'status_text',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load status_codes %}
 | 
			
		||||
 | 
			
		||||
{% block page_title %}
 | 
			
		||||
InvenTree | Build - {{ build }}
 | 
			
		||||
@@ -22,61 +24,70 @@ InvenTree | Build - {{ build }}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class='media-body'>
 | 
			
		||||
                <h4>Build Details</h4>
 | 
			
		||||
 | 
			
		||||
                <p>
 | 
			
		||||
                    <div class='btn-row'>
 | 
			
		||||
                        <div class='btn-group'>
 | 
			
		||||
                            <button type='button' class='btn btn-default btn-glyph' id='build-edit' title='Edit Build'>
 | 
			
		||||
                                <span class='glyphicon glyphicon-edit'/>
 | 
			
		||||
                            </button>
 | 
			
		||||
                            {% if build.is_active %}
 | 
			
		||||
                            <button type='button' class='btn btn-default btn-glyph' id='build-complete' title="Complete Build">
 | 
			
		||||
                                <span class='glyphicon glyphicon-send'/>
 | 
			
		||||
                            </button>
 | 
			
		||||
                            <button type='button' class='btn btn-default btn-glyph' id='build-cancel' title='Cancel Build'>
 | 
			
		||||
                                <span class='glyphicon glyphicon-remove'/>
 | 
			
		||||
                            </button>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            {% if build.status == BuildStatus.CANCELLED %}
 | 
			
		||||
                            <button type='button' class='btn btn-default btn-glyph' id='build-delete' title='Delete Build'>
 | 
			
		||||
                                <span class='glyphicon glyphicon-trash'/>
 | 
			
		||||
                            </button>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </div>
 | 
			
		||||
                <h4>{% trans "Build" %}</h4>
 | 
			
		||||
                <div class='btn-row'>
 | 
			
		||||
                    <div class='btn-group'>
 | 
			
		||||
                        <button type='button' class='btn btn-default btn-glyph' id='build-edit' title='Edit Build'>
 | 
			
		||||
                            <span class='glyphicon glyphicon-edit'/>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        {% if build.is_active %}
 | 
			
		||||
                        <button type='button' class='btn btn-default btn-glyph' id='build-complete' title="Complete Build">
 | 
			
		||||
                            <span class='glyphicon glyphicon-send'/>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button type='button' class='btn btn-default btn-glyph' id='build-cancel' title='Cancel Build'>
 | 
			
		||||
                            <span class='glyphicon glyphicon-remove'/>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        {% if build.status == BuildStatus.CANCELLED %}
 | 
			
		||||
                        <button type='button' class='btn btn-default btn-glyph' id='build-delete' title='Delete Build'>
 | 
			
		||||
                            <span class='glyphicon glyphicon-trash'/>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </p>
 | 
			
		||||
 | 
			
		||||
                <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 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>
 | 
			
		||||
 | 
			
		||||
<hr>
 | 
			
		||||
 
 | 
			
		||||
@@ -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 %}
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% block details %}
 | 
			
		||||
{% load status_codes %}
 | 
			
		||||
 | 
			
		||||
{% include "build/tabs.html" with tab='details' %}
 | 
			
		||||
 | 
			
		||||
@@ -39,7 +40,7 @@
 | 
			
		||||
<tr>
 | 
			
		||||
    <td><span class='fas fa-info'></span></td>
 | 
			
		||||
    <td>{% trans "Status" %}</td>
 | 
			
		||||
    <td>{% include "build_status.html" with build=build %}</td>
 | 
			
		||||
    <td>{% build_status build.status %}</td>
 | 
			
		||||
</tr>
 | 
			
		||||
{% if build.batch %}
 | 
			
		||||
<tr>
 | 
			
		||||
 
 | 
			
		||||
@@ -13,21 +13,23 @@ InvenTree | Build List
 | 
			
		||||
        <h3>Part Builds</h3>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='col-sm-6'>
 | 
			
		||||
        <div class='container' id='active-build-toolbar' style='float: right;'>
 | 
			
		||||
            <div class='btn-group' style='float: right;'>
 | 
			
		||||
                <button type='button' class="btn btn-success" id='new-build'>Start New Build</button>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <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>
 | 
			
		||||
 | 
			
		||||
<hr>
 | 
			
		||||
 | 
			
		||||
{% 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" %}
 | 
			
		||||
<table class='table table-striped table-condensed' id='build-table' data-toolbar='#button-toolbar'>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
@@ -38,38 +40,18 @@ InvenTree | Build List
 | 
			
		||||
 | 
			
		||||
    $("#new-build").click(function() {
 | 
			
		||||
        launchModalForm(
 | 
			
		||||
                        "{% url 'build-create' %}",
 | 
			
		||||
                        {
 | 
			
		||||
                            follow: true
 | 
			
		||||
                        });
 | 
			
		||||
            "{% url 'build-create' %}",
 | 
			
		||||
            {
 | 
			
		||||
                follow: true
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $(".build-table").inventreeTable({
 | 
			
		||||
        formatNoMatches: function() { return 'No builds found'; },
 | 
			
		||||
        columns: [
 | 
			
		||||
            {
 | 
			
		||||
                field: 'name',
 | 
			
		||||
                title: 'Build',
 | 
			
		||||
                sortable: true,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                field: 'part',
 | 
			
		||||
                title: 'Part',
 | 
			
		||||
                sortable: true,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                title: 'Quantity',
 | 
			
		||||
                sortable: true,
 | 
			
		||||
                searchable: false
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                title: 'Status',
 | 
			
		||||
                sortable: true,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                sortable: true,
 | 
			
		||||
            },
 | 
			
		||||
        ]
 | 
			
		||||
    loadBuildTable($("#build-table"), {
 | 
			
		||||
        url: "{% url 'api-build-list' %}",
 | 
			
		||||
        params: {
 | 
			
		||||
            part_detail: "true",
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -143,9 +143,7 @@ class TestBuildViews(TestCase):
 | 
			
		||||
 | 
			
		||||
        content = str(response.content)
 | 
			
		||||
 | 
			
		||||
        # Content should contain build titles
 | 
			
		||||
        for build in Build.objects.all():
 | 
			
		||||
            self.assertIn(build.title, content)
 | 
			
		||||
        self.assertIn("Part Builds", content)
 | 
			
		||||
 | 
			
		||||
    def test_build_detail(self):
 | 
			
		||||
        """ Test the detail view for a Build object """
 | 
			
		||||
 
 | 
			
		||||
@@ -30,21 +30,21 @@
 | 
			
		||||
 | 
			
		||||
    $("#part-create").click(function () {
 | 
			
		||||
        launchModalForm(
 | 
			
		||||
                        "{% url 'supplier-part-create' %}",
 | 
			
		||||
                        {
 | 
			
		||||
                            data: {
 | 
			
		||||
                                supplier: {{ company.id }}
 | 
			
		||||
                            },
 | 
			
		||||
                            reload: true,
 | 
			
		||||
                            secondary: [
 | 
			
		||||
                                {
 | 
			
		||||
                                    field: 'part',
 | 
			
		||||
                                    label: 'New Part',
 | 
			
		||||
                                    title: 'Create New Part',
 | 
			
		||||
                                    url: "{% url 'part-create' %}"
 | 
			
		||||
                                },
 | 
			
		||||
                            ]
 | 
			
		||||
                        });
 | 
			
		||||
            "{% url 'supplier-part-create' %}",
 | 
			
		||||
            {
 | 
			
		||||
                data: {
 | 
			
		||||
                    supplier: {{ company.id }}
 | 
			
		||||
                },
 | 
			
		||||
                reload: true,
 | 
			
		||||
                secondary: [
 | 
			
		||||
                    {
 | 
			
		||||
                        field: 'part',
 | 
			
		||||
                        label: 'New Part',
 | 
			
		||||
                        title: 'Create New Part',
 | 
			
		||||
                        url: "{% url 'part-create' %}"
 | 
			
		||||
                    },
 | 
			
		||||
                ]
 | 
			
		||||
            });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("#part-table").inventreeTable({
 | 
			
		||||
@@ -64,7 +64,22 @@
 | 
			
		||||
                field: 'part_detail.full_name',
 | 
			
		||||
                title: '{% trans "Part" %}',
 | 
			
		||||
                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;
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,11 @@
 | 
			
		||||
<hr>
 | 
			
		||||
 | 
			
		||||
<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>
 | 
			
		||||
        <div class='filter-list' id='filter-list-order'>
 | 
			
		||||
            <!-- Empty div -->
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 %}
 | 
			
		||||
@@ -96,8 +96,8 @@ class CompanySimpleTest(TestCase):
 | 
			
		||||
    def test_part_pricing(self):
 | 
			
		||||
        m2x4 = Part.objects.get(name='M2x4 LPHS')
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(m2x4.get_price_info(10), "70.00000 - 75.00000")
 | 
			
		||||
        self.assertEqual(m2x4.get_price_info(100), "125.00000 - 350.00000")
 | 
			
		||||
        self.assertEqual(m2x4.get_price_info(10), "70 - 75")
 | 
			
		||||
        self.assertEqual(m2x4.get_price_info(100), "125 - 350")
 | 
			
		||||
 | 
			
		||||
        pmin, pmax = m2x4.get_price_range(5)
 | 
			
		||||
        self.assertEqual(pmin, 35)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: \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"
 | 
			
		||||
"Last-Translator: Christian Schlüter <chschlue@gmail.com>\n"
 | 
			
		||||
"Language-Team: C <kde-i18n-doc@kde.org>\n"
 | 
			
		||||
@@ -17,30 +17,30 @@ msgstr ""
 | 
			
		||||
"Plural-Forms: nplurals=2; plural=(n != 1);\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"
 | 
			
		||||
msgstr "Keine gültige Menge"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:243
 | 
			
		||||
#: InvenTree/helpers.py:262
 | 
			
		||||
msgid "Empty serial number string"
 | 
			
		||||
msgstr "Keine Seriennummer angegeben"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:264 InvenTree/helpers.py:281
 | 
			
		||||
#: InvenTree/helpers.py:283 InvenTree/helpers.py:300
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Duplicate serial: {n}"
 | 
			
		||||
msgstr "Doppelte Seriennummer: {n}"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:268 InvenTree/helpers.py:271 InvenTree/helpers.py:274
 | 
			
		||||
#: InvenTree/helpers.py:285
 | 
			
		||||
#: InvenTree/helpers.py:287 InvenTree/helpers.py:290 InvenTree/helpers.py:293
 | 
			
		||||
#: InvenTree/helpers.py:304
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Invalid group: {g}"
 | 
			
		||||
msgstr "Ungültige Gruppe: {g}"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:291
 | 
			
		||||
#: InvenTree/helpers.py:310
 | 
			
		||||
msgid "No serial numbers found"
 | 
			
		||||
msgstr "Keine Seriennummern gefunden"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:295
 | 
			
		||||
#: InvenTree/helpers.py:314
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Number of unique serial number ({s}) must match quantity ({q})"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -71,49 +71,49 @@ msgstr "Französisch"
 | 
			
		||||
msgid "Polish"
 | 
			
		||||
msgstr "Polnisch"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:36 InvenTree/status_codes.py:97
 | 
			
		||||
#: InvenTree/status_codes.py:86 InvenTree/status_codes.py:162
 | 
			
		||||
msgid "Pending"
 | 
			
		||||
msgstr "Ausstehend"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:37
 | 
			
		||||
#: InvenTree/status_codes.py:87
 | 
			
		||||
msgid "Placed"
 | 
			
		||||
msgstr "Platziert"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:38 InvenTree/status_codes.py:100
 | 
			
		||||
#: InvenTree/status_codes.py:88 InvenTree/status_codes.py:165
 | 
			
		||||
msgid "Complete"
 | 
			
		||||
msgstr "Fertig"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:39 InvenTree/status_codes.py:99
 | 
			
		||||
#: InvenTree/status_codes.py:89 InvenTree/status_codes.py:164
 | 
			
		||||
msgid "Cancelled"
 | 
			
		||||
msgstr "Storniert"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:40 InvenTree/status_codes.py:71
 | 
			
		||||
#: InvenTree/status_codes.py:90 InvenTree/status_codes.py:130
 | 
			
		||||
msgid "Lost"
 | 
			
		||||
msgstr "Verloren"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:41
 | 
			
		||||
#: InvenTree/status_codes.py:91
 | 
			
		||||
msgid "Returned"
 | 
			
		||||
msgstr "Zurückgegeben"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:67
 | 
			
		||||
#: InvenTree/status_codes.py:126
 | 
			
		||||
msgid "OK"
 | 
			
		||||
msgstr "OK"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:68
 | 
			
		||||
#: InvenTree/status_codes.py:127
 | 
			
		||||
msgid "Attention needed"
 | 
			
		||||
msgstr "erfordert Eingriff"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:69
 | 
			
		||||
#: InvenTree/status_codes.py:128
 | 
			
		||||
msgid "Damaged"
 | 
			
		||||
msgstr "Beschädigt"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:70
 | 
			
		||||
#: InvenTree/status_codes.py:129
 | 
			
		||||
msgid "Destroyed"
 | 
			
		||||
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
 | 
			
		||||
#: 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"
 | 
			
		||||
msgstr "Zugeordnet"
 | 
			
		||||
 | 
			
		||||
@@ -178,7 +178,7 @@ msgstr ""
 | 
			
		||||
msgid "Number of parts to build"
 | 
			
		||||
msgstr "Anzahl der zu bauenden Teile"
 | 
			
		||||
 | 
			
		||||
#: build/models.py:82
 | 
			
		||||
#: build/models.py:82 templates/table_filters.html:42
 | 
			
		||||
msgid "Build status"
 | 
			
		||||
msgstr "Bau-Status"
 | 
			
		||||
 | 
			
		||||
@@ -231,10 +231,10 @@ msgstr "Zuweisung aufheben"
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/allocate_edit.html:19
 | 
			
		||||
#: 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
 | 
			
		||||
#: 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
 | 
			
		||||
msgid "Part"
 | 
			
		||||
msgstr "Teil"
 | 
			
		||||
@@ -266,16 +266,62 @@ msgstr "Teile bestellen"
 | 
			
		||||
#: company/templates/company/index.html:54
 | 
			
		||||
#: company/templates/company/supplier_part_base.html:50
 | 
			
		||||
#: 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
 | 
			
		||||
msgid "Description"
 | 
			
		||||
msgstr "Beschreibung"
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/allocate_view.html:22
 | 
			
		||||
#: part/templates/part/part_base.html:115
 | 
			
		||||
#: part/templates/part/part_base.html:121
 | 
			
		||||
msgid "On Order"
 | 
			
		||||
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
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#| msgid "Build status"
 | 
			
		||||
@@ -289,68 +335,49 @@ msgid "Are you sure you want to unallocate these parts?"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Sind Sie sicher, dass sie die folgenden Zulieferer-Teile löschen möchten?"
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:8
 | 
			
		||||
msgid "Build Details"
 | 
			
		||||
msgstr "Bau-Status"
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:16
 | 
			
		||||
#: build/templates/build/detail.html:17
 | 
			
		||||
msgid "Title"
 | 
			
		||||
msgstr "Titel"
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:26
 | 
			
		||||
#: 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
 | 
			
		||||
#: build/templates/build/detail.html:31
 | 
			
		||||
msgid "Stock Source"
 | 
			
		||||
msgstr "Lagerobjekt"
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:35
 | 
			
		||||
#: build/templates/build/detail.html:36
 | 
			
		||||
msgid "Stock can be taken from any available location."
 | 
			
		||||
msgstr "Bestand kann jedem verfügbaren Lagerort entnommen werden."
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:41
 | 
			
		||||
#: order/templates/order/order_base.html:71
 | 
			
		||||
#: stock/templates/stock/item_base.html:174
 | 
			
		||||
msgid "Status"
 | 
			
		||||
msgstr "Status"
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:47
 | 
			
		||||
#: stock/templates/stock/item_base.html:114
 | 
			
		||||
#: build/templates/build/detail.html:48
 | 
			
		||||
#: stock/templates/stock/item_base.html:115
 | 
			
		||||
msgid "Batch"
 | 
			
		||||
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_detail.html:24
 | 
			
		||||
#: part/templates/part/detail.html:67 part/templates/part/part_base.html:84
 | 
			
		||||
#: stock/templates/stock/item_base.html:142
 | 
			
		||||
#: part/templates/part/detail.html:67 part/templates/part/part_base.html:85
 | 
			
		||||
#: stock/templates/stock/item_base.html:143
 | 
			
		||||
msgid "External Link"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:60
 | 
			
		||||
#: order/templates/order/order_base.html:83
 | 
			
		||||
#: build/templates/build/detail.html:61
 | 
			
		||||
#: order/templates/order/order_base.html:84
 | 
			
		||||
msgid "Created"
 | 
			
		||||
msgstr "Erstellt"
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:66
 | 
			
		||||
#: build/templates/build/detail.html:67
 | 
			
		||||
msgid "Enough Parts?"
 | 
			
		||||
msgstr "Genügend Teile?"
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:69
 | 
			
		||||
#: build/templates/build/detail.html:70
 | 
			
		||||
msgid "Yes"
 | 
			
		||||
msgstr "Ja"
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:71
 | 
			
		||||
#: build/templates/build/detail.html:72
 | 
			
		||||
msgid "No"
 | 
			
		||||
msgstr "Nein"
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:79
 | 
			
		||||
#: build/templates/build/detail.html:80
 | 
			
		||||
msgid "Completed"
 | 
			
		||||
msgstr "Fertig"
 | 
			
		||||
 | 
			
		||||
@@ -622,7 +649,7 @@ msgstr "Zulieferer auswählen"
 | 
			
		||||
msgid "Supplier stock keeping unit"
 | 
			
		||||
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_detail.html:30
 | 
			
		||||
msgid "Manufacturer"
 | 
			
		||||
@@ -680,7 +707,7 @@ msgid "Company Details"
 | 
			
		||||
msgstr "Firmenbemerkungen"
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/detail.html:16
 | 
			
		||||
#: stock/templates/stock/item_base.html:135
 | 
			
		||||
#: stock/templates/stock/item_base.html:136
 | 
			
		||||
msgid "Customer"
 | 
			
		||||
msgstr "Kunde"
 | 
			
		||||
 | 
			
		||||
@@ -688,9 +715,9 @@ msgstr "Kunde"
 | 
			
		||||
#: company/templates/company/index.html:46
 | 
			
		||||
#: company/templates/company/supplier_part_base.html:44
 | 
			
		||||
#: 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
 | 
			
		||||
#: stock/templates/stock/item_base.html:149
 | 
			
		||||
#: stock/templates/stock/item_base.html:150
 | 
			
		||||
msgid "Supplier"
 | 
			
		||||
msgstr "Zulieferer"
 | 
			
		||||
 | 
			
		||||
@@ -716,13 +743,13 @@ msgstr ""
 | 
			
		||||
msgid "Delete Parts"
 | 
			
		||||
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_detail.html:22
 | 
			
		||||
msgid "SKU"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/detail_part.html:90
 | 
			
		||||
#: company/templates/company/detail_part.html:105
 | 
			
		||||
msgid "Link"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -783,7 +810,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/supplier_part_base.html:6
 | 
			
		||||
#: 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"
 | 
			
		||||
msgstr "Zulieferer-Teil"
 | 
			
		||||
 | 
			
		||||
@@ -810,7 +837,7 @@ msgstr "IPN (Interne Produktnummer)"
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/supplier_part_base.html:57
 | 
			
		||||
#: 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"
 | 
			
		||||
msgstr "Notiz"
 | 
			
		||||
 | 
			
		||||
@@ -1030,7 +1057,7 @@ msgstr "Position - Referenz"
 | 
			
		||||
msgid "Line item notes"
 | 
			
		||||
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"
 | 
			
		||||
msgstr "Kaufvertrag"
 | 
			
		||||
 | 
			
		||||
@@ -1042,16 +1069,16 @@ msgstr "Zulieferer-Teil"
 | 
			
		||||
msgid "Number of items received"
 | 
			
		||||
msgstr "Empfangene Objekt-Anzahl"
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/order_base.html:61
 | 
			
		||||
#: order/templates/order/order_base.html:62
 | 
			
		||||
msgid "Purchase Order Details"
 | 
			
		||||
msgstr "Bestelldetails"
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/order_base.html:89
 | 
			
		||||
#: order/templates/order/order_base.html:90
 | 
			
		||||
msgid "Issued"
 | 
			
		||||
msgstr "Aufgegeben"
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/order_base.html:96
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:31
 | 
			
		||||
#: order/templates/order/order_base.html:97
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:32
 | 
			
		||||
msgid "Received"
 | 
			
		||||
msgstr "Empfangen"
 | 
			
		||||
 | 
			
		||||
@@ -1152,23 +1179,23 @@ msgid "Are you sure you want to delete this attachment?"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"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"
 | 
			
		||||
msgstr "Position hinzufügen"
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:19
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:20
 | 
			
		||||
msgid "Order Items"
 | 
			
		||||
msgstr "Bestellungspositionen"
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:24
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:25
 | 
			
		||||
msgid "Line"
 | 
			
		||||
msgstr "Position"
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:27
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:28
 | 
			
		||||
msgid "Order Code"
 | 
			
		||||
msgstr "Bestellnummer"
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:28
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:29
 | 
			
		||||
msgid "Reference"
 | 
			
		||||
msgstr "Referenz"
 | 
			
		||||
 | 
			
		||||
@@ -1475,63 +1502,63 @@ msgstr "Bemerkungen - unterstüzt Markdown-Formatierung"
 | 
			
		||||
msgid "Stored BOM checksum"
 | 
			
		||||
msgstr "Prüfsumme der Stückliste gespeichert"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1040
 | 
			
		||||
#: part/models.py:1049
 | 
			
		||||
msgid "Parameter template name must be unique"
 | 
			
		||||
msgstr "Vorlagen-Name des Parameters muss eindeutig sein"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1045
 | 
			
		||||
#: part/models.py:1054
 | 
			
		||||
msgid "Parameter Name"
 | 
			
		||||
msgstr "Name des Parameters"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1047
 | 
			
		||||
#: part/models.py:1056
 | 
			
		||||
msgid "Parameter Units"
 | 
			
		||||
msgstr "Parameter Einheit"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1073
 | 
			
		||||
#: part/models.py:1082
 | 
			
		||||
msgid "Parent Part"
 | 
			
		||||
msgstr "Ausgangsteil"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1075
 | 
			
		||||
#: part/models.py:1084
 | 
			
		||||
msgid "Parameter Template"
 | 
			
		||||
msgstr "Parameter Vorlage"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1077
 | 
			
		||||
#: part/models.py:1086
 | 
			
		||||
msgid "Parameter Value"
 | 
			
		||||
msgstr "Parameter Wert"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1101
 | 
			
		||||
#: part/models.py:1110
 | 
			
		||||
msgid "Select parent part"
 | 
			
		||||
msgstr "Ausgangsteil auswählen"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1110
 | 
			
		||||
#: part/models.py:1119
 | 
			
		||||
msgid "Select part to be used in BOM"
 | 
			
		||||
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"
 | 
			
		||||
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)"
 | 
			
		||||
msgstr "Geschätzter Ausschuss (absolut oder prozentual)"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1123
 | 
			
		||||
#: part/models.py:1132
 | 
			
		||||
msgid "BOM item reference"
 | 
			
		||||
msgstr "Referenz des Objekts auf der Stückliste"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1126
 | 
			
		||||
#: part/models.py:1135
 | 
			
		||||
msgid "BOM item notes"
 | 
			
		||||
msgstr "Notizen zum Stücklisten-Objekt"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1128
 | 
			
		||||
#: part/models.py:1137
 | 
			
		||||
msgid "BOM line checksum"
 | 
			
		||||
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"
 | 
			
		||||
msgstr "Teil kann nicht zu seiner eigenen Stückliste hinzugefügt werden"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1198
 | 
			
		||||
#: part/models.py:1207
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Part '{p1}' is  used in BOM for '{p2}' (recursive)"
 | 
			
		||||
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"
 | 
			
		||||
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"
 | 
			
		||||
msgstr "IPN (Interne Produktnummer)"
 | 
			
		||||
 | 
			
		||||
@@ -1637,7 +1664,7 @@ msgstr "Teil ist virtuell (kein physisches Teil)"
 | 
			
		||||
msgid "Part is not a virtual part"
 | 
			
		||||
msgstr "Teil ist nicht virtuell"
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/detail.html:132
 | 
			
		||||
#: part/templates/part/detail.html:132 templates/table_filters.html:86
 | 
			
		||||
msgid "Assembly"
 | 
			
		||||
msgstr "Baugruppe"
 | 
			
		||||
 | 
			
		||||
@@ -1649,7 +1676,7 @@ msgstr "Teil kann aus anderen Teilen angefertigt werden"
 | 
			
		||||
msgid "Part cannot be assembled from other parts"
 | 
			
		||||
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"
 | 
			
		||||
msgstr "Komponente"
 | 
			
		||||
 | 
			
		||||
@@ -1713,31 +1740,47 @@ msgstr "Teile"
 | 
			
		||||
msgid "This part is not active"
 | 
			
		||||
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"
 | 
			
		||||
msgstr "Teil favorisieren"
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:43
 | 
			
		||||
#: part/templates/part/part_base.html:44
 | 
			
		||||
msgid "Show pricing information"
 | 
			
		||||
msgstr "Kosteninformationen ansehen"
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:98
 | 
			
		||||
#: part/templates/part/part_base.html:101
 | 
			
		||||
msgid "Available Stock"
 | 
			
		||||
msgstr "Verfügbarer Lagerbestand"
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:103
 | 
			
		||||
#: part/templates/part/part_base.html:107
 | 
			
		||||
msgid "In Stock"
 | 
			
		||||
msgstr "Auf Lager"
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:124
 | 
			
		||||
#: part/templates/part/part_base.html:131
 | 
			
		||||
msgid "Build Status"
 | 
			
		||||
msgstr "Bau-Status"
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:128
 | 
			
		||||
#: part/templates/part/part_base.html:136
 | 
			
		||||
msgid "Can Build"
 | 
			
		||||
msgstr "Herstellbar?"
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:133
 | 
			
		||||
#: part/templates/part/part_base.html:142
 | 
			
		||||
msgid "Underway"
 | 
			
		||||
msgstr "unterwegs"
 | 
			
		||||
 | 
			
		||||
@@ -1787,11 +1830,6 @@ msgstr "Varianten"
 | 
			
		||||
msgid "BOM"
 | 
			
		||||
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
 | 
			
		||||
msgid "Used In"
 | 
			
		||||
msgstr "Benutzt in"
 | 
			
		||||
@@ -2164,11 +2202,11 @@ msgstr "Link auf externe Seite für weitere Informationen"
 | 
			
		||||
msgid "Stock Tracking Information"
 | 
			
		||||
msgstr "Informationen zum Lagerbestands-Tracking"
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:10
 | 
			
		||||
#: stock/templates/stock/item_base.html:11
 | 
			
		||||
msgid "Stock Item Details"
 | 
			
		||||
msgstr "Lagerbestands-Details"
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:55
 | 
			
		||||
#: stock/templates/stock/item_base.html:56
 | 
			
		||||
msgid ""
 | 
			
		||||
"This stock item is serialized - it has a unique serial number and the "
 | 
			
		||||
"quantity cannot be adjusted."
 | 
			
		||||
@@ -2176,45 +2214,45 @@ msgstr ""
 | 
			
		||||
"Dieses Lagerobjekt ist serialisiert. Es hat eine eindeutige Seriennummer und "
 | 
			
		||||
"die Anzahl kann nicht angepasst werden."
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:59
 | 
			
		||||
#: stock/templates/stock/item_base.html:60
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#| msgid "Stock item cannot be created for a template Part"
 | 
			
		||||
msgid "This stock item cannot be deleted as it has child items"
 | 
			
		||||
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 ""
 | 
			
		||||
"This stock item will be automatically deleted when all stock is depleted."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Dieses Lagerobjekt wird automatisch gelöscht wenn der Lagerbestand "
 | 
			
		||||
"aufgebraucht ist."
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:68
 | 
			
		||||
#: stock/templates/stock/item_base.html:69
 | 
			
		||||
msgid "This stock item was split from "
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:88
 | 
			
		||||
#: stock/templates/stock/item_base.html:89
 | 
			
		||||
msgid "Belongs To"
 | 
			
		||||
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
 | 
			
		||||
msgid "Location"
 | 
			
		||||
msgstr "Standort"
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:101
 | 
			
		||||
#: stock/templates/stock/item_base.html:102
 | 
			
		||||
msgid "Serial Number"
 | 
			
		||||
msgstr "Seriennummer"
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:160
 | 
			
		||||
#: stock/templates/stock/item_base.html:161
 | 
			
		||||
msgid "Last Updated"
 | 
			
		||||
msgstr "Zuletzt aktualisiert"
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:165
 | 
			
		||||
#: stock/templates/stock/item_base.html:166
 | 
			
		||||
msgid "Last Stocktake"
 | 
			
		||||
msgstr "Letzte Inventur"
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:169
 | 
			
		||||
#: stock/templates/stock/item_base.html:170
 | 
			
		||||
msgid "No stocktake performed"
 | 
			
		||||
msgstr "Keine Inventur ausgeführt"
 | 
			
		||||
 | 
			
		||||
@@ -2540,6 +2578,80 @@ msgstr "bestellt"
 | 
			
		||||
msgid "Delete Stock"
 | 
			
		||||
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"
 | 
			
		||||
#~ msgstr "URL"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\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"
 | 
			
		||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
			
		||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
			
		||||
@@ -18,30 +18,30 @@ msgstr ""
 | 
			
		||||
"Content-Transfer-Encoding: 8bit\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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:243
 | 
			
		||||
#: InvenTree/helpers.py:262
 | 
			
		||||
msgid "Empty serial number string"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:264 InvenTree/helpers.py:281
 | 
			
		||||
#: InvenTree/helpers.py:283 InvenTree/helpers.py:300
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Duplicate serial: {n}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:268 InvenTree/helpers.py:271 InvenTree/helpers.py:274
 | 
			
		||||
#: InvenTree/helpers.py:285
 | 
			
		||||
#: InvenTree/helpers.py:287 InvenTree/helpers.py:290 InvenTree/helpers.py:293
 | 
			
		||||
#: InvenTree/helpers.py:304
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Invalid group: {g}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:291
 | 
			
		||||
#: InvenTree/helpers.py:310
 | 
			
		||||
msgid "No serial numbers found"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:295
 | 
			
		||||
#: InvenTree/helpers.py:314
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Number of unique serial number ({s}) must match quantity ({q})"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -70,49 +70,49 @@ msgstr ""
 | 
			
		||||
msgid "Polish"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:36 InvenTree/status_codes.py:97
 | 
			
		||||
#: InvenTree/status_codes.py:86 InvenTree/status_codes.py:162
 | 
			
		||||
msgid "Pending"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:37
 | 
			
		||||
#: InvenTree/status_codes.py:87
 | 
			
		||||
msgid "Placed"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:38 InvenTree/status_codes.py:100
 | 
			
		||||
#: InvenTree/status_codes.py:88 InvenTree/status_codes.py:165
 | 
			
		||||
msgid "Complete"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:39 InvenTree/status_codes.py:99
 | 
			
		||||
#: InvenTree/status_codes.py:89 InvenTree/status_codes.py:164
 | 
			
		||||
msgid "Cancelled"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:40 InvenTree/status_codes.py:71
 | 
			
		||||
#: InvenTree/status_codes.py:90 InvenTree/status_codes.py:130
 | 
			
		||||
msgid "Lost"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:41
 | 
			
		||||
#: InvenTree/status_codes.py:91
 | 
			
		||||
msgid "Returned"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:67
 | 
			
		||||
#: InvenTree/status_codes.py:126
 | 
			
		||||
msgid "OK"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:68
 | 
			
		||||
#: InvenTree/status_codes.py:127
 | 
			
		||||
msgid "Attention needed"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:69
 | 
			
		||||
#: InvenTree/status_codes.py:128
 | 
			
		||||
msgid "Damaged"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:70
 | 
			
		||||
#: InvenTree/status_codes.py:129
 | 
			
		||||
msgid "Destroyed"
 | 
			
		||||
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
 | 
			
		||||
#: 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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -175,7 +175,7 @@ msgstr ""
 | 
			
		||||
msgid "Number of parts to build"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/models.py:82
 | 
			
		||||
#: build/models.py:82 templates/table_filters.html:42
 | 
			
		||||
msgid "Build status"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -227,10 +227,10 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/allocate_edit.html:19
 | 
			
		||||
#: 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
 | 
			
		||||
#: 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
 | 
			
		||||
msgid "Part"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -262,16 +262,58 @@ msgstr ""
 | 
			
		||||
#: company/templates/company/index.html:54
 | 
			
		||||
#: company/templates/company/supplier_part_base.html:50
 | 
			
		||||
#: 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
 | 
			
		||||
msgid "Description"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/allocate_view.html:22
 | 
			
		||||
#: part/templates/part/part_base.html:115
 | 
			
		||||
#: part/templates/part/part_base.html:121
 | 
			
		||||
msgid "On Order"
 | 
			
		||||
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
 | 
			
		||||
msgid "Build Outputs"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -280,68 +322,49 @@ msgstr ""
 | 
			
		||||
msgid "Are you sure you want to unallocate these parts?"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:8
 | 
			
		||||
msgid "Build Details"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:16
 | 
			
		||||
#: build/templates/build/detail.html:17
 | 
			
		||||
msgid "Title"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:26
 | 
			
		||||
#: 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
 | 
			
		||||
#: build/templates/build/detail.html:31
 | 
			
		||||
msgid "Stock Source"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:35
 | 
			
		||||
#: build/templates/build/detail.html:36
 | 
			
		||||
msgid "Stock can be taken from any available location."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:41
 | 
			
		||||
#: order/templates/order/order_base.html:71
 | 
			
		||||
#: stock/templates/stock/item_base.html:174
 | 
			
		||||
msgid "Status"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:47
 | 
			
		||||
#: stock/templates/stock/item_base.html:114
 | 
			
		||||
#: build/templates/build/detail.html:48
 | 
			
		||||
#: stock/templates/stock/item_base.html:115
 | 
			
		||||
msgid "Batch"
 | 
			
		||||
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_detail.html:24
 | 
			
		||||
#: part/templates/part/detail.html:67 part/templates/part/part_base.html:84
 | 
			
		||||
#: stock/templates/stock/item_base.html:142
 | 
			
		||||
#: part/templates/part/detail.html:67 part/templates/part/part_base.html:85
 | 
			
		||||
#: stock/templates/stock/item_base.html:143
 | 
			
		||||
msgid "External Link"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:60
 | 
			
		||||
#: order/templates/order/order_base.html:83
 | 
			
		||||
#: build/templates/build/detail.html:61
 | 
			
		||||
#: order/templates/order/order_base.html:84
 | 
			
		||||
msgid "Created"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:66
 | 
			
		||||
#: build/templates/build/detail.html:67
 | 
			
		||||
msgid "Enough Parts?"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:69
 | 
			
		||||
#: build/templates/build/detail.html:70
 | 
			
		||||
msgid "Yes"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:71
 | 
			
		||||
#: build/templates/build/detail.html:72
 | 
			
		||||
msgid "No"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:79
 | 
			
		||||
#: build/templates/build/detail.html:80
 | 
			
		||||
msgid "Completed"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -581,7 +604,7 @@ msgstr ""
 | 
			
		||||
msgid "Supplier stock keeping unit"
 | 
			
		||||
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_detail.html:30
 | 
			
		||||
msgid "Manufacturer"
 | 
			
		||||
@@ -637,7 +660,7 @@ msgid "Company Details"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/detail.html:16
 | 
			
		||||
#: stock/templates/stock/item_base.html:135
 | 
			
		||||
#: stock/templates/stock/item_base.html:136
 | 
			
		||||
msgid "Customer"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -645,9 +668,9 @@ msgstr ""
 | 
			
		||||
#: company/templates/company/index.html:46
 | 
			
		||||
#: company/templates/company/supplier_part_base.html:44
 | 
			
		||||
#: 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
 | 
			
		||||
#: stock/templates/stock/item_base.html:149
 | 
			
		||||
#: stock/templates/stock/item_base.html:150
 | 
			
		||||
msgid "Supplier"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -669,13 +692,13 @@ msgstr ""
 | 
			
		||||
msgid "Delete Parts"
 | 
			
		||||
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_detail.html:22
 | 
			
		||||
msgid "SKU"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/detail_part.html:90
 | 
			
		||||
#: company/templates/company/detail_part.html:105
 | 
			
		||||
msgid "Link"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -727,7 +750,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/supplier_part_base.html:6
 | 
			
		||||
#: 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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -748,7 +771,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/supplier_part_base.html:57
 | 
			
		||||
#: 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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -930,7 +953,7 @@ msgstr ""
 | 
			
		||||
msgid "Line item notes"
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -942,16 +965,16 @@ msgstr ""
 | 
			
		||||
msgid "Number of items received"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/order_base.html:61
 | 
			
		||||
#: order/templates/order/order_base.html:62
 | 
			
		||||
msgid "Purchase Order Details"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/order_base.html:89
 | 
			
		||||
#: order/templates/order/order_base.html:90
 | 
			
		||||
msgid "Issued"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/order_base.html:96
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:31
 | 
			
		||||
#: order/templates/order/order_base.html:97
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:32
 | 
			
		||||
msgid "Received"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -1035,23 +1058,23 @@ msgstr ""
 | 
			
		||||
msgid "Are you sure you want to delete this attachment?"
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:19
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:20
 | 
			
		||||
msgid "Order Items"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:24
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:25
 | 
			
		||||
msgid "Line"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:27
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:28
 | 
			
		||||
msgid "Order Code"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:28
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:29
 | 
			
		||||
msgid "Reference"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -1324,63 +1347,63 @@ msgstr ""
 | 
			
		||||
msgid "Stored BOM checksum"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1040
 | 
			
		||||
#: part/models.py:1049
 | 
			
		||||
msgid "Parameter template name must be unique"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1045
 | 
			
		||||
#: part/models.py:1054
 | 
			
		||||
msgid "Parameter Name"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1047
 | 
			
		||||
#: part/models.py:1056
 | 
			
		||||
msgid "Parameter Units"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1073
 | 
			
		||||
#: part/models.py:1082
 | 
			
		||||
msgid "Parent Part"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1075
 | 
			
		||||
#: part/models.py:1084
 | 
			
		||||
msgid "Parameter Template"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1077
 | 
			
		||||
#: part/models.py:1086
 | 
			
		||||
msgid "Parameter Value"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1101
 | 
			
		||||
#: part/models.py:1110
 | 
			
		||||
msgid "Select parent part"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1110
 | 
			
		||||
#: part/models.py:1119
 | 
			
		||||
msgid "Select part to be used in BOM"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1117
 | 
			
		||||
#: part/models.py:1126
 | 
			
		||||
msgid "BOM quantity for this BOM item"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1120
 | 
			
		||||
#: part/models.py:1129
 | 
			
		||||
msgid "Estimated build wastage quantity (absolute or percentage)"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1123
 | 
			
		||||
#: part/models.py:1132
 | 
			
		||||
msgid "BOM item reference"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1126
 | 
			
		||||
#: part/models.py:1135
 | 
			
		||||
msgid "BOM item notes"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1128
 | 
			
		||||
#: part/models.py:1137
 | 
			
		||||
msgid "BOM line checksum"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1191
 | 
			
		||||
#: part/models.py:1200
 | 
			
		||||
msgid "Part cannot be added to its own Bill of Materials"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1198
 | 
			
		||||
#: part/models.py:1207
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Part '{p1}' is  used in BOM for '{p2}' (recursive)"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -1430,7 +1453,7 @@ msgstr ""
 | 
			
		||||
msgid "Part Details"
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -1482,7 +1505,7 @@ msgstr ""
 | 
			
		||||
msgid "Part is not a virtual part"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/detail.html:132
 | 
			
		||||
#: part/templates/part/detail.html:132 templates/table_filters.html:86
 | 
			
		||||
msgid "Assembly"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -1494,7 +1517,7 @@ msgstr ""
 | 
			
		||||
msgid "Part cannot be assembled from other parts"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/detail.html:141
 | 
			
		||||
#: part/templates/part/detail.html:141 templates/table_filters.html:90
 | 
			
		||||
msgid "Component"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -1554,31 +1577,43 @@ msgstr ""
 | 
			
		||||
msgid "This part is not active"
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:43
 | 
			
		||||
#: part/templates/part/part_base.html:44
 | 
			
		||||
msgid "Show pricing information"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:98
 | 
			
		||||
#: part/templates/part/part_base.html:101
 | 
			
		||||
msgid "Available Stock"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:103
 | 
			
		||||
#: part/templates/part/part_base.html:107
 | 
			
		||||
msgid "In Stock"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:124
 | 
			
		||||
#: part/templates/part/part_base.html:131
 | 
			
		||||
msgid "Build Status"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:128
 | 
			
		||||
#: part/templates/part/part_base.html:136
 | 
			
		||||
msgid "Can Build"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:133
 | 
			
		||||
#: part/templates/part/part_base.html:142
 | 
			
		||||
msgid "Underway"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -1618,11 +1653,6 @@ msgstr ""
 | 
			
		||||
msgid "BOM"
 | 
			
		||||
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
 | 
			
		||||
msgid "Used In"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -1938,51 +1968,51 @@ msgstr ""
 | 
			
		||||
msgid "Stock Tracking Information"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:10
 | 
			
		||||
#: stock/templates/stock/item_base.html:11
 | 
			
		||||
msgid "Stock Item Details"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:55
 | 
			
		||||
#: stock/templates/stock/item_base.html:56
 | 
			
		||||
msgid ""
 | 
			
		||||
"This stock item is serialized - it has a unique serial number and the "
 | 
			
		||||
"quantity cannot be adjusted."
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:63
 | 
			
		||||
#: stock/templates/stock/item_base.html:64
 | 
			
		||||
msgid ""
 | 
			
		||||
"This stock item will be automatically deleted when all stock is depleted."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:68
 | 
			
		||||
#: stock/templates/stock/item_base.html:69
 | 
			
		||||
msgid "This stock item was split from "
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:88
 | 
			
		||||
#: stock/templates/stock/item_base.html:89
 | 
			
		||||
msgid "Belongs To"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:94
 | 
			
		||||
#: stock/templates/stock/item_base.html:95
 | 
			
		||||
#: stock/templates/stock/stock_adjust.html:17
 | 
			
		||||
msgid "Location"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:101
 | 
			
		||||
#: stock/templates/stock/item_base.html:102
 | 
			
		||||
msgid "Serial Number"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:160
 | 
			
		||||
#: stock/templates/stock/item_base.html:161
 | 
			
		||||
msgid "Last Updated"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:165
 | 
			
		||||
#: stock/templates/stock/item_base.html:166
 | 
			
		||||
msgid "Last Stocktake"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:169
 | 
			
		||||
#: stock/templates/stock/item_base.html:170
 | 
			
		||||
msgid "No stocktake performed"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -2280,3 +2310,55 @@ msgstr ""
 | 
			
		||||
#: templates/stock_table.html:17
 | 
			
		||||
msgid "Delete Stock"
 | 
			
		||||
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 ""
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\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"
 | 
			
		||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
			
		||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
			
		||||
@@ -18,30 +18,30 @@ msgstr ""
 | 
			
		||||
"Content-Transfer-Encoding: 8bit\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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:243
 | 
			
		||||
#: InvenTree/helpers.py:262
 | 
			
		||||
msgid "Empty serial number string"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:264 InvenTree/helpers.py:281
 | 
			
		||||
#: InvenTree/helpers.py:283 InvenTree/helpers.py:300
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Duplicate serial: {n}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:268 InvenTree/helpers.py:271 InvenTree/helpers.py:274
 | 
			
		||||
#: InvenTree/helpers.py:285
 | 
			
		||||
#: InvenTree/helpers.py:287 InvenTree/helpers.py:290 InvenTree/helpers.py:293
 | 
			
		||||
#: InvenTree/helpers.py:304
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Invalid group: {g}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:291
 | 
			
		||||
#: InvenTree/helpers.py:310
 | 
			
		||||
msgid "No serial numbers found"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/helpers.py:295
 | 
			
		||||
#: InvenTree/helpers.py:314
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Number of unique serial number ({s}) must match quantity ({q})"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -70,49 +70,49 @@ msgstr ""
 | 
			
		||||
msgid "Polish"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:36 InvenTree/status_codes.py:97
 | 
			
		||||
#: InvenTree/status_codes.py:86 InvenTree/status_codes.py:162
 | 
			
		||||
msgid "Pending"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:37
 | 
			
		||||
#: InvenTree/status_codes.py:87
 | 
			
		||||
msgid "Placed"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:38 InvenTree/status_codes.py:100
 | 
			
		||||
#: InvenTree/status_codes.py:88 InvenTree/status_codes.py:165
 | 
			
		||||
msgid "Complete"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:39 InvenTree/status_codes.py:99
 | 
			
		||||
#: InvenTree/status_codes.py:89 InvenTree/status_codes.py:164
 | 
			
		||||
msgid "Cancelled"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:40 InvenTree/status_codes.py:71
 | 
			
		||||
#: InvenTree/status_codes.py:90 InvenTree/status_codes.py:130
 | 
			
		||||
msgid "Lost"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:41
 | 
			
		||||
#: InvenTree/status_codes.py:91
 | 
			
		||||
msgid "Returned"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:67
 | 
			
		||||
#: InvenTree/status_codes.py:126
 | 
			
		||||
msgid "OK"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:68
 | 
			
		||||
#: InvenTree/status_codes.py:127
 | 
			
		||||
msgid "Attention needed"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:69
 | 
			
		||||
#: InvenTree/status_codes.py:128
 | 
			
		||||
msgid "Damaged"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status_codes.py:70
 | 
			
		||||
#: InvenTree/status_codes.py:129
 | 
			
		||||
msgid "Destroyed"
 | 
			
		||||
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
 | 
			
		||||
#: 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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -175,7 +175,7 @@ msgstr ""
 | 
			
		||||
msgid "Number of parts to build"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/models.py:82
 | 
			
		||||
#: build/models.py:82 templates/table_filters.html:42
 | 
			
		||||
msgid "Build status"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -227,10 +227,10 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/allocate_edit.html:19
 | 
			
		||||
#: 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
 | 
			
		||||
#: 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
 | 
			
		||||
msgid "Part"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -262,16 +262,58 @@ msgstr ""
 | 
			
		||||
#: company/templates/company/index.html:54
 | 
			
		||||
#: company/templates/company/supplier_part_base.html:50
 | 
			
		||||
#: 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
 | 
			
		||||
msgid "Description"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/allocate_view.html:22
 | 
			
		||||
#: part/templates/part/part_base.html:115
 | 
			
		||||
#: part/templates/part/part_base.html:121
 | 
			
		||||
msgid "On Order"
 | 
			
		||||
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
 | 
			
		||||
msgid "Build Outputs"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -280,68 +322,49 @@ msgstr ""
 | 
			
		||||
msgid "Are you sure you want to unallocate these parts?"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:8
 | 
			
		||||
msgid "Build Details"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:16
 | 
			
		||||
#: build/templates/build/detail.html:17
 | 
			
		||||
msgid "Title"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:26
 | 
			
		||||
#: 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
 | 
			
		||||
#: build/templates/build/detail.html:31
 | 
			
		||||
msgid "Stock Source"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:35
 | 
			
		||||
#: build/templates/build/detail.html:36
 | 
			
		||||
msgid "Stock can be taken from any available location."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:41
 | 
			
		||||
#: order/templates/order/order_base.html:71
 | 
			
		||||
#: stock/templates/stock/item_base.html:174
 | 
			
		||||
msgid "Status"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:47
 | 
			
		||||
#: stock/templates/stock/item_base.html:114
 | 
			
		||||
#: build/templates/build/detail.html:48
 | 
			
		||||
#: stock/templates/stock/item_base.html:115
 | 
			
		||||
msgid "Batch"
 | 
			
		||||
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_detail.html:24
 | 
			
		||||
#: part/templates/part/detail.html:67 part/templates/part/part_base.html:84
 | 
			
		||||
#: stock/templates/stock/item_base.html:142
 | 
			
		||||
#: part/templates/part/detail.html:67 part/templates/part/part_base.html:85
 | 
			
		||||
#: stock/templates/stock/item_base.html:143
 | 
			
		||||
msgid "External Link"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:60
 | 
			
		||||
#: order/templates/order/order_base.html:83
 | 
			
		||||
#: build/templates/build/detail.html:61
 | 
			
		||||
#: order/templates/order/order_base.html:84
 | 
			
		||||
msgid "Created"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:66
 | 
			
		||||
#: build/templates/build/detail.html:67
 | 
			
		||||
msgid "Enough Parts?"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:69
 | 
			
		||||
#: build/templates/build/detail.html:70
 | 
			
		||||
msgid "Yes"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:71
 | 
			
		||||
#: build/templates/build/detail.html:72
 | 
			
		||||
msgid "No"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: build/templates/build/detail.html:79
 | 
			
		||||
#: build/templates/build/detail.html:80
 | 
			
		||||
msgid "Completed"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -581,7 +604,7 @@ msgstr ""
 | 
			
		||||
msgid "Supplier stock keeping unit"
 | 
			
		||||
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_detail.html:30
 | 
			
		||||
msgid "Manufacturer"
 | 
			
		||||
@@ -637,7 +660,7 @@ msgid "Company Details"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/detail.html:16
 | 
			
		||||
#: stock/templates/stock/item_base.html:135
 | 
			
		||||
#: stock/templates/stock/item_base.html:136
 | 
			
		||||
msgid "Customer"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -645,9 +668,9 @@ msgstr ""
 | 
			
		||||
#: company/templates/company/index.html:46
 | 
			
		||||
#: company/templates/company/supplier_part_base.html:44
 | 
			
		||||
#: 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
 | 
			
		||||
#: stock/templates/stock/item_base.html:149
 | 
			
		||||
#: stock/templates/stock/item_base.html:150
 | 
			
		||||
msgid "Supplier"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -669,13 +692,13 @@ msgstr ""
 | 
			
		||||
msgid "Delete Parts"
 | 
			
		||||
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_detail.html:22
 | 
			
		||||
msgid "SKU"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/detail_part.html:90
 | 
			
		||||
#: company/templates/company/detail_part.html:105
 | 
			
		||||
msgid "Link"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -727,7 +750,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/supplier_part_base.html:6
 | 
			
		||||
#: 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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -748,7 +771,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/supplier_part_base.html:57
 | 
			
		||||
#: 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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -930,7 +953,7 @@ msgstr ""
 | 
			
		||||
msgid "Line item notes"
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -942,16 +965,16 @@ msgstr ""
 | 
			
		||||
msgid "Number of items received"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/order_base.html:61
 | 
			
		||||
#: order/templates/order/order_base.html:62
 | 
			
		||||
msgid "Purchase Order Details"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/order_base.html:89
 | 
			
		||||
#: order/templates/order/order_base.html:90
 | 
			
		||||
msgid "Issued"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/order_base.html:96
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:31
 | 
			
		||||
#: order/templates/order/order_base.html:97
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:32
 | 
			
		||||
msgid "Received"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -1035,23 +1058,23 @@ msgstr ""
 | 
			
		||||
msgid "Are you sure you want to delete this attachment?"
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:19
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:20
 | 
			
		||||
msgid "Order Items"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:24
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:25
 | 
			
		||||
msgid "Line"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:27
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:28
 | 
			
		||||
msgid "Order Code"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:28
 | 
			
		||||
#: order/templates/order/purchase_order_detail.html:29
 | 
			
		||||
msgid "Reference"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -1324,63 +1347,63 @@ msgstr ""
 | 
			
		||||
msgid "Stored BOM checksum"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1040
 | 
			
		||||
#: part/models.py:1049
 | 
			
		||||
msgid "Parameter template name must be unique"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1045
 | 
			
		||||
#: part/models.py:1054
 | 
			
		||||
msgid "Parameter Name"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1047
 | 
			
		||||
#: part/models.py:1056
 | 
			
		||||
msgid "Parameter Units"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1073
 | 
			
		||||
#: part/models.py:1082
 | 
			
		||||
msgid "Parent Part"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1075
 | 
			
		||||
#: part/models.py:1084
 | 
			
		||||
msgid "Parameter Template"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1077
 | 
			
		||||
#: part/models.py:1086
 | 
			
		||||
msgid "Parameter Value"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1101
 | 
			
		||||
#: part/models.py:1110
 | 
			
		||||
msgid "Select parent part"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1110
 | 
			
		||||
#: part/models.py:1119
 | 
			
		||||
msgid "Select part to be used in BOM"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1117
 | 
			
		||||
#: part/models.py:1126
 | 
			
		||||
msgid "BOM quantity for this BOM item"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1120
 | 
			
		||||
#: part/models.py:1129
 | 
			
		||||
msgid "Estimated build wastage quantity (absolute or percentage)"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1123
 | 
			
		||||
#: part/models.py:1132
 | 
			
		||||
msgid "BOM item reference"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1126
 | 
			
		||||
#: part/models.py:1135
 | 
			
		||||
msgid "BOM item notes"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1128
 | 
			
		||||
#: part/models.py:1137
 | 
			
		||||
msgid "BOM line checksum"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1191
 | 
			
		||||
#: part/models.py:1200
 | 
			
		||||
msgid "Part cannot be added to its own Bill of Materials"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:1198
 | 
			
		||||
#: part/models.py:1207
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Part '{p1}' is  used in BOM for '{p2}' (recursive)"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -1430,7 +1453,7 @@ msgstr ""
 | 
			
		||||
msgid "Part Details"
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -1482,7 +1505,7 @@ msgstr ""
 | 
			
		||||
msgid "Part is not a virtual part"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/detail.html:132
 | 
			
		||||
#: part/templates/part/detail.html:132 templates/table_filters.html:86
 | 
			
		||||
msgid "Assembly"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -1494,7 +1517,7 @@ msgstr ""
 | 
			
		||||
msgid "Part cannot be assembled from other parts"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/detail.html:141
 | 
			
		||||
#: part/templates/part/detail.html:141 templates/table_filters.html:90
 | 
			
		||||
msgid "Component"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -1554,31 +1577,43 @@ msgstr ""
 | 
			
		||||
msgid "This part is not active"
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:43
 | 
			
		||||
#: part/templates/part/part_base.html:44
 | 
			
		||||
msgid "Show pricing information"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:98
 | 
			
		||||
#: part/templates/part/part_base.html:101
 | 
			
		||||
msgid "Available Stock"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:103
 | 
			
		||||
#: part/templates/part/part_base.html:107
 | 
			
		||||
msgid "In Stock"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:124
 | 
			
		||||
#: part/templates/part/part_base.html:131
 | 
			
		||||
msgid "Build Status"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:128
 | 
			
		||||
#: part/templates/part/part_base.html:136
 | 
			
		||||
msgid "Can Build"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/templates/part/part_base.html:133
 | 
			
		||||
#: part/templates/part/part_base.html:142
 | 
			
		||||
msgid "Underway"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -1618,11 +1653,6 @@ msgstr ""
 | 
			
		||||
msgid "BOM"
 | 
			
		||||
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
 | 
			
		||||
msgid "Used In"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -1938,51 +1968,51 @@ msgstr ""
 | 
			
		||||
msgid "Stock Tracking Information"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:10
 | 
			
		||||
#: stock/templates/stock/item_base.html:11
 | 
			
		||||
msgid "Stock Item Details"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:55
 | 
			
		||||
#: stock/templates/stock/item_base.html:56
 | 
			
		||||
msgid ""
 | 
			
		||||
"This stock item is serialized - it has a unique serial number and the "
 | 
			
		||||
"quantity cannot be adjusted."
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:63
 | 
			
		||||
#: stock/templates/stock/item_base.html:64
 | 
			
		||||
msgid ""
 | 
			
		||||
"This stock item will be automatically deleted when all stock is depleted."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:68
 | 
			
		||||
#: stock/templates/stock/item_base.html:69
 | 
			
		||||
msgid "This stock item was split from "
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:88
 | 
			
		||||
#: stock/templates/stock/item_base.html:89
 | 
			
		||||
msgid "Belongs To"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:94
 | 
			
		||||
#: stock/templates/stock/item_base.html:95
 | 
			
		||||
#: stock/templates/stock/stock_adjust.html:17
 | 
			
		||||
msgid "Location"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:101
 | 
			
		||||
#: stock/templates/stock/item_base.html:102
 | 
			
		||||
msgid "Serial Number"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:160
 | 
			
		||||
#: stock/templates/stock/item_base.html:161
 | 
			
		||||
msgid "Last Updated"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:165
 | 
			
		||||
#: stock/templates/stock/item_base.html:166
 | 
			
		||||
msgid "Last Stocktake"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/item_base.html:169
 | 
			
		||||
#: stock/templates/stock/item_base.html:170
 | 
			
		||||
msgid "No stocktake performed"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -2280,3 +2310,55 @@ msgstr ""
 | 
			
		||||
#: templates/stock_table.html:17
 | 
			
		||||
msgid "Delete Stock"
 | 
			
		||||
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 ""
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load inventree_extras %}
 | 
			
		||||
{% load status_codes %}
 | 
			
		||||
 | 
			
		||||
{% block page_title %}
 | 
			
		||||
InvenTree | {{ order }}
 | 
			
		||||
@@ -69,7 +70,7 @@ InvenTree | {{ order }}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td><span class='fas fa-info'></span></td>
 | 
			
		||||
                <td>{% trans "Status" %}</td>
 | 
			
		||||
                <td>{% include "order/order_status.html" %}</td>
 | 
			
		||||
                <td>{% order_status order.status %}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            {% if order.link %}
 | 
			
		||||
            <tr>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
{% extends "order/order_base.html" %}
 | 
			
		||||
 | 
			
		||||
{% load inventree_extras %}
 | 
			
		||||
{% load status_codes %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,11 @@ InvenTree | Purchase Orders
 | 
			
		||||
<hr>
 | 
			
		||||
 | 
			
		||||
<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>
 | 
			
		||||
        <div class='filter-list' id='filter-list-order'>
 | 
			
		||||
            <!-- An empty div in which the filter list will be constructed -->
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								InvenTree/order/test_api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								InvenTree/order/test_api.py
									
									
									
									
									
										Normal 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)
 | 
			
		||||
@@ -8,7 +8,8 @@ from __future__ import unicode_literals
 | 
			
		||||
from django_filters.rest_framework import DjangoFilterBackend
 | 
			
		||||
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.response import Response
 | 
			
		||||
@@ -19,6 +20,7 @@ from django.conf.urls import url, include
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
from decimal import Decimal
 | 
			
		||||
 | 
			
		||||
from .models import Part, PartCategory, BomItem, PartStar
 | 
			
		||||
from .models import PartParameter, PartParameterTemplate
 | 
			
		||||
@@ -147,6 +149,18 @@ class PartList(generics.ListCreateAPIView):
 | 
			
		||||
 | 
			
		||||
    - GET: Return list of objects
 | 
			
		||||
    - 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
 | 
			
		||||
@@ -210,11 +224,39 @@ class PartList(generics.ListCreateAPIView):
 | 
			
		||||
            'active',
 | 
			
		||||
        ).annotate(
 | 
			
		||||
            # Quantity of items which are "in stock"
 | 
			
		||||
            in_stock=Sum('stock_items__quantity', filter=stock_filter),
 | 
			
		||||
            on_order=Sum('supplier_parts__purchase_order_line_items__quantity', filter=order_filter),
 | 
			
		||||
            building=Sum('builds__quantity', filter=build_filter),
 | 
			
		||||
            in_stock=Coalesce(Sum('stock_items__quantity', filter=stock_filter), Decimal(0)),
 | 
			
		||||
            on_order=Coalesce(Sum('supplier_parts__purchase_order_line_items__quantity', filter=order_filter), Decimal(0)),
 | 
			
		||||
            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
 | 
			
		||||
        categories = {}
 | 
			
		||||
 | 
			
		||||
@@ -261,23 +303,23 @@ class PartList(generics.ListCreateAPIView):
 | 
			
		||||
 | 
			
		||||
        cascade = str2bool(self.request.query_params.get('cascade', False))
 | 
			
		||||
 | 
			
		||||
        if cat_id is not None:
 | 
			
		||||
 | 
			
		||||
            if isNull(cat_id):
 | 
			
		||||
        if cat_id is None:
 | 
			
		||||
            # Top-level parts
 | 
			
		||||
            if not cascade:
 | 
			
		||||
                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
 | 
			
		||||
                    if cascade:
 | 
			
		||||
                        parts_list = parts_list.filter(category__in=category.getUniqueChildren())
 | 
			
		||||
                    # Just return parts directly in the requested category
 | 
			
		||||
                    else:
 | 
			
		||||
                        parts_list = parts_list.filter(category=cat_id)
 | 
			
		||||
                except (ValueError, PartCategory.DoesNotExist):
 | 
			
		||||
                    pass
 | 
			
		||||
        else:
 | 
			
		||||
            try:
 | 
			
		||||
                category = PartCategory.objects.get(pk=cat_id)
 | 
			
		||||
 | 
			
		||||
                # If '?cascade=true' then include parts which exist in sub-categories
 | 
			
		||||
                if cascade:
 | 
			
		||||
                    parts_list = parts_list.filter(category__in=category.getUniqueChildren())
 | 
			
		||||
                # 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
 | 
			
		||||
        parts_list = self.get_serializer_class().setup_eager_loading(parts_list)
 | 
			
		||||
@@ -443,6 +485,19 @@ class BomList(generics.ListCreateAPIView):
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        queryset = BomItem.objects.all()
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
    permission_classes = [
 | 
			
		||||
@@ -456,8 +511,6 @@ class BomList(generics.ListCreateAPIView):
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    filter_fields = [
 | 
			
		||||
        'part',
 | 
			
		||||
        'sub_part',
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@ from InvenTree import helpers
 | 
			
		||||
from InvenTree import validators
 | 
			
		||||
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
 | 
			
		||||
from InvenTree.fields import InvenTreeURLField
 | 
			
		||||
from InvenTree.helpers import decimal2string
 | 
			
		||||
from InvenTree.helpers import decimal2string, normalize
 | 
			
		||||
 | 
			
		||||
from InvenTree.status_codes import BuildStatus, StockStatus, OrderStatus
 | 
			
		||||
 | 
			
		||||
@@ -659,7 +659,7 @@ class Part(models.Model):
 | 
			
		||||
        if total:
 | 
			
		||||
            return total
 | 
			
		||||
        else:
 | 
			
		||||
            return 0
 | 
			
		||||
            return Decimal(0)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def has_bom(self):
 | 
			
		||||
@@ -781,6 +781,9 @@ class Part(models.Model):
 | 
			
		||||
        if min_price == max_price:
 | 
			
		||||
            return min_price
 | 
			
		||||
 | 
			
		||||
        min_price = normalize(min_price)
 | 
			
		||||
        max_price = normalize(max_price)
 | 
			
		||||
 | 
			
		||||
        return "{a} - {b}".format(a=min_price, b=max_price)
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        min_price = normalize(min_price)
 | 
			
		||||
        max_price = normalize(max_price)
 | 
			
		||||
 | 
			
		||||
        return (min_price, max_price)
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        min_price = normalize(min_price)
 | 
			
		||||
        max_price = normalize(max_price)
 | 
			
		||||
 | 
			
		||||
        return (min_price, max_price)
 | 
			
		||||
 | 
			
		||||
    def get_price_range(self, quantity=1, buy=True, bom=True):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
{% extends "part/part_base.html" %}
 | 
			
		||||
{% block details %}
 | 
			
		||||
{% load status_codes %}
 | 
			
		||||
 | 
			
		||||
{% 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>{{ allocation.build.quantity }} × <a href="{% url 'part-detail' allocation.build.part.id %}">{{ allocation.build.part.full_name }}</a></td>
 | 
			
		||||
    <td>{{ allocation.quantity }}</td>
 | 
			
		||||
    <td>{% include "build_status.html" with build=allocation.build %}</td>
 | 
			
		||||
    <td>{% build_status allocation.build.status %}</td>
 | 
			
		||||
</tr>
 | 
			
		||||
{% endfor %}
 | 
			
		||||
</table>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,14 @@
 | 
			
		||||
<h3>Part Builds</h3>
 | 
			
		||||
 | 
			
		||||
<div id='button-toolbar'>
 | 
			
		||||
    {% if part.active %}
 | 
			
		||||
    <button class="btn btn-success" id='start-build'>Start New Build</button>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    <div class='button-toolbar container-flui' style='float: right';>
 | 
			
		||||
        {% if part.active %}
 | 
			
		||||
        <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>
 | 
			
		||||
 | 
			
		||||
<table class='table table-striped table-condensed' data-toolbar='#button-toolbar' id='build-table'>
 | 
			
		||||
@@ -31,64 +36,12 @@
 | 
			
		||||
                        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("#build-table").inventreeTable({
 | 
			
		||||
        queryParams: function(p) {
 | 
			
		||||
            return {
 | 
			
		||||
                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' %}",  
 | 
			
		||||
    loadBuildTable($("#build-table"), {
 | 
			
		||||
        url: "{% url 'api-build-list' %}",
 | 
			
		||||
        params: {
 | 
			
		||||
            part_detail: "true",
 | 
			
		||||
            part: {{ part.id }},
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
        
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -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 %}
 | 
			
		||||
@@ -99,7 +99,7 @@
 | 
			
		||||
    <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-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>
 | 
			
		||||
            <ul class='dropdown-menu'>
 | 
			
		||||
                <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>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class='filter-list' id='filter-list-parts'>
 | 
			
		||||
            <!-- Empty div -->
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,18 +8,19 @@
 | 
			
		||||
 | 
			
		||||
{% if part.active == False %}
 | 
			
		||||
<div class='alert alert-danger alert-block'>
 | 
			
		||||
    {% trans "This part is not active" %}"
 | 
			
		||||
    {% trans "This part is not active" %}
 | 
			
		||||
</div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% if part.is_template %}
 | 
			
		||||
<div class='alert alert-info alert-block'>
 | 
			
		||||
    This part is a <i>template part</i>.<br>
 | 
			
		||||
    It is not a <i>real</i> part, but real parts can be based on this template.
 | 
			
		||||
    {% trans "This part is a template part." %}
 | 
			
		||||
    <br>
 | 
			
		||||
    {% trans "It is not a real part, but real parts can be based on this template." %}
 | 
			
		||||
</div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% if part.variant_of %}
 | 
			
		||||
<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>
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
@@ -93,25 +94,30 @@
 | 
			
		||||
</div>
 | 
			
		||||
<div class="col-sm-6">
 | 
			
		||||
    <table class="table table-striped">
 | 
			
		||||
        <col width='25'>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-boxes'></span></td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <h4>{% trans "Available Stock" %}</h4>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td><h4>{% decimal part.available_stock %} {{ part.units }}</h4></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-map-marker-alt'></span></td>
 | 
			
		||||
            <td>{% trans "In Stock" %}</td>
 | 
			
		||||
            <td>{% include "part/stock_count.html" %}</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        {% if not part.is_template %}
 | 
			
		||||
        {% if part.allocation_count > 0 %}
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-dolly'></span></td>
 | 
			
		||||
            <td>{% trans "Allocated" %}</td>
 | 
			
		||||
            <td>{% decimal part.allocation_count %}</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% if part.on_order > 0 %}
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-shopping-cart'></span></td>
 | 
			
		||||
            <td>{% trans "On Order" %}</td>
 | 
			
		||||
            <td>{% decimal part.on_order %}</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
@@ -120,16 +126,19 @@
 | 
			
		||||
        {% if not part.is_template %}
 | 
			
		||||
        {% if part.assembly %}
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-tools'></span></td>
 | 
			
		||||
            <td colspan='2'>
 | 
			
		||||
                <b>{% trans "Build Status" %}</b>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td></td>
 | 
			
		||||
            <td>{% trans "Can Build" %}</td>
 | 
			
		||||
            <td>{% decimal part.can_build %}</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        {% if part.quantity_being_built > 0 %}
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td></td>
 | 
			
		||||
            <td>{% trans "Underway" %}</td>
 | 
			
		||||
            <td>{% decimal part.quantity_being_built %}</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
{% decimal part.total_stock %}
 | 
			
		||||
 | 
			
		||||
{% 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 %}
 | 
			
		||||
<span class='label label-warning'>{% trans "Low Stock" %}</span>
 | 
			
		||||
<span class='label label-warning label-right'>{% trans "Low Stock" %}</span>
 | 
			
		||||
{% endif %}
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
    <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>
 | 
			
		||||
    <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 %}
 | 
			
		||||
    {% if part.component or part.used_in_count > 0 %}
 | 
			
		||||
    <li{% ifequal tab 'used' %} class="active"{% endifequal %}>
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@
 | 
			
		||||
                title: 'Part',
 | 
			
		||||
                sortable: true,
 | 
			
		||||
                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) {
 | 
			
		||||
                        html += "<span class='label label-warning' style='float: right;'>INACTIVE</span>";
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								InvenTree/part/templatetags/status_codes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								InvenTree/part/templatetags/status_codes.py
									
									
									
									
									
										Normal 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 ''
 | 
			
		||||
@@ -82,7 +82,8 @@ class PartAPITest(APITestCase):
 | 
			
		||||
 | 
			
		||||
    def test_get_all_parts(self):
 | 
			
		||||
        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(len(response.data), 8)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -410,8 +410,16 @@ class StockList(generics.ListCreateAPIView):
 | 
			
		||||
        # Start with all objects
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
        # 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?
 | 
			
		||||
        part_id = self.request.query_params.get('part', None)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
{% extends "stock/stock_app_base.html" %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load inventree_extras %}
 | 
			
		||||
{% load status_codes %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
 | 
			
		||||
@@ -172,7 +173,7 @@
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td><span class='fas fa-info'></span></td>
 | 
			
		||||
                <td>{% trans "Status" %}</td>
 | 
			
		||||
                <td>{{ item.get_status_display }}</td>
 | 
			
		||||
                <td>{% stock_status item.status %}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            </table>
 | 
			
		||||
        </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<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/api.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/build.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/notification.js' %}"></script>
 | 
			
		||||
@@ -115,7 +118,10 @@ InvenTree
 | 
			
		||||
{% block js_load %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% include "table_filters.html" %}
 | 
			
		||||
 | 
			
		||||
<script type='text/javascript'>
 | 
			
		||||
 | 
			
		||||
$(document).ready(function () {
 | 
			
		||||
{% block js_ready %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -124,6 +130,7 @@ $(document).ready(function () {
 | 
			
		||||
    
 | 
			
		||||
    showCachedAlerts();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
{% block js %}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
							
								
								
									
										35
									
								
								InvenTree/templates/status_codes.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								InvenTree/templates/status_codes.html
									
									
									
									
									
										Normal 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>`;
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
        {% if read_only %}
 | 
			
		||||
        {% else %}
 | 
			
		||||
        <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>
 | 
			
		||||
            <ul class="dropdown-menu">
 | 
			
		||||
                <li><a href="#" id='multi-item-add' title='Add to selected stock items'>{% trans "Add stock" %}</a></li>
 | 
			
		||||
@@ -18,6 +18,9 @@
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        <div class='filter-list' id='filter-list-stock'>
 | 
			
		||||
            <!-- An empty div in which the filter list will be constructed -->
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										98
									
								
								InvenTree/templates/table_filters.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								InvenTree/templates/table_filters.html
									
									
									
									
									
										Normal 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>
 | 
			
		||||
		Reference in New Issue
	
	Block a user