mirror of
https://github.com/inventree/InvenTree.git
synced 2025-08-09 21:30:54 +00:00
Merge branch 'master' of https://github.com/inventree/InvenTree into pui-maintine-v7
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 192
|
||||
INVENTREE_API_VERSION = 193
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v193 - 2024-04-30 : https://github.com/inventree/InvenTree/pull/7144
|
||||
- Adds "assigned_to" filter to PurchaseOrder / SalesOrder / ReturnOrder API endpoints
|
||||
|
||||
v192 - 2024-04-23 : https://github.com/inventree/InvenTree/pull/7106
|
||||
- Adds 'trackable' ordering option to BuildLineLabel API endpoint
|
||||
|
||||
|
@@ -148,6 +148,10 @@ class OrderFilter(rest_filters.FilterSet):
|
||||
return queryset.exclude(project_code=None)
|
||||
return queryset.filter(project_code=None)
|
||||
|
||||
assigned_to = rest_filters.ModelChoiceFilter(
|
||||
queryset=Owner.objects.all(), field_name='responsible'
|
||||
)
|
||||
|
||||
|
||||
class LineItemFilter(rest_filters.FilterSet):
|
||||
"""Base class for custom API filters for order line item list(s)."""
|
||||
|
@@ -77,16 +77,18 @@ class AbstractOrderSerializer(serializers.Serializer):
|
||||
"""Abstract serializer class which provides fields common to all order types."""
|
||||
|
||||
# Number of line items in this order
|
||||
line_items = serializers.IntegerField(read_only=True)
|
||||
line_items = serializers.IntegerField(read_only=True, label=_('Line Items'))
|
||||
|
||||
# Number of completed line items (this is an annotated field)
|
||||
completed_lines = serializers.IntegerField(read_only=True)
|
||||
completed_lines = serializers.IntegerField(
|
||||
read_only=True, label=_('Completed Lines')
|
||||
)
|
||||
|
||||
# Human-readable status text (read-only)
|
||||
status_text = serializers.CharField(source='get_status_display', read_only=True)
|
||||
|
||||
# status field cannot be set directly
|
||||
status = serializers.IntegerField(read_only=True)
|
||||
status = serializers.IntegerField(read_only=True, label=_('Order Status'))
|
||||
|
||||
# Reference string is *required*
|
||||
reference = serializers.CharField(required=True)
|
||||
@@ -114,7 +116,9 @@ class AbstractOrderSerializer(serializers.Serializer):
|
||||
|
||||
barcode_hash = serializers.CharField(read_only=True)
|
||||
|
||||
creation_date = serializers.DateField(required=False, allow_null=True)
|
||||
creation_date = serializers.DateField(
|
||||
required=False, allow_null=True, label=_('Creation Date')
|
||||
)
|
||||
|
||||
def validate_reference(self, reference):
|
||||
"""Custom validation for the reference field."""
|
||||
|
@@ -5,6 +5,6 @@ import { ActionButton, ActionButtonProps } from './ActionButton';
|
||||
/**
|
||||
* A generic icon button which is used to add or create a new item
|
||||
*/
|
||||
export function AddItemButton(props: ActionButtonProps) {
|
||||
export function AddItemButton(props: Readonly<ActionButtonProps>) {
|
||||
return <ActionButton {...props} color="green" icon={<IconPlus />} />;
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ export function SplitButton({
|
||||
selected,
|
||||
setSelected,
|
||||
loading
|
||||
}: SplitButtonProps) {
|
||||
}: Readonly<SplitButtonProps>) {
|
||||
const [current, setCurrent] = useState<string>(defaultSelected);
|
||||
|
||||
useEffect(() => {
|
||||
|
@@ -180,7 +180,7 @@ function NameBadge({ pk, type }: { pk: string | number; type: BadgeType }) {
|
||||
* If owner is defined, only renders a badge
|
||||
* If user is defined, a badge is rendered in addition to main value
|
||||
*/
|
||||
function TableStringValue(props: FieldProps) {
|
||||
function TableStringValue(props: Readonly<FieldProps>) {
|
||||
let value = props?.field_value;
|
||||
|
||||
if (value === undefined) {
|
||||
@@ -217,11 +217,11 @@ function TableStringValue(props: FieldProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function BooleanValue(props: FieldProps) {
|
||||
function BooleanValue(props: Readonly<FieldProps>) {
|
||||
return <YesNoButton value={props.field_value} />;
|
||||
}
|
||||
|
||||
function TableAnchorValue(props: FieldProps) {
|
||||
function TableAnchorValue(props: Readonly<FieldProps>) {
|
||||
if (props.field_data.external) {
|
||||
return (
|
||||
<Anchor
|
||||
@@ -303,7 +303,7 @@ function TableAnchorValue(props: FieldProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function ProgressBarValue(props: FieldProps) {
|
||||
function ProgressBarValue(props: Readonly<FieldProps>) {
|
||||
return (
|
||||
<ProgressBar
|
||||
value={props.field_data.progress}
|
||||
@@ -313,7 +313,7 @@ function ProgressBarValue(props: FieldProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function StatusValue(props: FieldProps) {
|
||||
function StatusValue(props: Readonly<FieldProps>) {
|
||||
return (
|
||||
<StatusRenderer type={props.field_data.model} status={props.field_value} />
|
||||
);
|
||||
|
@@ -7,7 +7,7 @@ export type DetailsBadgeProps = {
|
||||
visible?: boolean;
|
||||
};
|
||||
|
||||
export default function DetailsBadge(props: DetailsBadgeProps) {
|
||||
export default function DetailsBadge(props: Readonly<DetailsBadgeProps>) {
|
||||
if (props.visible == false) {
|
||||
return null;
|
||||
}
|
||||
|
@@ -322,7 +322,7 @@ function ImageActionButtons({
|
||||
/**
|
||||
* Renders an image with action buttons for display on Details panels
|
||||
*/
|
||||
export function DetailsImage(props: DetailImageProps) {
|
||||
export function DetailsImage(props: Readonly<DetailImageProps>) {
|
||||
// Displays a group of ActionButtons on hover
|
||||
const { hovered, ref } = useHover();
|
||||
const [img, setImg] = useState<string>(props.src ?? backup_image);
|
||||
|
@@ -87,7 +87,7 @@ type TemplateEditorProps = {
|
||||
template: TemplateI;
|
||||
};
|
||||
|
||||
export function TemplateEditor(props: TemplateEditorProps) {
|
||||
export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
|
||||
const { downloadUrl, editors, previewAreas, preview } = props;
|
||||
const editorRef = useRef<EditorRef>();
|
||||
const previewRef = useRef<PreviewAreaRef>();
|
||||
|
@@ -6,7 +6,12 @@ interface DocInfoProps extends BaseDocProps {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export function DocInfo({ size = 18, text, detail, link }: DocInfoProps) {
|
||||
export function DocInfo({
|
||||
size = 18,
|
||||
text,
|
||||
detail,
|
||||
link
|
||||
}: Readonly<DocInfoProps>) {
|
||||
return (
|
||||
<DocTooltip text={text} detail={detail} link={link}>
|
||||
<IconInfoCircle size={size} />
|
||||
|
@@ -21,7 +21,7 @@ export function DocTooltip({
|
||||
detail,
|
||||
link,
|
||||
docchildren
|
||||
}: DocTooltipProps) {
|
||||
}: Readonly<DocTooltipProps>) {
|
||||
return (
|
||||
<HoverCard
|
||||
shadow="md"
|
||||
|
@@ -6,13 +6,14 @@ export type ProgressBarProps = {
|
||||
maximum?: number;
|
||||
label?: string;
|
||||
progressLabel?: boolean;
|
||||
size?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A progress bar element, built on mantine.Progress
|
||||
* The color of the bar is determined based on the value
|
||||
*/
|
||||
export function ProgressBar(props: ProgressBarProps) {
|
||||
export function ProgressBar(props: Readonly<ProgressBarProps>) {
|
||||
const progress = useMemo(() => {
|
||||
let maximum = props.maximum ?? 100;
|
||||
let value = Math.max(props.value, 0);
|
||||
@@ -31,8 +32,8 @@ export function ProgressBar(props: ProgressBarProps) {
|
||||
<Progress
|
||||
value={progress}
|
||||
color={progress < 100 ? 'orange' : progress > 100 ? 'blue' : 'green'}
|
||||
size="sm"
|
||||
radius="xs"
|
||||
size={props.size ?? 'md'}
|
||||
radius="sm"
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
@@ -14,7 +14,7 @@ export function TitleWithDoc({
|
||||
size,
|
||||
text,
|
||||
detail
|
||||
}: DocTitleProps) {
|
||||
}: Readonly<DocTitleProps>) {
|
||||
return (
|
||||
<Group>
|
||||
<Title variant={variant} order={order} size={size}>
|
||||
|
@@ -29,7 +29,7 @@ function DetailDrawerComponent({
|
||||
size,
|
||||
closeOnEscape = true,
|
||||
renderContent
|
||||
}: DrawerProps) {
|
||||
}: Readonly<DrawerProps>) {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
|
||||
@@ -80,7 +80,7 @@ function DetailDrawerComponent({
|
||||
);
|
||||
}
|
||||
|
||||
export function DetailDrawer(props: DrawerProps) {
|
||||
export function DetailDrawer(props: Readonly<DrawerProps>) {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path=":id?/" element={<DetailDrawerComponent {...props} />} />
|
||||
|
@@ -5,6 +5,17 @@ import { ApiImage } from '../images/ApiImage';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
import { Breadcrumb, BreadcrumbList } from './BreadcrumbList';
|
||||
|
||||
interface PageDetailInterface {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
imageUrl?: string;
|
||||
detail?: ReactNode;
|
||||
badges?: ReactNode[];
|
||||
breadcrumbs?: Breadcrumb[];
|
||||
breadcrumbAction?: () => void;
|
||||
actions?: ReactNode[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a "standard" page detail for common display between pages.
|
||||
*
|
||||
@@ -20,16 +31,7 @@ export function PageDetail({
|
||||
breadcrumbs,
|
||||
breadcrumbAction,
|
||||
actions
|
||||
}: {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
imageUrl?: string;
|
||||
detail?: ReactNode;
|
||||
badges?: ReactNode[];
|
||||
breadcrumbs?: Breadcrumb[];
|
||||
breadcrumbAction?: () => void;
|
||||
actions?: ReactNode[];
|
||||
}) {
|
||||
}: Readonly<PageDetailInterface>) {
|
||||
return (
|
||||
<Stack gap="xs">
|
||||
{breadcrumbs && breadcrumbs.length > 0 && (
|
||||
|
@@ -50,7 +50,7 @@ function BasePanelGroup({
|
||||
onPanelChange,
|
||||
selectedPanel,
|
||||
collapsible = true
|
||||
}: PanelProps): ReactNode {
|
||||
}: Readonly<PanelProps>): ReactNode {
|
||||
const navigate = useNavigate();
|
||||
const { panel } = useParams();
|
||||
|
||||
@@ -178,7 +178,11 @@ function BasePanelGroup({
|
||||
);
|
||||
}
|
||||
|
||||
function IndexPanelComponent({ pageKey, selectedPanel, panels }: PanelProps) {
|
||||
function IndexPanelComponent({
|
||||
pageKey,
|
||||
selectedPanel,
|
||||
panels
|
||||
}: Readonly<PanelProps>) {
|
||||
const lastUsedPanel = useLocalState((state) => {
|
||||
const panelName =
|
||||
selectedPanel || state.lastUsedPanels[pageKey] || panels[0]?.name;
|
||||
@@ -203,7 +207,7 @@ function IndexPanelComponent({ pageKey, selectedPanel, panels }: PanelProps) {
|
||||
* @param onPanelChange - Callback when the active panel changes
|
||||
* @param collapsible - If true, the panel group can be collapsed (defaults to true)
|
||||
*/
|
||||
export function PanelGroup(props: PanelProps) {
|
||||
export function PanelGroup(props: Readonly<PanelProps>) {
|
||||
return (
|
||||
<Routes>
|
||||
<Route index element={<IndexPanelComponent {...props} />} />
|
||||
|
@@ -3,6 +3,15 @@ import { IconSwitch } from '@tabler/icons-react';
|
||||
import { ReactNode } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
interface SettingsHeaderInterface {
|
||||
title: string | ReactNode;
|
||||
shorthand?: string;
|
||||
subtitle?: string | ReactNode;
|
||||
switch_condition?: boolean;
|
||||
switch_text?: string | ReactNode;
|
||||
switch_link?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a settings page header with interlinks to one other settings page
|
||||
*/
|
||||
@@ -13,14 +22,7 @@ export function SettingsHeader({
|
||||
switch_condition = true,
|
||||
switch_text,
|
||||
switch_link
|
||||
}: {
|
||||
title: string | ReactNode;
|
||||
shorthand?: string;
|
||||
subtitle?: string | ReactNode;
|
||||
switch_condition?: boolean;
|
||||
switch_text?: string | ReactNode;
|
||||
switch_link?: string;
|
||||
}) {
|
||||
}: Readonly<SettingsHeaderInterface>) {
|
||||
return (
|
||||
<Stack gap="0" ml={'sm'}>
|
||||
<Group>
|
||||
|
@@ -1,13 +1,15 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { RenderInlineModel } from './Instance';
|
||||
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
import { StatusRenderer } from './StatusRenderer';
|
||||
|
||||
/**
|
||||
* Inline rendering of a single BuildOrder instance
|
||||
*/
|
||||
export function RenderBuildOrder({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderBuildOrder({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
return (
|
||||
<RenderInlineModel
|
||||
primary={instance.reference}
|
||||
@@ -24,7 +26,9 @@ export function RenderBuildOrder({ instance }: { instance: any }): ReactNode {
|
||||
/*
|
||||
* Inline rendering of a single BuildLine instance
|
||||
*/
|
||||
export function RenderBuildLine({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderBuildLine({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
return (
|
||||
<RenderInlineModel
|
||||
primary={instance.part_detail.full_name}
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { RenderInlineModel } from './Instance';
|
||||
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
|
||||
/**
|
||||
* Inline rendering of a single Address instance
|
||||
*/
|
||||
export function RenderAddress({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderAddress({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
let text = [
|
||||
instance.country,
|
||||
instance.postal_code,
|
||||
@@ -23,7 +25,9 @@ export function RenderAddress({ instance }: { instance: any }): ReactNode {
|
||||
/**
|
||||
* Inline rendering of a single Company instance
|
||||
*/
|
||||
export function RenderCompany({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderCompany({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
// TODO: Handle URL
|
||||
|
||||
return (
|
||||
@@ -38,14 +42,18 @@ export function RenderCompany({ instance }: { instance: any }): ReactNode {
|
||||
/**
|
||||
* Inline rendering of a single Contact instance
|
||||
*/
|
||||
export function RenderContact({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderContact({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
return <RenderInlineModel primary={instance.name} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline rendering of a single SupplierPart instance
|
||||
*/
|
||||
export function RenderSupplierPart({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderSupplierPart({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
// TODO: handle URL
|
||||
|
||||
let supplier = instance.supplier_detail ?? {};
|
||||
@@ -66,9 +74,7 @@ export function RenderSupplierPart({ instance }: { instance: any }): ReactNode {
|
||||
*/
|
||||
export function RenderManufacturerPart({
|
||||
instance
|
||||
}: {
|
||||
instance: any;
|
||||
}): ReactNode {
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
let part = instance.part_detail ?? {};
|
||||
let manufacturer = instance.manufacturer_detail ?? {};
|
||||
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { RenderInlineModel } from './Instance';
|
||||
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
|
||||
export function RenderProjectCode({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderProjectCode({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
return (
|
||||
instance && (
|
||||
<RenderInlineModel
|
||||
|
@@ -37,7 +37,7 @@ type EnumDictionary<T extends string | symbol | number, U> = {
|
||||
*/
|
||||
const RendererLookup: EnumDictionary<
|
||||
ModelType,
|
||||
(props: { instance: any }) => ReactNode
|
||||
(props: Readonly<InstanceRenderInterface>) => ReactNode
|
||||
> = {
|
||||
[ModelType.address]: RenderAddress,
|
||||
[ModelType.build]: RenderBuildOrder,
|
||||
@@ -139,3 +139,7 @@ export function UnknownRenderer({
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export interface InstanceRenderInterface {
|
||||
instance: any;
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import { t } from '@lingui/macro';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { RenderInlineModel } from './Instance';
|
||||
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
import { StatusRenderer } from './StatusRenderer';
|
||||
|
||||
/**
|
||||
@@ -10,9 +10,7 @@ import { StatusRenderer } from './StatusRenderer';
|
||||
*/
|
||||
export function RenderPurchaseOrder({
|
||||
instance
|
||||
}: {
|
||||
instance: any;
|
||||
}): ReactNode {
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
let supplier = instance.supplier_detail || {};
|
||||
|
||||
// TODO: Handle URL
|
||||
@@ -32,7 +30,9 @@ export function RenderPurchaseOrder({
|
||||
/**
|
||||
* Inline rendering of a single ReturnOrder instance
|
||||
*/
|
||||
export function RenderReturnOrder({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderReturnOrder({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
let customer = instance.customer_detail || {};
|
||||
|
||||
return (
|
||||
@@ -51,7 +51,9 @@ export function RenderReturnOrder({ instance }: { instance: any }): ReactNode {
|
||||
/**
|
||||
* Inline rendering of a single SalesOrder instance
|
||||
*/
|
||||
export function RenderSalesOrder({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderSalesOrder({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
let customer = instance.customer_detail || {};
|
||||
|
||||
// TODO: Handle URL
|
||||
|
@@ -1,12 +1,14 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { RenderInlineModel } from './Instance';
|
||||
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
|
||||
/**
|
||||
* Inline rendering of a single Part instance
|
||||
*/
|
||||
export function RenderPart({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderPart({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
const stock = t`Stock` + `: ${instance.in_stock}`;
|
||||
|
||||
return (
|
||||
@@ -22,7 +24,9 @@ export function RenderPart({ instance }: { instance: any }): ReactNode {
|
||||
/**
|
||||
* Inline rendering of a PartCategory instance
|
||||
*/
|
||||
export function RenderPartCategory({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderPartCategory({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
// TODO: Handle URL
|
||||
|
||||
let lvl = '-'.repeat(instance.level || 0);
|
||||
|
@@ -14,7 +14,7 @@ export interface StatusCodeListInterface {
|
||||
[key: string]: StatusCodeInterface;
|
||||
}
|
||||
|
||||
interface renderStatusLabelOptionsInterface {
|
||||
interface RenderStatusLabelOptionsInterface {
|
||||
size?: MantineSize;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ interface renderStatusLabelOptionsInterface {
|
||||
function renderStatusLabel(
|
||||
key: string | number,
|
||||
codes: StatusCodeListInterface,
|
||||
options: renderStatusLabelOptionsInterface = {}
|
||||
options: RenderStatusLabelOptionsInterface = {}
|
||||
) {
|
||||
let text = null;
|
||||
let color = null;
|
||||
@@ -70,7 +70,7 @@ export const StatusRenderer = ({
|
||||
}: {
|
||||
status: string | number;
|
||||
type: ModelType | string;
|
||||
options?: renderStatusLabelOptionsInterface;
|
||||
options?: RenderStatusLabelOptionsInterface;
|
||||
}) => {
|
||||
const statusCodeList = useGlobalStatusState.getState().status;
|
||||
|
||||
|
@@ -1,16 +1,14 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { RenderInlineModel } from './Instance';
|
||||
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
|
||||
/**
|
||||
* Inline rendering of a single StockLocation instance
|
||||
*/
|
||||
export function RenderStockLocation({
|
||||
instance
|
||||
}: {
|
||||
instance: any;
|
||||
}): ReactNode {
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
return (
|
||||
<RenderInlineModel
|
||||
primary={instance.name}
|
||||
@@ -19,7 +17,9 @@ export function RenderStockLocation({
|
||||
);
|
||||
}
|
||||
|
||||
export function RenderStockItem({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderStockItem({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
let quantity_string = '';
|
||||
|
||||
if (instance?.serial !== null && instance?.serial !== undefined) {
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import { IconUser, IconUsersGroup } from '@tabler/icons-react';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { RenderInlineModel } from './Instance';
|
||||
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
|
||||
export function RenderOwner({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderOwner({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
return (
|
||||
instance && (
|
||||
<RenderInlineModel
|
||||
@@ -14,7 +16,9 @@ export function RenderOwner({ instance }: { instance: any }): ReactNode {
|
||||
);
|
||||
}
|
||||
|
||||
export function RenderUser({ instance }: { instance: any }): ReactNode {
|
||||
export function RenderUser({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
return (
|
||||
instance && (
|
||||
<RenderInlineModel
|
||||
|
@@ -6,13 +6,13 @@ import {
|
||||
useUserSettingsState
|
||||
} from '../states/SettingsState';
|
||||
|
||||
interface formatDecmimalOptionsType {
|
||||
interface FormatDecmimalOptionsInterface {
|
||||
digits?: number;
|
||||
minDigits?: number;
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
interface formatCurrencyOptionsType {
|
||||
interface FormatCurrencyOptionsInterface {
|
||||
digits?: number;
|
||||
minDigits?: number;
|
||||
currency?: string;
|
||||
@@ -22,7 +22,7 @@ interface formatCurrencyOptionsType {
|
||||
|
||||
export function formatDecimal(
|
||||
value: number | null | undefined,
|
||||
options: formatDecmimalOptionsType = {}
|
||||
options: FormatDecmimalOptionsInterface = {}
|
||||
) {
|
||||
let locale = options.locale || navigator.language || 'en-US';
|
||||
|
||||
@@ -45,7 +45,7 @@ export function formatDecimal(
|
||||
*/
|
||||
export function formatCurrency(
|
||||
value: number | string | null | undefined,
|
||||
options: formatCurrencyOptionsType = {}
|
||||
options: FormatCurrencyOptionsInterface = {}
|
||||
) {
|
||||
if (value == null || value == undefined) {
|
||||
return null;
|
||||
@@ -90,7 +90,7 @@ export function formatCurrency(
|
||||
export function formatPriceRange(
|
||||
minValue: number | null,
|
||||
maxValue: number | null,
|
||||
options: formatCurrencyOptionsType = {}
|
||||
options: FormatCurrencyOptionsInterface = {}
|
||||
) {
|
||||
// If neither values are provided, return a dash
|
||||
if (minValue == null && maxValue == null) {
|
||||
@@ -117,7 +117,7 @@ export function formatPriceRange(
|
||||
)}`;
|
||||
}
|
||||
|
||||
interface renderDateOptionsType {
|
||||
interface RenderDateOptionsInterface {
|
||||
showTime?: boolean;
|
||||
showSeconds?: boolean;
|
||||
}
|
||||
@@ -128,7 +128,10 @@ interface renderDateOptionsType {
|
||||
* The provided "date" variable is a string, nominally ISO format e.g. 2022-02-22
|
||||
* The user-configured setting DATE_DISPLAY_FORMAT determines how the date should be displayed.
|
||||
*/
|
||||
export function renderDate(date: string, options: renderDateOptionsType = {}) {
|
||||
export function renderDate(
|
||||
date: string,
|
||||
options: RenderDateOptionsInterface = {}
|
||||
) {
|
||||
if (!date) {
|
||||
return '-';
|
||||
}
|
||||
|
@@ -217,7 +217,7 @@ type InvenTreeIconProps = {
|
||||
iconProps?: TablerIconProps;
|
||||
};
|
||||
|
||||
export function InvenTreeIcon(props: InvenTreeIconProps) {
|
||||
export function InvenTreeIcon(props: Readonly<InvenTreeIconProps>) {
|
||||
let Icon: React.ForwardRefExoticComponent<React.RefAttributes<any>>;
|
||||
|
||||
if (props.icon in icons) {
|
||||
|
91
src/frontend/src/hooks/UseFilter.tsx
Normal file
91
src/frontend/src/hooks/UseFilter.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Custom hook for retrieving a list of items from the API,
|
||||
* and turning them into "filters" for use in the frontend table framework.
|
||||
*/
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { api } from '../App';
|
||||
import { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import { resolveItem } from '../functions/conversion';
|
||||
import { apiUrl } from '../states/ApiState';
|
||||
import { TableFilterChoice } from '../tables/Filter';
|
||||
|
||||
type UseFilterProps = {
|
||||
url: string;
|
||||
method?: 'GET' | 'POST' | 'OPTIONS';
|
||||
params?: any;
|
||||
accessor?: string;
|
||||
transform: (item: any) => TableFilterChoice;
|
||||
};
|
||||
|
||||
export function useFilters(props: UseFilterProps) {
|
||||
const query = useQuery({
|
||||
enabled: true,
|
||||
queryKey: [props.url, props.method, props.params],
|
||||
queryFn: async () => {
|
||||
return await api
|
||||
.request({
|
||||
url: props.url,
|
||||
method: props.method || 'GET',
|
||||
params: props.params
|
||||
})
|
||||
.then((response) => {
|
||||
let data = resolveItem(response, props.accessor ?? 'data');
|
||||
|
||||
if (data == null || data == undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return data;
|
||||
})
|
||||
.catch((error) => []);
|
||||
}
|
||||
});
|
||||
|
||||
const choices: TableFilterChoice[] = useMemo(() => {
|
||||
return query.data?.map(props.transform) ?? [];
|
||||
}, [props.transform, query.data]);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
query.refetch();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
choices,
|
||||
refresh
|
||||
};
|
||||
}
|
||||
|
||||
// Provide list of project code filters
|
||||
export function useProjectCodeFilters() {
|
||||
return useFilters({
|
||||
url: apiUrl(ApiEndpoints.project_code_list),
|
||||
transform: (item) => ({
|
||||
value: item.pk,
|
||||
label: item.code
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Provide list of user filters
|
||||
export function useUserFilters() {
|
||||
return useFilters({
|
||||
url: apiUrl(ApiEndpoints.user_list),
|
||||
transform: (item) => ({
|
||||
value: item.pk,
|
||||
label: item.username
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Provide list of owner filters
|
||||
export function useOwnerFilters() {
|
||||
return useFilters({
|
||||
url: apiUrl(ApiEndpoints.owner_list),
|
||||
transform: (item) => ({
|
||||
value: item.pk,
|
||||
label: item.name
|
||||
})
|
||||
});
|
||||
}
|
@@ -4,6 +4,7 @@ import {
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
Col,
|
||||
Container,
|
||||
Grid,
|
||||
Group,
|
||||
@@ -479,11 +480,11 @@ enum InputMethod {
|
||||
ImageBarcode = 'imageBarcode'
|
||||
}
|
||||
|
||||
interface inputProps {
|
||||
interface ScanInputInterface {
|
||||
action: (items: ScanItem[]) => void;
|
||||
}
|
||||
|
||||
function InputManual({ action }: inputProps) {
|
||||
function InputManual({ action }: Readonly<ScanInputInterface>) {
|
||||
const [value, setValue] = useState<string>('');
|
||||
|
||||
function btnAddItem() {
|
||||
@@ -535,7 +536,7 @@ function InputManual({ action }: inputProps) {
|
||||
}
|
||||
|
||||
/* Input that uses QR code detection from images */
|
||||
function InputImageBarcode({ action }: inputProps) {
|
||||
function InputImageBarcode({ action }: Readonly<ScanInputInterface>) {
|
||||
const [qrCodeScanner, setQrCodeScanner] = useState<Html5Qrcode | null>(null);
|
||||
const [camId, setCamId] = useLocalStorage<CameraDevice | null>({
|
||||
key: 'camId',
|
||||
|
@@ -56,7 +56,7 @@ export type CompanyDetailProps = {
|
||||
/**
|
||||
* Detail view for a single company instance
|
||||
*/
|
||||
export default function CompanyDetail(props: CompanyDetailProps) {
|
||||
export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
||||
const { id } = useParams();
|
||||
|
||||
const user = useUserState();
|
||||
|
@@ -104,7 +104,7 @@ export function RowActions({
|
||||
}, [actions]);
|
||||
|
||||
// Render a single action icon
|
||||
function RowActionIcon(action: RowAction) {
|
||||
function RowActionIcon(action: Readonly<RowAction>) {
|
||||
return (
|
||||
<Tooltip
|
||||
withinPortal={true}
|
||||
|
@@ -10,6 +10,11 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useBuildOrderFields } from '../../forms/BuildForms';
|
||||
import {
|
||||
useOwnerFilters,
|
||||
useProjectCodeFilters,
|
||||
useUserFilters
|
||||
} from '../../hooks/UseFilter';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
@@ -92,6 +97,10 @@ export function BuildOrderTable({
|
||||
}) {
|
||||
const tableColumns = useMemo(() => buildOrderTableColumns(), []);
|
||||
|
||||
const projectCodeFilters = useProjectCodeFilters();
|
||||
const userFilters = useUserFilters();
|
||||
const responsibleFilters = useOwnerFilters();
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@@ -117,18 +126,36 @@ export function BuildOrderTable({
|
||||
type: 'boolean',
|
||||
label: t`Assigned to me`,
|
||||
description: t`Show orders assigned to me`
|
||||
},
|
||||
{
|
||||
name: 'project_code',
|
||||
label: t`Project Code`,
|
||||
description: t`Filter by project code`,
|
||||
choices: projectCodeFilters.choices
|
||||
},
|
||||
{
|
||||
name: 'has_project_code',
|
||||
label: t`Has Project Code`,
|
||||
description: t`Filter by whether the purchase order has a project code`
|
||||
},
|
||||
{
|
||||
name: 'issued_by',
|
||||
label: t`Issued By`,
|
||||
description: t`Filter by user who issued this order`,
|
||||
choices: userFilters.choices
|
||||
},
|
||||
{
|
||||
name: 'assigned_to',
|
||||
label: t`Responsible`,
|
||||
description: t`Filter by responsible owner`,
|
||||
choices: responsibleFilters.choices
|
||||
}
|
||||
// TODO: 'assigned to' filter
|
||||
// TODO: 'issued by' filter
|
||||
// {
|
||||
// name: 'has_project_code',
|
||||
// title: t`Has Project Code`,
|
||||
// description: t`Show orders with project code`,
|
||||
// }
|
||||
// TODO: 'has project code' filter (see table_filters.js)
|
||||
// TODO: 'project code' filter (see table_filters.js)
|
||||
];
|
||||
}, []);
|
||||
}, [
|
||||
projectCodeFilters.choices,
|
||||
userFilters.choices,
|
||||
responsibleFilters.choices
|
||||
]);
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
|
@@ -116,13 +116,13 @@ export default function BuildOutputTable({
|
||||
/>,
|
||||
<ActionButton
|
||||
tooltip={t`Scrap selected outputs`}
|
||||
icon={<InvenTreeIcon icon="cancel" />}
|
||||
icon={<InvenTreeIcon icon="delete" />}
|
||||
color="red"
|
||||
disabled={!table.hasSelectedRecords}
|
||||
/>,
|
||||
<ActionButton
|
||||
tooltip={t`Cancel selected outputs`}
|
||||
icon={<InvenTreeIcon icon="delete" />}
|
||||
icon={<InvenTreeIcon icon="cancel" />}
|
||||
color="red"
|
||||
disabled={!table.hasSelectedRecords}
|
||||
/>
|
||||
@@ -153,14 +153,14 @@ export default function BuildOutputTable({
|
||||
{
|
||||
title: t`Scrap`,
|
||||
tooltip: t`Scrap build output`,
|
||||
color: 'red',
|
||||
icon: <InvenTreeIcon icon="cancel" />
|
||||
icon: <InvenTreeIcon icon="delete" />,
|
||||
color: 'red'
|
||||
},
|
||||
{
|
||||
title: t`Delete`,
|
||||
tooltip: t`Delete build output`,
|
||||
color: 'red',
|
||||
icon: <InvenTreeIcon icon="delete" />
|
||||
title: t`Cancel`,
|
||||
tooltip: t`Cancel build output`,
|
||||
icon: <InvenTreeIcon icon="cancel" />,
|
||||
color: 'red'
|
||||
}
|
||||
];
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Group, Text } from '@mantine/core';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { formatPriceRange } from '../../defaults/formatters';
|
||||
@@ -9,7 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { usePartFields } from '../../forms/PartForms';
|
||||
import { shortenString } from '../../functions/tables';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
@@ -43,13 +41,7 @@ function partTableColumns(): TableColumn[] {
|
||||
{
|
||||
accessor: 'category',
|
||||
sortable: true,
|
||||
|
||||
render: function (record: any) {
|
||||
// TODO: Link to the category detail page
|
||||
return shortenString({
|
||||
str: record.category_detail?.pathstring
|
||||
});
|
||||
}
|
||||
render: (record: any) => record.category_detail?.pathstring
|
||||
},
|
||||
{
|
||||
accessor: 'total_in_stock',
|
||||
|
@@ -52,7 +52,11 @@ type ThumbProps = {
|
||||
/**
|
||||
* Renders a single image thumbnail
|
||||
*/
|
||||
function PartThumbComponent({ selected, element, selectImage }: ThumbProps) {
|
||||
function PartThumbComponent({
|
||||
selected,
|
||||
element,
|
||||
selectImage
|
||||
}: Readonly<ThumbProps>) {
|
||||
const { hovered, ref } = useHover();
|
||||
|
||||
const hoverColor = 'rgba(127,127,127,0.2)';
|
||||
|
@@ -8,6 +8,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
||||
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
@@ -44,6 +45,9 @@ export function PurchaseOrderTable({
|
||||
const table = useTable('purchase-order');
|
||||
const user = useUserState();
|
||||
|
||||
const projectCodeFilters = useProjectCodeFilters();
|
||||
const responsibleFilters = useOwnerFilters();
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@@ -54,11 +58,26 @@ export function PurchaseOrderTable({
|
||||
},
|
||||
OutstandingFilter(),
|
||||
OverdueFilter(),
|
||||
AssignedToMeFilter()
|
||||
// TODO: has_project_code
|
||||
// TODO: project_code
|
||||
AssignedToMeFilter(),
|
||||
{
|
||||
name: 'project_code',
|
||||
label: t`Project Code`,
|
||||
description: t`Filter by project code`,
|
||||
choices: projectCodeFilters.choices
|
||||
},
|
||||
{
|
||||
name: 'has_project_code',
|
||||
label: t`Has Project Code`,
|
||||
description: t`Filter by whether the purchase order has a project code`
|
||||
},
|
||||
{
|
||||
name: 'assigned_to',
|
||||
label: t`Responsible`,
|
||||
description: t`Filter by responsible owner`,
|
||||
choices: responsibleFilters.choices
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
}, [projectCodeFilters.choices, responsibleFilters.choices]);
|
||||
|
||||
const tableColumns = useMemo(() => {
|
||||
return [
|
||||
|
@@ -8,6 +8,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
|
||||
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
@@ -35,6 +36,9 @@ export function ReturnOrderTable({ params }: { params?: any }) {
|
||||
const table = useTable('return-orders');
|
||||
const user = useUserState();
|
||||
|
||||
const projectCodeFilters = useProjectCodeFilters();
|
||||
const responsibleFilters = useOwnerFilters();
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@@ -45,9 +49,26 @@ export function ReturnOrderTable({ params }: { params?: any }) {
|
||||
},
|
||||
OutstandingFilter(),
|
||||
OverdueFilter(),
|
||||
AssignedToMeFilter()
|
||||
AssignedToMeFilter(),
|
||||
{
|
||||
name: 'project_code',
|
||||
label: t`Project Code`,
|
||||
description: t`Filter by project code`,
|
||||
choices: projectCodeFilters.choices
|
||||
},
|
||||
{
|
||||
name: 'has_project_code',
|
||||
label: t`Has Project Code`,
|
||||
description: t`Filter by whether the purchase order has a project code`
|
||||
},
|
||||
{
|
||||
name: 'assigned_to',
|
||||
label: t`Responsible`,
|
||||
description: t`Filter by responsible owner`,
|
||||
choices: responsibleFilters.choices
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
}, [projectCodeFilters.choices, responsibleFilters.choices]);
|
||||
|
||||
const tableColumns = useMemo(() => {
|
||||
return [
|
||||
|
@@ -8,6 +8,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
||||
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
@@ -41,6 +42,9 @@ export function SalesOrderTable({
|
||||
const table = useTable('sales-order');
|
||||
const user = useUserState();
|
||||
|
||||
const projectCodeFilters = useProjectCodeFilters();
|
||||
const responsibleFilters = useOwnerFilters();
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@@ -51,11 +55,26 @@ export function SalesOrderTable({
|
||||
},
|
||||
OutstandingFilter(),
|
||||
OverdueFilter(),
|
||||
AssignedToMeFilter()
|
||||
// TODO: has_project_code
|
||||
// TODO: project_code
|
||||
AssignedToMeFilter(),
|
||||
{
|
||||
name: 'project_code',
|
||||
label: t`Project Code`,
|
||||
description: t`Filter by project code`,
|
||||
choices: projectCodeFilters.choices
|
||||
},
|
||||
{
|
||||
name: 'has_project_code',
|
||||
label: t`Has Project Code`,
|
||||
description: t`Filter by whether the purchase order has a project code`
|
||||
},
|
||||
{
|
||||
name: 'assigned_to',
|
||||
label: t`Responsible`,
|
||||
description: t`Filter by responsible owner`,
|
||||
choices: responsibleFilters.choices
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
}, [projectCodeFilters.choices, responsibleFilters.choices]);
|
||||
|
||||
const salesOrderFields = useSalesOrderFields();
|
||||
|
||||
|
@@ -4,7 +4,7 @@ import { ReactNode, useMemo } from 'react';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
||||
import { formatCurrency, renderDate } from '../../defaults/formatters';
|
||||
import { formatCurrency } from '../../defaults/formatters';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
|
@@ -27,10 +27,19 @@ test('PUI - Parts', async ({ page }) => {
|
||||
await page.getByText('1551ACLR').click();
|
||||
await page.getByRole('tab', { name: 'Part Details' }).click();
|
||||
await page.getByRole('tab', { name: 'Parameters' }).click();
|
||||
// await page.getByRole('tab', { name: 'Stock' }).click();
|
||||
await page
|
||||
.getByRole('tab', { name: 'Part Details' })
|
||||
.locator('xpath=..')
|
||||
.getByRole('tab', { name: 'Stock', exact: true })
|
||||
.click();
|
||||
await page.getByRole('tab', { name: 'Allocations' }).click();
|
||||
await page.getByRole('tab', { name: 'Used In' }).click();
|
||||
await page.getByRole('tab', { name: 'Pricing' }).click();
|
||||
|
||||
await page.goto(`${baseUrl}/part/category/index/parts`);
|
||||
await page.getByText('Blue Chair').click();
|
||||
await page.getByRole('tab', { name: 'Bill of Materials' }).click();
|
||||
await page.getByRole('tab', { name: 'Build Orders' }).click();
|
||||
});
|
||||
|
||||
test('PUI - Parts - Manufacturer Parts', async ({ page }) => {
|
||||
|
@@ -16,6 +16,14 @@ test('PUI - Stock', async ({ page }) => {
|
||||
await page.getByRole('tab', { name: 'Stock Locations' }).click();
|
||||
await page.getByRole('tab', { name: 'Stock Items' }).click();
|
||||
await page.getByRole('tab', { name: 'Location Details' }).click();
|
||||
|
||||
await page.goto(`${baseUrl}/stock/item/1194/details`);
|
||||
await page.getByText('D.123 | Doohickey').waitFor();
|
||||
await page.getByText('Batch Code: BX-123-2024-2-7').waitFor();
|
||||
await page.getByRole('tab', { name: 'Stock Tracking' }).click();
|
||||
await page.getByRole('tab', { name: 'Test Data' }).click();
|
||||
await page.getByText('395c6d5586e5fb656901d047be27e1f7').waitFor();
|
||||
await page.getByRole('tab', { name: 'Installed Items' }).click();
|
||||
});
|
||||
|
||||
test('PUI - Build', async ({ page }) => {
|
||||
|
Reference in New Issue
Block a user