2
0
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:
Oliver
2023-05-26 16:57:23 +10:00
committed by GitHub
parent 717bb07dcf
commit 5dd6f18495
39 changed files with 878 additions and 251 deletions

View File

@ -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;
}
},
{

View File

@ -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',

View File

@ -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
}
);
}
},
]

View File

@ -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;

View File

@ -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" %}',