Skip to content

Commit

Permalink
[PUI] Platform fixes (#8324)
Browse files Browse the repository at this point in the history
* Add "IPN" column to build order allocated stock table

* Allow sorting and searching by IPN

* Handle allocations where allocated but required == 0

* Add "no info available" message to part scheduling

* Adjust PartSchedulingTable

* Icon fix

* Add "latest serial number" information to PartDetail page

* Cleanup code for serial-number placeholder in forms

* Logic fix for displaying non-unity pack quantity

* Fix description field on SupplierPart page

* Fix duplicate table column
  • Loading branch information
SchrodingersGat authored Oct 22, 2024
1 parent e219b7c commit ddea9fa
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 83 deletions.
3 changes: 3 additions & 0 deletions src/backend/InvenTree/build/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,10 +737,12 @@ def filter_queryset(self, queryset):
'quantity',
'location',
'reference',
'IPN',
]

ordering_field_aliases = {
'part': 'stock_item__part__name',
'IPN': 'stock_item__part__IPN',
'sku': 'stock_item__supplier_part__SKU',
'location': 'stock_item__location__name',
'reference': 'build_line__bom_item__reference',
Expand All @@ -749,6 +751,7 @@ def filter_queryset(self, queryset):
search_fields = [
'stock_item__supplier_part__SKU',
'stock_item__part__name',
'stock_item__part__IPN',
'build_line__bom_item__reference',
]

Expand Down
4 changes: 4 additions & 0 deletions src/frontend/src/components/details/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ function TableAnchorValue(props: Readonly<FieldProps>) {
}

function ProgressBarValue(props: Readonly<FieldProps>) {
if (props.field_data.total <= 0) {
return <Text size="sm">{props.field_data.progress}</Text>;
}

return (
<ProgressBar
value={props.field_data.progress}
Expand Down
82 changes: 40 additions & 42 deletions src/frontend/src/forms/StockForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
useBatchCodeGenerator,
useSerialNumberGenerator
} from '../hooks/UseGenerator';
import { useInstance } from '../hooks/UseInstance';
import { useSerialNumberPlaceholder } from '../hooks/UsePlaceholder';
import { apiUrl } from '../states/ApiState';
import { useGlobalSettingsState } from '../states/SettingsState';
Expand All @@ -48,73 +49,74 @@ import { useGlobalSettingsState } from '../states/SettingsState';
* Construct a set of fields for creating / editing a StockItem instance
*/
export function useStockFields({
item_detail,
part_detail,
partId,
stockItem,
create = false
}: {
partId?: number;
item_detail?: any;
part_detail?: any;
stockItem?: any;
create: boolean;
}): ApiFormFieldSet {
const globalSettings = useGlobalSettingsState();

const [part, setPart] = useState<number | null>(null);
const [supplierPart, setSupplierPart] = useState<number | null>(null);
// Keep track of the "part" instance
const [partInstance, setPartInstance] = useState<any>({});

const [batchCode, setBatchCode] = useState<string>('');
const [serialNumbers, setSerialNumbers] = useState<string>('');
const [supplierPart, setSupplierPart] = useState<number | null>(null);

const [trackable, setTrackable] = useState<boolean>(false);
const [nextBatchCode, setNextBatchCode] = useState<string>('');
const [nextSerialNumber, setNextSerialNumber] = useState<string>('');

const batchGenerator = useBatchCodeGenerator((value: any) => {
if (!batchCode) {
setBatchCode(value);
if (value) {
setNextBatchCode(`Next batch code` + `: ${value}`);
} else {
setNextBatchCode('');
}
});

const serialGenerator = useSerialNumberGenerator((value: any) => {
if (!serialNumbers && create && trackable) {
setSerialNumbers(value);
if (value) {
setNextSerialNumber(t`Next serial number` + `: ${value}`);
} else {
setNextSerialNumber('');
}
});

useEffect(() => {
if (partInstance?.pk) {
// Update the generators whenever the part ID changes
batchGenerator.update({ part: partInstance.pk });
serialGenerator.update({ part: partInstance.pk });
}
}, [partInstance.pk]);

return useMemo(() => {
const fields: ApiFormFieldSet = {
part: {
value: partId,
value: partInstance.pk,
disabled: !create,
filters: {
active: create ? true : undefined
},
onValueChange: (value, record) => {
setPart(value);
// TODO: implement remaining functionality from old stock.py

setTrackable(record.trackable ?? false);

batchGenerator.update({ part: value });
serialGenerator.update({ part: value });

if (!record.trackable) {
setSerialNumbers('');
}
// Update the tracked part instance
setPartInstance(record);

// Clear the 'supplier_part' field if the part is changed
setSupplierPart(null);
}
},
supplier_part: {
hidden: part_detail?.purchaseable == false,
hidden: partInstance?.purchaseable == false,
value: supplierPart,
onValueChange: (value) => {
setSupplierPart(value);
},
filters: {
part_detail: true,
supplier_detail: true,
...(part ? { part } : {})
part: partId
},
adjustFilters: (adjust: ApiFormAdjustFilterType) => {
if (adjust.data.part) {
Expand Down Expand Up @@ -148,22 +150,20 @@ export function useStockFields({
serial_numbers: {
field_type: 'string',
label: t`Serial Numbers`,
disabled: partInstance?.trackable == false,
description: t`Enter serial numbers for new stock (or leave blank)`,
required: false,
disabled: !trackable,
hidden: !create,
value: serialNumbers,
onValueChange: (value) => setSerialNumbers(value)
placeholder: nextSerialNumber
},
serial: {
hidden:
create ||
part_detail?.trackable == false ||
(!item_detail?.quantity != undefined && item_detail?.quantity != 1)
partInstance.trackable == false ||
(!stockItem?.quantity != undefined && stockItem?.quantity != 1)
},
batch: {
value: batchCode,
onValueChange: (value) => setBatchCode(value)
placeholder: nextBatchCode
},
status_custom_key: {
label: t`Stock Status`
Expand Down Expand Up @@ -195,16 +195,14 @@ export function useStockFields({

return fields;
}, [
item_detail,
part_detail,
part,
stockItem,
partInstance,
partId,
globalSettings,
supplierPart,
batchCode,
serialNumbers,
trackable,
create,
partId
nextSerialNumber,
nextBatchCode,
create
]);
}

Expand Down
8 changes: 7 additions & 1 deletion src/frontend/src/hooks/UseInstance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ export function useInstance<T = any>({
const [requestStatus, setRequestStatus] = useState<number>(0);

const instanceQuery = useQuery<T>({
queryKey: ['instance', endpoint, pk, params, pathParams],
queryKey: [
'instance',
endpoint,
pk,
JSON.stringify(params),
JSON.stringify(pathParams)
],
queryFn: async () => {
if (hasPrimaryKey) {
if (
Expand Down
12 changes: 10 additions & 2 deletions src/frontend/src/pages/company/SupplierPartDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,10 @@ export default function SupplierPartDetail() {
{
type: 'string',
name: 'part_detail.description',
label: t`Description`,
label: t`Part Description`,
copy: true,
icon: 'info'
icon: 'info',
hidden: !data.part_detail?.description
},
{
type: 'link',
Expand Down Expand Up @@ -133,6 +134,13 @@ export default function SupplierPartDetail() {
copy: true,
icon: 'reference'
},
{
type: 'string',
name: 'description',
label: t`Description`,
copy: true,
hidden: !data.description
},
{
type: 'link',
name: 'manufacturer',
Expand Down
52 changes: 41 additions & 11 deletions src/frontend/src/pages/part/PartDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ export default function PartDetail() {
const globalSettings = useGlobalSettingsState();
const userSettings = useUserSettingsState();

const { instance: serials } = useInstance({
endpoint: ApiEndpoints.part_serial_numbers,
pk: id,
hasPrimaryKey: true,
refetchOnMount: false,
defaultValue: {}
});

const {
instance: part,
refreshInstance,
Expand All @@ -132,15 +140,22 @@ export default function PartDetail() {
refetchOnMount: true
});

part.required =
(part?.required_for_build_orders ?? 0) +
(part?.required_for_sales_orders ?? 0);

const detailsPanel = useMemo(() => {
if (instanceQuery.isFetching) {
return <Skeleton />;
}

let data = { ...part };

data.required =
(data?.required_for_build_orders ?? 0) +
(data?.required_for_sales_orders ?? 0);

// Provide latest serial number info
if (!!serials.latest) {
data.latest_serial_number = serials.latest;
}

// Construct the details tables
let tl: DetailsField[] = [
{
Expand Down Expand Up @@ -277,15 +292,21 @@ export default function PartDetail() {
total: part.required_for_build_orders,
progress: part.allocated_to_build_orders,
label: t`Allocated to Build Orders`,
hidden: !part.component || part.required_for_build_orders <= 0
hidden:
!part.component ||
(part.required_for_build_orders <= 0 &&
part.allocated_to_build_orders <= 0)
},
{
type: 'progressbar',
name: 'allocated_to_sales_orders',
total: part.required_for_sales_orders,
progress: part.allocated_to_sales_orders,
label: t`Allocated to Sales Orders`,
hidden: !part.salable || part.required_for_sales_orders <= 0
hidden:
!part.salable ||
(part.required_for_sales_orders <= 0 &&
part.allocated_to_sales_orders <= 0)
},
{
type: 'string',
Expand Down Expand Up @@ -349,6 +370,7 @@ export default function PartDetail() {
{
type: 'boolean',
name: 'salable',
icon: 'saleable',
label: t`Saleable Part`
},
{
Expand Down Expand Up @@ -434,6 +456,14 @@ export default function PartDetail() {
});
}

br.push({
type: 'string',
name: 'latest_serial_number',
label: t`Latest Serial Number`,
hidden: !part.trackable || !data.latest_serial_number,
icon: 'serial'
});

// Add in stocktake information
if (id && part.last_stocktake) {
br.push({
Expand Down Expand Up @@ -526,17 +556,17 @@ export default function PartDetail() {
/>
</Grid.Col>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={part} />
<DetailsTable fields={tl} item={data} />
</Grid.Col>
</Grid>
<DetailsTable fields={tr} item={part} />
<DetailsTable fields={bl} item={part} />
<DetailsTable fields={br} item={part} />
<DetailsTable fields={tr} item={data} />
<DetailsTable fields={bl} item={data} />
<DetailsTable fields={br} item={data} />
</ItemDetailsGrid>
) : (
<Skeleton />
);
}, [globalSettings, part, instanceQuery]);
}, [globalSettings, part, serials, instanceQuery]);

// Part data panels (recalculate when part data changes)
const partPanels: PanelType[] = useMemo(() => {
Expand Down
Loading

0 comments on commit ddea9fa

Please sign in to comment.