2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-17 12:35:46 +00:00

[PUI] Sales order actions (#7837)

* Create build order from sales order table

* Allow creation of child build order from build page

* Add production and purcahse order quantitres to sales order item serializer

* Bump API version

* Fix playwright test
This commit is contained in:
Oliver
2024-08-08 20:01:56 +10:00
committed by GitHub
parent a5564090bb
commit 21f623eea8
10 changed files with 173 additions and 80 deletions

View File

@ -247,11 +247,7 @@ export default function BuildDetail() {
label: t`Line Items`,
icon: <IconListNumbers />,
content: build?.pk ? (
<BuildLineTable
params={{
build: id
}}
/>
<BuildLineTable buildId={build.pk} />
) : (
<Skeleton />
)

View File

@ -543,7 +543,7 @@ export default function PartDetail() {
label: t`Variants`,
icon: <IconVersions />,
hidden: !part.is_template,
content: <PartVariantTable partId={String(id)} />
content: <PartVariantTable part={part} />
},
{
name: 'allocations',

View File

@ -5,11 +5,14 @@ import {
IconShoppingCart,
IconTool
} from '@tabler/icons-react';
import { useCallback, useMemo } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { ProgressBar } from '../../components/items/ProgressBar';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useBuildOrderFields } from '../../forms/BuildForms';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
@ -19,7 +22,13 @@ import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
import { TableHoverCard } from '../TableHoverCard';
export default function BuildLineTable({ params = {} }: { params?: any }) {
export default function BuildLineTable({
buildId,
params = {}
}: {
buildId: number;
params?: any;
}) {
const table = useTable('buildline');
const user = useUserState();
@ -213,6 +222,19 @@ export default function BuildLineTable({ params = {} }: { params?: any }) {
];
}, []);
const buildOrderFields = useBuildOrderFields({ create: true });
const [initialData, setInitialData] = useState<any>({});
const newBuildOrder = useCreateApiFormModal({
url: ApiEndpoints.build_order_list,
title: t`Create Build Order`,
fields: buildOrderFields,
initialData: initialData,
follow: true,
modelType: ModelType.build
});
const rowActions = useCallback(
(record: any) => {
let part = record.part_detail;
@ -243,8 +265,16 @@ export default function BuildLineTable({ params = {} }: { params?: any }) {
{
icon: <IconTool />,
title: t`Build Stock`,
hidden: !part?.assembly,
color: 'blue'
hidden: !part?.assembly || !user.hasAddRole(UserRoles.build),
color: 'blue',
onClick: () => {
setInitialData({
part: record.part,
parent: buildId,
quantity: record.quantity - record.allocated
});
newBuildOrder.open();
}
}
];
},
@ -252,21 +282,25 @@ export default function BuildLineTable({ params = {} }: { params?: any }) {
);
return (
<InvenTreeTable
url={apiUrl(ApiEndpoints.build_line_list)}
tableState={table}
columns={tableColumns}
props={{
params: {
...params,
part_detail: true
},
tableFilters: tableFilters,
rowActions: rowActions,
modelType: ModelType.part,
modelField: 'part_detail.pk',
enableDownload: true
}}
/>
<>
{newBuildOrder.modal}
<InvenTreeTable
url={apiUrl(ApiEndpoints.build_line_list)}
tableState={table}
columns={tableColumns}
props={{
params: {
...params,
build: buildId,
part_detail: true
},
tableFilters: tableFilters,
rowActions: rowActions,
modelType: ModelType.part,
modelField: 'part_detail.pk',
enableDownload: true
}}
/>
</>
);
}

View File

@ -303,20 +303,28 @@ function partTableFilters(): TableFilter[] {
* @param {Object} params - The query parameters to pass to the API
* @returns
*/
export function PartListTable({ props }: { props: InvenTreeTableProps }) {
export function PartListTable({
props,
defaultPartData
}: {
props: InvenTreeTableProps;
defaultPartData?: any;
}) {
const tableColumns = useMemo(() => partTableColumns(), []);
const tableFilters = useMemo(() => partTableFilters(), []);
const table = useTable('part-list');
const user = useUserState();
const initialPartData = useMemo(() => {
return defaultPartData ?? props.params ?? {};
}, [defaultPartData, props.params]);
const newPart = useCreateApiFormModal({
url: ApiEndpoints.part_list,
title: t`Add Part`,
fields: usePartFields({ create: true }),
initialData: {
...(props.params ?? {})
},
initialData: initialPartData,
follow: true,
modelType: ModelType.part
});

View File

@ -7,7 +7,7 @@ import { PartListTable } from './PartTable';
/**
* Display variant parts for the specified parent part
*/
export function PartVariantTable({ partId }: { partId: string }) {
export function PartVariantTable({ part }: { part: any }) {
const tableFilters: TableFilter[] = useMemo(() => {
return [
{
@ -39,9 +39,14 @@ export function PartVariantTable({ partId }: { partId: string }) {
enableDownload: false,
tableFilters: tableFilters,
params: {
ancestor: partId
ancestor: part.pk
}
}}
defaultPartData={{
...part,
variant_of: part.pk,
is_template: false
}}
/>
);
}

View File

@ -13,6 +13,7 @@ import { formatCurrency } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useBuildOrderFields } from '../../forms/BuildForms';
import { useSalesOrderLineItemFields } from '../../forms/SalesOrderForms';
import {
useCreateApiFormModal,
@ -122,6 +123,22 @@ export default function SalesOrderLineItemTable({
extra.push(<Text size="sm">{t`Includes variant stock`}</Text>);
}
if (record.building > 0) {
extra.push(
<Text size="sm">
{t`In production`}: {record.building}
</Text>
);
}
if (record.on_order > 0) {
extra.push(
<Text size="sm">
{t`On order`}: {record.on_order}
</Text>
);
}
return (
<TableHoverCard
value={<Text color={color}>{text}</Text>}
@ -199,6 +216,17 @@ export default function SalesOrderLineItemTable({
table: table
});
const buildOrderFields = useBuildOrderFields({ create: true });
const newBuildOrder = useCreateApiFormModal({
url: ApiEndpoints.build_order_list,
title: t`Create Build Order`,
fields: buildOrderFields,
initialData: initialData,
follow: true,
modelType: ModelType.build
});
const tableActions = useMemo(() => {
return [
<AddItemButton
@ -235,7 +263,15 @@ export default function SalesOrderLineItemTable({
!record?.part_detail?.assembly,
title: t`Build stock`,
icon: <IconTools />,
color: 'blue'
color: 'blue',
onClick: () => {
setInitialData({
part: record.part,
quantity: (record?.quantity ?? 1) - (record?.allocated ?? 0),
sales_order: orderId
});
newBuildOrder.open();
}
},
{
hidden:
@ -277,6 +313,7 @@ export default function SalesOrderLineItemTable({
{editLine.modal}
{deleteLine.modal}
{newLine.modal}
{newBuildOrder.modal}
<InvenTreeTable
url={apiUrl(ApiEndpoints.sales_order_line_list)}
tableState={table}

View File

@ -11,7 +11,7 @@ test('PUI - Pages - Build Order', async ({ page }) => {
await page.getByRole('tab', { name: 'Build', exact: true }).click();
// We have now loaded the "Build Order" table. Check for some expected texts
await page.getByText('On Hold').waitFor();
await page.getByText('On Hold').first().waitFor();
await page.getByText('Pending').first().waitFor();
// Load a particular build order