diff --git a/debug.log b/debug.log index d628037..589f384 100644 --- a/debug.log +++ b/debug.log @@ -9,3 +9,6 @@ 2026-02-21 18:42:10 - 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-02-21 18:43: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-02-21 18:44:10 - 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-02-22 02:55:16 - Items case hit +2026-02-22 02:55:31 - 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-02-22 02:58:39 - Items case hit diff --git a/includes/lang.php b/includes/lang.php index 6a8db71..b75b43d 100644 --- a/includes/lang.php +++ b/includes/lang.php @@ -18,7 +18,7 @@ $translations = [ 'quotations' => 'Quotations', 'expenses' => 'Expenses', 'expense_categories' => 'Expense Categories', - 'accounting' => 'Meezan Accounting System', + 'accounting' => 'Accounting', 'trial_balance' => 'Trial Balance', 'profit_loss' => 'Profit & Loss', 'balance_sheet' => 'Balance Sheet', @@ -113,7 +113,7 @@ $translations = [ 'quotations' => 'عروض الأسعار', 'expenses' => 'المصاريف', 'expense_categories' => 'فئات المصاريف', - 'accounting' => 'نظام ميزان المحاسبي', + 'accounting' => 'المحاسبة', 'trial_balance' => 'ميزان المراجعة', 'profit_loss' => 'الأرباح والخسائر', 'balance_sheet' => 'الميزانية العمومية', diff --git a/index.php b/index.php index e7ebbc7..bb3c0e9 100644 --- a/index.php +++ b/index.php @@ -623,7 +623,17 @@ if (!isset($_SESSION['user_id'])) { } // Handle POST Requests -$message = ''; +$message = $_SESSION['message'] ?? ''; +unset($_SESSION['message']); + +function redirectWithMessage($msg, $url = null) { + if (!$url) { + $url = $_SERVER['REQUEST_URI']; + } + $_SESSION['message'] = $msg; + header("Location: $url"); + exit; +} // Fetch theme if not in session but user is logged in if (isset($_SESSION['user_id']) && !isset($_SESSION['theme'])) { @@ -687,7 +697,7 @@ function getPromotionalPrice($item) { $purchase_price = (float)($_POST['purchase_price'] ?? 0); $stock_quantity = (float)($_POST['stock_quantity'] ?? 0); $min_stock_level = (float)($_POST['min_stock_level'] ?? 0); - $vat_rate = (float)($_POST['vat_rate'] ?? 15.00); + $vat_rate = (float)($_POST['vat_rate'] ?? 0); $expiry_date = !empty($_POST['expiry_date']) ? $_POST['expiry_date'] : null; $is_promotion = isset($_POST['is_promotion']) ? 1 : 0; $promotion_start = !empty($_POST['promotion_start']) ? $_POST['promotion_start'] : null; @@ -703,7 +713,7 @@ function getPromotionalPrice($item) { } $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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); $stmt->execute([$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]); - $message = "Item added successfully!"; + redirectWithMessage("Item added successfully!"); } if (isset($_POST['edit_item'])) { @@ -718,7 +728,7 @@ function getPromotionalPrice($item) { $purchase_price = (float)($_POST['purchase_price'] ?? 0); $stock_quantity = (float)($_POST['stock_quantity'] ?? 0); $min_stock_level = (float)($_POST['min_stock_level'] ?? 0); - $vat_rate = (float)($_POST['vat_rate'] ?? 15.00); + $vat_rate = (float)($_POST['vat_rate'] ?? 0); $expiry_date = !empty($_POST['expiry_date']) ? $_POST['expiry_date'] : null; $is_promotion = isset($_POST['is_promotion']) ? 1 : 0; $promotion_start = !empty($_POST['promotion_start']) ? $_POST['promotion_start'] : null; @@ -731,17 +741,17 @@ function getPromotionalPrice($item) { $filename = 'uploads/item_' . $id . '_' . time() . '.' . $ext; if (move_uploaded_file($_FILES['image']['tmp_name'], $filename)) db()->prepare("UPDATE stock_items SET image_path = ? WHERE id = ?")->execute([$filename, $id]); } - $message = "Item updated successfully!"; + redirectWithMessage("Item updated successfully!"); } if (isset($_POST['delete_item'])) { db()->prepare("DELETE FROM stock_items WHERE id = ?")->execute([(int)$_POST['id']]); - $message = "Item deleted successfully!"; + redirectWithMessage("Item deleted successfully!"); } if (isset($_POST['cancel_all_promotions'])) { db()->query("UPDATE stock_items SET is_promotion = 0 WHERE is_promotion = 1"); - $message = "All active promotions have been cancelled."; + redirectWithMessage("All active promotions have been cancelled."); } // Auto-expire finished promotions @@ -749,45 +759,45 @@ function getPromotionalPrice($item) { if (isset($_POST['add_category'])) { db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '']); - $message = "Category added!"; + redirectWithMessage("Category added!"); } 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'] ?? '']); - $message = "Unit added!"; + redirectWithMessage("Unit added!"); } if (isset($_POST['edit_category'])) { db()->prepare("UPDATE stock_categories SET name_en = ?, name_ar = ? WHERE id = ?")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', (int)$_POST['id']]); - $message = "Category updated!"; + redirectWithMessage("Category updated!"); } if (isset($_POST['delete_category'])) { db()->prepare("DELETE FROM stock_categories WHERE id = ?")->execute([(int)$_POST['id']]); - $message = "Category deleted!"; + redirectWithMessage("Category deleted!"); } if (isset($_POST['edit_unit'])) { db()->prepare("UPDATE stock_units SET name_en = ?, name_ar = ?, short_name_en = ?, short_name_ar = ? WHERE id = ?")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', $_POST['short_en'] ?? '', $_POST['short_ar'] ?? '', (int)$_POST['id']]); - $message = "Unit updated!"; + redirectWithMessage("Unit updated!"); } if (isset($_POST['delete_unit'])) { db()->prepare("DELETE FROM stock_units WHERE id = ?")->execute([(int)$_POST['id']]); - $message = "Unit deleted!"; + redirectWithMessage("Unit deleted!"); } if (isset($_POST['add_customer'])) { $table = ($_POST['type'] ?? '') === 'supplier' ? 'suppliers' : 'customers'; $sql = "INSERT INTO $table (name, email, phone, tax_id, balance" . ($table === 'customers' ? ", loyalty_points" : "") . ") VALUES (?, ?, ?, ?, ?" . ($table === 'customers' ? ", 0" : "") . ")"; db()->prepare($sql)->execute([$_POST['name'] ?? '', $_POST['email'] ?? '', $_POST['phone'] ?? '', $_POST['tax_id'] ?? '', (float)($_POST['balance'] ?? 0)]); - $message = "Entity added!"; + redirectWithMessage("Entity added!"); } if (isset($_POST['edit_customer'])) { - $table = ($page === 'suppliers') ? 'suppliers' : 'customers'; + $table = ($_POST['type'] ?? '') === 'supplier' ? 'suppliers' : 'customers'; db()->prepare("UPDATE $table SET name = ?, email = ?, phone = ?, tax_id = ?, balance = ? WHERE id = ?")->execute([$_POST['name'] ?? '', $_POST['email'] ?? '', $_POST['phone'] ?? '', $_POST['tax_id'] ?? '', (float)($_POST['balance'] ?? 0), (int)$_POST['id']]); - $message = "Entity updated!"; + redirectWithMessage("Entity updated!"); } if (isset($_POST['delete_customer'])) { - $table = ($page === 'suppliers') ? 'suppliers' : 'customers'; + $table = ($_POST['type'] ?? '') === 'supplier' ? 'suppliers' : 'customers'; db()->prepare("DELETE FROM $table WHERE id = ?")->execute([(int)$_POST['id']]); - $message = "Entity deleted!"; + redirectWithMessage("Entity deleted!"); } // Invoices @@ -871,9 +881,8 @@ function getPromotionalPrice($item) { } $db->commit(); - $message = ($type === 'purchase' ? "Purchase" : "Invoice") . " #$inv_id created!"; - header("Location: index.php?page=" . ($type === 'purchase' ? 'purchases' : 'sales')); - exit; + $msg = ($type === 'purchase' ? "Purchase" : "Invoice") . " #$inv_id created!"; + redirectWithMessage($msg, "index.php?page=" . ($type === 'purchase' ? 'purchases' : 'sales')); } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } } @@ -931,10 +940,350 @@ function getPromotionalPrice($item) { $db->prepare("INSERT INTO quotation_items (quotation_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$quot_id, $item_id, $qty, $price, $vatAmount, $subtotal]); } $db->commit(); - $message = "Quotation #$quot_id updated!"; + $msg = "Quotation #$quot_id updated!"; + redirectWithMessage($msg, "index.php?page=quotations"); } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } } + if (isset($_POST['delete_quotation'])) { + $id = (int)$_POST['id']; + db()->prepare("DELETE FROM quotations WHERE id = ?")->execute([$id]); + redirectWithMessage("Quotation deleted!", "index.php?page=quotations"); + } + + if (isset($_POST['add_lpo'])) { + $db = db(); + try { + $db->beginTransaction(); + $supp_id = (int)$_POST['supplier_id']; + $lpo_date = $_POST['lpo_date'] ?: date('Y-m-d'); + $delivery_date = $_POST['delivery_date'] ?: null; + $status = 'pending'; + $terms = $_POST['terms_conditions'] ?? ''; + + $items = $_POST['item_ids'] ?? []; + $qtys = $_POST['quantities'] ?? []; + $prices = $_POST['prices'] ?? []; + + $total_subtotal = 0; + $total_vat = 0; + + foreach ($items as $i => $item_id) { + if (!$item_id) continue; + $qty = (float)$qtys[$i]; + $price = (float)$prices[$i]; + $subtotal = $qty * $price; + + $stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?"); + $stmtVat->execute([$item_id]); + $vatRate = (float)$stmtVat->fetchColumn(); + + $vatAmount = $subtotal * ($vatRate / 100); + $total_subtotal += $subtotal; + $total_vat += $vatAmount; + } + + $total_with_vat = $total_subtotal + $total_vat; + + $stmt = $db->prepare("INSERT INTO lpos (supplier_id, lpo_date, delivery_date, status, total_amount, vat_amount, total_with_vat, terms_conditions) VALUES (?, ?, ?, 'pending', ?, ?, ?, ?)"); + $stmt->execute([$supp_id, $lpo_date, $delivery_date, $total_subtotal, $total_vat, $total_with_vat, $terms]); + $lpo_id = $db->lastInsertId(); + + foreach ($items as $i => $item_id) { + if (!$item_id) continue; + $qty = (float)$qtys[$i]; + $price = (float)$prices[$i]; + $subtotal = $qty * $price; + + $stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?"); + $stmtVat->execute([$item_id]); + $vatRate = (float)$stmtVat->fetchColumn(); + $vatAmount = $subtotal * ($vatRate / 100); + + $db->prepare("INSERT INTO lpo_items (lpo_id, item_id, quantity, unit_price, vat_percentage, vat_amount, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?)")->execute([$lpo_id, $item_id, $qty, $price, $vatRate, $vatAmount, $subtotal]); + } + $db->commit(); + redirectWithMessage("LPO #$lpo_id created!", "index.php?page=lpos"); + } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } + } + + if (isset($_POST['edit_lpo'])) { + $db = db(); + try { + $db->beginTransaction(); + $lpo_id = (int)$_POST['lpo_id']; + $supp_id = (int)$_POST['supplier_id']; + $lpo_date = $_POST['lpo_date']; + $delivery_date = $_POST['delivery_date'] ?: null; + $status = $_POST['status'] ?? 'pending'; + $terms = $_POST['terms_conditions'] ?? ''; + + $items = $_POST['item_ids'] ?? []; + $qtys = $_POST['quantities'] ?? []; + $prices = $_POST['prices'] ?? []; + + $total_subtotal = 0; + $total_vat = 0; + + foreach ($items as $i => $item_id) { + if (!$item_id) continue; + $qty = (float)$qtys[$i]; + $price = (float)$prices[$i]; + $subtotal = $qty * $price; + + $stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?"); + $stmtVat->execute([$item_id]); + $vatRate = (float)$stmtVat->fetchColumn(); + + $vatAmount = $subtotal * ($vatRate / 100); + $total_subtotal += $subtotal; + $total_vat += $vatAmount; + } + + $total_with_vat = $total_subtotal + $total_vat; + + $stmt = $db->prepare("UPDATE lpos SET supplier_id = ?, lpo_date = ?, delivery_date = ?, status = ?, total_amount = ?, vat_amount = ?, total_with_vat = ?, terms_conditions = ? WHERE id = ?"); + $stmt->execute([$supp_id, $lpo_date, $delivery_date, $status, $total_subtotal, $total_vat, $total_with_vat, $terms, $lpo_id]); + + $db->prepare("DELETE FROM lpo_items WHERE lpo_id = ?")->execute([$lpo_id]); + + foreach ($items as $i => $item_id) { + if (!$item_id) continue; + $qty = (float)$qtys[$i]; + $price = (float)$prices[$i]; + $subtotal = $qty * $price; + + $stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?"); + $stmtVat->execute([$item_id]); + $vatRate = (float)$stmtVat->fetchColumn(); + $vatAmount = $subtotal * ($vatRate / 100); + + $db->prepare("INSERT INTO lpo_items (lpo_id, item_id, quantity, unit_price, vat_percentage, vat_amount, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?)")->execute([$lpo_id, $item_id, $qty, $price, $vatRate, $vatAmount, $subtotal]); + } + $db->commit(); + redirectWithMessage("LPO #$lpo_id updated!", "index.php?page=lpos"); + } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } + } + + if (isset($_POST['delete_lpo'])) { + $id = (int)$_POST['id']; + db()->prepare("DELETE FROM lpos WHERE id = ?")->execute([$id]); + redirectWithMessage("LPO deleted!", "index.php?page=lpos"); + } + + if (isset($_POST['convert_to_invoice'])) { + $db = db(); + try { + $db->beginTransaction(); + $quot_id = (int)$_POST['quotation_id']; + + $stmt = $db->prepare("SELECT * FROM quotations WHERE id = ?"); + $stmt->execute([$quot_id]); + $quot = $stmt->fetch(); + + if (!$quot) throw new Exception("Quotation not found."); + if ($quot['status'] === 'converted') throw new Exception("Quotation already converted."); + + $stmtItems = $db->prepare("SELECT * FROM quotation_items WHERE quotation_id = ?"); + $stmtItems->execute([$quot_id]); + $qItems = $stmtItems->fetchAll(); + + // Create Invoice + $inv_date = date('Y-m-d'); + $stmtInv = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0)"); + $stmtInv->execute([$quot['customer_id'], $inv_date, $quot['total_amount'], $quot['vat_amount'], $quot['total_with_vat']]); + $inv_id = $db->lastInsertId(); + + $items_for_journal = []; + foreach ($qItems as $item) { + $db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$inv_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['vat_amount'], $item['total_price']]); + + // Update stock + $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?")->execute([$item['quantity'], $item['item_id']]); + $items_for_journal[] = ['id' => $item['item_id'], 'qty' => $item['quantity']]; + } + + // Update Quotation status + $db->prepare("UPDATE quotations SET status = 'converted' WHERE id = ?")->execute([$quot_id]); + + // Accounting + recordSaleJournal($inv_id, $quot['total_with_vat'], $inv_date, $items_for_journal, $quot['vat_amount']); + + $db->commit(); + redirectWithMessage("Quotation converted to Invoice #$inv_id successfully!", "index.php?page=sales"); + } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } + } + + if (isset($_POST['convert_lpo_to_purchase'])) { + $db = db(); + try { + $db->beginTransaction(); + $lpo_id = (int)$_POST['lpo_id']; + + $stmt = $db->prepare("SELECT * FROM lpos WHERE id = ?"); + $stmt->execute([$lpo_id]); + $lpo = $stmt->fetch(); + + if (!$lpo) throw new Exception("LPO not found."); + if ($lpo['status'] === 'converted') throw new Exception("LPO already converted."); + + $stmtItems = $db->prepare("SELECT * FROM lpo_items WHERE lpo_id = ?"); + $stmtItems->execute([$lpo_id]); + $lItems = $stmtItems->fetchAll(); + + // Create Purchase Invoice + $pur_date = date('Y-m-d'); + $stmtPur = $db->prepare("INSERT INTO purchases (supplier_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0)"); + $stmtPur->execute([$lpo['supplier_id'], $pur_date, $lpo['total_amount'], $lpo['vat_amount'], $lpo['total_with_vat']]); + $pur_id = $db->lastInsertId(); + + $items_for_journal = []; + foreach ($lItems as $item) { + $db->prepare("INSERT INTO purchase_items (purchase_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$pur_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['vat_amount'], $item['total_amount']]); + + // Update stock + $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$item['quantity'], $item['item_id']]); + $items_for_journal[] = ['id' => $item['item_id'], 'qty' => $item['quantity']]; + } + + // Update LPO status + $db->prepare("UPDATE lpos SET status = 'converted' WHERE id = ?")->execute([$lpo_id]); + + $db->commit(); + redirectWithMessage("LPO converted to Purchase Invoice #$pur_id successfully!", "index.php?page=purchases"); + } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } + } + + if (isset($_POST['record_payment'])) { + $id = (int)$_POST['invoice_id']; + $amount = (float)$_POST['amount']; + $date = $_POST['payment_date'] ?: date('Y-m-d'); + $method = $_POST['payment_method'] ?? 'Cash'; + $type = ($page === 'purchases') ? 'purchase' : 'sale'; + $table = ($type === 'purchase') ? 'purchases' : 'invoices'; + $payment_table = ($type === 'purchase') ? 'purchase_payments' : 'payments'; + $fk_col = ($type === 'purchase') ? 'purchase_id' : 'invoice_id'; + + $db = db(); + $db->prepare("INSERT INTO $payment_table ($fk_col, amount, payment_date, payment_method, notes) VALUES (?, ?, ?, ?, ?)")->execute([$id, $amount, $date, $method, $_POST['notes'] ?? '']); + $pay_id = $db->lastInsertId(); + $db->prepare("UPDATE $table SET paid_amount = paid_amount + ?, status = IF(paid_amount + ? >= total_with_vat, 'paid', 'partially_paid') WHERE id = ?")->execute([$amount, $amount, $id]); + + if ($type === 'sale') recordPaymentReceivedJournal((int)$pay_id, $amount, $date, $method); + else recordPaymentMadeJournal((int)$pay_id, $amount, $date, $method); + + $_SESSION['trigger_receipt_modal'] = true; + $_SESSION['show_receipt_id'] = $pay_id; + redirectWithMessage("Payment recorded!", "index.php?page=" . ($type === 'purchase' ? 'purchases' : 'sales')); + } + + if (isset($_POST['add_expense'])) { + $amt = (float)$_POST['amount']; + $date = $_POST['expense_date'] ?: date('Y-m-d'); + $desc = $_POST['description'] ?? ''; + db()->prepare("INSERT INTO expenses (category_id, amount, expense_date, reference_no, description) VALUES (?, ?, ?, ?, ?)")->execute([(int)$_POST['category_id'], $amt, $date, $_POST['reference_no'] ?? '', $desc]); + recordExpenseJournal(db()->lastInsertId(), $amt, $date, $desc); + redirectWithMessage("Expense recorded!", "index.php?page=expenses"); + } + + 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) { + $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."; + } 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++; + } + 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($_POST['add_expense_category'])) { + $name_en = $_POST['name_en'] ?? ''; + $name_ar = $_POST['name_ar'] ?? ''; + db()->prepare("INSERT INTO expense_categories (name_en, name_ar) VALUES (?, ?)")->execute([$name_en, $name_ar]); + redirectWithMessage("Expense category added!", "index.php?page=expense_categories"); + } + + if (isset($_POST['add_payment_method'])) { + $name = $_POST['name'] ?? ''; + db()->prepare("INSERT INTO payment_methods (name) VALUES (?)")->execute([$name]); + redirectWithMessage("Payment method added!", "index.php?page=payment_methods"); + } + + if (isset($_POST['delete_invoice'])) { + $id = (int)$_POST['id']; + $type = ($page === 'purchases') ? 'purchase' : 'sale'; + $table = ($type === 'purchase') ? 'purchases' : 'invoices'; + $item_table = ($type === 'purchase') ? 'purchase_items' : 'invoice_items'; + $fk_col = ($type === 'purchase') ? 'purchase_id' : 'invoice_id'; + + db()->prepare("DELETE FROM $table WHERE id = ?")->execute([$id]); + db()->prepare("DELETE FROM $item_table WHERE $fk_col = ?")->execute([$id]); + redirectWithMessage(($type === 'purchase' ? "Purchase" : "Invoice") . " deleted!", "index.php?page=" . ($type === 'purchase' ? 'purchases' : 'sales')); + } + if (isset($_POST['delete_quotation'])) { $id = (int)$_POST['id']; db()->prepare("DELETE FROM quotations WHERE id = ?")->execute([$id]); @@ -995,9 +1344,7 @@ function getPromotionalPrice($item) { $db->prepare("INSERT INTO lpo_items (lpo_id, item_id, quantity, unit_price, vat_percentage, vat_amount, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?)")->execute([$lpo_id, $item_id, $qty, $price, $vatRate, $vatAmount, $subtotal]); } $db->commit(); - $message = "LPO #$lpo_id created!"; - header("Location: index.php?page=lpos"); - exit; + redirectWithMessage("LPO #$lpo_id created!", "index.php?page=lpos"); } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } } @@ -1383,9 +1730,8 @@ function getPromotionalPrice($item) { } $db->commit(); - $message = ($type === 'purchase' ? "Purchase" : "Invoice") . " updated successfully!"; - header("Location: index.php?page=" . ($type === 'purchase' ? 'purchases' : 'sales')); - exit; + $msg = ($type === 'purchase' ? "Purchase" : "Invoice") . " updated successfully!"; + redirectWithMessage($msg, "index.php?page=" . ($type === 'purchase' ? 'purchases' : 'sales')); } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } } @@ -1407,7 +1753,7 @@ if (isset($_POST['add_hr_department'])) { if ($id && $name) { $stmt = db()->prepare("UPDATE hr_departments SET name = ? WHERE id = ?"); $stmt->execute([$name, $id]); - $message = "Department updated successfully!"; + redirectWithMessage("Department updated successfully!", "index.php?page=hr_departments"); } } if (isset($_POST['delete_hr_department'])) { @@ -1415,39 +1761,41 @@ if (isset($_POST['add_hr_department'])) { if ($id) { $stmt = db()->prepare("DELETE FROM hr_departments WHERE id = ?"); $stmt->execute([$id]); - $message = "Department deleted successfully!"; + redirectWithMessage("Department deleted successfully!", "index.php?page=hr_departments"); } } if (isset($_POST['add_hr_employee'])) { - $dept_id = (int)$_POST['department_id'] ?: null; - $biometric_id = $_POST['biometric_id'] ?: null; + $dept_id = (int)$_POST['department_id']; + $biometric_id = $_POST['biometric_id'] ?? ''; $name = $_POST['name'] ?? ''; $email = $_POST['email'] ?? ''; $phone = $_POST['phone'] ?? ''; $pos = $_POST['position'] ?? ''; - $salary = (float)($_POST['salary'] ?? 0); - $j_date = $_POST['joining_date'] ?: null; + $salary = (float)$_POST['salary']; + $j_date = $_POST['joining_date'] ?: date('Y-m-d'); + if ($name) { $stmt = db()->prepare("INSERT INTO hr_employees (department_id, biometric_id, name, email, phone, position, salary, joining_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); $stmt->execute([$dept_id, $biometric_id, $name, $email, $phone, $pos, $salary, $j_date]); - $message = "Employee added successfully!"; + redirectWithMessage("Employee added successfully!", "index.php?page=hr_employees"); } } if (isset($_POST['edit_hr_employee'])) { $id = (int)$_POST['id']; - $dept_id = (int)$_POST['department_id'] ?: null; - $biometric_id = $_POST['biometric_id'] ?: null; + $dept_id = (int)$_POST['department_id']; + $biometric_id = $_POST['biometric_id'] ?? ''; $name = $_POST['name'] ?? ''; $email = $_POST['email'] ?? ''; $phone = $_POST['phone'] ?? ''; $pos = $_POST['position'] ?? ''; - $salary = (float)($_POST['salary'] ?? 0); - $j_date = $_POST['joining_date'] ?: null; + $salary = (float)$_POST['salary']; + $j_date = $_POST['joining_date']; $status = $_POST['status'] ?? 'active'; + if ($id && $name) { $stmt = db()->prepare("UPDATE hr_employees SET department_id = ?, biometric_id = ?, name = ?, email = ?, phone = ?, position = ?, salary = ?, joining_date = ?, status = ? WHERE id = ?"); $stmt->execute([$dept_id, $biometric_id, $name, $email, $phone, $pos, $salary, $j_date, $status, $id]); - $message = "Employee updated successfully!"; + redirectWithMessage("Employee updated successfully!", "index.php?page=hr_employees"); } } if (isset($_POST['delete_hr_employee'])) { @@ -1455,74 +1803,91 @@ if (isset($_POST['add_hr_department'])) { if ($id) { $stmt = db()->prepare("DELETE FROM hr_employees WHERE id = ?"); $stmt->execute([$id]); - $message = "Employee deleted successfully!"; + redirectWithMessage("Employee deleted successfully!", "index.php?page=hr_employees"); } } - if (isset($_POST['mark_attendance'])) { + if (isset($_POST['mark_hr_attendance'])) { $emp_id = (int)$_POST['employee_id']; $date = $_POST['attendance_date'] ?: date('Y-m-d'); $status = $_POST['status'] ?? 'present'; $in = $_POST['clock_in'] ?: null; $out = $_POST['clock_out'] ?: null; + if ($emp_id) { - $stmt = db()->prepare("INSERT INTO hr_attendance (employee_id, attendance_date, status, clock_in, clock_out) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE status = ?, clock_in = ?, clock_out = ?"); - $stmt->execute([$emp_id, $date, $status, $in, $out, $status, $in, $out]); - $message = "Attendance marked successfully!"; + $check = db()->prepare("SELECT id FROM hr_attendance WHERE employee_id = ? AND attendance_date = ?"); + $check->execute([$emp_id, $date]); + if ($check->fetch()) { + $stmt = db()->prepare("UPDATE hr_attendance SET status = ?, clock_in = ?, clock_out = ? WHERE employee_id = ? AND attendance_date = ?"); + $stmt->execute([$status, $in, $out, $emp_id, $date]); + } else { + $stmt = db()->prepare("INSERT INTO hr_attendance (employee_id, attendance_date, status, clock_in, clock_out) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([$emp_id, $date, $status, $in, $out]); + } + redirectWithMessage("Attendance marked successfully!", "index.php?page=hr_attendance&date=$date"); } } if (isset($_POST['generate_payroll'])) { $emp_id = (int)$_POST['employee_id']; $month = (int)$_POST['month']; $year = (int)$_POST['year']; - $bonus = (float)($_POST['bonus'] ?? 0); - $deductions = (float)($_POST['deductions'] ?? 0); - - $emp = db()->prepare("SELECT salary FROM hr_employees WHERE id = ?"); - $emp->execute([$emp_id]); - $salary = (float)$emp->fetchColumn(); - - $net = $salary + $bonus - $deductions; + $bonus = (float)$_POST['bonus']; + $deduct = (float)$_POST['deductions']; + $notes = $_POST['notes'] ?? ''; + $db = db(); try { - $stmt = db()->prepare("INSERT INTO hr_payroll (employee_id, payroll_month, payroll_year, basic_salary, bonus, deductions, net_salary, status) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')"); - $stmt->execute([$emp_id, $month, $year, $salary, $bonus, $deductions, $net]); - $message = "Payroll generated successfully!"; - } catch (PDOException $e) { - if ($e->getCode() == 23000) { // Integrity constraint violation - $message = "Error: Payroll already exists for this employee in the selected period."; - } else { - $message = "Error: " . $e->getMessage(); + $db->beginTransaction(); + $emp = $db->query("SELECT salary FROM hr_employees WHERE id = $emp_id")->fetch(); + if (!$emp) throw new Exception("Employee not found."); + + $basic = (float)$emp['salary']; + $net = $basic + $bonus - $deduct; + + $check = $db->prepare("SELECT id FROM hr_payroll WHERE employee_id = ? AND payroll_month = ? AND payroll_year = ?"); + $check->execute([$emp_id, $month, $year]); + if ($check->fetch()) { + throw new Exception("Payroll already exists for this employee in the selected period."); } + + $stmt = $db->prepare("INSERT INTO hr_payroll (employee_id, payroll_month, payroll_year, basic_salary, bonus, deductions, net_salary, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$emp_id, $month, $year, $basic, $bonus, $deduct, $net, $notes]); + $db->commit(); + redirectWithMessage("Payroll generated successfully!", "index.php?page=hr_payroll&month=$month&year=$year"); + } catch (Exception $e) { + $db->rollBack(); + $message = "Error: " . $e->getMessage(); } } - if (isset($_POST['pay_payroll'])) { $id = (int)$_POST['id']; - if ($id) { - // Get payroll details for accounting - $stmt = db()->prepare("SELECT p.*, e.name FROM hr_payroll p JOIN hr_employees e ON p.employee_id = e.id WHERE p.id = ?"); + $db = db(); + try { + $db->beginTransaction(); + $stmt = $db->prepare("SELECT * FROM hr_payroll WHERE id = ? AND status = 'unpaid'"); $stmt->execute([$id]); - $payroll = $stmt->fetch(); - - if ($payroll && $payroll['status'] !== 'paid') { - $stmt = db()->prepare("UPDATE hr_payroll SET status = 'paid', payment_date = CURDATE() WHERE id = ?"); - $stmt->execute([$id]); - - // Accounting Integration - recordPayrollJournal($id, (float)$payroll['net_salary'], date('Y-m-d'), $payroll['name']); - - $message = "Payroll marked as paid and recorded in accounting!"; + $p = $stmt->fetch(); + if ($p) { + $db->prepare("UPDATE hr_payroll SET status = 'paid', payment_date = CURDATE() WHERE id = ?")->execute([$id]); + // Accounting + recordExpenseJournalForPayroll($id, (float)$p['net_salary'], date('Y-m-d')); + $db->commit(); + redirectWithMessage("Payroll marked as paid and recorded in accounting!", "index.php?page=hr_payroll&month={$p['payroll_month']}&year={$p['payroll_year']}"); } else { - $message = "Payroll already paid or not found."; + throw new Exception("Payroll already paid or not found."); } + } catch (Exception $e) { + $db->rollBack(); + $message = "Error: " . $e->getMessage(); } } if (isset($_POST['delete_payroll'])) { $id = (int)$_POST['id']; - if ($id) { - $stmt = db()->prepare("DELETE FROM hr_payroll WHERE id = ?"); - $stmt->execute([$id]); - $message = "Payroll record deleted successfully!"; + $stmt = db()->prepare("SELECT payroll_month, payroll_year FROM hr_payroll WHERE id = ?"); + $stmt->execute([$id]); + $p = $stmt->fetch(); + if ($p) { + db()->prepare("DELETE FROM hr_payroll WHERE id = ?")->execute([$id]); + redirectWithMessage("Payroll record deleted successfully!", "index.php?page=hr_payroll&month={$p['payroll_month']}&year={$p['payroll_year']}"); } } @@ -1569,7 +1934,7 @@ if (isset($_POST['add_hr_department'])) { } $db->commit(); - $message = "Sales Return processed successfully!"; + redirectWithMessage("Sales Return processed successfully!"); } catch (Exception $e) { $db->rollBack(); $message = "Error processing return: " . $e->getMessage(); @@ -1620,7 +1985,7 @@ if (isset($_POST['add_hr_department'])) { } $db->commit(); - $message = "Purchase Return processed successfully!"; + redirectWithMessage("Purchase Return processed successfully!"); } catch (Exception $e) { $db->rollBack(); $message = "Error processing return: " . $e->getMessage(); @@ -1847,7 +2212,7 @@ if (isset($_POST['add_hr_department'])) { if ($name) { $stmt = db()->prepare("INSERT INTO pos_devices (device_name, device_type, connection_type, ip_address, port, baud_rate) VALUES (?, ?, ?, ?, ?, ?)"); $stmt->execute([$name, $type, $conn, $ip, $port, $baud]); - $message = "Device added successfully!"; + redirectWithMessage("Device added successfully!", "index.php?page=scale_devices"); } } if (isset($_POST['edit_pos_device'])) { @@ -1862,7 +2227,7 @@ if (isset($_POST['add_hr_department'])) { if ($id && $name) { $stmt = db()->prepare("UPDATE pos_devices SET device_name = ?, device_type = ?, connection_type = ?, ip_address = ?, port = ?, baud_rate = ?, status = ? WHERE id = ?"); $stmt->execute([$name, $type, $conn, $ip, $port, $baud, $status, $id]); - $message = "Device updated successfully!"; + redirectWithMessage("Device updated successfully!", "index.php?page=scale_devices"); } } if (isset($_POST['delete_pos_device'])) { @@ -1870,7 +2235,7 @@ if (isset($_POST['add_hr_department'])) { if ($id) { $stmt = db()->prepare("DELETE FROM pos_devices WHERE id = ?"); $stmt->execute([$id]); - $message = "Device deleted successfully!"; + redirectWithMessage("Device deleted successfully!", "index.php?page=scale_devices"); } } @@ -1901,7 +2266,7 @@ if (isset($_POST['add_hr_department'])) { $_SESSION['profile_pic'] = $filename; } } - $message = "Profile updated successfully!"; + redirectWithMessage("Profile updated successfully!", "index.php?page=my_profile"); } } @@ -1928,13 +2293,7 @@ if (isset($_POST['add_hr_department'])) { } } } - $message = "Settings updated successfully!"; - - // Reload settings for current request - $settings_raw = db()->query("SELECT * FROM settings")->fetchAll(); - foreach ($settings_raw as $s) { - $data['settings'][$s['key']] = $s['value']; - } + redirectWithMessage("Settings updated successfully!", "index.php?page=settings"); } } @@ -1950,7 +2309,112 @@ if (isset($_POST['add_hr_department'])) { if (can('users_view')) { $filename = $_POST['filename'] ?? ''; $res = BackupService::restoreBackup($filename); - $message = $res['success'] ? "Database restored successfully from $filename!" : "Error: " . $res['error']; + redirectWithMessage($res['success'] ? "Database restored successfully from $filename!" : "Error: " . $res['error'], "index.php?page=backups"); + } + } + + if (isset($_POST['delete_backup'])) { + if (can('users_view')) { + $filename = basename($_POST['filename'] ?? ''); + if (unlink(__DIR__ . '/backups/' . $filename)) { + redirectWithMessage("Backup deleted successfully.", "index.php?page=backups"); + } else { + redirectWithMessage("Error deleting backup.", "index.php?page=backups"); + } + } + } + + if (isset($_POST['save_backup_settings'])) { + $limit = (int)($_POST['backup_limit'] ?? 5); + $auto = $_POST['backup_auto_enabled'] ?? '0'; + $time = $_POST['backup_time'] ?? '00:00'; + + $stmt = $db->prepare("INSERT INTO settings (`key`, `value`) VALUES ('backup_limit', ?), ('backup_auto_enabled', ?), ('backup_time', ?) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)"); + $stmt->execute([$limit, $auto, $time]); + redirectWithMessage("Backup settings updated.", "index.php?page=backups"); + } + + if (isset($_GET['download_backup'])) { + $filename = basename($_GET['download_backup']); + $filepath = __DIR__ . '/backups/' . $filename; + if (file_exists($filepath)) { + header('Content-Description: File Transfer'); + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + header('Expires: 0'); + header('Cache-Control: must-revalidate'); + header('Pragma: public'); + header('Content-Length: ' . filesize($filepath)); + readfile($filepath); + exit; + } + } + + // --- Cash Register & Session Handlers --- + if (isset($_POST['add_cash_register'])) { + $name = $_POST['name'] ?? ''; + if ($name) { + // Check license limit + $allowed = LicenseService::getAllowedActivations(); + $stmt = db()->query("SELECT COUNT(*) FROM cash_registers"); + $current_count = (int)$stmt->fetchColumn(); + + if ($current_count >= $allowed) { + $message = "Error: Activation Limit Reached. Your license only allows $allowed register(s)."; + } else { + $stmt = db()->prepare("INSERT INTO cash_registers (name) VALUES (?)"); + $stmt->execute([$name]); + redirectWithMessage("Cash Register added successfully!", "index.php?page=cash_registers"); + } + } + } + if (isset($_POST['edit_cash_register'])) { + $id = (int)$_POST['id']; + $name = $_POST['name'] ?? ''; + $status = $_POST['status'] ?? 'active'; + if ($id && $name) { + $stmt = db()->prepare("UPDATE cash_registers SET name = ?, status = ? WHERE id = ?"); + $stmt->execute([$name, $status, $id]); + redirectWithMessage("Cash Register updated successfully!", "index.php?page=cash_registers"); + } + } + if (isset($_POST['delete_cash_register'])) { + $id = (int)$_POST['id']; + if ($id) { + $stmt = db()->prepare("DELETE FROM cash_registers WHERE id = ?"); + $stmt->execute([$id]); + redirectWithMessage("Cash Register deleted successfully!", "index.php?page=cash_registers"); + } + } + + if (isset($_POST['open_register'])) { + $register_id = (int)$_POST['register_id']; + $user_id = $_SESSION['user_id']; + $opening_balance = (float)$_POST['opening_balance']; + + // Check if user already has an open session + $check = db()->prepare("SELECT id FROM register_sessions WHERE user_id = ? AND status = 'open'"); + $check->execute([$user_id]); + if ($check->fetch()) { + $message = "Error: You already have an open register session."; + } else { + $stmt = db()->prepare("INSERT INTO register_sessions (register_id, user_id, opening_balance, status) VALUES (?, ?, ?, 'open')"); + $stmt->execute([$register_id, $user_id, $opening_balance]); + $_SESSION['register_session_id'] = db()->lastInsertId(); + redirectWithMessage("Register opened successfully!", "index.php?page=pos"); + } + } + + if (isset($_POST['close_register'])) { + $session_id = $_SESSION['register_session_id'] ?? null; + $closing_balance = (float)$_POST['closing_balance']; + $notes = $_POST['notes'] ?? ''; + + if ($session_id) { + $stmt = db()->prepare("UPDATE register_sessions SET closing_balance = ?, closed_at = NOW(), status = 'closed', notes = ? WHERE id = ?"); + $stmt->execute([$closing_balance, $notes, $session_id]); + unset($_SESSION['register_session_id']); + redirectWithMessage("Register closed successfully!", "index.php?page=pos"); } } @@ -3213,7 +3677,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';