diff --git a/index.php b/index.php index 9e11d48..4121bdf 100644 --- a/index.php +++ b/index.php @@ -328,11 +328,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $items = json_decode($_POST['items'] ?? '[]', true); // Fetch settings - $settings_res = $db->query("SELECT * FROM settings WHERE `key` IN ('loyalty_enabled', 'loyalty_points_per_unit')")->fetchAll(PDO::FETCH_ASSOC); + $settings_res = $db->query("SELECT * FROM settings WHERE `key` IN ('loyalty_enabled', 'loyalty_points_per_unit', 'allow_zero_stock_sell')")->fetchAll(PDO::FETCH_ASSOC); $app_settings = []; foreach ($settings_res as $s) $app_settings[$s['key']] = $s['value']; $loyalty_enabled = ($app_settings['loyalty_enabled'] ?? '0') === '1'; $points_per_unit = (float)($app_settings['loyalty_points_per_unit'] ?? 1); + $allow_zero_stock = ($app_settings['allow_zero_stock_sell'] ?? '0') === '1'; if (empty($items)) { throw new Exception("Cart is empty"); @@ -405,9 +406,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $price = (float)$item['price']; $subtotal = $qty * $price; - $stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?"); - $stmtVat->execute([$item['id']]); - $vat_rate = (float)$stmtVat->fetchColumn(); + $stmtItem = $db->prepare("SELECT vat_rate, stock_quantity, name_en FROM stock_items WHERE id = ?"); + $stmtItem->execute([$item['id']]); + $item_info = $stmtItem->fetch(PDO::FETCH_ASSOC); + $vat_rate = (float)($item_info['vat_rate'] ?? 0); + $stock_qty = (float)($item_info['stock_quantity'] ?? 0); + + if (!$allow_zero_stock && ($qty > $stock_qty)) { + throw new Exception("Insufficient stock for item: " . ($item_info['name_en'] ?? 'Unknown')); + } + + $item_vat = $subtotal * ($vat_rate / 100); $item_vat = $subtotal * ($vat_rate / 100); $total_vat += $item_vat; @@ -755,6 +764,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $db = db(); $db->beginTransaction(); try { + // Fetch settings + $allow_zero_stock = $db->query("SELECT `value` FROM settings WHERE `key` = 'allow_zero_stock_sell'")->fetchColumn() === '1'; + $subtotal = 0; $total_vat = 0; @@ -763,10 +775,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $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(); + // Fetch item info + $stmtItem = $db->prepare("SELECT vat_rate, stock_quantity, name_en FROM stock_items WHERE id = ?"); + $stmtItem->execute([$item_id]); + $item_info = $stmtItem->fetch(PDO::FETCH_ASSOC); + $vat_rate = (float)($item_info['vat_rate'] ?? 0); + $stock_qty = (float)($item_info['stock_quantity'] ?? 0); + + if ($type === 'sale' && !$allow_zero_stock && ($qty > $stock_qty)) { + throw new Exception("Insufficient stock for item: " . ($item_info['name_en'] ?? 'Unknown')); + } + + $line_total = $qty * $price; + $line_vat = $line_total * ($vat_rate / 100); $line_total = $qty * $price; $line_vat = $line_total * ($vat_rate / 100); @@ -889,6 +910,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $db = db(); $db->beginTransaction(); try { + // Fetch settings + $allow_zero_stock = $db->query("SELECT `value` FROM settings WHERE `key` = 'allow_zero_stock_sell'")->fetchColumn() === '1'; + // Get old invoice type and items to revert stock $stmt = $db->prepare("SELECT type, paid_amount FROM invoices WHERE id = ?"); $stmt->execute([$invoice_id]); @@ -921,9 +945,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $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(); + $stmtItem = $db->prepare("SELECT vat_rate, stock_quantity, name_en FROM stock_items WHERE id = ?"); + $stmtItem->execute([$item_id]); + $item_info = $stmtItem->fetch(PDO::FETCH_ASSOC); + $vat_rate = (float)($item_info['vat_rate'] ?? 0); + $stock_qty = (float)($item_info['stock_quantity'] ?? 0); + + if ($type === 'sale' && !$allow_zero_stock && ($qty > $stock_qty)) { + throw new Exception("Insufficient stock for item: " . ($item_info['name_en'] ?? 'Unknown')); + } $line_total = $qty * $price; $line_vat = $line_total * ($vat_rate / 100); @@ -986,15 +1016,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $db = db(); $db->beginTransaction(); try { + $allow_zero_stock = $db->query("SELECT `value` FROM settings WHERE `key` = 'allow_zero_stock_sell'")->fetchColumn() === '1'; + $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(); + + $stmtItem = $db->prepare("SELECT vat_rate, stock_quantity, name_en FROM stock_items WHERE id = ?"); + $stmtItem->execute([$item_id]); + $item_info = $stmtItem->fetch(PDO::FETCH_ASSOC); + $vat_rate = (float)($item_info['vat_rate'] ?? 0); + $stock_qty = (float)($item_info['stock_quantity'] ?? 0); + + if (!$allow_zero_stock && ($qty > $stock_qty)) { + throw new Exception("Insufficient stock for item: " . ($item_info['name_en'] ?? 'Unknown')); + } + + $line_total = $qty * $price; + $line_vat = $line_total * ($vat_rate / 100); $line_total = $qty * $price; $line_vat = $line_total * ($vat_rate / 100); $subtotal += $line_total; @@ -1032,6 +1074,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $db = db(); $db->beginTransaction(); try { + $allow_zero_stock = $db->query("SELECT `value` FROM settings WHERE `key` = 'allow_zero_stock_sell'")->fetchColumn() === '1'; + $stmt = $db->prepare("DELETE FROM quotation_items WHERE quotation_id = ?"); $stmt->execute([$quotation_id]); $subtotal = 0; @@ -1040,9 +1084,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { 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(); + + $stmtItem = $db->prepare("SELECT vat_rate, stock_quantity, name_en FROM stock_items WHERE id = ?"); + $stmtItem->execute([$item_id]); + $item_info = $stmtItem->fetch(PDO::FETCH_ASSOC); + $vat_rate = (float)($item_info['vat_rate'] ?? 0); + $stock_qty = (float)($item_info['stock_quantity'] ?? 0); + + if (!$allow_zero_stock && ($qty > $stock_qty)) { + throw new Exception("Insufficient stock for item: " . ($item_info['name_en'] ?? 'Unknown')); + } + $line_total = $qty * $price; $line_vat = $line_total * ($vat_rate / 100); $subtotal += $line_total; @@ -1080,16 +1132,26 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $db = db(); $db->beginTransaction(); try { + $allow_zero_stock = $db->query("SELECT `value` FROM settings WHERE `key` = 'allow_zero_stock_sell'")->fetchColumn() === '1'; + $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 = $db->prepare("SELECT qi.*, si.stock_quantity, si.name_en FROM quotation_items qi JOIN stock_items si ON qi.item_id = si.id WHERE qi.quotation_id = ?"); $stmt->execute([$quotation_id]); $items = $stmt->fetchAll(); + if (!$allow_zero_stock) { + foreach ($items as $item) { + if ($item['quantity'] > $item['stock_quantity']) { + throw new Exception("Insufficient stock for item: " . ($item['name_en'] ?? 'Unknown')); + } + } + } + // 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']]); @@ -3535,10 +3597,21 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; echo json_encode($custData); ?>, add(product) { + const allowZeroStock = companySettings.allow_zero_stock_sell === '1'; + const currentStock = parseFloat(product.stock_quantity) || 0; + const existing = this.items.find(item => item.id === product.id); if (existing) { + if (!allowZeroStock && (existing.qty + 1) > currentStock) { + alert('Insufficient stock!'); + return; + } existing.qty++; } else { + if (!allowZeroStock && currentStock <= 0) { + alert('Insufficient stock!'); + return; + } this.items.push({...product, qty: 1}); } this.render(); @@ -3550,6 +3623,14 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; updateQty(id, delta) { const item = this.items.find(i => i.id === id); if (item) { + const allowZeroStock = companySettings.allow_zero_stock_sell === '1'; + const currentStock = parseFloat(item.stock_quantity) || 0; + + if (delta > 0 && !allowZeroStock && (item.qty + delta) > currentStock) { + alert('Insufficient stock!'); + return; + } + item.qty += delta; if (item.qty <= 0) this.remove(id); else this.render(); @@ -6103,7 +6184,14 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; -