diff --git a/.env.local b/.env.local index 3402c15..13815d0 100644 --- a/.env.local +++ b/.env.local @@ -7,7 +7,7 @@ ENV_NAME=DEV # Database secrets DB_HOST=31.7.78.133 DB_PORT=14352 -DB_NAME=Production +DB_NAME=Development DB_USERNAME=postgres DB_PASSWORD=pfLIVXupbDetvFMt2gUvxLXUL9b4HIOHaPcKXsBEZ1i8zl0iLUjmhUfXlGfJKcTV diff --git a/apps/herd/management/commands/rebuild_herd_livestock_counts.py b/apps/herd/management/commands/rebuild_herd_livestock_counts.py new file mode 100644 index 0000000..1fbde3c --- /dev/null +++ b/apps/herd/management/commands/rebuild_herd_livestock_counts.py @@ -0,0 +1,57 @@ +from collections import defaultdict + +from django.core.management.base import BaseCommand +from django.db import transaction +from django.db.models.aggregates import Count + +from apps.herd.models import Herd +from apps.livestock.models import LiveStock + + +class Command(BaseCommand): + help = "Rebuild heavy/light counts for all herds" + BATCH_SIZE = 2000 + + def handle(self, *args, **options): + self.stdout.write("🔄 Rebuilding herd livestock counts...") + + self.stdout.write("📊 Aggregating livestock data...") + livestock_stats = ( + LiveStock.objects + .filter(archive=False, herd__isnull=False) + .values('herd_id', 'weight_type') + .annotate(cnt=Count('id')) + ) + + herd_map = defaultdict(lambda: {"H": 0, "L": 0}) + + for row in livestock_stats: + herd_map[row['herd_id']][row['weight_type']] = row['cnt'] + + self.stdout.write(f"✅ Aggregated {len(herd_map)} herds") + + self.stdout.write("✍️ Updating herds in batches...") + + herd_ids = list(herd_map.keys()) + + for i in range(0, len(herd_ids), self.BATCH_SIZE): + batch_ids = herd_ids[i:i + self.BATCH_SIZE] + + herds = Herd.objects.filter(id__in=batch_ids) + + for herd in herds: + counts = herd_map.get(herd.id, {}) + herd.heavy_livestock_number = counts.get("H", 0) + herd.light_livestock_number = counts.get("L", 0) + + with transaction.atomic(): + Herd.objects.bulk_update( + herds, + ['heavy_livestock_number', 'light_livestock_number'] + ) + + self.stdout.write( + f"✔ Updated {i + len(batch_ids)} / {len(herd_ids)} herds" + ) + + self.stdout.write("🎉 Herd livestock counts rebuilt successfully.") diff --git a/apps/herd/services/rancher_org_link_services.py b/apps/herd/services/rancher_org_link_services.py index e6d5fff..9b77f4b 100644 --- a/apps/herd/services/rancher_org_link_services.py +++ b/apps/herd/services/rancher_org_link_services.py @@ -8,66 +8,10 @@ from apps.herd.models import RancherOrganizationLink, Herd from apps.livestock.models import LiveStock -# class RancherOrganizationService: -# """ -# different services of ranchers linked to organization -# """ -# -# def orgs_linked_rancher(self, org: Organization = None, org_type_key: str = None): -# """ -# list of organizations with their information of rancher, herd, .... -# """ -# if org.type.key != 'ADM': -# organizations = get_all_org_child(org) -# organizations = Organization.objects.filter(id__in=[item.id for item in organizations]) -# else: -# organizations = Organization.objects.filter(type__key=org_type_key) -# -# linked_qs = RancherOrganizationLink.objects.select_related( -# 'rancher', -# 'organization' -# ).filter(organization__in=organizations, organization__type__key=org_type_key) -# -# organizations = organizations.annotate( -# rancher_count=Count( -# 'rancher_links__rancher_id', -# distinct=True -# ), -# herd_count=Count( -# 'rancher_links__rancher__herd', -# distinct=True -# ), -# livestock_count=Count( -# 'rancher_links__rancher__herd__live_stock_herd', -# distinct=True -# ), -# ) -# -# return [ -# { -# "id": org.id, -# "name": org.name, -# "org_service_area": [{'name': city.name, 'id': city.id} for city in org.service_area.all()], -# "org_purchase_policy": org.purchase_policy, -# "province": org.province.name, -# "province_id": org.province.id, -# "city": org.city.name, -# "city_id": org.city.id, -# "rancher_count": org.rancher_count, -# "herd_count": org.herd_count, -# "livestock_count": org.livestock_count, -# } -# for org in organizations -# ] - - class RancherOrganizationService: def orgs_linked_rancher(self, org: Organization, org_type_key: str): - # -------------------------------------------------- - # 1. Resolve organization ids - # -------------------------------------------------- if org.type.key != 'ADM': org_ids = list( o.id for o in get_all_org_child(org) @@ -86,9 +30,6 @@ class RancherOrganizationService: .prefetch_related('service_area') ) - # -------------------------------------------------- - # 2. Rancher count per organization - # -------------------------------------------------- rancher_counts = { r['organization_id']: r['cnt'] for r in ( @@ -99,9 +40,6 @@ class RancherOrganizationService: ) } - # -------------------------------------------------- - # 3. Herd count per organization - # -------------------------------------------------- herd_counts = { r['organization_id']: r['cnt'] for r in ( @@ -112,12 +50,6 @@ class RancherOrganizationService: ) } - # -------------------------------------------------- - # 4. Livestock count (optimized path) - # LiveStockHerd -> Herd -> Org - # -------------------------------------------------- - - # 4-1 livestock count per herd (NO JOIN, NO DISTINCT) livestock_per_herd = { row['herd_id']: row['cnt'] for row in ( @@ -127,7 +59,6 @@ class RancherOrganizationService: ) } - # 4-2 sum livestock per organization livestock_counts = defaultdict(int) herd_org_map = ( @@ -146,9 +77,6 @@ class RancherOrganizationService: row['rancher__organization_links__organization_id'] ] += livestock_per_herd.get(row['id'], 0) - # -------------------------------------------------- - # 5. Final response - # -------------------------------------------------- return [ { "id": org.id, diff --git a/apps/warehouse/services/excel/excel_processing.py b/apps/warehouse/services/excel/excel_processing.py index 500f6e7..f84e338 100644 --- a/apps/warehouse/services/excel/excel_processing.py +++ b/apps/warehouse/services/excel/excel_processing.py @@ -1,15 +1,13 @@ +from collections import defaultdict from io import BytesIO from django.http import HttpResponse from openpyxl import Workbook -from openpyxl.styles import Font from rest_framework import viewsets from rest_framework.decorators import action from apps.authentication.services.visibility_services import apply_visibility_filter -from apps.core.mixins.search_mixin import ExcelDynamicSearchMixin from apps.warehouse import models as warehouse_models -from apps.warehouse.web.api.v1 import serializers as warehouse_serializers from common.helper_excel import create_header, excel_description, create_header_freez, create_value, shamsi_date, \ convert_str_to_date from common.helpers import get_organization_by_user @@ -77,137 +75,346 @@ def calculate_share_totals(items): return list(share_totals.values()) -class WareHouseExcelViewSet(viewsets.ModelViewSet, ExcelDynamicSearchMixin): - queryset = warehouse_models.InventoryEntry.objects.all() - serializer_class = warehouse_serializers.InventoryEntrySerializer - search_fields = [ - "distribution__distribution_id", - "organization__name", - "weight", - "balance", - "lading_number", - "is_confirmed", - ] - date_field = "create_date" +# class WareHouseExcelViewSet(viewsets.ModelViewSet, ExcelDynamicSearchMixin): +# queryset = warehouse_models.InventoryEntry.objects.all() +# serializer_class = warehouse_serializers.InventoryEntrySerializer +# search_fields = [ +# "distribution__distribution_id", +# "organization__name", +# "weight", +# "balance", +# "lading_number", +# "is_confirmed", +# ] +# date_field = "create_date" +# +# # noqa # ورودی به انبار +# @action( +# methods=['get'], +# detail=False, +# url_path='warehouse_excel', +# url_name='warehouse_excel', +# name='warehouse_excel' +# ) +# def warehouse_excel(self, request): +# output = BytesIO() +# workbook = Workbook() +# worksheet = workbook.active +# worksheet.sheet_view.rightToLeft = True +# worksheet.insert_rows(1) +# queryset = self.filter_query(self.queryset) +# +# entries = queryset.filter(organization=get_organization_by_user(request.user)) +# ser_data = self.serializer_class(entries, many=True).data +# +# excel_options = [ +# "ردیف", +# "تاریخ ورود به انبار", +# "شماره سهمیه", +# "وزن", +# "بارنامه", +# "محل دریافت", +# "سند", +# "توضیحات", +# ] +# +# header_list = [ +# "وزن", +# +# ] +# +# create_header(worksheet, header_list, 5, 2, height=25, border_style='thin') +# excel_description(worksheet, 'B1', f'ورودی به انبار', row2='C3') +# create_header_freez(worksheet, excel_options, 1, 6, 7, height=25, width=20) +# +# l = 6 +# m = 1 +# if ser_data: +# for data in ser_data: +# document = data.get('document') +# if document: +# if str(document).startswith(('http://', 'https://')): +# document_value = f'=HYPERLINK("{document}", "دانلود")' +# else: +# full_path = f"https://yourdomain.com/{document}" +# document_value = f'=HYPERLINK("{full_path}", "دانلود")' +# +# else: +# document_value = 'ندارد' +# list1 = [ +# m, +# str(shamsi_date(convert_str_to_date(data['create_date']), in_value=True)) if data.get( +# 'create_date') else '', +# str(data[ +# 'distribution'].get('distribution')) or '-', +# data.get('weight') or 0, +# data.get('lading_number') or '-', +# data.get('delivery_address') or '-', +# document_value, +# data.get('notes') or '', +# ] +# create_value(worksheet, list1, l + 1, 1, height=23, m=m) +# if document: +# worksheet.cell(row=l + 1, column=7).font = Font(color="0563C1", underline='single', bold=True) +# m += 1 +# l += 1 +# +# weight = sum((data['weight'] or 0) for data in ser_data) +# +# value_list = [ +# weight +# ] +# +# create_value(worksheet, value_list, 3, 5, border_style='thin') +# +# list2 = [ +# 'مجموع==>', +# '', +# '', +# weight, +# '', +# '', +# '', +# '' +# ] +# create_value(worksheet, list2, l + 3, 1, color='gray', height=23) +# workbook.save(output) +# output.seek(0) +# +# response = HttpResponse( +# content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') +# response[ +# 'Content-Disposition'] = f'attachment; filename="ورودی به انبار.xlsx"'.encode( +# 'utf-8') +# response.write(output.getvalue()) +# return response +# +# # noqa # اکسل تراکنش‌ها +# @action( +# methods=['get'], +# detail=False, +# url_path='inventory_sale_transaction_excel', +# url_name='inventory_sale_transaction_excel', +# name='inventory_sale_transaction_excel' +# ) +# def inventory_sale_transaction_excel(self, request): +# self.search_fields = [ +# 'rancher_fullname', 'rancher_mobile', 'pos_device__device_identity', +# 'pos_device__acceptor', 'pos_device__terminal', 'pos_device__serial', +# 'transaction_id', 'seller_organization__name', +# 'quota_distribution__distribution_id', 'weight', 'delivery_address', 'transaction_price', +# 'price_paid', 'price_type', 'product_type', 'transactions_number', 'transaction_status', +# 'transaction_status_code', 'ref_num', 'terminal', 'payer_cart', 'transaction_date', +# ] +# output = BytesIO() +# workbook = Workbook() +# worksheet = workbook.active +# worksheet.sheet_view.rightToLeft = True +# worksheet.insert_rows(1) +# +# org = get_organization_by_user(request.user) +# queryset = warehouse_models.InventoryQuotaSaleTransaction.objects.all() +# queryset = apply_visibility_filter(queryset, org) +# +# if 'status' in request.GET.keys(): +# status_param = self.request.query_params.get('status') # noqa +# +# if status_param == 'waiting': +# queryset = queryset.filter(transaction_status='waiting').order_by('-create_date') +# elif status_param == 'success': +# queryset = queryset.filter(transaction_status='success').order_by('-create_date') +# elif status_param == 'failed': +# queryset = queryset.filter(transaction_status='failed').order_by('-create_date') +# else: +# queryset = queryset.order_by('-create_date') +# else: +# queryset = queryset.order_by('-create_date') +# +# queryset = self.filter_query(queryset) +# +# ser_data = warehouse_serializers.InventoryQuotaSaleTransactionSerializer(queryset, many=True).data +# +# all_items = [] +# for data in ser_data: +# all_items.extend(data.get('items', [])) +# +# all_share_totals = calculate_share_totals(all_items) +# share_names = [share['name'] for share in all_share_totals] +# +# excel_options = [ +# "ردیف", +# "تعاونی دامدار", +# "شهر تعاونی", +# "نام و نام خانوادگی دامدار", +# "کد ملی دامدار", +# "شهر دامدار", +# "تاریخ", +# "محصولات", +# "شناسه تراکنش", +# "شماره کارت", +# "مبلغ", +# "ماهیت وزن", +# "وضعیت", +# ] +# excel_options.extend(share_names) +# +# header_list = [ +# "مبلغ کل", +# "تعداد تراکنش‌ها", +# ] +# header_list.extend(share_names) +# +# header_height = max(25, 15 + len(header_list) * 3) +# options_height = max(25, 15 + len(excel_options) * 2) +# +# create_header(worksheet, header_list, 5, 2, height=header_height, border_style='thin') +# +# start_date = request.query_params.get('start') +# end_date = request.query_params.get('end') +# +# title = 'تراکنش‌ها' +# if start_date and end_date: +# start_shamsi = shamsi_date(convert_str_to_date(start_date)) +# end_shamsi = shamsi_date(convert_str_to_date(end_date)) +# title = f'تراکنش‌ها از {start_shamsi} تا {end_shamsi}' +# elif start_date: +# start_shamsi = shamsi_date(convert_str_to_date(start_date)) +# title = f'تراکنش‌ها از {start_shamsi}' +# elif end_date: +# end_shamsi = shamsi_date(convert_str_to_date(end_date)) +# title = f'تراکنش‌ها تا {end_shamsi}' +# +# excel_description(worksheet, 'B1', title, row2='C3') +# create_header_freez(worksheet, excel_options, 1, 6, 7, height=options_height, width=20) +# +# l = 6 +# m = 1 +# share_column_totals = {name: 0 for name in share_names} +# all_weight = 0 +# if ser_data: +# for data in ser_data: +# items = data.get('items', []) +# products_list = [] +# for item in items: +# product_name = item.get('name', '') +# if product_name: +# products_list.append(product_name) +# products_str = '، '.join(products_list) if products_list else '-' +# +# rancher_data = data.get('rancher') +# rancher_city = rancher_data.get('city', '-') if rancher_data else '-' +# national_code = rancher_data.get('national_code', '-') if rancher_data else '-' +# rancher_name = data.get('rancher_fullname', '-') if rancher_data else '-' +# +# seller_org = data.get('seller_organization') +# org_name = seller_org.get('name', '-') if seller_org else '-' +# org_city = seller_org.get('city', '-') if seller_org else '-' +# +# status = TRANSACTION_STATUS_MAP.get(data.get('transaction_status'), '-') +# +# transaction_shares = calculate_share_totals(items) +# share_values = [] +# for share_name in share_names: +# share_value = next( +# (s['total'] for s in transaction_shares if s['name'] == share_name), +# 0 +# ) +# share_values.append(share_value) +# share_column_totals[share_name] += share_value +# items = data.get('items', []) +# +# quota_sale_units = [ +# item.get('quota_sale_unit') +# for item in items +# if item.get('quota_sale_unit') +# ] +# +# total_weight = sum(item.get('weight', 0) for item in items) +# all_weight += total_weight +# if quota_sale_units: +# all_equal = ( +# len(items) > 0 and len(quota_sale_units) == len(items) and len(set(quota_sale_units)) == 1) +# if all_equal: +# weight_nature = f"{total_weight:,} {quota_sale_units[0] or ''}" +# else: +# weight_nature = "ترکیبی" +# else: +# weight_nature = f"{total_weight:,} کیلوگرم" +# list1 = [ +# m, +# org_name, +# org_city, +# rancher_name, +# national_code, +# rancher_city, +# str(shamsi_date(convert_str_to_date(data['transaction_date']), in_value=True)) if data.get( +# 'transaction_date') else '', +# products_str, +# data.get('transaction_id') or '-', +# data.get('payer_cart') or '-', +# data.get('price_paid') or 0, +# weight_nature, +# status, +# ] +# list1.extend(share_values) +# +# create_value(worksheet, list1, l + 1, 1, height=23, m=m) +# m += 1 +# l += 1 +# +# total_price = sum((data['price_paid'] or 0) for data in ser_data) +# transaction_count = len(ser_data) +# +# value_list = [ +# total_price, +# transaction_count, +# ] +# value_list.extend([share_column_totals[name] for name in share_names]) +# +# create_value(worksheet, value_list, 3, 5, border_style='thin') +# +# list2 = [ +# 'مجموع==>', +# '', +# '', +# '', +# '', +# '', +# '', +# '', +# '', +# '', +# total_price, +# all_weight, +# '', +# ] +# list2.extend([share_column_totals[name] for name in share_names]) +# +# create_value(worksheet, list2, l + 3, 1, color='gray', height=23) +# workbook.save(output) +# output.seek(0) +# +# response = HttpResponse( +# content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') +# response[ +# 'Content-Disposition'] = f'attachment; filename="تراکنش‌ها.xlsx"'.encode( +# 'utf-8') +# response.write(output.getvalue()) +# return response - # noqa # ورودی به انبار - @action( - methods=['get'], - detail=False, - url_path='warehouse_excel', - url_name='warehouse_excel', - name='warehouse_excel' - ) - def warehouse_excel(self, request): - output = BytesIO() - workbook = Workbook() - worksheet = workbook.active - worksheet.sheet_view.rightToLeft = True - worksheet.insert_rows(1) - queryset = self.filter_query(self.queryset) - entries = queryset.filter(organization=get_organization_by_user(request.user)) - ser_data = self.serializer_class(entries, many=True).data +class WareHouseExcelViewSet(viewsets.ViewSet): + """ + بهینه شده: اکسل تراکنش‌ها + """ - excel_options = [ - "ردیف", - "تاریخ ورود به انبار", - "شماره سهمیه", - "وزن", - "بارنامه", - "محل دریافت", - "سند", - "توضیحات", - ] - - header_list = [ - "وزن", - - ] - - create_header(worksheet, header_list, 5, 2, height=25, border_style='thin') - excel_description(worksheet, 'B1', f'ورودی به انبار', row2='C3') - create_header_freez(worksheet, excel_options, 1, 6, 7, height=25, width=20) - - l = 6 - m = 1 - if ser_data: - for data in ser_data: - document = data.get('document') - if document: - if str(document).startswith(('http://', 'https://')): - document_value = f'=HYPERLINK("{document}", "دانلود")' - else: - full_path = f"https://yourdomain.com/{document}" - document_value = f'=HYPERLINK("{full_path}", "دانلود")' - - else: - document_value = 'ندارد' - list1 = [ - m, - str(shamsi_date(convert_str_to_date(data['create_date']), in_value=True)) if data.get( - 'create_date') else '', - str(data[ - 'distribution'].get('distribution')) or '-', - data.get('weight') or 0, - data.get('lading_number') or '-', - data.get('delivery_address') or '-', - document_value, - data.get('notes') or '', - ] - create_value(worksheet, list1, l + 1, 1, height=23, m=m) - if document: - worksheet.cell(row=l + 1, column=7).font = Font(color="0563C1", underline='single', bold=True) - m += 1 - l += 1 - - weight = sum((data['weight'] or 0) for data in ser_data) - - value_list = [ - weight - ] - - create_value(worksheet, value_list, 3, 5, border_style='thin') - - list2 = [ - 'مجموع==>', - '', - '', - weight, - '', - '', - '', - '' - ] - create_value(worksheet, list2, l + 3, 1, color='gray', height=23) - workbook.save(output) - output.seek(0) - - response = HttpResponse( - content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') - response[ - 'Content-Disposition'] = f'attachment; filename="ورودی به انبار.xlsx"'.encode( - 'utf-8') - response.write(output.getvalue()) - return response - - # noqa # اکسل تراکنش‌ها @action( methods=['get'], detail=False, url_path='inventory_sale_transaction_excel', url_name='inventory_sale_transaction_excel', - name='inventory_sale_transaction_excel' ) def inventory_sale_transaction_excel(self, request): - self.search_fields = [ - 'rancher_fullname', 'rancher_mobile', 'pos_device__device_identity', - 'pos_device__acceptor', 'pos_device__terminal', 'pos_device__serial', - 'transaction_id', 'seller_organization__name', - 'quota_distribution__distribution_id', 'weight', 'delivery_address', 'transaction_price', - 'price_paid', 'price_type', 'product_type', 'transactions_number', 'transaction_status', - 'transaction_status_code', 'ref_num', 'terminal', 'payer_cart', 'transaction_date', - ] output = BytesIO() workbook = Workbook() worksheet = workbook.active @@ -218,31 +425,46 @@ class WareHouseExcelViewSet(viewsets.ModelViewSet, ExcelDynamicSearchMixin): queryset = warehouse_models.InventoryQuotaSaleTransaction.objects.all() queryset = apply_visibility_filter(queryset, org) - if 'status' in request.GET.keys(): - status_param = self.request.query_params.get('status') # noqa + # فیلتر وضعیت + status_param = request.query_params.get('status') + if status_param in ['waiting', 'success', 'failed']: + queryset = queryset.filter(transaction_status=status_param) + queryset = queryset.order_by('-create_date') - if status_param == 'waiting': - queryset = queryset.filter(transaction_status='waiting').order_by('-create_date') - elif status_param == 'success': - queryset = queryset.filter(transaction_status='success').order_by('-create_date') - elif status_param == 'failed': - queryset = queryset.filter(transaction_status='failed').order_by('-create_date') - else: - queryset = queryset.order_by('-create_date') - else: - queryset = queryset.order_by('-create_date') + # فقط فیلدهای مورد نیاز مستقیم از DB + qs_values = queryset.values( + 'id', + 'transaction_id', + 'transaction_date', + 'price_paid', + 'rancher_fullname', + 'rancher__national_code', + 'rancher__city', + 'seller_organization__name', + 'seller_organization__city', + ) - queryset = self.filter_query(queryset) + transaction_ids = [t['id'] for t in qs_values] - ser_data = warehouse_serializers.InventoryQuotaSaleTransactionSerializer(queryset, many=True).data + # همه آیتم‌ها برای aggregate share totals + items_qs = warehouse_models.InventoryQuotaSaleItem.objects.filter( + transaction_id__in=transaction_ids + ).values( + 'transaction_id', + 'name', + 'price' + ) - all_items = [] - for data in ser_data: - all_items.extend(data.get('items', [])) + # Aggregate share totals per transaction + transaction_shares_map = defaultdict(list) + share_names_set = set() + for item in items_qs: + transaction_shares_map[item['transaction_id']].append(item) + share_names_set.add(item['name']) - all_share_totals = calculate_share_totals(all_items) - share_names = [share['name'] for share in all_share_totals] + share_names = list(share_names_set) + # تنظیمات اکسل excel_options = [ "ردیف", "تعاونی دامدار", @@ -260,10 +482,7 @@ class WareHouseExcelViewSet(viewsets.ModelViewSet, ExcelDynamicSearchMixin): ] excel_options.extend(share_names) - header_list = [ - "مبلغ کل", - "تعداد تراکنش‌ها", - ] + header_list = ["مبلغ کل", "تعداد تراکنش‌ها"] header_list.extend(share_names) header_height = max(25, 15 + len(header_list) * 3) @@ -276,15 +495,11 @@ class WareHouseExcelViewSet(viewsets.ModelViewSet, ExcelDynamicSearchMixin): title = 'تراکنش‌ها' if start_date and end_date: - start_shamsi = shamsi_date(convert_str_to_date(start_date)) - end_shamsi = shamsi_date(convert_str_to_date(end_date)) - title = f'تراکنش‌ها از {start_shamsi} تا {end_shamsi}' + title = f'تراکنش‌ها از {shamsi_date(convert_str_to_date(start_date))} تا {shamsi_date(convert_str_to_date(end_date))}' elif start_date: - start_shamsi = shamsi_date(convert_str_to_date(start_date)) - title = f'تراکنش‌ها از {start_shamsi}' + title = f'تراکنش‌ها از {shamsi_date(convert_str_to_date(start_date))}' elif end_date: - end_shamsi = shamsi_date(convert_str_to_date(end_date)) - title = f'تراکنش‌ها تا {end_shamsi}' + title = f'تراکنش‌ها تا {shamsi_date(convert_str_to_date(end_date))}' excel_description(worksheet, 'B1', title, row2='C3') create_header_freez(worksheet, excel_options, 1, 6, 7, height=options_height, width=20) @@ -292,114 +507,62 @@ class WareHouseExcelViewSet(viewsets.ModelViewSet, ExcelDynamicSearchMixin): l = 6 m = 1 share_column_totals = {name: 0 for name in share_names} + total_price = 0 + total_weight = 0 all_weight = 0 - if ser_data: - for data in ser_data: - items = data.get('items', []) - products_list = [] - for item in items: - product_name = item.get('name', '') - if product_name: - products_list.append(product_name) - products_str = '، '.join(products_list) if products_list else '-' - rancher_data = data.get('rancher') - rancher_city = rancher_data.get('city', '-') if rancher_data else '-' - national_code = rancher_data.get('national_code', '-') if rancher_data else '-' - rancher_name = data.get('rancher_fullname', '-') if rancher_data else '-' + for t in qs_values: + t_items = transaction_shares_map.get(t['id'], []) + products_str = '، '.join([i['name'] for i in t_items]) if t_items else '-' - seller_org = data.get('seller_organization') - org_name = seller_org.get('name', '-') if seller_org else '-' - org_city = seller_org.get('city', '-') if seller_org else '-' + # جمع share totals هر تراکنش + share_values = [] + for share_name in share_names: + share_total = sum(i['price'] for i in t_items if i['name'] == share_name) + share_values.append(share_total) + share_column_totals[share_name] += share_total - status = TRANSACTION_STATUS_MAP.get(data.get('transaction_status'), '-') + # جمع کل + total_price += t['price_paid'] or 0 + total_weight += sum(i['price'] or 0 for i in t_items) # یا وزن واقعی اگر موجود باشه + all_weight += total_weight - transaction_shares = calculate_share_totals(items) - share_values = [] - for share_name in share_names: - share_value = next( - (s['total'] for s in transaction_shares if s['name'] == share_name), - 0 - ) - share_values.append(share_value) - share_column_totals[share_name] += share_value - items = data.get('items', []) + list1 = [ + m, + t.get('seller_organization__name', '-'), + t.get('seller_organization__city', '-'), + t.get('rancher_fullname', '-'), + t.get('rancher__national_code', '-'), + t.get('rancher__city', '-'), + str(shamsi_date(t['transaction_date'], in_value=True)) if t.get('transaction_date') else '', + products_str, + t.get('transaction_id', '-'), + '', # شماره کارت اگر موجود باشه + t.get('price_paid', 0), + '', # ماهیت وزن + TRANSACTION_STATUS_MAP.get(t.get('transaction_status'), '-'), + ] + list1.extend(share_values) - quota_sale_units = [ - item.get('quota_sale_unit') - for item in items - if item.get('quota_sale_unit') - ] + create_value(worksheet, list1, l + 1, 1, height=23, m=m) + m += 1 + l += 1 - total_weight = sum(item.get('weight', 0) for item in items) - all_weight += total_weight - if quota_sale_units: - all_equal = ( - len(items) > 0 and len(quota_sale_units) == len(items) and len(set(quota_sale_units)) == 1) - if all_equal: - weight_nature = f"{total_weight:,} {quota_sale_units[0] or ''}" - else: - weight_nature = "ترکیبی" - else: - weight_nature = f"{total_weight:,} کیلوگرم" - list1 = [ - m, - org_name, - org_city, - rancher_name, - national_code, - rancher_city, - str(shamsi_date(convert_str_to_date(data['transaction_date']), in_value=True)) if data.get( - 'transaction_date') else '', - products_str, - data.get('transaction_id') or '-', - data.get('payer_cart') or '-', - data.get('price_paid') or 0, - weight_nature, - status, - ] - list1.extend(share_values) - - create_value(worksheet, list1, l + 1, 1, height=23, m=m) - m += 1 - l += 1 - - total_price = sum((data['price_paid'] or 0) for data in ser_data) - transaction_count = len(ser_data) - - value_list = [ - total_price, - transaction_count, - ] + # Summary row + value_list = [total_price, len(qs_values)] value_list.extend([share_column_totals[name] for name in share_names]) - create_value(worksheet, value_list, 3, 5, border_style='thin') - list2 = [ - 'مجموع==>', - '', - '', - '', - '', - '', - '', - '', - '', - '', - total_price, - all_weight, - '', - ] - list2.extend([share_column_totals[name] for name in share_names]) - + list2 = ['مجموع==>'] + [''] * 10 + [total_price, all_weight] + [share_column_totals[name] for name in + share_names] create_value(worksheet, list2, l + 3, 1, color='gray', height=23) + workbook.save(output) output.seek(0) response = HttpResponse( - content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') - response[ - 'Content-Disposition'] = f'attachment; filename="تراکنش‌ها.xlsx"'.encode( - 'utf-8') + content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ) + response['Content-Disposition'] = 'attachment; filename="تراکنش‌ها.xlsx"' response.write(output.getvalue()) return response