mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Add modal API form to allocate stock items against a SalesOrder
- Added model renderer for SalesOrderShipment - Some refactorin'
This commit is contained in:
parent
bff9f0828a
commit
7252b299f7
@ -596,12 +596,15 @@ class SalesOrderShipmentSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
allocations = SalesOrderAllocationSerializer(many=True, read_only=True, location_detail=True)
|
allocations = SalesOrderAllocationSerializer(many=True, read_only=True, location_detail=True)
|
||||||
|
|
||||||
|
order_detail = SalesOrderSerializer(source='order', read_only=True, many=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = order.models.SalesOrderShipment
|
model = order.models.SalesOrderShipment
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
'pk',
|
'pk',
|
||||||
'order',
|
'order',
|
||||||
|
'order_detail',
|
||||||
'allocations',
|
'allocations',
|
||||||
'shipment_date',
|
'shipment_date',
|
||||||
'checked_by',
|
'checked_by',
|
||||||
|
@ -1216,7 +1216,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
|||||||
*
|
*
|
||||||
* options:
|
* options:
|
||||||
* - output: ID / PK of the associated build output (or null for untracked items)
|
* - output: ID / PK of the associated build output (or null for untracked items)
|
||||||
* - source_location: ID / PK of the top-level StockLocation to take parts from (or null)
|
* - source_location: ID / PK of the top-level StockLocation to source stock from (or null)
|
||||||
*/
|
*/
|
||||||
function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
||||||
|
|
||||||
@ -1329,7 +1329,7 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
|||||||
|
|
||||||
var html = ``;
|
var html = ``;
|
||||||
|
|
||||||
// Render a "take from" input
|
// Render a "source location" input
|
||||||
html += constructField(
|
html += constructField(
|
||||||
'take_from',
|
'take_from',
|
||||||
{
|
{
|
||||||
@ -1387,6 +1387,13 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
|||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add callback to "clear" button for take_from field
|
||||||
|
addClearCallback(
|
||||||
|
'take_from',
|
||||||
|
take_from_field,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
// Initialize stock item fields
|
// Initialize stock item fields
|
||||||
bom_items.forEach(function(bom_item) {
|
bom_items.forEach(function(bom_item) {
|
||||||
initializeRelatedField(
|
initializeRelatedField(
|
||||||
@ -1446,14 +1453,7 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add callback to "clear" button for take_from field
|
// Add remove-row button callbacks
|
||||||
addClearCallback(
|
|
||||||
'take_from',
|
|
||||||
take_from_field,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add button callbacks
|
|
||||||
$(options.modal).find('.button-row-remove').click(function() {
|
$(options.modal).find('.button-row-remove').click(function() {
|
||||||
var pk = $(this).attr('pk');
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
@ -1576,6 +1576,9 @@ function renderModelData(name, model, data, parameters, options) {
|
|||||||
case 'salesorder':
|
case 'salesorder':
|
||||||
renderer = renderSalesOrder;
|
renderer = renderSalesOrder;
|
||||||
break;
|
break;
|
||||||
|
case 'salesordershipment':
|
||||||
|
renderer = renderSalesOrderShipment;
|
||||||
|
break;
|
||||||
case 'manufacturerpart':
|
case 'manufacturerpart':
|
||||||
renderer = renderManufacturerPart;
|
renderer = renderManufacturerPart;
|
||||||
break;
|
break;
|
||||||
|
@ -241,6 +241,23 @@ function renderSalesOrder(name, data, parameters, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Renderer for "SalesOrderShipment" model
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
function renderSalesOrderShipment(name, data, parameters, options) {
|
||||||
|
|
||||||
|
var so_prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
|
||||||
|
|
||||||
|
var html = `
|
||||||
|
<span>${so_prefix}${data.order_detail.reference} - {% trans "Shipment" %} ${data.reference}</span>
|
||||||
|
<span class='float-right'>
|
||||||
|
<small>{% trans "Shipment ID" %}: ${data.pk}</small>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Renderer for "PartCategory" model
|
// Renderer for "PartCategory" model
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
function renderPartCategory(name, data, parameters, options) {
|
function renderPartCategory(name, data, parameters, options) {
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* exported
|
/* exported
|
||||||
|
allocateStockToSalesOrder,
|
||||||
createSalesOrder,
|
createSalesOrder,
|
||||||
editPurchaseOrderLineItem,
|
editPurchaseOrderLineItem,
|
||||||
exportOrder,
|
exportOrder,
|
||||||
@ -1211,6 +1212,306 @@ function loadSalesOrderShipmentTable(table, options={}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate stock items against a SalesOrder
|
||||||
|
*
|
||||||
|
* arguments:
|
||||||
|
* - order_id: The ID / PK value for the SalesOrder
|
||||||
|
* - lines: A list of SalesOrderLineItem objects to be allocated
|
||||||
|
*
|
||||||
|
* options:
|
||||||
|
* - source_location: ID / PK of the top-level StockLocation to source stock from (or null)
|
||||||
|
*/
|
||||||
|
function allocateStockToSalesOrder(order_id, line_items, options={}) {
|
||||||
|
|
||||||
|
function renderLineItemRow(line_item, quantity) {
|
||||||
|
// Function to render a single line_item row
|
||||||
|
|
||||||
|
var pk = line_item.pk;
|
||||||
|
|
||||||
|
var part = line_item.part_detail;
|
||||||
|
|
||||||
|
var thumb = thumbnailImage(part.thumbnail || part.image);
|
||||||
|
|
||||||
|
var delete_button = `<div class='btn-group float-right' role='group'>`;
|
||||||
|
|
||||||
|
delete_button += makeIconButton(
|
||||||
|
'fa-times icon-red',
|
||||||
|
'button-row-remove',
|
||||||
|
pk,
|
||||||
|
'{% trans "Remove row" %}',
|
||||||
|
);
|
||||||
|
|
||||||
|
delete_button += '</div>';
|
||||||
|
|
||||||
|
var quantity_input = constructField(
|
||||||
|
`items_quantity_${pk}`,
|
||||||
|
{
|
||||||
|
type: 'decimal',
|
||||||
|
min_value: 0,
|
||||||
|
value: quantity || 0,
|
||||||
|
title: '{% trans "Specify stock allocation quantity" %}',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hideLabels: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var stock_input = constructField(
|
||||||
|
`items_stock_item_${pk}`,
|
||||||
|
{
|
||||||
|
type: 'related field',
|
||||||
|
required: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hideLabels: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var html = `
|
||||||
|
<tr id='allocation_row_${pk}' class='line-allocation-row'>
|
||||||
|
<td id='part_${pk}'>
|
||||||
|
${thumb} ${part.full_name}
|
||||||
|
</td>
|
||||||
|
<td id='stock_item_${pk}'>
|
||||||
|
${stock_input}
|
||||||
|
</td>
|
||||||
|
<td id='quantity_${pk}'>
|
||||||
|
${quantity_input}
|
||||||
|
</td>
|
||||||
|
<td id='buttons_${pk}>
|
||||||
|
${delete_button}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
var table_entries = '';
|
||||||
|
|
||||||
|
for (var idx = 0; idx < line_items.length; idx++ ){
|
||||||
|
var line_item = line_items[idx];
|
||||||
|
|
||||||
|
var todo = "auto-calculate remaining quantity";
|
||||||
|
|
||||||
|
var remaining = 0;
|
||||||
|
|
||||||
|
table_entries += renderLineItemRow(line_item, remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (table_entries.length == 0) {
|
||||||
|
showAlertDialog(
|
||||||
|
'{% trans "Select Parts" %}',
|
||||||
|
'{% trans "You must select at least one part to allocate" %}',
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
// Render a "source location" input field
|
||||||
|
html += constructField(
|
||||||
|
'take_from',
|
||||||
|
{
|
||||||
|
type: 'related field',
|
||||||
|
label: '{% trans "Source Location" %}',
|
||||||
|
help_text: '{% trans "Select source location (leave blank to take from all locations)" %}',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create table of line items
|
||||||
|
html += `
|
||||||
|
<table class='table table-striped table-condensed' id='stock-allocation-table'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Part" %}</th>
|
||||||
|
<th style='min-width: 250px;'>{% trans "Stock Item" %}</th>
|
||||||
|
<th>{% trans "Quantity" %}</th>
|
||||||
|
<th></th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${table_entries}
|
||||||
|
</tbody>
|
||||||
|
</table>`;
|
||||||
|
|
||||||
|
constructForm(`/api/order/so/${order_id}/allocate/`, {
|
||||||
|
method: 'POST',
|
||||||
|
fields: {
|
||||||
|
shipment: {
|
||||||
|
filters: {
|
||||||
|
order: order_id,
|
||||||
|
shipped: false,
|
||||||
|
},
|
||||||
|
value: options.shipment || null,
|
||||||
|
auto_fill: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preFormContent: html,
|
||||||
|
confirm: true,
|
||||||
|
confirmMessage: '{% trans "Confirm stock allocation" %}',
|
||||||
|
title: '{% trans "Allocate Stock Items to Sales Order" %}',
|
||||||
|
afterRender: function(fields, opts) {
|
||||||
|
|
||||||
|
// Initialize source location field
|
||||||
|
var take_from_field = {
|
||||||
|
name: 'take_from',
|
||||||
|
model: 'stocklocation',
|
||||||
|
api_url: '{% url "api-location-list" %}',
|
||||||
|
required: false,
|
||||||
|
type: 'related field',
|
||||||
|
value: options.source_location || null,
|
||||||
|
noResults: function(query) {
|
||||||
|
return '{% trans "No matching stock locations" %}';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
initializeRelatedField(
|
||||||
|
take_from_field,
|
||||||
|
null,
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add callback to "clear" button for take_from field
|
||||||
|
addClearCallback(
|
||||||
|
'take_from',
|
||||||
|
take_from_field,
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initialize fields for each line item
|
||||||
|
line_items.forEach(function(line_item) {
|
||||||
|
var pk = line_item.pk;
|
||||||
|
|
||||||
|
initializeRelatedField(
|
||||||
|
{
|
||||||
|
name: `items_stock_item_${pk}`,
|
||||||
|
api_url: '{% url "api-stock-list" %}',
|
||||||
|
filters: {
|
||||||
|
part: line_item.part,
|
||||||
|
in_stock: true,
|
||||||
|
part_detail: true,
|
||||||
|
location_detail: true,
|
||||||
|
},
|
||||||
|
model: 'stockitem',
|
||||||
|
required: true,
|
||||||
|
render_part_detail: true,
|
||||||
|
render_location_detail: true,
|
||||||
|
auto_fill: true,
|
||||||
|
onSelect: function(data, field, opts) {
|
||||||
|
// Adjust the 'quantity' field based on availability
|
||||||
|
|
||||||
|
var todo = "actually do this";
|
||||||
|
},
|
||||||
|
adjustFilters: function(filters) {
|
||||||
|
// Restrict query to the selected location
|
||||||
|
var location = getFormFieldValue(
|
||||||
|
'take_from',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
modal: opts.modal,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
filters.location = location;
|
||||||
|
filters.cascade = true;
|
||||||
|
|
||||||
|
return filters;
|
||||||
|
},
|
||||||
|
noResults: function(query) {
|
||||||
|
return '{% trans "No matching stock items" %}';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add remove-row button callbacks
|
||||||
|
$(opts.modal).find('.button-row-remove').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
$(opts.modal).find(`#allocation_row_${pk}`).remove();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSubmit: function(fields, opts) {
|
||||||
|
// Extract data elements from the form
|
||||||
|
var data = {
|
||||||
|
items: [],
|
||||||
|
shipment: getFormFieldValue(
|
||||||
|
'shipment',
|
||||||
|
{},
|
||||||
|
opts
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
var item_pk_values = [];
|
||||||
|
|
||||||
|
line_items.forEach(function(item) {
|
||||||
|
|
||||||
|
var pk = item.pk;
|
||||||
|
|
||||||
|
var quantity = getFormFieldValue(
|
||||||
|
`items_quantity_${pk}`,
|
||||||
|
{},
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
|
||||||
|
var stock_item = getFormFieldValue(
|
||||||
|
`items_stock_item_${pk}`,
|
||||||
|
{},
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
|
||||||
|
if (quantity != null) {
|
||||||
|
data.items.push({
|
||||||
|
line_item: pk,
|
||||||
|
stock_item: stock_item,
|
||||||
|
quantity: quantity,
|
||||||
|
});
|
||||||
|
|
||||||
|
item_pk_values.push(pk);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Provide nested values
|
||||||
|
opts.nested = {
|
||||||
|
'items': item_pk_values
|
||||||
|
};
|
||||||
|
|
||||||
|
inventreePut(
|
||||||
|
opts.url,
|
||||||
|
data,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
success: function(response) {
|
||||||
|
$(opts.modal).modal('hide');
|
||||||
|
|
||||||
|
if (options.success) {
|
||||||
|
options.success(response);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
switch (xhr.status) {
|
||||||
|
case 400:
|
||||||
|
handleFormErrors(xhr.responseJSON, fields, opts);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$(opts.modal).modal('hide');
|
||||||
|
showApiError(xhr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function loadSalesOrderAllocationTable(table, options={}) {
|
function loadSalesOrderAllocationTable(table, options={}) {
|
||||||
/**
|
/**
|
||||||
@ -1772,6 +2073,20 @@ function loadSalesOrderLineItemTable(table, options={}) {
|
|||||||
|
|
||||||
var line_item = $(table).bootstrapTable('getRowByUniqueId', pk);
|
var line_item = $(table).bootstrapTable('getRowByUniqueId', pk);
|
||||||
|
|
||||||
|
allocateStockToSalesOrder(
|
||||||
|
options.order,
|
||||||
|
[
|
||||||
|
line_item
|
||||||
|
],
|
||||||
|
{
|
||||||
|
success: function() {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
// Quantity remaining to be allocated
|
// Quantity remaining to be allocated
|
||||||
var remaining = (line_item.quantity || 0) - (line_item.allocated || 0);
|
var remaining = (line_item.quantity || 0) - (line_item.allocated || 0);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user