mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 05:05:42 +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