Files
GTI_GERENCIADOR_FISCAL/central/app/enrichment/gtin.py
T

32 lines
1.8 KiB
Python

"""Deterministic GTIN normalization and validation (GTIN-8, UPC/GTIN-12, EAN/GTIN-13, GTIN-14)."""
import re
VALID, INVALID, MISSING, RESTRICTED = "VALID_GTIN", "INVALID_GTIN", "MISSING_GTIN", "RESTRICTED_GTIN"
def check_digit_ok(digits: str) -> bool:
body, check = digits[:-1], int(digits[-1])
total = sum(int(d) * (3 if i % 2 == 0 else 1) for i, d in enumerate(reversed(body)))
return (10 - total % 10) % 10 == check
def normalize_gtin(raw) -> dict:
"""Returns {status, gtin (digits as typed), key (zero-padded to 14: the KB identity), reason}. Never calls anything external.
RESTRICTED: EAN prefixes 02, 04 and 20-29 are store-internal / restricted circulation (not a global product identity), so no provider is asked."""
text = "" if raw is None else str(raw).strip()
if not text or text.upper() in ("SEM GTIN", "SEM EAN", "0", "NULL"):
return {"status": MISSING, "gtin": None, "key": None, "reason": "EMPTY"}
digits = re.sub(r"\D", "", text)
if not digits or len(digits) not in (8, 12, 13, 14):
return {"status": INVALID, "gtin": digits or None, "key": None, "reason": "BAD_LENGTH"}
if len(set(digits)) == 1:
return {"status": INVALID, "gtin": digits, "key": None, "reason": "REPEATED_DIGITS"}
if not check_digit_ok(digits):
return {"status": INVALID, "gtin": digits, "key": None, "reason": "BAD_CHECK_DIGIT"}
key = digits.zfill(14)
prefix = key[1:3] if len(digits) == 14 else key[1:3] # key = 0 + EAN-13 layout for 12/13; GTIN-14 keeps its indicator digit in key[0]
ean13 = key[1:]
if ean13[:2] in ("02", "04") or "20" <= ean13[:2] <= "29":
return {"status": RESTRICTED, "gtin": digits, "key": key, "reason": "STORE_INTERNAL_PREFIX"}
return {"status": VALID, "gtin": digits, "key": key, "reason": None}