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
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
1"""DRF serializers for barcode scanning API."""
3from django.core.exceptions import ValidationError
4from django.utils.translation import gettext_lazy as _
6from rest_framework import serializers
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
21class BarcodeScanResultSerializer(serializers.ModelSerializer):
22 """Serializer for barcode scan results."""
24 class Meta:
25 """Meta class for BarcodeScanResultSerializer."""
27 model = common.models.BarcodeScanResult
29 fields = [
30 'pk',
31 'data',
32 'timestamp',
33 'endpoint',
34 'context',
35 'response',
36 'result',
37 'user',
38 'user_detail',
39 ]
41 read_only_fields = fields
43 user_detail = UserSerializer(source='user', read_only=True)
46class BarcodeSerializer(serializers.Serializer):
47 """Generic serializer for receiving barcode data."""
49 MAX_BARCODE_LENGTH = 4095
51 barcode = serializers.CharField(
52 required=True,
53 help_text=_('Scanned barcode data'),
54 max_length=MAX_BARCODE_LENGTH,
55 )
58class BarcodeGenerateSerializer(serializers.Serializer):
59 """Serializer for generating a barcode."""
61 model = serializers.CharField(
62 required=True, help_text=_('Model name to generate barcode for')
63 )
65 pk = serializers.IntegerField(
66 required=True,
67 help_text=_('Primary key of model object to generate barcode for'),
68 )
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 )
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'))
79 return model
82class BarcodeAssignMixin(serializers.Serializer):
83 """Serializer for linking and unlinking barcode to an internal class."""
85 def __init__(self, *args, **kwargs):
86 """Generate serializer fields for each supported model type."""
87 super().__init__(*args, **kwargs)
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 )
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 ]
107 return fields
110class BarcodeAssignSerializer(BarcodeAssignMixin, BarcodeSerializer):
111 """Serializer class for linking a barcode to an internal model."""
113 class Meta:
114 """Meta class for BarcodeAssignSerializer."""
116 fields = ['barcode', *BarcodeAssignMixin.get_model_fields()]
119class BarcodeUnassignSerializer(BarcodeAssignMixin):
120 """Serializer class for unlinking a barcode from an internal model."""
122 class Meta:
123 """Meta class for BarcodeUnlinkSerializer."""
125 fields = BarcodeAssignMixin.get_model_fields()
128class BarcodePOAllocateSerializer(BarcodeSerializer):
129 """Serializer for allocating items against a purchase order.
131 The scanned barcode could be a Part, ManufacturerPart or SupplierPart object
132 """
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 )
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'))
145 return order
148class BarcodePOReceiveSerializer(BarcodeSerializer):
149 """Serializer for receiving items against a purchase order.
151 The following additional fields may be specified:
153 - purchase_order: PurchaseOrder object to receive items against
154 - location: Location to receive items into
155 """
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 )
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 )
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'))
176 return order
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 )
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'))
190 return location
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 )
199 auto_allocate = serializers.BooleanField(
200 required=False,
201 default=True,
202 help_text=_('Automatically allocate stock items to the purchase order'),
203 )
206class BarcodeSOAllocateSerializer(BarcodeSerializer):
207 """Serializr for allocating stock items to a sales order.
209 The scanned barcode must map to a StockItem object
210 """
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 )
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'))
223 return order
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 )
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 )
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'))
244 return shipment
246 quantity = serializers.IntegerField(
247 required=False, help_text=_('Quantity to allocate')
248 )