check for pos

This commit is contained in:
Flatlogic Bot 2026-02-28 17:25:51 +00:00
parent 2772970659
commit aa6cc744d0
2 changed files with 93 additions and 49 deletions

View File

@ -1,4 +1,8 @@
console.log('POS Script Loading...');
document.addEventListener('DOMContentLoaded', () => {
console.log('POS Script DOMContentLoaded');
// SweetAlert2 Toast Mixin
const Toast = (typeof Swal !== 'undefined') ? Swal.mixin({
toast: true,
@ -89,7 +93,7 @@ document.addEventListener('DOMContentLoaded', () => {
let currentCustomer = null;
const paymentModalEl = document.getElementById('paymentSelectionModal');
const paymentSelectionModal = paymentModalEl ? new bootstrap.Modal(paymentSelectionModal) : null;
const paymentSelectionModal = paymentModalEl ? new bootstrap.Modal(paymentModalEl) : null;
const paymentMethodsContainer = document.getElementById('payment-methods-container');
const productSearchInput = document.getElementById('product-search');
@ -103,7 +107,7 @@ document.addEventListener('DOMContentLoaded', () => {
const symbol = settings.currency_symbol || '$';
const decimals = parseInt(settings.currency_decimals || 2);
const position = settings.currency_position || 'before';
const formatted = parseFloat(Math.abs(amount)).toFixed(decimals);
const formatted = parseFloat(Math.abs(amount || 0)).toFixed(decimals);
if (position === 'after') {
return formatted + ' ' + symbol;
@ -117,15 +121,14 @@ document.addEventListener('DOMContentLoaded', () => {
items.forEach(item => {
const matchesCategory = (currentCategory == 'all' || item.dataset.category == currentCategory);
const name = (item.dataset.name || '').toLowerCase();
const name_ar = (item.dataset.nameAr || '').toLowerCase();
const sku = (item.dataset.sku || '').toLowerCase();
const matchesSearch = name.includes(currentSearchQuery) || name_ar.includes(currentSearchQuery) || sku.includes(currentSearchQuery);
const matchesSearch = name.includes(currentSearchQuery) || sku.includes(currentSearchQuery);
item.style.display = (matchesCategory && matchesSearch) ? 'block' : 'none';
});
}
function filterCategory(categoryId, btnElement) { window.filterCategory = filterCategory;
function filterCategory(categoryId, btnElement) {
currentCategory = categoryId;
document.querySelectorAll('.category-btn').forEach(btn => btn.classList.remove('active'));
if (btnElement) {
@ -135,7 +138,8 @@ document.addEventListener('DOMContentLoaded', () => {
if (btn) btn.classList.add('active');
}
filterProducts();
};
}
window.filterCategory = filterCategory;
document.querySelectorAll('.category-btn').forEach(btn => {
btn.addEventListener('click', () => {
@ -150,11 +154,28 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
function openRecallOrderModal() { window.openRecallOrderModal = openRecallOrderModal;
const productGrid = document.getElementById('product-grid');
if (productGrid) {
productGrid.addEventListener('click', (e) => {
const item = e.target.closest('.product-item');
if (item) {
try {
const product = JSON.parse(item.dataset.product);
const variants = JSON.parse(item.dataset.variants);
handleProductClick(product, variants);
} catch (err) {
console.error('Error parsing product data:', err);
}
}
});
}
function openRecallOrderModal() {
if (!recallModal) return;
fetchRecallOrders();
recallModal.show();
};
}
window.openRecallOrderModal = openRecallOrderModal;
function fetchRecallOrders() {
if (!recallList) return;
@ -357,7 +378,7 @@ document.addEventListener('DOMContentLoaded', () => {
item.innerHTML = `
<div class="d-flex justify-content-between align-items-center">
<div class="fw-bold text-capitalize">${h.reason}</div>
<span class="badge ${badgeClass}">${h.points_change > 0 ? '+' : ''}${h.points_change}</span>
<span class="badge ${badgeClass}">${(h.points_change > 0 ? '+' : '') + h.points_change}</span>
</div>
<div class="text-muted small">${h.created_at}</div>
`;
@ -416,11 +437,12 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
function openTableSelectionModal() { window.openTableSelectionModal = openTableSelectionModal;
function openTableSelectionModal() {
if (!tableSelectionModal) return;
fetchTables();
tableSelectionModal.show();
};
}
window.openTableSelectionModal = openTableSelectionModal;
function fetchTables() {
const grid = document.getElementById("tables-grid");
@ -477,7 +499,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
function selectTable(id, name) { window.selectTable = selectTable;
function selectTable(id, name) {
currentTableId = id;
currentTableName = name;
const nameDisplay = document.getElementById("selected-table-name");
@ -489,9 +511,10 @@ document.addEventListener('DOMContentLoaded', () => {
dineInInput.checked = true;
checkOrderType();
}
};
function checkOrderType() { window.checkOrderType = checkOrderType;
}
window.selectTable = selectTable;
function checkOrderType() {
const checked = document.querySelector('input[name="order_type"]:checked');
if (!checked) return;
const selected = checked.value;
@ -501,25 +524,28 @@ document.addEventListener('DOMContentLoaded', () => {
} else {
if (tableDisplay) tableDisplay.style.display = 'none';
}
};
}
window.checkOrderType = checkOrderType;
document.querySelectorAll('input[name="order_type"]').forEach(input => {
input.addEventListener('change', checkOrderType);
});
function handleProductClick(product, variants) { window.handleProductClick = handleProductClick;
function handleProductClick(product, variants) {
console.log('Product Clicked:', product.name);
if (variants && variants.length > 0) {
openVariantModal(product, variants);
} else {
addToCart({
id: product.id, name: product.name, name_ar: product.name_ar || "",
price: parseFloat(product.price), base_price: parseFloat(product.price),
price: parseFloat(product.price || 0), base_price: parseFloat(product.price || 0),
hasVariants: false, quantity: 1, variant_id: null, variant_name: null,
is_loyalty: parseInt(product.is_loyalty) === 1,
vat_percent: parseFloat(product.vat_percent || settings.vat_rate || 0)
});
}
};
}
window.handleProductClick = handleProductClick;
function openVariantModal(product, variants) {
if (!variantSelectionModal) return;
@ -531,13 +557,13 @@ document.addEventListener('DOMContentLoaded', () => {
variants.forEach(v => {
const btn = document.createElement('button');
btn.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-center';
const finalPrice = parseFloat(product.price) + parseFloat(v.price_adjustment);
const finalPrice = parseFloat(product.price || 0) + parseFloat(v.price_adjustment || 0);
const vName = (LANG === 'ar' && v.name_ar) ? v.name_ar : v.name;
btn.innerHTML = `<span>${vName}</span><span class="fw-bold">${formatCurrency(finalPrice)}</span>`;
btn.onclick = () => {
addToCart({
id: product.id, name: product.name, name_ar: product.name_ar || "",
price: finalPrice, base_price: parseFloat(product.price),
price: finalPrice, base_price: parseFloat(product.price || 0),
hasVariants: true, quantity: 1, variant_id: v.id, variant_name: v.name,
is_loyalty: parseInt(product.is_loyalty) === 1,
vat_percent: parseFloat(product.vat_percent || settings.vat_rate || 0)
@ -549,37 +575,51 @@ document.addEventListener('DOMContentLoaded', () => {
variantSelectionModal.show();
}
function addToCart(product) { window.addToCart = addToCart;
function addToCart(product) {
console.log('Adding to cart:', product);
const existing = cart.find(item => item.id === product.id && item.variant_id === product.variant_id);
if (existing) existing.quantity++;
else cart.push({...product});
if (existing) {
existing.quantity++;
console.log('Incremented quantity for:', product.name);
} else {
cart.push({...product});
console.log('Added new item to cart:', product.name);
}
updateCart();
};
}
window.addToCart = addToCart;
function changeQuantity(index, delta) { window.changeQuantity = changeQuantity;
function changeQuantity(index, delta) {
if (cart[index]) {
cart[index].quantity += delta;
if (cart[index].quantity <= 0) cart.splice(index, 1);
updateCart();
}
};
}
window.changeQuantity = changeQuantity;
function removeFromCart(index) { window.removeFromCart = removeFromCart;
function removeFromCart(index) {
cart.splice(index, 1);
updateCart();
};
}
window.removeFromCart = removeFromCart;
function clearCart() { window.clearCart = clearCart;
function clearCart() {
if (cart.length === 0) return;
cart = [];
currentOrderId = null;
isLoyaltyRedemption = false;
updateCart();
showToast("Cart cleared", "success");
};
}
window.clearCart = clearCart;
function updateCart() {
if (!cartItemsContainer) return;
console.log('Updating cart UI, item count:', cart.length);
if (!cartItemsContainer) {
console.error('Cart items container not found!');
return;
}
updateLoyaltyUI();
@ -600,8 +640,8 @@ document.addEventListener('DOMContentLoaded', () => {
let subtotal = 0;
let totalVat = 0;
cart.forEach((item, index) => {
const itemTotal = item.price * item.quantity;
const itemVat = itemTotal * (item.vat_percent / 100);
const itemTotal = (item.price || 0) * (item.quantity || 0);
const itemVat = itemTotal * ((item.vat_percent || 0) / 100);
subtotal += itemTotal;
totalVat += itemVat;
@ -612,9 +652,9 @@ document.addEventListener('DOMContentLoaded', () => {
row.innerHTML = `
<div class="flex-grow-1 me-2">
<div class="fw-bold text-truncate" style="max-width: 140px;">${itemName}</div>
<div class="fw-bold text-truncate" style="max-width: 140px;">${itemName || 'Product'}</div>
${otherName ? `<div class="text-muted small" style="font-size: 0.75rem;">${otherName}</div>` : ''}
<div class="small text-muted">${formatCurrency(item.price)} ${item.vat_percent > 0 ? `<span class="badge bg-light text-dark border ms-1" style="font-size: 0.6rem;">${item.vat_percent}% VAT</span>` : ''}</div>
<div class="small text-muted">${formatCurrency(item.price)} ${((item.vat_percent || 0) > 0 ? `<span class="badge bg-light text-dark border ms-1" style="font-size: 0.6rem;">${item.vat_percent}% VAT</span>` : '')}</div>
</div>
<div class="d-flex align-items-center bg-light rounded px-1">
<button class="btn btn-sm text-secondary p-0" style="width: 24px;" onclick="changeQuantity(${index}, -1)"><i class="bi bi-dash"></i></button>
@ -670,7 +710,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
function processOrder(paymentTypeId, paymentTypeName) { window.processOrder = processOrder;
function processOrder(paymentTypeId, paymentTypeName) {
const orderTypeInput = document.querySelector('input[name="order_type"]:checked');
const orderType = orderTypeInput ? orderTypeInput.value : 'takeaway';
@ -764,10 +804,11 @@ document.addEventListener('DOMContentLoaded', () => {
// For now, let's keep it cleared but show the error.
}
});
};
}
window.processOrder = processOrder;
function triggerNetworkPrint(orderId, type) { window.triggerNetworkPrint = triggerNetworkPrint;
if (!orderId) return;
function triggerNetworkPrint(orderId, type) {
if (!orderId) return;
const printerIp = (type === 'kitchen') ? CURRENT_OUTLET.kitchen_printer_ip : CURRENT_OUTLET.cashier_printer_ip;
if (!printerIp) return;
@ -787,9 +828,10 @@ document.addEventListener('DOMContentLoaded', () => {
}
})
.catch(err => console.error(`Network Print Fetch Error (${type}):`, err));
};
}
window.triggerNetworkPrint = triggerNetworkPrint;
function printThermalReceipt(data) { window.printThermalReceipt = printThermalReceipt;
function printThermalReceipt(data) {
let iframe = document.getElementById('print-iframe');
if (!iframe) {
iframe = document.createElement('iframe');
@ -847,8 +889,7 @@ document.addEventListener('DOMContentLoaded', () => {
const loyaltyHtml = data.loyaltyRedeemed ? `<div style="color: #d63384; font-weight: bold; margin: 5px 0; text-align: center;">* Loyalty Reward Applied *</div>` : '';
// We skip logo in receipt for absolute speed unless it's already cached.
// If users really want the logo, we can re-enable it.
const logoHtml = ''; // settings.logo_url ? `<img src="${BASE_URL}${settings.logo_url}" style="max-height: 80px; max-width: 150px; margin-bottom: 10px;">` : '';
const logoHtml = '';
const vatRate = settings.vat_rate || 0;
@ -929,9 +970,10 @@ document.addEventListener('DOMContentLoaded', () => {
// Print immediately without waiting for resources
iframe.contentWindow.focus();
iframe.contentWindow.print();
};
}
window.printThermalReceipt = printThermalReceipt;
function openRatingQRModal() { window.openRatingQRModal = openRatingQRModal;
function openRatingQRModal() {
const qrContainer = document.getElementById('rating-qr-container');
const ratingUrl = BASE_URL + '/rate.php';
const qrCodeUrl = "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=" + encodeURIComponent(ratingUrl);
@ -940,5 +982,6 @@ document.addEventListener('DOMContentLoaded', () => {
const modal = new bootstrap.Modal(document.getElementById('qrRatingModal'));
modal.show();
};
});
}
window.openRatingQRModal = openRatingQRModal;
});

View File

@ -247,7 +247,8 @@ $vat_rate = (float)($settings['vat_rate'] ?? 0);
data-category="<?= $product['category_id'] ?>"
data-name="<?= htmlspecialchars(strtolower($product['name'])) ?>"
data-sku="<?= htmlspecialchars(strtolower($product['sku'] ?? '')) ?>"
onclick="handleProductClick(<?= htmlspecialchars(json_encode($product), ENT_QUOTES) ?>, <?= htmlspecialchars(json_encode($variants_by_product[$product['id']] ?? []), ENT_QUOTES) ?>)">
data-product='<?= htmlspecialchars(json_encode($product), ENT_QUOTES) ?>'
data-variants='<?= htmlspecialchars(json_encode($variants_by_product[$product['id']] ?? []), ENT_QUOTES) ?>'>
<div class="card h-100 border-0 shadow-sm product-card rounded-3 overflow-hidden">
<div class="card-img-container">
<?php if (!empty($product['image_url'])): ?>