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') {
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;
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'] ?>
= $message ?>
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'] ?? '') ?>
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 per Item
Print A4 Sheet