[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
This commit is contained in:
Oliver 2024-08-16 15:53:29 +10:00 committed by GitHub
parent 09c4710107
commit 8b44dfbc4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 286 additions and 22 deletions

View File

@ -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/',

View File

@ -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: {}
};
}

View File

@ -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: <IconList />,
content: (
<PurchaseOrderLineItemTable
order={order}
orderId={Number(id)}
supplierId={Number(order.supplier)}
/>
<Accordion
multiple={true}
defaultValue={['line-items', 'extra-items']}
>
<Accordion.Item value="line-items" key="lineitems">
<Accordion.Control>
<StylishText size="lg">{t`Line Items`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<PurchaseOrderLineItemTable
order={order}
orderId={Number(id)}
supplierId={Number(order.supplier)}
/>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value="extra-items" key="extraitems">
<Accordion.Control>
<StylishText size="lg">{t`Extra Line Items`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<ExtraLineItemTable
endpoint={ApiEndpoints.purchase_order_extra_line_list}
orderId={order.pk}
role={UserRoles.purchase_order}
/>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
)
},
{

View File

@ -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: <IconList />,
content: (
<ReturnOrderLineItemTable
orderId={order.pk}
customerId={order.customer}
/>
<Accordion
multiple={true}
defaultValue={['line-items', 'extra-items']}
>
<Accordion.Item value="line-items" key="lineitems">
<Accordion.Control>
<StylishText size="lg">{t`Line Items`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<ReturnOrderLineItemTable
orderId={order.pk}
customerId={order.customer}
/>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value="extra-items" key="extraitems">
<Accordion.Control>
<StylishText size="lg">{t`Extra Line Items`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<ExtraLineItemTable
endpoint={ApiEndpoints.return_order_extra_line_list}
orderId={order.pk}
role={UserRoles.return_order}
/>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
)
},
{

View File

@ -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: <IconList />,
content: (
<SalesOrderLineItemTable
orderId={order.pk}
customerId={order.customer}
editable={
order.status != soStatus.COMPLETE &&
order.status != soStatus.CANCELLED
}
/>
<Accordion
multiple={true}
defaultValue={['line-items', 'extra-items']}
>
<Accordion.Item value="line-items" key="lineitems">
<Accordion.Control>
<StylishText size="lg">{t`Line Items`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<SalesOrderLineItemTable
orderId={order.pk}
customerId={order.customer}
editable={
order.status != soStatus.COMPLETE &&
order.status != soStatus.CANCELLED
}
/>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value="extra-items" key="extraitems">
<Accordion.Control>
<StylishText size="lg">{t`Extra Line Items`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<ExtraLineItemTable
endpoint={ApiEndpoints.sales_order_extra_line_list}
orderId={order.pk}
role={UserRoles.sales_order}
/>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
)
},
{

View File

@ -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<any>({});
const [selectedLine, setSelectedLine] = useState<number>(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 [
<AddItemButton
tooltip={t`Add Extra Line Item`}
hidden={!user.hasAddRole(role)}
onClick={() => {
setInitialData({
order: orderId
});
newLineItem.open();
}}
/>
];
}, [user, role]);
return (
<>
{newLineItem.modal}
{editLineItem.modal}
{deleteLineItem.modal}
<InvenTreeTable
tableState={table}
url={apiUrl(endpoint)}
columns={tableColumns}
props={{
params: {
order: orderId
},
rowActions: rowActions,
tableActions: tableActions
}}
/>
</>
);
}

View File

@ -241,7 +241,7 @@ export function PurchaseOrderLineItemTable({
supplierId: supplierId
});
const [initialData, setInitialData] = useState({});
const [initialData, setInitialData] = useState<any>({});
const newLine = useCreateApiFormModal({
url: ApiEndpoints.purchase_order_line_list,