diff --git a/admin.php b/admin.php index 1a077b3..9ab7a86 100644 --- a/admin.php +++ b/admin.php @@ -51,7 +51,7 @@ $recent_orders = $stmt->fetchAll();
-
SAR
+
@@ -118,7 +118,7 @@ $recent_orders = $stmt->fetchAll(); - + @@ -167,4 +167,3 @@ function getPaymentStatusColor($status) { ][$status] ?? 'info'; } require_once __DIR__ . '/includes/footer.php'; -?> \ No newline at end of file diff --git a/api/checkout.php b/api/checkout.php index 541565a..0911d2b 100644 --- a/api/checkout.php +++ b/api/checkout.php @@ -9,6 +9,7 @@ if (!isset($_SESSION['user_id'])) { } $input = json_decode(file_get_contents('php://input'), true); +$order_id = $input['order_id'] ?? null; $customer_id = $input['customer_id'] ?: null; $items = $input['items'] ?? []; $vat_total = (float)($input['vat_total'] ?? 0); @@ -25,15 +26,35 @@ try { $pdo = db(); $pdo->beginTransaction(); - // Recalculate total if not provided correctly, but for now we trust the client-side breakdown - // If we wanted to be more secure, we'd fetch prices from DB here. + if ($order_id) { + // Update existing order + $stmt = $pdo->prepare("UPDATE orders SET customer_id = ?, total_price = ?, vat_total = ? WHERE id = ? AND branch_id = ?"); + $stmt->execute([$customer_id, $total_price, $vat_total, $order_id, $branch_id]); + + // Remove existing items + $stmt = $pdo->prepare("DELETE FROM order_items WHERE order_id = ?"); + $stmt->execute([$order_id]); + } else { + // Create new order + $stmt = $pdo->prepare("INSERT INTO orders (branch_id, customer_id, user_id, order_number, total_price, vat_total, status, payment_status) + VALUES (?, ?, ?, NULL, ?, ?, 'received', 'unpaid')"); + $stmt->execute([$branch_id, $customer_id, $user_id, $total_price, $vat_total]); + $order_id = $pdo->lastInsertId(); - $order_number = 'ORD-' . time() . '-' . rand(100, 999); - - $stmt = $pdo->prepare("INSERT INTO orders (branch_id, customer_id, user_id, order_number, total_price, vat_total, status, payment_status) - VALUES (?, ?, ?, ?, ?, ?, 'received', 'unpaid')"); - $stmt->execute([$branch_id, $customer_id, $user_id, $order_number, $total_price, $vat_total]); - $order_id = $pdo->lastInsertId(); + // Get branch prefix + $stmt_prefix = $pdo->prepare("SELECT prefix FROM branches WHERE id = ?"); + $stmt_prefix->execute([$branch_id]); + $prefix = $stmt_prefix->fetchColumn() ?: 'ORD'; + if (strlen($prefix) > 3) $prefix = substr($prefix, 0, 3); + $prefix = str_pad($prefix, 3, 'X'); // Just in case it's shorter than 3 + + // Format order_number as XXX#####1 (5 digits for #####) + $order_number = strtoupper($prefix) . str_pad($order_id, 5, '0', STR_PAD_LEFT) . '1'; + + // Update the order with the generated order_number + $stmt_update = $pdo->prepare("UPDATE orders SET order_number = ? WHERE id = ?"); + $stmt_update->execute([$order_number, $order_id]); + } $stmt_item = $pdo->prepare("INSERT INTO order_items (order_id, item_id, variant_id, service_id, quantity, unit_price, vat_amount, subtotal) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); diff --git a/api/delete_order.php b/api/delete_order.php new file mode 100644 index 0000000..e0e8bba --- /dev/null +++ b/api/delete_order.php @@ -0,0 +1,31 @@ + false, 'error' => 'Unauthorized']); + exit; +} + +$input = json_decode(file_get_contents('php://input'), true); +$id = $input['id'] ?? null; + +if (!$id) { + echo json_encode(['success' => false, 'error' => 'Missing order ID']); + exit; +} + +try { + $pdo = db(); + $stmt = $pdo->prepare("DELETE FROM orders WHERE id = ?"); + $stmt->execute([$id]); + + if ($stmt->rowCount() > 0) { + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'Order not found or already deleted']); + } +} catch (Exception $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/api/get_order_items.php b/api/get_order_items.php new file mode 100644 index 0000000..f26fe80 --- /dev/null +++ b/api/get_order_items.php @@ -0,0 +1,35 @@ + false, 'error' => 'Order ID is required']); + exit; +} + +try { + $stmt = db()->prepare("SELECT oi.quantity, i.name_en as item_en, i.name_ar as item_ar, + s.name_en as service_en, s.name_ar as service_ar + FROM order_items oi + JOIN items i ON oi.item_id = i.id + JOIN services s ON oi.service_id = s.id + WHERE oi.order_id = ?"); + $stmt->execute([$order_id]); + $items = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // Fetch order number for the modal title + $stmt = db()->prepare("SELECT order_number FROM orders WHERE id = ?"); + $stmt->execute([$order_id]); + $order = $stmt->fetch(PDO::FETCH_ASSOC); + + echo json_encode([ + 'success' => true, + 'order_number' => $order['order_number'] ?? '', + 'items' => $items + ]); +} catch (Exception $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/api/update_order_status.php b/api/update_order_status.php new file mode 100644 index 0000000..892d6ba --- /dev/null +++ b/api/update_order_status.php @@ -0,0 +1,38 @@ + false, 'error' => 'Unauthorized']); + exit; +} + +$input = json_decode(file_get_contents('php://input'), true); +$id = $input['id'] ?? null; +$status = $input['status'] ?? null; + +if (!$id || !$status) { + echo json_encode(['success' => false, 'error' => 'Missing order ID or status']); + exit; +} + +$valid_statuses = ['received', 'processing', 'ready', 'delivered', 'cancelled']; +if (!in_array($status, $valid_statuses)) { + echo json_encode(['success' => false, 'error' => 'Invalid status']); + exit; +} + +try { + $pdo = db(); + $stmt = $pdo->prepare("UPDATE orders SET status = ? WHERE id = ?"); + $stmt->execute([$status, $id]); + + if ($stmt->rowCount() > 0) { + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'Order not found or status already same']); + } +} catch (Exception $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/branches.php b/branches.php index a3c63b9..79048e1 100644 --- a/branches.php +++ b/branches.php @@ -20,11 +20,33 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { $name_ar = $_POST['name_ar']; $company_id = $_POST['company_id']; $phone = $_POST['phone']; - $stmt = db()->prepare("INSERT INTO branches (name_en, name_ar, company_id, phone) VALUES (?, ?, ?, ?)"); - $stmt->execute([$name_en, $name_ar, $company_id, $phone]); + $prefix = strtoupper(substr($_POST['prefix'] ?? '', 0, 3)); + $stmt = db()->prepare("INSERT INTO branches (name_en, name_ar, company_id, phone, prefix) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([$name_en, $name_ar, $company_id, $phone, $prefix]); header('Location: branches.php'); exit; } + + if ($_POST['action'] === 'edit_branch') { + $id = $_POST['id']; + $name_en = $_POST['name_en']; + $name_ar = $_POST['name_ar']; + $company_id = $_POST['company_id']; + $phone = $_POST['phone']; + $prefix = strtoupper(substr($_POST['prefix'] ?? '', 0, 3)); + $stmt = db()->prepare("UPDATE branches SET name_en = ?, name_ar = ?, company_id = ?, phone = ?, prefix = ? WHERE id = ?"); + $stmt->execute([$name_en, $name_ar, $company_id, $phone, $prefix, $id]); + header('Location: branches.php'); + exit; + } +} + +if (isset($_GET['delete'])) { + $id = $_GET['delete']; + $stmt = db()->prepare("DELETE FROM branches WHERE id = ?"); + $stmt->execute([$id]); + header('Location: branches.php'); + exit; } // NOW Include header @@ -57,6 +79,11 @@ $companies = db()->query("SELECT * FROM companies")->fetchAll(); +
+ + +
+
@@ -73,8 +100,10 @@ $companies = db()->query("SELECT * FROM companies")->fetchAll(); # + - + + @@ -82,8 +111,26 @@ $companies = db()->query("SELECT * FROM companies")->fetchAll(); + - + + + + + + + @@ -93,4 +140,66 @@ $companies = db()->query("SELECT * FROM companies")->fetchAll();
+ + + + + \ No newline at end of file diff --git a/company_profile.php b/company_profile.php new file mode 100644 index 0000000..420f5c7 --- /dev/null +++ b/company_profile.php @@ -0,0 +1,138 @@ +' . __('Access Denied') . ''; + require_once __DIR__ . '/includes/footer.php'; + exit; +} + +$success = ''; +$error = ''; + +// Get company data +$stmt = db()->query("SELECT * FROM companies LIMIT 1"); +$company = $stmt->fetch(); + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $name_en = $_POST['name_en'] ?? ''; + $name_ar = $_POST['name_ar'] ?? ''; + $email = $_POST['email'] ?? ''; + $phone = $_POST['phone'] ?? ''; + $address_en = $_POST['address_en'] ?? ''; + $address_ar = $_POST['address_ar'] ?? ''; + $vat_number = $_POST['vat_no'] ?? ''; // Keep for compatibility + $ctr_no = $_POST['ctr_no'] ?? ''; + $vat_no = $_POST['vat_no'] ?? ''; + + // Handle Logo Upload + $logo = $company['logo']; + if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) { + $ext = pathinfo($_FILES['logo']['name'], PATHINFO_EXTENSION); + $filename = 'logo_' . time() . '.' . $ext; + $target = 'assets/images/company/' . $filename; + if (move_uploaded_file($_FILES['logo']['tmp_name'], $target)) { + $logo = $target; + } + } + + // Handle Favicon Upload + $favicon = $company['favicon']; + if (isset($_FILES['favicon']) && $_FILES['favicon']['error'] === UPLOAD_ERR_OK) { + $ext = pathinfo($_FILES['favicon']['name'], PATHINFO_EXTENSION); + $filename = 'favicon_' . time() . '.' . $ext; + $target = 'assets/images/company/' . $filename; + if (move_uploaded_file($_FILES['favicon']['tmp_name'], $target)) { + $favicon = $target; + } + } + + try { + $stmt = db()->prepare("UPDATE companies SET name_en = ?, name_ar = ?, logo = ?, favicon = ?, email = ?, phone = ?, address_en = ?, address_ar = ?, vat_number = ?, ctr_no = ?, vat_no = ? WHERE id = ?"); + $stmt->execute([$name_en, $name_ar, $logo, $favicon, $email, $phone, $address_en, $address_ar, $vat_number, $ctr_no, $vat_no, $company['id']]); + $success = __('success_update'); + // Refresh data + $stmt = db()->query("SELECT * FROM companies LIMIT 1"); + $company = $stmt->fetch(); + } catch (Exception $e) { + $error = __('error_update') . ' ' . $e->getMessage(); + } +} + +?> + +
+

+
+ + +
+ + + +
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + Logo + +
+
+ + + + Favicon + +
+
+
+
+ + +
+
+ + +
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/db/migrations/06_profile_additions.sql b/db/migrations/06_profile_additions.sql new file mode 100644 index 0000000..c1b49a3 --- /dev/null +++ b/db/migrations/06_profile_additions.sql @@ -0,0 +1,13 @@ +-- Add logo, favicon and other details to companies table +ALTER TABLE companies +ADD COLUMN logo VARCHAR(255) DEFAULT NULL AFTER name_ar, +ADD COLUMN favicon VARCHAR(255) DEFAULT NULL AFTER logo, +ADD COLUMN email VARCHAR(100) DEFAULT NULL AFTER favicon, +ADD COLUMN phone VARCHAR(20) DEFAULT NULL AFTER email, +ADD COLUMN address_en TEXT DEFAULT NULL AFTER phone, +ADD COLUMN address_ar TEXT DEFAULT NULL AFTER address_en, +ADD COLUMN vat_number VARCHAR(50) DEFAULT NULL AFTER address_ar; + +-- Add profile_picture to users table +ALTER TABLE users +ADD COLUMN profile_picture VARCHAR(255) DEFAULT NULL AFTER email; diff --git a/db/migrations/07_update_currency_precision.sql b/db/migrations/07_update_currency_precision.sql new file mode 100644 index 0000000..d73a331 --- /dev/null +++ b/db/migrations/07_update_currency_precision.sql @@ -0,0 +1,8 @@ +-- Update decimal precision for all monetary columns to 3 decimals +ALTER TABLE prices MODIFY COLUMN price DECIMAL(12, 3) NOT NULL DEFAULT 0.000; +ALTER TABLE orders MODIFY COLUMN total_price DECIMAL(12, 3) NOT NULL DEFAULT 0.000; +ALTER TABLE orders MODIFY COLUMN vat_total DECIMAL(12, 3) DEFAULT 0.000; +ALTER TABLE order_items MODIFY COLUMN unit_price DECIMAL(12, 3) NOT NULL; +ALTER TABLE order_items MODIFY COLUMN vat_amount DECIMAL(12, 3) DEFAULT 0.000; +ALTER TABLE order_items MODIFY COLUMN subtotal DECIMAL(12, 3) NOT NULL; +ALTER TABLE payments MODIFY COLUMN amount DECIMAL(12, 3) NOT NULL; diff --git a/db/migrations/08_add_ctr_vat_no.sql b/db/migrations/08_add_ctr_vat_no.sql new file mode 100644 index 0000000..fca8fe7 --- /dev/null +++ b/db/migrations/08_add_ctr_vat_no.sql @@ -0,0 +1,6 @@ +-- Add CTR No and VAT No to companies table +ALTER TABLE companies ADD COLUMN ctr_no VARCHAR(50) DEFAULT NULL AFTER vat_number; +ALTER TABLE companies ADD COLUMN vat_no VARCHAR(50) DEFAULT NULL AFTER ctr_no; + +-- Migrate existing vat_number to vat_no if any +UPDATE companies SET vat_no = vat_number WHERE vat_no IS NULL AND vat_number IS NOT NULL; diff --git a/db/migrations/09_add_branch_prefix.sql b/db/migrations/09_add_branch_prefix.sql new file mode 100644 index 0000000..7718501 --- /dev/null +++ b/db/migrations/09_add_branch_prefix.sql @@ -0,0 +1,2 @@ +-- Migration 09: Add prefix column to branches table +ALTER TABLE branches ADD COLUMN prefix VARCHAR(3) DEFAULT NULL; diff --git a/includes/header.php b/includes/header.php index c0333eb..935870d 100644 --- a/includes/header.php +++ b/includes/header.php @@ -8,18 +8,34 @@ if (!isset($_SESSION['user_id']) && basename($_SERVER['PHP_SELF']) !== 'login.ph exit; } -$current_user = $_SESSION['user_id'] ?? null; +$current_user_id = $_SESSION['user_id'] ?? null; $current_branch = $_SESSION['branch_id'] ?? null; $current_role = $_SESSION['role'] ?? 'cashier'; +// Fetch Global Company Info +$stmt = db()->query("SELECT * FROM companies LIMIT 1"); +$company_info = $stmt->fetch(); + +// Fetch Current User Info (for profile picture) +$current_user_data = null; +if ($current_user_id) { + $stmt = db()->prepare("SELECT * FROM users WHERE id = ?"); + $stmt->execute([$current_user_id]); + $current_user_data = $stmt->fetch(); +} + ?> - Laundry System - <?= __($title ?? 'dashboard') ?> + <?= htmlspecialchars(is_arabic() ? $company_info['name_ar'] : $company_info['name_en']) ?> - <?= __($title ?? 'dashboard') ?> + + + + @@ -69,6 +85,16 @@ $current_role = $_SESSION['role'] ?? 'cashier'; .lang-switch { font-size: 0.9rem; } + .user-avatar-sm { + width: 32px; + height: 32px; + object-fit: cover; + border-radius: 50%; + } + .dropdown-item i { + margin-right: 0.5rem; + + } @@ -78,8 +104,12 @@ $current_role = $_SESSION['role'] ?? 'cashier';