modifying items add

This commit is contained in:
Flatlogic Bot 2026-02-19 07:02:19 +00:00
parent a16d1400d4
commit a38832e8a0
6 changed files with 486 additions and 169 deletions

View File

@ -365,12 +365,16 @@ body {
padding: 10px;
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
line-height: 1.2;
line-height: 1.4;
background: #fff;
color: #000;
break-inside: avoid;
page-break-inside: avoid;
}
.thermal-receipt.rtl {
direction: rtl;
text-align: right;
}
.thermal-receipt .center {
text-align: center;
}
@ -386,9 +390,12 @@ body {
border-bottom: 1px dashed #000;
font-size: 10px;
}
.thermal-receipt.rtl table th {
text-align: right;
}
.thermal-receipt table td {
padding: 5px 0;
font-size: 10px;
font-size: 11px;
}
.thermal-receipt .total-row {
font-weight: bold;

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -238,24 +238,24 @@ if (empty($slides)) {
<div class="totals-section">
<div class="total-row">
<span>Subtotal</span>
<span id="labelSubtotal">Subtotal</span>
<span id="displaySubtotal">OMR 0.000</span>
</div>
<div id="displayTaxRow" class="total-row text-muted">
<span id="labelVAT">VAT</span>
<span id="displayTax">OMR 0.000</span>
</div>
<div id="displayDiscountRow" class="total-row text-danger" style="display: none;">
<span>Discount</span>
<span id="labelDiscount">Discount</span>
<span id="displayDiscount">- OMR 0.000</span>
</div>
<div id="displayLoyaltyRow" class="total-row text-success" style="display: none;">
<span>Loyalty Redeemed</span>
<span id="labelLoyalty">Loyalty Redeemed</span>
<span id="displayLoyalty">- OMR 0.000</span>
</div>
<div id="displayTaxRow" class="total-row text-muted" style="display: none;">
<small>VAT Included</small>
<small id="displayTax">OMR 0.000</small>
</div>
<div class="grand-total">
<div class="grand-total-label">Total to Pay</div>
<div class="grand-total-label" id="labelTotal">Total to Pay</div>
<div class="grand-total-amount" id="displayTotal">OMR 0.000</div>
</div>
</div>
@ -283,87 +283,40 @@ if (empty($slides)) {
<script>
let lastTimestamp = 0;
let currentCurrency = 'OMR';
function formatMoney(amount) {
return 'OMR ' + parseFloat(amount).toFixed(3);
return currentCurrency + ' ' + parseFloat(amount).toFixed(3);
}
function updateDisplay(data) {
if (!data) return;
lastTimestamp = data.timestamp || 0;
currentCurrency = data.currency || 'OMR';
// Labels translation if Arabic detected in items
const isAr = data.items && data.items.some(i => /[\u0600-\u06FF]/.test(i.name));
if (isAr) {
document.getElementById('labelSubtotal').innerText = 'المجموع (بدون الضريبة) / Subtotal';
document.getElementById('labelVAT').innerText = 'الضريبة / VAT';
document.getElementById('labelDiscount').innerText = 'الخصم / Discount';
document.getElementById('labelLoyalty').innerText = 'الولاء / Loyalty';
document.getElementById('labelTotal').innerText = 'الإجمالي / Total';
} else {
document.getElementById('labelSubtotal').innerText = 'Subtotal';
document.getElementById('labelVAT').innerText = 'VAT';
document.getElementById('labelDiscount').innerText = 'Discount';
document.getElementById('labelLoyalty').innerText = 'Loyalty';
document.getElementById('labelTotal').innerText = 'Total to Pay';
}
// Debug info
const debugEl = document.getElementById('debugInfo');
if (debugEl) {
debugEl.innerText = 'Items: ' + (data.items ? data.items.length : 0);
// debugEl.style.display = 'block'; // Uncomment to show always
}
// Update Theme
if (data.theme) {
document.body.className = data.theme;
}
if (!Array.isArray(data.items) || data.items.length === 0) {
document.getElementById('activeCart').style.display = 'none';
document.getElementById('welcomeScreen').style.display = 'flex';
document.getElementById('customerName').innerText = 'Welcome';
return;
}
document.getElementById('welcomeScreen').style.display = 'none';
document.getElementById('activeCart').style.display = 'flex';
// Update Customer Name
if (data.customerName && data.customerName !== 'Walk-in Customer' && data.customerName !== 'عميل عابر') {
document.getElementById('customerName').innerHTML = '<i class="bi bi-person me-1"></i> ' + data.customerName;
} else {
document.getElementById('customerName').innerText = 'Welcome';
}
// Update Items (Safer rendering)
const itemsList = document.getElementById('itemsList');
if (itemsList) {
itemsList.innerHTML = ''; // Clear existing
data.items.forEach(item => {
try {
const row = document.createElement('div');
row.className = 'cart-item';
const name = item.name || 'Item';
const price = parseFloat(item.price) || 0;
const qty = parseFloat(item.qty) || 0;
const total = price * qty;
row.innerHTML = `
<div>
<div class="item-name"></div>
<div class="item-details">
${qty} x ${formatMoney(price)}
</div>
</div>
<div class="item-price">
${formatMoney(total)}
</div>
`;
// Set text content safely
const nameEl = row.querySelector('.item-name');
if (nameEl) nameEl.textContent = name;
itemsList.appendChild(row);
} catch (err) {
console.error('Error rendering item:', err);
}
});
// Scroll to bottom of list
requestAnimationFrame(() => {
itemsList.scrollTop = itemsList.scrollHeight;
});
}
...
// Update Totals
document.getElementById('displaySubtotal').innerText = formatMoney(data.subtotal || 0);
const vat = parseFloat(data.vat) || 0;
const subtotal = parseFloat(data.subtotal) || 0;
document.getElementById('displaySubtotal').innerText = formatMoney(subtotal - vat);
document.getElementById('displayTax').innerText = formatMoney(vat);
const discount = parseFloat(data.discount) || 0;
if (discount > 0) {

View File

@ -71,6 +71,27 @@ $translations = [
'low_stock_items' => 'Low Stock Items',
'expired_items' => 'Expired Items',
'near_expiry_items' => 'Near Expiry Items',
'tax_invoice' => 'Tax Invoice',
'invoice_no' => 'Invoice No',
'date' => 'Date',
'customer' => 'Customer',
'item' => 'Item',
'qty' => 'Qty',
'price' => 'Price',
'subtotal' => 'Subtotal',
'discount' => 'Discount',
'total' => 'Total',
'loyalty' => 'Loyalty',
'payments' => 'Payments',
'thank_you' => 'Thank you for your business!',
'keep_receipt' => 'Please keep the receipt.',
'tel' => 'Tel',
'vat_no' => 'VAT No',
'walk_in_customer' => 'Walk-in Customer',
'currency' => 'ر.ع / OMR',
'cash' => 'Cash',
'card' => 'Card',
'credit' => 'Credit',
],
'ar' => [
'dashboard' => 'لوحة القيادة',
@ -143,6 +164,27 @@ $translations = [
'low_stock_items' => 'نواقص المخزون',
'expired_items' => 'أصناف منتهية الصلاحية',
'near_expiry_items' => 'أصناف قريبة الانتهاء',
'tax_invoice' => 'فاتورة ضريبية',
'invoice_no' => 'رقم الفاتورة',
'date' => 'التاريخ',
'customer' => 'العميل',
'item' => 'البند',
'qty' => 'الكمية',
'price' => 'السعر',
'subtotal' => 'المجموع الفرعي',
'discount' => 'خصم',
'total' => 'الإجمالي',
'loyalty' => 'الولاء',
'payments' => 'المدفوعات',
'thank_you' => 'شكراً لتعاملكم معنا!',
'keep_receipt' => 'يرجى الاحتفاظ بالإيصال.',
'tel' => 'هاتف',
'vat_no' => 'الرقم الضريبي',
'walk_in_customer' => 'عميل نقدي',
'currency' => 'ر.ع / OMR',
'cash' => 'نقد',
'card' => 'بطاقة',
'credit' => 'آجل',
]
];

483
index.php
View File

@ -485,7 +485,200 @@ function getPromotionalPrice($item) {
return $price;
}
// --- HR Handlers ---
// --- Inventory & Core Handlers ---
if (isset($_POST['add_item'])) {
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
$category_id = (int)$_POST['category_id'] ?: null;
$unit_id = (int)$_POST['unit_id'] ?: null;
$supplier_id = (int)$_POST['supplier_id'] ?: null;
$sku = $_POST['sku'] ?? '';
$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);
$vat_rate = (float)($_POST['vat_rate'] ?? 15.00);
$expiry_date = !empty($_POST['expiry_date']) ? $_POST['expiry_date'] : null;
$is_promotion = isset($_POST['is_promotion']) ? 1 : 0;
$promotion_start = !empty($_POST['promotion_start']) ? $_POST['promotion_start'] : null;
$promotion_end = !empty($_POST['promotion_end']) ? $_POST['promotion_end'] : null;
$promotion_percent = (float)($_POST['promotion_percent'] ?? 0);
$image_path = null;
if (isset($_FILES['image']) && $_FILES['image']['error'] === 0) {
$ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$filename = 'uploads/item_' . time() . '.' . $ext;
if (!is_dir('uploads')) mkdir('uploads', 0777, true);
if (move_uploaded_file($_FILES['image']['tmp_name'], $filename)) $image_path = $filename;
}
$stmt = db()->prepare("INSERT INTO stock_items (name_en, name_ar, category_id, unit_id, supplier_id, sku, sale_price, purchase_price, stock_quantity, min_stock_level, image_path, vat_rate, expiry_date, is_promotion, promotion_start, promotion_end, promotion_percent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$name_en, $name_ar, $category_id, $unit_id, $supplier_id, $sku, $sale_price, $purchase_price, $stock_quantity, $min_stock_level, $image_path, $vat_rate, $expiry_date, $is_promotion, $promotion_start, $promotion_end, $promotion_percent]);
$message = "Item added successfully!";
}
if (isset($_POST['edit_item'])) {
$id = (int)$_POST['id'];
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
$category_id = (int)$_POST['category_id'] ?: null;
$unit_id = (int)$_POST['unit_id'] ?: null;
$supplier_id = (int)$_POST['supplier_id'] ?: null;
$sku = $_POST['sku'] ?? '';
$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);
$vat_rate = (float)($_POST['vat_rate'] ?? 15.00);
$expiry_date = !empty($_POST['expiry_date']) ? $_POST['expiry_date'] : null;
$is_promotion = isset($_POST['is_promotion']) ? 1 : 0;
$promotion_start = !empty($_POST['promotion_start']) ? $_POST['promotion_start'] : null;
$promotion_end = !empty($_POST['promotion_end']) ? $_POST['promotion_end'] : null;
$promotion_percent = (float)($_POST['promotion_percent'] ?? 0);
$stmt = db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, category_id = ?, unit_id = ?, supplier_id = ?, sku = ?, sale_price = ?, purchase_price = ?, stock_quantity = ?, min_stock_level = ?, vat_rate = ?, expiry_date = ?, is_promotion = ?, promotion_start = ?, promotion_end = ?, promotion_percent = ? WHERE id = ?");
$stmt->execute([$name_en, $name_ar, $category_id, $unit_id, $supplier_id, $sku, $sale_price, $purchase_price, $stock_quantity, $min_stock_level, $vat_rate, $expiry_date, $is_promotion, $promotion_start, $promotion_end, $promotion_percent, $id]);
if (isset($_FILES['image']) && $_FILES['image']['error'] === 0) {
$ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$filename = 'uploads/item_' . $id . '_' . time() . '.' . $ext;
if (move_uploaded_file($_FILES['image']['tmp_name'], $filename)) db()->prepare("UPDATE stock_items SET image_path = ? WHERE id = ?")->execute([$filename, $id]);
}
$message = "Item updated successfully!";
}
if (isset($_POST['delete_item'])) {
db()->prepare("DELETE FROM stock_items WHERE id = ?")->execute([(int)$_POST['id']]);
$message = "Item deleted successfully!";
}
if (isset($_POST['add_category'])) {
db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '']);
$message = "Category added!";
}
if (isset($_POST['add_unit'])) {
db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar) VALUES (?, ?, ?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', $_POST['short_en'] ?? '', $_POST['short_ar'] ?? '']);
$message = "Unit added!";
}
if (isset($_POST['add_customer'])) {
db()->prepare("INSERT INTO customers (name, email, phone, tax_id, balance, type) VALUES (?, ?, ?, ?, ?, ?)")->execute([$_POST['name'] ?? '', $_POST['email'] ?? '', $_POST['phone'] ?? '', $_POST['tax_id'] ?? '', (float)($_POST['balance'] ?? 0), $_POST['type'] ?? 'customer']);
$message = "Entity added!";
}
if (isset($_POST['edit_customer'])) {
db()->prepare("UPDATE customers SET name = ?, email = ?, phone = ?, tax_id = ?, balance = ? WHERE id = ?")->execute([$_POST['name'] ?? '', $_POST['email'] ?? '', $_POST['phone'] ?? '', $_POST['tax_id'] ?? '', (float)($_POST['balance'] ?? 0), (int)$_POST['id']]);
$message = "Entity updated!";
}
if (isset($_POST['delete_customer'])) {
db()->prepare("DELETE FROM customers WHERE id = ?")->execute([(int)$_POST['id']]);
$message = "Entity deleted!";
}
// Invoices
if (isset($_POST['add_invoice'])) {
$db = db();
try {
$db->beginTransaction();
$type = $_POST['type'] ?? 'sale';
$cust_id = (int)$_POST['customer_id'];
$inv_date = $_POST['invoice_date'] ?: date('Y-m-d');
$status = $_POST['status'] ?? 'pending';
$pay_type = $_POST['payment_type'] ?? 'cash';
$total_vat = (float)($_POST['total_vat'] ?? 0);
$total_with_vat = (float)($_POST['total_with_vat'] ?? 0);
$paid = (float)($_POST['paid_amount'] ?? 0);
$stmt = $db->prepare("INSERT INTO invoices (customer_id, type, invoice_date, status, payment_type, total_vat, total_with_vat, paid_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$cust_id, $type, $inv_date, $status, $pay_type, $total_vat, $total_with_vat, $paid]);
$inv_id = $db->lastInsertId();
$items = $_POST['items'] ?? [];
$qtys = $_POST['qtys'] ?? [];
$prices = $_POST['prices'] ?? [];
$vats = $_POST['vats'] ?? [];
$items_for_journal = [];
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$vat = (float)($vats[$i] ?? 0);
$subtotal = $qty * $price;
$db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, vat_amount, subtotal) VALUES (?, ?, ?, ?, ?, ?)")->execute([$inv_id, $item_id, $qty, $price, $vat, $subtotal]);
$change = ($type === 'sale') ? -$qty : $qty;
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$change, $item_id]);
$items_for_journal[] = ['id' => $item_id, 'qty' => $qty];
}
if ($type === 'sale') recordSaleJournal($inv_id, $total_with_vat, $inv_date, $items_for_journal, $total_vat);
else recordPurchaseJournal($inv_id, $total_with_vat, $inv_date, $items_for_journal, $total_vat);
$db->commit();
$message = "Invoice #$inv_id created!";
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST['record_payment'])) {
$inv_id = (int)$_POST['invoice_id'];
$amount = (float)$_POST['amount'];
$date = $_POST['payment_date'] ?: date('Y-m-d');
$method = $_POST['payment_method'] ?? 'Cash';
$db = db();
$db->prepare("INSERT INTO payments (invoice_id, amount, payment_date, payment_method, notes) VALUES (?, ?, ?, ?, ?)")->execute([$inv_id, $amount, $date, $method, $_POST['notes'] ?? '']);
$pay_id = $db->lastInsertId();
$db->prepare("UPDATE invoices SET paid_amount = paid_amount + ?, status = IF(paid_amount + ? >= total_with_vat, 'paid', 'partially_paid') WHERE id = ?")->execute([$amount, $amount, $inv_id]);
$inv = $db->query("SELECT type FROM invoices WHERE id = $inv_id")->fetch();
if ($inv['type'] === 'sale') recordPaymentReceivedJournal($pay_id, $amount, $date, $method);
else recordPaymentMadeJournal($pay_id, $amount, $date, $method);
$message = "Payment recorded!";
$_SESSION['trigger_receipt_modal'] = true; $_SESSION['show_receipt_id'] = $pay_id;
}
if (isset($_POST['add_expense'])) {
$amt = (float)$_POST['amount'];
$date = $_POST['expense_date'] ?: date('Y-m-d');
$desc = $_POST['description'] ?? '';
db()->prepare("INSERT INTO expenses (category_id, amount, expense_date, reference_no, description) VALUES (?, ?, ?, ?, ?)")->execute([(int)$_POST['category_id'], $amt, $date, $_POST['reference_no'] ?? '', $desc]);
recordExpenseJournal(db()->lastInsertId(), $amt, $date, $desc);
$message = "Expense recorded!";
}
if (isset($_POST['import_items'])) {
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$handle = fopen($_FILES['excel_file']['tmp_name'], "r");
fgetcsv($handle); $count = 0;
while (($data = fgetcsv($handle)) !== FALSE) {
if ($data[1] || $data[2]) {
db()->prepare("INSERT INTO stock_items (sku, name_en, name_ar, sale_price, purchase_price) VALUES (?, ?, ?, ?, ?)")->execute([$data[0] ?? '', $data[1] ?? '', $data[2] ?? '', (float)($data[3] ?? 0), (float)($data[4] ?? 0)]);
$count++;
}
}
fclose($handle); $message = "Imported $count items!";
}
}
if (isset($_POST['add_expense_category'])) {
db()->prepare("INSERT INTO expense_categories (name_en, name_ar) VALUES (?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '']);
$message = "Expense category added!";
}
if (isset($_POST['add_payment_method'])) {
db()->prepare("INSERT INTO payment_methods (name, type) VALUES (?, ?)")->execute([$_POST['name'] ?? '', $_POST['type'] ?? 'Cash']);
$message = "Payment method added!";
}
if (isset($_POST['delete_invoice'])) {
$id = (int)$_POST['id'];
db()->prepare("DELETE FROM invoices WHERE id = ?")->execute([$id]);
db()->prepare("DELETE FROM invoice_items WHERE invoice_id = ?")->execute([$id]);
$message = "Invoice deleted!";
}
if (isset($_POST['edit_invoice'])) {
$id = (int)$_POST['id'];
$cust_id = (int)$_POST['customer_id'];
$date = $_POST['invoice_date'] ?: date('Y-m-d');
$status = $_POST['status'] ?? 'pending';
db()->prepare("UPDATE invoices SET customer_id = ?, invoice_date = ?, status = ? WHERE id = ?")->execute([$cust_id, $date, $status, $id]);
$message = "Invoice updated!";
}
// --- HR Handlers ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
error_log("POST Request detected. Action: " . (print_r($_POST, true)));
}
@ -3037,7 +3230,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
</div>
<div class="product-grid" id="productGrid">
<?php foreach ($products as $p): ?>
<div class="product-card" data-id="<?= $p['id'] ?>" data-name-en="<?= htmlspecialchars($p['name_en']) ?>" data-name-ar="<?= htmlspecialchars($p['name_ar']) ?>" data-price="<?= $p['sale_price'] ?>" data-sku="<?= htmlspecialchars($p['sku']) ?>" data-stock-quantity="<?= (float)$p['stock_quantity'] ?>">
<div class="product-card" data-id="<?= $p['id'] ?>" data-name-en="<?= htmlspecialchars($p['name_en']) ?>" data-name-ar="<?= htmlspecialchars($p['name_ar']) ?>" data-price="<?= $p['sale_price'] ?>" data-sku="<?= htmlspecialchars($p['sku']) ?>" data-stock-quantity="<?= (float)$p['stock_quantity'] ?>" data-vat-rate="<?= $p['vat_rate'] ?>">
<?php if ($p['image_path']): ?>
<img src="<?= htmlspecialchars($p['image_path']) ?>" alt="<?= htmlspecialchars($p['name_en']) ?>">
<?php else: ?>
@ -3130,12 +3323,16 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<div class="cart-total">
<div class="d-flex justify-content-between mb-1">
<span>Subtotal</span>
<span id="posSubtotal">OMR 0.000</span>
<span data-en="Subtotal (Excl. VAT)" data-ar="المجموع (بدون الضريبة)">Subtotal (Excl. VAT)</span>
<span id="posSubtotal"><?= __('currency') ?> 0.000</span>
</div>
<div class="d-flex justify-content-between mb-3 fw-bold fs-5">
<span>Total</span>
<span id="posTotal" class="text-primary">OMR 0.000</span>
<div class="d-flex justify-content-between mb-1">
<span data-en="VAT" data-ar="الضريبة">VAT</span>
<span id="posVat"><?= __('currency') ?> 0.000</span>
</div>
<div class="d-flex justify-content-between mb-3 fw-bold fs-5 border-top pt-2">
<span data-en="Total" data-ar="الإجمالي">Total</span>
<span id="posTotal" class="text-primary"><?= __('currency') ?> 0.000</span>
</div>
<button class="btn btn-primary w-100 py-2 fw-bold" id="checkoutBtn" onclick="cart.checkout()">
PLACE ORDER
@ -3172,6 +3369,12 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
if (!Array.isArray(this.items)) this.items = [];
const subtotal = this.items.reduce((sum, item) => sum + (parseFloat(item.price) * parseFloat(item.qty)), 0);
const totalVat = this.items.reduce((sum, item) => {
const price = parseFloat(item.price) || 0;
const qty = parseFloat(item.qty) || 0;
const vatRate = item.vatRate || 5;
return sum + (price * qty * (vatRate / (100 + vatRate)));
}, 0);
let discountAmount = 0;
if (this.discount) {
discountAmount = this.discount.type === 'percentage' ? subtotal * (parseFloat(this.discount.value) / 100) : parseFloat(this.discount.value);
@ -3185,15 +3388,24 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
const customerName = customerSelect ? customerSelect.options[customerSelect.selectedIndex].text : '';
const payload = {
items: this.items.map(i => ({
name: (document.documentElement.lang === 'ar' ? (i.nameAr || i.nameEn) : (i.nameEn || i.nameAr)) || 'Unknown Item',
price: parseFloat(i.price) || 0,
qty: parseFloat(i.qty) || 0
})),
items: this.items.map(i => {
const price = parseFloat(i.price) || 0;
const qty = parseFloat(i.qty) || 0;
const vatRate = i.vatRate || 5;
const vatAmount = price * qty * (vatRate / (100 + vatRate));
return {
name: (document.documentElement.lang === 'ar' ? (i.nameAr || i.nameEn) : (i.nameEn || i.nameAr)) || 'Unknown Item',
price: price,
qty: qty,
vat: vatAmount
};
}),
subtotal: parseFloat(subtotal) || 0,
vat: parseFloat(totalVat) || 0,
discount: parseFloat(discountAmount) || 0,
loyalty: parseFloat(loyaltyRedeemed) || 0,
total: parseFloat(total) || 0,
currency: "<?= __('currency') ?>",
customerName: customerName,
theme: document.body.className,
timestamp: Date.now()
@ -3450,32 +3662,40 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
const totalEl = document.getElementById('posTotal');
const checkoutBtn = document.getElementById('checkoutBtn');
if (subtotalEl) subtotalEl.innerText = 'OMR 0.000';
if (totalEl) totalEl.innerText = 'OMR 0.000';
if (subtotalEl) subtotalEl.innerText = '<?= __('currency') ?> 0.000';
if (totalEl) totalEl.innerText = '<?= __('currency') ?> 0.000';
if (checkoutBtn) checkoutBtn.disabled = true;
this.broadcast();
return;
}
let subtotal = 0;
let totalVat = 0;
container.innerHTML = items.map(item => {
const price = parseFloat(item.price) || 0;
const qty = parseFloat(item.qty) || 0;
subtotal += price * qty;
const itemTotal = price * qty;
subtotal += itemTotal;
const vatRate = item.vatRate || 5;
const itemVat = itemTotal * (vatRate / (100 + vatRate));
totalVat += itemVat;
const displayName = (lang === 'ar' ? (item.nameAr || item.nameEn) : (item.nameEn || item.nameAr)) || 'Unknown Item';
return `
<div class="cart-item">
<div class="flex-grow-1">
<div class="fw-bold small">${displayName}</div>
<div class="text-muted smaller">OMR ${price.toFixed(3)}</div>
<div class="text-muted smaller"><?= __('currency') ?> ${price.toFixed(3)} <span class="badge bg-light text-dark smaller">VAT ${vatRate}%</span></div>
</div>
<div class="qty-controls mx-3">
<button class="qty-btn" onclick="cart.updateQty(${item.id}, -1)">-</button>
<span class="small fw-bold">${qty}</span>
<button class="qty-btn" onclick="cart.updateQty(${item.id}, 1)">+</button>
</div>
<div class="fw-bold small">OMR ${(price * qty).toFixed(3)}</div>
<div class="text-end" style="min-width: 80px;">
<div class="fw-bold small"><?= __('currency') ?> ${itemTotal.toFixed(3)}</div>
<div class="smaller text-muted">VAT: ${itemVat.toFixed(3)}</div>
</div>
</div>
`;
}).join('');
@ -3503,18 +3723,21 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
const pointsToEarn = Math.floor(total * multiplier);
const subtotalDisplay = document.getElementById('posSubtotal');
if (subtotalDisplay) subtotalDisplay.innerText = 'OMR ' + subtotal.toFixed(3);
if (subtotalDisplay) subtotalDisplay.innerText = '<?= __('currency') ?> ' + (subtotal - totalVat).toFixed(3);
const vatDisplay = document.getElementById('posVat');
if (vatDisplay) vatDisplay.innerText = '<?= __('currency') ?> ' + totalVat.toFixed(3);
let totalHtml = '';
if (discountAmount > 0) totalHtml += `<div class="smaller text-danger">- Disc: OMR ${discountAmount.toFixed(3)}</div>`;
if (loyaltyRedeemedValue > 0) totalHtml += `<div class="smaller text-success">- Loyalty: OMR ${loyaltyRedeemedValue.toFixed(3)}</div>`;
if (discountAmount > 0) totalHtml += `<div class="smaller text-danger">- Disc: <?= __('currency') ?> ${discountAmount.toFixed(3)}</div>`;
if (loyaltyRedeemedValue > 0) totalHtml += `<div class="smaller text-success">- Loyalty: <?= __('currency') ?> ${loyaltyRedeemedValue.toFixed(3)}</div>`;
const customerId = document.getElementById('posCustomer') ? document.getElementById('posCustomer').value : '';
if (customerId) {
totalHtml += `<div class="smaller text-info">+ Earn: ${pointsToEarn} pts</div>`;
}
totalHtml += 'OMR ' + total.toFixed(3);
totalHtml += '<?= __('currency') ?> ' + total.toFixed(3);
const totalDisplay = document.getElementById('posTotal');
if (totalDisplay) {
@ -3546,7 +3769,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
this.payments = [];
this.renderPayments();
document.getElementById('paymentAmountDue').innerText = 'OMR ' + total.toFixed(3);
document.getElementById('paymentAmountDue').innerText = '<?= __('currency') ?> ' + total.toFixed(3);
document.getElementById('partialAmount').value = total.toFixed(3);
// Sync credit customer selection if credit is default or already selected
@ -3627,7 +3850,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<div class="payment-line">
<div>
<span class="method">${p.method}</span>
<span class="ms-2 badge bg-secondary small">OMR ${p.amount.toFixed(3)}</span>
<span class="ms-2 badge bg-secondary small"><?= __('currency') ?> ${p.amount.toFixed(3)}</span>
</div>
<button class="btn btn-sm btn-outline-danger border-0" onclick="cart.removePaymentLine(${i})">
<i class="bi bi-trash"></i>
@ -3757,53 +3980,76 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
showReceipt(invId, discountAmount, loyaltyRedeemed, transactionNo) {
const container = document.getElementById('posReceiptContent');
const customerSelect = document.getElementById('posCustomer');
const customerName = (customerSelect && customerSelect.selectedIndex >= 0) ? customerSelect.options[customerSelect.selectedIndex].text : 'Walk-in Customer';
const paymentsHtml = this.payments.map(p => `
const customerName = (customerSelect && customerSelect.selectedIndex >= 0 && customerSelect.value !== '') ? customerSelect.options[customerSelect.selectedIndex].text : '<?= $translations['ar']['walk_in_customer'] ?> / <?= $translations['en']['walk_in_customer'] ?>';
const paymentsHtml = this.payments.map(p => {
let m = p.method.toLowerCase();
let methodAr = m === 'cash' ? 'نقد' : (m === 'card' ? 'بطاقة' : (m === 'credit' ? 'آجل' : m));
let methodEn = m.charAt(0).toUpperCase() + m.slice(1);
return `
<div class="d-flex justify-content-between small">
<span class="text-uppercase">${p.method}</span>
<span>OMR ${p.amount.toFixed(3)}</span>
<span class="text-uppercase">${methodAr} / ${methodEn}</span>
<span><?= __('currency') ?> ${p.amount.toFixed(3)}</span>
</div>
`).join('');
`;
}).join('');
const date = new Date().toLocaleString();
let itemsHtml = this.items.map(item => `
let itemsHtml = this.items.map(item => {
const itemTotal = item.price * item.qty;
const vatRate = item.vatRate || 5; // Default to 5 if not set
const vatAmount = itemTotal * (vatRate / (100 + vatRate));
const exclVat = itemTotal - vatAmount;
return `
<tr>
<td>${item.nameEn}<br><small>${item.qty} x ${parseFloat(item.price).toFixed(3)}</small></td>
<td style="text-align: right; vertical-align: bottom;">${(item.price * item.qty).toFixed(3)}</td>
<td>
<div class="fw-bold">${item.nameAr || ''}</div>
<div>${item.nameEn}</div>
<small>${item.qty} x ${parseFloat(item.price).toFixed(3)}</small>
</td>
<td style="text-align: right; vertical-align: bottom;">${vatAmount.toFixed(3)}</td>
<td style="text-align: right; vertical-align: bottom;">${itemTotal.toFixed(3)}</td>
</tr>
`).join('');
`;
}).join('');
const subtotal = this.items.reduce((sum, item) => sum + (item.price * item.qty), 0);
const totalVat = this.items.reduce((sum, item) => {
const vatRate = item.vatRate || 5;
return sum + ((item.price * item.qty) * (vatRate / (100 + vatRate)));
}, 0);
const total = subtotal - discountAmount - loyaltyRedeemed;
const companyName = "<?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>";
const companyPhone = "<?= htmlspecialchars($data['settings']['company_phone'] ?? '') ?>";
const companyVat = "<?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?>";
const companyLogo = "<?= htmlspecialchars($data['settings']['company_logo'] ?? '') ?>";
container.innerHTML = `
<div class="thermal-receipt">
<div class="thermal-receipt <?= $lang === 'ar' ? 'rtl' : '' ?>">
<div class="center">
${companyLogo ? `<img src="${companyLogo}" alt="Logo" style="max-height: 60px; width: auto; margin-bottom: 10px; display: block; margin-left: auto; margin-right: auto;">` : ''}
<h5 class="mb-0 fw-bold">${companyName}</h5>
${companyPhone ? `<div>Tel: ${companyPhone}</div>` : ''}
${companyVat ? `<div>VAT: ${companyVat}</div>` : ''}
${companyPhone ? `<div>هاتف / Tel: ${companyPhone}</div>` : ''}
${companyVat ? `<div>الرقم الضريبي / VAT No: ${companyVat}</div>` : ''}
<div class="separator"></div>
<h6 class="fw-bold">TAX INVOICE</h6>
<div>Inv: ${transactionNo || 'POS-'+invId}</div>
<div>Date: ${date}</div>
<h6 class="fw-bold text-uppercase">فاتورة ضريبية / TAX INVOICE</h6>
<div>رقم الفاتورة / Invoice No: ${transactionNo || 'POS-'+invId}</div>
<div>التاريخ / Date: ${date}</div>
<div class="separator"></div>
</div>
<div>
<strong>Customer:</strong> ${customerName}
<strong>العميل / Customer:</strong> ${customerName}
</div>
<div class="mt-1">
<strong>Payments:</strong>
<strong>المدفوعات / Payments:</strong>
${paymentsHtml}
</div>
<div class="separator"></div>
<table>
<table class="table-borderless">
<thead>
<tr>
<th>ITEM</th>
<th style="text-align: right;">TOTAL</th>
<th>البند / Item</th>
<th style="text-align: right;">ضريبة / VAT</th>
<th style="text-align: right;">الإجمالي / Total</th>
</tr>
</thead>
<tbody>
@ -3812,20 +4058,28 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
</table>
<div class="separator"></div>
<div class="d-flex justify-content-between">
<span>Subtotal</span>
<span>OMR ${subtotal.toFixed(3)}</span>
<span>المجموع الفرعي (غير شامل الضريبة) / Subtotal (Excl. VAT)</span>
<span><?= __('currency') ?> ${(subtotal - totalVat).toFixed(3)}</span>
</div>
${discountAmount > 0 ? `<div class="d-flex justify-content-between text-danger"><span>Discount</span><span>- OMR ${parseFloat(discountAmount).toFixed(3)}</span></div>` : ''}
${loyaltyRedeemed > 0 ? `<div class="d-flex justify-content-between text-success"><span>Loyalty</span><span>- OMR ${parseFloat(loyaltyRedeemed).toFixed(3)}</span></div>` : ''}
<div class="d-flex justify-content-between">
<span>الضريبة / VAT</span>
<span><?= __('currency') ?> ${totalVat.toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between fw-bold">
<span>المجموع شامل الضريبة / Total (Incl. VAT)</span>
<span><?= __('currency') ?> ${subtotal.toFixed(3)}</span>
</div>
${discountAmount > 0 ? `<div class="d-flex justify-content-between text-danger"><span>خصم / Discount</span><span>- <?= __('currency') ?> ${parseFloat(discountAmount).toFixed(3)}</span></div>` : ''}
${loyaltyRedeemed > 0 ? `<div class="d-flex justify-content-between text-success"><span>الولاء / Loyalty</span><span>- <?= __('currency') ?> ${parseFloat(loyaltyRedeemed).toFixed(3)}</span></div>` : ''}
<div class="separator"></div>
<div class="d-flex justify-content-between total-row">
<span>TOTAL</span>
<span>OMR ${total.toFixed(3)}</span>
<span>الإجمالي / Total</span>
<span><?= __('currency') ?> ${total.toFixed(3)}</span>
</div>
<div class="separator"></div>
<div class="center small">
Thank you for your business!<br>
Please keep the receipt.
شكراً لتعاملكم معنا! / Thank you for your business!<br>
يرجى الاحتفاظ بالإيصال. / Please keep the receipt.
</div>
</div>
`;
@ -3856,7 +4110,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
nameEn: card.dataset.nameEn,
nameAr: card.dataset.nameAr,
price: parseFloat(card.dataset.price),
stock_quantity: parseFloat(card.dataset.stockQuantity)
stock_quantity: parseFloat(card.dataset.stockQuantity),
vatRate: parseFloat(card.dataset.vatRate) || 0
};
cart.add(product);
});
@ -3888,7 +4143,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
nameAr: card.dataset.nameAr,
price: parseFloat(card.dataset.price),
sku: card.dataset.sku,
stock_quantity: parseFloat(card.dataset.stockQuantity)
stock_quantity: parseFloat(card.dataset.stockQuantity),
vatRate: parseFloat(card.dataset.vatRate) || 0
};
cart.add(product);
e.target.value = '';
@ -7645,6 +7901,13 @@ document.addEventListener('DOMContentLoaded', function() {
const sku = Math.floor(100000000000 + Math.random() * 900000000000).toString();
skuInput.value = sku;
});
skuInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
console.log("Barcode scan detected in SKU field, preventing form submission");
}
});
}
// Toggle Expiry Date visibility
@ -8516,7 +8779,7 @@ document.addEventListener('DOMContentLoaded', function() {
<tfoot>
<tr>
<th colspan="4" class="text-end">Total Return Amount:</th>
<th class="text-end" id="purchase_return_total_display">OMR 0.000</th>
<th class="text-end" id="purchase_return_total_display"><?= __('currency') ?> 0.000</th>
</tr>
</tfoot>
</table>
@ -8580,7 +8843,7 @@ document.addEventListener('DOMContentLoaded', function() {
<tfoot>
<tr>
<th colspan="4" class="text-end">Total Return Amount:</th>
<th class="text-end" id="return_total_display">OMR 0.000</th>
<th class="text-end" id="return_total_display"><?= __('currency') ?> 0.000</th>
</tr>
</tfoot>
</table>
@ -8896,17 +9159,17 @@ document.addEventListener('DOMContentLoaded', function() {
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="subtotal">OMR 0.000</td>
<td class="text-end fw-bold" id="subtotal"><?= __('currency') ?> 0.000</td>
<td></td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="totalVat">OMR 0.000</td>
<td class="text-end fw-bold" id="totalVat"><?= __('currency') ?> 0.000</td>
<td></td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="grandTotal">OMR 0.000</td>
<td class="text-end fw-bold h5" id="grandTotal"><?= __('currency') ?> 0.000</td>
<td></td>
</tr>
</tfoot>
@ -8996,17 +9259,17 @@ document.addEventListener('DOMContentLoaded', function() {
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="edit_subtotal">OMR 0.000</td>
<td class="text-end fw-bold" id="edit_subtotal"><?= __('currency') ?> 0.000</td>
<td></td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="edit_totalVat">OMR 0.000</td>
<td class="text-end fw-bold" id="edit_totalVat"><?= __('currency') ?> 0.000</td>
<td></td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="edit_grandTotal">OMR 0.000</td>
<td class="text-end fw-bold h5" id="edit_grandTotal"><?= __('currency') ?> 0.000</td>
<td></td>
</tr>
</tfoot>
@ -9078,15 +9341,15 @@ document.addEventListener('DOMContentLoaded', function() {
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="quot_subtotal_display">OMR 0.000</td>
<td class="text-end fw-bold" id="quot_subtotal_display"><?= __('currency') ?> 0.000</td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="quot_vat_display">OMR 0.000</td>
<td class="text-end fw-bold" id="quot_vat_display"><?= __('currency') ?> 0.000</td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="quot_grand_display">OMR 0.000</td>
<td class="text-end fw-bold h5" id="quot_grand_display"><?= __('currency') ?> 0.000</td>
</tr>
</tfoot>
</table>
@ -9167,15 +9430,15 @@ document.addEventListener('DOMContentLoaded', function() {
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="edit_quot_subtotal_display">OMR 0.000</td>
<td class="text-end fw-bold" id="edit_quot_subtotal_display"><?= __('currency') ?> 0.000</td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="edit_quot_vat_display">OMR 0.000</td>
<td class="text-end fw-bold" id="edit_quot_vat_display"><?= __('currency') ?> 0.000</td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="edit_quot_grand_display">OMR 0.000</td>
<td class="text-end fw-bold h5" id="edit_quot_grand_display"><?= __('currency') ?> 0.000</td>
</tr>
</tfoot>
</table>
@ -9258,8 +9521,20 @@ document.addEventListener('DOMContentLoaded', function() {
.badge { border: 1px solid #000; color: #000 !important; }
/* Ensure the modal is the only thing visible ONLY when a modal is open */
body.modal-open:not(.printing-receipt) > *:not(.modal):not(.swal2-container) { display: none !important; }
body.modal-open:not(.printing-receipt) .main-content { display: none !important; }
body.modal-open:not(.printing-receipt) { visibility: hidden !important; }
body.modal-open:not(.printing-receipt) .modal.show {
visibility: visible !important;
display: block !important;
position: absolute !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
}
body.modal-open:not(.printing-receipt) .modal.show * { visibility: visible !important; }
/* Old rules that caused blank pages for nested modals */
/* body.modal-open:not(.printing-receipt) > *:not(.modal):not(.swal2-container) { display: none !important; } */
/* body.modal-open:not(.printing-receipt) .main-content { display: none !important; } */
/* POS Receipt printing specific */
body.printing-receipt .modal { display: none !important; }
@ -9369,7 +9644,7 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
<div class="col-6">
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Subtotal / المجموع الفرعي</span>
<span class="text-muted">Subtotal (Excl. VAT) / المجموع الفرعي (دون الضريبة)</span>
<span id="invSubtotal" class="fw-bold text-nowrap"></span>
</div>
<div class="d-flex justify-content-between mb-2">
@ -9495,6 +9770,11 @@ document.addEventListener('DOMContentLoaded', function() {
<div class="modal-body p-0" id="printableReceipt">
<div class="receipt-container p-4">
<div class="text-center mb-4">
<?php
$logo = $data['settings']['company_logo'] ?? '';
if ($logo): ?>
<img src="<?= htmlspecialchars($logo) ?>" alt="Logo" class="invoice-logo mb-3">
<?php endif; ?>
<h3 class="mb-1 fw-bold"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small mb-0"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
<hr class="my-4">
@ -9583,11 +9863,11 @@ document.addEventListener('DOMContentLoaded', function() {
<div class="d-flex justify-content-between px-3">
<div class="text-start">
<div class="label">Amount Due</div>
<div class="value" id="paymentAmountDue">OMR 0.000</div>
<div class="value" id="paymentAmountDue"><?= __('currency') ?> 0.000</div>
</div>
<div class="text-end">
<div class="label text-danger">Remaining</div>
<div class="value text-danger" id="paymentRemaining">OMR 0.000</div>
<div class="value text-danger" id="paymentRemaining"><?= __('currency') ?> 0.000</div>
</div>
</div>
</div>
@ -9640,7 +9920,7 @@ document.addEventListener('DOMContentLoaded', function() {
<div id="cashPaymentSection" style="display: none;">
<div class="d-flex justify-content-between align-items-center p-3 bg-primary-subtle rounded border border-primary-subtle">
<span class="fw-bold">Total Tendered (Cash)</span>
<span class="h4 m-0 fw-bold text-primary" id="changeDue">OMR 0.000</span>
<span class="h4 m-0 fw-bold text-primary" id="changeDue"><?= __('currency') ?> 0.000</span>
</div>
<div class="small text-muted mt-1">* Change is calculated based on cash payments only.</div>
</div>
@ -10126,21 +10406,24 @@ document.addEventListener('DOMContentLoaded', function() {
tr.innerHTML = `
<td>${item.name_en} / ${item.name_ar}</td>
<td class="text-center">${item.quantity}</td>
<td class="text-end"><small>OMR</small> ${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-end"><small><?= __('currency') ?></small> ${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-end">${parseFloat(item.vat_rate || 0).toFixed(0)}%</td>
<td class="text-end"><small>OMR</small> ${parseFloat(item.total_price).toFixed(3)}</td>
<td class="text-end"><small><?= __('currency') ?></small> ${parseFloat(item.total_price).toFixed(3)}</td>
`;
body.appendChild(tr);
});
}
if (document.getElementById('invSubtotal')) document.getElementById('invSubtotal').innerHTML = '<small>OMR</small> ' + parseFloat(data.total_amount).toFixed(3);
if (document.getElementById('invVatAmount')) document.getElementById('invVatAmount').innerHTML = '<small>OMR</small> ' + (parseFloat(data.vat_amount) || 0).toFixed(3);
const grandTotalValue = (parseFloat(data.total_with_vat) || parseFloat(data.total_amount));
if (document.getElementById('invGrandTotal')) document.getElementById('invGrandTotal').innerHTML = '<small>OMR</small> ' + grandTotalValue.toFixed(3);
const vatVal = parseFloat(data.vat_amount || 0);
const totalVal = parseFloat(data.total_amount || 0);
const grandTotalValue = (parseFloat(data.total_with_vat) || (totalVal + vatVal));
if (document.getElementById('invPaidInfo')) document.getElementById('invPaidInfo').innerHTML = '<small>OMR</small> ' + parseFloat(data.paid_amount || 0).toFixed(3);
if (document.getElementById('invSubtotal')) document.getElementById('invSubtotal').innerHTML = '<small><?= __('currency') ?></small> ' + (grandTotalValue - vatVal).toFixed(3);
if (document.getElementById('invVatAmount')) document.getElementById('invVatAmount').innerHTML = '<small><?= __('currency') ?></small> ' + vatVal.toFixed(3);
if (document.getElementById('invGrandTotal')) document.getElementById('invGrandTotal').innerHTML = '<small><?= __('currency') ?></small> ' + grandTotalValue.toFixed(3);
if (document.getElementById('invPaidInfo')) document.getElementById('invPaidInfo').innerHTML = '<small><?= __('currency') ?></small> ' + parseFloat(data.paid_amount || 0).toFixed(3);
const balance = grandTotalValue - parseFloat(data.paid_amount || 0);
if (document.getElementById('invBalanceInfo')) document.getElementById('invBalanceInfo').innerHTML = '<small>OMR</small> ' + balance.toFixed(3);
if (document.getElementById('invBalanceInfo')) document.getElementById('invBalanceInfo').innerHTML = '<small><?= __('currency') ?></small> ' + balance.toFixed(3);
// Generate QR Code for Zakat, Tax and Customs Authority (ZATCA) style or simple formal
const companyName = <?= json_encode($data['settings']['company_name'] ?? 'Accounting System') ?>;
@ -10181,20 +10464,35 @@ document.addEventListener('DOMContentLoaded', function() {
window.printPosReceiptFromInvoice = function(inv) {
const container = document.getElementById('posReceiptContent');
const itemsHtml = inv.items.map(item => `
const itemsHtml = inv.items.map(item => {
const itemTotal = item.unit_price * item.quantity;
const vatRate = parseFloat(item.vat_rate || 5);
const vatAmount = itemTotal * (vatRate / (100 + vatRate));
return `
<tr>
<td>${item.name_en} / ${item.name_ar}<br><small>${item.quantity} x ${parseFloat(item.unit_price).toFixed(3)}</small></td>
<td style="text-align: right; vertical-align: bottom;">${(item.unit_price * item.quantity).toFixed(3)}</td>
<td style="text-align: right; vertical-align: bottom;">${vatAmount.toFixed(3)}</td>
<td style="text-align: right; vertical-align: bottom;">${itemTotal.toFixed(3)}</td>
</tr>
`).join('');
`;
}).join('');
const totalVat = inv.items.reduce((sum, item) => {
const itemTotal = item.unit_price * item.quantity;
const vatRate = parseFloat(item.vat_rate || 5);
return sum + (itemTotal * (vatRate / (100 + vatRate)));
}, 0);
const subtotal = inv.items.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0);
const companyName = "<?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>";
const companyPhone = "<?= htmlspecialchars($data['settings']['company_phone'] ?? '') ?>";
const companyVat = "<?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?>";
const companyLogo = "<?= htmlspecialchars($data['settings']['company_logo'] ?? '') ?>";
container.innerHTML = `
<div class="thermal-receipt">
<div class="center">
${companyLogo ? `<img src="${companyLogo}" alt="Logo" style="max-height: 60px; width: auto; margin-bottom: 10px; display: block; margin-left: auto; margin-right: auto;">` : ''}
<h5 class="mb-0 fw-bold">${companyName}</h5>
${companyPhone ? `<div>Tel: ${companyPhone}</div>` : ''}
${companyVat ? `<div>VAT: ${companyVat}</div>` : ''}
@ -10212,6 +10510,7 @@ document.addEventListener('DOMContentLoaded', function() {
<thead>
<tr>
<th>ITEM / الصنف</th>
<th style="text-align: right;">VAT / الضريبة</th>
<th style="text-align: right;">TOTAL / الإجمالي</th>
</tr>
</thead>
@ -10220,17 +10519,25 @@ document.addEventListener('DOMContentLoaded', function() {
</tbody>
</table>
<div class="separator"></div>
<div class="d-flex justify-content-between small">
<span>Subtotal (Excl. VAT) / المجموع الفرعي (دون الضريبة)</span>
<span><?= __('currency') ?> ${(subtotal - totalVat).toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between small">
<span>VAT / الضريبة</span>
<span><?= __('currency') ?> ${totalVat.toFixed(3)}</span>
</div>
<div class="total-row d-flex justify-content-between">
<span>TOTAL / الإجمالي</span>
<span>OMR ${parseFloat(inv.total_with_vat).toFixed(3)}</span>
<span>TOTAL (Incl. VAT) / الإجمالي (شامل الضريبة)</span>
<span><?= __('currency') ?> ${subtotal.toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between small">
<span>PAID / المدفوع</span>
<span>OMR ${parseFloat(inv.paid_amount).toFixed(3)}</span>
<span><?= __('currency') ?> ${parseFloat(inv.paid_amount).toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between small fw-bold">
<span>BALANCE / الرصيد</span>
<span>OMR ${(inv.total_with_vat - inv.paid_amount).toFixed(3)}</span>
<span><?= __('currency') ?> ${(subtotal - inv.paid_amount).toFixed(3)}</span>
</div>
<div class="separator"></div>
<div class="center small">

View File

@ -49,3 +49,11 @@
2026-02-19 05:59:46 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.595}]","total_amount":"0.595","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.3825},{\"id\":2,\"qty\":1,\"price\":0.2125}]"}
2026-02-19 06:00:05 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.595}]","total_amount":"0.595","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.3825},{\"id\":2,\"qty\":1,\"price\":0.2125}]"}
2026-02-19 06:00:51 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.595}]","total_amount":"0.595","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.3825},{\"id\":2,\"qty\":1,\"price\":0.2125}]"}
2026-02-19 06:12:12 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.595}]","total_amount":"0.595","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.3825},{\"id\":2,\"qty\":1,\"price\":0.2125}]"}
2026-02-19 06:12:47 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.595}]","total_amount":"0.595","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":2,\"qty\":1,\"price\":0.2125},{\"id\":1,\"qty\":1,\"price\":0.3825}]"}
2026-02-19 06:17:13 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.595}]","total_amount":"0.595","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.3825},{\"id\":2,\"qty\":1,\"price\":0.2125}]"}
2026-02-19 06:17:33 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.595}]","total_amount":"0.595","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.3825},{\"id\":2,\"qty\":1,\"price\":0.2125}]"}
2026-02-19 06:23:28 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.595}]","total_amount":"0.595","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.3825},{\"id\":2,\"qty\":1,\"price\":0.2125}]"}
2026-02-19 06:50:40 - POST: {"name_en":"Tissue","name_ar":"\u0645\u062d\u0627\u0631\u0645 \u0648\u0631\u0642\u064a\u0629","category_id":"2","unit_id":"2","supplier_id":"6","sku":"5673086966938977","sale_price":"0.25","purchase_price":"0.2","stock_quantity":"0.000","min_stock_level":"0.000","vat_rate":"5","expiry_date":"","promotion_start":"","promotion_end":"","promotion_percent":"0.00","add_item":""}
2026-02-19 06:53:36 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.978}]","total_amount":"0.9775","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":2,\"price\":0.3825},{\"id\":2,\"qty\":1,\"price\":0.2125}]"}
2026-02-19 07:01:43 - POST: {"name_en":"Tissue","name_ar":"\u0645\u062d\u0627\u0631\u0645 \u0648\u0631\u0642\u064a\u0629","category_id":"2","unit_id":"2","supplier_id":"6","sku":"760115926272","sale_price":"0.25","purchase_price":"0.2","stock_quantity":"5","min_stock_level":"0.000","vat_rate":"5","expiry_date":"","promotion_start":"","promotion_end":"","promotion_percent":"0.00","add_item":""}