mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Set status when returning from customer (#8571)
* Set status when returning from customer * Fix default customer for return order table * Set status when receiving items against a ReturnOrder * Bump max query time for currency endpoint * Bump API version
This commit is contained in:
		| @@ -1,13 +1,16 @@ | ||||
| """InvenTree API version information.""" | ||||
|  | ||||
| # InvenTree API version | ||||
| INVENTREE_API_VERSION = 286 | ||||
| INVENTREE_API_VERSION = 287 | ||||
|  | ||||
| """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" | ||||
|  | ||||
|  | ||||
| INVENTREE_API_TEXT = """ | ||||
|  | ||||
| v287 - 2024-11-27 : https://github.com/inventree/InvenTree/pull/8571 | ||||
|     - Adds ability to set stock status when returning items from a customer | ||||
|  | ||||
| v286 - 2024-11-26 : https://github.com/inventree/InvenTree/pull/8054 | ||||
|     - Adds "SelectionList" and "SelectionListEntry" API endpoints | ||||
|  | ||||
|   | ||||
| @@ -1254,7 +1254,9 @@ class CurrencyAPITests(InvenTreeAPITestCase): | ||||
|  | ||||
|         # Updating via the external exchange may not work every time | ||||
|         for _idx in range(5): | ||||
|             self.post(reverse('api-currency-refresh'), expected_code=200) | ||||
|             self.post( | ||||
|                 reverse('api-currency-refresh'), expected_code=200, max_query_time=30 | ||||
|             ) | ||||
|  | ||||
|             # There should be some new exchange rate objects now | ||||
|             if Rate.objects.all().exists(): | ||||
|   | ||||
| @@ -2363,14 +2363,23 @@ class ReturnOrder(TotalPriceMixin, Order): | ||||
|     # endregion | ||||
|  | ||||
|     @transaction.atomic | ||||
|     def receive_line_item(self, line, location, user, note='', **kwargs): | ||||
|     def receive_line_item(self, line, location, user, **kwargs): | ||||
|         """Receive a line item against this ReturnOrder. | ||||
|  | ||||
|         Rules: | ||||
|         - Transfers the StockItem to the specified location | ||||
|         - Marks the StockItem as "quarantined" | ||||
|         - Adds a tracking entry to the StockItem | ||||
|         - Removes the 'customer' reference from the StockItem | ||||
|         Arguments: | ||||
|             line: ReturnOrderLineItem to receive | ||||
|             location: StockLocation to receive the item to | ||||
|             user: User performing the action | ||||
|  | ||||
|         Keyword Arguments: | ||||
|             note: Additional notes to add to the tracking entry | ||||
|             status: Status to set the StockItem to (default: StockStatus.QUARANTINED) | ||||
|  | ||||
|         Performs the following actions: | ||||
|             - Transfers the StockItem to the specified location | ||||
|             - Marks the StockItem as "quarantined" | ||||
|             - Adds a tracking entry to the StockItem | ||||
|             - Removes the 'customer' reference from the StockItem | ||||
|         """ | ||||
|         # Prevent an item from being "received" multiple times | ||||
|         if line.received_date is not None: | ||||
| @@ -2379,17 +2388,18 @@ class ReturnOrder(TotalPriceMixin, Order): | ||||
|  | ||||
|         stock_item = line.item | ||||
|  | ||||
|         deltas = { | ||||
|             'status': StockStatus.QUARANTINED.value, | ||||
|             'returnorder': self.pk, | ||||
|             'location': location.pk, | ||||
|         } | ||||
|         status = kwargs.get('status') | ||||
|  | ||||
|         if status is None: | ||||
|             status = StockStatus.QUARANTINED.value | ||||
|  | ||||
|         deltas = {'status': status, 'returnorder': self.pk, 'location': location.pk} | ||||
|  | ||||
|         if stock_item.customer: | ||||
|             deltas['customer'] = stock_item.customer.pk | ||||
|  | ||||
|         # Update the StockItem | ||||
|         stock_item.status = kwargs.get('status', StockStatus.QUARANTINED.value) | ||||
|         stock_item.status = status | ||||
|         stock_item.location = location | ||||
|         stock_item.customer = None | ||||
|         stock_item.sales_order = None | ||||
| @@ -2400,7 +2410,7 @@ class ReturnOrder(TotalPriceMixin, Order): | ||||
|         stock_item.add_tracking_entry( | ||||
|             StockHistoryCode.RETURNED_AGAINST_RETURN_ORDER, | ||||
|             user, | ||||
|             notes=note, | ||||
|             notes=kwargs.get('note', ''), | ||||
|             deltas=deltas, | ||||
|             location=location, | ||||
|             returnorder=self, | ||||
|   | ||||
| @@ -26,6 +26,7 @@ import part.filters as part_filters | ||||
| import part.models as part_models | ||||
| import stock.models | ||||
| import stock.serializers | ||||
| import stock.status_codes | ||||
| from common.serializers import ProjectCodeSerializer | ||||
| from company.serializers import ( | ||||
|     AddressBriefSerializer, | ||||
| @@ -1923,7 +1924,7 @@ class ReturnOrderLineItemReceiveSerializer(serializers.Serializer): | ||||
|     class Meta: | ||||
|         """Metaclass options.""" | ||||
|  | ||||
|         fields = ['item'] | ||||
|         fields = ['item', 'status'] | ||||
|  | ||||
|     item = serializers.PrimaryKeyRelatedField( | ||||
|         queryset=order.models.ReturnOrderLineItem.objects.all(), | ||||
| @@ -1933,6 +1934,15 @@ class ReturnOrderLineItemReceiveSerializer(serializers.Serializer): | ||||
|         label=_('Return order line item'), | ||||
|     ) | ||||
|  | ||||
|     status = serializers.ChoiceField( | ||||
|         choices=stock.status_codes.StockStatus.items(), | ||||
|         default=None, | ||||
|         label=_('Status'), | ||||
|         help_text=_('Stock item status code'), | ||||
|         required=False, | ||||
|         allow_blank=True, | ||||
|     ) | ||||
|  | ||||
|     def validate_line_item(self, item): | ||||
|         """Validation for a single line item.""" | ||||
|         if item.order != self.context['order']: | ||||
| @@ -1950,7 +1960,7 @@ class ReturnOrderReceiveSerializer(serializers.Serializer): | ||||
|     class Meta: | ||||
|         """Metaclass options.""" | ||||
|  | ||||
|         fields = ['items', 'location'] | ||||
|         fields = ['items', 'location', 'note'] | ||||
|  | ||||
|     items = ReturnOrderLineItemReceiveSerializer(many=True) | ||||
|  | ||||
| @@ -1963,6 +1973,14 @@ class ReturnOrderReceiveSerializer(serializers.Serializer): | ||||
|         help_text=_('Select destination location for received items'), | ||||
|     ) | ||||
|  | ||||
|     note = serializers.CharField( | ||||
|         label=_('Note'), | ||||
|         help_text=_('Additional note for incoming stock items'), | ||||
|         required=False, | ||||
|         default='', | ||||
|         allow_blank=True, | ||||
|     ) | ||||
|  | ||||
|     def validate(self, data): | ||||
|         """Perform data validation for this serializer.""" | ||||
|         order = self.context['order'] | ||||
| @@ -1993,7 +2011,14 @@ class ReturnOrderReceiveSerializer(serializers.Serializer): | ||||
|         with transaction.atomic(): | ||||
|             for item in items: | ||||
|                 line_item = item['item'] | ||||
|                 order.receive_line_item(line_item, location, request.user) | ||||
|  | ||||
|                 order.receive_line_item( | ||||
|                     line_item, | ||||
|                     location, | ||||
|                     request.user, | ||||
|                     note=data.get('note', ''), | ||||
|                     status=item.get('status', None), | ||||
|                 ) | ||||
|  | ||||
|  | ||||
| @register_importer() | ||||
|   | ||||
| @@ -1217,8 +1217,16 @@ class StockItem( | ||||
|     def return_from_customer(self, location, user=None, **kwargs): | ||||
|         """Return stock item from customer, back into the specified location. | ||||
|  | ||||
|         Arguments: | ||||
|             location: The location to return the stock item to | ||||
|             user: The user performing the action | ||||
|  | ||||
|         Keyword Arguments: | ||||
|             notes: Additional notes to add to the tracking entry | ||||
|             status: Optionally set the status of the stock item | ||||
|  | ||||
|         If the selected location is the same as the parent, merge stock back into the parent. | ||||
|         Otherwise create the stock in the new location | ||||
|         Otherwise create the stock in the new location. | ||||
|         """ | ||||
|         notes = kwargs.get('notes', '') | ||||
|  | ||||
| @@ -1228,6 +1236,17 @@ class StockItem( | ||||
|             tracking_info['customer'] = self.customer.id | ||||
|             tracking_info['customer_name'] = self.customer.name | ||||
|  | ||||
|         # Clear out allocation information for the stock item | ||||
|         self.customer = None | ||||
|         self.belongs_to = None | ||||
|         self.sales_order = None | ||||
|         self.location = location | ||||
|         self.clearAllocations() | ||||
|  | ||||
|         if status := kwargs.get('status'): | ||||
|             self.status = status | ||||
|             tracking_info['status'] = status | ||||
|  | ||||
|         self.add_tracking_entry( | ||||
|             StockHistoryCode.RETURNED_FROM_CUSTOMER, | ||||
|             user, | ||||
| @@ -1236,13 +1255,6 @@ class StockItem( | ||||
|             location=location, | ||||
|         ) | ||||
|  | ||||
|         # Clear out allocation information for the stock item | ||||
|         self.customer = None | ||||
|         self.belongs_to = None | ||||
|         self.sales_order = None | ||||
|         self.location = location | ||||
|         self.clearAllocations() | ||||
|  | ||||
|         trigger_event('stockitem.returnedfromcustomer', id=self.id) | ||||
|  | ||||
|         """If new location is the same as the parent location, merge this stock back in the parent""" | ||||
|   | ||||
| @@ -968,7 +968,7 @@ class ReturnStockItemSerializer(serializers.Serializer): | ||||
|     class Meta: | ||||
|         """Metaclass options.""" | ||||
|  | ||||
|         fields = ['location', 'note'] | ||||
|         fields = ['location', 'status', 'notes'] | ||||
|  | ||||
|     location = serializers.PrimaryKeyRelatedField( | ||||
|         queryset=StockLocation.objects.all(), | ||||
| @@ -979,6 +979,15 @@ class ReturnStockItemSerializer(serializers.Serializer): | ||||
|         help_text=_('Destination location for returned item'), | ||||
|     ) | ||||
|  | ||||
|     status = serializers.ChoiceField( | ||||
|         choices=stock.status_codes.StockStatus.items(), | ||||
|         default=None, | ||||
|         label=_('Status'), | ||||
|         help_text=_('Stock item status code'), | ||||
|         required=False, | ||||
|         allow_blank=True, | ||||
|     ) | ||||
|  | ||||
|     notes = serializers.CharField( | ||||
|         label=_('Notes'), | ||||
|         help_text=_('Add transaction note (optional)'), | ||||
| @@ -994,9 +1003,13 @@ class ReturnStockItemSerializer(serializers.Serializer): | ||||
|         data = self.validated_data | ||||
|  | ||||
|         location = data['location'] | ||||
|         notes = data.get('notes', '') | ||||
|  | ||||
|         item.return_from_customer(location, user=request.user, notes=notes) | ||||
|         item.return_from_customer( | ||||
|             location, | ||||
|             user=request.user, | ||||
|             notes=data.get('notes', ''), | ||||
|             status=data.get('status', None), | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class StockChangeStatusSerializer(serializers.Serializer): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user