diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py
index 946c2c7c8e..eaecb8b673 100644
--- a/src/backend/InvenTree/InvenTree/api_version.py
+++ b/src/backend/InvenTree/InvenTree/api_version.py
@@ -1,13 +1,16 @@
 """InvenTree API version information."""
 
 # InvenTree API version
-INVENTREE_API_VERSION = 270
+INVENTREE_API_VERSION = 271
 
 """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
 
 
 INVENTREE_API_TEXT = """
 
+v271 - 2024-10-22 : https://github.com/inventree/InvenTree/pull/8331
+    - Fixes for SalesOrderLineItem endpoints
+
 v270 - 2024-10-19 : https://github.com/inventree/InvenTree/pull/8307
     - Adds missing date fields from order API endpoint(s)
 
diff --git a/src/backend/InvenTree/order/api.py b/src/backend/InvenTree/order/api.py
index c32282c37b..5e3c05d09b 100644
--- a/src/backend/InvenTree/order/api.py
+++ b/src/backend/InvenTree/order/api.py
@@ -771,12 +771,29 @@ class SalesOrderLineItemFilter(LineItemFilter):
         queryset=Part.objects.all(), field_name='part', label=_('Part')
     )
 
-    completed = rest_filters.BooleanFilter(label='completed', method='filter_completed')
+    allocated = rest_filters.BooleanFilter(
+        label=_('Allocated'), method='filter_allocated'
+    )
+
+    def filter_allocated(self, queryset, name, value):
+        """Filter by lines which are 'allocated'.
+
+        A line is 'allocated' when allocated >= quantity
+        """
+        q = Q(allocated__gte=F('quantity'))
+
+        if str2bool(value):
+            return queryset.filter(q)
+        return queryset.exclude(q)
+
+    completed = rest_filters.BooleanFilter(
+        label=_('Completed'), method='filter_completed'
+    )
 
     def filter_completed(self, queryset, name, value):
         """Filter by lines which are "completed".
 
-        A line is completed when shipped >= quantity
+        A line is 'completed' when shipped >= quantity
         """
         q = Q(shipped__gte=F('quantity'))
 
@@ -855,6 +872,8 @@ class SalesOrderLineItemList(
         'part',
         'part__name',
         'quantity',
+        'allocated',
+        'shipped',
         'reference',
         'sale_price',
         'target_date',
diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py
index ac36216aba..ed979ac323 100644
--- a/src/backend/InvenTree/order/serializers.py
+++ b/src/backend/InvenTree/order/serializers.py
@@ -14,11 +14,12 @@ from django.db.models import (
     Value,
     When,
 )
+from django.db.models.functions import Coalesce
 from django.utils.translation import gettext_lazy as _
 
 from rest_framework import serializers
 from rest_framework.serializers import ValidationError
-from sql_util.utils import SubqueryCount
+from sql_util.utils import SubqueryCount, SubquerySum
 
 import order.models
 import part.filters as part_filters
@@ -1165,6 +1166,15 @@ class SalesOrderLineItemSerializer(
             building=part_filters.annotate_in_production_quantity(reference='part__')
         )
 
+        # Annotate total 'allocated' stock quantity
+        queryset = queryset.annotate(
+            allocated=Coalesce(
+                SubquerySum('allocations__quantity'),
+                Decimal(0),
+                output_field=models.DecimalField(),
+            )
+        )
+
         return queryset
 
     order_detail = SalesOrderSerializer(source='order', many=False, read_only=True)
@@ -1182,7 +1192,7 @@ class SalesOrderLineItemSerializer(
 
     quantity = InvenTreeDecimalField()
 
-    allocated = serializers.FloatField(source='allocated_quantity', read_only=True)
+    allocated = serializers.FloatField(read_only=True)
 
     shipped = InvenTreeDecimalField(read_only=True)
 
diff --git a/src/backend/InvenTree/order/test_api.py b/src/backend/InvenTree/order/test_api.py
index f635ac8622..aba949e7cb 100644
--- a/src/backend/InvenTree/order/test_api.py
+++ b/src/backend/InvenTree/order/test_api.py
@@ -1683,10 +1683,96 @@ class SalesOrderLineItemTest(OrderTest):
         self.filter({'has_pricing': 1}, 0)
         self.filter({'has_pricing': 0}, n)
 
-        # Filter by has_pricing status
+        # Filter by 'completed' status
         self.filter({'completed': 1}, 0)
         self.filter({'completed': 0}, n)
 
+        # Filter by 'allocated' status
+        self.filter({'allocated': 'true'}, 0)
+        self.filter({'allocated': 'false'}, n)
+
+    def test_so_line_allocated_filters(self):
+        """Test filtering by allocation status for a SalesOrderLineItem."""
+        self.assignRole('sales_order.add')
+
+        # Crete a new SalesOrder via the API
+        response = self.post(
+            reverse('api-so-list'),
+            {
+                'customer': Company.objects.filter(is_customer=True).first().pk,
+                'reference': 'SO-12345',
+                'description': 'Test Sales Order',
+            },
+        )
+
+        order_id = response.data['pk']
+        order = models.SalesOrder.objects.get(pk=order_id)
+
+        so_line_url = reverse('api-so-line-list')
+
+        # Initially, there should be no line items against this order
+        response = self.get(so_line_url, {'order': order_id})
+
+        self.assertEqual(len(response.data), 0)
+
+        parts = [25, 50, 100]
+
+        # Let's create some new line items
+        for part_id in parts:
+            self.post(so_line_url, {'order': order_id, 'part': part_id, 'quantity': 10})
+
+        # Should be three items now
+        response = self.get(so_line_url, {'order': order_id})
+
+        self.assertEqual(len(response.data), 3)
+
+        for item in response.data:
+            # Check that the line item has been created
+            self.assertEqual(item['order'], order_id)
+
+            # Check that the line quantities are correct
+            self.assertEqual(item['quantity'], 10)
+            self.assertEqual(item['allocated'], 0)
+            self.assertEqual(item['shipped'], 0)
+
+        # Initial API filters should return no results
+        self.filter({'order': order_id, 'allocated': 1}, 0)
+        self.filter({'order': order_id, 'completed': 1}, 0)
+
+        # Create a new shipment against this SalesOrder
+        shipment = models.SalesOrderShipment.objects.create(
+            order=order, reference='SHIP-12345'
+        )
+
+        # Next, allocate stock against 2 line items
+        for item in parts[:2]:
+            p = Part.objects.get(pk=item)
+            s = StockItem.objects.create(part=p, quantity=100)
+            l = models.SalesOrderLineItem.objects.filter(order=order, part=p).first()
+
+            # Allocate against the API
+            self.post(
+                reverse('api-so-allocate', kwargs={'pk': order.pk}),
+                {
+                    'items': [{'line_item': l.pk, 'stock_item': s.pk, 'quantity': 10}],
+                    'shipment': shipment.pk,
+                },
+            )
+
+        # Filter by 'fully allocated' status
+        self.filter({'order': order_id, 'allocated': 1}, 2)
+        self.filter({'order': order_id, 'allocated': 0}, 1)
+
+        self.filter({'order': order_id, 'completed': 1}, 0)
+        self.filter({'order': order_id, 'completed': 0}, 3)
+
+        # Finally, mark this shipment as 'shipped'
+        self.post(reverse('api-so-shipment-ship', kwargs={'pk': shipment.pk}), {})
+
+        # Filter by 'completed' status
+        self.filter({'order': order_id, 'completed': 1}, 2)
+        self.filter({'order': order_id, 'completed': 0}, 1)
+
 
 class SalesOrderDownloadTest(OrderTest):
     """Unit tests for downloading SalesOrder data via the API endpoint."""
diff --git a/src/frontend/src/tables/sales/SalesOrderLineItemTable.tsx b/src/frontend/src/tables/sales/SalesOrderLineItemTable.tsx
index 8c8fb11983..01864cef65 100644
--- a/src/frontend/src/tables/sales/SalesOrderLineItemTable.tsx
+++ b/src/frontend/src/tables/sales/SalesOrderLineItemTable.tsx
@@ -33,6 +33,7 @@ import { apiUrl } from '../../states/ApiState';
 import { useUserState } from '../../states/UserState';
 import { TableColumn } from '../Column';
 import { DateColumn, LinkColumn, PartColumn } from '../ColumnRenderers';
+import { TableFilter } from '../Filter';
 import { InvenTreeTable } from '../InvenTreeTable';
 import {
   RowAction,
@@ -161,6 +162,7 @@ export default function SalesOrderLineItemTable({
       },
       {
         accessor: 'allocated',
+        sortable: true,
         render: (record: any) => (
           <ProgressBar
             progressLabel={true}
@@ -171,6 +173,7 @@ export default function SalesOrderLineItemTable({
       },
       {
         accessor: 'shipped',
+        sortable: true,
         render: (record: any) => (
           <ProgressBar
             progressLabel={true}
@@ -266,6 +269,21 @@ export default function SalesOrderLineItemTable({
     }
   });
 
+  const tableFilters: TableFilter[] = useMemo(() => {
+    return [
+      {
+        name: 'allocated',
+        label: t`Allocated`,
+        description: t`Show lines which are fully allocated`
+      },
+      {
+        name: 'completed',
+        label: t`Completed`,
+        description: t`Show lines which are completed`
+      }
+    ];
+  }, []);
+
   const tableActions = useMemo(() => {
     return [
       <AddItemButton
@@ -404,6 +422,7 @@ export default function SalesOrderLineItemTable({
           },
           rowActions: rowActions,
           tableActions: tableActions,
+          tableFilters: tableFilters,
           modelType: ModelType.part,
           modelField: 'part'
         }}
diff --git a/src/frontend/tests/pages/pui_stock.spec.ts b/src/frontend/tests/pages/pui_stock.spec.ts
index 6819ef4d08..20290fb815 100644
--- a/src/frontend/tests/pages/pui_stock.spec.ts
+++ b/src/frontend/tests/pages/pui_stock.spec.ts
@@ -80,6 +80,9 @@ test('Stock - Serial Numbers', async ({ page }) => {
   await page.getByLabel('text-field-serial_numbers').fill('200-250');
   await page.getByLabel('number-field-quantity').fill('10');
 
+  // Add delay to account to field debounce
+  await page.waitForTimeout(250);
+
   await page.getByRole('button', { name: 'Submit' }).click();
 
   // Expected error messages