2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-12-25 13:43:30 +00:00

[bug] Trim stock allocation (#11060)

* Trim stock allocation

- Handle condition where allocated quantity exceeds available stock
- Prevent silent failure of build completion

* Fix display in buildallocatedstock table

* Consolidate table columns

* Fetch substitutes for BOM table
This commit is contained in:
Oliver
2025-12-23 12:16:51 +11:00
committed by GitHub
parent 9d2ac521ef
commit c1d7f2a300
4 changed files with 19 additions and 20 deletions

View File

@@ -1887,7 +1887,7 @@ class BuildItem(InvenTree.models.InvenTreeMetadataModel):
return self.build_line.bom_item if self.build_line else None return self.build_line.bom_item if self.build_line else None
@transaction.atomic @transaction.atomic
def complete_allocation(self, quantity=None, notes='', user=None) -> None: def complete_allocation(self, quantity=None, notes: str = '', user=None) -> None:
"""Complete the allocation of this BuildItem into the output stock item. """Complete the allocation of this BuildItem into the output stock item.
Arguments: Arguments:
@@ -1910,9 +1910,7 @@ class BuildItem(InvenTree.models.InvenTreeMetadataModel):
# Ensure we are not allocating more than available # Ensure we are not allocating more than available
if quantity > item.quantity: if quantity > item.quantity:
raise ValidationError({ quantity = item.quantity
'quantity': _('Allocated quantity exceeds available stock quantity')
})
# Split the allocated stock if there are more available than allocated # Split the allocated stock if there are more available than allocated
if item.quantity > quantity: if item.quantity > quantity:

View File

@@ -670,9 +670,10 @@ export function BomTable({
params: { params: {
...params, ...params,
part: partId, part: partId,
category_detail: true, substitutes: true,
part_detail: true, part_detail: true,
sub_part_detail: true sub_part_detail: true,
category_detail: true
}, },
tableActions: tableActions, tableActions: tableActions,
tableFilters: tableFilters, tableFilters: tableFilters,

View File

@@ -6,7 +6,7 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType'; import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles'; import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import { ActionButton } from '@lib/index'; import { ActionButton, formatDecimal } from '@lib/index';
import type { TableFilter } from '@lib/types/Filters'; import type { TableFilter } from '@lib/types/Filters';
import type { StockOperationProps } from '@lib/types/Forms'; import type { StockOperationProps } from '@lib/types/Forms';
import type { TableColumn } from '@lib/types/Tables'; import type { TableColumn } from '@lib/types/Tables';
@@ -113,13 +113,6 @@ export default function BuildAllocatedStockTable({
sortable: true, sortable: true,
switchable: true switchable: true
}, },
{
accessor: 'serial',
title: t`Serial Number`,
sortable: false,
switchable: true,
render: (record: any) => record?.stock_item_detail?.serial
},
{ {
accessor: 'batch', accessor: 'batch',
title: t`Batch Code`, title: t`Batch Code`,
@@ -128,15 +121,22 @@ export default function BuildAllocatedStockTable({
render: (record: any) => record?.stock_item_detail?.batch render: (record: any) => record?.stock_item_detail?.batch
}, },
DecimalColumn({ DecimalColumn({
accessor: 'available', accessor: 'stock_item_detail.quantity',
title: t`Available` title: t`Available`
}), }),
DecimalColumn({ {
accessor: 'quantity', accessor: 'quantity',
title: t`Allocated`, title: t`Allocated`,
sortable: true, render: (record: any) => {
switchable: false const serial = record?.stock_item_detail?.serial;
}),
if (serial && record?.quantity == 1) {
return `${t`Serial`}: ${serial}`;
}
return formatDecimal(record.quantity);
}
},
LocationColumn({ LocationColumn({
accessor: 'location_detail', accessor: 'location_detail',
switchable: true, switchable: true,

View File

@@ -135,7 +135,7 @@ test('Parts - BOM', async ({ browser }) => {
await page.getByRole('button', { name: 'Close' }).click(); await page.getByRole('button', { name: 'Close' }).click();
}); });
test('Part - Editing', async ({ browser }) => { test('Parts - Editing', async ({ browser }) => {
const page = await doCachedLogin(browser, { url: 'part/104/details' }); const page = await doCachedLogin(browser, { url: 'part/104/details' });
await page.getByText('A square table - with blue paint').first().waitFor(); await page.getByText('A square table - with blue paint').first().waitFor();