Autosave: 20260301-182559
This commit is contained in:
parent
9f50c8c2ab
commit
9419ae79eb
@ -1,2 +1,5 @@
|
|||||||
[INFO] AI agent editing: index.php
|
[INFO] AI agent editing: index.php
|
||||||
2026-02-26 08:18:16 - Items case hit
|
2026-02-26 08:18:16 - Items case hit
|
||||||
|
2026-03-01 13:00:29 - Items case hit
|
||||||
|
2026-03-01 13:17:12 - Items case hit
|
||||||
|
2026-03-01 18:24:40 - Items case hit
|
||||||
|
|||||||
30
fix_schema.php
Normal file
30
fix_schema.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Assign default outlet_id to NULLs
|
||||||
|
$pdo->exec("UPDATE _stock_items SET outlet_id = 1 WHERE outlet_id IS NULL");
|
||||||
|
$pdo->exec("UPDATE _stock_categories SET outlet_id = 1 WHERE outlet_id IS NULL");
|
||||||
|
$pdo->exec("UPDATE _stock_units SET outlet_id = 1 WHERE outlet_id IS NULL");
|
||||||
|
|
||||||
|
// 2. Fix _stock_items index
|
||||||
|
// First, drop old unique index if it exists
|
||||||
|
try {
|
||||||
|
$pdo->exec("ALTER TABLE _stock_items DROP INDEX sku");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Note: index 'sku' might not exist or already dropped: " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
|
// Add new unique index (outlet_id, sku)
|
||||||
|
$pdo->exec("ALTER TABLE _stock_items ADD UNIQUE KEY outlet_sku (outlet_id, sku)");
|
||||||
|
|
||||||
|
// 3. Fix _stock_categories index
|
||||||
|
$pdo->exec("ALTER TABLE _stock_categories ADD UNIQUE KEY outlet_cat_name (outlet_id, name_en)");
|
||||||
|
|
||||||
|
// 4. Fix _stock_units index
|
||||||
|
$pdo->exec("ALTER TABLE _stock_units ADD UNIQUE KEY outlet_unit_name (outlet_id, name_en)");
|
||||||
|
|
||||||
|
echo "Database schema updated successfully.\n";
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Error updating database schema: " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
222
index.php
222
index.php
@ -2995,6 +2995,127 @@ if (isset($_POST['add_hr_department'])) {
|
|||||||
$stmt->execute([$id]);
|
$stmt->execute([$id]);
|
||||||
redirectWithMessage("Outlet deleted successfully!", "index.php?page=outlets");
|
redirectWithMessage("Outlet deleted successfully!", "index.php?page=outlets");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Data Synchronization Handler ---
|
||||||
|
if (isset($_POST['perform_data_sync']) && ($_SESSION['user_role_name'] ?? '') === 'Administrator') {
|
||||||
|
$source_id = (int)$_POST['source_outlet_id'];
|
||||||
|
$target_id = (int)$_POST['target_outlet_id'];
|
||||||
|
|
||||||
|
if ($source_id === $target_id) {
|
||||||
|
redirectWithMessage("Source and target outlet must be different.", "index.php?page=data_sync");
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$db->beginTransaction();
|
||||||
|
try {
|
||||||
|
// 1. Sync & Map Categories
|
||||||
|
$cat_map = [];
|
||||||
|
if (isset($_POST['sync_categories'])) {
|
||||||
|
$source_cats = $db->prepare("SELECT * FROM _stock_categories WHERE outlet_id = ?");
|
||||||
|
$source_cats->execute([$source_id]);
|
||||||
|
foreach ($source_cats->fetchAll() as $cat) {
|
||||||
|
$stmt = $db->prepare("INSERT INTO _stock_categories (outlet_id, name_en, name_ar) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE name_ar = VALUES(name_ar)");
|
||||||
|
$stmt->execute([$target_id, $cat['name_en'], $cat['name_ar']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build category map based on Name EN (unique within outlet)
|
||||||
|
$s_cats = $db->prepare("SELECT id, name_en FROM _stock_categories WHERE outlet_id = ?");
|
||||||
|
$s_cats->execute([$source_id]);
|
||||||
|
$source_cats_by_name = $s_cats->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
|
||||||
|
$t_cats = $db->prepare("SELECT id, name_en FROM _stock_categories WHERE outlet_id = ?");
|
||||||
|
$t_cats->execute([$target_id]);
|
||||||
|
$target_cats_by_name = array_flip($t_cats->fetchAll(PDO::FETCH_KEY_PAIR));
|
||||||
|
|
||||||
|
foreach ($source_cats_by_name as $sid => $name) {
|
||||||
|
if (isset($target_cats_by_name[$name])) {
|
||||||
|
$cat_map[$sid] = $target_cats_by_name[$name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Sync & Map Units
|
||||||
|
$unit_map = [];
|
||||||
|
if (isset($_POST['sync_units'])) {
|
||||||
|
$source_units = $db->prepare("SELECT * FROM _stock_units WHERE outlet_id = ?");
|
||||||
|
$source_units->execute([$source_id]);
|
||||||
|
foreach ($source_units->fetchAll() as $unit) {
|
||||||
|
$stmt = $db->prepare("INSERT INTO _stock_units (outlet_id, name_en, name_ar, short_name_en, short_name_ar) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE name_ar = VALUES(name_ar), short_name_en = VALUES(short_name_en), short_name_ar = VALUES(short_name_ar)");
|
||||||
|
$stmt->execute([$target_id, $unit['name_en'], $unit['name_ar'], $unit['short_name_en'], $unit['short_name_ar']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$s_units = $db->prepare("SELECT id, name_en FROM _stock_units WHERE outlet_id = ?");
|
||||||
|
$s_units->execute([$source_id]);
|
||||||
|
$source_units_by_name = $s_units->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
|
||||||
|
$t_units = $db->prepare("SELECT id, name_en FROM _stock_units WHERE outlet_id = ?");
|
||||||
|
$t_units->execute([$target_id]);
|
||||||
|
$target_units_by_name = array_flip($t_units->fetchAll(PDO::FETCH_KEY_PAIR));
|
||||||
|
|
||||||
|
foreach ($source_units_by_name as $sid => $name) {
|
||||||
|
if (isset($target_units_by_name[$name])) {
|
||||||
|
$unit_map[$sid] = $target_units_by_name[$name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Sync Items
|
||||||
|
if (isset($_POST['sync_items'])) {
|
||||||
|
$source_items = $db->prepare("SELECT * FROM _stock_items WHERE outlet_id = ?");
|
||||||
|
$source_items->execute([$source_id]);
|
||||||
|
foreach ($source_items->fetchAll() as $item) {
|
||||||
|
// Check if item exists in target by SKU
|
||||||
|
$check_stmt = $db->prepare("SELECT id FROM _stock_items WHERE outlet_id = ? AND sku = ?");
|
||||||
|
$check_stmt->execute([$target_id, $item['sku']]);
|
||||||
|
$target_item_id = $check_stmt->fetchColumn();
|
||||||
|
|
||||||
|
$t_cat_id = $cat_map[$item['category_id']] ?? null;
|
||||||
|
$t_unit_id = $unit_map[$item['unit_id']] ?? null;
|
||||||
|
|
||||||
|
if ($target_item_id) {
|
||||||
|
// Update existing: Keep stock_quantity
|
||||||
|
$stmt = $db->prepare("UPDATE _stock_items SET
|
||||||
|
category_id = ?, unit_id = ?,
|
||||||
|
name_en = ?, name_ar = ?, purchase_price = ?, sale_price = ?,
|
||||||
|
min_stock_level = ?, expiry_date = ?, image_path = ?,
|
||||||
|
vat_rate = ?, is_promotion = ?, promotion_start = ?,
|
||||||
|
promotion_end = ?, promotion_percent = ?
|
||||||
|
WHERE id = ?");
|
||||||
|
$stmt->execute([
|
||||||
|
$t_cat_id, $t_unit_id,
|
||||||
|
$item['name_en'], $item['name_ar'], $item['purchase_price'], $item['sale_price'],
|
||||||
|
$item['min_stock_level'], $item['expiry_date'], $item['image_path'],
|
||||||
|
$item['vat_rate'], $item['is_promotion'], $item['promotion_start'],
|
||||||
|
$item['promotion_end'], $item['promotion_percent'], $target_item_id
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// Insert new: Set stock_quantity to 0
|
||||||
|
$stmt = $db->prepare("INSERT INTO _stock_items
|
||||||
|
(outlet_id, category_id, unit_id, supplier_id, name_en, name_ar, sku,
|
||||||
|
purchase_price, sale_price, stock_quantity, min_stock_level,
|
||||||
|
expiry_date, image_path, vat_rate, is_promotion, promotion_start,
|
||||||
|
promotion_end, promotion_percent)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([
|
||||||
|
$target_id, $t_cat_id, $t_unit_id, $item['supplier_id'],
|
||||||
|
$item['name_en'], $item['name_ar'], $item['sku'],
|
||||||
|
$item['purchase_price'], $item['sale_price'],
|
||||||
|
$item['min_stock_level'], $item['expiry_date'], $item['image_path'],
|
||||||
|
$item['vat_rate'], $item['is_promotion'], $item['promotion_start'],
|
||||||
|
$item['promotion_end'], $item['promotion_percent']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->commit();
|
||||||
|
redirectWithMessage("Data synchronization completed successfully!", "index.php?page=data_sync");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$db->rollBack();
|
||||||
|
redirectWithMessage("Error: " . $e->getMessage(), "index.php?page=data_sync");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Cash Register & Session Handlers ---
|
// --- Cash Register & Session Handlers ---
|
||||||
if (isset($_POST['add_cash_register'])) {
|
if (isset($_POST['add_cash_register'])) {
|
||||||
$name = $_POST['name'] ?? '';
|
$name = $_POST['name'] ?? '';
|
||||||
@ -3542,6 +3663,11 @@ switch ($page) {
|
|||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$data['outlets'] = $stmt->fetchAll();
|
$data['outlets'] = $stmt->fetchAll();
|
||||||
break;
|
break;
|
||||||
|
case 'data_sync':
|
||||||
|
$stmt = db()->prepare("SELECT * FROM outlets ORDER BY name ASC");
|
||||||
|
$stmt->execute();
|
||||||
|
$data['outlets'] = $stmt->fetchAll();
|
||||||
|
break;
|
||||||
case 'suppliers':
|
case 'suppliers':
|
||||||
$where = ["1=1"];
|
$where = ["1=1"];
|
||||||
$params = [];
|
$params = [];
|
||||||
@ -4770,6 +4896,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<a href="index.php?page=outlets" class="nav-link <?= $page === 'outlets' ? 'active' : '' ?>">
|
<a href="index.php?page=outlets" class="nav-link <?= $page === 'outlets' ? 'active' : '' ?>">
|
||||||
<i class="fas fa-shop"></i> <span>Outlets</span>
|
<i class="fas fa-shop"></i> <span>Outlets</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="index.php?page=data_sync" class="nav-link <?= $page === 'data_sync' ? 'active' : '' ?>">
|
||||||
|
<i class="fas fa-sync"></i> <span>Data Sync</span>
|
||||||
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<a href="index.php?page=role_groups" class="nav-link <?= $page === 'role_groups' ? 'active' : '' ?>">
|
<a href="index.php?page=role_groups" class="nav-link <?= $page === 'role_groups' ? 'active' : '' ?>">
|
||||||
<i class="fas fa-user-shield"></i> <span><?= __('role_groups') ?></span>
|
<i class="fas fa-user-shield"></i> <span><?= __('role_groups') ?></span>
|
||||||
@ -4795,6 +4924,11 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Tools Section -->
|
||||||
|
<a href="price_checker.php" target="_blank" class="nav-link">
|
||||||
|
<i class="fas fa-barcode"></i> <span>Price Checker</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
<!-- Help Section -->
|
<!-- Help Section -->
|
||||||
<a href="index.php?page=help" class="nav-link <?= $page === 'help' ? 'active' : '' ?>">
|
<a href="index.php?page=help" class="nav-link <?= $page === 'help' ? 'active' : '' ?>">
|
||||||
<i class="fas fa-question-circle"></i> <span><?= __('help') ?></span>
|
<i class="fas fa-question-circle"></i> <span><?= __('help') ?></span>
|
||||||
@ -5351,6 +5485,77 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<?php elseif ($page === 'data_sync' && ($_SESSION['user_role_name'] ?? '') === 'Administrator'): ?>
|
||||||
|
<div class="container-fluid p-0">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="h4 mb-0" data-en="Data Synchronization" data-ar="مزامنة البيانات">Data Synchronization</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm rounded-4">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold" data-en="Source Outlet" data-ar="الفرع المصدر">Source Outlet</label>
|
||||||
|
<select name="source_outlet_id" class="form-select form-select-lg" required>
|
||||||
|
<option value="">-- Select Source --</option>
|
||||||
|
<?php foreach ($data['outlets'] as $o): ?>
|
||||||
|
<option value="<?= $o['id'] ?>"><?= htmlspecialchars($o['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<div class="form-text mt-2" data-en="Data will be copied FROM this outlet." data-ar="سيتم نسخ البيانات من هذا الفرع.">Data will be copied FROM this outlet.</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold" data-en="Target Outlet" data-ar="الفرع الهدف">Target Outlet</label>
|
||||||
|
<select name="target_outlet_id" class="form-select form-select-lg" required>
|
||||||
|
<option value="">-- Select Target --</option>
|
||||||
|
<?php foreach ($data['outlets'] as $o): ?>
|
||||||
|
<option value="<?= $o['id'] ?>"><?= htmlspecialchars($o['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<div class="form-text mt-2" data-en="Data will be copied TO this outlet." data-ar="سيتم نسخ البيانات إلى هذا الفرع.">Data will be copied TO this outlet.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<hr class="my-4">
|
||||||
|
<h5 class="mb-3" data-en="Select Data to Sync" data-ar="اختر البيانات للمزامنة">Select Data to Sync</h5>
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="checkbox" name="sync_categories" id="sync_categories" checked>
|
||||||
|
<label class="form-check-label" for="sync_categories" data-en="Stock Categories" data-ar="فئات المخزون">Stock Categories</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="checkbox" name="sync_units" id="sync_units" checked>
|
||||||
|
<label class="form-check-label" for="sync_units" data-en="Stock Units" data-ar="وحدات المخزون">Stock Units</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="checkbox" name="sync_items" id="sync_items" checked>
|
||||||
|
<label class="form-check-label" for="sync_items" data-en="Stock Items" data-ar="أصناف المخزون">Stock Items</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mt-4">
|
||||||
|
<div class="alert alert-info border-0 shadow-sm">
|
||||||
|
<i class="bi bi-info-circle me-2"></i>
|
||||||
|
<span data-en="Note: For items, if they already exist in the target outlet (by SKU), their details will be updated but stock quantity will remain unchanged. If they are new, stock quantity will be set to 0."
|
||||||
|
data-ar="ملاحظة: بالنسبة للأصناف، إذا كانت موجودة بالفعل في الفرع الهدف (عن طريق SKU)، فسيتم تحديث تفاصيلها ولكن ستبقى كمية المخزون دون تغيير. إذا كانت جديدة، فسيتم تعيين كمية المخزون إلى 0.">
|
||||||
|
Note: For items, if they already exist in the target outlet (by SKU), their details will be updated but stock quantity will remain unchanged. If they are new, stock quantity will be set to 0.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mt-4 text-end">
|
||||||
|
<button type="submit" name="perform_data_sync" class="btn btn-primary btn-lg px-5 rounded-pill"
|
||||||
|
onclick="return confirm('Are you sure you want to sync data? This operation cannot be undone.')">
|
||||||
|
<i class="bi bi-arrow-repeat me-2"></i>
|
||||||
|
<span data-en="Start Synchronization" data-ar="بدء المزامنة">Start Synchronization</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<?php elseif ($page === 'customers' || $page === 'suppliers'): ?>
|
<?php elseif ($page === 'customers' || $page === 'suppliers'): ?>
|
||||||
<div class="card p-4">
|
<div class="card p-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
@ -10094,6 +10299,23 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php if (($_SESSION['user_role_name'] ?? '') === 'Administrator'): ?>
|
||||||
|
<div class="card p-4 mt-4">
|
||||||
|
<h5 class="mb-3" data-en="Advanced Tools" data-ar="أدوات متقدمة">Advanced Tools</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="p-3 border rounded bg-light">
|
||||||
|
<h6 class="fw-bold mb-2" data-en="Data Synchronization" data-ar="مزامنة البيانات">Data Synchronization</h6>
|
||||||
|
<p class="text-muted small" data-en="Copy items, categories, and units between outlets easily." data-ar="نسخ الأصناف والفئات والوحدات بين الفروع بسهولة.">Copy items, categories, and units between outlets easily.</p>
|
||||||
|
<a href="index.php?page=data_sync" class="btn btn-outline-primary btn-sm rounded-pill">
|
||||||
|
<i class="bi bi-arrow-repeat me-1"></i> <span data-en="Go to Data Sync" data-ar="الذهاب إلى مزامنة البيانات">Go to Data Sync</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php elseif ($page === 'role_groups'): ?>
|
<?php elseif ($page === 'role_groups'): ?>
|
||||||
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
|
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
|
||||||
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center border-0">
|
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center border-0">
|
||||||
|
|||||||
285
price_checker.php
Normal file
285
price_checker.php
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
|
||||||
|
// Fetch outlets for the selector
|
||||||
|
try {
|
||||||
|
$outlets = db()->query("SELECT id, name FROM outlets WHERE status = 'active'")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$outlets = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple API endpoint for price lookup
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] === 'lookup') {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$sku = $_GET['sku'] ?? '';
|
||||||
|
$outlet_id = $_GET['outlet_id'] ?? ($_COOKIE['preferred_outlet'] ?? null);
|
||||||
|
|
||||||
|
if (empty($sku)) {
|
||||||
|
echo json_encode(['error' => 'Empty SKU']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "SELECT name_en, name_ar, sale_price, image_path FROM stock_items WHERE sku = ?";
|
||||||
|
$params = [$sku];
|
||||||
|
|
||||||
|
if ($outlet_id) {
|
||||||
|
$sql .= " AND outlet_id = ?";
|
||||||
|
$params[] = $outlet_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = db()->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$item = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($item) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'item' => [
|
||||||
|
'name' => $item['name_en'] ?: $item['name_ar'],
|
||||||
|
'price' => number_format($item['sale_price'], 3),
|
||||||
|
'image' => $item['image_path'] ?: null
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Item not found']);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['error' => 'Database error']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<title>Price Checker</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
.checker-container {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
border-radius: 20px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.08);
|
||||||
|
}
|
||||||
|
#sku-input {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 15px;
|
||||||
|
border: 2px solid #dee2e6;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
#sku-input:focus {
|
||||||
|
border-color: #0d6efd;
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.1);
|
||||||
|
}
|
||||||
|
.result-card {
|
||||||
|
display: none;
|
||||||
|
margin-top: 20px;
|
||||||
|
animation: slideUp 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
}
|
||||||
|
@keyframes slideUp {
|
||||||
|
from { opacity: 0; transform: translateY(30px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
.price-tag {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #198754;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.currency {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
vertical-align: super;
|
||||||
|
margin-left: 5px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
.item-image {
|
||||||
|
max-width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
object-fit: contain;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
.outlet-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.settings-btn {
|
||||||
|
position: fixed;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
color: #6c757d;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<a href="#" class="settings-btn" data-bs-toggle="modal" data-bs-target="#settingsModal">
|
||||||
|
<i class="fas fa-cog fa-lg"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="checker-container">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<h1 class="fw-bold text-primary">Price Checker</h1>
|
||||||
|
<p class="text-muted">Quickly check item prices</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card p-4">
|
||||||
|
<form id="checker-form">
|
||||||
|
<div class="mb-0">
|
||||||
|
<input type="text" id="sku-input" class="form-control" placeholder="Scan Barcode..." autofocus autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100 py-3 mt-3 d-none">CHECK PRICE</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="result" class="result-card">
|
||||||
|
<div class="card p-4 text-center position-relative">
|
||||||
|
<div id="item-info">
|
||||||
|
<img id="item-img" src="" alt="" class="item-image d-none">
|
||||||
|
<h2 id="item-name" class="fw-bold mb-3"></h2>
|
||||||
|
<div class="price-tag mb-0">
|
||||||
|
<span id="item-price"></span>
|
||||||
|
<span class="currency">OMR</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="error-msg" class="alert alert-warning mt-3 d-none text-center rounded-pill"></div>
|
||||||
|
|
||||||
|
<div id="loading" class="text-center mt-3 d-none">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Settings Modal -->
|
||||||
|
<div class="modal fade" id="settingsModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow" style="border-radius: 20px;">
|
||||||
|
<div class="modal-header border-0">
|
||||||
|
<h5 class="modal-title fw-bold">Settings</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<label class="form-label fw-bold">Select Outlet</label>
|
||||||
|
<select id="outlet-select" class="form-select form-select-lg" style="border-radius: 12px;">
|
||||||
|
<option value="">All Outlets</option>
|
||||||
|
<?php foreach ($outlets as $o): ?>
|
||||||
|
<option value="<?= $o['id'] ?>" <?= ($_COOKIE['preferred_outlet'] ?? '') == $o['id'] ? 'selected' : '' ?>>
|
||||||
|
<?= htmlspecialchars($o['name']) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<p class="small text-muted mt-2">Prices will be checked for the selected outlet.</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0">
|
||||||
|
<button type="button" class="btn btn-primary w-100 py-2" style="border-radius: 12px;" data-bs-dismiss="modal" id="save-settings">Save Settings</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const form = document.getElementById('checker-form');
|
||||||
|
const input = document.getElementById('sku-input');
|
||||||
|
const resultDiv = document.getElementById('result');
|
||||||
|
const itemName = document.getElementById('item-name');
|
||||||
|
const itemPrice = document.getElementById('item-price');
|
||||||
|
const itemImg = document.getElementById('item-img');
|
||||||
|
const errorMsg = document.getElementById('error-msg');
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
|
const outletSelect = document.getElementById('outlet-select');
|
||||||
|
|
||||||
|
// Keep input focused
|
||||||
|
setInterval(() => {
|
||||||
|
if (document.activeElement !== input && document.activeElement.tagName !== 'SELECT' && !document.querySelector('.modal.show')) {
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// Save outlet preference
|
||||||
|
document.getElementById('save-settings').addEventListener('click', () => {
|
||||||
|
document.cookie = `preferred_outlet=${outletSelect.value}; path=/; max-age=${60*60*24*365}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
form.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const sku = input.value.trim();
|
||||||
|
if (!sku) return;
|
||||||
|
|
||||||
|
errorMsg.classList.add('d-none');
|
||||||
|
loading.classList.remove('d-none');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`?action=lookup&sku=${encodeURIComponent(sku)}`);
|
||||||
|
const data = await response.json();
|
||||||
|
loading.classList.add('d-none');
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
itemName.textContent = data.item.name;
|
||||||
|
itemPrice.textContent = data.item.price;
|
||||||
|
if (data.item.image) {
|
||||||
|
itemImg.src = data.item.image;
|
||||||
|
itemImg.classList.remove('d-none');
|
||||||
|
} else {
|
||||||
|
itemImg.classList.add('d-none');
|
||||||
|
}
|
||||||
|
resultDiv.style.display = 'block';
|
||||||
|
input.value = '';
|
||||||
|
|
||||||
|
// Vibrate if supported
|
||||||
|
if (navigator.vibrate) navigator.vibrate(50);
|
||||||
|
} else {
|
||||||
|
resultDiv.style.display = 'none';
|
||||||
|
errorMsg.textContent = data.message || 'Item not found';
|
||||||
|
errorMsg.classList.remove('d-none');
|
||||||
|
input.value = '';
|
||||||
|
if (navigator.vibrate) navigator.vibrate([100, 50, 100]);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
loading.classList.add('d-none');
|
||||||
|
errorMsg.textContent = 'Connection error';
|
||||||
|
errorMsg.classList.remove('d-none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-submit after 400ms of inactivity if something was typed (typical for barcode scanners)
|
||||||
|
let timer;
|
||||||
|
input.addEventListener('input', () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
if (input.value.length > 2) {
|
||||||
|
form.dispatchEvent(new Event('submit'));
|
||||||
|
}
|
||||||
|
}, 400);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user