from collections import defaultdict from io import BytesIO from django.http import HttpResponse from openpyxl import Workbook from rest_framework import viewsets from rest_framework.decorators import action from apps.authentication.services.visibility_services import apply_visibility_filter from apps.warehouse import models as warehouse_models 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 TRANSACTION_STATUS_MAP = { 'success': 'موفق', 'waiting': 'در انتظار', 'failed': 'ناموفق', } def process_shares(shares): """ پردازش سهم‌ها: سهم "حساب اصلی" رو با سهم‌هایی که shaba یکسان دارن ادغام می‌کنه """ if not shares or len(shares) == 0: return [] # کپی از سهم‌ها processed_shares = [dict(share) for share in shares] # پیدا کردن سهم‌های حساب اصلی main_account_shares = [ share for share in processed_shares if share.get('name') == 'حساب اصلی' ] for main_share in main_account_shares: # پیدا کردن سهم متناظر با همون shaba ولی اسم متفاوت matching_share = next( (share for share in processed_shares if share.get('name') != 'حساب اصلی' and share.get('shaba') == main_share.get('shaba') and share.get('shaba')), None ) if matching_share: matching_share['price'] = (matching_share.get('price') or 0) + (main_share.get('price') or 0) # برگردوندن سهم‌ها بدون حساب اصلی return [share for share in processed_shares if share.get('name') != 'حساب اصلی'] def calculate_share_totals(items): """ محاسبه جمع کل سهم‌ها از تمام آیتم‌ها """ share_totals = {} for item in items: item_share = item.get('item_share', []) if item_share and len(item_share) > 0: processed_shares = process_shares(item_share) for share in processed_shares: key = share.get('shaba') or share.get('name') or 'unknown' if key not in share_totals: share_totals[key] = { 'name': share.get('name') or '-', 'shaba': share.get('shaba') or '-', 'total': 0, } share_totals[key]['total'] += share.get('price') or 0 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" # # # 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 class WareHouseExcelViewSet(viewsets.ViewSet): """ بهینه شده: اکسل تراکنش‌ها """ @action( methods=['get'], detail=False, url_path='inventory_sale_transaction_excel', url_name='inventory_sale_transaction_excel', ) def inventory_sale_transaction_excel(self, request): 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) # فیلتر وضعیت 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') # فقط فیلدهای مورد نیاز مستقیم از 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', ) transaction_ids = [t['id'] for t in qs_values] # همه آیتم‌ها برای aggregate share totals items_qs = warehouse_models.InventoryQuotaSaleItem.objects.filter( transaction_id__in=transaction_ids ).values( 'transaction_id', 'name', 'unit_price' ) # 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']) share_names = list(share_names_set) # تنظیمات اکسل 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: title = f'تراکنش‌ها از {shamsi_date(convert_str_to_date(start_date))} تا {shamsi_date(convert_str_to_date(end_date))}' elif start_date: title = f'تراکنش‌ها از {shamsi_date(convert_str_to_date(start_date))}' elif end_date: 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) l = 6 m = 1 share_column_totals = {name: 0 for name in share_names} total_price = 0 total_weight = 0 all_weight = 0 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 '-' # جمع share totals هر تراکنش share_values = [] for share_name in share_names: share_total = sum(i['unit_price'] for i in t_items if i['name'] == share_name) share_values.append(share_total) share_column_totals[share_name] += share_total # جمع کل total_price += t['price_paid'] or 0 total_weight += sum(i['unit_price'] or 0 for i in t_items) # یا وزن واقعی اگر موجود باشه all_weight += total_weight 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) create_value(worksheet, list1, l + 1, 1, height=23, m=m) m += 1 l += 1 # 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 = ['مجموع==>'] + [''] * 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'] = 'attachment; filename="تراکنش‌ها.xlsx"' response.write(output.getvalue()) return response