Autosave: 20260422-182218
This commit is contained in:
parent
5bfaa401f2
commit
b2cb1b5a0b
@ -10,11 +10,36 @@ if (!in_array($user['role'], ['owner', 'manager'])) {
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$isAjax = strtolower((string) ($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '')) === 'xmlhttprequest';
|
||||
$respond = static function (bool $success, string $type, string $message, ?string $redirect = null) use ($isAjax): void {
|
||||
if ($isAjax) {
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
echo json_encode([
|
||||
'success' => $success,
|
||||
'type' => $type,
|
||||
'message' => $message,
|
||||
'redirect' => $redirect,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
set_flash($type, $message);
|
||||
header('Location: ' . ($redirect ?: '../index.php'));
|
||||
exit;
|
||||
};
|
||||
$redirectBack = static function (): string {
|
||||
$referer = $_SERVER['HTTP_REFERER'] ?? '../index.php';
|
||||
$returnModal = trim((string) ($_POST['return_modal'] ?? ''));
|
||||
if ($returnModal === 'wablas') {
|
||||
return append_query_params($referer, ['open_modal' => 'wablas']);
|
||||
}
|
||||
return $referer;
|
||||
};
|
||||
$pdo = db();
|
||||
$keys = [
|
||||
'timezone', 'company_name_ar', 'company_name_en', 'vat_percentage',
|
||||
'company_vat_number', 'company_phone', 'company_email', 'company_address',
|
||||
'wablas_enabled', 'wablas_token', 'wablas_secret_key',
|
||||
'wablas_enabled', 'wablas_token', 'wablas_secret_key', 'wablas_api_url',
|
||||
'wablas_invoice_recipients', 'wablas_report_recipients',
|
||||
'wablas_daily_auto_send', 'wablas_daily_auto_time', 'wablas_daily_auto_last_date',
|
||||
'wablas_template_invoice', 'wablas_template_daily_report',
|
||||
@ -30,10 +55,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($companyPhone !== '') {
|
||||
$companyPhone = normalize_oman_phone($companyPhone);
|
||||
if ($companyPhone === '') {
|
||||
set_flash('danger', tr('رقم هاتف الشركة يجب أن يكون عمانياً من 8 خانات.', 'Company phone must be an 8-digit Oman number.'));
|
||||
$referer = $_SERVER['HTTP_REFERER'] ?? '../index.php';
|
||||
header('Location: ' . $referer);
|
||||
exit;
|
||||
$respond(false, 'danger', tr('رقم هاتف الشركة يجب أن يكون عمانياً من 8 خانات.', 'Company phone must be an 8-digit Oman number.'), $redirectBack());
|
||||
}
|
||||
$_POST['company_phone'] = $companyPhone;
|
||||
}
|
||||
@ -41,10 +63,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
foreach (['wablas_invoice_recipients', 'wablas_report_recipients'] as $phoneListKey) {
|
||||
$parsed = wablas_parse_phone_list((string) ($_POST[$phoneListKey] ?? ''));
|
||||
if (!empty($parsed['invalid'])) {
|
||||
set_flash('danger', tr('يوجد رقم واتساب غير صالح في الحقل.', 'There is an invalid WhatsApp number in the field.') . ' ' . implode(', ', $parsed['invalid']));
|
||||
$referer = $_SERVER['HTTP_REFERER'] ?? '../index.php';
|
||||
header('Location: ' . $referer);
|
||||
exit;
|
||||
$respond(false, 'danger', tr('يوجد رقم واتساب غير صالح في الحقل.', 'There is an invalid WhatsApp number in the field.') . ' ' . implode(', ', $parsed['invalid']), $redirectBack());
|
||||
}
|
||||
$_POST[$phoneListKey] = implode(',', $parsed['phones']);
|
||||
}
|
||||
@ -90,10 +109,5 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
}
|
||||
|
||||
set_flash('success', tr('تم حفظ الإعدادات بنجاح.', 'Settings saved successfully.'));
|
||||
|
||||
// Redirect back to referring page
|
||||
$referer = $_SERVER['HTTP_REFERER'] ?? '../index.php';
|
||||
header('Location: ' . $referer);
|
||||
exit;
|
||||
$respond(true, 'success', tr('تم حفظ الإعدادات بنجاح.', 'Settings saved successfully.'), $redirectBack());
|
||||
}
|
||||
@ -12,48 +12,68 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
redirect_to('../index.php');
|
||||
}
|
||||
|
||||
$isAjax = strtolower((string) ($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '')) === 'xmlhttprequest';
|
||||
$respond = static function (bool $success, string $type, string $message, ?string $redirect = null) use ($isAjax): void {
|
||||
if ($isAjax) {
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
echo json_encode([
|
||||
'success' => $success,
|
||||
'type' => $type,
|
||||
'message' => $message,
|
||||
'redirect' => $redirect,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
set_flash($type, $message);
|
||||
header('Location: ' . ($redirect ?: '../index.php'));
|
||||
exit;
|
||||
};
|
||||
|
||||
$redirectBack = static function (): string {
|
||||
$referer = $_SERVER['HTTP_REFERER'] ?? '../index.php';
|
||||
$returnModal = trim((string) ($_POST['return_modal'] ?? ''));
|
||||
if ($returnModal === 'wablas') {
|
||||
return append_query_params($referer, ['open_modal' => 'wablas']);
|
||||
}
|
||||
return $referer;
|
||||
};
|
||||
|
||||
$phoneInput = trim((string) ($_POST['wablas_test_phone'] ?? ''));
|
||||
$message = trim((string) ($_POST['wablas_test_message'] ?? ''));
|
||||
$token = trim((string) ($_POST['wablas_token'] ?? get_setting('wablas_token', '')));
|
||||
$secretKey = trim((string) ($_POST['wablas_secret_key'] ?? get_setting('wablas_secret_key', '')));
|
||||
$apiUrl = trim((string) ($_POST['wablas_api_url'] ?? get_setting('wablas_api_url', '')));
|
||||
|
||||
if ($phoneInput === '') {
|
||||
set_flash('danger', tr('أدخل رقم واتساب تجريبي صالحاً من 8 خانات.', 'Enter a valid 8-digit test WhatsApp number.'));
|
||||
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '../index.php'));
|
||||
exit;
|
||||
$respond(false, 'danger', tr('أدخل رقم واتساب تجريبي صالحاً من 8 خانات.', 'Enter a valid 8-digit test WhatsApp number.'), $redirectBack());
|
||||
}
|
||||
|
||||
$phone = normalize_oman_phone($phoneInput);
|
||||
if ($phone === '') {
|
||||
set_flash('danger', tr('رقم الاختبار يجب أن يكون عمانياً من 8 خانات.', 'The test phone must be a valid 8-digit Oman number.'));
|
||||
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '../index.php'));
|
||||
exit;
|
||||
$respond(false, 'danger', tr('رقم الاختبار يجب أن يكون عمانياً من 8 خانات.', 'The test phone must be a valid 8-digit Oman number.'), $redirectBack());
|
||||
}
|
||||
|
||||
if ($message === '') {
|
||||
set_flash('danger', tr('اكتب رسالة الاختبار أولاً.', 'Write the test message first.'));
|
||||
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '../index.php'));
|
||||
exit;
|
||||
$respond(false, 'danger', tr('اكتب رسالة الاختبار أولاً.', 'Write the test message first.'), $redirectBack());
|
||||
}
|
||||
|
||||
if (!wablas_has_credentials($token, $secretKey)) {
|
||||
set_flash('danger', tr('أدخل Wablas Token و Secret Key قبل إرسال الاختبار.', 'Enter the Wablas token and secret key before sending a test message.'));
|
||||
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '../index.php'));
|
||||
exit;
|
||||
$respond(false, 'danger', tr('أدخل Wablas Token و Secret Key قبل إرسال الاختبار.', 'Enter the Wablas token and secret key before sending a test message.'), $redirectBack());
|
||||
}
|
||||
|
||||
$result = wablas_send_message($phone, $message, [
|
||||
'token' => $token,
|
||||
'secret_key' => $secretKey,
|
||||
'api_url' => $apiUrl,
|
||||
'ignore_enabled' => true,
|
||||
]);
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
set_flash('success', tr('تم إرسال رسالة الاختبار إلى 968 ', 'Test message sent to 968 ') . $phone . '.');
|
||||
$respond(true, 'success', tr('تم إرسال رسالة الاختبار إلى 968 ', 'Test message sent to 968 ') . $phone . '.', $redirectBack());
|
||||
} else {
|
||||
$status = isset($result['status']) ? (' (' . (int) $result['status'] . ')') : '';
|
||||
set_flash('danger', tr('فشل إرسال رسالة الاختبار.', 'Failed to send the test message.') . ' ' . (string) ($result['error'] ?? ('Wablas error' . $status)));
|
||||
$respond(false, 'danger', tr('فشل إرسال رسالة الاختبار.', 'Failed to send the test message.') . ' ' . (string) ($result['error'] ?? ('Wablas error' . $status)), $redirectBack());
|
||||
}
|
||||
|
||||
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '../index.php'));
|
||||
exit;
|
||||
$respond(false, 'danger', tr('فشل إرسال رسالة الاختبار.', 'Failed to send the test message.'), $redirectBack());
|
||||
|
||||
112
includes/app.php
112
includes/app.php
@ -44,6 +44,7 @@ try {
|
||||
$pdo->exec("UPDATE sales_orders SET due_amount = GREATEST(total_amount - paid_amount, 0)");
|
||||
$pdo->exec("UPDATE sales_orders SET payment_status = CASE WHEN due_amount <= 0.0005 THEN 'paid' WHEN paid_amount > 0 THEN 'partial' ELSE 'unpaid' END");
|
||||
$pdo->exec("INSERT IGNORE INTO settings (setting_key, setting_value) VALUES
|
||||
('wablas_api_url', 'https://wablas.com/api/send-message'),
|
||||
('wablas_invoice_recipients', ''),
|
||||
('wablas_report_recipients', ''),
|
||||
('wablas_template_invoice', ''),
|
||||
@ -1006,6 +1007,25 @@ function wablas_is_configured(bool $requireEnabled = true): bool
|
||||
&& (!$requireEnabled || wablas_is_enabled());
|
||||
}
|
||||
|
||||
function wablas_normalize_api_url(?string $value = null): string
|
||||
{
|
||||
$url = trim((string) ($value ?? get_setting('wablas_api_url', '')));
|
||||
if ($url === '') {
|
||||
return 'https://wablas.com/api/send-message';
|
||||
}
|
||||
|
||||
if (!preg_match('#^https?://#i', $url)) {
|
||||
$url = 'https://' . ltrim($url, '/');
|
||||
}
|
||||
|
||||
$url = rtrim($url, '/');
|
||||
if (!preg_match('#/api/(v2/)?send-message$#i', $url)) {
|
||||
$url .= '/api/send-message';
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
function wablas_order_status_label(string $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
@ -1502,6 +1522,7 @@ function wablas_send_message(string $phone, string $message, array $options = []
|
||||
$token = trim((string) ($options['token'] ?? get_setting('wablas_token', '')));
|
||||
$secretKey = trim((string) ($options['secret_key'] ?? get_setting('wablas_secret_key', '')));
|
||||
$ignoreEnabled = !empty($options['ignore_enabled']);
|
||||
$apiUrl = wablas_normalize_api_url($options['api_url'] ?? null);
|
||||
$message = trim($message);
|
||||
|
||||
if ($localPhone === '') {
|
||||
@ -1517,7 +1538,7 @@ function wablas_send_message(string $phone, string $message, array $options = []
|
||||
return ['success' => false, 'error' => 'Wablas is not configured'];
|
||||
}
|
||||
|
||||
$endpoint = 'https://wablas.com/api/send-message';
|
||||
$endpoint = $apiUrl;
|
||||
$payload = http_build_query([
|
||||
'phone' => '968' . $localPhone,
|
||||
'message' => $message,
|
||||
@ -1567,12 +1588,23 @@ function wablas_send_message(string $phone, string $message, array $options = []
|
||||
$decoded = json_decode((string) $responseBody, true);
|
||||
$success = $httpCode >= 200 && $httpCode < 300;
|
||||
|
||||
$error = '';
|
||||
if (!$success) {
|
||||
$error = (string) ($decoded['message'] ?? $decoded['data']['message'] ?? '');
|
||||
if ($error === '') {
|
||||
$error = 'Wablas request failed';
|
||||
}
|
||||
$error .= ' | URL: ' . $endpoint;
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => $success,
|
||||
'status' => $httpCode,
|
||||
'data' => $decoded,
|
||||
'raw' => $responseBody,
|
||||
'phone' => '968' . $localPhone,
|
||||
'endpoint' => $endpoint,
|
||||
'error' => $error,
|
||||
];
|
||||
}
|
||||
|
||||
@ -1656,20 +1688,26 @@ function create_sale(array $data): int
|
||||
{
|
||||
ensure_sales_table();
|
||||
|
||||
db()->beginTransaction();
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$stmt = db()->prepare('INSERT INTO sales_orders
|
||||
$receiptNo = isset($data['receipt_no']) && trim((string) $data['receipt_no']) !== ''
|
||||
? trim((string) $data['receipt_no'])
|
||||
: next_receipt_code($pdo);
|
||||
|
||||
$stmt = $pdo->prepare('INSERT INTO sales_orders
|
||||
(receipt_no, sale_mode, branch_code, cashier_username, cashier_name, role_name, customer_id, customer_name, payment_method, payment_status, items_json, item_count, subtotal, vat_amount, total_amount, paid_amount, due_amount, status, notes, sale_date)
|
||||
VALUES
|
||||
(:receipt_no, :sale_mode, :branch_code, :cashier_username, :cashier_name, :role_name, :customer_id, :customer_name, :payment_method, :payment_status, :items_json, :item_count, :subtotal, :vat_amount, :total_amount, :paid_amount, :due_amount, :status, :notes, NOW())');
|
||||
|
||||
$stmt->bindValue(':receipt_no', $data['receipt_no']);
|
||||
$stmt->bindValue(':receipt_no', $receiptNo);
|
||||
$stmt->bindValue(':sale_mode', $data['sale_mode']);
|
||||
$stmt->bindValue(':branch_code', $data['branch_code']);
|
||||
$stmt->bindValue(':cashier_username', $data['cashier_username']);
|
||||
$stmt->bindValue(':cashier_name', $data['cashier_name']);
|
||||
$stmt->bindValue(':role_name', $data['role_name']);
|
||||
$stmt->bindValue(':customer_id', $data['customer_id'] ?? null, PDO::PARAM_INT);
|
||||
$customerId = isset($data['customer_id']) && $data['customer_id'] !== '' ? (int) $data['customer_id'] : null;
|
||||
$stmt->bindValue(':customer_id', $customerId, $customerId === null ? PDO::PARAM_NULL : PDO::PARAM_INT);
|
||||
$stmt->bindValue(':customer_name', $data['customer_name']);
|
||||
$stmt->bindValue(':payment_method', $data['payment_method']);
|
||||
$stmt->bindValue(':payment_status', $data['payment_status'] ?? 'paid');
|
||||
@ -1684,14 +1722,14 @@ function create_sale(array $data): int
|
||||
$stmt->bindValue(':notes', $data['notes']);
|
||||
$stmt->execute();
|
||||
|
||||
$saleId = (int) db()->lastInsertId();
|
||||
$saleId = (int) $pdo->lastInsertId();
|
||||
sync_order_stock_reservation([], 'completed', $data['items'] ?? [], (string) ($data['status'] ?? 'completed'));
|
||||
|
||||
db()->commit();
|
||||
$pdo->commit();
|
||||
return $saleId;
|
||||
} catch (Throwable $e) {
|
||||
if (db()->inTransaction()) {
|
||||
db()->rollBack();
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
@ -1988,7 +2026,63 @@ function purchase_pipeline(): array
|
||||
];
|
||||
}
|
||||
|
||||
function next_receipt_code(PDO $pdo): string
|
||||
{
|
||||
$settingKey = 'invoice_sequence_next';
|
||||
|
||||
$seedStmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (:key, '1') ON DUPLICATE KEY UPDATE setting_key = VALUES(setting_key)");
|
||||
$seedStmt->bindValue(':key', $settingKey);
|
||||
$seedStmt->execute();
|
||||
|
||||
$selectStmt = $pdo->prepare("SELECT setting_value FROM settings WHERE setting_key = :key FOR UPDATE");
|
||||
$selectStmt->bindValue(':key', $settingKey);
|
||||
$selectStmt->execute();
|
||||
|
||||
$nextNumber = max(1, (int) $selectStmt->fetchColumn());
|
||||
$existsStmt = $pdo->prepare('SELECT 1 FROM sales_orders WHERE receipt_no = :receipt_no LIMIT 1');
|
||||
|
||||
while (true) {
|
||||
$candidate = (string) $nextNumber;
|
||||
$existsStmt->bindValue(':receipt_no', $candidate);
|
||||
$existsStmt->execute();
|
||||
if (!$existsStmt->fetchColumn()) {
|
||||
break;
|
||||
}
|
||||
$nextNumber++;
|
||||
}
|
||||
|
||||
$updateStmt = $pdo->prepare("UPDATE settings SET setting_value = :next_value WHERE setting_key = :key");
|
||||
$updateStmt->bindValue(':next_value', (string) ($nextNumber + 1));
|
||||
$updateStmt->bindValue(':key', $settingKey);
|
||||
$updateStmt->execute();
|
||||
|
||||
return (string) $nextNumber;
|
||||
}
|
||||
|
||||
function receipt_code(): string
|
||||
{
|
||||
$pdo = db();
|
||||
$ownsTransaction = !$pdo->inTransaction();
|
||||
|
||||
if ($ownsTransaction) {
|
||||
$pdo->beginTransaction();
|
||||
}
|
||||
|
||||
try {
|
||||
$receiptNo = next_receipt_code($pdo);
|
||||
if ($ownsTransaction && $pdo->inTransaction()) {
|
||||
$pdo->commit();
|
||||
}
|
||||
return $receiptNo;
|
||||
} catch (Throwable $e) {
|
||||
if ($ownsTransaction && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
function purchase_reference_code(): string
|
||||
{
|
||||
return (string) random_int(100000, 999999);
|
||||
}
|
||||
|
||||
@ -360,7 +360,8 @@
|
||||
<div class="modal fade" id="wablasSettingsModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-fullscreen">
|
||||
<div class="modal-content border-0 rounded-0">
|
||||
<form action="api/settings.php" method="POST">
|
||||
<form action="api/settings.php" method="POST" id="wablasSettingsForm">
|
||||
<input type="hidden" name="return_modal" value="wablas">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<h5 class="modal-title mb-1"><?= h(tr('إعدادات واتساب', 'WhatsApp Settings')) ?></h5>
|
||||
@ -369,6 +370,7 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert d-none" id="wablasSettingsAlert" role="alert"></div>
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="border rounded-4 px-3 py-3 bg-body-tertiary">
|
||||
@ -394,6 +396,11 @@
|
||||
<label class="form-label mb-1"><?= h(tr('Wablas Secret Key', 'Wablas Secret Key')) ?></label>
|
||||
<input type="password" class="form-control" name="wablas_secret_key" value="<?= h(get_setting('wablas_secret_key')) ?>">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1"><?= h(tr('رابط بوابة Wablas', 'Wablas Gateway URL')) ?></label>
|
||||
<input type="text" class="form-control" name="wablas_api_url" value="<?= h(get_setting('wablas_api_url', 'https://wablas.com/api/send-message')) ?>" placeholder="https://wablas.com/api/send-message">
|
||||
<div class="small text-muted mt-1"><?= h(tr('إذا كان حسابك مربوطاً بسيرفر محدد مثل tegal.wablas.com أو texas.wablas.com فاكتب رابط ذلك السيرفر هنا. يمكنك إدخال الدومين فقط أو رابط /api/send-message الكامل.', 'If your account is tied to a specific server like tegal.wablas.com or texas.wablas.com, enter that server here. You can paste either the domain or the full /api/send-message URL.')) ?></div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label mb-1"><?= h(tr('رقم الاختبار', 'Test Phone')) ?></label>
|
||||
@ -521,8 +528,28 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var settingsModalEl = document.getElementById('settingsModal');
|
||||
var wablasModalEl = document.getElementById('wablasSettingsModal');
|
||||
var wablasForm = document.getElementById('wablasSettingsForm');
|
||||
var wablasAlertEl = document.getElementById('wablasSettingsAlert');
|
||||
var reopenSettingsAfterWablas = false;
|
||||
|
||||
var showWablasAlert = function (type, message) {
|
||||
if (!wablasAlertEl) {
|
||||
return;
|
||||
}
|
||||
var alertClass = type === 'success' ? 'alert-success' : (type === 'warning' ? 'alert-warning' : 'alert-danger');
|
||||
wablasAlertEl.className = 'alert ' + alertClass;
|
||||
wablasAlertEl.textContent = message || '';
|
||||
wablasAlertEl.classList.remove('d-none');
|
||||
};
|
||||
|
||||
var clearWablasAlert = function () {
|
||||
if (!wablasAlertEl) {
|
||||
return;
|
||||
}
|
||||
wablasAlertEl.className = 'alert d-none';
|
||||
wablasAlertEl.textContent = '';
|
||||
};
|
||||
|
||||
document.querySelectorAll('[data-open-wablas-settings]').forEach(function (trigger) {
|
||||
trigger.addEventListener('click', function () {
|
||||
if (!wablasModalEl || typeof bootstrap === 'undefined') {
|
||||
@ -546,6 +573,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
if (settingsModalEl && wablasModalEl && typeof bootstrap !== 'undefined') {
|
||||
wablasModalEl.addEventListener('hidden.bs.modal', function () {
|
||||
clearWablasAlert();
|
||||
if (!reopenSettingsAfterWablas) {
|
||||
return;
|
||||
}
|
||||
@ -554,6 +582,74 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
}
|
||||
|
||||
if (wablasModalEl && typeof bootstrap !== 'undefined') {
|
||||
var wablasUrl = new URL(window.location.href);
|
||||
if (wablasUrl.searchParams.get('open_modal') === 'wablas') {
|
||||
bootstrap.Modal.getOrCreateInstance(wablasModalEl).show();
|
||||
wablasUrl.searchParams.delete('open_modal');
|
||||
if (window.history && typeof window.history.replaceState === 'function') {
|
||||
window.history.replaceState({}, document.title, wablasUrl.pathname + (wablasUrl.search ? '?' + wablasUrl.searchParams.toString() : '') + wablasUrl.hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wablasForm && window.fetch) {
|
||||
wablasForm.addEventListener('submit', function (event) {
|
||||
var submitter = event.submitter;
|
||||
if (!submitter) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
clearWablasAlert();
|
||||
|
||||
var action = submitter.getAttribute('formaction') || wablasForm.getAttribute('action') || window.location.href;
|
||||
var method = submitter.getAttribute('formmethod') || wablasForm.getAttribute('method') || 'POST';
|
||||
var formData = new FormData(wablasForm, submitter);
|
||||
var originalHtml = submitter.innerHTML;
|
||||
|
||||
submitter.disabled = true;
|
||||
submitter.setAttribute('aria-busy', 'true');
|
||||
submitter.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span><?= h(tr('جارٍ الحفظ...', 'Saving...')) ?>';
|
||||
|
||||
fetch(action, {
|
||||
method: method.toUpperCase(),
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
}).then(function (response) {
|
||||
return response.json().catch(function () {
|
||||
return { success: false, type: 'danger', message: 'Unexpected response.' };
|
||||
});
|
||||
}).then(function (data) {
|
||||
var type = data && data.type ? data.type : (data && data.success ? 'success' : 'danger');
|
||||
var message = data && data.message ? data.message : 'Request finished.';
|
||||
showWablasAlert(type, message);
|
||||
|
||||
if (typeof Swal !== 'undefined') {
|
||||
Swal.fire({
|
||||
icon: type === 'success' ? 'success' : (type === 'warning' ? 'warning' : 'error'),
|
||||
title: message,
|
||||
toast: true,
|
||||
position: 'top-end',
|
||||
showConfirmButton: false,
|
||||
timer: 3000,
|
||||
timerProgressBar: true
|
||||
});
|
||||
}
|
||||
}).catch(function () {
|
||||
showWablasAlert('danger', '<?= h(tr('تعذر حفظ الإعدادات حالياً. حاول مرة أخرى.', 'Could not save the settings right now. Please try again.')) ?>');
|
||||
}).finally(function () {
|
||||
submitter.disabled = false;
|
||||
submitter.removeAttribute('aria-busy');
|
||||
submitter.innerHTML = originalHtml;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-wablas-preview-source]').forEach(function (field) {
|
||||
var key = field.getAttribute('data-wablas-preview-source');
|
||||
var preview = document.querySelector('[data-wablas-preview="' + key + '"]');
|
||||
|
||||
@ -63,7 +63,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
} else {
|
||||
$cashierName = current_lang() === 'ar' ? $user['name_ar'] : $user['name_en'];
|
||||
$purchaseId = create_purchase([
|
||||
'reference_no' => receipt_code(),
|
||||
'reference_no' => purchase_reference_code(),
|
||||
'branch_code' => $branchCode,
|
||||
'user_username' => $user['username'],
|
||||
'user_name' => $cashierName,
|
||||
|
||||
@ -100,7 +100,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($error === '') {
|
||||
$cashierName = current_lang() === 'ar' ? $user['name_ar'] : $user['name_en'];
|
||||
$saleId = create_sale([
|
||||
'receipt_no' => receipt_code(),
|
||||
'sale_mode' => $saleMode,
|
||||
'branch_code' => $branchCode,
|
||||
'cashier_username' => $user['username'],
|
||||
|
||||
20
pos.php
20
pos.php
@ -80,7 +80,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($error === '') {
|
||||
$cashierName = current_lang() === 'ar' ? $user['name_ar'] : $user['name_en'];
|
||||
$saleId = create_sale([
|
||||
'receipt_no' => receipt_code(),
|
||||
'sale_mode' => $saleMode,
|
||||
'branch_code' => $branchCode,
|
||||
'cashier_username' => $user['username'],
|
||||
@ -954,7 +953,7 @@ function getCartTotals() {
|
||||
}
|
||||
|
||||
function updateModalDueHint() {
|
||||
const total = getCartTotals().total;
|
||||
const total = Number(getCartTotals().total.toFixed(3));
|
||||
const paidField = document.getElementById('modalPaidAmount');
|
||||
const dueHint = document.getElementById('modalDueHint');
|
||||
if (!paidField || !dueHint) {
|
||||
@ -967,14 +966,12 @@ function updateModalDueHint() {
|
||||
|
||||
function openPaymentModal() {
|
||||
if (Object.keys(cart).length === 0) return;
|
||||
const total = getCartTotals().total;
|
||||
const total = Number(getCartTotals().total.toFixed(3));
|
||||
const paidField = document.getElementById('modalPaidAmount');
|
||||
document.getElementById('modalTotalAmount').innerText = total.toFixed(3) + ' ' + currencyLabel;
|
||||
if (paidField) {
|
||||
if (paidField.value === '') {
|
||||
paidField.value = total.toFixed(3);
|
||||
}
|
||||
paidField.dataset.manual = paidField.value !== '' ? '1' : '0';
|
||||
paidField.value = total.toFixed(3);
|
||||
paidField.dataset.manual = '0';
|
||||
}
|
||||
updateModalDueHint();
|
||||
paymentModalObj.show();
|
||||
@ -985,6 +982,7 @@ function submitSale(method) {
|
||||
const customer = document.getElementById('posCustomer').value;
|
||||
const customerId = document.getElementById('posCustomer').dataset.id || '';
|
||||
const totals = getCartTotals();
|
||||
const totalAmount = Number(totals.total.toFixed(3));
|
||||
const paidField = document.getElementById('modalPaidAmount');
|
||||
let paidAmount = Math.max(0, parseFloat(paidField?.value || '0') || 0);
|
||||
const manual = paidField?.dataset.manual === '1';
|
||||
@ -992,14 +990,16 @@ function submitSale(method) {
|
||||
if (method === 'pay_later' && !manual) {
|
||||
paidAmount = 0;
|
||||
} else if (method !== 'pay_later' && !manual) {
|
||||
paidAmount = totals.total;
|
||||
paidAmount = totalAmount;
|
||||
}
|
||||
|
||||
if (paidAmount > totals.total + 0.0005) {
|
||||
paidAmount = Number(Math.min(totalAmount, Math.max(0, paidAmount)).toFixed(3));
|
||||
|
||||
if (paidAmount > totalAmount + 0.0005) {
|
||||
Swal.fire({icon: 'warning', text: '<?= h(tr('المبلغ المدفوع لا يمكن أن يتجاوز إجمالي الفاتورة.', 'Paid amount cannot exceed the invoice total.')) ?>'});
|
||||
return;
|
||||
}
|
||||
if (paidAmount < totals.total && !customerId) {
|
||||
if (paidAmount + 0.0005 < totalAmount && !customerId) {
|
||||
Swal.fire({icon: 'warning', text: '<?= h(tr('يجب اختيار عميل مسجل عند وجود مبلغ متبقٍ.', 'Select a registered customer when there is a remaining balance.')) ?>'});
|
||||
return;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user