827 lines
38 KiB
PHP
827 lines
38 KiB
PHP
<?php
|
|
$title = 'pos';
|
|
require_once __DIR__ . '/includes/header.php';
|
|
|
|
// Global check in header.php handles 'view' permission.
|
|
$edit_order_id = $_GET['edit_order_id'] ?? null;
|
|
if ($edit_order_id && !has_permission('edit')) {
|
|
header('Location: pos.php?error=no_edit_permission');
|
|
exit;
|
|
}
|
|
|
|
$branch_id = $_SESSION['branch_id'] ?? null;
|
|
if ($branch_id === 'all') $branch_id = null;
|
|
|
|
// Loyalty Settings
|
|
$loyalty_enabled = get_setting('loyalty_enabled', '0');
|
|
$loyalty_points_per_currency = get_setting('loyalty_points_per_currency', '1');
|
|
$loyalty_currency_per_point = get_setting('loyalty_currency_per_point', '0.05');
|
|
|
|
// Handle Edit Order
|
|
$edit_order = null;
|
|
$edit_items = [];
|
|
if ($edit_order_id) {
|
|
$stmt = db()->prepare("SELECT * FROM orders WHERE id = ?");
|
|
if ($branch_id) {
|
|
$stmt = db()->prepare("SELECT * FROM orders WHERE id = ? AND branch_id = ?");
|
|
$stmt->execute([$edit_order_id, $branch_id]);
|
|
} else {
|
|
$stmt->execute([$edit_order_id]);
|
|
}
|
|
$edit_order = $stmt->fetch();
|
|
|
|
if ($edit_order) {
|
|
$stmt = db()->prepare("SELECT oi.*, i.name_en, i.name_ar, i.vat_percent, s.name_en as service_en, s.name_ar as service_ar
|
|
FROM order_items oi
|
|
JOIN items i ON oi.item_id = i.id
|
|
JOIN services s ON oi.service_id = s.id
|
|
WHERE oi.order_id = ?");
|
|
$stmt->execute([$edit_order_id]);
|
|
$edit_items_raw = $stmt->fetchAll();
|
|
|
|
foreach ($edit_items_raw as $ei) {
|
|
$edit_items[] = [
|
|
'item_id' => $ei['item_id'],
|
|
'service_id' => $ei['service_id'],
|
|
'name' => $lang === 'en' ? $ei['name_en'] : $ei['name_ar'],
|
|
'service_name' => $lang === 'en' ? $ei['service_en'] : $ei['service_ar'],
|
|
'price' => (float)$ei['unit_price'],
|
|
'qty' => (int)$ei['quantity'],
|
|
'vat_percent' => (float)$ei['vat_percent']
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get all categories
|
|
$categories = db()->query("SELECT * FROM categories WHERE is_deleted = 0 ORDER BY name_en ASC")->fetchAll();
|
|
|
|
// Get all items with details
|
|
$stmt = db()->prepare("SELECT i.*, c.name_en as cat_en, c.name_ar as cat_ar
|
|
FROM items i
|
|
LEFT JOIN categories c ON i.category_id = c.id
|
|
WHERE i.is_deleted = 0
|
|
ORDER BY i.name_en ASC");
|
|
$stmt->execute();
|
|
$items_raw = $stmt->fetchAll();
|
|
|
|
// Get all services
|
|
$services_raw = db()->query("SELECT * FROM services WHERE is_deleted = 0 ORDER BY name_en ASC")->fetchAll();
|
|
|
|
// Get all prices (globally shared)
|
|
$stmt = db()->prepare("SELECT * FROM prices");
|
|
$stmt->execute();
|
|
$prices_raw = $stmt->fetchAll();
|
|
|
|
$prices = [];
|
|
foreach ($prices_raw as $p) {
|
|
$prices[$p['item_id']][$p['service_id']] = (float)$p['price'];
|
|
}
|
|
|
|
$items = [];
|
|
foreach ($items_raw as $i) {
|
|
$item_services = [];
|
|
foreach ($services_raw as $s) {
|
|
if (isset($prices[$i['id']][$s['id']])) {
|
|
$item_services[] = [
|
|
'id' => $s['id'],
|
|
'name_en' => $s['name_en'],
|
|
'name_ar' => $s['name_ar'],
|
|
'price' => $prices[$i['id']][$s['id']]
|
|
];
|
|
}
|
|
}
|
|
|
|
if (!empty($item_services)) {
|
|
$items[$i['id']] = [
|
|
'id' => $i['id'],
|
|
'name_en' => $i['name_en'],
|
|
'name_ar' => $i['name_ar'],
|
|
'category_id' => $i['category_id'],
|
|
'image_url' => $i['image_url'],
|
|
'vat_percent' => (float)($i['vat_percent'] ?? 15),
|
|
'services' => $item_services
|
|
];
|
|
}
|
|
}
|
|
|
|
// Get all customers
|
|
$stmt = db()->prepare("SELECT id, name_en, name_ar, phone, loyalty_points FROM customers ORDER BY name_en ASC");
|
|
$stmt->execute();
|
|
$customers = $stmt->fetchAll();
|
|
|
|
$pageTitle = $edit_order ? ($lang == 'en' ? 'Edit Order #' . $edit_order['order_number'] : 'تعديل طلب رقم ' . $edit_order['order_number']) : ($lang == 'en' ? 'Point of Sale' : 'نقطة البيع');
|
|
?>
|
|
|
|
<div class="container-fluid py-4">
|
|
<div class="row g-4">
|
|
<!-- Left: Items & Categories -->
|
|
<div class="col-lg-8">
|
|
<div class="mb-4 d-flex align-items-center justify-content-between">
|
|
<h4 class="fw-bold mb-0"><?= $pageTitle ?></h4>
|
|
<div class="input-group w-50 shadow-sm rounded-4 overflow-hidden">
|
|
<span class="input-group-text bg-white border-0"><i class="bi bi-search"></i></span>
|
|
<input type="text" id="itemSearch" class="form-control border-0 py-2 shadow-none" placeholder="<?= $lang == 'en' ? 'Search items...' : 'بحث عن المنتجات...' ?>">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Categories Scroller -->
|
|
<div class="mb-4 overflow-auto d-flex pb-2 hide-scrollbar" style="white-space: nowrap;">
|
|
<button class="btn btn-primary rounded-pill px-4 cat-filter me-2 shadow-sm" data-cat="all">
|
|
<?= $lang == 'en' ? 'All' : 'الكل' ?>
|
|
</button>
|
|
<?php foreach($categories as $cat): ?>
|
|
<button class="btn btn-white border rounded-pill px-4 cat-filter me-2 shadow-sm" data-cat="<?= $cat['id'] ?>">
|
|
<?= $lang == 'en' ? $cat['name_en'] : $cat['name_ar'] ?>
|
|
</button>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<div class="row g-3" id="itemsList">
|
|
<?php foreach($items as $item): ?>
|
|
<div class="col-6 col-md-4 col-xl-3 item-card-wrapper"
|
|
data-cat="<?= $item['category_id'] ?>"
|
|
data-name-en="<?= strtolower($item['name_en']) ?>"
|
|
data-name-ar="<?= $item['name_ar'] ?>">
|
|
<div class="card h-100 border-0 shadow-sm rounded-4 item-card pointer overflow-hidden transition-all" onclick="showOptions(<?= $item['id'] ?>)">
|
|
<div class="position-relative bg-light" style="height: 160px; border-radius: 1rem 1rem 0 0; overflow: hidden;">
|
|
<?php if($item['image_url']): ?>
|
|
<img src="<?= $item['image_url'] ?>?v=<?= time() ?>" class="w-100 h-100" style="object-fit: contain; padding: 10px;">
|
|
<?php else: ?>
|
|
<div class="w-100 h-100 d-flex align-items-center justify-content-center opacity-25">
|
|
<i class="bi bi-box" style="font-size: 3rem;"></i>
|
|
</div>
|
|
<?php endif; ?>
|
|
<div class="position-absolute top-0 end-0 m-2">
|
|
<span class="badge bg-white text-dark shadow-sm rounded-pill px-2 py-1 small" style="font-size: 0.65rem;">
|
|
<?= $item['vat_percent'] ?>% VAT
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-3 text-center">
|
|
<h6 class="card-title mb-1 text-truncate fw-bold small"><?= $lang == 'en' ? $item['name_en'] : $item['name_ar'] ?></h6>
|
|
<p class="small text-muted mb-0" style="font-size: 0.75rem;"><?= count($item['services']) ?> <?= $lang == 'en' ? 'Services' : 'خدمات' ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right: Cart -->
|
|
<div class="col-lg-4">
|
|
<div class="card border-0 shadow-sm rounded-4 h-100 d-flex flex-column" style="min-height: 80vh;">
|
|
<div class="card-header bg-white py-3 border-0">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0 fw-bold"><?= $lang == 'en' ? 'Current Order' : 'الطلب الحالي' ?></h5>
|
|
<button class="btn btn-sm btn-outline-danger rounded-3 border-0" onclick="clearCart()">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="px-3 pb-3">
|
|
<div class="d-flex gap-2 mb-2">
|
|
<!-- Persistent Customer Search -->
|
|
<div class="position-relative flex-grow-1" id="customerSearchWrapper">
|
|
<div class="input-group shadow-sm rounded-3 overflow-hidden border">
|
|
<span class="input-group-text bg-white border-0"><i class="bi bi-person"></i></span>
|
|
<input type="text" class="form-control border-0 py-2 shadow-none" id="customerSearchInput" placeholder="<?= $lang == 'en' ? 'Walk-in Customer' : 'عميل عابر' ?>" autocomplete="off">
|
|
<button class="btn btn-white border-0 d-none" type="button" id="clearCustomerBtn">
|
|
<i class="bi bi-x-circle-fill text-muted"></i>
|
|
</button>
|
|
</div>
|
|
<div class="position-absolute w-100 shadow-lg bg-white rounded-3 mt-1 d-none" id="customerResults" style="z-index: 1050; max-height: 300px; overflow-y: auto; border: 1px solid #eee;">
|
|
<div class="p-2 border-bottom">
|
|
<button class="btn btn-light btn-sm w-100 text-start rounded-2 customer-result-item" data-id="" data-name="<?= $lang == 'en' ? 'Walk-in Customer' : 'عميل عابر' ?>" data-search="walk-in" data-points="0">
|
|
<?= $lang == 'en' ? 'Walk-in Customer' : 'عميل عابر' ?>
|
|
</button>
|
|
</div>
|
|
<div id="customerResultsList">
|
|
<?php foreach($customers as $c):
|
|
$cname = ($lang == 'en' ? $c['name_en'] : $c['name_ar']) ?: $c['name_en'];
|
|
$searchStr = strtolower($c['name_en'] . ' ' . ($c['name_ar'] ?? '') . ' ' . $c['phone']);
|
|
?>
|
|
<div class="p-1">
|
|
<button class="btn btn-white btn-sm w-100 text-start rounded-2 customer-result-item p-2" type="button" data-id="<?= $c['id'] ?>" data-name="<?= $cname ?>" data-phone="<?= $c['phone'] ?>" data-search="<?= $searchStr ?>" data-points="<?= $c['loyalty_points'] ?>">
|
|
<div class="fw-bold small text-dark"><?= $cname ?></div>
|
|
<div class="text-muted small" style="font-size: 0.7rem;"><?= $c['phone'] ?></div>
|
|
</button>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
<input type="hidden" id="customerId" value="<?= $edit_order ? $edit_order['customer_id'] : '' ?>">
|
|
</div>
|
|
|
|
<?php if (has_permission('add', 'customers.php')): ?>
|
|
<button class="btn btn-primary rounded-3" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
|
|
<i class="bi bi-plus-lg"></i>
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- Loyalty Info Display -->
|
|
<div id="loyaltyDisplay" class="d-none bg-primary bg-opacity-10 rounded-3 p-2 mb-2 d-flex align-items-center justify-content-between border border-primary border-opacity-25">
|
|
<div class="d-flex align-items-center">
|
|
<i class="bi bi-star-fill text-primary me-2"></i>
|
|
<span class="small fw-bold text-primary"><?= $lang == 'en' ? 'Loyalty Points' : 'نقاط الولاء' ?></span>
|
|
</div>
|
|
<span class="badge bg-primary rounded-pill" id="customerPoints">0</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body p-0 flex-grow-1 overflow-auto" id="cartContainer">
|
|
<div id="cartItems" class="p-3"></div>
|
|
<div id="emptyCart" class="text-center py-5 opacity-50">
|
|
<i class="bi bi-cart3 display-1 d-block mb-3"></i>
|
|
<p><?= $lang == 'en' ? 'Your cart is empty' : 'السلة فارغة' ?></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-footer bg-white border-top-0 p-3">
|
|
<div class="bg-light rounded-4 p-3 mb-3">
|
|
<div class="d-flex justify-content-between mb-2 small text-muted">
|
|
<span><?= $lang == 'en' ? 'Subtotal' : 'المجموع الفرعي' ?></span>
|
|
<span id="cartSubtotal">0.000 <?= currency() ?></span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2 small text-muted">
|
|
<span><?= $lang == 'en' ? 'VAT' : 'الضريبة' ?></span>
|
|
<span id="cartVat">0.000 <?= currency() ?></span>
|
|
</div>
|
|
|
|
<!-- Loyalty Discount Display -->
|
|
<div id="loyaltyDiscountRow" class="d-none d-flex justify-content-between mb-2 small text-success fw-bold">
|
|
<span><?= $lang == 'en' ? 'Loyalty Discount' : 'خصم الولاء' ?></span>
|
|
<span id="cartLoyaltyDiscount">-0.000 <?= currency() ?></span>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-between pt-2 border-top border-secondary border-opacity-10 mt-2">
|
|
<h5 class="mb-0 fw-bold"><?= $lang == 'en' ? 'Total' : 'الإجمالي' ?></h5>
|
|
<h5 id="cartTotal" class="mb-0 fw-bold text-primary">0.000 <?= currency() ?></h5>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loyalty Redemption UI -->
|
|
<?php if ($loyalty_enabled == '1'): ?>
|
|
<div id="loyaltyRedeemUI" class="d-none mb-3">
|
|
<div class="input-group shadow-sm rounded-3 overflow-hidden border">
|
|
<span class="input-group-text bg-white border-0 small"><i class="bi bi-star text-primary"></i></span>
|
|
<input type="number" id="pointsToRedeem" class="form-control border-0 py-2 shadow-none small" placeholder="<?= $lang == 'en' ? 'Redeem Points' : 'استبدال النقاط' ?>" min="0">
|
|
<button class="btn btn-primary btn-sm px-3" onclick="applyPoints()">
|
|
<?= $lang == 'en' ? 'Apply' : 'تطبيق' ?>
|
|
</button>
|
|
</div>
|
|
<div id="redeemHint" class="small text-muted mt-1" style="font-size: 0.7rem;"></div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (($edit_order && has_permission('edit')) || (!$edit_order && has_permission('add'))): ?>
|
|
<button class="btn btn-primary w-100 py-3 rounded-4 fw-bold shadow-sm" onclick="checkout()">
|
|
<i class="bi bi-credit-card me-2"></i> <?= $edit_order ? ($lang == 'en' ? 'Update Order' : 'تحديث الطلب') : ($lang == 'en' ? 'Complete Order' : 'إتمام الطلب') ?>
|
|
</button>
|
|
<?php else: ?>
|
|
<button class="btn btn-secondary w-100 py-3 rounded-4 fw-bold shadow-sm" disabled>
|
|
<i class="bi bi-lock me-2"></i> <?= $lang == 'en' ? 'No Permission' : 'لا تملك صلاحية' ?>
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Selection Modal -->
|
|
<div class="modal fade" id="selectionModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content border-0 rounded-4 shadow-lg">
|
|
<div class="modal-header border-0 pb-0">
|
|
<h5 class="modal-title fw-bold" id="selectionItemName"></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body p-4">
|
|
<p class="text-muted small mb-3"><?= $lang == 'en' ? 'Select service type:' : 'اختر نوع الخدمة:' ?></p>
|
|
<div class="row g-2" id="optionsList"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Customer Modal -->
|
|
<div class="modal fade" id="addCustomerModal" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content border-0 rounded-4 shadow-lg">
|
|
<div class="modal-header border-0 pb-0 p-4">
|
|
<h5 class="modal-title fw-bold"><?= $lang == 'en' ? 'Add New Customer' : 'إضافة عميل جديد' ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body p-4">
|
|
<form id="addCustomerForm">
|
|
<div class="mb-3">
|
|
<label class="form-label small"><?= $lang == 'en' ? 'Name (English)' : 'الاسم (إنجليزي)' ?></label>
|
|
<input type="text" name="name_en" class="form-control rounded-3" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small"><?= $lang == 'en' ? 'Name (Arabic)' : 'الاسم (عربي)' ?></label>
|
|
<input type="text" name="name_ar" class="form-control rounded-3">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small"><?= $lang == 'en' ? 'Phone Number' : 'رقم الجوال' ?></label>
|
|
<input type="tel" name="phone" class="form-control rounded-3" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small"><?= $lang == 'en' ? 'Email (Optional)' : 'البريد (اختياري)' ?></label>
|
|
<input type="email" name="email" class="form-control rounded-3">
|
|
</div>
|
|
<button type="button" class="btn btn-primary w-100 py-2 rounded-3" onclick="saveCustomer()">
|
|
<?= $lang == 'en' ? 'Save Customer' : 'حفظ العميل' ?>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Payment Modal -->
|
|
<div class="modal fade" id="paymentModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content border-0 rounded-4 shadow-lg">
|
|
<div class="modal-header border-0 pb-0">
|
|
<h5 class="modal-title fw-bold"><?= $lang == 'en' ? 'Payment' : 'الدفع' ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body p-4">
|
|
<div class="text-center mb-4">
|
|
<h3 class="fw-bold text-primary mb-1" id="paymentTotalAmount">0.000</h3>
|
|
<div class="text-muted small"><?= $lang == 'en' ? 'Total Amount' : 'المبلغ الإجمالي' ?></div>
|
|
</div>
|
|
|
|
<div class="row g-3">
|
|
<div class="col-6">
|
|
<button class="btn btn-outline-primary w-100 py-3 rounded-3 d-flex flex-column align-items-center" onclick="completeCheckout('cash')">
|
|
<i class="bi bi-cash-stack mb-1 fs-4"></i>
|
|
<span class="small fw-bold"><?= $lang == 'en' ? 'Cash' : 'نقداً' ?></span>
|
|
</button>
|
|
</div>
|
|
<div class="col-6">
|
|
<button class="btn btn-outline-primary w-100 py-3 rounded-3 d-flex flex-column align-items-center" onclick="completeCheckout('card')">
|
|
<i class="bi bi-credit-card mb-1 fs-4"></i>
|
|
<span class="small fw-bold"><?= $lang == 'en' ? 'Card' : 'بطاقة' ?></span>
|
|
</button>
|
|
</div>
|
|
<div class="col-6">
|
|
<button class="btn btn-outline-primary w-100 py-3 rounded-3 d-flex flex-column align-items-center" onclick="completeCheckout('transfer')">
|
|
<i class="bi bi-arrow-left-right mb-1 fs-4"></i>
|
|
<span class="small fw-bold"><?= $lang == 'en' ? 'Transfer' : 'تحويل' ?></span>
|
|
</button>
|
|
</div>
|
|
<div class="col-6">
|
|
<button class="btn btn-outline-secondary w-100 py-3 rounded-3 d-flex flex-column align-items-center" onclick="completeCheckout('pay_later')">
|
|
<i class="bi bi-clock-history mb-1 fs-4"></i>
|
|
<span class="small fw-bold"><?= $lang == 'en' ? 'Pay Later' : 'الدفع لاحقاً' ?></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hidden Iframe for Printing -->
|
|
|
|
|
|
<script>
|
|
const itemsData = <?= json_encode((object)$items) ?>;
|
|
const lang = '<?= $lang ?>';
|
|
const currencyLabel = '<?= currency() ?>';
|
|
const decimalPrecision = <?= decimals() ?>;
|
|
const editOrderId = <?= $edit_order_id ?: 'null' ?>;
|
|
const loyaltyEnabled = <?= $loyalty_enabled ?>;
|
|
const pointsPerCurrency = <?= $loyalty_points_per_currency ?>;
|
|
const currencyPerPoint = <?= $loyalty_currency_per_point ?>;
|
|
|
|
let cart = <?= json_encode($edit_items) ?>;
|
|
let selectionModal;
|
|
let paymentModal;
|
|
let customerLoyaltyPoints = 0;
|
|
let pointsToRedeem = 0;
|
|
|
|
// If not editing, try to load from local storage
|
|
if (!editOrderId) {
|
|
try {
|
|
const saved = localStorage.getItem('pos_cart');
|
|
if (saved) {
|
|
const parsed = JSON.parse(saved);
|
|
if (Array.isArray(parsed) && parsed.length > 0) cart = parsed;
|
|
}
|
|
} catch (e) { console.error('Cart parse error', e); }
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
if (typeof bootstrap !== 'undefined') {
|
|
const modalEl = document.getElementById('selectionModal');
|
|
if (modalEl) selectionModal = new bootstrap.Modal(modalEl);
|
|
|
|
const payModalEl = document.getElementById('paymentModal');
|
|
if (payModalEl) paymentModal = new bootstrap.Modal(payModalEl);
|
|
}
|
|
|
|
// Category filtering
|
|
document.querySelectorAll('.cat-filter').forEach(btn => {
|
|
btn.onclick = () => {
|
|
const cat = btn.getAttribute('data-cat');
|
|
document.querySelectorAll('.cat-filter').forEach(b => b.classList.remove('btn-primary'));
|
|
document.querySelectorAll('.cat-filter').forEach(b => b.classList.add('btn-white', 'border'));
|
|
btn.classList.add('btn-primary');
|
|
btn.classList.remove('btn-white', 'border');
|
|
|
|
document.querySelectorAll('.item-card-wrapper').forEach(card => {
|
|
if (cat === 'all' || card.getAttribute('data-cat') === cat) card.style.display = 'block';
|
|
else card.style.display = 'none';
|
|
});
|
|
};
|
|
});
|
|
|
|
// Search
|
|
const searchInput = document.getElementById('itemSearch');
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', function(e) {
|
|
const q = e.target.value.toLowerCase();
|
|
document.querySelectorAll('.item-card-wrapper').forEach(card => {
|
|
const en = card.getAttribute('data-name-en') || '';
|
|
const ar = card.getAttribute('data-name-ar') || '';
|
|
if (en.includes(q) || ar.includes(q)) card.style.display = 'block';
|
|
else card.style.display = 'none';
|
|
});
|
|
});
|
|
}
|
|
|
|
// Persistent Customer Search
|
|
const custSearchInput = document.getElementById('customerSearchInput');
|
|
const custResults = document.getElementById('customerResults');
|
|
const clearCustBtn = document.getElementById('clearCustomerBtn');
|
|
const custIdInput = document.getElementById('customerId');
|
|
|
|
// Handle Edit Customer Pre-fill
|
|
<?php if ($edit_order && $edit_order['customer_id']): ?>
|
|
const initialCustId = "<?= $edit_order['customer_id'] ?>";
|
|
const initialCustBtn = document.querySelector(`.customer-result-item[data-id="${initialCustId}"]`);
|
|
if (initialCustBtn) {
|
|
selectCustomer(initialCustId, initialCustBtn.getAttribute('data-name'), initialCustBtn.getAttribute('data-points'));
|
|
}
|
|
<?php endif; ?>
|
|
|
|
if (custSearchInput) {
|
|
custSearchInput.addEventListener('focus', () => {
|
|
custResults.classList.remove('d-none');
|
|
});
|
|
|
|
custSearchInput.addEventListener('input', function(e) {
|
|
const q = e.target.value.toLowerCase();
|
|
custResults.classList.remove('d-none');
|
|
document.querySelectorAll('.customer-result-item').forEach(item => {
|
|
const search = item.getAttribute('data-search') || '';
|
|
if (search.includes(q)) {
|
|
item.parentElement.style.display = 'block';
|
|
} else {
|
|
item.parentElement.style.display = 'none';
|
|
}
|
|
});
|
|
});
|
|
|
|
document.addEventListener('click', function(e) {
|
|
if (!document.getElementById('customerSearchWrapper').contains(e.target)) {
|
|
custResults.classList.add('d-none');
|
|
}
|
|
});
|
|
}
|
|
|
|
if (clearCustBtn) {
|
|
clearCustBtn.addEventListener('click', () => {
|
|
resetCustomerSelection();
|
|
});
|
|
}
|
|
|
|
custResults.addEventListener('click', function(e) {
|
|
const btn = e.target.closest('.customer-result-item');
|
|
if (btn) {
|
|
const id = btn.getAttribute('data-id');
|
|
const name = btn.getAttribute('data-name');
|
|
const points = btn.getAttribute('data-points') || 0;
|
|
selectCustomer(id, name, points);
|
|
custResults.classList.add('d-none');
|
|
}
|
|
});
|
|
|
|
updateCart();
|
|
});
|
|
|
|
function selectCustomer(id, name, points) {
|
|
const custIdInput = document.getElementById('customerId');
|
|
const custSearchInput = document.getElementById('customerSearchInput');
|
|
const clearCustBtn = document.getElementById('clearCustomerBtn');
|
|
const loyaltyDisplay = document.getElementById('loyaltyDisplay');
|
|
const loyaltyRedeemUI = document.getElementById('loyaltyRedeemUI');
|
|
const customerPointsEl = document.getElementById('customerPoints');
|
|
const redeemHint = document.getElementById('redeemHint');
|
|
|
|
custIdInput.value = id;
|
|
customerLoyaltyPoints = parseFloat(points);
|
|
|
|
if (id) {
|
|
custSearchInput.value = name;
|
|
clearCustBtn.classList.remove('d-none');
|
|
if (loyaltyEnabled) {
|
|
loyaltyDisplay.classList.remove('d-none');
|
|
loyaltyRedeemUI.classList.remove('d-none');
|
|
customerPointsEl.innerText = customerLoyaltyPoints.toFixed(2);
|
|
if (redeemHint) {
|
|
redeemHint.innerText = lang === 'en'
|
|
? `1 Point = ${currencyPerPoint} ${currencyLabel}`
|
|
: `1 نقطة = ${currencyPerPoint} ${currencyLabel}`;
|
|
}
|
|
}
|
|
} else {
|
|
resetCustomerSelection();
|
|
}
|
|
}
|
|
|
|
function resetCustomerSelection() {
|
|
const custIdInput = document.getElementById('customerId');
|
|
const custSearchInput = document.getElementById('customerSearchInput');
|
|
const clearCustBtn = document.getElementById('clearCustomerBtn');
|
|
const loyaltyDisplay = document.getElementById('loyaltyDisplay');
|
|
const loyaltyRedeemUI = document.getElementById('loyaltyRedeemUI');
|
|
|
|
custIdInput.value = '';
|
|
custSearchInput.value = '';
|
|
custSearchInput.placeholder = lang === 'en' ? 'Walk-in Customer' : 'عميل عابر';
|
|
clearCustBtn.classList.add('d-none');
|
|
loyaltyDisplay.classList.add('d-none');
|
|
loyaltyRedeemUI.classList.add('d-none');
|
|
customerLoyaltyPoints = 0;
|
|
pointsToRedeem = 0;
|
|
const ptsInput = document.getElementById('pointsToRedeem');
|
|
if (ptsInput) ptsInput.value = '';
|
|
updateCart();
|
|
}
|
|
|
|
function applyPoints() {
|
|
const ptsInput = document.getElementById('pointsToRedeem');
|
|
let val = parseFloat(ptsInput.value) || 0;
|
|
if (val > customerLoyaltyPoints) {
|
|
alert(lang === 'en' ? 'Insufficient points' : 'نقاط غير كافية');
|
|
val = customerLoyaltyPoints;
|
|
ptsInput.value = val;
|
|
}
|
|
pointsToRedeem = val;
|
|
updateCart();
|
|
}
|
|
|
|
function showOptions(itemId) {
|
|
const item = itemsData[itemId];
|
|
if (!item) return;
|
|
const itemNameEl = document.getElementById('selectionItemName');
|
|
if (itemNameEl) itemNameEl.innerText = lang === 'en' ? item.name_en : item.name_ar;
|
|
const list = document.getElementById('optionsList');
|
|
if (list) {
|
|
list.innerHTML = '';
|
|
item.services.forEach(s => {
|
|
const col = document.createElement('div');
|
|
col.className = 'col-6';
|
|
col.innerHTML = `
|
|
<button class="btn btn-outline-primary w-100 p-3 rounded-4 border-2 text-center h-100 transition-all" onclick="addToCart(${item.id}, ${s.id})">
|
|
<div class="fw-bold mb-1 small">${lang === 'en' ? s.name_en : s.name_ar}</div>
|
|
<div class="small opacity-75">${s.price.toFixed(decimalPrecision)} ${currencyLabel}</div>
|
|
</button>
|
|
`;
|
|
list.appendChild(col);
|
|
});
|
|
}
|
|
if (selectionModal) selectionModal.show();
|
|
}
|
|
|
|
function addToCart(itemId, serviceId) {
|
|
const item = itemsData[itemId];
|
|
if (!item) return;
|
|
const service = item.services.find(s => s.id === serviceId);
|
|
if (!service) return;
|
|
|
|
const existing = cart.find(i => i.item_id === itemId && i.service_id === serviceId);
|
|
if (existing) {
|
|
existing.qty++;
|
|
} else {
|
|
cart.push({
|
|
item_id: itemId,
|
|
service_id: serviceId,
|
|
name: lang === 'en' ? item.name_en : item.name_ar,
|
|
service_name: lang === 'en' ? service.name_en : service.name_ar,
|
|
price: service.price,
|
|
qty: 1,
|
|
vat_percent: item.vat_percent
|
|
});
|
|
}
|
|
if (selectionModal) selectionModal.hide();
|
|
updateCart();
|
|
}
|
|
|
|
function changeQty(index, delta) {
|
|
cart[index].qty += delta;
|
|
if (cart[index].qty <= 0) cart.splice(index, 1);
|
|
updateCart();
|
|
}
|
|
|
|
function updateCart() {
|
|
if (!editOrderId) {
|
|
localStorage.setItem('pos_cart', JSON.stringify(cart));
|
|
}
|
|
const cartList = document.getElementById('cartItems');
|
|
const emptyCart = document.getElementById('emptyCart');
|
|
if (!cartList || !emptyCart) return;
|
|
|
|
if (cart.length === 0) {
|
|
cartList.innerHTML = '';
|
|
emptyCart.style.display = 'block';
|
|
} else {
|
|
emptyCart.style.display = 'none';
|
|
cartList.innerHTML = cart.map((item, index) => `
|
|
<div class="d-flex align-items-center mb-3 bg-light p-2 rounded-3">
|
|
<div class="flex-grow-1">
|
|
<div class="fw-bold small text-dark">${item.name}</div>
|
|
<div class="text-muted" style="font-size: 0.75rem;">${item.service_name}</div>
|
|
<div class="fw-bold text-primary">${item.price.toFixed(decimalPrecision)} ${currencyLabel}</div>
|
|
</div>
|
|
<div class="d-flex align-items-center bg-white rounded-3 p-1">
|
|
<button class="btn btn-sm p-0 px-2" onclick="changeQty(${index}, -1)"><i class="bi bi-dash"></i></button>
|
|
<span class="mx-2 fw-bold">${item.qty}</span>
|
|
<button class="btn btn-sm p-0 px-2" onclick="changeQty(${index}, 1)"><i class="bi bi-plus"></i></button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
let subtotal = 0;
|
|
let totalVat = 0;
|
|
|
|
cart.forEach(item => {
|
|
const itemSubtotal = item.price * item.qty;
|
|
subtotal += itemSubtotal;
|
|
totalVat += itemSubtotal * ((item.vat_percent || 15) / 100);
|
|
});
|
|
|
|
const subtotalEl = document.getElementById('cartSubtotal');
|
|
const vatEl = document.getElementById('cartVat');
|
|
const totalEl = document.getElementById('cartTotal');
|
|
const discountRow = document.getElementById('loyaltyDiscountRow');
|
|
const discountEl = document.getElementById('cartLoyaltyDiscount');
|
|
|
|
let totalBeforeDiscount = subtotal + totalVat;
|
|
let loyaltyDiscount = pointsToRedeem * currencyPerPoint;
|
|
|
|
if (loyaltyDiscount > totalBeforeDiscount) {
|
|
loyaltyDiscount = totalBeforeDiscount;
|
|
pointsToRedeem = loyaltyDiscount / currencyPerPoint;
|
|
}
|
|
|
|
if (loyaltyDiscount > 0) {
|
|
discountRow.classList.remove('d-none');
|
|
discountEl.innerText = '-' + loyaltyDiscount.toFixed(decimalPrecision) + ' ' + currencyLabel;
|
|
} else {
|
|
discountRow.classList.add('d-none');
|
|
}
|
|
|
|
const finalTotal = totalBeforeDiscount - loyaltyDiscount;
|
|
|
|
if (subtotalEl) subtotalEl.innerText = subtotal.toFixed(decimalPrecision) + ' ' + currencyLabel;
|
|
if (vatEl) vatEl.innerText = totalVat.toFixed(decimalPrecision) + ' ' + currencyLabel;
|
|
if (totalEl) totalEl.innerText = finalTotal.toFixed(decimalPrecision) + ' ' + currencyLabel;
|
|
}
|
|
|
|
function clearCart() {
|
|
if (confirm(lang === 'en' ? 'Clear cart?' : 'مسح السلة؟')) {
|
|
cart = [];
|
|
pointsToRedeem = 0;
|
|
const ptsInput = document.getElementById('pointsToRedeem');
|
|
if (ptsInput) ptsInput.value = '';
|
|
updateCart();
|
|
}
|
|
}
|
|
|
|
function checkout() {
|
|
if (cart.length === 0) return;
|
|
|
|
let subtotal = 0;
|
|
let totalVat = 0;
|
|
cart.forEach(item => {
|
|
const itemSubtotal = item.price * item.qty;
|
|
subtotal += itemSubtotal;
|
|
totalVat += itemSubtotal * ((item.vat_percent || 15) / 100);
|
|
});
|
|
|
|
let finalTotal = (subtotal + totalVat) - (pointsToRedeem * currencyPerPoint);
|
|
if (finalTotal < 0) finalTotal = 0;
|
|
|
|
document.getElementById('paymentTotalAmount').innerText = finalTotal.toFixed(decimalPrecision) + ' ' + currencyLabel;
|
|
if (paymentModal) paymentModal.show();
|
|
}
|
|
|
|
async function completeCheckout(paymentMethod) {
|
|
const cid = document.getElementById('customerId').value;
|
|
|
|
let subtotal = 0;
|
|
let totalVat = 0;
|
|
const itemsToSubmit = cart.map(item => {
|
|
const itemSubtotal = item.price * item.qty;
|
|
const itemVat = itemSubtotal * ((item.vat_percent || 15) / 100);
|
|
subtotal += itemSubtotal;
|
|
totalVat += itemVat;
|
|
return {
|
|
itemId: item.item_id,
|
|
serviceId: item.service_id,
|
|
variantId: null,
|
|
quantity: item.qty,
|
|
price: item.price,
|
|
vatAmount: itemVat
|
|
};
|
|
});
|
|
|
|
const totalPrice = subtotal + totalVat;
|
|
|
|
try {
|
|
const response = await fetch('api/checkout.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
order_id: editOrderId,
|
|
customer_id: cid,
|
|
items: itemsToSubmit,
|
|
vat_total: totalVat,
|
|
total_price: totalPrice,
|
|
payment_method: paymentMethod,
|
|
points_to_redeem: pointsToRedeem
|
|
})
|
|
});
|
|
const res = await response.json();
|
|
if (res.success) {
|
|
const orderId = res.order_id || editOrderId;
|
|
if (!editOrderId) { cart = []; updateCart(); localStorage.removeItem("pos_cart"); } window.location.href = "receipt.php?id=" + orderId;
|
|
} else alert(res.error);
|
|
} catch (e) { alert('Error'); }
|
|
}
|
|
|
|
|
|
async function saveCustomer() {
|
|
const form = document.getElementById('addCustomerForm');
|
|
if (!form) return;
|
|
const data = new FormData(form);
|
|
try {
|
|
const response = await fetch('api/add_customer.php', { method: 'POST', body: data });
|
|
const res = await response.json();
|
|
if (res.success) {
|
|
const id = res.customer.id;
|
|
const nameEn = res.customer.name_en;
|
|
const nameAr = res.customer.name_ar || nameEn;
|
|
const phone = res.customer.phone;
|
|
const displayName = lang === 'en' ? nameEn : nameAr;
|
|
|
|
selectCustomer(id, displayName, 0);
|
|
|
|
const list = document.getElementById('customerResultsList');
|
|
if (list) {
|
|
const searchStr = `${nameEn} ${nameAr} ${phone}`.toLowerCase();
|
|
const div = document.createElement('div');
|
|
div.className = 'p-1';
|
|
div.innerHTML = `
|
|
<button class="btn btn-white btn-sm w-100 text-start rounded-2 customer-result-item p-2" type="button" data-id="${id}" data-name="${displayName}" data-phone="${phone}" data-search="${searchStr}" data-points="0">
|
|
<div class="fw-bold small text-dark">${displayName}</div>
|
|
<div class="text-muted small" style="font-size: 0.7rem;">${phone}</div>
|
|
</button>
|
|
`;
|
|
list.prepend(div);
|
|
}
|
|
|
|
if (typeof bootstrap !== 'undefined') {
|
|
const modalEl = document.getElementById('addCustomerModal');
|
|
if (modalEl) {
|
|
const modal = bootstrap.Modal.getInstance(modalEl);
|
|
if (modal) modal.hide();
|
|
}
|
|
}
|
|
form.reset();
|
|
} else alert(res.error);
|
|
} catch (e) { alert('Error'); }
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.pointer { cursor: pointer; }
|
|
.item-card:hover { transform: translateY(-5px); }
|
|
.transition-all { transition: all 0.3s ease; }
|
|
.hide-scrollbar::-webkit-scrollbar { display: none; }
|
|
.hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
|
.btn-white { background: white; }
|
|
.customer-result-item:hover { background-color: #f8f9fa; }
|
|
</style>
|
|
|
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|