addin point of sale
This commit is contained in:
parent
c826515451
commit
49dc107b18
@ -65,7 +65,7 @@ body {
|
||||
}
|
||||
|
||||
.nav-section-title {
|
||||
font-size: 0.7rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: #64748b !important;
|
||||
letter-spacing: 0.05em;
|
||||
@ -303,3 +303,90 @@ body {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Payment Modal Styles */
|
||||
.payment-methods-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.payment-method-btn {
|
||||
border: 2px solid #e2e8f0;
|
||||
background: #fff;
|
||||
padding: 15px 10px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.payment-method-btn.active {
|
||||
border-color: #3b82f6;
|
||||
background: #eff6ff;
|
||||
color: #3b82f6;
|
||||
}
|
||||
.payment-method-btn i {
|
||||
font-size: 1.5rem;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.quick-pay-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.quick-pay-btn {
|
||||
padding: 10px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
.quick-pay-btn:hover {
|
||||
background: #edf2f7;
|
||||
}
|
||||
|
||||
.amount-due-box {
|
||||
background: #f1f5f9;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.amount-due-box .label {
|
||||
font-size: 0.75rem;
|
||||
color: #64748b;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
.amount-due-box .value {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
color: #0f172a;
|
||||
}
|
||||
.payment-line {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
.payment-line .method {
|
||||
font-weight: 600;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.payment-line .amount {
|
||||
font-weight: 700;
|
||||
}
|
||||
.payment-summary {
|
||||
border-top: 2px dashed #dee2e6;
|
||||
margin-top: 15px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
344
index.php
344
index.php
@ -101,19 +101,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$db->beginTransaction();
|
||||
|
||||
$customer_id = !empty($_POST['customer_id']) ? (int)$_POST['customer_id'] : null;
|
||||
$payment_method = $_POST['payment_method'] ?? 'cash';
|
||||
$payments = json_decode($_POST['payments'] ?? '[]', true);
|
||||
$total_amount = (float)$_POST['total_amount'];
|
||||
$discount_code_id = !empty($_POST['discount_code_id']) ? (int)$_POST['discount_code_id'] : null;
|
||||
$discount_amount = (float)($_POST['discount_amount'] ?? 0);
|
||||
$loyalty_redeemed = (float)($_POST['loyalty_redeemed'] ?? 0);
|
||||
$items = json_decode($_POST['items'], true);
|
||||
$items = json_decode($_POST['items'] ?? '[]', true);
|
||||
|
||||
$net_amount = $total_amount - $discount_amount - $loyalty_redeemed;
|
||||
if (empty($items)) {
|
||||
throw new Exception("Cart is empty");
|
||||
}
|
||||
|
||||
$net_amount = (float)($total_amount - $discount_amount - $loyalty_redeemed);
|
||||
if ($net_amount < 0) $net_amount = 0;
|
||||
|
||||
// Loyalty Calculation: 1 point per 1 OMR spent on net amount
|
||||
$loyalty_earned = floor($net_amount);
|
||||
|
||||
// Check if credit is used for walk-in
|
||||
foreach ($payments as $p) {
|
||||
if ($p['method'] === 'credit' && !$customer_id) {
|
||||
throw new Exception("Credit payment is only allowed for registered customers");
|
||||
}
|
||||
}
|
||||
|
||||
// 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]);
|
||||
@ -122,37 +133,52 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// 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);
|
||||
$stmt->execute([$transaction_no, $customer_id, $total_amount, $discount_code_id, $discount_amount, $loyalty_earned, $loyalty_redeemed, $net_amount, $payment_method]);
|
||||
$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();
|
||||
|
||||
foreach ($items as $item) {
|
||||
$qty = (float)$item['qty'];
|
||||
$price = (float)$item['price'];
|
||||
$subtotal = $qty * $price;
|
||||
|
||||
// Add to invoice_items
|
||||
$stmt = $db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$invoice_id, $item['id'], $item['qty'], $item['price'], $item['qty'] * $item['price']]);
|
||||
$stmt->execute([$invoice_id, $item['id'], $qty, $price, $subtotal]);
|
||||
|
||||
// Add to pos_items
|
||||
$stmt = $db->prepare("INSERT INTO pos_items (transaction_id, product_id, quantity, unit_price, subtotal) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$pos_id, $item['id'], $item['qty'], $item['price'], $item['qty'] * $item['price']]);
|
||||
$stmt->execute([$pos_id, $item['id'], $qty, $price, $subtotal]);
|
||||
|
||||
// Update stock
|
||||
$stmt = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
|
||||
$stmt->execute([$item['qty'], $item['id']]);
|
||||
$stmt->execute([$qty, $item['id']]);
|
||||
}
|
||||
|
||||
// Update Customer Loyalty Points
|
||||
// Update Customer Loyalty Points and Balance
|
||||
if ($customer_id) {
|
||||
$stmt = $db->prepare("UPDATE customers SET loyalty_points = loyalty_points - ? + ? WHERE id = ?");
|
||||
$stmt->execute([$loyalty_redeemed, $loyalty_earned, $customer_id]);
|
||||
$credit_total = 0;
|
||||
foreach ($payments as $p) {
|
||||
if ($p['method'] === 'credit') {
|
||||
$credit_total += (float)$p['amount'];
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE customers SET loyalty_points = loyalty_points - ? + ?, balance = balance - ? WHERE id = ?");
|
||||
$stmt->execute([(float)$loyalty_redeemed, (float)$loyalty_earned, (float)$credit_total, $customer_id]);
|
||||
}
|
||||
|
||||
// Add Payment
|
||||
$stmt = $db->prepare("INSERT INTO payments (invoice_id, amount, payment_date, payment_method, notes) VALUES (?, ?, CURDATE(), ?, 'POS Transaction')");
|
||||
$stmt->execute([$invoice_id, $net_amount, $payment_method]);
|
||||
// Add Payments
|
||||
foreach ($payments as $p) {
|
||||
$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']]);
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
echo json_encode(['success' => true, 'invoice_id' => $invoice_id, 'transaction_no' => $transaction_no]);
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
if (isset($db)) $db->rollBack();
|
||||
error_log("POS Error: " . $e->getMessage());
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
@ -1070,7 +1096,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</a>
|
||||
|
||||
<!-- Operations Section -->
|
||||
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase small text-muted <?= !in_array($page, ['sales', 'purchases']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#ops-collapse">
|
||||
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['sales', 'purchases']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#ops-collapse">
|
||||
<span data-en="Operations" data-ar="العمليات">Operations</span>
|
||||
<i class="bi bi-chevron-down"></i>
|
||||
</div>
|
||||
@ -1090,7 +1116,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
|
||||
<!-- Inventory Section -->
|
||||
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase small text-muted <?= !in_array($page, ['items', 'categories', 'units']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#inventory-collapse">
|
||||
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['items', 'categories', 'units']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#inventory-collapse">
|
||||
<span data-en="Inventory" data-ar="المخزون">Inventory</span>
|
||||
<i class="bi bi-chevron-down"></i>
|
||||
</div>
|
||||
@ -1107,7 +1133,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
|
||||
<!-- People Section -->
|
||||
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase small text-muted <?= !in_array($page, ['customers', 'suppliers']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#relationships-collapse">
|
||||
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['customers', 'suppliers']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#relationships-collapse">
|
||||
<span data-en="Relationships" data-ar="العلاقات">Relationships</span>
|
||||
<i class="bi bi-chevron-down"></i>
|
||||
</div>
|
||||
@ -1121,7 +1147,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
|
||||
<!-- Reports Section -->
|
||||
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase small text-muted <?= !in_array($page, ['customer_statement', 'supplier_statement']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#reports-collapse">
|
||||
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['customer_statement', 'supplier_statement']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#reports-collapse">
|
||||
<span data-en="Reports" data-ar="التقارير">Reports</span>
|
||||
<i class="bi bi-chevron-down"></i>
|
||||
</div>
|
||||
@ -1135,7 +1161,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
|
||||
<!-- Configuration Section -->
|
||||
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase small text-muted <?= !in_array($page, ['payment_methods', 'settings']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#config-collapse">
|
||||
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['payment_methods', 'settings']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#config-collapse">
|
||||
<span data-en="Configuration" data-ar="الإعدادات">Configuration</span>
|
||||
<i class="bi bi-chevron-down"></i>
|
||||
</div>
|
||||
@ -1674,14 +1700,6 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="small fw-bold mb-1">Payment Method</label>
|
||||
<select id="posPaymentMethod" class="form-select form-select-sm">
|
||||
<option value="cash">Cash</option>
|
||||
<option value="card">Card</option>
|
||||
<option value="transfer">Bank Transfer</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="small fw-bold mb-1">Discount Code</label>
|
||||
<div class="input-group input-group-sm">
|
||||
@ -1721,6 +1739,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
items: [],
|
||||
discount: null,
|
||||
customerPoints: 0,
|
||||
selectedPaymentMethod: 'cash',
|
||||
payments: [],
|
||||
add(product) {
|
||||
const existing = this.items.find(item => item.id === product.id);
|
||||
if (existing) {
|
||||
@ -1922,23 +1942,170 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
async checkout() {
|
||||
if (this.items.length === 0) return;
|
||||
|
||||
const btn = document.getElementById('checkoutBtn');
|
||||
const subtotal = this.items.reduce((sum, item) => sum + (item.price * item.qty), 0);
|
||||
let discountAmount = 0;
|
||||
if (this.discount) {
|
||||
discountAmount = this.discount.type === 'percentage' ? subtotal * (parseFloat(this.discount.value) / 100) : parseFloat(this.discount.value);
|
||||
}
|
||||
const redeemSwitch = document.getElementById('redeemLoyalty');
|
||||
let loyaltyRedeemed = (redeemSwitch && redeemSwitch.checked) ? Math.min(subtotal - discountAmount, this.customerPoints) : 0;
|
||||
const total = subtotal - discountAmount - loyaltyRedeemed;
|
||||
|
||||
this.payments = [];
|
||||
this.renderPayments();
|
||||
document.getElementById('paymentAmountDue').innerText = 'OMR ' + total.toFixed(3);
|
||||
document.getElementById('partialAmount').value = total.toFixed(3);
|
||||
this.updateRemaining();
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('posPaymentModal'));
|
||||
modal.show();
|
||||
},
|
||||
selectMethod(method, btn) {
|
||||
this.selectedPaymentMethod = method;
|
||||
document.querySelectorAll('.payment-method-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
this.updateRemaining();
|
||||
},
|
||||
fillPartial(amount) {
|
||||
const input = document.getElementById('partialAmount');
|
||||
input.value = parseFloat(amount).toFixed(3);
|
||||
this.updateRemaining();
|
||||
},
|
||||
addPaymentLine() {
|
||||
const amount = parseFloat(document.getElementById('partialAmount').value) || 0;
|
||||
if (amount <= 0) return;
|
||||
|
||||
this.payments.push({
|
||||
method: this.selectedPaymentMethod,
|
||||
amount: amount
|
||||
});
|
||||
this.renderPayments();
|
||||
this.updateRemaining();
|
||||
|
||||
// Auto-fill remaining for next line if any
|
||||
const remaining = this.getRemaining();
|
||||
document.getElementById('partialAmount').value = remaining > 0 ? remaining.toFixed(3) : '0.000';
|
||||
},
|
||||
removePaymentLine(index) {
|
||||
this.payments.splice(index, 1);
|
||||
this.renderPayments();
|
||||
this.updateRemaining();
|
||||
},
|
||||
getGrandTotal() {
|
||||
const subtotal = this.items.reduce((sum, item) => sum + (item.price * item.qty), 0);
|
||||
let discountAmount = 0;
|
||||
if (this.discount) {
|
||||
discountAmount = this.discount.type === 'percentage' ? subtotal * (parseFloat(this.discount.value) / 100) : parseFloat(this.discount.value);
|
||||
}
|
||||
const redeemSwitch = document.getElementById('redeemLoyalty');
|
||||
let loyaltyRedeemed = (redeemSwitch && redeemSwitch.checked) ? Math.min(subtotal - discountAmount, this.customerPoints) : 0;
|
||||
return subtotal - discountAmount - loyaltyRedeemed;
|
||||
},
|
||||
getRemaining() {
|
||||
const total = this.getGrandTotal();
|
||||
const paid = this.payments.reduce((sum, p) => sum + p.amount, 0);
|
||||
return total - paid;
|
||||
},
|
||||
renderPayments() {
|
||||
const container = document.getElementById('paymentList');
|
||||
container.innerHTML = this.payments.map((p, i) => `
|
||||
<div class="payment-line">
|
||||
<div>
|
||||
<span class="method">${p.method}</span>
|
||||
<span class="ms-2 badge bg-secondary small">OMR ${p.amount.toFixed(3)}</span>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-danger border-0" onclick="cart.removePaymentLine(${i})">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
`).join('');
|
||||
},
|
||||
updateRemaining() {
|
||||
const remaining = this.getRemaining();
|
||||
const currentInput = parseFloat(document.getElementById('partialAmount').value) || 0;
|
||||
const display = document.getElementById('paymentRemaining');
|
||||
display.innerText = 'OMR ' + Math.max(0, remaining).toFixed(3);
|
||||
|
||||
// Calculate potential change if the user types an amount > remaining
|
||||
const totalPaid = this.payments.reduce((sum, p) => sum + p.amount, 0);
|
||||
const grandTotal = this.getGrandTotal();
|
||||
const actualChange = Math.max(0, totalPaid - grandTotal);
|
||||
const potentialChange = Math.max(0, currentInput - remaining);
|
||||
const displayChange = Math.max(actualChange, potentialChange);
|
||||
|
||||
const changeDisplay = document.getElementById('changeDue');
|
||||
if (changeDisplay) {
|
||||
changeDisplay.innerText = 'OMR ' + displayChange.toFixed(3);
|
||||
const cashSection = document.getElementById('cashPaymentSection');
|
||||
if (displayChange > 0 || this.selectedPaymentMethod === 'cash' || this.payments.some(p => p.method === 'cash')) {
|
||||
cashSection.style.display = 'block';
|
||||
} else {
|
||||
cashSection.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (remaining <= 0.0001 || currentInput >= remaining - 0.0001) {
|
||||
display.classList.remove('text-danger');
|
||||
display.classList.add('text-success');
|
||||
document.getElementById('confirmPaymentBtn').disabled = false;
|
||||
} else {
|
||||
display.classList.remove('text-success');
|
||||
display.classList.add('text-danger');
|
||||
document.getElementById('confirmPaymentBtn').disabled = true;
|
||||
}
|
||||
},
|
||||
async completeOrder() {
|
||||
if (this.items.length === 0) {
|
||||
Swal.fire('Error', 'Cart is empty', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's an amount in the input and payments are not enough, add it
|
||||
const remainingBefore = this.getRemaining();
|
||||
const currentInput = parseFloat(document.getElementById('partialAmount').value) || 0;
|
||||
|
||||
if (remainingBefore > 0.0001 && currentInput >= remainingBefore - 0.0001) {
|
||||
this.payments.push({
|
||||
method: this.selectedPaymentMethod,
|
||||
amount: currentInput
|
||||
});
|
||||
} else if (this.payments.length === 0) {
|
||||
const total = this.getGrandTotal();
|
||||
this.payments.push({
|
||||
method: this.selectedPaymentMethod,
|
||||
amount: total
|
||||
});
|
||||
}
|
||||
|
||||
const remaining = this.getRemaining();
|
||||
if (remaining > 0.001) {
|
||||
Swal.fire('Error', 'Payment is incomplete', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const customerId = document.getElementById('posCustomer').value;
|
||||
if (this.payments.some(p => p.method === 'credit') && !customerId) {
|
||||
Swal.fire('Error', 'Credit payment is only allowed for registered customers', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('confirmPaymentBtn');
|
||||
const originalText = btn.innerText;
|
||||
btn.disabled = true;
|
||||
btn.innerText = 'PROCESSING...';
|
||||
|
||||
const subtotal = this.items.reduce((sum, item) => sum + (item.price * item.qty), 0);
|
||||
const subtotal = this.items.reduce((sum, item) => sum + (parseFloat(item.price) * item.qty), 0);
|
||||
let discountAmount = 0;
|
||||
if (this.discount) {
|
||||
discountAmount = this.discount.type === 'percentage' ? subtotal * (this.discount.value / 100) : this.discount.value;
|
||||
discountAmount = this.discount.type === 'percentage' ? subtotal * (parseFloat(this.discount.value) / 100) : parseFloat(this.discount.value);
|
||||
}
|
||||
const redeemSwitch = document.getElementById('redeemLoyalty');
|
||||
let loyaltyRedeemed = (redeemSwitch && redeemSwitch.checked) ? Math.min(subtotal - discountAmount, this.customerPoints) : 0;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'save_pos_transaction');
|
||||
formData.append('customer_id', document.getElementById('posCustomer').value);
|
||||
formData.append('payment_method', document.getElementById('posPaymentMethod').value);
|
||||
formData.append('customer_id', customerId);
|
||||
formData.append('payments', JSON.stringify(this.payments));
|
||||
formData.append('total_amount', subtotal);
|
||||
formData.append('discount_code_id', this.discount ? this.discount.id : '');
|
||||
formData.append('discount_amount', discountAmount);
|
||||
@ -1947,8 +2114,18 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
|
||||
try {
|
||||
const resp = await fetch('index.php', { method: 'POST', body: formData });
|
||||
const result = await resp.json();
|
||||
const text = await resp.text();
|
||||
let result;
|
||||
try {
|
||||
result = JSON.parse(text);
|
||||
} catch (e) {
|
||||
console.error('Invalid JSON response:', text);
|
||||
throw new Error('Server returned an invalid response');
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
const payModal = bootstrap.Modal.getInstance(document.getElementById('posPaymentModal'));
|
||||
if (payModal) payModal.hide();
|
||||
this.showReceipt(result.invoice_id, discountAmount, loyaltyRedeemed, result.transaction_no);
|
||||
} else {
|
||||
Swal.fire('Error', result.error, 'error');
|
||||
@ -1957,7 +2134,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
Swal.fire('Error', 'Something went wrong', 'error');
|
||||
Swal.fire('Error', err.message || 'Something went wrong', 'error');
|
||||
btn.disabled = false;
|
||||
btn.innerText = originalText;
|
||||
}
|
||||
@ -1965,7 +2142,12 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
showReceipt(invId, discountAmount, loyaltyRedeemed, transactionNo) {
|
||||
const container = document.getElementById('posReceiptContent');
|
||||
const customerName = document.getElementById('posCustomer').options[document.getElementById('posCustomer').selectedIndex].text;
|
||||
const paymentMethod = document.getElementById('posPaymentMethod').value.toUpperCase();
|
||||
const paymentsHtml = this.payments.map(p => `
|
||||
<div class="d-flex justify-content-between small">
|
||||
<span class="text-uppercase">${p.method}</span>
|
||||
<span>OMR ${p.amount.toFixed(3)}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
const date = new Date().toLocaleString();
|
||||
|
||||
let itemsHtml = this.items.map(item => `
|
||||
@ -1994,8 +2176,11 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
<div class="separator"></div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Customer:</strong> ${customerName}<br>
|
||||
<strong>Payment:</strong> ${paymentMethod}
|
||||
<strong>Customer:</strong> ${customerName}
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
<strong>Payments:</strong>
|
||||
${paymentsHtml}
|
||||
</div>
|
||||
<div class="separator"></div>
|
||||
<table>
|
||||
@ -3193,6 +3378,91 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<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>
|
||||
|
||||
<!-- POS Payment Modal -->
|
||||
<div class="modal fade" id="posPaymentModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content border-0">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">Payment</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="amount-due-box mb-3">
|
||||
<div class="d-flex justify-content-between px-3">
|
||||
<div class="text-start">
|
||||
<div class="label">Amount Due</div>
|
||||
<div class="value" id="paymentAmountDue">OMR 0.000</div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="label text-danger">Remaining</div>
|
||||
<div class="value text-danger" id="paymentRemaining">OMR 0.000</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="paymentList" class="mb-3">
|
||||
<!-- Added payments will appear here -->
|
||||
</div>
|
||||
|
||||
<div class="mb-3 p-3 border rounded bg-light">
|
||||
<label class="form-label small fw-bold">Add Payment Method</label>
|
||||
<div class="payment-methods-grid mb-2">
|
||||
<div class="payment-method-btn active" data-method="cash" onclick="cart.selectMethod('cash', this)">
|
||||
<i class="bi bi-cash-stack"></i>
|
||||
<span class="small fw-bold">Cash</span>
|
||||
</div>
|
||||
<div class="payment-method-btn" data-method="card" onclick="cart.selectMethod('card', this)">
|
||||
<i class="bi bi-credit-card"></i>
|
||||
<span class="small fw-bold">Card</span>
|
||||
</div>
|
||||
<div class="payment-method-btn" data-method="credit" onclick="cart.selectMethod('credit', this)">
|
||||
<i class="bi bi-person-badge"></i>
|
||||
<span class="small fw-bold">Credit</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col">
|
||||
<label class="form-label smaller fw-bold mb-1">Amount</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text p-1 small">OMR</span>
|
||||
<input type="number" step="0.001" id="partialAmount" class="form-control" placeholder="0.000" oninput="cart.updateRemaining()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-primary" onclick="cart.addPaymentLine()">
|
||||
<i class="bi bi-plus-lg"></i> ADD
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quick-pay-grid mt-2">
|
||||
<div class="quick-pay-btn" onclick="cart.fillPartial(1)">1</div>
|
||||
<div class="quick-pay-btn" onclick="cart.fillPartial(5)">5</div>
|
||||
<div class="quick-pay-btn" onclick="cart.fillPartial(10)">10</div>
|
||||
<div class="quick-pay-btn" onclick="cart.fillPartial(20)">20</div>
|
||||
<div class="quick-pay-btn" onclick="cart.fillPartial(50)">50</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cashPaymentSection" style="display: none;">
|
||||
<div class="d-flex justify-content-between align-items-center p-3 bg-primary-subtle rounded border border-primary-subtle">
|
||||
<span class="fw-bold">Total Tendered (Cash)</span>
|
||||
<span class="h4 m-0 fw-bold text-primary" id="changeDue">OMR 0.000</span>
|
||||
</div>
|
||||
<div class="small text-muted mt-1">* Change is calculated based on cash payments only.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary px-4" id="confirmPaymentBtn" onclick="cart.completeOrder()">
|
||||
PAY & COMPLETE
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- POS Receipt Modal -->
|
||||
<div class="modal fade" id="posReceiptModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user