2
0
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:
Oliver
2023-03-14 15:56:18 +11:00
parent 1ba51e51c3
commit 139170c26b
8 changed files with 223 additions and 26 deletions

View File

@ -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',
}

View File

@ -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'),

View File

@ -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)

View File

@ -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',
)

View File

@ -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)

View 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 %}

View File

@ -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 %}

View File

@ -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 %}