class PartInternalPriceSerializer(InvenTreeModelSerializer): """ Serializer for internal prices for Part model. """ quantity = InvenTreeDecimalField() price = InvenTreeMoneySerializer(allow_null=True) price_currency = serializers.ChoiceField( choices=currency_code_mappings(), default=currency_code_default, label=_('Currency'), help_text=_('Purchase currency of this stock item'), ) price_string = serializers.CharField(source='price', read_only=True) class Meta: model = PartInternalPriceBreak fields = [ 'pk', 'part', 'quantity', 'price', 'price_currency', 'price_string', ]
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 SupplierPriceBreakSerializer(InvenTreeModelSerializer): """ Serializer for SupplierPriceBreak object """ quantity = InvenTreeDecimalField() price = InvenTreeMoneySerializer( allow_null=True, required=True, label=_('Price'), ) price_currency = serializers.ChoiceField( choices=currency_code_mappings(), default=currency_code_default, label=_('Currency'), ) class Meta: model = SupplierPriceBreak fields = [ 'pk', 'part', 'quantity', 'price', 'price_currency', ]
def money_kwargs(): """ returns the database settings for MoneyFields """ from common.settings import currency_code_mappings, currency_code_default kwargs = {} kwargs['currency_choices'] = currency_code_mappings() kwargs['default_currency'] = currency_code_default() return kwargs
class CompanySerializer(InvenTreeModelSerializer): """Serializer for Company object (full detail)""" @staticmethod def annotate_queryset(queryset): """Annoate the supplied queryset with aggregated information""" # Add count of parts manufactured queryset = queryset.annotate( parts_manufactured=SubqueryCount('manufactured_parts') ) queryset = queryset.annotate( parts_supplied=SubqueryCount('supplied_parts') ) return queryset url = serializers.CharField(source='get_absolute_url', read_only=True) image = InvenTreeImageSerializerField(required=False, allow_null=True) parts_supplied = serializers.IntegerField(read_only=True) parts_manufactured = serializers.IntegerField(read_only=True) currency = serializers.ChoiceField( choices=currency_code_mappings(), initial=currency_code_default, help_text=_('Default currency used for this supplier'), label=_('Currency Code'), required=True, ) class Meta: """Metaclass options.""" model = Company fields = [ 'pk', 'url', 'name', 'description', 'website', 'name', 'phone', 'address', 'email', 'currency', 'contact', 'link', 'image', 'is_customer', 'is_manufacturer', 'is_supplier', 'notes', 'parts_supplied', 'parts_manufactured', ]
class AbstractExtraLineSerializer(serializers.Serializer): """ Abstract Serializer for a ExtraLine object """ def __init__(self, *args, **kwargs): order_detail = kwargs.pop('order_detail', False) super().__init__(*args, **kwargs) if order_detail is not True: self.fields.pop('order_detail') quantity = serializers.FloatField() price = InvenTreeMoneySerializer(allow_null=True) price_string = serializers.CharField(source='price', read_only=True) price_currency = serializers.ChoiceField( choices=currency_code_mappings(), help_text=_('Price currency'), )
class SupplierPriceBreakSerializer(InvenTreeModelSerializer): """ Serializer for SupplierPriceBreak object """ quantity = serializers.FloatField() price = serializers.CharField() price_currency = serializers.ChoiceField( choices=currency_code_mappings(), default=currency_code_default, label=_('Currency'), ) class Meta: model = SupplierPriceBreak fields = [ 'pk', 'part', 'quantity', 'price', 'price_currency', ]
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(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 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 StockItemSerializer(InvenTreeModelSerializer): """ Serializer for a StockItem: - Includes serialization for the linked part - Includes serialization for the item location """ @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) purchase_price = InvenTreeMoneySerializer(label=_('Purchase Price'), max_digits=19, decimal_places=4, allow_null=True) purchase_price_currency = serializers.ChoiceField( choices=currency_code_mappings(), default=currency_code_default, label=_('Currency'), ) purchase_price_string = serializers.SerializerMethodField() def get_purchase_price_string(self, obj): return str(obj.purchase_price) if obj.purchase_price else '-' 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): 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', 'purchase_order', 'purchase_order_reference', 'pk', 'quantity', 'required_tests', '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', 'in_stock' ]
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', ]