add 0 item to be sold
This commit is contained in:
parent
8d434a18ee
commit
fc756a875a
156
index.php
156
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';
|
||||
<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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user