fix sms
This commit is contained in:
1 parent
1dea3509a7
commit
e3a6d1f9d4
42 files changed
+33086
-15779
No files matched your search
@@ -0,0 +1,144 @@
|
||||
{% load custom_tags %}
|
||||
{% comment %}
|
||||
کارت پروفایل مشتری — ستون سمت راست صفحهٔ مشتری.
|
||||
تکمنبع (DRY): هم customerBase.html و هم customerBaseNew.html این را include میکنند
|
||||
تا ساختار، استایل و اسکریپت ویرایش/شمارهها در همهٔ صفحات مشتری یکسان بماند.
|
||||
شناسههای موردنیاز جاوااسکریپت:
|
||||
#formcsEdit, #enEdit, #editCs, #cancelEdit, #del_customer, #showUserDT, #userDT
|
||||
{% endcomment %}
|
||||
<form action="{% url 'admin_panel:customerEdit' customer.id %}" id="formcsEdit" class="has-background-white brad5" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="buttons m-0 level p-2 is-hidden-tablet">
|
||||
<p class="level-item">{{ customer.name_family }}</p>
|
||||
<span id="showUserDT" class="button level-item">مشاهده مشخصات</span>
|
||||
</div>
|
||||
|
||||
<div id="userDT" class="is-hidden-mobile">
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">نام ونام خانوادگی</label>
|
||||
<div class="control">
|
||||
<input required disabled value="{{ customer.name_family }}" name="an_name" class="input an_name" type="text" placeholder="نام نام خانوادگی">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">آدرس</label>
|
||||
<div class="control">
|
||||
<input disabled name="an_address" value="{{ customer.address }}" class="input an_address" type="text" placeholder="آدرس کامل">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "./_customer_phones.html" with phones_editable=1 %}
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">تاریخ نصب</label>
|
||||
<div class="control">
|
||||
<input disabled name="installDate" value="{{ customer.installDate | toJalali }}" id="persianDateP" class="input ss02 installDate" type="text" placeholder="مثل: 1400/06/01">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">کدشناسایی</label>
|
||||
<div class="control">
|
||||
<input disabled value="{{ customer.idCode }}" name="idnCode" class="input idnCode ss02" type="text" placeholder="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">توضیحات</label>
|
||||
<div class="control">
|
||||
<textarea disabled name="notes" class="textarea ss02 an_notes" rows="4" placeholder="یادداشت آزاد">{{ customer.notes }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">لغو بازدید</label>
|
||||
<div class="control">
|
||||
<select name="laghv_bazdid" style="width: 100%" class="select" id="LaghvBazdid">
|
||||
<option value="false" {% if not customer.laghv_bazdid %}selected{% endif %}>خیر</option>
|
||||
<option value="true" {% if customer.laghv_bazdid %}selected{% endif %}>بله</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
{% if perms.admin_panel.DelCustomer %}
|
||||
<span class="button is-danger" id="del_customer">حذف مشتری</span>
|
||||
{% endif %}
|
||||
<span class="button is-success" id="enEdit">ویرایش</span>
|
||||
<span class="button is-light is-hidden" id="cancelEdit">انصراف</span>
|
||||
<input class="button is-danger is-hidden" id="editCs" value="ثبت" type="submit">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// اسکریپت مشترک کارت پروفایل مشتری: ویرایش/انصراف، افزودن و حذف شماره،
|
||||
// باز/بستن در موبایل و حذف مشتری. (یکبار bind میشود.)
|
||||
(function () {
|
||||
if (window.__customerInfoCardInit) { return; }
|
||||
window.__customerInfoCardInit = true;
|
||||
|
||||
// --- ورود به حالت ویرایش ---
|
||||
$(document).on('click', '#enEdit', function () {
|
||||
$('#formcsEdit').find('input, textarea, select').prop('disabled', false);
|
||||
$('#formcsEdit').find('.remove-phone-btn, .add-phone-btn').prop('disabled', false);
|
||||
$('#enEdit').addClass('is-hidden');
|
||||
$('#cancelEdit').removeClass('is-hidden');
|
||||
$('#editCs').removeClass('is-hidden');
|
||||
});
|
||||
|
||||
// --- انصراف: بارگذاری مجدد؛ تغییرات ذخیرهنشده دور ریخته میشود ---
|
||||
$(document).on('click', '#cancelEdit', function () {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
// --- افزودن شماره (در حالت ویرایش) ---
|
||||
$(document).on('click', '.add-phone-btn', function () {
|
||||
var phoneRow = `
|
||||
<div class="field has-addons mt-2 ss02">
|
||||
<div class="control" style="width: 30%;">
|
||||
<input name="phone_titles" class="input" type="text" placeholder="عنوان">
|
||||
</div>
|
||||
<div class="control is-expanded">
|
||||
<input name="phones" class="input an_phone" type="text" placeholder="شمارهٔ تماس جدید">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="button" class="button is-danger remove-phone-btn">-</button>
|
||||
</div>
|
||||
</div>`;
|
||||
$('#dynamic-phones-edit').append(phoneRow);
|
||||
});
|
||||
|
||||
// --- حذف شماره (حداقل یک شماره باید بماند) ---
|
||||
$(document).on('click', '.remove-phone-btn', function () {
|
||||
if ($('#dynamic-phones-edit .field.has-addons').length > 1) {
|
||||
$(this).closest('.field.has-addons').remove();
|
||||
} else {
|
||||
window.alert('حداقل یک شمارهٔ تماس باید وجود داشته باشد.');
|
||||
}
|
||||
});
|
||||
|
||||
// --- باز/بستن مشخصات در موبایل ---
|
||||
$(document).on('click', '#showUserDT', function () {
|
||||
var $btn = $(this);
|
||||
if ($btn.text().trim() === 'مشاهده مشخصات') {
|
||||
$btn.text('مخفی کردن');
|
||||
$('#userDT').removeClass('is-hidden-mobile');
|
||||
} else {
|
||||
$btn.text('مشاهده مشخصات');
|
||||
$('#userDT').addClass('is-hidden-mobile');
|
||||
}
|
||||
});
|
||||
|
||||
// --- حذف مشتری ---
|
||||
$(document).on('click', '#del_customer', function () {
|
||||
if (window.confirm('با حذف مشتری، تمام سوابق، فاکتورها و اطلاعات از سیستم حذف میشود، مطمئن هستید؟')) {
|
||||
window.location.href = '/panel/customer/{{ customer.id }}/del';
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -19,6 +19,16 @@
|
||||
📞
|
||||
</a>
|
||||
</div>
|
||||
{% if perms.admin_panel.ManageSMS and entry.is_mobile %}
|
||||
<div class="control">
|
||||
<button type="button"
|
||||
class="button sms-primary-toggle {% if entry.is_primary %}is-warning{% else %}is-light{% endif %}"
|
||||
data-url="{% url 'admin_panel:sms_primary_toggle' customer.id %}"
|
||||
data-phone="{{ entry.raw }}"
|
||||
data-primary="{% if entry.is_primary %}true{% else %}false{% endif %}"
|
||||
title="{% if entry.is_primary %}برداشتن از شمارههای اصلی پیامک{% else %}تعیین بهعنوان شمارهٔ اصلی پیامک{% endif %}">{% if entry.is_primary %}★ اصلی{% else %}☆ اصلی{% endif %}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if phones_editable and perms.admin_panel.ViewStore %}
|
||||
<div class="control">
|
||||
<button type="button" disabled class="button is-danger remove-phone-btn">-</button>
|
||||
@@ -35,3 +45,50 @@
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if perms.admin_panel.ManageSMS %}
|
||||
<script>
|
||||
// تعیین شمارهٔ اصلی پیامک از همین ستون (بدون فرم تودرتو) — با درخواست async و
|
||||
// بارگذاری مجدد صفحه تا وضعیت شمارهها و رنگ دکمهها بهدرستی بهروز شوند.
|
||||
(function () {
|
||||
if (window.__smsPrimaryToggleInit) { return; }
|
||||
window.__smsPrimaryToggleInit = true;
|
||||
|
||||
function csrfToken() {
|
||||
var match = document.cookie.match(/(^|;\s*)csrftoken=([^;]+)/);
|
||||
return match ? decodeURIComponent(match[2]) : '';
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
var btn = event.target.closest ? event.target.closest('.sms-primary-toggle') : null;
|
||||
if (!btn || btn.disabled) { return; }
|
||||
event.preventDefault();
|
||||
|
||||
var body = new URLSearchParams();
|
||||
body.append('phone', btn.getAttribute('data-phone') || '');
|
||||
body.append('is_primary', btn.getAttribute('data-primary') === 'true' ? 'false' : 'true');
|
||||
|
||||
btn.classList.add('is-loading');
|
||||
btn.disabled = true;
|
||||
|
||||
fetch(btn.getAttribute('data-url'), {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken(),
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
},
|
||||
body: body.toString()
|
||||
}).then(function (response) {
|
||||
if (!response.ok) { throw new Error('HTTP ' + response.status); }
|
||||
window.location.reload();
|
||||
}).catch(function () {
|
||||
btn.classList.remove('is-loading');
|
||||
btn.disabled = false;
|
||||
window.alert('خطا در تغییر شمارهٔ اصلی. لطفاً دوباره تلاش کنید.');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
||||
@@ -3,37 +3,7 @@
|
||||
<h2 class="title is-5">پیامک</h2>
|
||||
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-6-desktop is-12-mobile">
|
||||
<h3 class="subtitle is-6">شمارههای اصلی (گیرنده پیامک)</h3>
|
||||
{# همهٔ شمارهها از هر منبعی که ذخیره شدهاند نمایش داده میشوند (جدول شمارهها یا فیلدهای قدیمی) #}
|
||||
{% for entry in sms_phone_entries %}
|
||||
<form method="post" action="{% url 'admin_panel:sms_primary_toggle' customer.id %}" class="field has-addons">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="phone" value="{{ entry.raw }}">
|
||||
<input type="hidden" name="is_primary" value="{% if entry.is_primary %}false{% else %}true{% endif %}">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="text"
|
||||
value="{{ entry.title }} — {{ entry.display }}{% if not entry.is_mobile %} (غیرقابل ارسال پیامک){% endif %}"
|
||||
disabled>
|
||||
</div>
|
||||
<div class="control">
|
||||
{% if entry.is_mobile %}
|
||||
<button class="button {% if entry.is_primary %}is-success{% else %}is-light{% endif %}" type="submit">
|
||||
{% if entry.is_primary %}اصلی ✓{% else %}تعیین بهعنوان اصلی{% endif %}
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="button is-light" type="button" disabled>غیرموبایل</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% empty %}
|
||||
<p class="help">شمارهای برای این مشتری ثبت نشده است.</p>
|
||||
{% endfor %}
|
||||
<p class="help">اگر شمارهٔ اصلی تعیین نشده باشد، شمارهٔ اولِ معتبر بهصورت خودکار استفاده میشود.</p>
|
||||
<p class="help">گیرندگان فعلی: {% for r in sms_recipients %}<span dir="ltr">{{ r }}</span>{% if not forloop.last %}، {% endif %}{% empty %}—{% endfor %}</p>
|
||||
</div>
|
||||
|
||||
<div class="column is-6-desktop is-12-mobile">
|
||||
<div class="column is-12">
|
||||
<h3 class="subtitle is-6">فعال/غیرفعال پیامکها برای این مشتری</h3>
|
||||
{% for item in sms_toggles %}
|
||||
<form method="post" action="{% url 'admin_panel:sms_customer_toggle' customer.id %}" class="field has-addons">
|
||||
@@ -58,7 +28,7 @@
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<textarea class="textarea" name="text" rows="2" placeholder="متن پیامک... (خالی بگذارید تا متن قالب «پیام سفارشی» استفاده شود)"></textarea>
|
||||
<textarea class="textarea" name="text" rows="2" placeholder="متن پیامک..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">میتوانید از متغیرهایی مثل {name} (نام مشتری) استفاده کنید.</p>
|
||||
|
||||
@@ -120,19 +120,26 @@
|
||||
|
||||
|
||||
|
||||
{% if perms.admin_panel.ManageSMS %}
|
||||
{% if perms.admin_panel.ManageSMS or perms.admin_panel.ManageSMSSettings %}
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<a class="navbar-link">پیامک</a>
|
||||
<div class="navbar-dropdown">
|
||||
{% if perms.admin_panel.ManageSMSSettings %}
|
||||
<a href="{% url 'admin_panel:sms_settings' %}" style="padding-right: 10px;" class="navbar-item">
|
||||
تنظیمات سامانه
|
||||
</a>
|
||||
<a href="{% url 'admin_panel:sms_templates' %}" style="padding-right: 10px;" class="navbar-item">
|
||||
قالبها
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.admin_panel.ManageSMS %}
|
||||
<a href="{% url 'admin_panel:sms_queue' %}" style="padding-right: 10px;" class="navbar-item">
|
||||
صف ارسال
|
||||
</a>
|
||||
<a href="{% url 'admin_panel:sms_report' %}" style="padding-right: 10px;" class="navbar-item">
|
||||
گزارش ارسال
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -6,55 +6,7 @@
|
||||
<div class="">
|
||||
<div class="columns m-0">
|
||||
<div class="column is-12-mobile is-4-desktop">
|
||||
<form action="{% url 'admin_panel:customerEdit' customer.id %}" id="formcsEdit" class="has-background-white brad5" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="buttons m-0 level p-2 is-hidden-tablet">
|
||||
<p class="level-item">محمد جواد فصاحت</p>
|
||||
<span id="showUserDT" class="button level-item">مشاهده مشخصات</span>
|
||||
</div>
|
||||
<div id="userDT" class="is-hidden-mobile">
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">نام ونام خانوادگی</label>
|
||||
<div class="control">
|
||||
<input disabled value="{{ customer.name_family }}" name="an_name" class="input an_name" type="text" placeholder="نام نام خانوادگی">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">آدرس</label>
|
||||
<div class="control">
|
||||
<input disabled name="an_address" value="{{ customer.address }}" class="input an_address" type="text" placeholder="آدرس کامل">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "./_customer_phones.html" %}
|
||||
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">تاریخ نصب</label>
|
||||
<div class="control">
|
||||
<input disabled name="installDate" value="{{ customer.installDate | toJalali }}" id="persianDateP" class="input installDate" type="text" placeholder="مثل: 1400/06/01">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">کدشناسایی</label>
|
||||
<div class="control">
|
||||
<input disabled value="{{ customer.idCode }}" name="idnCode" class="input idnCode" type="text" placeholder="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
{% if perms.admin_panel.DelCustomer %}
|
||||
<span class="button is-danger" id="del_customer">حذف مشتری</span>
|
||||
{% endif %}
|
||||
|
||||
<span class="button is-success" id="enEdit">ویرایش</span>
|
||||
<input class="button is-danger is-hidden" id="editCs" value="ثبت" type="submit">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% include "./_customer_info.html" %}
|
||||
</div>
|
||||
|
||||
<div class="column is-12-mobile is-8-desktop">
|
||||
@@ -229,11 +181,6 @@
|
||||
|
||||
});
|
||||
|
||||
$("#del_customer").click(function(){
|
||||
if(confirm("با حذف مشتری، تمام سوابق، فاکتورها و اطلاعات از سیستم حذف میشود، مطمئن هستید؟")){
|
||||
window.location.href = "/panel/customer/{{ customer.id }}/del";
|
||||
}
|
||||
});
|
||||
|
||||
$(".closeNewIteam").click(function(){
|
||||
$("#addNewIteam").removeClass("is-active");
|
||||
@@ -318,22 +265,6 @@ $(document).ready(function() {
|
||||
|
||||
var test = 0
|
||||
|
||||
$("#enEdit").click(function(){
|
||||
$("#formcsEdit").find("input").prop('disabled', false);
|
||||
$("#enEdit").addClass("is-hidden");
|
||||
$("#editCs").removeClass("is-hidden");
|
||||
});
|
||||
|
||||
$("#showUserDT").click(function(){
|
||||
if( $(this).html()=='مشاهده مشخصات' ){
|
||||
$(this).html('مخفی کردن');
|
||||
$("#userDT").removeClass("is-hidden-mobile");
|
||||
}else{
|
||||
$(this).html('مشاهده مشخصات');
|
||||
$("#userDT").addClass("is-hidden-mobile");
|
||||
}
|
||||
|
||||
});
|
||||
$(".closeSearch").click(function(){
|
||||
$(".SearchBox").removeClass("is-active")
|
||||
});
|
||||
|
||||
@@ -18,76 +18,7 @@
|
||||
<div class="">
|
||||
<div class="columns m-0">
|
||||
<div class="column is-12-mobile is-4-desktop">
|
||||
<form action="{% url 'admin_panel:customerEdit' customer.id %}" id="formcsEdit" class="has-background-white brad5" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="buttons m-0 level p-2 is-hidden-tablet">
|
||||
<p class="level-item">
|
||||
{{ customer.name_family }}
|
||||
</p>
|
||||
<span id="showUserDT" class="button level-item">مشاهده مشخصات</span>
|
||||
</div>
|
||||
|
||||
<div id="userDT" class="is-hidden-mobile">
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">نام ونام خانوادگی</label>
|
||||
<div class="control">
|
||||
<input required disabled value="{{ customer.name_family }}" name="an_name" class="input an_name" type="text" placeholder="نام نام خانوادگی">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">آدرس</label>
|
||||
<div class="control">
|
||||
<input disabled name="an_address" value="{{ customer.address }}" class="input an_address" type="text" placeholder="آدرس کامل">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "./_customer_phones.html" with phones_editable=1 %}
|
||||
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">تاریخ نصب</label>
|
||||
<div class="control">
|
||||
<input disabled name="installDate" value="{{ customer.installDate | toJalali }}" id="persianDateP" class="input ss02 installDate" type="text" placeholder="مثل: 1400/06/01">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">کدشناسایی</label>
|
||||
<div class="control">
|
||||
<input disabled value="{{ customer.idCode }}" name="idnCode" class="input idnCode ss02 " type="text" placeholder="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">توضیحات</label>
|
||||
<div class="control">
|
||||
<textarea disabled name="notes" class="textarea ss02 an_notes" rows="4" placeholder="یادداشت آزاد">{{ customer.notes }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
<label class="label">لغو بازدید</label>
|
||||
<div class="control">
|
||||
<select name="laghv_bazdid" style="width: 100%" class="select" id="LaghvBazdid">
|
||||
<option value="false">خیر</option>
|
||||
<option value="true">بله</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-desktop is-12-mobile m-0">
|
||||
{% if perms.admin_panel.DelCustomer %}
|
||||
<span class="button is-danger" id="del_customer">حذف مشتری</span>
|
||||
{% endif %}
|
||||
|
||||
<span class="button is-success" id="enEdit">ویرایش</span>
|
||||
<input class="button is-danger is-hidden" id="editCs" value="ثبت" type="submit">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% include "./_customer_info.html" %}
|
||||
</div>
|
||||
|
||||
<div class="column is-12-mobile is-8-desktop">
|
||||
@@ -102,6 +33,7 @@
|
||||
<li id="newFactorOpen" class="{% if url_name == 'customerNew' %}is-active{% else %} {% endif %} "><a href="{% url 'admin_panel:customerNew' customer.id %}">ایجاد فاکتور</a></li>
|
||||
<li id="FactorHaOpen" class="{% if url_name == 'customerFactors' %}is-active{% else %} {% endif %} "><a href="{% url 'admin_panel:customerFactors' customer.id %}">فاکتورها </a></li>
|
||||
<li class=""><a href="{% url 'admin_panel:customerAction' customer.id %}">بازاریابی</a></li>
|
||||
{% if perms.admin_panel.ManageSMS %}<li class="{% if url_name == 'customerSmsHistory' %}is-active{% else %} {% endif %} "><a href="{% url 'admin_panel:customerSmsHistory' customer.id %}">سوابق پیامک</a></li>{% endif %}
|
||||
|
||||
{% endwith %}
|
||||
|
||||
@@ -473,11 +405,6 @@
|
||||
|
||||
});
|
||||
|
||||
$("#del_customer").click(function(){
|
||||
if(confirm("با حذف مشتری، تمام سوابق، فاکتورها و اطلاعات از سیستم حذف میشود، مطمئن هستید؟")){
|
||||
window.location.href = "/panel/customer/{{ customer.id }}/del";
|
||||
}
|
||||
});
|
||||
|
||||
$(".closeNewIteam").click(function(){
|
||||
$("#addNewIteam").removeClass("is-active");
|
||||
@@ -603,50 +530,6 @@ $(document).ready(function() {
|
||||
|
||||
var test = 0;
|
||||
|
||||
$("#enEdit").click(function(){
|
||||
$("#formcsEdit").find("input, textarea").prop('disabled', false);
|
||||
// فعال کردن دکمههای کنترل تلفن
|
||||
$("#formcsEdit").find("button.remove-phone-btn, button.add-phone-btn").prop('disabled', false);
|
||||
|
||||
$("#enEdit").addClass("is-hidden");
|
||||
$("#editCs").removeClass("is-hidden");
|
||||
});
|
||||
|
||||
// توابع داینامیک فرم ویرایش
|
||||
$(document).on('click', '.add-phone-btn', function() {
|
||||
var phoneRow = `
|
||||
<div class="field has-addons mt-2 ss02">
|
||||
<div class="control" style="width: 35%;">
|
||||
<input name="phone_titles" class="input" type="text" placeholder="عنوان">
|
||||
</div>
|
||||
<div class="control is-expanded">
|
||||
<input name="phones" class="input an_phone" type="text" placeholder="شماره تماس جدید">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="button" class="button is-danger remove-phone-btn">-</button>
|
||||
</div>
|
||||
</div>`;
|
||||
$("#dynamic-phones-edit").append(phoneRow);
|
||||
});
|
||||
|
||||
$(document).on('click', '.remove-phone-btn', function() {
|
||||
if ($("#dynamic-phones-edit .has-addons").length > 1) {
|
||||
$(this).closest('.field.has-addons').remove();
|
||||
} else {
|
||||
alert("حداقل یک شماره تماس باید وجود داشته باشد.");
|
||||
}
|
||||
});
|
||||
|
||||
$("#showUserDT").click(function(){
|
||||
if( $(this).html()=='مشاهده مشخصات' ){
|
||||
$(this).html('مخفی کردن');
|
||||
$("#userDT").removeClass("is-hidden-mobile");
|
||||
}else{
|
||||
$(this).html('مشاهده مشخصات');
|
||||
$("#userDT").addClass("is-hidden-mobile");
|
||||
}
|
||||
|
||||
});
|
||||
$(".closeSearch").click(function(){
|
||||
$(".SearchBox").removeClass("is-active")
|
||||
});
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
{% extends "./customerBaseNew.html" %}
|
||||
{% load custom_tags %}
|
||||
{% load static %}
|
||||
|
||||
{% block BodyCustomer1 %}
|
||||
|
||||
<style>
|
||||
/* جدول سوابق پیامک مشتری */
|
||||
.smshist-msg {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
max-width: 34rem;
|
||||
line-height: 1.9;
|
||||
}
|
||||
.smshist-meta { font-size: .78rem; line-height: 1.7; }
|
||||
.smshist-badge { white-space: nowrap; }
|
||||
</style>
|
||||
|
||||
<div id="customerSmsHistory" class="column is-12-mobile is-12-desktop" dir="rtl">
|
||||
<h1 class="has-text-centered has-text-weight-bold mb-1">سوابق پیامک مشتری</h1>
|
||||
<p class="has-text-centered has-text-grey smshist-meta mb-3">
|
||||
همهٔ پیامکهای ارسالشده برای شمارههای این مشتری — جمعاً
|
||||
<strong>{{ total_count }}</strong> پیامک.
|
||||
</p>
|
||||
|
||||
<form method="get" class="field is-grouped is-grouped-multiline mb-3">
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select name="status">
|
||||
<option value="">همهٔ وضعیتهای ارسال</option>
|
||||
{% for value, label in status_choices %}
|
||||
<option value="{{ value }}" {% if status == value %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select name="delivery">
|
||||
<option value="">همهٔ وضعیتهای تحویل</option>
|
||||
{% for value, label in delivery_choices %}
|
||||
<option value="{{ value }}" {% if delivery == value %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control"><button class="button is-link" type="submit">فیلتر</button></div>
|
||||
{% if status or delivery %}
|
||||
<div class="control">
|
||||
<a class="button is-light" href="{% url 'admin_panel:customerSmsHistory' customer.id %}">حذف فیلتر</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
{% if status or delivery %}
|
||||
<p class="mb-2 smshist-meta">نتایج فیلترشده: <strong>{{ filtered_count }}</strong> مورد</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-narrow is-hoverable is-striped is-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>شناسه</th>
|
||||
<th style="min-width: 7rem;">نوع پیامک</th>
|
||||
<th style="min-width: 8rem;">شماره گیرنده</th>
|
||||
<th style="min-width: 18rem;">متن پیامک</th>
|
||||
<th>وضعیت ارسال</th>
|
||||
<th style="min-width: 8rem;">وضعیت تحویل</th>
|
||||
<th>تاریخ ایجاد</th>
|
||||
<th>تاریخ ارسال</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.get_sms_type_display|default:'—' }}</td>
|
||||
<td dir="ltr">{{ item.phone_number }}</td>
|
||||
<td class="smshist-msg">{{ item.message }}</td>
|
||||
<td>
|
||||
<span class="tag smshist-badge
|
||||
{% if item.status == 'sent' %}is-success
|
||||
{% elif item.status == 'failed' %}is-danger
|
||||
{% elif item.status == 'pending' %}is-warning
|
||||
{% elif item.status == 'approved' %}is-info
|
||||
{% else %}is-light{% endif %}">
|
||||
{{ item.get_status_display }}
|
||||
</span>
|
||||
{% if item.last_error %}
|
||||
<div class="has-text-danger smshist-meta" title="{{ item.last_error }}">
|
||||
خطا: {{ item.last_error|truncatechars:80 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="tag smshist-badge
|
||||
{% if item.delivery_status == 'delivered' %}is-success
|
||||
{% elif item.delivery_status == 'not_delivered' %}is-danger
|
||||
{% elif item.delivery_status == 'expired' %}is-danger
|
||||
{% elif item.delivery_status == 'pending' %}is-warning
|
||||
{% else %}is-light{% endif %}">
|
||||
{{ item.get_delivery_status_display|default:'بدون پیگیری' }}
|
||||
</span>
|
||||
{% if item.delivery_checked_at %}
|
||||
<div class="has-text-grey smshist-meta">استعلام: {{ item.delivery_checked_at|toJalaliDT }}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="smshist-meta">{{ item.created_at|toJalaliDT }}</td>
|
||||
<td class="smshist-meta">{{ item.sent_at|toJalaliDT|default:'—' }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="8" class="has-text-centered">پیامکی برای این مشتری ثبت نشده است.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if items.has_other_pages %}
|
||||
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
|
||||
{% if items.has_previous %}
|
||||
<a class="pagination-previous" href="?page={{ items.previous_page_number }}{% if status %}&status={{ status }}{% endif %}{% if delivery %}&delivery={{ delivery }}{% endif %}">قبلی</a>
|
||||
{% else %}
|
||||
<a class="pagination-previous" disabled>قبلی</a>
|
||||
{% endif %}
|
||||
{% if items.has_next %}
|
||||
<a class="pagination-next" href="?page={{ items.next_page_number }}{% if status %}&status={{ status }}{% endif %}{% if delivery %}&delivery={{ delivery }}{% endif %}">بعدی</a>
|
||||
{% else %}
|
||||
<a class="pagination-next" disabled>بعدی</a>
|
||||
{% endif %}
|
||||
<ul class="pagination-list">
|
||||
<li><span class="pagination-link">صفحهٔ {{ items.number }} از {{ items.paginator.num_pages }}</span></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -12,6 +12,9 @@
|
||||
حذف یک مورد، فقط همان نوبت را کنسل میکند. یادآوریهای دورهای (بازدید و ثبت
|
||||
اطلاعات چک) در <strong>دورهٔ بعدی</strong> دوباره ساخته میشوند؛ پس با حذف یک
|
||||
مورد، پیامک برای آن مشتری برای همیشه قطع نمیشود.
|
||||
<br>
|
||||
پیامکهای <strong>ارسالشده</strong> از این صف بیرون میروند؛ فهرست و سابقهٔ
|
||||
کاملشان در <a href="{% url 'admin_panel:sms_report' %}">گزارش ارسال</a> است.
|
||||
</div>
|
||||
|
||||
<div class="columns is-multiline mb-2" dir="rtl">
|
||||
@@ -21,6 +24,13 @@
|
||||
<p class="title is-5 mb-0">{{ total_count }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<a href="{% url 'admin_panel:sms_report' %}" class="box py-3 px-4 has-text-centered"
|
||||
title="مشاهدهٔ سوابق ارسالشدهها در گزارش ارسال">
|
||||
<p class="heading mb-0">ارسالشده (در گزارش)</p>
|
||||
<p class="title is-5 mb-0 has-text-success">{{ sent_count }}</p>
|
||||
</a>
|
||||
</div>
|
||||
{% for code, label, count in status_stats %}
|
||||
<div class="column is-narrow">
|
||||
<a href="?status={{ code }}" class="box py-3 px-4 has-text-centered"
|
||||
@@ -71,11 +81,12 @@
|
||||
</form>
|
||||
|
||||
<div class="notification is-danger is-light is-flex is-align-items-center is-justify-content-space-between mb-3">
|
||||
<span>خالی کردن کل صف: همهٔ پیامکها (در هر وضعیتی) حذف میشوند و دیگر بازنمیگردند.</span>
|
||||
<span>خالی کردن صف: همهٔ موارد باقیمانده (در انتظار تأیید، تأییدشده، در حال پردازش و ناموفق)
|
||||
حذف میشوند و دیگر بازنمیگردند. پیامکهای ارسالشده دستنخورده میمانند (در گزارش ارسال).</span>
|
||||
<form method="post" action="{% url 'admin_panel:sms_queue_empty' %}"
|
||||
onsubmit="return confirm('کل صف پیامک خالی شود؟ این پیامکها دیگر هرگز ارسال یا بازسازی نمیشوند.');">
|
||||
onsubmit="return confirm('صف پیامک خالی شود؟ این پیامکها دیگر هرگز ارسال یا بازسازی نمیشوند.');">
|
||||
{% csrf_token %}
|
||||
<button class="button is-danger" type="submit">خالی کردن کل صف</button>
|
||||
<button class="button is-danger" type="submit">خالی کردن صف</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -119,11 +130,17 @@
|
||||
<button class="button is-small is-warning" type="submit">ارسال مجدد</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if item.status != 'sent' %}
|
||||
<form method="post" action="{% url 'admin_panel:sms_queue_delete' item.id %}"
|
||||
onclick="return confirm('این پیامک از صف حذف شود؟');">
|
||||
{% csrf_token %}
|
||||
<button class="button is-small is-danger" type="submit">حذف</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<a class="button is-small is-light"
|
||||
href="{% url 'admin_panel:sms_report' %}?q={{ item.phone_number }}"
|
||||
title="این پیامک ارسال شده است؛ سابقهٔ آن در گزارش ارسال است">در گزارش</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
{% extends "./base.html" %}
|
||||
{% load custom_tags %}
|
||||
|
||||
{% block bodyPage %}
|
||||
<div class="container mt-5" dir="rtl">
|
||||
<h1 class="title is-4">گزارش ارسال پیامک</h1>
|
||||
<p class="has-text-grey mb-3">
|
||||
پیامکهای <strong>تأییدشده و ارسالشده</strong> (موارد «در انتظار تأیید» و «لغوشده»
|
||||
در این گزارش نمیآیند) بههمراه «تعداد پیامکی که اپراتور محاسبه کرده»
|
||||
(بر اساس کدگذاری GSM-7 برای انگلیسی/عدد و UCS-2 برای فارسی/یونیکد) — برای تحلیل هزینه.
|
||||
</p>
|
||||
|
||||
{# --- کارتهای خلاصه (روی کل نتیجهٔ فیلترشده) --- #}
|
||||
<div class="columns is-multiline mb-2">
|
||||
<div class="column is-narrow">
|
||||
<div class="box py-3 px-4 has-text-centered">
|
||||
<p class="heading mb-0">تعداد پیامک (رکورد)</p>
|
||||
<p class="title is-5 mb-0">{{ total_records }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<div class="box py-3 px-4 has-text-centered">
|
||||
<p class="heading mb-0">مجموع پیامک محاسبهشده</p>
|
||||
<p class="title is-5 mb-0 has-text-danger">{{ total_parts }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<div class="box py-3 px-4 has-text-centered">
|
||||
<p class="heading mb-0">GSM-7 / UCS-2</p>
|
||||
<p class="title is-6 mb-0">{{ gsm7_parts }} / {{ ucs2_parts }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% for code, label, count in status_stats %}
|
||||
<div class="column is-narrow">
|
||||
<a href="?status={{ code }}" class="box py-3 px-4 has-text-centered"
|
||||
{% if status == code %}style="border:2px solid #3273dc"{% endif %}>
|
||||
<p class="heading mb-0">{{ label }}</p>
|
||||
<p class="title is-5 mb-0">{{ count }}</p>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{# --- فرم فیلتر --- #}
|
||||
<form method="get" class="box">
|
||||
<div class="columns is-multiline is-variable is-2">
|
||||
<div class="column is-2">
|
||||
<label class="label is-small">از تاریخ (شمسی)</label>
|
||||
<input class="input" type="text" id="dateFromP" name="date_from"
|
||||
value="{{ date_from }}" placeholder="1404/01/01" autocomplete="off">
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<label class="label is-small">تا تاریخ (شمسی)</label>
|
||||
<input class="input" type="text" id="dateToP" name="date_to"
|
||||
value="{{ date_to }}" placeholder="1404/12/29" autocomplete="off">
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<label class="label is-small">وضعیت ارسال</label>
|
||||
<div class="select is-fullwidth">
|
||||
<select name="status">
|
||||
<option value="">همه</option>
|
||||
{% for value, label in status_choices %}
|
||||
<option value="{{ value }}" {% if status == value %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<label class="label is-small">وضعیت تحویل</label>
|
||||
<div class="select is-fullwidth">
|
||||
<select name="delivery">
|
||||
<option value="">همه</option>
|
||||
{% for value, label in delivery_choices %}
|
||||
<option value="{{ value }}" {% if delivery == value %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<label class="label is-small">نوع پیامک</label>
|
||||
<div class="select is-fullwidth">
|
||||
<select name="sms_type">
|
||||
<option value="">همه</option>
|
||||
{% for value, label in type_choices %}
|
||||
<option value="{{ value }}" {% if sms_type == value %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<label class="label is-small">جستجو (متن/شماره/نام مشتری)</label>
|
||||
<input class="input" type="text" name="q" value="{{ query }}" placeholder="جستجو…">
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button class="button is-link" type="submit">اعمال فیلتر</button>
|
||||
{% if has_filter %}
|
||||
<a class="button is-light" href="{% url 'admin_panel:sms_report' %}">حذف فیلترها</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{# --- جدول --- #}
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-narrow is-hoverable is-striped is-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>شناسه</th>
|
||||
<th style="min-width: 16rem;">متن دقیق پیامک</th>
|
||||
<th style="min-width: 8rem;">شماره گیرنده</th>
|
||||
<th>نوع پیامک</th>
|
||||
<th style="min-width: 8rem;">مشتری</th>
|
||||
<th>سوابق</th>
|
||||
<th>تعداد پیامک</th>
|
||||
<th>وضعیت ارسال</th>
|
||||
<th>تحویل</th>
|
||||
<th style="min-width: 8rem;">تاریخ ایجاد</th>
|
||||
<th style="min-width: 8rem;">تاریخ ارسال</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in rows %}
|
||||
<tr>
|
||||
<td>{{ row.item.id }}</td>
|
||||
<td class="smsrep-msg">{{ row.item.message }}</td>
|
||||
<td dir="ltr">{{ row.item.phone_number }}</td>
|
||||
<td>{{ row.item.get_sms_type_display|default:'دستی/سفارشی' }}</td>
|
||||
<td>
|
||||
{% if row.item.customer_id %}
|
||||
<a href="{% url 'admin_panel:customer' row.item.customer_id %}">{{ row.item.customer.name_family }}</a>
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if row.item.customer_id %}
|
||||
<a class="button is-small is-light"
|
||||
href="{% url 'admin_panel:customerSmsHistory' row.item.customer_id %}"
|
||||
title="مشاهدهٔ سوابق پیامک این مشتری">سوابق پیامک</a>
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="has-text-centered">
|
||||
<span class="tag {% if row.parts > 1 %}is-danger{% else %}is-success{% endif %} is-light"
|
||||
title="{{ row.encoding }} · {{ row.length }} واحد">{{ row.parts }}</span>
|
||||
</td>
|
||||
<td>{{ row.item.get_status_display }}</td>
|
||||
<td>{{ row.item.get_delivery_status_display|default:'—' }}</td>
|
||||
<td>{{ row.item.created_at|toJalaliDT }}</td>
|
||||
<td>{{ row.item.sent_at|toJalaliDT|default:'—' }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="11" class="has-text-centered">پیامکی با این شرایط یافت نشد.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if page.has_other_pages %}
|
||||
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
|
||||
{% if page.has_previous %}
|
||||
<a class="pagination-previous" href="?{{ base_query }}&page={{ page.previous_page_number }}">قبلی</a>
|
||||
{% else %}<a class="pagination-previous" disabled>قبلی</a>{% endif %}
|
||||
{% if page.has_next %}
|
||||
<a class="pagination-next" href="?{{ base_query }}&page={{ page.next_page_number }}">بعدی</a>
|
||||
{% else %}<a class="pagination-next" disabled>بعدی</a>{% endif %}
|
||||
<ul class="pagination-list">
|
||||
<li><span class="pagination-link">صفحهٔ {{ page.number }} از {{ page.paginator.num_pages }}</span></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block cssEnd %}
|
||||
<style>
|
||||
/* متن پیامک با حفظ شکست خطوط و شکستن متنهای طولانی/بدونفاصله */
|
||||
.smsrep-msg { white-space: pre-wrap; overflow-wrap: anywhere; max-width: 26rem; line-height: 1.9; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block jsEnd %}
|
||||
<script>
|
||||
(function () {
|
||||
if (typeof kamaDatepicker !== 'function') { return; }
|
||||
var opts = { buttonsColor: "red", forceFarsiDigits: true, sync: true };
|
||||
['dateFromP', 'dateToP'].forEach(function (id) {
|
||||
if (document.getElementById(id)) { kamaDatepicker(id, opts); }
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -7,6 +7,13 @@
|
||||
<div class="box">
|
||||
<form method="post" action="{% url 'admin_panel:sms_settings' %}">
|
||||
{% csrf_token %}
|
||||
<h2 class="title is-5">اتصال به سامانه پیامک</h2>
|
||||
<p class="help mb-3">
|
||||
این بخش مشخص میکند پیامکها <strong>از چه مسیری و با چه حساب کاربری</strong> به
|
||||
اپراتور سپرده میشوند. تا وقتی «وضعیت ارسال» روشن نباشد، هیچکدام از سرویسهای
|
||||
پسزمینه (worker) پیامکی نمیفرستند.
|
||||
</p>
|
||||
|
||||
<div class="columns is-multiline">
|
||||
<div class="field column is-4-desktop is-12-mobile">
|
||||
<label class="label">سرویسدهنده</label>
|
||||
@@ -19,6 +26,19 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">
|
||||
<strong>مسیر فنی ارسال را تعیین میکند</strong> و روی بقیهٔ فیلدها اثر دارد:
|
||||
<br>• <span dir="ltr">0098SMS (لینکی HTTP)</span>: سادهترین حالت؛ درخواست به
|
||||
آدرس <span dir="ltr">api_url</span> زده میشود. <strong>شناسهٔ پیامک برنمیگردد</strong>،
|
||||
پس استعلام «تحویل به گوشی» ممکن نیست.
|
||||
<br>• <span dir="ltr">0098SMS (وبسرویس)</span>: ارسال SOAP به
|
||||
<span dir="ltr">webservice_url</span>؛ <strong>شناسهٔ پیامک (smsid)</strong> برمیگردد و
|
||||
میتوان وضعیت واقعی تحویل را استعلام کرد.
|
||||
<br>• <span dir="ltr">HTTP عمومی</span>: برای سرویسهای دلخواه؛ با
|
||||
<span dir="ltr">api_token</span> احراز هویت میشود.
|
||||
<br>• <span dir="ltr">Console</span>: فقط برای تست/دیباگ؛ پیامک واقعی نمیرود و
|
||||
متن در لاگ چاپ میشود.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field column is-4-desktop is-12-mobile">
|
||||
@@ -29,6 +49,11 @@
|
||||
فعال باشد
|
||||
</label>
|
||||
</div>
|
||||
<p class="help">
|
||||
<strong>کلید اصلی خاموش/روشن ارسال.</strong> اگر تیک نخورد، سرویس worker هیچ
|
||||
پیامکی نمیفرستد (ردیفها در صف میمانند). اسکنر و صفکردن مستقل از این است،
|
||||
پس میتوانید پیامکها را از قبل در صف بچینید و بعد ارسال را روشن کنید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field column is-4-desktop is-12-mobile">
|
||||
@@ -36,6 +61,13 @@
|
||||
<div class="control">
|
||||
<input class="input" type="number" name="timeout_seconds" value="{{ config.timeout_seconds }}">
|
||||
</div>
|
||||
<p class="help">
|
||||
<strong>حداکثر زمان انتظار worker برای پاسخ سرویسدهنده</strong> در هر تلاش.
|
||||
اگر پاسخ دیرتر بیاید، آن تلاش «ناموفق» ثبت میشود و بر اساس سیاست تلاش مجدد
|
||||
دوباره در نوبت قرار میگیرد. مقدار کم (مثل ۵) ممکن است در سرویس کند، ارسالهای
|
||||
سالم را هم ناموفق کند؛ مقدار زیاد (مثل ۱۲۰) در قطعی سرویس، worker را بیکار
|
||||
نگه میدارد. پیشفرض ۳۰ ثانیه متعادل است.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-mobile">
|
||||
@@ -44,9 +76,14 @@
|
||||
<input class="input" type="text" name="api_url" value="{{ config.api_url }}" dir="ltr">
|
||||
</div>
|
||||
<p class="help">
|
||||
برای 0098SMS همین آدرس لینکی ارسال است
|
||||
<strong>آدرس درخواست ارسال برای providerهای «لینکی HTTP» و «HTTP عمومی».</strong>
|
||||
<br>• برای 0098SMS لینکی همین آدرس است
|
||||
(<span dir="ltr">http://www.0098sms.com/sendsmslink.aspx</span>)؛ این سرویس
|
||||
WSDL/SOAP ندارد و آدرس WSDL باعث خطای «Invalid XML content» میشود.
|
||||
WSDL/SOAP ندارد و اگر آدرس WSDL بگذارید خطای «Invalid XML content» میگیرید.
|
||||
<br>• برای provider وبسرویس، این فیلد استفاده <strong>نمیشود</strong> (آن یکی
|
||||
<span dir="ltr">webservice_url</span> را میخواند).
|
||||
<br>• اگر آدرس اشتباه یا در دسترس نباشد، پیامکها «ناموفق» میشوند و در صف تلاش
|
||||
مجدد میخورند.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -56,8 +93,9 @@
|
||||
<input class="input" type="text" name="webservice_url" value="{{ config.webservice_url }}" dir="ltr">
|
||||
</div>
|
||||
<p class="help">
|
||||
ارسال با شناسهٔ پیامک (<span dir="ltr">smsid</span>) و استعلام تحویل به گوشی
|
||||
از همین آدرس انجام میشود. پیشفرض:
|
||||
<strong>فقط وقتی provider = «وبسرویس» معنا دارد.</strong> هم ارسال با شناسهٔ
|
||||
پیامک (<span dir="ltr">smsid</span>) و هم استعلام تحویل به گوشی از همین آدرس
|
||||
انجام میشود. با providerهای دیگر این فیلد نادیده گرفته میشود. پیشفرض:
|
||||
<span dir="ltr">https://webservice.0098sms.com/service.asmx</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -67,7 +105,11 @@
|
||||
<div class="control">
|
||||
<input class="input" type="text" name="panel_no" value="{{ config.panel_no }}" dir="ltr">
|
||||
</div>
|
||||
<p class="help">اگر خالی بماند از «شماره خط فرستنده» استفاده میشود.</p>
|
||||
<p class="help">
|
||||
<strong>شمارهٔ اختصاصی حساب شما در وبسرویس</strong> (فقط provider وبسرویس).
|
||||
اگر خالی بماند، سیستم از «شماره خط فرستنده» استفاده میکند. مقدار اشتباه باعث
|
||||
رد شدن ارسال با خطای احراز هویت میشود.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field column is-4-desktop is-12-mobile">
|
||||
@@ -75,6 +117,10 @@
|
||||
<div class="control">
|
||||
<input class="input" type="text" name="username" value="{{ config.username }}" dir="ltr">
|
||||
</div>
|
||||
<p class="help">
|
||||
<strong>نام کاربری حساب سامانهٔ پیامک</strong> (در همهٔ providerها بهجز Console
|
||||
لازم است). اگر اشتباه باشد، ارسالها رد میشوند و در «خطا» صف ثبت میگردد.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field column is-4-desktop is-12-mobile">
|
||||
@@ -82,6 +128,11 @@
|
||||
<div class="control">
|
||||
<input class="input" type="password" name="password" placeholder="{% if config.password %}(بدون تغییر){% endif %}" dir="ltr">
|
||||
</div>
|
||||
<p class="help">
|
||||
<strong>رمز حساب سامانهٔ پیامک.</strong> برای امنیت، مقدار ذخیرهشده بازخوانی
|
||||
نمیشود؛ این کادر را <strong>خالی بگذارید</strong> تا رمز قبلی بماند و فقط وقتی
|
||||
رمز عوض شده، مقدار جدید را وارد کنید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field column is-4-desktop is-12-mobile">
|
||||
@@ -89,6 +140,12 @@
|
||||
<div class="control">
|
||||
<input class="input" type="text" name="sender" value="{{ config.sender }}" dir="ltr">
|
||||
</div>
|
||||
<p class="help">
|
||||
<strong>شماره یا عنوان خطی که پیامک با آن به دست مشتری میرسد.</strong> باید
|
||||
جزو خطوط مجاز حساب شما باشد؛ در غیر این صورت اپراتور ارسال را رد میکند.
|
||||
همچنین در provider وبسرویس، اگر «شمارهٔ پنل» خالی باشد همین مقدار جای آن
|
||||
استفاده میشود.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field column is-12-mobile">
|
||||
@@ -96,15 +153,23 @@
|
||||
<div class="control">
|
||||
<input class="input" type="password" name="api_token" placeholder="{% if config.api_token %}(بدون تغییر){% endif %}" dir="ltr">
|
||||
</div>
|
||||
<p class="help">
|
||||
<strong>کلید/توکن احراز هویت providerهای «HTTP عمومی».</strong> برای 0098SMS
|
||||
لازم نیست. مثل رمز، خالی گذاشتن آن یعنی «بدون تغییر».
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h2 class="title is-5">کنترل جریان ارسال</h2>
|
||||
<p class="help mb-3">
|
||||
برای جلوگیری از سیل پیامک: تنها مواردی ارسال میشوند که پنجرهٔ یادآوریشان از
|
||||
«خط شروع» به بعد باز شود. موارد قدیمیتر تنها با روشنکردن «اجازهٔ موارد قدیمی»
|
||||
و در سقف تعیینشده ارسال میشوند. بین هر پیامک هم فاصلهٔ زمانی رعایت میشود.
|
||||
این بخش تعیین میکند اسکنر دورهای <strong>چه چیزی و چقدر</strong> در هر اجرا در صف
|
||||
بگذارد. اسکنر هر چند دقیقه یکبار اجرا میشود و بر اساس «سررسیدها» تصمیم میگیرد
|
||||
کدام مشتری باید یادآوری بگیرد. منطق بودجه به این شکل است:
|
||||
<strong>سقف هر اجرا</strong> ← تعیین میکند کلاً چند مورد صف شود؛
|
||||
<strong>خط شروع</strong> ← موارد قدیمیتر از آن را کنار میگذارد؛
|
||||
<strong>اجازهٔ موارد قدیمی + سقف آن</strong> ← تصمیم میگیرد از آن موارد کنارگذاشتهشده
|
||||
چند تا (اگر لازم بود) ارسال شود.
|
||||
</p>
|
||||
|
||||
<div class="columns is-multiline">
|
||||
@@ -116,9 +181,15 @@
|
||||
placeholder="مثلاً 1403/07/18 — خالی = همین حالا" dir="ltr">
|
||||
</div>
|
||||
<p class="help">
|
||||
فقط یادآوریهایی صف میشوند که «پنجرهٔ» آنها از این تاریخ به بعد باز شده باشد.
|
||||
مشتریان قدیمیتری که پنجرهشان پیش از این تاریخ باز شده، وارد صف نمیشوند.
|
||||
قالب: <span dir="ltr">YYYY/MM/DD</span> (شمسی) — خالی = همین حالا.
|
||||
<strong>خط جداکنندهٔ «موارد جدید» از «موارد قدیمی».</strong>
|
||||
هر مورد (چک، نسیه، بازدید) یک «لحظهٔ باز شدن پنجرهٔ یادآوری» دارد. اگر این
|
||||
لحظه <strong>از این تاریخ به بعد</strong> باشد، مورد «جدید» است و آزادانه صف
|
||||
میشود؛ اگر <strong>قبل از</strong> آن باشد، «قدیمی» (backlog) حساب میشود.
|
||||
<br>• هدف اصلی: در نخستین راهاندازی، هزاران مشتری قدیمیِ واجد شرط یکباره پیامک
|
||||
نگیرند.
|
||||
<br>• قالب: <span dir="ltr">YYYY/MM/DD</span> شمسی — خالی = همین حالا.
|
||||
<br>• عوضکردن آن به گذشته، مشتریان قدیمیتر را «جدید» میکند و ممکن است سیل پیامک
|
||||
بسازد؛ با احتیاط تغییر دهید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -127,6 +198,19 @@
|
||||
<div class="control">
|
||||
<input class="input" type="number" min="0" name="max_per_run" value="{{ config.max_per_run }}">
|
||||
</div>
|
||||
<p class="help">
|
||||
<strong>سقف کل صفکردن در یک اجرا — بهازای هر اسکنر جداگانه.</strong>
|
||||
<br>• «هر اسکنر» یعنی این عدد برای هر نوع یادآوری مستقل اعمال میشود
|
||||
(تأمین موجودی چک، سررسید نسیه، بازدید دورهای)؛ پس اگر ۱۰۰ باشد و هر سه اسکنر
|
||||
واجد شرط باشند، در یک اجرا حداکثر ۱۰۰ + ۱۰۰ + ۱۰۰ = ۳۰۰ ردیف صف میشود.
|
||||
<br>• شمارش از مجموع «جدیدها + موارد قدیمی» است، نه فقط جدیدها.
|
||||
<br>• <strong>۰ = بینهایت</strong> (هیچ سقفی اعمال نمیشود).
|
||||
<br>• چون اسکنر دورهای اجرا میشود، سقف باعث «دستهدسته» شدن ارسالهای انبوه
|
||||
میشود (بقیه در اجراهای بعدی میآیند) و از ریختن یکبارهٔ صدها پیامک روی ارسال
|
||||
جلوگیری میکند.
|
||||
<br>• نکته: این سقفها روی «صفکردن» اثر دارند، نه سرعت ارسال واقعی؛ سرعت ارسال
|
||||
به «فاصلهٔ بین هر پیامک» بستگی دارد.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field column is-3-desktop is-6-mobile">
|
||||
@@ -134,6 +218,11 @@
|
||||
<div class="control">
|
||||
<input class="input" type="number" min="0" name="send_interval_seconds" value="{{ config.send_interval_seconds }}">
|
||||
</div>
|
||||
<p class="help">
|
||||
<strong>مکث سرویس worker پس از هر ارسال</strong> تا اپراتور زیر بار زیاد نرود.
|
||||
مثال: با مقدار ۳، ارسال ۱۰۰ پیامک حدود ۵ دقیقه طول میکشد. مقدار ۰ یعنی بدون
|
||||
مکث (سریع ولی پرخطر — ممکن است سرویسدهنده محدودتان کند).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field column is-3-desktop is-6-mobile">
|
||||
@@ -144,6 +233,12 @@
|
||||
فعال باشد
|
||||
</label>
|
||||
</div>
|
||||
<p class="help">
|
||||
<strong>دربِ موارد «قدیمیتر از تاریخ شروع دوره».</strong> بهصورت پیشفرض بسته
|
||||
است. تا وقتی بسته باشد، موارد قدیمی <strong>هرگز</strong> صف نمیشوند (حتی اگر
|
||||
سقف باقی باشد). اگر لازم بود این موارد هم پردازش شوند، آن را روشن کنید و مقدار
|
||||
«حداکثر موارد قدیمی در هر اجرا» را تعیین کنید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field column is-3-desktop is-6-mobile">
|
||||
@@ -151,7 +246,15 @@
|
||||
<div class="control">
|
||||
<input class="input" type="number" min="0" name="backlog_max" value="{{ config.backlog_max }}">
|
||||
</div>
|
||||
<p class="help">صفر = هیچ مورد قدیمی ارسال نمیشود.</p>
|
||||
<p class="help">
|
||||
<strong>سقف ارسال موارد قدیمی در هر اجرا، بهازای هر اسکنر.</strong>
|
||||
<br>• فقط وقتی «اجازهٔ ارسال موارد قدیمی» روشن باشد اثر دارد.
|
||||
<br>• صفر = هیچ مورد قدیمی ارسال نمیشود (حتی با روشنبودن کلید).
|
||||
<br>• این سقف <strong>در کنار</strong> «حداکثر پیامک هر اسکنر» اعمال میشود؛ پس
|
||||
ممکن است ابتدا سقف کل (max_per_run) پر شود.
|
||||
<br>• برای پاککردن تدریجی انبار قدیمی، عدد کوچکی مثل ۵۰ بگذارید تا در هر اجرا
|
||||
دستهای ارسال شود.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user