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,
|
||||
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,
|
||||
},
|
||||
|
||||
'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': {
|
||||
'name': _('Sales Order Reference Pattern'),
|
||||
'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.fields import Field
|
||||
|
||||
import order.models as models
|
||||
from InvenTree.admin import InvenTreeResource
|
||||
|
||||
from .models import (PurchaseOrder, PurchaseOrderExtraLine,
|
||||
PurchaseOrderLineItem, SalesOrder, SalesOrderAllocation,
|
||||
SalesOrderExtraLine, SalesOrderLineItem,
|
||||
SalesOrderShipment)
|
||||
|
||||
|
||||
# region general classes
|
||||
class GeneralExtraLineAdmin:
|
||||
@ -42,7 +38,7 @@ class GeneralExtraLineMeta:
|
||||
|
||||
class PurchaseOrderLineItemInlineAdmin(admin.StackedInline):
|
||||
"""Inline admin class for the PurchaseOrderLineItem model"""
|
||||
model = PurchaseOrderLineItem
|
||||
model = models.PurchaseOrderLineItem
|
||||
extra = 0
|
||||
|
||||
|
||||
@ -103,7 +99,7 @@ class PurchaseOrderResource(InvenTreeResource):
|
||||
|
||||
class Meta:
|
||||
"""Metaclass"""
|
||||
model = PurchaseOrder
|
||||
model = models.PurchaseOrder
|
||||
skip_unchanged = True
|
||||
clean_model_instances = True
|
||||
exclude = [
|
||||
@ -122,7 +118,7 @@ class PurchaseOrderLineItemResource(InvenTreeResource):
|
||||
|
||||
class Meta:
|
||||
"""Metaclass"""
|
||||
model = PurchaseOrderLineItem
|
||||
model = models.PurchaseOrderLineItem
|
||||
skip_unchanged = True
|
||||
report_skipped = False
|
||||
clean_model_instances = True
|
||||
@ -142,7 +138,7 @@ class PurchaseOrderExtraLineResource(InvenTreeResource):
|
||||
class Meta(GeneralExtraLineMeta):
|
||||
"""Metaclass options."""
|
||||
|
||||
model = PurchaseOrderExtraLine
|
||||
model = models.PurchaseOrderExtraLine
|
||||
|
||||
|
||||
class SalesOrderResource(InvenTreeResource):
|
||||
@ -150,7 +146,7 @@ class SalesOrderResource(InvenTreeResource):
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options"""
|
||||
model = SalesOrder
|
||||
model = models.SalesOrder
|
||||
skip_unchanged = True
|
||||
clean_model_instances = True
|
||||
exclude = [
|
||||
@ -169,7 +165,7 @@ class SalesOrderLineItemResource(InvenTreeResource):
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options"""
|
||||
model = SalesOrderLineItem
|
||||
model = models.SalesOrderLineItem
|
||||
skip_unchanged = True
|
||||
report_skipped = False
|
||||
clean_model_instances = True
|
||||
@ -199,7 +195,7 @@ class SalesOrderExtraLineResource(InvenTreeResource):
|
||||
class Meta(GeneralExtraLineMeta):
|
||||
"""Metaclass options."""
|
||||
|
||||
model = SalesOrderExtraLine
|
||||
model = models.SalesOrderExtraLine
|
||||
|
||||
|
||||
class PurchaseOrderLineItemAdmin(ImportExportModelAdmin):
|
||||
@ -281,13 +277,41 @@ class SalesOrderAllocationAdmin(ImportExportModelAdmin):
|
||||
autocomplete_fields = ('line', 'shipment', 'item',)
|
||||
|
||||
|
||||
admin.site.register(PurchaseOrder, PurchaseOrderAdmin)
|
||||
admin.site.register(PurchaseOrderLineItem, PurchaseOrderLineItemAdmin)
|
||||
admin.site.register(PurchaseOrderExtraLine, PurchaseOrderExtraLineAdmin)
|
||||
class ReturnOrderAdmin(ImportExportModelAdmin):
|
||||
"""Admin class for the ReturnOrder model"""
|
||||
|
||||
admin.site.register(SalesOrder, SalesOrderAdmin)
|
||||
admin.site.register(SalesOrderLineItem, SalesOrderLineItemAdmin)
|
||||
admin.site.register(SalesOrderExtraLine, SalesOrderExtraLineAdmin)
|
||||
exclude = [
|
||||
'reference_int',
|
||||
]
|
||||
|
||||
admin.site.register(SalesOrderShipment, SalesOrderShipmentAdmin)
|
||||
admin.site.register(SalesOrderAllocation, SalesOrderAllocationAdmin)
|
||||
list_display = [
|
||||
'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)
|
||||
from InvenTree.helpers import decimal2string, getSetting, notify_responsible
|
||||
from InvenTree.models import InvenTreeAttachment, ReferenceIndexingMixin
|
||||
from InvenTree.status_codes import (PurchaseOrderStatus, SalesOrderStatus,
|
||||
StockHistoryCode, StockStatus)
|
||||
from InvenTree.status_codes import (PurchaseOrderStatus, ReturnOrderStatus,
|
||||
SalesOrderStatus, StockHistoryCode,
|
||||
StockStatus)
|
||||
from part import models as PartModels
|
||||
from plugin.events import trigger_event
|
||||
from plugin.models import MetadataMixin
|
||||
@ -199,7 +200,7 @@ class PurchaseOrder(Order):
|
||||
|
||||
@classmethod
|
||||
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 = {
|
||||
'reference': order.validators.generate_next_purchase_order_reference(),
|
||||
@ -684,13 +685,16 @@ class SalesOrder(Order):
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
limit_choices_to={'is_customer': True},
|
||||
related_name='sales_orders',
|
||||
related_name='return_orders',
|
||||
verbose_name=_('Customer'),
|
||||
help_text=_("Company to which the items are being sold"),
|
||||
)
|
||||
|
||||
status = models.PositiveIntegerField(default=SalesOrderStatus.PENDING, choices=SalesOrderStatus.items(),
|
||||
verbose_name=_('Status'), help_text=_('Purchase order status'))
|
||||
status = models.PositiveIntegerField(
|
||||
default=SalesOrderStatus.PENDING,
|
||||
choices=SalesOrderStatus.items(),
|
||||
verbose_name=_('Status'), help_text=_('Purchase order status')
|
||||
)
|
||||
|
||||
@property
|
||||
def status_text(self):
|
||||
@ -1547,3 +1551,100 @@ class SalesOrderAllocation(models.Model):
|
||||
# (It may have changed if the stock was split)
|
||||
self.item = item
|
||||
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()
|
||||
|
||||
|
||||
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):
|
||||
"""Validate the SalesOrder reference 'pattern' setting"""
|
||||
|
||||
@ -33,6 +41,14 @@ def validate_purchase_order_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):
|
||||
"""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
|
||||
|
||||
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/po.html" %}
|
||||
{% include "InvenTree/settings/so.html" %}
|
||||
{% include "InvenTree/settings/returns.html" %}
|
||||
|
||||
{% include "InvenTree/settings/plugin.html" %}
|
||||
{% plugin_list as pl_list %}
|
||||
|
@ -52,6 +52,8 @@
|
||||
{% include "sidebar_item.html" with label='purchase-order' text=text icon="fa-shopping-cart" %}
|
||||
{% trans "Sales Orders" as text %}
|
||||
{% 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 %}
|
||||
{% include "sidebar_header.html" with text=text %}
|
||||
|
Reference in New Issue
Block a user