mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-15 19:45:46 +00:00
Adds ReturnOrder and ReturnOrderAttachment models
This commit is contained in:
@ -320,3 +320,23 @@ class BuildStatus(StatusCode):
|
|||||||
PENDING,
|
PENDING,
|
||||||
PRODUCTION,
|
PRODUCTION,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ReturnOrderStatus(StatusCode):
|
||||||
|
"""Defines a set of status codes for a ReturnOrder"""
|
||||||
|
|
||||||
|
PENDING = 10
|
||||||
|
COMPLETE = 30
|
||||||
|
CANCELLED = 40
|
||||||
|
|
||||||
|
options = {
|
||||||
|
PENDING: _("Pending"),
|
||||||
|
COMPLETE: _("Complete"),
|
||||||
|
CANCELLED: _("Cancelled"),
|
||||||
|
}
|
||||||
|
|
||||||
|
colors = {
|
||||||
|
PENDING: 'secondary',
|
||||||
|
COMPLETE: 'success',
|
||||||
|
CANCELLED: 'danger',
|
||||||
|
}
|
||||||
|
@ -1423,6 +1423,13 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
'validator': build.validators.validate_build_order_reference_pattern,
|
'validator': build.validators.validate_build_order_reference_pattern,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'RETURNORDER_REFERENCE_PATTERN': {
|
||||||
|
'name': _('Return Order Reference Pattern'),
|
||||||
|
'description': _('Required pattern for generating Return Order reference field'),
|
||||||
|
'default': 'RMA-{ref:04d}',
|
||||||
|
'validator': order.validators.validate_return_order_reference_pattern,
|
||||||
|
},
|
||||||
|
|
||||||
'SALESORDER_REFERENCE_PATTERN': {
|
'SALESORDER_REFERENCE_PATTERN': {
|
||||||
'name': _('Sales Order Reference Pattern'),
|
'name': _('Sales Order Reference Pattern'),
|
||||||
'description': _('Required pattern for generating Sales Order reference field'),
|
'description': _('Required pattern for generating Sales Order reference field'),
|
||||||
|
@ -6,13 +6,9 @@ import import_export.widgets as widgets
|
|||||||
from import_export.admin import ImportExportModelAdmin
|
from import_export.admin import ImportExportModelAdmin
|
||||||
from import_export.fields import Field
|
from import_export.fields import Field
|
||||||
|
|
||||||
|
import order.models as models
|
||||||
from InvenTree.admin import InvenTreeResource
|
from InvenTree.admin import InvenTreeResource
|
||||||
|
|
||||||
from .models import (PurchaseOrder, PurchaseOrderExtraLine,
|
|
||||||
PurchaseOrderLineItem, SalesOrder, SalesOrderAllocation,
|
|
||||||
SalesOrderExtraLine, SalesOrderLineItem,
|
|
||||||
SalesOrderShipment)
|
|
||||||
|
|
||||||
|
|
||||||
# region general classes
|
# region general classes
|
||||||
class GeneralExtraLineAdmin:
|
class GeneralExtraLineAdmin:
|
||||||
@ -42,7 +38,7 @@ class GeneralExtraLineMeta:
|
|||||||
|
|
||||||
class PurchaseOrderLineItemInlineAdmin(admin.StackedInline):
|
class PurchaseOrderLineItemInlineAdmin(admin.StackedInline):
|
||||||
"""Inline admin class for the PurchaseOrderLineItem model"""
|
"""Inline admin class for the PurchaseOrderLineItem model"""
|
||||||
model = PurchaseOrderLineItem
|
model = models.PurchaseOrderLineItem
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
@ -103,7 +99,7 @@ class PurchaseOrderResource(InvenTreeResource):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass"""
|
"""Metaclass"""
|
||||||
model = PurchaseOrder
|
model = models.PurchaseOrder
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
clean_model_instances = True
|
clean_model_instances = True
|
||||||
exclude = [
|
exclude = [
|
||||||
@ -122,7 +118,7 @@ class PurchaseOrderLineItemResource(InvenTreeResource):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass"""
|
"""Metaclass"""
|
||||||
model = PurchaseOrderLineItem
|
model = models.PurchaseOrderLineItem
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
clean_model_instances = True
|
clean_model_instances = True
|
||||||
@ -142,7 +138,7 @@ class PurchaseOrderExtraLineResource(InvenTreeResource):
|
|||||||
class Meta(GeneralExtraLineMeta):
|
class Meta(GeneralExtraLineMeta):
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = PurchaseOrderExtraLine
|
model = models.PurchaseOrderExtraLine
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderResource(InvenTreeResource):
|
class SalesOrderResource(InvenTreeResource):
|
||||||
@ -150,7 +146,7 @@ class SalesOrderResource(InvenTreeResource):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass options"""
|
"""Metaclass options"""
|
||||||
model = SalesOrder
|
model = models.SalesOrder
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
clean_model_instances = True
|
clean_model_instances = True
|
||||||
exclude = [
|
exclude = [
|
||||||
@ -169,7 +165,7 @@ class SalesOrderLineItemResource(InvenTreeResource):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass options"""
|
"""Metaclass options"""
|
||||||
model = SalesOrderLineItem
|
model = models.SalesOrderLineItem
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
clean_model_instances = True
|
clean_model_instances = True
|
||||||
@ -199,7 +195,7 @@ class SalesOrderExtraLineResource(InvenTreeResource):
|
|||||||
class Meta(GeneralExtraLineMeta):
|
class Meta(GeneralExtraLineMeta):
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = SalesOrderExtraLine
|
model = models.SalesOrderExtraLine
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderLineItemAdmin(ImportExportModelAdmin):
|
class PurchaseOrderLineItemAdmin(ImportExportModelAdmin):
|
||||||
@ -281,13 +277,41 @@ class SalesOrderAllocationAdmin(ImportExportModelAdmin):
|
|||||||
autocomplete_fields = ('line', 'shipment', 'item',)
|
autocomplete_fields = ('line', 'shipment', 'item',)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(PurchaseOrder, PurchaseOrderAdmin)
|
class ReturnOrderAdmin(ImportExportModelAdmin):
|
||||||
admin.site.register(PurchaseOrderLineItem, PurchaseOrderLineItemAdmin)
|
"""Admin class for the ReturnOrder model"""
|
||||||
admin.site.register(PurchaseOrderExtraLine, PurchaseOrderExtraLineAdmin)
|
|
||||||
|
|
||||||
admin.site.register(SalesOrder, SalesOrderAdmin)
|
exclude = [
|
||||||
admin.site.register(SalesOrderLineItem, SalesOrderLineItemAdmin)
|
'reference_int',
|
||||||
admin.site.register(SalesOrderExtraLine, SalesOrderExtraLineAdmin)
|
]
|
||||||
|
|
||||||
admin.site.register(SalesOrderShipment, SalesOrderShipmentAdmin)
|
list_display = [
|
||||||
admin.site.register(SalesOrderAllocation, SalesOrderAllocationAdmin)
|
'reference',
|
||||||
|
'customer',
|
||||||
|
'status',
|
||||||
|
]
|
||||||
|
|
||||||
|
search_fields = [
|
||||||
|
'reference',
|
||||||
|
'customer__name',
|
||||||
|
'description',
|
||||||
|
]
|
||||||
|
|
||||||
|
autocomplete_fields = [
|
||||||
|
'customer',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Purchase Order models
|
||||||
|
admin.site.register(models.PurchaseOrder, PurchaseOrderAdmin)
|
||||||
|
admin.site.register(models.PurchaseOrderLineItem, PurchaseOrderLineItemAdmin)
|
||||||
|
admin.site.register(models.PurchaseOrderExtraLine, PurchaseOrderExtraLineAdmin)
|
||||||
|
|
||||||
|
# Sales Order models
|
||||||
|
admin.site.register(models.SalesOrder, SalesOrderAdmin)
|
||||||
|
admin.site.register(models.SalesOrderLineItem, SalesOrderLineItemAdmin)
|
||||||
|
admin.site.register(models.SalesOrderExtraLine, SalesOrderExtraLineAdmin)
|
||||||
|
admin.site.register(models.SalesOrderShipment, SalesOrderShipmentAdmin)
|
||||||
|
admin.site.register(models.SalesOrderAllocation, SalesOrderAllocationAdmin)
|
||||||
|
|
||||||
|
# Return Order models
|
||||||
|
admin.site.register(models.ReturnOrder, ReturnOrderAdmin)
|
||||||
|
@ -34,8 +34,9 @@ from InvenTree.fields import (InvenTreeModelMoneyField, InvenTreeNotesField,
|
|||||||
InvenTreeURLField, RoundingDecimalField)
|
InvenTreeURLField, RoundingDecimalField)
|
||||||
from InvenTree.helpers import decimal2string, getSetting, notify_responsible
|
from InvenTree.helpers import decimal2string, getSetting, notify_responsible
|
||||||
from InvenTree.models import InvenTreeAttachment, ReferenceIndexingMixin
|
from InvenTree.models import InvenTreeAttachment, ReferenceIndexingMixin
|
||||||
from InvenTree.status_codes import (PurchaseOrderStatus, SalesOrderStatus,
|
from InvenTree.status_codes import (PurchaseOrderStatus, ReturnOrderStatus,
|
||||||
StockHistoryCode, StockStatus)
|
SalesOrderStatus, StockHistoryCode,
|
||||||
|
StockStatus)
|
||||||
from part import models as PartModels
|
from part import models as PartModels
|
||||||
from plugin.events import trigger_event
|
from plugin.events import trigger_event
|
||||||
from plugin.models import MetadataMixin
|
from plugin.models import MetadataMixin
|
||||||
@ -199,7 +200,7 @@ class PurchaseOrder(Order):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def api_defaults(cls, request):
|
def api_defaults(cls, request):
|
||||||
"""Return default values for thsi model when issuing an API OPTIONS request"""
|
"""Return default values for this model when issuing an API OPTIONS request"""
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
'reference': order.validators.generate_next_purchase_order_reference(),
|
'reference': order.validators.generate_next_purchase_order_reference(),
|
||||||
@ -684,13 +685,16 @@ class SalesOrder(Order):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
limit_choices_to={'is_customer': True},
|
limit_choices_to={'is_customer': True},
|
||||||
related_name='sales_orders',
|
related_name='return_orders',
|
||||||
verbose_name=_('Customer'),
|
verbose_name=_('Customer'),
|
||||||
help_text=_("Company to which the items are being sold"),
|
help_text=_("Company to which the items are being sold"),
|
||||||
)
|
)
|
||||||
|
|
||||||
status = models.PositiveIntegerField(default=SalesOrderStatus.PENDING, choices=SalesOrderStatus.items(),
|
status = models.PositiveIntegerField(
|
||||||
verbose_name=_('Status'), help_text=_('Purchase order status'))
|
default=SalesOrderStatus.PENDING,
|
||||||
|
choices=SalesOrderStatus.items(),
|
||||||
|
verbose_name=_('Status'), help_text=_('Purchase order status')
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status_text(self):
|
def status_text(self):
|
||||||
@ -1547,3 +1551,100 @@ class SalesOrderAllocation(models.Model):
|
|||||||
# (It may have changed if the stock was split)
|
# (It may have changed if the stock was split)
|
||||||
self.item = item
|
self.item = item
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
class ReturnOrder(Order):
|
||||||
|
"""A ReturnOrder represents goods returned from a customer, e.g. an RMA or warranty
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
customer: Reference to the customer
|
||||||
|
sales_order: Reference to an existing SalesOrder (optional)
|
||||||
|
status: The status of the order (refer to status_codes.ReturnOrderStatus)
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_api_url():
|
||||||
|
"""Return the API URL associated with the ReturnOrder model"""
|
||||||
|
return reverse('api-return-list')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def api_defaults(cls, request):
|
||||||
|
"""Return default values for this model when issuing an API OPTIONS request"""
|
||||||
|
defaults = {
|
||||||
|
'reference': order.validators.generate_next_return_order_reference(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaults
|
||||||
|
|
||||||
|
REFERENCE_PATTERN_SETTING = 'RETURNORDER_REFERENCE_PATTERN'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Render a string representation of this ReturnOrder"""
|
||||||
|
|
||||||
|
return f"{self.reference} - {self.customer.name if self.customer else _('no customer')}"
|
||||||
|
|
||||||
|
reference = models.CharField(
|
||||||
|
unique=True,
|
||||||
|
max_length=64,
|
||||||
|
blank=False,
|
||||||
|
verbose_name=_('Reference'),
|
||||||
|
help_text=_('Return Order reference'),
|
||||||
|
default=order.validators.generate_next_return_order_reference,
|
||||||
|
validators=[
|
||||||
|
order.validators.validate_return_order_reference,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
customer = models.ForeignKey(
|
||||||
|
Company,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
limit_choices_to={'is_customer': True},
|
||||||
|
related_name='sales_orders',
|
||||||
|
verbose_name=_('Customer'),
|
||||||
|
help_text=_("Company from which items are being returned"),
|
||||||
|
)
|
||||||
|
|
||||||
|
status = models.PositiveIntegerField(
|
||||||
|
default=ReturnOrderStatus.PENDING,
|
||||||
|
choices=ReturnOrderStatus.items(),
|
||||||
|
verbose_name=_('Status'), help_text=_('Return order status')
|
||||||
|
)
|
||||||
|
|
||||||
|
customer_reference = models.CharField(
|
||||||
|
max_length=64, blank=True,
|
||||||
|
verbose_name=_('Customer Reference '),
|
||||||
|
help_text=_("Customer order reference code")
|
||||||
|
)
|
||||||
|
|
||||||
|
issue_date = models.DateField(
|
||||||
|
blank=True, null=True,
|
||||||
|
verbose_name=_('Issue Date'),
|
||||||
|
help_text=_('Date order was issued')
|
||||||
|
)
|
||||||
|
|
||||||
|
complete_date = models.DateField(
|
||||||
|
blank=True, null=True,
|
||||||
|
verbose_name=_('Completion Date'),
|
||||||
|
help_text=_('Date order was completed')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReturnOrderAttachment(InvenTreeAttachment):
|
||||||
|
"""Model for storing file attachments against a ReturnOrder object"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_api_url():
|
||||||
|
"""Return the API URL associated with the ReturnOrderAttachment class"""
|
||||||
|
|
||||||
|
return reverse('api-return-attachment-list')
|
||||||
|
|
||||||
|
def getSubdir(self):
|
||||||
|
"""Return the directory path where ReturnOrderAttachment files are located"""
|
||||||
|
return os.path.join('return_files', str(self.order.id))
|
||||||
|
|
||||||
|
order = models.ForeignKey(
|
||||||
|
ReturnOrder,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='attachments',
|
||||||
|
)
|
||||||
|
@ -17,6 +17,14 @@ def generate_next_purchase_order_reference():
|
|||||||
return PurchaseOrder.generate_reference()
|
return PurchaseOrder.generate_reference()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_next_return_order_reference():
|
||||||
|
"""Generate the next available ReturnOrder reference"""
|
||||||
|
|
||||||
|
from order.models import ReturnOrder
|
||||||
|
|
||||||
|
return ReturnOrder.generate_reference()
|
||||||
|
|
||||||
|
|
||||||
def validate_sales_order_reference_pattern(pattern):
|
def validate_sales_order_reference_pattern(pattern):
|
||||||
"""Validate the SalesOrder reference 'pattern' setting"""
|
"""Validate the SalesOrder reference 'pattern' setting"""
|
||||||
|
|
||||||
@ -33,6 +41,14 @@ def validate_purchase_order_reference_pattern(pattern):
|
|||||||
PurchaseOrder.validate_reference_pattern(pattern)
|
PurchaseOrder.validate_reference_pattern(pattern)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_return_order_reference_pattern(pattern):
|
||||||
|
"""Validate the ReturnOrder reference 'pattern' setting"""
|
||||||
|
|
||||||
|
from order.models import ReturnOrder
|
||||||
|
|
||||||
|
ReturnOrder.validate_reference_pattern(pattern)
|
||||||
|
|
||||||
|
|
||||||
def validate_sales_order_reference(value):
|
def validate_sales_order_reference(value):
|
||||||
"""Validate that the SalesOrder reference field matches the required pattern"""
|
"""Validate that the SalesOrder reference field matches the required pattern"""
|
||||||
|
|
||||||
@ -47,3 +63,11 @@ def validate_purchase_order_reference(value):
|
|||||||
from order.models import PurchaseOrder
|
from order.models import PurchaseOrder
|
||||||
|
|
||||||
PurchaseOrder.validate_reference_field(value)
|
PurchaseOrder.validate_reference_field(value)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_return_order_reference(value):
|
||||||
|
"""Validate that the ReturnOrder reference field matches the required pattern"""
|
||||||
|
|
||||||
|
from order.models import ReturnOrder
|
||||||
|
|
||||||
|
ReturnOrder.validate_reference_field(value)
|
||||||
|
18
InvenTree/templates/InvenTree/settings/returns.html
Normal file
18
InvenTree/templates/InvenTree/settings/returns.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{% extends "panel.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block label %}return-order{% endblock %}
|
||||||
|
|
||||||
|
{% block heading %}
|
||||||
|
{% trans "Return Order Settings" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<table class='table table-striped table-condensed'>
|
||||||
|
<tbody>
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="RETURNORDER_REFERENCE_PATTERN" %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -42,6 +42,7 @@
|
|||||||
{% include "InvenTree/settings/build.html" %}
|
{% include "InvenTree/settings/build.html" %}
|
||||||
{% include "InvenTree/settings/po.html" %}
|
{% include "InvenTree/settings/po.html" %}
|
||||||
{% include "InvenTree/settings/so.html" %}
|
{% include "InvenTree/settings/so.html" %}
|
||||||
|
{% include "InvenTree/settings/returns.html" %}
|
||||||
|
|
||||||
{% include "InvenTree/settings/plugin.html" %}
|
{% include "InvenTree/settings/plugin.html" %}
|
||||||
{% plugin_list as pl_list %}
|
{% plugin_list as pl_list %}
|
||||||
|
@ -52,6 +52,8 @@
|
|||||||
{% include "sidebar_item.html" with label='purchase-order' text=text icon="fa-shopping-cart" %}
|
{% include "sidebar_item.html" with label='purchase-order' text=text icon="fa-shopping-cart" %}
|
||||||
{% trans "Sales Orders" as text %}
|
{% trans "Sales Orders" as text %}
|
||||||
{% include "sidebar_item.html" with label='sales-order' text=text icon="fa-truck" %}
|
{% include "sidebar_item.html" with label='sales-order' text=text icon="fa-truck" %}
|
||||||
|
{% trans "Return Orders" as text %}
|
||||||
|
{% include "sidebar_item.html" with label='return-order' text=text icon="fa-undo" %}
|
||||||
|
|
||||||
{% trans "Plugin Settings" as text %}
|
{% trans "Plugin Settings" as text %}
|
||||||
{% include "sidebar_header.html" with text=text %}
|
{% include "sidebar_header.html" with text=text %}
|
||||||
|
Reference in New Issue
Block a user