from django.shortcuts import redirect, render, get_object_or_404 from django.urls.base import reverse from django.urls import reverse # تکراری در کد اصلی شما بود، اما جهت عدم تغییر حفظ شد from persiantools.jdatetime import JalaliDate from persiantools import characters, digits import datetime from django.contrib import messages from django.views.decorators.http import require_POST from django.utils import timezone # جدید from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage from django.core.exceptions import PermissionDenied, ValidationError # جدید from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest # بخشBadRequest جدید from django.db import transaction, connection from django.db.models import Count, Sum, Q, Avg from django.contrib.auth.models import Permission, User from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required, permission_required import os import json # جدید import logging # جدید from .models import * from .services import create_transfer_request, approve_transfer_request, reject_transfer_request, cancel_transfer_request logger = logging.getLogger(__name__) class InventoryException(Exception): """خطای اختصاصی برای کنترل موجودی انبار""" pass def _safe_int(value, default=0): """تابع امن برای تبدیل مقادیر دریافتی از جاوااسکریپت به عدد""" if value is None: return default try: clean_val = str(value).strip() return int(clean_val) if clean_val else default except (ValueError, TypeError): return default def _parse_jalali_date(date_str): """تبدیل امن تاریخ ارسالی از pDatepicker به میلادی""" if not date_str or not str(date_str).strip(): return None try: clean_date = digits.fa_to_en(digits.ar_to_fa(str(date_str).strip())) y, m, d = map(int, clean_date.split("/")) return JalaliDate(y, m, d).to_gregorian() except Exception as e: logger.warning(f"Error parsing Jalali date '{date_str}': {e}") return None def _parse_timestamp(ts_value): """تبدیل یونیکس تایم‌استمپ‌های ارسالی از جاوااسکریپت""" if not ts_value: return None try: # تبدیل میلی‌ثانیه جاوااسکریپت به ثانیه پایتون return datetime.datetime.fromtimestamp(int(ts_value) / 1000.0, tz=timezone.get_current_timezone()) except (ValueError, TypeError): return None def p_login(request): username = request.POST.get('username', None ) password = request.POST.get('password', None ) if username != None and password != None: if not request.user.is_authenticated: user = authenticate(request, username=username, password=password) if user is not None: login(request, user) return redirect("/panel/ActionList/"+str(request.user.id)+"/today") else: data = {'message': 'یوزنیم یا پسورد اشتباه است'} return render(request, 'admin_panel/login.html', data) else: return redirect("/panel/") else: if request.user.is_authenticated: return redirect("/panel/") else: return render(request, 'admin_panel/login.html') #return logout(request) def p_logout(request): logout(request) return redirect("/panel/p_login/") @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewReportMali', raise_exception=True) def report(request): nahvePardakhat = [ {'fa': 'تخفیف', 'en': 'تخفیف'}, {'fa': 'نسیه', 'en': 'نسیه'}, {'fa': 'چک', 'en': 'چک'}, {'fa': 'کارتخوان', 'en': 'کارت خوان'}, {'fa': 'کارت به کارت', 'en': 'کارت به کارت'}, {'fa': 'نقدی', 'en': 'نقد'}, ] userHa = User.objects.filter() if request.method == "GET": nahvePardakht = request.GET.get("nahvepardakht", None) user = request.GET.get("user", None) azTarikh = request.GET.get("azTarikh", None) taTarikh = request.GET.get("taTarikh", None) pagenumber = request.GET.get("page", None) if azTarikh and nahvePardakht: query_data = { 'nahvePardakht': nahvePardakht, 'user': user, 'azTarikh': azTarikh, 'taTarikh': taTarikh, } azDateTime = datetime.datetime.fromtimestamp(int(azTarikh) / 1e3) azDateTime = azDateTime.strftime('%Y-%m-%d 00:00:00') taDateTime = datetime.datetime.fromtimestamp(int(taTarikh) / 1e3) taDateTime = taDateTime.strftime('%Y-%m-%d 23:59:59') print("b") user_a = User.objects.get(username=user) print(user_a) if nahvePardakht == "نسیه" or nahvePardakht == "چک": if nahvePardakht == 'نسیه': typeSarresid = 'nesiye' elif nahvePardakht == 'چک': typeSarresid = 'check' sumSarresidha = Sarresid.objects.filter( type=typeSarresid, user=user_a, createdDT__lte=taDateTime, createdDT__gte=azDateTime, payStatus='false' ).aggregate(Sum('price')) sarresidha = Sarresid.objects.filter( type=typeSarresid, user=user_a, createdDT__lte=taDateTime, createdDT__gte=azDateTime, payStatus='false' ) paginator = Paginator(sarresidha, 25) # Show 25 contacts per page. page_number = request.GET.get('page', None) all_sarresidha = paginator.get_page(pagenumber) data = {'sarresidha': all_sarresidha ,'sumSarresidha': sumSarresidha, 'nahvePardakhat': nahvePardakhat, 'userHa':userHa, 'query_data': query_data} return render(request, 'admin_panel/reportCheckNesiye.html', data) else: sumAricles = Article.objects.filter( nahvePardakht=nahvePardakht, user=user_a, createdDT__lte=taDateTime, createdDT__gte=azDateTime, ).aggregate(Sum('credit')) aricles1 = Article.objects.filter( nahvePardakht=nahvePardakht, user=user_a, createdDT__lte=taDateTime, createdDT__gte=azDateTime, ) paginator = Paginator(aricles1, 25) # Show 25 contacts per page. page_number = request.GET.get('page', None) all_aricles = paginator.get_page(page_number) data = {'aricles': all_aricles,'sum_credit': sumAricles, 'nahvePardakhat': nahvePardakhat, 'userHa':userHa, 'query_data': query_data} return render(request, 'admin_panel/report.html', data) else: data = {'nahvePardakhat': nahvePardakhat, 'userHa':userHa} return render(request, 'admin_panel/report.html', data) data = {'nahvePardakhat': nahvePardakhat, 'userHa':userHa} return render(request, 'admin_panel/report.html', data) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewReportMali', raise_exception=True) def inventory_movements_report(request): """ گزارش جامع گردش و خروج کالا از انبار به همراه فیلتر متنی نام کالا، نوع تراکنش، کاربر، انبار و بازه زمانی شمسی. """ all_users = User.objects.all().order_by('first_name') all_stores = StoreRoom.objects.all() movement_choices = ProductInStore.MovementType.choices # دریافت پارامترهای فیلتر از فرانت‌اند selected_type = request.GET.get('movement_type', '') selected_user = request.GET.get('user_id', '') selected_store = request.GET.get('store_id', '') selected_product_name = request.GET.get('product_name', '').strip() # فیلد جدید جستجوی نام کالا az_tarikh = request.GET.get('azTarikh', '') ta_tarikh = request.GET.get('taTarikh', '') # ۱. کوئری پایه بهینه movements_list = ProductInStore.objects.select_related( 'Product', 'StoreRoom', 'factor__customer', 'factor__user' ).filter(year=1400).order_by('-createdDT') # ۲. اعمال فیلترهای پویا if selected_type: movements_list = movements_list.filter(movement_type=selected_type) if selected_user: movements_list = movements_list.filter(factor__user__id=_safe_int(selected_user)) if selected_store: movements_list = movements_list.filter(StoreRoom__id=_safe_int(selected_store)) if selected_product_name: # فیلتر متنی حساس نبودن به حروف بزرگ و کوچک (Case-Insensitive Contains) movements_list = movements_list.filter(Product__product_name__icontains=selected_product_name) # ۳. پردازش بازه زمانی if az_tarikh: try: az_dt = datetime.datetime.fromtimestamp(int(az_tarikh) / 1000.0) movements_list = movements_list.filter(createdDT__gte=az_dt.strftime('%Y-%m-%d 00:00:00')) except (ValueError, TypeError): pass if ta_tarikh: try: ta_dt = datetime.datetime.fromtimestamp(int(ta_tarikh) / 1000.0) movements_list = movements_list.filter(createdDT__lte=ta_dt.strftime('%Y-%m-%d 23:59:59')) except (ValueError, TypeError): pass # ۴. محاسبه آماری سریع دیتابیس روی نتایج فیلتر شده summary_stats = movements_list.aggregate( total_out_qty=Sum('s_out'), total_in_qty=Sum('s_in') ) # ۵. صفحه‌بندی نتایج paginator = Paginator(movements_list, 50) page_number = request.GET.get('page') page_obj = paginator.get_page(page_number) context = { 'page_obj': page_obj, 'all_users': all_users, 'all_stores': all_stores, 'movement_choices': movement_choices, 'summary_stats': summary_stats, 'filters': { 'movement_type': selected_type, 'user_id': _safe_int(selected_user) if selected_user else '', 'store_id': _safe_int(selected_store) if selected_store else '', 'product_name': selected_product_name, # ارسال فیلد جدید به قالب برای حفظ مقدار ورودی 'azTarikh': az_tarikh, 'taTarikh': ta_tarikh, } } return render(request, 'admin_panel/report_inventory_movements.html', context) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewReportMali', raise_exception=True) def financial_dashboard(request): """ داشبورد هوش مالی و مدیریت نقدینگی سطح Enterprise (BI Dashboard) همراه با روند ۱۲ ماهه جمع کل بدهی مشتریان (تفاضل debit و credit). """ # دریافت تایم‌استمپ‌های ارسالی از تقویم شمسی فرانت‌اند az_tarikh = request.GET.get('azTarikh', '') ta_tarikh = request.GET.get('taTarikh', '') az_dt = None ta_dt = None if az_tarikh: try: az_dt = datetime.datetime.fromtimestamp(int(az_tarikh) / 1000.0) az_dt = az_dt.replace(hour=0, minute=0, second=0) except (ValueError, TypeError): pass if ta_tarikh: try: ta_dt = datetime.datetime.fromtimestamp(int(ta_tarikh) / 1000.0) ta_dt = ta_dt.replace(hour=23, minute=59, second=59) except (ValueError, TypeError): pass # نقطه اتکا (لنگر) محاسبات: زمان انتخابی کاربر یا زمان حال anchor_dt = ta_dt if ta_dt else datetime.datetime.now() # ========================================================================= # محاسبه روند ۱۲ ماهه غلتان (TTM) - نسخه اصلاح‌شده و امن # ========================================================================= jalali_months = ['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند'] trend_labels = [] trend_data = [] # روش استاندارد و بدون باگ برای تبدیل تاریخ میلادی به شمسی در persiantools # با این کار مطمئن می‌شویم مقادیر حتماً عدد صحیح و مجاز هستند anchor_jd = JalaliDate(anchor_dt.date()) curr_y = int(anchor_jd.year) curr_m = int(anchor_jd.month) # حلقه استخراج داده برای ۱۲ ماه گذشته for _ in range(12): if curr_m < 1: curr_m = 12 curr_y -= 1 label = f"{jalali_months[curr_m - 1]} {curr_y}" trend_labels.insert(0, label) # ❗️ اصلاح اساسی: ترتیب آرگومان‌ها در این تابع باید (month, year) باشد last_day = JalaliDate.days_in_month(curr_m, curr_y) # اما ترتیب سازنده کلاس اصلی همچنان (year, month, day) است end_of_month_jd = JalaliDate(curr_y, curr_m, last_day) end_of_month_gd = end_of_month_jd.to_gregorian() # ساخت رشته زمان برای پایان آن ماه (تا ساعت 23:59:59) end_dt_str = end_of_month_gd.strftime('%Y-%m-%d 23:59:59') # محاسبه مانده حساب کل تا پایان این ماه totals = Article.objects.filter(createdDT__lte=end_dt_str).aggregate( t_deb=Sum('debit'), t_cred=Sum('credit') ) t_deb = totals['t_deb'] or 0 t_cred = totals['t_cred'] or 0 month_total_debt = t_deb - t_cred trend_data.insert(0, month_total_debt) # شیفت به ماه قبل برای دور بعدی حلقه curr_m -= 1 # ========================================================================= # قرنطینه داده‌های سایر بخش‌های داشبورد بر اساس سال مالی ۱۴۰۰ # ========================================================================= sarresid_qs = Sarresid.objects.filter(factor__productinstore__year=1400).distinct() factor_qs = Factor.objects.filter(productinstore__year=1400, is_active=True).distinct() article_qs = Article.objects.filter(factor__productinstore__year=1400).distinct() store_qs = ProductInStore.objects.filter(year=1400) if az_dt: sarresid_qs = sarresid_qs.filter(createdDT__gte=az_dt) factor_qs = factor_qs.filter(create_date__gte=az_dt) article_qs = article_qs.filter(createdDT__gte=az_dt) store_qs = store_qs.filter(createdDT__gte=az_dt) if ta_dt: sarresid_qs = sarresid_qs.filter(createdDT__lte=ta_dt) factor_qs = factor_qs.filter(create_date__lte=ta_dt) article_qs = article_qs.filter(createdDT__lte=ta_dt) store_qs = store_qs.filter(createdDT__lte=ta_dt) # ۱. محاسبات کارت‌های شاخص عملکرد (KPI Cards) total_unpaid_debts = sarresid_qs.filter(payStatus="false").aggregate(Sum('price'))['price__sum'] or 0 total_sales_period = factor_qs.aggregate(Sum('amount'))['amount__sum'] or 0 overdue_checks_count = sarresid_qs.filter(payStatus="false", type="check", sarresidDT__lte=anchor_dt).count() # ۲. نمودار اول: تحلیل پویای سن مطالبات debt_future = sarresid_qs.filter(payStatus="false", sarresidDT__gt=anchor_dt).aggregate(Sum('price'))['price__sum'] or 0 dt_30_days_ago = anchor_dt - datetime.timedelta(days=30) debt_0_30 = sarresid_qs.filter(payStatus="false", sarresidDT__lte=anchor_dt, sarresidDT__gte=dt_30_days_ago).aggregate(Sum('price'))['price__sum'] or 0 dt_90_days_ago = anchor_dt - datetime.timedelta(days=90) debt_30_90 = sarresid_qs.filter(payStatus="false", sarresidDT__lt=dt_30_days_ago, sarresidDT__gte=dt_90_days_ago).aggregate(Sum('price'))['price__sum'] or 0 debt_above_90 = sarresid_qs.filter(payStatus="false", sarresidDT__lt=dt_90_days_ago).aggregate(Sum('price'))['price__sum'] or 0 # ۳. نمودار دوم: ماتریس کیفیت ورودی پول payment_articles = article_qs.filter(credit__gt=0).values('nahvePardakht').annotate(total_amount=Sum('credit')).order_by('-total_amount') pay_labels = [] pay_data = [] for item in payment_articles: label = item['nahvePardakht'] or 'سایر/نامشخص' pay_labels.append(label) pay_data.append(item['total_amount']) # ۴. نمودار سوم: مانیتورینگ نشت انبار store_leaks = store_qs.filter( movement_type__in=[ProductInStore.MovementType.WARRANTY, ProductInStore.MovementType.PROMOTION] ).values('movement_type').annotate(total_qty=Sum('s_out')) leak_data = {'WARRANTY': 0, 'PROMOTION': 0} for leak in store_leaks: leak_data[leak['movement_type']] = leak['total_qty'] # ارسال ساختار داده به تمپلیت context = { 'kpi_total_unpaid': total_unpaid_debts, 'kpi_total_sales': total_sales_period, 'kpi_overdue_checks': overdue_checks_count, 'trend_labels': trend_labels, # نام ماه‌ها برای محور X 'trend_data': trend_data, # جمع کل بدهی‌ها برای محور Y 'aging_data': [debt_future, debt_0_30, debt_30_90, debt_above_90], 'pay_labels': pay_labels, 'pay_data': pay_data, 'warranty_qty': leak_data['WARRANTY'], 'promotion_qty': leak_data['PROMOTION'], 'filters': { 'azTarikh': az_tarikh, 'taTarikh': ta_tarikh, } } return render(request, 'admin_panel/financial_dashboard.html', context) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewReportMali', raise_exception=True) def bedehi(request): try: debit = Article.objects.filter().aggregate(Sum('debit'))['debit__sum'] credit = Article.objects.filter().aggregate(Sum('credit'))['credit__sum'] mandehesabCs = debit - credit articls = Article.objects.filter().order_by('-createdDT') articlsPaginator = Paginator(articls, 10) pageNumber = request.GET.get("page", 1) articl_objs = articlsPaginator.get_page(pageNumber) except: articls =None mandehesabCs = 0 data = {'articls': articl_objs, 'mandehesabKP': mandehesabCs} return render(request, 'admin_panel/bedehi.html', data) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewReportMali', raise_exception=True) def hesabdari(request): page_name = 'sarresidShodeha' anchor_dt = datetime.datetime.now() # تعریف محدوده‌های زمانی ماتریس ریسک (سن مطالبات) dt_30_days_ago = anchor_dt - datetime.timedelta(days=30) dt_90_days_ago = anchor_dt - datetime.timedelta(days=90) # کوئری پایه: تمام سررسیدهای پرداخت‌نشده که از موعد آن‌ها گذشته است base_qs = Sarresid.objects.filter(payStatus="false", sarresidDT__lte=anchor_dt) # محاسبه پرسرعت مجموع مبالغ در هر دسته (توسط موتور دیتابیس) matrix_totals = base_qs.aggregate( minor_delay=Sum('price', filter=Q(sarresidDT__gte=dt_30_days_ago)), high_risk=Sum('price', filter=Q(sarresidDT__lt=dt_30_days_ago, sarresidDT__gte=dt_90_days_ago)), critical=Sum('price', filter=Q(sarresidDT__lt=dt_90_days_ago)), total_sum=Sum('price') ) # دریافت فیلتر انتخابی کاربر از URL risk_filter = request.GET.get('risk_level', 'all') # اعمال فیلتر روی لیست نمایش داده شده if risk_filter == 'minor': filtered_qs = base_qs.filter(sarresidDT__gte=dt_30_days_ago) elif risk_filter == 'high': filtered_qs = base_qs.filter(sarresidDT__lt=dt_30_days_ago, sarresidDT__gte=dt_90_days_ago) elif risk_filter == 'critical': filtered_qs = base_qs.filter(sarresidDT__lt=dt_90_days_ago) else: filtered_qs = base_qs # مرتب‌سازی: قدیمی‌ترین‌ها (خطرناک‌ترین‌ها) در صدر جدول قرار می‌گیرند filtered_qs = filtered_qs.order_by('-sarresidDT') page_number = request.GET.get('page', 1) paginator = Paginator(filtered_qs, per_page=50) all_sarresid_pgi = paginator.get_page(page_number) data = { 'page_name': page_name, 'dateNow': anchor_dt.strftime('%Y-%m-%d %H:%M:%S'), 'all_sarresid': all_sarresid_pgi, 'current_filter': risk_filter, # متغیرهای ماتریس ریسک 'sum_sarresid': matrix_totals['total_sum'] or 0, 'minor_sum': matrix_totals['minor_delay'] or 0, 'high_sum': matrix_totals['high_risk'] or 0, 'critical_sum': matrix_totals['critical'] or 0, } return render(request, 'admin_panel/hesabdari.html', data) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewReportMali', raise_exception=True) def hesabdari_checkPass(request): page_name = 'checkPass' dateNow = datetime.datetime.today().strftime('%Y-%m-%d 23:59:59') all_sarresid = Sarresid.objects.filter(payStatus="true", type='check').order_by('-sarresidDT') page_number = request.GET.get('page', None) paginator = Paginator(all_sarresid, per_page=50) all_sarresid_pgi = paginator.get_page(page_number) #paginator = Paginator(all_sarresid, per_page=200) data = {'all_sarresid': all_sarresid_pgi, "dateNow":dateNow, "page_name":page_name} return render(request, 'admin_panel/hesabdari.html', data) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewReportMali', raise_exception=True) def hesabdari_checkPassFail(request): page_name = 'checkPassFaild' dateNow = datetime.datetime.today().strftime('%Y-%m-%d 23:59:59') all_sarresid = Sarresid.objects.filter(payStatus="false", type='check').order_by('sarresidDT') count_check_passfail = all_sarresid.count() sum_check_passfail = all_sarresid.aggregate(Sum('price'))['price__sum'] page_number = request.GET.get('page', None) paginator = Paginator(all_sarresid, per_page=50) all_sarresid_pgi = paginator.get_page(page_number) #paginator = Paginator(all_sarresid, per_page=200) data = {'all_sarresid': all_sarresid_pgi, 'sum_check_passfail':sum_check_passfail, 'count_check_passfail': count_check_passfail, "dateNow":dateNow, "page_name":page_name} return render(request, 'admin_panel/hesabdari.html', data) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewReportMali', raise_exception=True) def hesabdari_residnashone(request): page_name = 'sarresidNaShodeha' dateNow = datetime.datetime.today().strftime('%Y-%m-%d 23:59:59') all_sarresid = Sarresid.objects.filter(payStatus="false", sarresidDT__gte=dateNow).order_by('sarresidDT') sarresidNashode = all_sarresid.aggregate(Sum('price'))['price__sum'] page_number = request.GET.get('page', None) paginator = Paginator(all_sarresid, per_page=50) all_sarresid_pgi = paginator.get_page(page_number) #paginator = Paginator(all_sarresid, per_page=200) data = {'sarresidNashode': sarresidNashode,'all_sarresid': all_sarresid_pgi, "dateNow":dateNow, "page_name":page_name} return render(request, 'admin_panel/hesabdari.html', data) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewReportMali', raise_exception=True) def bedehiKhadamat(request): #bedehiKhadamat try: debit = Article.objects.filter(typePy='khadamat').aggregate(Sum('debit'))['debit__sum'] credit = Article.objects.filter(typePy='khadamat').aggregate(Sum('credit'))['credit__sum'] mandehesabCs = debit - credit articls = Article.objects.filter(typePy='khadamat').order_by('-createdDT')[:100] except: articls =None mandehesabCs = 0 data = {'articls': articls, 'mandehesabKP': mandehesabCs} return render(request, 'admin_panel/bedehi.html', data) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewReportMali', raise_exception=True) def bedehiNasb(request): try: debit = Article.objects.filter(typePy='nasb').aggregate(Sum('debit'))['debit__sum'] credit = Article.objects.filter(typePy='nasb').aggregate(Sum('credit'))['credit__sum'] mandehesabCs = debit - credit articls = Article.objects.filter(typePy='nasb').order_by('-createdDT')[:100] except: articls =None mandehesabCs = 0 data = {'articls': articls, 'mandehesabKP': mandehesabCs} return render(request, 'admin_panel/bedehi.html', data) @csrf_exempt @login_required(login_url='/panel/p_login/') def customers(request): searchVal = request.GET.get('searchVal', '') searchOn = request.GET.get('searchOn', '') searchStatus = True full_path = request.get_full_path() if searchOn == 'code': all_customers = Customer.objects.filter(idCode__contains=searchVal).order_by('-create_date') elif searchOn == 'phone': all_customers = Customer.objects.filter(Q(phone1__contains=searchVal) | Q(phone2__contains=searchVal) | Q(phone3__contains=searchVal) ).order_by('-create_date') elif searchOn == 'name': all_customers = Customer.objects.filter(name_family__contains=searchVal).order_by('-create_date') elif searchOn == 'address': all_customers = Customer.objects.filter(address__contains=searchVal).order_by('-create_date') elif searchOn == 'laghv': all_customers = Customer.objects.filter(laghv_bazdid=True).order_by('-create_date') else: searchStatus = False all_customers = Customer.objects.filter(is_active=True).order_by('-create_date') paginator = Paginator(all_customers, 25) # Show 25 contacts per page. page_number = request.GET.get('page', None) all_customers = paginator.get_page(page_number) data = {'customersD': all_customers, 'searchStatus': searchStatus, 'full_path': full_path, 'searchVal': searchVal, 'searchOn':searchOn} return render(request, 'admin_panel/customers.html', data) @login_required(login_url='/panel/p_login/') def customersSearch(request, searchVal,searchOn): searchStatus = True if searchOn == 'code': all_customers = Customer.objects.filter(idCode__contains=searchVal).order_by('-create_date') elif searchOn == 'phone': all_customers = Customer.objects.filter(phone1__contains=searchVal).order_by('-create_date') elif searchOn == 'name': all_customers = Customer.objects.filter(name_family__contains=searchVal).order_by('-create_date') elif searchOn == 'address': all_customers = Customer.objects.filter(address__contains=searchVal).order_by('-create_date') else: searchStatus = False all_customers = Customer.objects.all().order_by('-create_date') paginator = Paginator(all_customers, 25) # Show 25 contacts per page. page_number = request.GET.get('page', None) all_customers = paginator.get_page(page_number) data = {'customersD': all_customers, 'searchStatus': searchStatus} return render(request, 'admin_panel/customers.html', data) @login_required(login_url='/panel/p_login/') def customer(request ,c_id): customer = Customer.objects.get(id=c_id) categorys = ProductCategory.objects.all() factorha = Factor.objects.filter(customer__id = c_id) #all_prd = serializers.serialize("json", all_prda) #return JsonResponse(all_prd,safe = False) dateNow = datetime.datetime.now().date data = {'customer': customer, 'categorys': categorys, 'dateNow': dateNow, 'factorha': factorha} return render(request, 'admin_panel/customerID.html', data) @login_required(login_url='/panel/p_login/') def customerNew(request ,c_id): customer = Customer.objects.get(id=c_id) categorys = ProductCategory.objects.all() factorha = Factor.objects.filter(customer__id = c_id) #all_prd = serializers.serialize("json", all_prda) #return JsonResponse(all_prd,safe = False) dateNow = datetime.datetime.now().date data = {'customer': customer, 'categorys': categorys, 'dateNow': dateNow, 'factorha': factorha} return render(request, 'admin_panel/customerNew.html', data) @login_required(login_url='/panel/p_login/') def customerNew2(request ,c_id): customer = get_object_or_404(Customer, id=c_id, is_active=True) categorys = ProductCategory.objects.all() factorha = Factor.objects.filter(customer__id = c_id) debit = Article.objects.filter(customer=customer).aggregate(Sum('debit'))['debit__sum'] credit = Article.objects.filter(customer=customer).aggregate(Sum('credit'))['credit__sum'] mandehesabCs = 0 try: if (debit - credit) < 0: mandehesabCs = abs(debit - credit) except: pass #all_prd = serializers.serialize("json", all_prda) #return JsonResponse(all_prd,safe = False) dateNow = datetime.datetime.now().date data = {'customer': customer, 'categorys': categorys, 'dateNow': dateNow, 'factorha': factorha, 'mandehesabCs':mandehesabCs} return render(request, 'admin_panel/customerNew2.html', data) @login_required(login_url='/panel/p_login/') def customerFactors(request, c_id): customer = get_object_or_404(Customer, id=c_id) categorys = ProductCategory.objects.all() # کوئری پایه برای دریافت همه فاکتورهای این مشتری factor_qs = Factor.objects.filter(customer=customer) # دریافت پارامترهای فیلتر و جستجو از آدرس URL search_query = request.GET.get('search_factor', '').strip() filter_status = request.GET.get('status', 'all') filter_type = request.GET.get('type', 'all') az_tarikh = request.GET.get('az_tarikh', '').strip() ta_tarikh = request.GET.get('ta_tarikh', '').strip() # ۱. اعمال فیلتر جستجو (شناسه فاکتور یا متن توضیحات) if search_query: if search_query.isdigit(): factor_qs = factor_qs.filter(models.Q(id=int(search_query)) | models.Q(desc__icontains=search_query)) else: factor_qs = factor_qs.filter(models.Q(desc__icontains=search_query)) # ۲. اعمال فیلتر وضعیت پرداخت و حذف نرم if filter_status != 'all': if filter_status == 'deleted': factor_qs = factor_qs.filter(is_active=False) else: factor_qs = factor_qs.filter(pay_status=filter_status, is_active=True) # ۳. اعمال فیلتر نوع فاکتور (نصب، خدمات، گارانتی) if filter_type != 'all': factor_qs = factor_qs.filter(FactorType=filter_type) # ۴. اعمال فیلتر تاریخ شروع (از تاریخ) if az_tarikh: az_gregorian = _parse_jalali_date(az_tarikh) if az_gregorian: az_datetime = datetime.datetime.combine(az_gregorian, datetime.time.min) factor_qs = factor_qs.filter(create_date__gte=az_datetime) # ۵. اعمال فیلتر تاریخ پایان (تا تاریخ) if ta_tarikh: ta_gregorian = _parse_jalali_date(ta_tarikh) if ta_gregorian: ta_datetime = datetime.datetime.combine(ta_gregorian, datetime.time.max) factor_qs = factor_qs.filter(create_date__lte=ta_datetime) # مرتب‌سازی بر اساس آخرین تاریخ ایجاد factorha = factor_qs.order_by('-create_date') dateNow = datetime.datetime.now().date data = { 'customer': customer, 'categorys': categorys, 'dateNow': dateNow, 'factorha': factorha, 'search_query': search_query, 'filter_status': filter_status, 'filter_type': filter_type, 'az_tarikh': az_tarikh, 'ta_tarikh': ta_tarikh } return render(request, 'admin_panel/customerIDFactor.html', data) @login_required(login_url='/panel/p_login/') def customerFactor(request ,c_id, f_id): customer = Customer.objects.get(id=c_id) categorys = ProductCategory.objects.all() factorO = Factor.objects.get(customer=customer, id=f_id) fiteam = FactorIteam.objects.filter(factor=factorO) #all_prd = serializers.serialize("json", all_prda) #return JsonResponse(all_prd,safe = False) dateNow = datetime.datetime.now().date data = {'customer': customer, 'categorys': categorys, 'dateNow': dateNow, 'factorO': factorO, 'fiteam': fiteam} return render(request, 'admin_panel/customerFactor.html', data) def productsJson(request): from django.core import serializers from django.http import JsonResponse all_prd = Product.objects.all() all_prd = serializers.serialize("json", all_prd) return JsonResponse(all_prd,safe = False) def ActionJson(request, action_id): from django.core import serializers from django.http import JsonResponse AcJson = Action.objects.filter(id=action_id) AcJson = serializers.serialize("json", AcJson) return JsonResponse(AcJson,safe = False) def CustomerJson(request, c_id): from django.core import serializers from django.http import JsonResponse customerJson = Customer.objects.filter(id=c_id) customerJson = serializers.serialize("json", customerJson) return JsonResponse(customerJson,safe = False) @login_required(login_url='/panel/p_login/') def productMojodi(request, p_id): # پیدا کردن انبار کاربر با متد filter به جای get برای جلوگیری از کرش سیستم store_user = StoreRoom.objects.filter(owners=request.user).first() # اگر کاربر هنوز مالک هیچ انباری نشده است if not store_user: return HttpResponse("کاربر مالکیت انبار ندارد") # یا هر مقداری که فرانت‌اند انتظار دارد # محاسبه موجودی برای انبار پیدا شده totals = ProductInStore.objects.filter( Product_id=p_id, StoreRoom=store_user, year=1400 ).aggregate( total_in=Sum('s_in'), total_out=Sum('s_out') ) total_in = totals['total_in'] or 0 total_out = totals['total_out'] or 0 mojodi_prd = total_in - total_out return HttpResponse(str(mojodi_prd)) @login_required(login_url='/panel/p_login/') def customersAdd(request): an_name = request.POST.get('an_name', '') an_address = request.POST.get('an_address', '') installDate = request.POST.get('installDate', '') idnCode = request.POST.get('idnCode', '') # دریافت آرایه‌های عنوان و شماره تلفن titles_list = request.POST.getlist('phone_titles') phones_list = request.POST.getlist('phones') # ترکیب عنوان‌ها و شماره‌ها و حذف ردیف‌های خالی valid_phones = [] for title, phone in zip(titles_list, phones_list): if phone.strip(): valid_phones.append({ 'title': title.strip() if title.strip() else 'تلفن', 'phone': phone.strip() }) year, month, day = map(int, installDate.split('/')) installDate_gregorian = JalaliDate(year, month, day).to_gregorian() newCs = Customer() newCs.name_family = an_name newCs.address = an_address newCs.installDate = installDate_gregorian newCs.idCode = idnCode # حفظ سازگاری با کدهای قدیمی (ریختن ۳ شماره اول در فیلدهای قدیمی) newCs.phone1 = valid_phones[0]['phone'] if len(valid_phones) > 0 else '' newCs.phone2 = valid_phones[1]['phone'] if len(valid_phones) > 1 else '' newCs.phone3 = valid_phones[2]['phone'] if len(valid_phones) > 2 else '' newCs.save() # ذخیره شماره‌ها به صورت نامحدود در جدول PhoneNumber for item in valid_phones: PhoneNumber.objects.create(customer=newCs, title=item['title'], phone_number=item['phone']) return redirect('/panel/customers/') @login_required(login_url='/panel/p_login/') def customerEdit(request, c_id): an_name = request.POST.get('an_name', '') an_address = request.POST.get('an_address', '') installDate = request.POST.get('installDate', '') idnCode = request.POST.get('idnCode', '') laghv_bazdid = request.POST.get('laghv_bazdid', False) # دریافت آرایه‌های عنوان و شماره تلفن titles_list = request.POST.getlist('phone_titles') phones_list = request.POST.getlist('phones') valid_phones = [] for title, phone in zip(titles_list, phones_list): if phone.strip(): valid_phones.append({ 'title': title.strip() if title.strip() else 'تلفن', 'phone': phone.strip() }) year, month, day = map(int, installDate.split('/')) installDate_gregorian = JalaliDate(year, month, day).to_gregorian() newCs = get_object_or_404(Customer, id=c_id) newCs.name_family = an_name newCs.address = an_address newCs.installDate = installDate_gregorian newCs.idCode = idnCode newCs.laghv_bazdid = True if laghv_bazdid == 'true' else False # آپدیت فیلدهای قدیمی جهت حفظ سازگاری newCs.phone1 = valid_phones[0]['phone'] if len(valid_phones) > 0 else '' newCs.phone2 = valid_phones[1]['phone'] if len(valid_phones) > 1 else '' newCs.phone3 = valid_phones[2]['phone'] if len(valid_phones) > 2 else '' newCs.save() # بروزرسانی جدول بی‌نهایت (حذف قبلی‌ها و درج مجدد) PhoneNumber.objects.filter(customer=newCs).delete() for item in valid_phones: PhoneNumber.objects.create(customer=newCs, title=item['title'], phone_number=item['phone']) return redirect('/panel/customer/'+str(c_id)) @login_required(login_url='/panel/p_login/') def entezar(request): a = datetime.datetime.now() searchVal = request.GET.get('searchVal', '') searchOn = request.GET.get('searchOn', '') searchStatus = True full_path = request.get_full_path() from django.db.models import Q bazdid = os.environ.get('bazdid', 9) bazdid = int ( int(bazdid) * 4.3 ) dateTime6MonthAgo = a - datetime.timedelta(weeks=bazdid) ''' customers = Customer.objects.filter(name_family__isnull=False) m6mbazdid = [] for customerObj in customers: try: actionAa = Action.objects.filter(customer=customerObj, actionType='bazdid', status='finish').last() if actionAa.status == 'finish': customerObj.last_bazdid_dt = actionAa.actionDateTime customerObj.save() if actionAa.actionDateTime.date() < dateTime6MonthAgo.date(): m6mbazdid.append(actionAa) except: pass ''' if searchOn == 'address': print("addressaddressaddressaddressaddress") customers_objj = Customer.objects.filter(Q(last_bazdid_dt__lte=dateTime6MonthAgo) | Q(last_bazdid_dt__isnull=True), installDate__lte=dateTime6MonthAgo, laghv_bazdid=False, name_family__isnull=False, address__contains=searchVal).order_by('last_bazdid_dt') else: searchStatus = False customers_objj = Customer.objects.filter(Q(last_bazdid_dt__lte=dateTime6MonthAgo) | Q(last_bazdid_dt__isnull=True), installDate__lte=dateTime6MonthAgo, laghv_bazdid=False, name_family__isnull=False).order_by('last_bazdid_dt') #customers_objj = Customer.objects.filter(name_family__isnull=False, last_bazdid_dt__lte=dateTime6MonthAgo) try: entezar_bazdid = CacheTable.objects.get(key='9mahbazdid') entezar_bazdid.value = Customer.objects.filter( Q(last_bazdid_dt__lte=dateTime6MonthAgo) | Q(last_bazdid_dt__isnull=True), installDate__lte=dateTime6MonthAgo, laghv_bazdid=False, name_family__isnull=False).count() entezar_bazdid.save() except: pass paginator = Paginator(customers_objj, 20) # Show 25 contacts per page. page_number = request.GET.get('page', None) customerHa = paginator.get_page(page_number) return render(request, 'admin_panel/entezar.html', {'customers': customerHa, 'searchStatus': searchStatus, 'full_path': full_path, 'searchVal': searchVal, 'searchOn':searchOn}) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.OnlyAnbar', raise_exception=True) def stores(request): if request.user.username not in ('admin', 'office1', 'office2', 'office3', 'khatibi'): all_store = StoreRoom.objects.filter(user__id=request.user.id).order_by('id')[:20] else: all_store = StoreRoom.objects.all().order_by('id')[:20] data = {'all_store': all_store} return render(request, 'admin_panel/stores.html', data) from django.db.models import Sum, Subquery, OuterRef, IntegerField, Value from django.db.models.functions import Coalesce from django.shortcuts import render, get_object_or_404 from django.contrib.auth.decorators import login_required, permission_required from .models import StoreRoom, ProductCategory, Product, ProductInStore, StoreTransferRequest @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.OnlyAnbar', raise_exception=True) def store(request, s_id): productCategory = ProductCategory.objects.all() storeO = get_object_or_404(StoreRoom, id=s_id) searchStatus = False searchQS = request.POST.get('searchQS', '').strip() mojodi = request.POST.get('mojodi', '') # ۱. محاسبه موجودی فیزیکی کل کالاها با یک کوئری تک‌ضرب و سریع GROUP BY stock_balances = ProductInStore.objects.filter( StoreRoom=storeO, year=1400 ).values('Product_id').annotate( total=Sum('s_in') - Sum('s_out') ).order_by() stock_dict = {item['Product_id']: item['total'] for item in stock_balances} # ۲. محاسبه کل کالاهای در انتظار خروج pending_out_balances = StoreTransferRequest.objects.filter( source_store=storeO, status=StoreTransferRequest.Status.PENDING ).values('product_id').annotate( total=Sum('quantity') ).order_by() pending_out_dict = {item['product_id']: item['total'] for item in pending_out_balances} # ۳. محاسبه کل کالاهای در انتظار ورود pending_in_balances = StoreTransferRequest.objects.filter( target_store=storeO, status=StoreTransferRequest.Status.PENDING ).values('product_id').annotate( total=Sum('quantity') ).order_by() pending_in_dict = {item['product_id']: item['total'] for item in pending_in_balances} # ۴. استخراج سریع تمام آیدی‌های منحصربه‌فرد مرتبط با این انبار در سطح حافظه all_pids = set(stock_dict.keys()) | set(pending_out_dict.keys()) | set(pending_in_dict.keys()) # ۵. کوئری نهایی فوق‌بهینه برای خواندن کالاها (حل باگ N+1 با دستور select_related) products_qs = Product.objects.select_related('ProductCategory').filter(id__in=all_pids) # اعمال فیلتر متنی جستجو در سطح دیتابیس if searchQS: products_qs = products_qs.filter(product_name__icontains=searchQS) searchStatus = True # 🛠️ اصلاح فرآیند ترتیب: استفاده از 'id' جهت انطباق ۱۰۰٪ با ترتیب پیش‌فرض و سنتی دیتابیس شما # نکته: اگر مایلید بر اساس زمان ایجاد مرتب شود، می‌توانید آن را به 'createdDT' یا '-createdDT' تغییر دهید. products_list = list(products_qs.order_by('id')) # ۶. معماری زمان ثابت O(1): چسباندن پرسرعت دیتاها در حافظه رم سرور for prd in products_list: prd.mojodi = stock_dict.get(prd.id, 0) prd.pending_out = pending_out_dict.get(prd.id, 0) prd.pending_in = pending_in_dict.get(prd.id, 0) # ۷. اعمال فیلترهای منطقی موجودی در لایه پایتون if mojodi == "0": products_list = [p for p in products_list if p.mojodi <= 0] searchStatus = True elif mojodi == "1": products_list = [p for p in products_list if p.mojodi > 0] searchStatus = True data = { 'storeO': storeO, 'productSum': products_list, 's_id': s_id, 'searchStatus': searchStatus, "mojodi": mojodi, 'djsearchQS': searchQS, 'productCategory': productCategory } return render(request, 'admin_panel/store.html', data) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.OnlyAnbar', raise_exception=True) def operations(request, s_id): page = request.GET.get('page', 1) productCategory = ProductCategory.objects.all() storeO = get_object_or_404(StoreRoom, id=s_id) # بهینه‌سازی کلیدی: استفاده از select_related برای جلوگیری از کندی و پر شدن RAM دیتابیس operationsList = ProductInStore.objects.filter( StoreRoom=storeO, year=1400 ).select_related('Product').order_by('-createdDT') paginator = Paginator(operationsList, 25) try: data_list = paginator.page(page) except PageNotAnInteger: data_list = paginator.page(1) except EmptyPage: data_list = paginator.page(paginator.num_pages) data = { 'storeO': storeO, 'operationsList': data_list, 's_id': s_id, 'productCategory': productCategory } return render(request, 'admin_panel/operations.html', data) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.OnlyAnbar', raise_exception=True) def operationProduct(request, s_id, p_id): productCategory = ProductCategory.objects.all() storeO = get_object_or_404(StoreRoom, id=s_id) # بهینه‌سازی کلیدی در فیلتر اختصاصی محصول operationsList = ProductInStore.objects.filter( StoreRoom=storeO, year=1400, Product__id=p_id ).select_related('Product').order_by('-createdDT') data = { 'storeO': storeO, 'operationsList': operationsList, 's_id': s_id, 'productCategory': productCategory } return render(request, 'admin_panel/operations.html', data) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.OnlyAnbar', raise_exception=True) def newOperation(request, s_id, p_id): productCategory = ProductCategory.objects.all() storeO = StoreRoom.objects.get(id=s_id) allStores = StoreRoom.objects.exclude(id=s_id) product = Product.objects.get(id=p_id) data = {'s_id': s_id,'p_id': p_id, 'allStores': allStores, 'storeO': storeO, 'product':product, 'productCategory': productCategory} return render(request, 'admin_panel/newOperation.html', data ) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.OnlyAnbar', raise_exception=True) def store_transfers(request, s_id): """ داشبورد و کارتابل جامع مدیریت انتقال‌های انبار """ storeO = get_object_or_404(StoreRoom, id=s_id) # ۱. کارتابل ورودی (بارهایی که به سمت این انبار آمده و منتظر تایید ما هستند) # استفاده از select_related برای جلوگیری از صدها کوئری اضافی به دیتابیس incoming_pending = StoreTransferRequest.objects.filter( target_store=storeO, status=StoreTransferRequest.Status.PENDING ).select_related('product', 'source_store', 'requester').order_by('-created_at') # ۲. کارتابل خروجی (بارهایی که ما فرستادیم و منتظر تایید انبار مقصد هستیم) outgoing_pending = StoreTransferRequest.objects.filter( source_store=storeO, status=StoreTransferRequest.Status.PENDING ).select_related('product', 'target_store', 'requester').order_by('-created_at') # ۳. تاریخچه انتقالات اخیر (۵۰ مورد آخر تعیین تکلیف شده برای این انبار) history_list = StoreTransferRequest.objects.filter( Q(source_store=storeO) | Q(target_store=storeO) ).exclude( status=StoreTransferRequest.Status.PENDING ).select_related('product', 'source_store', 'target_store', 'approver').order_by('-action_date')[:50] data = { 'storeO': storeO, 's_id': s_id, 'incoming_pending': incoming_pending, 'outgoing_pending': outgoing_pending, 'history_list': history_list, } return render(request, 'admin_panel/store_transfers.html', data) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.OnlyAnbar', raise_exception=True) @require_POST def process_transfer_action(request, transfer_id): """ پردازش امن اقدامات انباردار (تایید، رد، انصراف) با تراکنش‌های اتمیک """ action = request.POST.get('action') reason = request.POST.get('reason', '').strip() # دریافت درخواست انتقال transfer = get_object_or_404(StoreTransferRequest, id=transfer_id) # تعیین انباری که کاربر از آنجا اقدام کرده جهت ریدایرکت صحیح redirect_store_id = transfer.target_store_id if action in ['approve', 'reject'] else transfer.source_store_id try: if action == 'approve': approve_transfer_request(request_id=transfer.id, approver=request.user) messages.success(request, f"انتقال {transfer.quantity} عدد {transfer.product.product_name} با موفقیت تایید و به موجودی انبار اضافه شد.") elif action == 'reject': if not reason: messages.error(request, "برای رد کردن بار، وارد کردن علت رد الزامی است.") return redirect('admin_panel:store_transfers', s_id=redirect_store_id) reject_transfer_request(request_id=transfer.id, rejector=request.user, reason=reason) messages.warning(request, f"درخواست انتقال بار رد شد و به انبار مبدا اطلاع داده شد.") elif action == 'cancel': cancel_transfer_request(request_id=transfer.id, user=request.user) messages.info(request, "درخواست انتقال توسط شما لغو گردید.") else: messages.error(request, "دستور ارسالی نامعتبر است.") except ValidationError as e: # نمایش خطاهای منطقی (مثلاً کمبود موجودی انبار مبدا در لحظه تایید) error_text = ' '.join(e.messages) if hasattr(e, 'messages') else str(e) messages.error(request, f"خطا در انجام عملیات: {error_text}") except PermissionError as e: messages.error(request, str(e)) except Exception as e: logger.error(f"Error processing transfer action #{transfer_id}: {str(e)}", exc_info=True) messages.error(request, "خطای سیستمی در پردازش تراکنش رخ داد.") return redirect('admin_panel:store_transfers', s_id=redirect_store_id) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewStore', raise_exception=True) def newStore(request): storeName = request.POST.get('storeName', '') newStore = StoreRoom() newStore.store_name = storeName newStore.save() return redirect('/panel/store/') @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.OnlyAnbar', raise_exception=True) def productAdd(request): namePrd = request.POST.get('namePrd', '') category = request.POST.get('category', '') price = request.POST.get('price', '') mojodiAval = request.POST.get('mojodiAval', '') storeroom = request.POST.get('storeroom', '') newProduct = Product() newProduct.product_name = namePrd newProduct.ProductCategory = ProductCategory.objects.get(id=category) newProduct.product_price = price newProduct.save() prd_in_store = ProductInStore() prd_in_store.Product = newProduct prd_in_store.StoreRoom = StoreRoom.objects.get(id=storeroom) prd_in_store.s_in = mojodiAval prd_in_store.desc = "موجودی اولیه: "+str(mojodiAval) prd_in_store.movement_type = ProductInStore.MovementType.INITIAL prd_in_store.save() #return HttpResponse(installDate) return redirect('/panel/store/1') @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewStore', raise_exception=True) def productEdit(request): prd_code = request.POST.get('prd_code', '') namePrd = request.POST.get('namePrd', '') category = request.POST.get('category', '') price = request.POST.get('price', '') #return HttpResponse(prd_code) editProduct = Product.objects.get(id=prd_code) editProduct.product_name = namePrd editProduct.ProductCategory = ProductCategory.objects.get(id=category) editProduct.product_price = price editProduct.save() return redirect('/panel/store/1') @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewStore', raise_exception=True) def productDel(request, p_id, s_id): Product.objects.get(id=p_id).delete() return redirect('/panel/store/'+str(s_id)) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.OnlyAnbar', raise_exception=True) def operation(request): if request.method != 'POST': return HttpResponseBadRequest("متد ارسالی نامعتبر است.") user_op = request.user user_store_room = get_object_or_404(StoreRoom, owners=user_op) # استفاده از توابع امن اختصاصی پروژه برای جداسازی مقادیر عددی prd_id = _safe_int(request.POST.get('prd_id')) action_type = request.POST.get('actionType', '') tedad = _safe_int(request.POST.get('tedad')) azanbar = _safe_int(request.POST.get('azanbar')) beanbar = _safe_int(request.POST.get('beanbar')) desc = request.POST.get('desc', '').strip() if tedad <= 0: return HttpResponseBadRequest("تعداد وارد شده باید بیشتر از صفر باشد.") s_room = get_object_or_404(StoreRoom, id=azanbar) product_obj = get_object_or_404(Product, id=prd_id) try: # ===================================================================== # بخش اول: انتقال بین انبارها (واگذاری کامل به لایه سرویس) # ===================================================================== if action_type == 'transfer': if not beanbar: return HttpResponse("انبار مقصد مشخص نشده است.") target_store = get_object_or_404(StoreRoom, id=_safe_int(beanbar)) # فراخوانی لایه سرویس برای ثبت درخواست انتقال (بدون تغییر مستقیم موجودی) create_transfer_request( source_store=s_room, target_store=target_store, product=product_obj, quantity=tedad, requester=request.user, desc=desc or f"درخواست انتقال از انبار {s_room.store_name} به {target_store.store_name}" ) logger.info( f"Transfer request created: {tedad}x {product_obj.product_name} from Store #{s_room.id} to Store #{target_store.id} by User #{request.user.id}" ) return redirect('/panel/store/' + str(s_room.id)) # ===================================================================== # بخش دوم: ورود و خروج دستی (کنترل مستقیم با Transaction و Lock) # ===================================================================== elif action_type in ['vorod', 'khoroj']: user_s_room = get_object_or_404(StoreRoom, owners=user_op) if not user_op.is_superuser and s_room != user_s_room: return HttpResponse("شما دسترسی ورود / خروج به این انبار ندارید") with transaction.atomic(): # ساخت کوئری پایه برای دریافت تراکنش‌های سال جاری کالا qs = ProductInStore.objects.filter( Product=product_obj, StoreRoom=s_room, year=1400 ) # قفل‌گذاری ردیف‌ها برای جلوگیری از Race Condition (در دیتابیس‌های پشتیبان) if connection.features.has_select_for_update: qs = qs.select_for_update() locked_records = list(qs) # --- الف) عملیات ورود دستی --- if action_type == 'vorod': ProductInStore.objects.create( StoreRoom=s_room, Product=product_obj, s_in=tedad, s_out=0, year=1400, desc=f"ورود: {tedad} به انبار {s_room.store_name}#{desc}", movement_type=ProductInStore.MovementType.DIRECT_IN ) # --- ب) عملیات خروج دستی --- elif action_type == 'khoroj': # محاسبه سریع موجودی در رم بدون فشار مجدد به دیتابیس total_in = sum(item.s_in for item in locked_records if item.s_in) total_out = sum(item.s_out for item in locked_records if item.s_out) mojodi_prd = total_in - total_out if mojodi_prd < tedad: # تطابق ۱۰۰ درصدی با پاسخ متنی مورد انتظار فرانت‌اند return HttpResponse("موجودی کافی نیست، موجودی فعلی: " + str(mojodi_prd)) ProductInStore.objects.create( StoreRoom=s_room, Product=product_obj, s_in=0, s_out=tedad, year=1400, desc=f"خروج: {tedad} از انبار {s_room.store_name}#{desc}", movement_type=ProductInStore.MovementType.DIRECT_OUT ) else: return HttpResponseBadRequest("نوع عملیات نامعتبر است.") except ValidationError as e: # مدیریت خطاهای اعتبارسنجی (مثل کمبود موجودی در لایه سرویس یا یکسان بودن انبارها) error_msg = ' '.join(e.messages) if hasattr(e, 'messages') else str(e) logger.warning(f"Validation error in store operation: {error_msg}") return HttpResponse(error_msg) except PermissionDenied as e: # مدیریت خطای دسترسی کاربر به انبار logger.warning(f"Permission denied in store operation for user {request.user.id}: {str(e)}") return HttpResponse("شما دسترسی لازم برای انجام این عملیات در این انبار را ندارید.", status=403) except Exception as e: logger.error(f"Critical error in store operation view: {str(e)}", exc_info=True) return HttpResponse("خطای سیستمی رخ داد. تراکنش لغو شد.", status=500) return redirect('/panel/store/' + str(s_room.id)) @login_required(login_url='/panel/p_login/') def newFactor(request, c_id): prdsList = request.POST.get('prdsList', '') sarResidDate = request.POST.get('sarResidDate', None) pardakhti = request.POST.get('pardakhti', None) #return HttpResponse(pardakhti) if pardakhti: pardakhti=int(pardakhti) if pardakhti>=1: pass else: pardakhti = None #return HttpResponse(sarResidDate) try: sarResidDate = digits.fa_to_en(digits.ar_to_fa(sarResidDate)) #return HttpResponse(sarResidDate) sarResidDate = sarResidDate.split("/") #return HttpResponse(sarResidDate[2]) sarResidDate = JalaliDate(int(sarResidDate[0]), int(sarResidDate[1]), int(sarResidDate[2])).to_gregorian() #return HttpResponse(sarResidDate) except: sarResidDate = None #return HttpResponse(sarResidDate[1]) #sarResidDate = JalaliDate(sarResidDate[0], sarResidDate[1], sarResidDate[2]).to_gregorian() payType = request.POST.get('payType', '') FactorDate = request.POST.get('FactorDate', '') FactorType = request.POST.get('FactorType', '') FactorDesc = request.POST.get('FactorDesc', '') Factorfail = False customerObj=Customer.objects.get(id=c_id) storeUser = StoreRoom.objects.get(user__id=request.user.id) import json prdsList = json.loads(prdsList) sum_price = 0 for prd in prdsList: if prd['garanty_Esh'] == ' ' or prd['garanty_Esh'] == '' or prd['garanty_Esh'] == None: sum_price += prd['prd_count'] * prd['price'] #return HttpResponse(prd_code) try: newFactor = Factor() newFactor.customer= customerObj newFactor.amount = sum_price if payType in ('naghd', 'kartKhan', 'cardToCard'): newFactor.pay_status = 'paid' else: newFactor.pay_status = 'unpaid' newFactor.FactorType = FactorType newFactor.PayType = payType newFactor.desc = FactorDesc if sarResidDate != None: newFactor.deadline = sarResidDate newFactor.user = User.objects.get(id=request.user.id) newFactor.save() except: try: Factorfail=True newFactor.delete() except: pass if Factorfail != True: #accFactor To article is_naghd = False articleHesabDari = Article() articleHesabDari.debit = newFactor.amount articleHesabDari.credit = 0 articleHesabDari.customer = customerObj articleHesabDari.factor = newFactor if payType in ('naghd', 'kartKhan', 'cardToCard'): is_naghd = True articleHesabDari.type = payType articleHesabDari.desc = str(FactorType) + " " + str(customerObj.name_family) + " طی فاکتور شماره: " + str(newFactor.id) articleHesabDari.save() if is_naghd: articleHesabDari2 = Article() articleHesabDari2.debit = 0 articleHesabDari2.credit = newFactor.amount articleHesabDari2.customer = customerObj articleHesabDari2.type = payType articleHesabDari2.factor = newFactor articleHesabDari2.desc = str(FactorType) + " " + str(customerObj.name_family) + " تسویه نقدی فاکتور شماره: " + str(newFactor.id) articleHesabDari2.save() elif pardakhti != None and pardakhti!='' and pardakhti != ' ': articleHesabDari2 = Article() articleHesabDari2.debit = 0 articleHesabDari2.credit = pardakhti articleHesabDari2.customer = customerObj articleHesabDari2.type = payType articleHesabDari2.factor = newFactor articleHesabDari2.desc = str(customerObj.name_family) + " پرداخت نقد در محل برا فاکتور " + str(newFactor.id) articleHesabDari2.save() for prd in prdsList: try: fIteams = FactorIteam() fIteams.factor = newFactor if prd['prd_id'] == 9999: Gproduct = None fIteams.product = Gproduct fIteams.product_name = prd['product_name'] fIteams.num = prd['prd_count'] fIteams.amount = int(prd['priceKol']) else: Gproduct = Product.objects.get(id=prd['prd_id']) fIteams.product = Gproduct fIteams.product_name = Gproduct.product_name fIteams.num = prd['prd_count'] fIteams.amount = prd['prd_count'] * Gproduct.product_price fIteams.garantyEsh = prd['garanty_Esh'] fIteams.save() except: Factorfail = True newFactor.delete() break if Factorfail != True: for prd in prdsList: if prd['prd_id'] != 9999: pInStore = ProductInStore() sRoom = storeUser productO = Product.objects.get(id=prd['prd_id']) pInStore.StoreRoom = sRoom pInStore.Product = productO pInStore.factor = newFactor productS_in = ProductInStore.objects.filter(Product=productO, StoreRoom=sRoom, year=1400).aggregate(Sum('s_in'))['s_in__sum'] productS_out = ProductInStore.objects.filter(Product=productO, StoreRoom=sRoom, year=1400).aggregate(Sum('s_out'))['s_out__sum'] mojodi_prd = productS_in - productS_out if mojodi_prd >= prd['prd_count']: pInStore.s_out = prd['prd_count'] if prd['garanty_Esh'] != ' ' and prd['garanty_Esh'] != '' and prd['garanty_Esh'] != None: pInStore.desc = "خروج: " + str(prd['prd_count']) + "#"+str(prd['garanty_Esh'])+ "#" + str(customerObj.name_family) + " * " + str(request.user.first_name) + " " + str(request.user.last_name) else: pInStore.desc = "خروج: " + str(prd['prd_count']) + "#فروش" + "#" + str(customerObj.name_family) + " * " + str(request.user.first_name) + " " + str(request.user.last_name) pInStore.save() else: return HttpResponse("موجودی کافی نیست، موجودی فعلی: "+ str(mojodi_prd)) return redirect('/panel/customer/'+str(c_id)+'/factors/') @login_required(login_url='/panel/p_login/') def newFactor2(request, c_id): if request.method != 'POST': return HttpResponseBadRequest("متد ارسالی باید POST باشد.") user_op = request.user try: prds_list = json.loads(request.POST.get('prdsList') or '[]') json_pardakht_ha = json.loads(request.POST.get('PardakhtHa') or '{}') except json.JSONDecodeError: return HttpResponseBadRequest("فرمت داده‌های ارسالی JSON نامعتبر است.") factor_type = request.POST.get('FactorType', '') factor_desc = request.POST.get('FactorDesc', '') sar_resid_date = _parse_jalali_date(request.POST.get('sarResidDate')) customer_obj = get_object_or_404(Customer, id=c_id) store_room = get_object_or_404(StoreRoom, owners=user_op) # رفع مشکل متنی/عددی در استخراج شناسه کالاها product_ids = [] for item in prds_list: p_id = item.get('prd_id') if p_id and _safe_int(p_id) != 9999: product_ids.append(_safe_int(p_id)) products_by_id = {p.id: p for p in Product.objects.filter(id__in=product_ids)} sum_price = sum( _safe_int(item.get('prd_count')) * _safe_int(item.get('price')) for item in prds_list if not str(item.get('garanty_Esh', '')).strip() ) try: with transaction.atomic(): new_factor = Factor.objects.create( customer=customer_obj, amount=sum_price, FactorType=factor_type, desc=factor_desc, deadline=sar_resid_date, user=user_op ) Article.objects.create( debit=sum_price, credit=0, customer=customer_obj, factor=new_factor, desc=f"{customer_obj.name_family} فاکتور شماره: {new_factor.id}", user=user_op ) payment_map = { 'l_naghd': 'نقد', 'l_kartbekart': 'کارت به کارت', 'l_kartKhan': 'کارت خوان', 'l_takhfif': 'تخفیف' } for key, amount_raw in json_pardakht_ha.items(): amount = _safe_int(amount_raw) if amount <= 0: continue if key in payment_map: nahve_pardakht = payment_map[key] Article.objects.create( debit=0, credit=amount, customer=customer_obj, factor=new_factor, desc=f"{nahve_pardakht} {customer_obj.name_family} فاکتور شماره: {new_factor.id}", nahvePardakht=nahve_pardakht, payStatus="true", user=user_op ) elif key in ['l_nesiye', 'l_checkPrice']: is_nesiye = (key == 'l_nesiye') type_str = 'nesiye' if is_nesiye else 'check' ts_key = 'l_nesiyeSarResid' if is_nesiye else 'l_checkPriceSarResid' sar_resid_dt = _parse_timestamp(json_pardakht_ha.get(ts_key)) Sarresid.objects.create( customer=customer_obj, price=amount, payStatus="false", type=type_str, user=user_op, factor=new_factor, sarresidDT=sar_resid_dt ) factor_items_pool = [] user_fullname = f"{user_op.first_name} {user_op.last_name}".strip() or user_op.username for item in prds_list: # تبدیل امن شناسه به عدد prd_id = _safe_int(item.get('prd_id')) prd_count = _safe_int(item.get('prd_count')) garanty_esh = item.get('garanty_Esh', '') if prd_id == 9999: factor_items_pool.append(FactorIteam( factor=new_factor, product=None, product_name=item.get('product_name', ''), num=prd_count, amount=_safe_int(item.get('priceKol')), garantyEsh=garanty_esh )) else: product_obj = products_by_id.get(prd_id) if not product_obj: raise InventoryException(f"خطا در دریافت محصول: محصولی با شناسه {prd_id} در سیستم وجود ندارد.") factor_items_pool.append(FactorIteam( factor=new_factor, product=product_obj, product_name=product_obj.product_name, num=prd_count, amount=prd_count * _safe_int(product_obj.product_price), garantyEsh=garanty_esh )) FactorIteam.objects.bulk_create(factor_items_pool, batch_size=500) # فرآیند کسر از انبار (Enterprise Level) for item in prds_list: prd_id = _safe_int(item.get('prd_id')) if prd_id == 9999: continue prd_count = _safe_int(item.get('prd_count')) garanty_esh = item.get('garanty_Esh', '') product_obj = products_by_id.get(prd_id) if not product_obj: continue # ۱. کوئری پایه qs = ProductInStore.objects.filter( Product=product_obj, StoreRoom=store_room, year=1400 ) # ۲. قفل دیتابیس هوشمند و سازگار با تمام پایگاه‌های داده if connection.features.has_select_for_update: qs = qs.select_for_update() # ۳. بارگذاری امن در رم برای جلوگیری از خطای Aggregate در دیتابیس قفل‌شده locked_records = list(qs) # ۴. محاسبه دقیق و پرسرعت در پایتون total_in = sum(rec.s_in for rec in locked_records if rec.s_in) total_out = sum(rec.s_out for rec in locked_records if rec.s_out) mojodi_prd = total_in - total_out if mojodi_prd < prd_count: # استفاده از متن دقیق خطای قبلی جهت حفظ سازگاری با فرانت‌اند raise InventoryException(f"موجودی کافی نیست، موجودی فعلی {product_obj.product_name}: {mojodi_prd}") # ۵. تشخیص نوع تراکنش برای سیستم گزارش‌گیری جدید انبار if garanty_esh and str(garanty_esh).strip(): desc_text = f"خروج: {prd_count}#{garanty_esh}#{customer_obj.name_family} * {user_fullname}" if "اشانتیون" in str(garanty_esh): m_type = ProductInStore.MovementType.PROMOTION else: m_type = ProductInStore.MovementType.WARRANTY else: desc_text = f"خروج: {prd_count}#فروش#{customer_obj.name_family} * {user_fullname}" m_type = ProductInStore.MovementType.SALE # ۶. ثبت تراکنش خروجی انبار ProductInStore.objects.create( StoreRoom=store_room, Product=product_obj, factor=new_factor, s_out=prd_count, desc=desc_text, movement_type=m_type, # ثبت ساختاریافته نوع خروج برای سرعت در گزارش‌گیری year=1400 ) except InventoryException as e: return HttpResponse(str(e), status=400) except Exception as e: logger.error(f"Error creating factor for customer {c_id}: {str(e)}", exc_info=True) return HttpResponse("خطایی غیرمنتظره در سرور رخ داد. اطلاعات ذخیره نشد.", status=500) return redirect(f'/panel/customer/{c_id}/factors/') @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.DelFactor', raise_exception=True) @require_POST def FactorDel(request, c_id, f_id): customer_obj = get_object_or_404(Customer, id=c_id) factor_initial = get_object_or_404(Factor, id=f_id, customer=customer_obj) if factor_initial.pay_status == 'cancell' or not factor_initial.is_active: return HttpResponseBadRequest("این فاکتور قبلاً باطل یا برگشت داده شده است.") user_fullname = f"{request.user.first_name} {request.user.last_name}".strip() or request.user.username cancel_reason = request.POST.get('cancel_reason', 'error') payment_action = request.POST.get('payment_action', 'keep') if cancel_reason == 'return': reason_text = "برگشت از فروش (مرجوعی مشتری)" inv_movement = ProductInStore.MovementType.RETURN else: reason_text = "اصلاح خطای کاربری" inv_movement = ProductInStore.MovementType.ERROR_CORRECTION try: with transaction.atomic(): if connection.features.has_select_for_update: factor = Factor.objects.select_for_update().get(pk=factor_initial.pk) else: factor = Factor.objects.get(pk=factor_initial.pk) if factor.pay_status == 'cancell' or not factor.is_active: return HttpResponseBadRequest("خطای همزمانی: این فاکتور در لحظه فرستادن درخواست باطل شده بود.") factor.is_active = False factor.pay_status = 'cancell' factor.save(update_fields=['is_active', 'pay_status']) # واکشی اسناد پرداخت قبل از ایجاد سند جدید (جلوگیری از باگ تکرار سند) payments_to_reverse = [] if payment_action == 'reverse' or cancel_reason == 'error': payments_to_reverse = list(Article.objects.filter( Q(debit=0) | Q(debit__isnull=True), factor=factor, credit__gt=0 )) # ۱. صدور سند معکوس مالی برای کل مبلغ فاکتور factor_amount = int(factor.amount or 0) if factor_amount > 0: Article.objects.create( debit=0, credit=factor_amount, customer=customer_obj, factor=factor, type=factor.PayType, desc=f"برگشت از فروش و ابطال فاکتور شماره {factor.id} (مرجوعی کالا توسط مشتری) - کاربر: {user_fullname}" if cancel_reason == 'return' else f"اصلاح خطای سیستم: ابطال فاکتور اشتباه شماره {factor.id} (بدون جابجایی واقعی کالا) - کاربر: {user_fullname}", user=request.user, payStatus="true" ) # ۲. خنثی کردن پرداخت‌های اولیه فاکتور for p in payments_to_reverse: Article.objects.create( debit=p.credit, credit=0, customer=customer_obj, factor=factor, type=p.type, nahvePardakht=p.nahvePardakht, desc=f"عودت وجه پرداخت شده / ابطال رسید فاکتور {factor.id} (به دلیل مرجوعی کالا) - کاربر: {user_fullname}" if cancel_reason == 'return' else f"اصلاح خطای سیستم: ابطال رسید پرداخت فاکتور اشتباه شماره {factor.id} - کاربر: {user_fullname}", user=request.user, payStatus="true" ) # ۳. 🛠️ اصلاح انبار (حل باگ عدم تغییر موجودی سیستم) stock_movements = list(ProductInStore.objects.filter(factor=factor, s_out__gt=0)) for movement in stock_movements: pInStore = ProductInStore( StoreRoom=movement.StoreRoom, Product=movement.Product, factor=factor, s_in=movement.s_out, # برگرداندن تعداد کسر شده به ستون ورودی s_out=0, year=movement.year, # 💡 فیکس قطعی: استفاده دقیق از سال مالی تراکنش اصلی (۱۴۰۰) جهت همخوانی با توابع سیستم desc=f"ورود به انبار بابت برگشت از فروش کالا (فاکتور {factor.id}) - کاربر: {user_fullname}" if cancel_reason == 'return' else f"ورود به انبار بابت اصلاح خطای ثبت فاکتور {factor.id} - کاربر: {user_fullname}", movement_type=inv_movement ) pInStore.save() # فراخوانی متد save مدل شما که محاسبات تاریخچه و موجودی لحظه‌ای را انجام می‌دهد # ۴. ابطال اقساط و سررسیدهای پاس نشده Sarresid.objects.filter(factor=factor, payStatus="false").update( payStatus="cancell", desc=f"ابطال بابت {reason_text} فاکتور {factor.id}" ) logger.info(f"Factor {factor.id} successfully cancelled. Reason: {cancel_reason}, Payment: {payment_action}") except Exception as e: logger.error(f"Critical error in FactorDel for factor {f_id}: {str(e)}", exc_info=True) return HttpResponse("خطای سیستمی در فرآیند ابطال و صدور اسناد اصلاحی رخ داد.", status=500) return redirect('admin_panel:customerFactors', c_id=c_id) from django.contrib import messages from django.shortcuts import get_object_or_404, redirect import logging logger = logging.getLogger(__name__) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.DelCustomer', raise_exception=True) def customerDel(request, c_id): customer_obj = get_object_or_404(Customer, id=c_id) try: # ۱. بررسی وجود هرگونه سابقه مالی/حسابداری (حتی با مانده صفر) has_articles = Article.objects.filter(customer=customer_obj).exists() # ۲. بررسی وجود هرگونه سابقه چک یا نسیه (حتی در صورت پاس شدن و تسویه کامل) has_sarresids = Sarresid.objects.filter(customer=customer_obj).exists() # ۳. بررسی وجود هرگونه فاکتور ثبت شده برای این مشتری has_factors = Factor.objects.filter(customer=customer_obj).exists() # ۴. بررسی وجود هرگونه سابقه اقدام، بازدید یا نظرسنجی has_actions = Action.objects.filter(customer=customer_obj).exists() # ۵. اگر هر کدام از این سوابق وجود داشته باشد، به هیچ عنوان اجازه غیرفعال‌سازی/حذف نمی‌دهیم if has_articles or has_sarresids or has_factors or has_actions: error_msg = f"عملیات ناموفق: مشتری '{customer_obj.name_family}' به دلیل داشتن سابقه در سیستم قابل حذف نیست! دلایل: " reasons = [] if has_articles: reasons.append("داشتن تراکنش مالی یا گردش حسابداری") if has_sarresids: reasons.append("داشتن سابقه چک یا نسیه (حتی تسویه شده)") if has_factors: reasons.append("داشتن فاکتور ثبت‌شده") if has_actions: reasons.append("داشتن سابقه اقدامات، بازدید یا نظرسنجی") error_msg += "، ".join(reasons) + "." # ارسال پیام خطا به تمپلیت و بازگرداندن کاربر به صفحه قبلی messages.error(request, error_msg) return redirect(request.META.get('HTTP_REFERER', '/panel/customers/')) # ۶. در صورتی که مشتری کاملاً خام بود و هیچ سابقه‌ای در هیچ بخشی نداشت، حذف می‌شود customer_obj.delete() messages.success(request, f"مشتری '{customer_obj.name_family}' که فاقد هرگونه سابقه‌ای بود، با موفقیت از سیستم حذف شد.") return redirect('/panel/customers/') except Exception as e: logger.error(f"Error in deleting customer {c_id}: {str(e)}", exc_info=True) messages.error(request, "خطای سیستمی در بررسی سوابق و حذف مشتری رخ داد.") return redirect(request.META.get('HTTP_REFERER', '/panel/customers/')) @login_required(login_url='/panel/p_login/') def customerHesabdari(request ,c_id): customer = Customer.objects.get(id=c_id) try: debit = Article.objects.filter(customer=customer).aggregate(Sum('debit'))['debit__sum'] credit = Article.objects.filter(customer=customer).aggregate(Sum('credit'))['credit__sum'] mandehesabCs = debit - credit articls = Article.objects.filter(customer=customer).order_by('-createdDT') except: articls =None mandehesabCs = 0 dateNow = datetime.datetime.now().date data = {'customer': customer, 'mandehesabCs': mandehesabCs, 'articls': articls} return render(request, 'admin_panel/customerHesabDari.html', data) @login_required(login_url='/panel/p_login/') def sarresidh(request ,c_id): customer = Customer.objects.get(id=c_id) try: all_sarresid = Sarresid.objects.filter(customer=customer).order_by('-sarresidDT') except: all_sarresid = None data = {'customer': customer, 'all_sarresid': all_sarresid} return render(request, 'admin_panel/customerSarresid.html', data) @login_required(login_url='/panel/p_login/') def customerHesabdariAdd(request ,c_id): user_op = request.user price = request.POST.get('price', None) type = request.POST.get('type', None) typePy = request.POST.get('typePy', None) desc = request.POST.get('desc', None) nahvePardakhat = request.POST.get('nahvePardakhat', None) newArticl = Article() newArticl.customer = Customer.objects.get(id=c_id) newArticl.desc = desc newArticl.typePy = typePy if type == 'credit': newArticl.credit = price newArticl.debit = 0 elif type == 'debit': newArticl.debit = price newArticl.credit = 0 newArticl.user = user_op newArticl.nahvePardakht = nahvePardakhat newArticl.save() return redirect('/panel/customer/'+str(c_id)+'/hesabdari') @login_required(login_url='/panel/p_login/') def customerAction(request, c_id): # استفاده از get_object_or_404 برای امنیت بیشتر customer = get_object_or_404(Customer, id=c_id) # دریافت پارامترهای فیلتر از URL (پیش‌فرض: all) filter_type = request.GET.get('actionType', 'all') filter_status = request.GET.get('status', 'all') # کوئری پایه برای اقدامات این مشتری actions = Action.objects.filter(customer=customer) # اعمال فیلتر بر اساس نوع اقدام (بازدید، نصب و ...) if filter_type != 'all': actions = actions.filter(actionType=filter_type) # اعمال فیلتر بر اساس وضعیت (انجام شده، در انتظار و ...) if filter_status != 'all': actions = actions.filter(status=filter_status) # مرتب‌سازی نزولی بر اساس تاریخ ایجاد actions = actions.order_by("-createdDT") allUsers = User.objects.filter().exclude(username='admin').exclude(username='fesahat') data = { 'customer': customer, 'actions': actions, 'allUsers': allUsers, # ارسال مقادیر فیلتر به قالب برای حفظ وضعیت انتخاب‌شده در دراپ‌داون‌ها 'filter_type': filter_type, 'filter_status': filter_status } return render(request, 'admin_panel/customerAction.html', data) @login_required(login_url='/panel/p_login/') def customerNazar(request ,c_id): customer = Customer.objects.get(id=c_id) actions = Action.objects.filter(customer=customer, nazarDT__isnull=False).order_by("-createdDT") allUsers = User.objects.filter().exclude(username='admin').exclude(username='fesahat') data = {'customer': customer, 'actions': actions, 'allUsers': allUsers} return render(request, 'admin_panel/customer/nazar.html', data) @login_required(login_url='/panel/p_login/') def customerNewAction(request ,c_id): actionType = request.POST.get('actionType', None) dateTimeAnjam = request.POST.get('dateTimeAnjam', None) #return HttpResponse(float(dateTimeAnjam+".0")) try: dateTimeAnjam = datetime.datetime.fromtimestamp(int(dateTimeAnjam)/1e3) except: pass desc = request.POST.get('desc', None) userID = request.POST.get('userID', None) is_done = request.POST.get('is_done', None) result = request.POST.get('result', None) FinishDateTime = request.POST.get('FinishDateTime', None) try: FinishDateTime = datetime.datetime.fromtimestamp(int(FinishDateTime)/1e3) except: pass customer_obj = Customer.objects.get(id=int(c_id)) newAction = Action() newAction.actionType = actionType newAction.actionDateTime = dateTimeAnjam newAction.desc = desc newAction.forUser = User.objects.get(id=int(userID)) newAction.customer = customer_obj if is_done: newAction.status = 'finish' newAction.finishDateTime = FinishDateTime newAction.result = result else: newAction.status = 'waite' newAction.save() if newAction.status == 'finish' and newAction.actionType == 'bazdid': customer_obj.last_bazdid_dt = newAction.finishDateTime customer_obj.save() return redirect('/panel/customer/'+str(c_id)+'/action') @login_required(login_url='/panel/p_login/') def ActionList(request): actions = Action.objects.filter(forUser__id=request.user.id, status='waite').order_by("-createdDT") allUsers = User.objects.filter().exclude(username='admin').exclude(username='fesahat') data = {'actions': actions, 'allUsers': allUsers} return render(request, 'admin_panel/ActionList.html', data) @login_required(login_url='/panel/p_login/') def ActionListUser(request, u_id): actions = Action.objects.filter(forUser__id=u_id, status='waite').order_by("-createdDT") allUsers = User.objects.filter().exclude(username='admin').exclude(username='fesahat') data = {'actions': actions, 'allUsers': allUsers} return render(request, 'admin_panel/ActionList.html', data) @login_required(login_url='/panel/p_login/') def customerEditAction(request ,c_id, action_id): actionType = request.POST.get('actionType', None) dateTimeAnjam = request.POST.get('dateTimeAnjam', None) print("ok") #return HttpResponse(float(dateTimeAnjam+".0")) try: dateTimeAnjam = datetime.datetime.fromtimestamp(int(dateTimeAnjam)/1e3) except: pass desc = request.POST.get('desc', None) userID = request.POST.get('userID', None) is_done = request.POST.get('is_done', None) result = request.POST.get('result', None) FinishDateTime = request.POST.get('FinishDateTime', None) try: FinishDateTime = datetime.datetime.fromtimestamp(int(FinishDateTime)/1e3) except: pass customer_obj = Customer.objects.get(id=int(c_id)) newAction = Action.objects.get(id=action_id) newAction.actionType = actionType newAction.actionDateTime = dateTimeAnjam newAction.desc = desc newAction.forUser = User.objects.get(id=int(userID)) newAction.customer = customer_obj if is_done: newAction.status = 'finish' newAction.finishDateTime = FinishDateTime newAction.result = result else: newAction.status = 'waite' newAction.save() if newAction.status == 'finish' and newAction.actionType == 'bazdid': customer_obj.last_bazdid_dt = newAction.finishDateTime customer_obj.save() #return redirect('/panel/customer/'+str(c_id)+'/action') return redirect(request.META.get('HTTP_REFERER', 'redirect_if_referer_not_found')) @login_required(login_url='/panel/p_login/') def ActionListAll(request): actions = Action.objects.filter().order_by("-id") allUsers = User.objects.filter().exclude(username='admin').exclude(username='fesahat') data = {'actions': actions, 'allUsers': allUsers} return render(request, 'admin_panel/ActionList.html', data) @login_required(login_url='/panel/p_login/') def ActionListToday(request): startTodayDT= datetime.datetime.today().strftime('%Y-%m-%d 00:00:00') endTodayDT= datetime.datetime.today().strftime('%Y-%m-%d 23:59:59') #startTodayDT = datetime.datetime.strptime(startTodayDT, '%Y-%m-%d-00:00:00') #endTodayDT = datetime.datetime.strptime(endTodayDT, '%Y-%m-%d-23:59:59') #return HttpResponse(todayDT) actions = Action.objects.filter(actionDateTime__gte=startTodayDT,actionDateTime__lte=endTodayDT, forUser__id=request.user.id, status='waite').order_by("-actionDateTime")[:50] allUsers = User.objects.filter().exclude(username='admin').exclude(username='fesahat') data = {'actions': actions, 'allUsers': allUsers} return render(request, 'admin_panel/ActionList.html', data) @login_required(login_url='/panel/p_login/') def ActionListUserToday(request, u_id): dateTimeAction = request.GET.get('dateTime', None) status = request.GET.get('status', None) unixADT = dateTimeAction if dateTimeAction: dateTimeAction = datetime.datetime.fromtimestamp(int(dateTimeAction)/1e3) startTodayDT = dateTimeAction.strftime('%Y-%m-%d 00:00:00') endTodayDT= dateTimeAction.strftime('%Y-%m-%d 23:59:59') else: startTodayDT= datetime.datetime.today().strftime('%Y-%m-%d 00:00:00') endTodayDT= datetime.datetime.today().strftime('%Y-%m-%d 23:59:59') #startTodayDT = datetime.datetime.strptime(startTodayDT, '%Y-%m-%d-00:00:00') #endTodayDT = datetime.datetime.strptime(endTodayDT, '%Y-%m-%d-23:59:59') #return HttpResponse(todayDT) if status == 'waite' or status == None or status == '' or status == ' ': status = 'waite' actions = Action.objects.filter(actionDateTime__gte=startTodayDT,actionDateTime__lte=endTodayDT, forUser__id=u_id, status=status).order_by("-actionDateTime")[:50] actionPr = Action.objects.filter(actionDateTime__lt=startTodayDT, forUser__id=u_id, status=status).order_by("-actionDateTime")[:200] elif status == 'finish': actionPr = None actions = Action.objects.filter(actionDateTime__gte=startTodayDT,actionDateTime__lte=endTodayDT, forUser__id=u_id, status=status).order_by("-actionDateTime")[:50] allUsers = User.objects.filter().exclude(username='admin').exclude(username='fesahat') data = {'actions': actions, 'allUsers': allUsers, 'u_id': u_id, "unixADT": unixADT, "status": status, 'actionPr': actionPr} return render(request, 'admin_panel/ActionList.html', data) import datetime from django.db.models import Q from django.shortcuts import render from django.contrib.auth.decorators import login_required from .models import Action @login_required(login_url='/panel/p_login/') def rezayat(request): statusRezayat = request.GET.get('status', 'waite') now = datetime.datetime.now() # محاسبه بازه‌های زمانی ۳۰ روز و ۶۰ روز قبل dt_30_days_ago = now - datetime.timedelta(days=30) dt_60_days_ago = now - datetime.timedelta(days=60) if statusRezayat == 'finish': # نمایش نظرسنجی‌های تعیین‌تکلیف شده (ثبت شده یا کنسل شده) actions = Action.objects.exclude(actionType='vosol').filter( status='finish', nazarDT__isnull=False ).order_by("-nazarDT")[:500] else: statusRezayat = 'waite' # نمایش نظرسنجی‌های در انتظار با اعمال شرط عدم انقضای زمان actions = Action.objects.exclude(actionType='vosol').filter( status='finish', nazarDT__isnull=True, # نظرسنجی هنوز نهایی نشده createdDT__gte=dt_60_days_ago # حداکثر ۶۰ روز از ایجاد گذشته باشد ).filter( Q(finishDateTime__isnull=True) | Q(finishDateTime__gte=dt_30_days_ago) # حداکثر ۳۰ روز از اتمام گذشته باشد ).filter( Q(nextNazarDT__isnull=True) | Q(nextNazarDT__lte=now) # موعد پیگیری رسیده یا تعویقی نداشته باشد ).order_by("-id")[:500] data = {'actions': actions, "status": statusRezayat} return render(request, 'admin_panel/customerRezayat.html', data) @login_required(login_url='/panel/p_login/') def rezayatEdit(request, ac_id): csGrade = request.POST.get('csGrade', None) csDesc = request.POST.get('csDesc', None) if csDesc and csGrade: action = get_object_or_404(Action, id=ac_id) if csGrade == '-1': # عدم موفقیت: تنظیم nextNazarDT برای ۳ روز بعد (nazarDT پر نمی‌شود تا نهایی نشود) action.nazar = csDesc action.grade = int(csGrade) action.nextNazarDT = datetime.datetime.now() + datetime.timedelta(days=3) elif csGrade == '-2': # کنسل کردن: پر کردن nazarDT برای خروج کامل از لیست کارهای در انتظار action.nazar = csDesc action.grade = int(csGrade) action.nazarDT = datetime.datetime.now() action.nextNazarDT = None else: # ثبت امتیاز عادی action.nazar = csDesc action.grade = int(csGrade) action.nazarDT = datetime.datetime.now() action.nextNazarDT = None action.save() return redirect('/panel/rezayat/') else: return HttpResponse('لطفا همه فیلد ها را پر کنید') @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewReportMali', raise_exception=True) def rezayat_dashboard(request): # دریافت تمامی نظرسنجی‌های ثبت شده معتبر (که تاریخ نظرسنجی دارند) valid_surveys = Action.objects.filter( nazarDT__isnull=False, grade__in=[5, 10, 15, 20] ) # ۱. محاسبات شاخص‌های کلیدی (KPIs) total_surveys = valid_surveys.count() avg_grade = valid_surveys.aggregate(Avg('grade'))['grade__avg'] or 0 # ۲. استخراج داده‌ها برای نمودار توزیع رضایت grade_distribution = valid_surveys.values('grade').annotate(count=Count('id')) chart_dict = {20: 0, 15: 0, 10: 0, 5: 0} for item in grade_distribution: g = item['grade'] if g in chart_dict: chart_dict[g] = item['count'] chart_labels = ['عالی (۲۰)', 'متوسط (۱۵)', 'ضعیف (۱۰)', 'بد (۵)'] chart_data_list = [chart_dict[20], chart_dict[15], chart_dict[10], chart_dict[5]] # ۳. تبدیل امن آرایه‌ها به JSON برای Chart.js chart_labels_json = json.dumps(chart_labels, ensure_ascii=False) chart_data_json = json.dumps(chart_data_list) has_chart_data = sum(chart_data_list) > 0 # ۴. واکشی لیست مشتریان نیازمند پیگیری (شامل متوسط ۱۵، ضعیف ۱۰ و بد ۵) low_score_actions = Action.objects.filter( nazarDT__isnull=False, grade__in=[5, 10, 15] # <-- امتیاز ۱۵ (متوسط) نیز اضافه شد ).order_by('-nazarDT') allUsers = User.objects.filter().exclude(username='admin').exclude(username='fesahat') context = { 'total_surveys': total_surveys, 'avg_grade': round(avg_grade, 1), 'chart_labels_json': chart_labels_json, 'chart_data_json': chart_data_json, 'has_chart_data': has_chart_data, 'low_score_actions': low_score_actions, # تعداد کل موارد نیازمند پیگیری (متوسط + ضعیف + بد) 'unhappy_count': chart_dict[15] + chart_dict[10] + chart_dict[5], 'allUsers': allUsers, } return render(request, 'admin_panel/report_rezayat.html', context) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewStore', raise_exception=True) def passCheck(request, articl_id): sarresidINC = Sarresid.objects.get(id=articl_id) user_op = request.user desc = request.POST.get('message') articleHesabDari2 = Article() articleHesabDari2.debit = 0 articleHesabDari2.credit = sarresidINC.price articleHesabDari2.customer = sarresidINC.customer articleHesabDari2.factor = sarresidINC.factor articleHesabDari2.desc = 'وصول چک' + " " + str(sarresidINC.customer.name_family) if sarresidINC.factor: articleHesabDari2.desc += " فاکتور شماره: " + str(sarresidINC.factor.id) articleHesabDari2.nahvePardakht = 'وصول چک' articleHesabDari2.payStatus = "true" articleHesabDari2.user = user_op articleHesabDari2.save() sarresidINC.payStatus = "true" sarresidINC.desc = desc sarresidINC.save() return HttpResponse("ok") #return redirect(request.META.get('HTTP_REFERER')) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewStore', raise_exception=True) def addSarresid(request, cs_id): try: customerObj = Customer.objects.get(id=cs_id) sarrresidUNIX = request.POST.get('n_sarresidDateTime', None) sarresidPrice = request.POST.get('n_sarresidPrice', None) sarresidType = request.POST.get('n_sarresidType', None) sarResidDateTime01 = datetime.datetime.fromtimestamp(int(sarrresidUNIX)/1000.0) articleHesabDariObj = Article() articleHesabDariObj.debit = int(sarresidPrice) articleHesabDariObj.credit = 0 articleHesabDariObj.desc = "بدهی بابت ثبت " if sarresidType == "check": articleHesabDariObj.desc += " چک به صورت دستی " elif sarresidType == "nesiye": articleHesabDariObj.desc += " نسیه به صورت دستی " articleHesabDariObj.customer = customerObj articleHesabDariObj.save() sarresidINC = Sarresid() sarresidINC.customer = customerObj sarresidINC.price = int(sarresidPrice) sarresidINC.payStatus = "false" #check ya nesiye sarresidINC.type = sarresidType sarresidINC.user = request.user sarresidINC.sarresidDT = sarResidDateTime01 sarresidINC.save() return redirect(request.META.get('HTTP_REFERER')) except: return HttpResponse("خطا در ثبت ") #return HttpResponse(customerObj.id) @login_required(login_url='/panel/p_login/') @permission_required('admin_panel.ViewStore', raise_exception=True) def pardakhtNesiye(request, cs_id): try: customerObj = Customer.objects.get(id=cs_id) user_op = request.user sarrresidUNIX = request.POST.get('next_sarresidDateTime', None) sarresidPrice = int(request.POST.get('next_sarresidPrice', None)) sr_articlID = request.POST.get('sr_articlID', None) nahvePardakht = request.POST.get('nahvePardakht', None) desc_sar = request.POST.get('desc_sar', None) sarResidDateTime01 = datetime.datetime.fromtimestamp(int(sarrresidUNIX)/1000.0) SarresidObj = Sarresid.objects.get(id=sr_articlID) #return HttpResponse(sarresidPrice) debitNow = SarresidObj.price - sarresidPrice #return HttpResponse(debitNow) if debitNow==0: #توسیه بدهی بخورد SarresidObj.price = 0 SarresidObj.payStatus = "true" elif debitNow >= 1: SarresidObj.price = debitNow SarresidObj.sarresidDT = sarResidDateTime01 else: return HttpResponse("خطا در مورد مبلغ") SarresidObj.save() if str(nahvePardakht) == 'naghd': nahvePardakht = 'نقد' elif str(nahvePardakht) == 'kartbekart': nahvePardakht = 'کارت به کارت' elif str(nahvePardakht) == 'kartkhan': nahvePardakht = 'کارت خوان' elif str(nahvePardakht) == 'takhfif': nahvePardakht = 'تخفیف' elif str(nahvePardakht) == 'check': nahvePardakht = 'چک' articleHesabDariObj = Article() articleHesabDariObj.debit = 0 articleHesabDariObj.credit = int(sarresidPrice) if desc_sar: articleHesabDariObj.desc = "پرداختی بابت نسیه" + " | " + desc_sar else: articleHesabDariObj.desc = "پرداختی بابت نسیه" articleHesabDariObj.customer = customerObj articleHesabDariObj.nahvePardakht = nahvePardakht articleHesabDariObj.user = user_op articleHesabDariObj.save() return redirect(request.META.get('HTTP_REFERER')) except: return HttpResponse("خطا در ثبت ") #return HttpResponse(customerObj.id)