mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-02 19:50:59 +00:00
[UI] Add "can build" part info (#9798)
* Add proper serializer to PartRequirements API endpoint * Add API endpoint * Display "can_build" quantity * Add simple playwright tests * Bump API version * Updated docs * Fix formatting * Consolidate field names - Match field names to the PartSerializer * Adjust frontend * Add "can_build" to BuildDetail page * Tweak BuildDetail * Hide until load * serializer fixes
This commit is contained in:
@ -111,6 +111,7 @@ export enum ApiEndpoints {
|
||||
part_parameter_template_list = 'part/parameter/template/',
|
||||
part_thumbs_list = 'part/thumbs/',
|
||||
part_pricing = 'part/:id/pricing/',
|
||||
part_requirements = 'part/:id/requirements/',
|
||||
part_serial_numbers = 'part/:id/serial-numbers/',
|
||||
part_scheduling = 'part/:id/scheduling/',
|
||||
part_pricing_internal = 'part/internal-price/',
|
||||
|
@ -86,11 +86,24 @@ export default function BuildDetail() {
|
||||
refetchOnMount: true
|
||||
});
|
||||
|
||||
const { instance: partRequirements, instanceQuery: partRequirementsQuery } =
|
||||
useInstance({
|
||||
endpoint: ApiEndpoints.part_requirements,
|
||||
pk: build?.part,
|
||||
hasPrimaryKey: true,
|
||||
defaultValue: {}
|
||||
});
|
||||
|
||||
const detailsPanel = useMemo(() => {
|
||||
if (instanceQuery.isFetching) {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
const data = {
|
||||
...build,
|
||||
can_build: partRequirements?.can_build ?? 0
|
||||
};
|
||||
|
||||
const tl: DetailsField[] = [
|
||||
{
|
||||
type: 'link',
|
||||
@ -173,10 +186,17 @@ export default function BuildDetail() {
|
||||
|
||||
const tr: DetailsField[] = [
|
||||
{
|
||||
type: 'text',
|
||||
type: 'number',
|
||||
name: 'quantity',
|
||||
label: t`Build Quantity`
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'can_build',
|
||||
unit: build.part_detail?.units,
|
||||
label: t`Can Build`,
|
||||
hidden: partRequirementsQuery.isFetching
|
||||
},
|
||||
{
|
||||
type: 'progressbar',
|
||||
name: 'completed',
|
||||
@ -290,15 +310,20 @@ export default function BuildDetail() {
|
||||
pk={build.part}
|
||||
/>
|
||||
<Grid.Col span={{ base: 12, sm: 8 }}>
|
||||
<DetailsTable fields={tl} item={build} />
|
||||
<DetailsTable fields={tl} item={data} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<DetailsTable fields={tr} item={build} />
|
||||
<DetailsTable fields={bl} item={build} />
|
||||
<DetailsTable fields={br} item={build} />
|
||||
<DetailsTable fields={tr} item={data} />
|
||||
<DetailsTable fields={bl} item={data} />
|
||||
<DetailsTable fields={br} item={data} />
|
||||
</ItemDetailsGrid>
|
||||
);
|
||||
}, [build, instanceQuery]);
|
||||
}, [
|
||||
build,
|
||||
instanceQuery,
|
||||
partRequirements,
|
||||
partRequirementsQuery.isFetching
|
||||
]);
|
||||
|
||||
const buildPanels: PanelType[] = useMemo(() => {
|
||||
return [
|
||||
|
@ -143,6 +143,14 @@ export default function PartDetail() {
|
||||
refetchOnMount: true
|
||||
});
|
||||
|
||||
const { instance: partRequirements, instanceQuery: partRequirementsQuery } =
|
||||
useInstance({
|
||||
endpoint: ApiEndpoints.part_requirements,
|
||||
pk: id,
|
||||
hasPrimaryKey: true,
|
||||
refetchOnMount: true
|
||||
});
|
||||
|
||||
const detailsPanel = useMemo(() => {
|
||||
if (instanceQuery.isFetching) {
|
||||
return <Skeleton />;
|
||||
@ -151,8 +159,23 @@ export default function PartDetail() {
|
||||
const data = { ...part };
|
||||
|
||||
data.required =
|
||||
(data?.required_for_build_orders ?? 0) +
|
||||
(data?.required_for_sales_orders ?? 0);
|
||||
(partRequirements?.required_for_build_orders ??
|
||||
part?.required_for_build_orders ??
|
||||
0) +
|
||||
(partRequirements?.required_for_sales_orders ??
|
||||
part?.required_for_sales_orders ??
|
||||
0);
|
||||
|
||||
data.allocated =
|
||||
(partRequirements?.allocated_to_build_orders ??
|
||||
part?.allocated_to_build_orders ??
|
||||
0) +
|
||||
(partRequirements?.allocated_to_sales_orders ??
|
||||
part?.allocated_to_sales_orders ??
|
||||
0);
|
||||
|
||||
// Extract requirements data
|
||||
data.can_build = partRequirements?.can_build ?? 0;
|
||||
|
||||
// Provide latest serial number info
|
||||
if (!!serials.latest) {
|
||||
@ -315,13 +338,6 @@ export default function PartDetail() {
|
||||
(part.required_for_sales_orders <= 0 &&
|
||||
part.allocated_to_sales_orders <= 0)
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'can_build',
|
||||
unit: true,
|
||||
label: t`Can Build`,
|
||||
hidden: true // TODO: Expose "can_build" to the API
|
||||
},
|
||||
{
|
||||
type: 'progressbar',
|
||||
name: 'building',
|
||||
@ -329,6 +345,13 @@ export default function PartDetail() {
|
||||
progress: part.building,
|
||||
total: part.scheduled_to_build,
|
||||
hidden: !part.assembly || (!part.building && !part.scheduled_to_build)
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'can_build',
|
||||
unit: part.units,
|
||||
label: t`Can Build`,
|
||||
hidden: !part.assembly || partRequirementsQuery.isFetching
|
||||
}
|
||||
];
|
||||
|
||||
@ -489,7 +512,9 @@ export default function PartDetail() {
|
||||
id,
|
||||
serials,
|
||||
instanceQuery.isFetching,
|
||||
instanceQuery.data
|
||||
instanceQuery.data,
|
||||
partRequirementsQuery.isFetching,
|
||||
partRequirements
|
||||
]);
|
||||
|
||||
// Part data panels (recalculate when part data changes)
|
||||
|
@ -162,6 +162,26 @@ test('Parts - Locking', async ({ browser }) => {
|
||||
await page.getByText('Part parameters cannot be').waitFor();
|
||||
});
|
||||
|
||||
test('Parts - Details', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/113/details' });
|
||||
|
||||
// Check for expected values on this page
|
||||
await page.getByText('Required for Orders').waitFor();
|
||||
await page.getByText('Allocated to Sales Orders').waitFor();
|
||||
await page.getByText('Can Build').waitFor();
|
||||
|
||||
await page.getByText('0 / 10').waitFor();
|
||||
await page.getByText('4 / 49').waitFor();
|
||||
|
||||
// Badges
|
||||
await page.getByText('Required: 10').waitFor();
|
||||
await page.getByText('No Stock').waitFor();
|
||||
await page.getByText('In Production: 4').waitFor();
|
||||
|
||||
await page.getByText('Creation Date').waitFor();
|
||||
await page.getByText('2022-04-29').waitFor();
|
||||
});
|
||||
|
||||
test('Parts - Allocations', async ({ browser }) => {
|
||||
// Let's look at the allocations for a single stock item
|
||||
const page = await doCachedLogin(browser, { url: 'stock/item/324/' });
|
||||
|
Reference in New Issue
Block a user