Autosave: 20260302-132419

This commit is contained in:
Flatlogic Bot 2026-03-02 13:24:19 +00:00
parent 62b332799d
commit 268b4cc1cf
21 changed files with 1542 additions and 81 deletions

View File

@ -51,7 +51,7 @@ $recent_orders = $stmt->fetchAll();
</div> </div>
<div> <div>
<div class="text-muted small"><?= __('today_revenue') ?? 'Today Revenue' ?></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> </div>
</div> </div>
@ -118,7 +118,7 @@ $recent_orders = $stmt->fetchAll();
<tr> <tr>
<td><?= $order['id'] ?></td> <td><?= $order['id'] ?></td>
<td><?= $lang === 'ar' ? ($order['customer_name_ar'] ?: $order['customer_name_en']) : $order['customer_name_en'] ?></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-<?= getStatusColor($order['status']) ?>"><?= __($order['status']) ?></span></td>
<td><span class="badge bg-<?= getPaymentStatusColor($order['payment_status']) ?>"><?= __($order['payment_status']) ?></span></td> <td><span class="badge bg-<?= getPaymentStatusColor($order['payment_status']) ?>"><?= __($order['payment_status']) ?></span></td>
<td> <td>
@ -167,4 +167,3 @@ function getPaymentStatusColor($status) {
][$status] ?? 'info'; ][$status] ?? 'info';
} }
require_once __DIR__ . '/includes/footer.php'; require_once __DIR__ . '/includes/footer.php';
?>

View File

@ -9,6 +9,7 @@ if (!isset($_SESSION['user_id'])) {
} }
$input = json_decode(file_get_contents('php://input'), true); $input = json_decode(file_get_contents('php://input'), true);
$order_id = $input['order_id'] ?? null;
$customer_id = $input['customer_id'] ?: null; $customer_id = $input['customer_id'] ?: null;
$items = $input['items'] ?? []; $items = $input['items'] ?? [];
$vat_total = (float)($input['vat_total'] ?? 0); $vat_total = (float)($input['vat_total'] ?? 0);
@ -25,15 +26,35 @@ try {
$pdo = db(); $pdo = db();
$pdo->beginTransaction(); $pdo->beginTransaction();
// Recalculate total if not provided correctly, but for now we trust the client-side breakdown if ($order_id) {
// If we wanted to be more secure, we'd fetch prices from DB here. // 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); // Get branch prefix
$stmt_prefix = $pdo->prepare("SELECT prefix FROM branches WHERE id = ?");
$stmt = $pdo->prepare("INSERT INTO orders (branch_id, customer_id, user_id, order_number, total_price, vat_total, status, payment_status) $stmt_prefix->execute([$branch_id]);
VALUES (?, ?, ?, ?, ?, ?, 'received', 'unpaid')"); $prefix = $stmt_prefix->fetchColumn() ?: 'ORD';
$stmt->execute([$branch_id, $customer_id, $user_id, $order_number, $total_price, $vat_total]); if (strlen($prefix) > 3) $prefix = substr($prefix, 0, 3);
$order_id = $pdo->lastInsertId(); $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) $stmt_item = $pdo->prepare("INSERT INTO order_items (order_id, item_id, variant_id, service_id, quantity, unit_price, vat_amount, subtotal)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); VALUES (?, ?, ?, ?, ?, ?, ?, ?)");

31
api/delete_order.php Normal file
View 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
View 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()]);
}

View 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()]);
}

View File

@ -20,11 +20,33 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$name_ar = $_POST['name_ar']; $name_ar = $_POST['name_ar'];
$company_id = $_POST['company_id']; $company_id = $_POST['company_id'];
$phone = $_POST['phone']; $phone = $_POST['phone'];
$stmt = db()->prepare("INSERT INTO branches (name_en, name_ar, company_id, phone) VALUES (?, ?, ?, ?)"); $prefix = strtoupper(substr($_POST['prefix'] ?? '', 0, 3));
$stmt->execute([$name_en, $name_ar, $company_id, $phone]); $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'); header('Location: branches.php');
exit; 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 // NOW Include header
@ -57,6 +79,11 @@ $companies = db()->query("SELECT * FROM companies")->fetchAll();
<label class="form-label small fw-bold"><?= __('name_ar') ?></label> <label class="form-label small fw-bold"><?= __('name_ar') ?></label>
<input type="text" name="name_ar" class="form-control" style="border-radius: 12px;"> <input type="text" name="name_ar" class="form-control" style="border-radius: 12px;">
</div> </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"> <div class="mb-3">
<label class="form-label small fw-bold"><?= __('phone') ?></label> <label class="form-label small fw-bold"><?= __('phone') ?></label>
<input type="text" name="phone" class="form-control" style="border-radius: 12px;"> <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"> <tr class="py-3">
<th class="ps-4 py-3">#</th> <th class="ps-4 py-3">#</th>
<th class="py-3"><?= __('name') ?></th> <th class="py-3"><?= __('name') ?></th>
<th class="py-3"><?= __('initial_letters') ?></th>
<th class="py-3"><?= __('company') ?></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> </tr>
</thead> </thead>
<tbody> <tbody>
@ -82,8 +111,26 @@ $companies = db()->query("SELECT * FROM companies")->fetchAll();
<tr> <tr>
<td class="ps-4"><?= $b['id'] ?></td> <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="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><?= $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> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
@ -93,4 +140,66 @@ $companies = db()->query("SELECT * FROM companies")->fetchAll();
</div> </div>
</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'; ?> <?php require_once __DIR__ . '/includes/footer.php'; ?>

138
company_profile.php Normal file
View 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'; ?>

View 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;

View 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;

View 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;

View File

@ -0,0 +1,2 @@
-- Migration 09: Add prefix column to branches table
ALTER TABLE branches ADD COLUMN prefix VARCHAR(3) DEFAULT NULL;

View File

@ -8,18 +8,34 @@ if (!isset($_SESSION['user_id']) && basename($_SERVER['PHP_SELF']) !== 'login.ph
exit; exit;
} }
$current_user = $_SESSION['user_id'] ?? null; $current_user_id = $_SESSION['user_id'] ?? null;
$current_branch = $_SESSION['branch_id'] ?? null; $current_branch = $_SESSION['branch_id'] ?? null;
$current_role = $_SESSION['role'] ?? 'cashier'; $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> <!doctype html>
<html lang="<?= $lang ?>" dir="<?= is_rtl() ? 'rtl' : 'ltr' ?>"> <html lang="<?= $lang ?>" dir="<?= is_rtl() ? 'rtl' : 'ltr' ?>">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <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@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="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(); ?>"> <link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
@ -69,6 +85,16 @@ $current_role = $_SESSION['role'] ?? 'cashier';
.lang-switch { .lang-switch {
font-size: 0.9rem; 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> </style>
</head> </head>
<body> <body>
@ -78,8 +104,12 @@ $current_role = $_SESSION['role'] ?? 'cashier';
<!-- Sidebar --> <!-- Sidebar -->
<nav class="col-md-3 col-lg-2 d-md-block sidebar collapse"> <nav class="col-md-3 col-lg-2 d-md-block sidebar collapse">
<div class="position-sticky"> <div class="position-sticky">
<div class="px-4 mb-4 mt-2"> <div class="px-4 mb-4 mt-2 text-center">
<h5 class="fw-bold">Laundry Admin</h5> <?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> </div>
<ul class="nav flex-column px-3"> <ul class="nav flex-column px-3">
<li class="nav-item"> <li class="nav-item">
@ -100,6 +130,12 @@ $current_role = $_SESSION['role'] ?? 'cashier';
<?= __('orders') ?> <?= __('orders') ?>
</a> </a>
</li> </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"> <li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'customers.php' ? 'active' : '' ?>" href="customers.php"> <a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'customers.php' ? 'active' : '' ?>" href="customers.php">
<i class="bi bi-people"></i> <i class="bi bi-people"></i>
@ -126,6 +162,24 @@ $current_role = $_SESSION['role'] ?? 'cashier';
</a> </a>
</li> </li>
<?php endif; ?> <?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> </ul>
<hr class="mx-3 my-4"> <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"> <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> <h1 class="h2"><?= __($title ?? 'dashboard') ?></h1>
<div class="btn-toolbar mb-2 mb-md-0"> <div class="btn-toolbar mb-2 mb-md-0">
<div class="me-2"> <div class="d-flex align-items-center">
<span class="badge bg-primary px-3 py-2"><?= $_SESSION['branch_name'] ?? '' ?></span> <span class="badge bg-primary px-3 py-2 me-2"><?= $_SESSION['branch_name'] ?? '' ?></span>
<span class="badge bg-secondary px-3 py-2 ms-2"><?= $_SESSION['full_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>
</div> </div>
</header> </header>

View File

@ -22,6 +22,8 @@ $translations = [
'login' => 'Login', 'login' => 'Login',
'username' => 'Username', 'username' => 'Username',
'password' => 'Password', 'password' => 'Password',
'confirm_password' => 'Confirm Password',
'change_password' => 'Change Password',
'submit' => 'Submit', 'submit' => 'Submit',
'save' => 'Save', 'save' => 'Save',
'save_changes' => 'Save Changes', 'save_changes' => 'Save Changes',
@ -94,6 +96,48 @@ $translations = [
'no_items_found' => 'No items found', 'no_items_found' => 'No items found',
'pricing_management' => 'Pricing Management', 'pricing_management' => 'Pricing Management',
'are_you_sure' => 'Are you sure?', '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' => [ 'ar' => [
'dashboard' => 'لوحة القيادة', 'dashboard' => 'لوحة القيادة',
@ -109,6 +153,8 @@ $translations = [
'login' => 'تسجيل الدخول', 'login' => 'تسجيل الدخول',
'username' => 'اسم المستخدم', 'username' => 'اسم المستخدم',
'password' => 'كلمة المرور', 'password' => 'كلمة المرور',
'confirm_password' => 'تأكيد كلمة المرور',
'change_password' => 'تغيير كلمة المرور',
'submit' => 'إرسال', 'submit' => 'إرسال',
'save' => 'حفظ', 'save' => 'حفظ',
'save_changes' => 'حفظ التغييرات', 'save_changes' => 'حفظ التغييرات',
@ -181,6 +227,48 @@ $translations = [
'no_items_found' => 'لا يوجد أصناف', 'no_items_found' => 'لا يوجد أصناف',
'pricing_management' => 'إدارة الأسعار', 'pricing_management' => 'إدارة الأسعار',
'are_you_sure' => 'هل أنت متأكد؟', '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() { function is_arabic() {
global $lang; global $lang;
return $lang === 'ar'; return $lang === 'ar';
}
function currency() {
return __('currency');
}
function decimals() {
return 3;
}
function format_amount($amount) {
return number_format((float)$amount, decimals()) . ' ' . currency();
} }

View File

@ -513,7 +513,7 @@ foreach ($prices_raw as $p) {
<thead class="bg-light"> <thead class="bg-light">
<tr class="small text-muted"> <tr class="small text-muted">
<th style="min-width: 200px;"><?= __('service') ?></th> <th style="min-width: 200px;"><?= __('service') ?></th>
<th class="text-center"><?= __('price') ?> (SAR)</th> <th class="text-center"><?= __('price') ?> (<?= currency() ?>)</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -524,8 +524,8 @@ foreach ($prices_raw as $p) {
</td> </td>
<td class="text-center"> <td class="text-center">
<div class="input-group input-group-sm justify-content-center mx-auto" style="width: 150px; position: relative;"> <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" <input type="number" step="0.001" class="form-control text-center price-input"
value="<?= $prices[$item['id']][$service['id']] ?? 0.00 ?>" value="<?= number_format($prices[$item['id']][$service['id']] ?? 0, decimals(), '.', '') ?>"
data-item-id="<?= $item['id'] ?>" data-item-id="<?= $item['id'] ?>"
data-service-id="<?= $service['id'] ?>" data-service-id="<?= $service['id'] ?>"
style="border-radius: 8px;"> style="border-radius: 8px;">
@ -698,4 +698,4 @@ document.getElementById('itemImageFile').addEventListener('change', function(e)
}); });
</script> </script>
<?php require_once __DIR__ . '/includes/footer.php'; ?> <?php require_once __DIR__ . '/includes/footer.php'; ?>

275
lab.php Normal file
View 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';
?>

View File

@ -2,6 +2,10 @@
require_once __DIR__ . '/db/config.php'; require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/lang.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'])) { if (isset($_SESSION['user_id'])) {
header('Location: admin.php'); header('Location: admin.php');
exit; exit;
@ -39,8 +43,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <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="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> <style>
body { body {
background-color: #f8f9fa; background-color: #f8f9fa;
@ -48,39 +61,56 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-height: 100vh; min-height: 100vh;
font-family: 'Inter', sans-serif; font-family: 'Inter', <?= is_rtl() ? "'Cairo'," : '' ?> sans-serif;
} }
.login-card { .login-card {
width: 100%; width: 100%;
max-width: 400px; max-width: 400px;
padding: 2rem; padding: 2.5rem;
border-radius: 20px; border-radius: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.05); box-shadow: 0 10px 30px rgba(0,0,0,0.05);
background: #fff; background: #fff;
} }
.login-logo {
max-height: 80px;
width: auto;
margin-bottom: 1.5rem;
}
</style> </style>
</head> </head>
<body> <body>
<div class="login-card"> <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): ?> <?php if ($error): ?>
<div class="alert alert-danger"><?= $error ?></div> <div class="alert alert-danger py-2"><?= $error ?></div>
<?php endif; ?> <?php endif; ?>
<form method="POST"> <form method="POST">
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><?= __('username') ?></label> <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>
<div class="mb-4"> <div class="mb-4">
<label class="form-label"><?= __('password') ?></label> <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> </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> </form>
<div class="text-center mt-4"> <div class="text-center mt-3 pt-3 border-top">
<a href="?lang=en">English</a> | <a href="?lang=ar">العربية</a> <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>
</div> </div>
</body> </body>
</html> </html>

View File

@ -99,24 +99,24 @@ $payments = $stmt->fetchAll();
</td> </td>
<td><?= $lang === 'ar' ? ($item['service_ar'] ?: $item['service_en']) : $item['service_en'] ?></td> <td><?= $lang === 'ar' ? ($item['service_ar'] ?: $item['service_en']) : $item['service_en'] ?></td>
<td><?= $item['quantity'] ?></td> <td><?= $item['quantity'] ?></td>
<td class="text-end"><?= number_format($item['unit_price'], 2) ?> SAR</td> <td class="text-end"><?= format_amount($item['unit_price']) ?></td>
<td class="text-end"><?= number_format($item['vat_amount'] * $item['quantity'], 2) ?> SAR</td> <td class="text-end"><?= format_amount($item['vat_amount'] * $item['quantity']) ?></td>
<td class="text-end fw-bold"><?= number_format($item['subtotal'] + ($item['vat_amount'] * $item['quantity']), 2) ?> SAR</td> <td class="text-end fw-bold"><?= format_amount($item['subtotal'] + ($item['vat_amount'] * $item['quantity'])) ?></td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<th colspan="5" class="text-end text-muted small"><?= __('subtotal') ?></th> <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>
<tr> <tr>
<th colspan="5" class="text-end text-muted small"><?= __('vat_total') ?? 'VAT Total' ?></th> <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>
<tr> <tr>
<th colspan="5" class="text-end fs-5 fw-bold"><?= __('total') ?></th> <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> </tr>
</tfoot> </tfoot>
</table> </table>
@ -153,7 +153,7 @@ $payments = $stmt->fetchAll();
<tr> <tr>
<td><?= date('d M Y, h:i A', strtotime($p['created_at'])) ?></td> <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><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> </tr>
<?php endforeach; ?> <?php endforeach; ?>
<?php if (empty($payments)): ?> <?php if (empty($payments)): ?>
@ -194,7 +194,7 @@ $payments = $stmt->fetchAll();
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="d-flex justify-content-between align-items-center mb-3">
<span class="text-muted fw-bold small"><?= __('remaining_amount') ?></span> <span class="text-muted fw-bold small"><?= __('remaining_amount') ?></span>
<span class="fs-5 fw-bold <?= $remaining <= 0 ? 'text-success' : 'text-danger' ?>"> <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> </span>
</div> </div>
@ -203,7 +203,7 @@ $payments = $stmt->fetchAll();
<input type="hidden" name="action" value="add_payment"> <input type="hidden" name="action" value="add_payment">
<div class="mb-3"> <div class="mb-3">
<label class="form-label small fw-bold text-muted"><?= __('amount') ?></label> <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>
<div class="mb-3"> <div class="mb-3">
<label class="form-label small fw-bold text-muted"><?= __('payment_method') ?></label> <label class="form-label small fw-bold text-muted"><?= __('payment_method') ?></label>
@ -217,9 +217,18 @@ $payments = $stmt->fetchAll();
</form> </form>
<?php endif; ?> <?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()"> <div class="row mt-4">
<i class="bi bi-printer me-2"></i> <?= __('print_invoice') ?> <div class="col-6">
</button> <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> </div>
</div> </div>

View File

@ -65,7 +65,7 @@ $orders = $stmt->fetchAll();
<th><?= __('status') ?></th> <th><?= __('status') ?></th>
<th><?= __('payment_status') ?></th> <th><?= __('payment_status') ?></th>
<th><?= __('date') ?></th> <th><?= __('date') ?></th>
<th><?= __('actions') ?></th> <th class="text-end"><?= __('actions') ?></th>
</tr> </tr>
</thead> </thead>
<tbody> <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="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> <div class="small text-muted"><?= $order['customer_phone'] ?></div>
</td> </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-<?= 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><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 class="small"><?= date('d/m/Y H:i', strtotime($order['created_at'])) ?></td>
<td> <td class="text-end">
<a href="order_details.php?id=<?= $order['id'] ?>" class="btn btn-sm btn-light border-0 p-2" style="border-radius: 8px;"> <div class="d-flex gap-1 justify-content-end">
<i class="bi bi-eye-fill"></i> <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;">
</a> <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> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
@ -101,6 +112,120 @@ $orders = $stmt->fetchAll();
</div> </div>
</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 <?php
function getStatusColor($status) { function getStatusColor($status) {
return [ return [
@ -119,4 +244,4 @@ function getPaymentStatusColor($status) {
][$status] ?? 'info'; ][$status] ?? 'info';
} }
require_once __DIR__ . '/includes/footer.php'; require_once __DIR__ . '/includes/footer.php';
?> ?>

101
pos.php
View File

@ -4,6 +4,38 @@ require_once __DIR__ . '/includes/header.php';
$branch_id = $_SESSION['branch_id'] ?? 1; $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 // Get all categories
$categories = db()->query("SELECT * FROM categories ORDER BY name_en ASC")->fetchAll(); $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]); $stmt->execute([$branch_id]);
$customers = $stmt->fetchAll(); $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"> <div class="container-fluid py-4">
@ -164,7 +196,7 @@ $pageTitle = $lang == 'en' ? 'Point of Sale' : 'نقطة البيع';
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
</div> </div>
<input type="hidden" id="customerId" value=""> <input type="hidden" id="customerId" value="<?= $edit_order ? $edit_order['customer_id'] : '' ?>">
</div> </div>
<button class="btn btn-primary rounded-3" data-bs-toggle="modal" data-bs-target="#addCustomerModal"> <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="bg-light rounded-4 p-3 mb-3">
<div class="d-flex justify-content-between mb-2 small text-muted"> <div class="d-flex justify-content-between mb-2 small text-muted">
<span><?= $lang == 'en' ? 'Subtotal' : 'المجموع الفرعي' ?></span> <span><?= $lang == 'en' ? 'Subtotal' : 'المجموع الفرعي' ?></span>
<span id="cartSubtotal">0.00 SAR</span> <span id="cartSubtotal">0.000 <?= currency() ?></span>
</div> </div>
<div class="d-flex justify-content-between mb-2 small text-muted"> <div class="d-flex justify-content-between mb-2 small text-muted">
<span><?= $lang == 'en' ? 'VAT' : 'الضريبة' ?></span> <span><?= $lang == 'en' ? 'VAT' : 'الضريبة' ?></span>
<span id="cartVat">0.00 SAR</span> <span id="cartVat">0.000 <?= currency() ?></span>
</div> </div>
<div class="d-flex justify-content-between pt-2 border-top border-secondary border-opacity-10 mt-2"> <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 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>
</div> </div>
<button class="btn btn-primary w-100 py-3 rounded-4 fw-bold shadow-sm" onclick="checkout()"> <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> </button>
</div> </div>
</div> </div>
@ -259,16 +291,22 @@ $pageTitle = $lang == 'en' ? 'Point of Sale' : 'نقطة البيع';
<script> <script>
const itemsData = <?= json_encode((object)$items) ?>; const itemsData = <?= json_encode((object)$items) ?>;
const lang = '<?= $lang ?>'; 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; let selectionModal;
try { // If not editing, try to load from local storage
const saved = localStorage.getItem('pos_cart'); if (!editOrderId) {
if (saved) { try {
const parsed = JSON.parse(saved); const saved = localStorage.getItem('pos_cart');
if (Array.isArray(parsed)) cart = parsed; if (saved) {
} const parsed = JSON.parse(saved);
} catch (e) { console.error('Cart parse error', e); } if (Array.isArray(parsed) && parsed.length > 0) cart = parsed;
}
} catch (e) { console.error('Cart parse error', e); }
}
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
if (typeof bootstrap !== 'undefined') { if (typeof bootstrap !== 'undefined') {
@ -312,6 +350,16 @@ document.addEventListener('DOMContentLoaded', () => {
const clearCustBtn = document.getElementById('clearCustomerBtn'); const clearCustBtn = document.getElementById('clearCustomerBtn');
const custIdInput = document.getElementById('customerId'); 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) { if (custSearchInput) {
custSearchInput.addEventListener('focus', () => { custSearchInput.addEventListener('focus', () => {
custResults.classList.remove('d-none'); custResults.classList.remove('d-none');
@ -385,7 +433,7 @@ function showOptions(itemId) {
col.innerHTML = ` 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})"> <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="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> </button>
`; `;
list.appendChild(col); list.appendChild(col);
@ -426,7 +474,9 @@ function changeQty(index, delta) {
} }
function updateCart() { function updateCart() {
localStorage.setItem('pos_cart', JSON.stringify(cart)); if (!editOrderId) {
localStorage.setItem('pos_cart', JSON.stringify(cart));
}
const cartList = document.getElementById('cartItems'); const cartList = document.getElementById('cartItems');
const emptyCart = document.getElementById('emptyCart'); const emptyCart = document.getElementById('emptyCart');
if (!cartList || !emptyCart) return; if (!cartList || !emptyCart) return;
@ -441,7 +491,7 @@ function updateCart() {
<div class="flex-grow-1"> <div class="flex-grow-1">
<div class="fw-bold small text-dark">${item.name}</div> <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="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>
<div class="d-flex align-items-center bg-white rounded-3 p-1"> <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> <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 vatEl = document.getElementById('cartVat');
const totalEl = document.getElementById('cartTotal'); const totalEl = document.getElementById('cartTotal');
if (subtotalEl) subtotalEl.innerText = subtotal.toFixed(2) + ' SAR'; if (subtotalEl) subtotalEl.innerText = subtotal.toFixed(decimalPrecision) + ' ' + currencyLabel;
if (vatEl) vatEl.innerText = totalVat.toFixed(2) + ' SAR'; if (vatEl) vatEl.innerText = totalVat.toFixed(decimalPrecision) + ' ' + currencyLabel;
if (totalEl) totalEl.innerText = (subtotal + totalVat).toFixed(2) + ' SAR'; if (totalEl) totalEl.innerText = (subtotal + totalVat).toFixed(decimalPrecision) + ' ' + currencyLabel;
} }
function clearCart() { function clearCart() {
@ -507,6 +557,7 @@ async function checkout() {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
order_id: editOrderId,
customer_id: cid, customer_id: cid,
items: itemsToSubmit, items: itemsToSubmit,
vat_total: totalVat, vat_total: totalVat,
@ -515,9 +566,11 @@ async function checkout() {
}); });
const res = await response.json(); const res = await response.json();
if (res.success) { if (res.success) {
cart = []; if (!editOrderId) {
updateCart(); cart = [];
window.location.href = 'order_details.php?id=' + res.order_id; updateCart();
}
window.location.href = 'order_details.php?id=' + (res.order_id || editOrderId);
} else alert(res.error); } else alert(res.error);
} catch (e) { alert('Error'); } } catch (e) { alert('Error'); }
} }
@ -581,4 +634,4 @@ async function saveCustomer() {
.customer-result-item:hover { background-color: #f8f9fa; } .customer-result-item:hover { background-color: #f8f9fa; }
</style> </style>
<?php require_once __DIR__ . '/includes/footer.php'; ?> <?php require_once __DIR__ . '/includes/footer.php'; ?>

132
profile.php Normal file
View 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
View 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>