1970 lines
85 KiB
Python
1970 lines
85 KiB
Python
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.utils import timezone # جدید
|
||
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
|
||
from django.core.exceptions import 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
|
||
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 *
|
||
|
||
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.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, '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 = Customer.objects.get(id=c_id)
|
||
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 = Customer.objects.get(id=c_id)
|
||
categorys = ProductCategory.objects.all()
|
||
factorha = Factor.objects.filter(customer__id = c_id).order_by('-create_date')
|
||
#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/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):
|
||
storeUser = StoreRoom.objects.get(user__id=request.user.id)
|
||
|
||
# ۱. تجمیع ورودی و خروجی در یک کوئری بهینه برای افزایش سرعت دیتابیس
|
||
totals = ProductInStore.objects.filter(
|
||
Product__id=p_id, StoreRoom=storeUser, year=1400
|
||
).aggregate(total_in=Sum('s_in'), total_out=Sum('s_out'))
|
||
|
||
# ۲. رفع باگ قطعی NoneType: اگر رکوردی نباشد، مقدار None را به 0 تبدیل میکند
|
||
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', '')
|
||
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', '')
|
||
|
||
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()
|
||
|
||
newCs = Customer()
|
||
newCs.name_family = an_name
|
||
newCs.address = an_address
|
||
newCs.phone1 = phone1
|
||
newCs.phone2 = phone2
|
||
newCs.phone3 = phone3
|
||
newCs.installDate = installDate
|
||
newCs.idCode = idnCode
|
||
newCs.save()
|
||
|
||
#return HttpResponse(installDate)
|
||
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])
|
||
|
||
#return HttpResponse(year)
|
||
installDate = JalaliDate(year, month, day).to_gregorian()
|
||
|
||
newCs = Customer.objects.get(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.idCode = idnCode
|
||
if laghv_bazdid == 'true':
|
||
newCs.laghv_bazdid = True
|
||
else:
|
||
newCs.laghv_bazdid = False
|
||
newCs.save()
|
||
|
||
#return HttpResponse(installDate)
|
||
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/')
|
||
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)
|
||
|
||
@login_required(login_url='/panel/p_login/')
|
||
def store(request, s_id):
|
||
productCategory = ProductCategory.objects.all()
|
||
storeO = StoreRoom.objects.get(id=s_id)
|
||
searchStatus = False
|
||
searchQS = request.POST.get('searchQS', '')
|
||
mojodi = request.POST.get('mojodi', '')
|
||
#return HttpResponse(mojodi)
|
||
if searchQS !='':
|
||
productSum = Product.objects.filter(productinstore__StoreRoom=storeO ,productinstore__year=1400,product_name__contains=searchQS).annotate(mojodi=( Sum('productinstore__s_in') - Sum('productinstore__s_out') ))
|
||
if mojodi == "0":
|
||
productSum = productSum.filter(mojodi__lte=0)
|
||
searchStatus = True
|
||
else:
|
||
productSum = Product.objects.filter(productinstore__StoreRoom=storeO ,productinstore__year=1400).annotate(mojodi=( Sum('productinstore__s_in') - Sum('productinstore__s_out') ))
|
||
if mojodi == "0":
|
||
searchStatus = True
|
||
productSum = productSum.filter(mojodi__lte=0)
|
||
data = {'storeO': storeO,'productSum': productSum, '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.ViewStore', 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.ViewStore', 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.ViewStore', 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.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.ViewStore', 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.ViewStore', raise_exception=True)
|
||
def operation(request):
|
||
if request.method != 'POST':
|
||
return HttpResponseBadRequest("متد ارسالی نامعتبر است.")
|
||
|
||
# استفاده از توابع امن اختصاصی پروژه شما برای جداسازی مقادیر عددی
|
||
prd_id = _safe_int(request.POST.get('prd_id'))
|
||
actionType = 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', '')
|
||
|
||
if tedad <= 0:
|
||
return HttpResponseBadRequest("تعداد وارد شده باید بیشتر از صفر باشد.")
|
||
|
||
sRoom = get_object_or_404(StoreRoom, id=azanbar)
|
||
productO = get_object_or_404(Product, id=prd_id)
|
||
|
||
try:
|
||
with transaction.atomic():
|
||
|
||
# ساخت کوئری پایه برای دریافت تراکنشهای سال جاری کالا
|
||
qs = ProductInStore.objects.filter(
|
||
Product=productO, StoreRoom=sRoom, year=1400
|
||
)
|
||
|
||
# بررسی هوشمند پشتیبانی دیتابیس از قابلیت قفل ردیف (سازگار با SQLite و PostgreSQL)
|
||
if connection.features.has_select_for_update:
|
||
qs = qs.select_for_update()
|
||
|
||
# بارگذاری امن تراکنشها در حافظه (RAM) جهت جلوگیری از خطای توابع تجمیعی همزمان با قفل دیتابیس
|
||
locked_records = list(qs)
|
||
|
||
# --- عملیات ورود دستی ---
|
||
if actionType == 'vorod':
|
||
pInStore = ProductInStore(
|
||
StoreRoom=sRoom,
|
||
Product=productO,
|
||
year=1400
|
||
)
|
||
pInStore.s_in = tedad
|
||
pInStore.desc = "ورود: "+str(tedad)+" به انبار " + str(sRoom.store_name) + "#" + str(desc)
|
||
pInStore.movement_type = ProductInStore.MovementType.DIRECT_IN
|
||
pInStore.save()
|
||
|
||
# --- عملیات خروج دستی یا انتقال بین انبارها ---
|
||
elif actionType in ['khoroj', 'transfer']:
|
||
|
||
# محاسبات دقیق و سریع موجودی کالا در سطح پایتون بدون درگیر کردن دیسک
|
||
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))
|
||
|
||
# الف) خروج دستی ساده
|
||
if actionType == 'khoroj':
|
||
pInStore = ProductInStore(
|
||
StoreRoom=sRoom,
|
||
Product=productO,
|
||
year=1400
|
||
)
|
||
pInStore.s_out = tedad
|
||
pInStore.desc = "خروج: "+str(tedad)+" از انبار " + str(sRoom.store_name) + "#" + str(desc)
|
||
pInStore.movement_type = ProductInStore.MovementType.DIRECT_OUT
|
||
pInStore.save()
|
||
|
||
# ب) انتقال بین دو انبار مجزا
|
||
elif actionType == 'transfer':
|
||
if not beanbar:
|
||
return HttpResponse("انبار مقصد مشخص نشده است.")
|
||
|
||
beanbarOjb = get_object_or_404(StoreRoom, id=_safe_int(beanbar))
|
||
|
||
# ردیف اول: ثبت تراکنش خروج از انبار مبدأ
|
||
pInStore = ProductInStore(
|
||
StoreRoom=sRoom,
|
||
Product=productO,
|
||
year=1400
|
||
)
|
||
pInStore.s_out = tedad
|
||
pInStore.desc = "خروج: "+str(tedad)+" از انبار " + str(sRoom.store_name) + " جهت انتقال به انبار " + str(beanbarOjb.store_name) + "#" + str(desc)
|
||
pInStore.movement_type = ProductInStore.MovementType.TRANSFER
|
||
pInStore.save()
|
||
|
||
# ردیف دوم: ثبت تراکنش ورود به انبار مقصد (محافظت شده در بلاک اتمیک)
|
||
ProductInStore.objects.create(
|
||
StoreRoom=beanbarOjb,
|
||
Product=productO,
|
||
s_in=tedad,
|
||
year=1400,
|
||
desc="ورود: "+str(tedad)+" از انبار " + str(sRoom.store_name) + "#" + str(desc),
|
||
movement_type=ProductInStore.MovementType.TRANSFER
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in store operation view: {str(e)}", exc_info=True)
|
||
return HttpResponse("خطای سیستمی رخ داد. تراکنش لغو شد.")
|
||
|
||
return redirect('/panel/store/'+str(sRoom.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, user=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)
|
||
|
||
@login_required(login_url='/panel/p_login/')
|
||
@permission_required('admin_panel.DelCustomer', raise_exception=True)
|
||
def customerDel(request, c_id):
|
||
try:
|
||
Customer.objects.get(id=c_id).delete()
|
||
return redirect('/panel/customers/')
|
||
except:
|
||
return HttpResponse("خطا در حذف مشتری")
|
||
|
||
|
||
@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):
|
||
customer = Customer.objects.get(id=c_id)
|
||
actions = Action.objects.filter(customer=customer).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/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)
|
||
|
||
@login_required(login_url='/panel/p_login/')
|
||
def rezayat(request):
|
||
dateTimeAction = request.GET.get('dateTime', None)
|
||
statusRezayat = request.GET.get('status', None)
|
||
status='finish'
|
||
if statusRezayat=='finish':
|
||
actions = Action.objects.exclude(actionType='vosol').exclude(Q(nazar='') | Q(nazar=None)).filter(status='finish').order_by("-id")[:50]
|
||
else:
|
||
statusRezayat='waite'
|
||
actions = Action.objects.filter(Q(nazar='') | Q(nazar=None) & Q(status='finish')).exclude(actionType='vosol').order_by("id")[:50]
|
||
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 = Action.objects.get(id=ac_id)
|
||
action.nazar = csDesc
|
||
action.grade = csGrade
|
||
action.nazarDT = datetime.datetime.now()
|
||
action.save()
|
||
return redirect('/panel/rezayat/')
|
||
else:
|
||
return HttpResponse('لطفا همه فیلد ها را پر کنید')
|
||
#data = {'actions': actions, "status": status}
|
||
#return render(request, 'admin_panel/customerRezayat.html', data)
|
||
|
||
|
||
@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) |