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
+217 -213

No files matched your search

+210 -213
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.base import reverse
from django.urls import reverse # تکراری در کد اصلی شما بود، اما جهت عدم تغییر حفظ شد
from persiantools.jdatetime import JalaliDate from persiantools.jdatetime import JalaliDate
from persiantools import characters, digits from persiantools import characters, digits
import datetime import datetime
from django.utils import timezone # جدید
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage 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.csrf import csrf_exempt
# Create your views here. from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest # بخشBadRequest جدید
from django.http import HttpResponse from django.db import transaction # جدید
from django.urls import reverse from django.db.models import Count, Sum, Q
from django.http import Http404, HttpResponseRedirect
from django.db.models import Count, Sum
from django.contrib.auth.models import Permission, User from django.contrib.auth.models import Permission, User
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
import os import os
from django.shortcuts import get_object_or_404 import json # جدید
from django.db.models import Q import logging # جدید
from .models import * 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): def p_login(request):
username = request.POST.get('username', None ) username = request.POST.get('username', None )
password = request.POST.get('password', None ) password = request.POST.get('password', None )
@@ -915,233 +954,191 @@ def newFactor(request, c_id):
@login_required(login_url='/panel/p_login/') @login_required(login_url='/panel/p_login/')
def newFactor2(request, c_id): def newFactor2(request, c_id):
import json if request.method != 'POST':
prdsList = request.POST.get('prdsList', '') return HttpResponseBadRequest("متد ارسالی باید POST باشد.")
sarResidDate = request.POST.get('sarResidDate', None)
user_op = request.user user_op = request.user
# ۱. پارس داده‌های JSON ارسال شده از فرم (پشتیبانی کامل از ساختار paylist و orderList جاوااسکریپت)
try: try:
sarResidDate = digits.fa_to_en(digits.ar_to_fa(sarResidDate)) prds_list = json.loads(request.POST.get('prdsList') or '[]')
#return HttpResponse(sarResidDate) json_pardakht_ha = json.loads(request.POST.get('PardakhtHa') or '{}')
sarResidDate = sarResidDate.split("/") except json.JSONDecodeError:
#return HttpResponse(sarResidDate[2]) return HttpResponseBadRequest("فرمت داده‌های ارسالی JSON نامعتبر است.")
sarResidDate = JalaliDate(int(sarResidDate[0]), int(sarResidDate[1]), int(sarResidDate[2])).to_gregorian()
#return HttpResponse(sarResidDate)
except:
sarResidDate = None
#return HttpResponse(sarResidDate[1]) factor_type = request.POST.get('FactorType', '')
#sarResidDate = JalaliDate(sarResidDate[0], sarResidDate[1], sarResidDate[2]).to_gregorian() 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', '') customer_obj = get_object_or_404(Customer, id=c_id)
FactorDesc = request.POST.get('FactorDesc', '') store_room = get_object_or_404(StoreRoom, user=user_op)
PardakhtHa = request.POST.get('PardakhtHa', '')
jsonPardakhtHa = json.loads(PardakhtHa)
#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'])) sum_price = sum(
#for pardakhti in jsonPardakhtHa: _safe_int(item.get('prd_count')) * _safe_int(item.get('price'))
#if(jsonPardakhtHa[pardakhti] != None): for item in prds_list
#print(str(pardakhti)+" --> " +str(jsonPardakhtHa[pardakhti])) 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: try:
newFactor = Factor() # ۵. باز کردن یک تراکنش امن دیتابیس
newFactor.customer= customerObj with transaction.atomic():
newFactor.amount = sum_price
newFactor.FactorType = FactorType # ثبت آبجکت فاکتور
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.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: # ثبت ریزپرداختی‌ها بر اساس دیکشنری paylist جاوااسکریپت
newFactor.deadline = sarResidDate payment_map = {
newFactor.user = User.objects.get(id=request.user.id) 'l_naghd': 'نقد',
newFactor.save() 'l_kartbekart': 'کارت به کارت',
except: 'l_kartKhan': 'کارت خوان',
try: 'l_takhfif': 'تخفیف'
Factorfail=True }
newFactor.delete()
except:
pass
#ثبت مبالغ پرداختی for key, amount_raw in json_pardakht_ha.items():
if Factorfail != True: amount = _safe_int(amount_raw)
if amount <= 0:
continue
#مبلغ کلی فاکتور وارد می شود if key in payment_map:
is_naghd = False nahve_pardakht = payment_map[key]
'''articleHesabDari = Article() Article.objects.create(
articleHesabDari.debit = newFactor.amount debit=0,
articleHesabDari.credit = 0 credit=amount,
articleHesabDari.customer = customerObj customer=customer_obj,
articleHesabDari.factor = newFactor factor=new_factor,
desc=f"{nahve_pardakht} {customer_obj.name_family} فاکتور شماره: {new_factor.id}",
nahvePardakht=nahve_pardakht,
payStatus="true",
user=user_op
)
articleHesabDari.desc = str(FactorType) + " " + str(customerObj.name_family) + " طی فاکتور شماره: " + str(newFactor.id) # ثبت اسناد چک و نسیه
articleHesabDari.save()''' elif key in ['l_nesiye', 'l_checkPrice']:
#ابتدا مانده حساب بستانکار قبل گرفته میشود از فاکتور کم بشه is_nesiye = (key == 'l_nesiye')
debit = Article.objects.filter(customer=customerObj).aggregate(Sum('debit'))['debit__sum'] type_str = 'nesiye' if is_nesiye else 'check'
credit = Article.objects.filter(customer=customerObj).aggregate(Sum('credit'))['credit__sum'] ts_key = 'l_nesiyeSarResid' if is_nesiye else 'l_checkPriceSarResid'
mandehesabCs = 0 sar_resid_dt = _parse_timestamp(json_pardakht_ha.get(ts_key))
try:
if (debit - credit) < 0:
mandehesabCs = abs(debit - credit)
except:
pass
#کل مبلغ فاکتور بدهکار میشه اول Sarresid.objects.create(
# اگر مشتری بستانکار بود ازش کم میشه customer=customer_obj,
articleHesabDari2 = Article() price=amount,
articleHesabDari2.debit = (sum_price) payStatus="false",
articleHesabDari2.credit = 0 type=type_str,
articleHesabDari2.customer = customerObj user=user_op,
articleHesabDari2.factor = newFactor factor=new_factor,
strDes = str(customerObj.name_family) + " فاکتور شماره: " + str(newFactor.id) + "" sarresidDT=sar_resid_dt
#if mandehesabCs > 1: )
# strDes += " مبلغ کل فاکتور" +str(sum_price)+ " - " + str(mandehesabCs)+" بستانکار از قبل "
articleHesabDari2.desc = strDes
articleHesabDari2.user = user_op
articleHesabDari2.save()
#مبالغ پرداختی وارد می شود # ثبت آیتم‌های فاکتور با کارایی بالا (املای کلاس FactorIteam دقیقا منطبق بر models.py)
for pardakhti in jsonPardakhtHa: factor_items_pool = []
user_fullname = f"{user_op.first_name} {user_op.last_name}".strip() or user_op.username
if(jsonPardakhtHa[pardakhti] != None): for item in prds_list:
#بستانکارها prd_id = item.get('prd_id')
if str(pardakhti) in ['l_naghd', 'l_kartbekart', 'l_kartKhan']: prd_count = _safe_int(item.get('prd_count'))
nahvePardakht = '' garanty_esh = item.get('garanty_Esh', '')
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() if prd_id == 9999:
articleHesabDari2.debit = 0 factor_items_pool.append(FactorIteam(
articleHesabDari2.credit = int(jsonPardakhtHa[pardakhti]) factor=new_factor,
articleHesabDari2.customer = customerObj product=None,
articleHesabDari2.factor = newFactor product_name=item.get('product_name', ''),
articleHesabDari2.desc = nahvePardakht + " " + str(customerObj.name_family) + " فاکتور شماره: " + str(newFactor.id) num=prd_count,
articleHesabDari2.nahvePardakht = nahvePardakht amount=_safe_int(item.get('priceKol')),
articleHesabDari2.payStatus = "true" garantyEsh=garanty_esh
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()
#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()
#ایتم های فاکتور
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: else:
Gproduct = Product.objects.get(id=prd['prd_id']) product_obj = products_by_id.get(prd_id)
fIteams.product = Gproduct if not product_obj:
fIteams.product_name = Gproduct.product_name raise ValidationError(f"محصولی با شناسه {prd_id} یافت نشد.")
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/') 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/') @login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.DelFactor', raise_exception=True) @permission_required('admin_panel.DelFactor', raise_exception=True)
+7
View File
@@ -4,5 +4,12 @@ DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': Path('/app/sqlite/db.sqlite3'), 'NAME': Path('/app/sqlite/db.sqlite3'),
'OPTIONS': {
# ۱. افزایش زمان انتظار (Timeout) به ثانیه
# با این کار اگر دیتابیس قفل باشد، کاربر دوم کرش نمی‌کند بلکه تا ۳۰ ثانیه منتظر باز شدن قفل می‌ماند
'timeout': 30,
# ۲. فعال‌سازی حالت WAL برای بالا بردن قدرت پردازش هم‌زمانی در SQLite
'init_command': 'PRAGMA journal_mode=WAL;',
},
} }
} }