diff --git a/assets/pasted-20260220-114014-3aea48e0.png b/assets/pasted-20260220-114014-3aea48e0.png
new file mode 100644
index 0000000..e281797
Binary files /dev/null and b/assets/pasted-20260220-114014-3aea48e0.png differ
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';
-
-
+
+
= __('purchases') ?>
-
+
+
+
+
Local Purchase Orders (LPO)
+
+ Create New LPO
+
+
+
+
+
+
+
+
+
+ LPO #
+ Date
+ Delivery Date
+ Supplier
+ Status
+ Total
+ Actions
+
+
+
+ prepare("SELECT li.*, i.name_en, i.name_ar, i.vat_rate
+ FROM lpo_items li
+ JOIN stock_items i ON li.item_id = i.id
+ WHERE li.lpo_id = ?");
+ $items->execute([$q['id']]);
+ $q['items'] = $items->fetchAll(PDO::FETCH_ASSOC);
+ ?>
+
+ LPO-= str_pad((string)$q['id'], 5, '0', STR_PAD_LEFT) ?>
+ = $q['lpo_date'] ?>
+ = $q['delivery_date'] ?: '---' ?>
+ = htmlspecialchars($q['supplier_name'] ?? '---') ?>
+
+
+ = htmlspecialchars($q['status']) ?>
+
+ OMR = number_format((float)$q['total_with_vat'], 3) ?>
+
+
+
+
+
+
+
+
+
+
+ No LPOs found
+
+
+
+
+
+
@@ -5311,7 +5525,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
$prefix = ($page === 'purchases') ? 'PUR' : 'INV';
?>
- = $prefix ?>-= str_pad((string)$inv['id'], 5, '0', STR_PAD_LEFT) ?>
+ = $inv['is_pos'] ? htmlspecialchars($inv['transaction_no']) : $prefix . '-' . str_pad((string)$inv['id'], 5, '0', STR_PAD_LEFT) ?>
= $inv['invoice_date'] ?>
@@ -5439,7 +5653,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
?>
= $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' ? ($page === 'supplier_statement' ? 'PUR-' : '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) : '' ?>
@@ -6450,7 +6664,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
PRET-= str_pad((string)$ret['id'], 5, '0', STR_PAD_LEFT) ?>
= $ret['return_date'] ?>
- INV-= str_pad((string)$ret['invoice_id'], 5, '0', STR_PAD_LEFT) ?>
+ PUR-= str_pad((string)$ret['purchase_id'], 5, '0', STR_PAD_LEFT) ?>
= htmlspecialchars($ret['supplier_name'] ?? 'Unknown') ?>
OMR = number_format((float)$ret['total_amount'], 3) ?>
@@ -8060,15 +8274,17 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
OMR = number_format((float)$s['opening_balance'], 3) ?>
prepare("SELECT
- SUM(CASE WHEN LOWER(p.payment_method) = 'cash' THEN p.amount ELSE 0 END) as cash_total,
- SUM(CASE WHEN LOWER(p.payment_method) IN ('card', 'credit card', 'visa', 'mastercard') THEN p.amount ELSE 0 END) as card_total,
- SUM(CASE WHEN LOWER(p.payment_method) = 'credit' THEN p.amount ELSE 0 END) as credit_total,
- SUM(CASE WHEN LOWER(p.payment_method) LIKE '%transfer%' OR LOWER(p.payment_method) LIKE '%bank%' THEN p.amount ELSE 0 END) as transfer_total,
- SUM(p.amount) as total_sales
- FROM pos_payments p
- JOIN pos_transactions t ON p.transaction_id = t.id
- WHERE t.register_session_id = ? AND t.status = 'completed'");
- $stats_stmt->execute([$s['id']]);
+ SUM(CASE WHEN LOWER(payment_method) = 'cash' THEN amount ELSE 0 END) as cash_total,
+ SUM(CASE WHEN LOWER(payment_method) IN ('card', 'credit card', 'visa', 'mastercard') THEN amount ELSE 0 END) as card_total,
+ SUM(CASE WHEN LOWER(payment_method) = 'credit' THEN amount ELSE 0 END) as credit_total,
+ SUM(CASE WHEN LOWER(payment_method) LIKE '%transfer%' OR LOWER(payment_method) LIKE '%bank%' THEN amount ELSE 0 END) as transfer_total,
+ SUM(amount) as total_sales
+ 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_payments");
+ $stats_stmt->execute([$s['id'], $s['id']]);
$st = $stats_stmt->fetch();
$c_total = (float)($st['cash_total'] ?? 0);
$cd_total = (float)($st['card_total'] ?? 0);
@@ -8136,9 +8352,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");
- $breakdown->execute([$s['id']]);
+ // Breakdown of sales by payment method (Unified)
+ $breakdown = 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_payments GROUP BY payment_method");
+ $breakdown->execute([$s['id'], $s['id']]);
$methods = $breakdown->fetchAll();
$cash_sales = 0;
@@ -8167,9 +8387,13 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
OMR = number_format((float)$s['opening_balance'], 3) ?>
prepare("SELECT SUM(tax_amount) as total_tax, SUM(discount_amount) as total_discount, SUM(net_amount) as total_net FROM pos_transactions WHERE register_session_id = ? AND status = 'completed'");
- $extra_stmt->execute([$s['id']]);
+ // Additional totals (Unified)
+ $extra_stmt = db()->prepare("SELECT SUM(vat_amount) as total_tax, SUM(discount_amount) as total_discount, SUM(total_net) as total_net FROM (
+ SELECT tax_amount as vat_amount, discount_amount, net_amount as total_net FROM pos_transactions WHERE register_session_id = ? AND status = 'completed'
+ UNION ALL
+ SELECT vat_amount, discount_amount, total_with_vat as total_net FROM invoices WHERE register_session_id = ? AND status = 'paid' AND is_pos = 1
+ ) as combined_totals");
+ $extra_stmt->execute([$s['id'], $s['id']]);
$extra = $extra_stmt->fetch();
?>
@@ -8202,7 +8426,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
-
+
Cash Reconciliation
@@ -8218,14 +8442,15 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
Expected Cash:
OMR = number_format($expected_cash_total, 3) ?>
+
Actual Cash:
- OMR = number_format((float)$s['cash_in_hand'], 3) ?>
+ OMR = number_format((float)$s['cash_in_hand'], 3) ?>
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):
+ ?>
+
+ = date('H:i', strtotime($tx['created_at'])) ?>
+ = htmlspecialchars($tx['transaction_no']) ?>
+ = htmlspecialchars($tx['customer_name'] ?: 'Walk-in') ?>
+
+ 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'] . " ";
+ }
+ ?>
+
+ = htmlspecialchars($tx['methods'] ?: '---') ?>
+ = number_format($tx['total_with_vat'], 3) ?>
+
+
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 = '= in_array($page, ["sales", "quotations"]) ? "sale" : ($page === "purchases" ? "purchase" : "") ?>';
+ const invoiceType = '= in_array($page, ["sales", "quotations"]) ? "sale" : (in_array($page, ["purchases", "lpos"]) ? "purchase" : "") ?>';
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 ? `
` : ''}
+
${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()}
+
+
+
+
+
+
+
+ #
+ Description
+ Qty
+ Unit Price
+ VAT
+ Total
+
+
+ ${itemsHtml}
+
+
+ Subtotal
+ OMR ${parseFloat(data.total_amount).toFixed(3)}
+
+
+ VAT Amount
+ OMR ${parseFloat(data.vat_amount).toFixed(3)}
+
+
+ Grand Total
+ OMR ${parseFloat(data.total_with_vat).toFixed(3)}
+
+
+
+
+ ${data.terms_conditions ? `
+
+
+
Terms & Conditions
+
${data.terms_conditions.replace(/\n/g, ' ')}
+
+
` : ''}
+
+
+ `;
+
+ 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() {
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Supplier
+
+ ---
+
+ = htmlspecialchars($s['name']) ?>
+
+
+
+
+ Date
+
+
+
+ Delivery Date
+
+
+
+ Status
+
+ Pending
+ Converted
+ Cancelled
+
+
+
+ Terms
+
+
+
+
+
+
+
+
+
+
+ Item Details
+ Qty
+ Unit Price
+ VAT
+ Total
+
+
+
+
+
+
+ Subtotal
+ = __('currency') ?> 0.000
+
+
+ Total VAT
+ = __('currency') ?> 0.000
+
+
+ Grand Total
+ = __('currency') ?> 0.000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/post_debug.log b/post_debug.log
index 1bcc3d5..30a873a 100644
--- a/post_debug.log
+++ b/post_debug.log
@@ -133,3 +133,35 @@
2026-02-20 07:30:15 - POST: {"type":"purchase","customer_id":"7","invoice_date":"2026-02-20","due_date":"","payment_type":"cash","status":"paid","paid_amount":"0.000","item_ids":["1"],"quantities":["150"],"prices":["0.400"],"add_invoice":""}
2026-02-20 09:27:36 - POST: {"invoice_id":"7","return_date":"2026-02-20","quantities":["5"],"item_ids":["1"],"prices":["0.400"],"notes":"","add_purchase_return":""}
2026-02-20 09:28:59 - POST: {"invoice_id":"7","return_date":"2026-02-20","quantities":["3"],"item_ids":["1"],"prices":["0.400"],"notes":"","add_purchase_return":""}
+2026-02-20 09:31:35 - POST: {"invoice_id":"31","return_date":"2026-02-20","quantities":["1"],"item_ids":["1"],"prices":["0.450"],"notes":"","add_sales_return":""}
+2026-02-20 09:50:43 - POST: {"supplier_id":"7","lpo_date":"2026-02-20","delivery_date":"2026-02-22","terms_conditions":"","add_lpo":""}
+2026-02-20 09:50:46 - POST: {"supplier_id":"7","lpo_date":"2026-02-20","delivery_date":"2026-02-22","terms_conditions":"","add_lpo":""}
+2026-02-20 09:53:21 - POST: {"supplier_id":"7","lpo_date":"2026-02-20","delivery_date":"2026-02-22","terms_conditions":"","add_lpo":""}
+2026-02-20 10:00:29 - POST: {"supplier_id":"7","lpo_date":"2026-02-20","delivery_date":"2026-02-22","terms_conditions":"","add_lpo":""}
+2026-02-20 10:00:33 - POST: {"delete_lpo":"","id":"1"}
+2026-02-20 10:10:17 - POST: {"lpo_id":"4","supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"2026-02-22","status":"pending","terms_conditions":"","item_ids":["1"],"quantities":["100"],"prices":["0.400"],"edit_lpo":""}
+2026-02-20 10:21:07 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.95}]","total_amount":"0.95","tax_amount":"0.011904761904761904","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.45,\"vat_rate\":0,\"vat_amount\":0},{\"id\":3,\"qty\":1,\"price\":0.25,\"vat_rate\":0,\"vat_amount\":0},{\"id\":2,\"qty\":1,\"price\":0.25,\"vat_rate\":5,\"vat_amount\":0.011904761904761904}]"}
+2026-02-20 10:22:29 - POST: {"lpo_id":"4","supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"2026-02-22","status":"pending","terms_conditions":"","item_ids":["1"],"quantities":["10"],"prices":["0.400"],"edit_lpo":""}
+2026-02-20 11:13:39 - POST: {"lpo_id":"4","supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"2026-02-22","status":"pending","terms_conditions":"","item_ids":["1"],"quantities":["10"],"prices":["0.400"],"edit_lpo":""}
+2026-02-20 11:14:12 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":0.95}]","total_amount":"0.95","tax_amount":"0.011904761904761904","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.45,\"vat_rate\":0,\"vat_amount\":0},{\"id\":3,\"qty\":1,\"price\":0.25,\"vat_rate\":0,\"vat_amount\":0},{\"id\":2,\"qty\":1,\"price\":0.25,\"vat_rate\":5,\"vat_amount\":0.011904761904761904}]"}
+2026-02-20 11:15:52 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":6.3}]","total_amount":"6.3","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":14,\"price\":0.45,\"vat_rate\":0,\"vat_amount\":0}]"}
+2026-02-20 11:21:57 - POST: {"supplier_id":"7","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["1"],"quantities":["112"],"prices":["0.400"],"add_lpo":""}
+2026-02-20 11:22:28 - POST: {"delete_lpo":"","id":"2"}
+2026-02-20 11:22:40 - POST: {"delete_lpo":"","id":"3"}
+2026-02-20 11:22:48 - POST: {"delete_lpo":"","id":"4"}
+2026-02-20 11:27:45 - POST: {"supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["100"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 11:28:37 - POST: {"supplier_id":"5","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["100"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 11:32:03 - POST: {"supplier_id":"5","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["100"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 11:32:32 - POST: {"supplier_id":"5","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["100"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 11:33:08 - POST: {"supplier_id":"5","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["100"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 11:34:18 - POST: {"supplier_id":"5","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["100"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 11:34:41 - POST: {"supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 11:37:33 - POST: {"supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 11:39:11 - POST: {"supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 11:45:55 - POST: {"supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 11:46:07 - POST: {"supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 11:49:24 - POST: {"supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 12:39:16 - POST: {"supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 12:39:30 - POST: {"supplier_id":"6","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 12:40:43 - POST: {"supplier_id":"7","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""}
+2026-02-20 12:40:54 - POST: {"supplier_id":"7","lpo_date":"2026-02-20","delivery_date":"","terms_conditions":"","item_ids":["2"],"quantities":["1"],"prices":["0.150"],"add_lpo":""}
diff --git a/search_debug.log b/search_debug.log
index 3fb1295..24e5eab 100644
--- a/search_debug.log
+++ b/search_debug.log
@@ -2,3 +2,13 @@
2026-02-19 14:55:24 - search_items call: q=to
2026-02-20 07:29:03 - search_items call: q=to
2026-02-20 07:30:08 - search_items call: q=to
+2026-02-20 10:08:31 - search_items call: q=a
+2026-02-20 10:09:42 - search_items call: q=to
+2026-02-20 10:22:22 - search_items call: q=to
+2026-02-20 11:21:36 - search_items call: q=to
+2026-02-20 11:27:38 - search_items call: q=on
+2026-02-20 11:28:26 - search_items call: q=ob
+2026-02-20 11:28:28 - search_items call: q=oni
+2026-02-20 11:34:38 - search_items call: q=on
+2026-02-20 12:40:40 - search_items call: q=on
+2026-02-20 12:40:40 - search_items call: q=oni