2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-05-03 13:58:47 +00:00

Refactoring / Enhancements (#8307)

* Create simply PartSalesPanel component

* Updates

* Add API endpoint for SalesHistory

- And serializers
- Basic, needs lots of work still

* Fix for PartDetail page

* SalesOrder page updates

* More page updates

* Update API endpoint

* Backend improvements

* add API endpoint

* Front-end rendering

* Make frontend generic

* Fix for CompanyTable

* Make back-end API more generic

* More API improvements

* Implement history for purchasing

* API / UI fixes

* Remove debug statements

* Support file download

* Add endpoint for build order history

* Implement UI for build order history

* Revert backend

* Revert frontend

* Remove unsed imports

* Cleanup permission checks

* Bump API version

* Improve token management code

- Do not request token if other cookies are unavailable
- Do not fetch user data if token is unavailable
- Prevents connection error logs

* Fix for CompanyTable - onRowClick
This commit is contained in:
Oliver 2024-10-20 12:48:29 +11:00 committed by GitHub
parent 16d0fb4798
commit 29726d8d0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 224 additions and 79 deletions

View File

@ -1,13 +1,16 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # 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.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ 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 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 BuildOrder API endpoint
- Adds "include_variants" filter to the SalesOrder API endpoint - Adds "include_variants" filter to the SalesOrder API endpoint

View File

@ -6,9 +6,8 @@ from django.urls import include, path
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User from django.contrib.auth.models import User
from rest_framework.exceptions import ValidationError
from django_filters import rest_framework as rest_filters from django_filters import rest_framework as rest_filters
from rest_framework.exceptions import ValidationError
from importer.mixins import DataExportViewMixin from importer.mixins import DataExportViewMixin
@ -149,7 +148,7 @@ class BuildFilter(rest_filters.FilterSet):
def filter_responsible(self, queryset, name, owner): def filter_responsible(self, queryset, name, owner):
"""Filter by orders which are assigned to the specified 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 we query by a user, also find all ownerships through group memberships
if len(owners) > 0 and owners[0].label() == 'user': if len(owners) > 0 and owners[0].label() == 'user':

View File

@ -23,3 +23,7 @@ class BuildStatusGroups:
BuildStatus.ON_HOLD.value, BuildStatus.ON_HOLD.value,
BuildStatus.PRODUCTION.value, BuildStatus.PRODUCTION.value,
] ]
COMPLETE = [
BuildStatus.COMPLETE.value,
]

View File

@ -1773,6 +1773,8 @@ class ReturnOrderSerializer(
model = order.models.ReturnOrder model = order.models.ReturnOrder
fields = AbstractOrderSerializer.order_fields([ fields = AbstractOrderSerializer.order_fields([
'issue_date',
'complete_date',
'customer', 'customer',
'customer_detail', 'customer_detail',
'customer_reference', 'customer_reference',

View File

@ -35,6 +35,8 @@ class PurchaseOrderStatusGroups:
PurchaseOrderStatus.RETURNED.value, PurchaseOrderStatus.RETURNED.value,
] ]
COMPLETE = [PurchaseOrderStatus.COMPLETE.value]
class SalesOrderStatus(StatusCode): class SalesOrderStatus(StatusCode):
"""Defines a set of status codes for a SalesOrder.""" """Defines a set of status codes for a SalesOrder."""
@ -91,6 +93,8 @@ class ReturnOrderStatusGroups:
ReturnOrderStatus.IN_PROGRESS.value, ReturnOrderStatus.IN_PROGRESS.value,
] ]
COMPLETE = [ReturnOrderStatus.COMPLETE.value]
class ReturnOrderLineStatus(StatusCode): class ReturnOrderLineStatus(StatusCode):
"""Defines a set of status codes for a ReturnOrderLineItem.""" """Defines a set of status codes for a ReturnOrderLineItem."""

View File

@ -81,6 +81,7 @@ export enum ApiEndpoints {
build_order_auto_allocate = 'build/:id/auto-allocate/', build_order_auto_allocate = 'build/:id/auto-allocate/',
build_order_allocate = 'build/:id/allocate/', build_order_allocate = 'build/:id/allocate/',
build_order_deallocate = 'build/:id/unallocate/', build_order_deallocate = 'build/:id/unallocate/',
build_line_list = 'build/line/', build_line_list = 'build/line/',
build_item_list = 'build/item/', build_item_list = 'build/item/',
@ -160,11 +161,12 @@ export enum ApiEndpoints {
sales_order_cancel = 'order/so/:id/cancel/', sales_order_cancel = 'order/so/:id/cancel/',
sales_order_ship = 'order/so/:id/ship/', sales_order_ship = 'order/so/:id/ship/',
sales_order_complete = 'order/so/:id/complete/', 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_line_list = 'order/so-line/',
sales_order_extra_line_list = 'order/so-extra-line/', sales_order_extra_line_list = 'order/so-extra-line/',
sales_order_allocation_list = 'order/so-allocation/', 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_list = 'order/so/shipment/',
sales_order_shipment_complete = 'order/so/shipment/:id/ship/', sales_order_shipment_complete = 'order/so/shipment/:id/ship/',

View File

@ -13,8 +13,11 @@ import {
IconBuildingStore, IconBuildingStore,
IconBusinessplan, IconBusinessplan,
IconCalendar, IconCalendar,
IconCalendarCheck,
IconCalendarDot,
IconCalendarStats, IconCalendarStats,
IconCalendarTime, IconCalendarTime,
IconCalendarX,
IconCheck, IconCheck,
IconCircleCheck, IconCircleCheck,
IconCircleMinus, IconCircleMinus,
@ -179,8 +182,15 @@ const icons = {
locked: IconLock, locked: IconLock,
calendar: IconCalendar, calendar: IconCalendar,
calendar_target: IconCalendarDot,
calendar_cross: IconCalendarX,
calendar_check: IconCalendarCheck,
external: IconExternalLink, external: IconExternalLink,
creation_date: IconCalendarTime, creation_date: IconCalendarTime,
target_date: IconCalendarDot,
date: IconCalendar,
shipment_date: IconCalendarCheck,
complete_date: IconCalendarCheck,
location: IconMapPin, location: IconMapPin,
default_location: IconMapPinHeart, default_location: IconMapPinHeart,
category_default_location: IconMapPinHeart, category_default_location: IconMapPinHeart,

View File

@ -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 (
<>
<Accordion
multiple={true}
defaultValue={['buildallocations', 'salesallocations']}
>
{part.component && user.hasViewRole(UserRoles.build) && (
<Accordion.Item value="buildallocations" key="buildallocations">
<Accordion.Control>
<StylishText size="lg">{t`Build Order Allocations`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<BuildAllocatedStockTable
partId={part.pk}
modelField="build"
modelTarget={ModelType.build}
showBuildInfo
showPartInfo
allowEdit
/>
</Accordion.Panel>
</Accordion.Item>
)}
{part.salable && user.hasViewRole(UserRoles.sales_order) && (
<Accordion.Item value="salesallocations" key="salesallocations">
<Accordion.Control>
<StylishText size="lg">{t`Sales Order Allocations`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<SalesOrderAllocationTable
partId={part.pk}
modelField="order"
modelTarget={ModelType.salesorder}
showOrderInfo
/>
</Accordion.Panel>
</Accordion.Item>
)}
</Accordion>
</>
);
}

View File

@ -1,6 +1,5 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import {
Accordion,
Alert, Alert,
Center, Center,
Grid, Grid,
@ -54,7 +53,6 @@ import {
EditItemAction, EditItemAction,
OptionsActionDropdown OptionsActionDropdown
} from '../../components/items/ActionDropdown'; } from '../../components/items/ActionDropdown';
import { StylishText } from '../../components/items/StylishText';
import InstanceDetail from '../../components/nav/InstanceDetail'; import InstanceDetail from '../../components/nav/InstanceDetail';
import NavigationTree from '../../components/nav/NavigationTree'; import NavigationTree from '../../components/nav/NavigationTree';
import { PageDetail } from '../../components/nav/PageDetail'; import { PageDetail } from '../../components/nav/PageDetail';
@ -89,7 +87,6 @@ import {
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import { BomTable } from '../../tables/bom/BomTable'; import { BomTable } from '../../tables/bom/BomTable';
import { UsedInTable } from '../../tables/bom/UsedInTable'; import { UsedInTable } from '../../tables/bom/UsedInTable';
import BuildAllocatedStockTable from '../../tables/build/BuildAllocatedStockTable';
import { BuildOrderTable } from '../../tables/build/BuildOrderTable'; import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
import { PartParameterTable } from '../../tables/part/PartParameterTable'; import { PartParameterTable } from '../../tables/part/PartParameterTable';
import PartPurchaseOrdersTable from '../../tables/part/PartPurchaseOrdersTable'; import PartPurchaseOrdersTable from '../../tables/part/PartPurchaseOrdersTable';
@ -99,10 +96,10 @@ import { RelatedPartTable } from '../../tables/part/RelatedPartTable';
import { ManufacturerPartTable } from '../../tables/purchasing/ManufacturerPartTable'; import { ManufacturerPartTable } from '../../tables/purchasing/ManufacturerPartTable';
import { SupplierPartTable } from '../../tables/purchasing/SupplierPartTable'; import { SupplierPartTable } from '../../tables/purchasing/SupplierPartTable';
import { ReturnOrderTable } from '../../tables/sales/ReturnOrderTable'; import { ReturnOrderTable } from '../../tables/sales/ReturnOrderTable';
import SalesOrderAllocationTable from '../../tables/sales/SalesOrderAllocationTable';
import { SalesOrderTable } from '../../tables/sales/SalesOrderTable'; import { SalesOrderTable } from '../../tables/sales/SalesOrderTable';
import { StockItemTable } from '../../tables/stock/StockItemTable'; import { StockItemTable } from '../../tables/stock/StockItemTable';
import { TestStatisticsTable } from '../../tables/stock/TestStatisticsTable'; import { TestStatisticsTable } from '../../tables/stock/TestStatisticsTable';
import PartAllocationPanel from './PartAllocationPanel';
import PartPricingPanel from './PartPricingPanel'; import PartPricingPanel from './PartPricingPanel';
import PartSchedulingDetail from './PartSchedulingDetail'; import PartSchedulingDetail from './PartSchedulingDetail';
import PartStocktakeDetail from './PartStocktakeDetail'; import PartStocktakeDetail from './PartStocktakeDetail';
@ -351,7 +348,7 @@ export default function PartDetail() {
}, },
{ {
type: 'boolean', type: 'boolean',
name: 'saleable', name: 'salable',
label: t`Saleable Part` label: t`Saleable Part`
}, },
{ {
@ -591,45 +588,7 @@ export default function PartDetail() {
label: t`Allocations`, label: t`Allocations`,
icon: <IconBookmarks />, icon: <IconBookmarks />,
hidden: !part.component && !part.salable, hidden: !part.component && !part.salable,
content: ( content: part.pk ? <PartAllocationPanel part={part} /> : <Skeleton />
<Accordion
multiple={true}
defaultValue={['buildallocations', 'salesallocations']}
>
{part.component && (
<Accordion.Item value="buildallocations" key="buildallocations">
<Accordion.Control>
<StylishText size="lg">{t`Build Order Allocations`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<BuildAllocatedStockTable
partId={part.pk}
modelField="build"
modelTarget={ModelType.build}
showBuildInfo
showPartInfo
allowEdit
/>
</Accordion.Panel>
</Accordion.Item>
)}
{part.salable && (
<Accordion.Item value="salesallocations" key="salesallocations">
<Accordion.Control>
<StylishText size="lg">{t`Sales Order Allocations`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<SalesOrderAllocationTable
partId={part.pk}
modelField="order"
modelTarget={ModelType.salesorder}
showOrderInfo
/>
</Accordion.Panel>
</Accordion.Item>
)}
</Accordion>
)
}, },
{ {
name: 'bom', name: 'bom',
@ -644,8 +603,8 @@ export default function PartDetail() {
name: 'builds', name: 'builds',
label: t`Build Orders`, label: t`Build Orders`,
icon: <IconTools />, icon: <IconTools />,
hidden: !part.assembly || !part.active, hidden: !part.assembly || !user.hasViewRole(UserRoles.build),
content: part?.pk ? <BuildOrderTable partId={part.pk} /> : <Skeleton /> content: part.pk ? <BuildOrderTable partId={part.pk} /> : <Skeleton />
}, },
{ {
name: 'used_in', name: 'used_in',
@ -677,7 +636,8 @@ export default function PartDetail() {
name: 'suppliers', name: 'suppliers',
label: t`Suppliers`, label: t`Suppliers`,
icon: <IconBuilding />, icon: <IconBuilding />,
hidden: !part.purchaseable, hidden:
!part.purchaseable || !user.hasViewRole(UserRoles.purchase_order),
content: part.pk && ( content: part.pk && (
<SupplierPartTable <SupplierPartTable
params={{ params={{
@ -690,21 +650,29 @@ export default function PartDetail() {
name: 'purchase_orders', name: 'purchase_orders',
label: t`Purchase Orders`, label: t`Purchase Orders`,
icon: <IconShoppingCart />, icon: <IconShoppingCart />,
hidden: !part.purchaseable, hidden:
content: <PartPurchaseOrdersTable partId={part.pk} /> !part.purchaseable || !user.hasViewRole(UserRoles.purchase_order),
content: part.pk ? (
<PartPurchaseOrdersTable partId={part.pk} />
) : (
<Skeleton />
)
}, },
{ {
name: 'sales_orders', name: 'sales_orders',
label: t`Sales Orders`, label: t`Sales Orders`,
icon: <IconTruckDelivery />, icon: <IconTruckDelivery />,
hidden: !part.salable, hidden: !part.salable || !user.hasViewRole(UserRoles.sales_order),
content: part.pk ? <SalesOrderTable partId={part.pk} /> : <Skeleton /> content: part.pk ? <SalesOrderTable partId={part.pk} /> : <Skeleton />
}, },
{ {
name: 'return_orders', name: 'return_orders',
label: t`Return Orders`, label: t`Return Orders`,
icon: <IconTruckReturn />, icon: <IconTruckReturn />,
hidden: !part.salable || !globalSettings.isSet('RETURNORDER_ENABLED'), hidden:
!part.salable ||
!user.hasViewRole(UserRoles.return_order) ||
!globalSettings.isSet('RETURNORDER_ENABLED'),
content: part.pk ? <ReturnOrderTable partId={part.pk} /> : <Skeleton /> content: part.pk ? <ReturnOrderTable partId={part.pk} /> : <Skeleton />
}, },
{ {

View File

@ -196,18 +196,34 @@ export default function PurchaseOrderDetail() {
let br: DetailsField[] = [ let br: DetailsField[] = [
{ {
type: 'text', type: 'date',
name: 'creation_date', name: 'creation_date',
label: t`Created On`, label: t`Creation Date`,
icon: 'calendar' 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', name: 'target_date',
label: t`Target Date`, label: t`Target Date`,
icon: 'calendar', icon: 'calendar',
hidden: !order.target_date hidden: !order.target_date
}, },
{
type: 'date',
name: 'complete_date',
icon: 'calendar_check',
label: t`Completion Date`,
copy: true,
hidden: !order.complete_date
},
{ {
type: 'text', type: 'text',
name: 'responsible', name: 'responsible',

View File

@ -173,18 +173,36 @@ export default function ReturnOrderDetail() {
let br: DetailsField[] = [ let br: DetailsField[] = [
{ {
type: 'text', type: 'date',
name: 'creation_date', name: 'creation_date',
label: t`Created On`, label: t`Creation Date`,
icon: 'calendar' 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', name: 'target_date',
label: t`Target Date`, label: t`Target Date`,
icon: 'calendar', copy: true,
hidden: !order.target_date hidden: !order.target_date
}, },
{
type: 'date',
name: 'complete_date',
icon: 'calendar_check',
label: t`Completion Date`,
copy: true,
hidden: !order.complete_date
},
{ {
type: 'text', type: 'text',
name: 'responsible', name: 'responsible',

View File

@ -100,6 +100,7 @@ export default function SalesOrderDetail() {
name: 'customer_reference', name: 'customer_reference',
label: t`Customer Reference`, label: t`Customer Reference`,
copy: true, copy: true,
icon: 'reference',
hidden: !order.customer_reference hidden: !order.customer_reference
}, },
{ {
@ -184,17 +185,33 @@ export default function SalesOrderDetail() {
let br: DetailsField[] = [ let br: DetailsField[] = [
{ {
type: 'text', type: 'date',
name: 'creation_date', name: 'creation_date',
label: t`Created On`, 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', name: 'target_date',
label: t`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', type: 'text',

View File

@ -67,6 +67,15 @@ export const useUserState = create<UserStateProps>((set, get) => ({
setApiDefaults(); setApiDefaults();
}, },
fetchUserToken: async () => { 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 await api
.get(apiUrl(ApiEndpoints.user_token)) .get(apiUrl(ApiEndpoints.user_token))
.then((response) => { .then((response) => {
@ -85,6 +94,12 @@ export const useUserState = create<UserStateProps>((set, get) => ({
await get().fetchUserToken(); 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 // Fetch user data
await api await api
.get(apiUrl(ApiEndpoints.user_me), { .get(apiUrl(ApiEndpoints.user_me), {

View File

@ -95,6 +95,7 @@ export function BuildOrderTable({
TargetDateColumn({}), TargetDateColumn({}),
DateColumn({ DateColumn({
accessor: 'completion_date', accessor: 'completion_date',
title: t`Completion Date`,
sortable: true sortable: true
}), }),
{ {

View File

@ -9,6 +9,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles'; import { UserRoles } from '../../enums/Roles';
import { companyFields } from '../../forms/CompanyForms'; import { companyFields } from '../../forms/CompanyForms';
import { navigateToLink } from '../../functions/navigation';
import { import {
useCreateApiFormModal, useCreateApiFormModal,
useEditApiFormModal useEditApiFormModal
@ -157,16 +158,17 @@ export function CompanyTable({
params: { params: {
...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, tableFilters: tableFilters,
tableActions: tableActions, tableActions: tableActions,
enableDownload: true, enableDownload: true,
rowActions: rowActions, rowActions: rowActions
onRowClick: (row: any) => {
if (row.pk) {
let base = path ?? 'company';
navigate(`/${base}/${row.pk}`);
}
}
}} }}
/> />
</> </>

View File

@ -92,6 +92,10 @@ export default function PartPurchaseOrdersTable({
); );
} }
}, },
DateColumn({
accessor: 'order_detail.complete_date',
title: t`Order Completed Date`
}),
DateColumn({ DateColumn({
accessor: 'target_date', accessor: 'target_date',
title: t`Target Date` title: t`Target Date`

View File

@ -15,6 +15,7 @@ import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import { import {
CreationDateColumn, CreationDateColumn,
DateColumn,
DescriptionColumn, DescriptionColumn,
LineItemsProgressColumn, LineItemsProgressColumn,
ProjectCodeColumn, ProjectCodeColumn,
@ -116,6 +117,10 @@ export function ReturnOrderTable({
ProjectCodeColumn({}), ProjectCodeColumn({}),
CreationDateColumn({}), CreationDateColumn({}),
TargetDateColumn({}), TargetDateColumn({}),
DateColumn({
accessor: 'complete_date',
title: t`Completion Date`
}),
ResponsibleColumn({}), ResponsibleColumn({}),
{ {
accessor: 'total_price', accessor: 'total_price',

View File

@ -16,6 +16,7 @@ import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import { TableColumn } from '../Column'; import { TableColumn } from '../Column';
import { import {
DateColumn,
LocationColumn, LocationColumn,
PartColumn, PartColumn,
ReferenceColumn, ReferenceColumn,
@ -136,6 +137,12 @@ export default function SalesOrderAllocationTable({
switchable: true, switchable: true,
sortable: false sortable: false
}, },
DateColumn({
accessor: 'shipment_detail.shipment_date',
title: t`Shipment Date`,
switchable: true,
sortable: false
}),
{ {
accessor: 'shipment_date', accessor: 'shipment_date',
title: t`Shipped`, title: t`Shipped`,

View File

@ -4,6 +4,7 @@ import { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { AddItemButton } from '../../components/buttons/AddItemButton'; import { AddItemButton } from '../../components/buttons/AddItemButton';
import { YesNoButton } from '../../components/buttons/YesNoButton';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles'; import { UserRoles } from '../../enums/Roles';
@ -23,7 +24,12 @@ import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState'; import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import { TableColumn } from '../Column'; import { TableColumn } from '../Column';
import { DateColumn, LinkColumn, NoteColumn } from '../ColumnRenderers'; import {
BooleanColumn,
DateColumn,
LinkColumn,
NoteColumn
} from '../ColumnRenderers';
import { TableFilter } from '../Filter'; import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable'; import { InvenTreeTable } from '../InvenTreeTable';
import { RowAction, RowCancelAction, RowEditAction } from '../RowActions'; import { RowAction, RowCancelAction, RowEditAction } from '../RowActions';
@ -97,6 +103,13 @@ export default function SalesOrderShipmentTable({
switchable: false, switchable: false,
title: t`Items` title: t`Items`
}, },
{
accessor: 'shipped',
title: t`Shipped`,
switchable: true,
sortable: false,
render: (record: any) => <YesNoButton value={!!record.shipment_date} />
},
DateColumn({ DateColumn({
accessor: 'shipment_date', accessor: 'shipment_date',
title: t`Shipment Date` title: t`Shipment Date`