Autosave: 20260422-182218

This commit is contained in:
Flatlogic Bot 2026-04-22 18:22:05 +00:00
parent 5bfaa401f2
commit b2cb1b5a0b
7 changed files with 276 additions and 53 deletions

View File

@ -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());
}

View File

@ -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());

View File

@ -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);
}

View File

@ -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 + '"]');

View File

@ -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,

View File

@ -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
View File

@ -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;
}