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 [
+ {
+ setInitialData({
+ order: orderId
+ });
+ newLineItem.open();
+ }}
+ />
+ ];
+ }, [user, role]);
+
+ return (
+ <>
+ {newLineItem.modal}
+ {editLineItem.modal}
+ {deleteLineItem.modal}
+
+ >
+ );
+}
diff --git a/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx b/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx
index 895a40beff..2e3312491c 100644
--- a/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx
+++ b/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx
@@ -241,7 +241,7 @@ export function PurchaseOrderLineItemTable({
supplierId: supplierId
});
- const [initialData, setInitialData] = useState({});
+ const [initialData, setInitialData] = useState({});
const newLine = useCreateApiFormModal({
url: ApiEndpoints.purchase_order_line_list,