Autosave: 20260216-155320

This commit is contained in:
Flatlogic Bot 2026-02-16 15:53:20 +00:00
parent d19d0d272e
commit 5efe80f7c5
4 changed files with 294 additions and 292 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

@ -0,0 +1 @@
ALTER TABLE customers ADD COLUMN credit_limit DECIMAL(15,3) DEFAULT 0.000 AFTER balance;

585
index.php
View File

@ -169,15 +169,31 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
// Calculate actual paid amount (excluding credit)
$actual_paid = 0;
foreach ($payments as $p) {
if ($p['method'] !== 'credit') {
$actual_paid += (float)$p['amount'];
}
}
$status = 'paid';
if ($actual_paid <= 0) {
$status = 'unpaid';
} elseif ($actual_paid < $net_amount - 0.001) {
$status = 'partially_paid';
}
$methods_str = implode(', ', array_unique(array_map(fn($p) => $p['method'], $payments)));
// Create Invoice
$stmt = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, status, total_with_vat, paid_amount, type) VALUES (?, CURDATE(), 'paid', ?, ?, 'sale')");
$stmt->execute([$customer_id, $net_amount, $net_amount]);
$stmt = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, status, total_with_vat, paid_amount, type, payment_type) VALUES (?, CURDATE(), ?, ?, ?, 'sale', ?)");
$stmt->execute([$customer_id, $status, $net_amount, $actual_paid, $methods_str]);
$invoice_id = $db->lastInsertId();
// Add POS Transaction record
$stmt = $db->prepare("INSERT INTO pos_transactions (transaction_no, customer_id, total_amount, discount_code_id, discount_amount, loyalty_points_earned, loyalty_points_redeemed, net_amount, payment_method) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$transaction_no = 'POS-' . time() . rand(100, 999);
$methods_str = implode(', ', array_unique(array_map(fn($p) => $p['method'], $payments)));
$stmt->execute([$transaction_no, $customer_id, $total_amount, $discount_code_id, $discount_amount, $loyalty_earned, $loyalty_redeemed, $net_amount, $methods_str]);
$pos_id = $db->lastInsertId();
@ -214,6 +230,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Add Payments
foreach ($payments as $p) {
if ($p['method'] === 'credit') continue;
$stmt = $db->prepare("INSERT INTO payments (invoice_id, amount, payment_date, payment_method, notes) VALUES (?, ?, CURDATE(), ?, 'POS Transaction')");
$stmt->execute([$invoice_id, (float)$p['amount'], $p['method']]);
}
@ -979,13 +996,17 @@ switch ($page) {
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT v.*, c.name as customer_name, c.tax_id as customer_tax_id
$stmt = db()->prepare("SELECT v.*, c.name as customer_name, c.tax_id as customer_tax_id, c.phone as customer_phone
FROM invoices v
LEFT JOIN customers c ON v.customer_id = c.id
WHERE $whereSql
ORDER BY v.id DESC");
$stmt->execute($params);
$data['invoices'] = $stmt->fetchAll();
foreach ($data['invoices'] as &$inv) {
$inv['total_in_words'] = numberToWordsOMR($inv['total_with_vat']);
}
unset($inv);
$data['items_list'] = db()->query("SELECT id, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate FROM stock_items ORDER BY name_en ASC")->fetchAll();
$data['customers_list'] = db()->query("SELECT id, name FROM customers WHERE type = '" . ($type === 'sale' ? 'customer' : 'supplier') . "' ORDER BY name ASC")->fetchAll();
@ -1060,42 +1081,16 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<style>
@media print {
.sidebar, .topbar, .d-print-none, .btn, .modal-header, .modal-footer {
display: none !important;
}
.main-content {
margin-left: 0 !important;
padding: 0 !important;
}
.modal {
position: absolute;
left: 0;
top: 0;
margin: 0;
padding: 0;
visibility: visible;
overflow: visible !important;
display: block !important;
}
.modal-dialog {
max-width: 100% !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
box-shadow: none !important;
}
.modal-content {
border: none !important;
box-shadow: none !important;
}
body {
background: white !important;
}
.sidebar, .topbar, .d-print-none, .no-print { display: none !important; }
.main-content { margin-left: 0 !important; padding: 0 !important; }
}
</style>
</head>
@ -1759,6 +1754,16 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
customerPoints: 0,
selectedPaymentMethod: 'cash',
payments: [],
allCreditCustomers: <?php
$custData = [];
foreach ($customers as $c) {
$custData[] = [
'value' => (string)$c['id'],
'text' => $c['name'] . ' (' . ($c['phone'] ?? '') . ')'
];
}
echo json_encode($custData);
?>,
add(product) {
const existing = this.items.find(item => item.id === product.id);
if (existing) {
@ -2010,9 +2015,6 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
this.payments = [];
this.renderPayments();
if (document.getElementById('creditCustomerSearch')) {
document.getElementById('creditCustomerSearch').value = '';
}
document.getElementById('paymentAmountDue').innerText = 'OMR ' + total.toFixed(3);
document.getElementById('partialAmount').value = total.toFixed(3);
@ -2020,8 +2022,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
const creditSection = document.getElementById('creditCustomerSection');
if (this.selectedPaymentMethod === 'credit') {
creditSection.style.display = 'block';
this.filterCreditCustomers();
document.getElementById('paymentCreditCustomer').value = customerSelect.value;
const creditSelect = $('#paymentCreditCustomer');
creditSelect.val(customerSelect.value).trigger('change');
} else {
creditSection.style.display = 'none';
}
@ -2039,12 +2041,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
const creditSection = document.getElementById('creditCustomerSection');
if (method === 'credit') {
creditSection.style.display = 'block';
if (document.getElementById('creditCustomerSearch')) {
document.getElementById('creditCustomerSearch').value = '';
}
this.filterCreditCustomers();
// Sync with main customer select
document.getElementById('paymentCreditCustomer').value = document.getElementById('posCustomer').value;
const creditSelect = $('#paymentCreditCustomer');
creditSelect.val(document.getElementById('posCustomer').value).trigger('change');
} else {
creditSection.style.display = 'none';
}
@ -2308,37 +2307,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
location.reload();
}, { once: true });
},
filterCreditCustomers() {
const searchInput = document.getElementById('creditCustomerSearch');
if (!searchInput) return;
const search = searchInput.value.toLowerCase();
const select = document.getElementById('paymentCreditCustomer');
if (!select) return;
if (!this.allCreditCustomers || (this.allCreditCustomers.length <= 1 && select.options.length > 1)) {
this.allCreditCustomers = Array.from(select.options).map(opt => ({
value: opt.value,
text: opt.text,
search: opt.getAttribute('data-search') || opt.text.toLowerCase()
}));
}
if (!this.allCreditCustomers) return;
const currentValue = select.value;
select.innerHTML = '';
this.allCreditCustomers.forEach(opt => {
if (opt.value === "" || opt.search.includes(search)) {
const o = document.createElement('option');
o.value = opt.value;
o.text = opt.text;
o.setAttribute('data-search', opt.search);
if (opt.value === currentValue) o.selected = true;
select.appendChild(o);
}
});
},
async syncCustomer(val) {
document.getElementById('posCustomer').value = val;
const customerSelect = document.getElementById('posCustomer');
@ -2417,6 +2386,18 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
if (bc) bc.focus();
}
});
$(document).ready(function() {
$('#posCustomer').select2({
width: '100%',
placeholder: 'Select Customer'
});
$('#paymentCreditCustomer').select2({
width: '100%',
placeholder: 'Select Customer',
dropdownParent: $('#posPaymentModal')
});
});
</script>
<?php elseif ($page === 'sales' || $page === 'purchases'): ?>
@ -2508,12 +2489,12 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<td class="text-end text-danger fw-bold">OMR <?= number_format((float)($inv['total_with_vat'] - $inv['paid_amount']), 3) ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-info view-invoice-btn" data-json='<?= json_encode($inv) ?>' data-bs-toggle="modal" data-bs-target="#viewInvoiceModal" title="View"><i class="bi bi-eye"></i></button>
<button class="btn btn-outline-primary edit-invoice-btn" data-json='<?= json_encode($inv) ?>' data-bs-toggle="modal" data-bs-target="#editInvoiceModal" title="Edit"><i class="bi bi-pencil"></i></button>
<button class="btn btn-outline-info view-invoice-btn" data-json="<?= htmlspecialchars(json_encode($inv)) ?>" title="View"><i class="bi bi-eye"></i></button>
<button class="btn btn-outline-primary edit-invoice-btn" data-json="<?= htmlspecialchars(json_encode($inv)) ?>" data-bs-toggle="modal" data-bs-target="#editInvoiceModal" title="Edit"><i class="bi bi-pencil"></i></button>
<?php if ($inv['status'] !== 'paid'): ?>
<button class="btn btn-outline-success pay-invoice-btn" data-id="<?= $inv['id'] ?>" data-total="<?= $inv['total_with_vat'] ?>" data-paid="<?= $inv['paid_amount'] ?>" data-bs-toggle="modal" data-bs-target="#payInvoiceModal" title="Payment"><i class="bi bi-cash-coin"></i></button>
<?php endif; ?>
<button class="btn btn-outline-secondary" onclick='viewAndPrintA4Invoice(<?= json_encode($inv) ?>)' title="Print A4 Invoice"><i class="bi bi-printer"></i></button>
<button class="btn btn-outline-secondary print-a4-btn" data-json="<?= htmlspecialchars(json_encode($inv)) ?>" title="Print A4 Invoice"><i class="bi bi-printer"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this invoice?')">
<input type="hidden" name="id" value="<?= $inv['id'] ?>">
<button type="submit" name="delete_invoice" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
@ -3408,108 +3389,22 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
// View Invoice Logic
document.querySelectorAll('.view-invoice-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
document.getElementById('invNumber').textContent = 'INV-' + data.id.toString().padStart(5, '0');
document.getElementById('invDate').textContent = data.invoice_date;
document.getElementById('invPaymentType').textContent = data.payment_type ? data.payment_type.toUpperCase() : 'CASH';
document.getElementById('invCustomerName').textContent = data.customer_name || '---';
const taxIdEl = document.getElementById('invCustomerTaxId');
const taxIdContainer = document.getElementById('invCustomerTaxIdContainer');
if (data.customer_tax_id) {
taxIdEl.textContent = data.customer_tax_id;
taxIdContainer.style.display = 'block';
} else {
taxIdContainer.style.display = 'none';
// 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);
}
document.getElementById('invPartyLabel').textContent = data.type === 'sale' ? 'Bill To' : 'Bill From';
document.getElementById('invPartyLabel').setAttribute('data-en', data.type === 'sale' ? 'Bill To' : 'Bill From');
document.getElementById('invPartyLabel').setAttribute('data-ar', data.type === 'sale' ? 'فاتورة إلى' : 'فاتورة من');
document.getElementById('invoiceTypeLabel').textContent = data.type;
document.getElementById('invoiceTypeLabel').className = 'badge text-uppercase ' + (data.type === 'sale' ? 'bg-success' : 'bg-warning');
const statusLabel = document.getElementById('invoiceStatusLabel');
let statusClass = 'bg-secondary';
let statusEn = data.status.charAt(0).toUpperCase() + data.status.slice(1);
let statusAr = data.status;
if (data.status === 'paid') {
statusClass = 'bg-success';
statusAr = 'مدفوع';
} else if (data.status === 'unpaid') {
statusClass = 'bg-danger';
statusAr = 'غير مدفوع';
} else if (data.status === 'partially_paid') {
statusClass = 'bg-warning text-dark';
statusEn = 'Partially Paid';
statusAr = 'مدفوع جزئياً';
}
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);
}
statusLabel.textContent = statusEn;
statusLabel.setAttribute('data-en', statusEn);
statusLabel.setAttribute('data-ar', statusAr);
statusLabel.className = 'badge text-uppercase ' + statusClass;
const body = document.getElementById('invItemsBody');
body.innerHTML = '';
data.items.forEach(item => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${item.name_en} / ${item.name_ar}</td>
<td class="text-center">${item.quantity}</td>
<td class="text-end">OMR ${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-end">${parseFloat(item.vat_rate || 0).toFixed(3)}%</td>
<td class="text-end">OMR ${parseFloat(item.total_price).toFixed(3)}</td>
`;
body.appendChild(tr);
});
document.getElementById('invSubtotal').textContent = 'OMR ' + parseFloat(data.total_amount).toFixed(3);
document.getElementById('invVatAmount').textContent = 'OMR ' + (parseFloat(data.vat_amount) || 0).toFixed(3);
const grandTotalValue = (parseFloat(data.total_with_vat) || parseFloat(data.total_amount));
document.getElementById('invGrandTotal').textContent = 'OMR ' + grandTotalValue.toFixed(3);
// Payment Info
document.getElementById('invTotalInfo').textContent = 'OMR ' + grandTotalValue.toFixed(3);
document.getElementById('invPaidInfo').textContent = 'OMR ' + parseFloat(data.paid_amount || 0).toFixed(3);
const balance = grandTotalValue - parseFloat(data.paid_amount || 0);
document.getElementById('invBalanceInfo').textContent = 'OMR ' + balance.toFixed(3);
// Fetch and show payment history
const paymentsBody = document.getElementById('invPaymentsBody');
const paymentsSection = document.getElementById('invPaymentsSection');
paymentsBody.innerHTML = '';
fetch(`index.php?action=get_payments&invoice_id=${data.id}`)
.then(res => res.json())
.then(payments => {
if (payments && payments.length > 0) {
payments.forEach(p => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${p.payment_date}</td>
<td>${p.payment_method}</td>
<td class="text-end fw-bold">OMR ${parseFloat(p.amount).toFixed(3)}</td>
`;
paymentsBody.appendChild(tr);
});
paymentsSection.style.display = 'block';
} else {
paymentsSection.style.display = 'none';
}
});
// Generate QR Code
const companyName = "<?= htmlspecialchars($data['settings']['company_name'] ?? 'My Company') ?>";
const vatNo = "<?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?>";
const total = (parseFloat(data.total_with_vat) || parseFloat(data.total_amount)).toFixed(3);
const qrData = `Seller: ${companyName}\nVAT: ${vatNo}\nInvoice: INV-${data.id.toString().padStart(5, '0')}\nDate: ${data.invoice_date}\nTotal: ${total}`;
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=${encodeURIComponent(qrData)}`;
document.getElementById('invQrCode').innerHTML = `<img src="${qrUrl}" alt="QR Code" style="width: 100px; height: 100px;" class="border p-1 bg-white">`;
});
}
});
});
</script>
@ -3717,91 +3612,174 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</div>
<style>
@media print {
.no-print, .sidebar, .topbar, .card, .btn, .modal-header, .modal-footer, .d-print-none, .table-responsive,
table:not(.table-formal), .bg-light:not(.invoice-info-card):not(.p-3), .modal-backdrop { display: none !important; }
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; }
.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; }
.badge { border: 1px solid #000; color: #000 !important; }
/* 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; }
}
.invoice-logo { max-height: 80px; width: auto; }
.invoice-header { border-bottom: 2px solid #333; padding-bottom: 20px; }
.invoice-title { font-size: 2.5rem; color: #333; letter-spacing: 2px; }
.invoice-info-card { background: #f8f9fa; border-radius: 8px; padding: 15px; height: 100%; }
.table-formal thead th { background: #333; color: #fff; border: none; text-transform: uppercase; font-size: 0.85rem; }
</style>
<div class="modal fade" id="viewInvoiceModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header">
<div class="modal-header d-print-none">
<h5 class="modal-title" data-en="View Invoice" data-ar="عرض الفاتورة">View Invoice</h5>
<button type="button" class="btn-close d-print-none" data-bs-dismiss="modal"></button>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4" id="invoicePrintableArea">
<div class="row mb-4">
<div class="col-6">
<h3 class="mb-0 fw-bold"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small mb-0"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
<p class="text-muted small">VAT: <?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?></p>
</div>
<div class="col-6 text-end">
<h2 class="text-uppercase text-muted fw-bold mb-0">Invoice</h2>
<div class="mb-2"><span id="invoiceTypeLabel" class="badge"></span></div>
<p class="mb-0"><strong>No: <span id="invNumber"></span></strong></p>
<p class="mb-0 text-muted">Date: <span id="invDate"></span></p>
<p class="text-muted small">Status: <span id="invoiceStatusLabel"></span></p>
</div>
</div>
<div class="row mb-4 p-3 bg-light rounded">
<div class="col-6">
<p class="text-muted small text-uppercase fw-bold mb-1" id="invPartyLabel">Bill To</p>
<p class="mb-0 fw-bold"><span id="invCustomerName"></span></p>
<p class="small text-muted mb-0" id="invCustomerTaxIdContainer">VAT: <span id="invCustomerTaxId"></span></p>
</div>
<div class="col-6 text-end">
<p class="text-muted small text-uppercase fw-bold mb-1">Payment Type</p>
<p class="mb-0"><span id="invPaymentType"></span></p>
</div>
</div>
<table class="table table-bordered">
<thead class="bg-light">
<tr>
<th>Item Description</th>
<th class="text-center">Qty</th>
<th class="text-end">Unit Price</th>
<th class="text-end">VAT %</th>
<th class="text-end">Total</th>
</tr>
</thead>
<tbody id="invItemsBody"></tbody>
</table>
<div class="row justify-content-end mt-4">
<div class="col-md-5">
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Subtotal</span>
<span id="invSubtotal" class="fw-bold"></span>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">VAT Amount</span>
<span id="invVatAmount" class="fw-bold"></span>
</div>
<hr>
<div class="d-flex justify-content-between mb-3">
<span class="h5 fw-bold">Grand Total</span>
<span id="invGrandTotal" class="h5 fw-bold text-primary"></span>
</div>
<div id="invPaymentsSection" class="mt-4 border-top pt-3">
<p class="text-muted small text-uppercase fw-bold mb-2">Payment History</p>
<table class="table table-sm table-borderless small mb-0">
<tbody id="invPaymentsBody"></tbody>
</table>
<div class="bg-light p-2 mt-2 rounded">
<div class="d-flex justify-content-between small mb-1">
<span>Total Amount</span>
<span id="invTotalInfo"></span>
</div>
<div class="d-flex justify-content-between small mb-1 text-success">
<span>Paid Amount</span>
<span id="invPaidInfo"></span>
</div>
<div class="d-flex justify-content-between small fw-bold text-danger">
<span>Remaining Balance</span>
<span id="invBalanceInfo"></span>
<div class="modal-body p-0" id="invoicePrintableArea">
<div class="p-5">
<div class="invoice-header mb-4">
<div class="row align-items-center">
<div class="col-6">
<?php
$logo = $data['settings']['company_logo'] ?? $_SERVER['PROJECT_IMAGE_URL'] ?? '';
if ($logo):
?>
<img src="<?= htmlspecialchars($logo) ?>" alt="Logo" class="invoice-logo mb-3">
<?php endif; ?>
<h3 class="mb-1 fw-bold"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small mb-0"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
<p class="text-muted small mb-0">VAT: <?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?></p>
<?php if (!empty($data['settings']['company_phone'])): ?>
<p class="text-muted small mb-0">Tel: <?= htmlspecialchars($data['settings']['company_phone']) ?></p>
<?php endif; ?>
</div>
<div class="col-6 text-end">
<h1 class="invoice-title fw-bold mb-0 text-uppercase">Tax Invoice</h1>
<div class="mt-2"><span id="invoiceTypeLabel" class="badge"></span></div>
<div class="mt-3">
<p class="mb-0 fs-5">Invoice No: <strong id="invNumber" class="text-primary"></strong></p>
<p class="mb-0">Date: <span id="invDate" class="fw-bold"></span></p>
<p class="mb-0 small">Status: <span id="invoiceStatusLabel"></span></p>
</div>
</div>
</div>
</div>
</div>
<div class="mt-5 pt-3 border-top text-center">
<div id="invQrCode" class="mb-2"></div>
<p class="text-muted small">Generated by <?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></p>
<div class="row mb-4 g-3">
<div class="col-6">
<div class="invoice-info-card">
<p class="text-muted small text-uppercase fw-bold mb-2 border-bottom pb-1" id="invPartyLabel">Bill To</p>
<h5 class="mb-1 fw-bold"><span id="invCustomerName"></span></h5>
<p class="small text-muted mb-0" id="invCustomerTaxIdContainer">VAT: <span id="invCustomerTaxId"></span></p>
<p class="small text-muted mb-0" id="invCustomerPhoneContainer">Phone: <span id="invCustomerPhone"></span></p>
</div>
</div>
<div class="col-6">
<div class="invoice-info-card text-end">
<p class="text-muted small text-uppercase fw-bold mb-2 border-bottom pb-1">Payment Details</p>
<p class="mb-1">Method: <strong id="invPaymentType"></strong></p>
<p class="mb-0 small text-muted">Currency: <strong>OMR</strong></p>
</div>
</div>
</div>
<table class="table table-bordered table-formal">
<thead>
<tr>
<th>Item Description</th>
<th class="text-center" style="width: 80px;">Qty</th>
<th class="text-end" style="width: 120px;">Unit Price</th>
<th class="text-end" style="width: 100px;">VAT %</th>
<th class="text-end" style="width: 150px;">Total</th>
</tr>
</thead>
<tbody id="invItemsBody"></tbody>
</table>
<div class="row mt-4">
<div class="col-7">
<div class="p-3 bg-light rounded" style="min-height: 100px;">
<p class="text-muted small text-uppercase fw-bold mb-1">Amount in Words</p>
<p id="invAmountInWords" class="small fw-bold mb-0"></p>
</div>
<div class="mt-4">
<p class="text-muted small text-uppercase fw-bold mb-1">Terms & Conditions</p>
<ul class="small text-muted ps-3">
<li>Goods once sold will not be taken back or exchanged.</li>
<li>Payment is due within the agreed credit period.</li>
<li>This is a computer-generated invoice and does not require a physical signature.</li>
</ul>
</div>
</div>
<div class="col-5">
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Subtotal</span>
<span id="invSubtotal" class="fw-bold"></span>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">VAT Amount</span>
<span id="invVatAmount" class="fw-bold"></span>
</div>
<div class="d-flex justify-content-between mb-3 border-top pt-2">
<span class="h4 fw-bold">Grand Total</span>
<span id="invGrandTotal" class="h4 fw-bold text-primary"></span>
</div>
<div id="invPaymentsSection" class="mt-4 border-top pt-3">
<p class="text-muted small text-uppercase fw-bold mb-2">Payment Tracking</p>
<table class="table table-sm table-bordered small mb-3">
<thead class="table-light">
<tr>
<th>Date</th>
<th>Method</th>
<th class="text-end">Amount</th>
</tr>
</thead>
<tbody id="invPaymentsBody"></tbody>
</table>
<div class="bg-light p-3 rounded">
<div class="d-flex justify-content-between small mb-1">
<span>Paid Amount</span>
<span id="invPaidInfo" class="text-success fw-bold"></span>
</div>
<div class="d-flex justify-content-between small fw-bold">
<span>Balance Due</span>
<span id="invBalanceInfo" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
<div class="mt-5 pt-4 border-top text-center">
<div class="row">
<div class="col-4">
<div class="border-top pt-2 mx-auto" style="width: 150px;">
<p class="small text-muted">Customer Signature</p>
</div>
</div>
<div class="col-4">
<div id="invQrCode" class="mb-2 d-flex justify-content-center"></div>
</div>
<div class="col-4">
<div class="border-top pt-2 mx-auto" style="width: 150px;">
<p class="small text-muted">Authorized Signatory</p>
</div>
</div>
</div>
<p class="text-muted x-small mt-4 mb-0">Generated by <?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?> | Visit us at <?= $_SERVER['HTTP_HOST'] ?></p>
</div>
</div>
</div>
<div class="modal-footer d-print-none">
@ -3946,11 +3924,7 @@ document.addEventListener('DOMContentLoaded', function() {
<i class="bi bi-person-circle fs-3 text-secondary"></i>
</div>
<div id="creditCustomerSection" class="mt-2 pt-2 border-top" style="display:none;">
<label class="form-label smaller fw-bold mb-1">Select Credit Customer (Search by Name/Phone)</label>
<div class="input-group input-group-sm mb-1">
<span class="input-group-text bg-white"><i class="bi bi-search"></i></span>
<input type="text" id="creditCustomerSearch" class="form-control" placeholder="Search..." oninput="cart.filterCreditCustomers()">
</div>
<label class="form-label smaller fw-bold mb-1">Select Credit Customer</label>
<select id="paymentCreditCustomer" class="form-select form-select-sm" onchange="cart.syncCustomer(this.value)">
<option value="">--- Select Customer ---</option>
<?php foreach ($customers as $c): ?>
@ -4060,13 +4034,23 @@ document.addEventListener('DOMContentLoaded', function() {
<div id="posPrintArea" class="d-none d-print-block"></div>
<script>
window.viewAndPrintA4Invoice = function(data) {
window.viewAndPrintA4Invoice = function(data, autoPrint = true) {
if (!data) return;
// Reuse view logic
document.getElementById('invNumber').textContent = 'INV-' + data.id.toString().padStart(5, '0');
document.getElementById('invDate').textContent = data.invoice_date;
document.getElementById('invPaymentType').textContent = data.payment_type ? data.payment_type.toUpperCase() : 'CASH';
document.getElementById('invCustomerName').textContent = data.customer_name || '---';
const phoneEl = document.getElementById('invCustomerPhone');
const phoneContainer = document.getElementById('invCustomerPhoneContainer');
if (data.customer_phone) {
phoneEl.textContent = data.customer_phone;
phoneContainer.style.display = 'block';
} else {
phoneContainer.style.display = 'none';
}
const taxIdEl = document.getElementById('invCustomerTaxId');
const taxIdContainer = document.getElementById('invCustomerTaxIdContainer');
if (data.customer_tax_id) {
@ -4076,6 +4060,8 @@ document.addEventListener('DOMContentLoaded', function() {
taxIdContainer.style.display = 'none';
}
document.getElementById('invAmountInWords').textContent = data.total_in_words || '';
document.getElementById('invPartyLabel').textContent = data.type === 'sale' ? 'Bill To' : 'Bill From';
document.getElementById('invPartyLabel').setAttribute('data-en', data.type === 'sale' ? 'Bill To' : 'Bill From');
document.getElementById('invPartyLabel').setAttribute('data-ar', data.type === 'sale' ? 'فاتورة إلى' : 'فاتورة من');
@ -4084,7 +4070,7 @@ document.addEventListener('DOMContentLoaded', function() {
const statusLabel = document.getElementById('invoiceStatusLabel');
let statusClass = 'bg-secondary';
let statusEn = data.status.charAt(0).toUpperCase() + data.status.slice(1);
let statusEn = data.status ? (data.status.charAt(0).toUpperCase() + data.status.slice(1)) : '---';
if (data.status === 'paid') statusClass = 'bg-success';
else if (data.status === 'unpaid') statusClass = 'bg-danger';
else if (data.status === 'partially_paid') {
@ -4097,48 +4083,63 @@ document.addEventListener('DOMContentLoaded', function() {
const body = document.getElementById('invItemsBody');
body.innerHTML = '';
data.items.forEach(item => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${item.name_en} / ${item.name_ar}</td>
<td class="text-center">${item.quantity}</td>
<td class="text-end">OMR ${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-end">${parseFloat(item.vat_rate || 0).toFixed(3)}%</td>
<td class="text-end">OMR ${parseFloat(item.total_price).toFixed(3)}</td>
`;
body.appendChild(tr);
});
document.getElementById('invSubtotal').textContent = 'OMR ' + parseFloat(data.total_amount).toFixed(3);
document.getElementById('invVatAmount').textContent = 'OMR ' + (parseFloat(data.vat_amount) || 0).toFixed(3);
if (data.items) {
data.items.forEach(item => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${item.name_en} / ${item.name_ar}</td>
<td class="text-center">${item.quantity}</td>
<td class="text-end">OMR ${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-end">${parseFloat(item.vat_rate || 0).toFixed(3)}%</td>
<td class="text-end">OMR ${parseFloat(item.total_price).toFixed(3)}</td>
`;
body.appendChild(tr);
});
}
if (document.getElementById('invSubtotal')) document.getElementById('invSubtotal').textContent = 'OMR ' + parseFloat(data.total_amount).toFixed(3);
if (document.getElementById('invVatAmount')) document.getElementById('invVatAmount').textContent = 'OMR ' + (parseFloat(data.vat_amount) || 0).toFixed(3);
const grandTotalValue = (parseFloat(data.total_with_vat) || parseFloat(data.total_amount));
document.getElementById('invGrandTotal').textContent = 'OMR ' + grandTotalValue.toFixed(3);
if (document.getElementById('invGrandTotal')) document.getElementById('invGrandTotal').textContent = 'OMR ' + grandTotalValue.toFixed(3);
document.getElementById('invTotalInfo').textContent = 'OMR ' + grandTotalValue.toFixed(3);
document.getElementById('invPaidInfo').textContent = 'OMR ' + parseFloat(data.paid_amount || 0).toFixed(3);
if (document.getElementById('invPaidInfo')) document.getElementById('invPaidInfo').textContent = 'OMR ' + parseFloat(data.paid_amount || 0).toFixed(3);
const balance = grandTotalValue - parseFloat(data.paid_amount || 0);
document.getElementById('invBalanceInfo').textContent = 'OMR ' + balance.toFixed(3);
if (document.getElementById('invBalanceInfo')) document.getElementById('invBalanceInfo').textContent = 'OMR ' + balance.toFixed(3);
// Generate QR Code for Zakat, Tax and Customs Authority (ZATCA) style or simple formal
const companyName = <?= json_encode($data['settings']['company_name'] ?? 'Accounting System') ?>;
const vatNo = <?= json_encode($data['settings']['vat_number'] ?? '') ?>;
const qrData = `Seller: ${companyName}\nVAT: ${vatNo}\nInvoice: INV-${data.id.toString().padStart(5, '0')}\nDate: ${data.invoice_date}\nTotal: ${grandTotalValue.toFixed(3)}`;
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=${encodeURIComponent(qrData)}`;
if (document.getElementById('invQrCode')) {
document.getElementById('invQrCode').innerHTML = `<img src="${qrUrl}" alt="QR Code" style="width: 100px; height: 100px;" class="border p-1 bg-white">`;
}
const viewModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('viewInvoiceModal'));
viewModal.show();
if (autoPrint) {
setTimeout(() => { window.print(); }, 1000);
}
fetch(`index.php?action=get_payments&invoice_id=${data.id}`)
.then(res => res.json())
.then(payments => {
const paymentsBody = document.getElementById('invPaymentsBody');
const paymentsSection = document.getElementById('invPaymentsSection');
paymentsBody.innerHTML = '';
if (paymentsBody) paymentsBody.innerHTML = '';
if (payments && payments.length > 0) {
payments.forEach(p => {
const tr = document.createElement('tr');
tr.innerHTML = `<td>${p.payment_date}</td><td>${p.payment_method}</td><td class="text-end fw-bold">OMR ${parseFloat(p.amount).toFixed(3)}</td>`;
paymentsBody.appendChild(tr);
});
paymentsSection.style.display = 'block';
if (paymentsBody) {
payments.forEach(p => {
const tr = document.createElement('tr');
tr.innerHTML = `<td>${p.payment_date}</td><td>${p.payment_method}</td><td class="text-end fw-bold">OMR ${parseFloat(p.amount).toFixed(3)}</td>`;
paymentsBody.appendChild(tr);
});
}
if (paymentsSection) paymentsSection.style.display = 'block';
} else {
paymentsSection.style.display = 'none';
if (paymentsSection) paymentsSection.style.display = 'none';
}
const viewModal = new bootstrap.Modal(document.getElementById('viewInvoiceModal'));
viewModal.show();
setTimeout(() => { window.print(); }, 1000);
});
}).catch(err => console.error('Error fetching payments:', err));
};
window.printPosReceiptFromInvoice = function(inv) {