fix: Robust barcode scan handling for ANSI MH10.8.2 and InvenTree 1.x

- Capture scanner control keystrokes (Ctrl+]/^/\/_ → GS/RS/FS/US) in the
  scan input so ANSI MH10.8.2 field separators survive the HTML input
  filter, eliminating the Q-quantity-vs-next-DI ambiguity.
- Fall back to a DI-aware lazy regex when separators are stripped
  (e.g. pasted scans), so Q digits stop at the next data identifier
  instead of greedily eating into 11Z/12Z/etc.
- Make pending-part dicts JSON-serializable by isoformat-ing the
  timestamp; without this the worker's import_complete socket emit
  threw and the entry was never removed from the queue, causing
  every re-scan to 400 with "already queued" forever.
- Make /api/part/import idempotent: a re-scan of an already-queued
  part updates qty/location and returns 200 with already_queued=true
  instead of 400.
- Surface search/queue errors in the client log instead of silently
  swallowing them, and stop treating a 500 from /api/part/search as
  "not found" (which was causing re-queue loops).
- Log full tracebacks for /api/part/search failures and split the
  get_part_info / get_part_parameters error paths so failures can be
  attributed.
- Migrate get_part_parameters to the InvenTree 1.x endpoint
  /api/parameter/?model_type=part.part&model_id=<id>. The old
  /api/part/parameter/?part=<id> returns 404 on this instance, and
  even on the new endpoint the ?part= filter is silently ignored
  (would have returned every parameter in the database).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-22 14:25:35 +07:00
parent 379ed232df
commit bbc4b1e763
5 changed files with 159 additions and 28 deletions
+35 -13
View File
@@ -290,20 +290,28 @@ def get_part_info(host: str, token: str, part_id: int) -> Dict[str, Any]:
def get_part_parameters(host: str, token: str, part_id: int) -> List[Dict[str, Any]]:
"""
Get part parameters from InvenTree API.
Args:
host: InvenTree server URL
token: API authentication token
part_id: Part ID to get parameters for
Returns:
List of parameter dictionaries
"""
url = f"{host}/api/part/parameter/"
# InvenTree 1.x consolidated parameters under /api/parameter/ keyed by
# generic (model_type, model_id). The old /api/part/parameter/?part=<id>
# endpoint no longer exists, and just `?part=<id>` on the new endpoint
# is silently ignored (returns every parameter in the database).
url = f"{host}/api/parameter/"
headers = {'Authorization': f'Token {token}'}
resp = requests.get(url, headers=headers, params={'part': part_id})
resp = requests.get(
url,
headers=headers,
params={'model_type': 'part.part', 'model_id': part_id},
)
resp.raise_for_status()
data = resp.json()
return data.get('results', data) if isinstance(data, dict) and 'results' in data else data
@@ -461,12 +469,26 @@ def parse_scan(raw: str) -> Tuple[Optional[str], Optional[int]]:
part = None
qty = None
# Extract part number - it's after [)>06P and before the next field marker
# Look for common field markers: 1P, 30P (these are explicit markers)
# If GS/RS separators survived, parse field-by-field — unambiguous.
fields = SEP_RE.split(raw)
if len(fields) > 1:
for f in fields:
if not f:
continue
if f.startswith('30P'):
part = clean_part_code(f[3:])
elif f.startswith('1P'):
if not part:
part = clean_part_code(f[2:])
elif f.startswith('Q') and f[1:].isdigit():
qty = int(f[1:])
if part or qty:
return part, qty
# Separators stripped — extract part after [)>06P up to next field marker.
if len(raw) > 6 and raw[5] == 'P':
after_p = raw[6:]
# Find the next explicit field marker
markers_to_find = ['1P', '30P']
end_idx = len(after_p)
@@ -477,11 +499,11 @@ def parse_scan(raw: str) -> Tuple[Optional[str], Optional[int]]:
part = clean_part_code(after_p[:end_idx])
# Extract quantity after Q
if 'Q' in raw:
q_matches = re.findall(r'Q(\d+)', raw)
if q_matches:
qty = int(q_matches[0])
# Lazy-match Q digits, stopping at the next data identifier
# (e.g. 11Z, 12Z, 20Z, 1T, 9D, 4L) or end-of-string.
q_match = re.search(r'Q(\d+?)(?=\d{0,2}[A-Z]|$)', raw)
if q_match:
qty = int(q_match.group(1))
if part or qty:
return part, qty