class POLineItemSerializer(InvenTreeModelSerializer): def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) super().__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') self.fields.pop('supplier_part_detail') quantity = serializers.FloatField() received = serializers.FloatField() part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True) supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True) class Meta: model = PurchaseOrderLineItem fields = [ 'pk', 'quantity', 'reference', 'notes', 'order', 'part', 'part_detail', 'supplier_part_detail', 'received', ]
class SupplierPartSerializer(InvenTreeModelSerializer): """ Serializer for SupplierPart object """ part_detail = PartBriefSerializer(source='part', many=False, read_only=True) supplier_detail = CompanyBriefSerializer(source='supplier', many=False, read_only=True) manufacturer_detail = CompanyBriefSerializer(source='manufacturer', many=False, read_only=True) pretty_name = serializers.CharField(read_only=True) def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) supplier_detail = kwargs.pop('supplier_detail', False) manufacturer_detail = kwargs.pop('manufacturer_detail', False) prettify = kwargs.pop('pretty', False) super(SupplierPartSerializer, self).__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if supplier_detail is not True: self.fields.pop('supplier_detail') if manufacturer_detail is not True: self.fields.pop('manufacturer_detail') if prettify is not True: self.fields.pop('pretty_name') supplier = serializers.PrimaryKeyRelatedField( queryset=Company.objects.filter(is_supplier=True)) manufacturer = serializers.PrimaryKeyRelatedField( queryset=Company.objects.filter(is_manufacturer=True)) class Meta: model = SupplierPart fields = [ 'pk', 'part', 'part_detail', 'pretty_name', 'supplier', 'supplier_detail', 'SKU', 'manufacturer', 'manufacturer_detail', 'description', 'MPN', 'link', ]
class SalesOrderAllocationSerializer(InvenTreeModelSerializer): """ Serializer for the SalesOrderAllocation model. This includes some fields from the related model objects. """ part = serializers.PrimaryKeyRelatedField(source='item.part', read_only=True) order = serializers.PrimaryKeyRelatedField(source='line.order', many=False, read_only=True) serial = serializers.CharField(source='get_serial', read_only=True) quantity = serializers.FloatField(read_only=False) location = serializers.PrimaryKeyRelatedField(source='item.location', many=False, read_only=True) # Extra detail fields order_detail = SalesOrderSerializer(source='line.order', many=False, read_only=True) part_detail = PartBriefSerializer(source='item.part', many=False, read_only=True) item_detail = stock.serializers.StockItemSerializer(source='item', many=False, read_only=True) location_detail = stock.serializers.LocationSerializer(source='item.location', many=False, read_only=True) shipment_date = serializers.DateField(source='shipment.shipment_date', read_only=True) def __init__(self, *args, **kwargs): order_detail = kwargs.pop('order_detail', False) part_detail = kwargs.pop('part_detail', True) item_detail = kwargs.pop('item_detail', False) location_detail = kwargs.pop('location_detail', False) super().__init__(*args, **kwargs) if not order_detail: self.fields.pop('order_detail') if not part_detail: self.fields.pop('part_detail') if not item_detail: self.fields.pop('item_detail') if not location_detail: self.fields.pop('location_detail') class Meta: model = order.models.SalesOrderAllocation fields = [ 'pk', 'line', 'serial', 'quantity', 'location', 'location_detail', 'item', 'item_detail', 'order', 'order_detail', 'part', 'part_detail', 'shipment', 'shipment_date', ]
class SOLineItemSerializer(InvenTreeModelSerializer): """ Serializer for a SalesOrderLineItem object """ def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) order_detail = kwargs.pop('order_detail', False) allocations = kwargs.pop('allocations', False) super().__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if order_detail is not True: self.fields.pop('order_detail') if allocations is not True: self.fields.pop('allocations') order_detail = SalesOrderSerializer(source='order', many=False, read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) allocations = SalesOrderAllocationSerializer(many=True, read_only=True, location_detail=True) quantity = InvenTreeDecimalField() allocated = serializers.FloatField(source='allocated_quantity', read_only=True) shipped = InvenTreeDecimalField(read_only=True) sale_price = InvenTreeMoneySerializer( allow_null=True ) sale_price_string = serializers.CharField(source='sale_price', read_only=True) sale_price_currency = serializers.ChoiceField( choices=currency_code_mappings(), help_text=_('Sale price currency'), ) class Meta: model = order.models.SalesOrderLineItem fields = [ 'pk', 'allocated', 'allocations', 'quantity', 'reference', 'notes', 'order', 'order_detail', 'part', 'part_detail', 'sale_price', 'sale_price_currency', 'sale_price_string', 'shipped', ]
class StockItemSerializer(serializers.ModelSerializer): """ Serializer for a StockItem: - Includes serialization for the linked part - Includes serialization for the item location """ url = serializers.CharField(source='get_absolute_url', read_only=True) status_text = serializers.CharField(source='get_status_display', read_only=True) part_name = serializers.CharField(source='get_part_name', read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) location_detail = LocationBriefSerializer(source='location', many=False, read_only=True) def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) location_detail = kwargs.pop('location_detail', False) super(StockItemSerializer, self).__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if location_detail is not True: self.fields.pop('location_detail') class Meta: model = StockItem fields = [ 'pk', 'url', 'part', 'part_name', 'part_detail', 'supplier_part', 'location', 'location_detail', 'in_stock', 'quantity', 'serial', 'batch', 'status', 'status_text', 'notes', ] """ These fields are read-only in this context. They can be updated by accessing the appropriate API endpoints """ read_only_fields = [ 'stocktake_date', 'stocktake_user', 'updated', 'quantity', 'in_stock' ]
class SOLineItemSerializer(InvenTreeModelSerializer): """ Serializer for a SalesOrderLineItem object """ def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) order_detail = kwargs.pop('order_detail', False) allocations = kwargs.pop('allocations', False) super().__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if order_detail is not True: self.fields.pop('order_detail') if allocations is not True: self.fields.pop('allocations') order_detail = SalesOrderSerializer(source='order', many=False, read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) allocations = SalesOrderAllocationSerializer(many=True, read_only=True) # TODO: Once https://github.com/inventree/InvenTree/issues/1687 is fixed, remove default values quantity = serializers.FloatField(default=1) allocated = serializers.FloatField(source='allocated_quantity', read_only=True) fulfilled = serializers.FloatField(source='fulfilled_quantity', read_only=True) sale_price_string = serializers.CharField(source='sale_price', read_only=True) class Meta: model = SalesOrderLineItem fields = [ 'pk', 'allocated', 'allocations', 'quantity', 'fulfilled', 'reference', 'notes', 'order', 'order_detail', 'part', 'part_detail', 'sale_price', 'sale_price_currency', 'sale_price_string', ]
class SupplierPartSerializer(InvenTreeModelSerializer): """ Serializer for SupplierPart object """ url = serializers.CharField(source='get_absolute_url', read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) supplier_detail = CompanyBriefSerializer(source='supplier', many=False, read_only=True) manufacturer_detail = CompanyBriefSerializer(source='manufacturer', many=False, read_only=True) pricing = serializers.CharField(source='unit_pricing', read_only=True) def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) supplier_detail = kwargs.pop('supplier_detail', False) manufacturer_detail = kwargs.pop('manufacturer_detail', False) super(SupplierPartSerializer, self).__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if supplier_detail is not True: self.fields.pop('supplier_detail') if manufacturer_detail is not True: self.fields.pop('manufacturer_detail') class Meta: model = SupplierPart fields = [ 'pk', 'url', 'part', 'part_detail', 'supplier', 'supplier_detail', 'SKU', 'manufacturer', 'manufacturer_detail', 'description', 'MPN', 'link', 'pricing', ]
class SOLineItemSerializer(InvenTreeModelSerializer): """ Serializer for a SalesOrderLineItem object """ def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) order_detail = kwargs.pop('order_detail', False) allocations = kwargs.pop('allocations', False) super().__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if order_detail is not True: self.fields.pop('order_detail') if allocations is not True: self.fields.pop('allocations') order_detail = SalesOrderSerializer(source='order', many=False, read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) allocations = SalesOrderAllocationSerializer(many=True, read_only=True) quantity = serializers.FloatField() allocated = serializers.FloatField(source='allocated_quantity', read_only=True) fulfilled = serializers.FloatField(source='fulfilled_quantity', read_only=True) class Meta: model = SalesOrderLineItem fields = [ 'pk', 'allocated', 'allocations', 'quantity', 'fulfilled', 'reference', 'notes', 'order', 'order_detail', 'part', 'part_detail', ]
class BuildSerializer(InvenTreeModelSerializer): """ Serializes a Build object """ url = serializers.CharField(source='get_absolute_url', read_only=True) status_text = serializers.CharField(source='get_status_display', read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) quantity = serializers.FloatField() def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) super().__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') class Meta: model = Build fields = [ 'pk', 'url', 'title', 'creation_date', 'completed', 'completion_date', 'part', 'part_detail', 'reference', 'sales_order', 'quantity', 'status', 'status_text', 'notes', 'link', ] read_only_fields = [ 'completed', 'creation_date', 'completion_data', 'status', 'status_text', ]
class POLineItemSerializer(InvenTreeModelSerializer): def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) super().__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') self.fields.pop('supplier_part_detail') # TODO: Once https://github.com/inventree/InvenTree/issues/1687 is fixed, remove default values quantity = serializers.FloatField(default=1) received = serializers.FloatField(default=0) part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True) supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True) purchase_price_string = serializers.CharField(source='purchase_price', read_only=True) destination = LocationBriefSerializer(source='get_destination', read_only=True) class Meta: model = PurchaseOrderLineItem fields = [ 'pk', 'quantity', 'reference', 'notes', 'order', 'part', 'part_detail', 'supplier_part_detail', 'received', 'purchase_price', 'purchase_price_currency', 'purchase_price_string', 'destination', ]
class ManufacturerPartSerializer(InvenTreeModelSerializer): """Serializer for ManufacturerPart object.""" part_detail = PartBriefSerializer(source='part', many=False, read_only=True) manufacturer_detail = CompanyBriefSerializer(source='manufacturer', many=False, read_only=True) pretty_name = serializers.CharField(read_only=True) def __init__(self, *args, **kwargs): """Initialize this serializer with extra detail fields as required""" part_detail = kwargs.pop('part_detail', True) manufacturer_detail = kwargs.pop('manufacturer_detail', True) prettify = kwargs.pop('pretty', False) super(ManufacturerPartSerializer, self).__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if manufacturer_detail is not True: self.fields.pop('manufacturer_detail') if prettify is not True: self.fields.pop('pretty_name') manufacturer = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_manufacturer=True)) class Meta: """Metaclass options.""" model = ManufacturerPart fields = [ 'pk', 'part', 'part_detail', 'pretty_name', 'manufacturer', 'manufacturer_detail', 'description', 'MPN', 'link', ]
class StockItemSerializer(serializers.ModelSerializer): """ Serializer for a StockItem """ url = serializers.CharField(source='get_absolute_url', read_only=True) part = PartBriefSerializer(many=False, read_only=True) location = LocationBriefSerializer(many=False, read_only=True) class Meta: model = StockItem fields = [ 'pk', 'uuid', 'url', 'part', 'supplier_part', 'location', 'in_stock', #'belongs_to', #'customer', 'quantity', 'serial', 'batch', 'status', 'notes', #'updated', #'stocktake_date', #'stocktake_user', #'review_needed', ] """ These fields are read-only in this context. They can be updated by accessing the appropriate API endpoints """ read_only_fields = [ 'stocktake_date', 'stocktake_user', 'updated', #'quantity', ]
class StockItemSerializer(serializers.ModelSerializer): """ Serializer for a StockItem: - Includes serialization for the linked part - Includes serialization for the item location """ url = serializers.CharField(source='get_absolute_url', read_only=True) part = PartBriefSerializer(many=False, read_only=True) location = LocationBriefSerializer(many=False, read_only=True) status_text = serializers.CharField(source='get_status_display', read_only=True) class Meta: model = StockItem fields = [ 'pk', 'uuid', 'url', 'part', 'supplier_part', 'location', 'in_stock', 'quantity', 'serial', 'batch', 'status', 'status_text', 'notes', ] """ These fields are read-only in this context. They can be updated by accessing the appropriate API endpoints """ read_only_fields = [ 'stocktake_date', 'stocktake_user', 'updated', 'quantity', 'in_stock' ]
class POLineItemSerializer(InvenTreeModelSerializer): @staticmethod def annotate_queryset(queryset): """ Add some extra annotations to this queryset: - Total price = purchase_price * quantity """ queryset = queryset.annotate( total_price=ExpressionWrapper( F('purchase_price') * F('quantity'), output_field=models.DecimalField() ) ) return queryset def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) order_detail = kwargs.pop('order_detail', False) super().__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') self.fields.pop('supplier_part_detail') if order_detail is not True: self.fields.pop('order_detail') quantity = serializers.FloatField(default=1) received = serializers.FloatField(default=0) total_price = serializers.FloatField(read_only=True) part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True) supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True) purchase_price = InvenTreeMoneySerializer( allow_null=True ) purchase_price_string = serializers.CharField(source='purchase_price', read_only=True) destination_detail = stock.serializers.LocationBriefSerializer(source='get_destination', read_only=True) purchase_price_currency = serializers.ChoiceField( choices=currency_code_mappings(), help_text=_('Purchase price currency'), ) order_detail = POSerializer(source='order', read_only=True, many=False) class Meta: model = order.models.PurchaseOrderLineItem fields = [ 'pk', 'quantity', 'reference', 'notes', 'order', 'order_detail', 'part', 'part_detail', 'supplier_part_detail', 'received', 'purchase_price', 'purchase_price_currency', 'purchase_price_string', 'destination', 'destination_detail', 'total_price', ]
class StockItemSerializer(InvenTreeModelSerializer): """ Serializer for a StockItem: - Includes serialization for the linked part - Includes serialization for the item location """ @staticmethod def prefetch_queryset(queryset): """ Prefetch related database tables, to reduce database hits. """ return queryset.prefetch_related( 'belongs_to', 'build', 'customer', 'sales_order', 'supplier_part', 'supplier_part__supplier', 'supplier_part__manufacturer_part__manufacturer', 'allocations', 'sales_order_allocations', 'location', 'part', 'tracking_info', ) @staticmethod def annotate_queryset(queryset): """ Add some extra annotations to the queryset, performing database queries as efficiently as possible. """ # Annotate the queryset with the total allocated to sales orders queryset = queryset.annotate( allocated=Coalesce( SubquerySum('sales_order_allocations__quantity'), Decimal(0)) + Coalesce(SubquerySum('allocations__quantity'), Decimal(0))) # Annotate the queryset with the number of tracking items queryset = queryset.annotate( tracking_items=SubqueryCount('tracking_info')) # Add flag to indicate if the StockItem has expired queryset = queryset.annotate( expired=Case(When( StockItem.EXPIRED_FILTER, then=Value(True, output_field=BooleanField()), ), default=Value(False, output_field=BooleanField()))) # Add flag to indicate if the StockItem is stale stale_days = common.models.InvenTreeSetting.get_setting( 'STOCK_STALE_DAYS') stale_date = datetime.now().date() + timedelta(days=stale_days) stale_filter = StockItem.IN_STOCK_FILTER & ~Q(expiry_date=None) & Q( expiry_date__lt=stale_date) queryset = queryset.annotate(stale=Case( When( stale_filter, then=Value(True, output_field=BooleanField()), ), default=Value(False, output_field=BooleanField()), )) return queryset status_text = serializers.CharField(source='get_status_display', read_only=True) supplier_part_detail = SupplierPartSerializer(source='supplier_part', many=False, read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) location_detail = LocationBriefSerializer(source='location', many=False, read_only=True) tracking_items = serializers.IntegerField(source='tracking_info_count', read_only=True, required=False) quantity = serializers.FloatField() allocated = serializers.FloatField(source='allocation_count', required=False) expired = serializers.BooleanField(required=False, read_only=True) stale = serializers.BooleanField(required=False, read_only=True) serial = serializers.CharField(required=False) required_tests = serializers.IntegerField(source='required_test_count', read_only=True, required=False) def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) location_detail = kwargs.pop('location_detail', False) supplier_part_detail = kwargs.pop('supplier_part_detail', False) test_detail = kwargs.pop('test_detail', False) super(StockItemSerializer, self).__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if location_detail is not True: self.fields.pop('location_detail') if supplier_part_detail is not True: self.fields.pop('supplier_part_detail') if test_detail is not True: self.fields.pop('required_tests') class Meta: model = StockItem fields = [ 'allocated', 'batch', 'belongs_to', 'build', 'customer', 'expired', 'expiry_date', 'in_stock', 'is_building', 'link', 'location', 'location_detail', 'notes', 'packaging', 'part', 'part_detail', 'pk', 'quantity', 'required_tests', 'sales_order', 'serial', 'stale', 'status', 'status_text', 'stocktake_date', 'supplier_part', 'supplier_part_detail', 'tracking_items', 'uid', 'updated', ] """ These fields are read-only in this context. They can be updated by accessing the appropriate API endpoints """ read_only_fields = [ 'allocated', 'stocktake_date', 'stocktake_user', 'updated', 'in_stock' ]
class StockItemSerializer(InvenTreeModelSerializer): """ Serializer for a StockItem: - Includes serialization for the linked part - Includes serialization for the item location """ @staticmethod def prefetch_queryset(queryset): """ Prefetch related database tables, to reduce database hits. """ return queryset.prefetch_related( 'belongs_to', 'build', 'build_order', 'sales_order', 'supplier_part', 'supplier_part__supplier', 'supplier_part__manufacturer', 'allocations', 'sales_order_allocations', 'location', 'part', 'tracking_info', ) @staticmethod def annotate_queryset(queryset): """ Add some extra annotations to the queryset, performing database queries as efficiently as possible. """ queryset = queryset.annotate( allocated=Coalesce( Sum('sales_order_allocations__quantity', distinct=True), 0) + Coalesce( Sum('allocations__quantity', distinct=True), 0), tracking_items=Count('tracking_info'), ) return queryset status_text = serializers.CharField(source='get_status_display', read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) location_detail = LocationBriefSerializer(source='location', many=False, read_only=True) supplier_part_detail = SupplierPartSerializer(source='supplier_part', many=False, read_only=True) tracking_items = serializers.IntegerField() quantity = serializers.FloatField() allocated = serializers.FloatField() def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) location_detail = kwargs.pop('location_detail', False) supplier_part_detail = kwargs.pop('supplier_part_detail', False) super(StockItemSerializer, self).__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if location_detail is not True: self.fields.pop('location_detail') if supplier_part_detail is not True: self.fields.pop('supplier_part_detail') class Meta: model = StockItem fields = [ 'allocated', 'batch', 'build_order', 'belongs_to', 'in_stock', 'link', 'location', 'location_detail', 'notes', 'part', 'part_detail', 'pk', 'quantity', 'sales_order', 'serial', 'supplier_part', 'supplier_part_detail', 'status', 'status_text', 'tracking_items', 'uid', ] """ These fields are read-only in this context. They can be updated by accessing the appropriate API endpoints """ read_only_fields = [ 'allocated', 'stocktake_date', 'stocktake_user', 'updated', 'in_stock' ]
class SupplierPartSerializer(InvenTreeModelSerializer): """ Serializer for SupplierPart object """ part_detail = PartBriefSerializer(source='part', many=False, read_only=True) supplier_detail = CompanyBriefSerializer(source='supplier', many=False, read_only=True) manufacturer_detail = CompanyBriefSerializer( source='manufacturer_part.manufacturer', many=False, read_only=True) pretty_name = serializers.CharField(read_only=True) def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', True) supplier_detail = kwargs.pop('supplier_detail', True) manufacturer_detail = kwargs.pop('manufacturer_detail', True) prettify = kwargs.pop('pretty', False) super(SupplierPartSerializer, self).__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if supplier_detail is not True: self.fields.pop('supplier_detail') if manufacturer_detail is not True: self.fields.pop('manufacturer_detail') if prettify is not True: self.fields.pop('pretty_name') supplier = serializers.PrimaryKeyRelatedField( queryset=Company.objects.filter(is_supplier=True)) manufacturer = serializers.CharField(read_only=True) MPN = serializers.CharField(read_only=True) manufacturer_part_detail = ManufacturerPartSerializer( source='manufacturer_part', read_only=True) class Meta: model = SupplierPart fields = [ 'description', 'link', 'manufacturer', 'manufacturer_detail', 'manufacturer_part', 'manufacturer_part_detail', 'MPN', 'note', 'pk', 'packaging', 'part', 'part_detail', 'pretty_name', 'SKU', 'supplier', 'supplier_detail', ] def create(self, validated_data): """ Extract manufacturer data and process ManufacturerPart """ # Create SupplierPart supplier_part = super().create(validated_data) # Get ManufacturerPart raw data (unvalidated) manufacturer = self.initial_data.get('manufacturer', None) MPN = self.initial_data.get('MPN', None) if manufacturer and MPN: kwargs = { 'manufacturer': manufacturer, 'MPN': MPN, } supplier_part.save(**kwargs) return supplier_part
def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) serializer = self.get_serializer(queryset, many=True) data = serializer.data # Attempt to add extra context information to the historical data for item in data: deltas = item['deltas'] if not deltas: deltas = {} # Add part detail if 'part' in deltas: try: part = Part.objects.get(pk=deltas['part']) serializer = PartBriefSerializer(part) deltas['part_detail'] = serializer.data except: pass # Add location detail if 'location' in deltas: try: location = StockLocation.objects.get(pk=deltas['location']) serializer = StockSerializers.LocationSerializer(location) deltas['location_detail'] = serializer.data except: pass # Add stockitem detail if 'stockitem' in deltas: try: stockitem = StockItem.objects.get(pk=deltas['stockitem']) serializer = StockSerializers.StockItemSerializer( stockitem) deltas['stockitem_detail'] = serializer.data except: pass # Add customer detail if 'customer' in deltas: try: customer = Company.objects.get(pk=deltas['customer']) serializer = CompanySerializer(customer) deltas['customer_detail'] = serializer.data except: pass # Add purchaseorder detail if 'purchaseorder' in deltas: try: order = PurchaseOrder.objects.get( pk=deltas['purchaseorder']) serializer = PurchaseOrderSerializer(order) deltas['purchaseorder_detail'] = serializer.data except: pass if request.is_ajax(): return JsonResponse(data, safe=False) else: return Response(data)
def list(self, request, *args, **kwargs): """ Override the 'list' method, as the StockLocation objects are very expensive to serialize. So, we fetch and serialize the required StockLocation objects only as required. """ queryset = self.filter_queryset(self.get_queryset()) serializer = self.get_serializer(queryset, many=True) data = serializer.data # Keep track of which related models we need to query location_ids = set() part_ids = set() supplier_part_ids = set() # Iterate through each StockItem and grab some data for item in data: loc = item['location'] if loc: location_ids.add(loc) part = item['part'] if part: part_ids.add(part) sp = item['supplier_part'] if sp: supplier_part_ids.add(sp) # Do we wish to include Part detail? if str2bool(request.query_params.get('part_detail', False)): # Fetch only the required Part objects from the database parts = Part.objects.filter(pk__in=part_ids).prefetch_related( 'category', ) part_map = {} for part in parts: part_map[part.pk] = PartBriefSerializer(part).data # Now update each StockItem with the related Part data for stock_item in data: part_id = stock_item['part'] stock_item['part_detail'] = part_map.get(part_id, None) # Do we wish to include SupplierPart detail? if str2bool(request.query_params.get('supplier_part_detail', False)): supplier_parts = SupplierPart.objects.filter(pk__in=supplier_part_ids) supplier_part_map = {} for part in supplier_parts: supplier_part_map[part.pk] = SupplierPartSerializer(part).data for stock_item in data: part_id = stock_item['supplier_part'] stock_item['supplier_part_detail'] = supplier_part_map.get(part_id, None) # Do we wish to include StockLocation detail? if str2bool(request.query_params.get('location_detail', False)): # Fetch only the required StockLocation objects from the database locations = StockLocation.objects.filter(pk__in=location_ids).prefetch_related( 'parent', 'children', ) location_map = {} # Serialize each StockLocation object for location in locations: location_map[location.pk] = LocationBriefSerializer(location).data # Now update each StockItem with the related StockLocation data for stock_item in data: loc_id = stock_item['location'] stock_item['location_detail'] = location_map.get(loc_id, None) """ Determine the response type based on the request. a) For HTTP requests (e.g. via the browseable API) return a DRF response b) For AJAX requests, simply return a JSON rendered response. Note: b) is about 100x quicker than a), because the DRF framework adds a lot of cruft """ if request.is_ajax(): return JsonResponse(data, safe=False) else: return Response(data)
class PurchaseOrderLineItemSerializer(InvenTreeModelSerializer): @staticmethod def annotate_queryset(queryset): """ Add some extra annotations to this queryset: - Total price = purchase_price * quantity - "Overdue" status (boolean field) """ queryset = queryset.annotate( total_price=ExpressionWrapper(F('purchase_price') * F('quantity'), output_field=models.DecimalField())) queryset = queryset.annotate(overdue=Case( When(Q(order__status__in=PurchaseOrderStatus.OPEN) & order.models.OrderLineItem.OVERDUE_FILTER, then=Value(True, output_field=BooleanField())), default=Value(False, output_field=BooleanField()), )) return queryset def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) order_detail = kwargs.pop('order_detail', False) super().__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') self.fields.pop('supplier_part_detail') if order_detail is not True: self.fields.pop('order_detail') quantity = serializers.FloatField(min_value=0, required=True) def validate_quantity(self, quantity): if quantity <= 0: raise ValidationError(_("Quantity must be greater than zero")) return quantity def validate_purchase_order(self, purchase_order): if purchase_order.status not in PurchaseOrderStatus.OPEN: raise ValidationError(_('Order is not open')) return purchase_order received = serializers.FloatField(default=0, read_only=True) overdue = serializers.BooleanField(required=False, read_only=True) total_price = serializers.FloatField(read_only=True) part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True) supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True) purchase_price = InvenTreeMoneySerializer(allow_null=True) purchase_price_string = serializers.CharField(source='purchase_price', read_only=True) destination_detail = stock.serializers.LocationBriefSerializer( source='get_destination', read_only=True) purchase_price_currency = serializers.ChoiceField( choices=currency_code_mappings(), help_text=_('Purchase price currency'), ) order_detail = PurchaseOrderSerializer(source='order', read_only=True, many=False) def validate(self, data): data = super().validate(data) supplier_part = data.get('part', None) purchase_order = data.get('order', None) if not supplier_part: raise ValidationError({ 'part': _('Supplier part must be specified'), }) if not purchase_order: raise ValidationError({ 'order': _('Purchase order must be specified'), }) # Check that the supplier part and purchase order match if supplier_part is not None and supplier_part.supplier != purchase_order.supplier: raise ValidationError({ 'part': _('Supplier must match purchase order'), 'order': _('Purchase order must match supplier'), }) return data class Meta: model = order.models.PurchaseOrderLineItem fields = [ 'pk', 'quantity', 'reference', 'notes', 'order', 'order_detail', 'overdue', 'part', 'part_detail', 'supplier_part_detail', 'received', 'purchase_price', 'purchase_price_currency', 'purchase_price_string', 'destination', 'destination_detail', 'target_date', 'total_price', ]
class SupplierPartSerializer(InvenTreeModelSerializer): """Serializer for SupplierPart object.""" # Annotated field showing total in-stock quantity in_stock = serializers.FloatField(read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) supplier_detail = CompanyBriefSerializer(source='supplier', many=False, read_only=True) manufacturer_detail = CompanyBriefSerializer(source='manufacturer_part.manufacturer', many=False, read_only=True) pretty_name = serializers.CharField(read_only=True) def __init__(self, *args, **kwargs): """Initialize this serializer with extra detail fields as required""" # Check if 'available' quantity was supplied self.has_available_quantity = 'available' in kwargs.get('data', {}) part_detail = kwargs.pop('part_detail', True) supplier_detail = kwargs.pop('supplier_detail', True) manufacturer_detail = kwargs.pop('manufacturer_detail', True) prettify = kwargs.pop('pretty', False) super(SupplierPartSerializer, self).__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if supplier_detail is not True: self.fields.pop('supplier_detail') if manufacturer_detail is not True: self.fields.pop('manufacturer_detail') if prettify is not True: self.fields.pop('pretty_name') supplier = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_supplier=True)) manufacturer = serializers.CharField(read_only=True) MPN = serializers.CharField(read_only=True) manufacturer_part_detail = ManufacturerPartSerializer(source='manufacturer_part', read_only=True) class Meta: """Metaclass options.""" model = SupplierPart fields = [ 'available', 'availability_updated', 'description', 'in_stock', 'link', 'manufacturer', 'manufacturer_detail', 'manufacturer_part', 'manufacturer_part_detail', 'MPN', 'note', 'pk', 'packaging', 'part', 'part_detail', 'pretty_name', 'SKU', 'supplier', 'supplier_detail', ] read_only_fields = [ 'availability_updated', ] @staticmethod def annotate_queryset(queryset): """Annotate the SupplierPart queryset with extra fields: Fields: in_stock: Current stock quantity for each SupplierPart """ queryset = queryset.annotate( in_stock=part.filters.annotate_total_stock() ) return queryset def update(self, supplier_part, data): """Custom update functionality for the serializer""" available = data.pop('available', None) response = super().update(supplier_part, data) if available is not None and self.has_available_quantity: supplier_part.update_available_quantity(available) return response def create(self, validated_data): """Extract manufacturer data and process ManufacturerPart.""" # Extract 'available' quantity from the serializer available = validated_data.pop('available', None) # Create SupplierPart supplier_part = super().create(validated_data) if available is not None and self.has_available_quantity: supplier_part.update_available_quantity(available) # Get ManufacturerPart raw data (unvalidated) manufacturer = self.initial_data.get('manufacturer', None) MPN = self.initial_data.get('MPN', None) if manufacturer and MPN: kwargs = { 'manufacturer': manufacturer, 'MPN': MPN, } supplier_part.save(**kwargs) return supplier_part
class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer): """Serializer for a StockItem. - Includes serialization for the linked part - Includes serialization for the item location """ part = serializers.PrimaryKeyRelatedField( queryset=part_models.Part.objects.all(), many=False, allow_null=False, help_text=_("Base Part"), label=_("Part"), ) def validate_part(self, part): """Ensure the provided Part instance is valid""" if part.virtual: raise ValidationError( _("Stock item cannot be created for virtual parts")) return part def update(self, instance, validated_data): """Custom update method to pass the user information through to the instance.""" instance._user = self.context['user'] return super().update(instance, validated_data) @staticmethod def annotate_queryset(queryset): """Add some extra annotations to the queryset, performing database queries as efficiently as possible.""" queryset = queryset.prefetch_related( 'sales_order', 'purchase_order', ) # Annotate the queryset with the total allocated to sales orders queryset = queryset.annotate( allocated=Coalesce( SubquerySum('sales_order_allocations__quantity'), Decimal(0)) + Coalesce(SubquerySum('allocations__quantity'), Decimal(0))) # Annotate the queryset with the number of tracking items queryset = queryset.annotate( tracking_items=SubqueryCount('tracking_info')) # Add flag to indicate if the StockItem has expired queryset = queryset.annotate( expired=Case(When( StockItem.EXPIRED_FILTER, then=Value(True, output_field=BooleanField()), ), default=Value(False, output_field=BooleanField()))) # Add flag to indicate if the StockItem is stale stale_days = common.models.InvenTreeSetting.get_setting( 'STOCK_STALE_DAYS') stale_date = datetime.now().date() + timedelta(days=stale_days) stale_filter = StockItem.IN_STOCK_FILTER & ~Q(expiry_date=None) & Q( expiry_date__lt=stale_date) queryset = queryset.annotate(stale=Case( When( stale_filter, then=Value(True, output_field=BooleanField()), ), default=Value(False, output_field=BooleanField()), )) return queryset status_text = serializers.CharField(source='get_status_display', read_only=True) supplier_part_detail = SupplierPartSerializer(source='supplier_part', many=False, read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) location_detail = LocationBriefSerializer(source='location', many=False, read_only=True) quantity = InvenTreeDecimalField() # Annotated fields tracking_items = serializers.IntegerField(read_only=True, required=False) allocated = serializers.FloatField(required=False) expired = serializers.BooleanField(required=False, read_only=True) stale = serializers.BooleanField(required=False, read_only=True) purchase_price = InvenTree.serializers.InvenTreeMoneySerializer( label=_('Purchase Price'), max_digits=19, decimal_places=4, allow_null=True, help_text=_('Purchase price of this stock item'), ) purchase_price_currency = serializers.ChoiceField( choices=currency_code_mappings(), default=currency_code_default, label=_('Currency'), help_text=_('Purchase currency of this stock item'), ) purchase_price_string = serializers.SerializerMethodField() def get_purchase_price_string(self, obj): """Return purchase price as string.""" if obj.purchase_price: obj.purchase_price.decimal_places_display = 4 return str(obj.purchase_price) return '-' purchase_order_reference = serializers.CharField( source='purchase_order.reference', read_only=True) sales_order_reference = serializers.CharField( source='sales_order.reference', read_only=True) def __init__(self, *args, **kwargs): """Add detail fields.""" part_detail = kwargs.pop('part_detail', False) location_detail = kwargs.pop('location_detail', False) supplier_part_detail = kwargs.pop('supplier_part_detail', False) super(StockItemSerializer, self).__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if location_detail is not True: self.fields.pop('location_detail') if supplier_part_detail is not True: self.fields.pop('supplier_part_detail') class Meta: """Metaclass options.""" model = StockItem fields = [ 'allocated', 'batch', 'belongs_to', 'build', 'customer', 'delete_on_deplete', 'expired', 'expiry_date', 'is_building', 'link', 'location', 'location_detail', 'notes', 'owner', 'packaging', 'part', 'part_detail', 'purchase_order', 'purchase_order_reference', 'pk', 'quantity', 'sales_order', 'sales_order_reference', 'serial', 'stale', 'status', 'status_text', 'stocktake_date', 'supplier_part', 'supplier_part_detail', 'tracking_items', 'uid', 'updated', 'purchase_price', 'purchase_price_currency', 'purchase_price_string', ] """ These fields are read-only in this context. They can be updated by accessing the appropriate API endpoints """ read_only_fields = [ 'allocated', 'stocktake_date', 'stocktake_user', 'updated', ]
class SalesOrderLineItemSerializer(InvenTreeModelSerializer): """ Serializer for a SalesOrderLineItem object """ @staticmethod def annotate_queryset(queryset): """ Add some extra annotations to this queryset: - "Overdue" status (boolean field) """ queryset = queryset.annotate(overdue=Case( When( Q(order__status__in=SalesOrderStatus.OPEN) & order.models.OrderLineItem.OVERDUE_FILTER, then=Value(True, output_field=BooleanField()), ), default=Value(False, output_field=BooleanField()), )) def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) order_detail = kwargs.pop('order_detail', False) allocations = kwargs.pop('allocations', False) super().__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if order_detail is not True: self.fields.pop('order_detail') if allocations is not True: self.fields.pop('allocations') order_detail = SalesOrderSerializer(source='order', many=False, read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) allocations = SalesOrderAllocationSerializer(many=True, read_only=True, location_detail=True) overdue = serializers.BooleanField(required=False, read_only=True) quantity = InvenTreeDecimalField() allocated = serializers.FloatField(source='allocated_quantity', read_only=True) shipped = InvenTreeDecimalField(read_only=True) sale_price = InvenTreeMoneySerializer(allow_null=True) sale_price_string = serializers.CharField(source='sale_price', read_only=True) sale_price_currency = serializers.ChoiceField( choices=currency_code_mappings(), help_text=_('Sale price currency'), ) class Meta: model = order.models.SalesOrderLineItem fields = [ 'pk', 'allocated', 'allocations', 'quantity', 'reference', 'notes', 'order', 'order_detail', 'overdue', 'part', 'part_detail', 'sale_price', 'sale_price_currency', 'sale_price_string', 'shipped', 'target_date', ]
class BuildSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer): """ Serializes a Build object """ url = serializers.CharField(source='get_absolute_url', read_only=True) status_text = serializers.CharField(source='get_status_display', read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) quantity = InvenTreeDecimalField() overdue = serializers.BooleanField(required=False, read_only=True) issued_by_detail = UserSerializerBrief(source='issued_by', read_only=True) responsible_detail = OwnerSerializer(source='responsible', read_only=True) @staticmethod def annotate_queryset(queryset): """ Add custom annotations to the BuildSerializer queryset, performing database queries as efficiently as possible. The following annoted fields are added: - overdue: True if the build is outstanding *and* the completion date has past """ # Annotate a boolean 'overdue' flag queryset = queryset.annotate( overdue=Case(When( Build.OVERDUE_FILTER, then=Value(True, output_field=BooleanField()), ), default=Value(False, output_field=BooleanField()))) return queryset def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', True) super().__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') class Meta: model = Build fields = [ 'pk', 'url', 'title', 'batch', 'creation_date', 'completed', 'completion_date', 'destination', 'parent', 'part', 'part_detail', 'overdue', 'reference', 'sales_order', 'quantity', 'status', 'status_text', 'target_date', 'take_from', 'notes', 'link', 'issued_by', 'issued_by_detail', 'responsible', 'responsible_detail', ] read_only_fields = [ 'completed', 'creation_date', 'completion_data', 'status', 'status_text', ]
class SalesOrderLineItemSerializer(InvenTreeModelSerializer): """Serializer for a SalesOrderLineItem object.""" @staticmethod def annotate_queryset(queryset): """Add some extra annotations to this queryset: - "overdue" status (boolean field) - "available_quantity" """ queryset = queryset.annotate( overdue=Case( When( Q(order__status__in=SalesOrderStatus.OPEN) & order.models.SalesOrderLineItem.OVERDUE_FILTER, then=Value(True, output_field=BooleanField()), ), default=Value(False, output_field=BooleanField()), ) ) # Annotate each line with the available stock quantity # To do this, we need to look at the total stock and any allocations queryset = queryset.alias( total_stock=part.filters.annotate_total_stock(reference='part__'), allocated_to_sales_orders=part.filters.annotate_sales_order_allocations(reference='part__'), allocated_to_build_orders=part.filters.annotate_build_order_allocations(reference='part__'), ) queryset = queryset.annotate( available_stock=ExpressionWrapper( F('total_stock') - F('allocated_to_sales_orders') - F('allocated_to_build_orders'), output_field=models.DecimalField() ) ) return queryset def __init__(self, *args, **kwargs): """Initializion routine for the serializer: - Add extra related serializer information if required """ part_detail = kwargs.pop('part_detail', False) order_detail = kwargs.pop('order_detail', False) allocations = kwargs.pop('allocations', False) super().__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if order_detail is not True: self.fields.pop('order_detail') if allocations is not True: self.fields.pop('allocations') order_detail = SalesOrderSerializer(source='order', many=False, read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) allocations = SalesOrderAllocationSerializer(many=True, read_only=True, location_detail=True) # Annotated fields overdue = serializers.BooleanField(required=False, read_only=True) available_stock = serializers.FloatField(read_only=True) quantity = InvenTreeDecimalField() allocated = serializers.FloatField(source='allocated_quantity', read_only=True) shipped = InvenTreeDecimalField(read_only=True) sale_price = InvenTreeMoneySerializer( allow_null=True ) sale_price_string = serializers.CharField(source='sale_price', read_only=True) sale_price_currency = serializers.ChoiceField( choices=currency_code_mappings(), help_text=_('Sale price currency'), ) class Meta: """Metaclass options.""" model = order.models.SalesOrderLineItem fields = [ 'pk', 'allocated', 'allocations', 'available_stock', 'quantity', 'reference', 'notes', 'order', 'order_detail', 'overdue', 'part', 'part_detail', 'sale_price', 'sale_price_currency', 'sale_price_string', 'shipped', 'target_date', ]