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();