mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-16 01:36:29 +00:00
[Feature] Add RMA support (#4488)
* Adds ReturnOrder and ReturnOrderAttachment models
* Adds new 'role' specific for return orders
* Refactor total_price into a mixin
- Required for PurchaseOrder and SalesOrder
- May not be required for ReturnOrder (remains to be seen)
* Adds API endpoints for ReturnOrder
- Add list endpoint
- Add detail endpoint
- Adds required serializer models
* Adds basic "index" page for Return Order model
* Update API version
* Update navbar text
* Add db migration for new "role"
* Add ContactList and ContactDetail API endpoints
* Adds template and JS code for manipulation of contacts
- Display a table
- Create / edit / delete
* Splits order.js into multiple files
- Javascript files was becoming extremely large
- Hard to debug and find code
- Split into purchase_order / return_order / sales_order
* Fix role name (change 'returns' to 'return_order')
- Similar to existing roles for purchase_order and sales_order
* Adds detail page for ReturnOrder
* URL cleanup
- Use <int:pk> instead of complex regex
* More URL cleanup
* Add "return orders" list to company detail page
* Break JS status codes into new javascript file
- Always difficult to track down where these are rendered
- Enough to warrant their own file now
* Add ability to edit return order from detail page
* Database migrations
- Add new ReturnOrder modeles
- Add new 'contact' field to external orders
* Adds "contact" to ReturnOrder
- Implement check to ensure that the selected "contact" matches the selected "company"
* Adjust filters to limit contact options
* Fix typo
* Expose 'contact' field for PurchaseOrder model
* Render contact information
* Add "contact" for SalesOrder
* Adds setting to enable / disable return order functionality
- Simply hides the navigation elements
- API is not disabled
* Support filtering ReturnOrder by 'status'
- Refactors existing filter into the OrderFilter class
* js linting
* More JS linting
* Adds ReturnOrderReport model
* Add serializer for the ReturnOrderReport model
- A little bit of refactoring along the way
* Admin integration for new report model
* Refactoring for report.api
- Adds generic mixins for filtering queryset (based on updates to label.api)
- Reduces repeated code a *lot*
* Exposes API endpoints for ReturnOrderReport
* Adds default example report file for ReturnOrder
- Requires some more work :)
* Refactor report printing javascript code
- Replace all existing functions with 'printReports'
* Improvements for default StockItem test report template
- Fix bug in template
- Handle potential errors in template tags
- Add more helpers to report tags
- Improve test result rendering
* Reduce logging verbosity from weasyprint
* Refactor javascript for label printing
- Consolidate into a single function
- Similar to refactor of report functions
* Add report print button to return order page
* Record user reference when creating via API
* Refactor order serializers
- Move common code into AbstractOrderSerializer class
* Adds extra line item model for the return order
- Adds serializer and API endpoints as appropriate
* Render extra line table for return order
- Refactor existing functions into a single generic function
- Reduces repeated JS code a lot
* Add ability to create a new extra line item
* Adds button for creating a new lien item
* JS linting
* Update test
* Typo fix
(cherry picked from commit 28ac2be35b
)
* Enable search for return order
* Don't do pricing (yet) for returnorder extra line table
- Fixes an uncaught error
* Error catching for api.js
* Updates for order models:
- Add 'target_date' field to abstract Order model
- Add IN_PROGRESS status code for return order
- Refactor 'overdue' and 'outstanding' API queries
- Refactor OVERDUE_FILTER on order models
- Refactor is_overdue on order models
- More table filters for return order model
* JS cleanup
* Create ReturnOrderLineItem model
- New type of status label
- Add TotalPriceMixin to ReturnOrder model
* Adds an API serializer for the ReturnOrderLineItem model
* Add API endpoints for ReturnOrderLineItem model
- Including some refactoring along the way
* javascript: refactor loadTableFilters function
- Pass enforced query through to the filters
- Call Object.assign() to construct a superset query
- Removes a lot of code duplication
* Refactor hard-coded URLS to use {% url %} lookup
- Forces error if the URL is wrong
- If we ever change the URL, will still work
* Implement creation of new return order line items
* Adds 'part_detail' annotation to ReturnOrderLineItem serializer
- Required for rendering part information
* javascript: refactor method for creating a group of buttons in a table
* javascript: refactor common buttons with helper functions
* Allow edit and delete of return order line items
* Add form option to automatically reload a table on success
- Pass table name to options.refreshTable
* JS linting
* Add common function for createExtraLineItem
* Refactor loading of attachment tables
- Setup drag-and-drop as part of core function
* CI fixes
* Refactoring out some more common API endpoint code
* Update migrations
* Fix permission typo
* Refactor for unit testing code
* Add unit tests for Contact model
* Tests for returnorder list API
* Annotate 'line_items' to ReturnOrder serializer
* Driving the refactor tractor
* More unit tests for the ReturnOrder API endpoints
* Refactor "print orders" button for various order tables
- Move into "setupFilterList" code (generic)
* add generic 'label printing' button to table actions buttons
* Refactor build output table
* Refactoring icon generation for js
* Refactoring for Part API
* Fix database model type for 'received_date'
* Add API endpoint to "issue" a ReturnOrder
* Improvements for stock tracking table
- Add new status codes
- Add rendering for SalesOrder
- Add rendering for ReturnOrder
- Fix status badges
* Adds functionality to receive line items against a return order
* Add endpoints for completing and cancelling orders
* Add option to allow / prevent editing of ReturnOrder after completed
* js linting
* Wrap "add extra line" button in setting check
* Updates to order/admin.py
* Remove inline admin for returnorderline model
* Updates to pass CI
* Serializer fix
* order template fixes
* Unit test fix
* Fixes for ReturnOrder.receive_line_item
* Unit testing for receiving line items against an RMA
* Improve example report for return order
* Extend unit tests for reporting
* Cleanup here and there
* Unit testing for order views
* Clear "sales_order" field when returning against ReturnOrder
* Add 'location' to deltas when returning from customer
* Bug fix for unit test
This commit is contained in:
@@ -24,8 +24,6 @@
|
||||
modalSetTitle,
|
||||
modalSubmit,
|
||||
openModal,
|
||||
printStockItemLabels,
|
||||
printTestReports,
|
||||
renderLink,
|
||||
scanItemsIntoLocation,
|
||||
showAlertDialog,
|
||||
@@ -88,7 +86,7 @@ function serializeStockItem(pk, options={}) {
|
||||
|
||||
if (options.part) {
|
||||
// Work out the next available serial number
|
||||
inventreeGet(`/api/part/${options.part}/serial-numbers/`, {}, {
|
||||
inventreeGet(`{% url "api-part-list" %}${options.part}/serial-numbers/`, {}, {
|
||||
success: function(data) {
|
||||
if (data.next) {
|
||||
options.fields.serial_numbers.placeholder = `{% trans "Next available serial number" %}: ${data.next}`;
|
||||
@@ -230,7 +228,7 @@ function stockItemFields(options={}) {
|
||||
enableFormInput('serial_numbers', opts);
|
||||
|
||||
// Request part serial number information from the server
|
||||
inventreeGet(`/api/part/${data.pk}/serial-numbers/`, {}, {
|
||||
inventreeGet(`{% url "api-part-list" %}${data.pk}/serial-numbers/`, {}, {
|
||||
success: function(data) {
|
||||
var placeholder = '';
|
||||
if (data.next) {
|
||||
@@ -379,7 +377,7 @@ function duplicateStockItem(pk, options) {
|
||||
}
|
||||
|
||||
// First, we need the StockItem information
|
||||
inventreeGet(`/api/stock/${pk}/`, {}, {
|
||||
inventreeGet(`{% url "api-stock-list" %}${pk}/`, {}, {
|
||||
success: function(data) {
|
||||
|
||||
// Do not duplicate the serial number
|
||||
@@ -656,8 +654,7 @@ function assignStockToCustomer(items, options={}) {
|
||||
|
||||
var buttons = `<div class='btn-group' role='group'>`;
|
||||
|
||||
buttons += makeIconButton(
|
||||
'fa-times icon-red',
|
||||
buttons += makeRemoveButton(
|
||||
'button-stock-item-remove',
|
||||
pk,
|
||||
'{% trans "Remove row" %}',
|
||||
@@ -824,13 +821,13 @@ function mergeStockItems(items, options={}) {
|
||||
|
||||
quantity += stockStatusDisplay(item.status, {classes: 'float-right'});
|
||||
|
||||
var buttons = `<div class='btn-group' role='group'>`;
|
||||
|
||||
buttons += makeIconButton(
|
||||
'fa-times icon-red',
|
||||
'button-stock-item-remove',
|
||||
pk,
|
||||
'{% trans "Remove row" %}',
|
||||
let buttons = wrapButtons(
|
||||
makeIconButton(
|
||||
'fa-times icon-red',
|
||||
'button-stock-item-remove',
|
||||
pk,
|
||||
'{% trans "Remove row" %}',
|
||||
)
|
||||
);
|
||||
|
||||
html += `
|
||||
@@ -1094,16 +1091,11 @@ function adjustStock(action, items, options={}) {
|
||||
);
|
||||
}
|
||||
|
||||
var buttons = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
buttons += makeIconButton(
|
||||
'fa-times icon-red',
|
||||
let buttons = wrapButtons(makeRemoveButton(
|
||||
'button-stock-item-remove',
|
||||
pk,
|
||||
'{% trans "Remove stock item" %}',
|
||||
);
|
||||
|
||||
buttons += `</div>`;
|
||||
));
|
||||
|
||||
html += `
|
||||
<tr id='stock_item_${pk}' class='stock-item-row'>
|
||||
@@ -1341,18 +1333,11 @@ function loadStockTestResultsTable(table, options) {
|
||||
|
||||
var filterKey = options.filterKey || options.name || 'stocktests';
|
||||
|
||||
var filters = loadTableFilters(filterKey);
|
||||
|
||||
var params = {
|
||||
let params = {
|
||||
part: options.part,
|
||||
};
|
||||
|
||||
var original = {};
|
||||
|
||||
for (var k in params) {
|
||||
original[k] = params[k];
|
||||
filters[k] = params[k];
|
||||
}
|
||||
var filters = loadTableFilters(filterKey, params);
|
||||
|
||||
setupFilterList(filterKey, table, filterTarget);
|
||||
|
||||
@@ -1360,7 +1345,7 @@ function loadStockTestResultsTable(table, options) {
|
||||
|
||||
// Helper function for rendering buttons
|
||||
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
let html = '';
|
||||
|
||||
if (row.requires_attachment == false && row.requires_value == false && !row.result) {
|
||||
// Enable a "quick tick" option for this test result
|
||||
@@ -1371,13 +1356,11 @@ function loadStockTestResultsTable(table, options) {
|
||||
|
||||
if (!grouped && row.result != null) {
|
||||
var pk = row.pk;
|
||||
html += makeIconButton('fa-edit icon-blue', 'button-test-edit', pk, '{% trans "Edit test result" %}');
|
||||
html += makeIconButton('fa-trash-alt icon-red', 'button-test-delete', pk, '{% trans "Delete test result" %}');
|
||||
html += makeEditButton('button-test-edit', pk, '{% trans "Edit test result" %}');
|
||||
html += makeDeleteButton('button-test-delete', pk, '{% trans "Delete test result" %}');
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
return wrapButtons(html);
|
||||
}
|
||||
|
||||
var parent_node = 'parent node';
|
||||
@@ -1396,7 +1379,7 @@ function loadStockTestResultsTable(table, options) {
|
||||
return '{% trans "No test results found" %}';
|
||||
},
|
||||
queryParams: filters,
|
||||
original: original,
|
||||
original: params,
|
||||
onPostBody: function() {
|
||||
table.treegrid({
|
||||
treeColumn: 0,
|
||||
@@ -1444,7 +1427,7 @@ function loadStockTestResultsTable(table, options) {
|
||||
var html = value;
|
||||
|
||||
if (row.attachment) {
|
||||
var text = `<span class='fas fa-file-alt float-right'></span>`;
|
||||
let text = makeIconBadge('fa-file-alt', '');
|
||||
html += renderLink(text, row.attachment, {download: true});
|
||||
}
|
||||
|
||||
@@ -1700,65 +1683,50 @@ function locationDetail(row, showLink=true) {
|
||||
}
|
||||
|
||||
|
||||
/* Load data into a stock table with adjustable options.
|
||||
* Fetches data (via AJAX) and loads into a bootstrap table.
|
||||
* Also links in default button callbacks.
|
||||
*
|
||||
* Options:
|
||||
* url - URL for the stock query
|
||||
* params - query params for augmenting stock data request
|
||||
* buttons - Which buttons to link to stock selection callbacks
|
||||
* filterList - <ul> element where filters are displayed
|
||||
* disableFilters: If true, disable custom filters
|
||||
*/
|
||||
function loadStockTable(table, options) {
|
||||
/* Load data into a stock table with adjustable options.
|
||||
* Fetches data (via AJAX) and loads into a bootstrap table.
|
||||
* Also links in default button callbacks.
|
||||
*
|
||||
* Options:
|
||||
* url - URL for the stock query
|
||||
* 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
|
||||
* disableFilters: If true, disable custom filters
|
||||
*/
|
||||
|
||||
// List of user-params which override the default filters
|
||||
|
||||
options.params['location_detail'] = true;
|
||||
options.params['part_detail'] = true;
|
||||
|
||||
var params = options.params || {};
|
||||
|
||||
var filterTarget = options.filterTarget || '#filter-list-stock';
|
||||
const filterTarget = options.filterTarget || '#filter-list-stock';
|
||||
|
||||
var filters = {};
|
||||
const filterKey = options.filterKey || options.name || 'stock';
|
||||
|
||||
var filterKey = options.filterKey || options.name || 'stock';
|
||||
let filters = loadTableFilters(filterKey, params);
|
||||
|
||||
if (!options.disableFilters) {
|
||||
filters = loadTableFilters(filterKey);
|
||||
}
|
||||
|
||||
var original = {};
|
||||
|
||||
for (var k in params) {
|
||||
original[k] = params[k];
|
||||
}
|
||||
|
||||
setupFilterList(filterKey, table, filterTarget, {download: true});
|
||||
setupFilterList(filterKey, table, filterTarget, {
|
||||
download: true,
|
||||
report: {
|
||||
url: '{% url "api-stockitem-testreport-list" %}',
|
||||
key: 'item',
|
||||
},
|
||||
labels: {
|
||||
url: '{% url "api-stockitem-label-list" %}',
|
||||
key: 'item',
|
||||
}
|
||||
});
|
||||
|
||||
// Override the default values, or add new ones
|
||||
for (var key in params) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
var grouping = true;
|
||||
|
||||
if ('grouping' in options) {
|
||||
grouping = options.grouping;
|
||||
}
|
||||
|
||||
var col = null;
|
||||
|
||||
// Explicitly disable part grouping functionality
|
||||
// Might be able to add this in later on,
|
||||
// but there is a bug which makes this crash if paginating on the server side.
|
||||
// Ref: https://github.com/wenzhixin/bootstrap-table/issues/3250
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
grouping = false;
|
||||
|
||||
var columns = [
|
||||
{
|
||||
checkbox: true,
|
||||
@@ -2175,14 +2143,13 @@ function loadStockTable(table, options) {
|
||||
queryParams: filters,
|
||||
sidePagination: 'server',
|
||||
name: 'stock',
|
||||
original: original,
|
||||
original: params,
|
||||
showColumns: true,
|
||||
showFooter: true,
|
||||
columns: columns,
|
||||
});
|
||||
|
||||
var buttons = [
|
||||
'#stock-print-options',
|
||||
'#stock-options',
|
||||
];
|
||||
|
||||
@@ -2206,31 +2173,6 @@ function loadStockTable(table, options) {
|
||||
}
|
||||
|
||||
// Automatically link button callbacks
|
||||
|
||||
$('#multi-item-print-label').click(function() {
|
||||
var selections = getTableData(table);
|
||||
|
||||
var items = [];
|
||||
|
||||
selections.forEach(function(item) {
|
||||
items.push(item.pk);
|
||||
});
|
||||
|
||||
printStockItemLabels(items);
|
||||
});
|
||||
|
||||
$('#multi-item-print-test-report').click(function() {
|
||||
var selections = getTableData(table);
|
||||
|
||||
var items = [];
|
||||
|
||||
selections.forEach(function(item) {
|
||||
items.push(item.pk);
|
||||
});
|
||||
|
||||
printTestReports(items);
|
||||
});
|
||||
|
||||
if (global_settings.BARCODE_ENABLE) {
|
||||
$('#multi-item-barcode-scan-into-location').click(function() {
|
||||
var selections = getTableData(table);
|
||||
@@ -2420,21 +2362,17 @@ function loadStockLocationTable(table, options) {
|
||||
params.depth = global_settings.INVENTREE_TREE_DEPTH;
|
||||
}
|
||||
|
||||
var filters = {};
|
||||
|
||||
var filterKey = options.filterKey || options.name || 'location';
|
||||
|
||||
if (!options.disableFilters) {
|
||||
filters = loadTableFilters(filterKey);
|
||||
}
|
||||
let filters = loadTableFilters(filterKey, params);
|
||||
|
||||
var original = {};
|
||||
|
||||
for (var k in params) {
|
||||
original[k] = params[k];
|
||||
}
|
||||
|
||||
setupFilterList(filterKey, table, filterListElement, {download: true});
|
||||
setupFilterList(filterKey, table, filterListElement, {
|
||||
download: true,
|
||||
labels: {
|
||||
url: '{% url "api-stocklocation-label-list" %}',
|
||||
key: 'location'
|
||||
}
|
||||
});
|
||||
|
||||
for (var key in params) {
|
||||
filters[key] = params[key];
|
||||
@@ -2484,7 +2422,7 @@ function loadStockLocationTable(table, options) {
|
||||
url: options.url || '{% url "api-location-list" %}',
|
||||
queryParams: filters,
|
||||
name: 'location',
|
||||
original: original,
|
||||
original: params,
|
||||
sortable: true,
|
||||
showColumns: true,
|
||||
onPostBody: function() {
|
||||
@@ -2654,26 +2592,20 @@ function loadStockLocationTable(table, options) {
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Load stock history / tracking table for a given StockItem
|
||||
*/
|
||||
function loadStockTrackingTable(table, options) {
|
||||
|
||||
var cols = [];
|
||||
|
||||
var filterTarget = '#filter-list-stocktracking';
|
||||
const filterKey = 'stocktracking';
|
||||
|
||||
var filterKey = 'stocktracking';
|
||||
let params = options.params || {};
|
||||
|
||||
var filters = loadTableFilters(filterKey);
|
||||
let filters = loadTableFilters(filterKey, params);
|
||||
|
||||
var params = options.params;
|
||||
|
||||
var original = {};
|
||||
|
||||
for (var k in params) {
|
||||
original[k] = params[k];
|
||||
filters[k] = params[k];
|
||||
}
|
||||
|
||||
setupFilterList(filterKey, table, filterTarget);
|
||||
setupFilterList(filterKey, table, '#filter-list-stocktracking');
|
||||
|
||||
// Date
|
||||
cols.push({
|
||||
@@ -2747,10 +2679,10 @@ function loadStockTrackingTable(table, options) {
|
||||
html += '</td></tr>';
|
||||
}
|
||||
|
||||
// Purchase Order Information
|
||||
// PurchaseOrder Information
|
||||
if (details.purchaseorder) {
|
||||
|
||||
html += `<tr><th>{% trans "Purchase Order" %}</td>`;
|
||||
html += `<tr><th>{% trans "Purchase Order" %}</th>`;
|
||||
|
||||
html += '<td>';
|
||||
|
||||
@@ -2766,6 +2698,40 @@ function loadStockTrackingTable(table, options) {
|
||||
html += '</td></tr>';
|
||||
}
|
||||
|
||||
// SalesOrder information
|
||||
if (details.salesorder) {
|
||||
html += `<tr><th>{% trans "Sales Order" %}</th>`;
|
||||
html += '<td>';
|
||||
|
||||
if (details.salesorder_detail) {
|
||||
html += renderLink(
|
||||
details.salesorder_detail.reference,
|
||||
`/order/sales-order/${details.salesorder}`
|
||||
);
|
||||
} else {
|
||||
html += `<em>{% trans "Sales Order no longer exists" %}</em>`;
|
||||
}
|
||||
|
||||
html += `</td></tr>`;
|
||||
}
|
||||
|
||||
// ReturnOrder information
|
||||
if (details.returnorder) {
|
||||
html += `<tr><th>{% trans "Return Order" %}</th>`;
|
||||
html += '<td>';
|
||||
|
||||
if (details.returnorder_detail) {
|
||||
html += renderLink(
|
||||
details.returnorder_detail.reference,
|
||||
`/order/return-order/${details.returnorder}/`
|
||||
);
|
||||
} else {
|
||||
html += `<em>{% trans "Return Order no longer exists" %}</em>`;
|
||||
}
|
||||
|
||||
html += `</td></tr>`;
|
||||
}
|
||||
|
||||
// Customer information
|
||||
if (details.customer) {
|
||||
|
||||
@@ -2808,12 +2774,7 @@ function loadStockTrackingTable(table, options) {
|
||||
html += `<tr><th>{% trans "Status" %}</td>`;
|
||||
|
||||
html += '<td>';
|
||||
html += stockStatusDisplay(
|
||||
details.status,
|
||||
{
|
||||
classes: 'float-right',
|
||||
}
|
||||
);
|
||||
html += stockStatusDisplay(details.status);
|
||||
html += '</td></tr>';
|
||||
|
||||
}
|
||||
@@ -2865,7 +2826,7 @@ function loadStockTrackingTable(table, options) {
|
||||
table.inventreeTable({
|
||||
method: 'get',
|
||||
queryParams: filters,
|
||||
original: original,
|
||||
original: params,
|
||||
columns: cols,
|
||||
url: options.url,
|
||||
});
|
||||
@@ -2951,14 +2912,12 @@ function loadInstalledInTable(table, options) {
|
||||
title: '',
|
||||
switchable: false,
|
||||
formatter: function(value, row) {
|
||||
var pk = row.pk;
|
||||
var html = '';
|
||||
let pk = row.pk;
|
||||
let html = '';
|
||||
|
||||
html += `<div class='btn-group float-right' role='group'>`;
|
||||
html += makeIconButton('fa-unlink', 'button-uninstall', pk, '{% trans "Uninstall Stock Item" %}');
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
return wrapButtons(html);
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Reference in New Issue
Block a user