mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 13:05:42 +00:00
Part units (#4854)
* Add validation to part units field * Add "pack_units" field to the SupplierPart model * Migrate old units to new units, and remove old field * Table fix * Fixture fix * Update migration * Improve "hook" for loading custom unit database * Display part units column in part table - Also allow ordering by part units - Allow filtering to show parts which have defined units * Adds data migration for converting units to valid values * Add "pack_units_native" field to company.SupplierPart model * Clean pack units when saving a SupplierPart - Convert to native part units - Handle empty units value - Add unit tests * Add background function to rebuild supplier parts when a part is saved - Required to ensure that the "pack_size_native" is up to date * Template updates * Sort by native units first * Bump API version * Rename "pack_units" to "pack_quantity" * Update migration file - Allow reverse migration * Fix for currency migration - Handle case where no currencies are provided - Handle case where base currency is not in provided options * Adds unit test for data migration * Add unit test for part.units data migration - Check that units fields are updated correctly * Add some extra "default units" - each / piece - dozen / hundred / thousand - Add unit testing also * Update references to "pack_size" - Replace with "pack_quantity" or "pack_quantity_native" as appropriate * Improvements based on unit testing * catch error * Docs updates * Fixes for pricing tests * Update unit tests for part migrations · 1b6b6d9d * Bug fix for conversion code * javascript updates * JS formatting fix
This commit is contained in:
@ -138,7 +138,7 @@ function supplierPartFields(options={}) {
|
||||
packaging: {
|
||||
icon: 'fa-box',
|
||||
},
|
||||
pack_size: {},
|
||||
pack_quantity: {},
|
||||
};
|
||||
|
||||
if (options.part) {
|
||||
@ -1242,17 +1242,24 @@ function loadSupplierPartTable(table, url, options) {
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
field: 'pack_size',
|
||||
field: 'pack_quantity',
|
||||
title: '{% trans "Pack Quantity" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row) {
|
||||
var output = `${value}`;
|
||||
|
||||
if (row.part_detail && row.part_detail.units) {
|
||||
output += ` ${row.part_detail.units}`;
|
||||
let html = '';
|
||||
|
||||
if (value) {
|
||||
html = value;
|
||||
} else {
|
||||
html = '-';
|
||||
}
|
||||
|
||||
return output;
|
||||
if (row.part_detail && row.part_detail.units) {
|
||||
html += `<span class='fas fa-info-circle float-right' title='{% trans "Base Units" %}: ${row.part_detail.units}'></span>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1588,13 +1588,12 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
|
||||
formatter: function(value, row) {
|
||||
let data = value;
|
||||
|
||||
if (row.supplier_part_detail.pack_size != 1.0) {
|
||||
let pack_size = row.supplier_part_detail.pack_size;
|
||||
let total = value * pack_size;
|
||||
if (row.supplier_part_detail.pack_quantity_native != 1.0) {
|
||||
let total = value * row.supplier_part_detail.pack_quantity_native;
|
||||
|
||||
data += makeIconBadge(
|
||||
'fa-info-circle icon-blue',
|
||||
`{% trans "Pack Quantity" %}: ${pack_size} - {% trans "Total Quantity" %}: ${total}`
|
||||
`{% trans "Pack Quantity" %}: ${formatDecimal(row.pack_quantity)} - {% trans "Total Quantity" %}: ${total}`
|
||||
);
|
||||
}
|
||||
|
||||
@ -1647,10 +1646,9 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
|
||||
formatter: function(value, row) {
|
||||
var data = value;
|
||||
|
||||
if (value > 0 && row.supplier_part_detail.pack_size != 1.0) {
|
||||
var pack_size = row.supplier_part_detail.pack_size;
|
||||
var total = value * pack_size;
|
||||
data += `<span class='fas fa-info-circle icon-blue float-right' title='{% trans "Pack Quantity" %}: ${pack_size} - {% trans "Total Quantity" %}: ${total}'></span>`;
|
||||
if (value > 0 && row.supplier_part_detail.pack_quantity_native != 1.0) {
|
||||
let total = value * row.supplier_part_detail.pack_quantity_native;
|
||||
data += `<span class='fas fa-info-circle icon-blue float-right' title='{% trans "Pack Quantity" %}: ${row.pack_quantity} - {% trans "Total Quantity" %}: ${total}'></span>`;
|
||||
}
|
||||
|
||||
return data;
|
||||
@ -2038,6 +2036,12 @@ function loadPartTable(table, url, options={}) {
|
||||
}
|
||||
});
|
||||
|
||||
columns.push({
|
||||
field: 'units',
|
||||
title: '{% trans "Units" %}',
|
||||
sortable: true,
|
||||
});
|
||||
|
||||
columns.push({
|
||||
sortName: 'category',
|
||||
field: 'category_detail',
|
||||
|
@ -458,7 +458,7 @@ function loadPartSupplierPricingTable(options={}) {
|
||||
data = data.sort((a, b) => (a.quantity - b.quantity));
|
||||
|
||||
var graphLabels = Array.from(data, (x) => (`${x.part_detail.SKU} - {% trans "Quantity" %} ${x.quantity}`));
|
||||
var graphValues = Array.from(data, (x) => (x.price / x.part_detail.pack_size));
|
||||
var graphValues = Array.from(data, (x) => (x.price / x.part_detail.pack_quantity_native));
|
||||
|
||||
if (chart) {
|
||||
chart.destroy();
|
||||
@ -518,7 +518,7 @@ function loadPartSupplierPricingTable(options={}) {
|
||||
}
|
||||
|
||||
// Convert to unit pricing
|
||||
var unit_price = row.price / row.part_detail.pack_size;
|
||||
var unit_price = row.price / row.part_detail.pack_quantity_native;
|
||||
|
||||
var html = formatCurrency(unit_price, {
|
||||
currency: row.price_currency
|
||||
@ -811,9 +811,12 @@ function loadPurchasePriceHistoryTable(options={}) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return formatCurrency(row.purchase_price / row.supplier_part_detail.pack_size, {
|
||||
currency: row.purchase_price_currency
|
||||
});
|
||||
return formatCurrency(
|
||||
row.purchase_price / row.supplier_part_detail.pack_quantity_native,
|
||||
{
|
||||
currency: row.purchase_price_currency
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
]
|
||||
|
@ -229,8 +229,8 @@ function poLineItemFields(options={}) {
|
||||
supplier: options.supplier,
|
||||
},
|
||||
onEdit: function(value, name, field, opts) {
|
||||
// If the pack_size != 1, add a note to the field
|
||||
var pack_size = 1;
|
||||
// If the pack_quantity != 1, add a note to the field
|
||||
var pack_quantity = 1;
|
||||
var units = '';
|
||||
var supplier_part_id = value;
|
||||
var quantity = getFormFieldValue('quantity', {}, opts);
|
||||
@ -250,14 +250,14 @@ function poLineItemFields(options={}) {
|
||||
{
|
||||
success: function(response) {
|
||||
// Extract information from the returned query
|
||||
pack_size = response.pack_size || 1;
|
||||
pack_quantity = response.pack_quantity_native || 1;
|
||||
units = response.part_detail.units || '';
|
||||
},
|
||||
}
|
||||
).then(function() {
|
||||
// Update pack size information
|
||||
if (pack_size != 1) {
|
||||
var txt = `<span class='fas fa-info-circle icon-blue'></span> {% trans "Pack Quantity" %}: ${pack_size} ${units}`;
|
||||
if (pack_quantity != 1) {
|
||||
var txt = `<span class='fas fa-info-circle icon-blue'></span> {% trans "Pack Quantity" %}: ${formatDecimal(pack_quantity)} ${units}`;
|
||||
$(opts.modal).find('#hint_id_quantity').after(`<div class='form-info-message' id='info-pack-size'>${txt}</div>`);
|
||||
}
|
||||
}).then(function() {
|
||||
@ -766,7 +766,7 @@ function orderParts(parts_list, options) {
|
||||
// Callback function when supplier part is changed
|
||||
// This is used to update the "pack size" attribute
|
||||
var onSupplierPartChanged = function(value, name, field, opts) {
|
||||
var pack_size = 1;
|
||||
var pack_quantity = 1;
|
||||
var units = '';
|
||||
|
||||
$(opts.modal).find(`#info-pack-size-${pk}`).remove();
|
||||
@ -779,13 +779,13 @@ function orderParts(parts_list, options) {
|
||||
},
|
||||
{
|
||||
success: function(response) {
|
||||
pack_size = response.pack_size || 1;
|
||||
pack_quantity = response.pack_quantity_native || 1;
|
||||
units = response.part_detail.units || '';
|
||||
}
|
||||
}
|
||||
).then(function() {
|
||||
if (pack_size != 1) {
|
||||
var txt = `<span class='fas fa-info-circle icon-blue'></span> {% trans "Pack Quantity" %}: ${pack_size} ${units}`;
|
||||
if (pack_quantity != 1) {
|
||||
var txt = `<span class='fas fa-info-circle icon-blue'></span> {% trans "Pack Quantity" %}: ${pack_quantity} ${units}`;
|
||||
$(opts.modal).find(`#id_quantity_${pk}`).after(`<div class='form-info-message' id='info-pack-size-${pk}'>${txt}</div>`);
|
||||
}
|
||||
});
|
||||
@ -1021,15 +1021,17 @@ function receivePurchaseOrderItems(order_id, line_items, options={}) {
|
||||
}
|
||||
|
||||
var units = line_item.part_detail.units || '';
|
||||
var pack_size = line_item.supplier_part_detail.pack_size || 1;
|
||||
var pack_size_div = '';
|
||||
let pack_quantity = line_item.supplier_part_detail.pack_quantity;
|
||||
let native_pack_quantity = line_item.supplier_part_detail.pack_quantity_native || 1;
|
||||
|
||||
var received = quantity * pack_size;
|
||||
let pack_size_div = '';
|
||||
|
||||
if (pack_size != 1) {
|
||||
var received = quantity * native_pack_quantity;
|
||||
|
||||
if (native_pack_quantity != 1) {
|
||||
pack_size_div = `
|
||||
<div class='alert alert-small alert-block alert-info'>
|
||||
{% trans "Pack Quantity" %}: ${pack_size} ${units}<br>
|
||||
{% trans "Pack Quantity" %}: ${formatDecimal(pack_quantity)}<br>
|
||||
{% trans "Received Quantity" %}: <span class='pack_received_quantity' id='items_received_quantity_${pk}'>${received}</span> ${units}
|
||||
</div>`;
|
||||
}
|
||||
@ -1304,13 +1306,13 @@ function receivePurchaseOrderItems(order_id, line_items, options={}) {
|
||||
);
|
||||
|
||||
// Add change callback for quantity field
|
||||
if (item.supplier_part_detail.pack_size != 1) {
|
||||
if (item.supplier_part_detail.pack_quantity_native != 1) {
|
||||
$(opts.modal).find(`#id_items_quantity_${pk}`).change(function() {
|
||||
var value = $(opts.modal).find(`#id_items_quantity_${pk}`).val();
|
||||
|
||||
var el = $(opts.modal).find(`#quantity_${pk}`).find('.pack_received_quantity');
|
||||
|
||||
var actual = value * item.supplier_part_detail.pack_size;
|
||||
var actual = value * item.supplier_part_detail.pack_quantity_native;
|
||||
actual = formatDecimal(actual);
|
||||
el.text(actual);
|
||||
});
|
||||
@ -2005,10 +2007,10 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
|
||||
|
||||
let data = value;
|
||||
|
||||
if (row.supplier_part_detail && row.supplier_part_detail.pack_size != 1.0) {
|
||||
var pack_size = row.supplier_part_detail.pack_size;
|
||||
var total = value * pack_size;
|
||||
data += `<span class='fas fa-info-circle icon-blue float-right' title='{% trans "Pack Quantity" %}: ${pack_size}${units} - {% trans "Total Quantity" %}: ${total}${units}'></span>`;
|
||||
if (row.supplier_part_detail && row.supplier_part_detail.pack_quantity_native != 1.0) {
|
||||
let pack_quantity = row.supplier_part_detail.pack_quantity;
|
||||
let total = value * row.supplier_part_detail.pack_quantity_native;
|
||||
data += `<span class='fas fa-info-circle icon-blue float-right' title='{% trans "Pack Quantity" %}: ${pack_quantity} - {% trans "Total Quantity" %}: ${total}${units}'></span>`;
|
||||
}
|
||||
|
||||
return data;
|
||||
@ -2024,7 +2026,7 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
|
||||
{
|
||||
sortable: false,
|
||||
switchable: true,
|
||||
field: 'supplier_part_detail.pack_size',
|
||||
field: 'supplier_part_detail.pack_quantity',
|
||||
title: '{% trans "Pack Quantity" %}',
|
||||
formatter: function(value, row) {
|
||||
var units = row.part_detail.units;
|
||||
|
@ -626,6 +626,11 @@ function getPartTableFilters() {
|
||||
type: 'bool',
|
||||
title: '{% trans "Component" %}',
|
||||
},
|
||||
has_units: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Has Units" %}',
|
||||
description: '{% trans "Part has defined units" %}',
|
||||
},
|
||||
has_ipn: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Has IPN" %}',
|
||||
|
Reference in New Issue
Block a user