1188 lines
44 KiB
PHP
1188 lines
44 KiB
PHP
<?php
|
|
$saleMode = 'pos';
|
|
require_once __DIR__ . '/includes/app.php';
|
|
$user = require_permission('pos', 'show');
|
|
$pageTitle = tr('نقاط البيع', 'Smart POS');
|
|
$activeNav = 'pos';
|
|
$error = '';
|
|
$paymentAmountInput = (string) ($_POST['payment_amount'] ?? '');
|
|
$submitAction = trim((string) ($_POST['submit_action'] ?? 'save_print'));
|
|
if (!in_array($submitAction, ['save', 'save_print'], true)) {
|
|
$submitAction = 'save_print';
|
|
}
|
|
$catalog = catalog();
|
|
$allowedBranches = get_user_branches($user);
|
|
|
|
try {
|
|
$pdo = db();
|
|
$categories = $pdo->query('SELECT id, name_ar, name_en FROM categories ORDER BY name_ar ASC')->fetchAll();
|
|
$customers = $pdo->query('SELECT id, name, phone FROM customers ORDER BY name ASC')->fetchAll();
|
|
} catch (Throwable $e) {
|
|
$categories = [];
|
|
$customers = [];
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$branchCode = trim((string) ($_POST['branch_code'] ?? ''));
|
|
$customerId = isset($_POST['customer_id']) && $_POST['customer_id'] !== '' ? (int)$_POST['customer_id'] : null;
|
|
$customerName = trim((string) ($_POST['customer_name'] ?? ''));
|
|
$paymentMethod = trim((string) ($_POST['payment_method'] ?? 'cash'));
|
|
$paymentAmountInput = trim((string) ($_POST['payment_amount'] ?? ''));
|
|
$notes = trim((string) ($_POST['notes'] ?? ''));
|
|
$cartJson = (string) ($_POST['cart_json'] ?? '[]');
|
|
$items = json_decode($cartJson, true);
|
|
|
|
if (!in_array($branchCode, $allowedBranches, true)) {
|
|
$error = tr('اختر فرعاً صالحاً لهذه الصلاحية.', 'Choose a valid branch for this role.');
|
|
} elseif (!in_array($paymentMethod, ['cash', 'card', 'transfer', 'pay_later'], true)) {
|
|
$error = tr('اختر طريقة دفع صحيحة.', 'Choose a valid payment method.');
|
|
} elseif (!is_array($items) || $items === []) {
|
|
$error = tr('أضف صنفاً واحداً على الأقل إلى السلة.', 'Add at least one item to the cart.');
|
|
} else {
|
|
$normalized = [];
|
|
$subtotal = 0.0;
|
|
$totalVat = 0.0;
|
|
$itemCount = 0;
|
|
foreach ($items as $item) {
|
|
$sku = (string) ($item['sku'] ?? '');
|
|
$qty = (int) ($item['qty'] ?? 0);
|
|
if (!isset($catalog[$sku]) || $qty < 1) {
|
|
continue;
|
|
}
|
|
$product = $catalog[$sku];
|
|
$price = (float) $product['price'];
|
|
$lineTotal = $price * $qty;
|
|
$normalized[] = [
|
|
'sku' => $sku,
|
|
'name_ar' => $product['name_ar'],
|
|
'name_en' => $product['name_en'],
|
|
'qty' => $qty,
|
|
'price' => $price,
|
|
'line_total' => $lineTotal,
|
|
];
|
|
$subtotal += $lineTotal;
|
|
$vatPercent = (float) ($product['vat'] ?? 0);
|
|
$itemVat = $lineTotal * ($vatPercent / 100);
|
|
$totalVat += $itemVat;
|
|
$itemCount += $qty;
|
|
}
|
|
|
|
if ($normalized === []) {
|
|
$error = tr('السلة غير صالحة بعد التحقق من الأصناف.', 'The cart is invalid after product validation.');
|
|
} else {
|
|
$totalAmount = $subtotal + $totalVat;
|
|
if ($paymentAmountInput !== '' && !is_numeric($paymentAmountInput)) {
|
|
$error = tr('أدخل مبلغاً مدفوعاً صحيحاً.', 'Enter a valid paid amount.');
|
|
} else {
|
|
$paymentMeta = sale_payment_breakdown($totalAmount, $paymentMethod, $paymentAmountInput);
|
|
if ($paymentMeta['due_amount'] > 0.0005 && !$customerId) {
|
|
$error = tr('يجب اختيار عميل مسجل عند وجود مبلغ متبقٍ أو دفعة جزئية.', 'Select a registered customer when there is a remaining balance or partial payment.');
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($error === '') {
|
|
$cashierName = current_lang() === 'ar' ? $user['name_ar'] : $user['name_en'];
|
|
$saleId = create_sale([
|
|
'sale_mode' => $saleMode,
|
|
'branch_code' => $branchCode,
|
|
'cashier_username' => $user['username'],
|
|
'cashier_name' => $cashierName,
|
|
'role_name' => $user['role'],
|
|
'customer_id' => $customerId,
|
|
'customer_name' => $customerName !== '' ? $customerName : null,
|
|
'payment_method' => $paymentMethod,
|
|
'payment_status' => $paymentMeta['payment_status'],
|
|
'paid_amount' => $paymentMeta['paid_amount'],
|
|
'due_amount' => $paymentMeta['due_amount'],
|
|
'items' => $normalized,
|
|
'item_count' => $itemCount,
|
|
'subtotal' => $subtotal,
|
|
'vat_amount' => $totalVat,
|
|
'total_amount' => $totalAmount,
|
|
'notes' => $notes !== '' ? $notes : null,
|
|
]);
|
|
|
|
wablas_notify_sale_invoice($saleId);
|
|
|
|
set_flash('success', tr('تم حفظ عملية POS بنجاح.', 'POS sale saved successfully.'));
|
|
if ($submitAction === 'save') {
|
|
redirect_to('pos.php');
|
|
}
|
|
redirect_to('print_receipt.php', ['id' => $saleId, 'print' => 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
require __DIR__ . '/includes/header.php';
|
|
?>
|
|
|
|
<style>
|
|
/* Modern POS Styles */
|
|
.pos-wrapper {
|
|
height: calc(100vh - 140px); /* Adjust based on your header */
|
|
min-height: 600px;
|
|
display: flex;
|
|
gap: 1.5rem;
|
|
}
|
|
.pos-products-area {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
.pos-cart-area {
|
|
width: 380px;
|
|
background: #fff;
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.05);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Categories Scroll */
|
|
.cat-scroll-container {
|
|
display: flex;
|
|
gap: 0.75rem;
|
|
overflow-x: auto;
|
|
padding-bottom: 0.5rem;
|
|
scrollbar-width: none; /* Firefox */
|
|
}
|
|
.cat-scroll-container::-webkit-scrollbar {
|
|
display: none; /* Chrome */
|
|
}
|
|
.cat-btn {
|
|
white-space: nowrap;
|
|
border-radius: 20px;
|
|
padding: 0.5rem 1.25rem;
|
|
font-weight: 500;
|
|
transition: all 0.2s;
|
|
background: #fff;
|
|
border: 1px solid #e9ecef;
|
|
color: #495057;
|
|
cursor: pointer;
|
|
}
|
|
.cat-btn:hover {
|
|
background: #f8f9fa;
|
|
}
|
|
.cat-btn.active {
|
|
background: linear-gradient(90deg, #0d6efd, #0dcaf0);
|
|
color: #fff;
|
|
border-color: transparent;
|
|
box-shadow: 0 4px 10px rgba(13, 110, 253, 0.2);
|
|
}
|
|
|
|
/* Products Grid */
|
|
.products-grid {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 0.25rem 0.5rem 0.5rem 0;
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
gap: 1rem;
|
|
align-content: start;
|
|
}
|
|
.product-card {
|
|
background: #fff;
|
|
border-radius: 16px;
|
|
overflow: hidden;
|
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
|
|
cursor: pointer;
|
|
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
|
|
border: 1px solid #dbe4f0;
|
|
position: relative;
|
|
user-select: none;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 252px;
|
|
color: #1f2937;
|
|
}
|
|
.product-card[hidden] {
|
|
display: none !important;
|
|
}
|
|
.product-card:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 14px 28px rgba(13, 110, 253, 0.12);
|
|
border-color: #0d6efd;
|
|
}
|
|
.product-card:active {
|
|
transform: translateY(-1px);
|
|
}
|
|
.product-img-wrapper {
|
|
height: 132px;
|
|
width: 100%;
|
|
background: linear-gradient(180deg, #f8fbff 0%, #eef4fb 100%);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
border-bottom: 1px solid #edf2f7;
|
|
}
|
|
.product-img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
.product-placeholder {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.35rem;
|
|
color: #98a2b3;
|
|
font-size: 1.9rem;
|
|
}
|
|
.product-placeholder-label {
|
|
font-size: 0.78rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.01em;
|
|
color: #6b7280;
|
|
}
|
|
.product-info {
|
|
padding: 0.85rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
text-align: left;
|
|
gap: 0.45rem;
|
|
flex: 1;
|
|
}
|
|
.product-badges {
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
gap: 0.35rem;
|
|
flex-wrap: wrap;
|
|
margin-bottom: 0.1rem;
|
|
}
|
|
.product-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0.18rem 0.5rem;
|
|
border-radius: 999px;
|
|
font-size: 0.72rem;
|
|
font-weight: 700;
|
|
line-height: 1;
|
|
}
|
|
.product-badge-new {
|
|
background: rgba(13, 202, 240, 0.14);
|
|
color: #087990;
|
|
}
|
|
.product-badge-no-image {
|
|
background: rgba(108, 117, 125, 0.12);
|
|
color: #6c757d;
|
|
}
|
|
.product-title {
|
|
font-size: 0.95rem;
|
|
font-weight: 700;
|
|
color: #1f2937;
|
|
line-height: 1.35;
|
|
min-height: 2.6em;
|
|
max-height: 2.6em;
|
|
overflow: hidden;
|
|
word-break: break-word;
|
|
}
|
|
.product-meta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.2rem;
|
|
color: #667085;
|
|
margin-bottom: auto;
|
|
width: 100%;
|
|
}
|
|
.product-sku,
|
|
.product-created {
|
|
font-size: 0.78rem;
|
|
line-height: 1.25;
|
|
word-break: break-word;
|
|
}
|
|
.product-price {
|
|
font-weight: 800;
|
|
color: #0d6efd;
|
|
font-size: 1.05rem;
|
|
margin-top: 0.15rem;
|
|
}
|
|
[dir="rtl"] .product-info {
|
|
align-items: flex-end;
|
|
text-align: right;
|
|
}
|
|
[dir="rtl"] .product-badges {
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
/* Cart Styles */
|
|
.cart-header {
|
|
background: linear-gradient(90deg, #0d6efd, #0dcaf0);
|
|
color: #fff;
|
|
padding: 1rem 1.25rem;
|
|
}
|
|
.cart-items {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 1rem;
|
|
}
|
|
.cart-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 0.75rem 0;
|
|
border-bottom: 1px dashed #e9ecef;
|
|
}
|
|
.cart-item-info {
|
|
flex: 1;
|
|
padding-right: 1rem;
|
|
text-align: right;
|
|
}
|
|
.cart-items,
|
|
.cart-footer,
|
|
.cart-header h5 {
|
|
text-align: right;
|
|
}
|
|
.cart-item-title {
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
color: #343a40;
|
|
}
|
|
.cart-item-price {
|
|
color: #6c757d;
|
|
font-size: 0.85rem;
|
|
}
|
|
.cart-item-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
background: #f8f9fa;
|
|
border-radius: 20px;
|
|
padding: 0.25rem;
|
|
}
|
|
.cart-btn {
|
|
width: 28px;
|
|
height: 28px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 50%;
|
|
border: none;
|
|
background: #fff;
|
|
color: #495057;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
transition: all 0.2s;
|
|
}
|
|
.cart-btn:hover {
|
|
background: #e9ecef;
|
|
}
|
|
.cart-qty {
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
min-width: 20px;
|
|
text-align: center;
|
|
}
|
|
.cart-footer {
|
|
padding: 1.25rem;
|
|
background: #f8f9fa;
|
|
border-top: 1px solid #e9ecef;
|
|
}
|
|
.summary-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 0.5rem;
|
|
font-size: 0.95rem;
|
|
color: #495057;
|
|
}
|
|
.summary-total {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 1rem;
|
|
font-size: 1.25rem;
|
|
font-weight: 700;
|
|
color: #212529;
|
|
padding-top: 0.5rem;
|
|
border-top: 1px solid #dee2e6;
|
|
}
|
|
|
|
/* Pay / Action Buttons */
|
|
.btn-pay {
|
|
background: linear-gradient(90deg, #198754, #20c997);
|
|
border: none;
|
|
color: white;
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
padding: 0.75rem;
|
|
border-radius: 12px;
|
|
box-shadow: 0 4px 12px rgba(25, 135, 84, 0.2);
|
|
transition: all 0.2s;
|
|
}
|
|
.btn-pay:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 16px rgba(25, 135, 84, 0.3);
|
|
}
|
|
.btn-pay:disabled {
|
|
background: #adb5bd;
|
|
transform: none;
|
|
box-shadow: none;
|
|
}
|
|
.action-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 0.75rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
[dir="ltr"] .cart-item-info {
|
|
padding-right: 0;
|
|
padding-left: 1rem;
|
|
}
|
|
</style>
|
|
|
|
<?php if ($error !== ''): ?>
|
|
<div class="alert alert-danger shadow-sm border-0 rounded-3 mb-4"><?= h($error) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<div class="pos-wrapper">
|
|
<!-- Left Area: Products -->
|
|
<div class="pos-products-area">
|
|
<!-- Top Bar: Search & Hold -->
|
|
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
|
|
<div class="d-flex gap-2 flex-wrap">
|
|
<div class="position-relative" style="width: 200px;">
|
|
<input type="text" id="posBarcode" class="form-control rounded-pill ps-4 border-primary shadow-sm" placeholder="<?= h(tr('الباركود...', 'Barcode...')) ?>" autocomplete="off" autofocus>
|
|
<i class="bi bi-upc-scan position-absolute top-50 translate-middle-y text-primary" style="left: 15px;"></i>
|
|
</div>
|
|
<div class="position-relative" style="width: 250px;">
|
|
<input type="text" id="posSearch" class="form-control rounded-pill ps-4" placeholder="<?= h(tr('بحث بالاسم...', 'Search by name...')) ?>" autocomplete="off">
|
|
<i class="bi bi-search position-absolute top-50 translate-middle-y text-muted" style="left: 15px;"></i>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-outline-primary rounded-pill px-4 shadow-sm" onclick="openHeldOrdersModal()">
|
|
<i class="bi bi-clock-history me-1"></i> <span id="heldOrdersCount">0</span> <?= h(tr('قيد الانتظار', 'Held Orders')) ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Categories -->
|
|
<div class="cat-scroll-container mb-4" id="catContainer">
|
|
<button class="cat-btn active" data-cat="all" onclick="filterCat('all')"><?= h(tr('الكل', 'All')) ?></button>
|
|
<?php foreach($categories as $cat):
|
|
$catId = h($cat['id']);
|
|
$catName = h(current_lang() === 'ar' ? $cat['name_ar'] : $cat['name_en']);
|
|
?>
|
|
<button class="cat-btn" data-cat="<?= $catId ?>" onclick="filterCat('<?= $catId ?>')"><?= $catName ?></button>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<!-- Grid -->
|
|
<div class="products-grid" id="productsGrid">
|
|
<?php $productIndex = 0; foreach ($catalog as $sku => $item):
|
|
$itemSkuRaw = (string) $sku;
|
|
$primaryNameRaw = trim((string) (current_lang() === 'ar' ? ($item['name_ar'] ?? '') : ($item['name_en'] ?? '')));
|
|
$fallbackNameRaw = trim((string) (current_lang() === 'ar' ? ($item['name_en'] ?? '') : ($item['name_ar'] ?? '')));
|
|
$itemNameRaw = $primaryNameRaw !== ''
|
|
? $primaryNameRaw
|
|
: ($fallbackNameRaw !== '' ? $fallbackNameRaw : (tr('صنف ' . $itemSkuRaw, 'Item ' . $itemSkuRaw)));
|
|
$searchBits = [
|
|
(string) ($item['name_ar'] ?? ''),
|
|
(string) ($item['name_en'] ?? ''),
|
|
$itemSkuRaw,
|
|
];
|
|
$searchTextRaw = implode(' ', array_filter($searchBits, static fn($value) => trim((string) $value) !== ''));
|
|
$searchText = function_exists('mb_strtolower')
|
|
? mb_strtolower($searchTextRaw, 'UTF-8')
|
|
: strtolower($searchTextRaw);
|
|
$itemSku = h($itemSkuRaw);
|
|
$itemName = h($itemNameRaw);
|
|
$itemPrice = h($item['price']);
|
|
$itemCat = h($item['category_id'] ?? '');
|
|
$imageUrl = !empty($item['image_url']) ? h($item['image_url']) : '';
|
|
$createdAtRaw = (string)($item['created_at'] ?? '');
|
|
$createdAtStamp = $createdAtRaw !== '' ? strtotime($createdAtRaw) : false;
|
|
$isRecentlyAdded = $createdAtStamp && $createdAtStamp >= strtotime('-7 days');
|
|
$createdLabel = $createdAtStamp ? date('Y-m-d', $createdAtStamp) : '';
|
|
?>
|
|
<div class="product-card" data-sku="<?= $itemSku ?>" data-name="<?= $itemName ?>" data-price="<?= $itemPrice ?>" data-cat="<?= $itemCat ?>" data-search="<?= h($searchText) ?>" data-created="<?= h($createdAtRaw) ?>" data-index="<?= $productIndex++ ?>" onclick="addToCart('<?= $itemSku ?>')">
|
|
<div class="product-img-wrapper">
|
|
<?php if (!empty($imageUrl)):
|
|
$imgAlt = $itemName;
|
|
?>
|
|
<img src="<?= $imageUrl ?>" alt="<?= $imgAlt ?>" class="product-img" loading="lazy">
|
|
<?php else: ?>
|
|
<div class="product-placeholder" aria-hidden="true">
|
|
<i class="bi bi-image"></i>
|
|
<span class="product-placeholder-label"><?= h(tr('بدون صورة', 'No image')) ?></span>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div class="product-info">
|
|
<div class="product-badges">
|
|
<?php if ($isRecentlyAdded): ?>
|
|
<span class="product-badge product-badge-new"><?= h(tr('جديد', 'New')) ?></span>
|
|
<?php endif; ?>
|
|
<?php if (empty($imageUrl)): ?>
|
|
<span class="product-badge product-badge-no-image"><?= h(tr('بدون صورة', 'No image')) ?></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div class="product-title" title="<?= $itemName ?>"><?= $itemName ?></div>
|
|
<div class="product-price"><?= h(currency((float)$item['price'])) ?></div>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Area: Smart Cart -->
|
|
<div class="pos-cart-area">
|
|
<div class="cart-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0 fw-bold"><i class="bi bi-cart3 me-2"></i><?= h(tr('سلة المشتريات', 'Shopping Cart')) ?></h5>
|
|
<span class="badge bg-light text-primary rounded-pill px-2" id="cartBadgeCount">0</span>
|
|
</div>
|
|
|
|
<div class="p-3 bg-light border-bottom">
|
|
<select class="form-select border-0 shadow-sm rounded-3 mb-2" id="posBranch" <?= count($allowedBranches) === 1 ? 'disabled' : '' ?> required>
|
|
<?php foreach ($allowedBranches as $branchCode):
|
|
$branchLabel = h(branch_label($branchCode));
|
|
?>
|
|
<option value="<?= h($branchCode) ?>"><?= $branchLabel ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<div class="input-group position-relative">
|
|
<input type="text" id="posCustomer" class="form-control border-0 shadow-sm rounded-start-3" placeholder="<?= h(tr('بحث عن عميل (اسم أو هاتف)', 'Search Customer (Name or Phone)')) ?>" autocomplete="off">
|
|
<button class="btn btn-primary border-0 shadow-sm rounded-end-3" onclick="openNewCustomerModal()" type="button" title="<?= h(tr('إضافة عميل', 'Add Customer')) ?>">
|
|
<i class="bi bi-person-plus-fill"></i>
|
|
</button>
|
|
<div id="customerDropdown" class="list-group position-absolute w-100 shadow-lg d-none" style="top: 100%; left: 0; z-index: 1050; max-height: 200px; overflow-y: auto; border-radius: 8px;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cart-items" id="cartItemsList">
|
|
<!-- Items rendered via JS -->
|
|
<div class="text-center text-muted mt-5 pt-4">
|
|
<i class="bi bi-basket2 fs-1 d-block mb-3 opacity-50"></i>
|
|
<p><?= h(tr('السلة فارغة، اختر بعض الأصناف.', 'Cart is empty, pick some items.')) ?></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cart-footer">
|
|
<div class="summary-row">
|
|
<span><?= h(tr('المجموع الفرعي', 'Subtotal')) ?></span>
|
|
<span id="cartSubtotalVal">0.000</span>
|
|
</div>
|
|
<div class="summary-row">
|
|
<span><?= h(tr('الضريبة (مضافة)', 'VAT (Added)')) ?></span>
|
|
<span id="cartVatVal" class="text-muted">0.000</span>
|
|
</div>
|
|
<div class="summary-total">
|
|
<span><?= h(tr('الإجمالي', 'Total')) ?></span>
|
|
<span class="text-primary" id="cartTotalVal">0.000</span>
|
|
</div>
|
|
|
|
<div class="action-grid">
|
|
<button class="btn btn-outline-warning rounded-3 fw-semibold" onclick="holdCart()" id="btnHold" disabled>
|
|
<i class="bi bi-pause-circle"></i> <?= h(tr('تعليق', 'Hold')) ?>
|
|
</button>
|
|
<button class="btn btn-outline-danger rounded-3 fw-semibold" onclick="clearCart()" id="btnClear" disabled>
|
|
<i class="bi bi-trash"></i> <?= h(tr('إلغاء', 'Clear')) ?>
|
|
</button>
|
|
</div>
|
|
|
|
<button class="btn w-100 btn-pay" id="btnPay" onclick="openPaymentModal()" disabled>
|
|
<i class="bi bi-wallet2 me-2"></i> <?= h(tr('دفع وإصدار', 'Pay & Checkout')) ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hidden Form for Submission -->
|
|
<form method="POST" id="checkoutForm" style="display: none;">
|
|
<input type="hidden" name="branch_code" id="inputBranch">
|
|
<input type="hidden" name="customer_id" id="inputCustomerId">
|
|
<input type="hidden" name="customer_name" id="inputCustomer">
|
|
<input type="hidden" name="payment_method" id="inputPayment">
|
|
<input type="hidden" name="payment_amount" id="inputPaymentAmount">
|
|
<input type="hidden" name="notes" id="inputNotes">
|
|
<input type="hidden" name="submit_action" id="inputSubmitAction">
|
|
<input type="hidden" name="cart_json" id="inputCart">
|
|
</form>
|
|
|
|
<!-- Payment Modal -->
|
|
<div class="modal fade" id="paymentModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered modal-sm">
|
|
<div class="modal-content border-0 shadow-lg" style="border-radius: 16px;">
|
|
<div class="modal-header border-0 pb-0">
|
|
<h5 class="modal-title fw-bold"><?= h(tr('إتمام الدفع', 'Complete Payment')) ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body text-center pt-2">
|
|
<h2 class="text-primary fw-bold mb-3" id="modalTotalAmount">0.000</h2>
|
|
<div class="mb-3 text-start">
|
|
<label for="modalPaidAmount" class="form-label small text-muted"><?= h(tr('المبلغ المدفوع الآن', 'Paid Now')) ?></label>
|
|
<input type="number" id="modalPaidAmount" class="form-control rounded-3" min="0" step="0.001" value="<?= h($paymentAmountInput) ?>" placeholder="0.000">
|
|
<div class="form-text" id="modalDueHint"><?= h(tr('سيتم تتبع المبلغ المتبقي تلقائياً.', 'The remaining balance will be tracked automatically.')) ?></div>
|
|
</div>
|
|
<div class="mb-3 text-start">
|
|
<label class="form-label small text-muted d-block"><?= h(tr('بعد الحفظ', 'After saving')) ?></label>
|
|
<div class="btn-group w-100" role="group" aria-label="<?= h(tr('خيارات الحفظ', 'Save options')) ?>">
|
|
<input type="radio" class="btn-check" name="save_mode" id="saveActionOnly" autocomplete="off">
|
|
<label class="btn btn-outline-dark" for="saveActionOnly"><?= h(tr('حفظ فقط', 'Save only')) ?></label>
|
|
|
|
<input type="radio" class="btn-check" name="save_mode" id="saveActionPrint" autocomplete="off" checked>
|
|
<label class="btn btn-outline-dark" for="saveActionPrint"><?= h(tr('حفظ مع طباعة', 'Save with print')) ?></label>
|
|
</div>
|
|
<div class="form-text"><?= h(tr('اختر إذا كنت تريد حفظ الفاتورة فقط أو حفظها وفتح الطباعة مباشرة.', 'Choose whether to save the receipt only or save it and open printing immediately.')) ?></div>
|
|
</div>
|
|
<div class="d-grid gap-3">
|
|
<button type="button" class="btn btn-lg btn-outline-success rounded-pill fw-semibold" onclick="submitSale('cash')">
|
|
<i class="bi bi-cash-coin me-2"></i> <?= h(tr('نقداً', 'Cash')) ?>
|
|
</button>
|
|
<button type="button" class="btn btn-lg btn-outline-primary rounded-pill fw-semibold" onclick="submitSale('card')">
|
|
<i class="bi bi-credit-card me-2"></i> <?= h(tr('بطاقة', 'Card')) ?>
|
|
</button>
|
|
<button type="button" class="btn btn-lg btn-outline-secondary rounded-pill fw-semibold" onclick="submitSale('transfer')">
|
|
<i class="bi bi-phone me-2"></i> <?= h(tr('تحويل بنكي', 'Transfer')) ?>
|
|
</button>
|
|
<button type="button" class="btn btn-lg btn-outline-warning rounded-pill fw-semibold" onclick="submitSale('pay_later')">
|
|
<i class="bi bi-clock-history me-2"></i> <?= h(tr('آجل', 'Pay Later')) ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- New Customer Modal -->
|
|
<div class="modal fade" id="newCustomerModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered modal-sm">
|
|
<div class="modal-content border-0 shadow-lg" style="border-radius: 16px;">
|
|
<div class="modal-header border-0 pb-0">
|
|
<h5 class="modal-title fw-bold"><?= h(tr('إضافة عميل', 'Add Customer')) ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label class="form-label text-muted small mb-1"><?= h(tr('الاسم', 'Name')) ?> <span class="text-danger">*</span></label>
|
|
<input type="text" id="ncName" class="form-control rounded-3">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label text-muted small mb-1"><?= h(tr('رقم الهاتف', 'Phone')) ?></label>
|
|
<div class="input-group" dir="ltr">
|
|
<span class="input-group-text">968</span>
|
|
<input type="text" id="ncPhone" class="form-control rounded-end-3" inputmode="numeric" maxlength="8" pattern="\d{8}" placeholder="91234567">
|
|
</div>
|
|
</div>
|
|
<div class="d-grid mt-4">
|
|
<button class="btn btn-primary rounded-pill fw-semibold shadow-sm" onclick="saveNewCustomer()"><?= h(tr('حفظ العميل', 'Save Customer')) ?></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Held Orders Modal -->
|
|
<div class="modal fade" id="heldOrdersModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content border-0 shadow-lg" style="border-radius: 16px;">
|
|
<div class="modal-header bg-light border-0">
|
|
<h5 class="modal-title fw-bold"><i class="bi bi-clock-history me-2 text-primary"></i><?= h(tr('الطلبات المعلقة', 'Held Orders')) ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body p-0">
|
|
<div class="list-group list-group-flush" id="heldOrdersList">
|
|
<!-- Rendered by JS -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let cart = {};
|
|
let catalogData = <?= json_encode($catalog, JSON_UNESCAPED_UNICODE) ?>;
|
|
let customersData = <?= json_encode($customers, JSON_UNESCAPED_UNICODE) ?>;
|
|
let currencyLabel = '<?= h(tr('ر.ع', 'OMR')) ?>';
|
|
const partialPaymentText = '<?= h(tr('متبقٍ بعد الحفظ:', 'Due after save:')) ?>';
|
|
|
|
// Customer Autocomplete & Add Logic
|
|
const custInput = document.getElementById('posCustomer');
|
|
const custDropdown = document.getElementById('customerDropdown');
|
|
|
|
custInput.addEventListener('input', function() {
|
|
const q = this.value.toLowerCase().trim();
|
|
custDropdown.innerHTML = '';
|
|
if (q.length < 2) {
|
|
custDropdown.classList.add('d-none');
|
|
return;
|
|
}
|
|
|
|
const matches = customersData.filter(c =>
|
|
c.name.toLowerCase().includes(q) ||
|
|
(c.phone && c.phone.toLowerCase().includes(q))
|
|
).slice(0, 5);
|
|
|
|
if (matches.length > 0) {
|
|
matches.forEach(c => {
|
|
const a = document.createElement('a');
|
|
a.className = 'list-group-item list-group-item-action cursor-pointer border-0 border-bottom';
|
|
a.innerHTML = `<strong>${c.name}</strong> ${c.phone ? '<small class="text-muted ms-2">968 '+c.phone+'</small>' : ''}`;
|
|
a.onclick = function() {
|
|
custInput.value = c.name + (c.phone ? ' - 968 ' + c.phone : '');
|
|
custInput.dataset.id = c.id;
|
|
custDropdown.classList.add('d-none');
|
|
};
|
|
custDropdown.appendChild(a);
|
|
});
|
|
custDropdown.classList.remove('d-none');
|
|
} else {
|
|
custDropdown.classList.add('d-none');
|
|
}
|
|
});
|
|
|
|
document.addEventListener('click', function(e) {
|
|
if (!custInput.contains(e.target) && !custDropdown.contains(e.target)) {
|
|
custDropdown.classList.add('d-none');
|
|
}
|
|
});
|
|
|
|
let newCustomerModalObj = null;
|
|
function openNewCustomerModal() {
|
|
if (!newCustomerModalObj) {
|
|
newCustomerModalObj = new bootstrap.Modal(document.getElementById('newCustomerModal'));
|
|
}
|
|
document.getElementById('ncName').value = '';
|
|
document.getElementById('ncPhone').value = '';
|
|
newCustomerModalObj.show();
|
|
}
|
|
|
|
async function saveNewCustomer() {
|
|
const name = document.getElementById('ncName').value.trim();
|
|
const phone = document.getElementById('ncPhone').value.trim();
|
|
if (!name) {
|
|
Swal.fire({icon: 'warning', text: '<?= h(tr('الاسم مطلوب', 'Name is required')) ?>'});
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('name', name);
|
|
formData.append('phone', phone);
|
|
|
|
try {
|
|
const res = await fetch('api/customers.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
customersData.push(data.customer);
|
|
custInput.value = data.customer.name + (data.customer.phone ? ' - 968 ' + data.customer.phone : '');
|
|
custInput.dataset.id = data.customer.id;
|
|
newCustomerModalObj.hide();
|
|
Swal.fire({
|
|
icon: 'success',
|
|
text: '<?= h(tr('تم إضافة العميل', 'Customer added')) ?>',
|
|
position: 'center',
|
|
confirmButtonText: '<?= h(tr('حسناً', 'OK')) ?>',
|
|
confirmButtonColor: '#0d6efd'
|
|
});
|
|
} else {
|
|
Swal.fire({icon: 'warning', text: data.error});
|
|
}
|
|
} catch(err) {
|
|
Swal.fire({icon: 'warning', text: 'Error saving customer'});
|
|
}
|
|
}
|
|
|
|
// Product Grid Filtering & Searching
|
|
function filterCat(catId) {
|
|
document.querySelectorAll('.cat-btn').forEach(btn => btn.classList.remove('active'));
|
|
document.querySelector(`.cat-btn[data-cat="${catId}"]`).classList.add('active');
|
|
applyFilters();
|
|
}
|
|
|
|
document.getElementById('posSearch').addEventListener('input', applyFilters);
|
|
|
|
document.getElementById('posBarcode').addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
const sku = this.value.trim();
|
|
if (sku === '') return;
|
|
|
|
if (catalogData[sku]) {
|
|
addToCart(sku);
|
|
} else {
|
|
const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 2000 });
|
|
Toast.fire({ icon: 'error', title: '<?= h(tr('الصنف غير موجود', 'Item not found')) ?>' });
|
|
}
|
|
this.value = '';
|
|
}
|
|
});
|
|
|
|
const INITIAL_PRODUCT_LIMIT = 100;
|
|
|
|
function applyFilters() {
|
|
const q = document.getElementById('posSearch').value.toLowerCase().trim();
|
|
const activeCat = document.querySelector('.cat-btn.active').dataset.cat;
|
|
let visibleCount = 0;
|
|
|
|
document.querySelectorAll('.product-card').forEach(card => {
|
|
const searchable = (card.dataset.search || card.dataset.name || '').toLowerCase();
|
|
const cat = card.dataset.cat;
|
|
|
|
const matchesSearch = q === '' || searchable.includes(q);
|
|
const matchesCat = (activeCat === 'all' || cat === activeCat);
|
|
const matches = matchesSearch && matchesCat;
|
|
const withinInitialLimit = q !== '' || visibleCount < INITIAL_PRODUCT_LIMIT;
|
|
|
|
card.hidden = !(matches && withinInitialLimit);
|
|
|
|
if (matches) {
|
|
visibleCount += 1;
|
|
}
|
|
});
|
|
}
|
|
|
|
applyFilters();
|
|
|
|
// Cart Logic
|
|
function addToCart(sku) {
|
|
if (!catalogData[sku]) return;
|
|
|
|
if (cart[sku]) {
|
|
cart[sku].qty += 1;
|
|
} else {
|
|
cart[sku] = {
|
|
sku: sku,
|
|
name: document.querySelector(`.product-card[data-sku="${sku}"]`).dataset.name,
|
|
price: parseFloat(document.querySelector(`.product-card[data-sku="${sku}"]`).dataset.price),
|
|
qty: 1
|
|
};
|
|
}
|
|
renderCart();
|
|
}
|
|
|
|
function updateQty(sku, delta) {
|
|
if (!cart[sku]) return;
|
|
cart[sku].qty += delta;
|
|
if (cart[sku].qty <= 0) {
|
|
delete cart[sku];
|
|
}
|
|
renderCart();
|
|
}
|
|
|
|
function renderCart() {
|
|
const list = document.getElementById('cartItemsList');
|
|
const badge = document.getElementById('cartBadgeCount');
|
|
const subVal = document.getElementById('cartSubtotalVal');
|
|
const vatVal = document.getElementById('cartVatVal');
|
|
const totalVal = document.getElementById('cartTotalVal');
|
|
const btnPay = document.getElementById('btnPay');
|
|
const btnHold = document.getElementById('btnHold');
|
|
const btnClear = document.getElementById('btnClear');
|
|
|
|
list.innerHTML = '';
|
|
let total = 0;
|
|
let count = 0;
|
|
let totalVat = 0;
|
|
|
|
const skus = Object.keys(cart);
|
|
if (skus.length === 0) {
|
|
list.innerHTML = `
|
|
<div class="text-center text-muted mt-5 pt-4">
|
|
<i class="bi bi-basket2 fs-1 d-block mb-3 opacity-50"></i>
|
|
<p><?= h(tr('السلة فارغة، اختر بعض الأصناف.', 'Cart is empty, pick some items.')) ?></p>
|
|
</div>
|
|
`;
|
|
badge.innerText = '0';
|
|
subVal.innerText = `0.000 ${currencyLabel}`;
|
|
vatVal.innerText = `0.000`;
|
|
totalVal.innerText = `0.000 ${currencyLabel}`;
|
|
btnPay.disabled = true;
|
|
btnHold.disabled = true;
|
|
btnClear.disabled = true;
|
|
return;
|
|
}
|
|
|
|
skus.forEach(sku => {
|
|
const item = cart[sku];
|
|
const lineTotal = item.price * item.qty;
|
|
const vatPercent = parseFloat(catalogData[sku].vat) || 0;
|
|
const itemVat = lineTotal * (vatPercent / 100);
|
|
totalVat += itemVat;
|
|
total += lineTotal;
|
|
count += item.qty;
|
|
|
|
list.innerHTML += `
|
|
<div class="cart-item">
|
|
<div class="cart-item-info">
|
|
<div class="cart-item-title">
|
|
${item.name}
|
|
</div>
|
|
<div class="cart-item-price">
|
|
${item.price.toFixed(3)} ${currencyLabel}
|
|
</div>
|
|
</div>
|
|
<div class="cart-item-controls">
|
|
<button class="cart-btn" onclick="updateQty('${sku}', -1)"><i class="bi bi-dash"></i></button>
|
|
<span class="cart-qty">${item.qty}</span>
|
|
<button class="cart-btn" onclick="updateQty('${sku}', 1)"><i class="bi bi-plus"></i></button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
badge.innerText = count;
|
|
const subtotal = total;
|
|
const finalTotal = subtotal + totalVat;
|
|
const totalStr = `${finalTotal.toFixed(3)} ${currencyLabel}`;
|
|
subVal.innerText = subtotal.toFixed(3) + ' ' + currencyLabel;
|
|
vatVal.innerText = totalVat.toFixed(3);
|
|
totalVal.innerText = totalStr;
|
|
document.getElementById('modalTotalAmount').innerText = totalStr;
|
|
|
|
btnPay.disabled = false;
|
|
btnHold.disabled = false;
|
|
btnClear.disabled = false;
|
|
}
|
|
|
|
function clearCart() {
|
|
cart = {};
|
|
document.getElementById('posCustomer').value = '';
|
|
delete document.getElementById('posCustomer').dataset.id;
|
|
renderCart();
|
|
}
|
|
|
|
// Payment & Checkout
|
|
let paymentModalObj = null;
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
paymentModalObj = new bootstrap.Modal(document.getElementById('paymentModal'));
|
|
const paidField = document.getElementById('modalPaidAmount');
|
|
if (paidField) {
|
|
paidField.addEventListener('input', () => {
|
|
paidField.dataset.manual = '1';
|
|
updateModalDueHint();
|
|
});
|
|
}
|
|
updateHeldOrdersCount();
|
|
});
|
|
|
|
function getCartTotals() {
|
|
let subtotal = 0;
|
|
let vat = 0;
|
|
Object.values(cart).forEach(item => {
|
|
const lineTotal = item.qty * item.price;
|
|
subtotal += lineTotal;
|
|
const vatPercent = parseFloat(catalogData[item.sku]?.vat || 0) || 0;
|
|
vat += lineTotal * (vatPercent / 100);
|
|
});
|
|
return { total: subtotal + vat, vat, subtotal };
|
|
}
|
|
|
|
function updateModalDueHint() {
|
|
const total = Number(getCartTotals().total.toFixed(3));
|
|
const paidField = document.getElementById('modalPaidAmount');
|
|
const dueHint = document.getElementById('modalDueHint');
|
|
if (!paidField || !dueHint) {
|
|
return;
|
|
}
|
|
const paid = Math.max(0, parseFloat(paidField.value || '0') || 0);
|
|
const due = Math.max(0, total - Math.min(paid, total));
|
|
dueHint.innerText = partialPaymentText + ' ' + due.toFixed(3) + ' ' + currencyLabel;
|
|
}
|
|
|
|
function openPaymentModal() {
|
|
if (Object.keys(cart).length === 0) return;
|
|
const total = Number(getCartTotals().total.toFixed(3));
|
|
const paidField = document.getElementById('modalPaidAmount');
|
|
document.getElementById('modalTotalAmount').innerText = total.toFixed(3) + ' ' + currencyLabel;
|
|
if (paidField) {
|
|
paidField.value = total.toFixed(3);
|
|
paidField.dataset.manual = '0';
|
|
}
|
|
const saveActionPrint = document.getElementById('saveActionPrint');
|
|
if (saveActionPrint) {
|
|
saveActionPrint.checked = true;
|
|
}
|
|
updateModalDueHint();
|
|
paymentModalObj.show();
|
|
}
|
|
|
|
function getSelectedSubmitAction() {
|
|
return document.getElementById('saveActionOnly')?.checked ? 'save' : 'save_print';
|
|
}
|
|
|
|
function submitSale(method) {
|
|
const branch = document.getElementById('posBranch').value || '<?= h($allowedBranches[0] ?? '') ?>';
|
|
const customer = document.getElementById('posCustomer').value;
|
|
const customerId = document.getElementById('posCustomer').dataset.id || '';
|
|
const totals = getCartTotals();
|
|
const totalAmount = Number(totals.total.toFixed(3));
|
|
const paidField = document.getElementById('modalPaidAmount');
|
|
let paidAmount = Math.max(0, parseFloat(paidField?.value || '0') || 0);
|
|
const manual = paidField?.dataset.manual === '1';
|
|
|
|
if (method === 'pay_later' && !manual) {
|
|
paidAmount = 0;
|
|
} else if (method !== 'pay_later' && !manual) {
|
|
paidAmount = totalAmount;
|
|
}
|
|
|
|
paidAmount = Number(Math.min(totalAmount, Math.max(0, paidAmount)).toFixed(3));
|
|
|
|
if (paidAmount > totalAmount + 0.0005) {
|
|
Swal.fire({icon: 'warning', text: '<?= h(tr('المبلغ المدفوع لا يمكن أن يتجاوز إجمالي الفاتورة.', 'Paid amount cannot exceed the invoice total.')) ?>'});
|
|
return;
|
|
}
|
|
if (paidAmount + 0.0005 < totalAmount && !customerId) {
|
|
Swal.fire({icon: 'warning', text: '<?= h(tr('يجب اختيار عميل مسجل عند وجود مبلغ متبقٍ.', 'Select a registered customer when there is a remaining balance.')) ?>'});
|
|
return;
|
|
}
|
|
|
|
const itemsArr = Object.values(cart).map(item => ({
|
|
sku: item.sku,
|
|
qty: item.qty
|
|
}));
|
|
|
|
document.getElementById('inputBranch').value = branch;
|
|
document.getElementById('inputCustomerId').value = customerId;
|
|
document.getElementById('inputCustomer').value = customer;
|
|
document.getElementById('inputPayment').value = method;
|
|
document.getElementById('inputPaymentAmount').value = paidAmount.toFixed(3);
|
|
document.getElementById('inputSubmitAction').value = getSelectedSubmitAction();
|
|
document.getElementById('inputCart').value = JSON.stringify(itemsArr);
|
|
|
|
document.getElementById('checkoutForm').submit();
|
|
}
|
|
|
|
// Held Orders Logic (localStorage)
|
|
function getHeldOrders() {
|
|
const raw = localStorage.getItem('posHeldOrders');
|
|
return raw ? JSON.parse(raw) : [];
|
|
}
|
|
|
|
function saveHeldOrders(orders) {
|
|
localStorage.setItem('posHeldOrders', JSON.stringify(orders));
|
|
updateHeldOrdersCount();
|
|
}
|
|
|
|
function updateHeldOrdersCount() {
|
|
const orders = getHeldOrders();
|
|
document.getElementById('heldOrdersCount').innerText = orders.length;
|
|
}
|
|
|
|
function holdCart() {
|
|
if (Object.keys(cart).length === 0) return;
|
|
|
|
const orders = getHeldOrders();
|
|
const orderName = document.getElementById('posCustomer').value || `<?= h(tr('عميل غير معروف', 'Unknown')) ?> #${Math.floor(Math.random()*1000)}`;
|
|
const branch = document.getElementById('posBranch').value;
|
|
|
|
const newOrder = {
|
|
id: Date.now(),
|
|
name: orderName,
|
|
branch: branch,
|
|
cart: JSON.parse(JSON.stringify(cart)),
|
|
time: new Date().toLocaleTimeString()
|
|
};
|
|
|
|
orders.push(newOrder);
|
|
saveHeldOrders(orders);
|
|
|
|
// Clear current cart via toast
|
|
clearCart();
|
|
|
|
const Toast = Swal.mixin({
|
|
toast: true,
|
|
position: 'top-end',
|
|
showConfirmButton: false,
|
|
timer: 2000
|
|
});
|
|
Toast.fire({
|
|
icon: 'success',
|
|
title: '<?= h(tr('تم تعليق الطلب', 'Order held successfully')) ?>'
|
|
});
|
|
}
|
|
|
|
let heldOrdersModalObj = null;
|
|
function openHeldOrdersModal() {
|
|
if (!heldOrdersModalObj) {
|
|
heldOrdersModalObj = new bootstrap.Modal(document.getElementById('heldOrdersModal'));
|
|
}
|
|
|
|
const list = document.getElementById('heldOrdersList');
|
|
const orders = getHeldOrders();
|
|
|
|
list.innerHTML = '';
|
|
if (orders.length === 0) {
|
|
list.innerHTML = `<div class="p-4 text-center text-muted"><?= h(tr('لا توجد طلبات معلقة', 'No held orders')) ?></div>`;
|
|
} else {
|
|
orders.forEach((order, index) => {
|
|
const count = Object.values(order.cart).reduce((sum, i) => sum + i.qty, 0);
|
|
list.innerHTML += `
|
|
<div class="list-group-item d-flex justify-content-between align-items-center p-3">
|
|
<div>
|
|
<h6 class="mb-1 fw-bold">
|
|
${order.name}
|
|
</h6>
|
|
<small class="text-muted"><i class="bi bi-clock me-1"></i>${order.time} • ${count} <?= h(tr('قطعة', 'items')) ?></small>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<button class="btn btn-sm btn-outline-primary rounded-pill px-3" onclick="resumeOrder(${index})"><?= h(tr('استعادة', 'Resume')) ?></button>
|
|
<button class="btn btn-sm btn-outline-danger rounded-pill" onclick="deleteHeldOrder(${index})"><i class="bi bi-trash"></i></button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
heldOrdersModalObj.show();
|
|
}
|
|
|
|
function resumeOrder(index) {
|
|
const orders = getHeldOrders();
|
|
if (!orders[index]) return;
|
|
|
|
const order = orders[index];
|
|
|
|
// Warn if cart is not empty
|
|
if (Object.keys(cart).length > 0) {
|
|
Swal.fire({
|
|
title: '<?= h(tr("تنبيه", "Alert")) ?>',
|
|
text: '<?= h(tr("السلة الحالية غير فارغة، هل تريد استبدالها؟", "Current cart is not empty, replace it?")) ?>',
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonText: '<?= h(tr("نعم", "Yes")) ?>',
|
|
cancelButtonText: '<?= h(tr("إلغاء", "Cancel")) ?>'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
doResumeOrder(index, order, orders);
|
|
}
|
|
});
|
|
} else {
|
|
doResumeOrder(index, order, orders);
|
|
}
|
|
}
|
|
|
|
function doResumeOrder(index, order, orders) {
|
|
cart = order.cart;
|
|
document.getElementById('posCustomer').value = order.name.includes('#') ? '' : order.name;
|
|
if (document.getElementById('posBranch').querySelector(`option[value="${order.branch}"]`)) {
|
|
document.getElementById('posBranch').value = order.branch;
|
|
}
|
|
|
|
orders.splice(index, 1);
|
|
saveHeldOrders(orders);
|
|
|
|
renderCart();
|
|
heldOrdersModalObj.hide();
|
|
}
|
|
|
|
function deleteHeldOrder(index) {
|
|
const orders = getHeldOrders();
|
|
orders.splice(index, 1);
|
|
saveHeldOrders(orders);
|
|
openHeldOrdersModal(); // re-render
|
|
}
|
|
|
|
</script>
|
|
|
|
<?php require __DIR__ . '/includes/footer.php'; ?>
|