Coverage for src/backend/InvenTree/plugin/builtin/barcodes/inventree_barcode.py: 56%

65 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 17:47 +0000

1"""The InvenTreeBarcodePlugin validates barcodes generated by InvenTree itself. It can be used as a template for developing third-party barcode plugins. 

2 

3The data format is very simple, and maps directly to database objects, 

4via the "id" parameter. 

5 

6Parsing an InvenTree barcode simply involves validating that the 

7references model objects actually exist in the database. 

8""" 

9 

10import json 

11import re 

12from typing import cast 

13 

14from django.utils.translation import gettext_lazy as _ 

15 

16import plugin.base.barcodes.helper 

17from InvenTree.helpers import hash_barcode 

18from InvenTree.models import InvenTreeBarcodeMixin 

19from plugin import InvenTreePlugin 

20from plugin.mixins import BarcodeMixin, SettingsMixin 

21 

22 

23class InvenTreeInternalBarcodePlugin(SettingsMixin, BarcodeMixin, InvenTreePlugin): 

24 """Builtin BarcodePlugin for matching and generating internal barcodes.""" 

25 

26 NAME = 'InvenTreeBarcode' 

27 TITLE = _('InvenTree Barcodes') 

28 DESCRIPTION = _('Provides native support for barcodes') 

29 VERSION = '2.2.0' 

30 AUTHOR = _('InvenTree contributors') 

31 

32 SETTINGS = { 

33 'INTERNAL_BARCODE_FORMAT': { 

34 'name': _('Internal Barcode Format'), 

35 'description': _('Select an internal barcode format'), 

36 'choices': [ 

37 ('json', _('JSON barcodes (human readable)')), 

38 ('short', _('Short barcodes (space optimized)')), 

39 ], 

40 'default': 'short', 

41 }, 

42 'SHORT_BARCODE_PREFIX': { 

43 'name': _('Short Barcode Prefix'), 

44 'description': _( 

45 'Customize the prefix used for short barcodes, may be useful for environments with multiple InvenTree instances' 

46 ), 

47 'default': 'INV-', 

48 }, 

49 } 

50 

51 def format_matched_response(self, label, model, instance, user, **kwargs): 

52 """Format a response for the scanned data.""" 

53 return {label: instance.format_matched_response(user=user, **kwargs)} 

54 

55 def scan(self, barcode_data, user, **kwargs): 

56 """Scan a barcode against this plugin. 

57 

58 Here we are looking for a dict object which contains a reference to a particular InvenTree database object 

59 """ 

60 # Internal Barcodes - Short Format 

61 # Attempt to match the barcode data against the short barcode format 

62 prefix = cast(str, self.get_setting('SHORT_BARCODE_PREFIX')) 

63 if type(barcode_data) is str and ( 63 ↛ 68line 63 didn't jump to line 68 because the condition on line 63 was never true

64 m := re.match( 

65 f'^{re.escape(prefix)}([0-9A-Z $%*+-.\\/:]{"{2}"})(\\d+)$', barcode_data 

66 ) 

67 ): 

68 model_type_code, pk = m.groups() 

69 

70 supported_models_map = ( 

71 plugin.base.barcodes.helper.get_supported_barcode_model_codes_map() 

72 ) 

73 model = supported_models_map.get(model_type_code, None) 

74 

75 if model is None: 

76 return None 

77 

78 label = model.barcode_model_type() 

79 

80 try: 

81 instance = model.objects.get(pk=int(pk)) 

82 return self.format_matched_response(label, model, instance, user=user) 

83 except (ValueError, model.DoesNotExist): 

84 pass 

85 

86 # Internal Barcodes - JSON Format 

87 # Attempt to coerce the barcode data into a dict object 

88 # This is the internal JSON barcode representation that InvenTree uses 

89 barcode_dict = None 

90 

91 if type(barcode_data) is dict: 91 ↛ 92line 91 didn't jump to line 92 because the condition on line 91 was never true

92 barcode_dict = barcode_data 

93 elif type(barcode_data) is str: 93 ↛ 99line 93 didn't jump to line 99 because the condition on line 93 was always true

94 try: 

95 barcode_dict = json.loads(barcode_data) 

96 except json.JSONDecodeError: 

97 pass 

98 

99 supported_models = plugin.base.barcodes.helper.get_supported_barcode_models() 

100 

101 succcess_message = _('Found matching item') 

102 

103 if barcode_dict is not None and type(barcode_dict) is dict: 103 ↛ 105line 103 didn't jump to line 105 because the condition on line 103 was never true

104 # Look for various matches. First good match will be returned 

105 for model in supported_models: 

106 label = model.barcode_model_type() 

107 

108 if label in barcode_dict: 

109 try: 

110 pk = int(barcode_dict[label]) 

111 instance = model.objects.get(pk=pk) 

112 

113 return { 

114 **self.format_matched_response( 

115 label, model, instance, user=user 

116 ), 

117 'success': succcess_message, 

118 } 

119 except (ValueError, model.DoesNotExist): 

120 pass 

121 

122 # External Barcodes (Linked barcodes) 

123 # Create hash from raw barcode data 

124 barcode_hash = hash_barcode(barcode_data) 

125 

126 # If no "direct" hits are found, look for assigned third-party barcodes 

127 for model in supported_models: 

128 label = model.barcode_model_type() 

129 

130 instance = model.lookup_barcode(barcode_hash) 

131 

132 if instance is not None: 132 ↛ 133line 132 didn't jump to line 133 because the condition on line 132 was never true

133 return { 

134 **self.format_matched_response(label, model, instance, user=user), 

135 'success': succcess_message, 

136 } 

137 

138 def generate(self, model_instance: InvenTreeBarcodeMixin): 

139 """Generate a barcode for a given model instance.""" 

140 barcode_format = self.get_setting( 

141 'INTERNAL_BARCODE_FORMAT', backup_value='json' 

142 ) 

143 

144 if barcode_format == 'short': 144 ↛ 151line 144 didn't jump to line 151 because the condition on line 144 was always true

145 prefix = self.get_setting('SHORT_BARCODE_PREFIX') 

146 model_type_code = model_instance.barcode_model_type_code() 

147 

148 return f'{prefix}{model_type_code}{model_instance.pk}' 

149 else: 

150 # Default = JSON format 

151 return json.dumps({model_instance.barcode_model_type(): model_instance.pk})