This commit is contained in:
javad committed 2026-10-09 20:02:36 +03:30
1 parent 6f36db63a1
commit 250cdbab7f
42 files changed
+96363 -11699

No files matched your search

+465
View File
@@ -0,0 +1,465 @@
import json
from django.db import transaction, connection
from django.db.models import Sum, Q
from django.shortcuts import redirect, get_object_or_404
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.http import require_POST
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required, permission_required
from persiantools.jdatetime import JalaliDate
from persiantools import digits
from ..models import (Customer, StoreRoom, Factor, FactorIteam, Product,
ProductInStore, Article, Sarresid)
from ._helpers import logger, _safe_int, _parse_jalali_date, _parse_timestamp, InventoryException
@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, owners=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)