diff --git a/admin_panel/migrations/0054_auto_20260715_2250.py b/admin_panel/migrations/0054_auto_20260715_2250.py new file mode 100644 index 0000000..ebf73b7 --- /dev/null +++ b/admin_panel/migrations/0054_auto_20260715_2250.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.25 on 2026-07-15 19:20 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin_panel', '0053_storeroom_owners'), + ] + + operations = [ + migrations.AddField( + model_name='phonenumber', + name='title', + field=models.CharField(blank=True, default='تلفن', help_text='عنوان شماره (مثل: موبایل پدر)', max_length=50, null=True), + ), + migrations.AlterField( + model_name='phonenumber', + name='customer', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='phones', to='admin_panel.customer'), + ), + migrations.AlterField( + model_name='phonenumber', + name='phone_number', + field=models.CharField(max_length=20), + ), + ] diff --git a/admin_panel/models.py b/admin_panel/models.py index cc646b6..0af3a32 100644 --- a/admin_panel/models.py +++ b/admin_panel/models.py @@ -58,9 +58,12 @@ class Customer(models.Model): self.save() class PhoneNumber(models.Model): - customer = models.ForeignKey(Customer, on_delete=models.CASCADE) - phone_number = models.CharField(max_length=11) - + customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='phones') + title = models.CharField(max_length=50, default='تلفن', blank=True, null=True, help_text="عنوان شماره (مثل: موبایل پدر)") + phone_number = models.CharField(max_length=20) + + def __str__(self): + return f"{self.title}: {self.phone_number}" class StoreRoom(models.Model): class Meta: diff --git a/admin_panel/templates/admin_panel/customerBaseNew.html b/admin_panel/templates/admin_panel/customerBaseNew.html index 2c39954..3791d18 100644 --- a/admin_panel/templates/admin_panel/customerBaseNew.html +++ b/admin_panel/templates/admin_panel/customerBaseNew.html @@ -3,6 +3,18 @@ {% load static %} {% block bodyPage %} + {% if messages %} +
+
+ {% for message in messages %} +
+ + {{ message }} +
+ {% endfor %} +
+
+ {% endif %}
@@ -14,6 +26,7 @@

مشاهده مشخصات
+
@@ -29,24 +42,83 @@
-
- -
- - - - {% if not perms.admin_panel.ViewStore %} -

- {{ customer.phone1 }} -

-

- {{ customer.phone2 }} -

-

- {{ customer.phone3 }} -

+
+ +
+ + {% if customer.phones.all %} + {% for phone_obj in customer.phones.all %} +
+
+ +
+
+ +
+ {% if perms.admin_panel.ViewStore %} +
+ +
+ {% endif %} +
+ {% if not perms.admin_panel.ViewStore %} +

+ {{ phone_obj.title }}: {{ phone_obj.phone_number }} +

+ {% endif %} + {% endfor %} + + {% else %} + {% if customer.phone1 %} +
+
+ +
+
+ +
+ {% if perms.admin_panel.ViewStore %} +
+ {% endif %} +
+ {% endif %} + + {% if customer.phone2 %} +
+
+ +
+
+ +
+ {% if perms.admin_panel.ViewStore %} +
+ {% endif %} +
+ {% endif %} + + {% if customer.phone3 %} +
+
+ +
+
+ +
+ {% if perms.admin_panel.ViewStore %} +
+ {% endif %} +
+ {% endif %} {% endif %} +
+ + {% if perms.admin_panel.ViewStore %} + + {% endif %}
@@ -84,6 +156,7 @@
+
@@ -586,14 +659,49 @@ $(document).ready(function() { - var test = 0 + // فعال‌سازی دکمه بستن برای پیام‌های سیستم (جدید) + $(".close-notification").click(function(){ + $(this).parent().fadeOut(300, function() { + $(this).remove(); + }); + }); + + var test = 0; $("#enEdit").click(function(){ $("#formcsEdit").find("input").prop('disabled', false); + // فعال کردن دکمه‌های کنترل تلفن + $("#formcsEdit").find("button.remove-phone-btn, button.add-phone-btn").prop('disabled', false); + $("#enEdit").addClass("is-hidden"); $("#editCs").removeClass("is-hidden"); }); + // توابع داینامیک فرم ویرایش + $(document).on('click', '.add-phone-btn', function() { + var phoneRow = ` +
+
+ +
+
+ +
+
+ +
+
`; + $("#dynamic-phones-edit").append(phoneRow); + }); + + $(document).on('click', '.remove-phone-btn', function() { + if ($("#dynamic-phones-edit .has-addons").length > 1) { + $(this).closest('.field.has-addons').remove(); + } else { + alert("حداقل یک شماره تماس باید وجود داشته باشد."); + } + }); + $("#showUserDT").click(function(){ if( $(this).html()=='مشاهده مشخصات' ){ $(this).html('مخفی کردن'); diff --git a/admin_panel/templates/admin_panel/customers.html b/admin_panel/templates/admin_panel/customers.html index 52c6ccd..0b5bc39 100644 --- a/admin_panel/templates/admin_panel/customers.html +++ b/admin_panel/templates/admin_panel/customers.html @@ -155,14 +155,22 @@
-
- -
- - - +
+ +
+
+
+ +
+
+ +
+
+ +
+
-
+
@@ -251,6 +259,25 @@ $(document).ready(function() { kamaDatepicker('persianDateP', { buttonsColor: "red", forceFarsiDigits: true }); + $(document).on('click', '.add-phone-btn', function() { + var phoneRow = ` +
+
+ +
+
+ +
+
+ +
+
`; + $(this).closest('div[id^="dynamic-phones"]').append(phoneRow); + }); + + $(document).on('click', '.remove-phone-btn', function() { + $(this).closest('.field.has-addons').remove(); + }); $("#createCS").click(function(){ var u_name = $(".addNewUser").find('.an_name').val(); diff --git a/admin_panel/views.py b/admin_panel/views.py index 5f01459..ea39851 100644 --- a/admin_panel/views.py +++ b/admin_panel/views.py @@ -611,7 +611,7 @@ def customers(request): all_customers = Customer.objects.filter(laghv_bazdid=True).order_by('-create_date') else: searchStatus = False - all_customers = Customer.objects.all().order_by('-create_date') + 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) @@ -671,7 +671,7 @@ def customerNew(request ,c_id): @login_required(login_url='/panel/p_login/') def customerNew2(request ,c_id): - customer = Customer.objects.get(id=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) @@ -767,70 +767,86 @@ def productMojodi(request, p_id): def customersAdd(request): an_name = request.POST.get('an_name', '') an_address = request.POST.get('an_address', '') - phone1 = request.POST.get('phone1', '') - phone2 = request.POST.get('phone2', '') - phone3 = request.POST.get('phone3', '') installDate = request.POST.get('installDate', '') idnCode = request.POST.get('idnCode', '') - an_name = request.POST.get('an_name', '') + + # دریافت آرایه‌های عنوان و شماره تلفن + 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 = int(installDate.split('/')[0]) - month = int(installDate.split('/')[1]) - day = int(installDate.split('/')[2]) - - #return HttpResponse(year) - installDate = JalaliDate(year, month, day).to_gregorian() + 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.phone1 = phone1 - newCs.phone2 = phone2 - newCs.phone3 = phone3 - newCs.installDate = installDate + 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() - #return HttpResponse(installDate) + # ذخیره شماره‌ها به صورت نامحدود در جدول PhoneNumber + for item in valid_phones: + PhoneNumber.objects.create(customer=newCs, title=item['title'], phone_number=item['phone']) + return redirect('/panel/customers/') - #return render(request, 'admin_panel/customersAdd.html') + @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', '') - phone1 = request.POST.get('phone1', '') - phone2 = request.POST.get('phone2', '') - phone3 = request.POST.get('phone3', '') installDate = request.POST.get('installDate', '') idnCode = request.POST.get('idnCode', '') laghv_bazdid = request.POST.get('laghv_bazdid', False) - an_name = request.POST.get('an_name', '') - year = int(installDate.split('/')[0]) - month = int(installDate.split('/')[1]) - day = int(installDate.split('/')[2]) + # دریافت آرایه‌های عنوان و شماره تلفن + 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() + }) - #return HttpResponse(year) - installDate = JalaliDate(year, month, day).to_gregorian() + year, month, day = map(int, installDate.split('/')) + installDate_gregorian = JalaliDate(year, month, day).to_gregorian() - newCs = Customer.objects.get(id=c_id) + newCs = get_object_or_404(Customer, id=c_id) newCs.name_family = an_name newCs.address = an_address - newCs.phone1 = phone1 - newCs.phone2 = phone2 - newCs.phone3 = phone3 - newCs.installDate = installDate + newCs.installDate = installDate_gregorian newCs.idCode = idnCode - if laghv_bazdid == 'true': - newCs.laghv_bazdid = True - else: - newCs.laghv_bazdid = False + 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() - #return HttpResponse(installDate) - return redirect('/panel/customer/'+str(c_id)) + # بروزرسانی جدول بی‌نهایت (حذف قبلی‌ها و درج مجدد) + 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): @@ -1752,14 +1768,59 @@ def FactorDel(request, c_id, f_id): 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: - Customer.objects.get(id=c_id).delete() + # ۱. بررسی وجود هرگونه سابقه مالی/حسابداری (حتی با مانده صفر) + 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: - return HttpResponse("خطا در حذف مشتری") + + 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/')