2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-23 15:20:55 +00:00

Adds "active" field for Company model ()

* Add "active" field to Company model

* Expose 'active' parameter to API

* Fix default value

* Add 'active' column to PUI

* Update PUI table

* Update company detail pages

* Update API filters for SupplierPart and ManufacturerPart

* Bump API version

* Update order forms

* Add edit action to SalesOrderDetail page

* Enable editing of ReturnOrder

* Typo fix

* Adds explicit "active" field to SupplierPart model

* More updates

- Add "inactive" badge to SupplierPart page
- Update SupplierPartTable
- Update backend API fields

* Update ReturnOrderTable

- Also some refactoring

* Impove usePurchaseOrderLineItemFields hook

* Cleanup

* Implement duplicate action for SupplierPart

* Fix for ApiForm

- Only override initialValues for specified fields

* Allow edit and duplicate of StockItem

* Fix for ApiForm

- Default values were overriding initial data

* Add duplicate part option

* Cleanup ApiForm

- Cache props.fields

* Fix unused import

* More fixes

* Add unit tests

* Allow ordering company by 'active' status

* Update docs

* Merge migrations

* Fix for serializers.py

* Force new form value

* Remove debug call

* Further unit test fixes

* Update default CSRF_TRUSTED_ORIGINS values

* Reduce debug output
This commit is contained in:
Oliver
2024-04-20 23:18:25 +10:00
committed by GitHub
parent 2632bcfbbc
commit 2fe0eefa8f
41 changed files with 927 additions and 390 deletions

@ -107,6 +107,8 @@ export function OptionsApiForm({
const optionsQuery = useQuery({
enabled: true,
refetchOnMount: false,
refetchOnWindowFocus: false,
queryKey: [
'form-options-data',
id,
@ -181,21 +183,26 @@ export function ApiForm({
props: ApiFormProps;
optionsLoading: boolean;
}) {
const fields: ApiFormFieldSet = useMemo(() => {
return props.fields ?? {};
}, [props.fields]);
const defaultValues: FieldValues = useMemo(() => {
let defaultValuesMap = mapFields(props.fields ?? {}, (_path, field) => {
let defaultValuesMap = mapFields(fields ?? {}, (_path, field) => {
return field.value ?? field.default ?? undefined;
});
// If the user has specified initial data, use that instead
// If the user has specified initial data, that overrides default values
// But, *only* for the fields we have specified
if (props.initialData) {
defaultValuesMap = {
...defaultValuesMap,
...props.initialData
};
Object.keys(props.initialData).map((key) => {
if (key in defaultValuesMap) {
defaultValuesMap[key] =
props?.initialData?.[key] ?? defaultValuesMap[key];
}
});
}
// Update the form values, but only for the fields specified for this form
return defaultValuesMap;
}, [props.fields, props.initialData]);
@ -260,14 +267,22 @@ export function ApiForm({
};
// Process API response
const initialData: any = processFields(
props.fields ?? {},
response.data
);
const initialData: any = processFields(fields, response.data);
// Update form values, but only for the fields specified for this form
form.reset(initialData);
// Update the field references, too
Object.keys(fields).forEach((fieldName) => {
if (fieldName in initialData) {
let field = fields[fieldName] ?? {};
fields[fieldName] = {
...field,
value: initialData[fieldName]
};
}
});
return response;
} catch (error) {
console.error('Error fetching initial data:', error);
@ -301,12 +316,12 @@ export function ApiForm({
initialDataQuery.isFetching ||
optionsLoading ||
isSubmitting ||
!props.fields,
!fields,
[
isFormLoading,
initialDataQuery.isFetching,
isSubmitting,
props.fields,
fields,
optionsLoading
]
);
@ -319,7 +334,7 @@ export function ApiForm({
if (!focusField) {
// If a focus field is not specified, then focus on the first available field
Object.entries(props.fields ?? {}).forEach(([fieldName, field]) => {
Object.entries(fields).forEach(([fieldName, field]) => {
if (focusField || field.read_only || field.disabled || field.hidden) {
return;
}
@ -334,7 +349,7 @@ export function ApiForm({
form.setFocus(focusField);
setInitialFocus(focusField);
}, [props.focus, props.fields, form.setFocus, isLoading, initialFocus]);
}, [props.focus, fields, form.setFocus, isLoading, initialFocus]);
const submitForm: SubmitHandler<FieldValues> = async (data) => {
setNonFieldErrors([]);
@ -342,7 +357,7 @@ export function ApiForm({
let method = props.method?.toLowerCase() ?? 'get';
let hasFiles = false;
mapFields(props.fields ?? {}, (_path, field) => {
mapFields(fields, (_path, field) => {
if (field.field_type === 'file upload') {
hasFiles = true;
}
@ -474,16 +489,14 @@ export function ApiForm({
<FormProvider {...form}>
<Stack spacing="xs">
{!optionsLoading &&
Object.entries(props.fields ?? {}).map(
([fieldName, field]) => (
<ApiFormField
key={fieldName}
fieldName={fieldName}
definition={field}
control={form.control}
/>
)
)}
Object.entries(fields).map(([fieldName, field]) => (
<ApiFormField
key={fieldName}
fieldName={fieldName}
definition={field}
control={form.control}
/>
))}
</Stack>
</FormProvider>
{props.postFormContent}

@ -15,6 +15,7 @@ import { useMemo } from 'react';
import { Control, FieldValues, useController } from 'react-hook-form';
import { ModelType } from '../../../enums/ModelType';
import { isTrue } from '../../../functions/conversion';
import { ChoiceField } from './ChoiceField';
import DateField from './DateField';
import { NestedObjectField } from './NestedObjectField';
@ -210,7 +211,7 @@ export function ApiFormField({
id={fieldId}
radius="lg"
size="sm"
checked={value ?? false}
checked={isTrue(value)}
error={error?.message}
onChange={(event) => onChange(event.currentTarget.checked)}
/>

@ -74,13 +74,7 @@ export const StatusRenderer = ({
}) => {
const statusCodeList = useGlobalStatusState.getState().status;
if (status === undefined) {
console.log('StatusRenderer: status is undefined');
return null;
}
if (statusCodeList === undefined) {
console.log('StatusRenderer: statusCodeList is undefined');
if (status === undefined || statusCodeList === undefined) {
return null;
}

@ -7,57 +7,64 @@ import {
IconUser,
IconUsersGroup
} from '@tabler/icons-react';
import { useMemo } from 'react';
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
/**
* Field set for BuildOrder forms
*/
export function buildOrderFields(): ApiFormFieldSet {
return {
reference: {},
part: {
filters: {
assembly: true,
virtual: false
export function useBuildOrderFields({
create
}: {
create: boolean;
}): ApiFormFieldSet {
return useMemo(() => {
return {
reference: {},
part: {
filters: {
assembly: true,
virtual: false
}
},
title: {},
quantity: {},
project_code: {
icon: <IconList />
},
priority: {},
parent: {
icon: <IconSitemap />,
filters: {
part_detail: true
}
},
sales_order: {
icon: <IconTruckDelivery />
},
batch: {},
target_date: {
icon: <IconCalendar />
},
take_from: {},
destination: {
filters: {
structural: false
}
},
link: {
icon: <IconLink />
},
issued_by: {
icon: <IconUser />
},
responsible: {
icon: <IconUsersGroup />,
filters: {
is_active: true
}
}
},
title: {},
quantity: {},
project_code: {
icon: <IconList />
},
priority: {},
parent: {
icon: <IconSitemap />,
filters: {
part_detail: true
}
},
sales_order: {
icon: <IconTruckDelivery />
},
batch: {},
target_date: {
icon: <IconCalendar />
},
take_from: {},
destination: {
filters: {
structural: false
}
},
link: {
icon: <IconLink />
},
issued_by: {
icon: <IconUser />
},
responsible: {
icon: <IconUsersGroup />,
filters: {
is_active: true
}
}
};
};
}, [create]);
}

@ -10,34 +10,21 @@ import {
} from '@tabler/icons-react';
import { useEffect, useMemo, useState } from 'react';
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
import {
ApiFormAdjustFilterType,
ApiFormFieldSet
} from '../components/forms/fields/ApiFormField';
/**
* Field set for SupplierPart instance
*/
export function useSupplierPartFields({
partPk,
supplierPk,
hidePart
}: {
partPk?: number;
supplierPk?: number;
hidePart?: boolean;
}) {
const [part, setPart] = useState<number | undefined>(partPk);
useEffect(() => {
setPart(partPk);
}, [partPk]);
export function useSupplierPartFields() {
return useMemo(() => {
const fields: ApiFormFieldSet = {
part: {
hidden: hidePart,
value: part,
onValueChange: setPart,
filters: {
purchaseable: true
purchaseable: true,
active: true
}
},
manufacturer_part: {
@ -45,15 +32,18 @@ export function useSupplierPartFields({
part_detail: true,
manufacturer_detail: true
},
adjustFilters: (filters: any) => {
if (part) {
filters.part = part;
}
return filters;
adjustFilters: (adjust: ApiFormAdjustFilterType) => {
return {
...adjust.filters,
part: adjust.data.part
};
}
},
supplier: {
filters: {
active: true
}
},
supplier: {},
SKU: {
icon: <IconHash />
},
@ -67,15 +57,12 @@ export function useSupplierPartFields({
pack_quantity: {},
packaging: {
icon: <IconPackage />
}
},
active: {}
};
if (supplierPk !== undefined) {
fields.supplier.value = supplierPk;
}
return fields;
}, [part]);
}, []);
}
export function useManufacturerPartFields() {
@ -125,6 +112,7 @@ export function companyFields(): ApiFormFieldSet {
},
is_supplier: {},
is_manufacturer: {},
is_customer: {}
is_customer: {},
active: {}
};
}

@ -37,8 +37,12 @@ import { apiUrl } from '../states/ApiState';
* Construct a set of fields for creating / editing a PurchaseOrderLineItem instance
*/
export function usePurchaseOrderLineItemFields({
supplierId,
orderId,
create
}: {
supplierId?: number;
orderId?: number;
create?: boolean;
}) {
const [purchasePrice, setPurchasePrice] = useState<string>('');
@ -60,16 +64,20 @@ export function usePurchaseOrderLineItemFields({
filters: {
supplier_detail: true
},
hidden: true
disabled: true
},
part: {
filters: {
part_detail: true,
supplier_detail: true
supplier_detail: true,
active: true,
part_active: true
},
adjustFilters: (value: ApiFormAdjustFilterType) => {
// TODO: Adjust part based on the supplier associated with the supplier
return value.filters;
adjustFilters: (adjust: ApiFormAdjustFilterType) => {
return {
...adjust.filters,
supplier: supplierId
};
}
},
quantity: {},
@ -105,7 +113,7 @@ export function usePurchaseOrderLineItemFields({
}
return fields;
}, [create, autoPricing, purchasePrice]);
}, [create, orderId, supplierId, autoPricing, purchasePrice]);
return fields;
}
@ -113,50 +121,53 @@ export function usePurchaseOrderLineItemFields({
/**
* Construct a set of fields for creating / editing a PurchaseOrder instance
*/
export function purchaseOrderFields(): ApiFormFieldSet {
return {
reference: {
icon: <IconHash />
},
description: {},
supplier: {
filters: {
is_supplier: true
export function usePurchaseOrderFields(): ApiFormFieldSet {
return useMemo(() => {
return {
reference: {
icon: <IconHash />
},
description: {},
supplier: {
filters: {
is_supplier: true,
active: true
}
},
supplier_reference: {},
project_code: {
icon: <IconList />
},
order_currency: {
icon: <IconCoins />
},
target_date: {
icon: <IconCalendar />
},
link: {},
contact: {
icon: <IconUser />,
adjustFilters: (value: ApiFormAdjustFilterType) => {
return {
...value.filters,
company: value.data.supplier
};
}
},
address: {
icon: <IconAddressBook />,
adjustFilters: (value: ApiFormAdjustFilterType) => {
return {
...value.filters,
company: value.data.supplier
};
}
},
responsible: {
icon: <IconUsers />
}
},
supplier_reference: {},
project_code: {
icon: <IconList />
},
order_currency: {
icon: <IconCoins />
},
target_date: {
icon: <IconCalendar />
},
link: {},
contact: {
icon: <IconUser />,
adjustFilters: (value: ApiFormAdjustFilterType) => {
return {
...value.filters,
company: value.data.supplier
};
}
},
address: {
icon: <IconAddressBook />,
adjustFilters: (value: ApiFormAdjustFilterType) => {
return {
...value.filters,
company: value.data.supplier
};
}
},
responsible: {
icon: <IconUsers />
}
};
};
}, []);
}
/**

@ -1,44 +1,89 @@
import { IconAddressBook, IconUser, IconUsers } from '@tabler/icons-react';
import { useMemo } from 'react';
import {
ApiFormAdjustFilterType,
ApiFormFieldSet
} from '../components/forms/fields/ApiFormField';
export function salesOrderFields(): ApiFormFieldSet {
return {
reference: {},
description: {},
customer: {
filters: {
is_customer: true
export function useSalesOrderFields(): ApiFormFieldSet {
return useMemo(() => {
return {
reference: {},
description: {},
customer: {
filters: {
is_customer: true,
active: true
}
},
customer_reference: {},
project_code: {},
order_currency: {},
target_date: {},
link: {},
contact: {
icon: <IconUser />,
adjustFilters: (value: ApiFormAdjustFilterType) => {
return {
...value.filters,
company: value.data.customer
};
}
},
address: {
icon: <IconAddressBook />,
adjustFilters: (value: ApiFormAdjustFilterType) => {
return {
...value.filters,
company: value.data.customer
};
}
},
responsible: {
icon: <IconUsers />
}
},
customer_reference: {},
project_code: {},
order_currency: {},
target_date: {},
link: {},
contact: {
icon: <IconUser />,
adjustFilters: (value: ApiFormAdjustFilterType) => {
return {
...value.filters,
company: value.data.customer
};
}
},
address: {
icon: <IconAddressBook />,
adjustFilters: (value: ApiFormAdjustFilterType) => {
return {
...value.filters,
company: value.data.customer
};
}
},
responsible: {
icon: <IconUsers />
}
};
};
}, []);
}
export function useReturnOrderFields(): ApiFormFieldSet {
return useMemo(() => {
return {
reference: {},
description: {},
customer: {
filters: {
is_customer: true,
active: true
}
},
customer_reference: {},
project_code: {},
order_currency: {},
target_date: {},
link: {},
contact: {
icon: <IconUser />,
adjustFilters: (value: ApiFormAdjustFilterType) => {
return {
...value.filters,
company: value.data.customer
};
}
},
address: {
icon: <IconAddressBook />,
adjustFilters: (value: ApiFormAdjustFilterType) => {
return {
...value.filters,
company: value.data.customer
};
}
},
responsible: {
icon: <IconUsers />
}
};
}, []);
}

@ -39,7 +39,7 @@ export function useStockFields({
const fields: ApiFormFieldSet = {
part: {
value: part,
hidden: !create,
disabled: !create,
onValueChange: (change) => {
setPart(change);
// TODO: implement remaining functionality from old stock.py
@ -57,12 +57,12 @@ export function useStockFields({
supplier_detail: true,
...(part ? { part } : {})
},
adjustFilters: (value: ApiFormAdjustFilterType) => {
if (value.data.part) {
value.filters['part'] = value.data.part;
adjustFilters: (adjust: ApiFormAdjustFilterType) => {
if (adjust.data.part) {
adjust.filters['part'] = adjust.data.part;
}
return value.filters;
return adjust.filters;
}
},
use_pack_size: {
@ -137,29 +137,6 @@ export function useCreateStockItem() {
});
}
/**
* Launch a form to edit an existing StockItem instance
* @param item : primary key of the StockItem to edit
*/
export function useEditStockItem({
item_id,
callback
}: {
item_id: number;
callback?: () => void;
}) {
const fields = useStockFields({ create: false });
return useEditApiFormModal({
url: ApiEndpoints.stock_item_list,
pk: item_id,
fields: fields,
title: t`Edit Stock Item`,
successMessage: t`Stock item updated`,
onFormSuccess: callback
});
}
function StockItemDefaultMove({
stockItem,
value

@ -35,8 +35,7 @@ import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { buildOrderFields } from '../../forms/BuildForms';
import { partCategoryFields } from '../../forms/PartForms';
import { useBuildOrderFields } from '../../forms/BuildForms';
import { useEditApiFormModal } from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { apiUrl } from '../../states/ApiState';
@ -280,11 +279,13 @@ export default function BuildDetail() {
];
}, [build, id]);
const buildOrderFields = useBuildOrderFields({ create: false });
const editBuild = useEditApiFormModal({
url: ApiEndpoints.build_order_list,
pk: build.pk,
title: t`Edit Build Order`,
fields: buildOrderFields(),
fields: buildOrderFields,
onFormSuccess: () => {
refreshInstance();
}

@ -15,10 +15,11 @@ import {
IconTruckReturn,
IconUsersGroup
} from '@tabler/icons-react';
import { useMemo } from 'react';
import { ReactNode, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { DetailsField, DetailsTable } from '../../components/details/Details';
import DetailsBadge from '../../components/details/DetailsBadge';
import { DetailsImage } from '../../components/details/DetailsImage';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
@ -293,6 +294,12 @@ export default function CompanyDetail(props: CompanyDetailProps) {
];
}, [id, company, user]);
const badges: ReactNode[] = useMemo(() => {
return [
<DetailsBadge label={t`Inactive`} color="red" visible={!company.active} />
];
}, [company]);
return (
<>
{editCompany.modal}
@ -304,6 +311,7 @@ export default function CompanyDetail(props: CompanyDetailProps) {
actions={companyActions}
imageUrl={company.image}
breadcrumbs={props.breadcrumbs}
badges={badges}
/>
<PanelGroup pageKey="company" panels={companyPanels} />
</Stack>

@ -7,10 +7,11 @@ import {
IconPackages,
IconShoppingCart
} from '@tabler/icons-react';
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { ReactNode, useMemo } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { DetailsField, DetailsTable } from '../../components/details/Details';
import DetailsBadge from '../../components/details/DetailsBadge';
import { DetailsImage } from '../../components/details/DetailsImage';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
@ -25,7 +26,11 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useSupplierPartFields } from '../../forms/CompanyForms';
import { useEditApiFormModal } from '../../hooks/UseForm';
import { getDetailUrl } from '../../functions/urls';
import {
useCreateApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
@ -38,6 +43,8 @@ export default function SupplierPartDetail() {
const user = useUserState();
const navigate = useNavigate();
const {
instance: supplierPart,
instanceQuery,
@ -245,7 +252,8 @@ export default function SupplierPartDetail() {
icon={<IconDots />}
actions={[
DuplicateItemAction({
hidden: !user.hasAddRole(UserRoles.purchase_order)
hidden: !user.hasAddRole(UserRoles.purchase_order),
onClick: () => duplicateSupplierPart.open()
}),
EditItemAction({
hidden: !user.hasChangeRole(UserRoles.purchase_order),
@ -259,19 +267,30 @@ export default function SupplierPartDetail() {
];
}, [user]);
const editSupplierPartFields = useSupplierPartFields({
hidePart: true,
partPk: supplierPart?.pk
});
const supplierPartFields = useSupplierPartFields();
const editSuppliertPart = useEditApiFormModal({
url: ApiEndpoints.supplier_part_list,
pk: supplierPart?.pk,
title: t`Edit Supplier Part`,
fields: editSupplierPartFields,
fields: supplierPartFields,
onFormSuccess: refreshInstance
});
const duplicateSupplierPart = useCreateApiFormModal({
url: ApiEndpoints.supplier_part_list,
title: t`Add Supplier Part`,
fields: supplierPartFields,
initialData: {
...supplierPart
},
onFormSuccess: (response: any) => {
if (response.pk) {
navigate(getDetailUrl(ModelType.supplierpart, response.pk));
}
}
});
const breadcrumbs = useMemo(() => {
return [
{
@ -285,15 +304,27 @@ export default function SupplierPartDetail() {
];
}, [supplierPart]);
const badges: ReactNode[] = useMemo(() => {
return [
<DetailsBadge
label={t`Inactive`}
color="red"
visible={!supplierPart.active}
/>
];
}, [supplierPart]);
return (
<>
{editSuppliertPart.modal}
{duplicateSupplierPart.modal}
<Stack spacing="xs">
<LoadingOverlay visible={instanceQuery.isFetching} />
<PageDetail
title={t`Supplier Part`}
subtitle={`${supplierPart.SKU} - ${supplierPart?.part_detail?.name}`}
breadcrumbs={breadcrumbs}
badges={badges}
actions={supplierPartActions}
imageUrl={supplierPart?.part_detail?.thumbnail}
/>

@ -1,13 +1,5 @@
import { t } from '@lingui/macro';
import {
Badge,
Grid,
Group,
LoadingOverlay,
Skeleton,
Stack,
Text
} from '@mantine/core';
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
import {
IconBookmarks,
IconBuilding,
@ -32,13 +24,11 @@ import {
} from '@tabler/icons-react';
import { useSuspenseQuery } from '@tanstack/react-query';
import { ReactNode, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import { api } from '../../App';
import { DetailsField, DetailsTable } from '../../components/details/Details';
import DetailsBadge, {
DetailsBadgeProps
} from '../../components/details/DetailsBadge';
import DetailsBadge from '../../components/details/DetailsBadge';
import { DetailsImage } from '../../components/details/DetailsImage';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import { PartIcons } from '../../components/details/PartIcons';
@ -68,7 +58,10 @@ import {
} from '../../forms/StockForms';
import { InvenTreeIcon } from '../../functions/icons';
import { getDetailUrl } from '../../functions/urls';
import { useEditApiFormModal } from '../../hooks/UseForm';
import {
useCreateApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
@ -93,6 +86,7 @@ export default function PartDetail() {
const { id } = useParams();
const user = useUserState();
const navigate = useNavigate();
const [treeOpen, setTreeOpen] = useState(false);
@ -664,7 +658,8 @@ export default function PartDetail() {
label={t`In Production` + `: ${part.building}`}
color="blue"
visible={part.building > 0}
/>
/>,
<DetailsBadge label={t`Inactive`} color="red" visible={!part.active} />
];
}, [part, instanceQuery]);
@ -678,6 +673,20 @@ export default function PartDetail() {
onFormSuccess: refreshInstance
});
const duplicatePart = useCreateApiFormModal({
url: ApiEndpoints.part_list,
title: t`Add Part`,
fields: partFields,
initialData: {
...part
},
onFormSuccess: (response: any) => {
if (response.pk) {
navigate(getDetailUrl(ModelType.part, response.pk));
}
}
});
const stockActionProps: StockOperationProps = useMemo(() => {
return {
pk: part.pk,
@ -695,10 +704,10 @@ export default function PartDetail() {
actions={[
ViewBarcodeAction({}),
LinkBarcodeAction({
hidden: part?.barcode_hash
hidden: part?.barcode_hash || !user.hasChangeRole(UserRoles.part)
}),
UnlinkBarcodeAction({
hidden: !part?.barcode_hash
hidden: !part?.barcode_hash || !user.hasChangeRole(UserRoles.part)
})
]}
/>,
@ -737,7 +746,8 @@ export default function PartDetail() {
icon={<IconDots />}
actions={[
DuplicateItemAction({
hidden: !user.hasAddRole(UserRoles.part)
hidden: !user.hasAddRole(UserRoles.part),
onClick: () => duplicatePart.open()
}),
EditItemAction({
hidden: !user.hasChangeRole(UserRoles.part),
@ -753,6 +763,7 @@ export default function PartDetail() {
return (
<>
{duplicatePart.modal}
{editPart.modal}
<Stack spacing="xs">
<LoadingOverlay visible={instanceQuery.isFetching} />

@ -30,7 +30,7 @@ import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { purchaseOrderFields } from '../../forms/PurchaseOrderForms';
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
import { useEditApiFormModal } from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { apiUrl } from '../../states/ApiState';
@ -60,11 +60,13 @@ export default function PurchaseOrderDetail() {
refetchOnMount: true
});
const purchaseOrderFields = usePurchaseOrderFields();
const editPurchaseOrder = useEditApiFormModal({
url: ApiEndpoints.purchase_order_list,
pk: id,
title: t`Edit Purchase Order`,
fields: purchaseOrderFields(),
fields: purchaseOrderFields,
onFormSuccess: () => {
refreshInstance();
}
@ -227,7 +229,12 @@ export default function PurchaseOrderDetail() {
name: 'line-items',
label: t`Line Items`,
icon: <IconList />,
content: <PurchaseOrderLineItemTable orderId={Number(id)} />
content: (
<PurchaseOrderLineItemTable
orderId={Number(id)}
supplierId={Number(order.supplier)}
/>
)
},
{
name: 'received-stock',
@ -269,7 +276,6 @@ export default function PurchaseOrderDetail() {
}, [order, id]);
const poActions = useMemo(() => {
// TODO: Disable certain actions based on user permissions
return [
<BarcodeActionDropdown
actions={[
@ -288,11 +294,14 @@ export default function PurchaseOrderDetail() {
icon={<IconDots />}
actions={[
EditItemAction({
hidden: !user.hasChangeRole(UserRoles.purchase_order),
onClick: () => {
editPurchaseOrder.open();
}
}),
DeleteItemAction({})
DeleteItemAction({
hidden: !user.hasDeleteRole(UserRoles.purchase_order)
})
]}
/>
];

@ -1,6 +1,7 @@
import { t } from '@lingui/macro';
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
import {
IconDots,
IconInfoCircle,
IconList,
IconNotes,
@ -12,6 +13,11 @@ import { useParams } from 'react-router-dom';
import { DetailsField, DetailsTable } from '../../components/details/Details';
import { DetailsImage } from '../../components/details/DetailsImage';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
ActionDropdown,
DeleteItemAction,
EditItemAction
} from '../../components/items/ActionDropdown';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { StatusRenderer } from '../../components/render/StatusRenderer';
@ -19,8 +25,11 @@ import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
import { useEditApiFormModal } from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import { AttachmentTable } from '../../tables/general/AttachmentTable';
/**
@ -29,7 +38,13 @@ import { AttachmentTable } from '../../tables/general/AttachmentTable';
export default function ReturnOrderDetail() {
const { id } = useParams();
const { instance: order, instanceQuery } = useInstance({
const user = useUserState();
const {
instance: order,
instanceQuery,
refreshInstance
} = useInstance({
endpoint: ApiEndpoints.return_order_list,
pk: id,
params: {
@ -233,8 +248,43 @@ export default function ReturnOrderDetail() {
];
}, [order, instanceQuery]);
const returnOrderFields = useReturnOrderFields();
const editReturnOrder = useEditApiFormModal({
url: ApiEndpoints.return_order_list,
pk: order.pk,
title: t`Edit Return Order`,
fields: returnOrderFields,
onFormSuccess: () => {
refreshInstance();
}
});
const orderActions = useMemo(() => {
return [
<ActionDropdown
key="order-actions"
tooltip={t`Order Actions`}
icon={<IconDots />}
actions={[
EditItemAction({
hidden: !user.hasChangeRole(UserRoles.return_order),
onClick: () => {
editReturnOrder.open();
}
}),
DeleteItemAction({
hidden: !user.hasDeleteRole(UserRoles.return_order)
// TODO: Delete?
})
]}
/>
];
}, [user]);
return (
<>
{editReturnOrder.modal}
<Stack spacing="xs">
<LoadingOverlay visible={instanceQuery.isFetching} />
<PageDetail
@ -242,6 +292,7 @@ export default function ReturnOrderDetail() {
subtitle={order.description}
imageUrl={order.customer_detail?.image}
badges={orderBadges}
actions={orderActions}
breadcrumbs={[{ name: t`Sales`, url: '/sales/' }]}
/>
<PanelGroup pageKey="returnorder" panels={orderPanels} />

@ -1,6 +1,7 @@
import { t } from '@lingui/macro';
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
import {
IconDots,
IconInfoCircle,
IconList,
IconNotes,
@ -15,6 +16,11 @@ import { useParams } from 'react-router-dom';
import { DetailsField, DetailsTable } from '../../components/details/Details';
import { DetailsImage } from '../../components/details/DetailsImage';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
ActionDropdown,
DeleteItemAction,
EditItemAction
} from '../../components/items/ActionDropdown';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { StatusRenderer } from '../../components/render/StatusRenderer';
@ -22,8 +28,11 @@ import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
import { useEditApiFormModal } from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
import { AttachmentTable } from '../../tables/general/AttachmentTable';
@ -33,7 +42,13 @@ import { AttachmentTable } from '../../tables/general/AttachmentTable';
export default function SalesOrderDetail() {
const { id } = useParams();
const { instance: order, instanceQuery } = useInstance({
const user = useUserState();
const {
instance: order,
instanceQuery,
refreshInstance
} = useInstance({
endpoint: ApiEndpoints.sales_order_list,
pk: id,
params: {
@ -185,6 +200,18 @@ export default function SalesOrderDetail() {
);
}, [order, instanceQuery]);
const salesOrderFields = useSalesOrderFields();
const editSalesOrder = useEditApiFormModal({
url: ApiEndpoints.sales_order_list,
pk: order.pk,
title: t`Edit Sales Order`,
fields: salesOrderFields,
onFormSuccess: () => {
refreshInstance();
}
});
const orderPanels: PanelType[] = useMemo(() => {
return [
{
@ -245,6 +272,28 @@ export default function SalesOrderDetail() {
];
}, [order, id]);
const soActions = useMemo(() => {
return [
<ActionDropdown
key="order-actions"
tooltip={t`Order Actions`}
icon={<IconDots />}
actions={[
EditItemAction({
hidden: !user.hasChangeRole(UserRoles.sales_order),
onClick: () => {
editSalesOrder.open();
}
}),
DeleteItemAction({
hidden: !user.hasDeleteRole(UserRoles.sales_order)
// TODO: Delete?
})
]}
/>
];
}, [user]);
const orderBadges: ReactNode[] = useMemo(() => {
return instanceQuery.isLoading
? []
@ -259,6 +308,7 @@ export default function SalesOrderDetail() {
return (
<>
{editSalesOrder.modal}
<Stack spacing="xs">
<LoadingOverlay visible={instanceQuery.isFetching} />
<PageDetail
@ -266,6 +316,7 @@ export default function SalesOrderDetail() {
subtitle={order.description}
imageUrl={order.customer_detail?.image}
badges={orderBadges}
actions={soActions}
breadcrumbs={[{ name: t`Sales`, url: '/sales/' }]}
/>
<PanelGroup pageKey="salesorder" panels={orderPanels} />

@ -23,7 +23,7 @@ import {
IconSitemap
} from '@tabler/icons-react';
import { ReactNode, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import { DetailsField, DetailsTable } from '../../components/details/Details';
import DetailsBadge from '../../components/details/DetailsBadge';
@ -33,6 +33,7 @@ import {
ActionDropdown,
BarcodeActionDropdown,
DeleteItemAction,
DuplicateItemAction,
EditItemAction,
LinkBarcodeAction,
UnlinkBarcodeAction,
@ -50,12 +51,16 @@ import {
StockOperationProps,
useAddStockItem,
useCountStockItem,
useEditStockItem,
useRemoveStockItem,
useStockFields,
useTransferStockItem
} from '../../forms/StockForms';
import { InvenTreeIcon } from '../../functions/icons';
import { getDetailUrl } from '../../functions/urls';
import {
useCreateApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
@ -69,6 +74,8 @@ export default function StockDetail() {
const user = useUserState();
const navigate = useNavigate();
const [treeOpen, setTreeOpen] = useState(false);
const {
@ -349,9 +356,30 @@ export default function StockDetail() {
[stockitem]
);
const editStockItem = useEditStockItem({
item_id: stockitem.pk,
callback: () => refreshInstance()
const editStockItemFields = useStockFields({ create: false });
const editStockItem = useEditApiFormModal({
url: ApiEndpoints.stock_item_list,
pk: stockitem.pk,
title: t`Edit Stock Item`,
fields: editStockItemFields,
onFormSuccess: refreshInstance
});
const duplicateStockItemFields = useStockFields({ create: true });
const duplicateStockItem = useCreateApiFormModal({
url: ApiEndpoints.stock_item_list,
title: t`Add Stock Item`,
fields: duplicateStockItemFields,
initialData: {
...stockitem
},
onFormSuccess: (response: any) => {
if (response.pk) {
navigate(getDetailUrl(ModelType.stockitem, response.pk));
}
}
});
const stockActionProps: StockOperationProps = useMemo(() => {
@ -368,15 +396,17 @@ export default function StockDetail() {
const transferStockItem = useTransferStockItem(stockActionProps);
const stockActions = useMemo(
() => /* TODO: Disable actions based on user permissions*/ [
() => [
<BarcodeActionDropdown
actions={[
ViewBarcodeAction({}),
LinkBarcodeAction({
hidden: stockitem?.barcode_hash
hidden:
stockitem?.barcode_hash || !user.hasChangeRole(UserRoles.stock)
}),
UnlinkBarcodeAction({
hidden: !stockitem?.barcode_hash
hidden:
!stockitem?.barcode_hash || !user.hasChangeRole(UserRoles.stock)
})
]}
/>,
@ -425,16 +455,20 @@ export default function StockDetail() {
/>,
<ActionDropdown
key="stock"
// tooltip={t`Stock Actions`}
tooltip={t`Stock Item Actions`}
icon={<IconDots />}
actions={[
{
name: t`Duplicate`,
tooltip: t`Duplicate stock item`,
icon: <IconCopy />
},
EditItemAction({}),
DeleteItemAction({})
DuplicateItemAction({
hidden: !user.hasAddRole(UserRoles.stock),
onClick: () => duplicateStockItem.open()
}),
EditItemAction({
hidden: !user.hasChangeRole(UserRoles.stock),
onClick: () => editStockItem.open()
}),
DeleteItemAction({
hidden: !user.hasDeleteRole(UserRoles.stock)
})
]}
/>
],
@ -489,6 +523,7 @@ export default function StockDetail() {
/>
<PanelGroup pageKey="stockitem" panels={stockPanels} />
{editStockItem.modal}
{duplicateStockItem.modal}
{countStockItem.modal}
{addStockItem.modal}
{removeStockItem.modal}

@ -10,7 +10,7 @@ import { renderDate } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { buildOrderFields } from '../../forms/BuildForms';
import { useBuildOrderFields } from '../../forms/BuildForms';
import { getDetailUrl } from '../../functions/urls';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
@ -135,10 +135,12 @@ export function BuildOrderTable({
const table = useTable('buildorder');
const buildOrderFields = useBuildOrderFields({ create: true });
const newBuild = useCreateApiFormModal({
url: ApiEndpoints.build_order_list,
title: t`Add Build Order`,
fields: buildOrderFields(),
fields: buildOrderFields,
initialData: {
part: partId,
sales_order: salesOrderId,

@ -1,5 +1,6 @@
import { t } from '@lingui/macro';
import { Group, Text } from '@mantine/core';
import { access } from 'fs';
import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
@ -13,7 +14,8 @@ import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import { DescriptionColumn } from '../ColumnRenderers';
import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers';
import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
/**
@ -51,6 +53,12 @@ export function CompanyTable({
}
},
DescriptionColumn({}),
BooleanColumn({
accessor: 'active',
title: t`Active`,
sortable: true,
switchable: true
}),
{
accessor: 'website',
sortable: false
@ -73,6 +81,31 @@ export function CompanyTable({
}
});
const tableFilters: TableFilter[] = useMemo(() => {
return [
{
name: 'active',
label: t`Active`,
description: t`Show active companies`
},
{
name: 'is_supplier',
label: t`Supplier`,
description: t`Show companies which are suppliers`
},
{
name: 'is_manufacturer',
label: t`Manufacturer`,
description: t`Show companies which are manufacturers`
},
{
name: 'is_customer',
label: t`Customer`,
description: t`Show companies which are customers`
}
];
}, []);
const tableActions = useMemo(() => {
const can_add =
user.hasAddRole(UserRoles.purchase_order) ||
@ -98,6 +131,7 @@ export function CompanyTable({
params: {
...params
},
tableFilters: tableFilters,
tableActions: tableActions,
onRowClick: (row: any) => {
if (row.pk) {

@ -63,9 +63,7 @@ export function ContactTable({
};
}, []);
const [selectedContact, setSelectedContact] = useState<number | undefined>(
undefined
);
const [selectedContact, setSelectedContact] = useState<number>(0);
const editContact = useEditApiFormModal({
url: ApiEndpoints.contact_list,

@ -44,9 +44,11 @@ import { TableHoverCard } from '../TableHoverCard';
*/
export function PurchaseOrderLineItemTable({
orderId,
supplierId,
params
}: {
orderId: number;
supplierId?: number;
params?: any;
}) {
const table = useTable('purchase-order-line-item');
@ -67,7 +69,7 @@ export function PurchaseOrderLineItemTable({
return [
{
accessor: 'part',
title: t`Part`,
title: t`Internal Part`,
sortable: true,
switchable: false,
render: (record: any) => {
@ -183,25 +185,35 @@ export function PurchaseOrderLineItemTable({
];
}, [orderId, user]);
const addPurchaseOrderFields = usePurchaseOrderLineItemFields({
create: true,
orderId: orderId,
supplierId: supplierId
});
const [initialData, setInitialData] = useState({});
const newLine = useCreateApiFormModal({
url: ApiEndpoints.purchase_order_line_list,
title: t`Add Line Item`,
fields: usePurchaseOrderLineItemFields({ create: true }),
initialData: {
order: orderId
},
fields: addPurchaseOrderFields,
initialData: initialData,
onFormSuccess: table.refreshTable
});
const [selectedLine, setSelectedLine] = useState<number | undefined>(
undefined
);
const [selectedLine, setSelectedLine] = useState<number>(0);
const editPurchaseOrderFields = usePurchaseOrderLineItemFields({
create: false,
orderId: orderId,
supplierId: supplierId
});
const editLine = useEditApiFormModal({
url: ApiEndpoints.purchase_order_line_list,
pk: selectedLine,
title: t`Edit Line Item`,
fields: usePurchaseOrderLineItemFields({}),
fields: editPurchaseOrderFields,
onFormSuccess: table.refreshTable
});
@ -235,7 +247,11 @@ export function PurchaseOrderLineItemTable({
}
}),
RowDuplicateAction({
hidden: !user.hasAddRole(UserRoles.purchase_order)
hidden: !user.hasAddRole(UserRoles.purchase_order),
onClick: () => {
setInitialData({ ...record });
newLine.open();
}
}),
RowDeleteAction({
hidden: !user.hasDeleteRole(UserRoles.purchase_order),
@ -254,7 +270,12 @@ export function PurchaseOrderLineItemTable({
return [
<AddItemButton
tooltip={t`Add line item`}
onClick={() => newLine.open()}
onClick={() => {
setInitialData({
order: orderId
});
newLine.open();
}}
hidden={!user?.hasAddRole(UserRoles.purchase_order)}
/>,
<ActionButton

@ -8,7 +8,7 @@ import { formatCurrency } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { purchaseOrderFields } from '../../forms/PurchaseOrderForms';
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
import { getDetailUrl } from '../../functions/urls';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
@ -106,10 +106,12 @@ export function PurchaseOrderTable({
];
}, []);
const purchaseOrderFields = usePurchaseOrderFields();
const newPurchaseOrder = useCreateApiFormModal({
url: ApiEndpoints.purchase_order_list,
title: t`Add Purchase Order`,
fields: purchaseOrderFields(),
fields: purchaseOrderFields,
initialData: {
supplier: supplierId
},

@ -1,6 +1,6 @@
import { t } from '@lingui/macro';
import { Text } from '@mantine/core';
import { ReactNode, useCallback, useMemo } from 'react';
import { ReactNode, useCallback, useMemo, useState } from 'react';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { Thumbnail } from '../../components/images/Thumbnail';
@ -9,17 +9,23 @@ import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useSupplierPartFields } from '../../forms/CompanyForms';
import { openDeleteApiForm, openEditApiForm } from '../../functions/forms';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import {
useCreateApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import { TableColumn } from '../Column';
import {
BooleanColumn,
DescriptionColumn,
LinkColumn,
NoteColumn,
PartColumn
} from '../ColumnRenderers';
import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowDeleteAction, RowEditAction } from '../RowActions';
import { TableHoverCard } from '../TableHoverCard';
@ -88,6 +94,12 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
title: t`MPN`,
render: (record: any) => record?.manufacturer_part_detail?.MPN
},
BooleanColumn({
accessor: 'active',
title: t`Active`,
sortable: true,
switchable: true
}),
{
accessor: 'in_stock',
sortable: true
@ -145,35 +157,67 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
];
}, [params]);
const addSupplierPartFields = useSupplierPartFields({
partPk: params?.part,
supplierPk: params?.supplier,
hidePart: true
const supplierPartFields = useSupplierPartFields();
const addSupplierPart = useCreateApiFormModal({
url: ApiEndpoints.supplier_part_list,
title: t`Add Supplier Part`,
fields: supplierPartFields,
initialData: {
part: params?.part,
supplier: params?.supplier
},
onFormSuccess: table.refreshTable,
successMessage: t`Supplier part created`
});
const { modal: addSupplierPartModal, open: openAddSupplierPartForm } =
useCreateApiFormModal({
url: ApiEndpoints.supplier_part_list,
title: t`Add Supplier Part`,
fields: addSupplierPartFields,
onFormSuccess: table.refreshTable,
successMessage: t`Supplier part created`
});
// Table actions
const tableActions = useMemo(() => {
// TODO: Hide actions based on user permissions
return [
<AddItemButton
tooltip={t`Add supplier part`}
onClick={openAddSupplierPartForm}
onClick={() => addSupplierPart.open()}
hidden={!user.hasAddRole(UserRoles.purchase_order)}
/>
];
}, [user]);
const editSupplierPartFields = useSupplierPartFields({
hidePart: true,
partPk: params?.part
const tableFilters: TableFilter[] = useMemo(() => {
return [
{
name: 'active',
label: t`Active`,
description: t`Show active supplier parts`
},
{
name: 'part_active',
label: t`Active Part`,
description: t`Show active internal parts`
},
{
name: 'supplier_active',
label: t`Active Supplier`,
description: t`Show active suppliers`
}
];
}, []);
const editSupplierPartFields = useSupplierPartFields();
const [selectedSupplierPart, setSelectedSupplierPart] = useState<number>(0);
const editSupplierPart = useEditApiFormModal({
url: ApiEndpoints.supplier_part_list,
pk: selectedSupplierPart,
title: t`Edit Supplier Part`,
fields: editSupplierPartFields,
onFormSuccess: () => table.refreshTable()
});
const deleteSupplierPart = useDeleteApiFormModal({
url: ApiEndpoints.supplier_part_list,
pk: selectedSupplierPart,
title: t`Delete Supplier Part`,
onFormSuccess: () => table.refreshTable()
});
// Row action callback
@ -183,29 +227,15 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
RowEditAction({
hidden: !user.hasChangeRole(UserRoles.purchase_order),
onClick: () => {
record.pk &&
openEditApiForm({
url: ApiEndpoints.supplier_part_list,
pk: record.pk,
title: t`Edit Supplier Part`,
fields: editSupplierPartFields,
onFormSuccess: table.refreshTable,
successMessage: t`Supplier part updated`
});
setSelectedSupplierPart(record.pk);
editSupplierPart.open();
}
}),
RowDeleteAction({
hidden: !user.hasDeleteRole(UserRoles.purchase_order),
onClick: () => {
record.pk &&
openDeleteApiForm({
url: ApiEndpoints.supplier_part_list,
pk: record.pk,
title: t`Delete Supplier Part`,
successMessage: t`Supplier part deleted`,
onFormSuccess: table.refreshTable,
preFormWarning: t`Are you sure you want to remove this supplier part?`
});
setSelectedSupplierPart(record.pk);
deleteSupplierPart.open();
}
})
];
@ -215,7 +245,9 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
return (
<>
{addSupplierPartModal}
{addSupplierPart.modal}
{editSupplierPart.modal}
{deleteSupplierPart.modal}
<InvenTreeTable
url={apiUrl(ApiEndpoints.supplier_part_list)}
tableState={table}
@ -229,6 +261,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
},
rowActions: rowActions,
tableActions: tableActions,
tableFilters: tableFilters,
modelType: ModelType.supplierpart
}}
/>

@ -1,5 +1,6 @@
import { t } from '@lingui/macro';
import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { Thumbnail } from '../../components/images/Thumbnail';
@ -7,7 +8,10 @@ import { formatCurrency } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
import { notYetImplemented } from '../../functions/notifications';
import { getDetailUrl } from '../../functions/urls';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
@ -33,6 +37,7 @@ import { InvenTreeTable } from '../InvenTreeTable';
export function ReturnOrderTable({ params }: { params?: any }) {
const table = useTable('return-orders');
const user = useUserState();
const navigate = useNavigate();
const tableFilters: TableFilter[] = useMemo(() => {
return [
@ -48,10 +53,6 @@ export function ReturnOrderTable({ params }: { params?: any }) {
];
}, []);
// TODO: Row actions
// TODO: Table actions (e.g. create new return order)
const tableColumns = useMemo(() => {
return [
ReferenceColumn(),
@ -94,34 +95,48 @@ export function ReturnOrderTable({ params }: { params?: any }) {
];
}, []);
const addReturnOrder = useCallback(() => {
notYetImplemented();
}, []);
const returnOrderFields = useReturnOrderFields();
const newReturnOrder = useCreateApiFormModal({
url: ApiEndpoints.return_order_list,
title: t`Add Return Order`,
fields: returnOrderFields,
onFormSuccess: (response) => {
if (response.pk) {
navigate(getDetailUrl(ModelType.returnorder, response.pk));
} else {
table.refreshTable();
}
}
});
const tableActions = useMemo(() => {
return [
<AddItemButton
tooltip={t`Add Return Order`}
onClick={addReturnOrder}
hidden={!user.hasAddRole(UserRoles.sales_order)}
onClick={() => newReturnOrder.open()}
hidden={!user.hasAddRole(UserRoles.return_order)}
/>
];
}, [user]);
return (
<InvenTreeTable
url={apiUrl(ApiEndpoints.return_order_list)}
tableState={table}
columns={tableColumns}
props={{
params: {
...params,
customer_detail: true
},
tableFilters: tableFilters,
tableActions: tableActions,
modelType: ModelType.returnorder
}}
/>
<>
{newReturnOrder.modal}
<InvenTreeTable
url={apiUrl(ApiEndpoints.return_order_list)}
tableState={table}
columns={tableColumns}
props={{
params: {
...params,
customer_detail: true
},
tableFilters: tableFilters,
tableActions: tableActions,
modelType: ModelType.returnorder
}}
/>
</>
);
}

@ -8,7 +8,7 @@ import { formatCurrency } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { salesOrderFields } from '../../forms/SalesOrderForms';
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
import { getDetailUrl } from '../../functions/urls';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
@ -61,10 +61,12 @@ export function SalesOrderTable({
];
}, []);
const salesOrderFields = useSalesOrderFields();
const newSalesOrder = useCreateApiFormModal({
url: ApiEndpoints.sales_order_list,
title: t`Add Sales Order`,
fields: salesOrderFields(),
fields: salesOrderFields,
initialData: {
customer: customerId
},

@ -279,9 +279,7 @@ export default function StockItemTestResultTable({
successMessage: t`Test result added`
});
const [selectedTest, setSelectedTest] = useState<number | undefined>(
undefined
);
const [selectedTest, setSelectedTest] = useState<number>(0);
const editTestModal = useEditApiFormModal({
url: ApiEndpoints.stock_test_result_list,

@ -67,6 +67,11 @@ test('PUI - Purchasing', async ({ page }) => {
.click();
await page.getByRole('menuitem', { name: 'Edit' }).click();
await page.getByLabel('Address title *').waitFor();
// Read the current value of the cell, to ensure we always *change* it!
const value = await page.getByLabel('Line 2').inputValue();
await page.getByLabel('Line 2').fill(value == 'old' ? 'new' : 'old');
await page.getByRole('button', { name: 'Submit' }).isEnabled();
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByRole('tab', { name: 'Details' }).waitFor();