Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refactor] Barcode scanning #8658

Merged
merged 24 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
28be70f
Enhance SupplierPart barcode mixin
SchrodingersGat Dec 11, 2024
900389c
Add comment
SchrodingersGat Dec 12, 2024
44e4fa0
Add new fields to BarcodePOReceiveSerializer
SchrodingersGat Dec 12, 2024
358ac7c
Add "supplier" information to serializer
SchrodingersGat Dec 12, 2024
52f33b3
Add 'is_completed' method for PurchaseOrderLineItem
SchrodingersGat Dec 12, 2024
d2823e4
Refactor SupplierBarcodeMixin
SchrodingersGat Dec 12, 2024
f81d7ca
Tweak error message
SchrodingersGat Dec 12, 2024
ee18a68
Logic fix
SchrodingersGat Dec 12, 2024
675fb66
Fix response data
SchrodingersGat Dec 12, 2024
6f6488a
Improved error checking
SchrodingersGat Dec 12, 2024
60a2004
Bump API version
SchrodingersGat Dec 13, 2024
0640a20
Merge remote-tracking branch 'origin/master' into barcode-scanning
SchrodingersGat Dec 13, 2024
45f9cf9
Merge remote-tracking branch 'origin/master' into barcode-scanning
SchrodingersGat Dec 14, 2024
f4d3b9d
Bump API version
SchrodingersGat Dec 14, 2024
6b56f3c
Error handling
SchrodingersGat Dec 14, 2024
2296733
Merge branch 'barcode-scanning' of github.com:SchrodingersGat/InvenTr…
SchrodingersGat Dec 14, 2024
34cea60
Enhanced unit testing and exception handling
SchrodingersGat Dec 14, 2024
76cc371
More exception handling
SchrodingersGat Dec 14, 2024
6d5db6e
Fix circula imports
SchrodingersGat Dec 14, 2024
f210a33
Improve unit tests
SchrodingersGat Dec 14, 2024
21dd92f
Adjust filtering
SchrodingersGat Dec 15, 2024
9e0ed91
Merge branch 'master' into barcode-scanning
SchrodingersGat Dec 16, 2024
a52ce8a
Remove outdated tests
SchrodingersGat Dec 16, 2024
a82dc4a
Remove success code for matching item
SchrodingersGat Dec 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = 292
INVENTREE_API_VERSION = 293

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


INVENTREE_API_TEXT = """

v293 - 2024-12-14 : https://github.com/inventree/InvenTree/pull/8658
- Adds new fields to the supplier barcode API endpoints

v292 - 2024-12-03 : https://github.com/inventree/InvenTree/pull/8625
- Add "on_order" and "in_stock" annotations to SupplierPart API
- Enhanced filtering for the SupplierPart API
Expand Down
7 changes: 4 additions & 3 deletions src/backend/InvenTree/InvenTree/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,10 @@
from django.utils.translation import gettext_lazy as _

import rest_framework.views as drfviews
from error_report.models import Error
from rest_framework import serializers
from rest_framework.exceptions import ValidationError as DRFValidationError
from rest_framework.response import Response

import InvenTree.sentry

logger = logging.getLogger('inventree')


Expand All @@ -34,6 +31,8 @@ def log_error(path, error_name=None, error_info=None, error_data=None):
error_info: The error information (optional, overrides 'info')
error_data: The error data (optional, overrides 'data')
"""
from error_report.models import Error

kind, info, data = sys.exc_info()

# Check if the error is on the ignore list
Expand Down Expand Up @@ -75,6 +74,8 @@ def exception_handler(exc, context):

If sentry error reporting is enabled, we will also provide the original exception to sentry.io
"""
import InvenTree.sentry

response = None

# Pass exception to sentry.io handler
Expand Down
10 changes: 9 additions & 1 deletion src/backend/InvenTree/InvenTree/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,18 @@ def delete(self):

Note: Each plugin may raise a ValidationError to prevent deletion.
"""
from InvenTree.exceptions import log_error
from plugin.registry import registry

for plugin in registry.with_mixin('validation'):
plugin.validate_model_deletion(self)
try:
plugin.validate_model_deletion(self)
except ValidationError as e:
# Plugin might raise a ValidationError to prevent deletion
raise e
except Exception:
log_error('plugin.validate_model_deletion')
continue

super().delete()

Expand Down
2 changes: 2 additions & 0 deletions src/backend/InvenTree/common/icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,14 @@ def get_icon_packs():
)
]

from InvenTree.exceptions import log_error
from plugin import registry

for plugin in registry.with_mixin('icon_pack', active=True):
try:
icon_packs.extend(plugin.icon_packs())
except Exception as e:
log_error('get_icon_packs')
logger.warning('Error loading icon pack from plugin %s: %s', plugin, e)

_icon_packs = {pack.prefix: pack for pack in icon_packs}
Expand Down
9 changes: 8 additions & 1 deletion src/backend/InvenTree/order/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,19 +558,21 @@ def add_line_item(
group: bool = True,
reference: str = '',
purchase_price=None,
destination=None,
):
"""Add a new line item to this purchase order.

This function will check that:
* The supplier part matches the supplier specified for this purchase order
* The quantity is greater than zero

Args:
Arguments:
supplier_part: The supplier_part to add
quantity : The number of items to add
group (bool, optional): If True, this new quantity will be added to an existing line item for the same supplier_part (if it exists). Defaults to True.
reference (str, optional): Reference to item. Defaults to ''.
purchase_price (optional): Price of item. Defaults to None.
destination (optional): Destination for item. Defaults to None.

Returns:
The newly created PurchaseOrderLineItem instance
Expand Down Expand Up @@ -619,6 +621,7 @@ def add_line_item(
quantity=quantity,
reference=reference,
purchase_price=purchase_price,
destination=destination,
)

line.save()
Expand Down Expand Up @@ -1608,6 +1611,10 @@ def remaining(self):
r = self.quantity - self.received
return max(r, 0)

def is_completed(self) -> bool:
"""Determine if this lien item has been fully received."""
return self.received >= self.quantity

def update_pricing(self):
"""Update pricing information based on the supplier part data."""
if self.part:
Expand Down
10 changes: 7 additions & 3 deletions src/backend/InvenTree/plugin/base/action/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response

from InvenTree.exceptions import log_error
from plugin import registry


Expand Down Expand Up @@ -33,9 +34,12 @@ def post(self, request, *args, **kwargs):

action_plugins = registry.with_mixin('action')
for plugin in action_plugins:
if plugin.action_name() == action:
plugin.perform_action(request.user, data=data)
return Response(plugin.get_response(request.user, data=data))
try:
if plugin.action_name() == action:
plugin.perform_action(request.user, data=data)
return Response(plugin.get_response(request.user, data=data))
except Exception:
log_error('action_plugin')

# If we got to here, no matching action was found
return Response({'error': _('No matching action found'), 'action': action})
33 changes: 28 additions & 5 deletions src/backend/InvenTree/plugin/base/barcodes/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,18 @@ def scan_barcode(self, barcode: str, request, **kwargs):
response = {}

for current_plugin in plugins:
result = current_plugin.scan(barcode)
try:
result = current_plugin.scan(barcode)
except Exception:
log_error('BarcodeView.scan_barcode')
continue

if result is None:
continue

if len(result) == 0:
continue

if 'error' in result:
logger.info(
'%s.scan(...) returned an error: %s',
Expand All @@ -166,6 +173,7 @@ def scan_barcode(self, barcode: str, request, **kwargs):
plugin = current_plugin
response = result
else:
# Return the first successful match
plugin = current_plugin
response = result
break
Expand Down Expand Up @@ -280,6 +288,8 @@ def handle_barcode(self, barcode: str, request, **kwargs):
result['plugin'] = inventree_barcode_plugin.name
result['barcode_data'] = barcode

result.pop('success', None)

raise ValidationError(result)

barcode_hash = hash_barcode(barcode)
Expand Down Expand Up @@ -497,8 +507,11 @@ def handle_barcode(self, barcode: str, request, **kwargs):
logger.debug("BarcodePOReceive: scanned barcode - '%s'", barcode)

# Extract optional fields from the dataset
supplier = kwargs.get('supplier')
purchase_order = kwargs.get('purchase_order')
location = kwargs.get('location')
line_item = kwargs.get('line_item')
auto_allocate = kwargs.get('auto_allocate', True)

# Extract location from PurchaseOrder, if available
if not location and purchase_order:
Expand Down Expand Up @@ -532,9 +545,19 @@ def handle_barcode(self, barcode: str, request, **kwargs):
plugin_response = None

for current_plugin in plugins:
result = current_plugin.scan_receive_item(
barcode, request.user, purchase_order=purchase_order, location=location
)
try:
result = current_plugin.scan_receive_item(
barcode,
request.user,
supplier=supplier,
purchase_order=purchase_order,
location=location,
line_item=line_item,
auto_allocate=auto_allocate,
)
except Exception:
log_error('BarcodePOReceive.handle_barcode')
continue

if result is None:
continue
Expand All @@ -560,7 +583,7 @@ def handle_barcode(self, barcode: str, request, **kwargs):

# A plugin has not been found!
if plugin is None:
response['error'] = _('No match for supplier barcode')
response['error'] = _('No plugin match for supplier barcode')

self.log_scan(request, response, 'success' in response)

Expand Down
Loading
Loading