From 110c48220c51afd16f0475a782fff3371f2b561e Mon Sep 17 00:00:00 2001 From: jayasree723 Date: Sat, 1 Aug 2026 12:57:30 +0530 Subject: [PATCH 1/3] Fix test result ordering by test timestamps --- src/backend/InvenTree/stock/models.py | 7 +++ .../src/tables/build/BuildOutputTable.tsx | 43 +++++++++++++++-- .../tables/stock/StockItemTestResultTable.tsx | 47 +++++++++++++++++-- 3 files changed, 89 insertions(+), 8 deletions(-) diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 5db2235d211a..9c0c261c8dd4 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -3799,6 +3799,13 @@ class Meta: verbose_name = _('Stock Item Test Result') + ordering = ( + '-finished_datetime', + '-started_datetime', + '-date', + '-pk', + ) + def __str__(self): """Return string representation.""" return f'{self.test_name} - {self.result}' diff --git a/src/frontend/src/tables/build/BuildOutputTable.tsx b/src/frontend/src/tables/build/BuildOutputTable.tsx index c4829719034e..7e35ca0597de 100644 --- a/src/frontend/src/tables/build/BuildOutputTable.tsx +++ b/src/frontend/src/tables/build/BuildOutputTable.tsx @@ -79,6 +79,45 @@ type TestResultOverview = { result: boolean; }; +const compareTestResults = (a: any, b: any) => { + const finishedA = a.finished_datetime + ? new Date(a.finished_datetime).getTime() + : null; + const finishedB = b.finished_datetime + ? new Date(b.finished_datetime).getTime() + : null; + + if (finishedA !== finishedB) { + if (finishedA === null) return 1; + if (finishedB === null) return -1; + return finishedB - finishedA; + } + + const startedA = a.started_datetime + ? new Date(a.started_datetime).getTime() + : null; + const startedB = b.started_datetime + ? new Date(b.started_datetime).getTime() + : null; + + if (startedA !== startedB) { + if (startedA === null) return 1; + if (startedB === null) return -1; + return startedB - startedA; + } + + const dateA = a.date ? new Date(a.date).getTime() : null; + const dateB = b.date ? new Date(b.date).getTime() : null; + + if (dateA !== dateB) { + if (dateA === null) return 1; + if (dateB === null) return -1; + return dateB - dateA; + } + + return b.pk - a.pk; +}; + /** * Detail drawer view for allocating stock against a specific build output */ @@ -277,9 +316,7 @@ export default function BuildOutputTable({ // Find the "newest" result for this template in the returned data const result = record.tests ?.filter((test: any) => test.template == template.pk) - .sort((a: any, b: any) => { - return a.pk < b.pk ? 1 : -1; - }) + .sort(compareTestResults) .shift(); if (template?.required && result?.result) { diff --git a/src/frontend/src/tables/stock/StockItemTestResultTable.tsx b/src/frontend/src/tables/stock/StockItemTestResultTable.tsx index 4cbe2412c9fc..68836880e35e 100644 --- a/src/frontend/src/tables/stock/StockItemTestResultTable.tsx +++ b/src/frontend/src/tables/stock/StockItemTestResultTable.tsx @@ -85,6 +85,45 @@ export default function StockItemTestResultTable({ table.refreshTable(); }, [testTemplates]); + const compareTestResults = (a: any, b: any) => { + const finishedA = a.finished_datetime + ? new Date(a.finished_datetime).getTime() + : null; + const finishedB = b.finished_datetime + ? new Date(b.finished_datetime).getTime() + : null; + + if (finishedA !== finishedB) { + if (finishedA === null) return 1; + if (finishedB === null) return -1; + return finishedB - finishedA; + } + + const startedA = a.started_datetime + ? new Date(a.started_datetime).getTime() + : null; + const startedB = b.started_datetime + ? new Date(b.started_datetime).getTime() + : null; + + if (startedA !== startedB) { + if (startedA === null) return 1; + if (startedB === null) return -1; + return startedB - startedA; + } + + const dateA = a.date ? new Date(a.date).getTime() : null; + const dateB = b.date ? new Date(b.date).getTime() : null; + + if (dateA !== dateB) { + if (dateA === null) return 1; + if (dateB === null) return -1; + return dateB - dateA; + } + + return b.pk - a.pk; +}; + // Format the test results based on the returned data const formatRecords = useCallback( (records: any[]): any[] => { @@ -110,12 +149,10 @@ export default function StockItemTestResultTable({ }); // Iterate through the returned records - // Note that the results are sorted by oldest first, - // to ensure that the most recent result is displayed "on top" + // Sort test results using the same priority as the backend: + // finished_datetime -> started_datetime -> date -> pk records - .sort((a: any, b: any) => { - return a.pk > b.pk ? 1 : -1; - }) + .sort(compareTestResults) .forEach((record) => { // Find matching template const idx = results.findIndex( From dcfbae9551da2b9804fd45c696ff7ef9239aae2d Mon Sep 17 00:00:00 2001 From: jayasree723 Date: Sat, 1 Aug 2026 15:14:59 +0530 Subject: [PATCH 2/3] Refractor test result comparison helper --- src/frontend/src/functions/comparison.tsx | 39 ++++++++++++++++++ .../src/tables/build/BuildOutputTable.tsx | 40 +------------------ .../tables/stock/StockItemTestResultTable.tsx | 40 +------------------ 3 files changed, 41 insertions(+), 78 deletions(-) diff --git a/src/frontend/src/functions/comparison.tsx b/src/frontend/src/functions/comparison.tsx index a1f6293f42df..e1639f375923 100644 --- a/src/frontend/src/functions/comparison.tsx +++ b/src/frontend/src/functions/comparison.tsx @@ -53,3 +53,42 @@ export function isEquivalent(a: any, b: any): boolean { return false; } + +export function compareTestResults(a: any, b: any) { + const finishedA = a.finished_datetime + ? new Date(a.finished_datetime).getTime() + : null; + const finishedB = b.finished_datetime + ? new Date(b.finished_datetime).getTime() + : null; + + if (finishedA !== finishedB) { + if (finishedA === null) return 1; + if (finishedB === null) return -1; + return finishedB - finishedA; + } + + const startedA = a.started_datetime + ? new Date(a.started_datetime).getTime() + : null; + const startedB = b.started_datetime + ? new Date(b.started_datetime).getTime() + : null; + + if (startedA !== startedB) { + if (startedA === null) return 1; + if (startedB === null) return -1; + return startedB - startedA; + } + + const uploadTimeA = a.date ? new Date(a.date).getTime() : null; + const uploadTimeB = b.date ? new Date(b.date).getTime() : null; + + if (uploadTimeA !== uploadTimeB) { + if (uploadTimeA === null) return 1; + if (uploadTimeB === null) return -1; + return uploadTimeB - uploadTimeA; + } + + return b.pk - a.pk; +} diff --git a/src/frontend/src/tables/build/BuildOutputTable.tsx b/src/frontend/src/tables/build/BuildOutputTable.tsx index 7e35ca0597de..cfaee5019c9d 100644 --- a/src/frontend/src/tables/build/BuildOutputTable.tsx +++ b/src/frontend/src/tables/build/BuildOutputTable.tsx @@ -72,6 +72,7 @@ import { import useStatusCodes from '../../hooks/UseStatusCodes'; import { useStockAdjustActions } from '../../hooks/UseStockAdjustActions'; import { useUserState } from '../../states/UserState'; +import { compareTestResults } from '../../functions/comparison'; import BuildLineTable from './BuildLineTable'; type TestResultOverview = { @@ -79,45 +80,6 @@ type TestResultOverview = { result: boolean; }; -const compareTestResults = (a: any, b: any) => { - const finishedA = a.finished_datetime - ? new Date(a.finished_datetime).getTime() - : null; - const finishedB = b.finished_datetime - ? new Date(b.finished_datetime).getTime() - : null; - - if (finishedA !== finishedB) { - if (finishedA === null) return 1; - if (finishedB === null) return -1; - return finishedB - finishedA; - } - - const startedA = a.started_datetime - ? new Date(a.started_datetime).getTime() - : null; - const startedB = b.started_datetime - ? new Date(b.started_datetime).getTime() - : null; - - if (startedA !== startedB) { - if (startedA === null) return 1; - if (startedB === null) return -1; - return startedB - startedA; - } - - const dateA = a.date ? new Date(a.date).getTime() : null; - const dateB = b.date ? new Date(b.date).getTime() : null; - - if (dateA !== dateB) { - if (dateA === null) return 1; - if (dateB === null) return -1; - return dateB - dateA; - } - - return b.pk - a.pk; -}; - /** * Detail drawer view for allocating stock against a specific build output */ diff --git a/src/frontend/src/tables/stock/StockItemTestResultTable.tsx b/src/frontend/src/tables/stock/StockItemTestResultTable.tsx index 68836880e35e..9fb2d50f3540 100644 --- a/src/frontend/src/tables/stock/StockItemTestResultTable.tsx +++ b/src/frontend/src/tables/stock/StockItemTestResultTable.tsx @@ -44,6 +44,7 @@ import { } from '../../hooks/UseForm'; import { useGlobalSettingsState } from '../../states/SettingsStates'; import { useUserState } from '../../states/UserState'; +import { compareTestResults } from '../../functions/comparison'; export default function StockItemTestResultTable({ partId, @@ -85,45 +86,6 @@ export default function StockItemTestResultTable({ table.refreshTable(); }, [testTemplates]); - const compareTestResults = (a: any, b: any) => { - const finishedA = a.finished_datetime - ? new Date(a.finished_datetime).getTime() - : null; - const finishedB = b.finished_datetime - ? new Date(b.finished_datetime).getTime() - : null; - - if (finishedA !== finishedB) { - if (finishedA === null) return 1; - if (finishedB === null) return -1; - return finishedB - finishedA; - } - - const startedA = a.started_datetime - ? new Date(a.started_datetime).getTime() - : null; - const startedB = b.started_datetime - ? new Date(b.started_datetime).getTime() - : null; - - if (startedA !== startedB) { - if (startedA === null) return 1; - if (startedB === null) return -1; - return startedB - startedA; - } - - const dateA = a.date ? new Date(a.date).getTime() : null; - const dateB = b.date ? new Date(b.date).getTime() : null; - - if (dateA !== dateB) { - if (dateA === null) return 1; - if (dateB === null) return -1; - return dateB - dateA; - } - - return b.pk - a.pk; -}; - // Format the test results based on the returned data const formatRecords = useCallback( (records: any[]): any[] => { From cdfd19354ca78a553f0bf0b4265323875356d837 Mon Sep 17 00:00:00 2001 From: jayasree723 Date: Sat, 1 Aug 2026 20:48:15 +0530 Subject: [PATCH 3/3] Apply formatting fixes --- src/backend/InvenTree/stock/models.py | 7 +------ src/frontend/src/tables/build/BuildOutputTable.tsx | 2 +- src/frontend/src/tables/stock/StockItemTestResultTable.tsx | 6 ++---- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 9c0c261c8dd4..2db560105332 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -3799,12 +3799,7 @@ class Meta: verbose_name = _('Stock Item Test Result') - ordering = ( - '-finished_datetime', - '-started_datetime', - '-date', - '-pk', - ) + ordering = ('-finished_datetime', '-started_datetime', '-date', '-pk') def __str__(self): """Return string representation.""" diff --git a/src/frontend/src/tables/build/BuildOutputTable.tsx b/src/frontend/src/tables/build/BuildOutputTable.tsx index cfaee5019c9d..aeeabc30b085 100644 --- a/src/frontend/src/tables/build/BuildOutputTable.tsx +++ b/src/frontend/src/tables/build/BuildOutputTable.tsx @@ -63,6 +63,7 @@ import { useStockFields, useStockItemSerializeFields } from '../../forms/StockForms'; +import { compareTestResults } from '../../functions/comparison'; import { InvenTreeIcon } from '../../functions/icons'; import useBackgroundTask from '../../hooks/UseBackgroundTask'; import { @@ -72,7 +73,6 @@ import { import useStatusCodes from '../../hooks/UseStatusCodes'; import { useStockAdjustActions } from '../../hooks/UseStockAdjustActions'; import { useUserState } from '../../states/UserState'; -import { compareTestResults } from '../../functions/comparison'; import BuildLineTable from './BuildLineTable'; type TestResultOverview = { diff --git a/src/frontend/src/tables/stock/StockItemTestResultTable.tsx b/src/frontend/src/tables/stock/StockItemTestResultTable.tsx index 9fb2d50f3540..407dfb063965 100644 --- a/src/frontend/src/tables/stock/StockItemTestResultTable.tsx +++ b/src/frontend/src/tables/stock/StockItemTestResultTable.tsx @@ -36,6 +36,7 @@ import { InvenTreeTable } from '../../components/tables/InvenTreeTable'; import RowExpansionIcon from '../../components/tables/RowExpansionIcon'; import { useApi } from '../../contexts/ApiContext'; import { formatDate } from '../../defaults/formatters'; +import { compareTestResults } from '../../functions/comparison'; import { useTestResultFields } from '../../forms/StockForms'; import { useCreateApiFormModal, @@ -44,7 +45,6 @@ import { } from '../../hooks/UseForm'; import { useGlobalSettingsState } from '../../states/SettingsStates'; import { useUserState } from '../../states/UserState'; -import { compareTestResults } from '../../functions/comparison'; export default function StockItemTestResultTable({ partId, @@ -113,9 +113,7 @@ export default function StockItemTestResultTable({ // Iterate through the returned records // Sort test results using the same priority as the backend: // finished_datetime -> started_datetime -> date -> pk - records - .sort(compareTestResults) - .forEach((record) => { + records.sort(compareTestResults).forEach((record) => { // Find matching template const idx = results.findIndex( (r: any) => r.templateId == record.template