Revert to version 557ff40
This commit is contained in:
parent
5ee3913fe1
commit
f20efe1066
693
index.php
693
index.php
@ -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; }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user