2
0
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:
Oliver
2023-03-29 10:35:43 +11:00
committed by GitHub
parent d4a64b4f7d
commit 27aa16d55d
122 changed files with 10391 additions and 7053 deletions

View File

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