from django.db import models from django.db.models import Q from django.db.transaction import atomic from rest_framework import serializers, status from apps.herd.models import Rancher from apps.herd.pos.api.v1.serializers import RancherSerializer from apps.herd.services.services import get_rancher_statistics, rancher_quota_weight from apps.pos_device.models import POSFreeProducts from apps.pos_device.pos.api.v1.serializers.device import DeviceSerializer from apps.pos_device.services.services import pos_organizations_sharing_information from apps.product.exceptions import QuotaSaleTimeException from apps.product.models import ( QuotaDistribution, Product, OrganizationQuotaStats ) from apps.product.services.services import ( quota_live_stock_allocation_info, quota_incentive_plans_info, quota_attribute_value ) from apps.warehouse import models as warehouse_models from apps.warehouse.exceptions import WareHouseException from apps.warehouse.services.quota_usage_services import QuotaUsageService from apps.warehouse.services.services import create_extra_sale, create_pre_sale class InventoryEntrySerializer(serializers.ModelSerializer): class Meta: model = warehouse_models.InventoryEntry fields = [ "id", "entry_identity", "create_date", "modify_date", "organization", "distribution", "weight", "balance", "lading_number", "delivery_address", "is_confirmed", "notes", ] def to_representation(self, instance): """ custom output of inventory entry serializer """ representation = super().to_representation(instance) if instance.document: representation['document'] = instance.document if instance.distribution: # distribution data representation['distribution'] = { 'distribution_identity': instance.distribution.distribution_id, 'sale_unit': instance.distribution.quota.sale_unit.unit, 'id': instance.distribution.id } representation['quota'] = { 'quota_identity': instance.distribution.quota.quota_id, 'quota_weight': instance.distribution.quota.quota_weight, 'quota_livestock_allocations': quota_live_stock_allocation_info( instance.distribution.quota ), 'quota_incentive_plans': quota_incentive_plans_info(instance.distribution.quota) } representation['product'] = { 'image': instance.distribution.quota.product.img, 'name': instance.distribution.quota.product.name, 'id': instance.distribution.quota.product.id, } representation['pricing'] = { 'pricing_attributes': quota_attribute_value(instance.distribution.quota), 'sharing': pos_organizations_sharing_information( self.context['device'], instance.distribution.quota, instance.distribution ), 'base_prices': [ { "text": "درب کارخانه", # noqa "name": "base_price_factory", "value": instance.distribution.quota.base_price_factory }, { "text": "درب اتحادیه", # noqa "name": "base_price_cooperative", "value": instance.distribution.quota.base_price_cooperative } ] } if 'rancher' in self.context.keys(): # rancher herd & live stock statistics representation['rancher_statistics'] = get_rancher_statistics(self.context['rancher']) # rancher live stock statistics by inventory entry representation['rancher_quota_weight_statistics'] = rancher_quota_weight( self.context['rancher'], instance ) return representation class InventoryQuotaSaleTransactionSerializer(serializers.ModelSerializer): rancher_national_code = serializers.CharField(max_length=50, required=False) class Meta: # noqa model = warehouse_models.InventoryQuotaSaleTransaction fields = '__all__' depth = 0 def create(self, validated_data): items_data = self.context['request'].data['items'] rancher_code = validated_data.pop( 'rancher_national_code' ) if 'rancher_national_code' in self.context['request'].data.keys() else None with atomic(): # get rancher with national code rancher = None # noqa if rancher_code: rancher = Rancher.objects.get(national_code=rancher_code) validated_data['rancher'] = rancher # if transaction exists, update transaction status transaction = self.Meta.model.objects.filter( transaction_id=validated_data.get('transaction_id') ) if transaction.exists(): transaction = transaction.first() # --- Case 1: success => only update items if transaction.transaction_status == 'success': for item_data in items_data: qs = warehouse_models.InventoryQuotaSaleItem.objects.filter( Q(free_product_id=item_data.get('free_product', None)), Q(gov_product_id=item_data.get('gov_product', None)), transaction=transaction, ).update(**item_data) return transaction # --- Case 2: not success => update transaction fields + items for field in [ 'transaction_status', 'transaction_status_code', 'result_text', 'ref_num', 'terminal', 'payer_cart', 'pos_date', 'transaction_date', ]: if field in validated_data: setattr(transaction, field, validated_data[field]) transaction.save(update_fields=[ 'transaction_status', 'transaction_status_code', 'result_text', 'ref_num', 'terminal', 'payer_cart', 'pos_date', 'transaction_date', ]) # items can change for item_data in items_data: items = warehouse_models.InventoryQuotaSaleItem.objects.filter( Q(free_product_id=item_data.get('free_product', None)), Q(gov_product_id=item_data.get('gov_product', None)), transaction=transaction, ) items.update(**item_data) # if transaction status updated as success, call signal for inventory management if validated_data['transaction_status'] == 'success': for sale_item in items: sale_item.inventory_calculation = True sale_item.save() return transaction # --- Case 3: create new transaction transaction = warehouse_models.InventoryQuotaSaleTransaction.objects.create( seller_organization=self.context['organization'], pos_device=self.context['pos_device'], **validated_data ) # calculate total price of product items in shopping cart total_price = 0 for item_data in items_data: # get product by type gov_product = item_data.pop('gov_product', None) free_product = item_data.pop('free_product', None) distribution_id = item_data.pop('quota_distribution') distribution = QuotaDistribution.objects.filter( id=distribution_id ).first() if 'quota_distribution' in item_data.keys() else None quota_stat_id = item_data.pop('quota_stat') quota_stat = OrganizationQuotaStats.objects.get(id=quota_stat_id) # create item for transaction item = warehouse_models.InventoryQuotaSaleItem.objects.create( transaction=transaction, quota_distribution=distribution, quota_stat=quota_stat, gov_product=Product.objects.get( id=gov_product ) if Product.objects.filter(id=gov_product).exists() else None, free_product=POSFreeProducts.objects.get( id=free_product ) if POSFreeProducts.objects.filter(id=free_product).exists() else None, **item_data ) total_price += item.total_price # IF WE DO NOT HAVE DISTRIBUTION, THEN IT IS A FREE PRODUCT TRANSACTION if distribution_id and quota_stat_id in item_data.keys(): # create extra sale for distribution create_extra_sale(transaction=transaction, sale_item=item) # create pre sale for distribution create_pre_sale(transaction=transaction, sale_item=item) # calculate quota usage of rancher QuotaUsageService.allocate_usage( rancher=rancher, distribution=distribution, quota_stat=quota_stat, item_data=item_data ) transaction.transaction_price = total_price transaction.save() return transaction def validate(self, attrs): """ validate total inventory sale should be fewer than distribution weight """ items = self.context['request'].data['items'] for item in items: if 'quota_stat' in item.keys(): # get quota stat object quota_stat = OrganizationQuotaStats.objects.get( id=item.get('quota_stat') ) if not quota_stat.quota.pre_sale and not quota_stat.quota.free_sale: # if quota has not been in sale time if not quota_stat.quota.is_in_sale_licence_time(): raise QuotaSaleTimeException() total_sale_weight = quota_stat.sale_items.aggregate( total=models.Sum('weight') )['total'] or 0 if total_sale_weight + item.get('weight') > quota_stat.remaining_amount: raise WareHouseException( "وزن وارد شده بیشتر از باقیمانده سهمیه میباشد", # noqa status_code=status.HTTP_403_FORBIDDEN ) return attrs def to_representation(self, instance): """ customize output of transactions serializer """ representation = super().to_representation(instance) if instance.rancher: representation['rancher'] = RancherSerializer(instance.rancher).data representation['pos_device'] = DeviceSerializer(instance.pos_device).data if instance.seller_organization: representation['seller_organization'] = instance.seller_organization.name # get product items of transaction representation['items'] = InventoryQuotaSaleItemSerializer(instance.items.all(), many=True).data return representation class InventoryQuotaSaleItemSerializer(serializers.ModelSerializer): product_name = serializers.CharField(source='product.name', read_only=True) class Meta: model = warehouse_models.InventoryQuotaSaleItem fields = [ 'id', "transaction", "quota_stat", "gov_product", "free_product", "product_name", "image", "name", "price_type", "delivery_type", "item_type", "paid_type", "weight", "unit_price", "total_price", "paid_price", "livestock_statistic", "item_share", ] class QuotaPreSaleItemSerializer(serializers.ModelSerializer): class Meta: model = warehouse_models.QuotaPreSaleItem fields = '__all__'