2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-07-04 06:00:38 +00:00

fix(backend): import session metadata (#12184)

* add storage for historic import metadata for reporting and display purposes

fixes breakage fromhttps://github.com/inventree/InvenTree/pull/12169

* add api bump

* re-enable test

* fix migration

* ensure session is not overwritten

* fix statusrender without custom key
This commit is contained in:
Matthias Mair
2026-06-18 00:10:32 +02:00
committed by GitHub
parent c262efb25f
commit f602714dc9
11 changed files with 69 additions and 14 deletions
@@ -1,11 +1,14 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # 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.""" """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 = """
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 v508 -> 2026-06-17 : https://github.com/inventree/InvenTree/pull/11982
- An order's "status_custom_key" can be updated via PATCH API endpoint - An order's "status_custom_key" can be updated via PATCH API endpoint
@@ -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'),
),
]
+14
View File
@@ -385,7 +385,13 @@ class DataImportSession(models.Model):
if self.status != DataImportStatusCode.COMPLETE.value: if self.status != DataImportStatusCode.COMPLETE.value:
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() self.save()
# Clear staging data now that all rows have been imported # Clear staging data now that all rows have been imported
self.rows.all().delete() self.rows.all().delete()
self.column_mappings.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 the number of completed rows for this session."""
return self.rows.filter(complete=True).count() 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): def available_fields(self):
"""Returns information on the available fields. """Returns information on the available fields.
@@ -62,6 +62,8 @@ class DataImportSessionSerializer(InvenTreeModelSerializer):
'field_filters', 'field_filters',
'row_count', 'row_count',
'completed_row_count', 'completed_row_count',
'completed_row_count_history',
'row_count_history',
] ]
read_only_fields = ['pk', 'user', 'status', 'columns'] read_only_fields = ['pk', 'user', 'status', 'columns']
@@ -220,6 +222,8 @@ class DataImportAcceptRowSerializer(serializers.Serializer):
row.validate(commit=True, request=request) row.validate(commit=True, request=request)
if session := self.context.get('session', None): if session := self.context.get('session', None):
# ensure current state is available
session.refresh_from_db()
session.check_complete() session.check_complete()
return rows return rows
+1 -1
View File
@@ -783,7 +783,7 @@ export default function BuildDetail() {
? [] ? []
: [ : [
<StatusRenderer <StatusRenderer
status={build.status_custom_key} status={build.status_custom_key || build.status}
type={ModelType.build} type={ModelType.build}
options={{ size: 'lg' }} options={{ size: 'lg' }}
/>, />,
@@ -543,7 +543,7 @@ export default function PurchaseOrderDetail() {
? [] ? []
: [ : [
<StatusRenderer <StatusRenderer
status={order.status_custom_key} status={order.status_custom_key || order.status}
type={ModelType.purchaseorder} type={ModelType.purchaseorder}
options={{ size: 'lg' }} options={{ size: 'lg' }}
/> />
@@ -390,7 +390,7 @@ export default function ReturnOrderDetail() {
? [] ? []
: [ : [
<StatusRenderer <StatusRenderer
status={order.status_custom_key} status={order.status_custom_key || order.status}
type={ModelType.returnorder} type={ModelType.returnorder}
options={{ size: 'lg' }} options={{ size: 'lg' }}
/> />
@@ -605,7 +605,7 @@ export default function SalesOrderDetail() {
? [] ? []
: [ : [
<StatusRenderer <StatusRenderer
status={order.status_custom_key} status={order.status_custom_key || order.status}
type={ModelType.salesorder} type={ModelType.salesorder}
options={{ size: 'lg' }} options={{ size: 'lg' }}
key={order.pk} key={order.pk}
@@ -356,7 +356,7 @@ export default function TransferOrderDetail() {
? [] ? []
: [ : [
<StatusRenderer <StatusRenderer
status={order.status_custom_key} status={order.status_custom_key || order.status}
type={ModelType.transferorder} type={ModelType.transferorder}
options={{ size: 'lg' }} options={{ size: 'lg' }}
/> />
@@ -18,6 +18,7 @@ import {
useCreateApiFormModal, useCreateApiFormModal,
useDeleteApiFormModal useDeleteApiFormModal
} from '../../hooks/UseForm'; } from '../../hooks/UseForm';
import useStatusCodes from '../../hooks/UseStatusCodes';
import { useImporterState } from '../../states/ImporterState'; import { useImporterState } from '../../states/ImporterState';
import { DateColumn, StatusColumn } from '../ColumnRenderers'; import { DateColumn, StatusColumn } from '../ColumnRenderers';
import { StatusFilterOptions, UserFilter } from '../Filter'; import { StatusFilterOptions, UserFilter } from '../Filter';
@@ -26,6 +27,9 @@ import { InvenTreeTable } from '../InvenTreeTable';
export default function ImportSessionTable() { export default function ImportSessionTable() {
const table = useTable('importsession'); const table = useTable('importsession');
const openImporter = useImporterState((state) => state.openImporter); const openImporter = useImporterState((state) => state.openImporter);
const importSessionStatus = useStatusCodes({
modelType: ModelType.importsession
});
const [selectedSession, setSelectedSession] = useState<number | undefined>( const [selectedSession, setSelectedSession] = useState<number | undefined>(
undefined undefined
@@ -82,13 +86,20 @@ export default function ImportSessionTable() {
sortable: false, sortable: false,
accessor: 'row_count', accessor: 'row_count',
title: t`Imported Rows`, title: t`Imported Rows`,
render: (record: any) => ( render: (record: any) =>
<ProgressBar record.status == importSessionStatus.COMPLETE ? (
progressLabel={true} <ProgressBar
value={record.completed_row_count} progressLabel={true}
maximum={record.row_count} value={record.completed_row_count_history}
/> maximum={record.row_count_history}
) />
) : (
<ProgressBar
progressLabel={true}
value={record.completed_row_count}
maximum={record.row_count}
/>
)
} }
]; ];
}, []); }, []);
+1 -1
View File
@@ -60,7 +60,7 @@ test('Importing - Admin Center', async ({ browser }) => {
await page.getByRole('button', { name: 'Close' }).click(); await page.getByRole('button', { name: 'Close' }).click();
// Confirmation of full import success // 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 // Manually delete records
await page.getByRole('checkbox', { name: 'Select all records' }).check(); await page.getByRole('checkbox', { name: 'Select all records' }).check();