diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py
index 40e3951dbcf5..946c2c7c8e0c 100644
--- a/src/backend/InvenTree/InvenTree/api_version.py
+++ b/src/backend/InvenTree/InvenTree/api_version.py
@@ -1,13 +1,16 @@
"""InvenTree API version information."""
# InvenTree API version
-INVENTREE_API_VERSION = 269
+INVENTREE_API_VERSION = 270
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
+v270 - 2024-10-19 : https://github.com/inventree/InvenTree/pull/8307
+ - Adds missing date fields from order API endpoint(s)
+
v269 - 2024-10-16 : https://github.com/inventree/InvenTree/pull/8295
- Adds "include_variants" filter to the BuildOrder API endpoint
- Adds "include_variants" filter to the SalesOrder API endpoint
diff --git a/src/backend/InvenTree/build/api.py b/src/backend/InvenTree/build/api.py
index 1fb5c9f3de85..abeb1229c6ed 100644
--- a/src/backend/InvenTree/build/api.py
+++ b/src/backend/InvenTree/build/api.py
@@ -6,9 +6,8 @@
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User
-from rest_framework.exceptions import ValidationError
-
from django_filters import rest_framework as rest_filters
+from rest_framework.exceptions import ValidationError
from importer.mixins import DataExportViewMixin
@@ -149,7 +148,7 @@ def filter_issued_by(self, queryset, name, owner):
def filter_responsible(self, queryset, name, owner):
"""Filter by orders which are assigned to the specified owner."""
- owners = list(Owner.objects.filter(pk=value))
+ owners = list(Owner.objects.filter(pk=owner))
# if we query by a user, also find all ownerships through group memberships
if len(owners) > 0 and owners[0].label() == 'user':
diff --git a/src/backend/InvenTree/build/status_codes.py b/src/backend/InvenTree/build/status_codes.py
index bc7fc47ddc9b..1c8e66de3004 100644
--- a/src/backend/InvenTree/build/status_codes.py
+++ b/src/backend/InvenTree/build/status_codes.py
@@ -23,3 +23,7 @@ class BuildStatusGroups:
BuildStatus.ON_HOLD.value,
BuildStatus.PRODUCTION.value,
]
+
+ COMPLETE = [
+ BuildStatus.COMPLETE.value,
+ ]
diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py
index 2e9b0ae30b48..ac36216abade 100644
--- a/src/backend/InvenTree/order/serializers.py
+++ b/src/backend/InvenTree/order/serializers.py
@@ -1773,6 +1773,8 @@ class Meta:
model = order.models.ReturnOrder
fields = AbstractOrderSerializer.order_fields([
+ 'issue_date',
+ 'complete_date',
'customer',
'customer_detail',
'customer_reference',
diff --git a/src/backend/InvenTree/order/status_codes.py b/src/backend/InvenTree/order/status_codes.py
index 3dcbee01f518..fa0d4594b7a2 100644
--- a/src/backend/InvenTree/order/status_codes.py
+++ b/src/backend/InvenTree/order/status_codes.py
@@ -35,6 +35,8 @@ class PurchaseOrderStatusGroups:
PurchaseOrderStatus.RETURNED.value,
]
+ COMPLETE = [PurchaseOrderStatus.COMPLETE.value]
+
class SalesOrderStatus(StatusCode):
"""Defines a set of status codes for a SalesOrder."""
@@ -91,6 +93,8 @@ class ReturnOrderStatusGroups:
ReturnOrderStatus.IN_PROGRESS.value,
]
+ COMPLETE = [ReturnOrderStatus.COMPLETE.value]
+
class ReturnOrderLineStatus(StatusCode):
"""Defines a set of status codes for a ReturnOrderLineItem."""
diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx
index 9ee10250f64b..edd7f5a33030 100644
--- a/src/frontend/src/enums/ApiEndpoints.tsx
+++ b/src/frontend/src/enums/ApiEndpoints.tsx
@@ -81,6 +81,7 @@ export enum ApiEndpoints {
build_order_auto_allocate = 'build/:id/auto-allocate/',
build_order_allocate = 'build/:id/allocate/',
build_order_deallocate = 'build/:id/unallocate/',
+
build_line_list = 'build/line/',
build_item_list = 'build/item/',
@@ -160,11 +161,12 @@ export enum ApiEndpoints {
sales_order_cancel = 'order/so/:id/cancel/',
sales_order_ship = 'order/so/:id/ship/',
sales_order_complete = 'order/so/:id/complete/',
+ sales_order_allocate = 'order/so/:id/allocate/',
+ sales_order_allocate_serials = 'order/so/:id/allocate-serials/',
+
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_allocate = 'order/so/:id/allocate/',
- sales_order_allocate_serials = 'order/so/:id/allocate-serials/',
sales_order_shipment_list = 'order/so/shipment/',
sales_order_shipment_complete = 'order/so/shipment/:id/ship/',
diff --git a/src/frontend/src/functions/icons.tsx b/src/frontend/src/functions/icons.tsx
index 38f64de3be6e..c5f1adc03343 100644
--- a/src/frontend/src/functions/icons.tsx
+++ b/src/frontend/src/functions/icons.tsx
@@ -13,8 +13,11 @@ import {
IconBuildingStore,
IconBusinessplan,
IconCalendar,
+ IconCalendarCheck,
+ IconCalendarDot,
IconCalendarStats,
IconCalendarTime,
+ IconCalendarX,
IconCheck,
IconCircleCheck,
IconCircleMinus,
@@ -179,8 +182,15 @@ const icons = {
locked: IconLock,
calendar: IconCalendar,
+ calendar_target: IconCalendarDot,
+ calendar_cross: IconCalendarX,
+ calendar_check: IconCalendarCheck,
external: IconExternalLink,
creation_date: IconCalendarTime,
+ target_date: IconCalendarDot,
+ date: IconCalendar,
+ shipment_date: IconCalendarCheck,
+ complete_date: IconCalendarCheck,
location: IconMapPin,
default_location: IconMapPinHeart,
category_default_location: IconMapPinHeart,
diff --git a/src/frontend/src/pages/part/PartAllocationPanel.tsx b/src/frontend/src/pages/part/PartAllocationPanel.tsx
new file mode 100644
index 000000000000..e64a8a999ab0
--- /dev/null
+++ b/src/frontend/src/pages/part/PartAllocationPanel.tsx
@@ -0,0 +1,55 @@
+import { t } from '@lingui/macro';
+import { Accordion } from '@mantine/core';
+
+import { StylishText } from '../../components/items/StylishText';
+import { ModelType } from '../../enums/ModelType';
+import { UserRoles } from '../../enums/Roles';
+import { useUserState } from '../../states/UserState';
+import BuildAllocatedStockTable from '../../tables/build/BuildAllocatedStockTable';
+import SalesOrderAllocationTable from '../../tables/sales/SalesOrderAllocationTable';
+
+export default function PartAllocationPanel({ part }: { part: any }) {
+ const user = useUserState();
+
+ return (
+ <>
+
+ {part.component && user.hasViewRole(UserRoles.build) && (
+
+
+ {t`Build Order Allocations`}
+
+
+
+
+
+ )}
+ {part.salable && user.hasViewRole(UserRoles.sales_order) && (
+
+
+ {t`Sales Order Allocations`}
+
+
+
+
+
+ )}
+
+ >
+ );
+}
diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx
index cd6a1b30da24..2d6d4ecef47f 100644
--- a/src/frontend/src/pages/part/PartDetail.tsx
+++ b/src/frontend/src/pages/part/PartDetail.tsx
@@ -1,6 +1,5 @@
import { t } from '@lingui/macro';
import {
- Accordion,
Alert,
Center,
Grid,
@@ -54,7 +53,6 @@ import {
EditItemAction,
OptionsActionDropdown
} from '../../components/items/ActionDropdown';
-import { StylishText } from '../../components/items/StylishText';
import InstanceDetail from '../../components/nav/InstanceDetail';
import NavigationTree from '../../components/nav/NavigationTree';
import { PageDetail } from '../../components/nav/PageDetail';
@@ -89,7 +87,6 @@ import {
import { useUserState } from '../../states/UserState';
import { BomTable } from '../../tables/bom/BomTable';
import { UsedInTable } from '../../tables/bom/UsedInTable';
-import BuildAllocatedStockTable from '../../tables/build/BuildAllocatedStockTable';
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
import { PartParameterTable } from '../../tables/part/PartParameterTable';
import PartPurchaseOrdersTable from '../../tables/part/PartPurchaseOrdersTable';
@@ -99,10 +96,10 @@ import { RelatedPartTable } from '../../tables/part/RelatedPartTable';
import { ManufacturerPartTable } from '../../tables/purchasing/ManufacturerPartTable';
import { SupplierPartTable } from '../../tables/purchasing/SupplierPartTable';
import { ReturnOrderTable } from '../../tables/sales/ReturnOrderTable';
-import SalesOrderAllocationTable from '../../tables/sales/SalesOrderAllocationTable';
import { SalesOrderTable } from '../../tables/sales/SalesOrderTable';
import { StockItemTable } from '../../tables/stock/StockItemTable';
import { TestStatisticsTable } from '../../tables/stock/TestStatisticsTable';
+import PartAllocationPanel from './PartAllocationPanel';
import PartPricingPanel from './PartPricingPanel';
import PartSchedulingDetail from './PartSchedulingDetail';
import PartStocktakeDetail from './PartStocktakeDetail';
@@ -351,7 +348,7 @@ export default function PartDetail() {
},
{
type: 'boolean',
- name: 'saleable',
+ name: 'salable',
label: t`Saleable Part`
},
{
@@ -591,45 +588,7 @@ export default function PartDetail() {
label: t`Allocations`,
icon: ,
hidden: !part.component && !part.salable,
- content: (
-
- {part.component && (
-
-
- {t`Build Order Allocations`}
-
-
-
-
-
- )}
- {part.salable && (
-
-
- {t`Sales Order Allocations`}
-
-
-
-
-
- )}
-
- )
+ content: part.pk ? :
},
{
name: 'bom',
@@ -644,8 +603,8 @@ export default function PartDetail() {
name: 'builds',
label: t`Build Orders`,
icon: ,
- hidden: !part.assembly || !part.active,
- content: part?.pk ? :
+ hidden: !part.assembly || !user.hasViewRole(UserRoles.build),
+ content: part.pk ? :
},
{
name: 'used_in',
@@ -677,7 +636,8 @@ export default function PartDetail() {
name: 'suppliers',
label: t`Suppliers`,
icon: ,
- hidden: !part.purchaseable,
+ hidden:
+ !part.purchaseable || !user.hasViewRole(UserRoles.purchase_order),
content: part.pk && (
,
- hidden: !part.purchaseable,
- content:
+ hidden:
+ !part.purchaseable || !user.hasViewRole(UserRoles.purchase_order),
+ content: part.pk ? (
+
+ ) : (
+
+ )
},
{
name: 'sales_orders',
label: t`Sales Orders`,
icon: ,
- hidden: !part.salable,
+ hidden: !part.salable || !user.hasViewRole(UserRoles.sales_order),
content: part.pk ? :
},
{
name: 'return_orders',
label: t`Return Orders`,
icon: ,
- hidden: !part.salable || !globalSettings.isSet('RETURNORDER_ENABLED'),
+ hidden:
+ !part.salable ||
+ !user.hasViewRole(UserRoles.return_order) ||
+ !globalSettings.isSet('RETURNORDER_ENABLED'),
content: part.pk ? :
},
{
diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
index 60d5540585e6..b7631b984662 100644
--- a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
+++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
@@ -196,18 +196,34 @@ export default function PurchaseOrderDetail() {
let br: DetailsField[] = [
{
- type: 'text',
+ type: 'date',
name: 'creation_date',
- label: t`Created On`,
+ label: t`Creation Date`,
icon: 'calendar'
},
{
- type: 'text',
+ type: 'date',
+ name: 'issue_date',
+ label: t`Issue Date`,
+ icon: 'calendar',
+ copy: true,
+ hidden: !order.issue_date
+ },
+ {
+ type: 'date',
name: 'target_date',
label: t`Target Date`,
icon: 'calendar',
hidden: !order.target_date
},
+ {
+ type: 'date',
+ name: 'complete_date',
+ icon: 'calendar_check',
+ label: t`Completion Date`,
+ copy: true,
+ hidden: !order.complete_date
+ },
{
type: 'text',
name: 'responsible',
diff --git a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx
index 0f0ed61daffc..3fd7bf5765bb 100644
--- a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx
+++ b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx
@@ -173,18 +173,36 @@ export default function ReturnOrderDetail() {
let br: DetailsField[] = [
{
- type: 'text',
+ type: 'date',
name: 'creation_date',
- label: t`Created On`,
- icon: 'calendar'
+ label: t`Creation Date`,
+ icon: 'calendar',
+ copy: true,
+ hidden: !order.creation_date
},
{
- type: 'text',
+ type: 'date',
+ name: 'issue_date',
+ label: t`Issue Date`,
+ icon: 'calendar',
+ copy: true,
+ hidden: !order.issue_date
+ },
+ {
+ type: 'date',
name: 'target_date',
label: t`Target Date`,
- icon: 'calendar',
+ copy: true,
hidden: !order.target_date
},
+ {
+ type: 'date',
+ name: 'complete_date',
+ icon: 'calendar_check',
+ label: t`Completion Date`,
+ copy: true,
+ hidden: !order.complete_date
+ },
{
type: 'text',
name: 'responsible',
diff --git a/src/frontend/src/pages/sales/SalesOrderDetail.tsx b/src/frontend/src/pages/sales/SalesOrderDetail.tsx
index 6a49f2838038..7e5f94dc0fae 100644
--- a/src/frontend/src/pages/sales/SalesOrderDetail.tsx
+++ b/src/frontend/src/pages/sales/SalesOrderDetail.tsx
@@ -100,6 +100,7 @@ export default function SalesOrderDetail() {
name: 'customer_reference',
label: t`Customer Reference`,
copy: true,
+ icon: 'reference',
hidden: !order.customer_reference
},
{
@@ -184,17 +185,33 @@ export default function SalesOrderDetail() {
let br: DetailsField[] = [
{
- type: 'text',
+ type: 'date',
name: 'creation_date',
- label: t`Created On`,
- icon: 'calendar'
+ label: t`Creation Date`,
+ copy: true,
+ hidden: !order.creation_date
},
{
- type: 'text',
+ type: 'date',
+ name: 'issue_date',
+ label: t`Issue Date`,
+ icon: 'calendar',
+ copy: true,
+ hidden: !order.issue_date
+ },
+ {
+ type: 'date',
name: 'target_date',
label: t`Target Date`,
- icon: 'calendar',
- hidden: !order.target_date
+ hidden: !order.target_date,
+ copy: true
+ },
+ {
+ type: 'date',
+ name: 'shipment_date',
+ label: t`Completion Date`,
+ hidden: !order.shipment_date,
+ copy: true
},
{
type: 'text',
diff --git a/src/frontend/src/states/UserState.tsx b/src/frontend/src/states/UserState.tsx
index 6678e9dba0ad..c2b34b84f274 100644
--- a/src/frontend/src/states/UserState.tsx
+++ b/src/frontend/src/states/UserState.tsx
@@ -67,6 +67,15 @@ export const useUserState = create((set, get) => ({
setApiDefaults();
},
fetchUserToken: async () => {
+ // If neither the csrf or session cookies are available, we cannot fetch a token
+ if (
+ !document.cookie.includes('csrftoken') &&
+ !document.cookie.includes('sessionid')
+ ) {
+ get().clearToken();
+ return;
+ }
+
await api
.get(apiUrl(ApiEndpoints.user_token))
.then((response) => {
@@ -85,6 +94,12 @@ export const useUserState = create((set, get) => ({
await get().fetchUserToken();
}
+ // If we still don't have a token, clear the user state and return
+ if (!get().token) {
+ get().clearUserState();
+ return;
+ }
+
// Fetch user data
await api
.get(apiUrl(ApiEndpoints.user_me), {
diff --git a/src/frontend/src/tables/build/BuildOrderTable.tsx b/src/frontend/src/tables/build/BuildOrderTable.tsx
index 03004d5428e5..c293247ab748 100644
--- a/src/frontend/src/tables/build/BuildOrderTable.tsx
+++ b/src/frontend/src/tables/build/BuildOrderTable.tsx
@@ -95,6 +95,7 @@ export function BuildOrderTable({
TargetDateColumn({}),
DateColumn({
accessor: 'completion_date',
+ title: t`Completion Date`,
sortable: true
}),
{
diff --git a/src/frontend/src/tables/company/CompanyTable.tsx b/src/frontend/src/tables/company/CompanyTable.tsx
index eabdf1a18eb4..0e14bbf6f914 100644
--- a/src/frontend/src/tables/company/CompanyTable.tsx
+++ b/src/frontend/src/tables/company/CompanyTable.tsx
@@ -9,6 +9,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { companyFields } from '../../forms/CompanyForms';
+import { navigateToLink } from '../../functions/navigation';
import {
useCreateApiFormModal,
useEditApiFormModal
@@ -157,16 +158,17 @@ export function CompanyTable({
params: {
...params
},
+ onRowClick: (record: any, index: number, event: any) => {
+ if (record.pk) {
+ let base = path ?? 'company';
+ navigateToLink(`/${base}/${record.pk}`, navigate, event);
+ }
+ },
+ modelType: ModelType.company,
tableFilters: tableFilters,
tableActions: tableActions,
enableDownload: true,
- rowActions: rowActions,
- onRowClick: (row: any) => {
- if (row.pk) {
- let base = path ?? 'company';
- navigate(`/${base}/${row.pk}`);
- }
- }
+ rowActions: rowActions
}}
/>
>
diff --git a/src/frontend/src/tables/part/PartPurchaseOrdersTable.tsx b/src/frontend/src/tables/part/PartPurchaseOrdersTable.tsx
index ed50428c5277..d9878bbdeb5a 100644
--- a/src/frontend/src/tables/part/PartPurchaseOrdersTable.tsx
+++ b/src/frontend/src/tables/part/PartPurchaseOrdersTable.tsx
@@ -92,6 +92,10 @@ export default function PartPurchaseOrdersTable({
);
}
},
+ DateColumn({
+ accessor: 'order_detail.complete_date',
+ title: t`Order Completed Date`
+ }),
DateColumn({
accessor: 'target_date',
title: t`Target Date`
diff --git a/src/frontend/src/tables/sales/ReturnOrderTable.tsx b/src/frontend/src/tables/sales/ReturnOrderTable.tsx
index 906f2569b729..f399c5b26298 100644
--- a/src/frontend/src/tables/sales/ReturnOrderTable.tsx
+++ b/src/frontend/src/tables/sales/ReturnOrderTable.tsx
@@ -15,6 +15,7 @@ import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import {
CreationDateColumn,
+ DateColumn,
DescriptionColumn,
LineItemsProgressColumn,
ProjectCodeColumn,
@@ -116,6 +117,10 @@ export function ReturnOrderTable({
ProjectCodeColumn({}),
CreationDateColumn({}),
TargetDateColumn({}),
+ DateColumn({
+ accessor: 'complete_date',
+ title: t`Completion Date`
+ }),
ResponsibleColumn({}),
{
accessor: 'total_price',
diff --git a/src/frontend/src/tables/sales/SalesOrderAllocationTable.tsx b/src/frontend/src/tables/sales/SalesOrderAllocationTable.tsx
index a2d38b8b7fba..14a4465708d3 100644
--- a/src/frontend/src/tables/sales/SalesOrderAllocationTable.tsx
+++ b/src/frontend/src/tables/sales/SalesOrderAllocationTable.tsx
@@ -16,6 +16,7 @@ import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import { TableColumn } from '../Column';
import {
+ DateColumn,
LocationColumn,
PartColumn,
ReferenceColumn,
@@ -136,6 +137,12 @@ export default function SalesOrderAllocationTable({
switchable: true,
sortable: false
},
+ DateColumn({
+ accessor: 'shipment_detail.shipment_date',
+ title: t`Shipment Date`,
+ switchable: true,
+ sortable: false
+ }),
{
accessor: 'shipment_date',
title: t`Shipped`,
diff --git a/src/frontend/src/tables/sales/SalesOrderShipmentTable.tsx b/src/frontend/src/tables/sales/SalesOrderShipmentTable.tsx
index 72db46588fd9..b2c09f7c42af 100644
--- a/src/frontend/src/tables/sales/SalesOrderShipmentTable.tsx
+++ b/src/frontend/src/tables/sales/SalesOrderShipmentTable.tsx
@@ -4,6 +4,7 @@ import { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { AddItemButton } from '../../components/buttons/AddItemButton';
+import { YesNoButton } from '../../components/buttons/YesNoButton';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
@@ -23,7 +24,12 @@ import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import { TableColumn } from '../Column';
-import { DateColumn, LinkColumn, NoteColumn } from '../ColumnRenderers';
+import {
+ BooleanColumn,
+ DateColumn,
+ LinkColumn,
+ NoteColumn
+} from '../ColumnRenderers';
import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowAction, RowCancelAction, RowEditAction } from '../RowActions';
@@ -97,6 +103,13 @@ export default function SalesOrderShipmentTable({
switchable: false,
title: t`Items`
},
+ {
+ accessor: 'shipped',
+ title: t`Shipped`,
+ switchable: true,
+ sortable: false,
+ render: (record: any) =>
+ },
DateColumn({
accessor: 'shipment_date',
title: t`Shipment Date`