check and fix call mobile

This commit is contained in:
javad committed 2026-08-12 21:55:24 +03:30
1 parent b66fe69660
commit e353ed6fc2
5 files changed
+473 -20

No files matched your search

@@ -52,10 +52,10 @@
<input disabled value="{{ phone_obj.title }}" name="phone_titles" class="input" type="text" placeholder="عنوان"> <input disabled value="{{ phone_obj.title }}" name="phone_titles" class="input" type="text" placeholder="عنوان">
</div> </div>
<div class="control is-expanded"> <div class="control is-expanded">
<input disabled value="{{ phone_obj.phone_number }}" name="phones" class="input an_phone" type="text" placeholder="تلفن"> <input disabled value="0{{ phone_obj.phone_number }}" name="phones" class="input an_phone" type="text" placeholder="تلفن">
</div> </div>
<div class="control"> <div class="control">
<a href="tel:{{ phone_obj.phone_number }}" class="button is-success" title="تماس با {{ phone_obj.phone_number }}"> <a href="tel:0{{ phone_obj.phone_number }}" class="button is-success" title="تماس با {{ phone_obj.phone_number }}">
📞 📞
</a> </a>
</div> </div>
@@ -74,10 +74,10 @@
<input disabled value="تلفن 1" name="phone_titles" class="input" type="text" placeholder="عنوان"> <input disabled value="تلفن 1" name="phone_titles" class="input" type="text" placeholder="عنوان">
</div> </div>
<div class="control is-expanded"> <div class="control is-expanded">
<input disabled value="{{ customer.phone1 }}" name="phones" class="input an_phone" type="text" placeholder="تلفن"> <input disabled value="0{{ customer.phone1 }}" name="phones" class="input an_phone" type="text" placeholder="تلفن">
</div> </div>
<div class="control"> <div class="control">
<a href="tel:{{ customer.phone1 }}" class="button is-success" title="تماس"> <a href="tel:0{{ customer.phone1 }}" class="button is-success" title="تماس">
📞 📞
</a> </a>
</div> </div>
@@ -93,10 +93,10 @@
<input disabled value="تلفن 2" name="phone_titles" class="input" type="text"> <input disabled value="تلفن 2" name="phone_titles" class="input" type="text">
</div> </div>
<div class="control is-expanded"> <div class="control is-expanded">
<input disabled value="{{ customer.phone2 }}" name="phones" class="input an_phone" type="text"> <input disabled value="0{{ customer.phone2 }}" name="phones" class="input an_phone" type="text">
</div> </div>
<div class="control"> <div class="control">
<a href="tel:{{ customer.phone2 }}" class="button is-success" title="تماس"> <a href="tel:0{{ customer.phone2 }}" class="button is-success" title="تماس">
📞 📞
</a> </a>
</div> </div>
@@ -112,10 +112,10 @@
<input disabled value="تلفن 3" name="phone_titles" class="input" type="text"> <input disabled value="تلفن 3" name="phone_titles" class="input" type="text">
</div> </div>
<div class="control is-expanded"> <div class="control is-expanded">
<input disabled value="{{ customer.phone3 }}" name="phones" class="input an_phone" type="text"> <input disabled value="0{{ customer.phone3 }}" name="phones" class="input an_phone" type="text">
</div> </div>
<div class="control"> <div class="control">
<a href="tel:{{ customer.phone3 }}" class="button is-success" title="تماس"> <a href="tel:0{{ customer.phone3 }}" class="button is-success" title="تماس">
📞 📞
</a> </a>
</div> </div>
@@ -75,6 +75,15 @@
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
border-radius: 6px; border-radius: 6px;
} }
/* استایل‌های نتایج استعلام */
.inquiry-box {
background-color: #f8fafc;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
border: 1px solid #cbd5e1;
}
</style> </style>
<div id="hesabdari" class="column is-12-mobile is-12-desktop"> <div id="hesabdari" class="column is-12-mobile is-12-desktop">
@@ -181,7 +190,7 @@
data-owner="{{ sarresid.check_owner_name|default:'' }}" data-owner="{{ sarresid.check_owner_name|default:'' }}"
data-phone="{{ sarresid.check_owner_phone|default:'' }}"> data-phone="{{ sarresid.check_owner_phone|default:'' }}">
<span class="icon is-small"><i class="fas fa-edit"></i></span> <span class="icon is-small"><i class="fas fa-edit"></i></span>
<span>افزودن اطلاعات</span> <span>ویرایش و استعلام</span>
</button> </button>
{% else %} {% else %}
<span class="has-text-grey-light">-</span> <span class="has-text-grey-light">-</span>
@@ -276,7 +285,7 @@
<input type="hidden" name="n_sarresidDateTime" id="NewSarResidVal" class="reset_input"> <input type="hidden" name="n_sarresidDateTime" id="NewSarResidVal" class="reset_input">
<div id="check_extra_fields" style="display: none; background-color: #f8fafc; padding: 15px; border-radius: 8px; margin-top: 15px; border: 1px solid #cbd5e1;"> <div id="check_extra_fields" class="inquiry-box" style="display: none;">
<p class="has-text-weight-bold mb-3 has-text-info">اطلاعات چک صیادی</p> <p class="has-text-weight-bold mb-3 has-text-info">اطلاعات چک صیادی</p>
<div class="field"> <div class="field">
@@ -289,14 +298,15 @@
<div class="field"> <div class="field">
<label class="label">شناسه صیادی (۱۶ رقم) <span class="has-text-danger">*</span></label> <label class="label">شناسه صیادی (۱۶ رقم) <span class="has-text-danger">*</span></label>
<div class="control"> <div class="control">
<input name="check_sayyad_id" class="input reset_input is-required-check" type="text" inputmode="numeric" pattern="[0-9]{16}" minlength="16" maxlength="16" placeholder="فقط اعداد ۱۶ رقمی" title="لطفاً دقیقاً ۱۶ رقم وارد کنید"> <input id="new_check_sayyad" name="check_sayyad_id" class="input reset_input is-required-check sayyad-input-dynamic" data-target="new" type="text" inputmode="numeric" pattern="[0-9]{16}" minlength="16" maxlength="16" placeholder="فقط اعداد ۱۶ رقمی را وارد کنید تا استعلام خودکار انجام شود...">
</div> </div>
<div id="new_inquiry_results" class="mt-3" style="display:none;"></div>
</div> </div>
<div class="field"> <div class="field">
<label class="label">نام و نام‌خانوادگی صاحب حساب <span class="has-text-danger">*</span></label> <label class="label">نام و نام‌خانوادگی صاحب حساب <span class="has-text-danger">*</span></label>
<div class="control"> <div class="control">
<input name="check_owner_name" class="input reset_input is-required-check" type="text" placeholder="نام مالک چک"> <input name="check_owner_name" id="new_check_owner" class="input reset_input is-required-check" type="text" placeholder="نام مالک چک">
</div> </div>
</div> </div>
@@ -310,7 +320,7 @@
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<button type="submit" class="button is-success"> <button type="submit" class="button is-success" id="new_submit_btn" style="display: none;">
<i class="fas fa-save ml-1"></i> ثبت اطلاعات <i class="fas fa-save ml-1"></i> ثبت اطلاعات
</button> </button>
<span class="button cancellBTNSR">لغو</span> <span class="button cancellBTNSR">لغو</span>
@@ -323,7 +333,7 @@
<div class="modal-background"></div> <div class="modal-background"></div>
<div class="modal-card"> <div class="modal-card">
<header class="modal-card-head"> <header class="modal-card-head">
<p class="modal-card-title">تکمیل اطلاعات چک صیادی</p> <p class="modal-card-title">ویرایش و استعلام چک صیادی</p>
<button type="button" class="delete" aria-label="close"></button> <button type="button" class="delete" aria-label="close"></button>
</header> </header>
<section class="modal-card-body"> <section class="modal-card-body">
@@ -338,8 +348,9 @@
<div class="field"> <div class="field">
<label class="label">شناسه صیادی (۱۶ رقم) <span class="has-text-danger">*</span></label> <label class="label">شناسه صیادی (۱۶ رقم) <span class="has-text-danger">*</span></label>
<div class="control"> <div class="control">
<input id="up_check_sayyad" name="check_sayyad_id" class="input reset_input" type="text" required inputmode="numeric" pattern="[0-9]{16}" minlength="16" maxlength="16" placeholder="فقط اعداد ۱۶ رقمی" title="لطفاً دقیقاً ۱۶ رقم وارد کنید"> <input id="up_check_sayyad" name="check_sayyad_id" class="input reset_input sayyad-input-dynamic" data-target="up" type="text" required inputmode="numeric" pattern="[0-9]{16}" minlength="16" maxlength="16" placeholder="فقط اعداد ۱۶ رقمی را وارد کنید تا استعلام خودکار انجام شود...">
</div> </div>
<div id="up_inquiry_results" class="mt-3" style="display:none;"></div>
</div> </div>
<div class="field"> <div class="field">
@@ -358,7 +369,7 @@
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<button type="submit" class="button is-info"> <button type="submit" class="button is-info" id="up_submit_btn" style="display: none;">
<i class="fas fa-check ml-1"></i> ذخیره اطلاعات <i class="fas fa-check ml-1"></i> ذخیره اطلاعات
</button> </button>
<span class="button cancellBTNSR">لغو</span> <span class="button cancellBTNSR">لغو</span>
@@ -451,6 +462,7 @@
// فرمت‌بندی اعداد (۳ رقم ۳ رقم) // فرمت‌بندی اعداد (۳ رقم ۳ رقم)
// ---------------------------------------------------- // ----------------------------------------------------
function formatNumber(num) { function formatNumber(num) {
if (!num) return "0";
return num.toString().replace(/\D/g, "").replace(/\B(?=(\d{3})+(?!\d))/g, ","); return num.toString().replace(/\D/g, "").replace(/\B(?=(\d{3})+(?!\d))/g, ",");
} }
@@ -475,7 +487,163 @@
}); });
// ---------------------------------------------------- // ----------------------------------------------------
// مدیریت فرم و نمایش دینامیک چک // تبدیل JSON ثبت پاسارگاد به حالت خوانا و فارسی
// ----------------------------------------------------
function parseSabtData(dataObj) {
if (typeof dataObj === 'string') {
try { dataObj = JSON.parse(dataObj); } catch(e) { return dataObj; }
}
let onGoing = dataObj.onGoingAmount || 0;
let blocked = dataObj.blocked || 0;
let bounced = 0;
let cleared = 0;
if (dataObj.ownersInfo && dataObj.ownersInfo.length > 0) {
bounced = dataObj.ownersInfo[0].bouncedAmount || 0;
cleared = dataObj.ownersInfo[0].clearedAmount || 0;
}
// تبدیل از ریال به تومان (تقسیم بر ۱۰) و جدا کردن با کاما
let onGoingToman = formatNumber(onGoing / 10);
let blockedToman = formatNumber(blocked / 10);
let bouncedToman = formatNumber(bounced / 10);
let clearedToman = formatNumber(cleared / 10);
let colorClassSabt = bounced > 0 ? "is-danger" : "is-success";
return `
<div class="notification is-light ${colorClassSabt} py-3 px-4 mb-2 is-size-7">
<p class="mb-2"><strong><i class="fas fa-file-signature"></i> وضعیت ثبت در صیاد:</strong></p>
<ul style="list-style: none; margin: 0; padding: 0; line-height: 2;">
<li><i class="fas fa-money-bill-wave has-text-info"></i> <b>مبلغ در جریان:</b> <span dir="ltr">${onGoingToman}</span> تومان</li>
<li><i class="fas fa-ban has-text-danger"></i> <b>مبلغ مسدودی:</b> <span dir="ltr">${blockedToman}</span> تومان</li>
<li><i class="fas fa-exclamation-triangle has-text-warning"></i> <b>مبلغ برگشتی:</b> <span dir="ltr">${bouncedToman}</span> تومان</li>
<li><i class="fas fa-check-circle has-text-success"></i> <b>مبلغ پاس شده:</b> <span dir="ltr">${clearedToman}</span> تومان</li>
</ul>
</div>
`;
}
// ----------------------------------------------------
// استعلام خودکار با تایپ شدن کامل ۱۶ رقم صیادی
// ----------------------------------------------------
$(".sayyad-input-dynamic").on("input", function() {
let val = $(this).val();
let target = $(this).data('target'); // 'new' یا 'up'
let resultsContainer = $(`#${target}_inquiry_results`);
let submitBtn = $(`#${target}_submit_btn`);
// اگر طول رشته کمتر یا بیشتر از ۱۶ بود دکمه ثبت و نتایج را مخفی کن
if (val.length !== 16) {
resultsContainer.hide().html('');
submitBtn.hide();
return;
}
// به محض رسیدن به ۱۶ رقم، استعلام‌ها شروع می‌شود
let sayad_id = val;
resultsContainer.show().html(`
<div class="notification is-light is-info py-2 px-3 mb-2 is-size-7">
<i class="fas fa-spinner fa-spin"></i> در حال برقراری ارتباط با سامانه‌های بانک مرکزی و صیاد...
</div>
`);
submitBtn.hide();
let req1_CBI = $.ajax({
url: "{% url 'admin_panel:api_check_status_inquiry' %}",
data: { sayad_id: sayad_id },
method: "GET"
});
let req2_SABT = $.ajax({
url: "{% url 'admin_panel:api_check_sabt_inquiry' %}",
data: { sayad_id: sayad_id, national_id: '', id_type: 1 },
method: "GET"
});
// مدیریت همزمان هر دو درخواست
$.when(req1_CBI, req2_SABT).done(function(cbiResArray, sabtResArray) {
let resCBI = cbiResArray[0];
let resSABT = sabtResArray[0];
let htmlOutput = "";
// --- تحلیل نتیجه بانک مرکزی (رنگ چک) ---
if (resCBI.success && resCBI.data) {
let colorClassCBI = "is-info";
let statusText = resCBI.data.status || "";
// برطرف کردن باگ فاصله‌های زیاد و مخفی در نام صاحب حساب
let ownerNameClean = (resCBI.data.owner_name || "").replace(/&nbsp;/ig, ' ').replace(/\s+/g, ' ').trim();
if (statusText.includes("سفید")) colorClassCBI = "is-success";
else if (statusText.includes("زرد") || statusText.includes("نارنجی")) colorClassCBI = "is-warning";
else if (statusText.includes("قرمز") || statusText.includes("قهوه ای") || statusText.includes("برگشت")) colorClassCBI = "is-danger";
htmlOutput += `
<div class="notification is-light ${colorClassCBI} py-2 px-3 mb-2 is-size-7">
<strong><i class="fas fa-university"></i> وضعیت حساب صادرکننده در بانک مرکزی:</strong>
<br>وضعیت چک: <b>${statusText}</b>
<br>مالک حساب: <b>${ownerNameClean}</b>
</div>
`;
// به‌روزرسانی فیلد نام صاحب حساب با متن اصلاح‌شده و بدون فاصله اضافی
$(`#${target}_check_owner`).val(ownerNameClean);
} else {
htmlOutput += `
<div class="notification is-light is-danger py-2 px-3 mb-2 is-size-7">
<strong><i class="fas fa-exclamation-triangle"></i> خطا (بانک مرکزی):</strong> ${resCBI.error || 'ارتباط با بانک مرکزی برقرار نشد'}
</div>
`;
// در صورت وجود خطا در استعلام، فیلد نام صاحب حساب خالی شود
$(`#${target}_check_owner`).val('');
}
// --- تحلیل نتیجه سامانه ثبت صیاد ---
if (resSABT.success && resSABT.data) {
htmlOutput += parseSabtData(resSABT.data);
} else {
let sabtErrorMsg = resSABT.error || 'سیستم استعلام ثبت در دسترس نیست';
// بررسی خطای کد 524 و عدم ثبت چک توسط صادرکننده
if (sabtErrorMsg.includes("کارتابل مشتري وجود ندارد") || sabtErrorMsg.includes("524")) {
sabtErrorMsg = "مشتری هنوز چک را برای شما (کد ملی 1219968218) ثبت نکرده، از مشتری بخواهید چک را ثبت کند.";
}
htmlOutput += `
<div class="notification is-light is-warning py-2 px-3 mb-2 is-size-7">
<strong><i class="fas fa-exclamation-triangle"></i> خطا (استعلام ثبت):</strong> ${sabtErrorMsg}
</div>
`;
}
// نمایش نتایج و نمایش دادن دکمه ثبت
resultsContainer.html(htmlOutput);
submitBtn.fadeIn();
}).fail(function() {
// در صورتی که کلا اینترنت سرور یا ریکوئست ایجکس فیِل شود
resultsContainer.html(`
<div class="notification is-light is-danger py-2 px-3 mb-2 is-size-7">
<strong><i class="fas fa-wifi"></i> خطای سرور در برقراری ارتباط با سامانه‌های استعلام (احتمالا کپچا یا قطعی شبکه)</strong>
</div>
`);
// در صورت خطای کلی شبکه یا سرور، نام صاحب حساب پاک شود
$(`#${target}_check_owner`).val('');
// با وجود خطا، دکمه ذخیره را نمایش می‌دهیم تا کاربر بتواند اطلاعات را دستی ذخیره کند
submitBtn.fadeIn();
});
});
// ----------------------------------------------------
// مدیریت فرم و نمایش دینامیک باکس چک
// ---------------------------------------------------- // ----------------------------------------------------
$("#sarresidTypeSelect").change(function(){ $("#sarresidTypeSelect").change(function(){
let selectedType = $(this).val(); let selectedType = $(this).val();
@@ -486,6 +654,9 @@
$("#check_extra_fields").slideUp(250); $("#check_extra_fields").slideUp(250);
$(".is-required-check").prop('required', false); $(".is-required-check").prop('required', false);
$("#check_extra_fields input").val(''); $("#check_extra_fields input").val('');
$("#new_inquiry_results").hide().html('');
// برای نسیه دکمه ثبت مستقیما باز می‌شود
$("#new_submit_btn").show();
} }
}); });
@@ -501,9 +672,10 @@
// ---------------------------------------------------- // ----------------------------------------------------
$("#newSarresid").click(function(){ $("#newSarresid").click(function(){
$(".modalAddNew").addClass("is-active"); $(".modalAddNew").addClass("is-active");
$("#new_submit_btn").hide(); // پیش‌فرض مخفی تا نوع سند انتخاب شود
}); });
// رویداد کلیک برای باز کردن مُدال تکمیل اطلاعات چک // رویداد کلیک برای باز کردن مُدال ویرایش
$(".btn-update-check").click(function(){ $(".btn-update-check").click(function(){
let sarresidId = $(this).data('id'); let sarresidId = $(this).data('id');
let rowNum = $(this).data('row'); let rowNum = $(this).data('row');
@@ -522,6 +694,13 @@
// باز کردن مُدال // باز کردن مُدال
$(".modalUpdateCheck").addClass("is-active"); $(".modalUpdateCheck").addClass("is-active");
// اگر شناسه صیادی از قبل ۱۶ رقم است، استعلام خودکار فراخوانی شود
if ($("#up_check_sayyad").val().length === 16) {
$("#up_check_sayyad").trigger("input");
} else {
$("#up_submit_btn").show(); // اگر ناقص بود دکمه را نشان بده تا ویرایش کند
}
}); });
// بستن تمامی مدال‌ها // بستن تمامی مدال‌ها
@@ -531,6 +710,8 @@
$('#priceInWords, #nextPriceInWords').text(''); $('#priceInWords, #nextPriceInWords').text('');
$("#check_extra_fields").hide(); $("#check_extra_fields").hide();
$(".is-required-check").prop('required', false); $(".is-required-check").prop('required', false);
$("#new_inquiry_results, #up_inquiry_results").hide().html('');
$("#new_submit_btn, #up_submit_btn").hide();
}); });
// ---------------------------------------------------- // ----------------------------------------------------
+3
View File
@@ -25,6 +25,9 @@ urlpatterns = [
path('customers/add', views.customersAdd, name='customersAdd'), path('customers/add', views.customersAdd, name='customersAdd'),
path('customer/<int:c_id>', views.customerNew2, name='customer'), path('customer/<int:c_id>', views.customerNew2, name='customer'),
# === مسیرهای مربوط به استعلام چک صیادی ===
path('hesabdari/check/api/status-inquiry/', views.api_check_status_inquiry, name='api_check_status_inquiry'),
path('hesabdari/check/api/sabt-inquiry/', views.api_check_sabt_inquiry, name='api_check_sabt_inquiry'),
path('customer2/<int:c_id>', views.customerNew2, name='customer2'), path('customer2/<int:c_id>', views.customerNew2, name='customer2'),
+265 -1
View File
@@ -4,6 +4,15 @@ 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.http import JsonResponse
from asgiref.sync import async_to_sync
import asyncio
import cv2
import numpy as np
import ddddocr
import re
import base64
from playwright.async_api import async_playwright
from django.contrib import messages from django.contrib import messages
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from django.utils import timezone # جدید from django.utils import timezone # جدید
@@ -2472,4 +2481,259 @@ def pardakhtNesiye(request, cs_id):
except: except:
return HttpResponse("خطا در ثبت ") return HttpResponse("خطا در ثبت ")
#return HttpResponse(customerObj.id) # =====================================================================
# توابع کمکی و منطق استعلام چک (برگرفته از اسکریپت‌های شما)
# =====================================================================
def solve_base64_captcha(base64_string: str) -> str:
try:
if ',' in base64_string:
base64_string = base64_string.split(',')[1]
image_bytes = base64.b64decode(base64_string)
np_arr = np.frombuffer(image_bytes, np.uint8)
img = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
if img is None:
return ""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray, 3)
_, thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
kernel = np.ones((2, 2), np.uint8)
cleaned_img = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
final_img = cv2.bitwise_not(cleaned_img)
success, encoded_image = cv2.imencode('.png', final_img)
if not success:
return ""
processed_bytes = encoded_image.tobytes()
ocr = ddddocr.DdddOcr(show_ad=False)
extracted_text = ocr.classification(processed_bytes)
numbers = re.findall(r'\d+', extracted_text)
if len(numbers) >= 2:
num1, num2 = int(numbers[0]), int(numbers[1])
if '+' in extracted_text:
ans = num1 + num2
elif '-' in extracted_text:
ans = num1 - num2
elif '*' in extracted_text or 'x' in extracted_text.lower():
ans = num1 * num2
else:
ans = num1 + num2
return str(ans)
return ""
except Exception:
return ""
def parse_cheque_result_html(html_content: str) -> dict:
sayad_id = re.search(r'شناسه\s*<b>?\s*(\d+)\s*</b>?', html_content)
owner_name = re.search(r'متعلق به\s*<b>?\s*(.*?)\s*</b>?\s*می باشد', html_content)
validity_date = re.search(r'تا تاریخ\s*<b>?\s*([\d/]+)\s*</b>?', html_content)
status = re.search(r'در وضعیت\s*<b>?\s*(.*?)\s*</b>?\s*در پایگاه', html_content)
clean_full_text = re.sub(r'<[^>]+>', '', html_content).strip()
return {
"success": True,
"data": {
"sayad_id": sayad_id.group(1) if sayad_id else "",
"owner_name": owner_name.group(1).strip() if owner_name else "",
"validity_date": validity_date.group(1) if validity_date else "",
"status": status.group(1).strip() if status else "",
"raw_message": clean_full_text
},
"error": None
}
async def run_cbi_sayad_inquiry_logic(sayad_id: str, max_retries: int = 7) -> str:
lightpanda_url = os.environ.get("LIGHTPANDA_URL") or "ws://127.0.0.1:9222"
async with async_playwright() as p:
try:
browser = await p.chromium.connect_over_cdp(lightpanda_url)
except Exception as e:
return json.dumps({"success": False, "data": None, "error": f"عدم اتصال به Lightpanda: {str(e)}"})
for attempt in range(1, max_retries + 1):
context = await browser.new_context()
page = await context.new_page()
try:
url = "https://cbi.ir/EstelamSayad/19689.aspx"
await page.goto(url, wait_until="domcontentloaded", timeout=20000)
captcha_selector = "#ctl00_ucBody_ucContent_ctl00_imgcpatcha"
await page.wait_for_selector(captcha_selector, state="visible", timeout=15000)
await asyncio.sleep(1)
base64_src = await page.locator(captcha_selector).get_attribute("src")
viewstate = await page.locator("#__VIEWSTATE").get_attribute("value") or ""
viewstate_gen = await page.locator("#__VIEWSTATEGENERATOR").get_attribute("value") or "453B48D8"
if not base64_src or not viewstate:
await context.close()
continue
captcha_answer = solve_base64_captcha(base64_src)
if not captcha_answer:
await context.close()
continue
ajax_raw_response = await page.evaluate("""
async ({ sayadId, captchaAns, viewState, viewStateGen }) => {
try {
const formData = new URLSearchParams();
formData.append('ctl00$ucBody$ucContent$ctl00$ScriptManager1', 'ctl00$ucBody$ucContent$ctl00$UpdatePanel1|ctl00$ucBody$ucContent$ctl00$btnSearch');
formData.append('__EVENTTARGET', '');
formData.append('__EVENTARGUMENT', '');
formData.append('__VIEWSTATE', viewState);
formData.append('__VIEWSTATEGENERATOR', viewStateGen);
formData.append('__VIEWSTATEENCRYPTED', '');
formData.append('ctl00$ucHeader$txtQuickSearch', '');
formData.append('ctl00$ucBody$ucContent$ctl00$txtChequeNumber', sayadId);
formData.append('ctl00$ucBody$ucContent$ctl00$txtCaptchaInput', captchaAns);
formData.append('__ASYNCPOST', 'true');
formData.append('ctl00$ucBody$ucContent$ctl00$btnSearch', 'استعلام چک صیادی');
const response = await fetch(window.location.href, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Cache-Control': 'no-cache'
},
body: formData.toString()
});
return await response.text();
} catch (err) {
return 'FETCH_ERROR: ' + err.message;
}
}
""", {'sayadId': sayad_id, 'captchaAns': captcha_answer, 'viewState': viewstate, 'viewStateGen': viewstate_gen})
if "FETCH_ERROR:" in ajax_raw_response:
await context.close()
await asyncio.sleep(1)
continue
error_match = re.search(r'id="ctl00_ucBody_ucContent_ctl00_lblError"[^>]*>(.*?)</span>', ajax_raw_response, re.DOTALL)
if error_match and error_match.group(1).strip():
clean_error = re.sub(r'<[^>]+>', '', error_match.group(1)).strip()
if "عبارت محاسباتی" in clean_error:
await context.close()
continue
else:
await context.close()
await browser.close()
return json.dumps({"success": False, "data": None, "error": clean_error})
result_match = re.search(r'id="ctl00_ucBody_ucContent_ctl00_lblChequeNumberResult"[^>]*>(.*?)</span>', ajax_raw_response, re.DOTALL)
if result_match:
raw_html = result_match.group(1)
parsed_result = parse_cheque_result_html(raw_html)
await context.close()
await browser.close()
return json.dumps(parsed_result)
await context.close()
except Exception:
await context.close()
await asyncio.sleep(1)
await browser.close()
return json.dumps({"success": False, "data": None, "error": "استعلام ناموفق بود (خطای شبکه یا تشخیص کپچا)"})
async def run_bpi_cheque_inquiry_logic(national_id: str, sayad_id: str, id_type: int = 1):
lightpanda_url = os.environ.get("LIGHTPANDA_URL") or "ws://127.0.0.1:9222"
async with async_playwright() as p:
try:
browser = await p.chromium.connect_over_cdp(lightpanda_url)
except Exception as e:
return {"success": False, "error": f"عدم اتصال به مرورگر Lightpanda: {str(e)}"}
context = await browser.new_context()
page = await context.new_page()
try:
await page.goto("https://vbank.bpi.ir/public/inquiries/inquiry-bounced-cheques", wait_until="domcontentloaded", timeout=20000)
api_url = f"https://sec.bpi.ir/prls/api/v1/inquiry/chequeStatus?IdCode={national_id}&IdType={id_type}&SayadId={sayad_id}"
response = await context.request.get(
api_url,
headers={
"Referer": "https://vbank.bpi.ir/public/inquiries/inquiry-bounced-cheques",
"Accept": "application/json, text/plain, */*"
}
)
if response.ok:
data = await response.json()
return {"success": True, "data": data}
else:
error_text = await response.text()
return {"success": False, "error": f"سرور خطا برگرداند (کد {response.status}): {error_text}"}
except Exception as e:
return {"success": False, "error": f"خطایی در فرآیند استعلام رخ داد: {str(e)}"}
finally:
await context.close()
await browser.close()
# =====================================================================
# View های مربوط به استعلام (API)
# =====================================================================
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewStore', raise_exception=True)
def api_check_status_inquiry(request):
"""
استعلام وضعیت چک از بانک مرکزی
پارامتر ورودی: sayad_id (GET)
"""
sayad_id = request.GET.get('sayad_id', '').strip()
if not sayad_id or not sayad_id.isdigit() or len(sayad_id) != 16:
return JsonResponse({"success": False, "error": "شناسه صیادی باید دقیقاً ۱۶ رقم باشد."})
try:
# اجرای تابع Async به صورت Sync
result_json_str = async_to_sync(run_cbi_sayad_inquiry_logic)(sayad_id)
# تبدیل رشته جیسون به دیکشنری برای ارسال مجدد با JsonResponse
result_data = json.loads(result_json_str)
return JsonResponse(result_data)
except Exception as e:
logger.error(f"Error in CBI Check Status Inquiry: {str(e)}", exc_info=True)
return JsonResponse({"success": False, "error": "خطای سرور در برقراری ارتباط با بانک مرکزی."})
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewStore', raise_exception=True)
def api_check_sabt_inquiry(request):
"""
استعلام ثبت چک از بانک پاسارگاد
پارامترهای ورودی: sayad_id, national_id, id_type (GET)
"""
sayad_id = request.GET.get('sayad_id', '').strip()
national_id = request.GET.get('national_id', '').strip()
id_type = _safe_int(request.GET.get('id_type', 1)) # عدد 1 حقیقی، 2 حقوقی
# اگر کدملی از فرانت‌اند خالی ارسال شد، از یک کدملی پیش‌فرض استفاده می‌کنیم
if not national_id:
national_id = "1219968218"
if not sayad_id:
return JsonResponse({"success": False, "error": "شناسه صیادی برای استعلام ثبت الزامی است."})
try:
# اجرای تابع Async به صورت Sync
result_data = async_to_sync(run_bpi_cheque_inquiry_logic)(national_id, sayad_id, id_type)
return JsonResponse(result_data)
except Exception as e:
logger.error(f"Error in BPI Check Registration Inquiry: {str(e)}", exc_info=True)
return JsonResponse({"success": False, "error": "خطای سرور در برقراری ارتباط با سامانه بانک."})
+6 -1
View File
@@ -4,4 +4,9 @@ persian==0.5.0
persiantools==2.2.0 persiantools==2.2.0
django-extensions==3.2.1 django-extensions==3.2.1
boto3==1.34.131 boto3==1.34.131
botocore==1.34.131 botocore==1.34.131
playwright==1.44.0
opencv-python-headless==4.9.0.80
numpy==1.26.4
ddddocr==1.4.11
asgiref==3.4.1