Autosave: 20260319-060502

This commit is contained in:
Flatlogic Bot 2026-03-19 06:05:02 +00:00
parent eca10c44c7
commit fe82c03087
9 changed files with 346 additions and 108 deletions

View File

@ -0,0 +1,38 @@
-- Make definitions local to outlets
-- 1. Suppliers
ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT 1;
ALTER TABLE suppliers ADD CONSTRAINT fk_suppliers_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE;
-- 2. Categories
ALTER TABLE stock_categories ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT 1;
ALTER TABLE stock_categories ADD CONSTRAINT fk_categories_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE;
-- 3. Units
ALTER TABLE stock_units ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT 1;
ALTER TABLE stock_units ADD CONSTRAINT fk_units_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE;
-- 4. Items
ALTER TABLE stock_items ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT 1;
ALTER TABLE stock_items ADD CONSTRAINT fk_items_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE;
-- 5. Fix SKU Unique Constraint (Global -> Local)
-- First, drop the global unique index if it exists.
-- We need to know the name. Usually 'sku'. We'll try to drop it.
-- MySQL might fail if index doesn't exist, so we use a procedure or just try/catch in PHP?
-- For SQL file, we can't easily do try/catch.
-- We'll try to drop 'sku' index. If it fails, it fails (user might need to run manually).
-- Safest is to just ADD the new one and let the old one be (if duplicate SKUs are allowed across outlets).
-- But we want to ALLOW duplicate SKUs across outlets.
-- So we MUST drop the unique constraint on `sku`.
DROP INDEX sku ON stock_items;
-- Re-add as non-unique (just index) or part of composite unique
CREATE UNIQUE INDEX unique_sku_outlet ON stock_items (sku, outlet_id);
-- 6. Migrate Stock from outlet_stock to stock_items (for Outlet 1)
-- We assume current items belong to Outlet 1.
UPDATE stock_items si
JOIN outlet_stock os ON si.id = os.item_id AND os.outlet_id = 1
SET si.stock_quantity = os.quantity;
-- We leave outlet_stock for now, but application logic will switch to stock_items.stock_quantity

View File

@ -84,3 +84,22 @@
2026-03-18 22:32:15 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true} 2026-03-18 22:32:15 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
2026-03-18 22:32:28 - Items case hit 2026-03-18 22:32:28 - Items case hit
2026-03-18 22:33:07 - Items case hit 2026-03-18 22:33:07 - Items case hit
2026-03-18 22:40:07 - Items case hit
2026-03-18 22:40:13 - Items case hit
2026-03-18 22:40:18 - Items case hit
2026-03-18 22:40:29 - Items case hit
2026-03-18 22:40:46 - Items case hit
2026-03-18 22:41:01 - Items case hit
2026-03-18 22:41:16 - Items case hit
2026-03-18 23:02:18 - Items case hit
2026-03-18 23:03:06 - Items case hit
2026-03-18 23:03:19 - Items case hit
2026-03-18 23:05:39 - Items case hit
2026-03-18 23:06:02 - Items case hit
2026-03-19 05:57:24 - Items case hit
2026-03-19 05:57:45 - Items case hit
2026-03-19 05:58:17 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
2026-03-19 05:58:34 - Items case hit
2026-03-19 05:58:51 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
2026-03-19 05:59:01 - Items case hit
2026-03-19 06:00:05 - Items case hit

View File

@ -95,6 +95,7 @@ $translations = [
'cash' => 'Cash', 'cash' => 'Cash',
'card' => 'Credit Card', 'card' => 'Credit Card',
'credit' => 'Credit', 'credit' => 'Credit',
'copy_outlet_data' => 'Copy Outlet Data',
], ],
'ar' => [ 'ar' => [
'dashboard' => 'لوحة القيادة', 'dashboard' => 'لوحة القيادة',
@ -191,6 +192,7 @@ $translations = [
'cash' => 'نقد', 'cash' => 'نقد',
'card' => 'بطاقة ائتمان', 'card' => 'بطاقة ائتمان',
'credit' => 'آجل', 'credit' => 'آجل',
'copy_outlet_data' => 'نسخ بيانات الفرع',
] ]
]; ];

View File

@ -3,30 +3,21 @@
if (!function_exists('current_outlet_id')) { if (!function_exists('current_outlet_id')) {
function current_outlet_id() { function current_outlet_id() {
if (session_status() === PHP_SESSION_NONE) session_start(); if (session_status() === PHP_SESSION_NONE && !headers_sent()) session_start();
return (int)($_SESSION['outlet_id'] ?? 1); return (int)($_SESSION['outlet_id'] ?? 1);
} }
} }
if (!function_exists('update_stock')) { if (!function_exists('update_stock')) {
function update_stock($item_id, $qty, $outlet_id = null) { function update_stock($item_id, $qty, $outlet_id = null) {
if ($outlet_id === null) { // With local stock items, item_id is unique and holds the correct stock_quantity.
$outlet_id = current_outlet_id(); // We can update it directly.
} // We ignore $outlet_id because item_id already implies the outlet (if we enforce separation).
// However, for extra safety or validation, we could check.
// But for now, simple update is best.
$db = db(); $db = db();
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
// 1. Update/Insert into outlet_stock (Per-Outlet Source of Truth) $stmt->execute([$qty, $item_id]);
$check = $db->prepare("SELECT id FROM outlet_stock WHERE outlet_id = ? AND item_id = ?");
$check->execute([$outlet_id, $item_id]);
if (!$check->fetchColumn()) {
$db->prepare("INSERT INTO outlet_stock (outlet_id, item_id, quantity) VALUES (?, ?, 0)")->execute([$outlet_id, $item_id]);
}
$stmt = $db->prepare("UPDATE outlet_stock SET quantity = quantity + ? WHERE outlet_id = ? AND item_id = ?");
$stmt->execute([$qty, $outlet_id, $item_id]);
// 2. Update global stock_items (Legacy/Aggregate Cache)
$stmtGlobal = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
$stmtGlobal->execute([$qty, $item_id]);
} }
} }

129
index.php
View File

@ -446,7 +446,7 @@ if (isset($_GET['action']) || isset($_POST['action'])) {
exit; exit;
} }
$searchTerm = "%$q%"; $searchTerm = "%$q%";
$oid = current_outlet_id(); $stmt = db()->prepare("SELECT i.*, COALESCE(os.quantity, 0) as stock_quantity FROM stock_items i LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $oid WHERE (i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?) LIMIT 15"); $oid = current_outlet_id(); $stmt = db()->prepare("SELECT i.*, i.stock_quantity FROM stock_items i WHERE (i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?) LIMIT 15");
$stmt->execute([$searchTerm, $searchTerm, $searchTerm]); $stmt->execute([$searchTerm, $searchTerm, $searchTerm]);
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)); echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
exit; exit;
@ -840,12 +840,10 @@ function getPromotionalPrice($item) {
if (!is_dir('uploads')) mkdir('uploads', 0777, true); if (!is_dir('uploads')) mkdir('uploads', 0777, true);
if (move_uploaded_file($_FILES['image']['tmp_name'], $filename)) $image_path = $filename; if (move_uploaded_file($_FILES['image']['tmp_name'], $filename)) $image_path = $filename;
} }
$stmt = db()->prepare("INSERT INTO stock_items (name_en, name_ar, category_id, unit_id, supplier_id, sku, sale_price, purchase_price, stock_quantity, min_stock_level, image_path, vat_rate, expiry_date, is_promotion, promotion_start, promotion_end, promotion_percent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
// Insert 0 for global stock, real stock goes to outlet_stock
$stmt->execute([$name_en, $name_ar, $category_id, $unit_id, $supplier_id, $sku, $sale_price, $purchase_price, 0, $min_stock_level, $image_path, $vat_rate, $expiry_date, $is_promotion, $promotion_start, $promotion_end, $promotion_percent]);
$new_item_id = db()->lastInsertId();
$current_oid = current_outlet_id(); $current_oid = current_outlet_id();
db()->prepare("INSERT INTO outlet_stock (outlet_id, item_id, quantity) VALUES (?, ?, ?)")->execute([$current_oid, $new_item_id, $stock_quantity]); $stmt = db()->prepare("INSERT INTO stock_items (outlet_id, name_en, name_ar, category_id, unit_id, supplier_id, sku, sale_price, purchase_price, stock_quantity, min_stock_level, image_path, vat_rate, expiry_date, is_promotion, promotion_start, promotion_end, promotion_percent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$current_oid, $name_en, $name_ar, $category_id, $unit_id, $supplier_id, $sku, $sale_price, $purchase_price, $stock_quantity, $min_stock_level, $image_path, $vat_rate, $expiry_date, $is_promotion, $promotion_start, $promotion_end, $promotion_percent]);
$new_item_id = db()->lastInsertId();
redirectWithMessage("Item added successfully!"); redirectWithMessage("Item added successfully!");
} }
@ -867,19 +865,9 @@ function getPromotionalPrice($item) {
$promotion_start = !empty($_POST['promotion_start']) ? $_POST['promotion_start'] : null; $promotion_start = !empty($_POST['promotion_start']) ? $_POST['promotion_start'] : null;
$promotion_end = !empty($_POST['promotion_end']) ? $_POST['promotion_end'] : null; $promotion_end = !empty($_POST['promotion_end']) ? $_POST['promotion_end'] : null;
$promotion_percent = (float)($_POST['promotion_percent'] ?? 0); $promotion_percent = (float)($_POST['promotion_percent'] ?? 0);
// Update stock_items (excluding stock_quantity) // Update stock_items
$stmt = db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, category_id = ?, unit_id = ?, supplier_id = ?, sku = ?, sale_price = ?, purchase_price = ?, min_stock_level = ?, vat_rate = ?, expiry_date = ?, is_promotion = ?, promotion_start = ?, promotion_end = ?, promotion_percent = ? WHERE id = ?"); $stmt = db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, category_id = ?, unit_id = ?, supplier_id = ?, sku = ?, sale_price = ?, purchase_price = ?, stock_quantity = ?, min_stock_level = ?, vat_rate = ?, expiry_date = ?, is_promotion = ?, promotion_start = ?, promotion_end = ?, promotion_percent = ? WHERE id = ?");
$stmt->execute([$name_en, $name_ar, $category_id, $unit_id, $supplier_id, $sku, $sale_price, $purchase_price, $min_stock_level, $vat_rate, $expiry_date, $is_promotion, $promotion_start, $promotion_end, $promotion_percent, $id]); $stmt->execute([$name_en, $name_ar, $category_id, $unit_id, $supplier_id, $sku, $sale_price, $purchase_price, $stock_quantity, $min_stock_level, $vat_rate, $expiry_date, $is_promotion, $promotion_start, $promotion_end, $promotion_percent, $id]);
// Update outlet_stock
$current_oid = current_outlet_id();
$check = db()->prepare("SELECT id FROM outlet_stock WHERE outlet_id = ? AND item_id = ?");
$check->execute([$current_oid, $id]);
if ($check->fetch()) {
db()->prepare("UPDATE outlet_stock SET quantity = ? WHERE outlet_id = ? AND item_id = ?")->execute([$stock_quantity, $current_oid, $id]);
} else {
db()->prepare("INSERT INTO outlet_stock (outlet_id, item_id, quantity) VALUES (?, ?, ?)")->execute([$current_oid, $id, $stock_quantity]);
}
if (isset($_FILES['image']) && $_FILES['image']['error'] === 0) { if (isset($_FILES['image']) && $_FILES['image']['error'] === 0) {
$ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION); $ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$filename = 'uploads/item_' . $id . '_' . time() . '.' . $ext; $filename = 'uploads/item_' . $id . '_' . time() . '.' . $ext;
@ -902,11 +890,11 @@ function getPromotionalPrice($item) {
db()->query("UPDATE stock_items SET is_promotion = 0 WHERE is_promotion = 1 AND promotion_end IS NOT NULL AND promotion_end < '" . date('Y-m-d') . "'"); db()->query("UPDATE stock_items SET is_promotion = 0 WHERE is_promotion = 1 AND promotion_end IS NOT NULL AND promotion_end < '" . date('Y-m-d') . "'");
if (isset($_POST['add_category'])) { if (isset($_POST['add_category'])) {
db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '']); db()->prepare("INSERT INTO stock_categories (name_en, name_ar, outlet_id) VALUES (?, ?, current_outlet_id())")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '']);
redirectWithMessage("Category added!"); redirectWithMessage("Category added!");
} }
if (isset($_POST['add_unit'])) { if (isset($_POST['add_unit'])) {
db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar) VALUES (?, ?, ?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', $_POST['short_en'] ?? '', $_POST['short_ar'] ?? '']); db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar, outlet_id) VALUES (?, ?, ?, ?, current_outlet_id())")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', $_POST['short_en'] ?? '', $_POST['short_ar'] ?? '']);
redirectWithMessage("Unit added!"); redirectWithMessage("Unit added!");
} }
@ -929,7 +917,7 @@ function getPromotionalPrice($item) {
if (isset($_POST['add_customer'])) { if (isset($_POST['add_customer'])) {
$table = ($_POST['type'] ?? '') === 'supplier' ? 'suppliers' : 'customers'; $table = ($_POST['type'] ?? '') === 'supplier' ? 'suppliers' : 'customers';
$sql = "INSERT INTO $table (name, email, phone, tax_id, balance" . ($table === 'customers' ? ", loyalty_points" : "") . ") VALUES (?, ?, ?, ?, ?" . ($table === 'customers' ? ", 0" : "") . ")"; $sql = "INSERT INTO $table (name, email, phone, tax_id, balance" . ($table === 'customers' ? ", loyalty_points" : ", outlet_id") . ") VALUES (?, ?, ?, ?, ?" . ($table === 'customers' ? ", 0" : ", " . current_outlet_id()) . ")";
db()->prepare($sql)->execute([$_POST['name'] ?? '', $_POST['email'] ?? '', $_POST['phone'] ?? '', $_POST['tax_id'] ?? '', (float)($_POST['balance'] ?? 0)]); db()->prepare($sql)->execute([$_POST['name'] ?? '', $_POST['email'] ?? '', $_POST['phone'] ?? '', $_POST['tax_id'] ?? '', (float)($_POST['balance'] ?? 0)]);
redirectWithMessage("Entity added!"); redirectWithMessage("Entity added!");
} }
@ -1405,6 +1393,7 @@ function getPromotionalPrice($item) {
} }
if (isset($rows[0][0]) && stripos($rows[0][0], 'sku') !== false) array_shift($rows); if (isset($rows[0][0]) && stripos($rows[0][0], 'sku') !== false) array_shift($rows);
$current_oid = current_outlet_id();
foreach ($rows as $row) { foreach ($rows as $row) {
if (empty($row[0])) continue; if (empty($row[0])) continue;
$sku = trim((string)$row[0]); $sku = trim((string)$row[0]);
@ -1415,33 +1404,19 @@ function getPromotionalPrice($item) {
$qty = (float)($row[5] ?? 0); $qty = (float)($row[5] ?? 0);
$vat_rate = (float)($row[6] ?? 0); $vat_rate = (float)($row[6] ?? 0);
$check = db()->prepare("SELECT id FROM stock_items WHERE sku = ?"); $check = db()->prepare("SELECT id FROM stock_items WHERE sku = ? AND outlet_id = ?");
$check->execute([$sku]); $check->execute([$sku, $current_oid]);
$exists = $check->fetch(PDO::FETCH_ASSOC); $exists = $check->fetch(PDO::FETCH_ASSOC);
$current_oid = current_outlet_id();
if ($exists) { if ($exists) {
$item_id = $exists['id']; $item_id = $exists['id'];
// Update Item (excluding stock_quantity) // Update Item (including stock_quantity)
db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, sale_price = ?, purchase_price = ?, vat_rate = ? WHERE id = ?") db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, sale_price = ?, purchase_price = ?, vat_rate = ?, stock_quantity = ? WHERE id = ?")
->execute([$name_en, $name_ar, $sale_price, $purchase_price, $vat_rate, $item_id]); ->execute([$name_en, $name_ar, $sale_price, $purchase_price, $vat_rate, $qty, $item_id]);
// Update Outlet Stock
$os_check = db()->prepare("SELECT id FROM outlet_stock WHERE outlet_id = ? AND item_id = ?");
$os_check->execute([$current_oid, $item_id]);
if ($os_check->fetch()) {
db()->prepare("UPDATE outlet_stock SET quantity = ? WHERE outlet_id = ? AND item_id = ?")->execute([$qty, $current_oid, $item_id]);
} else {
db()->prepare("INSERT INTO outlet_stock (outlet_id, item_id, quantity) VALUES (?, ?, ?)")->execute([$current_oid, $item_id, $qty]);
}
} else { } else {
// Insert Item // Insert Item
db()->prepare("INSERT INTO stock_items (sku, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate) VALUES (?, ?, ?, ?, ?, ?, ?)") db()->prepare("INSERT INTO stock_items (outlet_id, sku, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
->execute([$sku, $name_en, $name_ar, $sale_price, $purchase_price, 0, $vat_rate]); ->execute([$current_oid, $sku, $name_en, $name_ar, $sale_price, $purchase_price, $qty, $vat_rate]);
$item_id = db()->lastInsertId();
// Insert Outlet Stock
db()->prepare("INSERT INTO outlet_stock (outlet_id, item_id, quantity) VALUES (?, ?, ?)")->execute([$current_oid, $item_id, $qty]);
} }
$count++; $count++;
} }
@ -1477,7 +1452,7 @@ function getPromotionalPrice($item) {
$phone = trim((string)($row[2] ?? '')); $phone = trim((string)($row[2] ?? ''));
$tax_id = trim((string)($row[3] ?? '')); $tax_id = trim((string)($row[3] ?? ''));
db()->prepare("INSERT INTO $table (name, email, phone, tax_id, created_at) VALUES (?, ?, ?, ?, NOW())") db()->prepare("INSERT INTO $table (name, email, phone, tax_id, created_at" . ($table === 'suppliers' ? ", outlet_id" : "") . ") VALUES (?, ?, ?, ?, NOW()" . ($table === 'suppliers' ? ", ".current_outlet_id() : "") . ")")
->execute([$name, $email, $phone, $tax_id]); ->execute([$name, $email, $phone, $tax_id]);
$count++; $count++;
} }
@ -1503,7 +1478,7 @@ function getPromotionalPrice($item) {
$name_en = trim((string)$row[0]); $name_en = trim((string)$row[0]);
$name_ar = trim((string)($row[1] ?? $name_en)); $name_ar = trim((string)($row[1] ?? $name_en));
db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)") db()->prepare("INSERT INTO stock_categories (name_en, name_ar, outlet_id) VALUES (?, ?, current_outlet_id())")
->execute([$name_en, $name_ar]); ->execute([$name_en, $name_ar]);
$count++; $count++;
} }
@ -2935,11 +2910,11 @@ if ($page === 'export') {
$headers = ['LPO #', 'Supplier', 'Date', 'Total', 'Status']; $headers = ['LPO #', 'Supplier', 'Date', 'Total', 'Status'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'categories') { } elseif ($type === 'categories') {
$stmt = db()->query("SELECT id, name_en, name_ar FROM stock_categories ORDER BY id DESC"); $stmt = db()->query("SELECT id, name_en, name_ar FROM stock_categories WHERE outlet_id = ".current_outlet_id()." ORDER BY id DESC");
$headers = ['ID', 'Name (EN)', 'Name (AR)']; $headers = ['ID', 'Name (EN)', 'Name (AR)'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'units') { } elseif ($type === 'units') {
$stmt = db()->query("SELECT id, name_en, name_ar, short_name_en, short_name_ar FROM stock_units ORDER BY id DESC"); $stmt = db()->query("SELECT id, name_en, name_ar, short_name_en, short_name_ar FROM stock_units WHERE outlet_id = ".current_outlet_id()." ORDER BY id DESC");
$headers = ['ID', 'Name (EN)', 'Name (AR)', 'Short (EN)', 'Short (AR)']; $headers = ['ID', 'Name (EN)', 'Name (AR)', 'Short (EN)', 'Short (AR)'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'sales_returns') { } elseif ($type === 'sales_returns') {
@ -2991,9 +2966,9 @@ if ($page === 'export') {
} }
// Global data for modals // Global data for modals
$data['categories'] = db()->query("SELECT * FROM stock_categories ORDER BY name_en ASC")->fetchAll(); $data['categories'] = db()->query("SELECT * FROM stock_categories WHERE outlet_id = ".current_outlet_id()." ORDER BY name_en ASC")->fetchAll();
$data['units'] = db()->query("SELECT * FROM stock_units ORDER BY name_en ASC")->fetchAll(); $data['units'] = db()->query("SELECT * FROM stock_units WHERE outlet_id = ".current_outlet_id()." ORDER BY name_en ASC")->fetchAll();
$data['suppliers'] = db()->query("SELECT * FROM suppliers ORDER BY name ASC")->fetchAll(); $data['suppliers'] = db()->query("SELECT * FROM suppliers WHERE outlet_id = ".current_outlet_id()." ORDER BY name ASC")->fetchAll();
$data['accounts'] = db()->query("SELECT * FROM acc_accounts ORDER BY code ASC")->fetchAll(); $data['accounts'] = db()->query("SELECT * FROM acc_accounts ORDER BY code ASC")->fetchAll();
$data['customers_list'] = db()->query("SELECT * FROM customers ORDER BY name ASC")->fetchAll(); $data['customers_list'] = db()->query("SELECT * FROM customers ORDER BY name ASC")->fetchAll();
$customers = $data['customers_list']; // For backward compatibility in some modals $customers = $data['customers_list']; // For backward compatibility in some modals
@ -3021,7 +2996,7 @@ if ($page_num < 1) $page_num = 1;
$offset = ($page_num - 1) * $limit; $offset = ($page_num - 1) * $limit;
switch ($page) { switch ($page) {
case 'suppliers': case 'suppliers':
$where = ["1=1"]; $where = ["outlet_id = " . current_outlet_id()];
$params = []; $params = [];
if (!empty($_GET['search'])) { if (!empty($_GET['search'])) {
$where[] = "(name LIKE ? OR email LIKE ? OR phone LIKE ? OR tax_id LIKE ?)"; $where[] = "(name LIKE ? OR email LIKE ? OR phone LIKE ? OR tax_id LIKE ?)";
@ -3088,7 +3063,7 @@ switch ($page) {
break; break;
case 'items': case 'items':
file_put_contents('debug.log', date('Y-m-d H:i:s') . " - Items case hit\n", FILE_APPEND); file_put_contents('debug.log', date('Y-m-d H:i:s') . " - Items case hit\n", FILE_APPEND);
$where = ["1=1"]; $where = ["i.outlet_id = " . current_outlet_id()];
$params = []; $params = [];
if (!empty($_GET['search'])) { if (!empty($_GET['search'])) {
$where[] = "(i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?)"; $where[] = "(i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?)";
@ -3108,9 +3083,9 @@ switch ($page) {
$data['current_page'] = $page_num; $data['current_page'] = $page_num;
$oid = current_outlet_id(); $oid = current_outlet_id();
$stmt = db()->prepare("SELECT i.*, COALESCE(os.quantity, 0) as stock_quantity, c.name_en as cat_en, c.name_ar as cat_ar, u.short_name_en as unit_en, u.short_name_ar as unit_ar, s.name as supplier_name $stmt = db()->prepare("SELECT i.*, i.stock_quantity, c.name_en as cat_en, c.name_ar as cat_ar, u.short_name_en as unit_en, u.short_name_ar as unit_ar, s.name as supplier_name
FROM stock_items i FROM stock_items i
LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $oid
LEFT JOIN stock_categories c ON i.category_id = c.id LEFT JOIN stock_categories c ON i.category_id = c.id
LEFT JOIN stock_units u ON i.unit_id = u.id LEFT JOIN stock_units u ON i.unit_id = u.id
LEFT JOIN suppliers s ON i.supplier_id = s.id LEFT JOIN suppliers s ON i.supplier_id = s.id
@ -3250,6 +3225,7 @@ switch ($page) {
} }
$data['outlets'] = db()->query("SELECT * FROM outlets ORDER BY id ASC")->fetchAll(); $data['outlets'] = db()->query("SELECT * FROM outlets ORDER BY id ASC")->fetchAll();
break; break;
case 'copy_outlet_data': require 'pages/copy_outlet_data_logic.php'; break;
case 'settings': case 'settings':
// Already fetched globally // Already fetched globally
break; break;
@ -3334,7 +3310,7 @@ switch ($page) {
} }
unset($inv); unset($inv);
$oid = current_outlet_id(); $items_list_raw = db()->query("SELECT i.id, i.name_en, i.name_ar, i.sale_price, i.purchase_price, COALESCE(os.quantity, 0) as stock_quantity, i.vat_rate, i.is_promotion, i.promotion_start, i.promotion_end, i.promotion_percent FROM stock_items i LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $oid ORDER BY i.name_en ASC")->fetchAll(PDO::FETCH_ASSOC); $oid = current_outlet_id(); $items_list_raw = db()->query("SELECT i.id, i.name_en, i.name_ar, i.sale_price, i.purchase_price, i.stock_quantity, i.vat_rate, i.is_promotion, i.promotion_start, i.promotion_end, i.promotion_percent FROM stock_items i ORDER BY i.name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
foreach ($items_list_raw as &$item) { foreach ($items_list_raw as &$item) {
$item['sale_price'] = getPromotionalPrice($item); $item['sale_price'] = getPromotionalPrice($item);
} }
@ -3544,13 +3520,13 @@ switch ($page) {
break; break;
case 'low_stock_report': case 'low_stock_report':
$oid = current_outlet_id(); $oid = current_outlet_id();
$stmt = db()->prepare("SELECT i.*, COALESCE(os.quantity, 0) as stock_quantity, c.name_en as cat_en, c.name_ar as cat_ar, s.name as supplier_name $stmt = db()->prepare("SELECT i.*, i.stock_quantity, c.name_en as cat_en, c.name_ar as cat_ar, s.name as supplier_name
FROM stock_items i FROM stock_items i
LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $oid
LEFT JOIN stock_categories c ON i.category_id = c.id LEFT JOIN stock_categories c ON i.category_id = c.id
LEFT JOIN suppliers s ON i.supplier_id = s.id LEFT JOIN suppliers s ON i.supplier_id = s.id
WHERE COALESCE(os.quantity, 0) <= i.min_stock_level WHERE i.outlet_id = $oid AND i.stock_quantity <= i.min_stock_level
ORDER BY (i.min_stock_level - COALESCE(os.quantity, 0)) DESC"); ORDER BY (i.min_stock_level - i.stock_quantity) DESC");
$stmt->execute(); $stmt->execute();
$data['low_stock_items'] = $stmt->fetchAll(); $data['low_stock_items'] = $stmt->fetchAll();
break; break;
@ -3697,7 +3673,7 @@ switch ($page) {
$pay_pur_cond = ($current_oid > 0) ? " WHERE p.outlet_id = $current_oid " : ""; $pay_pur_cond = ($current_oid > 0) ? " WHERE p.outlet_id = $current_oid " : "";
$low_stock_query = ($current_oid > 0) $low_stock_query = ($current_oid > 0)
? "SELECT COUNT(*) FROM stock_items i LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $current_oid WHERE COALESCE(os.quantity, 0) <= i.min_stock_level" ? "SELECT COUNT(*) FROM stock_items WHERE outlet_id = $current_oid AND stock_quantity <= min_stock_level"
: "SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level"; : "SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level";
$data['stats'] = [ $data['stats'] = [
@ -3728,7 +3704,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title><?= __('accounting') ?> - Admin Panel</title> <title><?= htmlspecialchars(__($page)) ?> - Admin Panel</title>
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" /> <meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php if (!empty($data['settings']['favicon'])): ?> <?php if (!empty($data['settings']['favicon'])): ?>
<link rel="icon" href="<?= htmlspecialchars($data['settings']['favicon']) ?>?v=<?= time() ?>"> <link rel="icon" href="<?= htmlspecialchars($data['settings']['favicon']) ?>?v=<?= time() ?>">
@ -3872,8 +3848,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<?php endif; ?> <?php endif; ?>
<div class="sidebar"> <div class="sidebar">
<div class="sidebar-header"> <div class="sidebar-header text-center">
<div class="text-primary fw-bold">Meezan Accounting System</div> <div class="text-white fw-bold"><i class="fas fa-balance-scale me-2"></i> Meezan Accounting System</div>
<div class="text-muted small" style="font-size: 0.7rem;">System v1.2.5</div> <div class="text-muted small" style="font-size: 0.7rem;">System v1.2.5</div>
</div> </div>
<nav class="mt-4"> <nav class="mt-4">
@ -4223,15 +4199,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<a href="?lang=<?= $lang === 'ar' ? 'en' : 'ar' ?>" class="btn btn-outline-secondary btn-sm me-3"> <a href="?lang=<?= $lang === 'ar' ? 'en' : 'ar' ?>" class="btn btn-outline-secondary btn-sm me-3">
<i class="bi bi-translate"></i> <span><?= $lang === 'ar' ? 'English' : 'العربية' ?></span> <i class="bi bi-translate"></i> <span><?= $lang === 'ar' ? 'English' : 'العربية' ?></span>
</a> </a>
<div class="me-3 d-none d-md-block text-end"> <div class="dropdown d-flex align-items-center">
<div class="fw-bold small">
<a href="index.php?page=my_profile" class="text-dark text-decoration-none">
<?= htmlspecialchars((string)($_SESSION['username'] ?? 'User')) ?>
</a>
</div>
<div class="text-muted" style="font-size: 0.7rem;"><?= htmlspecialchars((string)($_SESSION['user_role_name'] ?? '')) ?></div>
</div>
<div class="dropdown">
<!-- Outlet Switcher --> <!-- Outlet Switcher -->
<?php <?php
$user_outlets_list = $_SESSION['user_outlets'] ?? [1]; $user_outlets_list = $_SESSION['user_outlets'] ?? [1];
@ -4265,6 +4233,14 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
</ul> </ul>
</div> </div>
<?php endif; ?> <?php endif; ?>
<div class="me-3 d-none d-md-block text-end">
<div class="fw-bold small">
<a href="index.php?page=my_profile" class="text-dark text-decoration-none">
<?= htmlspecialchars((string)($_SESSION['username'] ?? 'User')) ?>
</a>
</div>
<div class="text-muted" style="font-size: 0.7rem;"><?= htmlspecialchars((string)($_SESSION['user_role_name'] ?? '')) ?></div>
</div>
<a href="index.php?page=my_profile" class="btn btn-light rounded-circle p-0 overflow-hidden shadow-sm d-inline-block position-relative" style="width: 40px; height: 40px;" title="<?= __('edit') ?>"> <a href="index.php?page=my_profile" class="btn btn-light rounded-circle p-0 overflow-hidden shadow-sm d-inline-block position-relative" style="width: 40px; height: 40px;" title="<?= __('edit') ?>">
<?php if (!empty($_SESSION['profile_pic'])): ?> <?php if (!empty($_SESSION['profile_pic'])): ?>
@ -8593,11 +8569,11 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<?php elseif ($page === "outlets" && ($_SESSION["user_role_name"] ?? "") === "Administrator"): ?> <?php elseif ($page === "outlets" && ($_SESSION["user_role_name"] ?? "") === "Administrator"): ?>
<?php require "outlets_html.php"; ?> <?php require "outlets_html.php"; ?>
<?php elseif ($page === 'settings'): ?> <?php elseif ($page === 'copy_outlet_data'): require 'pages/copy_outlet_data_view.php'; ?><?php elseif ($page === 'settings'): ?>
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-xl-10"> <div class="col-xl-10">
<div class="card border-0 shadow-sm rounded-4"> <div class="card border-0 shadow-sm rounded-4">
<div class="card-header bg-white py-3 border-bottom-0"> <div class="card-header bg-white py-3 border-bottom-0 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<div class="rounded-circle bg-primary bg-opacity-10 p-3 text-primary"> <div class="rounded-circle bg-primary bg-opacity-10 p-3 text-primary">
<i class="bi bi-building-gear fs-4"></i> <i class="bi bi-building-gear fs-4"></i>
@ -8607,6 +8583,11 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<p class="text-muted small mb-0" data-en="Manage your business identity and system preferences" data-ar="إدارة هوية عملك وتفضيلات النظام">Manage your business identity and system preferences</p> <p class="text-muted small mb-0" data-en="Manage your business identity and system preferences" data-ar="إدارة هوية عملك وتفضيلات النظام">Manage your business identity and system preferences</p>
</div> </div>
</div> </div>
<div>
<a href="index.php?page=copy_outlet_data" class="btn btn-outline-primary btn-sm">
<i class="bi bi-arrow-repeat me-1"></i> <span data-en="Sync Outlets" data-ar="مزامنة الفروع">Sync Outlets</span>
</a>
</div>
</div> </div>
<div class="card-body p-4"> <div class="card-body p-4">
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">

View File

@ -0,0 +1,142 @@
<?php
if (session_status() === PHP_SESSION_NONE) session_start();
if (!isset($_SESSION['user_id']) || ($_SESSION['user_role_name'] ?? '') !== 'Administrator') {
// If not admin, we might want to redirect or show error in view.
// For now, we'll let the view handle the error display or we can set a flag.
$access_denied = true;
return;
}
$db = db();
$outlets = $db->query("SELECT * FROM outlets ORDER BY id")->fetchAll();
$message = "";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$source_id = (int)$_POST['source_id'];
$target_id = (int)$_POST['target_id'];
$copy_items = isset($_POST['copy_items']);
$copy_categories = isset($_POST['copy_categories']);
$copy_units = isset($_POST['copy_units']);
$copy_suppliers = isset($_POST['copy_suppliers']);
if ($source_id === $target_id) {
$message = "<div class='alert alert-danger'>Source and Target cannot be the same.</div>";
} else {
try {
$db->beginTransaction();
// Helpers
function get_target_id($db, $table, $col_name, $val, $target_oid) {
$stmt = $db->prepare("SELECT id FROM $table WHERE $col_name = ? AND outlet_id = ?");
$stmt->execute([$val, $target_oid]);
return $stmt->fetchColumn();
}
// 1. Categories
$cat_map = []; // source_id => target_id
if ($copy_categories || $copy_items) {
$cats = $db->prepare("SELECT * FROM stock_categories WHERE outlet_id = ?");
$cats->execute([$source_id]);
while ($c = $cats->fetch()) {
$tid = get_target_id($db, 'stock_categories', 'name_en', $c['name_en'], $target_id);
if ($tid) {
// Update existing category
$upd = $db->prepare("UPDATE stock_categories SET name_ar = ? WHERE id = ?");
$upd->execute([$c['name_ar'], $tid]);
} else {
// Insert new category
$ins = $db->prepare("INSERT INTO stock_categories (name_en, name_ar, outlet_id) VALUES (?, ?, ?)");
$ins->execute([$c['name_en'], $c['name_ar'], $target_id]);
$tid = $db->lastInsertId();
}
$cat_map[$c['id']] = $tid;
}
}
// 2. Units
$unit_map = [];
if ($copy_units || $copy_items) {
$units = $db->prepare("SELECT * FROM stock_units WHERE outlet_id = ?");
$units->execute([$source_id]);
while ($u = $units->fetch()) {
$tid = get_target_id($db, 'stock_units', 'name_en', $u['name_en'], $target_id);
if ($tid) {
// Update existing unit
$upd = $db->prepare("UPDATE stock_units SET name_ar = ?, short_name_en = ?, short_name_ar = ? WHERE id = ?");
$upd->execute([$u['name_ar'], $u['short_name_en'], $u['short_name_ar'], $tid]);
} else {
// Insert new unit
$ins = $db->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar, outlet_id) VALUES (?, ?, ?, ?, ?)");
$ins->execute([$u['name_en'], $u['name_ar'], $u['short_name_en'], $u['short_name_ar'], $target_id]);
$tid = $db->lastInsertId();
}
$unit_map[$u['id']] = $tid;
}
}
// 3. Suppliers
$sup_map = [];
if ($copy_suppliers || $copy_items) {
$sups = $db->prepare("SELECT * FROM suppliers WHERE outlet_id = ?");
$sups->execute([$source_id]);
while ($s = $sups->fetch()) {
$tid = get_target_id($db, 'suppliers', 'name', $s['name'], $target_id);
if ($tid) {
// Update existing supplier
$upd = $db->prepare("UPDATE suppliers SET email = ?, phone = ?, tax_id = ?, credit_limit = ? WHERE id = ?");
$upd->execute([$s['email'], $s['phone'], $s['tax_id'], $s['credit_limit'], $tid]);
} else {
// Insert new supplier
$ins = $db->prepare("INSERT INTO suppliers (name, email, phone, tax_id, balance, credit_limit, created_at, outlet_id) VALUES (?, ?, ?, ?, 0, ?, NOW(), ?)");
$ins->execute([$s['name'], $s['email'], $s['phone'], $s['tax_id'], $s['credit_limit'], $target_id]);
$tid = $db->lastInsertId();
}
$sup_map[$s['id']] = $tid;
}
}
// 4. Items
if ($copy_items) {
$items = $db->prepare("SELECT * FROM stock_items WHERE outlet_id = ?");
$items->execute([$source_id]);
while ($i = $items->fetch()) {
// Check existence by SKU
$check = $db->prepare("SELECT id FROM stock_items WHERE sku = ? AND outlet_id = ?");
$check->execute([$i['sku'], $target_id]);
$exists_id = $check->fetchColumn();
$new_cat = $cat_map[$i['category_id']] ?? null;
$new_unit = $unit_map[$i['unit_id']] ?? null;
$new_sup = $sup_map[$i['supplier_id']] ?? null;
if ($exists_id) {
// Update Definition (Price, Names, etc) BUT KEEP STOCK QUANTITY
$upd = $db->prepare("UPDATE stock_items SET name_en=?, name_ar=?, category_id=?, unit_id=?, supplier_id=?, sale_price=?, purchase_price=?, min_stock_level=?, vat_rate=?, image_path=?, is_promotion=?, promotion_start=?, promotion_end=?, promotion_percent=? WHERE id=?");
$upd->execute([
$i['name_en'], $i['name_ar'], $new_cat, $new_unit, $new_sup,
$i['sale_price'], $i['purchase_price'], $i['min_stock_level'], $i['vat_rate'], $i['image_path'],
$i['is_promotion'], $i['promotion_start'], $i['promotion_end'], $i['promotion_percent'],
$exists_id
]);
} else {
// Insert New
// Start with 0 stock unless requested (User said "remains stock quantity as it" for existing. For new, we assume 0 or copy? 0 is safer for "Copy Data" vs "Transfer Stock").
$ins = $db->prepare("INSERT INTO stock_items (name_en, name_ar, category_id, unit_id, supplier_id, sku, sale_price, purchase_price, stock_quantity, min_stock_level, image_path, vat_rate, expiry_date, is_promotion, promotion_start, promotion_end, promotion_percent, outlet_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$ins->execute([
$i['name_en'], $i['name_ar'], $new_cat, $new_unit, $new_sup, $i['sku'],
$i['sale_price'], $i['purchase_price'], 0, $i['min_stock_level'], $i['image_path'],
$i['vat_rate'], $i['expiry_date'], $i['is_promotion'], $i['promotion_start'], $i['promotion_end'], $i['promotion_percent'],
$target_id
]);
}
}
}
$db->commit();
$message = "<div class='alert alert-success'>Data copied and synced successfully!</div>";
} catch (Exception $e) {
$db->rollBack();
$message = "<div class='alert alert-danger'>Error: " . $e->getMessage() . "</div>";
}
}
}

View File

@ -0,0 +1,62 @@
<?php if (isset($access_denied) && $access_denied): ?>
<div class="container mt-4">
<div class="alert alert-danger">Access Denied. Administrator only.</div>
</div>
<?php return; ?>
<?php endif; ?>
<div class="container mt-4">
<h2>Copy/Sync Outlet Data</h2>
<?= $message ?? '' ?>
<div class="card">
<div class="card-body">
<form method="POST">
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Source Outlet</label>
<select name="source_id" class="form-select" required>
<?php foreach($outlets as $o): ?>
<option value="<?= $o['id'] ?>"><?= htmlspecialchars($o['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Target Outlet</label>
<select name="target_id" class="form-select" required>
<?php foreach($outlets as $o): ?>
<option value="<?= $o['id'] ?>"><?= htmlspecialchars($o['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label">Data to Copy/Sync</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="copy_items" id="copy_items" checked>
<label class="form-check-label" for="copy_items">Items (will also sync Categories, Units, Suppliers)</label>
<div class="form-text text-muted">
Existing items will be updated (prices, names, etc) but <strong>stock quantity will be preserved</strong>. New items will be created with 0 stock.
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="copy_categories" id="copy_categories" checked>
<label class="form-check-label" for="copy_categories">Categories</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="copy_units" id="copy_units" checked>
<label class="form-check-label" for="copy_units">Units</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="copy_suppliers" id="copy_suppliers" checked>
<label class="form-check-label" for="copy_suppliers">Suppliers</label>
</div>
</div>
<button type="submit" class="btn btn-primary">Start Copy/Sync</button>
<a href="index.php?page=settings" class="btn btn-secondary">Back</a>
</form>
</div>
</div>
</div>

View File

@ -93,3 +93,10 @@
2026-03-18 18:32:01 - POST: {"id":"59","name_en":"LAMING FOUL MEDAMES 397G","name_ar":"\u0641\u0648\u0644 \u0645\u062f\u0645\u0633 \u0644\u0627\u0645\u064a\u0646\u062c 397\u063a","sku":"000023071575","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.2","purchase_price":"0.157","stock_quantity":"0","min_stock_level":"0","vat_rate":"5","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""} 2026-03-18 18:32:01 - POST: {"id":"59","name_en":"LAMING FOUL MEDAMES 397G","name_ar":"\u0641\u0648\u0644 \u0645\u062f\u0645\u0633 \u0644\u0627\u0645\u064a\u0646\u062c 397\u063a","sku":"000023071575","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.2","purchase_price":"0.157","stock_quantity":"0","min_stock_level":"0","vat_rate":"5","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-18 18:32:15 - POST: {"action":"translate","text":"\u0641\u0627\u0635\u0648\u0644\u064a\u0627 \u0644\u0627\u0645\u064a\u0646\u062c227\u062c\u0631\u0627\u0645","target":"en"} 2026-03-18 18:32:15 - POST: {"action":"translate","text":"\u0641\u0627\u0635\u0648\u0644\u064a\u0627 \u0644\u0627\u0645\u064a\u0646\u062c227\u062c\u0631\u0627\u0645","target":"en"}
2026-03-18 18:32:28 - POST: {"id":"58","name_en":"Leming Beans 227g","name_ar":"\u0641\u0627\u0635\u0648\u0644\u064a\u0627 \u0644\u0627\u0645\u064a\u0646\u062c227\u062c\u0631\u0627\u0645","sku":"000023071568","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.15","purchase_price":"0.111","stock_quantity":"0","min_stock_level":"0","vat_rate":"5","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""} 2026-03-18 18:32:28 - POST: {"id":"58","name_en":"Leming Beans 227g","name_ar":"\u0641\u0627\u0635\u0648\u0644\u064a\u0627 \u0644\u0627\u0645\u064a\u0646\u062c227\u062c\u0631\u0627\u0645","sku":"000023071568","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.15","purchase_price":"0.111","stock_quantity":"0","min_stock_level":"0","vat_rate":"5","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-18 18:40:45 - POST: {"id":"62","name_en":"LAMING RED KIDNEY BEANS ","name_ar":"\u0641\u0627\u0635\u0648\u0644\u064a\u0627\u0621 \u062d\u0645\u0631\u0627\u0621 \u0643\u064a\u062f\u0646\u064a \u0644\u0627\u0645\u064a\u0646\u063a","sku":"000023071605","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.25","purchase_price":"0.2","stock_quantity":"100","min_stock_level":"0","vat_rate":"5.00","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-18 18:41:00 - POST: {"id":"61","name_en":"LAMING PINEAPPLE 454G","name_ar":"\u0623\u0646\u0627\u0646\u0627\u0633 \u0644\u0627\u0645\u0646\u062c 454\u063a","sku":"000023071599","category_id":"3","unit_id":"4","supplier_id":"","sale_price":"0.4","purchase_price":"0.346","stock_quantity":"50","min_stock_level":"0","vat_rate":"5.00","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-19 01:57:45 - POST: {"import_items":""}
2026-03-19 01:58:17 - POST: {"action":"translate","text":"LAMING RED KIDNEY BEANS 425","target":"ar"}
2026-03-19 01:58:33 - POST: {"id":"117","name_en":"LAMING RED KIDNEY BEANS 425","name_ar":"\u0641\u0627\u0635\u0648\u0644\u064a\u0627 \u062d\u0645\u0631\u0627\u0621 \u0643\u064a\u062f\u0646\u064a \u0644\u0627\u0645\u064a\u0646\u062c 425","sku":"000023071605","category_id":"","unit_id":"","supplier_id":"","sale_price":"0.25","purchase_price":"0.2","stock_quantity":"0","min_stock_level":"0","vat_rate":"5","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}
2026-03-19 01:58:51 - POST: {"action":"translate","text":"LAMING PINEAPPLE 454G","target":"ar"}
2026-03-19 01:59:00 - POST: {"id":"116","name_en":"LAMING PINEAPPLE 454G","name_ar":"\u0623\u0646\u0627\u0646\u0627\u0633 \u0644\u0627\u0645\u064a\u0646\u063a 454 \u062c\u0631\u0627\u0645","sku":"000023071599","category_id":"","unit_id":"","supplier_id":"","sale_price":"0.4","purchase_price":"0.346","stock_quantity":"55","min_stock_level":"0","vat_rate":"5","promotion_start":"","promotion_end":"","promotion_percent":"0","edit_item":""}

View File

@ -1,18 +1,14 @@
I have restored the "Manage Outlets" feature. Plan:
- Update `index.php` to use a dynamic page title (`__($page)`) instead of the hardcoded "Accounting".
- Add "Copy Outlet Data" to `includes/lang.php` (both English and Arabic) to ensure the title displays correctly without hyphens.
**Plan:** Changed:
1. **Controller Logic:** Added the backend logic to handle `outlets` page requests (listing, adding, editing, deleting outlets) in `index.php`. - `index.php`: Changed `<title>` tag to use `__($page)`.
2. **Navigation:** Added a "Manage Outlets" link to the sidebar under the **Admin** section. - `includes/lang.php`: Added `'copy_outlet_data' => 'Copy Outlet Data'` (and Arabic equivalent).
3. **View Integration:** Re-enabled the `outlets_html.php` view file to display the outlet management interface.
4. **Correction:** Cleaned up some accidental code insertions that occurred during the process.
**Changed:** Notes:
* `index.php`: Added `case 'outlets':` logic, sidebar link, and view inclusion. - This change also fixes a bug where *every* page was previously titled "Accounting - Admin Panel". Now all pages will show their correct titles (e.g., "Dashboard", "Sales", "POS").
**Notes:** Next:
* You can now access **Manage Outlets** from the sidebar (under the Admin section). - Refresh the "Copy/Sync Outlet Data" page to see the new title "Copy Outlet Data - Admin Panel".
* The system supports multiple outlets as requested. - Reminder: click Save in the editor to sync changes.
* Only Administrators can access this page.
**Next:**
Please verify if you can now see the "Manage Outlets" link and manage your outlets successfully.