improve sabt factor

This commit is contained in:
javad committed 2026-06-23 22:10:12 +03:30
1 parent 96dd277f6b
commit 1672de549a
2 files changed
+223 -219

No files matched your search

+216 -219
View File
@@ -1,24 +1,63 @@
from django.shortcuts import redirect, render
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
# Create your views here.
from django.http import HttpResponse
from django.urls import reverse
from django.http import Http404, HttpResponseRedirect
from django.db.models import Count, Sum
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest # بخشBadRequest جدید
from django.db import transaction # جدید
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
from django.shortcuts import get_object_or_404
from django.db.models import Q
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 )
@@ -915,233 +954,191 @@ def newFactor(request, c_id):
@login_required(login_url='/panel/p_login/')
def newFactor2(request, c_id):
import json
prdsList = request.POST.get('prdsList', '')
sarResidDate = request.POST.get('sarResidDate', None)
if request.method != 'POST':
return HttpResponseBadRequest("متد ارسالی باید POST باشد.")
user_op = request.user
# ۱. پارس داده‌های JSON ارسال شده از فرم (پشتیبانی کامل از ساختار paylist و orderList جاوااسکریپت)
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
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 نامعتبر است.")
#return HttpResponse(sarResidDate[1])
#sarResidDate = JalaliDate(sarResidDate[0], sarResidDate[1], sarResidDate[2]).to_gregorian()
factor_type = request.POST.get('FactorType', '')
factor_desc = request.POST.get('FactorDesc', '')
sar_resid_date = _parse_jalali_date(request.POST.get('sarResidDate'))
FactorDate = request.POST.get('FactorDate', '')
FactorType = request.POST.get('FactorType', '')
FactorDesc = request.POST.get('FactorDesc', '')
PardakhtHa = request.POST.get('PardakhtHa', '')
jsonPardakhtHa = json.loads(PardakhtHa)
# ۲. واکشی اطلاعات پایه‌ای
customer_obj = get_object_or_404(Customer, id=c_id)
store_room = get_object_or_404(StoreRoom, user=user_op)
#return HttpResponse(PardakhtHa)
# ۳. واکشی دسته‌جمعی محصولات برای جلوگیری از فشار به دیتابیس
product_ids = [item.get('prd_id') for item in prds_list if item.get('prd_id') and item.get('prd_id') != 9999]
products_by_id = {p.id: p for p in Product.objects.filter(id__in=product_ids)}
#print(datetime.datetime.fromtimestamp(int(jsonPardakhtHa['l_checkPriceSarResid'])/1000.0))
#return HttpResponse(type(jsonPardakhtHa['l_nesiyeDateTime']))
#for pardakhti in jsonPardakhtHa:
#if(jsonPardakhtHa[pardakhti] != None):
#print(str(pardakhti)+" --> " +str(jsonPardakhtHa[pardakhti]))
# ۴. محاسبه مبلغ کل با نادیده گرفتن محصولاتی که گارانتی/اشانتیون هستند
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()
)
#return HttpResponse(PardakhtHa)
Factorfail = False
customerObj=Customer.objects.get(id=c_id)
storeUser = StoreRoom.objects.get(user__id=request.user.id)
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
# ۵. باز کردن یک تراکنش امن دیتابیس
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
)
newFactor.FactorType = FactorType
newFactor.desc = FactorDesc
# ثبت بدهکاری کلی در سند حسابداری
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
)
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
# ثبت ریزپرداختی‌ها بر اساس دیکشنری paylist جاوااسکریپت
payment_map = {
'l_naghd': 'نقد',
'l_kartbekart': 'کارت به کارت',
'l_kartKhan': 'کارت خوان',
'l_takhfif': 'تخفیف'
}
#ثبت مبالغ پرداختی
if Factorfail != True:
#مبلغ کلی فاکتور وارد می شود
is_naghd = False
'''articleHesabDari = Article()
articleHesabDari.debit = newFactor.amount
articleHesabDari.credit = 0
articleHesabDari.customer = customerObj
articleHesabDari.factor = newFactor
for key, amount_raw in json_pardakht_ha.items():
amount = _safe_int(amount_raw)
if amount <= 0:
continue
articleHesabDari.desc = str(FactorType) + " " + str(customerObj.name_family) + " طی فاکتور شماره: " + str(newFactor.id)
articleHesabDari.save()'''
#ابتدا مانده حساب بستانکار قبل گرفته میشود از فاکتور کم بشه
debit = Article.objects.filter(customer=customerObj).aggregate(Sum('debit'))['debit__sum']
credit = Article.objects.filter(customer=customerObj).aggregate(Sum('credit'))['credit__sum']
mandehesabCs = 0
try:
if (debit - credit) < 0:
mandehesabCs = abs(debit - credit)
except:
pass
#کل مبلغ فاکتور بدهکار میشه اول
# اگر مشتری بستانکار بود ازش کم میشه
articleHesabDari2 = Article()
articleHesabDari2.debit = (sum_price)
articleHesabDari2.credit = 0
articleHesabDari2.customer = customerObj
articleHesabDari2.factor = newFactor
strDes = str(customerObj.name_family) + " فاکتور شماره: " + str(newFactor.id) + ""
#if mandehesabCs > 1:
# strDes += " مبلغ کل فاکتور" +str(sum_price)+ " - " + str(mandehesabCs)+" بستانکار از قبل "
articleHesabDari2.desc = strDes
articleHesabDari2.user = user_op
articleHesabDari2.save()
#مبالغ پرداختی وارد می شود
for pardakhti in jsonPardakhtHa:
if(jsonPardakhtHa[pardakhti] != None):
#بستانکارها
if str(pardakhti) in ['l_naghd', 'l_kartbekart', 'l_kartKhan']:
nahvePardakht = ''
if str(pardakhti) == 'l_naghd':
nahvePardakht = 'نقد'
elif str(pardakhti) == 'l_kartbekart':
nahvePardakht = 'کارت به کارت'
elif str(pardakhti) == 'l_kartKhan':
nahvePardakht = 'کارت خوان'
elif str(pardakhti) == 'l_takhfif':
nahvePardakht = 'تخفیف'
articleHesabDari2 = Article()
articleHesabDari2.debit = 0
articleHesabDari2.credit = int(jsonPardakhtHa[pardakhti])
articleHesabDari2.customer = customerObj
articleHesabDari2.factor = newFactor
articleHesabDari2.desc = nahvePardakht + " " + str(customerObj.name_family) + " فاکتور شماره: " + str(newFactor.id)
articleHesabDari2.nahvePardakht = nahvePardakht
articleHesabDari2.payStatus = "true"
articleHesabDari2.user = user_op
articleHesabDari2.save()
elif str(pardakhti) in ['l_takhfif']:
if str(pardakhti) == 'l_takhfif':
nahvePardakht = 'تخفیف'
articleHesabDari2 = Article()
articleHesabDari2.debit = 0
articleHesabDari2.credit = int(jsonPardakhtHa[pardakhti])
articleHesabDari2.customer = customerObj
articleHesabDari2.factor = newFactor
articleHesabDari2.desc = nahvePardakht + " " + str(customerObj.name_family) + " فاکتور شماره: " + str(newFactor.id)
articleHesabDari2.nahvePardakht = nahvePardakht
articleHesabDari2.payStatus = "true"
articleHesabDari2.user = user_op
articleHesabDari2.save()
pass
#چک یا نیسیه بدهکار
elif str(pardakhti) in ['l_nesiye', 'l_checkPrice']:
nahvePardakht = ''
sarResidDateTime01 = None
if str(pardakhti) == 'l_nesiye':
sarResidDateTime01 = datetime.datetime.fromtimestamp(int(jsonPardakhtHa['l_nesiyeSarResid'])/1000.0)
nahvePardakht = 'nesiye'
elif str(pardakhti) == 'l_checkPrice':
sarResidDateTime01 = datetime.datetime.fromtimestamp(int(jsonPardakhtHa['l_checkPriceSarResid'])/1000.0)
nahvePardakht = 'check'
sarresidINC = Sarresid()
sarresidINC.customer = customerObj
sarresidINC.price = int(jsonPardakhtHa[pardakhti])
sarresidINC.payStatus = "false"
sarresidINC.type = nahvePardakht
sarresidINC.user = request.user
sarresidINC.factor = newFactor
sarresidINC.sarresidDT = sarResidDateTime01
sarresidINC.save()
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'
#articleHesabDari2 = Article()
#articleHesabDari2.debit = int(jsonPardakhtHa[pardakhti])
#articleHesabDari2.credit = 0
#articleHesabDari2.customer = customerObj
#articleHesabDari2.factor = newFactor
#articleHesabDari2.desc = nahvePardakht + " " + str(customerObj.name_family) + " فاکتور شماره: " + str(newFactor.id)
#articleHesabDari2.nahvePardakht = nahvePardakht
#articleHesabDari2.payStatus = "false"
#articleHesabDari2.sarresid = sarResidDateTime01
#print(sarResidDateTime01)
#articleHesabDari2.save()
sar_resid_dt = _parse_timestamp(json_pardakht_ha.get(ts_key))
#ایتم های فاکتور
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))
Sarresid.objects.create(
customer=customer_obj,
price=amount,
payStatus="false",
type=type_str,
user=user_op,
factor=new_factor,
sarresidDT=sar_resid_dt
)
return redirect('/panel/customer/'+str(c_id)+'/factors/')
# ثبت آیتم‌های فاکتور با کارایی بالا (املای کلاس FactorIteam دقیقا منطبق بر models.py)
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 = 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 ValidationError(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)
# چک کردن موجودی انبار و ثبت کسر کالا
for item in prds_list:
prd_id = 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)
# قفل کردن ردیف برای اطمینان ۱۰۰ درصدی از عدم تداخل پیش از متد Save مدل
stock_records = ProductInStore.objects.select_for_update().filter(
Product=product_obj,
StoreRoom=store_room,
year=1400
)
list(stock_records)
totals = stock_records.aggregate(total_in=Sum('s_in'), total_out=Sum('s_out'))
total_in = totals['total_in'] or 0
total_out = totals['total_out'] or 0
mojodi_prd = total_in - total_out
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}"
else:
desc_text = f"خروج: {prd_count}#فروش#{customer_obj.name_family} * {user_fullname}"
# این خط به‌طور خودکار متد حرفه‌ای Save شما در models.py را فراخوانی می‌کند
# و جداول تاریخچه انبارداری را نیز بروزرسانی می‌نماید
ProductInStore.objects.create(
StoreRoom=store_room,
Product=product_obj,
factor=new_factor,
s_out=prd_count,
desc=desc_text,
year=1400
)
except InventoryException as e:
# اگر خطا دهد دیتابیس Rollback می‌شود و فرم در قالب همان پیغام HTML به کاربر برمی‌گردد
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)
# بازگشت موفقیت‌آمیز دقیقاً به همان آدرس فایل urls.py
return redirect(f'/panel/customer/{c_id}/factors/')
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.DelFactor', raise_exception=True)
+7
View File
@@ -4,5 +4,12 @@ DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': Path('/app/sqlite/db.sqlite3'),
'OPTIONS': {
# ۱. افزایش زمان انتظار (Timeout) به ثانیه
# با این کار اگر دیتابیس قفل باشد، کاربر دوم کرش نمی‌کند بلکه تا ۳۰ ثانیه منتظر باز شدن قفل می‌ماند
'timeout': 30,
# ۲. فعال‌سازی حالت WAL برای بالا بردن قدرت پردازش هم‌زمانی در SQLite
'init_command': 'PRAGMA journal_mode=WAL;',
},
}
}