Autosave: 20260216-140209

This commit is contained in:
Flatlogic Bot 2026-02-16 14:02:09 +00:00
parent aaef7ed36c
commit d19d0d272e

701
index.php
View File

@ -73,6 +73,37 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action'])) {
echo json_encode($payment);
exit;
}
if ($_GET['action'] === 'get_held_carts') {
header('Content-Type: application/json');
$stmt = db()->query("SELECT h.*, c.name as customer_name FROM pos_held_carts h LEFT JOIN customers c ON h.customer_id = c.id ORDER BY h.id DESC");
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
exit;
}
if ($_GET['action'] === 'validate_discount') {
header('Content-Type: application/json');
$code = $_GET['code'] ?? '';
$stmt = db()->prepare("SELECT * FROM discount_codes WHERE code = ? AND status = 'active' AND (expiry_date IS NULL OR expiry_date >= CURDATE())");
$stmt->execute([$code]);
$discount = $stmt->fetch(PDO::FETCH_ASSOC);
if ($discount) {
echo json_encode(['success' => true, 'discount' => $discount]);
} else {
echo json_encode(['success' => false, 'error' => 'Invalid or expired code']);
}
exit;
}
if ($_GET['action'] === 'get_customer_loyalty') {
header('Content-Type: application/json');
$id = (int)($_GET['customer_id'] ?? 0);
$stmt = db()->prepare("SELECT loyalty_points FROM customers WHERE id = ?");
$stmt->execute([$id]);
$points = $stmt->fetchColumn();
echo json_encode(['success' => true, 'points' => (float)$points]);
exit;
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
@ -118,10 +149,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Loyalty Calculation: 1 point per 1 OMR spent on net amount
$loyalty_earned = floor($net_amount);
// Check if credit is used for walk-in
// Check if credit is used for walk-in or exceeds limit
$credit_total = 0;
foreach ($payments as $p) {
if ($p['method'] === 'credit' && !$customer_id) {
throw new Exception("Credit payment is only allowed for registered customers");
if ($p['method'] === 'credit') {
if (!$customer_id) {
throw new Exception("Credit payment is only allowed for registered customers");
}
$credit_total += (float)$p['amount'];
}
}
if ($customer_id && $credit_total > 0) {
$stmt = $db->prepare("SELECT balance, credit_limit FROM customers WHERE id = ?");
$stmt->execute([$customer_id]);
$cust = $stmt->fetch(PDO::FETCH_ASSOC);
if ($cust['credit_limit'] > 0 && (abs($cust['balance'] - $credit_total) > $cust['credit_limit'])) {
throw new Exception("Credit limit exceeded. Current Debt: " . number_format(abs($cust['balance']), 3) . ", Limit: " . number_format($cust['credit_limit'], 3));
}
}
@ -197,13 +241,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
exit;
}
if (isset($_GET['action']) && $_GET['action'] === 'get_held_carts') {
header('Content-Type: application/json');
$stmt = db()->query("SELECT h.*, c.name as customer_name FROM pos_held_carts h LEFT JOIN customers c ON h.customer_id = c.id ORDER BY h.id DESC");
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
exit;
}
if (isset($_POST['action']) && $_POST['action'] === 'delete_held_cart') {
header('Content-Type: application/json');
$id = (int)$_POST['id'];
@ -213,31 +250,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
exit;
}
if (isset($_GET['action']) && $_GET['action'] === 'validate_discount') {
header('Content-Type: application/json');
$code = $_GET['code'] ?? '';
$stmt = db()->prepare("SELECT * FROM discount_codes WHERE code = ? AND status = 'active' AND (expiry_date IS NULL OR expiry_date >= CURDATE())");
$stmt->execute([$code]);
$discount = $stmt->fetch(PDO::FETCH_ASSOC);
if ($discount) {
echo json_encode(['success' => true, 'discount' => $discount]);
} else {
echo json_encode(['success' => false, 'error' => 'Invalid or expired code']);
}
exit;
}
if (isset($_GET['action']) && $_GET['action'] === 'get_customer_loyalty') {
header('Content-Type: application/json');
$id = (int)($_GET['customer_id'] ?? 0);
$stmt = db()->prepare("SELECT loyalty_points FROM customers WHERE id = ?");
$stmt->execute([$id]);
$points = $stmt->fetchColumn();
echo json_encode(['success' => true, 'points' => (float)$points]);
exit;
}
if (isset($_POST['edit_customer'])) {
$id = (int)$_POST['id'];
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
@ -760,7 +774,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
$message = "Settings updated successfully!";
}
}
if (isset($_POST['record_payment'])) {
$invoice_id = (int)$_POST['invoice_id'];
@ -807,6 +820,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
}
}
// Routing & Data Fetching
@ -1644,10 +1658,14 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<span class="input-group-text bg-transparent border-end-0"><i class="bi bi-search"></i></span>
<input type="text" id="productSearch" class="form-control border-start-0" placeholder="Search products by name or SKU..." data-en="Search products..." data-ar="بحث عن منتجات...">
</div>
<div class="input-group" style="width: 250px;">
<div class="input-group" style="width: 200px;">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-upc-scan"></i></span>
<input type="text" id="barcodeInput" class="form-control border-start-0" placeholder="Scan barcode..." data-en="Scan barcode..." data-ar="امسح الباركود..." autofocus>
</div>
<button class="btn btn-warning d-flex align-items-center gap-2" onclick="cart.openHeldCartsModal()">
<i class="bi bi-pause-btn-fill"></i>
<span class="d-none d-xl-inline" data-en="Held List" data-ar="قائمة الانتظار">Held List</span>
</button>
</div>
<div class="product-grid" id="productGrid">
<?php foreach ($products as $p): ?>
@ -1674,8 +1692,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<div class="p-3 border-bottom d-flex justify-content-between align-items-center">
<h6 class="m-0 fw-bold"><i class="bi bi-cart3 me-2"></i>Cart</h6>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-warning" onclick="cart.openHeldCartsModal()" title="Resume Cart"><i class="bi bi-pause-fill"></i></button>
<button class="btn btn-sm btn-outline-secondary" onclick="cart.hold()" title="Hold Cart"><i class="bi bi-hand-index-thumb"></i></button>
<button class="btn btn-sm btn-outline-warning" onclick="cart.openHeldCartsModal()" title="Held List"><i class="bi bi-list-task"></i></button>
<button class="btn btn-sm btn-outline-secondary" onclick="cart.hold()" title="Hold Cart"><i class="bi bi-pause-circle"></i></button>
<button class="btn btn-sm btn-outline-danger" onclick="cart.clear()" title="Clear Cart"><i class="bi bi-trash"></i></button>
</div>
</div>
@ -1836,42 +1854,77 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
}
},
async openHeldCartsModal() {
const resp = await fetch('index.php?action=get_held_carts');
const carts = await resp.json();
let html = '<div class="list-group">';
if (carts.length === 0) html += '<p class="text-center p-3">No held carts</p>';
carts.forEach(c => {
html += `
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="text-start">
<strong>${c.cart_name}</strong><br>
<small>${c.customer_name || 'Walk-in'} - ${c.created_at}</small>
try {
const resp = await fetch('index.php?action=get_held_carts');
const text = await resp.text();
let carts;
try {
carts = JSON.parse(text);
} catch (e) {
console.error('Failed to parse held carts:', text);
throw new Error('Invalid server response');
}
const lang = document.documentElement.lang || 'en';
let html = '<div class="list-group list-group-flush shadow-sm rounded">';
if (carts.length === 0) {
html += `
<div class="text-center p-5 text-muted">
<i class="bi bi-folder2-open mb-3 d-block" style="font-size: 3rem;"></i>
<p data-en="No held carts found" data-ar="لا توجد طلبات معلقة">${lang === 'ar' ? 'لا توجد طلبات معلقة' : 'No held carts found'}</p>
</div>`;
}
carts.forEach(c => {
html += `
<div class="list-group-item d-flex justify-content-between align-items-center p-3 hover-bg-light border-start-0 border-end-0">
<div class="text-start">
<div class="fw-bold text-primary">${c.cart_name}</div>
<div class="small text-muted">
<i class="bi bi-person me-1"></i>${c.customer_name || (lang === 'ar' ? 'عميل عابر' : 'Walk-in')}
<span class="mx-2 text-silver">|</span>
<i class="bi bi-clock me-1"></i>${new Date(c.created_at).toLocaleString()}
</div>
</div>
<div class="btn-group">
<button class="btn btn-sm btn-primary" onclick="cart.resume(${c.id})">
<i class="bi bi-arrow-repeat me-1"></i><span data-en="Resume" data-ar="استرجاع">${lang === 'ar' ? 'استرجاع' : 'Resume'}</span>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="cart.deleteHeld(${c.id})">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
<div class="btn-group">
<button class="btn btn-sm btn-primary" onclick="cart.resume(${c.id})">Resume</button>
<button class="btn btn-sm btn-danger" onclick="cart.deleteHeld(${c.id})">Delete</button>
</div>
</div>
`;
});
html += '</div>';
Swal.fire({
title: 'Held Carts',
html: html,
showConfirmButton: false,
width: '600px'
});
`;
});
html += '</div>';
Swal.fire({
title: lang === 'ar' ? 'الطلبات المعلقة' : 'Held Carts',
html: html,
showConfirmButton: false,
width: '700px',
customClass: {
container: 'held-carts-swal'
}
});
} catch (err) {
console.error(err);
Swal.fire('Error', 'Failed to load held carts: ' + err.message, 'error');
}
},
async resume(id) {
const resp = await fetch('index.php?action=get_held_carts');
const carts = await resp.json();
const c = carts.find(x => x.id == id);
if (c) {
this.items = JSON.parse(c.items_json);
document.getElementById('posCustomer').value = c.customer_id || '';
await this.onCustomerChange();
await this.deleteHeld(id, true);
Swal.close();
try {
const resp = await fetch('index.php?action=get_held_carts');
const carts = await resp.json();
const c = carts.find(x => x.id == id);
if (c) {
this.items = JSON.parse(c.items_json);
document.getElementById('posCustomer').value = c.customer_id || '';
await this.onCustomerChange();
await this.deleteHeld(id, true);
Swal.close();
}
} catch (err) {
console.error(err);
Swal.fire('Error', 'Failed to resume cart', 'error');
}
},
async deleteHeld(id, silent = false) {
@ -1942,6 +1995,10 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
async checkout() {
if (this.items.length === 0) return;
const customerSelect = document.getElementById('posCustomer');
const customerName = customerSelect.options[customerSelect.selectedIndex].text;
document.getElementById('paymentCustomerName').innerText = customerName;
const subtotal = this.items.reduce((sum, item) => sum + (item.price * item.qty), 0);
let discountAmount = 0;
if (this.discount) {
@ -1953,8 +2010,22 @@ $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);
// Sync credit customer selection if credit is default or already selected
const creditSection = document.getElementById('creditCustomerSection');
if (this.selectedPaymentMethod === 'credit') {
creditSection.style.display = 'block';
this.filterCreditCustomers();
document.getElementById('paymentCreditCustomer').value = customerSelect.value;
} else {
creditSection.style.display = 'none';
}
this.updateRemaining();
const modal = new bootstrap.Modal(document.getElementById('posPaymentModal'));
@ -1964,6 +2035,20 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
this.selectedPaymentMethod = method;
document.querySelectorAll('.payment-method-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
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;
} else {
creditSection.style.display = 'none';
}
this.updateRemaining();
},
fillPartial(amount) {
@ -2222,6 +2307,44 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
document.getElementById('posReceiptModal').addEventListener('hidden.bs.modal', function () {
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');
const customerName = customerSelect.options[customerSelect.selectedIndex].text;
document.getElementById('paymentCustomerName').innerText = customerName;
await this.onCustomerChange();
}
};
@ -2372,14 +2495,29 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<td><?= $inv['invoice_date'] ?></td>
<td><?= htmlspecialchars($inv['customer_name'] ?? '---') ?></td>
<td>
<span class="badge bg-secondary"><?= htmlspecialchars($inv['status']) ?></span>
<?php
$statusClass = 'bg-secondary';
if ($inv['status'] === 'paid') $statusClass = 'bg-success';
elseif ($inv['status'] === 'unpaid') $statusClass = 'bg-danger';
elseif ($inv['status'] === 'partially_paid') $statusClass = 'bg-warning text-dark';
?>
<span class="badge text-uppercase <?= $statusClass ?>"><?= htmlspecialchars(str_replace('_', ' ', $inv['status'])) ?></span>
</td>
<td class="text-end fw-bold">OMR <?= number_format((float)$inv['total_with_vat'], 3) ?></td>
<td class="text-end text-success">OMR <?= number_format((float)$inv['paid_amount'], 3) ?></td>
<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"><i class="bi bi-printer"></i></button>
<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>
<?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>
<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>
</form>
</div>
</td>
</tr>
@ -3379,6 +3517,206 @@ document.addEventListener('DOMContentLoaded', function() {
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<!-- View Invoice Modal -->
<!-- Add Invoice Modal -->
<div class="modal fade" id="addInvoiceModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" data-en="Create New Tax Invoice" data-ar="إنشاء فاتورة ضريبية جديدة">Create New Tax Invoice</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="type" value="<?= $page === 'sales' ? 'sale' : 'purchase' ?>">
<div class="modal-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-4">
<label class="form-label fw-bold" data-en="<?= $page === 'sales' ? 'Customer' : 'Supplier' ?>" data-ar="<?= $page === 'sales' ? 'العميل' : 'المورد' ?>"><?= $page === 'sales' ? 'Customer' : 'Supplier' ?></label>
<select name="customer_id" class="form-select" required>
<option value="">---</option>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Date" data-ar="التاريخ">Date</label>
<input type="date" name="invoice_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Payment Type" data-ar="طريقة الدفع">Payment Type</label>
<select name="payment_type" class="form-select">
<option value="cash">Cash</option>
<option value="card">Card</option>
<option value="bank_transfer">Bank Transfer</option>
<option value="credit">Credit</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Status" data-ar="الحالة">Status</label>
<select name="status" id="add_status" class="form-select">
<option value="unpaid">Unpaid</option>
<option value="partially_paid">Partially Paid</option>
<option value="paid">Paid</option>
</select>
</div>
<div class="col-md-2" id="addPaidAmountContainer" style="display: none;">
<label class="form-label fw-bold" data-en="Paid Amount" data-ar="المبلغ المدفوع">Paid Amount</label>
<input type="number" step="0.001" name="paid_amount" class="form-control" value="0.000">
</div>
</div>
<div class="card mb-4">
<div class="card-body bg-light">
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
<div class="position-relative">
<input type="text" id="productSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
<div id="searchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead class="bg-light">
<tr>
<th style="width: 40%;" data-en="Item Details" data-ar="تفاصيل الصنف">Item Details</th>
<th style="width: 15%;" data-en="Qty" data-ar="الكمية">Qty</th>
<th style="width: 15%;" data-en="Unit Price" data-ar="سعر الوحدة">Unit Price</th>
<th style="width: 10%;" data-en="VAT" data-ar="الضريبة">VAT</th>
<th style="width: 15%;" data-en="Total" data-ar="الإجمالي">Total</th>
<th style="width: 5%;"></th>
</tr>
</thead>
<tbody id="invoiceItemsTableBody"></tbody>
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="subtotal">OMR 0.000</td>
<td></td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="totalVat">OMR 0.000</td>
<td></td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="grandTotal">OMR 0.000</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_invoice" class="btn btn-primary" data-en="Create Invoice" data-ar="إنشاء الفاتورة">Create Invoice</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit Invoice Modal -->
<div class="modal fade" id="editInvoiceModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" data-en="Edit Invoice" data-ar="تعديل الفاتورة">Edit Invoice</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="invoice_id" id="edit_invoice_id">
<div class="modal-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-4">
<label class="form-label fw-bold" data-en="<?= $page === 'sales' ? 'Customer' : 'Supplier' ?>" data-ar="<?= $page === 'sales' ? 'العميل' : 'المورد' ?>"><?= $page === 'sales' ? 'Customer' : 'Supplier' ?></label>
<select name="customer_id" id="edit_customer_id" class="form-select" required>
<option value="">---</option>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Date" data-ar="التاريخ">Date</label>
<input type="date" name="invoice_date" id="edit_invoice_date" class="form-control" required>
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Payment Type" data-ar="طريقة الدفع">Payment Type</label>
<select name="payment_type" id="edit_payment_type" class="form-select">
<option value="cash">Cash</option>
<option value="card">Card</option>
<option value="bank_transfer">Bank Transfer</option>
<option value="credit">Credit</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Status" data-ar="الحالة">Status</label>
<select name="status" id="edit_status" class="form-select">
<option value="unpaid">Unpaid</option>
<option value="partially_paid">Partially Paid</option>
<option value="paid">Paid</option>
</select>
</div>
<div class="col-md-2" id="editPaidAmountContainer" style="display: none;">
<label class="form-label fw-bold" data-en="Paid Amount" data-ar="المبلغ المدفوع">Paid Amount</label>
<input type="number" step="0.001" name="paid_amount" id="edit_paid_amount" class="form-control">
</div>
</div>
<div class="card mb-4">
<div class="card-body bg-light">
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
<div class="position-relative">
<input type="text" id="editProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
<div id="editSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead class="bg-light">
<tr>
<th style="width: 40%;" data-en="Item Details" data-ar="تفاصيل الصنف">Item Details</th>
<th style="width: 15%;" data-en="Qty" data-ar="الكمية">Qty</th>
<th style="width: 15%;" data-en="Unit Price" data-ar="سعر الوحدة">Unit Price</th>
<th style="width: 10%;" data-en="VAT" data-ar="الضريبة">VAT</th>
<th style="width: 15%;" data-en="Total" data-ar="الإجمالي">Total</th>
<th style="width: 5%;"></th>
</tr>
</thead>
<tbody id="editInvoiceItemsTableBody"></tbody>
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="edit_subtotal">OMR 0.000</td>
<td></td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="edit_totalVat">OMR 0.000</td>
<td></td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="edit_grandTotal">OMR 0.000</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_invoice" class="btn btn-primary" data-en="Update Invoice" data-ar="تحديث الفاتورة">Update Invoice</button>
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" id="viewInvoiceModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
@ -3474,6 +3812,55 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</div>
<!-- Pay Invoice Modal -->
<div class="modal fade" id="payInvoiceModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" data-en="Record Payment" data-ar="تسجيل دفعة">Record Payment</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<input type="hidden" name="invoice_id" id="pay_invoice_id">
<div class="mb-3">
<label class="form-label fw-bold" data-en="Total Amount" data-ar="المبلغ الإجمالي">Total Amount</label>
<input type="text" id="pay_invoice_total" class="form-control" readonly>
</div>
<div class="mb-3">
<label class="form-label fw-bold" data-en="Remaining Amount" data-ar="المبلغ المتبقي">Remaining Amount</label>
<input type="text" id="pay_remaining_amount" class="form-control" readonly>
</div>
<div class="mb-3">
<label class="form-label fw-bold" data-en="Amount to Pay" data-ar="المبلغ المراد دفعه">Amount to Pay</label>
<input type="number" step="0.001" name="amount" id="pay_amount" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label fw-bold" data-en="Payment Date" data-ar="تاريخ الدفع">Payment Date</label>
<input type="date" name="payment_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="mb-3">
<label class="form-label fw-bold" data-en="Payment Method" data-ar="طريقة الدفع">Payment Method</label>
<select name="payment_method" class="form-select" required>
<option value="Cash">Cash</option>
<option value="Card">Card</option>
<option value="Bank Transfer">Bank Transfer</option>
</select>
</div>
<div class="mb-3">
<label class="form-label fw-bold" data-en="Notes" data-ar="ملاحظات">Notes</label>
<textarea name="notes" class="form-control" rows="2"></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>
<button type="submit" name="record_payment" class="btn btn-success" data-en="Save Payment" data-ar="حفظ الدفعة">Save Payment</button>
</div>
</form>
</div>
</div>
</div>
<!-- Payment Receipt Modal -->
<div class="modal fade" id="receiptModal" tabindex="-1">
<div class="modal-dialog">
@ -3550,6 +3937,29 @@ document.addEventListener('DOMContentLoaded', function() {
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3 p-3 border rounded bg-light shadow-sm">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="small text-muted d-block">Customer</span>
<span class="h6 fw-bold m-0 text-primary" id="paymentCustomerName">Walk-in Customer</span>
</div>
<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>
<select id="paymentCreditCustomer" class="form-select form-select-sm" onchange="cart.syncCustomer(this.value)">
<option value="">--- Select Customer ---</option>
<?php foreach ($customers as $c): ?>
<option value="<?= $c['id'] ?>" data-search="<?= htmlspecialchars(strtolower($c['name'] . ' ' . ($c['phone'] ?? ''))) ?>"><?= htmlspecialchars($c['name']) ?> (<?= htmlspecialchars($c['phone'] ?? '') ?>)</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="amount-due-box mb-3">
<div class="d-flex justify-content-between px-3">
<div class="text-start">
@ -3650,6 +4060,151 @@ document.addEventListener('DOMContentLoaded', function() {
<div id="posPrintArea" class="d-none d-print-block"></div>
<script>
window.viewAndPrintA4Invoice = function(data) {
// 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 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';
}
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);
if (data.status === 'paid') statusClass = 'bg-success';
else if (data.status === 'unpaid') statusClass = 'bg-danger';
else if (data.status === 'partially_paid') {
statusClass = 'bg-warning text-dark';
statusEn = 'Partially Paid';
}
statusLabel.textContent = statusEn;
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);
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(`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 (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';
}
const viewModal = new bootstrap.Modal(document.getElementById('viewInvoiceModal'));
viewModal.show();
setTimeout(() => { window.print(); }, 1000);
});
};
window.printPosReceiptFromInvoice = function(inv) {
const container = document.getElementById('posReceiptContent');
const itemsHtml = inv.items.map(item => `
<tr>
<td>${item.name_en}<br><small>${item.quantity} x ${parseFloat(item.unit_price).toFixed(3)}</small></td>
<td style="text-align: right; vertical-align: bottom;">${(item.unit_price * item.quantity).toFixed(3)}</td>
</tr>
`).join('');
const companyName = "<?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>";
const companyPhone = "<?= htmlspecialchars($data['settings']['company_phone'] ?? '') ?>";
const companyVat = "<?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?>";
container.innerHTML = `
<div class="thermal-receipt">
<div class="center">
<h5 class="mb-0 fw-bold">${companyName}</h5>
${companyPhone ? `<div>Tel: ${companyPhone}</div>` : ''}
${companyVat ? `<div>VAT: ${companyVat}</div>` : ''}
<div class="separator"></div>
<h6 class="fw-bold">TAX INVOICE</h6>
<div>Inv: INV-${inv.id.toString().padStart(5, '0')}</div>
<div>Date: ${inv.invoice_date}</div>
<div class="separator"></div>
</div>
<div>
<strong>Customer:</strong> ${inv.customer_name || 'Walk-in'}
</div>
<div class="separator"></div>
<table>
<thead>
<tr>
<th>ITEM</th>
<th style="text-align: right;">TOTAL</th>
</tr>
</thead>
<tbody>
${itemsHtml}
</tbody>
</table>
<div class="separator"></div>
<div class="total-row d-flex justify-content-between">
<span>TOTAL</span>
<span>OMR ${parseFloat(inv.total_with_vat).toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between small">
<span>PAID</span>
<span>OMR ${parseFloat(inv.paid_amount).toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between small fw-bold">
<span>BALANCE</span>
<span>OMR ${(inv.total_with_vat - inv.paid_amount).toFixed(3)}</span>
</div>
<div class="separator"></div>
<div class="center small">
<p>Thank You for your business!</p>
</div>
</div>
`;
const posModal = new bootstrap.Modal(document.getElementById('posReceiptModal'));
posModal.show();
};
function printPosReceipt() {
const content = document.getElementById('posReceiptContent').innerHTML;
const printArea = document.getElementById('posPrintArea');