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 ($_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]);
$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();
foreach ($items as $item) {
$qty = (float)$item['qty'];
$price = (float)$item['price'];
$subtotal = $qty * $price;
// 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 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']]);
}
$db->commit();
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();
$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['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]);
$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();
}
}
}
}
// 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();
$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 '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 '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;
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,
];
$data['stats']['total_receivable'] = $data['stats']['total_sales'] - $data['stats']['total_received'];
$data['stats']['total_payable'] = $data['stats']['total_purchases'] - $data['stats']['total_paid'];
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' => 'فواتير المشتريات الضريبية'],
'customer_statement' => ['en' => 'Customer Statement', 'ar' => 'كشف حساب عميل'],
'supplier_statement' => ['en' => 'Supplier Statement', 'ar' => 'كشف حساب مورد'],
'settings' => ['en' => 'Company Profile', 'ar' => 'ملف الشركة'],
];
$currTitle = $titles[$page] ?? $titles['dashboard'];
?>
= $currTitle['en'] ?>
Total Sales
OMR = number_format((float)($data['stats']['total_sales'] ?? 0), 3) ?>
Total Received
OMR = number_format((float)($data['stats']['total_received'] ?? 0), 3) ?>
Total Receivable
OMR = number_format((float)($data['stats']['total_receivable'] ?? 0), 3) ?>
Total Purchases
OMR = number_format((float)($data['stats']['total_purchases'] ?? 0), 3) ?>
Total Paid to Suppliers
OMR = number_format((float)($data['stats']['total_paid'] ?? 0), 3) ?>
Total Payable
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) ?>
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
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.
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']) ?>
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) ?>%
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.