diff --git a/stdnum/gs1_128.py b/stdnum/gs1_128.py index 38e6e479..3381fad9 100644 --- a/stdnum/gs1_128.py +++ b/stdnum/gs1_128.py @@ -75,6 +75,10 @@ '8007': 'stdnum.iban', } +_TRADE_MEASURE_PREFIXES = { + '31', '32', '35', '36', +} + def compact(number: str) -> str: """Convert the GS1-128 to the minimal representation. @@ -195,6 +199,9 @@ def info(number: str, separator: str = '') -> dict[str, Any]: If a `separator` is provided it will be used as FNC1 to determine the end of variable-sized values. """ + for grp in re.findall(r'\((\d+)\)', number): + if grp[:2] in _TRADE_MEASURE_PREFIXES and len(grp) != 4: + raise InvalidComponent() number = compact(number) data = {} identifier = '' @@ -253,7 +260,14 @@ def encode(data: Mapping[str, object], separator: str = '', parentheses: bool = if ai in _ai_validators: mod = __import__(_ai_validators[ai], globals(), locals(), ['validate']) mod.validate(value) - value = _encode_value(info['format'], info['type'], value) + raw = _encode_value(info['format'], info['type'], value) + if info['type'] == 'decimal' and info['format'] == 'N6' and ai[:2] in _TRADE_MEASURE_PREFIXES: + # insert decimal count into AI and drop it from the value payload + dec = raw[0] + ai = ai + dec + value = raw[1:] + else: + value = raw # store variable-sized values separate from fixed-size values if info.get('fnc1'): variable_values.append((ai_fmt % ai, info['format'], info['type'], value)) diff --git a/tests/test_gs1_128.doctest b/tests/test_gs1_128.doctest index e074a847..e6d62406 100644 --- a/tests/test_gs1_128.doctest +++ b/tests/test_gs1_128.doctest @@ -65,7 +65,7 @@ properly. ... '313': '123.456', # str ... '391': ('123', Decimal('123.456')), # currency number combo ... }, parentheses=True) -'(310)2001723(311)0000456(312)5033333(313)3123456(391)3123123456' +'(3102)001723(3110)000456(3125)033333(3133)123456(391)3123123456' We generate dates in various formats, depending on the AI. @@ -88,6 +88,8 @@ We generate dates in various formats, depending on the AI. '(7011)181119' >>> gs1_128.encode({'7011': datetime.datetime(2018, 11, 19, 12, 45)}, parentheses=True) '(7011)1811191245' +>>> gs1_128.encode({'01': '98456789014533', '310': Decimal('0.035')}, parentheses=True) +'(01)98456789014533(3103)000035' If we try to encode an invalid EAN we will get an error. @@ -104,7 +106,7 @@ pprint.pprint(gs1_128.info('(01)38425876095074(17)181119(37)1 ')) {'01': '38425876095074', '17': datetime.date(2018, 11, 19), '37': 1} >>> pprint.pprint(gs1_128.info('013842587609507417181119371')) {'01': '38425876095074', '17': datetime.date(2018, 11, 19), '37': 1} ->>> pprint.pprint(gs1_128.info('(02)98412345678908(310)3017230(37)32')) +>>> pprint.pprint(gs1_128.info('(02)98412345678908(3103)017230(37)32')) {'02': '98412345678908', '310': Decimal('17.230'), '37': 32} >>> pprint.pprint(gs1_128.info('(01)58425876097843(10)123456 (17)181119(37)18')) {'01': '58425876097843', '10': '123456', '17': datetime.date(2018, 11, 19), '37': 18} @@ -124,8 +126,16 @@ InvalidComponent: ... We can decode decimal values from various formats. >>> pprint.pprint(gs1_128.info('(310)5033333')) +Traceback (most recent call last): + ... +InvalidComponent: ... +>>> pprint.pprint(gs1_128.info('(3105)033333')) +{'310': Decimal('0.33333')} +>>> pprint.pprint(gs1_128.info('(01)98456789014533(3103)000035')) +{'01': '98456789014533', '310': Decimal('0.035')} +>>> pprint.pprint(gs1_128.info('(3105)033333')) {'310': Decimal('0.33333')} ->>> pprint.pprint(gs1_128.info('(310)0033333')) +>>> pprint.pprint(gs1_128.info('(3100)033333')) {'310': Decimal('33333')} >>> pprint.pprint(gs1_128.info('(391)3123123456')) {'391': ('123', Decimal('123.456'))}