diff --git a/src/frontend/src/components/buttons/RemoveRowButton.tsx b/src/frontend/src/components/buttons/RemoveRowButton.tsx
index 69bf48130d..c0b549e969 100644
--- a/src/frontend/src/components/buttons/RemoveRowButton.tsx
+++ b/src/frontend/src/components/buttons/RemoveRowButton.tsx
@@ -1,21 +1,24 @@
import { t } from '@lingui/core/macro';
import { ActionButton } from '@lib/components/ActionButton';
+import type { FloatingPosition } from '@mantine/core';
import { InvenTreeIcon } from '../../functions/icons';
export default function RemoveRowButton({
onClick,
- tooltip = t`Remove this row`
+ tooltip = t`Remove this row`,
+ tooltipAlignment
}: Readonly<{
onClick: () => void;
tooltip?: string;
+ tooltipAlignment?: FloatingPosition;
}>) {
return (
}
tooltip={tooltip}
- tooltipAlignment='top-end'
+ tooltipAlignment={tooltipAlignment ?? 'top-end'}
color='red'
/>
);
diff --git a/src/frontend/src/components/render/Company.tsx b/src/frontend/src/components/render/Company.tsx
index ccedfb197e..96dbcd40ce 100644
--- a/src/frontend/src/components/render/Company.tsx
+++ b/src/frontend/src/components/render/Company.tsx
@@ -80,7 +80,7 @@ export function RenderSupplierPart(
const part = instance.part_detail ?? {};
const secondary: string = instance.SKU;
- let suffix: string = part.full_name;
+ let suffix: string = part?.full_name ?? '';
if (instance.pack_quantity) {
suffix += ` (${instance.pack_quantity})`;
diff --git a/src/frontend/src/components/wizards/OrderPartsWizard.tsx b/src/frontend/src/components/wizards/OrderPartsWizard.tsx
index d04f4a302c..ecb53fa739 100644
--- a/src/frontend/src/components/wizards/OrderPartsWizard.tsx
+++ b/src/frontend/src/components/wizards/OrderPartsWizard.tsx
@@ -3,22 +3,163 @@ import { AddItemButton } from '@lib/components/AddItemButton';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
+import { formatDecimal } from '@lib/functions/Formatting';
import type { ApiFormFieldSet } from '@lib/types/Forms';
import { t } from '@lingui/core/macro';
-import { Alert, Group, Paper, Tooltip } from '@mantine/core';
+import {
+ ActionIcon,
+ Alert,
+ Divider,
+ Group,
+ HoverCard,
+ Loader,
+ Paper,
+ Stack,
+ Text,
+ Tooltip
+} from '@mantine/core';
import { showNotification } from '@mantine/notifications';
-import { IconShoppingCart } from '@tabler/icons-react';
+import {
+ IconExclamationCircle,
+ IconInfoCircle,
+ IconShoppingCart
+} from '@tabler/icons-react';
import { DataTable } from 'mantine-datatable';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSupplierPartFields } from '../../forms/CompanyForms';
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
import { useCreateApiFormModal } from '../../hooks/UseForm';
+import { useInstance } from '../../hooks/UseInstance';
import useWizard from '../../hooks/UseWizard';
import { RenderPartColumn } from '../../tables/ColumnRenderers';
import RemoveRowButton from '../buttons/RemoveRowButton';
import { StandaloneField } from '../forms/StandaloneField';
import Expand from '../items/Expand';
+/**
+ * Render the "requirements" info for a part
+ * This fetches the information dynamically from the API
+ */
+function PartRequirementsInfo({
+ partId,
+ onQuantityChange
+}: {
+ partId: number | string;
+ onQuantityChange?: (quantity: number) => void;
+}) {
+ const [requiredQuantity, setRequiredQuantity] = useState(0);
+
+ // Notify parent component of quantity change
+ useEffect(() => {
+ onQuantityChange?.(requiredQuantity);
+ }, [requiredQuantity]);
+
+ const requirements = useInstance({
+ endpoint: ApiEndpoints.part_requirements,
+ pk: partId,
+ hasPrimaryKey: true,
+ defaultValue: {}
+ });
+
+ const widget = useMemo(() => {
+ if (
+ requirements.instanceQuery.isFetching ||
+ requirements.instanceQuery.isLoading
+ ) {
+ return ;
+ }
+
+ if (requirements.instanceQuery.isError) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ // Calculate the total requirements
+ const buildRequirements =
+ requirements.instance?.required_for_build_orders || 0;
+ const salesRequirements =
+ requirements.instance?.required_for_sales_orders || 0;
+ const totalRequirements = buildRequirements + salesRequirements;
+
+ const building = requirements.instance?.building || 0;
+ const ordering = requirements.instance?.ordering || 0;
+ const incoming = building + ordering;
+
+ const inStock = requirements.instance?.total_stock || 0;
+
+ const required = Math.max(0, totalRequirements - inStock - incoming);
+
+ setRequiredQuantity(required);
+
+ return (
+
+
+ 0 ? 'blue' : 'green'}
+ size='sm'
+ >
+
+
+
+
+
+ {t`Requirements`}
+
+ {buildRequirements > 0 && (
+
+ {t`Build Requirements`}
+ {formatDecimal(buildRequirements)}
+
+ )}
+ {salesRequirements > 0 && (
+
+ {t`Sales Requirements`}
+ {formatDecimal(salesRequirements)}
+
+ )}
+ {inStock > 0 && (
+
+ {t`In Stock`}
+ {formatDecimal(inStock)}
+
+ )}
+ {ordering > 0 && (
+
+ {t`On Order`}
+ {formatDecimal(ordering)}
+
+ )}
+ {building > 0 && (
+
+ {t`In Production`}
+ {formatDecimal(building)}
+
+ )}
+
+ {t`Required Quantity`}
+ {formatDecimal(required)}
+
+
+
+
+ );
+ }, [
+ requirements.instanceQuery.isFetching,
+ requirements.instanceQuery.isLoading,
+ requirements.instanceQuery.isError,
+ requirements.instance,
+ setRequiredQuantity
+ ]);
+
+ return widget;
+}
+
/**
* Attributes for each selected part
* - part: The part instance
@@ -123,7 +264,10 @@ function SelectPartsStep({
width: '1%',
render: (record: PartOrderRecord) => (
- onRemovePart(record.part)} />
+ onRemovePart(record.part)}
+ />
)
},
@@ -162,6 +306,7 @@ function SelectPartsStep({
filters: {
part: record.part.pk,
active: true,
+ part_detail: true,
supplier_detail: true
}
}}
@@ -169,7 +314,7 @@ function SelectPartsStep({
{
setSelectedRecord(record);
newSupplierPart.open();
@@ -207,7 +352,7 @@ function SelectPartsStep({
{
setSelectedRecord(record);
@@ -220,21 +365,29 @@ function SelectPartsStep({
{
accessor: 'quantity',
title: t`Quantity`,
- width: 125,
+ width: 150,
render: (record: PartOrderRecord) => (
- {
- onSelectQuantity(record.part.pk, value);
+
+ {
+ onSelectQuantity(record.part.pk, value);
+ }
+ }}
+ />
+
+ onSelectQuantity(record.part.pk, quantity)
}
- }}
- />
+ />
+
)
},
{
@@ -255,7 +408,7 @@ function SelectPartsStep({
}
icon={}
tooltip={t`Add to selected purchase order`}
- tooltipAlignment='top'
+ tooltipAlignment='top-end'
color='blue'
/>
diff --git a/src/frontend/src/tables/build/BuildLineTable.tsx b/src/frontend/src/tables/build/BuildLineTable.tsx
index 25a506c644..4d95f85075 100644
--- a/src/frontend/src/tables/build/BuildLineTable.tsx
+++ b/src/frontend/src/tables/build/BuildLineTable.tsx
@@ -870,8 +870,6 @@ export default function BuildLineTable({
*/
const formatRecords = useCallback(
(records: any[]): any[] => {
- console.log('format records:', records);
-
return records.map((record) => {
let allocations = [...record.allocations];