mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-02 21:38:48 +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:
parent
81e87a65e2
commit
a48d23b161
@ -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):
|
||||
|
@ -4,6 +4,7 @@ import { IconAddressBook, IconUser, IconUsers } from '@tabler/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import RemoveRowButton from '../components/buttons/RemoveRowButton';
|
||||
import { StandaloneField } from '../components/forms/StandaloneField';
|
||||
import type {
|
||||
ApiFormAdjustFilterType,
|
||||
ApiFormFieldSet
|
||||
@ -11,8 +12,10 @@ import type {
|
||||
import type { TableFieldRowProps } from '../components/forms/fields/TableField';
|
||||
import { Thumbnail } from '../components/images/Thumbnail';
|
||||
import { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import { ModelType } from '../enums/ModelType';
|
||||
import { useCreateApiFormModal } from '../hooks/UseForm';
|
||||
import { apiUrl } from '../states/ApiState';
|
||||
import { StatusFilterOptions } from '../tables/Filter';
|
||||
|
||||
export function useReturnOrderFields({
|
||||
duplicateOrderId
|
||||
@ -133,6 +136,17 @@ function ReturnOrderLineItemFormRow({
|
||||
props: TableFieldRowProps;
|
||||
record: any;
|
||||
}>) {
|
||||
const statusOptions = useMemo(() => {
|
||||
return (
|
||||
StatusFilterOptions(ModelType.stockitem)()?.map((choice) => {
|
||||
return {
|
||||
value: choice.value,
|
||||
display_name: choice.label
|
||||
};
|
||||
}) ?? []
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table.Tr>
|
||||
@ -146,7 +160,21 @@ function ReturnOrderLineItemFormRow({
|
||||
<div>{record.part_detail.name}</div>
|
||||
</Flex>
|
||||
</Table.Td>
|
||||
<Table.Td>{record.item_detail.serial}</Table.Td>
|
||||
<Table.Td># {record.item_detail.serial}</Table.Td>
|
||||
<Table.Td>
|
||||
<StandaloneField
|
||||
fieldDefinition={{
|
||||
field_type: 'choice',
|
||||
label: t`Status`,
|
||||
choices: statusOptions,
|
||||
onValueChange: (value) => {
|
||||
props.changeFn(props.idx, 'status', value);
|
||||
}
|
||||
}}
|
||||
defaultValue={record.item_detail?.status}
|
||||
error={props.rowErrors?.status?.message}
|
||||
/>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<RemoveRowButton onClick={() => props.removeFn(props.idx)} />
|
||||
</Table.Td>
|
||||
@ -181,7 +209,7 @@ export function useReceiveReturnOrderLineItems(
|
||||
/>
|
||||
);
|
||||
},
|
||||
headers: [t`Part`, t`Serial Number`]
|
||||
headers: [t`Part`, t`Stock Item`, t`Status`]
|
||||
},
|
||||
location: {
|
||||
filters: {
|
||||
|
@ -639,10 +639,12 @@ export default function StockDetail() {
|
||||
),
|
||||
fields: {
|
||||
location: {},
|
||||
status: {},
|
||||
notes: {}
|
||||
},
|
||||
initialData: {
|
||||
location: stockitem.location ?? stockitem.part_detail?.default_location
|
||||
location: stockitem.location ?? stockitem.part_detail?.default_location,
|
||||
status: stockitem.status_custom_key ?? stockitem.status
|
||||
},
|
||||
successMessage: t`Item returned to stock`,
|
||||
onFormSuccess: () => {
|
||||
|
@ -152,6 +152,9 @@ export function ReturnOrderTable({
|
||||
url: ApiEndpoints.return_order_list,
|
||||
title: t`Add Return Order`,
|
||||
fields: returnOrderFields,
|
||||
initialData: {
|
||||
customer: customerId
|
||||
},
|
||||
follow: true,
|
||||
modelType: ModelType.returnorder
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user