diff --git a/assets/css/custom.css b/assets/css/custom.css index 826be45..78260e6 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -487,7 +487,7 @@ body { font-weight: 600; } .amount-due-box .value { - font-size: 2.5rem; + font-size: 1.25rem; font-weight: 800; color: #0f172a; } diff --git a/db/migrations/20260219_add_trial_logic.sql b/db/migrations/20260219_add_trial_logic.sql new file mode 100644 index 0000000..645e985 --- /dev/null +++ b/db/migrations/20260219_add_trial_logic.sql @@ -0,0 +1,4 @@ +ALTER TABLE system_license ADD COLUMN trial_started_at DATETIME DEFAULT NULL; +INSERT INTO system_license (license_key, status, trial_started_at) +SELECT 'TRIAL', 'pending', NOW() +WHERE NOT EXISTS (SELECT 1 FROM system_license); \ No newline at end of file diff --git a/db/migrations/20260219_fix_vat_columns.sql b/db/migrations/20260219_fix_vat_columns.sql new file mode 100644 index 0000000..aba0850 --- /dev/null +++ b/db/migrations/20260219_fix_vat_columns.sql @@ -0,0 +1,3 @@ +-- Fix missing columns for VAT and totals +ALTER TABLE invoice_items ADD COLUMN vat_amount DECIMAL(15,3) DEFAULT 0.000 AFTER unit_price; +ALTER TABLE quotation_items ADD COLUMN vat_amount DECIMAL(15,3) DEFAULT 0.000 AFTER unit_price; diff --git a/index.php b/index.php index 18b94dc..77246d7 100644 --- a/index.php +++ b/index.php @@ -38,9 +38,11 @@ $dir = ($lang === 'ar') ? 'rtl' : 'ltr'; // Licensing Middleware $is_activated = LicenseService::isActivated(); +$trial_days = LicenseService::getTrialRemainingDays(); +$can_access = LicenseService::canAccess(); $page = $_GET['page'] ?? 'dashboard'; -if (!$is_activated && $page !== 'activate') { +if (!$can_access && $page !== 'activate') { header("Location: index.php?page=activate"); exit; } @@ -221,6 +223,43 @@ if (isset($_GET['action']) || isset($_POST['action'])) { exit; } + if ($action === 'search_items') { + file_put_contents('search_debug.log', date('Y-m-d H:i:s') . " - search_items call: q=" . ($_GET['q'] ?? '') . "\n", FILE_APPEND); + header('Content-Type: application/json'); + $q = $_GET['q'] ?? ''; + if (strlen($q) < 1) { + echo json_encode([]); + exit; + } + $searchTerm = "%$q%"; + $stmt = db()->prepare("SELECT * FROM stock_items WHERE name_en LIKE ? OR name_ar LIKE ? OR sku LIKE ? LIMIT 15"); + $stmt->execute([$searchTerm, $searchTerm, $searchTerm]); + echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)); + exit; + } + + if ($action === 'get_payments') { + header('Content-Type: application/json'); + $invoice_id = (int)$_GET['invoice_id']; + $stmt = db()->prepare("SELECT * FROM payments WHERE invoice_id = ? ORDER BY payment_date DESC"); + $stmt->execute([$invoice_id]); + echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)); + exit; + } + + if ($action === 'get_payment_details') { + header('Content-Type: application/json'); + $payment_id = (int)$_GET['payment_id']; + $stmt = db()->prepare("SELECT p.*, i.customer_id, c.name as customer_name + FROM payments p + JOIN invoices i ON p.invoice_id = i.id + JOIN customers c ON i.customer_id = c.id + WHERE p.id = ?"); + $stmt->execute([$payment_id]); + echo json_encode($stmt->fetch(PDO::FETCH_ASSOC)); + exit; + } + if ($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.created_at DESC"); @@ -594,27 +633,51 @@ function getPromotionalPrice($item) { $inv_date = $_POST['invoice_date'] ?: date('Y-m-d'); $status = $_POST['status'] ?? 'pending'; $pay_type = $_POST['payment_type'] ?? 'cash'; - $total_vat = (float)($_POST['total_vat'] ?? 0); - $total_with_vat = (float)($_POST['total_with_vat'] ?? 0); - $paid = (float)($_POST['paid_amount'] ?? 0); - $stmt = $db->prepare("INSERT INTO invoices (customer_id, type, invoice_date, status, payment_type, total_vat, total_with_vat, paid_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$cust_id, $type, $inv_date, $status, $pay_type, $total_vat, $total_with_vat, $paid]); - $inv_id = $db->lastInsertId(); - - $items = $_POST['items'] ?? []; - $qtys = $_POST['qtys'] ?? []; + $items = $_POST['item_ids'] ?? []; + $qtys = $_POST['quantities'] ?? []; $prices = $_POST['prices'] ?? []; - $vats = $_POST['vats'] ?? []; + + $total_subtotal = 0; + $total_vat = 0; + + // First pass to calculate totals + 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; + $paid = (float)($_POST['paid_amount'] ?? 0); + if ($status === 'paid') $paid = $total_with_vat; + + $stmt = $db->prepare("INSERT INTO invoices (customer_id, type, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$cust_id, $type, $inv_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]; - $vat = (float)($vats[$i] ?? 0); $subtotal = $qty * $price; - $db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, vat_amount, subtotal) VALUES (?, ?, ?, ?, ?, ?)")->execute([$inv_id, $item_id, $qty, $price, $vat, $subtotal]); + + $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 invoice_items (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]; @@ -626,6 +689,168 @@ function getPromotionalPrice($item) { } 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; + $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 quotations (customer_id, quotation_date, valid_until, status, total_amount, vat_amount, total_with_vat) VALUES (?, ?, ?, 'pending', ?, ?, ?)"); + $stmt->execute([$cust_id, $quot_date, $valid_until, $total_subtotal, $total_vat, $total_with_vat]); + $quot_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 quotation_items (quotation_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$quot_id, $item_id, $qty, $price, $vatAmount, $subtotal]); + } + $db->commit(); + $message = "Quotation #$quot_id created!"; + } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } + } + + if (isset($_POST['edit_quotation'])) { + $db = db(); + try { + $db->beginTransaction(); + $quot_id = (int)$_POST['quotation_id']; + $cust_id = (int)$_POST['customer_id']; + $quot_date = $_POST['quotation_date']; + $valid_until = $_POST['valid_until'] ?: null; + $status = $_POST['status'] ?? 'pending'; + + $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; + } + + $total_with_vat = $total_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([$cust_id, $quot_date, $valid_until, $status, $total_subtotal, $total_vat, $total_with_vat, $quot_id]); + + // Delete old items + $db->prepare("DELETE FROM quotation_items WHERE quotation_id = ?")->execute([$quot_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 quotation_items (quotation_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$quot_id, $item_id, $qty, $price, $vatAmount, $subtotal]); + } + $db->commit(); + $message = "Quotation #$quot_id updated!"; + } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } + } + + 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['convert_to_invoice'])) { + $db = db(); + try { + $db->beginTransaction(); + $quot_id = (int)$_POST['quotation_id']; + + $stmt = $db->prepare("SELECT * FROM quotations WHERE id = ?"); + $stmt->execute([$quot_id]); + $quot = $stmt->fetch(); + + if (!$quot) throw new Exception("Quotation not found."); + if ($quot['status'] === 'converted') throw new Exception("Quotation already converted."); + + $stmtItems = $db->prepare("SELECT * FROM quotation_items WHERE quotation_id = ?"); + $stmtItems->execute([$quot_id]); + $qItems = $stmtItems->fetchAll(); + + // Create Invoice + $inv_date = date('Y-m-d'); + $stmtInv = $db->prepare("INSERT INTO invoices (customer_id, type, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, 'sale', ?, 'unpaid', 'credit', ?, ?, ?, 0)"); + $stmtInv->execute([$quot['customer_id'], $inv_date, $quot['total_amount'], $quot['vat_amount'], $quot['total_with_vat']]); + $inv_id = $db->lastInsertId(); + + $items_for_journal = []; + foreach ($qItems as $item) { + $db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$inv_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['vat_amount'], $item['total_price']]); + + // Update stock + $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?")->execute([$item['quantity'], $item['item_id']]); + $items_for_journal[] = ['id' => $item['item_id'], 'qty' => $item['quantity']]; + } + + // Update Quotation status + $db->prepare("UPDATE quotations SET status = 'converted' WHERE id = ?")->execute([$quot_id]); + + // Accounting + recordSaleJournal($inv_id, $quot['total_with_vat'], $inv_date, $items_for_journal, $quot['vat_amount']); + + $db->commit(); + $message = "Quotation converted to Invoice #$inv_id successfully!"; + } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } + } + if (isset($_POST['record_payment'])) { $inv_id = (int)$_POST['invoice_id']; $amount = (float)$_POST['amount']; @@ -683,12 +908,78 @@ function getPromotionalPrice($item) { } if (isset($_POST['edit_invoice'])) { - $id = (int)$_POST['id']; - $cust_id = (int)$_POST['customer_id']; - $date = $_POST['invoice_date'] ?: date('Y-m-d'); - $status = $_POST['status'] ?? 'pending'; - db()->prepare("UPDATE invoices SET customer_id = ?, invoice_date = ?, status = ? WHERE id = ?")->execute([$cust_id, $date, $status, $id]); - $message = "Invoice updated!"; + $db = db(); + try { + $db->beginTransaction(); + $id = (int)$_POST['invoice_id']; + $cust_id = (int)$_POST['customer_id']; + $date = $_POST['invoice_date'] ?: date('Y-m-d'); + $status = $_POST['status'] ?? 'pending'; + $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; + } + + $total_with_vat = $total_subtotal + $total_vat; + $paid = (float)($_POST['paid_amount'] ?? 0); + if ($status === 'paid') $paid = $total_with_vat; + + $db->prepare("UPDATE invoices SET customer_id = ?, invoice_date = ?, status = ?, payment_type = ?, total_amount = ?, vat_amount = ?, total_with_vat = ?, paid_amount = ? WHERE id = ?") + ->execute([$cust_id, $date, $status, $pay_type, $total_subtotal, $total_vat, $total_with_vat, $paid, $id]); + + // Revert stock for old items + $stmtOld = $db->prepare("SELECT ii.item_id, ii.quantity, i.type FROM invoice_items ii JOIN invoices i ON ii.invoice_id = i.id WHERE ii.invoice_id = ?"); + $stmtOld->execute([$id]); + $oldItems = $stmtOld->fetchAll(); + foreach ($oldItems as $old) { + $change = ($old['type'] === 'sale') ? $old['quantity'] : -$old['quantity']; + $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$change, $old['item_id']]); + } + + // Delete old items + $db->prepare("DELETE FROM invoice_items WHERE invoice_id = ?")->execute([$id]); + + // Insert new items and update stock + $inv_type = db()->query("SELECT type FROM invoices WHERE id = $id")->fetchColumn(); + 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 invoice_items (invoice_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$id, $item_id, $qty, $price, $vatAmount, $subtotal]); + + $change = ($inv_type === 'sale') ? -$qty : $qty; + $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$change, $item_id]); + } + + $db->commit(); + $message = "Invoice updated successfully!"; + } catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); } } // --- HR Handlers --- @@ -1409,7 +1700,20 @@ if ($page === 'export') { $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['search'])) { + $s = $_GET['search']; + $clean_id = preg_replace('/[^0-9]/', '', $s); + if ($clean_id !== '') { + $where[] = "(v.id LIKE ? OR c.name LIKE ? OR v.id = ?)"; + $params[] = "%$s%"; + $params[] = "%$s%"; + $params[] = $clean_id; + } else { + $where[] = "(v.id LIKE ? OR c.name LIKE ?)"; + $params[] = "%$s%"; + $params[] = "%$s%"; + } + } 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']; } @@ -1518,9 +1822,18 @@ switch ($page) { $where = ["1=1"]; $params = []; if (!empty($_GET['search'])) { - $where[] = "(q.id LIKE ? OR c.name LIKE ?)"; - $params[] = "%{$_GET['search']}%"; - $params[] = "%{$_GET['search']}%"; + $s = $_GET['search']; + $clean_id = preg_replace('/[^0-9]/', '', $s); + if ($clean_id !== '') { + $where[] = "(q.id LIKE ? OR c.name LIKE ? OR q.id = ?)"; + $params[] = "%$s%"; + $params[] = "%$s%"; + $params[] = $clean_id; + } else { + $where[] = "(q.id LIKE ? OR c.name LIKE ?)"; + $params[] = "%$s%"; + $params[] = "%$s%"; + } } if (!empty($_GET['customer_id'])) { $where[] = "q.customer_id = ?"; @@ -1568,9 +1881,18 @@ switch ($page) { $params = [$type]; if (!empty($_GET['search'])) { - $where[] = "(v.id LIKE ? OR c.name LIKE ?)"; - $params[] = "%{$_GET['search']}%"; - $params[] = "%{$_GET['search']}%"; + $s = $_GET['search']; + $clean_id = preg_replace('/[^0-9]/', '', $s); + if ($clean_id !== '') { + $where[] = "(v.id LIKE ? OR c.name LIKE ? OR v.id = ?)"; + $params[] = "%$s%"; + $params[] = "%$s%"; + $params[] = $clean_id; + } else { + $where[] = "(v.id LIKE ? OR c.name LIKE ?)"; + $params[] = "%$s%"; + $params[] = "%$s%"; + } } if (!empty($_GET['customer_id'])) { @@ -1619,10 +1941,21 @@ switch ($page) { $where = ["1=1"]; $params = []; if (!empty($_GET['search'])) { - $where[] = "(sr.id LIKE ? OR c.name LIKE ? OR sr.invoice_id LIKE ?)"; - $params[] = "%{$_GET['search']}%"; - $params[] = "%{$_GET['search']}%"; - $params[] = "%{$_GET['search']}%"; + $s = $_GET['search']; + $clean_id = preg_replace('/[^0-9]/', '', $s); + if ($clean_id !== '') { + $where[] = "(sr.id LIKE ? OR c.name LIKE ? OR sr.invoice_id LIKE ? OR sr.id = ? OR sr.invoice_id = ?)"; + $params[] = "%$s%"; + $params[] = "%$s%"; + $params[] = "%$s%"; + $params[] = $clean_id; + $params[] = $clean_id; + } else { + $where[] = "(sr.id LIKE ? OR c.name LIKE ? OR sr.invoice_id LIKE ?)"; + $params[] = "%$s%"; + $params[] = "%$s%"; + $params[] = "%$s%"; + } } $whereSql = implode(" AND ", $where); $stmt = db()->prepare("SELECT sr.*, c.name as customer_name, i.total_with_vat as invoice_total @@ -1640,10 +1973,21 @@ switch ($page) { $where = ["1=1"]; $params = []; if (!empty($_GET['search'])) { - $where[] = "(pr.id LIKE ? OR c.name LIKE ? OR pr.invoice_id LIKE ?)"; - $params[] = "%{$_GET['search']}%"; - $params[] = "%{$_GET['search']}%"; - $params[] = "%{$_GET['search']}%"; + $s = $_GET['search']; + $clean_id = preg_replace('/[^0-9]/', '', $s); + if ($clean_id !== '') { + $where[] = "(pr.id LIKE ? OR c.name LIKE ? OR pr.invoice_id LIKE ? OR pr.id = ? OR pr.invoice_id = ?)"; + $params[] = "%$s%"; + $params[] = "%$s%"; + $params[] = "%$s%"; + $params[] = $clean_id; + $params[] = $clean_id; + } else { + $where[] = "(pr.id LIKE ? OR c.name LIKE ? OR pr.invoice_id LIKE ?)"; + $params[] = "%$s%"; + $params[] = "%$s%"; + $params[] = "%$s%"; + } } $whereSql = implode(" AND ", $where); $stmt = db()->prepare("SELECT pr.*, c.name as supplier_name, i.total_with_vat as invoice_total @@ -2019,6 +2363,14 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; + 0): ?> +
+ + . + +
+ +