import datetime import os from django.db import models from django.db.models import Sum, Q from django.shortcuts import redirect, render, get_object_or_404 from django.core.paginator import Paginator from django.views.decorators.csrf import csrf_exempt from django.contrib import messages from django.contrib.auth.decorators import login_required, permission_required from persiantools.jdatetime import JalaliDate from ..models import (Customer, PhoneNumber, ProductCategory, Product, Factor, FactorIteam, Article, Sarresid, Action, CacheTable) from ..sms.phones import (normalize_primary_values, save_primary_normalized) from ..sms.triggers import notify_payment_thanks, notify_welcome from ..sms.rules import customer_toggle_map from ._helpers import logger, _parse_jalali_date, _parse_phone_rows @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() # مشتریان غیرفعال هرگز نمایش داده نمی‌شوند — نه در فهرست کامل و نه در جستجو. all_customers = Customer.objects.filter(is_active=True) if searchOn == 'code': all_customers = all_customers.filter(idCode__contains=searchVal) elif searchOn == 'phone': all_customers = all_customers.filter(Q(phone1__contains=searchVal) | Q(phone2__contains=searchVal) | Q(phone3__contains=searchVal)) elif searchOn == 'name': all_customers = all_customers.filter(name_family__contains=searchVal) elif searchOn == 'address': all_customers = all_customers.filter(address__contains=searchVal) elif searchOn == 'laghv': all_customers = all_customers.filter(laghv_bazdid=True) else: searchStatus = False all_customers = all_customers.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 # همان قاعدهٔ فهرست اصلی: مشتری غیرفعال در هیچ نتیجه‌ای نمایش داده نمی‌شود. all_customers = Customer.objects.filter(is_active=True) if searchOn == 'code': all_customers = all_customers.filter(idCode__contains=searchVal) elif searchOn == 'phone': all_customers = all_customers.filter(phone1__contains=searchVal) elif searchOn == 'name': all_customers = all_customers.filter(name_family__contains=searchVal) elif searchOn == 'address': all_customers = all_customers.filter(address__contains=searchVal) else: searchStatus = False all_customers = all_customers.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 sms_toggles = customer_toggle_map(customer) data = { 'customer': customer, 'categorys': categorys, 'dateNow': dateNow, 'factorha': factorha, 'mandehesabCs': mandehesabCs, 'sms_toggles': sms_toggles, } 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 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', '') # آرایه‌های عنوان/شمارهٔ فرم + ایندکس ردیف‌های علامت‌خورده به‌عنوان «اصلی» valid_phones, phones_list, primary_indices = _parse_phone_rows(request) 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.sms_primary_phones = normalize_primary_values( phones_list[i] for i in primary_indices if i < len(phones_list) ) newCs.save() # ذخیره شماره‌ها به صورت نامحدود در جدول PhoneNumber for item in valid_phones: PhoneNumber.objects.create( customer=newCs, title=item['title'], phone_number=item['phone'], ) # پیامک خوش‌آمدگویی (در صورت فعال بودن قالب و تنظیم مشتری) notify_welcome(newCs, created_by=request.user) return redirect('/panel/customers/') @login_required(login_url='/panel/p_login/') def customerEdit(request, c_id): # این مسیر فقط endpoint فرم ویرایش است؛ دسترسی مستقیم با GET نباید ۵۰۰ بدهد. if request.method != 'POST': return redirect('/panel/customer/' + str(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) # آرایه‌های عنوان/شمارهٔ فرم + ایندکس ردیف‌های علامت‌خورده به‌عنوان «اصلی» valid_phones, phones_list, primary_indices = _parse_phone_rows(request) 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 # فقط وقتی فرم فیلد توضیحات را فرستاده باشد مقدارش عوض می‌شود؛ فرم‌های قدیمی # که این فیلد را ندارند نباید یادداشت‌های پاک‌سازی را پاک کنند. if 'notes' in request.POST: newCs.notes = request.POST.get('notes', '') # آپدیت فیلدهای قدیمی جهت حفظ سازگاری (تنها وقتی شماره‌ای ارسال شده باشد) if valid_phones: 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() # بروزرسانی جدول شماره‌ها فقط در صورت ارسال شماره‌های جدید. # اگر فرم شماره‌ای نفرستاده باشد (فیلدهای غیرفعال)، شماره‌های فعلی دست‌نخورده می‌مانند. if valid_phones: PhoneNumber.objects.filter(customer=newCs).delete() for item in valid_phones: PhoneNumber.objects.create( customer=newCs, title=item['title'], phone_number=item['phone'], ) # اگر فرم ویرایش ردیف‌های «اصلی» را مشخص کرده باشد، همان‌ها ذخیره می‌شوند # (مبتنی بر خودِ شماره، نه ایندکس رکورد). if primary_indices: save_primary_normalized( newCs, [phones_list[i] for i in primary_indices if i < len(phones_list)], ) 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.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() # سند بستانکار (credit>0 و debit=0) یعنی مشتری پول پرداخت کرده ⇒ پیامک تشکر. if type == 'credit': notify_payment_thanks(newArticl.customer, newArticl.credit, article_id=newArticl.id, method=nahvePardakhat, created_by=user_op) return redirect('/panel/customer/'+str(c_id)+'/hesabdari')