2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-23 17:37:38 +00:00

[UI] Hide actions for completed orders (#10617)

* Adjust error message target

* Hide UI actions if order is locked

* Refactor salesorderdetail page

* Refactor PurchaseOrderDetail page

* Refactor ReturnOrderDetail
This commit is contained in:
Oliver
2025-10-19 11:34:04 +11:00
committed by GitHub
parent 2182fe42db
commit 73ca468ff7
7 changed files with 90 additions and 38 deletions

View File

@@ -1687,7 +1687,7 @@ class OrderLineItem(InvenTree.models.InvenTreeMetadataModel):
""" """
if self.order and self.order.check_locked(): if self.order and self.order.check_locked():
raise ValidationError({ raise ValidationError({
'reference': _('The order is locked and cannot be modified') 'non_field_errors': _('The order is locked and cannot be modified')
}) })
update_order = kwargs.pop('update_order', True) update_order = kwargs.pop('update_order', True)
@@ -1703,7 +1703,7 @@ class OrderLineItem(InvenTree.models.InvenTreeMetadataModel):
""" """
if self.order and self.order.check_locked(): if self.order and self.order.check_locked():
raise ValidationError({ raise ValidationError({
'reference': _('The order is locked and cannot be modified') 'non_field_errors': _('The order is locked and cannot be modified')
}) })
super().delete(*args, **kwargs) super().delete(*args, **kwargs)

View File

@@ -93,6 +93,24 @@ export default function PurchaseOrderDetail() {
} }
}); });
const poStatus = useStatusCodes({ modelType: ModelType.purchaseorder });
const orderOpen: boolean = useMemo(() => {
return (
order.status == poStatus.PENDING ||
order.status == poStatus.PLACED ||
order.status == poStatus.ON_HOLD
);
}, [order, poStatus]);
const lineItemsEditable: boolean = useMemo(() => {
if (orderOpen) {
return true;
} else {
return globalSettings.isSet('PURCHASEORDER_EDIT_COMPLETED_ORDERS');
}
}, [orderOpen, globalSettings]);
const duplicatePurchaseOrderInitialData = useMemo(() => { const duplicatePurchaseOrderInitialData = useMemo(() => {
const data = { ...order }; const data = { ...order };
// if we set the reference to null/undefined, it will be left blank in the form // if we set the reference to null/undefined, it will be left blank in the form
@@ -335,6 +353,7 @@ export default function PurchaseOrderDetail() {
orderDetailRefresh={refreshInstance} orderDetailRefresh={refreshInstance}
currency={orderCurrency} currency={orderCurrency}
orderId={Number(id)} orderId={Number(id)}
editable={lineItemsEditable}
supplierId={Number(order.supplier)} supplierId={Number(order.supplier)}
/> />
</Accordion.Panel> </Accordion.Panel>
@@ -349,6 +368,7 @@ export default function PurchaseOrderDetail() {
orderId={order.pk} orderId={order.pk}
orderDetailRefresh={refreshInstance} orderDetailRefresh={refreshInstance}
currency={orderCurrency} currency={orderCurrency}
editable={lineItemsEditable}
role={UserRoles.purchase_order} role={UserRoles.purchase_order}
/> />
</Accordion.Panel> </Accordion.Panel>
@@ -380,8 +400,6 @@ export default function PurchaseOrderDetail() {
]; ];
}, [order, id, user]); }, [order, id, user]);
const poStatus = useStatusCodes({ modelType: ModelType.purchaseorder });
const issueOrder = useCreateApiFormModal({ const issueOrder = useCreateApiFormModal({
url: apiUrl(ApiEndpoints.purchase_order_issue, order.pk), url: apiUrl(ApiEndpoints.purchase_order_issue, order.pk),
title: t`Issue Purchase Order`, title: t`Issue Purchase Order`,

View File

@@ -68,6 +68,25 @@ export default function ReturnOrderDetail() {
} }
}); });
const roStatus = useStatusCodes({ modelType: ModelType.returnorder });
const orderOpen = useMemo(() => {
return (
order.status == roStatus.PENDING ||
order.status == roStatus.PLACED ||
order.status == roStatus.IN_PROGRESS ||
order.status == roStatus.ON_HOLD
);
}, [order, roStatus]);
const lineItemsEditable: boolean = useMemo(() => {
if (orderOpen) {
return true;
} else {
return globalSettings.isSet('RETURNORDER_EDIT_COMPLETED_ORDERS');
}
}, [orderOpen, globalSettings]);
const orderCurrency = useMemo(() => { const orderCurrency = useMemo(() => {
return ( return (
order.order_currency || order.order_currency ||
@@ -299,6 +318,7 @@ export default function ReturnOrderDetail() {
order={order} order={order}
orderDetailRefresh={refreshInstance} orderDetailRefresh={refreshInstance}
customerId={order.customer} customerId={order.customer}
editable={lineItemsEditable}
currency={orderCurrency} currency={orderCurrency}
/> />
</Accordion.Panel> </Accordion.Panel>
@@ -313,6 +333,7 @@ export default function ReturnOrderDetail() {
orderId={order.pk} orderId={order.pk}
orderDetailRefresh={refreshInstance} orderDetailRefresh={refreshInstance}
currency={orderCurrency} currency={orderCurrency}
editable={lineItemsEditable}
role={UserRoles.return_order} role={UserRoles.return_order}
/> />
</Accordion.Panel> </Accordion.Panel>
@@ -409,8 +430,6 @@ export default function ReturnOrderDetail() {
successMessage: t`Order completed` successMessage: t`Order completed`
}); });
const roStatus = useStatusCodes({ modelType: ModelType.returnorder });
const orderActions = useMemo(() => { const orderActions = useMemo(() => {
const canEdit: boolean = user.hasChangeRole(UserRoles.return_order); const canEdit: boolean = user.hasChangeRole(UserRoles.return_order);
@@ -488,7 +507,7 @@ export default function ReturnOrderDetail() {
]} ]}
/> />
]; ];
}, [user, order, roStatus]); }, [user, order, orderOpen, roStatus]);
const subtitle: string = useMemo(() => { const subtitle: string = useMemo(() => {
let t = order.customer_detail?.name || ''; let t = order.customer_detail?.name || '';

View File

@@ -284,6 +284,17 @@ export default function SalesOrderDetail() {
const soStatus = useStatusCodes({ modelType: ModelType.salesorder }); const soStatus = useStatusCodes({ modelType: ModelType.salesorder });
const lineItemsEditable: boolean = useMemo(() => {
const orderOpen: boolean =
order.status != soStatus.COMPLETE && order.status != soStatus.CANCELLED;
if (orderOpen) {
return true;
} else {
return globalSettings.isSet('SALESORDER_EDIT_COMPLETED_ORDERS');
}
}, [globalSettings, order.status, soStatus]);
const salesOrderFields = useSalesOrderFields({}); const salesOrderFields = useSalesOrderFields({});
const editSalesOrder = useEditApiFormModal({ const editSalesOrder = useEditApiFormModal({
@@ -345,10 +356,7 @@ export default function SalesOrderDetail() {
orderDetailRefresh={refreshInstance} orderDetailRefresh={refreshInstance}
currency={orderCurrency} currency={orderCurrency}
customerId={order.customer} customerId={order.customer}
editable={ editable={lineItemsEditable}
order.status != soStatus.COMPLETE &&
order.status != soStatus.CANCELLED
}
/> />
</Accordion.Panel> </Accordion.Panel>
</Accordion.Item> </Accordion.Item>
@@ -360,6 +368,7 @@ export default function SalesOrderDetail() {
<ExtraLineItemTable <ExtraLineItemTable
endpoint={ApiEndpoints.sales_order_extra_line_list} endpoint={ApiEndpoints.sales_order_extra_line_list}
orderId={order.pk} orderId={order.pk}
editable={lineItemsEditable}
orderDetailRefresh={refreshInstance} orderDetailRefresh={refreshInstance}
currency={orderCurrency} currency={orderCurrency}
role={UserRoles.sales_order} role={UserRoles.sales_order}

View File

@@ -34,10 +34,12 @@ export default function ExtraLineItemTable({
orderId, orderId,
orderDetailRefresh, orderDetailRefresh,
currency, currency,
editable,
role role
}: Readonly<{ }: Readonly<{
endpoint: ApiEndpoints; endpoint: ApiEndpoints;
orderId: number; orderId: number;
editable: boolean;
orderDetailRefresh: () => void; orderDetailRefresh: () => void;
currency: string; currency: string;
role: UserRoles; role: UserRoles;
@@ -119,21 +121,21 @@ export default function ExtraLineItemTable({
(record: any): RowAction[] => { (record: any): RowAction[] => {
return [ return [
RowEditAction({ RowEditAction({
hidden: !user.hasChangeRole(role), hidden: !editable || !user.hasChangeRole(role),
onClick: () => { onClick: () => {
setSelectedLine(record.pk); setSelectedLine(record.pk);
editLineItem.open(); editLineItem.open();
} }
}), }),
RowDuplicateAction({ RowDuplicateAction({
hidden: !user.hasAddRole(role), hidden: !editable || !user.hasAddRole(role),
onClick: () => { onClick: () => {
setInitialData({ ...record }); setInitialData({ ...record });
newLineItem.open(); newLineItem.open();
} }
}), }),
RowDeleteAction({ RowDeleteAction({
hidden: !user.hasDeleteRole(role), hidden: !editable || !user.hasDeleteRole(role),
onClick: () => { onClick: () => {
setSelectedLine(record.pk); setSelectedLine(record.pk);
deleteLineItem.open(); deleteLineItem.open();
@@ -141,7 +143,7 @@ export default function ExtraLineItemTable({
}) })
]; ];
}, },
[user, role] [editable, user, role]
); );
const tableActions = useMemo(() => { const tableActions = useMemo(() => {
@@ -149,7 +151,7 @@ export default function ExtraLineItemTable({
<AddItemButton <AddItemButton
key='add-line-item' key='add-line-item'
tooltip={t`Add Extra Line Item`} tooltip={t`Add Extra Line Item`}
hidden={!user.hasAddRole(role)} hidden={!editable || !user.hasAddRole(role)}
onClick={() => { onClick={() => {
setInitialData({ setInitialData({
order: orderId order: orderId
@@ -158,7 +160,7 @@ export default function ExtraLineItemTable({
}} }}
/> />
]; ];
}, [user, role]); }, [editable, user, role]);
return ( return (
<> <>

View File

@@ -36,6 +36,7 @@ import {
} from '../../hooks/UseForm'; } from '../../hooks/UseForm';
import useStatusCodes from '../../hooks/UseStatusCodes'; import useStatusCodes from '../../hooks/UseStatusCodes';
import { useTable } from '../../hooks/UseTable'; import { useTable } from '../../hooks/UseTable';
import { useGlobalSettingsState } from '../../states/SettingsStates';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import { import {
CurrencyColumn, CurrencyColumn,
@@ -59,6 +60,7 @@ export function PurchaseOrderLineItemTable({
orderId, orderId,
currency, currency,
supplierId, supplierId,
editable,
params params
}: Readonly<{ }: Readonly<{
order: any; order: any;
@@ -66,10 +68,12 @@ export function PurchaseOrderLineItemTable({
orderId: number; orderId: number;
currency: string; currency: string;
supplierId?: number; supplierId?: number;
editable: boolean;
params?: any; params?: any;
}>) { }>) {
const table = useTable('purchase-order-line-item'); const table = useTable('purchase-order-line-item');
const globalSettings = useGlobalSettingsState();
const navigate = useNavigate(); const navigate = useNavigate();
const user = useUserState(); const user = useUserState();
@@ -327,14 +331,6 @@ export function PurchaseOrderLineItemTable({
const poStatus = useStatusCodes({ modelType: ModelType.purchaseorder }); const poStatus = useStatusCodes({ modelType: ModelType.purchaseorder });
const orderOpen: boolean = useMemo(() => {
return (
order.status == poStatus.PENDING ||
order.status == poStatus.PLACED ||
order.status == poStatus.ON_HOLD
);
}, [order, poStatus]);
const orderPlaced: boolean = useMemo(() => { const orderPlaced: boolean = useMemo(() => {
return order.status == poStatus.PLACED; return order.status == poStatus.PLACED;
}, [order, poStatus]); }, [order, poStatus]);
@@ -343,6 +339,9 @@ export function PurchaseOrderLineItemTable({
(record: any): RowAction[] => { (record: any): RowAction[] => {
const received = (record?.received ?? 0) >= (record?.quantity ?? 0); const received = (record?.received ?? 0) >= (record?.quantity ?? 0);
const canEdit: boolean =
editable && user.hasChangeRole(UserRoles.purchase_order);
return [ return [
{ {
hidden: received || !orderPlaced, hidden: received || !orderPlaced,
@@ -362,21 +361,21 @@ export function PurchaseOrderLineItemTable({
navigate: navigate navigate: navigate
}), }),
RowEditAction({ RowEditAction({
hidden: !user.hasChangeRole(UserRoles.purchase_order), hidden: !canEdit,
onClick: () => { onClick: () => {
setSelectedLine(record.pk); setSelectedLine(record.pk);
editLine.open(); editLine.open();
} }
}), }),
RowDuplicateAction({ RowDuplicateAction({
hidden: !orderOpen || !user.hasAddRole(UserRoles.purchase_order), hidden: !canEdit || !user.hasAddRole(UserRoles.purchase_order),
onClick: () => { onClick: () => {
setInitialData({ ...record }); setInitialData({ ...record });
newLine.open(); newLine.open();
} }
}), }),
RowDeleteAction({ RowDeleteAction({
hidden: !user.hasDeleteRole(UserRoles.purchase_order), hidden: !canEdit || !user.hasDeleteRole(UserRoles.purchase_order),
onClick: () => { onClick: () => {
setSelectedLine(record.pk); setSelectedLine(record.pk);
deleteLine.open(); deleteLine.open();
@@ -384,7 +383,7 @@ export function PurchaseOrderLineItemTable({
}) })
]; ];
}, },
[orderId, user, orderOpen, orderPlaced] [orderId, user, editable, orderPlaced]
); );
// Custom table actions // Custom table actions
@@ -392,7 +391,7 @@ export function PurchaseOrderLineItemTable({
return [ return [
<ActionButton <ActionButton
key='import-line-items' key='import-line-items'
hidden={!orderOpen || !user.hasAddRole(UserRoles.purchase_order)} hidden={!editable || !user.hasAddRole(UserRoles.purchase_order)}
tooltip={t`Import Line Items`} tooltip={t`Import Line Items`}
icon={<IconFileArrowLeft />} icon={<IconFileArrowLeft />}
onClick={() => importLineItems.open()} onClick={() => importLineItems.open()}
@@ -406,7 +405,7 @@ export function PurchaseOrderLineItemTable({
}); });
newLine.open(); newLine.open();
}} }}
hidden={!orderOpen || !user?.hasAddRole(UserRoles.purchase_order)} hidden={!editable || !user?.hasAddRole(UserRoles.purchase_order)}
/>, />,
<ActionButton <ActionButton
key='receive-items' key='receive-items'
@@ -417,7 +416,7 @@ export function PurchaseOrderLineItemTable({
hidden={!orderPlaced || !user.hasChangeRole(UserRoles.purchase_order)} hidden={!orderPlaced || !user.hasChangeRole(UserRoles.purchase_order)}
/> />
]; ];
}, [orderId, user, table, orderOpen, orderPlaced]); }, [orderId, user, table, editable, orderPlaced]);
return ( return (
<> <>

View File

@@ -45,12 +45,14 @@ export default function ReturnOrderLineItemTable({
order, order,
orderDetailRefresh, orderDetailRefresh,
customerId, customerId,
editable,
currency currency
}: Readonly<{ }: Readonly<{
orderId: number; orderId: number;
order: any; order: any;
orderDetailRefresh: () => void; orderDetailRefresh: () => void;
customerId: number; customerId: number;
editable: boolean;
currency: string; currency: string;
}>) { }>) {
const table = useTable('return-order-line-item'); const table = useTable('return-order-line-item');
@@ -181,7 +183,7 @@ export default function ReturnOrderLineItemTable({
<AddItemButton <AddItemButton
key='add-line-item' key='add-line-item'
tooltip={t`Add Line Item`} tooltip={t`Add Line Item`}
hidden={!user.hasAddRole(UserRoles.return_order)} hidden={!editable || !user.hasAddRole(UserRoles.return_order)}
onClick={() => { onClick={() => {
newLine.open(); newLine.open();
}} }}
@@ -190,7 +192,9 @@ export default function ReturnOrderLineItemTable({
key='receive-items' key='receive-items'
tooltip={t`Receive selected items`} tooltip={t`Receive selected items`}
icon={<IconSquareArrowRight />} icon={<IconSquareArrowRight />}
hidden={!inProgress || !user.hasChangeRole(UserRoles.return_order)} hidden={
!editable || inProgress || !user.hasChangeRole(UserRoles.return_order)
}
onClick={() => { onClick={() => {
setSelectedItems( setSelectedItems(
table.selectedRecords.filter((record: any) => !record.received_date) table.selectedRecords.filter((record: any) => !record.received_date)
@@ -200,7 +204,7 @@ export default function ReturnOrderLineItemTable({
disabled={table.selectedRecords.length == 0} disabled={table.selectedRecords.length == 0}
/> />
]; ];
}, [user, inProgress, orderId, table.selectedRecords]); }, [user, editable, inProgress, orderId, table.selectedRecords]);
const [selectedItems, setSelectedItems] = useState<any[]>([]); const [selectedItems, setSelectedItems] = useState<any[]>([]);
@@ -218,6 +222,7 @@ export default function ReturnOrderLineItemTable({
{ {
hidden: hidden:
received || received ||
!editable ||
!inProgress || !inProgress ||
!user.hasChangeRole(UserRoles.return_order), !user.hasChangeRole(UserRoles.return_order),
title: t`Receive Item`, title: t`Receive Item`,
@@ -228,14 +233,14 @@ export default function ReturnOrderLineItemTable({
} }
}, },
RowEditAction({ RowEditAction({
hidden: !user.hasChangeRole(UserRoles.return_order), hidden: !editable || !user.hasChangeRole(UserRoles.return_order),
onClick: () => { onClick: () => {
setSelectedLine(record.pk); setSelectedLine(record.pk);
editLine.open(); editLine.open();
} }
}), }),
RowDeleteAction({ RowDeleteAction({
hidden: !user.hasDeleteRole(UserRoles.return_order), hidden: !editable || !user.hasDeleteRole(UserRoles.return_order),
onClick: () => { onClick: () => {
setSelectedLine(record.pk); setSelectedLine(record.pk);
deleteLine.open(); deleteLine.open();
@@ -243,7 +248,7 @@ export default function ReturnOrderLineItemTable({
}) })
]; ];
}, },
[user, inProgress] [user, editable, inProgress]
); );
return ( return (