diff --git a/src/frontend/src/components/previews/PreviewType.tsx b/src/frontend/src/components/previews/PreviewType.tsx
index 3dfb2eb19a..c3b0c3a433 100644
--- a/src/frontend/src/components/previews/PreviewType.tsx
+++ b/src/frontend/src/components/previews/PreviewType.tsx
@@ -7,8 +7,10 @@ import { PartPreviewComponent } from './models/PartPreview';
import { PurchaseOrderPreviewComponent } from './models/PurchaseOrderPreview';
import { ReturnOrderPreviewComponent } from './models/ReturnOrderPreview';
import { SalesOrderPreviewComponent } from './models/SalesOrderPreview';
+import { SalesOrderShipmentPreviewComponent } from './models/SalesOrderShipmentPreview';
import { StockPreviewComponent } from './models/StockPreview';
import { SupplierPartPreviewComponent } from './models/SupplierPartPreview';
+import { TransferOrderPreviewComponent } from './models/TransferOrderPreview';
export interface PreviewType {
preview: ReactNode;
@@ -49,6 +51,10 @@ export function getPreviewComponentForModel({
return CompanyPreviewComponent({ instance, modelId });
case ModelType.build:
return BuildOrderPreviewComponent({ instance, modelId });
+ case ModelType.salesordershipment:
+ return SalesOrderShipmentPreviewComponent({ instance, modelId });
+ case ModelType.transferorder:
+ return TransferOrderPreviewComponent({ instance, modelId });
default:
return null;
}
diff --git a/src/frontend/src/components/previews/models/SalesOrderShipmentPreview.tsx b/src/frontend/src/components/previews/models/SalesOrderShipmentPreview.tsx
new file mode 100644
index 0000000000..ce85b40946
--- /dev/null
+++ b/src/frontend/src/components/previews/models/SalesOrderShipmentPreview.tsx
@@ -0,0 +1,25 @@
+import { t } from '@lingui/core/macro';
+import { SalesOrderShipmentDetailsPanel } from '../../../pages/sales/SalesOrderShipmentDetailsPanel';
+import type { PreviewType } from '../PreviewType';
+
+export function SalesOrderShipmentPreviewComponent({
+ instance,
+ modelId
+}: Readonly<{
+ instance: any;
+ modelId: number;
+}>): PreviewType {
+ const order = instance?.order_detail?.reference;
+ const ref = instance?.reference ?? `#${modelId}`;
+
+ let title = `${t`Shipment`} ${ref}`;
+
+ if (order) {
+ title += ` (${order})`;
+ }
+
+ return {
+ title,
+ preview:
+ };
+}
diff --git a/src/frontend/src/components/previews/models/TransferOrderPreview.tsx b/src/frontend/src/components/previews/models/TransferOrderPreview.tsx
new file mode 100644
index 0000000000..0d8458d51b
--- /dev/null
+++ b/src/frontend/src/components/previews/models/TransferOrderPreview.tsx
@@ -0,0 +1,18 @@
+import { t } from '@lingui/core/macro';
+import { TransferOrderDetailsPanel } from '../../../pages/stock/TransferOrderDetailsPanel';
+import type { PreviewType } from '../PreviewType';
+
+export function TransferOrderPreviewComponent({
+ instance,
+ modelId
+}: Readonly<{
+ instance: any;
+ modelId: number;
+}>): PreviewType {
+ const ref = instance?.reference ?? `#${modelId}`;
+
+ return {
+ title: `${t`Transfer Order`} ${ref}`,
+ preview:
+ };
+}
diff --git a/src/frontend/src/pages/sales/SalesOrderShipmentDetail.tsx b/src/frontend/src/pages/sales/SalesOrderShipmentDetail.tsx
index 83d956b817..44de746498 100644
--- a/src/frontend/src/pages/sales/SalesOrderShipmentDetail.tsx
+++ b/src/frontend/src/pages/sales/SalesOrderShipmentDetail.tsx
@@ -1,5 +1,5 @@
import { t } from '@lingui/core/macro';
-import { Grid, Skeleton, Stack, Text } from '@mantine/core';
+import { Stack } from '@mantine/core';
import {
IconBookmark,
IconCircleCheck,
@@ -13,18 +13,11 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
import { getDetailUrl } from '@lib/functions/Navigation';
-import { TagsList } from '@lib/index';
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 DetailsBadge from '../../components/details/DetailsBadge';
-import { DetailsImage } from '../../components/details/DetailsImage';
-import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
BarcodeActionDropdown,
CancelItemAction,
@@ -37,9 +30,6 @@ 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 { RenderUser } from '../../components/render/User';
-import { formatDate } from '../../defaults/formatters';
import {
useCheckShipmentForm,
useCompleteShipmentForm,
@@ -53,6 +43,7 @@ import {
import { useInstance } from '../../hooks/UseInstance';
import { useUserState } from '../../states/UserState';
import SalesOrderAllocationTable from '../../tables/sales/SalesOrderAllocationTable';
+import { SalesOrderShipmentDetailsPanel } from './SalesOrderShipmentDetailsPanel';
export default function SalesOrderShipmentDetail() {
const { id } = useParams();
@@ -74,193 +65,21 @@ export default function SalesOrderShipmentDetail() {
}
});
- const { instance: customer, instanceQuery: customerQuery } = useInstance({
- endpoint: ApiEndpoints.company_list,
- pk: shipment.order_detail?.customer,
- hasPrimaryKey: true
- });
-
const isPending = useMemo(() => !shipment.shipment_date, [shipment]);
-
const isChecked = useMemo(() => !!shipment.checked_by, [shipment]);
- const detailsPanel = useMemo(() => {
- if (shipmentQuery.isFetching || customerQuery.isFetching) {
- return ;
- }
-
- const data: any = {
- ...shipment,
- customer: customer?.pk,
- customer_name: customer?.name,
- customer_reference: shipment.order_detail?.customer_reference
- };
-
- // Top Left: Order / customer information
- const tl: DetailsField[] = [
- {
- type: 'link',
- model: ModelType.salesorder,
- name: 'order',
- label: t`Sales Order`,
- icon: 'sales_orders',
- model_field: 'reference'
- },
- {
- type: 'link',
- name: 'customer',
- icon: 'customers',
- label: t`Customer`,
- model: ModelType.company,
- model_field: 'name',
- hidden: !data.customer
- },
- {
- type: 'link',
- external: true,
- name: 'link',
- label: t`Link`,
- copy: true,
- hidden: !shipment.link
- }
- ];
-
- // Top right: Shipment information
- const tr: DetailsField[] = [
- {
- type: 'text',
- name: 'customer_reference',
- icon: 'serial',
- label: t`Customer Reference`,
- hidden: !data.customer_reference,
- copy: true
- },
- {
- type: 'text',
- name: 'reference',
- icon: 'serial',
- label: t`Shipment Reference`,
- copy: true
- },
- {
- type: 'text',
- name: 'tracking_number',
- label: t`Tracking Number`,
- icon: 'trackable',
- value_formatter: () => shipment.tracking_number || '---',
- copy: !!shipment.tracking_number
- },
- {
- type: 'text',
- name: 'invoice_number',
- label: t`Invoice Number`,
- icon: 'serial',
- value_formatter: () => shipment.invoice_number || '---',
- copy: !!shipment.invoice_number
- }
- ];
-
- const address: any =
- shipment.shipment_address_detail || shipment.order_detail?.address_detail;
-
- const bl: DetailsField[] = [
- {
- type: 'text',
- name: 'address',
- label: t`Shipping Address`,
- icon: 'address',
- value_formatter: () =>
- address ? (
-
- ) : (
- {t`Not specified`}
- )
- }
- ];
-
- const br: DetailsField[] = [
- {
- type: 'text',
- name: 'allocated_items',
- icon: 'packages',
- label: t`Allocated Items`
- },
- {
- type: 'text',
- name: 'checked_by',
- label: t`Checked By`,
- icon: 'check',
- value_formatter: () =>
- shipment.checked_by_detail ? (
-
- ) : (
- {t`Not checked`}
- )
- },
- {
- type: 'text',
- name: 'shipment_date',
- label: t`Shipment Date`,
- icon: 'calendar',
- value_formatter: () => formatDate(shipment.shipment_date),
- hidden: !shipment.shipment_date
- },
- {
- type: 'text',
- name: 'delivery_date',
- label: t`Delivery Date`,
- icon: 'calendar',
- value_formatter: () => formatDate(shipment.delivery_date),
- hidden: !shipment.delivery_date
- }
- ];
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
- >
- );
- }, [shipment, shipmentQuery, customer, customerQuery]);
-
const shipmentPanels: PanelType[] = useMemo(() => {
return [
{
name: 'detail',
label: t`Shipment Details`,
icon: ,
- content: detailsPanel
+ content: (
+
+ )
},
{
name: 'items',
@@ -291,7 +110,7 @@ export default function SalesOrderShipmentDetail() {
has_note: !!shipment.notes
})
];
- }, [isPending, shipment, detailsPanel]);
+ }, [isPending, shipment]);
const editShipmentFields = useSalesOrderShipmentFields({
pending: isPending,
@@ -457,7 +276,7 @@ export default function SalesOrderShipmentDetail() {
}
]}
badges={shipmentBadges}
- imageUrl={customer?.image}
+ imageUrl={shipment.order_detail?.customer_detail?.image}
editAction={editShipment.open}
editEnabled={user.hasChangePermission(ModelType.salesordershipment)}
actions={shipmentActions}
diff --git a/src/frontend/src/pages/sales/SalesOrderShipmentDetailsPanel.tsx b/src/frontend/src/pages/sales/SalesOrderShipmentDetailsPanel.tsx
new file mode 100644
index 0000000000..5280abac94
--- /dev/null
+++ b/src/frontend/src/pages/sales/SalesOrderShipmentDetailsPanel.tsx
@@ -0,0 +1,205 @@
+import { t } from '@lingui/core/macro';
+import { Grid, Skeleton, Stack, Text } from '@mantine/core';
+import { useMemo } from 'react';
+
+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 { TagsList } from '@lib/index';
+
+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 { RenderUser } from '../../components/render/User';
+import { formatDate } from '../../defaults/formatters';
+import { useInstance } from '../../hooks/UseInstance';
+
+export function SalesOrderShipmentDetailsPanel({
+ instance,
+ refreshInstance
+}: Readonly<{
+ instance: any;
+ refreshInstance?: () => void;
+}>) {
+ const { instance: customer } = useInstance({
+ endpoint: ApiEndpoints.company_list,
+ pk: instance?.order_detail?.customer,
+ hasPrimaryKey: true
+ });
+
+ const data = useMemo(
+ () => ({
+ ...instance,
+ customer: customer?.pk,
+ customer_name: customer?.name,
+ customer_reference: instance?.order_detail?.customer_reference
+ }),
+ [instance, customer]
+ );
+
+ const address = useMemo(
+ () =>
+ instance?.shipment_address_detail ||
+ instance?.order_detail?.address_detail,
+ [instance]
+ );
+
+ const tl: DetailsField[] = [
+ {
+ type: 'link',
+ model: ModelType.salesorder,
+ name: 'order',
+ label: t`Sales Order`,
+ icon: 'sales_orders',
+ model_field: 'reference'
+ },
+ {
+ type: 'link',
+ name: 'customer',
+ icon: 'customers',
+ label: t`Customer`,
+ model: ModelType.company,
+ model_field: 'name',
+ hidden: !data.customer
+ },
+ {
+ type: 'link',
+ external: true,
+ name: 'link',
+ label: t`Link`,
+ copy: true,
+ hidden: !instance?.link
+ }
+ ];
+
+ const tr: DetailsField[] = [
+ {
+ type: 'text',
+ name: 'customer_reference',
+ icon: 'serial',
+ label: t`Customer Reference`,
+ hidden: !data.customer_reference,
+ copy: true
+ },
+ {
+ type: 'text',
+ name: 'reference',
+ icon: 'serial',
+ label: t`Shipment Reference`,
+ copy: true
+ },
+ {
+ type: 'text',
+ name: 'tracking_number',
+ label: t`Tracking Number`,
+ icon: 'trackable',
+ value_formatter: () => instance?.tracking_number || '---',
+ copy: !!instance?.tracking_number
+ },
+ {
+ type: 'text',
+ name: 'invoice_number',
+ label: t`Invoice Number`,
+ icon: 'serial',
+ value_formatter: () => instance?.invoice_number || '---',
+ copy: !!instance?.invoice_number
+ }
+ ];
+
+ const bl: DetailsField[] = [
+ {
+ type: 'text',
+ name: 'address',
+ label: t`Shipping Address`,
+ icon: 'address',
+ value_formatter: () =>
+ address ? (
+
+ ) : (
+ {t`Not specified`}
+ )
+ }
+ ];
+
+ const br: DetailsField[] = [
+ {
+ type: 'text',
+ name: 'allocated_items',
+ icon: 'packages',
+ label: t`Allocated Items`
+ },
+ {
+ type: 'text',
+ name: 'checked_by',
+ label: t`Checked By`,
+ icon: 'check',
+ value_formatter: () =>
+ instance?.checked_by_detail ? (
+
+ ) : (
+ {t`Not checked`}
+ )
+ },
+ {
+ type: 'text',
+ name: 'shipment_date',
+ label: t`Shipment Date`,
+ icon: 'calendar',
+ value_formatter: () => formatDate(instance?.shipment_date),
+ hidden: !instance?.shipment_date
+ },
+ {
+ type: 'text',
+ name: 'delivery_date',
+ label: t`Delivery Date`,
+ icon: 'calendar',
+ value_formatter: () => formatDate(instance?.delivery_date),
+ hidden: !instance?.delivery_date
+ }
+ ];
+
+ const parametersTable = useParameterDetailsGrid({
+ model_type: ModelType.salesordershipment,
+ model_id: instance?.pk
+ });
+
+ if (!instance?.pk) return ;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/frontend/src/pages/stock/TransferOrderDetail.tsx b/src/frontend/src/pages/stock/TransferOrderDetail.tsx
index 41efe4e8b4..edc9dba94d 100644
--- a/src/frontend/src/pages/stock/TransferOrderDetail.tsx
+++ b/src/frontend/src/pages/stock/TransferOrderDetail.tsx
@@ -1,12 +1,12 @@
import { t } from '@lingui/core/macro';
-import { Grid, Skeleton, Stack } from '@mantine/core';
+import { Skeleton, Stack } from '@mantine/core';
import { type ReactNode, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
-import { type PanelType, TagsList, apiUrl } from '@lib/index';
+import { type PanelType, apiUrl } from '@lib/index';
import {
IconBookmark,
IconInfoCircle,
@@ -16,11 +16,6 @@ import {
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 { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
BarcodeActionDropdown,
CancelItemAction,
@@ -47,6 +42,7 @@ import { useGlobalSettingsState } from '../../states/SettingsStates';
import { useUserState } from '../../states/UserState';
import TransferOrderAllocationTable from '../../tables/stock/TransferOrderAllocationTable';
import TransferOrderLineItemTable from '../../tables/stock/TransferOrderLineItemTable';
+import { TransferOrderDetailsPanel } from './TransferOrderDetailsPanel';
export default function TransferOrderDetail() {
const { id } = useParams();
@@ -93,172 +89,11 @@ export default function TransferOrderDetail() {
);
}, [order, toStatus]);
- const detailsPanel = useMemo(() => {
- if (instanceQuery.isFetching) {
- return ;
- }
-
- const tl: DetailsField[] = [
- {
- type: 'text',
- name: 'reference',
- label: t`Reference`,
- copy: true
- },
- {
- type: 'link',
- name: 'take_from',
- icon: 'location',
- label: t`Source Location`,
- model: ModelType.stocklocation
- },
- {
- type: 'link',
- name: 'destination',
- icon: 'location',
- label: t`Destination Location`,
- model: ModelType.stocklocation
- },
- {
- type: 'text',
- name: 'description',
- label: t`Description`,
- copy: true
- },
- {
- type: 'status',
- name: 'status',
- label: t`Status`,
- model: ModelType.transferorder
- },
- {
- type: 'status',
- name: 'status_custom_key',
- label: t`Custom Status`,
- model: ModelType.transferorder,
- icon: 'status',
- hidden:
- !order.status_custom_key || order.status_custom_key == order.status
- }
- ];
-
- const tr: DetailsField[] = [
- {
- type: 'boolean',
- name: 'consume',
- icon: 'consume',
- label: t`Consume Stock`
- },
- {
- type: 'text',
- name: 'line_items',
- label: t`Line Items`,
- icon: 'list'
- },
- {
- type: 'progressbar',
- name: 'completed',
- icon: 'progress',
- label: t`Completed Line Items`,
- total: order.line_items,
- progress: order.completed_lines
- }
- ];
-
- const bl: DetailsField[] = [
- {
- type: 'link',
- external: true,
- name: 'link',
- label: t`Link`,
- copy: true,
- hidden: !order.link
- },
- {
- 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`,
- icon: 'calendar',
- 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',
- copy: true,
- hidden: !order.start_date
- },
- {
- type: 'date',
- name: 'target_date',
- label: t`Target Date`,
- copy: true,
- hidden: !order.target_date
- },
- {
- type: 'date',
- name: 'complete_date',
- icon: 'calendar_check',
- label: t`Completion Date`,
- copy: true,
- hidden: !order.complete_date
- }
- ];
-
- return (
-
-
-
- {/* TODO: what image do we show for a Transfer Order? */}
- {/* */}
-
-
-
-
-
-
-
- );
- }, [order, instanceQuery]);
+ const detailsPanel = instanceQuery.isFetching ? (
+
+ ) : (
+
+ );
const orderPanels: PanelType[] = useMemo(() => {
return [
diff --git a/src/frontend/src/pages/stock/TransferOrderDetailsPanel.tsx b/src/frontend/src/pages/stock/TransferOrderDetailsPanel.tsx
new file mode 100644
index 0000000000..a3388b7bc9
--- /dev/null
+++ b/src/frontend/src/pages/stock/TransferOrderDetailsPanel.tsx
@@ -0,0 +1,181 @@
+import { t } from '@lingui/core/macro';
+import { Grid, Skeleton, Stack } from '@mantine/core';
+
+import { ModelType } from '@lib/enums/ModelType';
+import { TagsList } from '@lib/index';
+
+import {
+ type DetailsField,
+ DetailsTable
+} from '../../components/details/Details';
+import { ItemDetailsGrid } from '../../components/details/ItemDetails';
+import { useParameterDetailsGrid } from '../../components/details/ParameterDetailsGrid';
+
+export function TransferOrderDetailsPanel({
+ instance
+}: Readonly<{
+ instance: any;
+}>) {
+ const tl: DetailsField[] = [
+ {
+ type: 'text',
+ name: 'reference',
+ label: t`Reference`,
+ copy: true
+ },
+ {
+ type: 'link',
+ name: 'take_from',
+ icon: 'location',
+ label: t`Source Location`,
+ model: ModelType.stocklocation
+ },
+ {
+ type: 'link',
+ name: 'destination',
+ icon: 'location',
+ label: t`Destination Location`,
+ model: ModelType.stocklocation
+ },
+ {
+ type: 'text',
+ name: 'description',
+ label: t`Description`,
+ copy: true
+ },
+ {
+ type: 'status',
+ name: 'status',
+ label: t`Status`,
+ model: ModelType.transferorder
+ },
+ {
+ type: 'status',
+ name: 'status_custom_key',
+ label: t`Custom Status`,
+ model: ModelType.transferorder,
+ icon: 'status',
+ hidden:
+ !instance?.status_custom_key ||
+ instance?.status_custom_key == instance?.status
+ }
+ ];
+
+ const tr: DetailsField[] = [
+ {
+ type: 'boolean',
+ name: 'consume',
+ icon: 'consume',
+ label: t`Consume Stock`
+ },
+ {
+ type: 'text',
+ name: 'line_items',
+ label: t`Line Items`,
+ icon: 'list'
+ },
+ {
+ type: 'progressbar',
+ name: 'completed',
+ icon: 'progress',
+ label: t`Completed Line Items`,
+ total: instance?.line_items,
+ progress: instance?.completed_lines
+ }
+ ];
+
+ const bl: DetailsField[] = [
+ {
+ type: 'link',
+ external: true,
+ name: 'link',
+ label: t`Link`,
+ copy: true,
+ hidden: !instance?.link
+ },
+ {
+ type: 'text',
+ name: 'project_code_label',
+ label: t`Project Code`,
+ icon: 'reference',
+ copy: true,
+ hidden: !instance?.project_code
+ },
+ {
+ type: 'text',
+ name: 'responsible',
+ label: t`Responsible`,
+ badge: 'owner',
+ hidden: !instance?.responsible
+ }
+ ];
+
+ const br: DetailsField[] = [
+ {
+ type: 'date',
+ name: 'creation_date',
+ label: t`Creation Date`,
+ icon: 'calendar',
+ 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',
+ copy: true,
+ hidden: !instance?.start_date
+ },
+ {
+ type: 'date',
+ name: 'target_date',
+ label: t`Target Date`,
+ copy: true,
+ hidden: !instance?.target_date
+ },
+ {
+ type: 'date',
+ name: 'complete_date',
+ icon: 'calendar_check',
+ label: t`Completion Date`,
+ copy: true,
+ hidden: !instance?.complete_date
+ }
+ ];
+
+ const parametersTable = useParameterDetailsGrid({
+ model_type: ModelType.transferorder,
+ model_id: instance?.pk
+ });
+
+ if (!instance?.pk) return ;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}