Exemplo n.º 1
0
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',
        ]
Exemplo n.º 2
0
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',
            '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'
        ]
Exemplo n.º 3
0
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',
            'customer',
            '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)

    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)

    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',
            'customer',
            'build_order',
            'in_stock',
            'link',
            'location',
            'location_detail',
            'notes',
            'part',
            'part_detail',
            'pk',
            'quantity',
            'required_tests',
            'sales_order',
            'serial',
            'status',
            'status_text',
            'supplier_part',
            'supplier_part_detail',
            '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'
        ]
Exemplo n.º 4
0
class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
    """ Serializer for a StockItem:

    - Includes serialization for the linked part
    - Includes serialization for the item location
    """
    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.
        """

        # 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 = InvenTreeDecimalField()

    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 = 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 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',
            'delete_on_deplete',
            'expired',
            'expiry_date',
            'in_stock',
            'is_building',
            'link',
            'location',
            'location_detail',
            'notes',
            'owner',
            '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'
        ]
Exemplo n.º 5
0
    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)