diff --git a/docs/docs/part/scheduling.md b/docs/docs/part/scheduling.md
deleted file mode 100644
index 199075d259..0000000000
--- a/docs/docs/part/scheduling.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-title: Part Scheduling
----
-
-## Part Scheduling
-
-
-The *Scheduling* tab provides an overview of the *predicted* future available quantity of a particular part.
-
-The *Scheduling* tab displays a chart of estimated future part stock levels. It begins at the current date, with the current stock level. It then projects into the "future", taking information from:
-
-#### Incoming Stock
-
-- **Purchase Orders** - Incoming goods will increase stock levels
-- **Build Orders** - Completed build outputs will increase stock levels
-
-#### Outgoing Stock
-
-- **Sales Orders** - Outgoing stock items will reduce stock levels
-- **Build Orders** - Allocated stock items will reduce stock levels
-
-#### Caveats
-
-The scheduling information only works as an adequate predictor of future stock quantity if there is sufficient information available in the database.
-
-In particular, stock movements due to orders (Purchase Orders / Sales Orders / Build Orders) will only be counted in the scheduling *if a target date is set for the order*. If the order does not have a target date set, we cannot know *when* (in the future) the stock levels will be adjusted. Thus, orders without target date information do not contribute to the scheduling information.
-
-Additionally, any orders with a target date in the "past" are also ignored for the purpose of part scheduling.
-
-Finally, any unexpected or unscheduled stock operations which are not associated with future orders cannot be predicted or displayed in the scheduling tab.
-
-{{ image("part/scheduling.png", "Part Scheduling View") }}
diff --git a/docs/docs/part/views.md b/docs/docs/part/views.md
index cf5bd850b7..8d927a243b 100644
--- a/docs/docs/part/views.md
+++ b/docs/docs/part/views.md
@@ -107,10 +107,6 @@ This tab is only displayed if the part is marked as *Purchaseable*.
The *Sales Orders* tab shows a list of the sales orders for this part. It provides a view for important sales order information like customer, status, creation and shipment dates.
-### Scheduling
-
-The *Scheduling* tab provides an overview of the *predicted* future availability of a particular part. Refer to the [scheduling documentation](./scheduling.md) for further information.
-
### Stocktake
The *Stocktake* tab provide historical stock level information, based on user-provided stocktake data. Refer to the [stocktake documentation](./stocktake.md) for further information.
diff --git a/docs/docs/settings/user.md b/docs/docs/settings/user.md
index 69d1026d98..a49ee19f8f 100644
--- a/docs/docs/settings/user.md
+++ b/docs/docs/settings/user.md
@@ -23,7 +23,6 @@ The *Display Settings* screen shows general display configuration options:
{{ usersetting("DATE_DISPLAY_FORMAT") }}
{{ usersetting("FORMS_CLOSE_USING_ESCAPE") }}
{{ usersetting("PART_SHOW_QUANTITY_IN_FORMS") }}
-{{ usersetting("DISPLAY_SCHEDULE_TAB") }}
{{ usersetting("DISPLAY_STOCKTAKE_TAB") }}
{{ usersetting("ENABLE_LAST_BREADCRUMB") }}
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 6472fb8906..63096e1dcf 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -152,7 +152,6 @@ nav:
- Templates: part/template.md
- Tests: part/test.md
- Pricing: part/pricing.md
- - Scheduling: part/scheduling.md
- Stocktake: part/stocktake.md
- Notifications: part/notification.md
- Stock:
diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py
index 3ea7e8c2d1..bb726466ea 100644
--- a/src/backend/InvenTree/InvenTree/api_version.py
+++ b/src/backend/InvenTree/InvenTree/api_version.py
@@ -1,11 +1,14 @@
"""InvenTree API version information."""
# InvenTree API version
-INVENTREE_API_VERSION = 354
+INVENTREE_API_VERSION = 355
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
+v355 -> 2025-06-20 : https://github.com/inventree/InvenTree/pull/9811
+ - Removes legacy "PartScheduling" API endpoints
+
v354 -> 2025-06-09 : https://github.com/inventree/InvenTree/pull/9532
- Adds "merge" field to the ReportTemplate model
diff --git a/src/backend/InvenTree/common/setting/user.py b/src/backend/InvenTree/common/setting/user.py
index 1c1f8db3c3..644f75b75e 100644
--- a/src/backend/InvenTree/common/setting/user.py
+++ b/src/backend/InvenTree/common/setting/user.py
@@ -211,12 +211,6 @@ USER_SETTINGS: dict[str, InvenTreeSettingsKeyType] = {
('MMM DD YYYY', 'Feb 22 2022'),
],
},
- 'DISPLAY_SCHEDULE_TAB': {
- 'name': _('Part Scheduling'),
- 'description': _('Display part scheduling information'),
- 'default': True,
- 'validator': bool,
- },
'DISPLAY_STOCKTAKE_TAB': {
'name': _('Part Stocktake'),
'description': _(
diff --git a/src/backend/InvenTree/part/api.py b/src/backend/InvenTree/part/api.py
index a64bde2cdd..53a4b5c167 100644
--- a/src/backend/InvenTree/part/api.py
+++ b/src/backend/InvenTree/part/api.py
@@ -1,8 +1,6 @@
"""Provides a JSON API for the Part app."""
-import functools
import re
-from datetime import datetime
from django.db.models import Count, F, Q
from django.urls import include, path
@@ -17,10 +15,7 @@ from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
import InvenTree.permissions
-import order.models
import part.filters
-from build.models import Build, BuildItem
-from build.status_codes import BuildStatusGroups
from data_exporter.mixins import DataExportViewMixin
from InvenTree.api import BulkUpdateMixin, ListCreateDestroyAPIView, MetadataView
from InvenTree.filters import (
@@ -43,7 +38,6 @@ from InvenTree.mixins import (
UpdateAPI,
)
from InvenTree.serializers import EmptySerializer
-from order.status_codes import PurchaseOrderStatusGroups, SalesOrderStatusGroups
from stock.models import StockLocation
from . import serializers as part_serializers
@@ -547,205 +541,6 @@ class PartThumbsUpdate(RetrieveUpdateAPI):
filter_backends = [DjangoFilterBackend]
-class PartScheduling(RetrieveAPI):
- """API endpoint for delivering "scheduling" information about a given part via the API.
-
- Returns a chronologically ordered list about future "scheduled" events,
- concerning stock levels for the part:
-
- - Purchase Orders (incoming stock)
- - Sales Orders (outgoing stock)
- - Build Orders (incoming completed stock)
- - Build Orders (outgoing allocated stock)
- """
-
- queryset = Part.objects.all()
- serializer_class = part_serializers.PartSchedulingSerializer
-
- def retrieve(self, request, *args, **kwargs):
- """Return scheduling information for the referenced Part instance."""
- part = self.get_object()
-
- schedule = []
-
- def add_schedule_entry(
- date: datetime,
- quantity: float,
- title: str,
- instance,
- speculative_quantity: float = 0,
- ):
- """Add a new entry to the schedule list.
-
- Args:
- date (datetime): The date of the scheduled event.
- quantity (float): The quantity of stock to be added or removed.
- title (str): The title of the scheduled event.
- instance (Model): The associated model instance (e.g., SalesOrder object).
- speculative_quantity (float, optional): A speculative quantity to be added or removed. Defaults to 0.
- """
- schedule.append({
- 'date': date,
- 'quantity': quantity,
- 'speculative_quantity': speculative_quantity,
- 'title': title,
- 'label': str(instance.reference),
- 'model': instance.__class__.__name__.lower(),
- 'model_id': instance.pk,
- })
-
- # Add purchase order (incoming stock) information
- po_lines = order.models.PurchaseOrderLineItem.objects.filter(
- part__part=part, order__status__in=PurchaseOrderStatusGroups.OPEN
- )
-
- for line in po_lines:
- target_date = line.target_date or line.order.target_date
-
- line_quantity = max(line.quantity - line.received, 0)
-
- # Multiply by the pack quantity of the SupplierPart
- quantity = line.part.base_quantity(line_quantity)
-
- add_schedule_entry(
- target_date, quantity, _('Incoming Purchase Order'), line.order
- )
-
- # Add sales order (outgoing stock) information
- so_lines = order.models.SalesOrderLineItem.objects.filter(
- part=part, order__status__in=SalesOrderStatusGroups.OPEN
- )
-
- for line in so_lines:
- target_date = line.target_date or line.order.target_date
-
- quantity = max(line.quantity - line.shipped, 0)
-
- add_schedule_entry(
- target_date, -quantity, _('Outgoing Sales Order'), line.order
- )
-
- # Add build orders (incoming stock) information
- build_orders = Build.objects.filter(
- part=part, status__in=BuildStatusGroups.ACTIVE_CODES
- )
-
- for build in build_orders:
- quantity = max(build.quantity - build.completed, 0)
-
- add_schedule_entry(
- build.target_date, quantity, _('Stock produced by Build Order'), build
- )
-
- """
- Add build order allocation (outgoing stock) information.
-
- Here we need some careful consideration:
-
- - 'Tracked' stock items are removed from stock when the individual Build Output is completed
- - 'Untracked' stock items are removed from stock when the Build Order is completed
-
- The 'simplest' approach here is to look at existing BuildItem allocations which reference this part,
- and "schedule" them for removal at the time of build order completion.
-
- This assumes that the user is responsible for correctly allocating parts.
-
- However, it has the added benefit of side-stepping the various BOM substitution options,
- and just looking at what stock items the user has actually allocated against the Build.
- """
-
- # Grab a list of BomItem objects that this part might be used in
- bom_items = BomItem.objects.filter(part.get_used_in_bom_item_filter())
-
- # Track all outstanding build orders
- seen_builds = set()
-
- for bom_item in bom_items:
- # Find a list of active builds for this BomItem
-
- if bom_item.inherited:
- # An "inherited" BOM item filters down to variant parts also
- children = bom_item.part.get_descendants(include_self=True)
- builds = Build.objects.filter(
- status__in=BuildStatusGroups.ACTIVE_CODES, part__in=children
- )
- else:
- builds = Build.objects.filter(
- status__in=BuildStatusGroups.ACTIVE_CODES, part=bom_item.part
- )
-
- for build in builds:
- # Ensure we don't double-count any builds
- if build in seen_builds:
- continue
-
- seen_builds.add(build)
-
- if bom_item.sub_part.trackable:
- # Trackable parts are allocated against the outputs
- required_quantity = build.remaining * bom_item.quantity
- else:
- # Non-trackable parts are allocated against the build itself
- required_quantity = build.quantity * bom_item.quantity
-
- # Grab all allocations against the specified BomItem
- allocations = BuildItem.objects.filter(
- build_line__bom_item=bom_item, build_line__build=build
- )
-
- # Total allocated for *this* part
- part_allocated_quantity = 0
-
- # Total allocated for *any* part
- total_allocated_quantity = 0
-
- for allocation in allocations:
- total_allocated_quantity += allocation.quantity
-
- if allocation.stock_item.part == part:
- part_allocated_quantity += allocation.quantity
-
- speculative_quantity = 0
-
- # Consider the case where the build order is *not* fully allocated
- if required_quantity > total_allocated_quantity:
- speculative_quantity = -1 * (
- required_quantity - total_allocated_quantity
- )
-
- add_schedule_entry(
- build.target_date,
- -part_allocated_quantity,
- _('Stock required for Build Order'),
- build,
- speculative_quantity=speculative_quantity,
- )
-
- def compare(entry_1, entry_2):
- """Comparison function for sorting entries by date.
-
- Account for the fact that either date might be None
- """
- date_1 = entry_1['date']
- date_2 = entry_2['date']
-
- if date_1 is None:
- return -1
- elif date_2 is None:
- return 1
-
- return -1 if date_1 < date_2 else 1
-
- # Sort by incrementing date values
- schedules = sorted(schedule, key=functools.cmp_to_key(compare))
-
- serializers = part_serializers.PartSchedulingSerializer(
- schedules, many=True, context={'request': request}
- )
-
- return Response(serializers.data)
-
-
class PartRequirements(RetrieveAPI):
"""API endpoint detailing 'requirements' information for a particular part.
@@ -2210,8 +2005,6 @@ part_api_urls = [
PartSerialNumberDetail.as_view(),
name='api-part-serial-number-detail',
),
- # Endpoint for future scheduling information
- path('scheduling/', PartScheduling.as_view(), name='api-part-scheduling'),
path(
'requirements/',
PartRequirements.as_view(),
diff --git a/src/backend/InvenTree/part/serializers.py b/src/backend/InvenTree/part/serializers.py
index 3cb7051c8b..465d3cfd92 100644
--- a/src/backend/InvenTree/part/serializers.py
+++ b/src/backend/InvenTree/part/serializers.py
@@ -253,39 +253,6 @@ class PartInternalPriceSerializer(InvenTree.serializers.InvenTreeModelSerializer
)
-class PartSchedulingSerializer(serializers.Serializer):
- """Serializer class for a PartScheduling entry."""
-
- class Meta:
- """Metaclass options for this serializer."""
-
- fields = [
- 'date',
- 'quantity',
- 'speculative_quantity',
- 'title',
- 'label',
- 'model',
- 'model_id',
- ]
-
- date = serializers.DateField(label=_('Date'), required=True, allow_null=True)
-
- quantity = serializers.FloatField(label=_('Quantity'), required=True)
-
- speculative_quantity = serializers.FloatField(
- label=_('Speculative Quantity'), required=False
- )
-
- title = serializers.CharField(label=_('Title'), required=True)
-
- label = serializers.CharField(label=_('Label'), required=True)
-
- model = serializers.CharField(label=_('Model'), required=True)
-
- model_id = serializers.IntegerField(label=_('Model ID'), required=True)
-
-
class PartThumbSerializer(serializers.Serializer):
"""Serializer for the 'image' field of the Part model.
diff --git a/src/backend/InvenTree/part/test_api.py b/src/backend/InvenTree/part/test_api.py
index 790bfd912b..4c5b91f594 100644
--- a/src/backend/InvenTree/part/test_api.py
+++ b/src/backend/InvenTree/part/test_api.py
@@ -3063,22 +3063,6 @@ class PartMetadataAPITest(InvenTreeAPITestCase):
self.metatester(apikey, model)
-class PartSchedulingTest(PartAPITestBase):
- """Unit tests for the 'part scheduling' API endpoint."""
-
- def test_get_schedule(self):
- """Test that the scheduling endpoint returns OK."""
- part_ids = [1, 3, 100, 101]
-
- for pk in part_ids:
- url = reverse('api-part-scheduling', kwargs={'pk': pk})
- data = self.get(url, expected_code=200).data
-
- for entry in data:
- for k in ['date', 'quantity', 'label']:
- self.assertIn(k, entry)
-
-
class PartTestTemplateTest(PartAPITestBase):
"""API unit tests for the PartTestTemplate model."""
diff --git a/src/frontend/src/pages/Index/Settings/UserSettings.tsx b/src/frontend/src/pages/Index/Settings/UserSettings.tsx
index 5975fc461e..cda57dcbfb 100644
--- a/src/frontend/src/pages/Index/Settings/UserSettings.tsx
+++ b/src/frontend/src/pages/Index/Settings/UserSettings.tsx
@@ -54,7 +54,6 @@ export default function UserSettings() {
'DATE_DISPLAY_FORMAT',
'FORMS_CLOSE_USING_ESCAPE',
'PART_SHOW_QUANTITY_IN_FORMS',
- 'DISPLAY_SCHEDULE_TAB',
'DISPLAY_STOCKTAKE_TAB',
'ENABLE_LAST_BREADCRUMB'
]}
diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx
index 8e4671f548..85922fd400 100644
--- a/src/frontend/src/pages/part/PartDetail.tsx
+++ b/src/frontend/src/pages/part/PartDetail.tsx
@@ -11,7 +11,6 @@ import {
import {
IconBookmarks,
IconBuilding,
- IconCalendarStats,
IconClipboardList,
IconCurrencyDollar,
IconInfoCircle,
@@ -102,7 +101,6 @@ import { SalesOrderTable } from '../../tables/sales/SalesOrderTable';
import { StockItemTable } from '../../tables/stock/StockItemTable';
import PartAllocationPanel from './PartAllocationPanel';
import PartPricingPanel from './PartPricingPanel';
-import PartSchedulingDetail from './PartSchedulingDetail';
import PartStocktakeDetail from './PartStocktakeDetail';
import PartSupplierDetail from './PartSupplierDetail';
@@ -652,13 +650,6 @@ export default function PartDetail() {
!globalSettings.isSet('STOCKTAKE_ENABLE') ||
!userSettings.isSet('DISPLAY_STOCKTAKE_TAB')
},
- {
- name: 'scheduling',
- label: t`Scheduling`,
- icon: ,
- content: part ? : ,
- hidden: !userSettings.isSet('DISPLAY_SCHEDULE_TAB')
- },
{
name: 'test_templates',
label: t`Test Templates`,
diff --git a/src/frontend/src/pages/part/PartSchedulingDetail.tsx b/src/frontend/src/pages/part/PartSchedulingDetail.tsx
deleted file mode 100644
index cbcb04d6e8..0000000000
--- a/src/frontend/src/pages/part/PartSchedulingDetail.tsx
+++ /dev/null
@@ -1,315 +0,0 @@
-import { t } from '@lingui/core/macro';
-import { type ChartTooltipProps, LineChart } from '@mantine/charts';
-import {
- Alert,
- Center,
- Divider,
- Loader,
- Paper,
- SimpleGrid,
- Text
-} from '@mantine/core';
-import { type ReactNode, useMemo } from 'react';
-import { useNavigate } from 'react-router-dom';
-
-import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
-import { apiUrl } from '@lib/functions/Api';
-import { getDetailUrl } from '@lib/functions/Navigation';
-import { navigateToLink } from '@lib/functions/Navigation';
-import dayjs from 'dayjs';
-import { formatDate } from '../../defaults/formatters';
-import { useTable } from '../../hooks/UseTable';
-import type { TableColumn } from '../../tables/Column';
-import { DateColumn, DescriptionColumn } from '../../tables/ColumnRenderers';
-import { InvenTreeTable } from '../../tables/InvenTreeTable';
-import { TableHoverCard } from '../../tables/TableHoverCard';
-
-/*
- * Render a tooltip for the chart, with correct date information
- */
-function ChartTooltip({ label, payload }: Readonly) {
- if (!payload) {
- return null;
- }
-
- if (label && typeof label == 'number') {
- label = formatDate(dayjs().format('YYYY-MM-DD'));
- }
-
- const scheduled = payload.find((item) => item.name == 'scheduled');
- const minimum = payload.find((item) => item.name == 'minimum');
- const maximum = payload.find((item) => item.name == 'maximum');
-
- return (
-
- {label}
-
-
- {t`Maximum`} : {maximum?.value}
-
-
- {t`Scheduled`} : {scheduled?.value}
-
-
- {t`Minimum`} : {minimum?.value}
-
-
- );
-}
-
-export default function PartSchedulingDetail({
- part
-}: Readonly<{ part: any }>) {
- const table = useTable('part-scheduling');
- const navigate = useNavigate();
-
- const tableColumns: TableColumn[] = useMemo(() => {
- return [
- {
- accessor: 'label',
- switchable: false,
- title: t`Order`
- },
- DescriptionColumn({
- accessor: 'title',
- switchable: false
- }),
- DateColumn({
- sortable: false,
- switchable: false
- }),
- {
- accessor: 'quantity',
- title: t`Quantity`,
- switchable: false,
- render: (record: any) => {
- let q = record.quantity;
- const extra: ReactNode[] = [];
-
- if (record.speculative_quantity != 0) {
- q = record.speculative_quantity;
- extra.push(
- {t`Quantity is speculative`}
- );
- }
-
- if (!record.date) {
- extra.push(
- {t`No date available for provided quantity`}
- );
- } else if (new Date(record.date) < new Date()) {
- extra.push(
- {t`Date is in the past`}
- );
- }
-
- return (
- {q}}
- title={t`Scheduled Quantity`}
- extra={extra}
- />
- );
- }
- }
- ];
- }, []);
-
- const chartData = useMemo(() => {
- /* Rebuild chart data whenever the table data changes.
- * Note: We assume that the data is provided in increasing date order,
- * with "null" date entries placed first.
- */
-
- const today = new Date();
- today.setHours(0, 0, 0, 0);
-
- // Date bounds
- let min_date: Date = new Date();
- let max_date: Date = new Date();
-
- // Track stock scheduling throughout time
- let stock = part.in_stock ?? 0;
- let stock_min = stock;
- let stock_max = stock;
-
- // First, iterate through each entry and find any entries without an associated date, or in the past
- table.records.forEach((record) => {
- const q = record.quantity + record.speculative_quantity;
-
- if (record.date == null || new Date(record.date) < today) {
- if (q < 0) {
- stock_min += q;
- } else {
- stock_max += q;
- }
- }
- });
-
- // Construct initial chart entry (for today)
- const entries: any[] = [
- {
- date: today.valueOf(),
- delta: 0,
- scheduled: stock,
- minimum: stock_min,
- maximum: stock_max,
- low_stock: part.minimum_stock
- }
- ];
-
- table.records.forEach((record) => {
- const q = record.quantity + record.speculative_quantity;
-
- if (!record.date) {
- return;
- }
-
- const date = new Date(record.date);
-
- // In the past? Ignore this entry
- if (date < today) {
- return;
- }
-
- // Update date limits
-
- if (date < min_date) {
- min_date = date;
- }
-
- if (date > max_date) {
- max_date = date;
- }
-
- // Update stock levels
- stock += record.quantity;
-
- stock_min += record.quantity;
- stock_max += record.quantity;
-
- // Speculative quantities expand the expected stock range
- if (record.speculative_quantity < 0) {
- stock_min += record.speculative_quantity;
- } else if (record.speculative_quantity > 0) {
- stock_max += record.speculative_quantity;
- }
-
- entries.push({
- ...record,
- date: new Date(record.date).valueOf(),
- scheduled: stock,
- minimum: stock_min,
- maximum: stock_max,
- low_stock: part.minimum_stock
- });
- });
-
- return entries;
- }, [part, table.records]);
-
- // Calculate the date limits of the chart
- const chartLimits: number[] = useMemo(() => {
- let min_date = new Date();
- let max_date = new Date();
-
- if (chartData.length > 0) {
- min_date = new Date(chartData[0].date);
- max_date = new Date(chartData[chartData.length - 1].date);
- }
-
- // Expand limits by one day on either side
- min_date.setDate(min_date.getDate() - 1);
- max_date.setDate(max_date.getDate() + 1);
-
- return [min_date.valueOf(), max_date.valueOf()];
- }, [chartData]);
-
- const hasSchedulingInfo: boolean = useMemo(
- () => table.recordCount > 0,
- [table.recordCount]
- );
-
- return (
- <>
- {!table.isLoading && !hasSchedulingInfo && (
-
- {t`There is no scheduling information available for the selected part`}
-
- )}
-
- {
- const url = getDetailUrl(record.model, record.model_id);
-
- if (url) {
- navigateToLink(url, navigate, event);
- }
- }
- }}
- />
- {table.isLoading ? (
-
-
-
- ) : (
- (
-
- )
- }}
- yAxisLabel={t`Expected Quantity`}
- xAxisLabel={t`Date`}
- xAxisProps={{
- domain: chartLimits,
- scale: 'time',
- type: 'number',
- tickFormatter: (value: number) => {
- return formatDate(dayjs().format('YYYY-MM-DD'));
- }
- }}
- series={[
- {
- name: 'scheduled',
- label: t`Scheduled`,
- color: 'blue.6'
- },
- {
- name: 'minimum',
- label: t`Minimum`,
- color: 'yellow.6'
- },
- {
- name: 'maximum',
- label: t`Maximum`,
- color: 'teal.6'
- },
- {
- name: 'low_stock',
- label: t`Low Stock`,
- color: 'red.6'
- }
- ]}
- />
- )}
-
- >
- );
-}
diff --git a/src/frontend/tests/pages/pui_part.spec.ts b/src/frontend/tests/pages/pui_part.spec.ts
index f0232fb342..fe6aba0197 100644
--- a/src/frontend/tests/pages/pui_part.spec.ts
+++ b/src/frontend/tests/pages/pui_part.spec.ts
@@ -32,7 +32,6 @@ test('Parts - Tabs', async ({ browser }) => {
await loadTab(page, 'Pricing');
await loadTab(page, 'Suppliers');
await loadTab(page, 'Purchase Orders');
- await loadTab(page, 'Scheduling');
await loadTab(page, 'Stock History');
await loadTab(page, 'Attachments');
await loadTab(page, 'Notes');