import - distributioin stat type for distribution tab

This commit is contained in:
2025-12-09 11:28:06 +03:30
parent f4958e657e
commit 9aef4967c7
4 changed files with 107 additions and 31 deletions

View File

@@ -12,7 +12,7 @@ class Command(BaseCommand):
quotas = Quota.objects.prefetch_related('assigned_organizations').select_related( quotas = Quota.objects.prefetch_related('assigned_organizations').select_related(
'registerer_organization', 'product' 'registerer_organization', 'product'
) )
print("sssss")
created = 0 created = 0
existing = 0 existing = 0
updated = 0 updated = 0
@@ -43,23 +43,24 @@ class Command(BaseCommand):
qs.exclude(id=first.id).delete() qs.exclude(id=first.id).delete()
existing += 1 existing += 1
continue # continue
# Create new one # Create new one
quota_stat = OrganizationQuotaStats.objects.create( # quota_stat = OrganizationQuotaStats.objects.create(
quota=quota, # quota=quota,
organization=org, # organization=org,
total_amount=quota.quota_weight, # total_amount=quota.quota_weight,
remaining_amount=quota.remaining_weight, # remaining_amount=quota.remaining_weight,
total_distributed=quota.quota_distributed, # total_distributed=quota.quota_distributed,
stat_type="quota" # stat_type="quota"
) # )
created += 1 # created += 1
# ---- 2) Create OrganizationQuotaStats for each QuotaDistribution ---- # ---- 2) Create OrganizationQuotaStats for each QuotaDistribution ----
distributions = QuotaDistribution.objects.select_related( distributions = QuotaDistribution.objects.select_related(
"assigned_organization", "quota" "assigned_organization", "quota"
) )
print(len(distributions))
for dist in distributions: for dist in distributions:
org = dist.assigned_organization org = dist.assigned_organization
@@ -68,27 +69,27 @@ class Command(BaseCommand):
if not quota or not org: if not quota or not org:
continue continue
qs = OrganizationQuotaStats.objects.filter( dist_qs = OrganizationQuotaStats.objects.filter(
quota=quota, organization=org quota=quota, organization=org
) )
if qs.exists(): if dist_qs.exists():
if qs.count() > 1: if dist_qs.count() > 1:
first = qs.first() first = dist_qs.first()
qs.exclude(id=first.id).delete() dist_qs.exclude(id=first.id).delete()
merged += 1 merged += 1
# Update existing record with distribution weight (optional) # Update existing record with distribution weight (optional)
record = qs.first() # record = qs.first()
record.total_distributed += dist.weight # record.total_distributed += dist.weight
record.total_amount += dist.weight # record.total_amount += dist.weight
record.remaining_amount = max(record.total_amount - record.sold_amount, 0) # record.remaining_amount = max(record.total_amount - record.sold_amount, 0)
record.save() # record.save()
updated += 1 # updated += 1
else: else:
# Create new stats for this distribution # Create new stats for this distribution
dist_stat = OrganizationQuotaStats.objects.create( dist_qs = OrganizationQuotaStats.objects.create(
quota=quota, quota=quota,
organization=org, organization=org,
total_amount=dist.weight, total_amount=dist.weight,
@@ -98,7 +99,7 @@ class Command(BaseCommand):
pre_sale_balance=dist.pre_sale_balance, pre_sale_balance=dist.pre_sale_balance,
stat_type="distribution", stat_type="distribution",
) )
quota_stat.distributions.add(dist) qs.first().distributions.add(dist)
created += 1 created += 1
self.stdout.write( self.stdout.write(

View File

@@ -16,14 +16,27 @@ class QuotaDashboardService:
def get_dashboard(self, org: Organization, start_date: str = None, end_date: str = None, def get_dashboard(self, org: Organization, start_date: str = None, end_date: str = None,
search_fields: list[str] = None, quota_is_closed: bool = False, query_string: str = None): search_fields: list[str] = None, quota_is_closed: bool = False, query_string: str = None):
distribution_number = 0
if org.type.key == 'ADM': if org.type.key == 'ADM':
org_quota_stats = OrganizationQuotaStats.objects.filter(stat_type='quota', quota__is_closed=quota_is_closed) org_quota_stats = OrganizationQuotaStats.objects.filter(
print(len(org_quota_stats), quota_is_closed) stat_type='quota',
quota__is_closed=quota_is_closed
)
dist_org_quota_stats = OrganizationQuotaStats.objects.filter(
stat_type='distribution',
quota__is_closed=quota_is_closed
)
else: else:
org_quota_stats = OrganizationQuotaStats.objects.filter( org_quota_stats = OrganizationQuotaStats.objects.filter(
organization=org, organization=org,
quota__is_closed=quota_is_closed, quota__is_closed=quota_is_closed,
) )
dist_org_quota_stats = OrganizationQuotaStats.objects.filter(
stat_type='distribution',
organization=org,
quota__is_closed=quota_is_closed
)
# filter queryset (transactions & items) by date # filter queryset (transactions & items) by date
if (start_date and end_date) or query_string: if (start_date and end_date) or query_string:
@@ -36,6 +49,19 @@ class QuotaDashboardService:
query_string=query_string query_string=query_string
).apply() ).apply()
for stat in org_quota_stats.distinct('quota'):
if org.type.key == 'ADM':
distribution_number += QuotaDistribution.objects.select_related('quota').filter(
quota=stat.quota,
quota__is_closed=False
).count()
else:
distribution_number += QuotaDistribution.objects.filter(
Q(assigner_organization=org),
quota=stat.quota,
quota__is_closed=False
).count()
org_quota_stats = org_quota_stats.aggregate( org_quota_stats = org_quota_stats.aggregate(
total_quotas=Count("quota", distinct=True), total_quotas=Count("quota", distinct=True),
total_distributed=Coalesce(Sum("total_distributed", ), 0), total_distributed=Coalesce(Sum("total_distributed", ), 0),
@@ -46,12 +72,25 @@ class QuotaDashboardService:
inventory_entry_balance=Coalesce(Sum("inventory_entry_balance", ), 0), inventory_entry_balance=Coalesce(Sum("inventory_entry_balance", ), 0),
) )
org_quota_stats.update(
distribution_number=distribution_number,
dist_remaining_amount=dist_org_quota_stats.aggregate(
remaining_amount=Coalesce(Sum("remaining_amount"), 0), )['remaining_amount'] or 0,
)
return { return {
"quotas_summary": org_quota_stats, "quotas_summary": org_quota_stats,
} }
@staticmethod @staticmethod
def get_dashboard_by_product(self, organization: Organization, products: dict): def get_dashboard_by_product(
self,
organization: Organization,
products: dict,
start_date: str = None, end_date: str = None,
search_fields: list[str] = None, quota_is_closed: bool = False,
query_string: str = None
):
stat_by_prod = [] stat_by_prod = []
for prod_name, prod_id in products.items(): for prod_name, prod_id in products.items():
@@ -59,6 +98,18 @@ class QuotaDashboardService:
organization=organization, organization=organization,
quota__product_id=prod_id quota__product_id=prod_id
) )
# filter queryset (transactions & items) by date
if (start_date and end_date) or query_string:
org_quota_stats = DynamicSearchService(
queryset=org_quota_stat,
start=start_date,
end=end_date,
date_field="create_date",
search_fields=search_fields,
query_string=query_string
).apply()
product_stat_data = org_quota_stat.aggregate( product_stat_data = org_quota_stat.aggregate(
quotas_count=Count('id'), quotas_count=Count('id'),
total_quotas_weight=Coalesce(models.Sum('total_amount'), 0), total_quotas_weight=Coalesce(models.Sum('total_amount'), 0),
@@ -88,13 +139,13 @@ class QuotaDashboardService:
# product total distributed weight from quota # product total distributed weight from quota
given_distribution_weight = QuotaDistribution.objects.select_related( given_distribution_weight = QuotaDistribution.objects.select_related(
'quota', 'assigned_organization' 'quota', 'assigner_organization'
).filter( ).filter(
quota__product_id=prod_id, quota__product_id=prod_id,
quota__is_closed=False, quota__is_closed=False,
assigner_organization=organization, assigner_organization=organization,
) if not organization.type.key == 'ADM' else QuotaDistribution.objects.select_related( ) if not organization.type.key == 'ADM' else QuotaDistribution.objects.select_related(
'quota', 'assigned_organization' 'quota', 'assigner_organization'
).filter( ).filter(
quota__product_id=prod_id, quota__product_id=prod_id,
quota__is_closed=False, quota__is_closed=False,

View File

@@ -410,12 +410,23 @@ class QuotaViewSet(BaseViewSet, SoftDeleteMixin, QuotaDashboardService, viewsets
dashboard of all quotas & their information dashboard of all quotas & their information
""" """
search_fields = [
"quota__registerer_organization__name",
"quota__quota_id",
"quota__product__name",
"quota__sale_type",
"quota__sale_unit__unit",
"quota__group",
"quota__creator_info",
]
org = get_organization_by_user(request.user) org = get_organization_by_user(request.user)
query_param = self.request.query_params # noqa query_param = self.request.query_params # noqa
# filter by date # filter by date
start_date = query_param.get('start') if 'start' in query_param.keys() else None start_date = query_param.get('start') if 'start' in query_param.keys() else None
end_date = query_param.get('end') if 'end' in query_param.keys() else None end_date = query_param.get('end') if 'end' in query_param.keys() else None
query_string = query_param.get('search') if 'search' in query_param.keys() else None
# filter by quota is close or open # filter by quota is close or open
is_closed = True if 'is_closed' in query_param.keys() else False is_closed = True if 'is_closed' in query_param.keys() else False
@@ -428,7 +439,16 @@ class QuotaViewSet(BaseViewSet, SoftDeleteMixin, QuotaDashboardService, viewsets
products = {f'{stat.quota.product.name}': stat.quota.product.id for stat in org_quota_stat} products = {f'{stat.quota.product.name}': stat.quota.product.id for stat in org_quota_stat}
dashboard_data_by_product = self.get_dashboard_by_product(self, org, products) dashboard_data_by_product = self.get_dashboard_by_product(
self,
org,
products,
start_date=start_date,
end_date=end_date,
search_fields=search_fields,
quota_is_closed=is_closed,
query_string=query_string
)
return Response(dashboard_data_by_product) return Response(dashboard_data_by_product)

View File

@@ -126,7 +126,11 @@ class InventoryQuotaSaleTransactionSerializer(serializers.ModelSerializer):
'id': instance.rancher.id, 'id': instance.rancher.id,
'national_code': instance.rancher.national_code 'national_code': instance.rancher.national_code
} }
if instance.pos_device:
representation['pos_device'] = {
'device_identity': instance.pos_device.device_identity,
'serial': instance.pos_device.serial
}
if instance.seller_organization: if instance.seller_organization:
representation['seller_organization'] = { representation['seller_organization'] = {
'id': instance.seller_organization.id, 'id': instance.seller_organization.id,