diff --git a/debug.log b/debug.log index 2d199da..b45e1be 100644 --- a/debug.log +++ b/debug.log @@ -57,3 +57,9 @@ 2026-03-18 06:00:05 - Items case hit 2026-03-18 14:33:50 - Items case hit 2026-03-18 14:34:10 - Items case hit +2026-03-18 17:01:37 - Items case hit +2026-03-18 17:07:17 - Items case hit +2026-03-18 17:09:46 - Items case hit +2026-03-18 17:35:42 - Items case hit +2026-03-18 17:36:53 - Items case hit +2026-03-18 17:49:12 - Items case hit diff --git a/index.php b/index.php index e54265a..57a21ad 100644 --- a/index.php +++ b/index.php @@ -49,6 +49,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { file_put_contents('post_debug.log', date('Y-m-d H:i:s') . " - POST: " . json_encode($_POST) . "\n", FILE_APPEND); } require_once 'db/config.php'; +require_once 'includes/SimpleXLSX.php'; require_once 'includes/stock_helper.php'; // Helper for current outlet @@ -1369,79 +1370,117 @@ function getPromotionalPrice($item) { redirectWithMessage("Expense recorded!", "index.php?page=expenses"); } + + # --- Unified Import Logic (Excel & CSV) --- + # --- Unified Import Logic (Excel & CSV) --- if (isset($_POST['import_items'])) { - error_log("Import items triggered. POST: " . print_r($_POST, true)); + error_log("Import items triggered."); + $count = 0; if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) { $tmpPath = $_FILES['excel_file']['tmp_name']; - error_log("File uploaded to: $tmpPath"); - $firstBytes = file_get_contents($tmpPath, false, null, 0, 4); - if ($firstBytes === "PK\x03\x04") { - $message = "Error: It looks like you uploaded an Excel (.xlsx) file. Please save it as CSV (UTF-8) and try again."; + $rows = []; + if ( $xlsx = SimpleXLSX::parse($tmpPath) ) { + $rows = $xlsx->rows(); } else { - // Check for BOM and skip it - if (substr($firstBytes, 0, 3) === "\xEF\xBB\xBF") { - $handle = fopen($tmpPath, "r"); - fseek($handle, 3); - } else { - $handle = fopen($tmpPath, "r"); - } - - $firstLine = fgets($handle); - rewind($handle); - if (substr($firstBytes, 0, 3) === "\xEF\xBB\xBF") fseek($handle, 3); - - error_log("First line of CSV: " . $firstLine); - - $seps = [",", ";", "\t", "|"]; - $sep = ","; - $maxCount = 0; - foreach ($seps as $s) { - $count = substr_count($firstLine, $s); - if ($count > $maxCount) { - $maxCount = $count; - $sep = $s; - } - } - error_log("Detected separator: $sep"); - - $count = 0; - $errors = 0; - $rowNum = 0; - while (($data = fgetcsv($handle, 10000, $sep)) !== FALSE) { - $rowNum++; - if ($rowNum === 1) continue; // Skip header - - if (count($data) < 4) { $errors++; continue; } - - $sku = trim($data[0]); - $name_en = trim($data[1]); - $name_ar = trim($data[2]); - $price = (float)($data[3] ?? 0); - $qty = (float)($data[4] ?? 0); - $vat_rate = (float)($data[5] ?? 0); - - if (!$sku || !$name_en) { $errors++; continue; } - - $check = db()->prepare("SELECT id FROM stock_items WHERE sku = ?"); - $check->execute([$sku]); - if ($check->fetch()) { - db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, sale_price = ?, stock_quantity = ?, vat_rate = ? WHERE sku = ?") - ->execute([$name_en, $name_ar, $price, $qty, $vat_rate, $sku]); - } else { - db()->prepare("INSERT INTO stock_items (sku, name_en, name_ar, sale_price, stock_quantity, vat_rate) VALUES (?, ?, ?, ?, ?, ?)") - ->execute([$sku, $name_en, $name_ar, $price, $qty, $vat_rate]); - } - $count++; - } + $handle = fopen($tmpPath, "r"); + $firstLine = fgets($handle); rewind($handle); + $sep = (substr_count($firstLine, ';') > substr_count($firstLine, ',')) ? ';' : ','; + $bom = fread($handle, 3); if ($bom !== "") rewind($handle); + while (($data = fgetcsv($handle, 0, $sep)) !== FALSE) $rows[] = $data; fclose($handle); - redirectWithMessage("Import completed! $count items processed." . ($errors > 0 ? " ($errors rows skipped due to data errors.)" : ""), "index.php?page=items"); } - } else { - $errCode = $_FILES['excel_file']['error'] ?? 'no file'; - $message = "Error: File upload failed. (Error code: $errCode)"; + if (isset($rows[0][0]) && stripos($rows[0][0], 'sku') !== false) array_shift($rows); + + foreach ($rows as $row) { + if (empty($row[0])) continue; + $sku = trim((string)$row[0]); + $name_en = trim((string)($row[1] ?? '')); + $name_ar = trim((string)($row[2] ?? '')); + $sale_price = (float)($row[3] ?? 0); + $purchase_price = (float)($row[4] ?? 0); + $qty = (float)($row[5] ?? 0); + $vat_rate = (float)($row[6] ?? 0); + + $check = db()->prepare("SELECT id FROM stock_items WHERE sku = ?"); + $check->execute([$sku]); + if ($check->fetch()) { + db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, sale_price = ?, purchase_price = ?, stock_quantity = ?, vat_rate = ? WHERE sku = ?") + ->execute([$name_en, $name_ar, $sale_price, $purchase_price, $qty, $vat_rate, $sku]); + } else { + db()->prepare("INSERT INTO stock_items (sku, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate) VALUES (?, ?, ?, ?, ?, ?, ?)") + ->execute([$sku, $name_en, $name_ar, $sale_price, $purchase_price, $qty, $vat_rate]); + } + $count++; + } + redirectWithMessage("Import items completed! $count processed.", "index.php?page=items"); } } + if (isset($_POST['import_customers']) || isset($_POST['import_suppliers'])) { + $type = isset($_POST['import_customers']) ? 'customers' : 'suppliers'; + $table = $type; + error_log("Import $type triggered."); + $count = 0; + if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) { + $tmpPath = $_FILES['excel_file']['tmp_name']; + $rows = []; + if ( $xlsx = SimpleXLSX::parse($tmpPath) ) { + $rows = $xlsx->rows(); + } else { + $handle = fopen($tmpPath, "r"); + $firstLine = fgets($handle); rewind($handle); + $sep = (substr_count($firstLine, ';') > substr_count($firstLine, ',')) ? ';' : ','; + $bom = fread($handle, 3); if ($bom !== "") rewind($handle); + while (($data = fgetcsv($handle, 0, $sep)) !== FALSE) $rows[] = $data; + fclose($handle); + } + if (isset($rows[0][0]) && (stripos($rows[0][0], 'name') !== false || stripos($rows[0][0], 'id') !== false)) array_shift($rows); + + foreach ($rows as $row) { + if (empty($row[0])) continue; + $name = trim((string)$row[0]); + if (!$name) continue; + $email = trim((string)($row[1] ?? '')); + $phone = trim((string)($row[2] ?? '')); + $tax_id = trim((string)($row[3] ?? '')); + + db()->prepare("INSERT INTO $table (name, email, phone, tax_id, created_at) VALUES (?, ?, ?, ?, NOW())") + ->execute([$name, $email, $phone, $tax_id]); + $count++; + } + redirectWithMessage("Import $type completed! $count processed.", "index.php?page=$type"); + } + } + + if (isset($_POST['import_categories'])) { + $count = 0; + if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) { + $tmpPath = $_FILES['excel_file']['tmp_name']; + $rows = []; + if ( $xlsx = SimpleXLSX::parse($tmpPath) ) { $rows = $xlsx->rows(); } + else { + $handle = fopen($tmpPath, "r"); + while (($data = fgetcsv($handle)) !== FALSE) $rows[] = $data; + fclose($handle); + } + if (isset($rows[0][0]) && stripos($rows[0][0], 'name') !== false) array_shift($rows); + + foreach ($rows as $row) { + if (empty($row[0])) continue; + $name_en = trim((string)$row[0]); + $name_ar = trim((string)($row[1] ?? $name_en)); + + db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)") + ->execute([$name_en, $name_ar]); + $count++; + } + redirectWithMessage("Import categories completed! $count processed.", "index.php?page=categories"); + } + } + + if (isset($_POST['import_units'])) { + $count = 0; + if (isset($_POST['add_expense_category'])) { $name_en = $_POST['name_en'] ?? ''; $name_ar = $_POST['name_ar'] ?? ''; @@ -1718,9 +1757,7 @@ function getPromotionalPrice($item) { $message = "Expense recorded!"; } - if (isset($_POST['import_items'])) { - error_log("Import items triggered. POST: " . print_r($_POST, true)); - if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) { + if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) { $tmpPath = $_FILES['excel_file']['tmp_name']; error_log("File uploaded to: $tmpPath"); $firstBytes = file_get_contents($tmpPath, false, null, 0, 4); @@ -4997,7 +5034,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; - + item @@ -5034,7 +5071,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
- +
@@ -5071,7 +5108,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
@@ -8608,104 +8645,202 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; -
-
Company Profile
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - - -
- Logo -
- -
-
- - - -
- Favicon -
- -
-
- - - -
- Signature -
- -
-
-
Loyalty Configuration
-
-
- - +
+
+
+
+
+
+
-
- - -
-
- - +
+
Company Profile & Settings
+

Manage your business identity and system preferences

-
- +
+ + + +
+
+ Company Details +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ + +
+
+ Contact Information +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ + +
+
+ System Configuration +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ Visual Identity +
+
+
+
+ +
+ + Logo + + + +
+ +
+
+
+
+ +
+ + Favicon + + + +
+ +
+
+
+
+ +
+ + Signature + + + +
+ +
+
+
+
+ + +
+
+ Loyalty Program +
+
+
+ + +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+ +
+
- +
@@ -12864,6 +12999,8 @@ document.addEventListener('DOMContentLoaded', function() { + +
@@ -12918,6 +13055,13 @@ document.addEventListener('DOMContentLoaded', function() { .avery-layout-4x10 { grid-template-columns: repeat(4, 1fr); grid-auto-rows: 27mm; } .avery-layout-L7651 { grid-template-columns: repeat(5, 1fr); grid-auto-rows: 21mm; } .avery-layout-L4736 { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 38mm; } + .avery-layout-L7431 { grid-template-columns: repeat(6, 1fr); grid-auto-rows: 33mm; } + .avery-layout-L4716 { grid-template-columns: repeat(6, 1fr); grid-auto-rows: 33mm; } + + .avery-layout-L4716 .avery-label { border-radius: 50%; } + .avery-layout-L7431 .avery-label, .avery-layout-L4716 .avery-label { padding: 1mm; } + .avery-layout-L7431 .avery-label div, .avery-layout-L4716 .avery-label div { font-size: 8px !important; } + .avery-layout-L7431 .avery-label svg, .avery-layout-L4716 .avery-label svg { height: 20px; } .avery-layout-L7651 .avery-label { padding: 2mm; } .avery-layout-L7651 .avery-label svg { height: 25px; } @@ -13063,6 +13207,8 @@ document.addEventListener('DOMContentLoaded', function() { checkedItems.forEach(cb => { const sku = cb.dataset.sku; + const nameAr = cb.dataset.nameAr || ''; + const nameEn = cb.dataset.nameEn || ''; const name = cb.dataset.name; const price = cb.dataset.price; const id = cb.dataset.id; @@ -13081,8 +13227,19 @@ document.addEventListener('DOMContentLoaded', function() { label.className = 'avery-label'; const uniqueId = Math.random().toString(36).substr(2, 9); const svgId = `bc-${sku}-${uniqueId}`; + + let nameHtml = ''; + if (nameAr || nameEn) { + const arText = nameAr || name; + const enText = nameEn || ''; + nameHtml = `
${arText}
+
${enText}
`; + } else { + nameHtml = `
${name}
`; + } + label.innerHTML = ` -
${name}
+ ${nameHtml}
OMR ${price}
`; @@ -13118,12 +13275,12 @@ document.addEventListener('DOMContentLoaded', function() {