Implement for SalesOrder
This commit is contained in:
parent
c42fcd5ed3
commit
b28b1f7330
|
|
@ -2,6 +2,7 @@ import { ModelType } from '@lib/enums/ModelType';
|
|||
import type { ReactNode } from 'react';
|
||||
import { PartPreviewComponent } from './models/PartPreview';
|
||||
import { PurchaseOrderPreviewComponent } from './models/PurchaseOrderPreview';
|
||||
import { SalesOrderPreviewComponent } from './models/SalesOrderPreview';
|
||||
import { StockPreviewComponent } from './models/StockPreview';
|
||||
|
||||
export interface PreviewType {
|
||||
|
|
@ -31,6 +32,8 @@ export function getPreviewComponentForModel({
|
|||
return StockPreviewComponent({ instance, modelId });
|
||||
case ModelType.purchaseorder:
|
||||
return PurchaseOrderPreviewComponent({ instance, modelId });
|
||||
case ModelType.salesorder:
|
||||
return SalesOrderPreviewComponent({ instance, modelId });
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import { t } from '@lingui/core/macro';
|
||||
import { SalesOrderDetailsPanel } from '../../../pages/sales/SalesOrderDetailsPanel';
|
||||
import type { PreviewType } from '../PreviewType';
|
||||
|
||||
export function SalesOrderPreviewComponent({
|
||||
instance,
|
||||
modelId
|
||||
}: Readonly<{
|
||||
instance: any;
|
||||
modelId: number;
|
||||
}>): PreviewType {
|
||||
const customer = instance?.customer_detail?.name ?? instance?.customer_name;
|
||||
const ref = instance?.reference ?? `#${modelId}`;
|
||||
|
||||
let title = `${t`Sales Order`} ${ref}`;
|
||||
|
||||
if (customer) {
|
||||
title += ` (${customer})`;
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
preview: <SalesOrderDetailsPanel instance={instance} />
|
||||
};
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { t } from '@lingui/core/macro';
|
||||
import { Accordion, Grid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Accordion, Skeleton, Stack } from '@mantine/core';
|
||||
import {
|
||||
IconBookmark,
|
||||
IconCubeSend,
|
||||
|
|
@ -11,7 +11,6 @@ import { type ReactNode, useMemo } from 'react';
|
|||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { StylishText } from '@lib/components/StylishText';
|
||||
import TagsList from '@lib/components/TagsList';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { UserRoles } from '@lib/enums/Roles';
|
||||
|
|
@ -20,12 +19,6 @@ import type { PanelType } from '@lib/types/Panel';
|
|||
import AdminButton from '../../components/buttons/AdminButton';
|
||||
import PrimaryActionButton from '../../components/buttons/PrimaryActionButton';
|
||||
import { PrintingActions } from '../../components/buttons/PrintingActions';
|
||||
import {
|
||||
type DetailsField,
|
||||
DetailsTable
|
||||
} from '../../components/details/Details';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
import {
|
||||
BarcodeActionDropdown,
|
||||
CancelItemAction,
|
||||
|
|
@ -40,9 +33,7 @@ import AttachmentPanel from '../../components/panels/AttachmentPanel';
|
|||
import NotesPanel from '../../components/panels/NotesPanel';
|
||||
import { PanelGroup } from '../../components/panels/PanelGroup';
|
||||
import ParametersPanel from '../../components/panels/ParametersPanel';
|
||||
import { RenderAddress } from '../../components/render/Company';
|
||||
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
||||
import { formatCurrency } from '../../defaults/formatters';
|
||||
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
|
|
@ -57,6 +48,7 @@ import ExtraLineItemTable from '../../tables/general/ExtraLineItemTable';
|
|||
import SalesOrderAllocationTable from '../../tables/sales/SalesOrderAllocationTable';
|
||||
import SalesOrderLineItemTable from '../../tables/sales/SalesOrderLineItemTable';
|
||||
import SalesOrderShipmentTable from '../../tables/sales/SalesOrderShipmentTable';
|
||||
import { SalesOrderDetailsPanel } from './SalesOrderDetailsPanel';
|
||||
|
||||
/**
|
||||
* Detail page for a single SalesOrder
|
||||
|
|
@ -89,230 +81,6 @@ export default function SalesOrderDetail() {
|
|||
);
|
||||
}, [order, globalSettings]);
|
||||
|
||||
const detailsPanel = useMemo(() => {
|
||||
if (instanceQuery.isFetching) {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
const tl: DetailsField[] = [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'reference',
|
||||
label: t`Reference`,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'customer_reference',
|
||||
label: t`Customer Reference`,
|
||||
copy: true,
|
||||
icon: 'reference',
|
||||
hidden: !order.customer_reference
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
name: 'customer',
|
||||
icon: 'customers',
|
||||
label: t`Customer`,
|
||||
model: ModelType.company
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'description',
|
||||
label: t`Description`,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'status',
|
||||
name: 'status',
|
||||
label: t`Status`,
|
||||
model: ModelType.salesorder
|
||||
},
|
||||
{
|
||||
type: 'status',
|
||||
name: 'status_custom_key',
|
||||
label: t`Custom Status`,
|
||||
model: ModelType.salesorder,
|
||||
icon: 'status',
|
||||
hidden:
|
||||
!order.status_custom_key || order.status_custom_key == order.status
|
||||
}
|
||||
];
|
||||
|
||||
const tr: DetailsField[] = [
|
||||
{
|
||||
type: 'progressbar',
|
||||
name: 'completed',
|
||||
icon: 'progress',
|
||||
label: t`Completed Line Items`,
|
||||
total: order.line_items,
|
||||
progress: order.completed_lines,
|
||||
hidden: !order.line_items
|
||||
},
|
||||
{
|
||||
type: 'progressbar',
|
||||
name: 'shipments',
|
||||
icon: 'shipment',
|
||||
label: t`Completed Shipments`,
|
||||
total: order.shipments_count,
|
||||
progress: order.completed_shipments_count,
|
||||
hidden: !order.shipments_count
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'currency',
|
||||
label: t`Order Currency`,
|
||||
value_formatter: () => orderCurrency
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'total_price',
|
||||
label: t`Total Cost`,
|
||||
value_formatter: () => {
|
||||
return formatCurrency(order?.total_price, {
|
||||
currency: orderCurrency
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const bl: DetailsField[] = [
|
||||
{
|
||||
type: 'link',
|
||||
external: true,
|
||||
name: 'link',
|
||||
label: t`Link`,
|
||||
copy: true,
|
||||
hidden: !order.link
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'address',
|
||||
label: t`Shipping Address`,
|
||||
icon: 'address',
|
||||
value_formatter: () =>
|
||||
order.address_detail ? (
|
||||
<RenderAddress instance={order.address_detail} />
|
||||
) : (
|
||||
<Text size='sm' c='red'>{t`Not specified`}</Text>
|
||||
)
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'contact_detail.name',
|
||||
label: t`Contact`,
|
||||
icon: 'user',
|
||||
copy: true,
|
||||
hidden: !order.contact
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'contact_detail.email',
|
||||
label: t`Contact Email`,
|
||||
icon: 'email',
|
||||
copy: true,
|
||||
hidden: !order.contact_detail?.email
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'contact_detail.phone',
|
||||
label: t`Contact Phone`,
|
||||
icon: 'phone',
|
||||
copy: true,
|
||||
hidden: !order.contact_detail?.phone
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'project_code_label',
|
||||
label: t`Project Code`,
|
||||
icon: 'reference',
|
||||
copy: true,
|
||||
hidden: !order.project_code
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'responsible',
|
||||
label: t`Responsible`,
|
||||
badge: 'owner',
|
||||
hidden: !order.responsible
|
||||
}
|
||||
];
|
||||
|
||||
const br: DetailsField[] = [
|
||||
{
|
||||
type: 'date',
|
||||
name: 'creation_date',
|
||||
label: t`Creation Date`,
|
||||
copy: true,
|
||||
hidden: !order.creation_date
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'issue_date',
|
||||
label: t`Issue Date`,
|
||||
icon: 'calendar',
|
||||
copy: true,
|
||||
hidden: !order.issue_date
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'start_date',
|
||||
label: t`Start Date`,
|
||||
icon: 'calendar',
|
||||
hidden: !order.start_date,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'target_date',
|
||||
label: t`Target Date`,
|
||||
hidden: !order.target_date,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'shipment_date',
|
||||
label: t`Completion Date`,
|
||||
hidden: !order.shipment_date,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'updated_at',
|
||||
label: t`Last Updated`,
|
||||
icon: 'calendar',
|
||||
copy: true,
|
||||
showTime: true,
|
||||
hidden: !order.updated_at
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<ItemDetailsGrid
|
||||
tables={[
|
||||
{ fields: tr, item: order },
|
||||
{ fields: bl, item: order },
|
||||
{ fields: br, item: order }
|
||||
]}
|
||||
>
|
||||
<Stack gap='xs'>
|
||||
<Grid grow>
|
||||
<DetailsImage
|
||||
appRole={UserRoles.purchase_order}
|
||||
apiPath={ApiEndpoints.company_list}
|
||||
src={order.customer_detail?.image}
|
||||
pk={order.customer}
|
||||
/>
|
||||
<Grid.Col span={{ base: 12, sm: 8 }}>
|
||||
<DetailsTable fields={tl} item={order} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<TagsList tags={order.tags} />
|
||||
</Stack>
|
||||
</ItemDetailsGrid>
|
||||
);
|
||||
}, [order, orderCurrency, instanceQuery]);
|
||||
|
||||
const soStatus = useStatusCodes({ modelType: ModelType.salesorder });
|
||||
|
||||
const lineItemsEditable: boolean = useMemo(() => {
|
||||
|
|
@ -367,7 +135,13 @@ export default function SalesOrderDetail() {
|
|||
name: 'detail',
|
||||
label: t`Order Details`,
|
||||
icon: <IconInfoCircle />,
|
||||
content: detailsPanel
|
||||
content: (
|
||||
<SalesOrderDetailsPanel
|
||||
instance={order}
|
||||
allowImageEdit
|
||||
refreshInstance={refreshInstance}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'line-items',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,270 @@
|
|||
import { t } from '@lingui/core/macro';
|
||||
import { Grid, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import TagsList from '@lib/components/TagsList';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { UserRoles } from '@lib/enums/Roles';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
|
||||
import {
|
||||
type DetailsField,
|
||||
DetailsTable
|
||||
} from '../../components/details/Details';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
import { useParameterDetailsGrid } from '../../components/details/ParameterDetailsGrid';
|
||||
import { RenderAddress } from '../../components/render/Company';
|
||||
import { formatCurrency } from '../../defaults/formatters';
|
||||
import { useGlobalSettingsState } from '../../states/SettingsStates';
|
||||
|
||||
export function SalesOrderDetailsPanel({
|
||||
instance,
|
||||
allowImageEdit = false,
|
||||
refreshInstance
|
||||
}: Readonly<{
|
||||
instance: any;
|
||||
allowImageEdit?: boolean;
|
||||
refreshInstance?: () => void;
|
||||
}>) {
|
||||
const globalSettings = useGlobalSettingsState();
|
||||
|
||||
const orderCurrency = useMemo(
|
||||
() =>
|
||||
instance?.order_currency ||
|
||||
instance?.customer_detail?.currency ||
|
||||
globalSettings.getSetting('INVENTREE_DEFAULT_CURRENCY'),
|
||||
[instance, globalSettings]
|
||||
);
|
||||
|
||||
const tl: DetailsField[] = [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'reference',
|
||||
label: t`Reference`,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'customer_reference',
|
||||
label: t`Customer Reference`,
|
||||
copy: true,
|
||||
icon: 'reference',
|
||||
hidden: !instance?.customer_reference
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
name: 'customer',
|
||||
icon: 'customers',
|
||||
label: t`Customer`,
|
||||
model: ModelType.company
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'description',
|
||||
label: t`Description`,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'status',
|
||||
name: 'status',
|
||||
label: t`Status`,
|
||||
model: ModelType.salesorder
|
||||
},
|
||||
{
|
||||
type: 'status',
|
||||
name: 'status_custom_key',
|
||||
label: t`Custom Status`,
|
||||
model: ModelType.salesorder,
|
||||
icon: 'status',
|
||||
hidden:
|
||||
!instance?.status_custom_key ||
|
||||
instance?.status_custom_key == instance?.status
|
||||
}
|
||||
];
|
||||
|
||||
const tr: DetailsField[] = [
|
||||
{
|
||||
type: 'progressbar',
|
||||
name: 'completed',
|
||||
icon: 'progress',
|
||||
label: t`Completed Line Items`,
|
||||
total: instance?.line_items,
|
||||
progress: instance?.completed_lines,
|
||||
hidden: !instance?.line_items
|
||||
},
|
||||
{
|
||||
type: 'progressbar',
|
||||
name: 'shipments',
|
||||
icon: 'shipment',
|
||||
label: t`Completed Shipments`,
|
||||
total: instance?.shipments_count,
|
||||
progress: instance?.completed_shipments_count,
|
||||
hidden: !instance?.shipments_count
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'currency',
|
||||
label: t`Order Currency`,
|
||||
value_formatter: () => orderCurrency
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'total_price',
|
||||
label: t`Total Cost`,
|
||||
value_formatter: () =>
|
||||
formatCurrency(instance?.total_price, {
|
||||
currency: orderCurrency
|
||||
})
|
||||
}
|
||||
];
|
||||
|
||||
const bl: DetailsField[] = [
|
||||
{
|
||||
type: 'link',
|
||||
external: true,
|
||||
name: 'link',
|
||||
label: t`Link`,
|
||||
copy: true,
|
||||
hidden: !instance?.link
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'address',
|
||||
label: t`Shipping Address`,
|
||||
icon: 'address',
|
||||
value_formatter: () =>
|
||||
instance?.address_detail ? (
|
||||
<RenderAddress instance={instance.address_detail} />
|
||||
) : (
|
||||
<Text size='sm' c='red'>{t`Not specified`}</Text>
|
||||
)
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'contact_detail.name',
|
||||
label: t`Contact`,
|
||||
icon: 'user',
|
||||
copy: true,
|
||||
hidden: !instance?.contact_detail?.name
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'contact_detail.email',
|
||||
label: t`Contact Email`,
|
||||
icon: 'email',
|
||||
copy: true,
|
||||
hidden: !instance?.contact_detail?.email
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'contact_detail.phone',
|
||||
label: t`Contact Phone`,
|
||||
icon: 'phone',
|
||||
copy: true,
|
||||
hidden: !instance?.contact_detail?.phone
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'project_code_label',
|
||||
label: t`Project Code`,
|
||||
icon: 'reference',
|
||||
copy: true,
|
||||
hidden: !instance?.project_code_label
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'responsible',
|
||||
label: t`Responsible`,
|
||||
badge: 'owner',
|
||||
hidden: !instance?.responsible
|
||||
}
|
||||
];
|
||||
|
||||
const br: DetailsField[] = [
|
||||
{
|
||||
type: 'date',
|
||||
name: 'creation_date',
|
||||
label: t`Creation Date`,
|
||||
copy: true,
|
||||
hidden: !instance?.creation_date
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'issue_date',
|
||||
label: t`Issue Date`,
|
||||
icon: 'calendar',
|
||||
copy: true,
|
||||
hidden: !instance?.issue_date
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'start_date',
|
||||
label: t`Start Date`,
|
||||
icon: 'calendar',
|
||||
hidden: !instance?.start_date,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'target_date',
|
||||
label: t`Target Date`,
|
||||
hidden: !instance?.target_date,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'shipment_date',
|
||||
label: t`Completion Date`,
|
||||
hidden: !instance?.shipment_date,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'updated_at',
|
||||
label: t`Last Updated`,
|
||||
icon: 'calendar',
|
||||
copy: true,
|
||||
showTime: true,
|
||||
hidden: !instance?.updated_at
|
||||
}
|
||||
];
|
||||
|
||||
const parametersTable = useParameterDetailsGrid({
|
||||
model_type: ModelType.salesorder,
|
||||
model_id: instance?.pk
|
||||
});
|
||||
|
||||
if (!instance?.pk) return <Skeleton />;
|
||||
|
||||
return (
|
||||
<ItemDetailsGrid
|
||||
tables={[
|
||||
{ fields: tr, item: instance },
|
||||
{ fields: bl, item: instance },
|
||||
{ fields: br, item: instance },
|
||||
parametersTable
|
||||
]}
|
||||
>
|
||||
<Stack gap='xs'>
|
||||
<Grid grow>
|
||||
<DetailsImage
|
||||
appRole={allowImageEdit ? UserRoles.sales_order : undefined}
|
||||
imageActions={
|
||||
allowImageEdit ? { uploadFile: true, deleteFile: true } : {}
|
||||
}
|
||||
apiPath={apiUrl(ApiEndpoints.company_list, instance?.customer)}
|
||||
src={instance?.customer_detail?.image}
|
||||
pk={instance?.customer}
|
||||
refresh={refreshInstance}
|
||||
/>
|
||||
<Grid.Col span={{ base: 12, sm: 8 }}>
|
||||
<DetailsTable fields={tl} item={instance} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<TagsList tags={instance?.tags} />
|
||||
</Stack>
|
||||
</ItemDetailsGrid>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue