2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-22 04:36:30 +00:00

Return Order - Improvements ()

* Increase query limit

* Add "quantity" field to ReturnOrderLineItem model

* Add 'quantity' to serializer

* Optionally split stock when returning from customer

* Update the line item when splitting

* PUI updates

* Bump API version

* Add unit test
This commit is contained in:
Oliver
2024-11-29 17:06:35 +11:00
committed by GitHub
parent dd9a6a8a2d
commit 20d862e350
7 changed files with 156 additions and 13 deletions

@@ -1,13 +1,16 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 289
INVENTREE_API_VERSION = 290
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v290 - 2024-11-29 : https://github.com/inventree/InvenTree/pull/8590
- Adds "quantity" field to ReturnOrderLineItem model and API
v289 - 2024-11-27 : https://github.com/inventree/InvenTree/pull/8570
- Enable status change when transferring stock items

@@ -0,0 +1,19 @@
# Generated by Django 4.2.16 on 2024-11-29 00:37
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('order', '0103_alter_salesorderallocation_shipment'),
]
operations = [
migrations.AlterField(
model_name='returnorderlineitem',
name='quantity',
field=models.DecimalField(decimal_places=5, default=1, help_text='Quantity to return', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'),
),
]

@@ -2391,6 +2391,14 @@ class ReturnOrder(TotalPriceMixin, Order):
stock_item = line.item
if not stock_item.serialized and line.quantity < stock_item.quantity:
# Split the stock item if we are returning less than the full quantity
stock_item = stock_item.splitStock(line.quantity, user=user)
# Update the line item to point to the *new* stock item
line.item = stock_item
line.save()
status = kwargs.get('status')
if status is None:
@@ -2423,7 +2431,7 @@ class ReturnOrder(TotalPriceMixin, Order):
line.received_date = InvenTree.helpers.current_date()
line.save()
trigger_event(ReturnOrderEvents.RECEIVED, id=self.pk)
trigger_event(ReturnOrderEvents.RECEIVED, id=self.pk, line_item_id=line.pk)
# Notify responsible users
notify_responsible(
@@ -2452,9 +2460,22 @@ class ReturnOrderLineItem(OrderLineItem):
"""Perform extra validation steps for the ReturnOrderLineItem model."""
super().clean()
if self.item and not self.item.serialized:
if not self.item:
raise ValidationError({'item': _('Stock item must be specified')})
if self.quantity > self.item.quantity:
raise ValidationError({
'item': _('Only serialized items can be assigned to a Return Order')
'quantity': _('Return quantity exceeds stock quantity')
})
if self.quantity <= 0:
raise ValidationError({
'quantity': _('Return quantity must be greater than zero')
})
if self.item.serialized and self.quantity != 1:
raise ValidationError({
'quantity': _('Invalid quantity for serialized stock item')
})
order = models.ForeignKey(
@@ -2473,6 +2494,15 @@ class ReturnOrderLineItem(OrderLineItem):
help_text=_('Select item to return from customer'),
)
quantity = models.DecimalField(
verbose_name=('Quantity'),
help_text=('Quantity to return'),
max_digits=15,
decimal_places=5,
validators=[MinValueValidator(0)],
default=1,
)
received_date = models.DateField(
null=True,
blank=True,

@@ -2040,6 +2040,7 @@ class ReturnOrderLineItemSerializer(
'order_detail',
'item',
'item_detail',
'quantity',
'received_date',
'outcome',
'part_detail',
@@ -2070,9 +2071,15 @@ class ReturnOrderLineItemSerializer(
self.fields.pop('part_detail', None)
order_detail = ReturnOrderSerializer(source='order', many=False, read_only=True)
quantity = serializers.FloatField(
label=_('Quantity'), help_text=_('Quantity to return')
)
item_detail = stock.serializers.StockItemSerializer(
source='item', many=False, read_only=True
)
part_detail = PartBriefSerializer(source='item.part', many=False, read_only=True)
price = InvenTreeMoneySerializer(allow_null=True)

@@ -2395,6 +2395,71 @@ class ReturnOrderTests(InvenTreeAPITestCase):
self.assertEqual(deltas['location'], 1)
self.assertEqual(deltas['returnorder'], rma.pk)
def test_receive_untracked(self):
"""Test that we can receive untracked items against a ReturnOrder.
Ref: https://github.com/inventree/InvenTree/pull/8590
"""
self.assignRole('return_order.add')
company = Company.objects.get(pk=4)
# Create a new ReturnOrder
rma = models.ReturnOrder.objects.create(
customer=company, description='A return order'
)
rma.issue_order()
# Create some new line items
part = Part.objects.get(pk=25)
n_items = part.stock_entries().count()
for idx in range(2):
stock_item = StockItem.objects.create(
part=part, customer=company, quantity=10
)
models.ReturnOrderLineItem.objects.create(
order=rma, item=stock_item, quantity=(idx + 1) * 5
)
self.assertEqual(part.stock_entries().count(), n_items + 2)
line_items = rma.lines.all()
# Receive items against the order
url = reverse('api-return-order-receive', kwargs={'pk': rma.pk})
LOCATION_ID = 1
self.post(
url,
{
'items': [
{'item': line.pk, 'status': StockStatus.DAMAGED.value}
for line in line_items
],
'location': LOCATION_ID,
},
expected_code=201,
)
# Due to the quantities received, we should have created 1 new stock item
self.assertEqual(part.stock_entries().count(), n_items + 3)
rma.refresh_from_db()
for line in rma.lines.all():
self.assertTrue(line.received)
self.assertIsNotNone(line.received_date)
# Check that the associated StockItem has been updated correctly
self.assertEqual(line.item.status, StockStatus.DAMAGED)
self.assertIsNone(line.item.customer)
self.assertIsNone(line.item.sales_order)
self.assertEqual(line.item.location.pk, LOCATION_ID)
def test_ro_calendar(self):
"""Test the calendar export endpoint."""
# Full test is in test_po_calendar. Since these use the same backend, test only