modifying items add
This commit is contained in:
parent
a16d1400d4
commit
a38832e8a0
@ -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;
|
||||
|
||||
BIN
assets/pasted-20260219-061427-9bafaf53.png
Normal file
BIN
assets/pasted-20260219-061427-9bafaf53.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
@ -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) {
|
||||
|
||||
@ -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
483
index.php
@ -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">
|
||||
|
||||
@ -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":""}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user