adjusting sidebar
This commit is contained in:
parent
1f2d599fe1
commit
b2f7e860d8
@ -30,6 +30,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
sidebar.classList.remove('show');
|
sidebar.classList.remove('show');
|
||||||
overlay.classList.remove('show');
|
overlay.classList.remove('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sidebar Accordion logic: Close other collapses when one is opened
|
||||||
|
const sidebarCollapses = document.querySelectorAll('.sidebar .collapse');
|
||||||
|
sidebarCollapses.forEach(collapseEl => {
|
||||||
|
collapseEl.addEventListener('show.bs.collapse', function () {
|
||||||
|
sidebarCollapses.forEach(otherCollapse => {
|
||||||
|
if (otherCollapse !== collapseEl && otherCollapse.classList.contains('show')) {
|
||||||
|
const bsCollapse = bootstrap.Collapse.getOrCreateInstance(otherCollapse);
|
||||||
|
bsCollapse.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function setLanguage(lang) {
|
function setLanguage(lang) {
|
||||||
|
|||||||
4
check_settings.php
Normal file
4
check_settings.php
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
$settings = db()->query("SELECT * FROM settings")->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
echo json_encode($settings, JSON_PRETTY_PRINT);
|
||||||
245
index.php
245
index.php
@ -106,6 +106,27 @@ function can(string $permission): bool {
|
|||||||
return is_array($perms) && in_array($permission, $perms);
|
return is_array($perms) && in_array($permission, $perms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Missing helper functions
|
||||||
|
function getLoyaltyMultiplier($tier) {
|
||||||
|
switch (strtolower((string)$tier)) {
|
||||||
|
case 'gold': return 2.0;
|
||||||
|
case 'silver': return 1.5;
|
||||||
|
default: return 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberToWords($num) {
|
||||||
|
$num = (int)$num;
|
||||||
|
if ($num == 0) return "zero";
|
||||||
|
$ones = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"];
|
||||||
|
$tens = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"];
|
||||||
|
if ($num < 20) return $ones[$num];
|
||||||
|
if ($num < 100) return $tens[(int)($num / 10)] . ($num % 10 ? "-" . $ones[$num % 10] : "");
|
||||||
|
if ($num < 1000) return $ones[(int)($num / 100)] . " hundred" . ($num % 100 ? " and " . numberToWords($num % 100) : "");
|
||||||
|
if ($num < 1000000) return numberToWords((int)($num / 1000)) . " thousand" . ($num % 1000 ? " " . numberToWords($num % 1000) : "");
|
||||||
|
return (string)$num;
|
||||||
|
}
|
||||||
|
|
||||||
// Login Logic
|
// Login Logic
|
||||||
$login_error = '';
|
$login_error = '';
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
|
||||||
@ -719,6 +740,39 @@ if (isset($_POST['add_hr_department'])) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($_POST['update_settings'])) {
|
||||||
|
if (can('settings_view')) {
|
||||||
|
$db = db();
|
||||||
|
if (isset($_POST['settings']) && is_array($_POST['settings'])) {
|
||||||
|
foreach ($_POST['settings'] as $key => $value) {
|
||||||
|
$stmt = $db->prepare("INSERT INTO settings (`key`, `value`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `value` = ?");
|
||||||
|
$stmt->execute([$key, $value, $value]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle file uploads
|
||||||
|
$files = ['company_logo', 'favicon', 'manager_signature'];
|
||||||
|
foreach ($files as $file_key) {
|
||||||
|
if (isset($_FILES[$file_key]) && $_FILES[$file_key]['error'] === 0) {
|
||||||
|
$ext = pathinfo($_FILES[$file_key]['name'], PATHINFO_EXTENSION);
|
||||||
|
$filename = 'uploads/' . $file_key . '_' . time() . '.' . $ext;
|
||||||
|
if (!is_dir('uploads')) mkdir('uploads', 0777, true);
|
||||||
|
if (move_uploaded_file($_FILES[$file_key]['tmp_name'], $filename)) {
|
||||||
|
$stmt = $db->prepare("INSERT INTO settings (`key`, `value`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `value` = ?");
|
||||||
|
$stmt->execute([$file_key, $filename, $filename]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$message = "Settings updated successfully!";
|
||||||
|
|
||||||
|
// Reload settings for current request
|
||||||
|
$settings_raw = db()->query("SELECT * FROM settings")->fetchAll();
|
||||||
|
foreach ($settings_raw as $s) {
|
||||||
|
$data['settings'][$s['key']] = $s['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Backup Handlers ---
|
// --- Backup Handlers ---
|
||||||
if (isset($_POST['create_backup'])) {
|
if (isset($_POST['create_backup'])) {
|
||||||
if (can('users_view')) { // Admin check
|
if (can('users_view')) { // Admin check
|
||||||
@ -1825,10 +1879,13 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const companySettings = <?= json_encode($data['settings']) ?>;
|
||||||
|
</script>
|
||||||
|
|
||||||
<?php if ($message): ?>
|
<?php if ($message): ?>
|
||||||
<script>
|
<script>
|
||||||
const companySettings = <?= json_encode($data['settings']) ?>;
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
let msg = <?= json_encode($message) ?>;
|
let msg = <?= json_encode($message) ?>;
|
||||||
let type = 'success';
|
let type = 'success';
|
||||||
let title = 'Success';
|
let title = 'Success';
|
||||||
@ -2735,7 +2792,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<span class="input-group-text bg-transparent border-end-0"><i class="bi bi-search"></i></span>
|
<span class="input-group-text bg-transparent border-end-0"><i class="bi bi-search"></i></span>
|
||||||
<input type="text" id="productSearch" class="form-control border-start-0" placeholder="Search products by name or SKU..." data-en="Search products..." data-ar="بحث عن منتجات...">
|
<input type="text" id="productSearch" class="form-control border-start-0" placeholder="Search products by name or SKU..." data-en="Search products..." data-ar="بحث عن منتجات...">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group" style="width: 200px;">
|
<div class="input-group flex-grow-1">
|
||||||
<span class="input-group-text bg-light border-end-0"><i class="bi bi-upc-scan"></i></span>
|
<span class="input-group-text bg-light border-end-0"><i class="bi bi-upc-scan"></i></span>
|
||||||
<input type="text" id="barcodeInput" class="form-control border-start-0" placeholder="Scan barcode..." data-en="Scan barcode..." data-ar="امسح الباركود..." autofocus>
|
<input type="text" id="barcodeInput" class="form-control border-start-0" placeholder="Scan barcode..." data-en="Scan barcode..." data-ar="امسح الباركود..." autofocus>
|
||||||
</div>
|
</div>
|
||||||
@ -2746,7 +2803,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
</div>
|
</div>
|
||||||
<div class="product-grid" id="productGrid">
|
<div class="product-grid" id="productGrid">
|
||||||
<?php foreach ($products as $p): ?>
|
<?php foreach ($products as $p): ?>
|
||||||
<div class="product-card" data-id="<?= $p['id'] ?>" data-name-en="<?= htmlspecialchars($p['name_en']) ?>" data-name-ar="<?= htmlspecialchars($p['name_ar']) ?>" data-price="<?= $p['sale_price'] ?>" data-sku="<?= htmlspecialchars($p['sku']) ?>">
|
<div class="product-card" data-id="<?= $p['id'] ?>" data-name-en="<?= htmlspecialchars($p['name_en']) ?>" data-name-ar="<?= htmlspecialchars($p['name_ar']) ?>" data-price="<?= $p['sale_price'] ?>" data-sku="<?= htmlspecialchars($p['sku']) ?>" data-stock-quantity="<?= (float)$p['stock_quantity'] ?>">
|
||||||
<?php if ($p['image_path']): ?>
|
<?php if ($p['image_path']): ?>
|
||||||
<img src="<?= htmlspecialchars($p['image_path']) ?>" alt="<?= htmlspecialchars($p['name_en']) ?>">
|
<img src="<?= htmlspecialchars($p['image_path']) ?>" alt="<?= htmlspecialchars($p['name_en']) ?>">
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
@ -2872,37 +2929,52 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
echo json_encode($custData);
|
echo json_encode($custData);
|
||||||
?>,
|
?>,
|
||||||
add(product) {
|
add(product) {
|
||||||
const allowZeroStock = companySettings.allow_zero_stock_sell === '1';
|
if (!this.items) this.items = [];
|
||||||
|
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
|
||||||
const currentStock = parseFloat(product.stock_quantity) || 0;
|
const currentStock = parseFloat(product.stock_quantity) || 0;
|
||||||
|
|
||||||
const existing = this.items.find(item => item.id === product.id);
|
const existing = this.items.find(item => item.id === product.id);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
if (!allowZeroStock && (existing.qty + 1) > currentStock) {
|
if (!allowZeroStock && (existing.qty + 1) > currentStock) {
|
||||||
alert('Insufficient stock!');
|
Swal.fire('Error', 'Insufficient stock!', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
existing.qty++;
|
existing.qty++;
|
||||||
} else {
|
} else {
|
||||||
if (!allowZeroStock && currentStock <= 0) {
|
if (!allowZeroStock && currentStock <= 0) {
|
||||||
alert('Insufficient stock!');
|
Swal.fire('Error', 'Insufficient stock!', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.items.push({...product, qty: 1});
|
this.items.push({...product, qty: 1});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
|
// Add visual feedback
|
||||||
|
const lang = document.documentElement.lang || 'en';
|
||||||
|
const displayName = lang === 'ar' ? (product.nameAr || product.nameEn) : (product.nameEn || product.nameAr);
|
||||||
|
Swal.fire({
|
||||||
|
toast: true,
|
||||||
|
position: 'top-end',
|
||||||
|
icon: 'success',
|
||||||
|
title: (lang === 'ar' ? 'تم إضافة: ' : 'Added: ') + displayName,
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 800
|
||||||
|
});
|
||||||
},
|
},
|
||||||
remove(id) {
|
remove(id) {
|
||||||
this.items = this.items.filter(item => item.id !== id);
|
this.items = this.items.filter(item => item.id !== id);
|
||||||
this.render();
|
this.render();
|
||||||
},
|
},
|
||||||
updateQty(id, delta) {
|
updateQty(id, delta) {
|
||||||
|
if (!this.items) return;
|
||||||
const item = this.items.find(i => i.id === id);
|
const item = this.items.find(i => i.id === id);
|
||||||
if (item) {
|
if (item) {
|
||||||
const allowZeroStock = companySettings.allow_zero_stock_sell === '1';
|
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
|
||||||
const currentStock = parseFloat(item.stock_quantity) || 0;
|
const currentStock = parseFloat(item.stock_quantity) || 0;
|
||||||
|
|
||||||
if (delta > 0 && !allowZeroStock && (item.qty + delta) > currentStock) {
|
if (delta > 0 && !allowZeroStock && (item.qty + delta) > currentStock) {
|
||||||
alert('Insufficient stock!');
|
Swal.fire('Error', 'Insufficient stock!', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3091,70 +3163,94 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
if (!silent) this.openHeldCartsModal();
|
if (!silent) this.openHeldCartsModal();
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
const container = document.getElementById('cartItems');
|
try {
|
||||||
const lang = document.documentElement.lang || 'en';
|
const container = document.getElementById('cartItems');
|
||||||
if (this.items.length === 0) {
|
if (!container) return;
|
||||||
container.innerHTML = `<div class="text-center text-muted mt-5"><i class="bi bi-cart-x" style="font-size: 3rem;"></i><p data-en="Cart is empty" data-ar="السلة فارغة">${lang === 'ar' ? 'السلة فارغة' : 'Cart is empty'}</p></div>`;
|
|
||||||
document.getElementById('posSubtotal').innerText = 'OMR 0.000';
|
|
||||||
document.getElementById('posTotal').innerText = 'OMR 0.000';
|
|
||||||
document.getElementById('checkoutBtn').disabled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let subtotal = 0;
|
const lang = document.documentElement.lang || 'en';
|
||||||
container.innerHTML = this.items.map(item => {
|
const items = Array.isArray(this.items) ? this.items : [];
|
||||||
subtotal += item.price * item.qty;
|
|
||||||
const displayName = lang === 'ar' ? item.nameAr : item.nameEn;
|
|
||||||
return `
|
|
||||||
<div class="cart-item">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<div class="fw-bold small">${displayName}</div>
|
|
||||||
<div class="text-muted smaller">OMR ${parseFloat(item.price).toFixed(3)}</div>
|
|
||||||
</div>
|
|
||||||
<div class="qty-controls mx-3">
|
|
||||||
<button class="qty-btn" onclick="cart.updateQty(${item.id}, -1)">-</button>
|
|
||||||
<span class="small fw-bold">${item.qty}</span>
|
|
||||||
<button class="qty-btn" onclick="cart.updateQty(${item.id}, 1)">+</button>
|
|
||||||
</div>
|
|
||||||
<div class="fw-bold small">OMR ${(item.price * item.qty).toFixed(3)}</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
let discountAmount = 0;
|
if (items.length === 0) {
|
||||||
if (this.discount) {
|
container.innerHTML = `<div class="text-center text-muted mt-5"><i class="bi bi-cart-x" style="font-size: 3rem;"></i><p data-en="Cart is empty" data-ar="السلة فارغة">${lang === 'ar' ? 'السلة فارغة' : 'Cart is empty'}</p></div>`;
|
||||||
if (this.discount.type === 'percentage') {
|
const subtotalEl = document.getElementById('posSubtotal');
|
||||||
discountAmount = subtotal * (parseFloat(this.discount.value) / 100);
|
const totalEl = document.getElementById('posTotal');
|
||||||
} else {
|
const checkoutBtn = document.getElementById('checkoutBtn');
|
||||||
discountAmount = parseFloat(this.discount.value);
|
|
||||||
|
if (subtotalEl) subtotalEl.innerText = 'OMR 0.000';
|
||||||
|
if (totalEl) totalEl.innerText = 'OMR 0.000';
|
||||||
|
if (checkoutBtn) checkoutBtn.disabled = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let subtotal = 0;
|
||||||
|
container.innerHTML = items.map(item => {
|
||||||
|
const price = parseFloat(item.price) || 0;
|
||||||
|
const qty = parseFloat(item.qty) || 0;
|
||||||
|
subtotal += price * qty;
|
||||||
|
const displayName = (lang === 'ar' ? (item.nameAr || item.nameEn) : (item.nameEn || item.nameAr)) || 'Unknown Item';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="cart-item">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<div class="fw-bold small">${displayName}</div>
|
||||||
|
<div class="text-muted smaller">OMR ${price.toFixed(3)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="qty-controls mx-3">
|
||||||
|
<button class="qty-btn" onclick="cart.updateQty(${item.id}, -1)">-</button>
|
||||||
|
<span class="small fw-bold">${qty}</span>
|
||||||
|
<button class="qty-btn" onclick="cart.updateQty(${item.id}, 1)">+</button>
|
||||||
|
</div>
|
||||||
|
<div class="fw-bold small">OMR ${(price * qty).toFixed(3)}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
let discountAmount = 0;
|
||||||
|
if (this.discount) {
|
||||||
|
if (this.discount.type === 'percentage') {
|
||||||
|
discountAmount = subtotal * (parseFloat(this.discount.value) / 100);
|
||||||
|
} else {
|
||||||
|
discountAmount = parseFloat(this.discount.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let loyaltyRedeemedValue = 0;
|
||||||
|
const redeemSwitch = document.getElementById('redeemLoyalty');
|
||||||
|
if (redeemSwitch && redeemSwitch.checked) {
|
||||||
|
const maxRedeemValue = subtotal - discountAmount;
|
||||||
|
const redeemRate = (this.loyaltySettings && this.loyaltySettings.redeemPointsPerUnit) ? this.loyaltySettings.redeemPointsPerUnit : 100;
|
||||||
|
const availableRedeemValue = (parseFloat(this.customerPoints) || 0) / redeemRate;
|
||||||
|
loyaltyRedeemedValue = Math.min(Math.max(0, maxRedeemValue), availableRedeemValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = Math.max(0, subtotal - discountAmount - loyaltyRedeemedValue);
|
||||||
|
const multiplier = parseFloat(this.customerMultiplier) || 1.0;
|
||||||
|
const pointsToEarn = Math.floor(total * multiplier);
|
||||||
|
|
||||||
|
const subtotalDisplay = document.getElementById('posSubtotal');
|
||||||
|
if (subtotalDisplay) subtotalDisplay.innerText = 'OMR ' + subtotal.toFixed(3);
|
||||||
|
|
||||||
|
let totalHtml = '';
|
||||||
|
if (discountAmount > 0) totalHtml += `<div class="smaller text-danger">- Disc: OMR ${discountAmount.toFixed(3)}</div>`;
|
||||||
|
if (loyaltyRedeemedValue > 0) totalHtml += `<div class="smaller text-success">- Loyalty: OMR ${loyaltyRedeemedValue.toFixed(3)}</div>`;
|
||||||
|
|
||||||
|
const customerId = document.getElementById('posCustomer') ? document.getElementById('posCustomer').value : '';
|
||||||
|
if (customerId) {
|
||||||
|
totalHtml += `<div class="smaller text-info">+ Earn: ${pointsToEarn} pts</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalHtml += 'OMR ' + total.toFixed(3);
|
||||||
|
|
||||||
|
const totalDisplay = document.getElementById('posTotal');
|
||||||
|
if (totalDisplay) {
|
||||||
|
totalDisplay.innerHTML = totalHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkoutBtn = document.getElementById('checkoutBtn');
|
||||||
|
if (checkoutBtn) checkoutBtn.disabled = false;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Cart render error:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let loyaltyRedeemedValue = 0;
|
|
||||||
const redeemSwitch = document.getElementById('redeemLoyalty');
|
|
||||||
if (redeemSwitch && redeemSwitch.checked) {
|
|
||||||
const maxRedeemValue = subtotal - discountAmount;
|
|
||||||
const availableRedeemValue = this.customerPoints / 100;
|
|
||||||
loyaltyRedeemedValue = Math.min(maxRedeemValue, availableRedeemValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
const total = subtotal - discountAmount - loyaltyRedeemedValue;
|
|
||||||
const pointsToEarn = Math.floor(total * (this.customerMultiplier || 1.0));
|
|
||||||
|
|
||||||
document.getElementById('posSubtotal').innerText = 'OMR ' + subtotal.toFixed(3);
|
|
||||||
|
|
||||||
let totalHtml = '';
|
|
||||||
if (discountAmount > 0) totalHtml += `<div class="smaller text-danger">- Disc: OMR ${discountAmount.toFixed(3)}</div>`;
|
|
||||||
if (loyaltyRedeemedValue > 0) totalHtml += `<div class="smaller text-success">- Loyalty: OMR ${loyaltyRedeemedValue.toFixed(3)}</div>`;
|
|
||||||
|
|
||||||
if (document.getElementById('posCustomer').value) {
|
|
||||||
totalHtml += `<div class="smaller text-info">+ Earn: ${pointsToEarn} pts</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalHtml += 'OMR ' + total.toFixed(3);
|
|
||||||
|
|
||||||
document.getElementById('posTotal').innerHTML = totalHtml;
|
|
||||||
document.getElementById('checkoutBtn').disabled = false;
|
|
||||||
},
|
},
|
||||||
async checkout() {
|
async checkout() {
|
||||||
if (this.items.length === 0) return;
|
if (this.items.length === 0) return;
|
||||||
@ -3482,7 +3578,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
id: parseInt(card.dataset.id),
|
id: parseInt(card.dataset.id),
|
||||||
nameEn: card.dataset.nameEn,
|
nameEn: card.dataset.nameEn,
|
||||||
nameAr: card.dataset.nameAr,
|
nameAr: card.dataset.nameAr,
|
||||||
price: parseFloat(card.dataset.price)
|
price: parseFloat(card.dataset.price),
|
||||||
|
stock_quantity: parseFloat(card.dataset.stockQuantity)
|
||||||
};
|
};
|
||||||
cart.add(product);
|
cart.add(product);
|
||||||
});
|
});
|
||||||
@ -3512,7 +3609,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
id: parseInt(card.dataset.id),
|
id: parseInt(card.dataset.id),
|
||||||
nameEn: card.dataset.nameEn,
|
nameEn: card.dataset.nameEn,
|
||||||
nameAr: card.dataset.nameAr,
|
nameAr: card.dataset.nameAr,
|
||||||
price: parseFloat(card.dataset.price)
|
price: parseFloat(card.dataset.price),
|
||||||
|
sku: card.dataset.sku,
|
||||||
|
stock_quantity: parseFloat(card.dataset.stockQuantity)
|
||||||
};
|
};
|
||||||
cart.add(product);
|
cart.add(product);
|
||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
@ -7373,7 +7472,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (suggestions) suggestions.style.display = 'none';
|
if (suggestions) suggestions.style.display = 'none';
|
||||||
if (searchInput) searchInput.value = '';
|
if (searchInput) searchInput.value = '';
|
||||||
|
|
||||||
const allowZeroStock = companySettings.allow_zero_stock_sell === '1';
|
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
|
||||||
const currentStock = parseFloat(item.stock_quantity) || 0;
|
const currentStock = parseFloat(item.stock_quantity) || 0;
|
||||||
|
|
||||||
if (invoiceType === 'sale' && !allowZeroStock && !customData) {
|
if (invoiceType === 'sale' && !allowZeroStock && !customData) {
|
||||||
@ -7448,7 +7547,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
function attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl) {
|
function attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl) {
|
||||||
row.querySelector('.item-qty').addEventListener('input', function() {
|
row.querySelector('.item-qty').addEventListener('input', function() {
|
||||||
const allowZeroStock = companySettings.allow_zero_stock_sell === '1';
|
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
|
||||||
if (invoiceType === 'sale' && !allowZeroStock) {
|
if (invoiceType === 'sale' && !allowZeroStock) {
|
||||||
const stock = parseFloat(row.querySelector('.item-row-stock').value) || 0;
|
const stock = parseFloat(row.querySelector('.item-row-stock').value) || 0;
|
||||||
const qty = parseFloat(this.value) || 0;
|
const qty = parseFloat(this.value) || 0;
|
||||||
|
|||||||
@ -14,3 +14,6 @@
|
|||||||
2026-02-18 11:43:14 - POST: {"register_id":"1","opening_balance":"0.000","open_register":""}
|
2026-02-18 11:43:14 - POST: {"register_id":"1","opening_balance":"0.000","open_register":""}
|
||||||
2026-02-18 11:50:03 - POST: {"register_id":"1","opening_balance":"0.000","open_register":""}
|
2026-02-18 11:50:03 - POST: {"register_id":"1","opening_balance":"0.000","open_register":""}
|
||||||
2026-02-18 12:01:49 - POST: {"session_id":"1","cash_in_hand":"0","notes":"","close_register":""}
|
2026-02-18 12:01:49 - POST: {"session_id":"1","cash_in_hand":"0","notes":"","close_register":""}
|
||||||
|
2026-02-18 13:05:39 - POST: {"settings":{"company_name":"Bahjet Al-Safa Trading","company_phone":"99359472","company_email":"aalabry@gmail.com","vat_number":"OM25418","company_address":"AL Hamra\r\nOman","allow_zero_stock_sell":"0","loyalty_enabled":"0","loyalty_points_per_unit":"1","loyalty_redeem_points_per_unit":"100"},"update_settings":""}
|
||||||
|
2026-02-18 13:06:21 - POST: {"settings":{"company_name":"Bahjet Al-Safa Trading","company_phone":"99359472","company_email":"aalabry@gmail.com","vat_number":"OM25418","company_address":"AL Hamra\r\nOman","allow_zero_stock_sell":"1","loyalty_enabled":"0","loyalty_points_per_unit":"1","loyalty_redeem_points_per_unit":"100"},"update_settings":""}
|
||||||
|
2026-02-18 13:33:14 - POST: {"action":"save_pos_transaction","customer_id":"1","payments":"[{\"method\":\"credit\",\"amount\":1.19}]","total_amount":"1.19","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":2,\"price\":0.3825},{\"id\":2,\"qty\":2,\"price\":0.2125}]"}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user