mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 21:15:41 +00:00
Update part details
This commit is contained in:
@ -94,354 +94,6 @@ export default function PartDetail() {
|
|||||||
refetchOnMount: true
|
refetchOnMount: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const detailFields = (part: any): ItemDetailFields => {
|
|
||||||
let left: DetailsField[][] = [];
|
|
||||||
let right: DetailsField[][] = [];
|
|
||||||
let bottom_right: DetailsField[][] = [];
|
|
||||||
let bottom_left: DetailsField[][] = [];
|
|
||||||
|
|
||||||
let image: DetailsImageType = {
|
|
||||||
name: 'image',
|
|
||||||
imageActions: {
|
|
||||||
selectExisting: true,
|
|
||||||
uploadFile: true,
|
|
||||||
deleteFile: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
left.push([
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
name: 'description',
|
|
||||||
label: t`Description`,
|
|
||||||
copy: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (part.variant_of) {
|
|
||||||
left.push([
|
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
name: 'variant_of',
|
|
||||||
label: t`Variant of`,
|
|
||||||
model: ModelType.part
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
right.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'unallocated_stock',
|
|
||||||
unit: true,
|
|
||||||
label: t`Available Stock`
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
right.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'total_in_stock',
|
|
||||||
unit: true,
|
|
||||||
label: t`In Stock`
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (part.minimum_stock) {
|
|
||||||
right.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'minimum_stock',
|
|
||||||
unit: true,
|
|
||||||
label: t`Minimum Stock`
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.ordering <= 0) {
|
|
||||||
right.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'ordering',
|
|
||||||
label: t`On order`,
|
|
||||||
unit: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
part.assembly &&
|
|
||||||
(part.allocated_to_build_orders > 0 || part.required_for_build_orders > 0)
|
|
||||||
) {
|
|
||||||
right.push([
|
|
||||||
{
|
|
||||||
type: 'progressbar',
|
|
||||||
name: 'allocated_to_build_orders',
|
|
||||||
total: part.required_for_build_orders,
|
|
||||||
progress: part.allocated_to_build_orders,
|
|
||||||
label: t`Allocated to Build Orders`
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
part.salable &&
|
|
||||||
(part.allocated_to_sales_orders > 0 || part.required_for_sales_orders > 0)
|
|
||||||
) {
|
|
||||||
right.push([
|
|
||||||
{
|
|
||||||
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`
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.assembly) {
|
|
||||||
right.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'can_build',
|
|
||||||
unit: true,
|
|
||||||
label: t`Can Build`
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.assembly) {
|
|
||||||
right.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'building',
|
|
||||||
unit: true,
|
|
||||||
label: t`Building`
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.category) {
|
|
||||||
bottom_left.push([
|
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
name: 'category',
|
|
||||||
label: t`Category`,
|
|
||||||
model: ModelType.partcategory
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.IPN) {
|
|
||||||
bottom_left.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'IPN',
|
|
||||||
label: t`IPN`,
|
|
||||||
copy: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.revision) {
|
|
||||||
bottom_left.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'revision',
|
|
||||||
label: t`Revision`,
|
|
||||||
copy: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.units) {
|
|
||||||
bottom_left.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'units',
|
|
||||||
label: t`Units`
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.keywords) {
|
|
||||||
bottom_left.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'keywords',
|
|
||||||
label: t`Keywords`,
|
|
||||||
copy: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
bottom_right.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'creation_date',
|
|
||||||
label: t`Creation Date`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'creation_user',
|
|
||||||
badge: 'user'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
id &&
|
|
||||||
bottom_right.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'pricing',
|
|
||||||
label: t`Price Range`,
|
|
||||||
value_formatter: () => {
|
|
||||||
const { data } = useSuspenseQuery({
|
|
||||||
queryKey: ['pricing', id],
|
|
||||||
queryFn: async () => {
|
|
||||||
const url = apiUrl(ApiEndpoints.part_pricing_get, null, {
|
|
||||||
id: id
|
|
||||||
});
|
|
||||||
|
|
||||||
return api
|
|
||||||
.get(url)
|
|
||||||
.then((response) => {
|
|
||||||
switch (response.status) {
|
|
||||||
case 200:
|
|
||||||
return response.data;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return `${formatPriceRange(data.overall_min, data.overall_max)}${
|
|
||||||
part.units && ' / ' + part.units
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
id &&
|
|
||||||
part.last_stocktake &&
|
|
||||||
bottom_right.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'stocktake',
|
|
||||||
label: t`Last Stocktake`,
|
|
||||||
unit: true,
|
|
||||||
value_formatter: () => {
|
|
||||||
const { data } = useSuspenseQuery({
|
|
||||||
queryKey: ['stocktake', id],
|
|
||||||
queryFn: async () => {
|
|
||||||
const url = apiUrl(ApiEndpoints.part_stocktake_list);
|
|
||||||
|
|
||||||
return api
|
|
||||||
.get(url, { params: { part: id, ordering: 'date' } })
|
|
||||||
.then((response) => {
|
|
||||||
switch (response.status) {
|
|
||||||
case 200:
|
|
||||||
return response.data[response.data.length - 1];
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return data?.quantity;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'stocktake_user',
|
|
||||||
badge: 'user',
|
|
||||||
value_formatter: () => {
|
|
||||||
const { data } = useSuspenseQuery({
|
|
||||||
queryKey: ['stocktake', id],
|
|
||||||
queryFn: async () => {
|
|
||||||
const url = apiUrl(ApiEndpoints.part_stocktake_list);
|
|
||||||
|
|
||||||
return api
|
|
||||||
.get(url, { params: { part: id, ordering: 'date' } })
|
|
||||||
.then((response) => {
|
|
||||||
switch (response.status) {
|
|
||||||
case 200:
|
|
||||||
return response.data[response.data.length - 1];
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return data?.user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (part.default_location) {
|
|
||||||
bottom_right.push([
|
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
name: 'default_location',
|
|
||||||
label: t`Default Location`,
|
|
||||||
model: ModelType.stocklocation
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.default_supplier) {
|
|
||||||
bottom_right.push([
|
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
name: 'default_supplier',
|
|
||||||
label: t`Default Supplier`,
|
|
||||||
model: ModelType.supplierpart
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.link) {
|
|
||||||
bottom_right.push([
|
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
name: 'link',
|
|
||||||
label: t`Link`,
|
|
||||||
external: true,
|
|
||||||
copy: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.responsible) {
|
|
||||||
bottom_right.push([
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
name: 'responsible',
|
|
||||||
label: t`Responsible`,
|
|
||||||
badge: 'owner'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let fields: ItemDetailFields = {
|
|
||||||
left: left,
|
|
||||||
right: right,
|
|
||||||
bottom_left: bottom_left,
|
|
||||||
bottom_right: bottom_right,
|
|
||||||
image: image
|
|
||||||
};
|
|
||||||
|
|
||||||
return fields;
|
|
||||||
};
|
|
||||||
|
|
||||||
const detailsPanel = useMemo(() => {
|
const detailsPanel = useMemo(() => {
|
||||||
if (instanceQuery.isFetching) {
|
if (instanceQuery.isFetching) {
|
||||||
return <Skeleton />;
|
return <Skeleton />;
|
||||||
@ -536,6 +188,13 @@ export default function PartDetail() {
|
|||||||
label: t`Category`,
|
label: t`Category`,
|
||||||
model: ModelType.partcategory
|
model: ModelType.partcategory
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
name: 'default_location',
|
||||||
|
label: t`Default Location`,
|
||||||
|
model: ModelType.stocklocation,
|
||||||
|
hidden: !part.default_location
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'IPN',
|
name: 'IPN',
|
||||||
@ -554,6 +213,7 @@ export default function PartDetail() {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'units',
|
name: 'units',
|
||||||
label: t`Units`,
|
label: t`Units`,
|
||||||
|
copy: true,
|
||||||
hidden: !part.units
|
hidden: !part.units
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -564,11 +224,12 @@ export default function PartDetail() {
|
|||||||
hidden: !part.keywords
|
hidden: !part.keywords
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'string',
|
type: 'link',
|
||||||
name: 'responsible',
|
name: 'link',
|
||||||
label: t`Responsible`,
|
label: t`Link`,
|
||||||
badge: 'owner',
|
external: true,
|
||||||
hidden: !part.responsible
|
copy: true,
|
||||||
|
hidden: !part.link
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -585,11 +246,11 @@ export default function PartDetail() {
|
|||||||
badge: 'user'
|
badge: 'user'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'link',
|
type: 'string',
|
||||||
name: 'default_location',
|
name: 'responsible',
|
||||||
label: t`Default Location`,
|
label: t`Responsible`,
|
||||||
model: ModelType.stocklocation,
|
badge: 'owner',
|
||||||
hidden: !part.default_location
|
hidden: !part.responsible
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'link',
|
type: 'link',
|
||||||
@ -597,17 +258,112 @@ export default function PartDetail() {
|
|||||||
label: t`Default Supplier`,
|
label: t`Default Supplier`,
|
||||||
model: ModelType.supplierpart,
|
model: ModelType.supplierpart,
|
||||||
hidden: !part.default_supplier
|
hidden: !part.default_supplier
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'link',
|
|
||||||
name: 'link',
|
|
||||||
label: t`Link`,
|
|
||||||
external: true,
|
|
||||||
copy: true,
|
|
||||||
hidden: !part.link
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Add in price range data
|
||||||
|
id &&
|
||||||
|
br.push({
|
||||||
|
type: 'string',
|
||||||
|
name: 'pricing',
|
||||||
|
label: t`Price Range`,
|
||||||
|
value_formatter: () => {
|
||||||
|
const { data } = useSuspenseQuery({
|
||||||
|
queryKey: ['pricing', id],
|
||||||
|
queryFn: async () => {
|
||||||
|
const url = apiUrl(ApiEndpoints.part_pricing_get, null, {
|
||||||
|
id: id
|
||||||
|
});
|
||||||
|
|
||||||
|
return api
|
||||||
|
.get(url)
|
||||||
|
.then((response) => {
|
||||||
|
switch (response.status) {
|
||||||
|
case 200:
|
||||||
|
return response.data;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return `${formatPriceRange(data.overall_min, data.overall_max)}${
|
||||||
|
part.units && ' / ' + part.units
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add in stocktake information
|
||||||
|
if (id && part.last_stocktake) {
|
||||||
|
br.push({
|
||||||
|
type: 'string',
|
||||||
|
name: 'stocktake',
|
||||||
|
label: t`Last Stocktake`,
|
||||||
|
unit: true,
|
||||||
|
value_formatter: () => {
|
||||||
|
const { data } = useSuspenseQuery({
|
||||||
|
queryKey: ['stocktake', id],
|
||||||
|
queryFn: async () => {
|
||||||
|
const url = apiUrl(ApiEndpoints.part_stocktake_list);
|
||||||
|
|
||||||
|
return api
|
||||||
|
.get(url, { params: { part: id, ordering: 'date' } })
|
||||||
|
.then((response) => {
|
||||||
|
switch (response.status) {
|
||||||
|
case 200:
|
||||||
|
return response.data[response.data.length - 1];
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.quantity) {
|
||||||
|
return `${data.quantity} (${data.date})`;
|
||||||
|
} else {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
br.push({
|
||||||
|
type: 'string',
|
||||||
|
name: 'stocktake_user',
|
||||||
|
label: t`Stocktake By`,
|
||||||
|
badge: 'user',
|
||||||
|
value_formatter: () => {
|
||||||
|
const { data } = useSuspenseQuery({
|
||||||
|
queryKey: ['stocktake', id],
|
||||||
|
queryFn: async () => {
|
||||||
|
const url = apiUrl(ApiEndpoints.part_stocktake_list);
|
||||||
|
|
||||||
|
return api
|
||||||
|
.get(url, { params: { part: id, ordering: 'date' } })
|
||||||
|
.then((response) => {
|
||||||
|
switch (response.status) {
|
||||||
|
case 200:
|
||||||
|
return response.data[response.data.length - 1];
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data?.user;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemDetailsGrid>
|
<ItemDetailsGrid>
|
||||||
<DetailsTable fields={tl} item={part} />
|
<DetailsTable fields={tl} item={part} />
|
||||||
@ -623,7 +379,7 @@ export default function PartDetail() {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'details',
|
name: 'details',
|
||||||
label: t`Details`,
|
label: t`Part Details`,
|
||||||
icon: <IconInfoCircle />,
|
icon: <IconInfoCircle />,
|
||||||
content: detailsPanel
|
content: detailsPanel
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user