Coverage for src/backend/InvenTree/plugin/base/barcodes/serializers.py: 80%

75 statements  

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

1"""DRF serializers for barcode scanning API.""" 

2 

3from django.core.exceptions import ValidationError 

4from django.utils.translation import gettext_lazy as _ 

5 

6from rest_framework import serializers 

7 

8import common.models 

9import company.models 

10import order.models 

11import plugin.base.barcodes.helper 

12import stock.models 

13from order.status_codes import ( 

14 PurchaseOrderStatus, 

15 PurchaseOrderStatusGroups, 

16 SalesOrderStatusGroups, 

17) 

18from users.serializers import UserSerializer 

19 

20 

21class BarcodeScanResultSerializer(serializers.ModelSerializer): 

22 """Serializer for barcode scan results.""" 

23 

24 class Meta: 

25 """Meta class for BarcodeScanResultSerializer.""" 

26 

27 model = common.models.BarcodeScanResult 

28 

29 fields = [ 

30 'pk', 

31 'data', 

32 'timestamp', 

33 'endpoint', 

34 'context', 

35 'response', 

36 'result', 

37 'user', 

38 'user_detail', 

39 ] 

40 

41 read_only_fields = fields 

42 

43 user_detail = UserSerializer(source='user', read_only=True) 

44 

45 

46class BarcodeSerializer(serializers.Serializer): 

47 """Generic serializer for receiving barcode data.""" 

48 

49 MAX_BARCODE_LENGTH = 4095 

50 

51 barcode = serializers.CharField( 

52 required=True, 

53 help_text=_('Scanned barcode data'), 

54 max_length=MAX_BARCODE_LENGTH, 

55 ) 

56 

57 

58class BarcodeGenerateSerializer(serializers.Serializer): 

59 """Serializer for generating a barcode.""" 

60 

61 model = serializers.CharField( 

62 required=True, help_text=_('Model name to generate barcode for') 

63 ) 

64 

65 pk = serializers.IntegerField( 

66 required=True, 

67 help_text=_('Primary key of model object to generate barcode for'), 

68 ) 

69 

70 def validate_model(self, model: str): 

71 """Validate the provided model.""" 

72 supported_models = ( 

73 plugin.base.barcodes.helper.get_supported_barcode_models_map() 

74 ) 

75 

76 if model not in supported_models: 76 ↛ 79line 76 didn't jump to line 79 because the condition on line 76 was always true

77 raise ValidationError(_('Model is not supported')) 

78 

79 return model 

80 

81 

82class BarcodeAssignMixin(serializers.Serializer): 

83 """Serializer for linking and unlinking barcode to an internal class.""" 

84 

85 def __init__(self, *args, **kwargs): 

86 """Generate serializer fields for each supported model type.""" 

87 super().__init__(*args, **kwargs) 

88 

89 for model in plugin.base.barcodes.helper.get_supported_barcode_models(): 

90 self.fields[model.barcode_model_type()] = ( 

91 serializers.PrimaryKeyRelatedField( 

92 queryset=model.objects.all(), 

93 required=False, 

94 allow_null=True, 

95 label=model._meta.verbose_name, 

96 ) 

97 ) 

98 

99 @staticmethod 

100 def get_model_fields(): 

101 """Return a list of model fields.""" 

102 fields = [ 

103 model.barcode_model_type() 

104 for model in plugin.base.barcodes.helper.get_supported_barcode_models() 

105 ] 

106 

107 return fields 

108 

109 

110class BarcodeAssignSerializer(BarcodeAssignMixin, BarcodeSerializer): 

111 """Serializer class for linking a barcode to an internal model.""" 

112 

113 class Meta: 

114 """Meta class for BarcodeAssignSerializer.""" 

115 

116 fields = ['barcode', *BarcodeAssignMixin.get_model_fields()] 

117 

118 

119class BarcodeUnassignSerializer(BarcodeAssignMixin): 

120 """Serializer class for unlinking a barcode from an internal model.""" 

121 

122 class Meta: 

123 """Meta class for BarcodeUnlinkSerializer.""" 

124 

125 fields = BarcodeAssignMixin.get_model_fields() 

126 

127 

128class BarcodePOAllocateSerializer(BarcodeSerializer): 

129 """Serializer for allocating items against a purchase order. 

130 

131 The scanned barcode could be a Part, ManufacturerPart or SupplierPart object 

132 """ 

133 

134 purchase_order = serializers.PrimaryKeyRelatedField( 

135 queryset=order.models.PurchaseOrder.objects.all(), 

136 required=True, 

137 help_text=_('Purchase Order to allocate items against'), 

138 ) 

139 

140 def validate_purchase_order(self, order: order.models.PurchaseOrder): 

141 """Validate the provided order.""" 

142 if order.status not in PurchaseOrderStatusGroups.OPEN: 

143 raise ValidationError(_('Purchase order is not open')) 

144 

145 return order 

146 

147 

148class BarcodePOReceiveSerializer(BarcodeSerializer): 

149 """Serializer for receiving items against a purchase order. 

150 

151 The following additional fields may be specified: 

152 

153 - purchase_order: PurchaseOrder object to receive items against 

154 - location: Location to receive items into 

155 """ 

156 

157 supplier = serializers.PrimaryKeyRelatedField( 

158 queryset=company.models.Company.objects.all(), 

159 required=False, 

160 allow_null=True, 

161 help_text=_('Supplier to receive items from'), 

162 ) 

163 

164 purchase_order = serializers.PrimaryKeyRelatedField( 

165 queryset=order.models.PurchaseOrder.objects.all(), 

166 required=False, 

167 allow_null=True, 

168 help_text=_('PurchaseOrder to receive items against'), 

169 ) 

170 

171 def validate_purchase_order(self, order: order.models.PurchaseOrder): 

172 """Validate the provided order.""" 

173 if order and order.status != PurchaseOrderStatus.PLACED.value: 173 ↛ 174line 173 didn't jump to line 174 because the condition on line 173 was never true

174 raise ValidationError(_('Purchase order has not been placed')) 

175 

176 return order 

177 

178 location = serializers.PrimaryKeyRelatedField( 

179 queryset=stock.models.StockLocation.objects.all(), 

180 required=False, 

181 allow_null=True, 

182 help_text=_('Location to receive items into'), 

183 ) 

184 

185 def validate_location(self, location: stock.models.StockLocation): 

186 """Validate the provided location.""" 

187 if location and location.structural: 187 ↛ 188line 187 didn't jump to line 188 because the condition on line 187 was never true

188 raise ValidationError(_('Cannot select a structural location')) 

189 

190 return location 

191 

192 line_item = serializers.PrimaryKeyRelatedField( 

193 queryset=order.models.PurchaseOrderLineItem.objects.all(), 

194 required=False, 

195 allow_null=True, 

196 help_text=_('Purchase order line item to receive items against'), 

197 ) 

198 

199 auto_allocate = serializers.BooleanField( 

200 required=False, 

201 default=True, 

202 help_text=_('Automatically allocate stock items to the purchase order'), 

203 ) 

204 

205 

206class BarcodeSOAllocateSerializer(BarcodeSerializer): 

207 """Serializr for allocating stock items to a sales order. 

208 

209 The scanned barcode must map to a StockItem object 

210 """ 

211 

212 sales_order = serializers.PrimaryKeyRelatedField( 

213 queryset=order.models.SalesOrder.objects.all(), 

214 required=True, 

215 help_text=_('Sales Order to allocate items against'), 

216 ) 

217 

218 def validate_sales_order(self, order: order.models.SalesOrder): 

219 """Validate the provided order.""" 

220 if order and order.status not in SalesOrderStatusGroups.OPEN: 

221 raise ValidationError(_('Sales order is not open')) 

222 

223 return order 

224 

225 line = serializers.PrimaryKeyRelatedField( 

226 queryset=order.models.SalesOrderLineItem.objects.all(), 

227 required=False, 

228 allow_null=True, 

229 help_text=_('Sales order line item to allocate items against'), 

230 ) 

231 

232 shipment = serializers.PrimaryKeyRelatedField( 

233 queryset=order.models.SalesOrderShipment.objects.all(), 

234 required=False, 

235 allow_null=True, 

236 help_text=_('Sales order shipment to allocate items against'), 

237 ) 

238 

239 def validate_shipment(self, shipment: order.models.SalesOrderShipment): 

240 """Validate the provided shipment.""" 

241 if shipment and shipment.is_delivered(): 241 ↛ 242line 241 didn't jump to line 242 because the condition on line 241 was never true

242 raise ValidationError(_('Shipment has already been delivered')) 

243 

244 return shipment 

245 

246 quantity = serializers.IntegerField( 

247 required=False, help_text=_('Quantity to allocate') 

248 )