468 lines
22 KiB
PHP
468 lines
22 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
require_once __DIR__ . '/../db/config.php';
|
|
require_once __DIR__ . '/../includes/functions.php';
|
|
|
|
$pdo = db();
|
|
require_permission('orders_view');
|
|
|
|
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
|
if (!$id) {
|
|
header("Location: orders.php");
|
|
exit;
|
|
}
|
|
|
|
// Fetch Order Details
|
|
$stmt = $pdo->prepare("SELECT o.*, ot.name as outlet_name, pt.name as payment_type_name,
|
|
c.name as customer_name, c.phone as customer_phone, c.email as customer_email, c.address as customer_address,
|
|
u.username as created_by_username
|
|
FROM orders o
|
|
LEFT JOIN outlets ot ON o.outlet_id = ot.id
|
|
LEFT JOIN payment_types pt ON o.payment_type_id = pt.id
|
|
LEFT JOIN customers c ON o.customer_id = c.id
|
|
LEFT JOIN users u ON o.user_id = u.id
|
|
WHERE o.id = ?");
|
|
$stmt->execute([$id]);
|
|
$order = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$order) {
|
|
die("Order not found.");
|
|
}
|
|
|
|
// Fetch Order Items
|
|
$stmt = $pdo->prepare("SELECT oi.*, p.name as product_name, pv.name as variant_name
|
|
FROM order_items oi
|
|
JOIN products p ON oi.product_id = p.id
|
|
LEFT JOIN product_variants pv ON oi.variant_id = pv.id
|
|
WHERE oi.order_id = ?");
|
|
$stmt->execute([$id]);
|
|
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
include 'includes/header.php';
|
|
|
|
// Calculate subtotal from items to be sure
|
|
$subtotal = 0;
|
|
foreach ($items as $item) {
|
|
$subtotal += $item['unit_price'] * $item['quantity'];
|
|
}
|
|
?>
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h2 class="fw-bold mb-0">Order #<?= $order['id'] ?></h2>
|
|
<p class="text-muted mb-0">Placed on <?= date('M d, Y H:i', strtotime($order['created_at'])) ?></p>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<a href="orders.php" class="btn btn-outline-secondary">
|
|
<i class="bi bi-arrow-left"></i> Back to List
|
|
</a>
|
|
<button onclick="printThermalReceipt()" class="btn btn-warning border-warning"><i class="bi bi-printer-fill"></i> Thermal Receipt</button>
|
|
<button onclick="window.print()" class="btn btn-light border">
|
|
<i class="bi bi-printer"></i> Print Receipt
|
|
</button>
|
|
<?php if (has_permission('orders_add')): ?>
|
|
<a href="order_edit.php?id=<?= $order['id'] ?>" class="btn btn-primary">
|
|
<i class="bi bi-pencil"></i> Edit Order
|
|
</a>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<!-- Order Items -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white py-3">
|
|
<h5 class="card-title mb-0 fw-bold">Order Items</h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table align-middle mb-0">
|
|
<thead class="bg-light text-muted small text-uppercase">
|
|
<tr>
|
|
<th class="ps-4">Product</th>
|
|
<th class="text-center">Price</th>
|
|
<th class="text-center">Qty</th>
|
|
<th class="text-end pe-4">Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($items as $item): ?>
|
|
<tr>
|
|
<td class="ps-4">
|
|
<div class="fw-bold text-dark"><?= htmlspecialchars($item['product_name']) ?></div>
|
|
<?php if ($item['variant_name']): ?>
|
|
<small class="text-muted">Variant: <?= htmlspecialchars($item['variant_name']) ?></small>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="text-center"><?= format_currency($item['unit_price']) ?></td>
|
|
<td class="text-center"><?= $item['quantity'] ?></td>
|
|
<td class="text-end pe-4 fw-bold"><?= format_currency($item['unit_price'] * $item['quantity']) ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
<tfoot class="bg-light">
|
|
<tr>
|
|
<td colspan="3" class="text-end py-3 ps-4">
|
|
<div class="text-muted mb-1">Subtotal</div>
|
|
<?php if ($order['discount'] > 0): ?>
|
|
<div class="text-muted mb-1">Discount</div>
|
|
<?php endif; ?>
|
|
<div class="text-muted mb-1">VAT / Tax</div>
|
|
<h5 class="fw-bold mb-0 text-dark">Total Amount</h5>
|
|
</td>
|
|
<td class="text-end py-3 pe-4">
|
|
<div class="mb-1"><?= format_currency($subtotal) ?></div>
|
|
<?php if ($order['discount'] > 0): ?>
|
|
<div class="mb-1 text-danger">-<?= format_currency($order['discount']) ?></div>
|
|
<?php endif; ?>
|
|
<div class="mb-1"><?= format_currency(0) ?></div>
|
|
<h5 class="fw-bold mb-0 text-primary"><?= format_currency($order['total_amount']) ?></h5>
|
|
</td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Additional Info -->
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<h5 class="fw-bold mb-3">Internal Notes</h5>
|
|
<p class="text-muted"><?= htmlspecialchars($order['notes'] ?? 'No notes provided for this order.') ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<!-- Status & Payment -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-body">
|
|
<h6 class="text-muted small text-uppercase fw-bold mb-3">Order Status</h6>
|
|
<div class="d-flex align-items-center mb-4">
|
|
<span class="badge rounded-pill fs-6 px-3 py-2 status-<?= $order['status'] ?>">
|
|
<?= ucfirst($order['status']) ?>
|
|
</span>
|
|
<span class="ms-3 text-muted small">Last updated: <?= date('M d, H:i', strtotime($order['updated_at'] ?? $order['created_at'])) ?></span>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<h6 class="text-muted small text-uppercase fw-bold mb-3 mt-4">Payment Information</h6>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span class="text-muted">Method:</span>
|
|
<span class="fw-bold text-dark"><?= htmlspecialchars($order['payment_type_name'] ?? 'Unpaid') ?></span>
|
|
</div>
|
|
<div class="d-flex justify-content-between">
|
|
<span class="text-muted">Status:</span>
|
|
<span class="badge bg-success bg-opacity-10 text-success border border-success">Paid</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Order Details -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-body">
|
|
<h6 class="text-muted small text-uppercase fw-bold mb-3">Order Details</h6>
|
|
<div class="mb-3">
|
|
<label class="text-muted small d-block">Outlet</label>
|
|
<div class="fw-bold"><?= htmlspecialchars($order['outlet_name'] ?? 'N/A') ?></div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="text-muted small d-block">Order Type</label>
|
|
<div class="fw-bold"><?= ucfirst($order['order_type']) ?></div>
|
|
</div>
|
|
<?php if ($order['order_type'] === 'dine-in'): ?>
|
|
<div class="mb-3">
|
|
<label class="text-muted small d-block">Table Number</label>
|
|
<div class="fw-bold">Table <?= htmlspecialchars((string)$order['table_number']) ?></div>
|
|
</div>
|
|
<?php endif; ?>
|
|
<div class="mb-0">
|
|
<label class="text-muted small d-block">Processed By</label>
|
|
<div class="fw-bold"><?= htmlspecialchars($order['created_by_username'] ?? 'System') ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Customer Info -->
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<h6 class="text-muted small text-uppercase fw-bold mb-3">Customer Information</h6>
|
|
<?php if ($order['customer_name']): ?>
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="bg-primary bg-opacity-10 text-primary p-2 rounded-circle me-3">
|
|
<i class="bi bi-person fs-4"></i>
|
|
</div>
|
|
<div>
|
|
<div class="fw-bold"><?= htmlspecialchars($order['customer_name']) ?></div>
|
|
<small class="text-muted">Customer ID: #<?= $order['customer_id'] ?></small>
|
|
</div>
|
|
</div>
|
|
<?php if ($order['customer_phone']): ?>
|
|
<div class="mb-2">
|
|
<i class="bi bi-telephone text-muted me-2"></i>
|
|
<a href="tel:<?= $order['customer_phone'] ?>" class="text-decoration-none text-dark"><?= htmlspecialchars($order['customer_phone'] ?? '') ?></a>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php if ($order['customer_email']): ?>
|
|
<div class="mb-0">
|
|
<i class="bi bi-envelope text-muted me-2"></i>
|
|
<a href="mailto:<?= $order['customer_email'] ?>" class="text-decoration-none text-dark"><?= htmlspecialchars($order['customer_email'] ?? '') ?></a>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php else: ?>
|
|
<div class="text-center py-3">
|
|
<i class="bi bi-person-x fs-1 text-muted opacity-25"></i>
|
|
<p class="text-muted small mb-0 mt-2">No customer attached to this order (Guest)</p>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php include 'includes/footer.php'; ?>
|
|
|
|
<script>
|
|
const COMPANY_SETTINGS = <?= json_encode(get_company_settings()) ?>;
|
|
const CURRENT_USER = { name: '<?= addslashes($order['created_by_username'] ?? 'System') ?>' };
|
|
const CURRENT_OUTLET = { name: '<?= addslashes($order['outlet_name'] ?? 'N/A') ?>' };
|
|
const BASE_URL = '<?= get_base_url() ?>';
|
|
|
|
function formatCurrency(amount) {
|
|
const symbol = COMPANY_SETTINGS.currency_symbol || '$';
|
|
const decimals = parseInt(COMPANY_SETTINGS.currency_decimals || 2);
|
|
return symbol + parseFloat(amount).toFixed(decimals);
|
|
}
|
|
|
|
function printThermalReceipt() {
|
|
const data = {
|
|
orderId: '<?= $order['id'] ?>',
|
|
customer: <?= $order['customer_name'] ? json_encode(['name' => $order['customer_name'], 'phone' => $order['customer_phone'], 'address' => $order['customer_address'] ?? '']) : 'null' ?>,
|
|
items: <?= json_encode(array_map(function($i) {
|
|
return [
|
|
'name' => $i['product_name'],
|
|
'variant_name' => $i['variant_name'],
|
|
'quantity' => $i['quantity'],
|
|
'price' => $i['unit_price']
|
|
];
|
|
}, $items)) ?>,
|
|
total: <?= (float)$order['total_amount'] ?>,
|
|
discount: <?= (float)$order['discount'] ?>,
|
|
orderType: '<?= $order['order_type'] ?>',
|
|
tableNumber: '<?= $order['table_number'] ?>',
|
|
date: '<?= date('M d, Y H:i', strtotime($order['created_at'])) ?>',
|
|
paymentMethod: '<?= $order['payment_type_name'] ?? 'Unpaid' ?>',
|
|
loyaltyRedeemed: <?= ($order['discount'] >= $subtotal && $order['discount'] > 0) ? 'true' : 'false' ?>
|
|
};
|
|
|
|
const width = 400;
|
|
const height = 800;
|
|
const left = (screen.width - width) / 2;
|
|
const top = (screen.height - height) / 2;
|
|
|
|
const win = window.open('', 'Receipt', `width=${width},height=${height},top=${top},left=${left}`);
|
|
|
|
const tr = {
|
|
'Order': 'الطلب',
|
|
'Type': 'النوع',
|
|
'Date': 'التاريخ',
|
|
'Staff': 'الموظف',
|
|
'Table': 'طاولة',
|
|
'Payment': 'الدفع',
|
|
'ITEM': 'الصنف',
|
|
'TOTAL': 'المجموع',
|
|
'Subtotal': 'المجموع الفرعي',
|
|
'Discount': 'الخصم',
|
|
'Tax Included': 'شامل الضريبة',
|
|
'THANK YOU FOR YOUR VISIT!': 'شكراً لزيارتكم!',
|
|
'Please come again.': 'يرجى زيارتنا مرة أخرى.',
|
|
'Customer Details': 'تفاصيل العميل',
|
|
'Tel': 'هاتف',
|
|
'takeaway': 'سفري',
|
|
'dine-in': 'محلي',
|
|
'delivery': 'توصيل',
|
|
'VAT No': 'الرقم الضريبي',
|
|
'CTR No': 'رقم السجل التجاري'
|
|
};
|
|
|
|
const itemsHtml = data.items.map(item => `
|
|
<tr>
|
|
<td style="padding: 5px 0; border-bottom: 1px solid #eee;">
|
|
<div style="font-weight: bold;">${item.name}</div>
|
|
${item.variant_name ? `<div style="font-size: 10px; color: #555;">(${item.variant_name})</div>` : ''}
|
|
<div style="font-size: 11px;">${item.quantity} x ${formatCurrency(item.price)}</div>
|
|
</td>
|
|
<td style="text-align: right; vertical-align: middle; font-weight: bold;">${formatCurrency(item.quantity * item.price)}</td>
|
|
</tr>
|
|
`).join('');
|
|
|
|
const customerHtml = data.customer ? `
|
|
<div style="margin-bottom: 10px; border: 1px solid #eee; padding: 8px; border-radius: 4px;">
|
|
<div style="font-weight: bold; text-transform: uppercase; font-size: 10px; color: #666; margin-bottom: 3px;">
|
|
<span style="float: left;">Customer Details</span>
|
|
<span style="float: right;">${tr['Customer Details']}</span>
|
|
<div style="clear: both;"></div>
|
|
</div>
|
|
<div style="font-weight: bold;">${data.customer.name}</div>
|
|
${data.customer.phone ? `<div>Tel: ${data.customer.phone}</div>` : ''}
|
|
${data.customer.address ? `<div style="font-size: 11px;">${data.customer.address}</div>` : ''}
|
|
</div>
|
|
` : '';
|
|
|
|
const tableHtml = data.tableNumber && data.orderType === 'dine-in' ? `
|
|
<div style="display: flex; justify-content: space-between;">
|
|
<span><strong>Table:</strong> ${data.tableNumber}</span>
|
|
<span><strong>${tr['Table']}:</strong> ${data.tableNumber}</span>
|
|
</div>` : '';
|
|
|
|
const paymentHtml = data.paymentMethod ? `
|
|
<div style="display: flex; justify-content: space-between;">
|
|
<span><strong>Payment:</strong> ${data.paymentMethod}</span>
|
|
<span><strong>${tr['Payment']}:</strong> ${data.paymentMethod}</span>
|
|
</div>` : '';
|
|
|
|
const loyaltyHtml = data.loyaltyRedeemed ? `<div style="color: #d63384; font-weight: bold; margin: 5px 0; text-align: center;">* Loyalty Reward Applied *</div>` : '';
|
|
|
|
const vatRate = parseFloat(COMPANY_SETTINGS.vat_rate) || 0;
|
|
const subtotal = data.total + data.discount;
|
|
const vatAmount = vatRate > 0 ? (data.total * (vatRate / (100 + vatRate))) : 0;
|
|
|
|
const logoHtml = COMPANY_SETTINGS.logo_url ? `<img src="${BASE_URL}${COMPANY_SETTINGS.logo_url}" style="max-height: 80px; max-width: 150px; margin-bottom: 10px;">` : '';
|
|
|
|
const html = `
|
|
<html dir="ltr">
|
|
<head>
|
|
<title>Receipt #${data.orderId}</title>
|
|
<style>
|
|
body {
|
|
font-family: 'Courier New', Courier, monospace;
|
|
font-size: 13px;
|
|
margin: 0;
|
|
padding: 15px;
|
|
color: #000;
|
|
line-height: 1.4;
|
|
}
|
|
.header { text-align: center; margin-bottom: 15px; }
|
|
.header h2 { margin: 0 0 5px 0; font-size: 20px; font-weight: bold; }
|
|
.header div { font-size: 12px; }
|
|
table { width: 100%; border-collapse: collapse; }
|
|
.divider { border-bottom: 2px dashed #000; margin: 10px 0; }
|
|
.thick-divider { border-bottom: 2px solid #000; margin: 10px 0; }
|
|
.totals td { padding: 3px 0; }
|
|
.footer { text-align: center; margin-top: 25px; font-size: 12px; }
|
|
.order-info { font-size: 11px; margin-bottom: 10px; }
|
|
.order-info-row { display: flex; justify-content: space-between; margin-bottom: 2px; }
|
|
.rtl { direction: rtl; unicode-bidi: embed; }
|
|
@media print {
|
|
body { width: 80mm; padding: 5mm; }
|
|
@page { margin: 0; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
${logoHtml}
|
|
<h2>${COMPANY_SETTINGS.company_name}</h2>
|
|
<div style="font-weight: bold;">${CURRENT_OUTLET.name}</div>
|
|
<div>${COMPANY_SETTINGS.address}</div>
|
|
<div>Tel: ${COMPANY_SETTINGS.phone}</div>
|
|
${COMPANY_SETTINGS.vat_number ? `<div style="margin-top: 4px;">VAT No / الرقم الضريبي: ${COMPANY_SETTINGS.vat_number}</div>` : ''}
|
|
${COMPANY_SETTINGS.ctr_number ? `<div>CTR No / رقم السجل: ${COMPANY_SETTINGS.ctr_number}</div>` : ''}
|
|
</div>
|
|
|
|
<div class="divider"></div>
|
|
|
|
<div class="order-info">
|
|
<div class="order-info-row">
|
|
<span><strong>Order:</strong> #${data.orderId}</span>
|
|
<span><strong>${tr['Order']}:</strong> #${data.orderId}</span>
|
|
</div>
|
|
<div class="order-info-row">
|
|
<span><strong>Type:</strong> ${data.orderType.toUpperCase()}</span>
|
|
<span><strong>${tr['Type']}:</strong> ${tr[data.orderType] || data.orderType}</span>
|
|
</div>
|
|
<div class="order-info-row">
|
|
<span><strong>Date:</strong> ${data.date}</span>
|
|
<span><strong>${tr['Date']}:</strong> ${data.date}</span>
|
|
</div>
|
|
<div class="order-info-row">
|
|
<span><strong>Staff:</strong> ${CURRENT_USER.name}</span>
|
|
<span><strong>${tr['Staff']}:</strong> ${CURRENT_USER.name}</span>
|
|
</div>
|
|
</div>
|
|
|
|
${tableHtml}
|
|
${paymentHtml}
|
|
${loyaltyHtml}
|
|
|
|
<div class="thick-divider"></div>
|
|
${customerHtml}
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th style="text-align: left; padding-bottom: 5px;">
|
|
ITEM / الصنف
|
|
</th>
|
|
<th style="text-align: right; padding-bottom: 5px;">
|
|
TOTAL / المجموع
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${itemsHtml}
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="divider"></div>
|
|
|
|
<div class="totals">
|
|
<table style="width: 100%">
|
|
<tr>
|
|
<td>Subtotal / ${tr['Subtotal']}</td>
|
|
<td style="text-align: right">${formatCurrency(subtotal)}</td>
|
|
</tr>
|
|
${data.discount > 0 ? `
|
|
<tr>
|
|
<td>Discount / ${tr['Discount']}</td>
|
|
<td style="text-align: right">-${formatCurrency(data.discount)}</td>
|
|
</tr>` : ''}
|
|
${vatRate > 0 ? `
|
|
<tr>
|
|
<td style="font-size: 11px;">Tax Incl. (${vatRate}%) / ${tr['Tax Included']}</td>
|
|
<td style="text-align: right">${formatCurrency(vatAmount)}</td>
|
|
</tr>` : ''}
|
|
<tr style="font-weight: bold; font-size: 18px;">
|
|
<td style="padding-top: 10px;">TOTAL / ${tr['TOTAL']}</td>
|
|
<td style="text-align: right; padding-top: 10px;">${formatCurrency(data.total)}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="thick-divider"></div>
|
|
|
|
<div class="footer">
|
|
<div style="font-weight: bold; font-size: 14px; margin-bottom: 2px;">THANK YOU FOR YOUR VISIT!</div>
|
|
<div style="font-weight: bold; font-size: 14px; margin-bottom: 5px;" class="rtl">${tr['THANK YOU FOR YOUR VISIT!']}</div>
|
|
<div>Please come again.</div>
|
|
<div class="rtl">${tr['Please come again.']}</div>
|
|
${COMPANY_SETTINGS.email ? `<div style="margin-top: 5px; font-size: 10px;">${COMPANY_SETTINGS.email}</div>` : ''}
|
|
<div style="margin-top: 15px; font-size: 10px; color: #888;">Powered by Flatlogic POS</div>
|
|
</div>
|
|
|
|
<script>
|
|
window.onload = function() {
|
|
window.print();
|
|
setTimeout(function() { window.close(); }, 1500);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`;
|
|
win.document.write(html);
|
|
win.document.close();
|
|
}
|
|
</script>
|