class TaskDashboardSerializer(TaskSerializer): answers_result_count = serializers.IntegerField(read_only=True) answers = AnswerSerializer(many=True) tx_hash = serializers.CharField(source='initial_tx_hash', required=False) type = serializers.CharField(read_only=True) reward_per_click = MoneyField(max_digits=11, decimal_places=5) remaining_balance = MoneyField(max_digits=9, decimal_places=3, required=False) initial_budget = MoneyField(max_digits=9, decimal_places=3, required=False) class Meta: model = models.Task fields = [ 'id', 'title', 'description', 'chain', 'user', 'og_image_link', 'uuid', 'website_link', 'reward_per_click', 'reward_usd_per_click', 'time_duration', 'questions', 'answers_result_count', 'answers', 'remaining_balance', 'initial_budget', 'tx_hash', 'type', ]
class PriceSerializer(ModelCleanFieldsSerializer): """The price serializer.""" service = AccountServiceForeignKey() price = MoneyField( max_digits=settings.D8B_MONEY_MAX_DIGITS, decimal_places=settings.D8B_MONEY_DECIMAL_PLACES, required=False, allow_null=True, ) start_price = MoneyField( max_digits=settings.D8B_MONEY_MAX_DIGITS, decimal_places=settings.D8B_MONEY_DECIMAL_PLACES, required=False, allow_null=True, ) end_price = MoneyField( max_digits=settings.D8B_MONEY_MAX_DIGITS, decimal_places=settings.D8B_MONEY_DECIMAL_PLACES, required=False, allow_null=True, ) class Meta: """The metainformation.""" model = Price fields = ("id", "service", "price", "price_currency", "start_price", "start_price_currency", "end_price", "end_price_currency", "is_price_fixed", "payment_methods", "modified", "created_by", "modified_by") read_only_fields = ("created", "modified", "created_by", "modified_by") extra_kwargs = {"is_price_fixed": {"required": True}}
class LotDetailSerializer(BaseLotSerializer): bids = serializers.SerializerMethodField() highest_bid = serializers.SerializerMethodField() highest_bid_price = MoneyField(source="highest_bid.price", max_digits=10, decimal_places=2) class Meta: model = BaseLotSerializer.Meta.model read_only_fields = BaseLotSerializer.Meta.read_only_fields + ( "bids", "highest_bid", "highest_bid_price", "modified_at", ) fields = read_only_fields + BaseLotSerializer.Meta.fields + ( "description", ) def _get_highest_bid(self, object) -> Optional[Bid]: return object.highest_bid def get_highest_bid(self, object) -> str: highest_bid = self._get_highest_bid(object) view_name = "api:auction:v1:bids:retrieve" path = reverse(view_name, kwargs={"bid_public_id": highest_bid.public_id}) url = self.context["request"].build_absolute_uri(path) return url def get_bids(self, object) -> str: view_name = "api:auction:v1:lot:bid_history" path = reverse(view_name, kwargs={"lot_public_id": object.public_id}) url = self.context["request"].build_absolute_uri(path) return url
class PrepaymentSerializer(serializers.ModelSerializer): created_by = UsernameSerializer(read_only=True) updated_by = UsernameSerializer(read_only=True) value = MoneyField(max_digits=14, decimal_places=2, required=True) class Meta: model = Prepayment fields = ( 'id', 'record', 'created_at', 'created_by', 'updated_at', 'updated_by', 'value', ) read_only_fields = ('created_at', 'created_by', 'updated_at', 'updated_by') def update(self, instance, validated_data): validated_data['updated_by'] = self.context['request'].user validated_data['updated_at'] = timezone.now() return super(PrepaymentSerializer, self).update(instance, validated_data) def create(self, validated_data): validated_data['created_by'] = self.context['request'].user return Prepayment.create_payment(**validated_data)
def __new__(cls, name, bases, attrs): from djmoney.contrib.django_rest_framework import MoneyField if field_name is not None and field_kwargs is not None: attrs[field_name] = MoneyField(max_digits=10, decimal_places=2, **field_kwargs) return super().__new__(cls, name, bases, attrs)
class ProductModelSerializer(ModelSerializer): created_at = serializers.ReadOnlyField() updated_at = serializers.ReadOnlyField() price = MoneyField(max_digits=10, decimal_places=2) class Meta: model = Product fields = '__all__'
class OrderProductSerializer(ModelSerializer): product_price = MoneyField(max_digits=10, decimal_places=2) product_name = serializers.CharField(source='product.name') product_quantity = serializers.IntegerField() class Meta: model = OrderProduct fields = ('product_name', 'product_price', 'product_quantity')
class InvoiceSerializer(serializers.HyperlinkedModelSerializer): lnnode_id = serializers.StringRelatedField(read_only=True, source='object_id') tax_currency_ex_rate = MoneyField(max_digits=10, decimal_places=2) info_currency_ex_rate = MoneyField(max_digits=10, decimal_places=2) price_in_tax_currency = serializers.CharField() tax_in_tax_currency = serializers.CharField() price_in_info_currency = serializers.CharField() class Meta: model = Invoice exclude = ( 'content_type', 'object_id', 'preimage' # never reveal the preimage! )
class JobDetailSerializer(serializers.ModelSerializer): price = MoneyField( max_digits=14, decimal_places=2, ) author = UserPublicSerializer(read_only=True) class Meta: model = Job fields = '__all__'
class JobListSerializer(serializers.ModelSerializer): price = MoneyField( max_digits=14, decimal_places=2, ) author = UserPublicSerializer(read_only=True) class Meta: model = Job fields = ('id', 'title', 'description', 'author', 'price', 'price_currency', 'is_price_fixed', 'views', 'deadline')
class ProductSerializer(rest.TransformDatesMixin, serializers.ModelSerializer): show_name = serializers.SerializerMethodField() price_purchase = MoneyField(max_digits=10, decimal_places=2) price_selling = MoneyField(max_digits=10, decimal_places=2) price_selling_alt = MoneyField(max_digits=10, decimal_places=2) price_purchase_ex = MoneyField(max_digits=10, decimal_places=2) price_selling_ex = MoneyField(max_digits=10, decimal_places=2) price_selling_alt_ex = MoneyField(max_digits=10, decimal_places=2) image = Base64ImageField(required=False) def get_show_name(self, obj): if hasattr(obj, 'show_name'): return obj.show_name() return '' class Meta: model = models.Product fields = ('id', 'identifier', 'show_name', 'name', 'name_short', 'unit', 'supplier', 'product_type', 'modified', 'price_purchase', 'price_selling', 'price_selling_alt', 'price_purchase_ex', 'price_selling_ex', 'price_selling_alt_ex', 'image')
class BookingSerializer(ModelSerializer): amusement_park_prebookings = PrimaryKeyRelatedField( many=True, queryset=AmusementParkPreBooking.objects.all(), ) museum_prebookings = PrimaryKeyRelatedField( many=True, queryset=MuseumPreBooking.objects.all(), ) restaurant_prebookings = PrimaryKeyRelatedField( many=True, queryset=RestaurantPreBooking.objects.all() ) payment_card_token = CharField(write_only=True) total = MoneyField(max_digits=6, decimal_places=2, read_only=True) reference = CharField(read_only=True) @staticmethod def validate_at_least_one_prebooking(data): keys = ( 'amusement_park_prebookings', 'museum_prebookings', 'restaurant_prebookings', ) if not any(data[key] for key in keys): raise ValidationError('At least one prebooking is required') return data def validate(self, data): data = super().validate(data) data = self.validate_at_least_one_prebooking(data) return data def create(self, validated_data) -> Booking: booking: Booking = super().create(validated_data) return booking class Meta: model = Booking read_only_fields = ( 'total', 'reference', ) fields = read_only_fields + ( 'amusement_park_prebookings', 'museum_prebookings', 'restaurant_prebookings', 'payment_card_token', )
class EmployeeRecordPaymentSerializer(serializers.ModelSerializer): employee = ProfileUsernameSerializer(read_only=True) amount = MoneyField(max_digits=14, decimal_places=2, read_only=True) class Meta: model = EmployeeRecordPayment fields = ( 'id', 'type', 'employee', 'amount', ) read_only_fields = fields
class CrateSerializer(serializers.ModelSerializer): crate_price = MoneyField(max_digits=10, decimal_places=2) bottle_price = MoneyField(max_digits=10, decimal_places=2, read_only=True) class Meta: model = Crate fields = ( "id", "billed", "billed_at", "billed_document", "created_at", "updated_at", "name", "crate_price", "bottle_price", "bottles", "calories", "bottle_contents", "bottle_calories", ) extra_kwargs = map_kwargs("billed", read_only=True)
class BookingSerializer(serializers.ModelSerializer): user_id = serializers.IntegerField() service = serializers.CharField() price = MoneyField(max_digits=14, decimal_places=2) date = serializers.DateField() time = serializers.CharField() def create(self, validated_data): # instance = self.Meta.model(**validated_data) print(validated_data) user = User.objects.get(id=validated_data['user_id']) service = ServiceNail.objects.get(service_ID=validated_data['service']) time = Slots.objects.get(slots_ID=validated_data['time']) profile = Profile.objects.get(user=user) #print(user) #print(profile.home_address) # print(profile.contact) time.is_booked = True time.save() instance = Booking.objects.create(user=user, price=validated_data['price'], date=validated_data['date'], service=service, time=time) instance.save() subject = 'iNailForFung Booking Confirmation' message = ( 'Thank you for choosing iNailForFung Services. I am pleased to confirm your booking as below:\n\n' + 'Username:'******'\n' + 'Price:' + str(validated_data['price']) + '\n' + 'Date of Booking:' + str(validated_data['date']) + '\n' + 'Service Type:' + str(service) + '\n' + 'Time Booked:' + str(time) + '\n' + 'Email:' + str(user.email) + '\n' + 'Contact:' + str(profile.contact) + '\n\n' + 'If you have to change or cancel your booking, please DM me at my instagram profile via the social media links in the website.' ) email_from = settings.EMAIL_HOST_USER recipient_list = [ user.email, ] send_mail(subject, message, email_from, recipient_list) return instance # return category class Meta: model = Booking fields = ('user_id', 'service', 'price', 'date', 'time')
class TransactionSerializer(serializers.ModelSerializer): amount_total = MoneyField(max_digits=10, decimal_places=2, read_only=True) crate_name = serializers.CharField(source="crate.name", read_only=True) class Meta: model = Transaction fields = ( "created_at", "updated_at", "id", "user", "crate", "crate_name", "amount", "amount_total", ) extra_kwargs = map_kwargs("user", read_only=True)
class SentOrderSerializer(ModelCleanFieldsSerializer): """The sent order serializer.""" client = serializers.HiddenField(default=serializers.CurrentUserDefault()) service = serializers.PrimaryKeyRelatedField( many=False, queryset=Service.objects.get_list().filter(is_enabled=True), ) service_location = serializers.PrimaryKeyRelatedField( many=False, queryset=ServiceLocation.objects.get_list(), allow_null=True, required=False, ) client_location = serializers.PrimaryKeyRelatedField( many=False, queryset=UserLocation.objects.get_list(), allow_null=True, required=False, ) price = serializers.HiddenField(default=None) price_amount = MoneyField( max_digits=settings.D8B_MONEY_MAX_DIGITS, decimal_places=settings.D8B_MONEY_DECIMAL_PLACES, read_only=True, source="price", ) end_datetime = serializers.DateTimeField(allow_null=True, required=False) class Meta: """The metainformation.""" model = Order fields = ("id", "service", "service_location", "client", "client_location", "status", "note", "price", "price_amount", "price_currency", "is_another_person", "first_name", "last_name", "phone", "start_datetime", "end_datetime", "duration", "created", "modified") read_only_fields = ("client", "status", "price", "price_currency", "duration", "created", "modified", "created_by", "modified_by")
class OrderItemSerializer(serializers.ModelSerializer): """ A Basic serializer for order items """ price = MoneyField(max_digits=14, decimal_places=2, required=False, allow_null=True, read_only=True) data_format = serializers.SlugRelatedField( required=False, queryset=DataFormat.objects.all(), slug_field='name') product = serializers.SlugRelatedField(queryset=Product.objects.all(), slug_field='label') product_id = serializers.PrimaryKeyRelatedField(read_only=True) available_formats = serializers.ListField(read_only=True) class Meta: model = OrderItem exclude = [ '_price_currency', '_price', '_base_fee_currency', '_base_fee', 'last_download', 'extract_result', 'validation_date', 'token' ] read_only_fields = ['price_status', 'order']
class PublicLoanSerializer(serializers.ModelSerializer): borrower = serializers.SerializerMethodField() amount = MoneyField(max_digits=19, decimal_places=2, source='original_amount') def get_borrower(self, obj): """ Get user details for public viewers of a loan """ borrower = obj.credit.user return { 'firstname': borrower.first_name, 'lastname': borrower.last_name } class Meta: model = Loan fields = ( 'amount', 'borrower', 'description', 'id', )
class TaskSerializer(serializers.HyperlinkedModelSerializer): questions = QuestionSerializer(many=True) og_image_link = serializers.URLField(read_only=True) user = UserDetailsSerializer(read_only=True) tx_hash = serializers.CharField(source='initial_tx_hash', required=False) type = serializers.CharField(read_only=True) reward_per_click = MoneyField(max_digits=11, decimal_places=5) reward_usd_per_click = MoneyField(max_digits=11, decimal_places=5, read_only=True) remaining_balance = MoneyField(max_digits=9, decimal_places=3, read_only=True) initial_budget = MoneyField(max_digits=9, decimal_places=3, required=False) class Meta: model = models.Task fields = [ 'id', 'title', 'description', 'chain', 'user', 'og_image_link', 'uuid', 'website_link', 'website_image', 'contract_address', 'reward_per_click', 'reward_usd_per_click', 'time_duration', 'questions', 'warning_message', 'is_active', 'remaining_balance', 'initial_budget', 'tx_hash', 'type', ] read_only_fields = [ 'user', 'og_image_link', 'remaining_balance', 'website_image', 'warning_message', 'is_active', ] def create(self, validated_data): validated_data['is_active_web3'] = False questions = validated_data.pop('questions') task = super(TaskSerializer, self).create(validated_data) for question in questions: q = models.Question.objects.create(title=question['title'], task=task) for option in question['options']: models.Option.objects.create(title=option['title'], question=q, is_correct=option.get( 'is_correct', False)) if task.initial_tx_hash: tasks.update_task_is_active_balance.delay( task_id=task.id, wait_for_tx=str(task.initial_tx_hash)) else: tasks.update_task_is_active_balance.delay(task_id=task.id, should_be_active=True, retry=10) tasks.create_task_screenshot.delay(task.id) return task def validate_website_link(self, website_link): return convert_url(website_link) def validate_questions(self, questions): for question in questions: # Validate number of correct options is max one correct_question_sum = sum([ option.get('is_correct', False) for option in question.get('options', []) ]) if not correct_question_sum <= 1: raise serializers.ValidationError({ 'question': f"Question {question.get('title', '')} has {correct_question_sum} correct questions." }) return questions def validate(self, attrs): website_link = attrs.get('website_link') if website_link: try: og = OpenGraph(url=website_link) except requests.exceptions.ConnectionError as e: L.warning(e) raise serializers.ValidationError( {'website_link': f'Connection error for {website_link}'}) if og.response.status_code != status.HTTP_200_OK: message = f'Website {website_link} responded with status code: {og.response.status_code}' L.warning(message) # raise serializers.ValidationError({'website_link': f'{message}. Has to be 200'}) attrs.update({ 'og_image_link': og.image, 'website_link': og.RESOLVED_URL or website_link, }) if og.X_FRAME_OPTIONS: attrs.update({ 'warning_message': f'Website has strict X-Frame-Options: {og.X_FRAME_OPTIONS}', }) return super().validate(attrs)
class BomItemSerializer(InvenTreeModelSerializer): """Serializer for BomItem object.""" price_range = serializers.CharField(read_only=True) quantity = InvenTreeDecimalField(required=True) def validate_quantity(self, quantity): """Perform validation for the BomItem quantity field""" if quantity <= 0: raise serializers.ValidationError( _("Quantity must be greater than zero")) return quantity part = serializers.PrimaryKeyRelatedField(queryset=Part.objects.filter( assembly=True)) substitutes = BomItemSubstituteSerializer(many=True, read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) sub_part = serializers.PrimaryKeyRelatedField(queryset=Part.objects.filter( component=True)) sub_part_detail = PartBriefSerializer(source='sub_part', many=False, read_only=True) validated = serializers.BooleanField(read_only=True, source='is_line_valid') purchase_price_min = MoneyField(max_digits=19, decimal_places=4, read_only=True) purchase_price_max = MoneyField(max_digits=19, decimal_places=4, read_only=True) purchase_price_avg = serializers.SerializerMethodField() purchase_price_range = serializers.SerializerMethodField() on_order = serializers.FloatField(read_only=True) # Annotated fields for available stock available_stock = serializers.FloatField(read_only=True) available_substitute_stock = serializers.FloatField(read_only=True) available_variant_stock = serializers.FloatField(read_only=True) def __init__(self, *args, **kwargs): """Determine if extra detail fields are to be annotated on this serializer - part_detail and sub_part_detail serializers are only included if requested. - This saves a bunch of database requests """ part_detail = kwargs.pop('part_detail', False) sub_part_detail = kwargs.pop('sub_part_detail', False) include_pricing = kwargs.pop('include_pricing', False) super(BomItemSerializer, self).__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if sub_part_detail is not True: self.fields.pop('sub_part_detail') if not include_pricing: # Remove all pricing related fields self.fields.pop('price_range') self.fields.pop('purchase_price_min') self.fields.pop('purchase_price_max') self.fields.pop('purchase_price_avg') self.fields.pop('purchase_price_range') @staticmethod def setup_eager_loading(queryset): """Prefetch against the provided queryset to speed up database access""" queryset = queryset.prefetch_related('part') queryset = queryset.prefetch_related('part__category') queryset = queryset.prefetch_related('part__stock_items') queryset = queryset.prefetch_related('sub_part') queryset = queryset.prefetch_related('sub_part__category') queryset = queryset.prefetch_related( 'sub_part__stock_items', 'sub_part__stock_items__allocations', 'sub_part__stock_items__sales_order_allocations', ) queryset = queryset.prefetch_related( 'substitutes', 'substitutes__part__stock_items', ) queryset = queryset.prefetch_related( 'sub_part__supplier_parts__pricebreaks') return queryset @staticmethod def annotate_queryset(queryset): """Annotate the BomItem queryset with extra information: Annotations: available_stock: The amount of stock available for the sub_part Part object """ """ Construct an "available stock" quantity: available_stock = total_stock - build_order_allocations - sales_order_allocations """ ref = 'sub_part__' # Annotate with the total "on order" amount for the sub-part queryset = queryset.annotate( on_order=part.filters.annotate_on_order_quantity(ref), ) # Calculate "total stock" for the referenced sub_part # Calculate the "build_order_allocations" for the sub_part # Note that these fields are only aliased, not annotated queryset = queryset.alias( total_stock=part.filters.annotate_total_stock(reference=ref), allocated_to_sales_orders=part.filters. annotate_sales_order_allocations(reference=ref), allocated_to_build_orders=part.filters. annotate_build_order_allocations(reference=ref), ) # Calculate 'available_stock' based on previously annotated fields queryset = queryset.annotate(available_stock=ExpressionWrapper( F('total_stock') - F('allocated_to_sales_orders') - F('allocated_to_build_orders'), output_field=models.DecimalField(), )) ref = 'substitutes__part__' # Extract similar information for any 'substitute' parts queryset = queryset.alias( substitute_stock=part.filters.annotate_total_stock(reference=ref), substitute_build_allocations=part.filters. annotate_build_order_allocations(reference=ref), substitute_sales_allocations=part.filters. annotate_sales_order_allocations(reference=ref)) # Calculate 'available_substitute_stock' field queryset = queryset.annotate( available_substitute_stock=ExpressionWrapper( F('substitute_stock') - F('substitute_build_allocations') - F('substitute_sales_allocations'), output_field=models.DecimalField(), )) # Annotate the queryset with 'available variant stock' information variant_stock_query = part.filters.variant_stock_query( reference='sub_part__') queryset = queryset.alias( variant_stock_total=part.filters.annotate_variant_quantity( variant_stock_query, reference='quantity'), variant_bo_allocations=part.filters.annotate_variant_quantity( variant_stock_query, reference='sales_order_allocations__quantity'), variant_so_allocations=part.filters.annotate_variant_quantity( variant_stock_query, reference='allocations__quantity'), ) queryset = queryset.annotate(available_variant_stock=ExpressionWrapper( F('variant_stock_total') - F('variant_bo_allocations') - F('variant_so_allocations'), output_field=FloatField(), )) return queryset def get_purchase_price_range(self, obj): """Return purchase price range.""" try: purchase_price_min = obj.purchase_price_min except AttributeError: return None try: purchase_price_max = obj.purchase_price_max except AttributeError: return None if purchase_price_min and not purchase_price_max: # Get price range purchase_price_range = str(purchase_price_max) elif not purchase_price_min and purchase_price_max: # Get price range purchase_price_range = str(purchase_price_max) elif purchase_price_min and purchase_price_max: # Get price range if purchase_price_min >= purchase_price_max: # If min > max: use min only purchase_price_range = str(purchase_price_min) else: purchase_price_range = str(purchase_price_min) + " - " + str( purchase_price_max) else: purchase_price_range = '-' return purchase_price_range def get_purchase_price_avg(self, obj): """Return purchase price average.""" try: purchase_price_avg = obj.purchase_price_avg except AttributeError: return None if purchase_price_avg: # Get string representation of price average purchase_price_avg = str(purchase_price_avg) else: purchase_price_avg = '-' return purchase_price_avg class Meta: """Metaclass defining serializer fields""" model = BomItem fields = [ 'allow_variants', 'inherited', 'note', 'optional', 'overage', 'pk', 'part', 'part_detail', 'purchase_price_avg', 'purchase_price_max', 'purchase_price_min', 'purchase_price_range', 'quantity', 'reference', 'sub_part', 'sub_part_detail', 'substitutes', 'price_range', 'validated', # Annotated fields describing available quantity 'available_stock', 'available_substitute_stock', 'available_variant_stock', # Annotated field describing quantity on order 'on_order', ]
class RecordSerializer(serializers.ModelSerializer): status_display = serializers.CharField(source='get_status_display', read_only=True) parlor = PrimaryKeyField( serializer=ParlorSerializer, queryset=Parlor.objects.all(), model=Parlor, ) created_by = PrimaryKeyField( serializer=ProfileSerializer, queryset=Profile.objects.all(), model=Profile, ) customer = PrimaryKeyField( serializer=CustomerSerializer, queryset=Customer.objects.all(), model=Customer, ) performer = PrimaryKeyField( serializer=ProfileSerializer, queryset=Profile.objects.all(), model=Profile, ) type = PrimaryKeyField( serializer=SessionTypeSerializer, queryset=SessionType.objects.all(), model=SessionType, ) sketch = Base64ImageField(required=False, allow_null=True) prepayments = PrepaymentSerializer(many=True, read_only=True) prepayment = MoneyField(write_only=True, allow_null=True, max_digits=14, decimal_places=2, default_currency=settings.DEFAULT_CURRENCY, min_value=Money( 0, currency=settings.DEFAULT_CURRENCY)) price = MoneyField(required=False, max_digits=14, decimal_places=2, default_currency=settings.DEFAULT_CURRENCY, min_value=Money(0, currency=settings.DEFAULT_CURRENCY)) used_coupon = PrimaryKeyField( serializer=CouponSerializer, queryset=Coupon.objects.all(), model=Coupon, required=False, allow_null=True, ) employee_payments = EmployeeRecordPaymentSerializer(many=True, read_only=True) class Meta: model = Record fields = ( 'id', 'status', 'status_display', 'status_changed', 'created_at', 'created_by', 'parlor', 'customer', 'performer', 'type', 'datetime', 'approximate_time', 'comment', 'sketch', 'prepayments', 'prepayment', 'reason', 'rollback_prepayment', 'price', 'used_coupon', 'employee_payments', ) extra_kwargs = { 'status': { 'required': False }, 'status_changed': { 'read_only': True }, 'created_at': { 'read_only': True }, 'rollback_prepayment': { 'required': False }, 'reason': { 'required': False }, } def validate(self, attrs): if self.context['request'].method == "PATCH": if (status := attrs.get('status', None)) is not None: if status == Record.STATUS.canceled: if attrs.get('reason', None) is None: # Показать ошибку, если не указана причина отмены записи. raise serializers.ValidationError( {'reason': settings.STRINGS['REQUIRED']}) if attrs.get('rollback_prepayment', None) is None: # Показать ошибку, если не указан тип возврата предоплаты. raise serializers.ValidationError({ 'rollback_prepayment': settings.STRINGS['REQUIRED'] }) elif status == Record.STATUS.in_work: # Показать ошибку, если нет мастера и пытаемся начать сеанс. if not self.instance.performer: raise serializers.ValidationError( "Перед началом сеанса выберите исполнителя!") elif status == Record.STATUS.finished: if attrs.get('price', None) is None: raise serializers.ValidationError( {'price': settings.STRINGS['REQUIRED']}) elif self.instance.status not in Record.EDIT_STATUSES: # Если статус записи не "Новый" и не "Ожидается" - запретить изменение данных. raise serializers.ValidationError( f"Нельзя изменить запись в статусе: {self.instance.get_status_display()}" ) if (_type := attrs.get('type', None)) is not None: if self.instance.performer: if not self.instance.performer.session_motivations.filter( session_type=_type).exists(): # Обнулить исполнителя, если у текущего нет мотивации за сеанс. attrs['performer'] = None
class BomItemSerializer(InvenTreeModelSerializer): """ Serializer for BomItem object """ price_range = serializers.CharField(read_only=True) quantity = serializers.FloatField() part = serializers.PrimaryKeyRelatedField(queryset=Part.objects.filter( assembly=True)) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) sub_part = serializers.PrimaryKeyRelatedField(queryset=Part.objects.filter( component=True)) sub_part_detail = PartBriefSerializer(source='sub_part', many=False, read_only=True) validated = serializers.BooleanField(read_only=True, source='is_line_valid') purchase_price_min = MoneyField(max_digits=10, decimal_places=6, read_only=True) purchase_price_max = MoneyField(max_digits=10, decimal_places=6, read_only=True) purchase_price_avg = serializers.SerializerMethodField() purchase_price_range = serializers.SerializerMethodField() def __init__(self, *args, **kwargs): # part_detail and sub_part_detail serializers are only included if requested. # This saves a bunch of database requests part_detail = kwargs.pop('part_detail', False) sub_part_detail = kwargs.pop('sub_part_detail', False) super(BomItemSerializer, self).__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if sub_part_detail is not True: self.fields.pop('sub_part_detail') @staticmethod def setup_eager_loading(queryset): queryset = queryset.prefetch_related('part') queryset = queryset.prefetch_related('part__category') queryset = queryset.prefetch_related('part__stock_items') queryset = queryset.prefetch_related('sub_part') queryset = queryset.prefetch_related('sub_part__category') queryset = queryset.prefetch_related('sub_part__stock_items') queryset = queryset.prefetch_related( 'sub_part__supplier_parts__pricebreaks') return queryset def get_purchase_price_range(self, obj): """ Return purchase price range """ try: purchase_price_min = obj.purchase_price_min except AttributeError: return None try: purchase_price_max = obj.purchase_price_max except AttributeError: return None if purchase_price_min and not purchase_price_max: # Get price range purchase_price_range = str(purchase_price_max) elif not purchase_price_min and purchase_price_max: # Get price range purchase_price_range = str(purchase_price_max) elif purchase_price_min and purchase_price_max: # Get price range if purchase_price_min >= purchase_price_max: # If min > max: use min only purchase_price_range = str(purchase_price_min) else: purchase_price_range = str(purchase_price_min) + " - " + str( purchase_price_max) else: purchase_price_range = '-' return purchase_price_range def get_purchase_price_avg(self, obj): """ Return purchase price average """ try: purchase_price_avg = obj.purchase_price_avg except AttributeError: return None if purchase_price_avg: # Get string representation of price average purchase_price_avg = str(purchase_price_avg) else: purchase_price_avg = '-' return purchase_price_avg class Meta: model = BomItem fields = [ 'allow_variants', 'inherited', 'note', 'optional', 'overage', 'pk', 'part', 'part_detail', 'purchase_price_avg', 'purchase_price_max', 'purchase_price_min', 'purchase_price_range', 'quantity', 'reference', 'sub_part', 'sub_part_detail', 'price_range', 'validated', ]
class BomItemSerializer(InvenTreeModelSerializer): """ Serializer for BomItem object """ price_range = serializers.CharField(read_only=True) quantity = InvenTreeDecimalField(required=True) def validate_quantity(self, quantity): if quantity <= 0: raise serializers.ValidationError( _("Quantity must be greater than zero")) return quantity part = serializers.PrimaryKeyRelatedField(queryset=Part.objects.filter( assembly=True)) substitutes = BomItemSubstituteSerializer(many=True, read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) sub_part = serializers.PrimaryKeyRelatedField(queryset=Part.objects.filter( component=True)) sub_part_detail = PartBriefSerializer(source='sub_part', many=False, read_only=True) validated = serializers.BooleanField(read_only=True, source='is_line_valid') purchase_price_min = MoneyField(max_digits=19, decimal_places=4, read_only=True) purchase_price_max = MoneyField(max_digits=19, decimal_places=4, read_only=True) purchase_price_avg = serializers.SerializerMethodField() purchase_price_range = serializers.SerializerMethodField() # Annotated fields for available stock available_stock = serializers.FloatField(read_only=True) available_substitute_stock = serializers.FloatField(read_only=True) available_variant_stock = serializers.FloatField(read_only=True) def __init__(self, *args, **kwargs): # part_detail and sub_part_detail serializers are only included if requested. # This saves a bunch of database requests part_detail = kwargs.pop('part_detail', False) sub_part_detail = kwargs.pop('sub_part_detail', False) include_pricing = kwargs.pop('include_pricing', False) super(BomItemSerializer, self).__init__(*args, **kwargs) if part_detail is not True: self.fields.pop('part_detail') if sub_part_detail is not True: self.fields.pop('sub_part_detail') if not include_pricing: # Remove all pricing related fields self.fields.pop('price_range') self.fields.pop('purchase_price_min') self.fields.pop('purchase_price_max') self.fields.pop('purchase_price_avg') self.fields.pop('purchase_price_range') @staticmethod def setup_eager_loading(queryset): queryset = queryset.prefetch_related('part') queryset = queryset.prefetch_related('part__category') queryset = queryset.prefetch_related('part__stock_items') queryset = queryset.prefetch_related('sub_part') queryset = queryset.prefetch_related('sub_part__category') queryset = queryset.prefetch_related( 'sub_part__stock_items', 'sub_part__stock_items__allocations', 'sub_part__stock_items__sales_order_allocations', ) queryset = queryset.prefetch_related( 'substitutes', 'substitutes__part__stock_items', ) queryset = queryset.prefetch_related( 'sub_part__supplier_parts__pricebreaks') return queryset @staticmethod def annotate_queryset(queryset): """ Annotate the BomItem queryset with extra information: Annotations: available_stock: The amount of stock available for the sub_part Part object """ """ Construct an "available stock" quantity: available_stock = total_stock - build_order_allocations - sales_order_allocations """ build_order_filter = Q(build__status__in=BuildStatus.ACTIVE_CODES) sales_order_filter = Q( line__order__status__in=SalesOrderStatus.OPEN, shipment__shipment_date=None, ) # Calculate "total stock" for the referenced sub_part # Calculate the "build_order_allocations" for the sub_part # Note that these fields are only aliased, not annotated queryset = queryset.alias( total_stock=Coalesce( SubquerySum('sub_part__stock_items__quantity', filter=StockItem.IN_STOCK_FILTER), Decimal(0), output_field=models.DecimalField(), ), allocated_to_sales_orders=Coalesce( SubquerySum( 'sub_part__stock_items__sales_order_allocations__quantity', filter=sales_order_filter, ), Decimal(0), output_field=models.DecimalField(), ), allocated_to_build_orders=Coalesce( SubquerySum( 'sub_part__stock_items__allocations__quantity', filter=build_order_filter, ), Decimal(0), output_field=models.DecimalField(), ), ) # Calculate 'available_stock' based on previously annotated fields queryset = queryset.annotate(available_stock=ExpressionWrapper( F('total_stock') - F('allocated_to_sales_orders') - F('allocated_to_build_orders'), output_field=models.DecimalField(), )) # Extract similar information for any 'substitute' parts queryset = queryset.alias( substitute_stock=Coalesce( SubquerySum( 'substitutes__part__stock_items__quantity', filter=StockItem.IN_STOCK_FILTER, ), Decimal(0), output_field=models.DecimalField(), ), substitute_build_allocations=Coalesce( SubquerySum( 'substitutes__part__stock_items__allocations__quantity', filter=build_order_filter, ), Decimal(0), output_field=models.DecimalField(), ), substitute_sales_allocations=Coalesce( SubquerySum( 'substitutes__part__stock_items__sales_order_allocations__quantity', filter=sales_order_filter, ), Decimal(0), output_field=models.DecimalField(), ), ) # Calculate 'available_substitute_stock' field queryset = queryset.annotate( available_substitute_stock=ExpressionWrapper( F('substitute_stock') - F('substitute_build_allocations') - F('substitute_sales_allocations'), output_field=models.DecimalField(), )) # Annotate the queryset with 'available variant stock' information variant_stock_query = StockItem.objects.filter( part__tree_id=OuterRef('sub_part__tree_id'), part__lft__gt=OuterRef('sub_part__lft'), part__rght__lt=OuterRef('sub_part__rght'), ).filter(StockItem.IN_STOCK_FILTER) queryset = queryset.alias( variant_stock_total=Coalesce(Subquery( variant_stock_query.annotate(total=Func( F('quantity'), function='SUM', output_field=FloatField())).values('total')), 0, output_field=FloatField()), variant_stock_build_order_allocations=Coalesce( Subquery( variant_stock_query.annotate(total=Func( F('sales_order_allocations__quantity'), function='SUM', output_field=FloatField()), ).values('total')), 0, output_field=FloatField(), ), variant_stock_sales_order_allocations=Coalesce( Subquery( variant_stock_query.annotate(total=Func( F('allocations__quantity'), function='SUM', output_field=FloatField()), ).values('total')), 0, output_field=FloatField(), )) queryset = queryset.annotate(available_variant_stock=ExpressionWrapper( F('variant_stock_total') - F('variant_stock_build_order_allocations') - F('variant_stock_sales_order_allocations'), output_field=FloatField(), )) return queryset def get_purchase_price_range(self, obj): """ Return purchase price range """ try: purchase_price_min = obj.purchase_price_min except AttributeError: return None try: purchase_price_max = obj.purchase_price_max except AttributeError: return None if purchase_price_min and not purchase_price_max: # Get price range purchase_price_range = str(purchase_price_max) elif not purchase_price_min and purchase_price_max: # Get price range purchase_price_range = str(purchase_price_max) elif purchase_price_min and purchase_price_max: # Get price range if purchase_price_min >= purchase_price_max: # If min > max: use min only purchase_price_range = str(purchase_price_min) else: purchase_price_range = str(purchase_price_min) + " - " + str( purchase_price_max) else: purchase_price_range = '-' return purchase_price_range def get_purchase_price_avg(self, obj): """ Return purchase price average """ try: purchase_price_avg = obj.purchase_price_avg except AttributeError: return None if purchase_price_avg: # Get string representation of price average purchase_price_avg = str(purchase_price_avg) else: purchase_price_avg = '-' return purchase_price_avg class Meta: model = BomItem fields = [ 'allow_variants', 'inherited', 'note', 'optional', 'overage', 'pk', 'part', 'part_detail', 'purchase_price_avg', 'purchase_price_max', 'purchase_price_min', 'purchase_price_range', 'quantity', 'reference', 'sub_part', 'sub_part_detail', 'substitutes', 'price_range', 'validated', # Annotated fields describing available quantity 'available_stock', 'available_substitute_stock', 'available_variant_stock', ]
class AbstractPaymentEntitySerializer(serializers.ModelSerializer): created_by = UsernameSerializer(read_only=True) status_changed_by = UsernameSerializer(read_only=True) employee = PrimaryKeyField(serializer=ProfileUsernameSerializer, model=Profile, queryset=Profile.objects.all()) amount = MoneyField(max_digits=14, decimal_places=2) class Meta: fields = ( 'id', 'created_by', 'created_at', 'month', 'year', 'type', 'employee', 'amount', 'note', 'status', 'href', 'status_changed_by', 'status_changed_at', 'status_change_reason', ) read_only_fields = ('created_at', 'status_changed_at') extra_kwargs = { 'status': { 'allow_null': True }, 'status_change_reason': { 'required': False }, 'month': { 'required': False }, 'year': { 'required': False }, } def validate(self, attrs): if self.context['request'].method == "PATCH": if attrs.get('status') is False: if not attrs.get('status_change_reason'): raise serializers.ValidationError( {'status_change_reason': _("Укажите причину отмены.")}) return attrs def update(self, instance, validated_data): if (status := validated_data.get('status', "null")) != "null": timestamp = timezone.now() user = self.context['request'].user return self.Meta.model.proceed_status( entity_pk=instance.id, status=status, user=user, timestamp=timestamp, status_change_reason=validated_data.get( 'status_change_reason', None), ) return super(AbstractPaymentEntitySerializer, self).update(instance, validated_data)