2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-09-13 22:21:37 +00:00

Virtual parts enhancements (#10257)

* Prevent virtual parts from being linked in a BuildOrder

* Hide "stock" tab for virtual parts

* Filter out virtual parts when creating a new stock item

* Support virtual parts in sales orders

* Add 'virtual' filter for BomItem

* Hide stock badges for virtual parts

* Tweak PartDetail page

* docs

* Adjust completion logic for SalesOrder

* Fix backend filter

* Remove restriction

* Adjust table

* Fix for "pending_line_items"

* Hide more panels for "Virtual" part

* Add badge for "virtual" part

* Bump API version

* Fix docs link
This commit is contained in:
Oliver
2025-09-03 15:13:08 +10:00
committed by GitHub
parent 41cc0850c6
commit 7969d2d9ce
14 changed files with 195 additions and 42 deletions

View File

@@ -103,6 +103,7 @@ export function useStockFields({
value: partId || partInstance?.pk,
disabled: !create,
filters: {
virtual: false,
active: create ? true : undefined
},
onValueChange: (value, record) => {

View File

@@ -532,7 +532,8 @@ export default function PartDetail() {
type: 'number',
name: 'total_in_stock',
unit: part.units,
label: t`In Stock`
label: t`In Stock`,
hidden: part.virtual
},
{
type: 'progressbar',
@@ -540,7 +541,7 @@ export default function PartDetail() {
total: data.total_in_stock,
progress: data.unallocated,
label: t`Available Stock`,
hidden: data.total_in_stock == data.unallocated
hidden: part.virtual || data.total_in_stock == data.unallocated
},
{
type: 'number',
@@ -803,6 +804,7 @@ export default function PartDetail() {
name: 'stock',
label: t`Stock`,
icon: <IconPackages />,
hidden: part.virtual || !user.hasViewRole(UserRoles.stock),
content: part.pk ? (
<StockItemTable
tableName='part-stock'
@@ -828,7 +830,7 @@ export default function PartDetail() {
name: 'allocations',
label: t`Allocations`,
icon: <IconBookmarks />,
hidden: !part.component && !part.salable,
hidden: (!part.component && !part.salable) || part.virtual,
content: part.pk ? <PartAllocationPanel part={part} /> : <Skeleton />
},
{
@@ -915,6 +917,8 @@ export default function PartDetail() {
<Skeleton />
),
hidden:
part.virtual ||
!user.hasViewRole(UserRoles.stock) ||
!globalSettings.isSet('STOCKTAKE_ENABLE') ||
!userSettings.isSet('DISPLAY_STOCKTAKE_TAB')
},
@@ -973,7 +977,7 @@ export default function PartDetail() {
? 'green'
: 'orange'
}
visible={partRequirements.total_stock > 0}
visible={!part.virtual && partRequirements.total_stock > 0}
key='in_stock'
/>,
<DetailsBadge
@@ -981,13 +985,14 @@ export default function PartDetail() {
color='yellow'
key='available_stock'
visible={
!part.virtual &&
partRequirements.unallocated_stock != partRequirements.total_stock
}
/>,
<DetailsBadge
label={t`No Stock`}
color='orange'
visible={partRequirements.total_stock == 0}
visible={!part.virtual && partRequirements.total_stock == 0}
key='no_stock'
/>,
<DetailsBadge
@@ -1013,6 +1018,12 @@ export default function PartDetail() {
color='red'
visible={!part.active}
key='inactive'
/>,
<DetailsBadge
label={t`Virtual Part`}
color='cyan.4'
visible={part.virtual}
key='virtual'
/>
];
}, [partRequirements, partRequirementsQuery.isFetching, part]);
@@ -1143,6 +1154,7 @@ export default function PartDetail() {
<ActionDropdown
tooltip={t`Stock Actions`}
icon={<IconPackages />}
hidden={part.virtual || !user.hasViewRole(UserRoles.stock)}
actions={[
...stockAdjustActions.menuActions,
{

View File

@@ -137,6 +137,11 @@ export function BomTable({
DescriptionColumn({
accessor: 'sub_part_detail.description'
}),
BooleanColumn({
accessor: 'sub_part_detail.virtual',
defaultVisible: false,
title: t`Virtual Part`
}),
ReferenceColumn({
switchable: true
}),
@@ -404,6 +409,11 @@ export function BomTable({
label: t`Assembled Part`,
description: t`Show assembled items`
},
{
name: 'sub_part_virtual',
label: t`Virtual Part`,
description: t`Show virtual items`
},
{
name: 'available_stock',
label: t`Available Stock`,

View File

@@ -82,10 +82,12 @@ export default function SalesOrderLineItemTable({
render: (record: any) => {
return (
<Group wrap='nowrap'>
<RowExpansionIcon
enabled={record.allocated}
expanded={table.isRowExpanded(record.pk)}
/>
{record.part_detail?.virtual || (
<RowExpansionIcon
enabled={record.allocated}
expanded={table.isRowExpanded(record.pk)}
/>
)}
<RenderPartColumn part={record.part_detail} />
</Group>
);
@@ -133,6 +135,10 @@ export default function SalesOrderLineItemTable({
accessor: 'stock',
title: t`Available Stock`,
render: (record: any) => {
if (record.part_detail?.virtual) {
return <Text size='sm' fs='italic'>{t`Virtual part`}</Text>;
}
const part_stock = record?.available_stock ?? 0;
const variant_stock = record?.available_variant_stock ?? 0;
const available = part_stock + variant_stock;
@@ -186,24 +192,36 @@ export default function SalesOrderLineItemTable({
{
accessor: 'allocated',
sortable: true,
render: (record: any) => (
<ProgressBar
progressLabel={true}
value={record.allocated}
maximum={record.quantity}
/>
)
render: (record: any) => {
if (record.part_detail?.virtual) {
return <Text size='sm' fs='italic'>{t`Virtual part`}</Text>;
}
return (
<ProgressBar
progressLabel={true}
value={record.allocated}
maximum={record.quantity}
/>
);
}
},
{
accessor: 'shipped',
sortable: true,
render: (record: any) => (
<ProgressBar
progressLabel={true}
value={record.shipped}
maximum={record.quantity}
/>
)
render: (record: any) => {
if (record.part_detail?.virtual) {
return <Text size='sm' fs='italic'>{t`Virtual part`}</Text>;
}
return (
<ProgressBar
progressLabel={true}
value={record.shipped}
maximum={record.quantity}
/>
);
}
},
{
accessor: 'notes'
@@ -371,6 +389,7 @@ export default function SalesOrderLineItemTable({
const rowActions = useCallback(
(record: any): RowAction[] => {
const allocated = (record?.allocated ?? 0) > (record?.quantity ?? 0);
const virtual = record?.part_detail?.virtual ?? false;
return [
RowViewAction({
@@ -383,6 +402,7 @@ export default function SalesOrderLineItemTable({
{
hidden:
allocated ||
virtual ||
!editable ||
!user.hasChangeRole(UserRoles.sales_order),
title: t`Allocate Stock`,
@@ -397,6 +417,7 @@ export default function SalesOrderLineItemTable({
hidden:
!record?.part_detail?.trackable ||
allocated ||
virtual ||
!editable ||
!user.hasChangeRole(UserRoles.sales_order),
title: t`Allocate serials`,
@@ -414,6 +435,7 @@ export default function SalesOrderLineItemTable({
{
hidden:
allocated ||
virtual ||
!user.hasAddRole(UserRoles.build) ||
!record?.part_detail?.assembly,
title: t`Build stock`,
@@ -431,6 +453,7 @@ export default function SalesOrderLineItemTable({
{
hidden:
allocated ||
virtual ||
!user.hasAddRole(UserRoles.purchase_order) ||
!record?.part_detail?.purchaseable,
title: t`Order stock`,
@@ -472,6 +495,9 @@ export default function SalesOrderLineItemTable({
return {
allowMultiple: true,
expandable: ({ record }: { record: any }) => {
if (record?.part_detail?.virtual) {
return false;
}
return table.isRowExpanded(record.pk) || record.allocated > 0;
},
content: ({ record }: { record: any }) => {