Skip to content

Commit

Permalink
Barcode logging (#8150)
Browse files Browse the repository at this point in the history
* Add model for recording barcode scan results

* Add "admin" interface for new model

* Add API endpoints for barcode scan history

* Add global setting to control barcode result save

* Add frontend API endpoint

* Add PUI table in "admin center"

* Add API filter class

* Enable table filtering

* Update model definition

* Allow more characters for barcode log

* Log results to server

* Add setting to control how long results are stored

* Table updates

* Add background task to delete old barcode scans

* Add detail drawer for barcode scan

* Log messages for BarcodePOReceive

* Add warning message if barcode logging is not enabled

* Add "context" data to BarcodeScanResult

* Display context data (if available)

* Add context data when scanning

* Simplify / refactor BarcodeSOAllocate

* Refactor BarcodePOAllocate

* Limit the number of saved scans

* Improve error message display in PUI

* Simplify barcode logging

* Improve table

* Updates

* Settings page fix

* Fix panel tooltips

* Adjust table

* Add "result" field

* Refactor calls to "log_scan"

* Display result in PUI table

* Updates

* Fix typo

* Update unit test

* Improve exception handling

* Unit test updates

* Enhanced unit test

* Ensure all database key config values are upper case

* Refactor some playwright helpers

* Adds playwright test for barcode scan history table

* Requires some timeout

* Add docs
  • Loading branch information
SchrodingersGat authored Sep 23, 2024
1 parent f7e0edb commit 6002103
Show file tree
Hide file tree
Showing 28 changed files with 929 additions and 168 deletions.
8 changes: 8 additions & 0 deletions docs/docs/barcodes/barcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,11 @@ If no match is found for the scanned barcode, the following error message is dis
## App Integration

Barcode scanning is a key feature of the [companion mobile app](../app/barcode.md).

## Barcode History

If enabled, InvenTree can retain logs of the most recent barcode scans. This can be very useful for debugging or auditing purpopes.

Refer to the [barcode settings](../settings/global.md#barcodes) to enable barcode history logging.

The barcode history can be viewed via the admin panel in the web interface.
2 changes: 2 additions & 0 deletions docs/docs/settings/global.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ Configuration of barcode functionality:
{{ globalsetting("BARCODE_WEBCAM_SUPPORT") }}
{{ globalsetting("BARCODE_SHOW_TEXT") }}
{{ globalsetting("BARCODE_GENERATION_PLUGIN") }}
{{ globalsetting("BARCODE_STORE_RESULTS") }}
{{ globalsetting("BARCODE_RESULTS_MAX_NUM") }}

### Pricing and Currency

Expand Down
5 changes: 4 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""InvenTree API version information."""

# InvenTree API version
INVENTREE_API_VERSION = 256
INVENTREE_API_VERSION = 257

"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""


INVENTREE_API_TEXT = """
v257 - 2024-09-22 : https://github.com/inventree/InvenTree/pull/8150
- Adds API endpoint for reporting barcode scan history
v256 - 2024-09-19 : https://github.com/inventree/InvenTree/pull/7704
- Adjustments for "stocktake" (stock history) API endpoints
Expand Down
4 changes: 4 additions & 0 deletions src/backend/InvenTree/InvenTree/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,9 @@ class RetrieveUpdateDestroyAPI(CleanMixin, generics.RetrieveUpdateDestroyAPIView
"""View for retrieve, update and destroy API."""


class RetrieveDestroyAPI(generics.RetrieveDestroyAPIView):
"""View for retrieve and destroy API."""


class UpdateAPI(CleanMixin, generics.UpdateAPIView):
"""View for update API."""
3 changes: 3 additions & 0 deletions src/backend/InvenTree/InvenTree/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,9 @@
# Check that required database configuration options are specified
required_keys = ['ENGINE', 'NAME']

# Ensure all database keys are upper case
db_config = {key.upper(): value for key, value in db_config.items()}

for key in required_keys:
if key not in db_config: # pragma: no cover
error_msg = f'Missing required database configuration value {key}'
Expand Down
9 changes: 9 additions & 0 deletions src/backend/InvenTree/common/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ def formfield_for_dbfield(self, db_field, request, **kwargs):
search_fields = ('content_type', 'comment')


@admin.register(common.models.BarcodeScanResult)
class BarcodeScanResultAdmin(admin.ModelAdmin):
"""Admin interface for BarcodeScanResult objects."""

list_display = ('data', 'timestamp', 'user', 'endpoint', 'result')

list_filter = ('user', 'endpoint', 'result')


@admin.register(common.models.ProjectCode)
class ProjectCodeAdmin(ImportExportModelAdmin):
"""Admin settings for ProjectCode."""
Expand Down
34 changes: 34 additions & 0 deletions src/backend/InvenTree/common/migrations/0030_barcodescanresult.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 4.2.15 on 2024-09-21 06:05

import InvenTree.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('common', '0029_inventreecustomuserstatemodel'),
]

operations = [
migrations.CreateModel(
name='BarcodeScanResult',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('data', models.CharField(help_text='Barcode data', max_length=250, verbose_name='Data')),
('timestamp', models.DateTimeField(auto_now_add=True, help_text='Date and time of the barcode scan', verbose_name='Timestamp')),
('endpoint', models.CharField(blank=True, help_text='URL endpoint which processed the barcode', max_length=250, null=True, verbose_name='Path')),
('context', models.JSONField(blank=True, help_text='Context data for the barcode scan', max_length=1000, null=True, verbose_name='Context')),
('response', models.JSONField(blank=True, help_text='Response data from the barcode scan', max_length=1000, null=True, verbose_name='Response')),
('result', models.BooleanField(default=False, help_text='Was the barcode scan successful?', verbose_name='Result')),
('user', models.ForeignKey(blank=True, help_text='User who scanned the barcode', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Barcode Scan',
},
bases=(InvenTree.models.PluginValidationMixin, models.Model),
),
]
77 changes: 77 additions & 0 deletions src/backend/InvenTree/common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import build.validators
import common.currency
import common.validators
import InvenTree.exceptions
import InvenTree.fields
import InvenTree.helpers
import InvenTree.models
Expand Down Expand Up @@ -1398,6 +1399,18 @@ def save(self, *args, **kwargs):
'default': True,
'validator': bool,
},
'BARCODE_STORE_RESULTS': {
'name': _('Store Barcode Results'),
'description': _('Store barcode scan results in the database'),
'default': False,
'validator': bool,
},
'BARCODE_RESULTS_MAX_NUM': {
'name': _('Barcode Scans Maximum Count'),
'description': _('Maximum number of barcode scan results to store'),
'default': 100,
'validator': [int, MinValueValidator(1)],
},
'BARCODE_INPUT_DELAY': {
'name': _('Barcode Input Delay'),
'description': _('Barcode input processing delay time'),
Expand Down Expand Up @@ -3445,3 +3458,67 @@ def clean(self) -> None:
})

return super().clean()


class BarcodeScanResult(InvenTree.models.InvenTreeModel):
"""Model for storing barcode scans results."""

BARCODE_SCAN_MAX_LEN = 250

class Meta:
"""Model meta options."""

verbose_name = _('Barcode Scan')

data = models.CharField(
max_length=BARCODE_SCAN_MAX_LEN,
verbose_name=_('Data'),
help_text=_('Barcode data'),
blank=False,
null=False,
)

user = models.ForeignKey(
User,
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name=_('User'),
help_text=_('User who scanned the barcode'),
)

timestamp = models.DateTimeField(
auto_now_add=True,
verbose_name=_('Timestamp'),
help_text=_('Date and time of the barcode scan'),
)

endpoint = models.CharField(
max_length=250,
verbose_name=_('Path'),
help_text=_('URL endpoint which processed the barcode'),
blank=True,
null=True,
)

context = models.JSONField(
max_length=1000,
verbose_name=_('Context'),
help_text=_('Context data for the barcode scan'),
blank=True,
null=True,
)

response = models.JSONField(
max_length=1000,
verbose_name=_('Response'),
help_text=_('Response data from the barcode scan'),
blank=True,
null=True,
)

result = models.BooleanField(
verbose_name=_('Result'),
help_text=_('Was the barcode scan successful?'),
default=False,
)
Loading

0 comments on commit 6002103

Please sign in to comment.