mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
[PUI] Sales order tables (#7793)
* Add placeholder for more sales order actions * Add <SalesOrderShipmentTable /> * Allow filtering by date fields * Add <ReturnOrderLineItemTable /> - Add label rendering for ReturnOrderLineItem * Add placeholder actions * Edit / delete / add line items for return order * Fix for duplicate action * Cleanup unused code * Bump API version * Update playwright tests
This commit is contained in:
parent
dee519e848
commit
abe9b19ead
@ -1,12 +1,16 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 231
|
INVENTREE_API_VERSION = 232
|
||||||
|
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v232 - 2024-08-03 : https://github.com/inventree/InvenTree/pull/7793
|
||||||
|
- Allow ordering of SalesOrderShipment API by 'shipment_date' and 'delivery_date'
|
||||||
|
|
||||||
v231 - 2024-08-03 : https://github.com/inventree/InvenTree/pull/7794
|
v231 - 2024-08-03 : https://github.com/inventree/InvenTree/pull/7794
|
||||||
- Optimize BuildItem and BuildLine serializers to improve API efficiency
|
- Optimize BuildItem and BuildLine serializers to improve API efficiency
|
||||||
|
|
||||||
|
@ -1042,7 +1042,9 @@ class SalesOrderShipmentList(ListCreateAPI):
|
|||||||
serializer_class = serializers.SalesOrderShipmentSerializer
|
serializer_class = serializers.SalesOrderShipmentSerializer
|
||||||
filterset_class = SalesOrderShipmentFilter
|
filterset_class = SalesOrderShipmentFilter
|
||||||
|
|
||||||
filter_backends = [rest_filters.DjangoFilterBackend]
|
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||||
|
|
||||||
|
ordering_fields = ['delivery_date', 'shipment_date']
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderShipmentDetail(RetrieveUpdateDestroyAPI):
|
class SalesOrderShipmentDetail(RetrieveUpdateDestroyAPI):
|
||||||
|
@ -21,6 +21,7 @@ import { ModelInformationDict } from './ModelType';
|
|||||||
import {
|
import {
|
||||||
RenderPurchaseOrder,
|
RenderPurchaseOrder,
|
||||||
RenderReturnOrder,
|
RenderReturnOrder,
|
||||||
|
RenderReturnOrderLineItem,
|
||||||
RenderSalesOrder,
|
RenderSalesOrder,
|
||||||
RenderSalesOrderShipment
|
RenderSalesOrderShipment
|
||||||
} from './Order';
|
} from './Order';
|
||||||
@ -73,6 +74,7 @@ const RendererLookup: EnumDictionary<
|
|||||||
[ModelType.purchaseorder]: RenderPurchaseOrder,
|
[ModelType.purchaseorder]: RenderPurchaseOrder,
|
||||||
[ModelType.purchaseorderlineitem]: RenderPurchaseOrder,
|
[ModelType.purchaseorderlineitem]: RenderPurchaseOrder,
|
||||||
[ModelType.returnorder]: RenderReturnOrder,
|
[ModelType.returnorder]: RenderReturnOrder,
|
||||||
|
[ModelType.returnorderlineitem]: RenderReturnOrderLineItem,
|
||||||
[ModelType.salesorder]: RenderSalesOrder,
|
[ModelType.salesorder]: RenderSalesOrder,
|
||||||
[ModelType.salesordershipment]: RenderSalesOrderShipment,
|
[ModelType.salesordershipment]: RenderSalesOrderShipment,
|
||||||
[ModelType.stocklocation]: RenderStockLocation,
|
[ModelType.stocklocation]: RenderStockLocation,
|
||||||
|
@ -173,6 +173,11 @@ export const ModelInformationDict: ModelDict = {
|
|||||||
api_endpoint: ApiEndpoints.return_order_list,
|
api_endpoint: ApiEndpoints.return_order_list,
|
||||||
admin_url: '/order/returnorder/'
|
admin_url: '/order/returnorder/'
|
||||||
},
|
},
|
||||||
|
returnorderlineitem: {
|
||||||
|
label: t`Return Order Line Item`,
|
||||||
|
label_multiple: t`Return Order Line Items`,
|
||||||
|
api_endpoint: ApiEndpoints.return_order_line_list
|
||||||
|
},
|
||||||
address: {
|
address: {
|
||||||
label: t`Address`,
|
label: t`Address`,
|
||||||
label_multiple: t`Addresses`,
|
label_multiple: t`Addresses`,
|
||||||
|
@ -62,6 +62,23 @@ export function RenderReturnOrder(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RenderReturnOrderLineItem(
|
||||||
|
props: Readonly<InstanceRenderInterface>
|
||||||
|
): ReactNode {
|
||||||
|
const { instance } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RenderInlineModel
|
||||||
|
{...props}
|
||||||
|
primary={instance.reference}
|
||||||
|
suffix={StatusRenderer({
|
||||||
|
status: instance.outcome,
|
||||||
|
type: ModelType.returnorderlineitem
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline rendering of a single SalesOrder instance
|
* Inline rendering of a single SalesOrder instance
|
||||||
*/
|
*/
|
||||||
|
@ -2,6 +2,7 @@ import { Badge, Center, MantineSize } from '@mantine/core';
|
|||||||
|
|
||||||
import { colorMap } from '../../defaults/backendMappings';
|
import { colorMap } from '../../defaults/backendMappings';
|
||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
|
import { resolveItem } from '../../functions/conversion';
|
||||||
import { useGlobalStatusState } from '../../states/StatusState';
|
import { useGlobalStatusState } from '../../states/StatusState';
|
||||||
|
|
||||||
interface StatusCodeInterface {
|
interface StatusCodeInterface {
|
||||||
@ -132,10 +133,16 @@ export const StatusRenderer = ({
|
|||||||
* Render the status badge in a table
|
* Render the status badge in a table
|
||||||
*/
|
*/
|
||||||
export function TableStatusRenderer(
|
export function TableStatusRenderer(
|
||||||
type: ModelType
|
type: ModelType,
|
||||||
|
accessor?: string
|
||||||
): ((record: any) => any) | undefined {
|
): ((record: any) => any) | undefined {
|
||||||
return (record: any) =>
|
return (record: any) => {
|
||||||
record.status && (
|
const status = resolveItem(record, accessor ?? 'status');
|
||||||
<Center>{StatusRenderer({ status: record.status, type: type })}</Center>
|
|
||||||
|
return (
|
||||||
|
status && (
|
||||||
|
<Center>{StatusRenderer({ status: status, type: type })}</Center>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ import { ModelType } from '../enums/ModelType';
|
|||||||
export const statusCodeList: Record<string, ModelType> = {
|
export const statusCodeList: Record<string, ModelType> = {
|
||||||
BuildStatus: ModelType.build,
|
BuildStatus: ModelType.build,
|
||||||
PurchaseOrderStatus: ModelType.purchaseorder,
|
PurchaseOrderStatus: ModelType.purchaseorder,
|
||||||
ReturnOrderLineStatus: ModelType.purchaseorderlineitem,
|
|
||||||
ReturnOrderStatus: ModelType.returnorder,
|
ReturnOrderStatus: ModelType.returnorder,
|
||||||
|
ReturnOrderLineStatus: ModelType.returnorderlineitem,
|
||||||
SalesOrderStatus: ModelType.salesorder,
|
SalesOrderStatus: ModelType.salesorder,
|
||||||
StockHistoryCode: ModelType.stockhistory,
|
StockHistoryCode: ModelType.stockhistory,
|
||||||
StockStatus: ModelType.stockitem,
|
StockStatus: ModelType.stockitem,
|
||||||
|
@ -132,6 +132,7 @@ export enum ApiEndpoints {
|
|||||||
sales_order_shipment_list = 'order/so/shipment/',
|
sales_order_shipment_list = 'order/so/shipment/',
|
||||||
|
|
||||||
return_order_list = 'order/ro/',
|
return_order_list = 'order/ro/',
|
||||||
|
return_order_line_list = 'order/ro-line/',
|
||||||
|
|
||||||
// Template API endpoints
|
// Template API endpoints
|
||||||
label_list = 'label/template/',
|
label_list = 'label/template/',
|
||||||
|
@ -22,6 +22,7 @@ export enum ModelType {
|
|||||||
salesorder = 'salesorder',
|
salesorder = 'salesorder',
|
||||||
salesordershipment = 'salesordershipment',
|
salesordershipment = 'salesordershipment',
|
||||||
returnorder = 'returnorder',
|
returnorder = 'returnorder',
|
||||||
|
returnorderlineitem = 'returnorderlineitem',
|
||||||
importsession = 'importsession',
|
importsession = 'importsession',
|
||||||
address = 'address',
|
address = 'address',
|
||||||
contact = 'contact',
|
contact = 'contact',
|
||||||
|
38
src/frontend/src/forms/ReturnOrderForms.tsx
Normal file
38
src/frontend/src/forms/ReturnOrderForms.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export function useReturnOrderLineItemFields({
|
||||||
|
orderId,
|
||||||
|
customerId,
|
||||||
|
create
|
||||||
|
}: {
|
||||||
|
orderId: number;
|
||||||
|
customerId: number;
|
||||||
|
create?: boolean;
|
||||||
|
}) {
|
||||||
|
return useMemo(() => {
|
||||||
|
return {
|
||||||
|
order: {
|
||||||
|
disabled: true,
|
||||||
|
filters: {
|
||||||
|
customer_detail: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
filters: {
|
||||||
|
customer: customerId,
|
||||||
|
part_detail: true,
|
||||||
|
serialized: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reference: {},
|
||||||
|
outcome: {
|
||||||
|
hidden: create == true
|
||||||
|
},
|
||||||
|
price: {},
|
||||||
|
price_currency: {},
|
||||||
|
target_date: {},
|
||||||
|
notes: {},
|
||||||
|
link: {}
|
||||||
|
};
|
||||||
|
}, [create, orderId, customerId]);
|
||||||
|
}
|
@ -84,6 +84,23 @@ export function useSalesOrderLineItemFields({
|
|||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useSalesOrderShipmentFields(): ApiFormFieldSet {
|
||||||
|
return useMemo(() => {
|
||||||
|
return {
|
||||||
|
order: {
|
||||||
|
disabled: true
|
||||||
|
},
|
||||||
|
reference: {},
|
||||||
|
shipment_date: {},
|
||||||
|
delivery_date: {},
|
||||||
|
tracking_number: {},
|
||||||
|
invoice_number: {},
|
||||||
|
link: {},
|
||||||
|
notes: {}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
export function useReturnOrderFields(): ApiFormFieldSet {
|
export function useReturnOrderFields(): ApiFormFieldSet {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return {
|
return {
|
||||||
|
@ -26,7 +26,6 @@ import {
|
|||||||
UnlinkBarcodeAction,
|
UnlinkBarcodeAction,
|
||||||
ViewBarcodeAction
|
ViewBarcodeAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
|
||||||
import InstanceDetail from '../../components/nav/InstanceDetail';
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
@ -43,6 +42,7 @@ import {
|
|||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
||||||
|
import ReturnOrderLineItemTable from '../../tables/sales/ReturnOrderLineItemTable';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detail page for a single ReturnOrder
|
* Detail page for a single ReturnOrder
|
||||||
@ -227,7 +227,12 @@ export default function ReturnOrderDetail() {
|
|||||||
name: 'line-items',
|
name: 'line-items',
|
||||||
label: t`Line Items`,
|
label: t`Line Items`,
|
||||||
icon: <IconList />,
|
icon: <IconList />,
|
||||||
content: <PlaceholderPanel />
|
content: (
|
||||||
|
<ReturnOrderLineItemTable
|
||||||
|
orderId={order.pk}
|
||||||
|
customerId={order.customer}
|
||||||
|
/>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'attachments',
|
name: 'attachments',
|
||||||
|
@ -7,8 +7,7 @@ import {
|
|||||||
IconNotes,
|
IconNotes,
|
||||||
IconPaperclip,
|
IconPaperclip,
|
||||||
IconTools,
|
IconTools,
|
||||||
IconTruckDelivery,
|
IconTruckDelivery
|
||||||
IconTruckLoading
|
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { ReactNode, useMemo } from 'react';
|
import { ReactNode, useMemo } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
@ -29,7 +28,6 @@ import {
|
|||||||
UnlinkBarcodeAction,
|
UnlinkBarcodeAction,
|
||||||
ViewBarcodeAction
|
ViewBarcodeAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
|
||||||
import InstanceDetail from '../../components/nav/InstanceDetail';
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
@ -48,6 +46,7 @@ import { useUserState } from '../../states/UserState';
|
|||||||
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
||||||
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
||||||
import SalesOrderLineItemTable from '../../tables/sales/SalesOrderLineItemTable';
|
import SalesOrderLineItemTable from '../../tables/sales/SalesOrderLineItemTable';
|
||||||
|
import SalesOrderShipmentTable from '../../tables/sales/SalesOrderShipmentTable';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detail page for a single SalesOrder
|
* Detail page for a single SalesOrder
|
||||||
@ -258,16 +257,10 @@ export default function SalesOrderDetail() {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'pending-shipments',
|
name: 'shipments',
|
||||||
label: t`Pending Shipments`,
|
label: t`Shipments`,
|
||||||
icon: <IconTruckLoading />,
|
|
||||||
content: <PlaceholderPanel />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'completed-shipments',
|
|
||||||
label: t`Completed Shipments`,
|
|
||||||
icon: <IconTruckDelivery />,
|
icon: <IconTruckDelivery />,
|
||||||
content: <PlaceholderPanel />
|
content: <SalesOrderShipmentTable orderId={order.pk} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'build-orders',
|
name: 'build-orders',
|
||||||
|
@ -163,16 +163,19 @@ export function ProjectCodeColumn(props: TableColumnProps): TableColumn {
|
|||||||
export function StatusColumn({
|
export function StatusColumn({
|
||||||
model,
|
model,
|
||||||
sortable,
|
sortable,
|
||||||
accessor
|
accessor,
|
||||||
|
title
|
||||||
}: {
|
}: {
|
||||||
model: ModelType;
|
model: ModelType;
|
||||||
sortable?: boolean;
|
sortable?: boolean;
|
||||||
accessor?: string;
|
accessor?: string;
|
||||||
|
title?: string;
|
||||||
}) {
|
}) {
|
||||||
return {
|
return {
|
||||||
accessor: accessor ?? 'status',
|
accessor: accessor ?? 'status',
|
||||||
sortable: sortable ?? true,
|
sortable: sortable ?? true,
|
||||||
render: TableStatusRenderer(model)
|
title: title,
|
||||||
|
render: TableStatusRenderer(model, accessor ?? 'status')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
199
src/frontend/src/tables/sales/ReturnOrderLineItemTable.tsx
Normal file
199
src/frontend/src/tables/sales/ReturnOrderLineItemTable.tsx
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { IconSquareArrowRight } from '@tabler/icons-react';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
|
import { formatCurrency } from '../../defaults/formatters';
|
||||||
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
|
import { ModelType } from '../../enums/ModelType';
|
||||||
|
import { UserRoles } from '../../enums/Roles';
|
||||||
|
import { useReturnOrderLineItemFields } from '../../forms/ReturnOrderForms';
|
||||||
|
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 {
|
||||||
|
DateColumn,
|
||||||
|
LinkColumn,
|
||||||
|
NoteColumn,
|
||||||
|
PartColumn,
|
||||||
|
ReferenceColumn,
|
||||||
|
StatusColumn
|
||||||
|
} from '../ColumnRenderers';
|
||||||
|
import { StatusFilterOptions, TableFilter } from '../Filter';
|
||||||
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
|
import { RowDeleteAction, RowEditAction } from '../RowActions';
|
||||||
|
|
||||||
|
export default function ReturnOrderLineItemTable({
|
||||||
|
orderId,
|
||||||
|
customerId
|
||||||
|
}: {
|
||||||
|
orderId: number;
|
||||||
|
customerId: number;
|
||||||
|
}) {
|
||||||
|
const table = useTable('return-order-line-item');
|
||||||
|
const user = useUserState();
|
||||||
|
|
||||||
|
const [selectedLine, setSelectedLine] = useState<number>(0);
|
||||||
|
|
||||||
|
const newLineFields = useReturnOrderLineItemFields({
|
||||||
|
orderId: orderId,
|
||||||
|
customerId: customerId,
|
||||||
|
create: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const editLineFields = useReturnOrderLineItemFields({
|
||||||
|
orderId: orderId,
|
||||||
|
customerId: customerId
|
||||||
|
});
|
||||||
|
|
||||||
|
const newLine = useCreateApiFormModal({
|
||||||
|
url: ApiEndpoints.return_order_line_list,
|
||||||
|
title: t`Add Line Item`,
|
||||||
|
fields: newLineFields,
|
||||||
|
initialData: {
|
||||||
|
order: orderId
|
||||||
|
},
|
||||||
|
table: table
|
||||||
|
});
|
||||||
|
|
||||||
|
const editLine = useEditApiFormModal({
|
||||||
|
url: ApiEndpoints.return_order_line_list,
|
||||||
|
pk: selectedLine,
|
||||||
|
title: t`Edit Line Item`,
|
||||||
|
fields: editLineFields,
|
||||||
|
table: table
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteLine = useDeleteApiFormModal({
|
||||||
|
url: ApiEndpoints.return_order_line_list,
|
||||||
|
pk: selectedLine,
|
||||||
|
title: t`Delete Line Item`,
|
||||||
|
table: table
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableColumns: TableColumn[] = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
accessor: 'part',
|
||||||
|
title: t`Part`,
|
||||||
|
switchable: false,
|
||||||
|
render: (record: any) => PartColumn(record?.part_detail)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'item',
|
||||||
|
title: t`Stock Item`,
|
||||||
|
switchable: false
|
||||||
|
},
|
||||||
|
ReferenceColumn({}),
|
||||||
|
StatusColumn({
|
||||||
|
model: ModelType.returnorderlineitem,
|
||||||
|
sortable: true,
|
||||||
|
accessor: 'outcome'
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
accessor: 'price',
|
||||||
|
render: (record: any) =>
|
||||||
|
formatCurrency(record.price, { currency: record.price_currency })
|
||||||
|
},
|
||||||
|
DateColumn({
|
||||||
|
accessor: 'target_date',
|
||||||
|
title: t`Target Date`
|
||||||
|
}),
|
||||||
|
DateColumn({
|
||||||
|
accessor: 'received_date',
|
||||||
|
title: t`Received Date`
|
||||||
|
}),
|
||||||
|
NoteColumn({
|
||||||
|
accessor: 'notes'
|
||||||
|
}),
|
||||||
|
LinkColumn({})
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const tableFilters: TableFilter[] = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'received',
|
||||||
|
label: t`Received`,
|
||||||
|
description: t`Show items which have been received`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'status',
|
||||||
|
label: t`Status`,
|
||||||
|
description: t`Filter by line item status`,
|
||||||
|
choiceFunction: StatusFilterOptions(ModelType.returnorderlineitem)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const tableActions = useMemo(() => {
|
||||||
|
return [
|
||||||
|
<AddItemButton
|
||||||
|
tooltip={t`Add line item`}
|
||||||
|
hidden={!user.hasAddRole(UserRoles.return_order)}
|
||||||
|
onClick={() => {
|
||||||
|
newLine.open();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
];
|
||||||
|
}, [user, orderId]);
|
||||||
|
|
||||||
|
const rowActions = useCallback(
|
||||||
|
(record: any) => {
|
||||||
|
const received: boolean = !!record?.received_date;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
hidden: received || !user.hasChangeRole(UserRoles.return_order),
|
||||||
|
title: t`Receive Item`,
|
||||||
|
icon: <IconSquareArrowRight />
|
||||||
|
},
|
||||||
|
RowEditAction({
|
||||||
|
hidden: !user.hasChangeRole(UserRoles.return_order),
|
||||||
|
onClick: () => {
|
||||||
|
setSelectedLine(record.pk);
|
||||||
|
editLine.open();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
RowDeleteAction({
|
||||||
|
hidden: !user.hasDeleteRole(UserRoles.return_order),
|
||||||
|
onClick: () => {
|
||||||
|
setSelectedLine(record.pk);
|
||||||
|
deleteLine.open();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
];
|
||||||
|
},
|
||||||
|
[user]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{newLine.modal}
|
||||||
|
{editLine.modal}
|
||||||
|
{deleteLine.modal}
|
||||||
|
<InvenTreeTable
|
||||||
|
url={apiUrl(ApiEndpoints.return_order_line_list)}
|
||||||
|
tableState={table}
|
||||||
|
columns={tableColumns}
|
||||||
|
props={{
|
||||||
|
params: {
|
||||||
|
order: orderId,
|
||||||
|
part_detail: true,
|
||||||
|
item_detail: true,
|
||||||
|
order_detail: true
|
||||||
|
},
|
||||||
|
tableActions: tableActions,
|
||||||
|
tableFilters: tableFilters,
|
||||||
|
rowActions: rowActions
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,6 +1,10 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Text } from '@mantine/core';
|
import { Text } from '@mantine/core';
|
||||||
import { IconSquareArrowRight } from '@tabler/icons-react';
|
import {
|
||||||
|
IconShoppingCart,
|
||||||
|
IconSquareArrowRight,
|
||||||
|
IconTools
|
||||||
|
} from '@tabler/icons-react';
|
||||||
import { ReactNode, useCallback, useMemo, useState } from 'react';
|
import { ReactNode, useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
@ -206,7 +210,7 @@ export default function SalesOrderLineItemTable({
|
|||||||
hidden={!user.hasAddRole(UserRoles.sales_order)}
|
hidden={!user.hasAddRole(UserRoles.sales_order)}
|
||||||
/>
|
/>
|
||||||
];
|
];
|
||||||
}, [user]);
|
}, [user, orderId]);
|
||||||
|
|
||||||
const rowActions = useCallback(
|
const rowActions = useCallback(
|
||||||
(record: any) => {
|
(record: any) => {
|
||||||
@ -219,6 +223,24 @@ export default function SalesOrderLineItemTable({
|
|||||||
icon: <IconSquareArrowRight />,
|
icon: <IconSquareArrowRight />,
|
||||||
color: 'green'
|
color: 'green'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
hidden:
|
||||||
|
allocated ||
|
||||||
|
!user.hasAddRole(UserRoles.build) ||
|
||||||
|
!record?.part_detail?.assembly,
|
||||||
|
title: t`Build stock`,
|
||||||
|
icon: <IconTools />,
|
||||||
|
color: 'blue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hidden:
|
||||||
|
allocated ||
|
||||||
|
!user.hasAddRole(UserRoles.purchase_order) ||
|
||||||
|
!record?.part_detail?.purchaseable,
|
||||||
|
title: t`Order stock`,
|
||||||
|
icon: <IconShoppingCart />,
|
||||||
|
color: 'blue'
|
||||||
|
},
|
||||||
RowEditAction({
|
RowEditAction({
|
||||||
hidden: !user.hasChangeRole(UserRoles.sales_order),
|
hidden: !user.hasChangeRole(UserRoles.sales_order),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
175
src/frontend/src/tables/sales/SalesOrderShipmentTable.tsx
Normal file
175
src/frontend/src/tables/sales/SalesOrderShipmentTable.tsx
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { IconTruckDelivery } from '@tabler/icons-react';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
|
import { UserRoles } from '../../enums/Roles';
|
||||||
|
import { useSalesOrderShipmentFields } from '../../forms/SalesOrderForms';
|
||||||
|
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 { DateColumn, LinkColumn, NoteColumn } from '../ColumnRenderers';
|
||||||
|
import { TableFilter } from '../Filter';
|
||||||
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
|
import { RowDeleteAction, RowEditAction } from '../RowActions';
|
||||||
|
|
||||||
|
export default function SalesOrderShipmentTable({
|
||||||
|
orderId
|
||||||
|
}: {
|
||||||
|
orderId: number;
|
||||||
|
}) {
|
||||||
|
const user = useUserState();
|
||||||
|
const table = useTable('sales-order-shipment');
|
||||||
|
|
||||||
|
const [selectedShipment, setSelectedShipment] = useState<number>(0);
|
||||||
|
|
||||||
|
const newShipmentFields = useSalesOrderShipmentFields();
|
||||||
|
const editShipmentFields = useSalesOrderShipmentFields();
|
||||||
|
|
||||||
|
const newShipment = useCreateApiFormModal({
|
||||||
|
url: ApiEndpoints.sales_order_shipment_list,
|
||||||
|
fields: newShipmentFields,
|
||||||
|
title: t`Create Shipment`,
|
||||||
|
table: table,
|
||||||
|
initialData: {
|
||||||
|
order: orderId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteShipment = useDeleteApiFormModal({
|
||||||
|
url: ApiEndpoints.sales_order_shipment_list,
|
||||||
|
pk: selectedShipment,
|
||||||
|
title: t`Delete Shipment`,
|
||||||
|
table: table
|
||||||
|
});
|
||||||
|
|
||||||
|
const editShipment = useEditApiFormModal({
|
||||||
|
url: ApiEndpoints.sales_order_shipment_list,
|
||||||
|
pk: selectedShipment,
|
||||||
|
fields: editShipmentFields,
|
||||||
|
title: t`Edit Shipment`,
|
||||||
|
table: table
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableColumns: TableColumn[] = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
accessor: 'reference',
|
||||||
|
title: t`Shipment Reference`,
|
||||||
|
switchable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'allocations',
|
||||||
|
title: t`Items`,
|
||||||
|
render: (record: any) => {
|
||||||
|
let allocations = record?.allocations ?? [];
|
||||||
|
return allocations.length;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DateColumn({
|
||||||
|
accessor: 'shipment_date',
|
||||||
|
title: t`Shipment Date`
|
||||||
|
}),
|
||||||
|
DateColumn({
|
||||||
|
accessor: 'delivery_date',
|
||||||
|
title: t`Delivery Date`
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
accessor: 'tracking_number'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'invoice_number'
|
||||||
|
},
|
||||||
|
LinkColumn({
|
||||||
|
accessor: 'link'
|
||||||
|
}),
|
||||||
|
NoteColumn({
|
||||||
|
accessor: 'notes'
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const rowActions = useCallback(
|
||||||
|
(record: any) => {
|
||||||
|
const shipped: boolean = !!record.shipment_date;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
hidden: shipped || !user.hasChangeRole(UserRoles.sales_order),
|
||||||
|
title: t`Complete Shipment`,
|
||||||
|
icon: <IconTruckDelivery />
|
||||||
|
},
|
||||||
|
RowEditAction({
|
||||||
|
hidden: !user.hasChangeRole(UserRoles.sales_order),
|
||||||
|
onClick: () => {
|
||||||
|
setSelectedShipment(record.pk);
|
||||||
|
editShipment.open();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
RowDeleteAction({
|
||||||
|
hidden: !user.hasDeleteRole(UserRoles.sales_order),
|
||||||
|
onClick: () => {
|
||||||
|
setSelectedShipment(record.pk);
|
||||||
|
deleteShipment.open();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
];
|
||||||
|
},
|
||||||
|
[user]
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableActions = useMemo(() => {
|
||||||
|
return [
|
||||||
|
<AddItemButton
|
||||||
|
tooltip={t`Add shipment`}
|
||||||
|
hidden={!user.hasAddRole(UserRoles.sales_order)}
|
||||||
|
onClick={() => {
|
||||||
|
newShipment.open();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
];
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const tableFilters: TableFilter[] = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'shipped',
|
||||||
|
label: t`Shipped`,
|
||||||
|
description: t`Show shipments which have been shipped`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delivered',
|
||||||
|
label: t`Delivered`,
|
||||||
|
description: t`Show shipments which have been delivered`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{newShipment.modal}
|
||||||
|
{editShipment.modal}
|
||||||
|
{deleteShipment.modal}
|
||||||
|
<InvenTreeTable
|
||||||
|
url={apiUrl(ApiEndpoints.sales_order_shipment_list)}
|
||||||
|
tableState={table}
|
||||||
|
columns={tableColumns}
|
||||||
|
props={{
|
||||||
|
tableActions: tableActions,
|
||||||
|
tableFilters: tableFilters,
|
||||||
|
rowActions: rowActions,
|
||||||
|
params: {
|
||||||
|
order: orderId
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -101,8 +101,7 @@ test('PUI - Sales', async ({ page }) => {
|
|||||||
.getByText('Selling some stuff')
|
.getByText('Selling some stuff')
|
||||||
.waitFor();
|
.waitFor();
|
||||||
await page.getByRole('tab', { name: 'Line Items' }).click();
|
await page.getByRole('tab', { name: 'Line Items' }).click();
|
||||||
await page.getByRole('tab', { name: 'Pending Shipments' }).click();
|
await page.getByRole('tab', { name: 'Shipments' }).click();
|
||||||
await page.getByRole('tab', { name: 'Completed Shipments' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Build Orders' }).click();
|
await page.getByRole('tab', { name: 'Build Orders' }).click();
|
||||||
await page.getByText('No records found').first().waitFor();
|
await page.getByText('No records found').first().waitFor();
|
||||||
await page.getByRole('tab', { name: 'Attachments' }).click();
|
await page.getByRole('tab', { name: 'Attachments' }).click();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user