mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-30 18:50:53 +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:
@ -90,7 +90,7 @@ function editBuildOrder(pk) {
|
||||
|
||||
var fields = buildFormFields();
|
||||
|
||||
constructForm(`/api/build/${pk}/`, {
|
||||
constructForm(`{% url "api-build-list" %}${pk}/`, {
|
||||
fields: fields,
|
||||
reload: true,
|
||||
title: '{% trans "Edit Build Order" %}',
|
||||
@ -147,7 +147,7 @@ function newBuildOrder(options={}) {
|
||||
*/
|
||||
function duplicateBuildOrder(build_id, options={}) {
|
||||
|
||||
inventreeGet(`/api/build/${build_id}/`, {}, {
|
||||
inventreeGet(`{% url "api-build-list" %}${build_id}/`, {}, {
|
||||
success: function(data) {
|
||||
// Clear out data we do not want to be duplicated
|
||||
delete data['pk'];
|
||||
@ -166,7 +166,7 @@ function duplicateBuildOrder(build_id, options={}) {
|
||||
function cancelBuildOrder(build_id, options={}) {
|
||||
|
||||
constructForm(
|
||||
`/api/build/${build_id}/cancel/`,
|
||||
`{% url "api-build-list" %}${build_id}/cancel/`,
|
||||
{
|
||||
method: 'POST',
|
||||
title: '{% trans "Cancel Build Order" %}',
|
||||
@ -208,7 +208,7 @@ function cancelBuildOrder(build_id, options={}) {
|
||||
/* Construct a form to "complete" (finish) a build order */
|
||||
function completeBuildOrder(build_id, options={}) {
|
||||
|
||||
constructForm(`/api/build/${build_id}/finish/`, {
|
||||
constructForm(`{% url "api-build-list" %}${build_id}/finish/`, {
|
||||
fieldsFunction: function(opts) {
|
||||
var ctx = opts.context || {};
|
||||
|
||||
@ -287,7 +287,7 @@ function createBuildOutput(build_id, options) {
|
||||
|
||||
// Request build order information from the server
|
||||
inventreeGet(
|
||||
`/api/build/${build_id}/`,
|
||||
`{% url "api-build-list" %}${build_id}/`,
|
||||
{},
|
||||
{
|
||||
success: function(build) {
|
||||
@ -312,7 +312,7 @@ function createBuildOutput(build_id, options) {
|
||||
};
|
||||
|
||||
// Work out the next available serial numbers
|
||||
inventreeGet(`/api/part/${build.part}/serial-numbers/`, {}, {
|
||||
inventreeGet(`{% url "api-part-list" %}${build.part}/serial-numbers/`, {}, {
|
||||
success: function(data) {
|
||||
if (data.next) {
|
||||
fields.serial_numbers.placeholder = `{% trans "Next available serial number" %}: ${data.next}`;
|
||||
@ -341,7 +341,7 @@ function createBuildOutput(build_id, options) {
|
||||
`;
|
||||
}
|
||||
|
||||
constructForm(`/api/build/${build_id}/create-output/`, {
|
||||
constructForm(`{% url "api-build-list" %}${build_id}/create-output/`, {
|
||||
method: 'POST',
|
||||
title: '{% trans "Create Build Output" %}',
|
||||
confirm: true,
|
||||
@ -364,7 +364,7 @@ function createBuildOutput(build_id, options) {
|
||||
*/
|
||||
function makeBuildOutputButtons(output_id, build_info, options={}) {
|
||||
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
var html = '';
|
||||
|
||||
// Tracked parts? Must be individually allocated
|
||||
if (options.has_bom_items) {
|
||||
@ -398,17 +398,13 @@ function makeBuildOutputButtons(output_id, build_info, options={}) {
|
||||
);
|
||||
|
||||
// Add a button to "delete" this build output
|
||||
html += makeIconButton(
|
||||
'fa-trash-alt icon-red',
|
||||
html += makeDeleteButton(
|
||||
'button-output-delete',
|
||||
output_id,
|
||||
'{% trans "Delete build output" %}',
|
||||
);
|
||||
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
|
||||
return wrapButtons(html);
|
||||
}
|
||||
|
||||
|
||||
@ -421,7 +417,7 @@ function makeBuildOutputButtons(output_id, build_info, options={}) {
|
||||
*/
|
||||
function unallocateStock(build_id, options={}) {
|
||||
|
||||
var url = `/api/build/${build_id}/unallocate/`;
|
||||
var url = `{% url "api-build-list" %}${build_id}/unallocate/`;
|
||||
|
||||
var html = `
|
||||
<div class='alert alert-block alert-warning'>
|
||||
@ -486,7 +482,7 @@ function completeBuildOutputs(build_id, outputs, options={}) {
|
||||
|
||||
var buttons = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
buttons += makeIconButton('fa-times icon-red', 'button-row-remove', pk, '{% trans "Remove row" %}');
|
||||
buttons += makeRemoveButton('button-row-remove', pk, '{% trans "Remove row" %}');
|
||||
|
||||
buttons += '</div>';
|
||||
|
||||
@ -529,7 +525,7 @@ function completeBuildOutputs(build_id, outputs, options={}) {
|
||||
</tbody>
|
||||
</table>`;
|
||||
|
||||
constructForm(`/api/build/${build_id}/complete/`, {
|
||||
constructForm(`{% url "api-build-list" %}${build_id}/complete/`, {
|
||||
method: 'POST',
|
||||
preFormContent: html,
|
||||
fields: {
|
||||
@ -647,7 +643,7 @@ function deleteBuildOutputs(build_id, outputs, options={}) {
|
||||
|
||||
var buttons = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
buttons += makeIconButton('fa-times icon-red', 'button-row-remove', pk, '{% trans "Remove row" %}');
|
||||
buttons += makeRemoveButton('button-row-remove', pk, '{% trans "Remove row" %}');
|
||||
|
||||
buttons += '</div>';
|
||||
|
||||
@ -690,7 +686,7 @@ function deleteBuildOutputs(build_id, outputs, options={}) {
|
||||
</tbody>
|
||||
</table>`;
|
||||
|
||||
constructForm(`/api/build/${build_id}/delete-outputs/`, {
|
||||
constructForm(`{% url "api-build-list" %}${build_id}/delete-outputs/`, {
|
||||
method: 'POST',
|
||||
preFormContent: html,
|
||||
fields: {},
|
||||
@ -768,11 +764,7 @@ function loadBuildOrderAllocationTable(table, options={}) {
|
||||
options.params['location_detail'] = true;
|
||||
options.params['stock_detail'] = true;
|
||||
|
||||
var filters = loadTableFilters('buildorderallocation');
|
||||
|
||||
for (var key in options.params) {
|
||||
filters[key] = options.params[key];
|
||||
}
|
||||
var filters = loadTableFilters('buildorderallocation', options.params);
|
||||
|
||||
setupFilterList('buildorderallocation', $(table));
|
||||
|
||||
@ -893,7 +885,12 @@ function loadBuildOutputTable(build_info, options={}) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
setupFilterList('builditems', $(table), options.filterTarget || '#filter-list-incompletebuilditems');
|
||||
setupFilterList('builditems', $(table), options.filterTarget || '#filter-list-incompletebuilditems', {
|
||||
labels: {
|
||||
url: '{% url "api-stockitem-label-list" %}',
|
||||
key: 'item',
|
||||
}
|
||||
});
|
||||
|
||||
function setupBuildOutputButtonCallbacks() {
|
||||
|
||||
@ -1407,19 +1404,6 @@ function loadBuildOutputTable(build_info, options={}) {
|
||||
);
|
||||
});
|
||||
|
||||
// Print stock item labels
|
||||
$('#incomplete-output-print-label').click(function() {
|
||||
var outputs = getTableData(table);
|
||||
|
||||
var stock_id_values = [];
|
||||
|
||||
outputs.forEach(function(output) {
|
||||
stock_id_values.push(output.pk);
|
||||
});
|
||||
|
||||
printStockItemLabels(stock_id_values);
|
||||
});
|
||||
|
||||
$('#outputs-expand').click(function() {
|
||||
$(table).bootstrapTable('expandAllRows');
|
||||
});
|
||||
@ -1482,13 +1466,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
}
|
||||
|
||||
// Filters
|
||||
var filters = loadTableFilters('builditems');
|
||||
|
||||
var params = options.params || {};
|
||||
|
||||
for (var key in params) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
let filters = loadTableFilters('builditems', options.params);
|
||||
|
||||
setupFilterList('builditems', $(table), options.filterTarget);
|
||||
|
||||
@ -1703,6 +1681,8 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
name: 'build-allocation',
|
||||
uniqueId: 'sub_part',
|
||||
search: options.search || false,
|
||||
queryParams: filters,
|
||||
original: options.params,
|
||||
onPostBody: function(data) {
|
||||
// Setup button callbacks
|
||||
setupCallbacks();
|
||||
@ -1796,15 +1776,13 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
|
||||
var pk = row.pk;
|
||||
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
var html = '';
|
||||
|
||||
html += makeIconButton('fa-edit icon-blue', 'button-allocation-edit', pk, '{% trans "Edit stock allocation" %}');
|
||||
html += makeEditButton('button-allocation-edit', pk, '{% trans "Edit stock allocation" %}');
|
||||
|
||||
html += makeIconButton('fa-trash-alt icon-red', 'button-allocation-delete', pk, '{% trans "Delete stock allocation" %}');
|
||||
html += makeDeleteButton('button-allocation-delete', pk, '{% trans "Delete stock allocation" %}');
|
||||
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
return wrapButtons(html);
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -1814,7 +1792,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
subTable.find('.button-allocation-edit').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
constructForm(`/api/build/item/${pk}/`, {
|
||||
constructForm(`{% url "api-build-item-list" %}${pk}/`, {
|
||||
fields: {
|
||||
quantity: {},
|
||||
},
|
||||
@ -1826,7 +1804,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
subTable.find('.button-allocation-delete').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
constructForm(`/api/build/item/${pk}/`, {
|
||||
constructForm(`{% url "api-build-item-list" %}${pk}/`, {
|
||||
method: 'DELETE',
|
||||
title: '{% trans "Remove Allocation" %}',
|
||||
onSuccess: reloadAllocationData,
|
||||
@ -1935,9 +1913,9 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
icons += `<span class='fas fa-info-circle icon-blue float-right' title='{% trans "Consumable item" %}'></span>`;
|
||||
} else {
|
||||
if (available_stock < (required - allocated)) {
|
||||
icons += `<span class='fas fa-times-circle icon-red float-right' title='{% trans "Insufficient stock available" %}'></span>`;
|
||||
icons += makeIconBadge('fa-times-circle icon-red', '{% trans "Insufficient stock available" %}');
|
||||
} else {
|
||||
icons += `<span class='fas fa-check-circle icon-green float-right' title='{% trans "Sufficient stock available" %}'></span>`;
|
||||
icons += makeIconBadge('fa-check-circle icon-green', '{% trans "Sufficient stock available" %}');
|
||||
}
|
||||
|
||||
if (available_stock <= 0) {
|
||||
@ -1953,13 +1931,15 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
}
|
||||
|
||||
if (extra) {
|
||||
icons += `<span title='${extra}' class='fas fa-info-circle float-right icon-blue'></span>`;
|
||||
icons += makeInfoButton('fa-info-circle icon-blue', extra);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (row.on_order && row.on_order > 0) {
|
||||
icons += `<span class='fas fa-shopping-cart float-right' title='{% trans "On Order" %}: ${row.on_order}'></span>`;
|
||||
makeIconBadge('fa-shopping-cart', '{% trans "On Order" %}', {
|
||||
content: row.on_order,
|
||||
});
|
||||
}
|
||||
|
||||
return renderLink(text, url) + icons;
|
||||
@ -2027,7 +2007,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
}
|
||||
|
||||
// Generate action buttons for this build output
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
let html = '';
|
||||
|
||||
if (allocatedQuantity(row) < requiredQuantity(row)) {
|
||||
if (row.sub_part_detail.assembly) {
|
||||
@ -2041,17 +2021,16 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
html += makeIconButton('fa-sign-in-alt icon-green', 'button-add', row.sub_part, '{% trans "Allocate stock" %}');
|
||||
}
|
||||
|
||||
html += makeIconButton(
|
||||
'fa-minus-circle icon-red', 'button-unallocate', row.sub_part,
|
||||
html += makeRemoveButton(
|
||||
'button-unallocate',
|
||||
row.sub_part,
|
||||
'{% trans "Unallocate stock" %}',
|
||||
{
|
||||
disabled: allocatedQuantity(row) == 0,
|
||||
}
|
||||
);
|
||||
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
return wrapButtons(html);
|
||||
}
|
||||
},
|
||||
]
|
||||
@ -2093,7 +2072,7 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
||||
|
||||
if (output_id) {
|
||||
// Request information on the particular build output (stock item)
|
||||
inventreeGet(`/api/stock/${output_id}/`, {}, {
|
||||
inventreeGet(`{% url "api-stock-list" %}${output_id}/`, {}, {
|
||||
success: function(output) {
|
||||
if (output.quantity == 1 && output.serial != null) {
|
||||
auto_fill_filters.serial = output.serial;
|
||||
@ -2112,8 +2091,7 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
||||
|
||||
var delete_button = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
delete_button += makeIconButton(
|
||||
'fa-times icon-red',
|
||||
delete_button += makeRemoveButton(
|
||||
'button-row-remove',
|
||||
pk,
|
||||
'{% trans "Remove row" %}',
|
||||
@ -2245,7 +2223,7 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
||||
</table>
|
||||
`;
|
||||
|
||||
constructForm(`/api/build/${build_id}/allocate/`, {
|
||||
constructForm(`{% url "api-build-list" %}${build_id}/allocate/`, {
|
||||
method: 'POST',
|
||||
fields: {},
|
||||
preFormContent: html,
|
||||
@ -2459,7 +2437,7 @@ function autoAllocateStockToBuild(build_id, bom_items=[], options={}) {
|
||||
},
|
||||
};
|
||||
|
||||
constructForm(`/api/build/${build_id}/auto-allocate/`, {
|
||||
constructForm(`{% url "api-build-list" %}${build_id}/auto-allocate/`, {
|
||||
method: 'POST',
|
||||
fields: fields,
|
||||
title: '{% trans "Allocate Stock Items" %}',
|
||||
@ -2484,21 +2462,19 @@ function loadBuildTable(table, options) {
|
||||
|
||||
var params = options.params || {};
|
||||
|
||||
var filters = {};
|
||||
|
||||
params['part_detail'] = true;
|
||||
|
||||
if (!options.disableFilters) {
|
||||
filters = loadTableFilters('build');
|
||||
}
|
||||
|
||||
for (var key in params) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
var filters = loadTableFilters('build', params);
|
||||
|
||||
var filterTarget = options.filterTarget || null;
|
||||
|
||||
setupFilterList('build', table, filterTarget, {download: true});
|
||||
setupFilterList('build', table, filterTarget, {
|
||||
download: true,
|
||||
report: {
|
||||
url: '{% url "api-build-report-list" %}',
|
||||
key: 'build',
|
||||
}
|
||||
});
|
||||
|
||||
// Which display mode to use for the build table?
|
||||
var display_mode = inventreeLoad('build-table-display-mode', 'list');
|
||||
|
Reference in New Issue
Block a user