diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py
index e4602695e0..bf324d275c 100644
--- a/src/backend/InvenTree/InvenTree/api_version.py
+++ b/src/backend/InvenTree/InvenTree/api_version.py
@@ -1,11 +1,14 @@
"""InvenTree API version information."""
# InvenTree API version
-INVENTREE_API_VERSION = 530
+INVENTREE_API_VERSION = 531
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
+v531 -> 2026-08-13 : https://github.com/inventree/InvenTree/pull/12635
+ - Adds "bulk update" support for the ReturnOrderLineItem API endpoint
+
v530 -> 2026-07-28 : https://github.com/inventree/InvenTree/pull/12487
- Adds searching against ReportSnippet API endpoint
diff --git a/src/backend/InvenTree/order/api.py b/src/backend/InvenTree/order/api.py
index d6b66ae4c4..9ca3108b85 100644
--- a/src/backend/InvenTree/order/api.py
+++ b/src/backend/InvenTree/order/api.py
@@ -1793,6 +1793,7 @@ class ReturnOrderLineItemOutputOptions(OutputConfiguration):
class ReturnOrderLineItemList(
ReturnOrderLineItemMixin,
+ BulkUpdateMixin,
DataExportViewMixin,
OutputOptionsMixin,
ListCreateDestroyAPIView,
diff --git a/src/backend/InvenTree/order/test_api.py b/src/backend/InvenTree/order/test_api.py
index 83a90d50a3..893976cdb2 100644
--- a/src/backend/InvenTree/order/test_api.py
+++ b/src/backend/InvenTree/order/test_api.py
@@ -3716,6 +3716,42 @@ class ReturnOrderLineItemTests(InvenTreeAPITestCase):
self.assertEqual(models.ReturnOrderLineItem.objects.count(), n - 1)
+ def test_bulk_update(self):
+ """Test that we can bulk update the 'outcome' field for multiple ReturnOrderLineItems via the API."""
+ ro = models.ReturnOrder.objects.get(pk=6)
+
+ # Create some extra line items against the same order, so we have multiple to update
+ models.ReturnOrderLineItem.objects.bulk_create([
+ models.ReturnOrderLineItem(order=ro, item_id=1006, quantity=1),
+ models.ReturnOrderLineItem(order=ro, item_id=1007, quantity=1),
+ ])
+
+ items = list(
+ models.ReturnOrderLineItem.objects.filter(order=ro).values_list(
+ 'pk', flat=True
+ )
+ )
+
+ self.assertEqual(len(items), 3)
+
+ for line in models.ReturnOrderLineItem.objects.filter(pk__in=items):
+ self.assertEqual(line.outcome, ReturnOrderLineStatus.PENDING)
+
+ url = reverse('api-return-order-line-list')
+
+ data = {'items': items, 'outcome': ReturnOrderLineStatus.REPAIR.value}
+
+ # Update should fail without the correct role
+ self.patch(url, data, expected_code=403)
+
+ self.assignRole('return_order.change')
+
+ response = self.patch(url, data, expected_code=200).data
+ self.assertEqual(len(response['items']), 3)
+
+ for line in models.ReturnOrderLineItem.objects.filter(pk__in=items):
+ self.assertEqual(line.outcome, ReturnOrderLineStatus.REPAIR.value)
+
def test_extra_line_bulk_delete(self):
"""Test that we can bulk delete multiple ReturnOrderExtraLine items via the API."""
ro = models.ReturnOrder.objects.first()
diff --git a/src/frontend/src/tables/sales/ReturnOrderLineItemTable.tsx b/src/frontend/src/tables/sales/ReturnOrderLineItemTable.tsx
index 2f533067f3..918fe31955 100644
--- a/src/frontend/src/tables/sales/ReturnOrderLineItemTable.tsx
+++ b/src/frontend/src/tables/sales/ReturnOrderLineItemTable.tsx
@@ -1,5 +1,5 @@
import { t } from '@lingui/core/macro';
-import { IconSquareArrowRight } from '@tabler/icons-react';
+import { IconFlag, IconSquareArrowRight } from '@tabler/icons-react';
import { useCallback, useMemo, useState } from 'react';
import { ActionButton } from '@lib/components/ActionButton';
@@ -16,6 +16,7 @@ import { apiUrl } from '@lib/functions/Api';
import useTable from '@lib/hooks/UseTable';
import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables';
+import { Alert } from '@mantine/core';
import {
DateColumn,
DescriptionColumn,
@@ -37,6 +38,7 @@ import {
useReturnOrderLineItemFields
} from '../../forms/ReturnOrderForms';
import {
+ useBulkEditApiFormModal,
useCreateApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal
@@ -110,6 +112,21 @@ export default function ReturnOrderLineItemTable({
table: table
});
+ const setOutcome = useBulkEditApiFormModal({
+ url: ApiEndpoints.return_order_line_list,
+ items: table.selectedIds,
+ title: t`Set Outcome`,
+ preFormContent: (
+
+ {t`Adjust the outcome for the selected line items.`}
+
+ ),
+ fields: {
+ outcome: {}
+ },
+ onFormSuccess: table.refreshTable
+ });
+
const tableColumns: TableColumn[] = useMemo(() => {
return [
LineItemColumn({}),
@@ -211,6 +228,16 @@ export default function ReturnOrderLineItemTable({
receiveLineItems.open();
}}
disabled={table.selectedRecords.length == 0}
+ />,
+ }
+ hidden={!editable || !user.hasChangeRole(UserRoles.return_order)}
+ onClick={() => {
+ setOutcome.open();
+ }}
+ disabled={table.selectedRecords.length == 0}
/>
];
}, [user, editable, inProgress, orderId, table.selectedRecords]);
@@ -266,6 +293,7 @@ export default function ReturnOrderLineItemTable({
{editLine.modal}
{deleteLine.modal}
{receiveLineItems.modal}
+ {setOutcome.modal}