2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-17 20:45:44 +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

@ -1,6 +1,6 @@
"""JSON API for the Build app."""
from django.urls import include, re_path
from django.urls import include, path, re_path
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User
@ -509,13 +509,13 @@ build_api_urls = [
# Attachments
re_path(r'^attachment/', include([
re_path(r'^(?P<pk>\d+)/', BuildAttachmentDetail.as_view(), name='api-build-attachment-detail'),
path(r'<int:pk>/', BuildAttachmentDetail.as_view(), name='api-build-attachment-detail'),
re_path(r'^.*$', BuildAttachmentList.as_view(), name='api-build-attachment-list'),
])),
# Build Items
re_path(r'^item/', include([
re_path(r'^(?P<pk>\d+)/', include([
path(r'<int:pk>/', include([
re_path(r'^metadata/', BuildItemMetadata.as_view(), name='api-build-item-metadata'),
re_path(r'^.*$', BuildItemDetail.as_view(), name='api-build-item-detail'),
])),
@ -523,7 +523,7 @@ build_api_urls = [
])),
# Build Detail
re_path(r'^(?P<pk>\d+)/', include([
path(r'<int:pk>/', include([
re_path(r'^allocate/', BuildAllocate.as_view(), name='api-build-allocate'),
re_path(r'^auto-allocate/', BuildAutoAllocate.as_view(), name='api-build-auto-allocate'),
re_path(r'^complete/', BuildOutputComplete.as_view(), name='api-build-output-complete'),

View File

@ -36,9 +36,10 @@ from plugin.events import trigger_event
from plugin.models import MetadataMixin
import common.notifications
from part import models as PartModels
from stock import models as StockModels
from users import models as UserModels
import part.models
import stock.models
import users.models
class Build(MPTTModel, MetadataMixin, ReferenceIndexingMixin):
@ -279,7 +280,7 @@ class Build(MPTTModel, MetadataMixin, ReferenceIndexingMixin):
)
responsible = models.ForeignKey(
UserModels.Owner,
users.models.Owner,
on_delete=models.SET_NULL,
blank=True, null=True,
verbose_name=_('Responsible'),
@ -395,9 +396,9 @@ class Build(MPTTModel, MetadataMixin, ReferenceIndexingMixin):
if in_stock is not None:
if in_stock:
outputs = outputs.filter(StockModels.StockItem.IN_STOCK_FILTER)
outputs = outputs.filter(stock.models.StockItem.IN_STOCK_FILTER)
else:
outputs = outputs.exclude(StockModels.StockItem.IN_STOCK_FILTER)
outputs = outputs.exclude(stock.models.StockItem.IN_STOCK_FILTER)
# Filter by 'complete' status
complete = kwargs.get('complete', None)
@ -659,7 +660,7 @@ class Build(MPTTModel, MetadataMixin, ReferenceIndexingMixin):
else:
serial = None
output = StockModels.StockItem.objects.create(
output = stock.models.StockItem.objects.create(
quantity=1,
location=location,
part=self.part,
@ -677,11 +678,11 @@ class Build(MPTTModel, MetadataMixin, ReferenceIndexingMixin):
parts = bom_item.get_valid_parts_for_allocation()
items = StockModels.StockItem.objects.filter(
items = stock.models.StockItem.objects.filter(
part__in=parts,
serial=str(serial),
quantity=1,
).filter(StockModels.StockItem.IN_STOCK_FILTER)
).filter(stock.models.StockItem.IN_STOCK_FILTER)
"""
Test if there is a matching serial number!
@ -701,7 +702,7 @@ class Build(MPTTModel, MetadataMixin, ReferenceIndexingMixin):
else:
"""Create a single build output of the given quantity."""
StockModels.StockItem.objects.create(
stock.models.StockItem.objects.create(
quantity=quantity,
location=location,
part=self.part,
@ -877,7 +878,7 @@ class Build(MPTTModel, MetadataMixin, ReferenceIndexingMixin):
)
# Look for available stock items
available_stock = StockModels.StockItem.objects.filter(StockModels.StockItem.IN_STOCK_FILTER)
available_stock = stock.models.StockItem.objects.filter(stock.models.StockItem.IN_STOCK_FILTER)
# Filter by list of available parts
available_stock = available_stock.filter(
@ -1220,7 +1221,7 @@ class BuildItem(MetadataMixin, models.Model):
'quantity': _('Quantity must be 1 for serialized stock')
})
except (StockModels.StockItem.DoesNotExist, PartModels.Part.DoesNotExist):
except (stock.models.StockItem.DoesNotExist, part.models.Part.DoesNotExist):
pass
"""
@ -1259,8 +1260,8 @@ class BuildItem(MetadataMixin, models.Model):
for idx, ancestor in enumerate(ancestors):
try:
bom_item = PartModels.BomItem.objects.get(part=self.build.part, sub_part=ancestor)
except PartModels.BomItem.DoesNotExist:
bom_item = part.models.BomItem.objects.get(part=self.build.part, sub_part=ancestor)
except part.models.BomItem.DoesNotExist:
continue
# A matching BOM item has been found!
@ -1350,7 +1351,7 @@ class BuildItem(MetadataMixin, models.Model):
# Internal model which links part <-> sub_part
# We need to track this separately, to allow for "variant' stock
bom_item = models.ForeignKey(
PartModels.BomItem,
part.models.BomItem,
on_delete=models.CASCADE,
related_name='allocate_build_items',
blank=True, null=True,

View File

@ -247,7 +247,11 @@ src="{% static 'img/blank_image.png' %}"
{% if report_enabled %}
$('#print-build-report').click(function() {
printBuildReports([{{ build.pk }}]);
printReports({
items: [{{ build.pk }}],
key: 'build',
url: '{% url "api-build-report-list" %}',
});
});
{% endif %}

View File

@ -268,19 +268,6 @@
{% endif %}
</ul>
</div>
<!-- Label Printing Actions -->
<div class='btn-group'>
<button id='output-print-options' class='btn btn-primary dropdown-toggle' type='button' data-bs-toggle='dropdown' title='{% trans "Printing Actions" %}'>
<span class='fas fa-print'></span> <span class='caret'></span>
</button>
<ul class='dropdown-menu'>
<li><a class='dropdown-item' href='#' id='incomplete-output-print-label' title='{% trans "Print labels" %}'>
<span class='fas fa-tags'></span> {% trans "Print labels" %}
</a></li>
</ul>
</div>
{% include "filter_list.html" with id='incompletebuilditems' %}
</div>
{% endif %}
@ -367,20 +354,6 @@ onPanelLoad('children', function() {
onPanelLoad('attachments', function() {
enableDragAndDrop(
'#attachment-dropzone',
'{% url "api-build-attachment-list" %}',
{
data: {
build: {{ build.id }},
},
label: 'attachment',
success: function(data, status, xhr) {
$('#attachment-table').bootstrapTable('refresh');
}
}
);
loadAttachmentTable('{% url "api-build-attachment-list" %}', {
filters: {
build: {{ build.pk }},
@ -409,10 +382,6 @@ onPanelLoad('notes', function() {
);
});
function reloadTable() {
$('#allocation-table-untracked').bootstrapTable('refresh');
}
onPanelLoad('outputs', function() {
{% if build.active %}

View File

@ -26,20 +26,6 @@
<div id='button-toolbar'>
<div class='button-toolbar container-fluid' style='float: right;'>
<div class='btn-group' role='group'>
{% if report_enabled %}
<div class='btn-group' role='group'>
<!-- Print actions -->
<button id='build-print-options' class='btn btn-primary dropdown-toggle' data-bs-toggle='dropdown'>
<span class='fas fa-print'></span> <span class='caret'></span>
</button>
<ul class='dropdown-menu'>
<li><a class='dropdown-item' href='#' id='multi-build-print' title='{% trans "Print Build Orders" %}'>
<span class='fas fa-file-pdf'></span> {% trans "Print Build Orders" %}
</a></li>
</ul>
</div>
{% endif %}
{% include "filter_list.html" with id="build" %}
</div>
</div>
@ -62,17 +48,4 @@ loadBuildTable($("#build-table"), {
locale: '{{ request.LANGUAGE_CODE }}',
});
{% if report_enabled %}
$('#multi-build-print').click(function() {
var rows = getTableData("#build-table");
var build_ids = [];
rows.forEach(function(row) {
build_ids.push(row.pk);
});
printBuildReports(build_ids);
});
{% endif %}
{% endblock %}

View File

@ -1,13 +1,13 @@
"""URL lookup for Build app."""
from django.urls import include, re_path
from django.urls import include, path, re_path
from . import views
build_urls = [
re_path(r'^(?P<pk>\d+)/', include([
path(r'<int:pk>/', include([
re_path(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),
])),