From 8b44dfbc4ed0bf8eb19ac309088a8aaaabe4df34 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 16 Aug 2024 15:53:29 +1000 Subject: [PATCH] [PUI] Extra line table (#7889) * Add generic "extra line item" table * Add "role" as parameter * Add placeholder actions * Fix price rendering * Add forms to create / edit / delete extra line items * Tweak type annotation --- src/frontend/src/enums/ApiEndpoints.tsx | 3 + src/frontend/src/forms/CommonForms.tsx | 15 ++ .../pages/purchasing/PurchaseOrderDetail.tsx | 38 +++- .../src/pages/sales/ReturnOrderDetail.tsx | 36 +++- .../src/pages/sales/SalesOrderDetail.tsx | 45 +++-- .../src/tables/general/ExtraLineItemTable.tsx | 169 ++++++++++++++++++ .../purchasing/PurchaseOrderLineItemTable.tsx | 2 +- 7 files changed, 286 insertions(+), 22 deletions(-) create mode 100644 src/frontend/src/tables/general/ExtraLineItemTable.tsx diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index 3cb3b1b1f0..a7221d62b7 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -132,6 +132,7 @@ export enum ApiEndpoints { purchase_order_cancel = 'order/po/:id/cancel/', purchase_order_complete = 'order/po/:id/complete/', purchase_order_line_list = 'order/po-line/', + purchase_order_extra_line_list = 'order/po-extra-line/', purchase_order_receive = 'order/po/:id/receive/', sales_order_list = 'order/so/', @@ -141,6 +142,7 @@ export enum ApiEndpoints { sales_order_ship = 'order/so/:id/ship/', sales_order_complete = 'order/so/:id/complete/', sales_order_line_list = 'order/so-line/', + sales_order_extra_line_list = 'order/so-extra-line/', sales_order_allocation_list = 'order/so-allocation/', sales_order_shipment_list = 'order/so/shipment/', @@ -150,6 +152,7 @@ export enum ApiEndpoints { return_order_cancel = 'order/ro/:id/cancel/', return_order_complete = 'order/ro/:id/complete/', return_order_line_list = 'order/ro-line/', + return_order_extra_line_list = 'order/ro-extra-line/', // Template API endpoints label_list = 'label/template/', diff --git a/src/frontend/src/forms/CommonForms.tsx b/src/frontend/src/forms/CommonForms.tsx index 58d267ec8b..10ce0a082e 100644 --- a/src/frontend/src/forms/CommonForms.tsx +++ b/src/frontend/src/forms/CommonForms.tsx @@ -19,3 +19,18 @@ export function customUnitsFields(): ApiFormFieldSet { symbol: {} }; } + +export function extraLineItemFields(): ApiFormFieldSet { + return { + order: { + hidden: true + }, + reference: {}, + description: {}, + quantity: {}, + price: {}, + price_currency: {}, + notes: {}, + link: {} + }; +} diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx index 3a68a21023..fb1ec9a21c 100644 --- a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx +++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/macro'; -import { Grid, Skeleton, Stack } from '@mantine/core'; +import { Accordion, Grid, Skeleton, Stack } from '@mantine/core'; import { IconDots, IconInfoCircle, @@ -29,6 +29,7 @@ import { UnlinkBarcodeAction, ViewBarcodeAction } from '../../components/items/ActionDropdown'; +import { StylishText } from '../../components/items/StylishText'; import InstanceDetail from '../../components/nav/InstanceDetail'; import { PageDetail } from '../../components/nav/PageDetail'; import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; @@ -47,6 +48,7 @@ import useStatusCodes from '../../hooks/UseStatusCodes'; import { apiUrl } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; import { AttachmentTable } from '../../tables/general/AttachmentTable'; +import ExtraLineItemTable from '../../tables/general/ExtraLineItemTable'; import { PurchaseOrderLineItemTable } from '../../tables/purchasing/PurchaseOrderLineItemTable'; import { StockItemTable } from '../../tables/stock/StockItemTable'; @@ -245,11 +247,35 @@ export default function PurchaseOrderDetail() { label: t`Line Items`, icon: , content: ( - + + + + {t`Line Items`} + + + + + + + + {t`Extra Line Items`} + + + + + + ) }, { diff --git a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx index c892f62302..53876e641c 100644 --- a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx +++ b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/macro'; -import { Grid, Skeleton, Stack } from '@mantine/core'; +import { Accordion, Grid, Skeleton, Stack } from '@mantine/core'; import { IconDots, IconInfoCircle, @@ -28,6 +28,7 @@ import { UnlinkBarcodeAction, ViewBarcodeAction } from '../../components/items/ActionDropdown'; +import { StylishText } from '../../components/items/StylishText'; import InstanceDetail from '../../components/nav/InstanceDetail'; import { PageDetail } from '../../components/nav/PageDetail'; import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; @@ -46,6 +47,7 @@ import useStatusCodes from '../../hooks/UseStatusCodes'; import { apiUrl } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; import { AttachmentTable } from '../../tables/general/AttachmentTable'; +import ExtraLineItemTable from '../../tables/general/ExtraLineItemTable'; import ReturnOrderLineItemTable from '../../tables/sales/ReturnOrderLineItemTable'; /** @@ -223,10 +225,34 @@ export default function ReturnOrderDetail() { label: t`Line Items`, icon: , content: ( - + + + + {t`Line Items`} + + + + + + + + {t`Extra Line Items`} + + + + + + ) }, { diff --git a/src/frontend/src/pages/sales/SalesOrderDetail.tsx b/src/frontend/src/pages/sales/SalesOrderDetail.tsx index cd1467e0c6..4cdcc186cb 100644 --- a/src/frontend/src/pages/sales/SalesOrderDetail.tsx +++ b/src/frontend/src/pages/sales/SalesOrderDetail.tsx @@ -1,7 +1,6 @@ import { t } from '@lingui/macro'; -import { Grid, Skeleton, Stack } from '@mantine/core'; +import { Accordion, Grid, Skeleton, Stack } from '@mantine/core'; import { - IconBook, IconBookmark, IconDots, IconInfoCircle, @@ -32,6 +31,7 @@ import { UnlinkBarcodeAction, ViewBarcodeAction } from '../../components/items/ActionDropdown'; +import { StylishText } from '../../components/items/StylishText'; import InstanceDetail from '../../components/nav/InstanceDetail'; import { PageDetail } from '../../components/nav/PageDetail'; import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; @@ -51,6 +51,7 @@ import { apiUrl } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; import { BuildOrderTable } from '../../tables/build/BuildOrderTable'; import { AttachmentTable } from '../../tables/general/AttachmentTable'; +import ExtraLineItemTable from '../../tables/general/ExtraLineItemTable'; import SalesOrderAllocationTable from '../../tables/sales/SalesOrderAllocationTable'; import SalesOrderLineItemTable from '../../tables/sales/SalesOrderLineItemTable'; import SalesOrderShipmentTable from '../../tables/sales/SalesOrderShipmentTable'; @@ -259,14 +260,38 @@ export default function SalesOrderDetail() { label: t`Line Items`, icon: , content: ( - + + + + {t`Line Items`} + + + + + + + + {t`Extra Line Items`} + + + + + + ) }, { diff --git a/src/frontend/src/tables/general/ExtraLineItemTable.tsx b/src/frontend/src/tables/general/ExtraLineItemTable.tsx new file mode 100644 index 0000000000..8eee2b2663 --- /dev/null +++ b/src/frontend/src/tables/general/ExtraLineItemTable.tsx @@ -0,0 +1,169 @@ +import { t } from '@lingui/macro'; +import { useCallback, useMemo, useState } from 'react'; + +import { AddItemButton } from '../../components/buttons/AddItemButton'; +import { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField'; +import { formatCurrency } from '../../defaults/formatters'; +import { ApiEndpoints } from '../../enums/ApiEndpoints'; +import { ModelType } from '../../enums/ModelType'; +import { UserRoles } from '../../enums/Roles'; +import { extraLineItemFields } from '../../forms/CommonForms'; +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 { LinkColumn, NoteColumn } from '../ColumnRenderers'; +import { InvenTreeTable } from '../InvenTreeTable'; +import { + RowDeleteAction, + RowDuplicateAction, + RowEditAction +} from '../RowActions'; + +export default function ExtraLineItemTable({ + endpoint, + orderId, + role +}: { + endpoint: ApiEndpoints; + orderId: number; + role: UserRoles; +}) { + const table = useTable('extra-line-item'); + const user = useUserState(); + + const tableColumns: TableColumn[] = useMemo(() => { + return [ + { + accessor: 'reference', + switchable: false + }, + { + accessor: 'description' + }, + { + accessor: 'quantity', + switchable: false + }, + { + accessor: 'price', + title: t`Unit Price`, + render: (record: any) => + formatCurrency(record.price, { + currency: record.price_currency + }) + }, + { + accessor: 'total_price', + title: t`Total Price`, + render: (record: any) => + formatCurrency(record.price, { + currency: record.price_currency, + multiplier: record.quantity + }) + }, + NoteColumn({ + accessor: 'notes' + }), + LinkColumn({ + accessor: 'link' + }) + ]; + }, []); + + const [initialData, setInitialData] = useState({}); + + const [selectedLine, setSelectedLine] = useState(0); + + const newLineItem = useCreateApiFormModal({ + url: endpoint, + title: t`Add Line Item`, + fields: extraLineItemFields(), + initialData: initialData, + table: table + }); + + const editLineItem = useEditApiFormModal({ + url: endpoint, + pk: selectedLine, + title: t`Edit Line Item`, + fields: extraLineItemFields(), + table: table + }); + + const deleteLineItem = useDeleteApiFormModal({ + url: endpoint, + pk: selectedLine, + title: t`Delete Line Item`, + table: table + }); + + const rowActions = useCallback( + (record: any) => { + return [ + RowEditAction({ + hidden: !user.hasChangeRole(role), + onClick: () => { + setSelectedLine(record.pk); + editLineItem.open(); + } + }), + RowDuplicateAction({ + hidden: !user.hasAddRole(role), + onClick: () => { + setInitialData({ ...record }); + newLineItem.open(); + } + }), + RowDeleteAction({ + hidden: !user.hasDeleteRole(role), + onClick: () => { + setSelectedLine(record.pk); + deleteLineItem.open(); + } + }) + ]; + }, + [user, role] + ); + + const tableActions = useMemo(() => { + return [ +