0) {
$result .= " and " . $baisasWords . " Baisas";
}
return $result . " Only";
}
function numberToWords($num) {
$num = (int)$num;
if ($num === 0) return "Zero";
$ones = ["", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"];
$tens = ["", "", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"];
if ($num < 20) return $ones[$num];
if ($num < 100) return $tens[(int)($num / 10)] . ($num % 10 ? " " . $ones[$num % 10] : "");
if ($num < 1000) return $ones[(int)($num / 100)] . " Hundred" . ($num % 100 ? " and " . numberToWords($num % 100) : "");
if ($num < 1000000) return numberToWords((int)($num / 1000)) . " Thousand" . ($num % 1000 ? " " . numberToWords($num % 1000) : "");
return (string)$num;
}
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action'])) {
if ($_GET['action'] === 'search_items') {
header('Content-Type: application/json');
$q = $_GET['q'] ?? '';
$stmt = db()->prepare("SELECT id, name_en, name_ar, sku, sale_price, purchase_price, stock_quantity, vat_rate FROM stock_items WHERE name_en LIKE ? OR name_ar LIKE ? OR sku LIKE ? LIMIT 10");
$stmt->execute(["%$q%", "%$q%", "%$q%"]);
echo json_encode($stmt->fetchAll());
exit;
}
if ($_GET['action'] === 'get_payments') {
header('Content-Type: application/json');
$invoice_id = (int)$_GET['invoice_id'];
$stmt = db()->prepare("SELECT p.*, i.id as inv_id, i.type as inv_type, c.name as customer_name
FROM payments p
JOIN invoices i ON p.invoice_id = i.id
LEFT JOIN customers c ON i.customer_id = c.id
WHERE p.invoice_id = ? ORDER BY p.payment_date DESC, p.id DESC");
$stmt->execute([$invoice_id]);
$payments = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($payments as &$p) {
$p['amount_words'] = numberToWordsOMR($p['amount']);
}
echo json_encode($payments);
exit;
}
if ($_GET['action'] === 'get_payment_details') {
header('Content-Type: application/json');
$payment_id = (int)$_GET['payment_id'];
$stmt = db()->prepare("SELECT p.*, i.id as inv_id, i.type as inv_type, c.name as customer_name
FROM payments p
JOIN invoices i ON p.invoice_id = i.id
LEFT JOIN customers c ON i.customer_id = c.id
WHERE p.id = ?");
$stmt->execute([$payment_id]);
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
if ($payment) {
$payment['amount_words'] = numberToWordsOMR($payment['amount']);
}
echo json_encode($payment);
exit;
}
if ($_GET['action'] === 'get_held_carts') {
header('Content-Type: application/json');
$stmt = db()->query("SELECT h.*, c.name as customer_name FROM pos_held_carts h LEFT JOIN customers c ON h.customer_id = c.id ORDER BY h.id DESC");
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
exit;
}
if ($_GET['action'] === 'validate_discount') {
header('Content-Type: application/json');
$code = $_GET['code'] ?? '';
$stmt = db()->prepare("SELECT * FROM discount_codes WHERE code = ? AND status = 'active' AND (expiry_date IS NULL OR expiry_date >= CURDATE())");
$stmt->execute([$code]);
$discount = $stmt->fetch(PDO::FETCH_ASSOC);
if ($discount) {
echo json_encode(['success' => true, 'discount' => $discount]);
} else {
echo json_encode(['success' => false, 'error' => 'Invalid or expired code']);
}
exit;
}
if ($_GET['action'] === 'get_customer_loyalty') {
header('Content-Type: application/json');
$id = (int)($_GET['customer_id'] ?? 0);
$stmt = db()->prepare("SELECT loyalty_points FROM customers WHERE id = ?");
$stmt->execute([$id]);
$points = $stmt->fetchColumn();
echo json_encode(['success' => true, 'points' => (float)$points]);
exit;
}
if ($_GET['action'] === 'get_invoice_items') {
header('Content-Type: application/json');
$invoice_id = (int)$_GET['invoice_id'];
$stmt = db()->prepare("SELECT ii.*, i.name_en, i.name_ar, i.sku
FROM invoice_items ii
JOIN stock_items i ON ii.item_id = i.id
WHERE ii.invoice_id = ?");
$stmt->execute([$invoice_id]);
echo json_encode($stmt->fetchAll());
exit;
}
if ($_GET['action'] === 'get_return_details') {
header('Content-Type: application/json');
$return_id = (int)$_GET['return_id'];
$type = $_GET['type'] ?? 'sale'; // 'sale' or 'purchase'
$table = ($type === 'purchase') ? 'purchase_returns' : 'sales_returns';
$item_table = ($type === 'purchase') ? 'purchase_return_items' : 'sales_return_items';
$stmt = db()->prepare("SELECT r.*, c.name as party_name, i.invoice_date as original_invoice_date
FROM $table r
LEFT JOIN customers c ON " . ($type === 'purchase' ? 'r.supplier_id' : 'r.customer_id') . " = c.id
LEFT JOIN invoices i ON r.invoice_id = i.id
WHERE r.id = ?");
$stmt->execute([$return_id]);
$return = $stmt->fetch(PDO::FETCH_ASSOC);
if ($return) {
$stmt = db()->prepare("SELECT ri.*, si.name_en, si.name_ar, si.sku
FROM $item_table ri
JOIN stock_items si ON ri.item_id = si.id
WHERE ri.return_id = ?");
$stmt->execute([$return_id]);
$return['items'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
echo json_encode($return);
exit;
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// --- Expense Categories Handlers ---
if (isset($_POST['add_expense_category'])) {
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
if ($name_en && $name_ar) {
$stmt = db()->prepare("INSERT INTO expense_categories (name_en, name_ar) VALUES (?, ?)");
$stmt->execute([$name_en, $name_ar]);
$message = "Expense Category added successfully!";
}
}
if (isset($_POST['edit_expense_category'])) {
$id = (int)$_POST['id'];
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
if ($id && $name_en && $name_ar) {
$stmt = db()->prepare("UPDATE expense_categories SET name_en = ?, name_ar = ? WHERE id = ?");
$stmt->execute([$name_en, $name_ar, $id]);
$message = "Expense Category updated successfully!";
}
}
if (isset($_POST['delete_expense_category'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM expense_categories WHERE id = ?");
$stmt->execute([$id]);
$message = "Expense Category deleted successfully!";
}
}
// --- Expenses Handlers ---
if (isset($_POST['add_expense'])) {
$category_id = (int)$_POST['category_id'];
$amount = (float)$_POST['amount'];
$date = $_POST['expense_date'] ?: date('Y-m-d');
$desc = $_POST['description'] ?? '';
$ref = $_POST['reference_no'] ?? '';
if ($category_id && $amount > 0) {
$stmt = db()->prepare("INSERT INTO expenses (category_id, amount, expense_date, description, reference_no) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$category_id, $amount, $date, $desc, $ref]);
$expense_id = db()->lastInsertId();
// Accounting Integration
$catStmt = db()->prepare("SELECT name_en FROM expense_categories WHERE id = ?");
$catStmt->execute([$category_id]);
$cat_name = $catStmt->fetchColumn();
recordExpenseJournal($expense_id, $amount, $date, $cat_name);
$message = "Expense recorded successfully!";
}
}
if (isset($_POST['edit_expense'])) {
$id = (int)$_POST['id'];
$category_id = (int)$_POST['category_id'];
$amount = (float)$_POST['amount'];
$date = $_POST['expense_date'] ?: date('Y-m-d');
$desc = $_POST['description'] ?? '';
$ref = $_POST['reference_no'] ?? '';
if ($id && $category_id && $amount > 0) {
$stmt = db()->prepare("UPDATE expenses SET category_id = ?, amount = ?, expense_date = ?, description = ?, reference_no = ? WHERE id = ?");
$stmt->execute([$category_id, $amount, $date, $desc, $ref, $id]);
$message = "Expense updated successfully!";
}
}
if (isset($_POST['delete_expense'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM expenses WHERE id = ?");
$stmt->execute([$id]);
$message = "Expense deleted successfully!";
}
}
if (isset($_POST['add_customer'])) {
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$phone = $_POST['phone'] ?? '';
$tax_id = $_POST['tax_id'] ?? '';
$balance = (float)($_POST['balance'] ?? 0);
$type = $_POST['type'] ?? 'customer';
if ($name) {
$stmt = db()->prepare("INSERT INTO customers (name, email, phone, tax_id, balance, type) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$name, $email, $phone, $tax_id, $balance, $type]);
if (isset($_POST['ajax'])) {
echo json_encode(['success' => true, 'id' => db()->lastInsertId(), 'name' => $name]);
exit;
}
$message = ucfirst($type) . " added successfully!";
}
}
if (isset($_POST['action']) && $_POST['action'] === 'save_pos_transaction') {
header('Content-Type: application/json');
try {
$db = db();
$db->beginTransaction();
$customer_id = !empty($_POST['customer_id']) ? (int)$_POST['customer_id'] : null;
$payments = json_decode($_POST['payments'] ?? '[]', true);
$total_amount = (float)$_POST['total_amount'];
$discount_code_id = !empty($_POST['discount_code_id']) ? (int)$_POST['discount_code_id'] : null;
$discount_amount = (float)($_POST['discount_amount'] ?? 0);
$loyalty_redeemed = (float)($_POST['loyalty_redeemed'] ?? 0);
$items = json_decode($_POST['items'] ?? '[]', true);
if (empty($items)) {
throw new Exception("Cart is empty");
}
$net_amount = (float)($total_amount - $discount_amount - $loyalty_redeemed);
if ($net_amount < 0) $net_amount = 0;
// Loyalty Calculation: 1 point per 1 OMR spent on net amount
$loyalty_earned = floor($net_amount);
// Check if credit is used for walk-in or exceeds limit
$credit_total = 0;
foreach ($payments as $p) {
if ($p['method'] === 'credit') {
if (!$customer_id) {
throw new Exception("Credit payment is only allowed for registered customers");
}
$credit_total += (float)$p['amount'];
}
}
if ($customer_id && $credit_total > 0) {
$stmt = $db->prepare("SELECT balance, credit_limit FROM customers WHERE id = ?");
$stmt->execute([$customer_id]);
$cust = $stmt->fetch(PDO::FETCH_ASSOC);
if ($cust['credit_limit'] > 0 && (abs($cust['balance'] - $credit_total) > $cust['credit_limit'])) {
throw new Exception("Credit limit exceeded. Current Debt: " . number_format(abs($cust['balance']), 3) . ", Limit: " . number_format($cust['credit_limit'], 3));
}
}
// Calculate actual paid amount (excluding credit)
$actual_paid = 0;
foreach ($payments as $p) {
if ($p['method'] !== 'credit') {
$actual_paid += (float)$p['amount'];
}
}
$status = 'paid';
if ($actual_paid <= 0) {
$status = 'unpaid';
} elseif ($actual_paid < $net_amount - 0.001) {
$status = 'partially_paid';
}
$methods_str = implode(', ', array_unique(array_map(fn($p) => $p['method'], $payments)));
// Create Invoice
$stmt = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, status, total_with_vat, paid_amount, type, payment_type) VALUES (?, CURDATE(), ?, ?, ?, 'sale', ?)");
$stmt->execute([$customer_id, $status, $net_amount, $actual_paid, $methods_str]);
$invoice_id = $db->lastInsertId();
// Add POS Transaction record
$stmt = $db->prepare("INSERT INTO pos_transactions (transaction_no, customer_id, total_amount, discount_code_id, discount_amount, loyalty_points_earned, loyalty_points_redeemed, net_amount, payment_method) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$transaction_no = 'POS-' . time() . rand(100, 999);
$stmt->execute([$transaction_no, $customer_id, $total_amount, $discount_code_id, $discount_amount, $loyalty_earned, $loyalty_redeemed, $net_amount, $methods_str]);
$pos_id = $db->lastInsertId();
$total_vat = 0;
foreach ($items as &$item) {
$qty = (float)$item['qty'];
$price = (float)$item['price'];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item['id']]);
$vat_rate = (float)$stmtVat->fetchColumn();
$item_vat = $subtotal * ($vat_rate / 100);
$total_vat += $item_vat;
// Add to invoice_items
$stmt = $db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $item['id'], $qty, $price, $subtotal]);
// Add to pos_items
$stmt = $db->prepare("INSERT INTO pos_items (transaction_id, product_id, quantity, unit_price, subtotal) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$pos_id, $item['id'], $qty, $price, $subtotal]);
// Update stock
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
$stmt->execute([$qty, $item['id']]);
}
// Update Invoice with VAT info
$stmt = $db->prepare("UPDATE invoices SET total_amount = ?, vat_amount = ? WHERE id = ?");
$stmt->execute([$net_amount - $total_vat, $total_vat, $invoice_id]);
// Update Customer Loyalty Points and Balance
if ($customer_id) {
$credit_total = 0;
foreach ($payments as $p) {
if ($p['method'] === 'credit') {
$credit_total += (float)$p['amount'];
}
}
$stmt = $db->prepare("UPDATE customers SET loyalty_points = loyalty_points - ? + ?, balance = balance - ? WHERE id = ?");
$stmt->execute([(float)$loyalty_redeemed, (float)$loyalty_earned, (float)$credit_total, $customer_id]);
}
// Add Payments
foreach ($payments as $p) {
if ($p['method'] === 'credit') continue;
$stmt = $db->prepare("INSERT INTO payments (invoice_id, amount, payment_date, payment_method, notes) VALUES (?, ?, CURDATE(), ?, 'POS Transaction')");
$stmt->execute([$invoice_id, (float)$p['amount'], $p['method']]);
$payment_id = $db->lastInsertId();
recordPaymentReceivedJournal($payment_id, (float)$p['amount'], date('Y-m-d'), $p['method']);
}
$db->commit();
// Accounting Integration for the Sale itself
recordSaleJournal($invoice_id, (float)$net_amount, date('Y-m-d'), $items, (float)$total_vat);
echo json_encode(['success' => true, 'invoice_id' => $invoice_id, 'transaction_no' => $transaction_no]);
} catch (Exception $e) {
if (isset($db)) $db->rollBack();
error_log("POS Error: " . $e->getMessage());
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
exit;
}
// New Handlers for Advanced POS Features
if (isset($_POST['action']) && $_POST['action'] === 'hold_pos_cart') {
header('Content-Type: application/json');
$name = $_POST['cart_name'] ?? 'Cart ' . date('H:i');
$items = $_POST['items'] ?? '[]';
$customer_id = !empty($_POST['customer_id']) ? (int)$_POST['customer_id'] : null;
$stmt = db()->prepare("INSERT INTO pos_held_carts (cart_name, items_json, customer_id) VALUES (?, ?, ?)");
$stmt->execute([$name, $items, $customer_id]);
echo json_encode(['success' => true]);
exit;
}
if (isset($_POST['action']) && $_POST['action'] === 'delete_held_cart') {
header('Content-Type: application/json');
$id = (int)$_POST['id'];
$stmt = db()->prepare("DELETE FROM pos_held_carts WHERE id = ?");
$stmt->execute([$id]);
echo json_encode(['success' => true]);
exit;
}
if (isset($_POST['edit_customer'])) {
$id = (int)$_POST['id'];
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$phone = $_POST['phone'] ?? '';
$tax_id = $_POST['tax_id'] ?? '';
$balance = (float)($_POST['balance'] ?? 0);
if ($id && $name) {
$stmt = db()->prepare("UPDATE customers SET name = ?, email = ?, phone = ?, tax_id = ?, balance = ? WHERE id = ?");
$stmt->execute([$name, $email, $phone, $tax_id, $balance, $id]);
$message = "Record updated successfully!";
}
}
if (isset($_POST['delete_customer'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM customers WHERE id = ?");
$stmt->execute([$id]);
$message = "Record deleted successfully!";
}
}
if (isset($_POST['add_category'])) {
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
if ($name_en && $name_ar) {
$stmt = db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)");
$stmt->execute([$name_en, $name_ar]);
$message = "Category added successfully!";
}
}
if (isset($_POST['add_unit'])) {
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
$short_en = $_POST['short_en'] ?? '';
$short_ar = $_POST['short_ar'] ?? '';
if ($name_en && $name_ar) {
$stmt = db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar) VALUES (?, ?, ?, ?)");
$stmt->execute([$name_en, $name_ar, $short_en, $short_ar]);
$message = "Unit added successfully!";
}
}
if (isset($_POST['add_item'])) {
$cat_id = $_POST['category_id'] ?: null;
$unit_id = $_POST['unit_id'] ?: null;
$supplier_id = $_POST['supplier_id'] ?: null;
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
$sku = $_POST['sku'] ?? '';
$p_price = (float)($_POST['purchase_price'] ?? 0);
$s_price = (float)($_POST['sale_price'] ?? 0);
$qty = (float)($_POST['stock_quantity'] ?? 0);
$min_stock = (float)($_POST['min_stock_level'] ?? 0);
$vat_rate = (float)($_POST['vat_rate'] ?? 0);
$expiry = $_POST['expiry_date'] ?: null;
$image_path = null;
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
$ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$filename = uniqid('item_') . '.' . $ext;
$target = 'uploads/items/' . $filename;
if (move_uploaded_file($_FILES['image']['tmp_name'], $target)) {
$image_path = $target;
}
}
if ($name_en && $name_ar) {
$stmt = db()->prepare("INSERT INTO stock_items (category_id, unit_id, supplier_id, name_en, name_ar, sku, purchase_price, sale_price, stock_quantity, min_stock_level, expiry_date, image_path, vat_rate) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$cat_id, $unit_id, $supplier_id, $name_en, $name_ar, $sku, $p_price, $s_price, $qty, $min_stock, $expiry, $image_path, $vat_rate]);
$message = "Item added successfully!";
}
}
if (isset($_POST['delete_item'])) {
$id = (int)$_POST['id'];
// Optional: delete image file
$item = db()->prepare("SELECT image_path FROM stock_items WHERE id = ?");
$item->execute([$id]);
$path = $item->fetchColumn();
if ($path && file_exists($path)) {
unlink($path);
}
$stmt = db()->prepare("DELETE FROM stock_items WHERE id = ?");
$stmt->execute([$id]);
$message = "Item deleted successfully!";
}
if (isset($_POST['edit_item'])) {
$id = (int)$_POST['id'];
$cat_id = $_POST['category_id'] ?: null;
$unit_id = $_POST['unit_id'] ?: null;
$supplier_id = $_POST['supplier_id'] ?: null;
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
$sku = $_POST['sku'] ?? '';
$p_price = (float)($_POST['purchase_price'] ?? 0);
$s_price = (float)($_POST['sale_price'] ?? 0);
$qty = (float)($_POST['stock_quantity'] ?? 0);
$min_stock = (float)($_POST['min_stock_level'] ?? 0);
$vat_rate = (float)($_POST['vat_rate'] ?? 0);
$expiry = $_POST['expiry_date'] ?: null;
$stmt = db()->prepare("SELECT image_path FROM stock_items WHERE id = ?");
$stmt->execute([$id]);
$image_path = $stmt->fetchColumn();
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
// Delete old image
if ($image_path && file_exists($image_path)) {
unlink($image_path);
}
$ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$filename = uniqid('item_') . '.' . $ext;
$target = 'uploads/items/' . $filename;
if (move_uploaded_file($_FILES['image']['tmp_name'], $target)) {
$image_path = $target;
}
}
if ($name_en && $name_ar) {
$stmt = db()->prepare("UPDATE stock_items SET category_id = ?, unit_id = ?, supplier_id = ?, name_en = ?, name_ar = ?, sku = ?, purchase_price = ?, sale_price = ?, stock_quantity = ?, min_stock_level = ?, expiry_date = ?, image_path = ?, vat_rate = ? WHERE id = ?");
$stmt->execute([$cat_id, $unit_id, $supplier_id, $name_en, $name_ar, $sku, $p_price, $s_price, $qty, $min_stock, $expiry, $image_path, $vat_rate, $id]);
$message = "Item updated successfully!";
}
}
if (isset($_POST['import_items'])) {
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === UPLOAD_ERR_OK) {
$file = $_FILES['excel_file']['tmp_name'];
$handle = fopen($file, 'r');
$header = fgetcsv($handle); // Skip header row
$count = 0;
while (($row = fgetcsv($handle)) !== FALSE) {
// Mapping: sku, eng name, arabic name, sale price, cost price
if (count($row) < 5) continue;
$sku = trim($row[0]);
$name_en = trim($row[1]);
$name_ar = trim($row[2]);
$sale_price = (float)trim($row[3]);
$purchase_price = (float)trim($row[4]);
if ($name_en && $name_ar) {
// Check if SKU exists to update or insert
$existingId = null;
if ($sku !== "") {
$check = db()->prepare("SELECT id FROM stock_items WHERE sku = ?");
$check->execute([$sku]);
$existingId = $check->fetchColumn();
}
if ($existingId) {
$stmt = db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, sale_price = ?, purchase_price = ? WHERE id = ?");
$stmt->execute([$name_en, $name_ar, $sale_price, $purchase_price, $existingId]);
} else {
$stmt = db()->prepare("INSERT INTO stock_items (sku, name_en, name_ar, sale_price, purchase_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$sku, $name_en, $name_ar, $sale_price, $purchase_price]);
}
$count++;
}
}
fclose($handle);
$message = "$count items processed successfully!";
}
}
if (isset($_POST['import_customers']) || isset($_POST['import_suppliers'])) {
$type = isset($_POST['import_customers']) ? 'customer' : 'supplier';
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === UPLOAD_ERR_OK) {
$file = $_FILES['excel_file']['tmp_name'];
$handle = fopen($file, 'r');
$header = fgetcsv($handle);
$count = 0;
while (($row = fgetcsv($handle)) !== FALSE) {
if (count($row) < 4) continue;
$name = trim($row[0]);
$email = trim($row[1]);
$phone = trim($row[2]);
$tax_id = isset($row[3]) ? trim($row[3]) : '';
$balance = isset($row[4]) ? (float)trim($row[4]) : (float)trim($row[3] ?? 0);
if ($name) {
$stmt = db()->prepare("INSERT INTO customers (name, email, phone, tax_id, balance, type) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$name, $email, $phone, $tax_id, $balance, $type]);
$count++;
}
}
fclose($handle);
$message = "$count " . ($type === 'customer' ? 'customers' : 'suppliers') . " imported successfully!";
}
}
if (isset($_POST['import_categories'])) {
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === UPLOAD_ERR_OK) {
$file = $_FILES['excel_file']['tmp_name'];
$handle = fopen($file, 'r');
$header = fgetcsv($handle);
$count = 0;
while (($row = fgetcsv($handle)) !== FALSE) {
if (count($row) < 2) continue;
$name_en = trim($row[0]);
$name_ar = trim($row[1]);
if ($name_en && $name_ar) {
$stmt = db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)");
$stmt->execute([$name_en, $name_ar]);
$count++;
}
}
fclose($handle);
$message = "$count categories imported successfully!";
}
}
if (isset($_POST['import_units'])) {
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === UPLOAD_ERR_OK) {
$file = $_FILES['excel_file']['tmp_name'];
$handle = fopen($file, 'r');
$header = fgetcsv($handle);
$count = 0;
while (($row = fgetcsv($handle)) !== FALSE) {
if (count($row) < 4) continue;
$name_en = trim($row[0]);
$name_ar = trim($row[1]);
$short_en = trim($row[2]);
$short_ar = trim($row[3]);
if ($name_en && $name_ar) {
$stmt = db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar) VALUES (?, ?, ?, ?)");
$stmt->execute([$name_en, $name_ar, $short_en, $short_ar]);
$count++;
}
}
fclose($handle);
$message = "$count units imported successfully!";
}
}
if (isset($_POST['add_invoice'])) {
$customer_id = $_POST['customer_id'] ?: null;
$invoice_date = $_POST['invoice_date'] ?: date('Y-m-d');
$type = $_POST['type'] ?? 'sale'; // 'sale' or 'purchase'
$payment_type = $_POST['payment_type'] ?? 'cash';
$status = $_POST['status'] ?? 'unpaid';
$item_ids = $_POST['item_ids'] ?? [];
$quantities = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
if (!empty($item_ids)) {
$db = db();
$db->beginTransaction();
try {
$subtotal = 0;
$total_vat = 0;
$items_data = [];
foreach ($item_ids as $index => $item_id) {
$qty = (float)$quantities[$index];
$price = (float)$prices[$index];
// Fetch vat_rate for this item
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vat_rate = (float)$stmtVat->fetchColumn();
$line_total = $qty * $price;
$line_vat = $line_total * ($vat_rate / 100);
$subtotal += $line_total;
$total_vat += $line_vat;
$items_data[] = [
'id' => $item_id,
'qty' => $qty,
'price' => $price,
'total' => $line_total
];
}
$total_with_vat = $subtotal + $total_vat;
$paid_amount = ($status === 'paid') ? $total_with_vat : (($status === 'unpaid') ? 0 : (float)($_POST['paid_amount'] ?? 0));
$stmt = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, type, payment_type, status, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$customer_id, $invoice_date, $type, $payment_type, $status, $subtotal, $total_vat, $total_with_vat, $paid_amount]);
$invoice_id = $db->lastInsertId();
if ($paid_amount > 0) {
$stmt = $db->prepare("INSERT INTO payments (invoice_id, payment_date, amount, payment_method, notes) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $invoice_date, $paid_amount, 'Cash', 'Initial payment']);
}
foreach ($items_data as $item) {
$stmt = $db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $item['id'], $item['qty'], $item['price'], $item['total']]);
// Update stock level
if ($type === 'sale') {
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
} else {
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
}
$stmt->execute([$item['qty'], $item['id']]);
}
$db->commit();
// Accounting Integration
if ($type === 'sale') {
recordSaleJournal($invoice_id, (float)$total_with_vat, $invoice_date, $items_data, (float)$total_vat);
} else {
recordPurchaseJournal($invoice_id, (float)$total_with_vat, $invoice_date, $items_data, (float)$total_vat);
}
if ($paid_amount > 0) {
// We need the payment_id. Since we only recorded one payment above, we can fetch it.
$stmtPayId = db()->prepare("SELECT id FROM payments WHERE invoice_id = ? ORDER BY id DESC LIMIT 1");
$stmtPayId->execute([$invoice_id]);
$pay_id = $stmtPayId->fetchColumn();
if ($type === 'sale') {
recordPaymentReceivedJournal($pay_id, $paid_amount, $invoice_date, 'Cash');
} else {
recordPaymentMadeJournal($pay_id, $paid_amount, $invoice_date, 'Cash');
}
}
$message = "Invoice #$invoice_id created successfully!";
} catch (Exception $e) {
$db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
}
if (isset($_POST['delete_invoice'])) {
$id = (int)$_POST['id'];
if ($id) {
$db = db();
$db->beginTransaction();
try {
// Get invoice details
$stmt = $db->prepare("SELECT type FROM invoices WHERE id = ?");
$stmt->execute([$id]);
$type = $stmt->fetchColumn();
// Get items to restore stock
$stmt = $db->prepare("SELECT item_id, quantity FROM invoice_items WHERE invoice_id = ?");
$stmt->execute([$id]);
$items = $stmt->fetchAll();
foreach ($items as $item) {
if ($type === 'sale') {
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
} else {
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
}
$stmt->execute([$item['quantity'], $item['item_id']]);
}
$stmt = $db->prepare("DELETE FROM invoice_items WHERE invoice_id = ?");
$stmt->execute([$id]);
$stmt = $db->prepare("DELETE FROM invoices WHERE id = ?");
$stmt->execute([$id]);
$db->commit();
$message = "Invoice deleted successfully and stock restored!";
} catch (Exception $e) {
$db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
}
if (isset($_POST['edit_invoice'])) {
$invoice_id = (int)$_POST['invoice_id'];
$customer_id = $_POST['customer_id'] ?: null;
$invoice_date = $_POST['invoice_date'] ?: date('Y-m-d');
$payment_type = $_POST['payment_type'] ?? 'cash';
$status = $_POST['status'] ?? 'unpaid';
$item_ids = $_POST['item_ids'] ?? [];
$quantities = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
if ($invoice_id && !empty($item_ids)) {
$db = db();
$db->beginTransaction();
try {
// Get old invoice type and items to revert stock
$stmt = $db->prepare("SELECT type, paid_amount FROM invoices WHERE id = ?");
$stmt->execute([$invoice_id]);
$inv_info = $stmt->fetch();
$type = $inv_info['type'];
$old_paid_amount = (float)$inv_info['paid_amount'];
$stmt = $db->prepare("SELECT item_id, quantity FROM invoice_items WHERE invoice_id = ?");
$stmt->execute([$invoice_id]);
$old_items = $stmt->fetchAll();
foreach ($old_items as $item) {
if ($type === 'sale') {
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
} else {
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
}
$stmt->execute([$item['quantity'], $item['item_id']]);
}
// Delete old items
$stmt = $db->prepare("DELETE FROM invoice_items WHERE invoice_id = ?");
$stmt->execute([$invoice_id]);
// Calculate new totals
$subtotal = 0;
$total_vat = 0;
$items_data = [];
foreach ($item_ids as $index => $item_id) {
$qty = (float)$quantities[$index];
$price = (float)$prices[$index];
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vat_rate = (float)$stmtVat->fetchColumn();
$line_total = $qty * $price;
$line_vat = $line_total * ($vat_rate / 100);
$subtotal += $line_total;
$total_vat += $line_vat;
$items_data[] = [
'id' => $item_id,
'qty' => $qty,
'price' => $price,
'total' => $line_total
];
}
$total_with_vat = $subtotal + $total_vat;
$paid_amount = ($status === 'paid') ? $total_with_vat : (($status === 'unpaid') ? 0 : ($old_paid_amount > 0 ? $old_paid_amount : ($_POST['paid_amount'] ?? 0)));
if ($paid_amount > $old_paid_amount) {
$diff = $paid_amount - $old_paid_amount;
$stmt = $db->prepare("INSERT INTO payments (invoice_id, payment_date, amount, payment_method, notes) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $invoice_date, $diff, 'Cash', 'Payment via edit']);
}
// Update invoice
$stmt = $db->prepare("UPDATE invoices SET customer_id = ?, invoice_date = ?, payment_type = ?, status = ?, total_amount = ?, vat_amount = ?, total_with_vat = ?, paid_amount = ? WHERE id = ?");
$stmt->execute([$customer_id, $invoice_date, $payment_type, $status, $subtotal, $total_vat, $total_with_vat, $paid_amount, $invoice_id]);
// Insert new items and update stock
foreach ($items_data as $item) {
$stmt = $db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $item['id'], $item['qty'], $item['price'], $item['total']]);
if ($type === 'sale') {
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
} else {
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
}
$stmt->execute([$item['qty'], $item['id']]);
}
$db->commit();
$message = "Invoice #$invoice_id updated successfully!";
} catch (Exception $e) {
$db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
}
if (isset($_POST['add_quotation'])) {
$customer_id = $_POST['customer_id'] ?: null;
$quotation_date = $_POST['quotation_date'] ?: date('Y-m-d');
$valid_until = $_POST['valid_until'] ?: null;
$item_ids = $_POST['item_ids'] ?? [];
$quantities = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
if (!empty($item_ids)) {
$db = db();
$db->beginTransaction();
try {
$subtotal = 0;
$total_vat = 0;
$items_data = [];
foreach ($item_ids as $index => $item_id) {
$qty = (float)$quantities[$index];
$price = (float)$prices[$index];
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vat_rate = (float)$stmtVat->fetchColumn();
$line_total = $qty * $price;
$line_vat = $line_total * ($vat_rate / 100);
$subtotal += $line_total;
$total_vat += $line_vat;
$items_data[] = ['id' => $item_id, 'qty' => $qty, 'price' => $price, 'total' => $line_total];
}
$total_with_vat = $subtotal + $total_vat;
$stmt = $db->prepare("INSERT INTO quotations (customer_id, quotation_date, valid_until, total_amount, vat_amount, total_with_vat) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$customer_id, $quotation_date, $valid_until, $subtotal, $total_vat, $total_with_vat]);
$quotation_id = $db->lastInsertId();
foreach ($items_data as $item) {
$stmt = $db->prepare("INSERT INTO quotation_items (quotation_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$quotation_id, $item['id'], $item['qty'], $item['price'], $item['total']]);
}
$db->commit();
$message = "Quotation #$quotation_id created successfully!";
} catch (Exception $e) {
$db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
}
if (isset($_POST['edit_quotation'])) {
$quotation_id = (int)$_POST['quotation_id'];
$customer_id = $_POST['customer_id'] ?: null;
$quotation_date = $_POST['quotation_date'] ?: date('Y-m-d');
$valid_until = $_POST['valid_until'] ?: null;
$status = $_POST['status'] ?? 'pending';
$item_ids = $_POST['item_ids'] ?? [];
$quantities = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
if ($quotation_id && !empty($item_ids)) {
$db = db();
$db->beginTransaction();
try {
$stmt = $db->prepare("DELETE FROM quotation_items WHERE quotation_id = ?");
$stmt->execute([$quotation_id]);
$subtotal = 0;
$total_vat = 0;
$items_data = [];
foreach ($item_ids as $index => $item_id) {
$qty = (float)$quantities[$index];
$price = (float)$prices[$index];
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vat_rate = (float)$stmtVat->fetchColumn();
$line_total = $qty * $price;
$line_vat = $line_total * ($vat_rate / 100);
$subtotal += $line_total;
$total_vat += $line_vat;
$items_data[] = ['id' => $item_id, 'qty' => $qty, 'price' => $price, 'total' => $line_total];
}
$total_with_vat = $subtotal + $total_vat;
$stmt = $db->prepare("UPDATE quotations SET customer_id = ?, quotation_date = ?, valid_until = ?, status = ?, total_amount = ?, vat_amount = ?, total_with_vat = ? WHERE id = ?");
$stmt->execute([$customer_id, $quotation_date, $valid_until, $status, $subtotal, $total_vat, $total_with_vat, $quotation_id]);
foreach ($items_data as $item) {
$stmt = $db->prepare("INSERT INTO quotation_items (quotation_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$quotation_id, $item['id'], $item['qty'], $item['price'], $item['total']]);
}
$db->commit();
$message = "Quotation #$quotation_id updated successfully!";
} catch (Exception $e) {
$db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
}
if (isset($_POST['delete_quotation'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM quotations WHERE id = ?");
$stmt->execute([$id]);
$message = "Quotation deleted successfully!";
}
}
if (isset($_POST['convert_to_invoice'])) {
$quotation_id = (int)$_POST['quotation_id'];
if ($quotation_id) {
$db = db();
$db->beginTransaction();
try {
$stmt = $db->prepare("SELECT * FROM quotations WHERE id = ?");
$stmt->execute([$quotation_id]);
$q = $stmt->fetch();
if (!$q) throw new Exception("Quotation not found");
if ($q['status'] === 'converted') throw new Exception("Quotation already converted");
$stmt = $db->prepare("SELECT * FROM quotation_items WHERE quotation_id = ?");
$stmt->execute([$quotation_id]);
$items = $stmt->fetchAll();
// Create Invoice
$stmt = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, type, status, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, CURDATE(), 'sale', 'unpaid', ?, ?, ?, 0)");
$stmt->execute([$q['customer_id'], $q['total_amount'], $q['vat_amount'], $q['total_with_vat']]);
$invoice_id = $db->lastInsertId();
foreach ($items as $item) {
$stmt = $db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['total_price']]);
// Update stock
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
$stmt->execute([$item['quantity'], $item['item_id']]);
}
// Update quotation status
$stmt = $db->prepare("UPDATE quotations SET status = 'converted' WHERE id = ?");
$stmt->execute([$quotation_id]);
$db->commit();
$message = "Quotation converted to Invoice #$invoice_id successfully!";
} catch (Exception $e) {
$db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
}
if (isset($_POST['add_payment_method'])) {
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
if ($name_en && $name_ar) {
$stmt = db()->prepare("INSERT INTO payment_methods (name_en, name_ar) VALUES (?, ?)");
$stmt->execute([$name_en, $name_ar]);
$message = "Payment method added successfully!";
}
}
if (isset($_POST['edit_payment_method'])) {
$id = (int)$_POST['id'];
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
if ($id && $name_en && $name_ar) {
$stmt = db()->prepare("UPDATE payment_methods SET name_en = ?, name_ar = ? WHERE id = ?");
$stmt->execute([$name_en, $name_ar, $id]);
$message = "Payment method updated successfully!";
}
}
if (isset($_POST['delete_payment_method'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM payment_methods WHERE id = ?");
$stmt->execute([$id]);
$message = "Payment method deleted successfully!";
}
}
if (isset($_POST['update_settings'])) {
foreach ($_POST['settings'] as $key => $value) {
$stmt = db()->prepare("INSERT INTO settings (`key`, `value`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `value` = ?");
$stmt->execute([$key, $value, $value]);
}
if (isset($_FILES['company_logo']) && $_FILES['company_logo']['error'] === UPLOAD_ERR_OK) {
$ext = pathinfo($_FILES['company_logo']['name'], PATHINFO_EXTENSION);
$filename = 'logo.' . $ext;
$target = 'uploads/' . $filename;
if (!is_dir('uploads')) mkdir('uploads', 0775, true);
if (move_uploaded_file($_FILES['company_logo']['tmp_name'], $target)) {
$stmt = db()->prepare("INSERT INTO settings (`key`, `value`) VALUES ('company_logo', ?) ON DUPLICATE KEY UPDATE `value` = ?");
$stmt->execute([$target, $target]);
}
}
if (isset($_FILES['favicon']) && $_FILES['favicon']['error'] === UPLOAD_ERR_OK) {
$ext = pathinfo($_FILES['favicon']['name'], PATHINFO_EXTENSION);
$filename = 'favicon.' . $ext;
$target = 'uploads/' . $filename;
if (!is_dir('uploads')) mkdir('uploads', 0775, true);
if (move_uploaded_file($_FILES['favicon']['tmp_name'], $target)) {
$stmt = db()->prepare("INSERT INTO settings (`key`, `value`) VALUES ('favicon', ?) ON DUPLICATE KEY UPDATE `value` = ?");
$stmt->execute([$target, $target]);
}
}
if (isset($_FILES['manager_signature']) && $_FILES['manager_signature']['error'] === UPLOAD_ERR_OK) {
$ext = pathinfo($_FILES['manager_signature']['name'], PATHINFO_EXTENSION);
$filename = 'signature.' . $ext;
$target = 'uploads/' . $filename;
if (!is_dir('uploads')) mkdir('uploads', 0775, true);
if (move_uploaded_file($_FILES['manager_signature']['tmp_name'], $target)) {
$stmt = db()->prepare("INSERT INTO settings (`key`, `value`) VALUES ('manager_signature', ?) ON DUPLICATE KEY UPDATE `value` = ?");
$stmt->execute([$target, $target]);
}
}
$message = "Settings updated successfully!";
}
if (isset($_POST['sync_accounting'])) {
try {
$db = db();
$db->beginTransaction();
// Clear existing automatic entries
$db->exec("DELETE FROM acc_journal_entries WHERE source_type IN ('invoice', 'payment', 'expense', 'payroll', 'sales_return', 'purchase_return')");
// 1. Invoices
$invoices = $db->query("SELECT * FROM invoices ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC);
foreach ($invoices as $inv) {
$items = $db->prepare("SELECT item_id as id, quantity as qty, unit_price as price FROM invoice_items WHERE invoice_id = ?");
$items->execute([$inv['id']]);
$items_data = $items->fetchAll(PDO::FETCH_ASSOC);
if ($inv['type'] === 'sale') {
recordSaleJournal($inv['id'], (float)$inv['total_with_vat'], $inv['invoice_date'], $items_data, (float)$inv['vat_amount']);
} else {
recordPurchaseJournal($inv['id'], (float)$inv['total_with_vat'], $inv['invoice_date'], $items_data, (float)$inv['vat_amount']);
}
}
// 2. Payments
$payments = $db->query("SELECT p.*, i.type FROM payments p JOIN invoices i ON p.invoice_id = i.id ORDER BY p.id ASC")->fetchAll(PDO::FETCH_ASSOC);
foreach ($payments as $pay) {
if ($pay['type'] === 'sale') {
recordPaymentReceivedJournal($pay['id'], (float)$pay['amount'], $pay['payment_date'], $pay['payment_method']);
} else {
recordPaymentMadeJournal($pay['id'], (float)$pay['amount'], $pay['payment_date'], $pay['payment_method']);
}
}
// 3. Expenses
$expenses = $db->query("SELECT e.*, c.name_en FROM expenses e JOIN expense_categories c ON e.category_id = c.id ORDER BY e.id ASC")->fetchAll(PDO::FETCH_ASSOC);
foreach ($expenses as $exp) {
recordExpenseJournal($exp['id'], (float)$exp['amount'], $exp['expense_date'], $exp['name_en']);
}
// 4. Payroll
$payrolls = $db->query("SELECT p.*, e.name FROM hr_payroll p JOIN hr_employees e ON p.employee_id = e.id WHERE p.status = 'paid' ORDER BY p.id ASC")->fetchAll(PDO::FETCH_ASSOC);
foreach ($payrolls as $proll) {
recordPayrollJournal($proll['id'], (float)$proll['net_salary'], $proll['payment_date'] ?? date('Y-m-d'), $proll['name']);
}
// 5. Returns
$s_returns = $db->query("SELECT * FROM sales_returns ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC);
foreach ($s_returns as $ret) {
recordSalesReturnJournal($ret['id'], (float)$ret['total_amount'], $ret['return_date']);
}
$p_returns = $db->query("SELECT * FROM purchase_returns ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC);
foreach ($p_returns as $ret) {
recordPurchaseReturnJournal($ret['id'], (float)$ret['total_amount'], $ret['return_date']);
}
$db->commit();
$message = "Accounting sync completed successfully!";
} catch (Exception $e) {
if (isset($db)) $db->rollBack();
$message = "Sync Error: " . $e->getMessage();
}
}
if (isset($_POST['add_account'])) {
$code = $_POST['code'] ?? '';
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
$type = $_POST['type'] ?? '';
$parent_id = $_POST['parent_id'] ?: null;
if ($code && $name_en && $type) {
$stmt = db()->prepare("INSERT INTO acc_accounts (code, name_en, name_ar, type, parent_id) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$code, $name_en, $name_ar, $type, $parent_id]);
$message = "Account added successfully!";
}
}
if (isset($_POST['add_journal_entry'])) {
$date = $_POST['entry_date'] ?: date('Y-m-d');
$desc = $_POST['description'] ?? '';
$ref = $_POST['reference'] ?? '';
$codes = $_POST['codes'] ?? [];
$debits = $_POST['debits'] ?? [];
$credits = $_POST['credits'] ?? [];
$items = [];
$total_debit = 0;
$total_credit = 0;
foreach ($codes as $index => $code) {
if (!$code) continue;
$deb = (float)($debits[$index] ?? 0);
$cre = (float)($credits[$index] ?? 0);
if ($deb == 0 && $cre == 0) continue;
$items[] = ['code' => $code, 'debit' => $deb, 'credit' => $cre];
$total_debit += $deb;
$total_credit += $cre;
}
if (abs($total_debit - $total_credit) < 0.0001 && !empty($items)) {
createJournalEntry($date, $desc, $ref, 'manual', null, $items);
$message = "Manual Journal Entry recorded successfully!";
} else {
$message = "Error: Journal entry is not balanced (Debit: $total_debit, Credit: $total_credit)";
}
}
if (isset($_POST['record_payment'])) {
$invoice_id = (int)$_POST['invoice_id'];
$amount = (float)$_POST['amount'];
$payment_date = $_POST['payment_date'] ?: date('Y-m-d');
$payment_method = $_POST['payment_method'] ?: 'Cash';
$notes = $_POST['notes'] ?? '';
if ($invoice_id && $amount > 0) {
$db = db();
$db->beginTransaction();
try {
// Record the payment
$stmt = $db->prepare("INSERT INTO payments (invoice_id, payment_date, amount, payment_method, notes) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $payment_date, $amount, $payment_method, $notes]);
$payment_id = $db->lastInsertId();
// Update invoice paid_amount and status
$stmt = $db->prepare("SELECT total_with_vat, paid_amount FROM invoices WHERE id = ?");
$stmt->execute([$invoice_id]);
$inv = $stmt->fetch();
$new_paid_amount = (float)$inv['paid_amount'] + $amount;
$total = (float)$inv['total_with_vat'];
$new_status = 'partially_paid';
if ($new_paid_amount >= $total) {
$new_status = 'paid';
}
$stmt = $db->prepare("UPDATE invoices SET paid_amount = ?, status = ? WHERE id = ?");
$stmt->execute([$new_paid_amount, $new_status, $invoice_id]);
// Accounting Integration
$stmtInvType = $db->prepare("SELECT type FROM invoices WHERE id = ?");
$stmtInvType->execute([$invoice_id]);
$invType = $stmtInvType->fetchColumn();
if ($invType === 'sale') {
recordPaymentReceivedJournal($payment_id, $amount, $payment_date, $payment_method);
} else {
recordPaymentMadeJournal($payment_id, $amount, $payment_date, $payment_method);
}
$db->commit();
$message = "Payment of OMR " . number_format($amount, 3) . " recorded successfully! Receipt ID: $payment_id";
// For showing receipt after redirect/refresh, we can use a session or just a message.
// The user wants to "issue a receipt". I'll add a trigger to show the receipt modal.
$_SESSION['show_receipt_id'] = $payment_id;
$_SESSION['trigger_receipt_modal'] = true;
} catch (Exception $e) {
$db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
}
if (isset($_POST['add_sales_return'])) {
$invoice_id = (int)$_POST['invoice_id'];
$return_date = $_POST['return_date'] ?: date('Y-m-d');
$notes = $_POST['notes'] ?? '';
$item_ids = $_POST['item_ids'] ?? [];
$quantities = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
if ($invoice_id && !empty($item_ids)) {
$db = db();
$db->beginTransaction();
try {
// Get customer ID from invoice
$stmt = $db->prepare("SELECT customer_id FROM invoices WHERE id = ?");
$stmt->execute([$invoice_id]);
$customer_id = $stmt->fetchColumn();
$total_return_amount = 0;
$items_data = [];
foreach ($item_ids as $index => $item_id) {
$qty = (float)$quantities[$index];
$price = (float)$prices[$index];
if ($qty <= 0) continue;
$line_total = $qty * $price;
$total_return_amount += $line_total;
$items_data[] = [
'id' => $item_id,
'qty' => $qty,
'price' => $price,
'total' => $line_total
];
}
if (empty($items_data)) throw new Exception("No items to return");
// Create Sales Return record
$stmt = $db->prepare("INSERT INTO sales_returns (invoice_id, customer_id, return_date, total_amount, notes) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $customer_id, $return_date, $total_return_amount, $notes]);
$return_id = $db->lastInsertId();
foreach ($items_data as $item) {
$stmt = $db->prepare("INSERT INTO sales_return_items (return_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$return_id, $item['id'], $item['qty'], $item['price'], $item['total']]);
// Restore stock
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
$stmt->execute([$item['qty'], $item['id']]);
}
// Accounting Integration
recordSalesReturnJournal($return_id, $total_return_amount, $return_date);
// If it was a credit sale, we might want to reduce the customer balance.
if ($customer_id) {
$stmt = $db->prepare("UPDATE customers SET balance = balance + ? WHERE id = ?");
$stmt->execute([$total_return_amount, $customer_id]);
}
$db->commit();
$message = "Sales Return #$return_id processed successfully!";
} catch (Exception $e) {
if (isset($db)) $db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
}
if (isset($_POST['add_purchase_return'])) {
$invoice_id = (int)$_POST['invoice_id'];
$return_date = $_POST['return_date'] ?: date('Y-m-d');
$notes = $_POST['notes'] ?? '';
$item_ids = $_POST['item_ids'] ?? [];
$quantities = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
if ($invoice_id && !empty($item_ids)) {
$db = db();
$db->beginTransaction();
try {
// Get supplier ID from invoice
$stmt = $db->prepare("SELECT customer_id FROM invoices WHERE id = ?");
$stmt->execute([$invoice_id]);
$supplier_id = $stmt->fetchColumn();
$total_return_amount = 0;
$items_data = [];
foreach ($item_ids as $index => $item_id) {
$qty = (float)$quantities[$index];
$price = (float)$prices[$index];
if ($qty <= 0) continue;
$line_total = $qty * $price;
$total_return_amount += $line_total;
$items_data[] = [
'id' => $item_id,
'qty' => $qty,
'price' => $price,
'total' => $line_total
];
}
if (empty($items_data)) throw new Exception("No items to return");
// Create Purchase Return record
$stmt = $db->prepare("INSERT INTO purchase_returns (invoice_id, supplier_id, return_date, total_amount, notes) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $supplier_id, $return_date, $total_return_amount, $notes]);
$return_id = $db->lastInsertId();
foreach ($items_data as $item) {
$stmt = $db->prepare("INSERT INTO purchase_return_items (return_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$return_id, $item['id'], $item['qty'], $item['price'], $item['total']]);
// Update stock (decrease)
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
$stmt->execute([$item['qty'], $item['id']]);
}
// Accounting Integration
recordPurchaseReturnJournal($return_id, $total_return_amount, $return_date);
// Reduce debt to supplier
if ($supplier_id) {
$stmt = $db->prepare("UPDATE customers SET balance = balance + ? WHERE id = ?");
$stmt->execute([$total_return_amount, $supplier_id]);
}
$db->commit();
$message = "Purchase Return #$return_id processed successfully!";
} catch (Exception $e) {
if (isset($db)) $db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
}
}
// --- HR Handlers ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_hr_department'])) {
$name = $_POST['name'] ?? '';
if ($name) {
$stmt = db()->prepare("INSERT INTO hr_departments (name) VALUES (?)");
$stmt->execute([$name]);
$message = "Department added successfully!";
}
}
if (isset($_POST['edit_hr_department'])) {
$id = (int)$_POST['id'];
$name = $_POST['name'] ?? '';
if ($id && $name) {
$stmt = db()->prepare("UPDATE hr_departments SET name = ? WHERE id = ?");
$stmt->execute([$name, $id]);
$message = "Department updated successfully!";
}
}
if (isset($_POST['delete_hr_department'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM hr_departments WHERE id = ?");
$stmt->execute([$id]);
$message = "Department deleted successfully!";
}
}
if (isset($_POST['add_hr_employee'])) {
$dept_id = (int)$_POST['department_id'] ?: null;
$biometric_id = $_POST['biometric_id'] ?: null;
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$phone = $_POST['phone'] ?? '';
$pos = $_POST['position'] ?? '';
$salary = (float)($_POST['salary'] ?? 0);
$j_date = $_POST['joining_date'] ?: null;
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!";
}
}
if (isset($_POST['edit_hr_employee'])) {
$id = (int)$_POST['id'];
$dept_id = (int)$_POST['department_id'] ?: null;
$biometric_id = $_POST['biometric_id'] ?: null;
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$phone = $_POST['phone'] ?? '';
$pos = $_POST['position'] ?? '';
$salary = (float)($_POST['salary'] ?? 0);
$j_date = $_POST['joining_date'] ?: null;
$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!";
}
}
if (isset($_POST['delete_hr_employee'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM hr_employees WHERE id = ?");
$stmt->execute([$id]);
$message = "Employee deleted successfully!";
}
}
if (isset($_POST['mark_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!";
}
}
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;
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();
}
}
}
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 = ?");
$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!";
} else {
$message = "Payroll already paid or not found.";
}
}
}
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!";
}
}
// --- Biometric Devices Handlers ---
if (isset($_POST['add_biometric_device'])) {
$name = $_POST['device_name'] ?? '';
$ip = $_POST['ip_address'] ?? '';
$port = (int)($_POST['port'] ?? 4370);
$io = $_POST['io_address'] ?? '';
$serial = $_POST['serial_number'] ?? '';
if ($name && $ip) {
$stmt = db()->prepare("INSERT INTO hr_biometric_devices (device_name, ip_address, port, io_address, serial_number) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$name, $ip, $port, $io, $serial]);
$message = "Device added successfully!";
}
}
if (isset($_POST['edit_biometric_device'])) {
$id = (int)$_POST['id'];
$name = $_POST['device_name'] ?? '';
$ip = $_POST['ip_address'] ?? '';
$port = (int)($_POST['port'] ?? 4370);
$io = $_POST['io_address'] ?? '';
$serial = $_POST['serial_number'] ?? '';
if ($id && $name && $ip) {
$stmt = db()->prepare("UPDATE hr_biometric_devices SET device_name = ?, ip_address = ?, port = ?, io_address = ?, serial_number = ? WHERE id = ?");
$stmt->execute([$name, $ip, $port, $io, $serial, $id]);
$message = "Device updated successfully!";
}
}
if (isset($_POST['delete_biometric_device'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM hr_biometric_devices WHERE id = ?");
$stmt->execute([$id]);
$message = "Device deleted successfully!";
}
}
if (isset($_POST['pull_biometric_data'])) {
$devices = db()->query("SELECT * FROM hr_biometric_devices WHERE status = 'active'")->fetchAll();
if (empty($devices)) {
$message = "No active biometric devices found to pull data from.";
} else {
// Simulation of pulling data from multiple devices
$employees = db()->query("SELECT id, biometric_id FROM hr_employees WHERE biometric_id IS NOT NULL")->fetchAll();
$pulled_count = 0;
$device_count = 0;
$date = date('Y-m-d');
foreach ($devices as $device) {
$device_pulled = 0;
foreach ($employees as $emp) {
// Randomly simulate logs for each employee for this device
if (rand(0, 1)) {
$check_in = $date . ' ' . str_pad((string)rand(7, 9), 2, '0', STR_PAD_LEFT) . ':' . str_pad((string)rand(0, 59), 2, '0', STR_PAD_LEFT) . ':00';
$check_out = $date . ' ' . str_pad((string)rand(16, 18), 2, '0', STR_PAD_LEFT) . ':' . str_pad((string)rand(0, 59), 2, '0', STR_PAD_LEFT) . ':00';
// Log check-in
$stmt = db()->prepare("INSERT INTO hr_biometric_logs (biometric_id, device_id, employee_id, timestamp, type) VALUES (?, ?, ?, ?, 'check_in')");
$stmt->execute([$emp['biometric_id'], $device['id'], $emp['id'], $check_in]);
// Log check-out
$stmt = db()->prepare("INSERT INTO hr_biometric_logs (biometric_id, device_id, employee_id, timestamp, type) VALUES (?, ?, ?, ?, 'check_out')");
$stmt->execute([$emp['biometric_id'], $device['id'], $emp['id'], $check_out]);
$device_pulled += 2;
$pulled_count += 2;
$in_time = date('H:i:s', strtotime($check_in));
$out_time = date('H:i:s', strtotime($check_out));
// Update attendance record (earliest in, latest out)
$stmt = db()->prepare("INSERT INTO hr_attendance (employee_id, attendance_date, status, clock_in, clock_out)
VALUES (?, ?, 'present', ?, ?)
ON DUPLICATE KEY UPDATE status = 'present',
clock_in = IF(clock_in IS NULL OR ? < clock_in, ?, clock_in),
clock_out = IF(clock_out IS NULL OR ? > clock_out, ?, clock_out)");
$stmt->execute([$emp['id'], $date, $in_time, $out_time, $in_time, $in_time, $out_time, $out_time]);
}
}
db()->prepare("UPDATE hr_biometric_devices SET last_sync = CURRENT_TIMESTAMP WHERE id = ?")->execute([$device['id']]);
$device_count++;
}
$message = "Successfully synced $device_count devices and pulled $pulled_count records.";
}
}
if (isset($_POST['test_device_connection'])) {
$id = (int)$_POST['id'];
$device = db()->prepare("SELECT * FROM hr_biometric_devices WHERE id = ?");
$device->execute([$id]);
$d = $device->fetch();
if ($d) {
// Simulated connection check
$message = "Connection to device '{$d['device_name']}' ({$d['ip_address']}) was successful! (Simulated)";
}
}
}
// Routing & Data Fetching
$page = $_GET['page'] ?? 'dashboard';
$data = [];
if ($page === 'export') {
$type = $_GET['type'] ?? 'sales';
$filename = $type . "_export_" . date('Y-m-d') . ".csv";
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=' . $filename);
$output = fopen('php://output', 'w');
// Add UTF-8 BOM for Excel
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
if ($type === 'sales' || $type === 'purchases') {
$invType = ($type === 'sales') ? 'sale' : 'purchase';
$where = ["v.type = ?"];
$params = [$invType];
if (!empty($_GET['search'])) { $where[] = "(v.id LIKE ? OR c.name LIKE ?)"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; }
if (!empty($_GET['customer_id'])) { $where[] = "v.customer_id = ?"; $params[] = $_GET['customer_id']; }
if (!empty($_GET['start_date'])) { $where[] = "v.invoice_date >= ?"; $params[] = $_GET['start_date']; }
if (!empty($_GET['end_date'])) { $where[] = "v.invoice_date <= ?"; $params[] = $_GET['end_date']; }
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT v.id, c.name as customer_name, v.invoice_date, v.payment_type, v.status, v.total_with_vat, v.paid_amount, (v.total_with_vat - v.paid_amount) as balance
FROM invoices v LEFT JOIN customers c ON v.customer_id = c.id
WHERE $whereSql ORDER BY v.id DESC");
$stmt->execute($params);
fputcsv($output, ['Invoice ID', 'Customer/Supplier', 'Date', 'Payment', 'Status', 'Total', 'Paid', 'Balance']);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) fputcsv($output, $row);
} elseif ($type === 'customers' || $type === 'suppliers') {
$custType = ($type === 'suppliers') ? 'supplier' : 'customer';
$where = ["type = ?"];
$params = [$custType];
if (!empty($_GET['search'])) { $where[] = "(name LIKE ? OR email LIKE ? OR phone LIKE ? OR tax_id LIKE ?)"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; }
if (!empty($_GET['start_date'])) { $where[] = "DATE(created_at) >= ?"; $params[] = $_GET['start_date']; }
if (!empty($_GET['end_date'])) { $where[] = "DATE(created_at) <= ?"; $params[] = $_GET['end_date']; }
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT id, name, email, phone, tax_id, balance, created_at FROM customers WHERE $whereSql ORDER BY id DESC");
$stmt->execute($params);
fputcsv($output, ['ID', 'Name', 'Email', 'Phone', 'Tax ID', 'Balance', 'Created At']);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) fputcsv($output, $row);
} elseif ($type === 'items') {
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) { $where[] = "(i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?)"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; }
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT i.sku, i.name_en, i.name_ar, c.name_en as category, i.purchase_price, i.sale_price, i.stock_quantity, i.vat_rate
FROM stock_items i LEFT JOIN stock_categories c ON i.category_id = c.id
WHERE $whereSql ORDER BY i.id DESC");
$stmt->execute($params);
fputcsv($output, ['SKU', 'Name (EN)', 'Name (AR)', 'Category', 'Purchase Price', 'Sale Price', 'Quantity', 'VAT %']);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) fputcsv($output, $row);
}
fclose($output);
exit;
}
// Global data for modals
$data['categories'] = db()->query("SELECT * FROM stock_categories ORDER BY name_en ASC")->fetchAll();
$data['units'] = db()->query("SELECT * FROM stock_units ORDER BY name_en ASC")->fetchAll();
$data['suppliers'] = db()->query("SELECT * FROM customers WHERE type = 'supplier' ORDER BY name ASC")->fetchAll();
$data['accounts'] = db()->query("SELECT * FROM acc_accounts ORDER BY code ASC")->fetchAll();
$data['customers_list'] = db()->query("SELECT * FROM customers WHERE type = 'customer' ORDER BY name ASC")->fetchAll();
$customers = $data['customers_list']; // For backward compatibility in some modals
$settings_raw = db()->query("SELECT * FROM settings")->fetchAll();
$data['settings'] = [];
foreach ($settings_raw as $s) {
$data['settings'][$s['key']] = $s['value'];
}
switch ($page) {
case 'suppliers':
case 'customers':
$type = ($page === 'suppliers') ? 'supplier' : 'customer';
$where = ["type = ?"];
$params = [$type];
if (!empty($_GET['search'])) {
$where[] = "(name LIKE ? OR email LIKE ? OR phone LIKE ? OR tax_id LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
if (!empty($_GET['start_date'])) {
$where[] = "DATE(created_at) >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "DATE(created_at) <= ?";
$params[] = $_GET['end_date'];
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT * FROM customers WHERE $whereSql ORDER BY id DESC");
$stmt->execute($params);
$data['customers'] = $stmt->fetchAll();
break;
case 'categories':
// Already fetched globally
break;
case 'units':
// Already fetched globally
break;
case 'items':
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) {
$where[] = "(i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT i.*, 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
LEFT JOIN stock_categories c ON i.category_id = c.id
LEFT JOIN stock_units u ON i.unit_id = u.id
LEFT JOIN customers s ON i.supplier_id = s.id
WHERE $whereSql
ORDER BY i.id DESC");
$stmt->execute($params);
$data['items'] = $stmt->fetchAll();
break;
case 'quotations':
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) {
$where[] = "(q.id LIKE ? OR c.name LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
if (!empty($_GET['customer_id'])) {
$where[] = "q.customer_id = ?";
$params[] = $_GET['customer_id'];
}
if (!empty($_GET['start_date'])) {
$where[] = "q.quotation_date >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "q.quotation_date <= ?";
$params[] = $_GET['end_date'];
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT q.*, c.name as customer_name
FROM quotations q
LEFT JOIN customers c ON q.customer_id = c.id
WHERE $whereSql
ORDER BY q.id DESC");
$stmt->execute($params);
$data['quotations'] = $stmt->fetchAll();
$data['items_list'] = db()->query("SELECT id, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate FROM stock_items ORDER BY name_en ASC")->fetchAll();
$data['customers_list'] = db()->query("SELECT id, name FROM customers WHERE type = 'customer' ORDER BY name ASC")->fetchAll();
break;
case 'payment_methods':
$data['payment_methods'] = db()->query("SELECT * FROM payment_methods ORDER BY id DESC")->fetchAll();
break;
case 'settings':
// Already fetched globally
break;
case 'sales':
case 'purchases':
$type = ($page === 'sales') ? 'sale' : 'purchase';
$where = ["v.type = ?"];
$params = [$type];
if (!empty($_GET['search'])) {
$where[] = "(v.id LIKE ? OR c.name LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
if (!empty($_GET['customer_id'])) {
$where[] = "v.customer_id = ?";
$params[] = $_GET['customer_id'];
}
if (!empty($_GET['start_date'])) {
$where[] = "v.invoice_date >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "v.invoice_date <= ?";
$params[] = $_GET['end_date'];
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT v.*, c.name as customer_name, c.tax_id as customer_tax_id, c.phone as customer_phone
FROM invoices v
LEFT JOIN customers c ON v.customer_id = c.id
WHERE $whereSql
ORDER BY v.id DESC");
$stmt->execute($params);
$data['invoices'] = $stmt->fetchAll();
foreach ($data['invoices'] as &$inv) {
$inv['total_in_words'] = numberToWordsOMR($inv['total_with_vat']);
}
unset($inv);
$data['items_list'] = db()->query("SELECT id, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate FROM stock_items ORDER BY name_en ASC")->fetchAll();
$data['customers_list'] = db()->query("SELECT id, name FROM customers WHERE type = '" . ($type === 'sale' ? 'customer' : 'supplier') . "' ORDER BY name ASC")->fetchAll();
break;
case 'sales_returns':
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) {
$where[] = "(sr.id LIKE ? OR c.name LIKE ? OR sr.invoice_id LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT sr.*, c.name as customer_name, i.total_with_vat as invoice_total
FROM sales_returns sr
LEFT JOIN customers c ON sr.customer_id = c.id
LEFT JOIN invoices i ON sr.invoice_id = i.id
WHERE $whereSql
ORDER BY sr.id DESC");
$stmt->execute($params);
$data['returns'] = $stmt->fetchAll();
$data['sales_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM invoices WHERE type = 'sale' ORDER BY id DESC")->fetchAll();
break;
case 'purchase_returns':
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) {
$where[] = "(pr.id LIKE ? OR c.name LIKE ? OR pr.invoice_id LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT pr.*, c.name as supplier_name, i.total_with_vat as invoice_total
FROM purchase_returns pr
LEFT JOIN customers c ON pr.supplier_id = c.id
LEFT JOIN invoices i ON pr.invoice_id = i.id
WHERE $whereSql
ORDER BY pr.id DESC");
$stmt->execute($params);
$data['returns'] = $stmt->fetchAll();
$data['purchase_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM invoices WHERE type = 'purchase' ORDER BY id DESC")->fetchAll();
break;
case 'customer_statement':
case 'supplier_statement':
$type = ($page === 'customer_statement') ? 'customer' : 'supplier';
$invType = ($type === 'customer') ? 'sale' : 'purchase';
$data['entities'] = db()->query("SELECT id, name, balance FROM customers WHERE type = '$type' ORDER BY name ASC")->fetchAll();
$entity_id = (int)($_GET['entity_id'] ?? 0);
if ($entity_id) {
$data['selected_entity'] = db()->query("SELECT * FROM customers WHERE id = $entity_id")->fetch();
$start_date = $_GET['start_date'] ?? date('Y-m-01');
$end_date = $_GET['end_date'] ?? date('Y-m-d');
// Fetch Opening Balance (Balance before start_date)
// This is complex as we don't have a ledger table.
// We can calculate it: Initial Balance + Invoices(before start_date) - Payments(before start_date)
// But for now, let's just show all transactions if no date filter, or just transactions in range.
$stmt = db()->prepare("SELECT 'invoice' as trans_type, id, invoice_date as trans_date, total_with_vat as amount, status, id as ref_no
FROM invoices
WHERE customer_id = ? AND type = ? AND invoice_date BETWEEN ? AND ?");
$stmt->execute([$entity_id, $invType, $start_date, $end_date]);
$invoices = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt = db()->prepare("SELECT 'payment' as trans_type, p.id, p.payment_date as trans_date, p.amount, p.payment_method, p.invoice_id as ref_no
FROM payments p
JOIN invoices i ON p.invoice_id = i.id
WHERE i.customer_id = ? AND i.type = ? AND p.payment_date BETWEEN ? AND ?");
$stmt->execute([$entity_id, $invType, $start_date, $end_date]);
$payments = $stmt->fetchAll(PDO::FETCH_ASSOC);
$transactions = array_merge($invoices, $payments);
usort($transactions, function($a, $b) {
return strtotime($a['trans_date']) <=> strtotime($b['trans_date']);
});
$data['transactions'] = $transactions;
}
break;
case 'expense_categories':
$data['expense_categories'] = db()->query("SELECT * FROM expense_categories ORDER BY name_en ASC")->fetchAll();
break;
case 'expenses':
$where = ["1=1"];
$params = [];
if (!empty($_GET['category_id'])) {
$where[] = "e.category_id = ?";
$params[] = $_GET['category_id'];
}
if (!empty($_GET['start_date'])) {
$where[] = "e.expense_date >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "e.expense_date <= ?";
$params[] = $_GET['end_date'];
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT e.*, c.name_en as cat_en, c.name_ar as cat_ar
FROM expenses e
LEFT JOIN expense_categories c ON e.category_id = c.id
WHERE $whereSql
ORDER BY e.expense_date DESC, e.id DESC");
$stmt->execute($params);
$data['expenses'] = $stmt->fetchAll();
$data['expense_categories'] = db()->query("SELECT * FROM expense_categories ORDER BY name_en ASC")->fetchAll();
break;
case 'accounting':
$data['journal_entries'] = db()->query("SELECT je.*,
(SELECT SUM(debit) FROM acc_ledger WHERE journal_entry_id = je.id) as total_debit
FROM acc_journal_entries je
ORDER BY je.entry_date DESC, je.id DESC LIMIT 100")->fetchAll();
$data['accounts'] = db()->query("SELECT * FROM acc_accounts ORDER BY code ASC")->fetchAll();
if (isset($_GET['action']) && $_GET['action'] === 'get_entry_details') {
header('Content-Type: application/json');
$id = (int)$_GET['id'];
$stmt = db()->prepare("SELECT l.*, a.name_en, a.code FROM acc_ledger l JOIN acc_accounts a ON l.account_id = a.id WHERE l.journal_entry_id = ?");
$stmt->execute([$id]);
echo json_encode($stmt->fetchAll());
exit;
}
if (isset($_GET['view']) && $_GET['view'] === 'trial_balance') {
$data['trial_balance'] = db()->query("SELECT a.code, a.name_en, SUM(l.debit) as total_debit, SUM(l.credit) as total_credit
FROM acc_accounts a
LEFT JOIN acc_ledger l ON a.id = l.account_id
GROUP BY a.id
HAVING total_debit > 0 OR total_credit > 0
ORDER BY a.code ASC")->fetchAll();
}
if (isset($_GET['view']) && $_GET['view'] === 'profit_loss') {
$data['revenue_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'revenue' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll();
$data['expense_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'expense' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll();
}
if (isset($_GET['view']) && $_GET['view'] === 'balance_sheet') {
$data['asset_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'asset' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll();
$data['liability_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'liability' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll();
$data['equity_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'equity' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll();
}
if (isset($_GET['view']) && $_GET['view'] === 'vat_report') {
$start = $_GET['start_date'] ?? date('Y-m-01');
$end = $_GET['end_date'] ?? date('Y-m-d');
$data['vat_report'] = getVatReport($start, $end);
$data['start_date'] = $start;
$data['end_date'] = $end;
}
if (isset($_GET['view']) && $_GET['view'] === 'coa') {
$data['coa'] = db()->query("SELECT a.*, p.name_en as parent_name
FROM acc_accounts a
LEFT JOIN acc_accounts p ON a.parent_id = p.id
ORDER BY a.code ASC")->fetchAll();
}
break;
case 'expense_report':
$start_date = $_GET['start_date'] ?? date('Y-m-01');
$end_date = $_GET['end_date'] ?? date('Y-m-d');
$stmt = db()->prepare("SELECT c.name_en, c.name_ar, SUM(e.amount) as total
FROM expenses e
JOIN expense_categories c ON e.category_id = c.id
WHERE e.expense_date BETWEEN ? AND ?
GROUP BY c.id
ORDER BY total DESC");
$stmt->execute([$start_date, $end_date]);
$data['report_by_category'] = $stmt->fetchAll();
$stmt = db()->prepare("SELECT SUM(amount) FROM expenses WHERE expense_date BETWEEN ? AND ?");
$stmt->execute([$start_date, $end_date]);
$data['total_expenses'] = $stmt->fetchColumn() ?: 0;
break;
case 'expiry_report':
$where = ["expiry_date IS NOT NULL"];
$params = [];
$filter = $_GET['filter'] ?? 'all';
if ($filter === 'expired') {
$where[] = "expiry_date <= CURDATE()";
} elseif ($filter === 'near_expiry') {
$where[] = "expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)";
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT i.*, c.name_en as cat_en, c.name_ar as cat_ar
FROM stock_items i
LEFT JOIN stock_categories c ON i.category_id = c.id
WHERE $whereSql
ORDER BY i.expiry_date ASC");
$stmt->execute($params);
$data['expiry_items'] = $stmt->fetchAll();
break;
case 'low_stock_report':
$stmt = db()->prepare("SELECT i.*, c.name_en as cat_en, c.name_ar as cat_ar, s.name as supplier_name
FROM stock_items i
LEFT JOIN stock_categories c ON i.category_id = c.id
LEFT JOIN customers s ON i.supplier_id = s.id AND s.type = 'supplier'
WHERE i.stock_quantity <= i.min_stock_level
ORDER BY (i.min_stock_level - i.stock_quantity) DESC");
$stmt->execute();
$data['low_stock_items'] = $stmt->fetchAll();
break;
case 'cashflow_report':
$start_date = $_GET['start_date'] ?? date('Y-m-01');
$end_date = $_GET['end_date'] ?? date('Y-m-d');
// Fetch Cash & Bank Account IDs
$cash_accounts = db()->query("SELECT id FROM acc_accounts WHERE code IN (1100, 1200)")->fetchAll(PDO::FETCH_COLUMN);
$cash_ids_str = implode(',', $cash_accounts);
if (!empty($cash_ids_str)) {
// Opening Balance
$stmt = db()->prepare("SELECT SUM(debit - credit) FROM acc_ledger l JOIN acc_journal_entries je ON l.journal_entry_id = je.id WHERE l.account_id IN ($cash_ids_str) AND je.entry_date < ?");
$stmt->execute([$start_date]);
$data['opening_balance'] = $stmt->fetchColumn() ?: 0;
// Transactions in range
$stmt = db()->prepare("SELECT
je.entry_date,
je.description,
l.debit as inflow,
l.credit as outflow,
a.name_en as other_account,
a.type as other_type
FROM acc_ledger l
JOIN acc_journal_entries je ON l.journal_entry_id = je.id
LEFT JOIN acc_ledger l2 ON l2.journal_entry_id = je.id AND l2.id != l.id
LEFT JOIN acc_accounts a ON l2.account_id = a.id
WHERE l.account_id IN ($cash_ids_str)
AND je.entry_date BETWEEN ? AND ?
ORDER BY je.entry_date ASC, je.id ASC");
$stmt->execute([$start_date, $end_date]);
$data['cash_transactions'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
$data['opening_balance'] = 0;
$data['cash_transactions'] = [];
}
break;
case 'hr_departments':
$data['departments'] = db()->query("SELECT * FROM hr_departments ORDER BY id DESC")->fetchAll();
break;
case 'hr_employees':
$data['employees'] = db()->query("SELECT e.*, d.name as dept_name FROM hr_employees e LEFT JOIN hr_departments d ON e.department_id = d.id ORDER BY e.id DESC")->fetchAll();
$data['departments'] = db()->query("SELECT * FROM hr_departments ORDER BY name ASC")->fetchAll();
break;
case 'hr_attendance':
$date = $_GET['date'] ?? date('Y-m-d');
$data['attendance_date'] = $date;
$data['employees'] = db()->query("SELECT e.id, e.name, d.name as dept_name, a.status, a.clock_in, a.clock_out
FROM hr_employees e
LEFT JOIN hr_departments d ON e.department_id = d.id
LEFT JOIN hr_attendance a ON e.id = a.employee_id AND a.attendance_date = '$date'
WHERE e.status = 'active' ORDER BY e.name ASC")->fetchAll();
break;
case 'hr_payroll':
$month = (int)($_GET['month'] ?? date('m'));
$year = (int)($_GET['year'] ?? date('Y'));
$data['month'] = $month;
$data['year'] = $year;
$data['payroll'] = db()->query("SELECT p.*, e.name as emp_name FROM hr_payroll p JOIN hr_employees e ON p.employee_id = e.id WHERE p.payroll_month = $month AND p.payroll_year = $year ORDER BY p.id DESC")->fetchAll();
$data['employees'] = db()->query("SELECT id, name, salary FROM hr_employees WHERE status = 'active' ORDER BY name ASC")->fetchAll();
break;
case 'devices':
$data['devices'] = db()->query("SELECT * FROM hr_biometric_devices ORDER BY id DESC")->fetchAll();
break;
default:
$data['customers'] = db()->query("SELECT * FROM customers WHERE type = 'customer' ORDER BY id DESC LIMIT 5")->fetchAll();
// Dashboard stats
$data['stats'] = [
'total_customers' => db()->query("SELECT COUNT(*) FROM customers WHERE type = 'customer'")->fetchColumn(),
'total_items' => db()->query("SELECT COUNT(*) FROM stock_items")->fetchColumn(),
'total_sales' => db()->query("SELECT SUM(total_with_vat) FROM invoices WHERE type = 'sale'")->fetchColumn() ?: 0,
'total_received' => db()->query("SELECT SUM(amount) FROM payments p JOIN invoices i ON p.invoice_id = i.id WHERE i.type = 'sale'")->fetchColumn() ?: 0,
'total_purchases' => db()->query("SELECT SUM(total_with_vat) FROM invoices WHERE type = 'purchase'")->fetchColumn() ?: 0,
'total_paid' => db()->query("SELECT SUM(amount) FROM payments p JOIN invoices i ON p.invoice_id = i.id WHERE i.type = 'purchase'")->fetchColumn() ?: 0,
'expired_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE()")->fetchColumn(),
'near_expiry_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)")->fetchColumn(),
'low_stock_items_count' => db()->query("SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level")->fetchColumn(),
];
$data['stats']['total_receivable'] = $data['stats']['total_sales'] - $data['stats']['total_received'];
$data['stats']['total_payable'] = $data['stats']['total_purchases'] - $data['stats']['total_paid'];
// Sales Chart Data
$data['monthly_sales'] = db()->query("SELECT DATE_FORMAT(invoice_date, '%M %Y') as label, SUM(total_with_vat) as total FROM invoices WHERE type = 'sale' GROUP BY DATE_FORMAT(invoice_date, '%Y-%m') ORDER BY invoice_date ASC LIMIT 12")->fetchAll(PDO::FETCH_ASSOC);
$data['yearly_sales'] = db()->query("SELECT YEAR(invoice_date) as label, SUM(total_with_vat) as total FROM invoices WHERE type = 'sale' GROUP BY label ORDER BY label ASC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
break;
}
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
?>
Accounting Admin
['en' => 'Dashboard', 'ar' => 'لوحة القيادة'],
'pos' => ['en' => 'Point of Sale', 'ar' => 'نقطة البيع'],
'quotations' => ['en' => 'Quotations', 'ar' => 'العروض'],
'customers' => ['en' => 'Customers', 'ar' => 'العملاء'],
'suppliers' => ['en' => 'Suppliers', 'ar' => 'الموردون'],
'categories' => ['en' => 'Stock Categories', 'ar' => 'فئات المخزون'],
'units' => ['en' => 'Stock Units', 'ar' => 'وحدات المخزون'],
'items' => ['en' => 'Stock Items', 'ar' => 'أصناف المخزون'],
'payment_methods' => ['en' => 'Payment Methods', 'ar' => 'طرق الدفع'],
'sales' => ['en' => 'Sales Tax Invoices', 'ar' => 'فواتير المبيعات الضريبية'],
'purchases' => ['en' => 'Purchase Tax Invoices', 'ar' => 'فواتير المشتريات الضريبية'],
'sales_returns' => ['en' => 'Sales Returns', 'ar' => 'مرتجع المبيعات'],
'customer_statement' => ['en' => 'Customer Statement', 'ar' => 'كشف حساب عميل'],
'supplier_statement' => ['en' => 'Supplier Statement', 'ar' => 'كشف حساب مورد'],
'expiry_report' => ['en' => 'Expiry Report', 'ar' => 'تقرير انتهاء الصلاحية'],
'low_stock_report' => ['en' => 'Low Stock Report', 'ar' => 'تقرير نواقص المخزون'],
'settings' => ['en' => 'Company Profile', 'ar' => 'ملف الشركة'],
'devices' => ['en' => 'Biometric Devices', 'ar' => 'أجهزة البصمة'],
'hr_departments' => ['en' => 'HR Departments', 'ar' => 'أقسام الموارد البشرية'],
'hr_employees' => ['en' => 'HR Employees', 'ar' => 'موظفي الموارد البشرية'],
'hr_attendance' => ['en' => 'HR Attendance', 'ar' => 'حضور الموارد البشرية'],
'hr_payroll' => ['en' => 'HR Payroll', 'ar' => 'رواتب الموارد البشرية'],
'cashflow_report' => ['en' => 'Cashflow Statement', 'ar' => 'قائمة التدفقات النقدية'],
];
$currTitle = $titles[$page] ?? $titles['dashboard'];
?>
= $currTitle['en'] ?>
0 || $data['stats']['near_expiry_items'] > 0 || $data['stats']['low_stock_items_count'] > 0): ?>
Inventory Alert:
0): ?>
= $data['stats']['expired_items'] ?> items have expired.
0): ?>
= $data['stats']['near_expiry_items'] ?> items are expiring soon (within 30 days).
0): ?>
= $data['stats']['low_stock_items_count'] ?> items are below minimum level.
Total Sales
OMR = number_format((float)($data['stats']['total_sales'] ?? 0), 3) ?>
Total Received
OMR = number_format((float)($data['stats']['total_received'] ?? 0), 3) ?>
Customer Due
OMR = number_format((float)($data['stats']['total_receivable'] ?? 0), 3) ?>
Total Purchases
OMR = number_format((float)($data['stats']['total_purchases'] ?? 0), 3) ?>
Total Paid
OMR = number_format((float)($data['stats']['total_paid'] ?? 0), 3) ?>
Supplier Due
OMR = number_format((float)($data['stats']['total_payable'] ?? 0), 3) ?>
Total Customers
= (int)($data['stats']['total_customers'] ?? 0) ?>
Total Items
= (int)($data['stats']['total_items'] ?? 0) ?>
Sales Performance
Monthly
Yearly
Recent Customers
View All
Name
Phone
Balance
= htmlspecialchars($c['name']) ?>
= htmlspecialchars($c['phone']) ?>
OMR = number_format((float)$c['balance'], 3) ?>
= $currTitle['en'] ?> Management
Import Excel
Add = $currTitle['en'] ?>
Name
Tax ID
Email
Phone
Balance
Actions
= htmlspecialchars($c['name']) ?>
= htmlspecialchars($c['tax_id'] ?? '---') ?>
= htmlspecialchars($c['email']) ?>
= htmlspecialchars($c['phone']) ?>
OMR = number_format((float)$c['balance'], 3) ?>
Stock Categories
Import Excel
Add Category
ID
Name (EN)
Name (AR)
= $cat['id'] ?>
= htmlspecialchars($cat['name_en']) ?>
= htmlspecialchars($cat['name_ar']) ?>
Stock Units
Import Excel
Add Unit
Name (EN)
Short (EN)
Name (AR)
Short (AR)
= htmlspecialchars($u['name_en']) ?>
= htmlspecialchars($u['short_name_en']) ?>
= htmlspecialchars($u['name_ar']) ?>
= htmlspecialchars($u['short_name_ar']) ?>
Stock Items
Avery Labels
Import Excel
Add Item
SKU
Item Name
Category
Stock Level
Expiry Date
Status
No items found.
= htmlspecialchars($item['sku']) ?>
= htmlspecialchars($item['name_en']) ?>
= htmlspecialchars($item['name_ar']) ?>
= htmlspecialchars($item['cat_en'] ?? '---') ?>
= number_format((float)$item['stock_quantity'], 3) ?>
= htmlspecialchars($item['expiry_date']) ?>
Expired
Near Expiry
Good
Low Stock Report
Print
SKU
Item Name
Category
Supplier
Min Level
Current Stock
Shortage
All items are above minimum levels.
= htmlspecialchars($item['sku']) ?>
= htmlspecialchars($item['name_en']) ?>
= htmlspecialchars($item['name_ar']) ?>
= htmlspecialchars($item['cat_en'] ?? '---') ?>
= htmlspecialchars($item['supplier_name'] ?? '---') ?>
= number_format((float)$item['min_stock_level'], 2) ?>
= number_format((float)$item['stock_quantity'], 3) ?>
= number_format($shortage, 3) ?>
query("SELECT * FROM stock_items WHERE stock_quantity > 0 ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
$customers = db()->query("SELECT * FROM customers WHERE type = 'customer' ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC);
?>
= htmlspecialchars($p['name_en']) ?>
= htmlspecialchars($p['sku']) ?>
OMR = number_format((float)$p['sale_price'], 3) ?>
= (float)$p['stock_quantity'] ?> left
Customer
Walk-in Customer
= htmlspecialchars($c['name']) ?>
Subtotal
OMR 0.000
Total
OMR 0.000
PLACE ORDER
Quotations
Create New Quotation
Quotation #
Date
Valid Until
Customer
Status
Total
Actions
prepare("SELECT qi.*, i.name_en, i.name_ar, i.vat_rate
FROM quotation_items qi
JOIN stock_items i ON qi.item_id = i.id
WHERE qi.quotation_id = ?");
$items->execute([$q['id']]);
$q['items'] = $items->fetchAll(PDO::FETCH_ASSOC);
?>
QUO-= str_pad((string)$q['id'], 5, '0', STR_PAD_LEFT) ?>
= $q['quotation_date'] ?>
= $q['valid_until'] ?: '---' ?>
= htmlspecialchars($q['customer_name'] ?? '---') ?>
= htmlspecialchars($q['status']) ?>
OMR = number_format((float)$q['total_with_vat'], 3) ?>
No quotations found
= $currTitle['en'] ?>
Create New Tax Invoice
Invoice #
Date
= $page === 'sales' ? 'Customer' : 'Supplier' ?>
Status
Total
Paid
Balance
Actions
prepare("SELECT ii.*, i.name_en, i.name_ar, i.vat_rate
FROM invoice_items ii
JOIN stock_items i ON ii.item_id = i.id
WHERE ii.invoice_id = ?");
$items->execute([$inv['id']]);
$inv['items'] = $items->fetchAll(PDO::FETCH_ASSOC);
?>
INV-= str_pad((string)$inv['id'], 5, '0', STR_PAD_LEFT) ?>
= $inv['invoice_date'] ?>
= htmlspecialchars($inv['customer_name'] ?? '---') ?>
= htmlspecialchars(str_replace('_', ' ', $inv['status'])) ?>
OMR = number_format((float)$inv['total_with_vat'], 3) ?>
OMR = number_format((float)$inv['paid_amount'], 3) ?>
OMR = number_format((float)($inv['total_with_vat'] - $inv['paid_amount']), 3) ?>
= $currTitle['en'] ?>
Print
= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>
= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?>
Statement of Account
= htmlspecialchars($data['selected_entity']['name']) ?>
= htmlspecialchars($data['selected_entity']['email']) ?> | = htmlspecialchars($data['selected_entity']['phone']) ?> Period: = $_GET['start_date'] ?> to = $_GET['end_date'] ?>
Date
Reference
Description
Debit
Credit
Balance
= $t['trans_date'] ?>
= $t['trans_type'] === 'invoice' ? 'INV-'.str_pad((string)$t['ref_no'], 5, '0', STR_PAD_LEFT) : 'RCP-'.str_pad((string)$t['id'], 5, '0', STR_PAD_LEFT) ?>
= $t['trans_type'] === 'invoice' ? 'Tax Invoice' : 'Payment - '.$t['payment_method'] ?>
= $debit > 0 ? number_format($debit, 3) : '' ?>
= $credit > 0 ? number_format($credit, 3) : '' ?>
= number_format($running_balance, 3) ?>
Closing Balance
OMR = number_format($running_balance, 3) ?>
Please select an entity and date range to generate the statement.
Cashflow Statement
Print
= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>
= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?>
Cashflow Statement
Period: = htmlspecialchars($_GET['start_date'] ?? date('Y-m-01')) ?> to = htmlspecialchars($_GET['end_date'] ?? date('Y-m-d')) ?>
Description
Amount (OMR)
Opening Cash Balance
= number_format($data['opening_balance'], 3) ?>
Operating Activities
0) $op_inflow += $amt; else $op_outflow += abs($amt);
} elseif ($t['other_type'] === 'asset' && !in_array($t['other_account'], ['Accounts Receivable', 'Inventory'])) {
// Fixed assets etc
if ($amt > 0) $inv_inflow += $amt; else $inv_outflow += abs($amt);
} elseif ($t['other_type'] === 'equity' || $t['other_type'] === 'liability') {
if ($amt > 0) $fin_inflow += $amt; else $fin_outflow += abs($amt);
} else {
// Default to operating if unsure
if ($amt > 0) $op_inflow += $amt; else $op_outflow += abs($amt);
}
}
?>
Cash Received from Customers & Others
= number_format($op_inflow, 3) ?>
Cash Paid to Suppliers & Expenses
(= number_format($op_outflow, 3) ?>)
Net Cash from Operating Activities
= number_format($op_inflow - $op_outflow, 3) ?>
Investing Activities
Net Cash from Investing Activities
= number_format($inv_inflow - $inv_outflow, 3) ?>
Financing Activities
Net Cash from Financing Activities
= number_format($fin_inflow - $fin_outflow, 3) ?>
Net Change in Cash
= number_format($net_change, 3) ?>
Closing Cash Balance
= number_format($data['opening_balance'] + $net_change, 3) ?>
___________________ Prepared By
___________________ Approved By
Payment Methods
Add Payment Method
ID
Name (EN)
Name (AR)
Actions
= $pm['id'] ?>
= htmlspecialchars($pm['name_en'] ?? '') ?>
= htmlspecialchars($pm['name_ar'] ?? '') ?>
Expense Categories
Add Category
ID
Name (EN)
Name (AR)
Actions
= $cat['id'] ?>
= htmlspecialchars($cat['name_en']) ?>
= htmlspecialchars($cat['name_ar']) ?>
= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>
= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?>
= isset($_GET['view']) ? ucwords(str_replace('_', ' ', $_GET['view'])) : 'Journal' ?>
Date
Description
Reference
Amount
Action
= $entry['entry_date'] ?>
= htmlspecialchars($entry['description']) ?>
= htmlspecialchars($entry['reference']) ?>
= number_format((float)$entry['total_debit'], 3) ?>
View
Add Account
Code
Name
Type
Parent
Balance
= $acc['code'] ?>
= htmlspecialchars($acc['name_en']) ?>
= htmlspecialchars($acc['name_ar']) ?>
= $acc['type'] ?>
= htmlspecialchars($acc['parent_name'] ?? '---') ?>
= number_format(getAccountBalance($acc['code']), 3) ?>
VAT Summary Report
VAT Input (Purchases)
= number_format($data['vat_report']['input_vat'], 3) ?>
VAT Output (Sales)
= number_format($data['vat_report']['output_vat'], 3) ?>
Net VAT Payable / (Refundable)
= number_format($data['vat_report']['net_vat'], 3) ?>
This report calculates the difference between VAT collected on sales and VAT paid on purchases for the selected period.
Code
Account Name
Debit
Credit
= $row['code'] ?>
= htmlspecialchars($row['name_en']) ?>
= number_format((float)$row['total_debit'], 3) ?>
= number_format((float)$row['total_credit'], 3) ?>
Total
= number_format($total_d, 3) ?>
= number_format($total_c, 3) ?>
Profit & Loss Statement
Revenue
= htmlspecialchars($acc['name_en']) ?>
= number_format($bal, 3) ?>
Total Revenue
= number_format($total_rev, 3) ?>
Expenses
= htmlspecialchars($acc['name_en']) ?>
= number_format($bal, 3) ?>
Total Expenses
= number_format($total_exp, 3) ?>
Net Profit / Loss
= number_format($total_rev - $total_exp, 3) ?>
Balance Sheet
Assets
= htmlspecialchars($acc['name_en']) ?>
= number_format($bal, 3) ?>
Total Assets
= number_format($total_assets, 3) ?>
Liabilities & Equity
Liabilities
= htmlspecialchars($acc['name_en']) ?>
= number_format($bal, 3) ?>
Equity
= htmlspecialchars($acc['name_en']) ?>
= number_format($bal, 3) ?>
query("SELECT code FROM acc_accounts WHERE type='revenue' AND parent_id IS NOT NULL")->fetchAll() as $a) $rev += getAccountBalance($a['code']);
$exp = 0; foreach(db()->query("SELECT code FROM acc_accounts WHERE type='expense' AND parent_id IS NOT NULL")->fetchAll() as $a) $exp += getAccountBalance($a['code']);
$earnings = $rev - $exp;
$total_equity += $earnings;
?>
Retained Earnings (Current)
= number_format($earnings, 3) ?>
Total Liab. & Equity
= number_format($total_liab + $total_equity, 3) ?>
Expenses List
Add Expense
Date
Reference
Category
Description
Amount
Actions
= $exp['expense_date'] ?>
= htmlspecialchars($exp['reference_no'] ?: '---') ?>
= htmlspecialchars($exp['cat_en'] ?? 'Unknown') ?>
= htmlspecialchars($exp['description']) ?>
OMR = number_format((float)$exp['amount'], 3) ?>
Expense Report
Print
Total Expenses
OMR = number_format((float)$data['total_expenses'], 3) ?>
Period: = htmlspecialchars($_GET['start_date'] ?? date('Y-m-01')) ?> to = htmlspecialchars($_GET['end_date'] ?? date('Y-m-d')) ?>
Category
Total Amount
% of Total
No expenses found for this period.
0 ? ($row['total'] / $data['total_expenses'] * 100) : 0;
?>
= htmlspecialchars($row['name_en']) ?>
= htmlspecialchars($row['name_ar']) ?>
OMR = number_format((float)$row['total'], 3) ?>
= number_format($percent, 1) ?>%
Sales Returns
Create New Return
Return #
Date
Invoice #
Customer
Total Amount
Actions
RET-= str_pad((string)$ret['id'], 5, '0', STR_PAD_LEFT) ?>
= $ret['return_date'] ?>
INV-= str_pad((string)$ret['invoice_id'], 5, '0', STR_PAD_LEFT) ?>
= htmlspecialchars($ret['customer_name'] ?? 'Walk-in') ?>
OMR = number_format((float)$ret['total_amount'], 3) ?>
No returns found
Purchase Returns
Create New Return
Return #
Date
Invoice #
Supplier
Total Amount
Actions
PRET-= str_pad((string)$ret['id'], 5, '0', STR_PAD_LEFT) ?>
= $ret['return_date'] ?>
INV-= str_pad((string)$ret['invoice_id'], 5, '0', STR_PAD_LEFT) ?>
= htmlspecialchars($ret['supplier_name'] ?? 'Unknown') ?>
OMR = number_format((float)$ret['total_amount'], 3) ?>
No returns found
HR Departments
Add Department
ID
Department Name
Actions
= $d['id'] ?>
= htmlspecialchars($d['name']) ?>
HR Employees
Add Employee
Name
Biometric ID
Department
Position
Salary
Status
Actions
= htmlspecialchars($e['name']) ?>
= htmlspecialchars($e['email']) ?>
= htmlspecialchars($e['biometric_id'] ?? '---') ?>
= htmlspecialchars($e['dept_name'] ?? '---') ?>
= htmlspecialchars($e['position']) ?>
OMR = number_format($e['salary'], 3) ?>
= $e['status'] ?>
Employee
Department
Status
Clock In
Clock Out
Action
= htmlspecialchars($e['name']) ?>
= htmlspecialchars($e['dept_name'] ?? '---') ?>
= $e['status'] ?>
Not Marked
= $e['clock_in'] ?? '---' ?>
= $e['clock_out'] ?? '---' ?>
Mark
Status
>Present
>Absent
>On Leave
To sync attendance from your biometric device, use the following API endpoint:
= (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]" ?>/api/biometric_sync.php
Expected JSON format:
[
{
"biometric_id": "101",
"device_id": 1,
"timestamp": "2026-02-17 08:30:00",
"type": "in"
},
{
"biometric_id": "101",
"device_id": 1,
"timestamp": "2026-02-17 17:30:00",
"type": "out"
}
]
Note: Ensure Employee Biometric IDs match those in the device logs.
HR Payroll
>= date('F', mktime(0, 0, 0, $m, 1)) ?>
=date('Y')-2; $y--): ?>
>= $y ?>
Generate
Employee
Basic
Bonus
Deductions
Net Salary
Status
Actions
= htmlspecialchars($p['emp_name']) ?>
OMR = number_format($p['basic_salary'], 3) ?>
+ OMR = number_format($p['bonus'], 3) ?>
- OMR = number_format($p['deductions'], 3) ?>
OMR = number_format($p['net_salary'], 3) ?>
= $p['status'] ?>
Employee
--- Select ---
= htmlspecialchars($e['name']) ?> (Basic: = number_format($e['salary'], 3) ?>)
Biometric Devices
Add Device
Device Name
IP / IO Address
Port
Serial
Last Sync
Status
Actions
= htmlspecialchars($d['device_name']) ?>
IP: = htmlspecialchars($d['ip_address']) ?>
IO: = htmlspecialchars($d['io_address']) ?>
= $d['port'] ?>
= htmlspecialchars($d['serial_number'] ?? '---') ?>
= $d['last_sync'] ?? 'Never' ?>
= $d['status'] ?>
Returned Items
Item
Returned Qty
Unit Price
Total Price
Total Amount:
Items for Return
Item
Purchased Qty
Return Qty
Price
Total
Total Return Amount:
OMR 0.000
Notes / Reason for Return
Items for Return
Item
Sold Qty
Return Qty
Price
Total
Total Return Amount:
OMR 0.000
Notes / Reason for Return
Journal Details
Journal is not balanced! Difference: 0.000
Item Details
Qty
Unit Price
VAT
Total
Subtotal
OMR 0.000
Total VAT
OMR 0.000
Grand Total
OMR 0.000
Item Details
Qty
Unit Price
VAT
Total
Subtotal
OMR 0.000
Total VAT
OMR 0.000
Grand Total
OMR 0.000
Item Details
Qty
Unit Price
VAT
Total
Subtotal
OMR 0.000
Total VAT
OMR 0.000
Grand Total
OMR 0.000
Item Details
Qty
Unit Price
VAT
Total
Subtotal
OMR 0.000
Total VAT
OMR 0.000
Grand Total
OMR 0.000
Payment Details
Method:
Currency: OMR
Terms & Conditions
Goods once sold will not be taken back or exchanged.
Payment is due within the agreed credit period.
This is a computer-generated invoice and does not require a physical signature.
Subtotal
VAT Amount
Grand Total
Generated by = htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?> | Visit us at = $_SERVER['HTTP_HOST'] ?>
= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>
= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?>
Payment Receipt
Customer
Walk-in Customer
Select Credit Customer
--- Select Customer ---
= htmlspecialchars($c['name']) ?> (= htmlspecialchars($c['phone'] ?? '') ?>)
Total Tendered (Cash)
OMR 0.000
* Change is calculated based on cash payments only.
Label Layout
3 x 7 (21 Labels per sheet)
3 x 8 (24 Labels per sheet)
4 x 10 (40 Labels per sheet)
L7651 (5 x 13 - 65 Labels)
L4736 (2 x 7 - 14 Labels)
Copies (Set All)
Print A4 Sheet
Quantities per Item
Select items to adjust quantities.