diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py
index 8637e9232d..2b522c0987 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 = 508
+INVENTREE_API_VERSION = 509
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
+v509 -> 2026-06-17 : https://github.com/inventree/InvenTree/pull/12184
+ - Adds "completed_row_count_history" and "row_count_history" fields to the DataImportSession model, which store the historic count of completed rows and total rows for a data import session.
+
v508 -> 2026-06-17 : https://github.com/inventree/InvenTree/pull/11982
- An order's "status_custom_key" can be updated via PATCH API endpoint
diff --git a/src/backend/InvenTree/importer/migrations/0007_dataimportsession_completed_row_count_history_and_more.py b/src/backend/InvenTree/importer/migrations/0007_dataimportsession_completed_row_count_history_and_more.py
new file mode 100644
index 0000000000..c70be9e5b3
--- /dev/null
+++ b/src/backend/InvenTree/importer/migrations/0007_dataimportsession_completed_row_count_history_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.2.15 on 2026-06-17 05:57
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('importer', '0006_dataimportcolumnmap_lookup_field'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='dataimportsession',
+ name='completed_row_count_history',
+ field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Completed Row Count History'),
+ ),
+ migrations.AddField(
+ model_name='dataimportsession',
+ name='row_count_history',
+ field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Row Count History'),
+ ),
+ ]
diff --git a/src/backend/InvenTree/importer/models.py b/src/backend/InvenTree/importer/models.py
index 042e8df43b..00f361be02 100644
--- a/src/backend/InvenTree/importer/models.py
+++ b/src/backend/InvenTree/importer/models.py
@@ -385,7 +385,13 @@ class DataImportSession(models.Model):
if self.status != DataImportStatusCode.COMPLETE.value:
self.status = DataImportStatusCode.COMPLETE.value
+
+ # persist historic count values for reporting purposes
+ self.completed_row_count_history = self.completed_row_count
+ self.row_count_history = self.row_count
+
self.save()
+
# Clear staging data now that all rows have been imported
self.rows.all().delete()
self.column_mappings.all().delete()
@@ -402,6 +408,14 @@ class DataImportSession(models.Model):
"""Return the number of completed rows for this session."""
return self.rows.filter(complete=True).count()
+ # Historic values for reporting purposes
+ completed_row_count_history = models.PositiveIntegerField(
+ blank=True, null=True, verbose_name=_('Completed Row Count History')
+ )
+ row_count_history = models.PositiveIntegerField(
+ blank=True, null=True, verbose_name=_('Row Count History')
+ )
+
def available_fields(self):
"""Returns information on the available fields.
diff --git a/src/backend/InvenTree/importer/serializers.py b/src/backend/InvenTree/importer/serializers.py
index 2da60e0e32..46bc8562fc 100644
--- a/src/backend/InvenTree/importer/serializers.py
+++ b/src/backend/InvenTree/importer/serializers.py
@@ -62,6 +62,8 @@ class DataImportSessionSerializer(InvenTreeModelSerializer):
'field_filters',
'row_count',
'completed_row_count',
+ 'completed_row_count_history',
+ 'row_count_history',
]
read_only_fields = ['pk', 'user', 'status', 'columns']
@@ -220,6 +222,8 @@ class DataImportAcceptRowSerializer(serializers.Serializer):
row.validate(commit=True, request=request)
if session := self.context.get('session', None):
+ # ensure current state is available
+ session.refresh_from_db()
session.check_complete()
return rows
diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx
index f249332eb9..6b8d8e3459 100644
--- a/src/frontend/src/pages/build/BuildDetail.tsx
+++ b/src/frontend/src/pages/build/BuildDetail.tsx
@@ -783,7 +783,7 @@ export default function BuildDetail() {
? []
: [
,
diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
index 99099bad1a..43f5e84301 100644
--- a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
+++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
@@ -543,7 +543,7 @@ export default function PurchaseOrderDetail() {
? []
: [
diff --git a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx
index 67c1eba98f..b094826d7c 100644
--- a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx
+++ b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx
@@ -390,7 +390,7 @@ export default function ReturnOrderDetail() {
? []
: [
diff --git a/src/frontend/src/pages/sales/SalesOrderDetail.tsx b/src/frontend/src/pages/sales/SalesOrderDetail.tsx
index ce5c4ac094..959ea726d1 100644
--- a/src/frontend/src/pages/sales/SalesOrderDetail.tsx
+++ b/src/frontend/src/pages/sales/SalesOrderDetail.tsx
@@ -605,7 +605,7 @@ export default function SalesOrderDetail() {
? []
: [
diff --git a/src/frontend/src/tables/settings/ImportSessionTable.tsx b/src/frontend/src/tables/settings/ImportSessionTable.tsx
index 39df628147..6dfd8101a4 100644
--- a/src/frontend/src/tables/settings/ImportSessionTable.tsx
+++ b/src/frontend/src/tables/settings/ImportSessionTable.tsx
@@ -18,6 +18,7 @@ import {
useCreateApiFormModal,
useDeleteApiFormModal
} from '../../hooks/UseForm';
+import useStatusCodes from '../../hooks/UseStatusCodes';
import { useImporterState } from '../../states/ImporterState';
import { DateColumn, StatusColumn } from '../ColumnRenderers';
import { StatusFilterOptions, UserFilter } from '../Filter';
@@ -26,6 +27,9 @@ import { InvenTreeTable } from '../InvenTreeTable';
export default function ImportSessionTable() {
const table = useTable('importsession');
const openImporter = useImporterState((state) => state.openImporter);
+ const importSessionStatus = useStatusCodes({
+ modelType: ModelType.importsession
+ });
const [selectedSession, setSelectedSession] = useState(
undefined
@@ -82,13 +86,20 @@ export default function ImportSessionTable() {
sortable: false,
accessor: 'row_count',
title: t`Imported Rows`,
- render: (record: any) => (
-
- )
+ render: (record: any) =>
+ record.status == importSessionStatus.COMPLETE ? (
+
+ ) : (
+
+ )
}
];
}, []);
diff --git a/src/frontend/tests/pui_importing.spec.ts b/src/frontend/tests/pui_importing.spec.ts
index 026f7117c5..b7d5f64061 100644
--- a/src/frontend/tests/pui_importing.spec.ts
+++ b/src/frontend/tests/pui_importing.spec.ts
@@ -60,7 +60,7 @@ test('Importing - Admin Center', async ({ browser }) => {
await page.getByRole('button', { name: 'Close' }).click();
// Confirmation of full import success
- // await page.getByRole('cell', { name: '3 / 3' }).first().waitFor();
+ await page.getByRole('cell', { name: '3 / 3' }).first().waitFor();
// Manually delete records
await page.getByRole('checkbox', { name: 'Select all records' }).check();