From e9db807320cd7a74f9b6e31e17d59bba76fa1860 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 20 Feb 2026 12:42:58 +0000 Subject: [PATCH] Autosave: 20260220-124258 --- assets/pasted-20260220-114014-3aea48e0.png | Bin 0 -> 35151 bytes check_schema.php | 14 + db/migrations/20260220_unify_pos_sales.php | 23 + db/migrations/20260220_unify_pos_sales.sql | 13 + db/migrations/fix_lpo_foreign_key.sql | 2 + describe_tables.php | 13 + includes/lang.php | 2 + index.php | 854 ++++++++++++++++++--- post_debug.log | 32 + search_debug.log | 10 + 10 files changed, 849 insertions(+), 114 deletions(-) create mode 100644 assets/pasted-20260220-114014-3aea48e0.png create mode 100644 check_schema.php create mode 100644 db/migrations/20260220_unify_pos_sales.php create mode 100644 db/migrations/20260220_unify_pos_sales.sql create mode 100644 db/migrations/fix_lpo_foreign_key.sql create mode 100644 describe_tables.php diff --git a/assets/pasted-20260220-114014-3aea48e0.png b/assets/pasted-20260220-114014-3aea48e0.png new file mode 100644 index 0000000000000000000000000000000000000000..e281797b3073e318ceebf597809429bf5eb1f118 GIT binary patch literal 35151 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 bhx!pSuRKWhUpC!Kjel&d94rryc3=Hpp~0#r literal 0 HcmV?d00001 diff --git a/check_schema.php b/check_schema.php new file mode 100644 index 0000000..fdcea91 --- /dev/null +++ b/check_schema.php @@ -0,0 +1,14 @@ +query("DESCRIBE $t"); + while ($r = $res->fetch(PDO::FETCH_ASSOC)) { + echo " {$r['Field']} ({$r['Type']})\n"; + } + } catch (Exception $e) { + echo " Error: " . $e->getMessage() . "\n"; + } +} diff --git a/db/migrations/20260220_unify_pos_sales.php b/db/migrations/20260220_unify_pos_sales.php new file mode 100644 index 0000000..56a8ec2 --- /dev/null +++ b/db/migrations/20260220_unify_pos_sales.php @@ -0,0 +1,23 @@ +exec($q); + echo "Executed: $q\n"; + } catch (Exception $e) { + echo "Error executing $q: " . $e->getMessage() . "\n"; + } +} diff --git a/db/migrations/20260220_unify_pos_sales.sql b/db/migrations/20260220_unify_pos_sales.sql new file mode 100644 index 0000000..ccd9e9b --- /dev/null +++ b/db/migrations/20260220_unify_pos_sales.sql @@ -0,0 +1,13 @@ +-- Unify POS and Sales Invoices +-- Date: 2026-02-20 + +ALTER TABLE invoices ADD COLUMN IF NOT EXISTS transaction_no VARCHAR(50) DEFAULT NULL AFTER id; +ALTER TABLE invoices ADD COLUMN IF NOT EXISTS is_pos TINYINT(1) DEFAULT 0; +ALTER TABLE invoices ADD COLUMN IF NOT EXISTS discount_amount DECIMAL(15,3) DEFAULT 0; +ALTER TABLE invoices ADD COLUMN IF NOT EXISTS loyalty_points_earned DECIMAL(15,3) DEFAULT 0; +ALTER TABLE invoices ADD COLUMN IF NOT EXISTS loyalty_points_redeemed DECIMAL(15,3) DEFAULT 0; +ALTER TABLE invoices ADD COLUMN IF NOT EXISTS created_by INT(11) DEFAULT NULL; + +ALTER TABLE payments ADD COLUMN IF NOT EXISTS transaction_id INT(11) DEFAULT NULL; + +ALTER TABLE invoices MODIFY COLUMN status ENUM('paid','unpaid','partially_paid','refunded','cancelled') DEFAULT 'unpaid'; diff --git a/db/migrations/fix_lpo_foreign_key.sql b/db/migrations/fix_lpo_foreign_key.sql new file mode 100644 index 0000000..89c5334 --- /dev/null +++ b/db/migrations/fix_lpo_foreign_key.sql @@ -0,0 +1,2 @@ +ALTER TABLE lpos DROP FOREIGN KEY lpos_ibfk_1; +ALTER TABLE lpos ADD CONSTRAINT lpos_ibfk_1 FOREIGN KEY (supplier_id) REFERENCES suppliers(id); diff --git a/describe_tables.php b/describe_tables.php new file mode 100644 index 0000000..e17c954 --- /dev/null +++ b/describe_tables.php @@ -0,0 +1,13 @@ +query("DESCRIBE $t"); + while($r = $res->fetch(PDO::FETCH_ASSOC)) { + echo " {$r['Field']} ({$r['Type']})\n"; + } + } catch (Exception $e) { + echo " Error: " . $e->getMessage() . "\n"; + } +} diff --git a/includes/lang.php b/includes/lang.php index 72e1d15..9c93b55 100644 --- a/includes/lang.php +++ b/includes/lang.php @@ -14,6 +14,7 @@ $translations = [ 'sales_returns' => 'Sales Returns', 'purchases' => 'Purchases', 'purchase_returns' => 'Purchase Returns', + 'lpos' => 'Local Purchase Orders (LPO)', 'quotations' => 'Quotations', 'expenses' => 'Expenses', 'expense_categories' => 'Expense Categories', @@ -107,6 +108,7 @@ $translations = [ 'sales_returns' => 'مرتجعات المبيعات', 'purchases' => 'المشتريات', 'purchase_returns' => 'مرتجعات المشتريات', + 'lpos' => 'أوامر الشراء المحلية (LPO)', 'quotations' => 'عروض الأسعار', 'expenses' => 'المصاريف', 'expense_categories' => 'فئات المصاريف', diff --git a/index.php b/index.php index 1786d2a..0e4b261 100644 --- a/index.php +++ b/index.php @@ -362,27 +362,34 @@ if (isset($_GET['action']) || isset($_POST['action'])) { } } - // Insert Transaction - $stmt = $db->prepare("INSERT INTO pos_transactions (transaction_no, customer_id, total_amount, tax_amount, discount_code_id, discount_amount, loyalty_points_redeemed, net_amount, register_session_id, created_by, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'completed')"); - $stmt->execute([$transaction_no, $customer_id, $total_amount, $tax_amount, $discount_code_id, $discount_amount, $loyalty_redeemed, $net_amount, $session_id, $_SESSION['user_id']]); + // Insert into unified Invoice table + $items_for_journal = []; + foreach ($items as $item) { + $items_for_journal[] = ['id' => $item['id'], 'qty' => $item['qty']]; + } + + $stmt = $db->prepare("INSERT INTO invoices (transaction_no, customer_id, invoice_date, payment_type, total_amount, vat_amount, total_with_vat, paid_amount, status, register_session_id, is_pos, discount_amount, loyalty_points_redeemed, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'paid', ?, 1, ?, ?, ?)"); + $stmt->execute([$transaction_no, $customer_id, date('Y-m-d'), 'pos', $total_amount, $tax_amount, $net_amount, $net_amount, $session_id, $discount_amount, $loyalty_redeemed, $_SESSION['user_id']]); $transaction_id = (int)$db->lastInsertId(); // Insert Items & Update Stock - $stmtItem = $db->prepare("INSERT INTO pos_items (transaction_id, product_id, quantity, unit_price, vat_rate, vat_amount, subtotal) VALUES (?, ?, ?, ?, ?, ?, ?)"); + $stmtItem = $db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)"); $stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?"); foreach ($items as $item) { $sub = (float)$item['price'] * (float)$item['qty']; - $vr = (float)($item['vat_rate'] ?? 0); $va = (float)($item['vat_amount'] ?? 0); - $stmtItem->execute([$transaction_id, $item['id'], $item['qty'], $item['price'], $vr, $va, $sub]); + $stmtItem->execute([$transaction_id, $item['id'], $item['qty'], $item['price'], $va, $sub]); $stmtStock->execute([$item['qty'], $item['id']]); } // Insert Payments - $stmtPay = $db->prepare("INSERT INTO pos_payments (transaction_id, payment_method, amount) VALUES (?, ?, ?)"); + require_once 'includes/accounting_helper.php'; + $stmtPay = $db->prepare("INSERT INTO payments (invoice_id, amount, payment_date, payment_method, notes) VALUES (?, ?, ?, ?, ?)"); foreach ($payments as $p) { - $stmtPay->execute([$transaction_id, $p['method'], $p['amount']]); + $stmtPay->execute([$transaction_id, $p['amount'], date('Y-m-d'), $p['method'], 'POS Transaction']); + $payment_id = $db->lastInsertId(); + recordPaymentReceivedJournal((int)$payment_id, $p['amount'], date('Y-m-d'), $p['method']); } // Update Loyalty Points if customer exists @@ -402,10 +409,13 @@ if (isset($_GET['action']) || isset($_POST['action'])) { ->execute([$customer_id, $transaction_id, -$loyalty_redeemed * 100, "Redeemed for POS order #$transaction_no"]); } - // Update transaction with points earned - $db->prepare("UPDATE pos_transactions SET loyalty_points_earned = ? WHERE id = ?")->execute([$points_earned, $transaction_id]); + // Update invoice with points earned + $db->prepare("UPDATE invoices SET loyalty_points_earned = ? WHERE id = ?")->execute([$points_earned, $transaction_id]); } + // Record Sale Journal for POS + recordSaleJournal($transaction_id, $net_amount, date('Y-m-d'), $items_for_journal, $tax_amount); + $db->commit(); echo json_encode(['success' => true, 'invoice_id' => $transaction_id, 'transaction_no' => $transaction_no]); } catch (Exception $e) { @@ -512,7 +522,7 @@ if (isset($_GET['action']) || isset($_POST['action'])) { } // Redirect to login if not authenticated -if (false && !isset($_SESSION['user_id'])) { +if (!isset($_SESSION['user_id'])) { ?> @@ -753,69 +763,11 @@ function getPromotionalPrice($item) { $pay_type = $_POST['payment_type'] ?? 'cash'; $items = $_POST['item_ids'] ?? []; - $qtys = $_POST['quantities'] ?? []; - $prices = $_POST['prices'] ?? []; - - $total_subtotal = 0; - $total_vat = 0; - - foreach ($items as $i => $item_id) { - if (!$item_id) continue; - $qty = (float)$qtys[$i]; - $price = (float)$prices[$i]; - $subtotal = $qty * $price; - - $stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?"); - $stmtVat->execute([$item_id]); - $vatRate = (float)$stmtVat->fetchColumn(); - - $vatAmount = $subtotal * ($vatRate / 100); - $total_subtotal += $subtotal; - $total_vat += $vatAmount; + if (empty($items)) { + throw new Exception("Please add at least one item."); } - - $total_with_vat = $total_subtotal + $total_vat; - $paid = (float)($_POST['paid_amount'] ?? 0); - if ($status === 'paid') $paid = $total_with_vat; - - $stmt = $db->prepare("INSERT INTO $table ($cust_supplier_col, invoice_date, due_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$cust_id, $inv_date, $due_date, $status, $pay_type, $total_subtotal, $total_vat, $total_with_vat, $paid]); - $inv_id = $db->lastInsertId(); - - $items_for_journal = []; - foreach ($items as $i => $item_id) { - if (!$item_id) continue; - $qty = (float)$qtys[$i]; - $price = (float)$prices[$i]; - $subtotal = $qty * $price; - - $stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?"); - $stmtVat->execute([$item_id]); - $vatRate = (float)$stmtVat->fetchColumn(); - $vatAmount = $subtotal * ($vatRate / 100); - - $db->prepare("INSERT INTO $item_table (" . (($type === 'purchase') ? 'purchase_id' : 'invoice_id') . ", item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$inv_id, $item_id, $qty, $price, $vatAmount, $subtotal]); - $change = ($type === 'sale') ? -$qty : $qty; - $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$change, $item_id]); - $items_for_journal[] = ['id' => $item_id, 'qty' => $qty]; - } - if ($type === 'sale') recordSaleJournal((int)$inv_id, $total_with_vat, $inv_date, $items_for_journal, $total_vat); - else recordPurchaseJournal((int)$inv_id, $total_with_vat, $inv_date, $items_for_journal, $total_vat); - $db->commit(); - $message = ($type === 'purchase' ? "Purchase" : "Invoice") . " #$inv_id created!"; - } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } - } - - if (isset($_POST['add_quotation'])) { - $db = db(); - try { - $db->beginTransaction(); - $cust_id = (int)$_POST['customer_id']; - $quot_date = $_POST['quotation_date'] ?: date('Y-m-d'); - $valid_until = $_POST['valid_until'] ?: null; - - $items = $_POST['item_ids'] ?? []; $qtys = $_POST['quantities'] ?? []; + $prices = $_POST['prices'] ?? []; $total_subtotal = 0; @@ -921,10 +873,134 @@ function getPromotionalPrice($item) { if (isset($_POST['delete_quotation'])) { $id = (int)$_POST['id']; db()->prepare("DELETE FROM quotations WHERE id = ?")->execute([$id]); - db()->prepare("DELETE FROM quotation_items WHERE quotation_id = ?")->execute([$id]); $message = "Quotation deleted!"; } + if (isset($_POST['add_lpo'])) { + $db = db(); + try { + $db->beginTransaction(); + $supp_id = (int)$_POST['supplier_id']; + $lpo_date = $_POST['lpo_date'] ?: date('Y-m-d'); + $delivery_date = $_POST['delivery_date'] ?: null; + $terms = $_POST['terms_conditions'] ?? ''; + + $items = $_POST['item_ids'] ?? []; + if (empty($items)) { + throw new Exception("Please add at least one item."); + } + $qtys = $_POST['quantities'] ?? []; + $prices = $_POST['prices'] ?? []; + + $total_subtotal = 0; + $total_vat = 0; + + foreach ($items as $i => $item_id) { + if (!$item_id) continue; + $qty = (float)$qtys[$i]; + $price = (float)$prices[$i]; + $subtotal = $qty * $price; + + $stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?"); + $stmtVat->execute([$item_id]); + $vatRate = (float)$stmtVat->fetchColumn(); + + $vatAmount = $subtotal * ($vatRate / 100); + $total_subtotal += $subtotal; + $total_vat += $vatAmount; + } + + $total_with_vat = $total_subtotal + $total_vat; + + $stmt = $db->prepare("INSERT INTO lpos (supplier_id, lpo_date, delivery_date, status, total_amount, vat_amount, total_with_vat, terms_conditions) VALUES (?, ?, ?, 'pending', ?, ?, ?, ?)"); + $stmt->execute([$supp_id, $lpo_date, $delivery_date, $total_subtotal, $total_vat, $total_with_vat, $terms]); + $lpo_id = $db->lastInsertId(); + + foreach ($items as $i => $item_id) { + if (!$item_id) continue; + $qty = (float)$qtys[$i]; + $price = (float)$prices[$i]; + $subtotal = $qty * $price; + + $stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?"); + $stmtVat->execute([$item_id]); + $vatRate = (float)$stmtVat->fetchColumn(); + $vatAmount = $subtotal * ($vatRate / 100); + + $db->prepare("INSERT INTO lpo_items (lpo_id, item_id, quantity, unit_price, vat_percentage, vat_amount, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?)")->execute([$lpo_id, $item_id, $qty, $price, $vatRate, $vatAmount, $subtotal]); + } + $db->commit(); + $message = "LPO #$lpo_id created!"; + } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } + } + + if (isset($_POST['edit_lpo'])) { + $db = db(); + try { + $db->beginTransaction(); + $lpo_id = (int)$_POST['lpo_id']; + $supp_id = (int)$_POST['supplier_id']; + $lpo_date = $_POST['lpo_date']; + $delivery_date = $_POST['delivery_date'] ?: null; + $status = $_POST['status'] ?? 'pending'; + $terms = $_POST['terms_conditions'] ?? ''; + + $items = $_POST['item_ids'] ?? []; + if (empty($items)) { + throw new Exception("Please add at least one item."); + } + $qtys = $_POST['quantities'] ?? []; + $prices = $_POST['prices'] ?? []; + + $total_subtotal = 0; + $total_vat = 0; + + foreach ($items as $i => $item_id) { + if (!$item_id) continue; + $qty = (float)$qtys[$i]; + $price = (float)$prices[$i]; + $subtotal = $qty * $price; + + $stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?"); + $stmtVat->execute([$item_id]); + $vatRate = (float)$stmtVat->fetchColumn(); + + $vatAmount = $subtotal * ($vatRate / 100); + $total_subtotal += $subtotal; + $total_vat += $vatAmount; + } + + $total_with_vat = $total_subtotal + $total_vat; + + $stmt = $db->prepare("UPDATE lpos SET supplier_id = ?, lpo_date = ?, delivery_date = ?, status = ?, total_amount = ?, vat_amount = ?, total_with_vat = ?, terms_conditions = ? WHERE id = ?"); + $stmt->execute([$supp_id, $lpo_date, $delivery_date, $status, $total_subtotal, $total_vat, $total_with_vat, $terms, $lpo_id]); + + $db->prepare("DELETE FROM lpo_items WHERE lpo_id = ?")->execute([$lpo_id]); + + foreach ($items as $i => $item_id) { + if (!$item_id) continue; + $qty = (float)$qtys[$i]; + $price = (float)$prices[$i]; + $subtotal = $qty * $price; + + $stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?"); + $stmtVat->execute([$item_id]); + $vatRate = (float)$stmtVat->fetchColumn(); + $vatAmount = $subtotal * ($vatRate / 100); + + $db->prepare("INSERT INTO lpo_items (lpo_id, item_id, quantity, unit_price, vat_percentage, vat_amount, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?)")->execute([$lpo_id, $item_id, $qty, $price, $vatRate, $vatAmount, $subtotal]); + } + $db->commit(); + $message = "LPO #$lpo_id updated!"; + } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } + } + + if (isset($_POST['delete_lpo'])) { + $id = (int)$_POST['id']; + db()->prepare("DELETE FROM lpos WHERE id = ?")->execute([$id]); + $message = "LPO deleted!"; + } + if (isset($_POST['convert_to_invoice'])) { $db = db(); try { @@ -1763,7 +1839,7 @@ if (isset($_POST['add_hr_department'])) { $session->execute([$session_id]); $opening = (float)$session->fetchColumn(); - $sales = db()->prepare("SELECT SUM(p.amount) FROM pos_payments p JOIN pos_transactions t ON p.transaction_id = t.id WHERE t.register_session_id = ? AND t.status = 'completed' AND p.payment_method = 'cash'"); + $sales = db()->prepare("SELECT SUM(p.amount) FROM payments p JOIN invoices i ON p.invoice_id = i.id WHERE i.register_session_id = ? AND i.status = 'paid' AND i.is_pos = 1 AND p.payment_method = 'cash'"); $sales->execute([$session_id]); $cash_sales = (float)$sales->fetchColumn(); @@ -1791,6 +1867,7 @@ $page_permissions = [ 'purchases' => 'purchases_view', 'purchase_returns' => 'purchase_returns_view', 'quotations' => 'quotations_view', + 'lpos' => 'lpos_view', 'accounting' => 'accounting_view', 'expense_categories' => 'expense_categories_view', 'expenses' => 'expenses_view', @@ -1873,6 +1950,7 @@ $permission_groups = [ ], 'Purchases' => [ 'purchases' => 'Purchases', + 'lpos' => 'LPOs', 'purchase_returns' => 'Purchase Returns' ], 'Expenses' => [ @@ -2102,18 +2180,50 @@ switch ($page) { } $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"); + FROM quotations q + JOIN customers c ON q.customer_id = c.id + WHERE $whereSql + ORDER BY q.id DESC"); $stmt->execute($params); $data['quotations'] = $stmt->fetchAll(); - $items_list_raw = db()->query("SELECT id, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate, is_promotion, promotion_start, promotion_end, promotion_percent FROM stock_items ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC); - foreach ($items_list_raw as &$item) { - $item['sale_price'] = getPromotionalPrice($item); + break; + case 'lpos': + $where = ["1=1"]; + $params = []; + if (!empty($_GET['search'])) { + $s = $_GET['search']; + $clean_id = preg_replace('/[^0-9]/', '', $s); + if ($clean_id !== '') { + $where[] = "(q.id LIKE ? OR s.name LIKE ? OR q.id = ?)"; + $params[] = "%$s%"; + $params[] = "%$s%"; + $params[] = $clean_id; + } else { + $where[] = "(q.id LIKE ? OR s.name LIKE ?)"; + $params[] = "%$s%"; + $params[] = "%$s%"; + } } - $data['items_list'] = $items_list_raw; - $data['customers_list'] = db()->query("SELECT id, name FROM customers ORDER BY name ASC")->fetchAll(); + if (!empty($_GET['supplier_id'])) { + $where[] = "q.supplier_id = ?"; + $params[] = $_GET['supplier_id']; + } + if (!empty($_GET['start_date'])) { + $where[] = "q.lpo_date >= ?"; + $params[] = $_GET['start_date']; + } + if (!empty($_GET['end_date'])) { + $where[] = "q.lpo_date <= ?"; + $params[] = $_GET['end_date']; + } + $whereSql = implode(" AND ", $where); + $stmt = db()->prepare("SELECT q.*, s.name as supplier_name + FROM lpos q + JOIN suppliers s ON q.supplier_id = s.id + WHERE $whereSql + ORDER BY q.id DESC"); + $stmt->execute($params); + $data['lpos'] = $stmt->fetchAll(); break; case 'payment_methods': $data['payment_methods'] = db()->query("SELECT * FROM payment_methods ORDER BY id DESC")->fetchAll(); @@ -2565,8 +2675,8 @@ switch ($page) { $data['stats'] = [ 'total_customers' => db()->query("SELECT COUNT(*) FROM customers")->fetchColumn(), 'total_items' => db()->query("SELECT COUNT(*) FROM stock_items")->fetchColumn(), - 'total_sales' => db()->query("SELECT SUM(total_with_vat) FROM invoices")->fetchColumn() ?: 0, - 'total_received' => db()->query("SELECT SUM(amount) FROM payments")->fetchColumn() ?: 0, + 'total_sales' => (db()->query("SELECT SUM(total_with_vat) FROM invoices")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(net_amount) FROM pos_transactions WHERE status = 'completed'")->fetchColumn() ?: 0), + 'total_received' => (db()->query("SELECT SUM(amount) FROM payments")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(amount) FROM pos_payments")->fetchColumn() ?: 0), 'total_purchases' => db()->query("SELECT SUM(total_with_vat) FROM purchases")->fetchColumn() ?: 0, 'total_paid' => db()->query("SELECT SUM(amount) FROM purchase_payments")->fetchColumn() ?: 0, 'expired_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE()")->fetchColumn(), @@ -2720,17 +2830,22 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; - - - +
Cash Reconciliation
@@ -8218,14 +8442,15 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; Expected Cash: OMR
+
Actual Cash: - OMR + OMR
0 ? 'text-info' : 'text-success'); + $shortage = (float)$s['cash_in_hand'] - $expected_cash_total; + $shortage_class = $shortage < 0 ? 'text-danger' : 'text-success'; ?>
Balance: @@ -8252,6 +8477,30 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; + prepare("SELECT i.*, c.name as customer_name, GROUP_CONCAT(p.payment_method SEPARATOR ', ') as methods FROM invoices i LEFT JOIN payments p ON i.id = p.invoice_id LEFT JOIN customers c ON i.customer_id = c.id WHERE i.register_session_id = ? AND i.status = 'paid' AND i.is_pos = 1 GROUP BY i.id ORDER BY i.created_at DESC"); + $txs_stmt->execute([$s['id']]); + $txs = $txs_stmt->fetchAll(); + foreach ($txs as $tx): + ?> + + + + + + prepare("SELECT si.name_en, ii.quantity FROM invoice_items ii JOIN stock_items si ON ii.item_id = si.id WHERE ii.invoice_id = ?"); + $items_stmt->execute([$tx['id']]); + $items = $items_stmt->fetchAll(); + foreach ($items as $item) { + echo "" . htmlspecialchars($item['name_en']) . " x " . (float)$item['quantity'] . ""; + } + ?> + + + + + prepare("SELECT t.*, c.name as customer_name, GROUP_CONCAT(p.payment_method SEPARATOR ', ') as methods FROM pos_transactions t LEFT JOIN pos_payments p ON t.id = p.transaction_id LEFT JOIN customers c ON t.customer_id = c.id WHERE t.register_session_id = ? AND t.status = 'completed' GROUP BY t.id ORDER BY t.created_at DESC"); $txs_stmt->execute([$s['id']]); @@ -8354,8 +8603,13 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
prepare("SELECT p.payment_method, SUM(p.amount) as total FROM pos_payments p JOIN pos_transactions t ON p.transaction_id = t.id WHERE t.register_session_id = ? AND t.status = 'completed' GROUP BY p.payment_method"); - $curBreakdown->execute([$session['id']]); + // Unified breakdown for closing + $curBreakdown = db()->prepare("SELECT payment_method, SUM(amount) as total FROM ( + SELECT p.payment_method, p.amount FROM pos_payments p JOIN pos_transactions t ON p.transaction_id = t.id WHERE t.register_session_id = ? AND t.status = 'completed' + UNION ALL + SELECT p.payment_method, p.amount FROM payments p JOIN invoices i ON p.invoice_id = i.id WHERE i.register_session_id = ? AND i.status = 'paid' AND i.is_pos = 1 + ) as combined GROUP BY payment_method"); + $curBreakdown->execute([$session['id'], $session['id']]); $curMethods = $curBreakdown->fetchAll(); $cash_sales = 0; @@ -9284,7 +9538,7 @@ document.addEventListener('DOMContentLoaded', function() { document.getElementById('receiptNo').textContent = 'RCP-' + data.id.toString().padStart(5, '0'); document.getElementById('receiptDate').textContent = data.payment_date; document.getElementById('receiptCustomer').textContent = data.customer_name || '---'; - document.getElementById('receiptInvNo').textContent = 'INV-' + data.inv_id.toString().padStart(5, '0'); + document.getElementById('receiptInvNo').textContent = (data.inv_type === 'purchase' ? 'PUR-' : 'INV-') + data.inv_id.toString().padStart(5, '0'); document.getElementById('receiptMethod').textContent = data.payment_method; document.getElementById('receiptAmount').textContent = parseFloat(data.amount).toFixed(3); document.getElementById('receiptAmountWords').textContent = data.amount_words; @@ -9549,10 +9803,188 @@ document.addEventListener('DOMContentLoaded', function() { }); } - const invoiceType = ''; + const invoiceType = ''; initInvoiceForm('productSearchInput', 'searchSuggestions', 'invoiceItemsTableBody', 'grandTotal', 'subtotal', 'totalVat'); initInvoiceForm('editProductSearchInput', 'editSearchSuggestions', 'editInvoiceItemsTableBody', 'edit_grandTotal', 'edit_subtotal', 'edit_totalVat'); + // LPO Form Logic + initInvoiceForm('lpoProductSearchInput', 'lpoSearchSuggestions', 'lpoItemsTableBody', 'lpo_grand_display', 'lpo_subtotal_display', 'lpo_vat_display'); + initInvoiceForm('editLpoProductSearchInput', 'editLpoSearchSuggestions', 'editLpoItemsTableBody', 'edit_lpo_grand_display', 'edit_lpo_subtotal_display', 'edit_lpo_vat_display'); + + document.querySelectorAll('.edit-lpo-btn').forEach(btn => { + btn.addEventListener('click', function() { + const data = JSON.parse(this.dataset.json); + document.getElementById('edit_lpo_id').value = data.id; + const supplierSelect = document.getElementById('edit_lpo_supplier_id'); + supplierSelect.value = data.supplier_id; + if (window.jQuery && $(supplierSelect).data('select2')) { + $(supplierSelect).trigger('change'); + } + document.getElementById('edit_lpo_date').value = data.lpo_date; + document.getElementById('edit_lpo_delivery_date').value = data.delivery_date || ''; + document.getElementById('edit_lpo_status').value = data.status || 'pending'; + document.getElementById('edit_lpo_terms').value = data.terms_conditions || ''; + + const tableBody = document.getElementById('editLpoItemsTableBody'); + tableBody.innerHTML = ''; + + data.items.forEach(item => { + addItemToTable({ + id: item.item_id, + name_en: item.name_en, + name_ar: item.name_ar, + sku: '', + vat_rate: item.vat_rate || 0 + }, tableBody, null, null, + document.getElementById('edit_lpo_grand_display'), + document.getElementById('edit_lpo_subtotal_display'), + document.getElementById('edit_lpo_vat_display'), + { quantity: item.quantity, unit_price: item.unit_price }); + }); + }); + }); + + document.querySelectorAll('.view-lpo-btn').forEach(btn => { + btn.addEventListener('click', function() { + const data = JSON.parse(this.dataset.json); + window.viewAndPrintLPO(data); + }); + }); + + window.viewAndPrintLPO = function(data) { + const modal = new bootstrap.Modal(document.getElementById('viewLpoModal')); + const content = document.getElementById('lpoDetailsContent'); + + const logoUrl = companySettings.company_logo || ''; + const companyHeader = ` +
+
+ ${logoUrl ? `Logo` : ''} +

${companySettings.company_name || 'Your Company'}

+

+ ${companySettings.company_address || ''}
+ Phone: ${companySettings.company_phone || ''} | Email: ${companySettings.company_email || ''} + ${companySettings.tax_number ? `
TRN: ${companySettings.tax_number}` : ''} +

+
+
+

LOCAL PURCHASE ORDER

+

LPO-${data.id.toString().padStart(5, '0')}

+
+
+ `; + + let itemsHtml = ''; + data.items.forEach((item, index) => { + itemsHtml += ` + + ${index + 1} + ${item.name_en}
${item.name_ar} + ${item.quantity} + ${parseFloat(item.unit_price).toFixed(3)} + ${item.vat_rate}% + ${parseFloat(item.total_amount).toFixed(3)} + + `; + }); + + content.innerHTML = ` + ${companyHeader} +
+
+
+
Supplier
+

${data.supplier_name}

+

+ ${data.supplier_phone ? `Phone: ${data.supplier_phone}` : ''} +

+
+
+
Details
+
+ Date: + ${data.lpo_date} +
+
+ Delivery: + ${data.delivery_date || '---'} +
+
+ Status: + ${data.status.toUpperCase()} +
+
+
+
+ + + + + + + + + + + + ${itemsHtml} + + + + + + + + + + + + + + +
#DescriptionQtyUnit PriceVATTotal
SubtotalOMR ${parseFloat(data.total_amount).toFixed(3)}
VAT AmountOMR ${parseFloat(data.vat_amount).toFixed(3)}
Grand TotalOMR ${parseFloat(data.total_with_vat).toFixed(3)}
+
+ ${data.terms_conditions ? ` +
+
+
Terms & Conditions
+

${data.terms_conditions.replace(/\n/g, '
')}

+
+
` : ''} + +
+
+
+

Prepared By

+
+
+
+
+
+

Authorized Signature

+
+
+
+ `; + + window.printLPO = function() { + const printWindow = window.open('', '_blank'); + printWindow.document.write('LPO-' + data.id + ''); + printWindow.document.write(''); + printWindow.document.write(''); + printWindow.document.write(''); + printWindow.document.write(content.innerHTML); + printWindow.document.write(''); + printWindow.document.close(); + setTimeout(() => { + printWindow.print(); + printWindow.close(); + }, 500); + }; + + modal.show(); + }; + // Quotation Form Logic initInvoiceForm('quotProductSearchInput', 'quotSearchSuggestions', 'quotItemsTableBody', 'quot_grand_display', 'quot_subtotal_display', 'quot_vat_display'); initInvoiceForm('editQuotProductSearchInput', 'editQuotSearchSuggestions', 'editQuotItemsTableBody', 'edit_quot_grand_display', 'edit_quot_subtotal_display', 'edit_quot_vat_display'); @@ -10593,6 +11025,200 @@ document.addEventListener('DOMContentLoaded', function() {
+ + + + + + + + +