Revert to version 557ff40

This commit is contained in:
Flatlogic Bot 2026-02-17 02:28:19 +00:00
parent 5ee3913fe1
commit f20efe1066

693
index.php
View File

@ -725,6 +725,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$customer_id = $_POST['customer_id'] ?: null;
$quotation_date = $_POST['quotation_date'] ?: date('Y-m-d');
$valid_until = $_POST['valid_until'] ?: null;
$terms_conditions = $_POST['terms_conditions'] ?? '';
$item_ids = $_POST['item_ids'] ?? [];
$quantities = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
@ -749,8 +750,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$items_data[] = ['id' => $item_id, 'qty' => $qty, 'price' => $price, 'total' => $line_total];
}
$total_with_vat = $subtotal + $total_vat;
$stmt = $db->prepare("INSERT INTO quotations (customer_id, quotation_date, valid_until, total_amount, vat_amount, total_with_vat) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$customer_id, $quotation_date, $valid_until, $subtotal, $total_vat, $total_with_vat]);
$stmt = $db->prepare("INSERT INTO quotations (customer_id, quotation_date, valid_until, total_amount, vat_amount, total_with_vat, terms_conditions) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$customer_id, $quotation_date, $valid_until, $subtotal, $total_vat, $total_with_vat, $terms_conditions]);
$quotation_id = $db->lastInsertId();
foreach ($items_data as $item) {
$stmt = $db->prepare("INSERT INTO quotation_items (quotation_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
@ -771,6 +772,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$quotation_date = $_POST['quotation_date'] ?: date('Y-m-d');
$valid_until = $_POST['valid_until'] ?: null;
$status = $_POST['status'] ?? 'pending';
$terms_conditions = $_POST['terms_conditions'] ?? '';
$item_ids = $_POST['item_ids'] ?? [];
$quantities = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
@ -797,8 +799,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$items_data[] = ['id' => $item_id, 'qty' => $qty, 'price' => $price, 'total' => $line_total];
}
$total_with_vat = $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([$customer_id, $quotation_date, $valid_until, $status, $subtotal, $total_vat, $total_with_vat, $quotation_id]);
$stmt = $db->prepare("UPDATE quotations SET customer_id = ?, quotation_date = ?, valid_until = ?, status = ?, total_amount = ?, vat_amount = ?, total_with_vat = ?, terms_conditions = ? WHERE id = ?");
$stmt->execute([$customer_id, $quotation_date, $valid_until, $status, $subtotal, $total_vat, $total_with_vat, $terms_conditions, $quotation_id]);
foreach ($items_data as $item) {
$stmt = $db->prepare("INSERT INTO quotation_items (quotation_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$quotation_id, $item['id'], $item['qty'], $item['price'], $item['total']]);
@ -838,8 +840,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$items = $stmt->fetchAll();
// 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']]);
$stmt = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, type, status, total_amount, vat_amount, total_with_vat, paid_amount, terms_conditions) VALUES (?, CURDATE(), 'sale', 'unpaid', ?, ?, ?, 0, ?)");
$stmt->execute([$q['customer_id'], $q['total_amount'], $q['vat_amount'], $q['total_with_vat'], $q['terms_conditions']]);
$invoice_id = $db->lastInsertId();
foreach ($items as $item) {
@ -1125,7 +1127,7 @@ switch ($page) {
$params[] = $_GET['end_date'];
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT q.*, c.name as customer_name
$stmt = db()->prepare("SELECT q.*, c.name as customer_name, c.phone as customer_phone, c.tax_id as customer_tax_id
FROM quotations q
LEFT JOIN customers c ON q.customer_id = c.id
WHERE $whereSql
@ -2791,7 +2793,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<td class="text-end">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-info view-quotation-btn" data-json="<?= htmlspecialchars(json_encode($q)) ?>" title="View"><i class="bi bi-eye"></i></button>
<button class="btn btn-outline-secondary" onclick="window.viewAndPrintQuotation(<?= htmlspecialchars(json_encode($q)) ?>)" title="Print"><i class="bi bi-printer"></i></button>
<button class="btn btn-outline-secondary print-quotation-btn" data-json="<?= htmlspecialchars(json_encode($q)) ?>" title="Print"><i class="bi bi-printer"></i></button>
<button class="btn btn-outline-primary edit-quotation-btn" data-json="<?= htmlspecialchars(json_encode($q)) ?>" data-bs-toggle="modal" data-bs-target="#editQuotationModal" title="Edit"><i class="bi bi-pencil-square"></i></button>
<?php if ($q['status'] === 'pending'): ?>
<button class="btn btn-outline-success convert-quotation-btn" data-id="<?= $q['id'] ?>" title="Convert to Invoice"><i class="bi bi-receipt"></i></button>
@ -3441,7 +3443,175 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// View Quotation Logic
window.viewAndPrintQuotation = function(data, autoPrint = false) {
if (!data || !data.items) {
console.error('Invalid quotation data');
return;
}
const modalEl = document.getElementById('viewQuotationModal');
if (!modalEl) return;
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
const content = document.getElementById('quotationPrintableArea');
let itemsHtml = '';
data.items.forEach((item, index) => {
itemsHtml += `
<tr>
<td>${index + 1}</td>
<td>${item.name_en}<br><small>${item.name_ar}</small></td>
<td class="text-center">${item.quantity}</td>
<td class="text-end">${parseFloat(item.unit_price || 0).toFixed(3)}</td>
<td class="text-center">${item.vat_rate}%</td>
<td class="text-end">${parseFloat(item.total_price || 0).toFixed(3)}</td>
</tr>
`;
});
const logo = "<?= $data['settings']['company_logo'] ?? '' ?>";
const companyName = "<?= htmlspecialchars($data['settings']['company_name'] ?? '' ) ?>";
const companyPhone = "<?= htmlspecialchars($data['settings']['company_phone'] ?? '' ) ?>";
const companyEmail = "<?= htmlspecialchars($data['settings']['company_email'] ?? '' ) ?>";
const companyAddress = "<?= htmlspecialchars($data['settings']['company_address'] ?? '' ) ?>";
const vatNumber = "<?= htmlspecialchars($data['settings']['vat_number'] ?? '' ) ?>";
content.innerHTML = `
<div class="p-4">
<div class="d-flex justify-content-between align-items-start mb-4 border-bottom pb-4">
<div class="d-flex align-items-center">
${logo ? `<img src="${logo}" alt="Logo" style="max-height: 80px;" class="me-3">` : ''}
<div>
<h2 class="fw-bold mb-0 text-primary">${companyName}</h2>
<div class="small text-muted">
${companyAddress ? `<div>${companyAddress}</div>` : ''}
${companyPhone ? `<span>Tel: ${companyPhone}</span>` : ''}
${companyEmail ? `<span class="ms-3">Email: ${companyEmail}</span>` : ''}
${vatNumber ? `<div class="mt-1 fw-bold">VAT No: ${vatNumber}</div>` : ''}
</div>
</div>
</div>
<div class="text-end">
<h1 class="display-6 fw-bold text-uppercase mb-0" style="letter-spacing: 2px;">QUOTATION</h1>
<div class="mt-2 h5">
<span class="text-muted">No:</span> <span class="fw-bold">QUO-${(data.id || '').toString().padStart(5, '0')}</span>
</div>
<div class="small">
<span class="text-muted">Date:</span> <span>${data.quotation_date || ''}</span>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-6">
<div class="bg-light p-3 rounded h-100">
<h6 class="text-muted text-uppercase small fw-bold mb-2">Quote To:</h6>
<h5 class="fw-bold mb-1">${data.customer_name || 'Walk-in Customer'}</h5>
${data.customer_phone ? `<div class="small">${data.customer_phone}</div>` : ''}
${data.customer_tax_id ? `<div class="small">VAT No: ${data.customer_tax_id}</div>` : ''}
</div>
</div>
<div class="col-6 text-end">
<div class="p-3 h-100">
<div class="mb-1"><span class="text-muted small text-uppercase">Status:</span> <span class="badge ${data.status === 'converted' ? 'bg-success' : 'bg-warning text-dark'} text-uppercase ms-2">${data.status || 'pending'}</span></div>
<div class="mb-1"><span class="text-muted small text-uppercase">Valid Until:</span> <span class="ms-2">${data.valid_until || '---'}</span></div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered">
<thead class="bg-primary text-white text-uppercase small">
<tr>
<th class="text-center" style="width: 50px;">#</th>
<th>Item Description</th>
<th class="text-center" style="width: 100px;">Qty</th>
<th class="text-end" style="width: 120px;">Unit Price</th>
<th class="text-center" style="width: 80px;">VAT</th>
<th class="text-end" style="width: 120px;">Total</th>
</tr>
</thead>
<tbody>${itemsHtml}</tbody>
<tfoot class="bg-light">
<tr>
<th colspan="5" class="text-end py-2">Subtotal</th>
<td class="text-end py-2 fw-bold">${parseFloat(data.total_amount || 0).toFixed(3)}</td>
</tr>
<tr>
<th colspan="5" class="text-end py-2">VAT Amount</th>
<td class="text-end py-2 fw-bold">${parseFloat(data.vat_amount || 0).toFixed(3)}</td>
</tr>
<tr class="table-primary">
<th colspan="5" class="text-end py-2 h5 mb-0">Grand Total (OMR)</th>
<td class="text-end py-2 h5 mb-0 fw-bold">${parseFloat(data.total_with_vat || 0).toFixed(3)}</td>
</tr>
</tfoot>
</table>
</div>
<div class="mt-4">
<div class="row g-4">
<div class="col-8">
<h6 class="fw-bold text-uppercase small border-bottom pb-2 mb-2">Terms & Conditions</h6>
<div class="small text-muted" style="white-space: pre-line;">
${data.terms_conditions || `1. This quotation is valid for the period mentioned above.
2. Prices are inclusive of VAT as per Omani law.
3. Payment terms as agreed upon.`}
</div>
</div>
<div class="col-4 text-center">
<div class="mt-5 pt-4">
<div class="border-top border-dark pt-2 mx-auto" style="width: 80%;">
<div class="fw-bold">Authorized Signature</div>
<div class="small text-muted">${companyName}</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
const actionButtons = document.getElementById('quotationActionButtons');
if (actionButtons) {
actionButtons.innerHTML = '';
if (data.status === 'pending') {
const convertBtn = document.createElement('button');
convertBtn.className = 'btn btn-success me-2';
convertBtn.innerHTML = '<i class="bi bi-receipt"></i> Convert to Invoice';
convertBtn.onclick = function() {
if (confirm('Convert this quotation to an invoice?')) {
const f = document.createElement('form');
f.method = 'POST';
f.innerHTML = `<input type="hidden" name="convert_to_invoice" value="1"><input type="hidden" name="quotation_id" value="${data.id}">`;
document.body.appendChild(f);
f.submit();
}
};
actionButtons.appendChild(convertBtn);
const editBtn = document.createElement('button');
editBtn.className = 'btn btn-primary';
editBtn.innerHTML = '<i class="bi bi-pencil-square"></i> Edit';
editBtn.onclick = function() {
const editModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('editQuotationModal'));
modal.hide();
const originalEditBtn = document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id},']`) ||
document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id}']`);
if (originalEditBtn) originalEditBtn.click();
};
actionButtons.appendChild(editBtn);
}
}
modal.show();
if (autoPrint) {
setTimeout(() => { window.print(); }, 800);
}
};
document.addEventListener('DOMContentLoaded', function() {
const hasExpiryToggle = document.getElementById('hasExpiryToggle');
const expiryDateContainer = document.getElementById('expiryDateContainer');
@ -3482,22 +3652,6 @@ document.addEventListener('DOMContentLoaded', function() {
togglePaidAmount('add_status', 'addPaidAmountContainer');
togglePaidAmount('edit_status', 'editPaidAmountContainer');
// Pay Invoice Logic
document.querySelectorAll('.pay-invoice-btn').forEach(btn => {
btn.addEventListener('click', function() {
const id = this.getAttribute('data-id');
const total = parseFloat(this.getAttribute('data-total'));
const paid = parseFloat(this.getAttribute('data-paid') || 0);
const remaining = total - paid;
document.getElementById('pay_invoice_id').value = id;
document.getElementById('pay_invoice_total').value = `OMR ${total.toFixed(3)}`;
document.getElementById('pay_remaining_amount').value = `OMR ${remaining.toFixed(3)}`;
document.getElementById('pay_amount').value = remaining.toFixed(3);
document.getElementById('pay_amount').max = remaining.toFixed(3);
});
});
// Show receipt modal if needed
<?php if (isset($_SESSION['trigger_receipt_modal'])):
$rid = (int)$_SESSION['show_receipt_id'];
@ -3627,8 +3781,78 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
// Invoice Form Logic
const initInvoiceForm = (searchInputId, suggestionsId, tableBodyId, grandTotalId, subtotalId, totalVatId) => {
// Global Functions for Invoice/Quotation Forms
window.addItemToTable = function(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl, customData = null) {
if (suggestions) suggestions.style.display = 'none';
if (searchInput) searchInput.value = '';
const existingRow = Array.from(tableBody.querySelectorAll('.item-id-input')).find(input => input.value == item.id);
if (existingRow && !customData) {
const row = existingRow.closest('tr');
const qtyInput = row.querySelector('.item-qty');
qtyInput.value = parseFloat(qtyInput.value) + 1;
window.recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
return;
}
const row = document.createElement('tr');
row.className = 'item-row';
const currentInvoiceType = window.invoiceType || 'sale';
const price = customData ? customData.unit_price : (currentInvoiceType === 'sale' ? item.sale_price : item.purchase_price);
const qty = customData ? customData.quantity : 1;
const vatRate = item.vat_rate || 0;
row.innerHTML = `
<td>
<input type="hidden" name="item_ids[]" class="item-id-input" value="${item.id}">
<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>
</td>
<td><input type="number" step="0.001" name="quantities[]" class="form-control item-qty" value="${qty}" required></td>
<td><input type="number" step="0.001" name="prices[]" class="form-control item-price" value="${price}" required></td>
<td><input type="text" class="form-control bg-light" value="${vatRate}%" readonly></td>
<td><input type="number" step="0.001" class="form-control item-total" value="${(qty * price).toFixed(3)}" readonly></td>
<td><button type="button" class="btn btn-outline-danger btn-sm remove-row"><i class="bi bi-trash"></i></button></td>
`;
tableBody.appendChild(row);
window.attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl);
window.recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
};
window.recalculate = function(tableBody, grandTotalEl, subtotalEl, totalVatEl) {
let subtotal = 0;
let totalVat = 0;
tableBody.querySelectorAll('.item-row').forEach(row => {
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const price = parseFloat(row.querySelector('.item-price').value) || 0;
const vatRate = parseFloat(row.querySelector('.item-vat-rate').value) || 0;
const total = qty * price;
const vatAmount = total * (vatRate / 100);
row.querySelector('.item-total').value = total.toFixed(3);
subtotal += total;
totalVat += vatAmount;
});
const grandTotal = subtotal + totalVat;
if (subtotalEl) subtotalEl.textContent = 'OMR ' + subtotal.toFixed(3);
if (totalVatEl) totalVatEl.textContent = 'OMR ' + totalVat.toFixed(3);
if (grandTotalEl) grandTotalEl.textContent = 'OMR ' + grandTotal.toFixed(3);
};
window.attachRowListeners = function(row, tableBody, grandTotalEl, subtotalEl, totalVatEl) {
row.querySelector('.item-qty').addEventListener('input', () => window.recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl));
row.querySelector('.item-price').addEventListener('input', () => window.recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl));
row.querySelector('.remove-row').addEventListener('click', function() {
row.remove();
window.recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
});
};
window.initInvoiceForm = function(searchInputId, suggestionsId, tableBodyId, grandTotalId, subtotalId, totalVatId) {
const searchInput = document.getElementById(searchInputId);
const suggestions = document.getElementById(suggestionsId);
const tableBody = document.getElementById(tableBodyId);
@ -3663,7 +3887,7 @@ document.addEventListener('DOMContentLoaded', function() {
<span class="text-muted small">Stock: ${item.stock_quantity}</span>
</div>
`;
btn.onclick = () => addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl);
btn.onclick = () => window.addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl);
suggestions.appendChild(btn);
});
suggestions.style.display = 'block';
@ -3674,7 +3898,6 @@ document.addEventListener('DOMContentLoaded', function() {
}, 300);
});
// Close suggestions when clicking outside
document.addEventListener('click', function(e) {
if (!searchInput.contains(e.target) && !suggestions.contains(e.target)) {
suggestions.style.display = 'none';
@ -3682,314 +3905,136 @@ 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 existingRow = Array.from(tableBody.querySelectorAll('.item-id-input')).find(input => input.value == item.id);
if (existingRow && !customData) {
const row = existingRow.closest('tr');
const qtyInput = row.querySelector('.item-qty');
qtyInput.value = parseFloat(qtyInput.value) + 1;
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
return;
}
window.invoiceType = '<?= in_array($page, ["sales", "quotations"]) ? "sale" : ($page === "purchases" ? "purchase" : "") ?>';
const row = document.createElement('tr');
row.className = 'item-row';
const price = customData ? customData.unit_price : (invoiceType === 'sale' ? item.sale_price : item.purchase_price);
const qty = customData ? customData.quantity : 1;
const vatRate = item.vat_rate || 0;
row.innerHTML = `
<td>
<input type="hidden" name="item_ids[]" class="item-id-input" value="${item.id}">
<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>
</td>
<td><input type="number" step="0.001" name="quantities[]" class="form-control item-qty" value="${qty}" required></td>
<td><input type="number" step="0.001" name="prices[]" class="form-control item-price" value="${price}" required></td>
<td><input type="text" class="form-control bg-light" value="${vatRate}%" readonly></td>
<td><input type="number" step="0.001" class="form-control item-total" value="${(qty * price).toFixed(3)}" readonly></td>
<td><button type="button" class="btn btn-outline-danger btn-sm remove-row"><i class="bi bi-trash"></i></button></td>
`;
tableBody.appendChild(row);
attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl);
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
}
function recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl) {
let subtotal = 0;
let totalVat = 0;
tableBody.querySelectorAll('.item-row').forEach(row => {
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const price = parseFloat(row.querySelector('.item-price').value) || 0;
const vatRate = parseFloat(row.querySelector('.item-vat-rate').value) || 0;
const total = qty * price;
const vatAmount = total * (vatRate / 100);
row.querySelector('.item-total').value = total.toFixed(3);
subtotal += total;
totalVat += vatAmount;
});
const grandTotal = subtotal + totalVat;
if (subtotalEl) subtotalEl.textContent = 'OMR ' + subtotal.toFixed(3);
if (totalVatEl) totalVatEl.textContent = 'OMR ' + totalVat.toFixed(3);
if (grandTotalEl) grandTotalEl.textContent = 'OMR ' + grandTotal.toFixed(3);
}
function attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl) {
row.querySelector('.item-qty').addEventListener('input', () => 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();
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
});
}
const invoiceType = '<?= in_array($page, ["sales", "quotations"]) ? "sale" : ($page === "purchases" ? "purchase" : "") ?>';
initInvoiceForm('productSearchInput', 'searchSuggestions', 'invoiceItemsTableBody', 'grandTotal', 'subtotal', 'totalVat');
initInvoiceForm('editProductSearchInput', 'editSearchSuggestions', 'editInvoiceItemsTableBody', 'edit_grandTotal', 'edit_subtotal', 'edit_totalVat');
// Quotation Form Logic
initInvoiceForm('quotProductSearchInput', 'quotSearchSuggestions', 'quotItemsTableBody', 'quot_grand_display', 'quot_subtotal_display', 'quot_vat_display');
initInvoiceForm('editQuotProductSearchInput', 'editQuotSearchSuggestions', 'editQuotItemsTableBody', 'edit_quot_grand_display', 'edit_quot_subtotal_display', 'edit_quot_vat_display');
window.initInvoiceForm('quotProductSearchInput', 'quotSearchSuggestions', 'quotItemsTableBody', 'quot_grand_display', 'quot_subtotal_display', 'quot_vat_display');
window.initInvoiceForm('editQuotProductSearchInput', 'editQuotSearchSuggestions', 'editQuotItemsTableBody', 'edit_quot_grand_display', 'edit_quot_subtotal_display', 'edit_quot_vat_display');
document.querySelectorAll('.edit-quotation-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
document.getElementById('edit_quotation_id').value = data.id;
document.getElementById('edit_quot_customer_id').value = data.customer_id;
document.getElementById('edit_quot_date').value = data.quotation_date;
document.getElementById('edit_quot_valid').value = data.valid_until || '';
document.getElementById('edit_quot_status').value = data.status || 'pending';
const tableBody = document.getElementById('editQuotItemsTableBody');
tableBody.innerHTML = '';
data.items.forEach(item => {
addItemToTable({
id: item.item_id,
name_en: item.name_en,
name_ar: item.name_ar,
sku: '',
vat_rate: item.vat_rate || 0
}, tableBody, null, null,
document.getElementById('edit_quot_grand_display'),
document.getElementById('edit_quot_subtotal_display'),
document.getElementById('edit_quot_vat_display'),
{ quantity: item.quantity, unit_price: item.unit_price });
});
});
});
// Global Actions Handler - Delegation for list buttons
document.addEventListener('click', function(e) {
const target = e.target.closest('button');
if (!target) return;
document.querySelectorAll('.convert-quotation-btn').forEach(btn => {
btn.addEventListener('click', function() {
if (confirm('Convert this quotation to an invoice? This will reduce stock.')) {
// --- Quotations ---
if (target.classList.contains('view-quotation-btn')) {
try {
const data = JSON.parse(target.dataset.json);
if (window.viewAndPrintQuotation) window.viewAndPrintQuotation(data, false);
} catch(ex) { console.error(ex); }
}
else if (target.classList.contains('print-quotation-btn')) {
try {
const data = JSON.parse(target.dataset.json);
if (window.viewAndPrintQuotation) window.viewAndPrintQuotation(data, true);
} catch(ex) { console.error(ex); }
}
else if (target.classList.contains('edit-quotation-btn')) {
try {
const data = JSON.parse(target.dataset.json);
if (document.getElementById('edit_quotation_id')) {
document.getElementById('edit_quotation_id').value = data.id;
document.getElementById('edit_quot_customer_id').value = data.customer_id;
document.getElementById('edit_quot_date').value = data.quotation_date;
document.getElementById('edit_quot_valid').value = data.valid_until || '';
document.getElementById('edit_quot_status').value = data.status || 'pending';
document.getElementById('edit_quot_terms').value = data.terms_conditions || '';
const tableBody = document.getElementById('editQuotItemsTableBody');
if (tableBody) {
tableBody.innerHTML = '';
(data.items || []).forEach(item => {
if (window.addItemToTable) {
window.addItemToTable({
id: item.item_id, name_en: item.name_en, name_ar: item.name_ar,
sku: '', vat_rate: item.vat_rate || 0
}, tableBody, null, null,
document.getElementById('edit_quot_grand_display'),
document.getElementById('edit_quot_subtotal_display'),
document.getElementById('edit_quot_vat_display'),
{ quantity: item.quantity, unit_price: item.unit_price });
}
});
}
}
} catch(ex) { console.error(ex); }
}
else if (target.classList.contains('convert-quotation-btn')) {
if (target.dataset.id && confirm('Convert this quotation to an invoice? This will reduce stock.')) {
const f = document.createElement('form');
f.method = 'POST';
f.innerHTML = `<input type="hidden" name="convert_to_invoice" value="1"><input type="hidden" name="quotation_id" value="${this.dataset.id}">`;
f.innerHTML = `<input type="hidden" name="convert_to_invoice" value="1"><input type="hidden" name="quotation_id" value="${target.dataset.id}">`;
document.body.appendChild(f);
f.submit();
}
});
});
}
// View Quotation Logic
window.viewAndPrintQuotation = function(data, autoPrint = false) {
const modal = new bootstrap.Modal(document.getElementById('viewQuotationModal'));
const content = document.getElementById('quotationPrintableArea');
let itemsHtml = '';
data.items.forEach((item, index) => {
itemsHtml += `
<tr>
<td>${index + 1}</td>
<td>${item.name_en}<br><small>${item.name_ar}</small></td>
<td class="text-center">${item.quantity}</td>
<td class="text-end">${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-center">${item.vat_rate}%</td>
<td class="text-end">${parseFloat(item.total_price).toFixed(3)}</td>
</tr>
`;
});
content.innerHTML = `
<div class="p-5">
<div class="d-flex justify-content-between mb-4 border-bottom pb-3">
<div>
<h3 class="text-primary fw-bold">QUOTATION</h3>
<p class="mb-0"><strong>No:</strong> QUO-${data.id.toString().padStart(5, '0')}</p>
<p class="mb-0"><strong>Date:</strong> ${data.quotation_date}</p>
<p class="mb-0"><strong>Valid Until:</strong> ${data.valid_until || 'N/A'}</p>
</div>
<div class="text-end">
<h4 class="fw-bold">${data.customer_name || 'Walk-in Customer'}</h4>
<p class="mb-0">Status: <span class="badge ${data.status === 'converted' ? 'bg-success' : 'bg-secondary'}">${data.status.toUpperCase()}</span></p>
</div>
</div>
<table class="table table-bordered table-striped">
<thead class="bg-dark text-white">
<tr>
<th>#</th>
<th>Item Description</th>
<th class="text-center">Qty</th>
<th class="text-end">Unit Price</th>
<th class="text-center">VAT</th>
<th class="text-end">Total</th>
</tr>
</thead>
<tbody>${itemsHtml}</tbody>
<tfoot>
<tr>
<th colspan="5" class="text-end">Subtotal</th>
<td class="text-end fw-bold">${parseFloat(data.total_amount).toFixed(3)}</td>
</tr>
<tr>
<th colspan="5" class="text-end">VAT Amount</th>
<td class="text-end fw-bold">${parseFloat(data.vat_amount).toFixed(3)}</td>
</tr>
<tr class="table-primary">
<th colspan="5" class="text-end h5">Grand Total (OMR)</th>
<td class="text-end h5 fw-bold">${parseFloat(data.total_with_vat).toFixed(3)}</td>
</tr>
</tfoot>
</table>
<div class="mt-5 pt-3 border-top">
<div class="row">
<div class="col-6">
<p class="small text-muted">Terms & Conditions:</p>
<ul class="small text-muted">
<li>Quotation is valid until the date mentioned above.</li>
<li>Prices are inclusive of VAT where applicable.</li>
</ul>
</div>
<div class="col-6 text-end pt-4">
<div class="border-top d-inline-block px-5">Authorized Signature</div>
</div>
</div>
</div>
</div>
`;
const actionButtons = document.getElementById('quotationActionButtons');
actionButtons.innerHTML = '';
if (data.status === 'pending') {
const convertBtn = document.createElement('button');
convertBtn.className = 'btn btn-success me-2';
convertBtn.innerHTML = '<i class="bi bi-receipt"></i> Convert to Invoice';
convertBtn.onclick = function() {
if (confirm('Convert this quotation to an invoice?')) {
const f = document.createElement('form');
f.method = 'POST';
f.innerHTML = `<input type="hidden" name="convert_to_invoice" value="1"><input type="hidden" name="quotation_id" value="${data.id}">`;
document.body.appendChild(f);
f.submit();
// --- Invoices ---
else if (target.classList.contains('view-invoice-btn')) {
try {
const data = JSON.parse(target.dataset.json);
if (window.viewAndPrintA4Invoice) window.viewAndPrintA4Invoice(data, false);
} catch(ex) { console.error(ex); }
}
else if (target.classList.contains('print-a4-btn')) {
try {
const data = JSON.parse(target.dataset.json);
if (window.viewAndPrintA4Invoice) window.viewAndPrintA4Invoice(data, true);
} catch(ex) { console.error(ex); }
}
else if (target.classList.contains('edit-invoice-btn')) {
try {
const data = JSON.parse(target.dataset.json);
if (document.getElementById('edit_invoice_id')) {
document.getElementById('edit_invoice_id').value = data.id;
document.getElementById('edit_customer_id').value = data.customer_id;
document.getElementById('edit_invoice_date').value = data.invoice_date;
document.getElementById('edit_payment_type').value = data.payment_type || 'cash';
document.getElementById('edit_status').value = data.status || 'unpaid';
document.getElementById('edit_paid_amount').value = parseFloat(data.paid_amount || 0).toFixed(3);
const paidContainer = document.getElementById('editPaidAmountContainer');
if (paidContainer) paidContainer.style.display = (data.status === 'partially_paid') ? 'block' : 'none';
const tableBody = document.getElementById('editInvoiceItemsTableBody');
if (tableBody) {
tableBody.innerHTML = '';
(data.items || []).forEach(item => {
if (window.addItemToTable) {
window.addItemToTable({
id: item.item_id, name_en: item.name_en, name_ar: item.name_ar,
sku: '', vat_rate: item.vat_rate || 0
}, tableBody, null, null,
document.getElementById('edit_grandTotal'),
document.getElementById('edit_subtotal'),
document.getElementById('edit_totalVat'),
{ quantity: item.quantity, unit_price: item.unit_price });
}
});
}
}
};
actionButtons.appendChild(convertBtn);
const editBtn = document.createElement('button');
editBtn.className = 'btn btn-primary';
editBtn.innerHTML = '<i class="bi bi-pencil-square"></i> Edit';
editBtn.onclick = function() {
const editModal = new bootstrap.Modal(document.getElementById('editQuotationModal'));
modal.hide();
// Trigger the existing edit button click logic or manually populate
const originalEditBtn = document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id},']`) ||
document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id}']`);
if (originalEditBtn) originalEditBtn.click();
};
actionButtons.appendChild(editBtn);
} catch(ex) { console.error(ex); }
}
modal.show();
if (autoPrint) {
setTimeout(() => { window.print(); }, 500);
}
};
else if (target.classList.contains('pay-invoice-btn')) {
const id = target.getAttribute('data-id');
const total = parseFloat(target.getAttribute('data-total'));
const paid = parseFloat(target.getAttribute('data-paid') || 0);
const remaining = total - paid;
document.querySelectorAll('.view-quotation-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
window.viewAndPrintQuotation(data, false);
});
});
// Edit Invoice Logic
document.querySelectorAll('.edit-invoice-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
document.getElementById('edit_invoice_id').value = data.id;
document.getElementById('edit_customer_id').value = data.customer_id;
document.getElementById('edit_invoice_date').value = data.invoice_date;
document.getElementById('edit_payment_type').value = data.payment_type || 'cash';
document.getElementById('edit_status').value = data.status || 'unpaid';
document.getElementById('edit_paid_amount').value = parseFloat(data.paid_amount || 0).toFixed(3);
if (data.status === 'partially_paid') {
document.getElementById('editPaidAmountContainer').style.display = 'block';
} else {
document.getElementById('editPaidAmountContainer').style.display = 'none';
}
const tableBody = document.getElementById('editInvoiceItemsTableBody');
tableBody.innerHTML = '';
data.items.forEach(item => {
// We need more data than what's in invoice_items (like SKU and names, but we have them from the join in PHP)
// The dataset-json already contains name_en, name_ar etc because of the PHP logic at line 1093
const itemMeta = {
id: item.item_id,
name_en: item.name_en,
name_ar: item.name_ar,
sku: '', // Optional, or fetch if needed
vat_rate: 0 // Will be handled if we have it in the join
};
// Fetch current item details to get VAT rate if possible, or use stored if available
// For simplicity, let's assume we want to use the item's current VAT rate or store it.
// Looking at the join at line 1093, it doesn't fetch vat_rate. Let's fix that in PHP too.
addItemToTable({
id: item.item_id,
name_en: item.name_en,
name_ar: item.name_ar,
sku: '',
vat_rate: item.vat_rate || 0 // We'll add this to PHP join
}, tableBody, null, null,
document.getElementById('edit_grandTotal'),
document.getElementById('edit_subtotal'),
document.getElementById('edit_totalVat'),
{ quantity: item.quantity, unit_price: item.unit_price });
});
});
});
// View and Print Invoice Logic
document.addEventListener('click', function(e) {
if (e.target.closest('.view-invoice-btn')) {
const btn = e.target.closest('.view-invoice-btn');
const data = JSON.parse(btn.dataset.json);
if (window.viewAndPrintA4Invoice) {
window.viewAndPrintA4Invoice(data, false);
}
}
if (e.target.closest('.print-a4-btn')) {
const btn = e.target.closest('.print-a4-btn');
const data = JSON.parse(btn.dataset.json);
if (window.viewAndPrintA4Invoice) {
window.viewAndPrintA4Invoice(data, true);
if (document.getElementById('pay_invoice_id')) {
document.getElementById('pay_invoice_id').value = id;
document.getElementById('pay_invoice_total').value = `OMR ${total.toFixed(3)}`;
document.getElementById('pay_remaining_amount').value = `OMR ${remaining.toFixed(3)}`;
document.getElementById('pay_amount').value = remaining.toFixed(3);
document.getElementById('pay_amount').max = remaining.toFixed(3);
}
}
});
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<!-- View Invoice Modal -->
@ -4262,6 +4307,10 @@ document.addEventListener('DOMContentLoaded', function() {
</tfoot>
</table>
</div>
<div class="mt-3">
<label class="form-label fw-bold" data-en="Terms & Conditions" data-ar="الشروط والأحكام">Terms & Conditions</label>
<textarea name="terms_conditions" class="form-control" rows="3" placeholder="Enter terms and conditions here..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
@ -4351,6 +4400,10 @@ document.addEventListener('DOMContentLoaded', function() {
</tfoot>
</table>
</div>
<div class="mt-3">
<label class="form-label fw-bold" data-en="Terms & Conditions" data-ar="الشروط والأحكام">Terms & Conditions</label>
<textarea name="terms_conditions" id="edit_quot_terms" class="form-control" rows="3" placeholder="Enter terms and conditions here..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
@ -4388,10 +4441,10 @@ document.addEventListener('DOMContentLoaded', function() {
body { background: white !important; margin: 0 !important; padding: 0 !important; }
.main-content { margin: 0 !important; padding: 0 !important; background: white !important; }
.modal { position: absolute !important; left: 0 !important; top: 0 !important; margin: 0 !important; padding: 0 !important; overflow: visible !important; display: block !important; visibility: visible !important; background: white !important; }
#viewInvoiceModal { display: block !important; background: white !important; }
#viewInvoiceModal .modal-dialog { max-width: 100% !important; width: 100% !important; margin: 0 !important; padding: 0 !important; }
#viewInvoiceModal .modal-content { border: none !important; box-shadow: none !important; background: white !important; }
#viewInvoiceModal .modal-body { padding: 0 !important; margin: 0 !important; background: white !important; }
#viewInvoiceModal, #viewQuotationModal { display: block !important; background: white !important; }
#viewInvoiceModal .modal-dialog, #viewQuotationModal .modal-dialog { max-width: 100% !important; width: 100% !important; margin: 0 !important; padding: 0 !important; }
#viewInvoiceModal .modal-content, #viewQuotationModal .modal-content { border: none !important; box-shadow: none !important; background: white !important; }
#viewInvoiceModal .modal-body, #viewQuotationModal .modal-body { padding: 0 !important; margin: 0 !important; background: white !important; }
.table-bordered th, .table-bordered td { border: 1px solid #dee2e6 !important; }
.bg-light { background-color: #f8f9fa !important; -webkit-print-color-adjust: exact; }
.text-primary { color: #0d6efd !important; -webkit-print-color-adjust: exact; }
@ -4399,7 +4452,7 @@ document.addEventListener('DOMContentLoaded', function() {
/* Ensure the modal is the only thing visible */
body > *:not(.main-content):not(.modal) { display: none !important; }
.main-content > *:not(#viewInvoiceModal):not(.modal) { display: none !important; }
.main-content > *:not(#viewInvoiceModal):not(#viewQuotationModal):not(.modal) { display: none !important; }
}
.invoice-logo { max-height: 80px; width: auto; }
.invoice-header { border-bottom: 2px solid #333; padding-bottom: 20px; }