add 0 item to be sold

This commit is contained in:
Flatlogic Bot 2026-02-18 05:01:23 +00:00
parent 8d434a18ee
commit fc756a875a

156
index.php
View File

@ -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';
<label class="form-label" data-en="Address" data-ar="العنوان">Address</label>
<textarea name="settings[company_address]" class="form-control" rows="3"><?= htmlspecialchars($data['settings']['company_address'] ?? '') ?></textarea>
</div>
<div class="col-md-4">
<div class="col-md-4 mt-3">
<label class="form-label" data-en="Allow Selling Out of Stock" data-ar="السماح بالبيع عند نفاذ المخزون">Allow Selling Out of Stock</label>
<select name="settings[allow_zero_stock_sell]" class="form-select">
<option value="0" <?= ($data['settings']['allow_zero_stock_sell'] ?? '0') === '0' ? 'selected' : '' ?> data-en="Disabled" data-ar="معطل">Disabled</option>
<option value="1" <?= ($data['settings']['allow_zero_stock_sell'] ?? '1') === '1' ? 'selected' : '' ?> data-en="Enabled" data-ar="مفعل">Enabled</option>
</select>
</div>
<div class="col-md-4 mt-3">
<label class="form-label" data-en="Company Logo" data-ar="شعار الشركة">Company Logo</label>
<input type="file" name="company_logo" class="form-control" accept="image/*">
<?php if (!empty($data['settings']['company_logo'])): ?>
@ -6900,6 +6988,22 @@ document.addEventListener('DOMContentLoaded', function() {
function addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl, customData = null) {
if (suggestions) suggestions.style.display = 'none';
if (searchInput) searchInput.value = '';
const allowZeroStock = companySettings.allow_zero_stock_sell === '1';
const currentStock = parseFloat(item.stock_quantity) || 0;
if (invoiceType === 'sale' && !allowZeroStock && !customData) {
const existingInTable = Array.from(tableBody.querySelectorAll('.item-row')).find(row => row.querySelector('.item-id-input').value == item.id);
let currentQtyInTable = 0;
if (existingInTable) {
currentQtyInTable = parseFloat(existingInTable.querySelector('.item-qty').value) || 0;
}
if (currentQtyInTable + 1 > currentStock) {
alert('Insufficient stock! Available: ' + currentStock);
return;
}
}
const existingRow = Array.from(tableBody.querySelectorAll('.item-id-input')).find(input => input.value == item.id);
if (existingRow && !customData) {
@ -6919,6 +7023,7 @@ document.addEventListener('DOMContentLoaded', function() {
row.innerHTML = `
<td>
<input type="hidden" name="item_ids[]" class="item-id-input" value="${item.id}">
<input type="hidden" class="item-row-stock" value="${item.stock_quantity}">
<input type="hidden" class="item-vat-rate" value="${vatRate}">
<div><strong>${item.name_en}</strong></div>
<div class="small text-muted">${item.name_ar} (${item.sku})</div>
@ -6958,7 +7063,18 @@ document.addEventListener('DOMContentLoaded', function() {
}
function attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl) {
row.querySelector('.item-qty').addEventListener('input', () => recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl));
row.querySelector('.item-qty').addEventListener('input', function() {
const allowZeroStock = companySettings.allow_zero_stock_sell === '1';
if (invoiceType === 'sale' && !allowZeroStock) {
const stock = parseFloat(row.querySelector('.item-row-stock').value) || 0;
const qty = parseFloat(this.value) || 0;
if (qty > stock) {
alert('Insufficient stock! Available: ' + stock);
this.value = stock;
}
}
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
});
row.querySelector('.item-price').addEventListener('input', () => recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl));
row.querySelector('.remove-row').addEventListener('click', function() {
row.remove();