Autosave: 20260303-131517
This commit is contained in:
parent
538704f9d0
commit
e135bdd9e0
153
api/checkout.php
153
api/checkout.php
@ -16,36 +16,26 @@ $items = $input['items'] ?? [];
|
||||
$vat_total = (float)($input['vat_total'] ?? 0);
|
||||
$total_price = (float)($input['total_price'] ?? 0);
|
||||
$payment_method = $input['payment_method'] ?? null;
|
||||
$points_to_redeem = (float)($input['points_to_redeem'] ?? 0);
|
||||
$branch_id = $_SESSION['branch_id'];
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
// Check if branch_id is 'all' (happens when super_admin views all branches)
|
||||
// Check if branch_id is 'all'
|
||||
if ($branch_id === 'all') {
|
||||
// We need a specific branch_id to save an order.
|
||||
// If we're in "all" mode, we'll try to get the user's primary branch or just the first available branch.
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt_u = $pdo->prepare("SELECT branch_id FROM users WHERE id = ?");
|
||||
$stmt_u->execute([$user_id]);
|
||||
$u_branch = $stmt_u->fetchColumn();
|
||||
|
||||
if ($u_branch && $u_branch != 'all') {
|
||||
$branch_id = $u_branch;
|
||||
} else {
|
||||
// Fallback to first branch in user_branches
|
||||
$stmt_ub = $pdo->prepare("SELECT branch_id FROM user_branches WHERE user_id = ? LIMIT 1");
|
||||
$stmt_ub->execute([$user_id]);
|
||||
$ub_branch = $stmt_ub->fetchColumn();
|
||||
if ($ub_branch) {
|
||||
$branch_id = $ub_branch;
|
||||
} else {
|
||||
// Last resort: first branch in system
|
||||
$branch_id = $pdo->query("SELECT id FROM branches LIMIT 1")->fetchColumn();
|
||||
}
|
||||
$branch_id = $ub_branch ?: $pdo->query("SELECT id FROM branches LIMIT 1")->fetchColumn();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Silently continue, might fail at insert
|
||||
}
|
||||
} catch (Exception $e) {}
|
||||
}
|
||||
|
||||
if (empty($items)) {
|
||||
@ -60,72 +50,92 @@ try {
|
||||
$payment_status = ($payment_method && $payment_method !== 'pay_later') ? 'paid' : 'unpaid';
|
||||
|
||||
if ($order_id) {
|
||||
// Update existing order
|
||||
$stmt = $pdo->prepare("UPDATE orders SET customer_id = ?, total_price = ?, vat_total = ?, payment_status = ? WHERE id = ? AND branch_id = ?");
|
||||
$stmt->execute([$customer_id, $total_price, $vat_total, $payment_status, $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', ?)");
|
||||
$stmt->execute([$branch_id, $customer_id, $user_id, $total_price, $vat_total, $payment_status]);
|
||||
$order_id = $pdo->lastInsertId();
|
||||
|
||||
// Get branch prefix
|
||||
// Order Number Generation
|
||||
$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 = strtoupper(str_pad($prefix, 3, 'X'));
|
||||
$prefix = strtoupper(str_pad(substr($stmt_prefix->fetchColumn() ?: 'ORD', 0, 3), 3, 'X'));
|
||||
|
||||
// Determine the next serial number for this branch
|
||||
$stmt_max = $pdo->prepare("SELECT order_number FROM orders WHERE branch_id = ? AND order_number LIKE ? ORDER BY id DESC LIMIT 100");
|
||||
$stmt_max->execute([$branch_id, "$prefix-%"]);
|
||||
$existing_orders = $stmt_max->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
$existing = $stmt_max->fetchAll(PDO::FETCH_COLUMN);
|
||||
$max_serial = 0;
|
||||
foreach ($existing_orders as $onum) {
|
||||
foreach ($existing as $onum) {
|
||||
if (preg_match('/' . preg_quote($prefix) . '-(\d{6})/', $onum, $matches)) {
|
||||
$serial = (int)$matches[1];
|
||||
if ($serial > $max_serial) $max_serial = $serial;
|
||||
if ((int)$matches[1] > $max_serial) $max_serial = (int)$matches[1];
|
||||
}
|
||||
}
|
||||
$next_serial = $max_serial + 1;
|
||||
|
||||
// Format order_number as XXX-###### (6 digits)
|
||||
$order_number = $prefix . '-' . str_pad($next_serial, 6, '0', STR_PAD_LEFT);
|
||||
|
||||
// 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]);
|
||||
$order_number = $prefix . '-' . str_pad($max_serial + 1, 6, '0', STR_PAD_LEFT);
|
||||
$pdo->prepare("UPDATE orders SET order_number = ? WHERE id = ?")->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 (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt_item = $pdo->prepare("INSERT INTO order_items (order_id, item_id, variant_id, service_id, quantity, unit_price, vat_amount, subtotal) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
foreach ($items as $item) {
|
||||
$subtotal = ($item['price'] * $item['quantity']);
|
||||
$stmt_item->execute([
|
||||
$order_id,
|
||||
$item['itemId'],
|
||||
$item['variantId'] ?: null,
|
||||
$item['serviceId'],
|
||||
$item['quantity'],
|
||||
$item['price'],
|
||||
$item['vatAmount'] ?: 0,
|
||||
$subtotal
|
||||
]);
|
||||
$stmt_item->execute([$order_id, $item['itemId'], $item['variantId'] ?: null, $item['serviceId'], $item['quantity'], $item['price'], $item['vatAmount'] ?: 0, ($item['price'] * $item['quantity'])]);
|
||||
}
|
||||
|
||||
// Handle Payment recording
|
||||
if ($payment_method && $payment_method !== 'pay_later') {
|
||||
$stmt_payment = $pdo->prepare("INSERT INTO payments (order_id, amount, payment_method) VALUES (?, ?, ?)");
|
||||
$stmt_payment->execute([$order_id, $total_price, $payment_method]);
|
||||
// Loyalty Redemption Logic
|
||||
$redeem_amount = 0;
|
||||
if ($customer_id && $points_to_redeem > 0 && get_setting('loyalty_enabled') === '1') {
|
||||
$stmt_c = $pdo->prepare("SELECT loyalty_points FROM customers WHERE id = ? FOR UPDATE");
|
||||
$stmt_c->execute([$customer_id]);
|
||||
$curr_points = (float)$stmt_c->fetchColumn();
|
||||
|
||||
if ($curr_points >= $points_to_redeem) {
|
||||
$point_val = (float)get_setting('loyalty_currency_per_point', 0.05);
|
||||
$redeem_amount = $points_to_redeem * $point_val;
|
||||
|
||||
// Cap redemption at order total
|
||||
if ($redeem_amount > $total_price) {
|
||||
$redeem_amount = $total_price;
|
||||
$points_to_redeem = $redeem_amount / $point_val;
|
||||
}
|
||||
|
||||
// Deduct points
|
||||
$pdo->prepare("UPDATE customers SET loyalty_points = loyalty_points - ? WHERE id = ?")->execute([$points_to_redeem, $customer_id]);
|
||||
|
||||
// Record loyalty transaction
|
||||
$pdo->prepare("INSERT INTO loyalty_transactions (customer_id, order_id, points, type, description) VALUES (?, ?, ?, 'redeemed', ?)")
|
||||
->execute([$customer_id, $order_id, -$points_to_redeem, "Redeemed for order $order_number"]);
|
||||
|
||||
// Record loyalty payment
|
||||
$pdo->prepare("INSERT INTO payments (order_id, amount, payment_method) VALUES (?, ?, 'loyalty')")
|
||||
->execute([$order_id, $redeem_amount]);
|
||||
|
||||
// Save loyalty discount to order
|
||||
$pdo->prepare("UPDATE orders SET loyalty_discount = ? WHERE id = ?")
|
||||
->execute([$redeem_amount, $order_id]);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Remaining Payment
|
||||
$remaining_to_pay = $total_price - $redeem_amount;
|
||||
if ($payment_method && $payment_method !== 'pay_later' && $remaining_to_pay > 0) {
|
||||
$pdo->prepare("INSERT INTO payments (order_id, amount, payment_method) VALUES (?, ?, ?)")
|
||||
->execute([$order_id, $remaining_to_pay, $payment_method]);
|
||||
}
|
||||
|
||||
// Loyalty Earning Logic
|
||||
if ($customer_id && get_setting('loyalty_enabled') === '1' && $payment_status === 'paid') {
|
||||
$pts_per_curr = (float)get_setting('loyalty_points_per_currency', 1);
|
||||
$points_earned = $remaining_to_pay * $pts_per_curr;
|
||||
|
||||
if ($points_earned > 0) {
|
||||
$pdo->prepare("UPDATE customers SET loyalty_points = loyalty_points + ? WHERE id = ?")->execute([$points_earned, $customer_id]);
|
||||
$pdo->prepare("INSERT INTO loyalty_transactions (customer_id, order_id, points, type, description) VALUES (?, ?, ?, 'earned', ?)")
|
||||
->execute([$customer_id, $order_id, $points_earned, "Earned from order $order_number"]);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch customer details for notifications
|
||||
$customer = null;
|
||||
if ($customer_id) {
|
||||
$stmt_cust = $pdo->prepare('SELECT name_ar, phone FROM customers WHERE id = ?');
|
||||
@ -135,10 +145,9 @@ try {
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
// WhatsApp Notifications
|
||||
// WhatsApp
|
||||
try {
|
||||
if (get_setting('whatsapp_enabled') === '1' && $customer && !empty($customer['phone'])) {
|
||||
// 1. Order Created Notification
|
||||
$template = get_setting('msg_order_created_ar');
|
||||
if (!empty($template)) {
|
||||
$details_parts = [];
|
||||
@ -148,39 +157,11 @@ try {
|
||||
$names = $stmt_names->fetch();
|
||||
$details_parts[] = ($names['item_name'] ?? 'صنف') . ' (' . ($names['service_name'] ?? 'خدمة') . ') x' . $item['quantity'];
|
||||
}
|
||||
$order_details = implode(', ', $details_parts);
|
||||
|
||||
// Get order number if not already available
|
||||
if (!isset($order_number)) {
|
||||
$stmt_onum = $pdo->prepare('SELECT order_number FROM orders WHERE id = ?');
|
||||
$stmt_onum->execute([$order_id]);
|
||||
$order_number = $stmt_onum->fetchColumn();
|
||||
}
|
||||
|
||||
$message = str_replace(
|
||||
['{customer_name}', '{order_number}', '{order_details}', '{total_price}'],
|
||||
[$customer['name_ar'], $order_number, $order_details, $total_price],
|
||||
$template
|
||||
);
|
||||
$message = str_replace(['{customer_name}', '{order_number}', '{order_details}', '{total_price}'], [$customer['name_ar'], $order_number, implode(', ', $details_parts), $total_price], $template);
|
||||
send_whatsapp_message($customer['phone'], $message);
|
||||
}
|
||||
|
||||
// 2. Payment Received Notification (if paid during checkout)
|
||||
if ($payment_method && $payment_method !== 'pay_later') {
|
||||
$template_pay = get_setting('msg_payment_ar');
|
||||
if (!empty($template_pay)) {
|
||||
$message_pay = str_replace(
|
||||
['{customer_name}', '{order_number}', '{amount}', '{remaining_balance}'],
|
||||
[$customer['name_ar'], $order_number, $total_price, 0],
|
||||
$template_pay
|
||||
);
|
||||
send_whatsapp_message($customer['phone'], $message_pay);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Silently fail for notifications
|
||||
}
|
||||
} catch (Exception $e) {}
|
||||
|
||||
echo json_encode(['success' => true, 'order_id' => $order_id]);
|
||||
} catch (Exception $e) {
|
||||
|
||||
@ -16,10 +16,10 @@ if (strlen($query) < 2) {
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = db()->prepare("SELECT id, name_en, name_ar, phone FROM customers WHERE name_en LIKE ? OR name_ar LIKE ? OR phone LIKE ? LIMIT 10");
|
||||
$stmt = db()->prepare("SELECT id, name_en, name_ar, phone, loyalty_points FROM customers WHERE name_en LIKE ? OR name_ar LIKE ? OR phone LIKE ? LIMIT 10");
|
||||
$stmt->execute(["%$query%", "%$query%", "%$query%"]);
|
||||
$customers = $stmt->fetchAll();
|
||||
echo json_encode($customers);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([]);
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,6 @@ $title = 'company_profile';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
if ($current_role === 'limited_viewer') { header('Location: admin.php'); exit; }
|
||||
|
||||
// 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';
|
||||
@ -14,7 +13,6 @@ $success = '';
|
||||
$error = '';
|
||||
$is_super = ($current_role === 'super_admin');
|
||||
|
||||
// Get company data
|
||||
$stmt = db()->query("SELECT * FROM companies LIMIT 1");
|
||||
$company = $stmt->fetch();
|
||||
|
||||
@ -26,45 +24,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$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'] ?? '';
|
||||
$ctr_no = $_POST['ctr_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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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']]);
|
||||
$stmt->execute([$name_en, $name_ar, $logo, $favicon, $email, $phone, $address_en, $address_ar, $vat_no, $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();
|
||||
}
|
||||
} catch (Exception $e) { $error = __('error_update') . ' ' . $e->getMessage(); }
|
||||
}
|
||||
|
||||
// Handle WhatsApp Settings Save
|
||||
if ($is_super && isset($_POST['save_whatsapp_settings'])) {
|
||||
set_setting('whatsapp_enabled', $_POST['whatsapp_enabled'] ?? '0');
|
||||
set_setting('wablas_token', $_POST['wablas_token'] ?? '');
|
||||
@ -73,10 +60,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
set_setting('msg_order_created_ar', $_POST['msg_order_created_ar'] ?? '');
|
||||
set_setting('msg_order_ready_ar', $_POST['msg_order_ready_ar'] ?? '');
|
||||
set_setting('msg_payment_ar', $_POST['msg_payment_ar'] ?? '');
|
||||
$success = is_arabic() ? 'تم حفظ إعدادات الواتساب بنجاح' : 'WhatsApp settings saved successfully';
|
||||
$success = __('success_update');
|
||||
}
|
||||
|
||||
if ($is_super && isset($_POST['save_loyalty_settings'])) {
|
||||
set_setting('loyalty_enabled', $_POST['loyalty_enabled'] ?? '0');
|
||||
set_setting('loyalty_points_per_currency', $_POST['loyalty_points_per_currency'] ?? '1');
|
||||
set_setting('loyalty_currency_per_point', $_POST['loyalty_currency_per_point'] ?? '0.05');
|
||||
$success = __('success_update');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
@ -84,122 +77,105 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</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 mb-4 shadow-sm" style="border-radius: 20px;">
|
||||
<h5 class="fw-bold mb-4"><i class="bi bi-building me-2"></i> <?= is_arabic() ? 'معلومات الشركة' : 'Company Information' ?></h5>
|
||||
<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" name="save_company_profile" class="btn btn-primary px-5" style="border-radius: 12px;">
|
||||
<i class="bi bi-save me-2"></i> <?= __('save_changes') ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php if ($is_super): ?>
|
||||
<div class="card p-4 shadow-sm" style="border-radius: 20px;">
|
||||
<h5 class="fw-bold mb-4"><i class="bi bi-whatsapp text-success me-2"></i> <?= __('whatsapp_settings') ?></h5>
|
||||
<form method="POST">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="whatsapp_enabled" name="whatsapp_enabled" value="1" <?= get_setting('whatsapp_enabled') == '1' ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="whatsapp_enabled"><?= __('whatsapp_enabled') ?></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold"><?= __('wablas_token') ?></label>
|
||||
<input type="text" class="form-control" name="wablas_token" value="<?= htmlspecialchars(get_setting('wablas_token', '')) ?>" placeholder="Wablas API Token">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold"><?= __('wablas_server') ?></label>
|
||||
<input type="text" class="form-control" name="wablas_server" value="<?= htmlspecialchars(get_setting('wablas_server', 'https://console.wablas.com')) ?>" placeholder="https://console.wablas.com">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-semibold"><?= __('wablas_security_key') ?></label>
|
||||
<input type="text" class="form-control" name="wablas_security_key" value="<?= htmlspecialchars(get_setting('wablas_security_key', '')) ?>" placeholder="Wablas Security Key">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-semibold"><?= __('msg_order_created_ar') ?></label>
|
||||
<textarea class="form-control mb-2" name="msg_order_created_ar" rows="2"><?= htmlspecialchars(get_setting('msg_order_created_ar', '')) ?></textarea>
|
||||
<small class="text-muted">Tags: {customer_name}, {order_number}, {order_details}, {total_price}</small>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-semibold"><?= __('msg_order_ready_ar') ?></label>
|
||||
<textarea class="form-control mb-2" name="msg_order_ready_ar" rows="2"><?= htmlspecialchars(get_setting('msg_order_ready_ar', '')) ?></textarea>
|
||||
<small class="text-muted">Tags: {customer_name}, {order_number}</small>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-semibold"><?= __('msg_payment_ar') ?></label>
|
||||
<textarea class="form-control mb-2" name="msg_payment_ar" rows="2"><?= htmlspecialchars(get_setting('msg_payment_ar', '')) ?></textarea>
|
||||
<small class="text-muted">Tags: {customer_name}, {order_number}, {amount}, {remaining_balance}</small>
|
||||
</div>
|
||||
<div class="col-12 mt-4">
|
||||
<button type="submit" name="save_whatsapp_settings" class="btn btn-primary px-4 py-2" style="border-radius: 12px;">
|
||||
<i class="bi bi-save me-2"></i> <?= is_arabic() ? 'حفظ' : 'Save' ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="alert alert-success alert-dismissible fade show rounded-4 border-0 shadow-sm" role="alert">
|
||||
<i class="bi bi-check-circle-fill me-2"></i> <?= $success ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show rounded-4 border-0 shadow-sm" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i> <?= $error ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card p-4 mb-4 shadow-sm border-0" style="border-radius: 20px;">
|
||||
<h5 class="fw-bold mb-4"><i class="bi bi-building me-2"></i> <?= is_arabic() ? 'معلومات الشركة' : 'Company Information' ?></h5>
|
||||
<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 rounded-3" 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 rounded-3" 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 rounded-3" 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 rounded-3" 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 rounded-3" 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 rounded-3" 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 rounded-3" 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 rounded-3" 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 rounded-3" 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 rounded-3" rows="3"><?= htmlspecialchars($company['address_ar'] ?? '') ?></textarea></div>
|
||||
</div>
|
||||
<div class="text-end"><button type="submit" name="save_company_profile" class="btn btn-primary px-5 rounded-4 shadow-sm"><i class="bi bi-save me-2"></i> <?= __('save_changes') ?></button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<?php if ($is_super): ?>
|
||||
<div class="col-md-6">
|
||||
<div class="card p-4 shadow-sm border-0 h-100" style="border-radius: 20px;">
|
||||
<h5 class="fw-bold mb-4"><i class="bi bi-whatsapp text-success me-2"></i> <?= __('whatsapp_settings') ?></h5>
|
||||
<form method="POST">
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="whatsapp_enabled" name="whatsapp_enabled" value="1" <?= get_setting('whatsapp_enabled') == '1' ? 'checked' : '' ?>>
|
||||
<label class="form-check-label fw-semibold" for="whatsapp_enabled"><?= __('whatsapp_enabled') ?></label>
|
||||
</div>
|
||||
<div class="mb-3"><label class="form-label small fw-bold"><?= __('wablas_token') ?></label><input type="text" class="form-control rounded-3" name="wablas_token" value="<?= htmlspecialchars(get_setting('wablas_token', '')) ?>"></div>
|
||||
<div class="mb-3"><label class="form-label small fw-bold"><?= __('wablas_server') ?></label><input type="text" class="form-control rounded-3" name="wablas_server" value="<?= htmlspecialchars(get_setting('wablas_server', 'https://console.wablas.com')) ?>"></div>
|
||||
<div class="mb-4"><label class="form-label small fw-bold"><?= __('wablas_security_key') ?></label><input type="text" class="form-control rounded-3" name="wablas_security_key" value="<?= htmlspecialchars(get_setting('wablas_security_key', '')) ?>"></div>
|
||||
<div class="text-end"><button type="submit" name="save_whatsapp_settings" class="btn btn-primary px-4 rounded-3 shadow-sm"><?= __('save') ?></button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card p-4 shadow-sm border-0 h-100" style="border-radius: 20px;">
|
||||
<h5 class="fw-bold mb-4"><i class="bi bi-star-fill text-warning me-2"></i> <?= __('loyalty_settings') ?></h5>
|
||||
<form method="POST">
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="loyalty_enabled" name="loyalty_enabled" value="1" <?= get_setting('loyalty_enabled') == '1' ? 'checked' : '' ?>>
|
||||
<label class="form-check-label fw-semibold" for="loyalty_enabled"><?= __('loyalty_enabled') ?></label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('loyalty_points_per_currency') ?></label>
|
||||
<input type="number" step="0.01" class="form-control rounded-3" name="loyalty_points_per_currency" value="<?= htmlspecialchars(get_setting('loyalty_points_per_currency', '1')) ?>">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label small fw-bold"><?= __('loyalty_currency_per_point') ?></label>
|
||||
<input type="number" step="0.001" class="form-control rounded-3" name="loyalty_currency_per_point" value="<?= htmlspecialchars(get_setting('loyalty_currency_per_point', '0.05')) ?>">
|
||||
</div>
|
||||
<div class="text-end"><button type="submit" name="save_loyalty_settings" class="btn btn-primary px-4 rounded-3 shadow-sm"><?= __('save') ?></button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($is_super): ?>
|
||||
<div class="card p-4 shadow-sm border-0" style="border-radius: 20px;">
|
||||
<h5 class="fw-bold mb-4"><i class="bi bi-chat-dots me-2"></i> <?= is_arabic() ? 'قوالب الرسائل' : 'Message Templates' ?></h5>
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('msg_order_created_ar') ?></label>
|
||||
<textarea class="form-control rounded-3 mb-1" name="msg_order_created_ar" rows="2"><?= htmlspecialchars(get_setting('msg_order_created_ar', '')) ?></textarea>
|
||||
<small class="text-muted">Tags: {customer_name}, {order_number}, {order_details}, {total_price}</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('msg_order_ready_ar') ?></label>
|
||||
<textarea class="form-control rounded-3 mb-1" name="msg_order_ready_ar" rows="2"><?= htmlspecialchars(get_setting('msg_order_ready_ar', '')) ?></textarea>
|
||||
<small class="text-muted">Tags: {customer_name}, {order_number}</small>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label small fw-bold"><?= __('msg_payment_ar') ?></label>
|
||||
<textarea class="form-control rounded-3 mb-1" name="msg_payment_ar" rows="2"><?= htmlspecialchars(get_setting('msg_payment_ar', '')) ?></textarea>
|
||||
<small class="text-muted">Tags: {customer_name}, {order_number}, {amount}, {remaining_balance}</small>
|
||||
</div>
|
||||
<div class="text-end"><button type="submit" name="save_whatsapp_settings" class="btn btn-primary px-4 rounded-3 shadow-sm"><?= __('save') ?></button></div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
|
||||
@ -3,45 +3,24 @@ $title = 'customer_statement';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
$customer_id = $_GET['id'] ?? null;
|
||||
if (!$customer_id) {
|
||||
header('Location: customers.php');
|
||||
exit;
|
||||
}
|
||||
if (!$customer_id) { header('Location: customers.php'); exit; }
|
||||
|
||||
// Fetch customer details
|
||||
$stmt = db()->prepare("SELECT * FROM customers WHERE id = ?");
|
||||
$stmt->execute([$customer_id]);
|
||||
$customer = $stmt->fetch();
|
||||
|
||||
if (!$customer) {
|
||||
header('Location: customers.php');
|
||||
exit;
|
||||
}
|
||||
if (!$customer) { header('Location: customers.php'); exit; }
|
||||
|
||||
$from_date = $_GET['from_date'] ?? '';
|
||||
$to_date = $_GET['to_date'] ?? '';
|
||||
$active_tab = $_GET['tab'] ?? 'financial';
|
||||
|
||||
// Base queries
|
||||
// Financial Logic
|
||||
$orders_sql = "SELECT id, order_number, total_price, created_at FROM orders WHERE customer_id = ?";
|
||||
$payments_sql = "SELECT p.id, p.amount, p.payment_method, p.created_at, o.order_number
|
||||
FROM payments p
|
||||
JOIN orders o ON p.order_id = o.id
|
||||
WHERE o.customer_id = ?";
|
||||
$params = [$customer_id];
|
||||
|
||||
if ($from_date) {
|
||||
$orders_sql .= " AND DATE(created_at) >= ?";
|
||||
$payments_sql .= " AND DATE(p.created_at) >= ?";
|
||||
$params[] = $from_date;
|
||||
}
|
||||
if ($to_date) {
|
||||
$orders_sql .= " AND DATE(created_at) <= ?";
|
||||
$payments_sql .= " AND DATE(p.created_at) <= ?";
|
||||
$params[] = $to_date;
|
||||
}
|
||||
$payments_sql = "SELECT p.id, p.amount, p.payment_method, p.created_at, o.order_number FROM payments p JOIN orders o ON p.order_id = o.id WHERE o.customer_id = ?";
|
||||
if ($from_date) { $orders_sql .= " AND DATE(created_at) >= ?"; $payments_sql .= " AND DATE(p.created_at) >= ?"; }
|
||||
if ($to_date) { $orders_sql .= " AND DATE(created_at) <= ?"; $payments_sql .= " AND DATE(p.created_at) <= ?"; }
|
||||
|
||||
$stmt_orders = db()->prepare($orders_sql);
|
||||
// We need to be careful with params if both filters are set
|
||||
$orders_params = [$customer_id];
|
||||
if ($from_date) $orders_params[] = $from_date;
|
||||
if ($to_date) $orders_params[] = $to_date;
|
||||
@ -55,292 +34,161 @@ if ($to_date) $payments_params[] = $to_date;
|
||||
$stmt_payments->execute($payments_params);
|
||||
$payments = $stmt_payments->fetchAll();
|
||||
|
||||
// Combine and sort
|
||||
$transactions = [];
|
||||
foreach ($orders as $o) {
|
||||
$transactions[] = [
|
||||
'date' => $o['created_at'],
|
||||
'type' => 'order',
|
||||
'ref' => $o['order_number'],
|
||||
'debit' => $o['total_price'],
|
||||
'credit' => 0,
|
||||
'description' => __('order') . ' #' . $o['order_number']
|
||||
];
|
||||
}
|
||||
foreach ($payments as $p) {
|
||||
$transactions[] = [
|
||||
'date' => $p['created_at'],
|
||||
'type' => 'payment',
|
||||
'ref' => $p['order_number'],
|
||||
'debit' => 0,
|
||||
'credit' => $p['amount'],
|
||||
'description' => __('payment') . ' (' . __($p['payment_method']) . ') - ' . __('order') . ' #' . $p['order_number']
|
||||
];
|
||||
foreach ($orders as $o) { $transactions[] = ['date' => $o['created_at'], 'type' => 'order', 'ref' => $o['order_number'], 'debit' => $o['total_price'], 'credit' => 0, 'description' => __('order') . ' #' . $o['order_number']]; }
|
||||
foreach ($payments as $p) { $transactions[] = ['date' => $p['created_at'], 'type' => 'payment', 'ref' => $p['order_number'], 'debit' => 0, 'credit' => $p['amount'], 'description' => __('payment') . ' (' . __($p['payment_method']) . ') - ' . __('order') . ' #' . $p['order_number']]; }
|
||||
usort($transactions, function($a, $b) { return strtotime($a['date']) - strtotime($b['date']); });
|
||||
$total_debit = 0; $total_credit = 0; $balance = 0;
|
||||
foreach ($transactions as &$t) { $total_debit += $t['debit']; $total_credit += $t['credit']; $balance += ($t['debit'] - $t['credit']); $t['running_balance'] = $balance; }
|
||||
|
||||
// Loyalty Logic
|
||||
$loyalty_sql = "SELECT lt.*, o.order_number FROM loyalty_transactions lt LEFT JOIN orders o ON lt.order_id = o.id WHERE lt.customer_id = ?";
|
||||
if ($from_date) $loyalty_sql .= " AND DATE(lt.created_at) >= ?";
|
||||
if ($to_date) $loyalty_sql .= " AND DATE(lt.created_at) <= ?";
|
||||
$loyalty_sql .= " ORDER BY lt.created_at ASC";
|
||||
$stmt_loyalty = db()->prepare($loyalty_sql);
|
||||
$loyalty_params = [$customer_id];
|
||||
if ($from_date) $loyalty_params[] = $from_date;
|
||||
if ($to_date) $loyalty_params[] = $to_date;
|
||||
$stmt_loyalty->execute($loyalty_params);
|
||||
$loyalty_transactions = $stmt_loyalty->fetchAll();
|
||||
|
||||
$loyalty_balance = 0;
|
||||
foreach ($loyalty_transactions as &$lt) {
|
||||
$loyalty_balance += $lt['points'];
|
||||
$lt['running_balance'] = $loyalty_balance;
|
||||
}
|
||||
|
||||
usort($transactions, function($a, $b) {
|
||||
return strtotime($a['date']) - strtotime($b['date']);
|
||||
});
|
||||
|
||||
$total_debit = 0;
|
||||
$total_credit = 0;
|
||||
$balance = 0;
|
||||
foreach ($transactions as &$t) {
|
||||
$total_debit += $t['debit'];
|
||||
$total_credit += $t['credit'];
|
||||
$balance += ($t['debit'] - $t['credit']);
|
||||
$t['running_balance'] = $balance;
|
||||
}
|
||||
|
||||
// Fallback values if database is empty
|
||||
$display_company_name = ($lang === 'ar' ? ($company_info['name_ar'] ?: $company_info['name_en']) : $company_info['name_en']) ?: 'Laundry Brand';
|
||||
$display_address = ($lang === 'ar' ? ($company_info['address_ar'] ?: $company_info['address_en']) : $company_info['address_en']);
|
||||
$display_phone = $company_info['phone'];
|
||||
$display_email = $company_info['email'];
|
||||
$display_vat = $company_info['vat_no'] ?: $company_info['vat_number'];
|
||||
$display_ctr = $company_info['ctr_no'];
|
||||
|
||||
?>
|
||||
|
||||
<div class="d-print-none mb-4">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="customers.php"><?= __('customers') ?></a></li>
|
||||
<li class="breadcrumb-item active"><?= __('customer_statement') ?></li>
|
||||
</ol>
|
||||
<ol class="breadcrumb"><li class="breadcrumb-item"><a href="customers.php"><?= __('customers') ?></a></li><li class="breadcrumb-item active"><?= __('customer_statement') ?></li></ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="card p-4 border-0 shadow-sm mb-4 d-print-none" style="border-radius: 20px;">
|
||||
<div class="card p-4 border-0 shadow-sm mb-4 d-print-none rounded-4">
|
||||
<form action="" method="GET" class="row g-3 align-items-end">
|
||||
<input type="hidden" name="id" value="<?= $customer_id ?>">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold"><?= __('from_date') ?></label>
|
||||
<input type="date" name="from_date" class="form-control" value="<?= $from_date ?>" style="border-radius: 12px;">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold"><?= __('to_date') ?></label>
|
||||
<input type="date" name="to_date" class="form-control" value="<?= $to_date ?>" style="border-radius: 12px;">
|
||||
</div>
|
||||
<input type="hidden" name="tab" value="<?= $active_tab ?>">
|
||||
<div class="col-md-4"><label class="form-label small fw-bold"><?= __('from_date') ?></label><input type="date" name="from_date" class="form-control rounded-3" value="<?= $from_date ?>"></div>
|
||||
<div class="col-md-4"><label class="form-label small fw-bold"><?= __('to_date') ?></label><input type="date" name="to_date" class="form-control rounded-3" value="<?= $to_date ?>"></div>
|
||||
<div class="col-md-4 d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary px-4 w-100" style="border-radius: 12px;">
|
||||
<i class="bi bi-filter me-1"></i> <?= __('filter') ?? 'Filter' ?>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-dark px-4 w-100" style="border-radius: 12px;" onclick="window.print()">
|
||||
<i class="bi bi-printer me-1"></i> <?= __('print') ?>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary px-4 w-100 rounded-3"><i class="bi bi-filter me-1"></i> <?= __('filter') ?></button>
|
||||
<button type="button" class="btn btn-outline-dark px-4 w-100 rounded-3" onclick="window.print()"><i class="bi bi-printer me-1"></i> <?= __('print') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="printableStatement" class="card p-5 border-0 shadow-sm" style="border-radius: 20px;">
|
||||
<!-- Formal Header for Printing -->
|
||||
<!-- Tabs -->
|
||||
<ul class="nav nav-tabs d-print-none mb-4 border-0 gap-2">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link rounded-3 fw-bold <?= $active_tab === 'financial' ? 'active bg-primary text-white border-primary' : 'bg-white text-muted border' ?>" href="?id=<?= $customer_id ?>&tab=financial&from_date=<?= $from_date ?>&to_date=<?= $to_date ?>">
|
||||
<i class="bi bi-cash-stack me-2"></i><?= __('financial_statement') ?? 'Financial Statement' ?>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link rounded-3 fw-bold <?= $active_tab === 'loyalty' ? 'active bg-primary text-white border-primary' : 'bg-white text-muted border' ?>" href="?id=<?= $customer_id ?>&tab=loyalty&from_date=<?= $from_date ?>&to_date=<?= $to_date ?>">
|
||||
<i class="bi bi-star-fill me-2"></i><?= __('loyalty_statement') ?? 'Loyalty Statement' ?>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div id="printableStatement" class="card p-4 p-md-5 border-0 shadow-sm rounded-4">
|
||||
<!-- Formal Header -->
|
||||
<div class="d-none d-print-block mb-5">
|
||||
<div class="row align-items-center mb-4">
|
||||
<div class="col-7">
|
||||
<?php if (!empty($company_info['logo'])): ?>
|
||||
<img src="<?= $company_info['logo'] ?>" alt="Logo" style="max-height: 100px;" class="mb-3">
|
||||
<?php else: ?>
|
||||
<div class="mb-3 d-flex align-items-center text-primary">
|
||||
<i class="bi bi-tsunami fs-1 me-2"></i>
|
||||
<span class="fs-4 fw-bold">Laundry Management</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($company_info['logo'])): ?><img src="<?= $company_info['logo'] ?>" alt="Logo" style="max-height: 100px;" class="mb-3"><?php endif; ?>
|
||||
<h2 class="fw-bold mb-1"><?= htmlspecialchars($display_company_name) ?></h2>
|
||||
<div class="mb-0 text-muted small">
|
||||
<?php if ($display_address): ?>
|
||||
<?= htmlspecialchars($display_address) ?><br>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($display_phone): ?>
|
||||
<?= __('phone') ?>: <?= htmlspecialchars($display_phone) ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($display_email): ?>
|
||||
<?= $display_phone ? ' | ' : '' ?><?= __('email') ?>: <?= htmlspecialchars($display_email) ?><br>
|
||||
<?php elseif($display_phone): ?>
|
||||
<br>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($display_vat): ?>
|
||||
<?= __('vat_no') ?>: <?= htmlspecialchars($display_vat) ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($display_ctr): ?>
|
||||
<?= $display_vat ? ' | ' : '' ?><?= __('ctr_no') ?>: <?= htmlspecialchars($display_ctr) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="text-muted small"><?= htmlspecialchars($display_address) ?><br><?= __('phone') ?>: <?= htmlspecialchars($company_info['phone']) ?></div>
|
||||
</div>
|
||||
<div class="col-5 text-end">
|
||||
<h1 class="fw-bold text-uppercase mb-2" style="color: #0d6efd;"><?= __('statement') ?></h1>
|
||||
<div class="mt-3">
|
||||
<h1 class="fw-bold text-uppercase mb-2 text-primary"><?= $active_tab === 'loyalty' ? (__('loyalty_statement') ?? 'Loyalty Statement') : __('statement') ?></h1>
|
||||
<div class="mt-3 small">
|
||||
<p class="mb-0 fw-bold"><?= __('date') ?>: <?= date('d/m/Y') ?></p>
|
||||
<p class="mb-0 text-muted small"><?= __('cashier') ?>: <?= htmlspecialchars($_SESSION['full_name'] ?? 'System Admin') ?></p>
|
||||
<?php if ($from_date || $to_date): ?>
|
||||
<p class="mb-0 text-muted small mt-1">
|
||||
<?= $from_date ? __('from_date') . ': ' . date('d/m/Y', strtotime($from_date)) : '' ?>
|
||||
<?= $to_date ? ' ' . __('to_date') . ': ' . date('d/m/Y', strtotime($to_date)) : '' ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<?php if ($from_date || $to_date): ?><p class="mb-0 text-muted mt-1"><?= $from_date ? __('from_date').': '.date('d/m/Y',strtotime($from_date)) : '' ?> <?= $to_date ? __('to_date').': '.date('d/m/Y',strtotime($to_date)) : '' ?></p><?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row p-3 bg-light rounded-3 mb-4 mx-0 border">
|
||||
<div class="col-12">
|
||||
<h6 class="fw-bold text-muted text-uppercase mb-2 small"><?= __('customer_details') ?></h6>
|
||||
<h4 class="fw-bold mb-1"><?= $lang === 'ar' ? ($customer['name_ar'] ?: $customer['name_en']) : $customer['name_en'] ?></h4>
|
||||
<p class="mb-0 text-muted small"><?= $customer['phone'] ?> <?= $customer['email'] ? ' | ' . $customer['email'] : '' ?></p>
|
||||
</div>
|
||||
<div class="col-12"><h6 class="fw-bold text-muted text-uppercase mb-2 small"><?= __('customer_details') ?></h6><h4 class="fw-bold mb-1"><?= $lang === 'ar' ? ($customer['name_ar'] ?: $customer['name_en']) : $customer['name_en'] ?></h4><p class="mb-0 text-muted small"><?= $customer['phone'] ?> <?= $customer['email'] ? ' | '.$customer['email'] : '' ?></p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Screen Header -->
|
||||
<div class="d-print-none mb-4">
|
||||
<h4 class="fw-bold mb-1"><?= $lang === 'ar' ? ($customer['name_ar'] ?: $customer['name_en']) : $customer['name_en'] ?></h4>
|
||||
<p class="text-muted mb-0"><?= $customer['phone'] ?> <?= $customer['email'] ? ' | ' . $customer['email'] : '' ?></p>
|
||||
<p class="text-muted mb-0"><?= $customer['phone'] ?> <?= $customer['email'] ? ' | '.$customer['email'] : '' ?></p>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped align-middle mt-4">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th><?= __('date') ?></th>
|
||||
<th><?= __('description') ?></th>
|
||||
<th class="text-center"><?= __('ref') ?? 'Ref' ?></th>
|
||||
<th class="text-end"><?= __('debit') ?></th>
|
||||
<th class="text-end"><?= __('credit') ?></th>
|
||||
<th class="text-end"><?= __('balance') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($transactions)): ?>
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-5 text-muted">
|
||||
<?= __('no_transactions_found') ?? 'No transactions found' ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($transactions as $t): ?>
|
||||
<tr>
|
||||
<td class="small"><?= date('d/m/Y H:i', strtotime($t['date'])) ?></td>
|
||||
<td><?= $t['description'] ?></td>
|
||||
<td class="text-center"><span class="badge bg-light text-dark border"><?= $t['ref'] ?></span></td>
|
||||
<td class="text-end"><?= $t['debit'] > 0 ? format_amount($t['debit']) : '-' ?></td>
|
||||
<td class="text-end"><?= $t['credit'] > 0 ? format_amount($t['credit']) : '-' ?></td>
|
||||
<td class="text-end fw-bold"><?= format_amount($t['running_balance']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot class="table-light fw-bold border-top">
|
||||
<tr>
|
||||
<td colspan="3" class="text-end"><?= __('total') ?></td>
|
||||
<td class="text-end"><?= format_amount($total_debit) ?></td>
|
||||
<td class="text-end"><?= format_amount($total_credit) ?></td>
|
||||
<td class="text-end text-primary"><?= format_amount($balance) ?></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="row mt-5">
|
||||
<div class="col-md-5 ms-auto">
|
||||
<div class="card bg-white border" style="border-radius: 15px;">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-4 border-bottom pb-2 small text-uppercase text-muted"><?= __('summary') ?? 'Summary' ?></h5>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted small"><?= __('total_debit') ?>:</span>
|
||||
<span class="fw-bold small"><?= format_amount($total_debit) ?></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted small"><?= __('total_credit') ?>:</span>
|
||||
<span class="fw-bold small"><?= format_amount($total_credit) ?></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between pt-2 mt-2 border-top">
|
||||
<span class="fw-bold fs-6"><?= __('closing_balance') ?>:</span>
|
||||
<span class="fw-bold text-primary fs-6"><?= format_amount($balance) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($active_tab === 'financial'): ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped align-middle mt-2">
|
||||
<thead class="table-dark">
|
||||
<tr><th><?= __('date') ?></th><th><?= __('description') ?></th><th class="text-center"><?= __('ref') ?></th><th class="text-end"><?= __('debit') ?></th><th class="text-end"><?= __('credit') ?></th><th class="text-end"><?= __('balance') ?></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($transactions)): ?><tr><td colspan="6" class="text-center py-5 text-muted"><?= __('no_transactions_found') ?></td></tr>
|
||||
<?php else: foreach ($transactions as $t): ?>
|
||||
<tr><td class="small"><?= date('d/m/Y H:i', strtotime($t['date'])) ?></td><td><?= $t['description'] ?></td><td class="text-center"><span class="badge bg-light text-dark border"><?= $t['ref'] ?></span></td><td class="text-end"><?= $t['debit'] > 0 ? format_amount($t['debit']) : '-' ?></td><td class="text-end"><?= $t['credit'] > 0 ? format_amount($t['credit']) : '-' ?></td><td class="text-end fw-bold"><?= format_amount($t['running_balance']) ?></td></tr>
|
||||
<?php endforeach; endif; ?>
|
||||
</tbody>
|
||||
<tfoot class="table-light fw-bold"><tr><td colspan="3" class="text-end"><?= __('total') ?></td><td class="text-end"><?= format_amount($total_debit) ?></td><td class="text-end"><?= format_amount($total_credit) ?></td><td class="text-end text-primary"><?= format_amount($balance) ?></td></tr></tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Print Footer -->
|
||||
<div class="d-none d-print-block mt-5 pt-4 border-top">
|
||||
<div class="row text-center small text-muted">
|
||||
<div class="col-4">
|
||||
<div class="mb-4">_______________________</div>
|
||||
<div><?= __('customer_signature') ?? 'Customer Signature' ?></div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="mb-4"><?= date('d/m/Y H:i') ?></div>
|
||||
<div><?= __('print_date') ?? 'Print Date' ?></div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="mb-4">_______________________</div>
|
||||
<div><?= __('authorized_signature') ?? 'Authorized Signature' ?></div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped align-middle mt-2">
|
||||
<thead class="table-dark">
|
||||
<tr><th><?= __('date') ?></th><th><?= __('description') ?></th><th class="text-center"><?= __('ref') ?></th><th class="text-end"><?= __('earned') ?? 'Earned' ?></th><th class="text-end"><?= __('redeemed') ?? 'Redeemed' ?></th><th class="text-end"><?= __('balance') ?></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($loyalty_transactions)): ?><tr><td colspan="6" class="text-center py-5 text-muted"><?= __('no_transactions_found') ?></td></tr>
|
||||
<?php else: foreach ($loyalty_transactions as $lt): ?>
|
||||
<tr>
|
||||
<td class="small"><?= date('d/m/Y H:i', strtotime($lt['created_at'])) ?></td>
|
||||
<td><?= $lt['description'] ?> <span class="badge bg-soft-primary text-primary small ms-2"><?= __($lt['type']) ?></span></td>
|
||||
<td class="text-center"><?= $lt['order_number'] ? '<span class="badge bg-light text-dark border">'.$lt['order_number'].'</span>' : '-' ?></td>
|
||||
<td class="text-end text-success"><?= $lt['points'] > 0 ? '+'.number_format($lt['points'], 2) : '-' ?></td>
|
||||
<td class="text-end text-danger"><?= $lt['points'] < 0 ? number_format($lt['points'], 2) : '-' ?></td>
|
||||
<td class="text-end fw-bold"><?= number_format($lt['running_balance'], 2) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; endif; ?>
|
||||
</tbody>
|
||||
<tfoot class="table-light fw-bold"><tr><td colspan="5" class="text-end"><?= __('current_balance') ?? 'Current Balance' ?></td><td class="text-end text-primary"><?= number_format($customer['loyalty_points'], 2) ?></td></tr></tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Summary Box -->
|
||||
<div class="row mt-5 d-print-none">
|
||||
<div class="col-md-5 ms-auto">
|
||||
<div class="card bg-light border-0 p-4 rounded-4">
|
||||
<h6 class="fw-bold mb-3 text-muted text-uppercase small"><?= __('summary') ?></h6>
|
||||
<?php if ($active_tab === 'financial'): ?>
|
||||
<div class="d-flex justify-content-between mb-2"><span><?= __('total_debit') ?>:</span><span class="fw-bold"><?= format_amount($total_debit) ?></span></div>
|
||||
<div class="d-flex justify-content-between mb-2"><span><?= __('total_credit') ?>:</span><span class="fw-bold"><?= format_amount($total_credit) ?></span></div>
|
||||
<div class="d-flex justify-content-between pt-2 mt-2 border-top"><span class="fw-bold"><?= __('closing_balance') ?>:</span><span class="fw-bold text-primary"><?= format_amount($balance) ?></span></div>
|
||||
<?php else: ?>
|
||||
<div class="d-flex justify-content-between pt-2 mt-2 border-top"><span class="fw-bold"><?= __('loyalty_points') ?>:</span><span class="fw-bold text-primary fs-4"><?= number_format($customer['loyalty_points'], 2) ?></span></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.bg-soft-primary { background-color: rgba(13, 110, 253, 0.1); }
|
||||
@media print {
|
||||
@page {
|
||||
size: auto;
|
||||
margin: 10mm 15mm;
|
||||
}
|
||||
body {
|
||||
background-color: white !important;
|
||||
font-size: 11px;
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
.main-content {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
header, .sidebar, .d-print-none, .breadcrumb, nav {
|
||||
display: none !important;
|
||||
}
|
||||
.container-fluid, .row, .col-lg-10 {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
flex: 0 0 100% !important;
|
||||
}
|
||||
.card {
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
#printableStatement {
|
||||
padding: 0 !important;
|
||||
}
|
||||
table {
|
||||
width: 100% !important;
|
||||
border-collapse: collapse !important;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #dee2e6 !important;
|
||||
padding: 6px !important;
|
||||
}
|
||||
.badge {
|
||||
border: none !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.bg-light {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
.table-dark {
|
||||
background-color: #212529 !important;
|
||||
color: white !important;
|
||||
}
|
||||
body { background-color: white !important; font-size: 10px; }
|
||||
.main-content { padding: 0 !important; margin: 0 !important; }
|
||||
header, .sidebar, .d-print-none, .breadcrumb, nav { display: none !important; }
|
||||
.container-fluid, .row, .col-lg-10 { padding: 0 !important; margin: 0 !important; width: 100% !important; max-width: 100% !important; flex: 0 0 100% !important; }
|
||||
.card { box-shadow: none !important; border: none !important; padding: 0 !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
|
||||
202
customers.php
202
customers.php
@ -1,5 +1,4 @@
|
||||
<?php
|
||||
// ACTION HANDLING FIRST (to allow redirects)
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/lang.php';
|
||||
|
||||
@ -8,23 +7,22 @@ if (!isset($_SESSION['user_id'])) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Initial view check
|
||||
if (!has_permission('view')) {
|
||||
header('Location: admin.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$branch_id = $_SESSION['branch_id'];
|
||||
$loyalty_enabled = get_setting('loyalty_enabled', '0');
|
||||
|
||||
// Handle Actions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
$action = $_POST['action'];
|
||||
|
||||
// Permission mapping for actions
|
||||
$required_permission = 'view';
|
||||
if ($action === 'add_customer') $required_permission = 'add';
|
||||
if ($action === 'edit_customer') $required_permission = 'edit';
|
||||
if ($action === 'delete_customer') $required_permission = 'delete';
|
||||
if ($action === 'adjust_points') $required_permission = 'edit';
|
||||
|
||||
if (!has_permission($required_permission)) {
|
||||
header('Location: customers.php?error=no_permission');
|
||||
@ -60,42 +58,54 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
exit;
|
||||
} elseif ($action === 'delete_customer') {
|
||||
$id = $_POST['id'];
|
||||
|
||||
// Check if customer has orders
|
||||
$stmt = db()->prepare("SELECT COUNT(*) FROM orders WHERE customer_id = ?");
|
||||
$stmt->execute([$id]);
|
||||
if ($stmt->fetchColumn() > 0) {
|
||||
header('Location: customers.php?error=customer_has_orders');
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = db()->prepare("DELETE FROM customers WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
header('Location: customers.php?success=customer_deleted');
|
||||
exit;
|
||||
} elseif ($action === 'adjust_points') {
|
||||
$id = $_POST['id'];
|
||||
$points = (float)$_POST['points'];
|
||||
$type = $_POST['type']; // 'add' or 'subtract'
|
||||
$desc = $_POST['description'] ?? 'Manual adjustment';
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$val = ($type === 'add') ? $points : -$points;
|
||||
$pdo->prepare("UPDATE customers SET loyalty_points = loyalty_points + ? WHERE id = ?")->execute([$val, $id]);
|
||||
$pdo->prepare("INSERT INTO loyalty_transactions (customer_id, points, type, description) VALUES (?, ?, 'adjusted', ?)")
|
||||
->execute([$id, $val, $desc]);
|
||||
$pdo->commit();
|
||||
header('Location: customers.php?success=points_adjusted');
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
header('Location: customers.php?error=' . urlencode($e->getMessage()));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$title = 'customers';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
$search = $_GET['search'] ?? '';
|
||||
|
||||
$sql = "SELECT * FROM customers WHERE 1=1";
|
||||
$params = [];
|
||||
|
||||
if ($search) {
|
||||
$sql .= " AND (name_en LIKE ? OR name_ar LIKE ? OR phone LIKE ?)";
|
||||
$params[] = "%$search%";
|
||||
$params[] = "%$search%";
|
||||
$params[] = "%$search%";
|
||||
$params[] = "%$search%"; $params[] = "%$search%"; $params[] = "%$search%";
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY created_at DESC";
|
||||
$stmt = db()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$customers = $stmt->fetchAll();
|
||||
|
||||
?>
|
||||
|
||||
<div class="card p-4 border-0 shadow-sm" style="border-radius: 20px;">
|
||||
@ -104,31 +114,27 @@ $customers = $stmt->fetchAll();
|
||||
<div class="d-flex gap-2">
|
||||
<form action="" method="GET" class="d-flex gap-2">
|
||||
<div class="input-group shadow-sm rounded-4 overflow-hidden border">
|
||||
<input type="text" name="search" class="form-control border-0" placeholder="<?= __('search') ?>" value="<?= htmlspecialchars($search) ?>">
|
||||
<button class="btn btn-white border-0" type="submit">
|
||||
<i class="bi bi-search"></i>
|
||||
</button>
|
||||
<input type="text" name="search" class="form-control border-0 py-2 shadow-none" placeholder="<?= __('search') ?>" value="<?= htmlspecialchars($search) ?>">
|
||||
<button class="btn btn-white border-0" type="submit"><i class="bi bi-search"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
<?php if (has_permission('add')):
|
||||
?><button class="btn btn-primary px-4 shadow-sm" style="border-radius: 12px;" onclick="openCustomerModal()">
|
||||
<?php if (has_permission('add')): ?>
|
||||
<button class="btn btn-primary px-4 shadow-sm rounded-4" onclick="openCustomerModal()">
|
||||
<i class="bi bi-person-plus-fill me-1"></i> <?= __('add_new') ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if(isset($_GET['success'])):
|
||||
?><div class="alert alert-success alert-dismissible fade show rounded-4 shadow-sm mb-4 border-0" role="alert">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>
|
||||
<?= __($_GET['success']) ?? 'Action completed successfully' ?>
|
||||
<?php if(isset($_GET['success'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show rounded-4 shadow-sm mb-4 border-0" role="alert">
|
||||
<i class="bi bi-check-circle-fill me-2"></i> <?= __($_GET['success']) ?? 'Action completed successfully' ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if(isset($_GET['error'])):
|
||||
?><div class="alert alert-danger alert-dismissible fade show rounded-4 shadow-sm mb-4 border-0" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||
<?= __($_GET['error']) ?? 'Permission Denied' ?>
|
||||
<?php if(isset($_GET['error'])): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show rounded-4 shadow-sm mb-4 border-0" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i> <?= __($_GET['error']) ?? 'An error occurred' ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@ -140,33 +146,41 @@ $customers = $stmt->fetchAll();
|
||||
<th>#</th>
|
||||
<th><?= __('name') ?></th>
|
||||
<th><?= __('phone') ?></th>
|
||||
<?php if ($loyalty_enabled == '1'): ?>
|
||||
<th><?= __('loyalty_points') ?? 'Loyalty Points' ?></th>
|
||||
<?php endif; ?>
|
||||
<th><?= __('email') ?></th>
|
||||
<th><?= __('date') ?></th>
|
||||
<th class="text-end"><?= __('actions') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($customers as $c):
|
||||
?><tr >
|
||||
<?php foreach($customers as $c): ?>
|
||||
<tr>
|
||||
<td><?= $c['id'] ?></td>
|
||||
<td>
|
||||
<div class="fw-bold"><?= $lang === 'ar' ? ($c['name_ar'] ?: $c['name_en']) : $c['name_en'] ?></div>
|
||||
</td>
|
||||
<td><div class="fw-bold"><?= $lang === 'ar' ? ($c['name_ar'] ?: $c['name_en']) : $c['name_en'] ?></div></td>
|
||||
<td><?= $c['phone'] ?></td>
|
||||
<?php if ($loyalty_enabled == '1'): ?>
|
||||
<td>
|
||||
<span class="badge bg-primary rounded-pill px-3 py-2 pointer" onclick="openLoyaltyModal(<?= $c['id'] ?>, '<?= addslashes($lang === 'ar' ? ($c['name_ar'] ?: $c['name_en']) : $c['name_en']) ?>', <?= $c['loyalty_points'] ?>)">
|
||||
<?= number_format($c['loyalty_points'], 2) ?>
|
||||
</span>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
<td><?= $c['email'] ?: '-' ?></td>
|
||||
<td class="small text-muted"><?= date('d/m/Y', strtotime($c['created_at'])) ?></td>
|
||||
<td class="text-end">
|
||||
<div class="d-flex gap-1 justify-content-end">
|
||||
<a href="customer_statement.php?id=<?= $c['id'] ?>" class="btn btn-sm btn-light border-0 p-2 text-info" title="<?= __('statement') ?>" style="border-radius: 8px;">
|
||||
<a href="customer_statement.php?id=<?= $c['id'] ?>" class="btn btn-sm btn-light border-0 p-2 text-info rounded-3" title="<?= __('statement') ?>">
|
||||
<i class="bi bi-file-earmark-text-fill"></i>
|
||||
</a>
|
||||
<?php if (has_permission('edit')):
|
||||
?><button class="btn btn-sm btn-light border-0 p-2 text-primary" style="border-radius: 8px;" onclick="openCustomerModal(<?= htmlspecialchars(json_encode($c)) ?>)">
|
||||
<?php if (has_permission('edit')): ?>
|
||||
<button class="btn btn-sm btn-light border-0 p-2 text-primary rounded-3" onclick="openCustomerModal(<?= htmlspecialchars(json_encode($c)) ?>)">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if (has_permission('delete')):
|
||||
?><button class="btn btn-sm btn-light border-0 p-2 text-danger" style="border-radius: 8px;" onclick="confirmDelete('customer', <?= $c['id'] ?>)">
|
||||
<?php if (has_permission('delete')): ?>
|
||||
<button class="btn btn-sm btn-light border-0 p-2 text-danger rounded-3" onclick="confirmDelete('customer', <?= $c['id'] ?>)">
|
||||
<i class="bi bi-trash-fill"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
@ -174,13 +188,8 @@ $customers = $stmt->fetchAll();
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($customers)):
|
||||
?><tr >
|
||||
<td colspan="6" class="text-center py-5 text-muted">
|
||||
<i class="bi bi-people fs-1 d-block mb-3 opacity-25"></i>
|
||||
<?= __('no_customers_found') ?? 'No customers found' ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php if (empty($customers)): ?>
|
||||
<tr><td colspan="7" class="text-center py-5 text-muted"><i class="bi bi-people fs-1 d-block mb-3 opacity-25"></i><?= __('no_customers_found') ?? 'No customers found' ?></td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -190,66 +199,94 @@ $customers = $stmt->fetchAll();
|
||||
<!-- Customer Modal (Add/Edit) -->
|
||||
<div class="modal fade" id="customerModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg" style="border-radius: 20px;">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold" id="customerModalLabel"><?= __('add_new_customer') ?? 'Add New Customer' ?></h5>
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<div class="modal-header border-0 pb-0 p-4">
|
||||
<h5 class="modal-title fw-bold" id="customerModalLabel"><?= __('add_new_customer') ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<form id="customerForm" method="POST">
|
||||
<input type="hidden" name="action" id="customerAction" value="add_customer">
|
||||
<input type="hidden" name="id" id="customerId">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('phone') ?></label>
|
||||
<input type="text" name="phone" id="customerPhone" class="form-control" required style="border-radius: 12px;">
|
||||
<input type="text" name="phone" id="customerPhone" class="form-control rounded-3" required>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('name_en') ?? 'Name (English)' ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="name_en" id="customerNameEn" class="form-control" required style="border-top-left-radius: 12px; border-bottom-left-radius: 12px;">
|
||||
<button type="button" class="btn btn-outline-secondary" style="border-top-right-radius: 12px; border-bottom-right-radius: 12px;" onclick="translateField('customerNameEn', 'customerNameAr', 'en-ar')">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
<input type="text" name="name_en" id="customerNameEn" class="form-control rounded-start-3" required>
|
||||
<button type="button" class="btn btn-outline-secondary rounded-end-3" onclick="translateField('customerNameEn', 'customerNameAr', 'en-ar')"><i class="bi bi-translate"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('name_ar') ?? 'Name (Arabic)' ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="name_ar" id="customerNameAr" class="form-control" style="border-top-left-radius: 12px; border-bottom-left-radius: 12px;">
|
||||
<button type="button" class="btn btn-outline-secondary" style="border-top-right-radius: 12px; border-bottom-right-radius: 12px;" onclick="translateField('customerNameAr', 'customerNameEn', 'ar-en')">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
<input type="text" name="name_ar" id="customerNameAr" class="form-control rounded-start-3">
|
||||
<button type="button" class="btn btn-outline-secondary rounded-end-3" onclick="translateField('customerNameAr', 'customerNameEn', 'ar-en')"><i class="bi bi-translate"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('email') ?></label>
|
||||
<input type="email" name="email" id="customerEmail" class="form-control" style="border-radius: 12px;">
|
||||
<input type="email" name="email" id="customerEmail" class="form-control rounded-3">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('address_en') ?? 'Address (English)' ?></label>
|
||||
<textarea name="address_en" id="customerAddressEn" class="form-control" style="border-radius: 12px;" rows="2"></textarea>
|
||||
<textarea name="address_en" id="customerAddressEn" class="form-control rounded-3" rows="2"></textarea>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label small fw-bold"><?= __('address_ar') ?? 'Address (Arabic)' ?></label>
|
||||
<textarea name="address_ar" id="customerAddressAr" class="form-control" style="border-radius: 12px;" rows="2"></textarea>
|
||||
<textarea name="address_ar" id="customerAddressAr" class="form-control rounded-3" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100 py-3 mt-2 fw-bold shadow-sm" style="border-radius: 15px;"><?= __('save') ?></button>
|
||||
<button type="submit" class="btn btn-primary w-100 py-3 mt-2 fw-bold shadow-sm rounded-4"><?= __('save') ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loyalty Adjustment Modal -->
|
||||
<div class="modal fade" id="loyaltyModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<div class="modal-header border-0 pb-0 p-4">
|
||||
<h5 class="modal-title fw-bold"><?= __('loyalty_points') ?? 'Loyalty Points' ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4 text-center">
|
||||
<h6 id="loyaltyCustomerName" class="fw-bold mb-1"></h6>
|
||||
<div class="display-6 fw-bold text-primary mb-4" id="loyaltyCurrentPoints">0.00</div>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="adjust_points">
|
||||
<input type="hidden" name="id" id="loyaltyCustomerId">
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-6">
|
||||
<input type="radio" class="btn-check" name="type" id="typeAdd" value="add" checked>
|
||||
<label class="btn btn-outline-success w-100 py-2 rounded-3" for="typeAdd"><?= __('add') ?></label>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<input type="radio" class="btn-check" name="type" id="typeSubtract" value="subtract">
|
||||
<label class="btn btn-outline-danger w-100 py-2 rounded-3" for="typeSubtract"><?= __('subtract') ?></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="number" name="points" class="form-control form-control-lg text-center rounded-3" placeholder="0.00" step="0.01" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<input type="text" name="description" class="form-control rounded-3" placeholder="<?= __('description') ?>">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100 py-3 fw-bold rounded-4 shadow-sm"><?= __('confirm') ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Confirmation Form -->
|
||||
<form id="deleteForm" method="POST" style="display: none;">
|
||||
<input type="hidden" name="action" id="deleteAction">
|
||||
<input type="hidden" name="id" id="deleteId">
|
||||
@ -263,15 +300,11 @@ async function translateField(sourceId, targetId, direction) {
|
||||
if (!text) return;
|
||||
const btn = event.currentTarget;
|
||||
const originalHtml = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = "<span class=\"spinner-border spinner-border-sm\"></span>";
|
||||
btn.disabled = true; btn.innerHTML = "<span class=\"spinner-border spinner-border-sm\"></span>";
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("text", text);
|
||||
formData.append("direction", direction);
|
||||
const formData = new FormData(); formData.append("text", text); formData.append("direction", direction);
|
||||
const resp = await fetch("api/translate.php", { method: "POST", body: formData });
|
||||
const data = await resp.json();
|
||||
if (data.success) targetEl.value = data.translation;
|
||||
const data = await resp.json(); if (data.success) targetEl.value = data.translation;
|
||||
} catch (e) { console.error(e); }
|
||||
finally { btn.disabled = false; btn.innerHTML = originalHtml; }
|
||||
}
|
||||
@ -280,12 +313,10 @@ function openCustomerModal(customer = null) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('customerModal'));
|
||||
const form = document.getElementById('customerForm');
|
||||
const label = document.getElementById('customerModalLabel');
|
||||
const actionInput = document.getElementById('customerAction');
|
||||
const idInput = document.getElementById('customerId');
|
||||
|
||||
if (customer) {
|
||||
label.innerText = "<?= __('edit') ?> <?= __('customer') ?>";
|
||||
actionInput.value = 'edit_customer';
|
||||
document.getElementById('customerAction').value = 'edit_customer';
|
||||
idInput.value = customer.id;
|
||||
document.getElementById('customerPhone').value = customer.phone;
|
||||
document.getElementById('customerNameEn').value = customer.name_en;
|
||||
@ -295,11 +326,17 @@ function openCustomerModal(customer = null) {
|
||||
document.getElementById('customerAddressAr').value = customer.address_ar || '';
|
||||
} else {
|
||||
label.innerText = "<?= __('add_new_customer') ?>";
|
||||
actionInput.value = 'add_customer';
|
||||
form.reset();
|
||||
idInput.value = '';
|
||||
document.getElementById('customerAction').value = 'add_customer';
|
||||
form.reset(); idInput.value = '';
|
||||
}
|
||||
|
||||
modal.show();
|
||||
}
|
||||
|
||||
function openLoyaltyModal(id, name, points) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('loyaltyModal'));
|
||||
document.getElementById('loyaltyCustomerId').value = id;
|
||||
document.getElementById('loyaltyCustomerName').innerText = name;
|
||||
document.getElementById('loyaltyCurrentPoints').innerText = parseFloat(points).toFixed(2);
|
||||
modal.show();
|
||||
}
|
||||
|
||||
@ -312,4 +349,5 @@ function confirmDelete(type, id) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<style>.pointer { cursor: pointer; }</style>
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
22
db/migrations/16_add_loyalty_system.sql
Normal file
22
db/migrations/16_add_loyalty_system.sql
Normal file
@ -0,0 +1,22 @@
|
||||
-- Add loyalty points to customers table
|
||||
ALTER TABLE customers ADD COLUMN IF NOT EXISTS loyalty_points DECIMAL(10, 2) DEFAULT 0.00;
|
||||
|
||||
-- Create loyalty transactions table
|
||||
CREATE TABLE IF NOT EXISTS loyalty_transactions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
customer_id INT NOT NULL,
|
||||
order_id INT NULL,
|
||||
points DECIMAL(10, 2) NOT NULL,
|
||||
type ENUM('earned', 'redeemed', 'adjusted') NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX (customer_id),
|
||||
FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Initial settings for loyalty system
|
||||
INSERT IGNORE INTO settings (setting_key, setting_value) VALUES
|
||||
('loyalty_enabled', '1'),
|
||||
('loyalty_points_per_currency', '1'), -- 1 point per 1 unit spent
|
||||
('loyalty_currency_per_point', '0.05'); -- 1 point = 0.05 unit discount (20 points = 1 unit)
|
||||
2
db/migrations/17_add_loyalty_discount_to_orders.sql
Normal file
2
db/migrations/17_add_loyalty_discount_to_orders.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- Add loyalty_discount column to orders table
|
||||
ALTER TABLE orders ADD COLUMN IF NOT EXISTS loyalty_discount DECIMAL(12, 3) DEFAULT 0.000;
|
||||
@ -18,7 +18,14 @@ $translations = [
|
||||
'branches' => 'Branches',
|
||||
'users' => 'Users',
|
||||
'settings' => 'Settings',
|
||||
'whatsapp_settings' => 'WhatsApp Settings', 'whatsapp_enabled' => 'WhatsApp Enabled', 'wablas_token' => 'Wablas API Token', 'wablas_server' => 'Wablas Server URL', 'wablas_security_key' => 'Wablas Security Key', 'msg_order_created_ar' => 'Order Created Message (Arabic)', 'msg_order_ready_ar' => 'Order Ready Message (Arabic)', 'msg_payment_ar' => 'Payment Received Message (Arabic)',
|
||||
'whatsapp_settings' => 'WhatsApp Settings',
|
||||
'whatsapp_enabled' => 'WhatsApp Enabled',
|
||||
'wablas_token' => 'Wablas API Token',
|
||||
'wablas_server' => 'Wablas Server URL',
|
||||
'wablas_security_key' => 'Wablas Security Key',
|
||||
'msg_order_created_ar' => 'Order Created Message (Arabic)',
|
||||
'msg_order_ready_ar' => 'Order Ready Message (Arabic)',
|
||||
'msg_payment_ar' => 'Payment Received Message (Arabic)',
|
||||
'messages_enabled' => 'Messages Enabled',
|
||||
'messages_disabled' => 'Messages Disabled',
|
||||
'logout' => 'Logout',
|
||||
@ -213,6 +220,15 @@ $translations = [
|
||||
'customer_updated' => 'Customer updated successfully',
|
||||
'customer_deleted' => 'Customer deleted successfully',
|
||||
'customer_has_orders' => 'Cannot delete customer because they have existing orders',
|
||||
'loyalty_settings' => 'Loyalty System Settings',
|
||||
'loyalty_enabled' => 'Loyalty System Enabled',
|
||||
'loyalty_points_per_currency' => 'Points earned per 1 unit spent',
|
||||
'loyalty_currency_per_point' => 'Value of 1 point (discount amount)',
|
||||
'loyalty_points' => 'Loyalty Points',
|
||||
'points_adjusted' => 'Points adjusted successfully',
|
||||
'loyalty' => 'Loyalty Points',
|
||||
'loyalty_discount' => 'Loyalty Discount',
|
||||
'final_total' => 'Final Total',
|
||||
],
|
||||
'ar' => [
|
||||
'dashboard' => 'لوحة القيادة',
|
||||
@ -224,7 +240,14 @@ $translations = [
|
||||
'branches' => 'الفروع',
|
||||
'users' => 'المستخدمين',
|
||||
'settings' => 'الإعدادات',
|
||||
'whatsapp_settings' => 'إعدادات الواتساب', 'whatsapp_enabled' => 'تفعيل الواتساب', 'wablas_token' => 'رمز API Wablas', 'wablas_server' => 'رابط خادم Wablas', 'wablas_security_key' => 'مفتاح الأمان Wablas', 'msg_order_created_ar' => 'رسالة إنشاء الطلب (بالعربية)', 'msg_order_ready_ar' => 'رسالة الطلب جاهز (بالعربية)', 'msg_payment_ar' => 'رسالة استلام الدفعة (بالعربية)',
|
||||
'whatsapp_settings' => 'إعدادات الواتساب',
|
||||
'whatsapp_enabled' => 'تفعيل الواتساب',
|
||||
'wablas_token' => 'رمز API Wablas',
|
||||
'wablas_server' => 'رابط خادم Wablas',
|
||||
'wablas_security_key' => 'مفتاح الأمان Wablas',
|
||||
'msg_order_created_ar' => 'رسالة إنشاء الطلب (بالعربية)',
|
||||
'msg_order_ready_ar' => 'رسالة الطلب جاهز (بالعربية)',
|
||||
'msg_payment_ar' => 'رسالة استلام الدفعة (بالعربية)',
|
||||
'messages_enabled' => 'الرسائل مفعلة',
|
||||
'messages_disabled' => 'الرسائل معطلة',
|
||||
'logout' => 'تسجيل الخروج',
|
||||
@ -419,6 +442,15 @@ $translations = [
|
||||
'customer_updated' => 'تم تحديث العميل بنجاح',
|
||||
'customer_deleted' => 'تم حذف العميل بنجاح',
|
||||
'customer_has_orders' => 'لا يمكن حذف العميل لوجود طلبات مرتبطة به',
|
||||
'loyalty_settings' => 'إعدادات نظام الولاء',
|
||||
'loyalty_enabled' => 'تفعيل نظام الولاء',
|
||||
'loyalty_points_per_currency' => 'النقاط المكتسبة لكل وحدة عملة يتم إنفاقها',
|
||||
'loyalty_currency_per_point' => 'قيمة النقطة الواحدة (مبلغ الخصم)',
|
||||
'loyalty_points' => 'نقاط الولاء',
|
||||
'points_adjusted' => 'تم تعديل النقاط بنجاح',
|
||||
'loyalty' => 'نقاط الولاء',
|
||||
'loyalty_discount' => 'خصم الولاء',
|
||||
'final_total' => 'الإجمالي النهائي',
|
||||
]
|
||||
];
|
||||
|
||||
@ -459,4 +491,4 @@ function branch_url($new_branch_id) {
|
||||
$params = $_GET;
|
||||
$params['switch_branch'] = $new_branch_id;
|
||||
return '?' . http_build_query($params);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,14 +56,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
$stmt->execute([$order_id]);
|
||||
$total_paid = $stmt->fetch()['total_paid'] ?? 0;
|
||||
|
||||
$payment_status = $total_paid >= $order['total_price'] ? 'paid' : ($total_paid > 0 ? 'partially_paid' : 'unpaid');
|
||||
$payment_status = $total_paid >= ($order['total_price'] - $order['loyalty_discount']) ? 'paid' : ($total_paid > 0 ? 'partially_paid' : 'unpaid');
|
||||
$stmt = db()->prepare("UPDATE orders SET payment_status = ? WHERE id = ?");
|
||||
$stmt->execute([$payment_status, $order_id]);
|
||||
|
||||
// WhatsApp Notification for Payment Received
|
||||
try {
|
||||
if (get_setting('whatsapp_enabled') === '1' && !empty($order['customer_phone'])) {
|
||||
$remaining_now = (float)$order['total_price'] - (float)$total_paid;
|
||||
$remaining_now = ((float)$order['total_price'] - (float)$order['loyalty_discount']) - (float)$total_paid;
|
||||
$template = get_setting('msg_payment_ar');
|
||||
if (!empty($template)) {
|
||||
$message = str_replace(
|
||||
@ -151,9 +151,15 @@ $payments = $stmt->fetchAll();
|
||||
<th colspan="5" class="text-end text-muted small"><?= __('vat_total') ?? 'VAT Total' ?></th>
|
||||
<th class="text-end"><?= format_amount($order['vat_total']) ?></th>
|
||||
</tr>
|
||||
<?php if ($order['loyalty_discount'] > 0): ?>
|
||||
<tr>
|
||||
<th colspan="5" class="text-end text-success small"><?= __('loyalty_discount') ?? 'Loyalty Discount' ?></th>
|
||||
<th class="text-end text-success fw-bold">-<?= format_amount($order['loyalty_discount']) ?></th>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<tr>
|
||||
<th colspan="5" class="text-end fs-5 fw-bold"><?= __('total') ?></th>
|
||||
<th class="text-end fs-5 fw-bold text-primary"><?= format_amount($order['total_price']) ?></th>
|
||||
<th class="text-end fs-5 fw-bold text-primary"><?= format_amount($order['total_price'] - $order['loyalty_discount']) ?></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -226,21 +232,21 @@ $payments = $stmt->fetchAll();
|
||||
$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 = (float)$order['total_price'] - (float)$total_paid;
|
||||
$remaining = ((float)$order['total_price'] - (float)$order['loyalty_discount']) - (float)$total_paid;
|
||||
?>
|
||||
<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' ?>">
|
||||
<span class="fs-5 fw-bold <?= $remaining <= 0.001 ? 'text-success' : 'text-danger' ?>">
|
||||
<?= format_amount(max(0, $remaining)) ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<?php if ($remaining > 0): ?>
|
||||
<?php if ($remaining > 0.001): ?>
|
||||
<form method="POST">
|
||||
<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.001" 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="<?= round($remaining, 3) ?>" required style="border-radius: 12px;">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold text-muted"><?= __('payment_method') ?></label>
|
||||
@ -288,4 +294,4 @@ function getPaymentStatusColor($status) {
|
||||
][$status] ?? 'info';
|
||||
}
|
||||
require_once __DIR__ . '/includes/footer.php';
|
||||
?>
|
||||
?>
|
||||
|
||||
10
orders.php
10
orders.php
@ -141,6 +141,8 @@ if ($current_role === 'super_admin') {
|
||||
<?php endif; ?>
|
||||
<th><?= __('customer') ?></th>
|
||||
<th><?= __('total') ?></th>
|
||||
<th><?= __('loyalty_discount') ?? 'Loyalty Discount' ?></th>
|
||||
<th><?= __('final_total') ?? 'Final Total' ?></th>
|
||||
<th><?= __('status') ?></th>
|
||||
<th><?= __('payment_status') ?></th>
|
||||
<th><?= __('date') ?></th>
|
||||
@ -159,7 +161,9 @@ if ($current_role === 'super_admin') {
|
||||
<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"><?= format_amount($order['total_price']) ?></td>
|
||||
<td class="small text-muted"><?= format_amount($order['total_price']) ?></td>
|
||||
<td class="text-success small fw-bold"><?= $order['loyalty_discount'] > 0 ? '-' . format_amount($order['loyalty_discount']) : '-' ?></td>
|
||||
<td class="fw-bold text-primary"><?= format_amount($order['total_price'] - $order['loyalty_discount']) ?></td>
|
||||
<td><span class="badge bg-<?= getStatusColor($order['status']) ?> rounded-pill px-3 py-2"><?= __($order['status']) ?></span></td>
|
||||
<td><span class="badge badge-soft-<?= 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>
|
||||
@ -187,7 +191,7 @@ if ($current_role === 'super_admin') {
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($orders)): ?>
|
||||
<tr>
|
||||
<td colspan="<?= $current_role === 'super_admin' ? 9 : 8 ?>" class="text-center py-5 text-muted">
|
||||
<td colspan="<?= $current_role === 'super_admin' ? 11 : 10 ?>" 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>
|
||||
@ -349,4 +353,4 @@ function getPaymentStatusColor($status) {
|
||||
][$status] ?? 'info';
|
||||
}
|
||||
require_once __DIR__ . '/includes/footer.php';
|
||||
?>
|
||||
?>
|
||||
|
||||
204
pos.php
204
pos.php
@ -3,7 +3,6 @@ $title = 'pos';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
// Global check in header.php handles 'view' permission.
|
||||
// We should check 'edit' permission if we are editing an order.
|
||||
$edit_order_id = $_GET['edit_order_id'] ?? null;
|
||||
if ($edit_order_id && !has_permission('edit')) {
|
||||
header('Location: pos.php?error=no_edit_permission');
|
||||
@ -12,6 +11,11 @@ if ($edit_order_id && !has_permission('edit')) {
|
||||
|
||||
$branch_id = $_SESSION['branch_id'] ?? 1;
|
||||
|
||||
// Loyalty Settings
|
||||
$loyalty_enabled = get_setting('loyalty_enabled', '0');
|
||||
$loyalty_points_per_currency = get_setting('loyalty_points_per_currency', '1');
|
||||
$loyalty_currency_per_point = get_setting('loyalty_currency_per_point', '0.05');
|
||||
|
||||
// Handle Edit Order
|
||||
$edit_order = null;
|
||||
$edit_items = [];
|
||||
@ -81,7 +85,6 @@ foreach ($items_raw as $i) {
|
||||
}
|
||||
}
|
||||
|
||||
// Only include if has services
|
||||
if (!empty($item_services)) {
|
||||
$items[$i['id']] = [
|
||||
'id' => $i['id'],
|
||||
@ -96,7 +99,7 @@ foreach ($items_raw as $i) {
|
||||
}
|
||||
|
||||
// Get all customers for this branch
|
||||
$stmt = db()->prepare("SELECT * FROM customers ORDER BY name_en ASC");
|
||||
$stmt = db()->prepare("SELECT id, name_en, name_ar, phone, loyalty_points FROM customers ORDER BY name_en ASC");
|
||||
$stmt->execute();
|
||||
$customers = $stmt->fetchAll();
|
||||
|
||||
@ -111,7 +114,7 @@ $pageTitle = $edit_order ? ($lang == 'en' ? 'Edit Order #' . $edit_order['order_
|
||||
<h4 class="fw-bold mb-0"><?= $pageTitle ?></h4>
|
||||
<div class="input-group w-50 shadow-sm rounded-4 overflow-hidden">
|
||||
<span class="input-group-text bg-white border-0"><i class="bi bi-search"></i></span>
|
||||
<input type="text" id="itemSearch" class="form-control border-0 py-2" placeholder="<?= $lang == 'en' ? 'Search items...' : 'بحث عن المنتجات...' ?>">
|
||||
<input type="text" id="itemSearch" class="form-control border-0 py-2 shadow-none" placeholder="<?= $lang == 'en' ? 'Search items...' : 'بحث عن المنتجات...' ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -171,7 +174,7 @@ $pageTitle = $edit_order ? ($lang == 'en' ? 'Edit Order #' . $edit_order['order_
|
||||
</div>
|
||||
|
||||
<div class="px-3 pb-3">
|
||||
<div class="d-flex gap-2">
|
||||
<div class="d-flex gap-2 mb-2">
|
||||
<!-- Persistent Customer Search -->
|
||||
<div class="position-relative flex-grow-1" id="customerSearchWrapper">
|
||||
<div class="input-group shadow-sm rounded-3 overflow-hidden border">
|
||||
@ -183,7 +186,7 @@ $pageTitle = $edit_order ? ($lang == 'en' ? 'Edit Order #' . $edit_order['order_
|
||||
</div>
|
||||
<div class="position-absolute w-100 shadow-lg bg-white rounded-3 mt-1 d-none" id="customerResults" style="z-index: 1050; max-height: 300px; overflow-y: auto; border: 1px solid #eee;">
|
||||
<div class="p-2 border-bottom">
|
||||
<button class="btn btn-light btn-sm w-100 text-start rounded-2 customer-result-item" data-id="" data-name="<?= $lang == 'en' ? 'Walk-in Customer' : 'عميل عابر' ?>" data-search="walk-in">
|
||||
<button class="btn btn-light btn-sm w-100 text-start rounded-2 customer-result-item" data-id="" data-name="<?= $lang == 'en' ? 'Walk-in Customer' : 'عميل عابر' ?>" data-search="walk-in" data-points="0">
|
||||
<?= $lang == 'en' ? 'Walk-in Customer' : 'عميل عابر' ?>
|
||||
</button>
|
||||
</div>
|
||||
@ -193,7 +196,7 @@ $pageTitle = $edit_order ? ($lang == 'en' ? 'Edit Order #' . $edit_order['order_
|
||||
$searchStr = strtolower($c['name_en'] . ' ' . ($c['name_ar'] ?? '') . ' ' . $c['phone']);
|
||||
?>
|
||||
<div class="p-1">
|
||||
<button class="btn btn-white btn-sm w-100 text-start rounded-2 customer-result-item p-2" type="button" data-id="<?= $c['id'] ?>" data-name="<?= $cname ?>" data-phone="<?= $c['phone'] ?>" data-search="<?= $searchStr ?>">
|
||||
<button class="btn btn-white btn-sm w-100 text-start rounded-2 customer-result-item p-2" type="button" data-id="<?= $c['id'] ?>" data-name="<?= $cname ?>" data-phone="<?= $c['phone'] ?>" data-search="<?= $searchStr ?>" data-points="<?= $c['loyalty_points'] ?>">
|
||||
<div class="fw-bold small text-dark"><?= $cname ?></div>
|
||||
<div class="text-muted small" style="font-size: 0.7rem;"><?= $c['phone'] ?></div>
|
||||
</button>
|
||||
@ -210,6 +213,15 @@ $pageTitle = $edit_order ? ($lang == 'en' ? 'Edit Order #' . $edit_order['order_
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Loyalty Info Display -->
|
||||
<div id="loyaltyDisplay" class="d-none bg-primary bg-opacity-10 rounded-3 p-2 mb-2 d-flex align-items-center justify-content-between border border-primary border-opacity-25">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-star-fill text-primary me-2"></i>
|
||||
<span class="small fw-bold text-primary"><?= $lang == 'en' ? 'Loyalty Points' : 'نقاط الولاء' ?></span>
|
||||
</div>
|
||||
<span class="badge bg-primary rounded-pill" id="customerPoints">0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-0 flex-grow-1 overflow-auto" id="cartContainer">
|
||||
@ -230,11 +242,33 @@ $pageTitle = $edit_order ? ($lang == 'en' ? 'Edit Order #' . $edit_order['order_
|
||||
<span><?= $lang == 'en' ? 'VAT' : 'الضريبة' ?></span>
|
||||
<span id="cartVat">0.000 <?= currency() ?></span>
|
||||
</div>
|
||||
|
||||
<!-- Loyalty Discount Display -->
|
||||
<div id="loyaltyDiscountRow" class="d-none d-flex justify-content-between mb-2 small text-success fw-bold">
|
||||
<span><?= $lang == 'en' ? 'Loyalty Discount' : 'خصم الولاء' ?></span>
|
||||
<span id="cartLoyaltyDiscount">-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.000 <?= currency() ?></h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loyalty Redemption UI -->
|
||||
<?php if ($loyalty_enabled == '1'): ?>
|
||||
<div id="loyaltyRedeemUI" class="d-none mb-3">
|
||||
<div class="input-group shadow-sm rounded-3 overflow-hidden border">
|
||||
<span class="input-group-text bg-white border-0 small"><i class="bi bi-star text-primary"></i></span>
|
||||
<input type="number" id="pointsToRedeem" class="form-control border-0 py-2 shadow-none small" placeholder="<?= $lang == 'en' ? 'Redeem Points' : 'استبدال النقاط' ?>" min="0">
|
||||
<button class="btn btn-primary btn-sm px-3" onclick="applyPoints()">
|
||||
<?= $lang == 'en' ? 'Apply' : 'تطبيق' ?>
|
||||
</button>
|
||||
</div>
|
||||
<div id="redeemHint" class="small text-muted mt-1" style="font-size: 0.7rem;"></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (($edit_order && has_permission('edit')) || (!$edit_order && has_permission('add'))): ?>
|
||||
<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> <?= $edit_order ? ($lang == 'en' ? 'Update Order' : 'تحديث الطلب') : ($lang == 'en' ? 'Complete Order' : 'إتمام الطلب') ?>
|
||||
@ -355,9 +389,15 @@ const lang = '<?= $lang ?>';
|
||||
const currencyLabel = '<?= currency() ?>';
|
||||
const decimalPrecision = <?= decimals() ?>;
|
||||
const editOrderId = <?= $edit_order_id ?: 'null' ?>;
|
||||
const loyaltyEnabled = <?= $loyalty_enabled ?>;
|
||||
const pointsPerCurrency = <?= $loyalty_points_per_currency ?>;
|
||||
const currencyPerPoint = <?= $loyalty_currency_per_point ?>;
|
||||
|
||||
let cart = <?= json_encode($edit_items) ?>;
|
||||
let selectionModal;
|
||||
let paymentModal;
|
||||
let customerLoyaltyPoints = 0;
|
||||
let pointsToRedeem = 0;
|
||||
|
||||
// If not editing, try to load from local storage
|
||||
if (!editOrderId) {
|
||||
@ -420,8 +460,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
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');
|
||||
selectCustomer(initialCustId, initialCustBtn.getAttribute('data-name'), initialCustBtn.getAttribute('data-points'));
|
||||
}
|
||||
<?php endif; ?>
|
||||
|
||||
@ -443,7 +482,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Close results when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!document.getElementById('customerSearchWrapper').contains(e.target)) {
|
||||
custResults.classList.add('d-none');
|
||||
@ -453,30 +491,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
if (clearCustBtn) {
|
||||
clearCustBtn.addEventListener('click', () => {
|
||||
custIdInput.value = '';
|
||||
custSearchInput.value = '';
|
||||
custSearchInput.placeholder = lang === 'en' ? 'Walk-in Customer' : 'عميل عابر';
|
||||
clearCustBtn.classList.add('d-none');
|
||||
resetCustomerSelection();
|
||||
});
|
||||
}
|
||||
|
||||
// Customer selection delegation
|
||||
custResults.addEventListener('click', function(e) {
|
||||
const btn = e.target.closest('.customer-result-item');
|
||||
if (btn) {
|
||||
const id = btn.getAttribute('data-id');
|
||||
const name = btn.getAttribute('data-name');
|
||||
custIdInput.value = id;
|
||||
|
||||
if (id) {
|
||||
custSearchInput.value = name;
|
||||
clearCustBtn.classList.remove('d-none');
|
||||
} else {
|
||||
custSearchInput.value = '';
|
||||
custSearchInput.placeholder = name;
|
||||
clearCustBtn.classList.add('d-none');
|
||||
}
|
||||
|
||||
const points = btn.getAttribute('data-points') || 0;
|
||||
selectCustomer(id, name, points);
|
||||
custResults.classList.add('d-none');
|
||||
}
|
||||
});
|
||||
@ -484,6 +509,68 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
updateCart();
|
||||
});
|
||||
|
||||
function selectCustomer(id, name, points) {
|
||||
const custIdInput = document.getElementById('customerId');
|
||||
const custSearchInput = document.getElementById('customerSearchInput');
|
||||
const clearCustBtn = document.getElementById('clearCustomerBtn');
|
||||
const loyaltyDisplay = document.getElementById('loyaltyDisplay');
|
||||
const loyaltyRedeemUI = document.getElementById('loyaltyRedeemUI');
|
||||
const customerPointsEl = document.getElementById('customerPoints');
|
||||
const redeemHint = document.getElementById('redeemHint');
|
||||
|
||||
custIdInput.value = id;
|
||||
customerLoyaltyPoints = parseFloat(points);
|
||||
|
||||
if (id) {
|
||||
custSearchInput.value = name;
|
||||
clearCustBtn.classList.remove('d-none');
|
||||
if (loyaltyEnabled) {
|
||||
loyaltyDisplay.classList.remove('d-none');
|
||||
loyaltyRedeemUI.classList.remove('d-none');
|
||||
customerPointsEl.innerText = customerLoyaltyPoints.toFixed(2);
|
||||
if (redeemHint) {
|
||||
redeemHint.innerText = lang === 'en'
|
||||
? `1 Point = ${currencyPerPoint} ${currencyLabel}`
|
||||
: `1 نقطة = ${currencyPerPoint} ${currencyLabel}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resetCustomerSelection();
|
||||
}
|
||||
}
|
||||
|
||||
function resetCustomerSelection() {
|
||||
const custIdInput = document.getElementById('customerId');
|
||||
const custSearchInput = document.getElementById('customerSearchInput');
|
||||
const clearCustBtn = document.getElementById('clearCustomerBtn');
|
||||
const loyaltyDisplay = document.getElementById('loyaltyDisplay');
|
||||
const loyaltyRedeemUI = document.getElementById('loyaltyRedeemUI');
|
||||
|
||||
custIdInput.value = '';
|
||||
custSearchInput.value = '';
|
||||
custSearchInput.placeholder = lang === 'en' ? 'Walk-in Customer' : 'عميل عابر';
|
||||
clearCustBtn.classList.add('d-none');
|
||||
loyaltyDisplay.classList.add('d-none');
|
||||
loyaltyRedeemUI.classList.add('d-none');
|
||||
customerLoyaltyPoints = 0;
|
||||
pointsToRedeem = 0;
|
||||
const ptsInput = document.getElementById('pointsToRedeem');
|
||||
if (ptsInput) ptsInput.value = '';
|
||||
updateCart();
|
||||
}
|
||||
|
||||
function applyPoints() {
|
||||
const ptsInput = document.getElementById('pointsToRedeem');
|
||||
let val = parseFloat(ptsInput.value) || 0;
|
||||
if (val > customerLoyaltyPoints) {
|
||||
alert(lang === 'en' ? 'Insufficient points' : 'نقاط غير كافية');
|
||||
val = customerLoyaltyPoints;
|
||||
ptsInput.value = val;
|
||||
}
|
||||
pointsToRedeem = val;
|
||||
updateCart();
|
||||
}
|
||||
|
||||
function showOptions(itemId) {
|
||||
const item = itemsData[itemId];
|
||||
if (!item) return;
|
||||
@ -504,7 +591,6 @@ function showOptions(itemId) {
|
||||
list.appendChild(col);
|
||||
});
|
||||
}
|
||||
|
||||
if (selectionModal) selectionModal.show();
|
||||
}
|
||||
|
||||
@ -579,15 +665,37 @@ function updateCart() {
|
||||
const subtotalEl = document.getElementById('cartSubtotal');
|
||||
const vatEl = document.getElementById('cartVat');
|
||||
const totalEl = document.getElementById('cartTotal');
|
||||
const discountRow = document.getElementById('loyaltyDiscountRow');
|
||||
const discountEl = document.getElementById('cartLoyaltyDiscount');
|
||||
|
||||
let totalBeforeDiscount = subtotal + totalVat;
|
||||
let loyaltyDiscount = pointsToRedeem * currencyPerPoint;
|
||||
|
||||
if (loyaltyDiscount > totalBeforeDiscount) {
|
||||
loyaltyDiscount = totalBeforeDiscount;
|
||||
pointsToRedeem = loyaltyDiscount / currencyPerPoint;
|
||||
}
|
||||
|
||||
if (loyaltyDiscount > 0) {
|
||||
discountRow.classList.remove('d-none');
|
||||
discountEl.innerText = '-' + loyaltyDiscount.toFixed(decimalPrecision) + ' ' + currencyLabel;
|
||||
} else {
|
||||
discountRow.classList.add('d-none');
|
||||
}
|
||||
|
||||
const finalTotal = totalBeforeDiscount - loyaltyDiscount;
|
||||
|
||||
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;
|
||||
if (totalEl) totalEl.innerText = finalTotal.toFixed(decimalPrecision) + ' ' + currencyLabel;
|
||||
}
|
||||
|
||||
function clearCart() {
|
||||
if (confirm(lang === 'en' ? 'Clear cart?' : 'مسح السلة؟')) {
|
||||
cart = [];
|
||||
pointsToRedeem = 0;
|
||||
const ptsInput = document.getElementById('pointsToRedeem');
|
||||
if (ptsInput) ptsInput.value = '';
|
||||
updateCart();
|
||||
}
|
||||
}
|
||||
@ -595,13 +703,18 @@ function clearCart() {
|
||||
function checkout() {
|
||||
if (cart.length === 0) return;
|
||||
|
||||
let total = 0;
|
||||
let subtotal = 0;
|
||||
let totalVat = 0;
|
||||
cart.forEach(item => {
|
||||
const itemSubtotal = item.price * item.qty;
|
||||
total += itemSubtotal + (itemSubtotal * ((item.vat_percent || 15) / 100));
|
||||
subtotal += itemSubtotal;
|
||||
totalVat += itemSubtotal * ((item.vat_percent || 15) / 100);
|
||||
});
|
||||
|
||||
document.getElementById('paymentTotalAmount').innerText = total.toFixed(decimalPrecision) + ' ' + currencyLabel;
|
||||
let finalTotal = (subtotal + totalVat) - (pointsToRedeem * currencyPerPoint);
|
||||
if (finalTotal < 0) finalTotal = 0;
|
||||
|
||||
document.getElementById('paymentTotalAmount').innerText = finalTotal.toFixed(decimalPrecision) + ' ' + currencyLabel;
|
||||
if (paymentModal) paymentModal.show();
|
||||
}
|
||||
|
||||
@ -610,14 +723,11 @@ async function completeCheckout(paymentMethod) {
|
||||
|
||||
let subtotal = 0;
|
||||
let totalVat = 0;
|
||||
|
||||
const itemsToSubmit = cart.map(item => {
|
||||
const itemSubtotal = item.price * item.qty;
|
||||
const itemVat = itemSubtotal * ((item.vat_percent || 15) / 100);
|
||||
|
||||
subtotal += itemSubtotal;
|
||||
totalVat += itemVat;
|
||||
|
||||
return {
|
||||
itemId: item.item_id,
|
||||
serviceId: item.service_id,
|
||||
@ -640,28 +750,21 @@ async function completeCheckout(paymentMethod) {
|
||||
items: itemsToSubmit,
|
||||
vat_total: totalVat,
|
||||
total_price: totalPrice,
|
||||
payment_method: paymentMethod
|
||||
payment_method: paymentMethod,
|
||||
points_to_redeem: pointsToRedeem
|
||||
})
|
||||
});
|
||||
const res = await response.json();
|
||||
if (res.success) {
|
||||
const orderId = res.order_id || editOrderId;
|
||||
|
||||
// Direct Print
|
||||
printReceipt(orderId);
|
||||
|
||||
if (!editOrderId) {
|
||||
cart = [];
|
||||
updateCart();
|
||||
localStorage.removeItem('pos_cart');
|
||||
}
|
||||
|
||||
if (paymentModal) paymentModal.hide();
|
||||
|
||||
// Wait a bit for print dialog to start then redirect
|
||||
setTimeout(() => {
|
||||
window.location.href = 'pos.php';
|
||||
}, 2000);
|
||||
setTimeout(() => { window.location.href = 'pos.php'; }, 2000);
|
||||
} else alert(res.error);
|
||||
} catch (e) { alert('Error'); }
|
||||
}
|
||||
@ -669,7 +772,6 @@ async function completeCheckout(paymentMethod) {
|
||||
function printReceipt(orderId) {
|
||||
const iframe = document.getElementById('printFrame');
|
||||
iframe.src = 'receipt.php?id=' + orderId;
|
||||
|
||||
iframe.onload = function() {
|
||||
iframe.contentWindow.focus();
|
||||
iframe.contentWindow.print();
|
||||
@ -690,21 +792,15 @@ async function saveCustomer() {
|
||||
const phone = res.customer.phone;
|
||||
const displayName = lang === 'en' ? nameEn : nameAr;
|
||||
|
||||
// Update hidden input
|
||||
document.getElementById('customerId').value = id;
|
||||
// Update search input
|
||||
const custSearchInput = document.getElementById('customerSearchInput');
|
||||
custSearchInput.value = displayName;
|
||||
document.getElementById('clearCustomerBtn').classList.remove('d-none');
|
||||
selectCustomer(id, displayName, 0);
|
||||
|
||||
// Add to results list
|
||||
const list = document.getElementById('customerResultsList');
|
||||
if (list) {
|
||||
const searchStr = `${nameEn} ${nameAr} ${phone}`.toLowerCase();
|
||||
const div = document.createElement('div');
|
||||
div.className = 'p-1';
|
||||
div.innerHTML = `
|
||||
<button class="btn btn-white btn-sm w-100 text-start rounded-2 customer-result-item p-2" type="button" data-id="${id}" data-name="${displayName}" data-phone="${phone}" data-search="${searchStr}">
|
||||
<button class="btn btn-white btn-sm w-100 text-start rounded-2 customer-result-item p-2" type="button" data-id="${id}" data-name="${displayName}" data-phone="${phone}" data-search="${searchStr}" data-points="0">
|
||||
<div class="fw-bold small text-dark">${displayName}</div>
|
||||
<div class="text-muted small" style="font-size: 0.7rem;">${phone}</div>
|
||||
</button>
|
||||
@ -735,4 +831,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'; ?>
|
||||
14
receipt.php
14
receipt.php
@ -41,7 +41,7 @@ $order_items = $stmt->fetchAll();
|
||||
$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;
|
||||
$remaining = ($order['total_price'] - $order['loyalty_discount']) - $total_paid;
|
||||
|
||||
// Bilingual Helper for Labels
|
||||
function b_label($key) {
|
||||
@ -301,9 +301,17 @@ $display_ctr = $company['ctr_no'];
|
||||
<div class="info-label"><?= b_label('vat') ?></div>
|
||||
<div class="info-value"><?= format_amount($order['vat_total']) ?></div>
|
||||
</div>
|
||||
|
||||
<?php if ($order['loyalty_discount'] > 0): ?>
|
||||
<div class="total-row text-success fw-bold">
|
||||
<div class="info-label"><?= b_label('loyalty_discount') ?></div>
|
||||
<div class="info-value">-<?= format_amount($order['loyalty_discount']) ?></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="total-row fw-bold grand-total">
|
||||
<div class="info-label"><?= b_label('total') ?></div>
|
||||
<div class="info-value" style="font-size: 16px;"><?= format_amount($order['total_price']) ?></div>
|
||||
<div class="info-value" style="font-size: 16px;"><?= format_amount($order['total_price'] - $order['loyalty_discount']) ?></div>
|
||||
</div>
|
||||
|
||||
<div class="total-row mt-2">
|
||||
@ -334,4 +342,4 @@ $display_ctr = $company['ctr_no'];
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user