From 73ca468ff7eb95267a51c15fbf31958038b2e37d Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 19 Oct 2025 11:34:04 +1100 Subject: [PATCH] [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 --- src/backend/InvenTree/order/models.py | 4 +-- .../pages/purchasing/PurchaseOrderDetail.tsx | 22 ++++++++++++-- .../src/pages/sales/ReturnOrderDetail.tsx | 25 ++++++++++++++-- .../src/pages/sales/SalesOrderDetail.tsx | 17 ++++++++--- .../src/tables/general/ExtraLineItemTable.tsx | 14 +++++---- .../purchasing/PurchaseOrderLineItemTable.tsx | 29 +++++++++---------- .../tables/sales/ReturnOrderLineItemTable.tsx | 17 +++++++---- 7 files changed, 90 insertions(+), 38 deletions(-) diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index d6e7de2300..cf35229a47 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -1687,7 +1687,7 @@ class OrderLineItem(InvenTree.models.InvenTreeMetadataModel): """ if self.order and self.order.check_locked(): 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) @@ -1703,7 +1703,7 @@ class OrderLineItem(InvenTree.models.InvenTreeMetadataModel): """ if self.order and self.order.check_locked(): 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) diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx index 20430cae28..87e1c4cbba 100644 --- a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx +++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx @@ -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 data = { ...order }; // 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} currency={orderCurrency} orderId={Number(id)} + editable={lineItemsEditable} supplierId={Number(order.supplier)} /> @@ -349,6 +368,7 @@ export default function PurchaseOrderDetail() { orderId={order.pk} orderDetailRefresh={refreshInstance} currency={orderCurrency} + editable={lineItemsEditable} role={UserRoles.purchase_order} /> @@ -380,8 +400,6 @@ export default function PurchaseOrderDetail() { ]; }, [order, id, user]); - const poStatus = useStatusCodes({ modelType: ModelType.purchaseorder }); - const issueOrder = useCreateApiFormModal({ url: apiUrl(ApiEndpoints.purchase_order_issue, order.pk), title: t`Issue Purchase Order`, diff --git a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx index e4040da166..e1116aed94 100644 --- a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx +++ b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx @@ -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(() => { return ( order.order_currency || @@ -299,6 +318,7 @@ export default function ReturnOrderDetail() { order={order} orderDetailRefresh={refreshInstance} customerId={order.customer} + editable={lineItemsEditable} currency={orderCurrency} /> @@ -313,6 +333,7 @@ export default function ReturnOrderDetail() { orderId={order.pk} orderDetailRefresh={refreshInstance} currency={orderCurrency} + editable={lineItemsEditable} role={UserRoles.return_order} /> @@ -409,8 +430,6 @@ export default function ReturnOrderDetail() { successMessage: t`Order completed` }); - const roStatus = useStatusCodes({ modelType: ModelType.returnorder }); - const orderActions = useMemo(() => { 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(() => { let t = order.customer_detail?.name || ''; diff --git a/src/frontend/src/pages/sales/SalesOrderDetail.tsx b/src/frontend/src/pages/sales/SalesOrderDetail.tsx index 1bd67d2eab..06e3a18918 100644 --- a/src/frontend/src/pages/sales/SalesOrderDetail.tsx +++ b/src/frontend/src/pages/sales/SalesOrderDetail.tsx @@ -284,6 +284,17 @@ export default function SalesOrderDetail() { 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 editSalesOrder = useEditApiFormModal({ @@ -345,10 +356,7 @@ export default function SalesOrderDetail() { orderDetailRefresh={refreshInstance} currency={orderCurrency} customerId={order.customer} - editable={ - order.status != soStatus.COMPLETE && - order.status != soStatus.CANCELLED - } + editable={lineItemsEditable} /> @@ -360,6 +368,7 @@ export default function SalesOrderDetail() { void; currency: string; role: UserRoles; @@ -119,21 +121,21 @@ export default function ExtraLineItemTable({ (record: any): RowAction[] => { return [ RowEditAction({ - hidden: !user.hasChangeRole(role), + hidden: !editable || !user.hasChangeRole(role), onClick: () => { setSelectedLine(record.pk); editLineItem.open(); } }), RowDuplicateAction({ - hidden: !user.hasAddRole(role), + hidden: !editable || !user.hasAddRole(role), onClick: () => { setInitialData({ ...record }); newLineItem.open(); } }), RowDeleteAction({ - hidden: !user.hasDeleteRole(role), + hidden: !editable || !user.hasDeleteRole(role), onClick: () => { setSelectedLine(record.pk); deleteLineItem.open(); @@ -141,7 +143,7 @@ export default function ExtraLineItemTable({ }) ]; }, - [user, role] + [editable, user, role] ); const tableActions = useMemo(() => { @@ -149,7 +151,7 @@ export default function ExtraLineItemTable({