diff --git a/assets/css/custom.css b/assets/css/custom.css
index 1cece62..118ef2f 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -504,8 +504,10 @@ body:not(.theme-default) .form-select:focus {
/* Thermal Receipt Styles */
.thermal-receipt {
width: 80mm;
+ max-width: 100%;
margin: 0 auto;
padding: 10px;
+ box-sizing: border-box;
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
line-height: 1.4;
@@ -527,11 +529,14 @@ body:not(.theme-default) .form-select:focus {
}
.thermal-receipt table {
width: 100%;
+ table-layout: fixed;
+ border-collapse: collapse;
}
.thermal-receipt table th {
text-align: left;
border-bottom: 1px dashed #000;
font-size: 10px;
+ word-break: break-word;
}
.thermal-receipt.rtl table th {
text-align: right;
@@ -539,6 +544,7 @@ body:not(.theme-default) .form-select:focus {
.thermal-receipt table td {
padding: 5px 0;
font-size: 11px;
+ word-break: break-word;
}
.thermal-receipt .total-row {
font-weight: bold;
@@ -547,9 +553,11 @@ body:not(.theme-default) .form-select:focus {
@media print {
.thermal-receipt-print {
- width: 80mm !important;
- margin: 0 !important;
- padding: 5mm !important;
+ width: 76mm !important;
+ max-width: 76mm !important;
+ margin: 0 auto !important;
+ padding: 2mm !important;
+ box-sizing: border-box !important;
}
body.printing-receipt * {
visibility: hidden;
@@ -560,12 +568,21 @@ body:not(.theme-default) .form-select:focus {
}
body.printing-receipt #posPrintArea {
visibility: visible !important;
- display: block !important;
+ display: flex !important;
+ justify-content: center !important;
+ align-items: flex-start !important;
+ position: fixed !important;
+ inset: 0 !important;
+ width: 100% !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ z-index: 9999 !important;
+ background: #fff !important;
}
body.printing-receipt .thermal-receipt-print {
- position: absolute;
- left: 0;
- top: 0;
+ position: static !important;
+ left: auto !important;
+ top: auto !important;
display: block !important;
}
}
diff --git a/includes/quantity_helper.php b/includes/quantity_helper.php
new file mode 100644
index 0000000..a4fc165
--- /dev/null
+++ b/includes/quantity_helper.php
@@ -0,0 +1,34 @@
+prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
- $stmt->execute([$qty, $item_id]);
+ $stmt->execute([$normalizedQty, $item_id]);
}
}
diff --git a/index.php b/index.php
index 47d3759..66cdf7a 100644
--- a/index.php
+++ b/index.php
@@ -467,6 +467,7 @@ if (!function_exists('register_wablas_helper_fallback')) {
runtime_debug_boot_mark('boot:loading_core_dependencies');
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/SimpleXLSX.php';
+require_once __DIR__ . '/includes/quantity_helper.php';
require_once __DIR__ . '/includes/stock_helper.php';
$wablasHelperPath = __DIR__ . '/includes/wablas_helper.php';
if (is_file($wablasHelperPath)) {
@@ -1860,7 +1861,7 @@ if (isset($_GET['action']) || isset($_POST['action'])) {
// Render Card HTML
?>
-
+
![<?= htmlspecialchars($p['name_en']) ?>](<?= htmlspecialchars($p['image_path']) ?>)
@@ -1883,7 +1884,7 @@ if (isset($_GET['action']) || isset($_POST['action'])) {
OMR = number_format((float)$p['sale_price'], 3) ?>
-
= (float)$p['stock_quantity'] ?> left
+
= format_quantity($p['stock_quantity']) ?> left
'This item cannot use price-based scale barcodes because its sale price is zero.']);
exit;
}
- $qty = round(((float)$weightBarcode['value']) / (float)$p['sale_price'], 3);
+ $qty = normalize_quantity(((float)$weightBarcode['value']) / (float)$p['sale_price']);
} else {
- $qty = round((float)$weightBarcode['value'], 3);
+ $qty = normalize_quantity((float)$weightBarcode['value']);
}
if ($qty <= 0) {
@@ -2003,6 +2004,12 @@ if (isset($_GET['action']) || isset($_POST['action'])) {
$customer_id = !empty($_POST['customer_id']) ? (int)$_POST['customer_id'] : null;
$payments = json_decode($_POST['payments'] ?? '[]', true);
$items = json_decode($_POST['items'] ?? '[]', true);
+ if (!is_array($items)) {
+ $items = [];
+ }
+ foreach ($items as $itemIndex => $item) {
+ $items[$itemIndex]['qty'] = normalize_quantity($item['qty'] ?? 0);
+ }
$total_amount = (float)($_POST['total_amount'] ?? 0);
$tax_amount = (float)($_POST['tax_amount'] ?? 0);
$discount_code_id = !empty($_POST['discount_code_id']) ? (int)$_POST['discount_code_id'] : null;
@@ -2833,8 +2840,8 @@ function getPromotionalPrice($item) {
}
$sale_price = (float)($_POST['sale_price'] ?? 0);
$purchase_price = (float)($_POST['purchase_price'] ?? 0);
- $stock_quantity = (float)($_POST['stock_quantity'] ?? 0);
- $min_stock_level = (float)($_POST['min_stock_level'] ?? 0);
+ $stock_quantity = normalize_quantity($_POST['stock_quantity'] ?? 0);
+ $min_stock_level = normalize_quantity($_POST['min_stock_level'] ?? 0);
$vat_rate = (float)($_POST['vat_rate'] ?? 0);
$expiry_date = !empty($_POST['expiry_date']) ? $_POST['expiry_date'] : null;
$is_promotion = isset($_POST['is_promotion']) ? 1 : 0;
@@ -2869,8 +2876,8 @@ function getPromotionalPrice($item) {
}
$sale_price = (float)($_POST['sale_price'] ?? 0);
$purchase_price = (float)($_POST['purchase_price'] ?? 0);
- $stock_quantity = (float)($_POST['stock_quantity'] ?? 0);
- $min_stock_level = (float)($_POST['min_stock_level'] ?? 0);
+ $stock_quantity = normalize_quantity($_POST['stock_quantity'] ?? 0);
+ $min_stock_level = normalize_quantity($_POST['min_stock_level'] ?? 0);
$vat_rate = (float)($_POST['vat_rate'] ?? 0);
$expiry_date = !empty($_POST['expiry_date']) ? $_POST['expiry_date'] : null;
$is_promotion = isset($_POST['is_promotion']) ? 1 : 0;
@@ -3014,7 +3021,7 @@ function getPromotionalPrice($item) {
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
- $qty = (float)$qtys[$i];
+ $qty = normalize_quantity($qtys[$i] ?? 0);
$price = (float)$prices[$i];
$subtotal = $qty * $price;
@@ -3045,7 +3052,7 @@ function getPromotionalPrice($item) {
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
- $qty = (float)$qtys[$i];
+ $qty = normalize_quantity($qtys[$i] ?? 0);
$price = (float)$prices[$i];
$subtotal = $qty * $price;
@@ -3080,7 +3087,7 @@ function getPromotionalPrice($item) {
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
- $qty = (float)$qtys[$i];
+ $qty = normalize_quantity($qtys[$i] ?? 0);
$price = (float)$prices[$i];
$subtotal = $qty * $price;
@@ -3103,7 +3110,7 @@ function getPromotionalPrice($item) {
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
- $qty = (float)$qtys[$i];
+ $qty = normalize_quantity($qtys[$i] ?? 0);
$price = (float)$prices[$i];
$subtotal = $qty * $price;
@@ -3145,7 +3152,7 @@ function getPromotionalPrice($item) {
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
- $qty = (float)$qtys[$i];
+ $qty = normalize_quantity($qtys[$i] ?? 0);
$price = (float)$prices[$i];
$subtotal = $qty * $price;
@@ -3177,7 +3184,7 @@ function getPromotionalPrice($item) {
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
- $qty = (float)$qtys[$i];
+ $qty = normalize_quantity($qtys[$i] ?? 0);
$price = (float)$prices[$i];
$subtotal = $qty * $price;
@@ -3213,7 +3220,7 @@ function getPromotionalPrice($item) {
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
- $qty = (float)$qtys[$i];
+ $qty = normalize_quantity($qtys[$i] ?? 0);
$price = (float)$prices[$i];
$subtotal = $qty * $price;
@@ -3235,7 +3242,7 @@ function getPromotionalPrice($item) {
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
- $qty = (float)$qtys[$i];
+ $qty = normalize_quantity($qtys[$i] ?? 0);
$price = (float)$prices[$i];
$subtotal = $qty * $price;
@@ -3303,7 +3310,7 @@ function getPromotionalPrice($item) {
$name_ar = trim((string)($row[2] ?? ''));
$sale_price = (float)($row[3] ?? 0);
$purchase_price = (float)($row[4] ?? 0);
- $qty = (float)($row[5] ?? 0);
+ $qty = normalize_quantity($row[5] ?? 0);
$vat_rate = (float)($row[6] ?? 0);
$check = db()->prepare("SELECT id FROM stock_items WHERE sku = ? AND outlet_id = ?");
@@ -3580,7 +3587,7 @@ function getPromotionalPrice($item) {
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
- $qty = (float)$qtys[$i];
+ $qty = normalize_quantity($qtys[$i] ?? 0);
$price = (float)$prices[$i];
$subtotal = $qty * $price;
@@ -3612,7 +3619,7 @@ function getPromotionalPrice($item) {
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
- $qty = (float)$qtys[$i];
+ $qty = normalize_quantity($qtys[$i] ?? 0);
$price = (float)$prices[$i];
$subtotal = $qty * $price;
@@ -3651,7 +3658,7 @@ function getPromotionalPrice($item) {
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
- $qty = (float)$qtys[$i];
+ $qty = normalize_quantity($qtys[$i] ?? 0);
$price = (float)$prices[$i];
$subtotal = $qty * $price;
@@ -3673,7 +3680,7 @@ function getPromotionalPrice($item) {
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
- $qty = (float)$qtys[$i];
+ $qty = normalize_quantity($qtys[$i] ?? 0);
$price = (float)$prices[$i];
$subtotal = $qty * $price;
@@ -3889,7 +3896,7 @@ if (isset($_POST['add_hr_department'])) {
$total_return = 0;
foreach ($quantities as $i => $qty) {
- $total_return += (float)$qty * (float)$prices[$i];
+ $total_return += normalize_quantity($qty) * (float)$prices[$i];
}
// Insert Sales Return
@@ -3911,7 +3918,7 @@ if (isset($_POST['add_hr_department'])) {
// $stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
foreach ($item_ids as $i => $item_id) {
- $qty = (float)$quantities[$i];
+ $qty = normalize_quantity($quantities[$i] ?? 0);
if ($qty > 0) {
$price = (float)$prices[$i];
$line_total = $qty * $price;
@@ -3949,7 +3956,7 @@ if (isset($_POST['add_hr_department'])) {
$total_return = 0;
foreach ($quantities as $i => $qty) {
- $total_return += (float)$qty * (float)$prices[$i];
+ $total_return += normalize_quantity($qty) * (float)$prices[$i];
}
// Insert Purchase Return
@@ -3971,7 +3978,7 @@ if (isset($_POST['add_hr_department'])) {
// $stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
foreach ($item_ids as $i => $item_id) {
- $qty = (float)$quantities[$i];
+ $qty = normalize_quantity($quantities[$i] ?? 0);
if ($qty > 0) {
$price = (float)$prices[$i];
$line_total = $qty * $price;
@@ -7333,8 +7340,8 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
- = number_format((float)$item['stock_quantity'], 3) ?>
- Min: = number_format((float)$item['min_stock_level'], 3) ?>
+ = format_quantity($item['stock_quantity']) ?>
+ Min: = format_quantity($item['min_stock_level']) ?>
Low Stock
@@ -7385,7 +7392,7 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
| Category | = htmlspecialchars($item['cat_en'] ?? '---') ?> |
| Supplier | = htmlspecialchars($item['supplier_name'] ?? '---') ?> |
| Sale Price | OMR = number_format((float)$item['sale_price'], 3) ?> |
|---|
- | Stock Level | = number_format((float)$item['stock_quantity'], 3) ?> |
|---|
+ | Stock Level | = format_quantity($item['stock_quantity']) ?> |
| VAT Rate | = number_format((float)$item['vat_rate'], 2) ?>% |
@@ -7420,8 +7427,8 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
-
-
+
+
Promotion Details
@@ -7498,7 +7505,7 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
= htmlspecialchars($item['name_ar']) ?>
|
= htmlspecialchars($item['cat_en'] ?? '---') ?> |
- = number_format((float)$item['stock_quantity'], 3) ?> |
+ = format_quantity($item['stock_quantity']) ?> |
= $expiry_date !== '' ? htmlspecialchars((string)$expiry_date) : '---' ?> |
@@ -7555,10 +7562,10 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
|
= htmlspecialchars($item['cat_en'] ?? '---') ?> |
= htmlspecialchars($item['supplier_name'] ?? '---') ?> |
- = number_format((float)$item['min_stock_level'], 2) ?> |
+ = format_quantity($item['min_stock_level']) ?> |
- = number_format((float)$item['stock_quantity'], 3) ?>
+ = format_quantity($item['stock_quantity']) ?>
|
= number_format((float)$shortage, 3) ?> |
@@ -7697,7 +7704,7 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
-
+
![<?= htmlspecialchars($p['name_en']) ?>](<?= htmlspecialchars($p['image_path']) ?>)
@@ -7720,7 +7727,7 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
OMR = number_format((float)$p['sale_price'], 3) ?>
-
= (float)$p['stock_quantity'] ?> left
+
= format_quantity($p['stock_quantity']) ?> left
@@ -7746,7 +7753,7 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
- ${qty}
+ ${formatQuantity(qty)}
@@ -8328,10 +8335,10 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
renderPayments() {
const container = document.getElementById('paymentList');
const methodLabels = {
- 'cash': 'Cash',
- 'card': 'Credit Card',
- 'credit': 'Credit',
- 'transfer': 'Bank Transfer'
+ 'cash': = json_encode($lang === 'ar' ? 'نقدًا' : 'Cash', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
+ 'card': = json_encode($lang === 'ar' ? 'بطاقة ائتمان' : 'Credit Card', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
+ 'credit': = json_encode($lang === 'ar' ? 'آجل' : 'Credit', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
+ 'transfer': = json_encode($lang === 'ar' ? 'تحويل بنكي' : 'Bank Transfer', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>
};
container.innerHTML = this.payments.map((p, i) => `
@@ -8381,7 +8388,7 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
},
async completeOrder() {
if (this.items.length === 0) {
- Swal.fire('Error', 'Cart is empty', 'error');
+ Swal.fire(= json_encode($lang === 'ar' ? 'خطأ' : 'Error', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, = json_encode($lang === 'ar' ? 'السلة فارغة' : 'Cart is empty', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, 'error');
return;
}
@@ -8404,20 +8411,20 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
const remaining = this.getRemaining();
if (remaining > 0.001) {
- Swal.fire('Error', 'Payment is incomplete', 'error');
+ Swal.fire(= json_encode($lang === 'ar' ? 'خطأ' : 'Error', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, = json_encode($lang === 'ar' ? 'الدفع غير مكتمل' : 'Payment is incomplete', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, 'error');
return;
}
const customerId = document.getElementById('posCustomer').value;
if (this.payments.some(p => p.method === 'credit') && !customerId) {
- Swal.fire('Error', 'Credit payment is only allowed for registered customers', 'error');
+ Swal.fire(= json_encode($lang === 'ar' ? 'خطأ' : 'Error', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, = json_encode($lang === 'ar' ? 'الدفع الآجل مسموح للعملاء المسجلين فقط' : 'Credit payment is only allowed for registered customers', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, 'error');
return;
}
const btn = document.getElementById('confirmPaymentBtn');
const originalText = btn.innerText;
btn.disabled = true;
- btn.innerText = 'PROCESSING...';
+ btn.innerText = = json_encode($lang === 'ar' ? 'جارٍ المعالجة...' : 'PROCESSING...', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const subtotal = this.items.reduce((sum, item) => sum + (parseFloat(item.price) * item.qty), 0);
const totalVat = this.items.reduce((sum, item) => {
@@ -8454,7 +8461,7 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
result = JSON.parse(text);
} catch (e) {
console.error('Invalid JSON response:', text);
- throw new Error('Server returned an invalid response');
+ throw new Error(= json_encode($lang === 'ar' ? 'أعاد الخادم استجابة غير صالحة' : 'Server returned an invalid response', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>);
}
if (result.success) {
@@ -8462,13 +8469,13 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
if (payModal) payModal.hide();
this.showReceipt(result.invoice_id, discountAmount, loyaltyRedeemed, result.transaction_no);
} else {
- Swal.fire('Error', result.error, 'error');
+ Swal.fire(= json_encode($lang === 'ar' ? 'خطأ' : 'Error', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, result.error, 'error');
btn.disabled = false;
btn.innerText = originalText;
}
} catch (err) {
console.error(err);
- Swal.fire('Error', err.message || 'Something went wrong', 'error');
+ Swal.fire(= json_encode($lang === 'ar' ? 'خطأ' : 'Error', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, err.message || = json_encode($lang === 'ar' ? 'حدث خطأ ما' : 'Something went wrong', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, 'error');
btn.disabled = false;
btn.innerText = originalText;
}
@@ -8476,7 +8483,7 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
showReceipt(invId, discountAmount, loyaltyRedeemed, transactionNo) {
const container = document.getElementById('posReceiptContent');
const customerSelect = document.getElementById('posCustomer');
- const customerName = (customerSelect && customerSelect.selectedIndex >= 0 && customerSelect.value !== '') ? customerSelect.options[customerSelect.selectedIndex].text : '= $translations['ar']['walk_in_customer'] ?> / = $translations['en']['walk_in_customer'] ?>';
+ const customerName = (customerSelect && customerSelect.selectedIndex >= 0 && customerSelect.value !== '') ? customerSelect.options[customerSelect.selectedIndex].text : = json_encode(__('walk_in_customer'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const paymentsHtml = this.payments.map(p => {
let m = p.method.toLowerCase();
let methodAr = m === 'cash' ? 'نقد' : (m === 'card' ? 'بطاقة ائتمان' : (m === 'credit' ? 'آجل' : (m === 'transfer' ? 'تحويل بنكي' : m)));
@@ -8500,7 +8507,7 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
${item.nameAr || ''}
${item.nameEn}
- ${item.qty} x ${parseFloat(item.price).toFixed(3)}
+ ${formatQuantity(item.qty)} x ${parseFloat(item.price).toFixed(3)}
|
${vatAmount.toFixed(2)} |
${itemTotal.toFixed(3)} |
@@ -8702,11 +8709,11 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);
$(document).ready(function() {
$('#posCustomer').select2({
width: '100%',
- placeholder: 'Select Customer'
+ placeholder: = json_encode($lang === 'ar' ? 'اختر العميل' : 'Select Customer', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>
});
$('#paymentCreditCustomer').select2({
width: '100%',
- placeholder: 'Select Customer',
+ placeholder: = json_encode($lang === 'ar' ? 'اختر العميل' : 'Select Customer', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
dropdownParent: $('#posPaymentModal')
});
@@ -8723,13 +8730,13 @@ runtime_debug_mark('page:rendering', ['page' => (string)$page]);