Autosave: 20260302-132419
This commit is contained in:
parent
62b332799d
commit
268b4cc1cf
@ -51,7 +51,7 @@ $recent_orders = $stmt->fetchAll();
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted small"><?= __('today_revenue') ?? 'Today Revenue' ?></div>
|
||||
<div class="fw-bold fs-5"><?= number_format($stats['today_revenue'], 2) ?> SAR</div>
|
||||
<div class="fw-bold fs-5"><?= format_amount($stats['today_revenue']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -118,7 +118,7 @@ $recent_orders = $stmt->fetchAll();
|
||||
<tr>
|
||||
<td><?= $order['id'] ?></td>
|
||||
<td><?= $lang === 'ar' ? ($order['customer_name_ar'] ?: $order['customer_name_en']) : $order['customer_name_en'] ?></td>
|
||||
<td><?= number_format($order['total_price'], 2) ?></td>
|
||||
<td><?= format_amount($order['total_price']) ?></td>
|
||||
<td><span class="badge bg-<?= getStatusColor($order['status']) ?>"><?= __($order['status']) ?></span></td>
|
||||
<td><span class="badge bg-<?= getPaymentStatusColor($order['payment_status']) ?>"><?= __($order['payment_status']) ?></span></td>
|
||||
<td>
|
||||
@ -167,4 +167,3 @@ function getPaymentStatusColor($status) {
|
||||
][$status] ?? 'info';
|
||||
}
|
||||
require_once __DIR__ . '/includes/footer.php';
|
||||
?>
|
||||
@ -9,6 +9,7 @@ if (!isset($_SESSION['user_id'])) {
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$order_id = $input['order_id'] ?? null;
|
||||
$customer_id = $input['customer_id'] ?: null;
|
||||
$items = $input['items'] ?? [];
|
||||
$vat_total = (float)($input['vat_total'] ?? 0);
|
||||
@ -25,15 +26,35 @@ try {
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Recalculate total if not provided correctly, but for now we trust the client-side breakdown
|
||||
// If we wanted to be more secure, we'd fetch prices from DB here.
|
||||
if ($order_id) {
|
||||
// Update existing order
|
||||
$stmt = $pdo->prepare("UPDATE orders SET customer_id = ?, total_price = ?, vat_total = ? WHERE id = ? AND branch_id = ?");
|
||||
$stmt->execute([$customer_id, $total_price, $vat_total, $order_id, $branch_id]);
|
||||
|
||||
// Remove existing items
|
||||
$stmt = $pdo->prepare("DELETE FROM order_items WHERE order_id = ?");
|
||||
$stmt->execute([$order_id]);
|
||||
} else {
|
||||
// Create new order
|
||||
$stmt = $pdo->prepare("INSERT INTO orders (branch_id, customer_id, user_id, order_number, total_price, vat_total, status, payment_status)
|
||||
VALUES (?, ?, ?, NULL, ?, ?, 'received', 'unpaid')");
|
||||
$stmt->execute([$branch_id, $customer_id, $user_id, $total_price, $vat_total]);
|
||||
$order_id = $pdo->lastInsertId();
|
||||
|
||||
$order_number = 'ORD-' . time() . '-' . rand(100, 999);
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO orders (branch_id, customer_id, user_id, order_number, total_price, vat_total, status, payment_status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'received', 'unpaid')");
|
||||
$stmt->execute([$branch_id, $customer_id, $user_id, $order_number, $total_price, $vat_total]);
|
||||
$order_id = $pdo->lastInsertId();
|
||||
// Get branch prefix
|
||||
$stmt_prefix = $pdo->prepare("SELECT prefix FROM branches WHERE id = ?");
|
||||
$stmt_prefix->execute([$branch_id]);
|
||||
$prefix = $stmt_prefix->fetchColumn() ?: 'ORD';
|
||||
if (strlen($prefix) > 3) $prefix = substr($prefix, 0, 3);
|
||||
$prefix = str_pad($prefix, 3, 'X'); // Just in case it's shorter than 3
|
||||
|
||||
// Format order_number as XXX#####1 (5 digits for #####)
|
||||
$order_number = strtoupper($prefix) . str_pad($order_id, 5, '0', STR_PAD_LEFT) . '1';
|
||||
|
||||
// Update the order with the generated order_number
|
||||
$stmt_update = $pdo->prepare("UPDATE orders SET order_number = ? WHERE id = ?");
|
||||
$stmt_update->execute([$order_number, $order_id]);
|
||||
}
|
||||
|
||||
$stmt_item = $pdo->prepare("INSERT INTO order_items (order_id, item_id, variant_id, service_id, quantity, unit_price, vat_amount, subtotal)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
|
||||
31
api/delete_order.php
Normal file
31
api/delete_order.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$id = $input['id'] ?? null;
|
||||
|
||||
if (!$id) {
|
||||
echo json_encode(['success' => false, 'error' => 'Missing order ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("DELETE FROM orders WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Order not found or already deleted']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
35
api/get_order_items.php
Normal file
35
api/get_order_items.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../includes/lang.php';
|
||||
|
||||
$order_id = $_GET['id'] ?? null;
|
||||
|
||||
if (!$order_id) {
|
||||
echo json_encode(['success' => false, 'error' => 'Order ID is required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = db()->prepare("SELECT oi.quantity, i.name_en as item_en, i.name_ar as item_ar,
|
||||
s.name_en as service_en, s.name_ar as service_ar
|
||||
FROM order_items oi
|
||||
JOIN items i ON oi.item_id = i.id
|
||||
JOIN services s ON oi.service_id = s.id
|
||||
WHERE oi.order_id = ?");
|
||||
$stmt->execute([$order_id]);
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch order number for the modal title
|
||||
$stmt = db()->prepare("SELECT order_number FROM orders WHERE id = ?");
|
||||
$stmt->execute([$order_id]);
|
||||
$order = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'order_number' => $order['order_number'] ?? '',
|
||||
'items' => $items
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
38
api/update_order_status.php
Normal file
38
api/update_order_status.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$id = $input['id'] ?? null;
|
||||
$status = $input['status'] ?? null;
|
||||
|
||||
if (!$id || !$status) {
|
||||
echo json_encode(['success' => false, 'error' => 'Missing order ID or status']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$valid_statuses = ['received', 'processing', 'ready', 'delivered', 'cancelled'];
|
||||
if (!in_array($status, $valid_statuses)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid status']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE orders SET status = ? WHERE id = ?");
|
||||
$stmt->execute([$status, $id]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Order not found or status already same']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
117
branches.php
117
branches.php
@ -20,11 +20,33 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
$name_ar = $_POST['name_ar'];
|
||||
$company_id = $_POST['company_id'];
|
||||
$phone = $_POST['phone'];
|
||||
$stmt = db()->prepare("INSERT INTO branches (name_en, name_ar, company_id, phone) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$name_en, $name_ar, $company_id, $phone]);
|
||||
$prefix = strtoupper(substr($_POST['prefix'] ?? '', 0, 3));
|
||||
$stmt = db()->prepare("INSERT INTO branches (name_en, name_ar, company_id, phone, prefix) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$name_en, $name_ar, $company_id, $phone, $prefix]);
|
||||
header('Location: branches.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'edit_branch') {
|
||||
$id = $_POST['id'];
|
||||
$name_en = $_POST['name_en'];
|
||||
$name_ar = $_POST['name_ar'];
|
||||
$company_id = $_POST['company_id'];
|
||||
$phone = $_POST['phone'];
|
||||
$prefix = strtoupper(substr($_POST['prefix'] ?? '', 0, 3));
|
||||
$stmt = db()->prepare("UPDATE branches SET name_en = ?, name_ar = ?, company_id = ?, phone = ?, prefix = ? WHERE id = ?");
|
||||
$stmt->execute([$name_en, $name_ar, $company_id, $phone, $prefix, $id]);
|
||||
header('Location: branches.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['delete'])) {
|
||||
$id = $_GET['delete'];
|
||||
$stmt = db()->prepare("DELETE FROM branches WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
header('Location: branches.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// NOW Include header
|
||||
@ -57,6 +79,11 @@ $companies = db()->query("SELECT * FROM companies")->fetchAll();
|
||||
<label class="form-label small fw-bold"><?= __('name_ar') ?></label>
|
||||
<input type="text" name="name_ar" class="form-control" style="border-radius: 12px;">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('initial_letters') ?></label>
|
||||
<input type="text" name="prefix" class="form-control" maxlength="3" minlength="3" required style="border-radius: 12px;" placeholder="e.g. MCT">
|
||||
<div class="form-text small"><?= __('exactly_3_letters') ?? 'Exactly 3 letters' ?></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('phone') ?></label>
|
||||
<input type="text" name="phone" class="form-control" style="border-radius: 12px;">
|
||||
@ -73,8 +100,10 @@ $companies = db()->query("SELECT * FROM companies")->fetchAll();
|
||||
<tr class="py-3">
|
||||
<th class="ps-4 py-3">#</th>
|
||||
<th class="py-3"><?= __('name') ?></th>
|
||||
<th class="py-3"><?= __('initial_letters') ?></th>
|
||||
<th class="py-3"><?= __('company') ?></th>
|
||||
<th class="pe-4 py-3"><?= __('phone') ?></th>
|
||||
<th class="py-3"><?= __('phone') ?></th>
|
||||
<th class="pe-4 py-3 text-end"><?= __('actions') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -82,8 +111,26 @@ $companies = db()->query("SELECT * FROM companies")->fetchAll();
|
||||
<tr>
|
||||
<td class="ps-4"><?= $b['id'] ?></td>
|
||||
<td class="fw-bold"><?= $lang === 'ar' ? ($b['name_ar'] ?: $b['name_en']) : $b['name_en'] ?></td>
|
||||
<td class="text-primary fw-bold"><?= $b['prefix'] ?></td>
|
||||
<td><?= $b['company_name_en'] ?></td>
|
||||
<td class="pe-4"><?= $b['phone'] ?></td>
|
||||
<td><?= $b['phone'] ?></td>
|
||||
<td class="pe-4 text-end">
|
||||
<button class="btn btn-sm btn-outline-primary border-0 edit-branch"
|
||||
data-id="<?= $b['id'] ?>"
|
||||
data-name_en="<?= htmlspecialchars($b['name_en']) ?>"
|
||||
data-name_ar="<?= htmlspecialchars($b['name_ar']) ?>"
|
||||
data-company_id="<?= $b['company_id'] ?>"
|
||||
data-phone="<?= htmlspecialchars($b['phone']) ?>"
|
||||
data-prefix="<?= htmlspecialchars($b['prefix']) ?>"
|
||||
style="border-radius: 8px;">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<a href="?delete=<?= $b['id'] ?>" class="btn btn-sm btn-outline-danger border-0"
|
||||
onclick="return confirm('<?= __('are_you_sure') ?>')"
|
||||
style="border-radius: 8px;">
|
||||
<i class="bi bi-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
@ -93,4 +140,66 @@ $companies = db()->query("SELECT * FROM companies")->fetchAll();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Branch Modal -->
|
||||
<div class="modal fade" id="editBranchModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow" style="border-radius: 20px;">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="fw-bold"><?= __('edit') ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" value="edit_branch">
|
||||
<input type="hidden" name="id" id="edit_id">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('company') ?? 'Company' ?></label>
|
||||
<select name="company_id" id="edit_company_id" class="form-select" style="border-radius: 12px;">
|
||||
<?php foreach($companies as $c): ?>
|
||||
<option value="<?= $c['id'] ?>"><?= $lang === 'ar' ? $c['name_ar'] : $c['name_en'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('name_en') ?></label>
|
||||
<input type="text" name="name_en" id="edit_name_en" class="form-control" required style="border-radius: 12px;">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('name_ar') ?></label>
|
||||
<input type="text" name="name_ar" id="edit_name_ar" class="form-control" style="border-radius: 12px;">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('initial_letters') ?></label>
|
||||
<input type="text" name="prefix" id="edit_prefix" class="form-control" maxlength="3" minlength="3" required style="border-radius: 12px;">
|
||||
<div class="form-text small"><?= __('exactly_3_letters') ?? 'Exactly 3 letters' ?></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('phone') ?></label>
|
||||
<input type="text" name="phone" id="edit_phone" class="form-control" style="border-radius: 12px;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0 pt-0">
|
||||
<button type="button" class="btn btn-light fw-bold" data-bs-dismiss="modal" style="border-radius: 12px;"><?= __('cancel') ?></button>
|
||||
<button type="submit" class="btn btn-primary fw-bold" style="border-radius: 12px;"><?= __('save_changes') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('.edit-branch').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const d = this.dataset;
|
||||
document.getElementById('edit_id').value = d.id;
|
||||
document.getElementById('edit_name_en').value = d.name_en;
|
||||
document.getElementById('edit_name_ar').value = d.name_ar;
|
||||
document.getElementById('edit_company_id').value = d.company_id;
|
||||
document.getElementById('edit_phone').value = d.phone;
|
||||
document.getElementById('edit_prefix').value = d.prefix;
|
||||
new bootstrap.Modal(document.getElementById('editBranchModal')).show();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
138
company_profile.php
Normal file
138
company_profile.php
Normal file
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
$title = 'company_profile';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
// Only super_admin can access company profile
|
||||
if ($current_role !== 'super_admin') {
|
||||
echo '<div class="alert alert-danger">' . __('Access Denied') . '</div>';
|
||||
require_once __DIR__ . '/includes/footer.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
$success = '';
|
||||
$error = '';
|
||||
|
||||
// Get company data
|
||||
$stmt = db()->query("SELECT * FROM companies LIMIT 1");
|
||||
$company = $stmt->fetch();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$name_en = $_POST['name_en'] ?? '';
|
||||
$name_ar = $_POST['name_ar'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$phone = $_POST['phone'] ?? '';
|
||||
$address_en = $_POST['address_en'] ?? '';
|
||||
$address_ar = $_POST['address_ar'] ?? '';
|
||||
$vat_number = $_POST['vat_no'] ?? ''; // Keep for compatibility
|
||||
$ctr_no = $_POST['ctr_no'] ?? '';
|
||||
$vat_no = $_POST['vat_no'] ?? '';
|
||||
|
||||
// Handle Logo Upload
|
||||
$logo = $company['logo'];
|
||||
if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) {
|
||||
$ext = pathinfo($_FILES['logo']['name'], PATHINFO_EXTENSION);
|
||||
$filename = 'logo_' . time() . '.' . $ext;
|
||||
$target = 'assets/images/company/' . $filename;
|
||||
if (move_uploaded_file($_FILES['logo']['tmp_name'], $target)) {
|
||||
$logo = $target;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Favicon Upload
|
||||
$favicon = $company['favicon'];
|
||||
if (isset($_FILES['favicon']) && $_FILES['favicon']['error'] === UPLOAD_ERR_OK) {
|
||||
$ext = pathinfo($_FILES['favicon']['name'], PATHINFO_EXTENSION);
|
||||
$filename = 'favicon_' . time() . '.' . $ext;
|
||||
$target = 'assets/images/company/' . $filename;
|
||||
if (move_uploaded_file($_FILES['favicon']['tmp_name'], $target)) {
|
||||
$favicon = $target;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = db()->prepare("UPDATE companies SET name_en = ?, name_ar = ?, logo = ?, favicon = ?, email = ?, phone = ?, address_en = ?, address_ar = ?, vat_number = ?, ctr_no = ?, vat_no = ? WHERE id = ?");
|
||||
$stmt->execute([$name_en, $name_ar, $logo, $favicon, $email, $phone, $address_en, $address_ar, $vat_number, $ctr_no, $vat_no, $company['id']]);
|
||||
$success = __('success_update');
|
||||
// Refresh data
|
||||
$stmt = db()->query("SELECT * FROM companies LIMIT 1");
|
||||
$company = $stmt->fetch();
|
||||
} catch (Exception $e) {
|
||||
$error = __('error_update') . ' ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="h3 mb-0"><?= __('company_profile') ?></h2>
|
||||
</div>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success"><?= $success ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?= $error ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card p-4">
|
||||
<form action="company_profile.php" method="POST" enctype="multipart/form-data">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('name_en') ?></label>
|
||||
<input type="text" name="name_en" class="form-control" value="<?= htmlspecialchars($company['name_en']) ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('name_ar') ?></label>
|
||||
<input type="text" name="name_ar" class="form-control" value="<?= htmlspecialchars($company['name_ar']) ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('email') ?></label>
|
||||
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($company['email'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('phone') ?></label>
|
||||
<input type="text" name="phone" class="form-control" value="<?= htmlspecialchars($company['phone'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('ctr_no') ?></label>
|
||||
<input type="text" name="ctr_no" class="form-control" value="<?= htmlspecialchars($company['ctr_no'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('vat_no') ?></label>
|
||||
<input type="text" name="vat_no" class="form-control" value="<?= htmlspecialchars($company['vat_no'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label"><?= __('logo') ?></label>
|
||||
<input type="file" name="logo" class="form-control" accept="image/*">
|
||||
<?php if ($company['logo']): ?>
|
||||
<img src="<?= $company['logo'] ?>" alt="Logo" class="mt-2 rounded border" style="max-height: 50px;">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label"><?= __('favicon') ?></label>
|
||||
<input type="file" name="favicon" class="form-control" accept="image/*">
|
||||
<?php if ($company['favicon']): ?>
|
||||
<img src="<?= $company['favicon'] ?>" alt="Favicon" class="mt-2 rounded border" style="max-height: 32px;">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('address_en') ?></label>
|
||||
<textarea name="address_en" class="form-control" rows="3"><?= htmlspecialchars($company['address_en'] ?? '') ?></textarea>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('address_ar') ?></label>
|
||||
<textarea name="address_ar" class="form-control" rows="3"><?= htmlspecialchars($company['address_ar'] ?? '') ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-primary px-5"><?= __('save_changes') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
13
db/migrations/06_profile_additions.sql
Normal file
13
db/migrations/06_profile_additions.sql
Normal file
@ -0,0 +1,13 @@
|
||||
-- Add logo, favicon and other details to companies table
|
||||
ALTER TABLE companies
|
||||
ADD COLUMN logo VARCHAR(255) DEFAULT NULL AFTER name_ar,
|
||||
ADD COLUMN favicon VARCHAR(255) DEFAULT NULL AFTER logo,
|
||||
ADD COLUMN email VARCHAR(100) DEFAULT NULL AFTER favicon,
|
||||
ADD COLUMN phone VARCHAR(20) DEFAULT NULL AFTER email,
|
||||
ADD COLUMN address_en TEXT DEFAULT NULL AFTER phone,
|
||||
ADD COLUMN address_ar TEXT DEFAULT NULL AFTER address_en,
|
||||
ADD COLUMN vat_number VARCHAR(50) DEFAULT NULL AFTER address_ar;
|
||||
|
||||
-- Add profile_picture to users table
|
||||
ALTER TABLE users
|
||||
ADD COLUMN profile_picture VARCHAR(255) DEFAULT NULL AFTER email;
|
||||
8
db/migrations/07_update_currency_precision.sql
Normal file
8
db/migrations/07_update_currency_precision.sql
Normal file
@ -0,0 +1,8 @@
|
||||
-- Update decimal precision for all monetary columns to 3 decimals
|
||||
ALTER TABLE prices MODIFY COLUMN price DECIMAL(12, 3) NOT NULL DEFAULT 0.000;
|
||||
ALTER TABLE orders MODIFY COLUMN total_price DECIMAL(12, 3) NOT NULL DEFAULT 0.000;
|
||||
ALTER TABLE orders MODIFY COLUMN vat_total DECIMAL(12, 3) DEFAULT 0.000;
|
||||
ALTER TABLE order_items MODIFY COLUMN unit_price DECIMAL(12, 3) NOT NULL;
|
||||
ALTER TABLE order_items MODIFY COLUMN vat_amount DECIMAL(12, 3) DEFAULT 0.000;
|
||||
ALTER TABLE order_items MODIFY COLUMN subtotal DECIMAL(12, 3) NOT NULL;
|
||||
ALTER TABLE payments MODIFY COLUMN amount DECIMAL(12, 3) NOT NULL;
|
||||
6
db/migrations/08_add_ctr_vat_no.sql
Normal file
6
db/migrations/08_add_ctr_vat_no.sql
Normal file
@ -0,0 +1,6 @@
|
||||
-- Add CTR No and VAT No to companies table
|
||||
ALTER TABLE companies ADD COLUMN ctr_no VARCHAR(50) DEFAULT NULL AFTER vat_number;
|
||||
ALTER TABLE companies ADD COLUMN vat_no VARCHAR(50) DEFAULT NULL AFTER ctr_no;
|
||||
|
||||
-- Migrate existing vat_number to vat_no if any
|
||||
UPDATE companies SET vat_no = vat_number WHERE vat_no IS NULL AND vat_number IS NOT NULL;
|
||||
2
db/migrations/09_add_branch_prefix.sql
Normal file
2
db/migrations/09_add_branch_prefix.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- Migration 09: Add prefix column to branches table
|
||||
ALTER TABLE branches ADD COLUMN prefix VARCHAR(3) DEFAULT NULL;
|
||||
@ -8,18 +8,34 @@ if (!isset($_SESSION['user_id']) && basename($_SERVER['PHP_SELF']) !== 'login.ph
|
||||
exit;
|
||||
}
|
||||
|
||||
$current_user = $_SESSION['user_id'] ?? null;
|
||||
$current_user_id = $_SESSION['user_id'] ?? null;
|
||||
$current_branch = $_SESSION['branch_id'] ?? null;
|
||||
$current_role = $_SESSION['role'] ?? 'cashier';
|
||||
|
||||
// Fetch Global Company Info
|
||||
$stmt = db()->query("SELECT * FROM companies LIMIT 1");
|
||||
$company_info = $stmt->fetch();
|
||||
|
||||
// Fetch Current User Info (for profile picture)
|
||||
$current_user_data = null;
|
||||
if ($current_user_id) {
|
||||
$stmt = db()->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$stmt->execute([$current_user_id]);
|
||||
$current_user_data = $stmt->fetch();
|
||||
}
|
||||
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="<?= $lang ?>" dir="<?= is_rtl() ? 'rtl' : 'ltr' ?>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Laundry System - <?= __($title ?? 'dashboard') ?></title>
|
||||
<title><?= htmlspecialchars(is_arabic() ? $company_info['name_ar'] : $company_info['name_en']) ?> - <?= __($title ?? 'dashboard') ?></title>
|
||||
|
||||
<?php if ($company_info['favicon']): ?>
|
||||
<link rel="icon" type="image/x-icon" href="<?= $company_info['favicon'] ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.<?= is_rtl() ? 'rtl.' : '' ?>min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
@ -69,6 +85,16 @@ $current_role = $_SESSION['role'] ?? 'cashier';
|
||||
.lang-switch {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.user-avatar-sm {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dropdown-item i {
|
||||
margin-right: 0.5rem;
|
||||
<?= is_rtl() ? 'margin-left: 0.5rem; margin-right: 0;' : '' ?>
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -78,8 +104,12 @@ $current_role = $_SESSION['role'] ?? 'cashier';
|
||||
<!-- Sidebar -->
|
||||
<nav class="col-md-3 col-lg-2 d-md-block sidebar collapse">
|
||||
<div class="position-sticky">
|
||||
<div class="px-4 mb-4 mt-2">
|
||||
<h5 class="fw-bold">Laundry Admin</h5>
|
||||
<div class="px-4 mb-4 mt-2 text-center">
|
||||
<?php if ($company_info['logo']): ?>
|
||||
<img src="<?= $company_info['logo'] ?>" alt="Logo" class="img-fluid mb-2" style="max-height: 60px;">
|
||||
<?php else: ?>
|
||||
<h5 class="fw-bold"><?= htmlspecialchars(is_arabic() ? $company_info['name_ar'] : $company_info['name_en']) ?></h5>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<ul class="nav flex-column px-3">
|
||||
<li class="nav-item">
|
||||
@ -100,6 +130,12 @@ $current_role = $_SESSION['role'] ?? 'cashier';
|
||||
<?= __('orders') ?>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'lab.php' ? 'active' : '' ?>" href="lab.php">
|
||||
<i class="bi bi-flask"></i>
|
||||
<?= __('lab') ?>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'customers.php' ? 'active' : '' ?>" href="customers.php">
|
||||
<i class="bi bi-people"></i>
|
||||
@ -126,6 +162,24 @@ $current_role = $_SESSION['role'] ?? 'cashier';
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<hr class="mx-3 my-2 text-secondary">
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'profile.php' ? 'active' : '' ?>" href="profile.php">
|
||||
<i class="bi bi-person-circle"></i>
|
||||
<?= __('user_profile') ?>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<?php if ($current_role === 'super_admin'): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'company_profile.php' ? 'active' : '' ?>" href="company_profile.php">
|
||||
<i class="bi bi-building"></i>
|
||||
<?= __('company_profile') ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
|
||||
<hr class="mx-3 my-4">
|
||||
@ -150,9 +204,39 @@ $current_role = $_SESSION['role'] ?? 'cashier';
|
||||
<header class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2"><?= __($title ?? 'dashboard') ?></h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="me-2">
|
||||
<span class="badge bg-primary px-3 py-2"><?= $_SESSION['branch_name'] ?? '' ?></span>
|
||||
<span class="badge bg-secondary px-3 py-2 ms-2"><?= $_SESSION['full_name'] ?? '' ?></span>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="badge bg-primary px-3 py-2 me-2"><?= $_SESSION['branch_name'] ?? '' ?></span>
|
||||
|
||||
<div class="dropdown">
|
||||
<div class="d-flex align-items-center bg-white border rounded-pill px-3 py-1 dropdown-toggle" role="button" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false" style="cursor: pointer;">
|
||||
<?php if ($current_user_data['profile_picture']): ?>
|
||||
<img src="<?= $current_user_data['profile_picture'] ?>" alt="Profile" class="user-avatar-sm me-2">
|
||||
<?php else: ?>
|
||||
<i class="bi bi-person-circle fs-5 me-2 text-secondary"></i>
|
||||
<?php endif; ?>
|
||||
<span class="small fw-bold"><?= $_SESSION['full_name'] ?? '' ?></span>
|
||||
</div>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow border-0 mt-2" aria-labelledby="userDropdown" style="border-radius: 12px;">
|
||||
<li>
|
||||
<a class="dropdown-item py-2" href="profile.php">
|
||||
<i class="bi bi-person-circle"></i> <?= __('user_profile') ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php if ($current_role === 'super_admin'): ?>
|
||||
<li>
|
||||
<a class="dropdown-item py-2" href="company_profile.php">
|
||||
<i class="bi bi-building"></i> <?= __('company_profile') ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item py-2 text-danger" href="logout.php">
|
||||
<i class="bi bi-box-arrow-right"></i> <?= __('logout') ?>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
@ -22,6 +22,8 @@ $translations = [
|
||||
'login' => 'Login',
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'confirm_password' => 'Confirm Password',
|
||||
'change_password' => 'Change Password',
|
||||
'submit' => 'Submit',
|
||||
'save' => 'Save',
|
||||
'save_changes' => 'Save Changes',
|
||||
@ -94,6 +96,48 @@ $translations = [
|
||||
'no_items_found' => 'No items found',
|
||||
'pricing_management' => 'Pricing Management',
|
||||
'are_you_sure' => 'Are you sure?',
|
||||
'company_profile' => 'Company Profile',
|
||||
'user_profile' => 'User Profile',
|
||||
'logo' => 'Logo',
|
||||
'favicon' => 'Aيقونة الموقع',
|
||||
'email' => 'Email',
|
||||
'address_en' => 'Address (EN)',
|
||||
'address_ar' => 'Address (AR)',
|
||||
'vat_number' => 'VAT Number',
|
||||
'profile_picture' => 'Profile Picture',
|
||||
'full_name_en' => 'Full Name (EN)',
|
||||
'full_name_ar' => 'Full Name (AR)',
|
||||
'update_profile' => 'Update Profile',
|
||||
'success_update' => 'Updated successfully!',
|
||||
'error_update' => 'Error updating!',
|
||||
'print' => 'Print',
|
||||
'print_invoice' => 'Print Invoice',
|
||||
'thermal_receipt' => 'Thermal Receipt',
|
||||
'order' => 'Order',
|
||||
'customer' => 'Customer',
|
||||
'qty' => 'Qty',
|
||||
'close' => 'Close',
|
||||
'payments' => 'Payments',
|
||||
'no_payments' => 'No payments',
|
||||
'update_status' => 'Update Status',
|
||||
'update' => 'Update',
|
||||
'remaining_amount' => 'Remaining Amount',
|
||||
'add_payment' => 'Add Payment',
|
||||
'payment_method' => 'Payment Method',
|
||||
'customer_details' => 'Customer Details',
|
||||
'order_date' => 'Order Date',
|
||||
'method' => 'Method',
|
||||
'amount' => 'Amount',
|
||||
'currency' => 'OMR',
|
||||
'ctr_no' => 'CR Number',
|
||||
'vat_no' => 'VAT Number',
|
||||
'add_new_user' => 'Add New User',
|
||||
'role' => 'Role',
|
||||
'add_user' => 'Add User',
|
||||
'lab' => 'Lab Module',
|
||||
'outlet' => 'Outlet',
|
||||
'view_items' => 'View Items',
|
||||
'initial_letters' => 'Initial letters (3 letters)'
|
||||
],
|
||||
'ar' => [
|
||||
'dashboard' => 'لوحة القيادة',
|
||||
@ -109,6 +153,8 @@ $translations = [
|
||||
'login' => 'تسجيل الدخول',
|
||||
'username' => 'اسم المستخدم',
|
||||
'password' => 'كلمة المرور',
|
||||
'confirm_password' => 'تأكيد كلمة المرور',
|
||||
'change_password' => 'تغيير كلمة المرور',
|
||||
'submit' => 'إرسال',
|
||||
'save' => 'حفظ',
|
||||
'save_changes' => 'حفظ التغييرات',
|
||||
@ -181,6 +227,48 @@ $translations = [
|
||||
'no_items_found' => 'لا يوجد أصناف',
|
||||
'pricing_management' => 'إدارة الأسعار',
|
||||
'are_you_sure' => 'هل أنت متأكد؟',
|
||||
'company_profile' => 'ملف الشركة',
|
||||
'user_profile' => 'ملف المستخدم',
|
||||
'logo' => 'الشعار',
|
||||
'favicon' => 'أيقونة الموقع',
|
||||
'email' => 'البريد الإلكتروني',
|
||||
'address_en' => 'العنوان (EN)',
|
||||
'address_ar' => 'العنوان (AR)',
|
||||
'vat_number' => 'الرقم الضريبي',
|
||||
'profile_picture' => 'الصورة الشخصية',
|
||||
'full_name_en' => 'الاسم الكامل (EN)',
|
||||
'full_name_ar' => 'الاسم الكامل (AR)',
|
||||
'update_profile' => 'تحديث الملف الشخصي',
|
||||
'success_update' => 'تم التحديث بنجاح!',
|
||||
'error_update' => 'خطأ في التحديث!',
|
||||
'print' => 'طباعة',
|
||||
'print_invoice' => 'طباعة الفاتورة',
|
||||
'thermal_receipt' => 'إيصال حراري',
|
||||
'order' => 'الطلب',
|
||||
'customer' => 'العميل',
|
||||
'qty' => 'الكمية',
|
||||
'close' => 'إغلاق',
|
||||
'payments' => 'المدفوعات',
|
||||
'no_payments' => 'لا يوجد مدفوعات',
|
||||
'update_status' => 'تحديث الحالة',
|
||||
'update' => 'تحديث',
|
||||
'remaining_amount' => 'المبلغ المتبقي',
|
||||
'add_payment' => 'إضافة دفعة',
|
||||
'payment_method' => 'طريقة الدفع',
|
||||
'customer_details' => 'تفاصيل العميل',
|
||||
'order_date' => 'تاريخ الطلب',
|
||||
'method' => 'الطريقة',
|
||||
'amount' => 'المبلغ',
|
||||
'currency' => 'ر.ع.',
|
||||
'ctr_no' => 'رقم السجل التجاري',
|
||||
'vat_no' => 'الرقم الضريبي',
|
||||
'add_new_user' => 'إضافة مستخدم جديد',
|
||||
'role' => 'الدور',
|
||||
'add_user' => 'إضافة مستخدم',
|
||||
'lab' => 'وحدة المختبر',
|
||||
'outlet' => 'المنفذ',
|
||||
'view_items' => 'عرض الأصناف',
|
||||
'initial_letters' => 'الحروف الأولى (3 حروف)'
|
||||
]
|
||||
];
|
||||
|
||||
@ -197,4 +285,16 @@ function is_rtl() {
|
||||
function is_arabic() {
|
||||
global $lang;
|
||||
return $lang === 'ar';
|
||||
}
|
||||
|
||||
function currency() {
|
||||
return __('currency');
|
||||
}
|
||||
|
||||
function decimals() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
function format_amount($amount) {
|
||||
return number_format((float)$amount, decimals()) . ' ' . currency();
|
||||
}
|
||||
@ -513,7 +513,7 @@ foreach ($prices_raw as $p) {
|
||||
<thead class="bg-light">
|
||||
<tr class="small text-muted">
|
||||
<th style="min-width: 200px;"><?= __('service') ?></th>
|
||||
<th class="text-center"><?= __('price') ?> (SAR)</th>
|
||||
<th class="text-center"><?= __('price') ?> (<?= currency() ?>)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -524,8 +524,8 @@ foreach ($prices_raw as $p) {
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="input-group input-group-sm justify-content-center mx-auto" style="width: 150px; position: relative;">
|
||||
<input type="number" step="0.01" class="form-control text-center price-input"
|
||||
value="<?= $prices[$item['id']][$service['id']] ?? 0.00 ?>"
|
||||
<input type="number" step="0.001" class="form-control text-center price-input"
|
||||
value="<?= number_format($prices[$item['id']][$service['id']] ?? 0, decimals(), '.', '') ?>"
|
||||
data-item-id="<?= $item['id'] ?>"
|
||||
data-service-id="<?= $service['id'] ?>"
|
||||
style="border-radius: 8px;">
|
||||
@ -698,4 +698,4 @@ document.getElementById('itemImageFile').addEventListener('change', function(e)
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
|
||||
275
lab.php
Normal file
275
lab.php
Normal file
@ -0,0 +1,275 @@
|
||||
<?php
|
||||
$title = 'lab';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
$current_role = $_SESSION['role'] ?? 'cashier';
|
||||
|
||||
$status_filter = $_GET['status'] ?? '';
|
||||
$branch_filter = $_GET['branch_id'] ?? '';
|
||||
|
||||
$sql = "SELECT o.*, c.name_en as customer_name_en, c.name_ar as customer_name_ar,
|
||||
b.name_en as branch_name_en, b.name_ar as branch_name_ar
|
||||
FROM orders o
|
||||
LEFT JOIN customers c ON o.customer_id = c.id
|
||||
LEFT JOIN branches b ON o.branch_id = b.id
|
||||
WHERE 1=1";
|
||||
$params = [];
|
||||
|
||||
if ($status_filter) {
|
||||
$sql .= " AND o.status = ?";
|
||||
$params[] = $status_filter;
|
||||
}
|
||||
if ($branch_filter) {
|
||||
$sql .= " AND o.branch_id = ?";
|
||||
$params[] = $branch_filter;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY o.created_at DESC";
|
||||
$stmt = db()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$orders = $stmt->fetchAll();
|
||||
|
||||
$branches = db()->query("SELECT id, name_en, name_ar FROM branches")->fetchAll();
|
||||
|
||||
?>
|
||||
|
||||
<div class="card p-4 shadow-sm border-0" style="border-radius: 20px;">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h5 class="fw-bold mb-0"><?= __('lab') ?? 'Lab Module' ?></h5>
|
||||
<p class="text-muted small mb-0"><?= __('manage_orders_across_outlets') ?? 'Manage orders across all outlets' ?></p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<form action="" method="GET" class="d-flex gap-2 flex-wrap">
|
||||
<select name="branch_id" class="form-select border-radius-12" style="border-radius: 12px; width: auto;" onchange="this.form.submit()">
|
||||
<option value=""><?= __('all_branches') ?? 'All Branches' ?></option>
|
||||
<?php foreach($branches as $b): ?>
|
||||
<option value="<?= $b['id'] ?>" <?= $branch_filter == $b['id'] ? 'selected' : '' ?>>
|
||||
<?= $lang === 'ar' ? ($b['name_ar'] ?: $b['name_en']) : $b['name_en'] ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select name="status" class="form-select border-radius-12" style="border-radius: 12px; width: auto;" onchange="this.form.submit()">
|
||||
<option value=""><?= __('all_status') ?? 'All Status' ?></option>
|
||||
<option value="received" <?= $status_filter == 'received' ? 'selected' : '' ?>><?= __('received') ?></option>
|
||||
<option value="processing" <?= $status_filter == 'processing' ? 'selected' : '' ?>><?= __('processing') ?></option>
|
||||
<option value="ready" <?= $status_filter == 'ready' ? 'selected' : '' ?>><?= __('ready') ?></option>
|
||||
<option value="delivered" <?= $status_filter == 'delivered' ? 'selected' : '' ?>><?= __('delivered') ?></option>
|
||||
<option value="cancelled" <?= $status_filter == 'cancelled' ? 'selected' : '' ?>><?= __('cancelled') ?></option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">#</th>
|
||||
<th><?= __('order_number') ?? 'Order #' ?></th>
|
||||
<th><?= __('outlet') ?? 'Outlet' ?></th>
|
||||
<th><?= __('customer') ?></th>
|
||||
<th><?= __('status') ?></th>
|
||||
<th><?= __('date') ?></th>
|
||||
<th class="text-end pe-4"><?= __('actions') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($orders as $order): ?>
|
||||
<tr>
|
||||
<td class="ps-4"><?= $order['id'] ?></td>
|
||||
<td class="fw-bold"><?= $order['order_number'] ?></td>
|
||||
<td>
|
||||
<span class="badge bg-light text-dark border fw-normal">
|
||||
<?= $lang === 'ar' ? ($order['branch_name_ar'] ?: $order['branch_name_en']) : $order['branch_name_en'] ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-bold"><?= $lang === 'ar' ? ($order['customer_name_ar'] ?: $order['customer_name_en']) : $order['customer_name_en'] ?></div>
|
||||
</td>
|
||||
<td><span class="badge bg-<?= getStatusColor($order['status']) ?> rounded-pill px-3 py-2"><?= __($order['status']) ?></span></td>
|
||||
<td class="small text-muted"><?= date('d/m/Y H:i', strtotime($order['created_at'])) ?></td>
|
||||
<td class="text-end pe-4">
|
||||
<div class="d-flex gap-1 justify-content-end">
|
||||
<button class="btn btn-sm btn-light border-0 p-2 text-primary view-items-btn" data-id="<?= $order['id'] ?>" title="<?= __('view_items') ?>" style="border-radius: 8px;">
|
||||
<i class="bi bi-eye-fill"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-light border-0 p-2 text-info status-change-btn" data-id="<?= $order['id'] ?>" data-status="<?= $order['status'] ?>" title="<?= __('update_status') ?>" style="border-radius: 8px;">
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($orders)): ?>
|
||||
<tr>
|
||||
<td colspan="7" class="text-center py-5 text-muted">
|
||||
<i class="bi bi-inbox fs-1 d-block mb-3 opacity-25"></i>
|
||||
<?= __('no_orders_found') ?? 'No orders found' ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View Items Modal -->
|
||||
<div class="modal fade" id="itemsModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 rounded-4 shadow-lg">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold"><?= __('order') ?> #<span id="displayOrderNumber"></span></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<div id="itemsContainer">
|
||||
<div class="text-center py-4">
|
||||
<div class="spinner-border text-primary" role="status"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn btn-light rounded-3" data-bs-dismiss="modal"><?= __('close') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Update Modal (Reused from orders.php) -->
|
||||
<div class="modal fade" id="statusModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 rounded-4 shadow-lg">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold"><?= __('update_status') ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<input type="hidden" id="modalOrderId">
|
||||
<div class="row g-2">
|
||||
<div class="col-6">
|
||||
<button class="btn btn-outline-secondary w-100 py-3 rounded-4 status-opt" data-status="received"><?= __('received') ?></button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<button class="btn btn-outline-primary w-100 py-3 rounded-4 status-opt" data-status="processing"><?= __('processing') ?></button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<button class="btn btn-outline-success w-100 py-3 rounded-4 status-opt" data-status="ready"><?= __('ready') ?></button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<button class="btn btn-outline-dark w-100 py-3 rounded-4 status-opt" data-status="delivered"><?= __('delivered') ?></button>
|
||||
</div>
|
||||
<div class="col-12 mt-2">
|
||||
<button class="btn btn-outline-danger w-100 py-3 rounded-4 status-opt" data-status="cancelled"><?= __('cancelled') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const statusModal = new bootstrap.Modal(document.getElementById('statusModal'));
|
||||
const itemsModal = new bootstrap.Modal(document.getElementById('itemsModal'));
|
||||
|
||||
// View Items Logic
|
||||
document.querySelectorAll('.view-items-btn').forEach(btn => {
|
||||
btn.onclick = async () => {
|
||||
const id = btn.dataset.id;
|
||||
document.getElementById('displayOrderNumber').innerText = '...';
|
||||
document.getElementById('itemsContainer').innerHTML = `
|
||||
<div class="text-center py-4">
|
||||
<div class="spinner-border text-primary" role="status"></div>
|
||||
</div>
|
||||
`;
|
||||
itemsModal.show();
|
||||
|
||||
try {
|
||||
const res = await fetch(`api/get_order_items.php?id=${id}`);
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
document.getElementById('displayOrderNumber').innerText = data.order_number;
|
||||
let html = `<ul class="list-group list-group-flush">`;
|
||||
data.items.forEach(item => {
|
||||
const itemName = '<?= $lang ?>' === 'ar' ? (item.item_ar || item.item_en) : item.item_en;
|
||||
html += `
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center py-3 border-0 border-bottom">
|
||||
<div class="fw-bold text-dark">${itemName}</div>
|
||||
<span class="badge bg-primary rounded-pill px-3 fs-6">${item.quantity}</span>
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
html += `</ul>`;
|
||||
document.getElementById('itemsContainer').innerHTML = html;
|
||||
} else {
|
||||
document.getElementById('itemsContainer').innerHTML = `<div class="alert alert-danger">${data.error}</div>`;
|
||||
}
|
||||
} catch (e) {
|
||||
document.getElementById('itemsContainer').innerHTML = `<div class="alert alert-danger">Error loading items</div>`;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Status Change Logic
|
||||
document.querySelectorAll('.status-change-btn').forEach(btn => {
|
||||
btn.onclick = () => {
|
||||
const id = btn.dataset.id;
|
||||
const currentStatus = btn.dataset.status;
|
||||
document.getElementById('modalOrderId').value = id;
|
||||
document.querySelectorAll('.status-opt').forEach(opt => {
|
||||
opt.classList.remove('active');
|
||||
if (opt.dataset.status === currentStatus) opt.classList.add('active');
|
||||
});
|
||||
statusModal.show();
|
||||
};
|
||||
});
|
||||
|
||||
document.querySelectorAll('.status-opt').forEach(btn => {
|
||||
btn.onclick = async () => {
|
||||
const id = document.getElementById('modalOrderId').value;
|
||||
const status = btn.dataset.status;
|
||||
try {
|
||||
const res = await fetch('api/update_order_status.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, status })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(data.error);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error updating status');
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.status-opt.active {
|
||||
background-color: var(--bs-primary);
|
||||
color: white;
|
||||
border-color: var(--bs-primary);
|
||||
}
|
||||
.status-opt:hover {
|
||||
transform: translateY(-2px);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php
|
||||
function getStatusColor($status) {
|
||||
return [
|
||||
'received' => 'secondary',
|
||||
'processing' => 'primary',
|
||||
'ready' => 'success',
|
||||
'delivered' => 'dark',
|
||||
'cancelled' => 'danger',
|
||||
][$status] ?? 'info';
|
||||
}
|
||||
require_once __DIR__ . '/includes/footer.php';
|
||||
?>
|
||||
52
login.php
52
login.php
@ -2,6 +2,10 @@
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/lang.php';
|
||||
|
||||
// Fetch Global Company Info
|
||||
$stmt = db()->query("SELECT * FROM companies LIMIT 1");
|
||||
$company_info = $stmt->fetch();
|
||||
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
header('Location: admin.php');
|
||||
exit;
|
||||
@ -39,8 +43,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= __('login') ?></title>
|
||||
<title><?= htmlspecialchars(is_arabic() ? $company_info['name_ar'] : $company_info['name_en']) ?> - <?= __('login') ?></title>
|
||||
|
||||
<?php if ($company_info['favicon']): ?>
|
||||
<link rel="icon" type="image/x-icon" href="<?= $company_info['favicon'] ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.<?= is_rtl() ? 'rtl.' : '' ?>min.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Cairo:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
@ -48,39 +61,56 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-family: 'Inter', <?= is_rtl() ? "'Cairo'," : '' ?> sans-serif;
|
||||
}
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 2rem;
|
||||
padding: 2.5rem;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.05);
|
||||
background: #fff;
|
||||
}
|
||||
.login-logo {
|
||||
max-height: 80px;
|
||||
width: auto;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-card">
|
||||
<h3 class="text-center mb-4 fw-bold">Laundry Admin</h3>
|
||||
<div class="text-center">
|
||||
<?php if ($company_info['logo']): ?>
|
||||
<img src="<?= $company_info['logo'] ?>" alt="Logo" class="login-logo">
|
||||
<?php else: ?>
|
||||
<h3 class="mb-4 fw-bold"><?= htmlspecialchars(is_arabic() ? $company_info['name_ar'] : $company_info['name_en']) ?></h3>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?= $error ?></div>
|
||||
<div class="alert alert-danger py-2"><?= $error ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= __('username') ?></label>
|
||||
<input type="text" name="username" class="form-control" required>
|
||||
<input type="text" name="username" class="form-control" placeholder="<?= __('username') ?>" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label"><?= __('password') ?></label>
|
||||
<input type="password" name="password" class="form-control" required>
|
||||
<input type="password" name="password" class="form-control" placeholder="<?= __('password') ?>" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100 py-2 fw-bold" style="border-radius: 12px;"><?= __('login') ?></button>
|
||||
<button type="submit" class="btn btn-primary w-100 py-2 fw-bold mb-3" style="border-radius: 12px;"><?= __('login') ?></button>
|
||||
</form>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<a href="?lang=en">English</a> | <a href="?lang=ar">العربية</a>
|
||||
<div class="text-center mt-3 pt-3 border-top">
|
||||
<div class="nav-link text-muted small">
|
||||
<a href="?lang=en" class="text-decoration-none <?= $lang === 'en' ? 'fw-bold text-primary' : '' ?>">English</a>
|
||||
<span class="mx-2 text-secondary opacity-25">|</span>
|
||||
<a href="?lang=ar" class="text-decoration-none <?= $lang === 'ar' ? 'fw-bold text-primary' : '' ?>">العربية</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -99,24 +99,24 @@ $payments = $stmt->fetchAll();
|
||||
</td>
|
||||
<td><?= $lang === 'ar' ? ($item['service_ar'] ?: $item['service_en']) : $item['service_en'] ?></td>
|
||||
<td><?= $item['quantity'] ?></td>
|
||||
<td class="text-end"><?= number_format($item['unit_price'], 2) ?> SAR</td>
|
||||
<td class="text-end"><?= number_format($item['vat_amount'] * $item['quantity'], 2) ?> SAR</td>
|
||||
<td class="text-end fw-bold"><?= number_format($item['subtotal'] + ($item['vat_amount'] * $item['quantity']), 2) ?> SAR</td>
|
||||
<td class="text-end"><?= format_amount($item['unit_price']) ?></td>
|
||||
<td class="text-end"><?= format_amount($item['vat_amount'] * $item['quantity']) ?></td>
|
||||
<td class="text-end fw-bold"><?= format_amount($item['subtotal'] + ($item['vat_amount'] * $item['quantity'])) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="5" class="text-end text-muted small"><?= __('subtotal') ?></th>
|
||||
<th class="text-end"><?= number_format($subtotal_sum, 2) ?> SAR</th>
|
||||
<th class="text-end"><?= format_amount($subtotal_sum) ?></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="5" class="text-end text-muted small"><?= __('vat_total') ?? 'VAT Total' ?></th>
|
||||
<th class="text-end"><?= number_format($order['vat_total'], 2) ?> SAR</th>
|
||||
<th class="text-end"><?= format_amount($order['vat_total']) ?></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="5" class="text-end fs-5 fw-bold"><?= __('total') ?></th>
|
||||
<th class="text-end fs-5 fw-bold text-primary"><?= number_format($order['total_price'], 2) ?> SAR</th>
|
||||
<th class="text-end fs-5 fw-bold text-primary"><?= format_amount($order['total_price']) ?></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -153,7 +153,7 @@ $payments = $stmt->fetchAll();
|
||||
<tr>
|
||||
<td><?= date('d M Y, h:i A', strtotime($p['created_at'])) ?></td>
|
||||
<td><span class="badge bg-light text-dark px-3 py-2 rounded-pill"><?= __($p['payment_method']) ?></span></td>
|
||||
<td class="text-end fw-bold"><?= number_format($p['amount'], 2) ?> SAR</td>
|
||||
<td class="text-end fw-bold"><?= format_amount($p['amount']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($payments)): ?>
|
||||
@ -194,7 +194,7 @@ $payments = $stmt->fetchAll();
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<span class="text-muted fw-bold small"><?= __('remaining_amount') ?></span>
|
||||
<span class="fs-5 fw-bold <?= $remaining <= 0 ? 'text-success' : 'text-danger' ?>">
|
||||
<?= number_format(max(0, $remaining), 2) ?> <small>SAR</small>
|
||||
<?= format_amount(max(0, $remaining)) ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -203,7 +203,7 @@ $payments = $stmt->fetchAll();
|
||||
<input type="hidden" name="action" value="add_payment">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold text-muted"><?= __('amount') ?></label>
|
||||
<input type="number" step="0.01" name="amount" class="form-control bg-light border-0" value="<?= $remaining ?>" required style="border-radius: 12px;">
|
||||
<input type="number" step="0.001" name="amount" class="form-control bg-light border-0" value="<?= $remaining ?>" required style="border-radius: 12px;">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold text-muted"><?= __('payment_method') ?></label>
|
||||
@ -217,9 +217,18 @@ $payments = $stmt->fetchAll();
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<button class="btn btn-outline-dark w-100 mt-4 py-3 fw-bold shadow-sm" style="border-radius: 15px;" onclick="window.print()">
|
||||
<i class="bi bi-printer me-2"></i> <?= __('print_invoice') ?>
|
||||
</button>
|
||||
<div class="row mt-4">
|
||||
<div class="col-6">
|
||||
<button class="btn btn-outline-dark w-100 py-3 fw-bold shadow-sm" style="border-radius: 15px;" onclick="window.print()">
|
||||
<i class="bi bi-printer me-2"></i> <?= __('print_invoice') ?>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<a href="receipt.php?id=<?= $order_id ?>" target="_blank" class="btn btn-dark w-100 py-3 fw-bold shadow-sm" style="border-radius: 15px;">
|
||||
<i class="bi bi-receipt me-2"></i> <?= __('thermal_receipt') ?? 'Thermal Receipt' ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
139
orders.php
139
orders.php
@ -65,7 +65,7 @@ $orders = $stmt->fetchAll();
|
||||
<th><?= __('status') ?></th>
|
||||
<th><?= __('payment_status') ?></th>
|
||||
<th><?= __('date') ?></th>
|
||||
<th><?= __('actions') ?></th>
|
||||
<th class="text-end"><?= __('actions') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -77,14 +77,25 @@ $orders = $stmt->fetchAll();
|
||||
<div class="fw-bold"><?= $lang === 'ar' ? ($order['customer_name_ar'] ?: $order['customer_name_en']) : $order['customer_name_en'] ?></div>
|
||||
<div class="small text-muted"><?= $order['customer_phone'] ?></div>
|
||||
</td>
|
||||
<td class="fw-bold text-primary"><?= number_format($order['total_price'], 2) ?></td>
|
||||
<td class="fw-bold text-primary"><?= format_amount($order['total_price']) ?></td>
|
||||
<td><span class="badge bg-<?= getStatusColor($order['status']) ?> rounded-pill px-3 py-2"><?= __($order['status']) ?></span></td>
|
||||
<td><span class="badge bg-<?= getPaymentStatusColor($order['payment_status']) ?> rounded-pill px-3 py-2"><?= __($order['payment_status']) ?></span></td>
|
||||
<td class="small"><?= date('d/m/Y H:i', strtotime($order['created_at'])) ?></td>
|
||||
<td>
|
||||
<a href="order_details.php?id=<?= $order['id'] ?>" class="btn btn-sm btn-light border-0 p-2" style="border-radius: 8px;">
|
||||
<i class="bi bi-eye-fill"></i>
|
||||
</a>
|
||||
<td class="text-end">
|
||||
<div class="d-flex gap-1 justify-content-end">
|
||||
<a href="order_details.php?id=<?= $order['id'] ?>" class="btn btn-sm btn-light border-0 p-2 text-primary" title="<?= __('view') ?? 'View' ?>" style="border-radius: 8px;">
|
||||
<i class="bi bi-eye-fill"></i>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-light border-0 p-2 text-info status-change-btn" data-id="<?= $order['id'] ?>" data-status="<?= $order['status'] ?>" title="<?= __('update_status') ?? 'Update Status' ?>" style="border-radius: 8px;">
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</button>
|
||||
<a href="pos.php?edit_order_id=<?= $order['id'] ?>" class="btn btn-sm btn-light border-0 p-2 text-warning" title="<?= __('edit') ?>" style="border-radius: 8px;">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-light border-0 p-2 text-danger delete-order-btn" data-id="<?= $order['id'] ?>" title="<?= __('delete') ?>" style="border-radius: 8px;">
|
||||
<i class="bi bi-trash-fill"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@ -101,6 +112,120 @@ $orders = $stmt->fetchAll();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Update Modal -->
|
||||
<div class="modal fade" id="statusModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 rounded-4 shadow-lg">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold"><?= __('update_status') ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<input type="hidden" id="modalOrderId">
|
||||
<div class="row g-2">
|
||||
<div class="col-6">
|
||||
<button class="btn btn-outline-secondary w-100 py-3 rounded-4 status-opt" data-status="received"><?= __('received') ?></button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<button class="btn btn-outline-primary w-100 py-3 rounded-4 status-opt" data-status="processing"><?= __('processing') ?></button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<button class="btn btn-outline-success w-100 py-3 rounded-4 status-opt" data-status="ready"><?= __('ready') ?></button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<button class="btn btn-outline-dark w-100 py-3 rounded-4 status-opt" data-status="delivered"><?= __('delivered') ?></button>
|
||||
</div>
|
||||
<div class="col-12 mt-2">
|
||||
<button class="btn btn-outline-danger w-100 py-3 rounded-4 status-opt" data-status="cancelled"><?= __('cancelled') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const statusModal = new bootstrap.Modal(document.getElementById('statusModal'));
|
||||
|
||||
// Status Change
|
||||
document.querySelectorAll('.status-change-btn').forEach(btn => {
|
||||
btn.onclick = () => {
|
||||
const id = btn.dataset.id;
|
||||
const currentStatus = btn.dataset.status;
|
||||
document.getElementById('modalOrderId').value = id;
|
||||
|
||||
// Highlight current status
|
||||
document.querySelectorAll('.status-opt').forEach(opt => {
|
||||
opt.classList.remove('active');
|
||||
if (opt.dataset.status === currentStatus) opt.classList.add('active');
|
||||
});
|
||||
|
||||
statusModal.show();
|
||||
};
|
||||
});
|
||||
|
||||
document.querySelectorAll('.status-opt').forEach(btn => {
|
||||
btn.onclick = async () => {
|
||||
const id = document.getElementById('modalOrderId').value;
|
||||
const status = btn.dataset.status;
|
||||
|
||||
try {
|
||||
const res = await fetch('api/update_order_status.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, status })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(data.error);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error updating status');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Delete Order
|
||||
document.querySelectorAll('.delete-order-btn').forEach(btn => {
|
||||
btn.onclick = async () => {
|
||||
if (confirm('<?= __('are_you_sure') ?>')) {
|
||||
const id = btn.dataset.id;
|
||||
try {
|
||||
const res = await fetch('api/delete_order.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(data.error);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error deleting order');
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.status-opt.active {
|
||||
background-color: var(--bs-primary);
|
||||
color: white;
|
||||
border-color: var(--bs-primary);
|
||||
}
|
||||
.status-opt:hover {
|
||||
transform: translateY(-2px);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php
|
||||
function getStatusColor($status) {
|
||||
return [
|
||||
@ -119,4 +244,4 @@ function getPaymentStatusColor($status) {
|
||||
][$status] ?? 'info';
|
||||
}
|
||||
require_once __DIR__ . '/includes/footer.php';
|
||||
?>
|
||||
?>
|
||||
101
pos.php
101
pos.php
@ -4,6 +4,38 @@ require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
$branch_id = $_SESSION['branch_id'] ?? 1;
|
||||
|
||||
// Handle Edit Order
|
||||
$edit_order_id = $_GET['edit_order_id'] ?? null;
|
||||
$edit_order = null;
|
||||
$edit_items = [];
|
||||
if ($edit_order_id) {
|
||||
$stmt = db()->prepare("SELECT * FROM orders WHERE id = ? AND branch_id = ?");
|
||||
$stmt->execute([$edit_order_id, $branch_id]);
|
||||
$edit_order = $stmt->fetch();
|
||||
|
||||
if ($edit_order) {
|
||||
$stmt = db()->prepare("SELECT oi.*, i.name_en, i.name_ar, i.vat_percent, s.name_en as service_en, s.name_ar as service_ar
|
||||
FROM order_items oi
|
||||
JOIN items i ON oi.item_id = i.id
|
||||
JOIN services s ON oi.service_id = s.id
|
||||
WHERE oi.order_id = ?");
|
||||
$stmt->execute([$edit_order_id]);
|
||||
$edit_items_raw = $stmt->fetchAll();
|
||||
|
||||
foreach ($edit_items_raw as $ei) {
|
||||
$edit_items[] = [
|
||||
'item_id' => $ei['item_id'],
|
||||
'service_id' => $ei['service_id'],
|
||||
'name' => $lang === 'en' ? $ei['name_en'] : $ei['name_ar'],
|
||||
'service_name' => $lang === 'en' ? $ei['service_en'] : $ei['service_ar'],
|
||||
'price' => (float)$ei['unit_price'],
|
||||
'qty' => (int)$ei['quantity'],
|
||||
'vat_percent' => (float)$ei['vat_percent']
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all categories
|
||||
$categories = db()->query("SELECT * FROM categories ORDER BY name_en ASC")->fetchAll();
|
||||
|
||||
@ -63,7 +95,7 @@ $stmt = db()->prepare("SELECT * FROM customers WHERE branch_id = ? ORDER BY name
|
||||
$stmt->execute([$branch_id]);
|
||||
$customers = $stmt->fetchAll();
|
||||
|
||||
$pageTitle = $lang == 'en' ? 'Point of Sale' : 'نقطة البيع';
|
||||
$pageTitle = $edit_order ? ($lang == 'en' ? 'Edit Order #' . $edit_order['order_number'] : 'تعديل طلب رقم ' . $edit_order['order_number']) : ($lang == 'en' ? 'Point of Sale' : 'نقطة البيع');
|
||||
?>
|
||||
|
||||
<div class="container-fluid py-4">
|
||||
@ -164,7 +196,7 @@ $pageTitle = $lang == 'en' ? 'Point of Sale' : 'نقطة البيع';
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" id="customerId" value="">
|
||||
<input type="hidden" id="customerId" value="<?= $edit_order ? $edit_order['customer_id'] : '' ?>">
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary rounded-3" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
|
||||
@ -185,19 +217,19 @@ $pageTitle = $lang == 'en' ? 'Point of Sale' : 'نقطة البيع';
|
||||
<div class="bg-light rounded-4 p-3 mb-3">
|
||||
<div class="d-flex justify-content-between mb-2 small text-muted">
|
||||
<span><?= $lang == 'en' ? 'Subtotal' : 'المجموع الفرعي' ?></span>
|
||||
<span id="cartSubtotal">0.00 SAR</span>
|
||||
<span id="cartSubtotal">0.000 <?= currency() ?></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2 small text-muted">
|
||||
<span><?= $lang == 'en' ? 'VAT' : 'الضريبة' ?></span>
|
||||
<span id="cartVat">0.00 SAR</span>
|
||||
<span id="cartVat">0.000 <?= currency() ?></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between pt-2 border-top border-secondary border-opacity-10 mt-2">
|
||||
<h5 class="mb-0 fw-bold"><?= $lang == 'en' ? 'Total' : 'الإجمالي' ?></h5>
|
||||
<h5 id="cartTotal" class="mb-0 fw-bold text-primary">0.00 SAR</h5>
|
||||
<h5 id="cartTotal" class="mb-0 fw-bold text-primary">0.000 <?= currency() ?></h5>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary w-100 py-3 rounded-4 fw-bold shadow-sm" onclick="checkout()">
|
||||
<i class="bi bi-credit-card me-2"></i> <?= $lang == 'en' ? 'Complete Order' : 'إتمام الطلب' ?>
|
||||
<i class="bi bi-credit-card me-2"></i> <?= $edit_order ? ($lang == 'en' ? 'Update Order' : 'تحديث الطلب') : ($lang == 'en' ? 'Complete Order' : 'إتمام الطلب') ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -259,16 +291,22 @@ $pageTitle = $lang == 'en' ? 'Point of Sale' : 'نقطة البيع';
|
||||
<script>
|
||||
const itemsData = <?= json_encode((object)$items) ?>;
|
||||
const lang = '<?= $lang ?>';
|
||||
let cart = [];
|
||||
const currencyLabel = '<?= currency() ?>';
|
||||
const decimalPrecision = <?= decimals() ?>;
|
||||
const editOrderId = <?= $edit_order_id ?: 'null' ?>;
|
||||
let cart = <?= json_encode($edit_items) ?>;
|
||||
let selectionModal;
|
||||
|
||||
try {
|
||||
const saved = localStorage.getItem('pos_cart');
|
||||
if (saved) {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (Array.isArray(parsed)) cart = parsed;
|
||||
}
|
||||
} catch (e) { console.error('Cart parse error', e); }
|
||||
// If not editing, try to load from local storage
|
||||
if (!editOrderId) {
|
||||
try {
|
||||
const saved = localStorage.getItem('pos_cart');
|
||||
if (saved) {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (Array.isArray(parsed) && parsed.length > 0) cart = parsed;
|
||||
}
|
||||
} catch (e) { console.error('Cart parse error', e); }
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (typeof bootstrap !== 'undefined') {
|
||||
@ -312,6 +350,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const clearCustBtn = document.getElementById('clearCustomerBtn');
|
||||
const custIdInput = document.getElementById('customerId');
|
||||
|
||||
// Handle Edit Customer Pre-fill
|
||||
<?php if ($edit_order && $edit_order['customer_id']): ?>
|
||||
const initialCustId = "<?= $edit_order['customer_id'] ?>";
|
||||
const initialCustBtn = document.querySelector(`.customer-result-item[data-id="${initialCustId}"]`);
|
||||
if (initialCustBtn) {
|
||||
custSearchInput.value = initialCustBtn.getAttribute('data-name');
|
||||
clearCustBtn.classList.remove('d-none');
|
||||
}
|
||||
<?php endif; ?>
|
||||
|
||||
if (custSearchInput) {
|
||||
custSearchInput.addEventListener('focus', () => {
|
||||
custResults.classList.remove('d-none');
|
||||
@ -385,7 +433,7 @@ function showOptions(itemId) {
|
||||
col.innerHTML = `
|
||||
<button class="btn btn-outline-primary w-100 p-3 rounded-4 border-2 text-center h-100 transition-all" onclick="addToCart(${item.id}, ${s.id})">
|
||||
<div class="fw-bold mb-1 small">${lang === 'en' ? s.name_en : s.name_ar}</div>
|
||||
<div class="small opacity-75">${s.price.toFixed(2)} SAR</div>
|
||||
<div class="small opacity-75">${s.price.toFixed(decimalPrecision)} ${currencyLabel}</div>
|
||||
</button>
|
||||
`;
|
||||
list.appendChild(col);
|
||||
@ -426,7 +474,9 @@ function changeQty(index, delta) {
|
||||
}
|
||||
|
||||
function updateCart() {
|
||||
localStorage.setItem('pos_cart', JSON.stringify(cart));
|
||||
if (!editOrderId) {
|
||||
localStorage.setItem('pos_cart', JSON.stringify(cart));
|
||||
}
|
||||
const cartList = document.getElementById('cartItems');
|
||||
const emptyCart = document.getElementById('emptyCart');
|
||||
if (!cartList || !emptyCart) return;
|
||||
@ -441,7 +491,7 @@ function updateCart() {
|
||||
<div class="flex-grow-1">
|
||||
<div class="fw-bold small text-dark">${item.name}</div>
|
||||
<div class="text-muted" style="font-size: 0.75rem;">${item.service_name}</div>
|
||||
<div class="fw-bold text-primary">${item.price.toFixed(2)} SAR</div>
|
||||
<div class="fw-bold text-primary">${item.price.toFixed(decimalPrecision)} ${currencyLabel}</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center bg-white rounded-3 p-1">
|
||||
<button class="btn btn-sm p-0 px-2" onclick="changeQty(${index}, -1)"><i class="bi bi-dash"></i></button>
|
||||
@ -465,9 +515,9 @@ function updateCart() {
|
||||
const vatEl = document.getElementById('cartVat');
|
||||
const totalEl = document.getElementById('cartTotal');
|
||||
|
||||
if (subtotalEl) subtotalEl.innerText = subtotal.toFixed(2) + ' SAR';
|
||||
if (vatEl) vatEl.innerText = totalVat.toFixed(2) + ' SAR';
|
||||
if (totalEl) totalEl.innerText = (subtotal + totalVat).toFixed(2) + ' SAR';
|
||||
if (subtotalEl) subtotalEl.innerText = subtotal.toFixed(decimalPrecision) + ' ' + currencyLabel;
|
||||
if (vatEl) vatEl.innerText = totalVat.toFixed(decimalPrecision) + ' ' + currencyLabel;
|
||||
if (totalEl) totalEl.innerText = (subtotal + totalVat).toFixed(decimalPrecision) + ' ' + currencyLabel;
|
||||
}
|
||||
|
||||
function clearCart() {
|
||||
@ -507,6 +557,7 @@ async function checkout() {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
order_id: editOrderId,
|
||||
customer_id: cid,
|
||||
items: itemsToSubmit,
|
||||
vat_total: totalVat,
|
||||
@ -515,9 +566,11 @@ async function checkout() {
|
||||
});
|
||||
const res = await response.json();
|
||||
if (res.success) {
|
||||
cart = [];
|
||||
updateCart();
|
||||
window.location.href = 'order_details.php?id=' + res.order_id;
|
||||
if (!editOrderId) {
|
||||
cart = [];
|
||||
updateCart();
|
||||
}
|
||||
window.location.href = 'order_details.php?id=' + (res.order_id || editOrderId);
|
||||
} else alert(res.error);
|
||||
} catch (e) { alert('Error'); }
|
||||
}
|
||||
@ -581,4 +634,4 @@ async function saveCustomer() {
|
||||
.customer-result-item:hover { background-color: #f8f9fa; }
|
||||
</style>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
132
profile.php
Normal file
132
profile.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
$title = 'user_profile';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
$success = '';
|
||||
$error = '';
|
||||
|
||||
// Get user data
|
||||
$stmt = db()->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$stmt->execute([$current_user_id]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$full_name_en = $_POST['full_name_en'] ?? '';
|
||||
$full_name_ar = $_POST['full_name_ar'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
$confirm_password = $_POST['confirm_password'] ?? '';
|
||||
|
||||
// Handle Profile Picture Upload
|
||||
$profile_picture = $user['profile_picture'];
|
||||
if (isset($_FILES['profile_picture']) && $_FILES['profile_picture']['error'] === UPLOAD_ERR_OK) {
|
||||
$ext = pathinfo($_FILES['profile_picture']['name'], PATHINFO_EXTENSION);
|
||||
$filename = 'user_' . $current_user_id . '_' . time() . '.' . $ext;
|
||||
$target = 'assets/images/users/' . $filename;
|
||||
if (move_uploaded_file($_FILES['profile_picture']['tmp_name'], $target)) {
|
||||
$profile_picture = $target;
|
||||
}
|
||||
}
|
||||
|
||||
if ($password !== '' && $password !== $confirm_password) {
|
||||
$error = 'Passwords do not match';
|
||||
} else {
|
||||
try {
|
||||
if ($password !== '') {
|
||||
$password_hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = db()->prepare("UPDATE users SET full_name_en = ?, full_name_ar = ?, email = ?, profile_picture = ?, password_hash = ? WHERE id = ?");
|
||||
$stmt->execute([$full_name_en, $full_name_ar, $email, $profile_picture, $password_hash, $current_user_id]);
|
||||
} else {
|
||||
$stmt = db()->prepare("UPDATE users SET full_name_en = ?, full_name_ar = ?, email = ?, profile_picture = ? WHERE id = ?");
|
||||
$stmt->execute([$full_name_en, $full_name_ar, $email, $profile_picture, $current_user_id]);
|
||||
}
|
||||
|
||||
// Update Session
|
||||
$_SESSION['full_name'] = is_arabic() ? ($full_name_ar ?: $full_name_en) : $full_name_en;
|
||||
|
||||
$success = __('success_update');
|
||||
// Refresh data
|
||||
$stmt = db()->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$stmt->execute([$current_user_id]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
// Refresh header data
|
||||
$current_user_data = $user;
|
||||
} catch (Exception $e) {
|
||||
$error = __('error_update') . ' ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="h3 mb-0"><?= __('user_profile') ?></h2>
|
||||
</div>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success"><?= $success ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?= $error ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card p-4">
|
||||
<form action="profile.php" method="POST" enctype="multipart/form-data">
|
||||
<div class="row align-items-center mb-4">
|
||||
<div class="col-md-3 text-center border-end">
|
||||
<div class="mb-3 position-relative d-inline-block">
|
||||
<?php if ($user['profile_picture']): ?>
|
||||
<img src="<?= $user['profile_picture'] ?>" alt="Profile Picture" class="rounded-circle border p-1" style="width: 150px; height: 150px; object-fit: cover;">
|
||||
<?php else: ?>
|
||||
<div class="rounded-circle bg-light d-flex align-items-center justify-content-center mx-auto text-primary border" style="width: 150px; height: 150px; font-size: 3rem;">
|
||||
<i class="bi bi-person"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<label class="btn btn-outline-primary btn-sm px-3">
|
||||
<i class="bi bi-camera me-1"></i> <?= __('profile_picture') ?>
|
||||
<input type="file" name="profile_picture" class="d-none" accept="image/*">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('full_name_en') ?></label>
|
||||
<input type="text" name="full_name_en" class="form-control" value="<?= htmlspecialchars($user['full_name_en'] ?? '') ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('full_name_ar') ?></label>
|
||||
<input type="text" name="full_name_ar" class="form-control" value="<?= htmlspecialchars($user['full_name_ar'] ?? '') ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('email') ?></label>
|
||||
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($user['email'] ?? '') ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('username') ?></label>
|
||||
<input type="text" class="form-control bg-light" value="<?= htmlspecialchars($user['username']) ?>" disabled>
|
||||
</div>
|
||||
<hr class="my-3">
|
||||
<h5 class="mb-3"><?= __('change_password') ?></h5>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('password') ?></label>
|
||||
<input type="password" name="password" class="form-control" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= __('confirm_password') ?></label>
|
||||
<input type="password" name="confirm_password" class="form-control" autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end border-top pt-3">
|
||||
<button type="submit" class="btn btn-primary px-5 fw-bold"><?= __('update_profile') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
253
receipt.php
Normal file
253
receipt.php
Normal file
@ -0,0 +1,253 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/lang.php';
|
||||
|
||||
$order_id = $_GET['id'] ?? null;
|
||||
if (!$order_id) {
|
||||
die("Order ID is required");
|
||||
}
|
||||
|
||||
// Fetch Global Company Info
|
||||
$stmt = db()->query("SELECT * FROM companies LIMIT 1");
|
||||
$company = $stmt->fetch();
|
||||
|
||||
// Fetch Order Details with Customer and Branch info
|
||||
$stmt = db()->prepare("SELECT o.*,
|
||||
c.name_en as customer_name_en, c.name_ar as customer_name_ar, c.phone as customer_phone,
|
||||
b.name_en as branch_name_en, b.name_ar as branch_name_ar, b.address_en as branch_address_en, b.address_ar as branch_address_ar, b.phone as branch_phone
|
||||
FROM orders o
|
||||
LEFT JOIN customers c ON o.customer_id = c.id
|
||||
JOIN branches b ON o.branch_id = b.id
|
||||
WHERE o.id = ?");
|
||||
$stmt->execute([$order_id]);
|
||||
$order = $stmt->fetch();
|
||||
|
||||
if (!$order) {
|
||||
die("Order not found");
|
||||
}
|
||||
|
||||
// Fetch Order Items
|
||||
$stmt = db()->prepare("SELECT oi.*, i.name_en as item_en, i.name_ar as item_ar,
|
||||
s.name_en as service_en, s.name_ar as service_ar
|
||||
FROM order_items oi
|
||||
JOIN items i ON oi.item_id = i.id
|
||||
JOIN services s ON oi.service_id = s.id
|
||||
WHERE oi.order_id = ?");
|
||||
$stmt->execute([$order_id]);
|
||||
$order_items = $stmt->fetchAll();
|
||||
|
||||
// Fetch Payments
|
||||
$stmt = db()->prepare("SELECT SUM(amount) as total_paid FROM payments WHERE order_id = ?");
|
||||
$stmt->execute([$order_id]);
|
||||
$total_paid = $stmt->fetch()['total_paid'] ?? 0;
|
||||
$remaining = $order['total_price'] - $total_paid;
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= $lang ?>" dir="<?= is_rtl() ? 'rtl' : 'ltr' ?>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Receipt #<?= $order['order_number'] ?></title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;700&family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body {
|
||||
font-family: 'Inter', 'Cairo', sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
width: 80mm;
|
||||
padding: 5mm;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.text-center { text-align: center; }
|
||||
.text-end { text-align: <?= is_rtl() ? 'left' : 'right' ?>; }
|
||||
.text-start { text-align: <?= is_rtl() ? 'right' : 'left' ?>; }
|
||||
.fw-bold { font-weight: bold; }
|
||||
.mb-1 { margin-bottom: 2mm; }
|
||||
.mb-2 { margin-bottom: 4mm; }
|
||||
.mt-2 { margin-top: 4mm; }
|
||||
|
||||
.logo {
|
||||
max-width: 50mm;
|
||||
max-height: 30mm;
|
||||
margin-bottom: 3mm;
|
||||
}
|
||||
|
||||
.receipt-header {
|
||||
border-bottom: 1px dashed #000;
|
||||
padding-bottom: 3mm;
|
||||
margin-bottom: 3mm;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1mm;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 3mm;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
border-bottom: 1px solid #000;
|
||||
padding: 1mm 0;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 1.5mm 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.totals {
|
||||
border-top: 1px dashed #000;
|
||||
padding-top: 2mm;
|
||||
}
|
||||
|
||||
.total-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1mm;
|
||||
}
|
||||
|
||||
.grand-total {
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #000;
|
||||
padding-top: 1mm;
|
||||
margin-top: 1mm;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 5mm;
|
||||
border-top: 1px dashed #000;
|
||||
padding-top: 3mm;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
width: 80mm;
|
||||
margin: 0;
|
||||
padding: 5mm;
|
||||
}
|
||||
.no-print {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="no-print text-center mb-2">
|
||||
<button onclick="window.print()" style="padding: 10px 20px; cursor: pointer; border-radius: 8px; border: 1px solid #000; background: #eee;"><?= __('print') ?? 'Print' ?></button>
|
||||
<button onclick="window.close()" style="padding: 10px 20px; cursor: pointer; border-radius: 8px; border: 1px solid #000; background: #fff;"><?= __('close') ?? 'Close' ?></button>
|
||||
</div>
|
||||
|
||||
<div class="text-center receipt-header">
|
||||
<?php if ($company['logo']): ?>
|
||||
<img src="<?= $company['logo'] ?>" alt="Logo" class="logo">
|
||||
<?php else: ?>
|
||||
<h2 class="fw-bold"><?= is_arabic() ? $company['name_ar'] : $company['name_en'] ?></h2>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="fw-bold fs-6"><?= is_arabic() ? $order['branch_name_ar'] : $order['branch_name_en'] ?></div>
|
||||
<div><?= is_arabic() ? $order['branch_address_ar'] : $order['branch_address_en'] ?></div>
|
||||
<div><?= __('phone') ?>: <?= $order['branch_phone'] ?></div>
|
||||
<?php if (!empty($company['ctr_no'])): ?>
|
||||
<div><?= __('ctr_no') ?>: <?= $company['ctr_no'] ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($company['vat_no'])): ?>
|
||||
<div><?= __('vat_no') ?>: <?= $company['vat_no'] ?></div>
|
||||
<?php elseif (!empty($company['vat_number'])): ?>
|
||||
<div><?= __('vat_no') ?>: <?= $company['vat_number'] ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<div class="info-row">
|
||||
<span class="fw-bold"><?= __('order') ?> #</span>
|
||||
<span><?= $order['order_number'] ?></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="fw-bold"><?= __('date') ?>:</span>
|
||||
<span><?= date('d/m/Y H:i', strtotime($order['created_at'])) ?></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="fw-bold"><?= __('customer') ?>:</span>
|
||||
<span><?= is_arabic() ? ($order['customer_name_ar'] ?: $order['customer_name_en']) : $order['customer_name_en'] ?></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="fw-bold"><?= __('phone') ?>:</span>
|
||||
<span><?= $order['customer_phone'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= __('item') ?></th>
|
||||
<th class="text-center"><?= __('qty') ?? 'Qty' ?></th>
|
||||
<th class="text-end"><?= __('total') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($order_items as $item): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-bold"><?= is_arabic() ? ($item['item_ar'] ?: $item['item_en']) : $item['item_en'] ?></div>
|
||||
<div style="font-size: 10px; color: #444;"><?= is_arabic() ? ($item['service_ar'] ?: $item['service_en']) : $item['service_en'] ?></div>
|
||||
</td>
|
||||
<td class="text-center"><?= $item['quantity'] ?></td>
|
||||
<td class="text-end"><?= format_amount($item['subtotal'] + ($item['vat_amount'] * $item['quantity'])) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="totals">
|
||||
<div class="total-row">
|
||||
<span><?= __('subtotal') ?></span>
|
||||
<span><?= format_amount($order['total_price'] - $order['vat_total']) ?></span>
|
||||
</div>
|
||||
<div class="total-row">
|
||||
<span><?= __('vat') ?></span>
|
||||
<span><?= format_amount($order['vat_total']) ?></span>
|
||||
</div>
|
||||
<div class="total-row fw-bold" style="font-size: 14px; border-top: 1px solid #000; margin-top: 1mm; padding-top: 1mm;">
|
||||
<span><?= __('total') ?></span>
|
||||
<span><?= format_amount($order['total_price']) ?></span>
|
||||
</div>
|
||||
|
||||
<div class="total-row mt-2">
|
||||
<span><?= __('paid') ?></span>
|
||||
<span><?= format_amount($total_paid) ?></span>
|
||||
</div>
|
||||
<div class="total-row">
|
||||
<span><?= __('remaining_amount') ?></span>
|
||||
<span><?= format_amount(max(0, $remaining)) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center footer">
|
||||
<div class="mb-1 fw-bold"><?= is_arabic() ? 'شكراً لزيارتكم!' : 'Thank you for your visit!' ?></div>
|
||||
<div><?= is_arabic() ? 'يرجى الاحتفاظ بالإيصال' : 'Please keep your receipt' ?></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Uncomment for automatic print dialog
|
||||
// window.print();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user