Autosave: 20260224-030706
This commit is contained in:
parent
9561548a1e
commit
0bec862e83
@ -17,6 +17,9 @@ $pdo->exec("CREATE TABLE IF NOT EXISTS ads_images (
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
|
||||
// Ensure display_layout column exists (for older installations)
|
||||
$pdo->exec("ALTER TABLE ads_images ADD COLUMN IF NOT EXISTS display_layout ENUM('both', 'split', 'fullscreen') DEFAULT 'both' AFTER is_active");
|
||||
|
||||
if (isset($_GET['delete'])) {
|
||||
if (!has_permission('ads_del')) {
|
||||
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3">Access Denied: You do not have permission to delete advertisements.</div>';
|
||||
|
||||
@ -1,89 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
if (!isset($_GET['id'])) {
|
||||
header("Location: customers.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'];
|
||||
$message = '';
|
||||
|
||||
// Fetch Customer
|
||||
$stmt = $pdo->prepare("SELECT * FROM customers WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$customer = $stmt->fetch();
|
||||
|
||||
if (!$customer) {
|
||||
header("Location: customers.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle Update
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$name = $_POST['name'];
|
||||
$email = $_POST['email'];
|
||||
$phone = $_POST['phone'];
|
||||
$address = $_POST['address'];
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE customers SET name = ?, email = ?, phone = ?, address = ? WHERE id = ?");
|
||||
if ($stmt->execute([$name, $email, $phone, $address, $id])) {
|
||||
$message = '<div class="alert alert-success">Customer updated successfully!</div>';
|
||||
// Refresh data
|
||||
$stmt = $pdo->prepare("SELECT * FROM customers WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$customer = $stmt->fetch();
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error updating customer.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="mb-4">
|
||||
<a href="customers.php" class="text-decoration-none text-muted mb-2 d-inline-block"><i class="bi bi-arrow-left"></i> Back to Customers</a>
|
||||
<h2 class="fw-bold mb-0">Edit Customer: <?= htmlspecialchars($customer['name']) ?></h2>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($customer['name']) ?>" 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($customer['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($customer['phone']) ?>">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Loyalty Points</label>
|
||||
<div class="form-control bg-light"><?= intval($customer['points']) ?></div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Total Redemptions</label>
|
||||
<div class="form-control bg-light"><?= intval($customer['loyalty_redemptions_count']) ?></div>
|
||||
</div>
|
||||
<div class="col-md-12 mb-3">
|
||||
<label class="form-label">Address</label>
|
||||
<textarea name="address" class="form-control" rows="3"><?= htmlspecialchars($customer['address']) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="customers.php" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -6,21 +6,40 @@ $pdo = db();
|
||||
|
||||
$message = '';
|
||||
|
||||
// Handle Add Customer
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_customer') {
|
||||
if (!has_permission('customers_add')) {
|
||||
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to add customers.</div>';
|
||||
} else {
|
||||
$name = $_POST['name'];
|
||||
$email = $_POST['email'];
|
||||
$phone = $_POST['phone'];
|
||||
$address = $_POST['address'];
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO customers (name, email, phone, address) VALUES (?, ?, ?, ?)");
|
||||
if ($stmt->execute([$name, $email, $phone, $address])) {
|
||||
$message = '<div class="alert alert-success">Customer added successfully!</div>';
|
||||
// Handle Add/Edit Customer
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
if ($_POST['action'] === 'add_customer') {
|
||||
if (!has_permission('customers_add')) {
|
||||
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to add customers.</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error adding customer.</div>';
|
||||
$name = $_POST['name'];
|
||||
$email = $_POST['email'];
|
||||
$phone = $_POST['phone'];
|
||||
$address = $_POST['address'];
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO customers (name, email, phone, address) VALUES (?, ?, ?, ?)");
|
||||
if ($stmt->execute([$name, $email, $phone, $address])) {
|
||||
$message = '<div class="alert alert-success">Customer added successfully!</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error adding customer.</div>';
|
||||
}
|
||||
}
|
||||
} elseif ($_POST['action'] === 'edit_customer') {
|
||||
if (!has_permission('customers_add')) { // Use customers_add for editing as well
|
||||
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to edit customers.</div>';
|
||||
} else {
|
||||
$id = $_POST['id'];
|
||||
$name = $_POST['name'];
|
||||
$email = $_POST['email'];
|
||||
$phone = $_POST['phone'];
|
||||
$address = $_POST['address'];
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE customers SET name = ?, email = ?, phone = ?, address = ? WHERE id = ?");
|
||||
if ($stmt->execute([$name, $email, $phone, $address, $id])) {
|
||||
$message = '<div class="alert alert-success">Customer updated successfully!</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error updating customer.</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -48,7 +67,7 @@ include 'includes/header.php';
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Customers</h2>
|
||||
<?php if (has_permission('customers_add')): ?>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#customerModal" onclick="openAddModal()">
|
||||
<i class="bi bi-plus-lg"></i> Add Customer
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
@ -87,7 +106,11 @@ include 'includes/header.php';
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<?php if (has_permission('customers_add')): ?>
|
||||
<a href="customer_edit.php?id=<?= $customer['id'] ?>" class="btn btn-sm btn-outline-primary" title="Edit Customer"><i class="bi bi-pencil"></i></a>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#customerModal"
|
||||
onclick="openEditModal(<?= htmlspecialchars(json_encode($customer)) ?>)"
|
||||
title="Edit Customer"><i class="bi bi-pencil"></i></button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (has_permission('customers_del')): ?>
|
||||
@ -112,33 +135,34 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Customer Modal -->
|
||||
<!-- Customer Modal -->
|
||||
<?php if (has_permission('customers_add')): ?>
|
||||
<div class="modal fade" id="addCustomerModal" tabindex="-1">
|
||||
<div class="modal fade" id="customerModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add New Customer</h5>
|
||||
<h5 class="modal-title" id="customerModalTitle">Add New Customer</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<form method="POST" id="customerForm">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" value="add_customer">
|
||||
<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">Name</label>
|
||||
<input type="text" name="name" class="form-control" required>
|
||||
<input type="text" name="name" id="customerName" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control">
|
||||
<input type="email" name="email" id="customerEmail" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Phone</label>
|
||||
<input type="text" name="phone" class="form-control">
|
||||
<input type="text" name="phone" id="customerPhone" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Address</label>
|
||||
<textarea name="address" class="form-control" rows="3"></textarea>
|
||||
<textarea name="address" id="customerAddress" class="form-control" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@ -149,6 +173,25 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openAddModal() {
|
||||
document.getElementById('customerModalTitle').innerText = 'Add New Customer';
|
||||
document.getElementById('customerAction').value = 'add_customer';
|
||||
document.getElementById('customerForm').reset();
|
||||
document.getElementById('customerId').value = '';
|
||||
}
|
||||
|
||||
function openEditModal(customer) {
|
||||
document.getElementById('customerModalTitle').innerText = 'Edit Customer';
|
||||
document.getElementById('customerAction').value = 'edit_customer';
|
||||
document.getElementById('customerId').value = customer.id;
|
||||
document.getElementById('customerName').value = customer.name;
|
||||
document.getElementById('customerEmail').value = customer.email || '';
|
||||
document.getElementById('customerPhone').value = customer.phone || '';
|
||||
document.getElementById('customerAddress').value = customer.address || '';
|
||||
}
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -1,133 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("expenses_edit");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
$expense = null;
|
||||
$message = '';
|
||||
$isEdit = false;
|
||||
|
||||
if ($id) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM expenses WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$expense = $stmt->fetch();
|
||||
if ($expense) {
|
||||
$isEdit = true;
|
||||
} else {
|
||||
header("Location: expenses.php");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$category_id = $_POST['category_id'];
|
||||
$outlet_id = $_POST['outlet_id'];
|
||||
$amount = $_POST['amount'];
|
||||
$description = trim($_POST['description']);
|
||||
$expense_date = $_POST['expense_date'];
|
||||
|
||||
if (empty($category_id) || empty($amount) || empty($expense_date)) {
|
||||
$message = '<div class="alert alert-danger">Category, amount, and date are required.</div>';
|
||||
} else {
|
||||
try {
|
||||
if ($isEdit) {
|
||||
$stmt = $pdo->prepare("UPDATE expenses SET category_id = ?, outlet_id = ?, amount = ?, description = ?, expense_date = ? WHERE id = ?");
|
||||
$stmt->execute([$category_id, $outlet_id, $amount, $description, $expense_date, $id]);
|
||||
$message = '<div class="alert alert-success">Expense updated successfully!</div>';
|
||||
$stmt = $pdo->prepare("SELECT * FROM expenses WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$expense = $stmt->fetch();
|
||||
} else {
|
||||
$stmt = $pdo->prepare("INSERT INTO expenses (category_id, outlet_id, amount, description, expense_date) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$category_id, $outlet_id, $amount, $description, $expense_date]);
|
||||
header("Location: expenses.php?success=created");
|
||||
exit;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$message = '<div class="alert alert-danger">Database error: ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isEdit) {
|
||||
$expense = [
|
||||
'category_id' => $_POST['category_id'] ?? '',
|
||||
'outlet_id' => $_POST['outlet_id'] ?? '',
|
||||
'amount' => $_POST['amount'] ?? '',
|
||||
'description' => $_POST['description'] ?? '',
|
||||
'expense_date' => $_POST['expense_date'] ?? date('Y-m-d')
|
||||
];
|
||||
}
|
||||
|
||||
$expense_categories = $pdo->query("SELECT * FROM expense_categories ORDER BY name ASC")->fetchAll();
|
||||
$outlets = $pdo->query("SELECT * FROM outlets ORDER BY name ASC")->fetchAll();
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="mb-4">
|
||||
<a href="expenses.php" class="text-decoration-none text-muted mb-2 d-inline-block"><i class="bi bi-arrow-left"></i> Back to Expenses</a>
|
||||
<h2 class="fw-bold mb-0"><?= $isEdit ? 'Edit Expense' : 'Add New Expense' ?></h2>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Category <span class="text-danger">*</span></label>
|
||||
<select name="category_id" class="form-select" required>
|
||||
<option value="">Select Category</option>
|
||||
<?php foreach ($expense_categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>" <?= $expense['category_id'] == $cat['id'] ? 'selected' : '' ?>><?= htmlspecialchars($cat['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Outlet <span class="text-danger">*</span></label>
|
||||
<select name="outlet_id" class="form-select" required>
|
||||
<?php foreach ($outlets as $outlet): ?>
|
||||
<option value="<?= $outlet['id'] ?>" <?= $expense['outlet_id'] == $outlet['id'] ? 'selected' : '' ?>><?= htmlspecialchars($outlet['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Amount <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" step="0.01" name="amount" class="form-control" value="<?= htmlspecialchars($expense['amount']) ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Date <span class="text-danger">*</span></label>
|
||||
<input type="date" name="expense_date" class="form-control" value="<?= htmlspecialchars($expense['expense_date']) ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea name="description" class="form-control" rows="3" placeholder="What was this expense for?"><?= htmlspecialchars($expense['description']) ?></textarea>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="expenses.php" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary"><?= $isEdit ? 'Save Changes' : 'Record Expense' ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -6,17 +6,56 @@ $pdo = db();
|
||||
|
||||
$message = '';
|
||||
|
||||
// Handle Create/Update
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
if ($_POST['action'] === 'save_expense') {
|
||||
if (!has_permission('expenses_add') && !has_permission('expenses_edit')) {
|
||||
$message = '<div class="alert alert-danger">Access Denied: You do not have permission.</div>';
|
||||
} else {
|
||||
$id = isset($_POST['id']) ? (int)$_POST['id'] : null;
|
||||
$category_id = $_POST['category_id'];
|
||||
$outlet_id = $_POST['outlet_id'];
|
||||
$amount = $_POST['amount'];
|
||||
$description = trim($_POST['description']);
|
||||
$expense_date = $_POST['expense_date'];
|
||||
|
||||
if (empty($category_id) || empty($amount) || empty($expense_date)) {
|
||||
$message = '<div class="alert alert-danger">Category, amount, and date are required.</div>';
|
||||
} else {
|
||||
try {
|
||||
if ($id) {
|
||||
$stmt = $pdo->prepare("UPDATE expenses SET category_id = ?, outlet_id = ?, amount = ?, description = ?, expense_date = ? WHERE id = ?");
|
||||
$stmt->execute([$category_id, $outlet_id, $amount, $description, $expense_date, $id]);
|
||||
$message = '<div class="alert alert-success">Expense updated successfully!</div>';
|
||||
} else {
|
||||
$stmt = $pdo->prepare("INSERT INTO expenses (category_id, outlet_id, amount, description, expense_date) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$category_id, $outlet_id, $amount, $description, $expense_date]);
|
||||
$message = '<div class="alert alert-success">Expense recorded successfully!</div>';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$message = '<div class="alert alert-danger">Database error: ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Delete
|
||||
if (isset($_GET['delete'])) {
|
||||
if (!has_permission('expenses_del')) {
|
||||
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to delete expenses.</div>';
|
||||
} else {
|
||||
$id = $_GET['delete'];
|
||||
$pdo->prepare("DELETE FROM expenses WHERE id = ?")->execute([$id]);
|
||||
header("Location: expenses.php");
|
||||
header("Location: expenses.php?success=deleted");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['success']) && $_GET['success'] === 'deleted') {
|
||||
$message = '<div class="alert alert-success">Expense deleted successfully!</div>';
|
||||
}
|
||||
|
||||
$expense_categories = $pdo->query("SELECT * FROM expense_categories ORDER BY name")->fetchAll();
|
||||
$outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll();
|
||||
|
||||
@ -65,9 +104,9 @@ include 'includes/header.php';
|
||||
<p class="text-muted mb-0">Track and manage business expenditures</p>
|
||||
</div>
|
||||
<?php if (has_permission('expenses_add')): ?>
|
||||
<a href="expense_edit.php" class="btn btn-primary">
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#expenseModal" onclick="resetExpenseModal()">
|
||||
<i class="bi bi-plus-lg"></i> Add Expense
|
||||
</a>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@ -131,8 +170,13 @@ include 'includes/header.php';
|
||||
<td><?= htmlspecialchars($exp['description']) ?></td>
|
||||
<td class="fw-bold"><?= format_currency($exp['amount']) ?></td>
|
||||
<td class="text-end pe-4">
|
||||
<?php if (has_permission('expenses_add')): ?>
|
||||
<a href="expense_edit.php?id=<?= $exp['id'] ?>" class="btn btn-sm btn-outline-primary me-1"><i class="bi bi-pencil"></i></a>
|
||||
<?php if (has_permission('expenses_edit')): ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary me-1"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#expenseModal"
|
||||
onclick='editExpense(<?= htmlspecialchars(json_encode($exp), ENT_QUOTES, 'UTF-8') ?>)'>
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if (has_permission('expenses_del')): ?>
|
||||
<a href="?delete=<?= $exp['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure?')"><i class="bi bi-trash"></i></a>
|
||||
@ -154,4 +198,94 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expense Modal -->
|
||||
<div class="modal fade" id="expenseModal" tabindex="-1" aria-labelledby="expenseModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="save_expense">
|
||||
<input type="hidden" name="id" id="expense_id">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="expenseModalLabel">Add New Expense</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Category <span class="text-danger">*</span></label>
|
||||
<select name="category_id" id="expense_category_id" class="form-select" required>
|
||||
<option value="">Select Category</option>
|
||||
<?php foreach ($expense_categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Outlet <span class="text-danger">*</span></label>
|
||||
<select name="outlet_id" id="expense_outlet_id" class="form-select" required>
|
||||
<?php foreach ($outlets as $outlet): ?>
|
||||
<option value="<?= $outlet['id'] ?>"><?= htmlspecialchars($outlet['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Amount <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" step="0.01" name="amount" id="expense_amount" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Date <span class="text-danger">*</span></label>
|
||||
<input type="date" name="expense_date" id="expense_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea name="description" id="expense_description" class="form-control" rows="3" placeholder="What was this expense for?"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" id="expenseSubmitBtn">Record Expense</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function resetExpenseModal() {
|
||||
document.getElementById('expenseModalLabel').innerText = 'Add New Expense';
|
||||
document.getElementById('expenseSubmitBtn').innerText = 'Record Expense';
|
||||
document.getElementById('expense_id').value = '';
|
||||
document.getElementById('expense_category_id').value = '';
|
||||
document.getElementById('expense_outlet_id').value = '<?= !empty($outlets) ? $outlets[0]['id'] : '' ?>';
|
||||
document.getElementById('expense_amount').value = '';
|
||||
document.getElementById('expense_date').value = '<?= date('Y-m-d') ?>';
|
||||
document.getElementById('expense_description').value = '';
|
||||
}
|
||||
|
||||
function editExpense(exp) {
|
||||
document.getElementById('expenseModalLabel').innerText = 'Edit Expense';
|
||||
document.getElementById('expenseSubmitBtn').innerText = 'Save Changes';
|
||||
document.getElementById('expense_id').value = exp.id;
|
||||
document.getElementById('expense_category_id').value = exp.category_id;
|
||||
document.getElementById('expense_outlet_id').value = exp.outlet_id;
|
||||
document.getElementById('expense_amount').value = exp.amount;
|
||||
document.getElementById('expense_date').value = exp.expense_date;
|
||||
document.getElementById('expense_description').value = exp.description;
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -357,7 +357,7 @@ function can_view($module) {
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$peopleGroup = ['customers.php', 'customer_edit.php', 'suppliers.php', 'supplier_edit.php', 'loyalty.php'];
|
||||
$peopleGroup = ['customers.php', 'suppliers.php', 'loyalty.php'];
|
||||
$canViewPeopleGroup = can_view('customers') || can_view('suppliers') || can_view('loyalty');
|
||||
if ($canViewPeopleGroup):
|
||||
?>
|
||||
@ -396,7 +396,7 @@ function can_view($module) {
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$financialsGroup = ['expenses.php', 'expense_edit.php', 'expense_categories.php', 'expense_category_edit.php', 'purchases.php', 'purchase_edit.php'];
|
||||
$financialsGroup = ['expenses.php', 'expense_categories.php', 'expense_category_edit.php', 'purchases.php', 'purchase_edit.php'];
|
||||
$canViewFinancialsGroup = can_view('expenses') || can_view('expense_categories') || can_view('purchases');
|
||||
if ($canViewFinancialsGroup):
|
||||
?>
|
||||
@ -417,7 +417,7 @@ function can_view($module) {
|
||||
<?php endif; ?>
|
||||
<?php if (can_view('expenses')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('expenses.php') || isActive('expense_edit.php') ? 'active' : '' ?>" href="expenses.php">
|
||||
<a class="nav-link <?= isActive('expenses.php') ? 'active' : '' ?>" href="expenses.php">
|
||||
<i class="bi bi-cash-stack me-2"></i> Expenses
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -14,7 +14,7 @@ if (!$id) {
|
||||
|
||||
// Fetch Order Details
|
||||
$stmt = $pdo->prepare("SELECT o.*, ot.name as outlet_name, pt.name as payment_type_name,
|
||||
c.name as customer_name, c.phone as customer_phone, c.email as customer_email,
|
||||
c.name as customer_name, c.phone as customer_phone, c.email as customer_email, c.address as customer_address,
|
||||
u.username as created_by_username
|
||||
FROM orders o
|
||||
LEFT JOIN outlets ot ON o.outlet_id = ot.id
|
||||
@ -56,6 +56,7 @@ foreach ($items as $item) {
|
||||
<a href="orders.php" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to List
|
||||
</a>
|
||||
<button onclick="printThermalReceipt()" class="btn btn-warning border-warning"><i class="bi bi-printer-fill"></i> Thermal Receipt</button>
|
||||
<button onclick="window.print()" class="btn btn-light border">
|
||||
<i class="bi bi-printer"></i> Print Receipt
|
||||
</button>
|
||||
@ -222,4 +223,246 @@ foreach ($items as $item) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
|
||||
<script>
|
||||
const COMPANY_SETTINGS = <?= json_encode(get_company_settings()) ?>;
|
||||
const CURRENT_USER = { name: '<?= addslashes($order['created_by_username'] ?? 'System') ?>' };
|
||||
const CURRENT_OUTLET = { name: '<?= addslashes($order['outlet_name'] ?? 'N/A') ?>' };
|
||||
const BASE_URL = '<?= get_base_url() ?>';
|
||||
|
||||
function formatCurrency(amount) {
|
||||
const symbol = COMPANY_SETTINGS.currency_symbol || '$';
|
||||
const decimals = parseInt(COMPANY_SETTINGS.currency_decimals || 2);
|
||||
return symbol + parseFloat(amount).toFixed(decimals);
|
||||
}
|
||||
|
||||
function printThermalReceipt() {
|
||||
const data = {
|
||||
orderId: '<?= $order['id'] ?>',
|
||||
customer: <?= $order['customer_name'] ? json_encode(['name' => $order['customer_name'], 'phone' => $order['customer_phone'], 'address' => $order['customer_address'] ?? '']) : 'null' ?>,
|
||||
items: <?= json_encode(array_map(function($i) {
|
||||
return [
|
||||
'name' => $i['product_name'],
|
||||
'variant_name' => $i['variant_name'],
|
||||
'quantity' => $i['quantity'],
|
||||
'price' => $i['unit_price']
|
||||
];
|
||||
}, $items)) ?>,
|
||||
total: <?= (float)$order['total_amount'] ?>,
|
||||
discount: <?= (float)$order['discount'] ?>,
|
||||
orderType: '<?= $order['order_type'] ?>',
|
||||
tableNumber: '<?= $order['table_number'] ?>',
|
||||
date: '<?= date('M d, Y H:i', strtotime($order['created_at'])) ?>',
|
||||
paymentMethod: '<?= $order['payment_type_name'] ?? 'Unpaid' ?>',
|
||||
loyaltyRedeemed: <?= ($order['discount'] >= $subtotal && $order['discount'] > 0) ? 'true' : 'false' ?>
|
||||
};
|
||||
|
||||
const width = 400;
|
||||
const height = 800;
|
||||
const left = (screen.width - width) / 2;
|
||||
const top = (screen.height - height) / 2;
|
||||
|
||||
const win = window.open('', 'Receipt', `width=${width},height=${height},top=${top},left=${left}`);
|
||||
|
||||
const tr = {
|
||||
'Order': 'الطلب',
|
||||
'Type': 'النوع',
|
||||
'Date': 'التاريخ',
|
||||
'Staff': 'الموظف',
|
||||
'Table': 'طاولة',
|
||||
'Payment': 'الدفع',
|
||||
'ITEM': 'الصنف',
|
||||
'TOTAL': 'المجموع',
|
||||
'Subtotal': 'المجموع الفرعي',
|
||||
'Discount': 'الخصم',
|
||||
'Tax Included': 'شامل الضريبة',
|
||||
'THANK YOU FOR YOUR VISIT!': 'شكراً لزيارتكم!',
|
||||
'Please come again.': 'يرجى زيارتنا مرة أخرى.',
|
||||
'Customer Details': 'تفاصيل العميل',
|
||||
'Tel': 'هاتف',
|
||||
'takeaway': 'سفري',
|
||||
'dine-in': 'محلي',
|
||||
'delivery': 'توصيل',
|
||||
'VAT No': 'الرقم الضريبي',
|
||||
'CTR No': 'رقم السجل التجاري'
|
||||
};
|
||||
|
||||
const itemsHtml = data.items.map(item => `
|
||||
<tr>
|
||||
<td style="padding: 5px 0; border-bottom: 1px solid #eee;">
|
||||
<div style="font-weight: bold;">${item.name}</div>
|
||||
${item.variant_name ? `<div style="font-size: 10px; color: #555;">(${item.variant_name})</div>` : ''}
|
||||
<div style="font-size: 11px;">${item.quantity} x ${formatCurrency(item.price)}</div>
|
||||
</td>
|
||||
<td style="text-align: right; vertical-align: middle; font-weight: bold;">${formatCurrency(item.quantity * item.price)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
const customerHtml = data.customer ? `
|
||||
<div style="margin-bottom: 10px; border: 1px solid #eee; padding: 8px; border-radius: 4px;">
|
||||
<div style="font-weight: bold; text-transform: uppercase; font-size: 10px; color: #666; margin-bottom: 3px;">
|
||||
<span style="float: left;">Customer Details</span>
|
||||
<span style="float: right;">${tr['Customer Details']}</span>
|
||||
<div style="clear: both;"></div>
|
||||
</div>
|
||||
<div style="font-weight: bold;">${data.customer.name}</div>
|
||||
${data.customer.phone ? `<div>Tel: ${data.customer.phone}</div>` : ''}
|
||||
${data.customer.address ? `<div style="font-size: 11px;">${data.customer.address}</div>` : ''}
|
||||
</div>
|
||||
` : '';
|
||||
|
||||
const tableHtml = data.tableNumber && data.orderType === 'dine-in' ? `
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span><strong>Table:</strong> ${data.tableNumber}</span>
|
||||
<span><strong>${tr['Table']}:</strong> ${data.tableNumber}</span>
|
||||
</div>` : '';
|
||||
|
||||
const paymentHtml = data.paymentMethod ? `
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span><strong>Payment:</strong> ${data.paymentMethod}</span>
|
||||
<span><strong>${tr['Payment']}:</strong> ${data.paymentMethod}</span>
|
||||
</div>` : '';
|
||||
|
||||
const loyaltyHtml = data.loyaltyRedeemed ? `<div style="color: #d63384; font-weight: bold; margin: 5px 0; text-align: center;">* Loyalty Reward Applied *</div>` : '';
|
||||
|
||||
const vatRate = parseFloat(COMPANY_SETTINGS.vat_rate) || 0;
|
||||
const subtotal = data.total + data.discount;
|
||||
const vatAmount = vatRate > 0 ? (data.total * (vatRate / (100 + vatRate))) : 0;
|
||||
|
||||
const logoHtml = COMPANY_SETTINGS.logo_url ? `<img src="${BASE_URL}${COMPANY_SETTINGS.logo_url}" style="max-height: 80px; max-width: 150px; margin-bottom: 10px;">` : '';
|
||||
|
||||
const html = `
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<title>Receipt #${data.orderId}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
padding: 15px;
|
||||
color: #000;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.header { text-align: center; margin-bottom: 15px; }
|
||||
.header h2 { margin: 0 0 5px 0; font-size: 20px; font-weight: bold; }
|
||||
.header div { font-size: 12px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
.divider { border-bottom: 2px dashed #000; margin: 10px 0; }
|
||||
.thick-divider { border-bottom: 2px solid #000; margin: 10px 0; }
|
||||
.totals td { padding: 3px 0; }
|
||||
.footer { text-align: center; margin-top: 25px; font-size: 12px; }
|
||||
.order-info { font-size: 11px; margin-bottom: 10px; }
|
||||
.order-info-row { display: flex; justify-content: space-between; margin-bottom: 2px; }
|
||||
.rtl { direction: rtl; unicode-bidi: embed; }
|
||||
@media print {
|
||||
body { width: 80mm; padding: 5mm; }
|
||||
@page { margin: 0; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
${logoHtml}
|
||||
<h2>${COMPANY_SETTINGS.company_name}</h2>
|
||||
<div style="font-weight: bold;">${CURRENT_OUTLET.name}</div>
|
||||
<div>${COMPANY_SETTINGS.address}</div>
|
||||
<div>Tel: ${COMPANY_SETTINGS.phone}</div>
|
||||
${COMPANY_SETTINGS.vat_number ? `<div style="margin-top: 4px;">VAT No / الرقم الضريبي: ${COMPANY_SETTINGS.vat_number}</div>` : ''}
|
||||
${COMPANY_SETTINGS.ctr_number ? `<div>CTR No / رقم السجل: ${COMPANY_SETTINGS.ctr_number}</div>` : ''}
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="order-info">
|
||||
<div class="order-info-row">
|
||||
<span><strong>Order:</strong> #${data.orderId}</span>
|
||||
<span><strong>${tr['Order']}:</strong> #${data.orderId}</span>
|
||||
</div>
|
||||
<div class="order-info-row">
|
||||
<span><strong>Type:</strong> ${data.orderType.toUpperCase()}</span>
|
||||
<span><strong>${tr['Type']}:</strong> ${tr[data.orderType] || data.orderType}</span>
|
||||
</div>
|
||||
<div class="order-info-row">
|
||||
<span><strong>Date:</strong> ${data.date}</span>
|
||||
<span><strong>${tr['Date']}:</strong> ${data.date}</span>
|
||||
</div>
|
||||
<div class="order-info-row">
|
||||
<span><strong>Staff:</strong> ${CURRENT_USER.name}</span>
|
||||
<span><strong>${tr['Staff']}:</strong> ${CURRENT_USER.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${tableHtml}
|
||||
${paymentHtml}
|
||||
${loyaltyHtml}
|
||||
|
||||
<div class="thick-divider"></div>
|
||||
${customerHtml}
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: left; padding-bottom: 5px;">
|
||||
ITEM / الصنف
|
||||
</th>
|
||||
<th style="text-align: right; padding-bottom: 5px;">
|
||||
TOTAL / المجموع
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${itemsHtml}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="totals">
|
||||
<table style="width: 100%">
|
||||
<tr>
|
||||
<td>Subtotal / ${tr['Subtotal']}</td>
|
||||
<td style="text-align: right">${formatCurrency(subtotal)}</td>
|
||||
</tr>
|
||||
${data.discount > 0 ? `
|
||||
<tr>
|
||||
<td>Discount / ${tr['Discount']}</td>
|
||||
<td style="text-align: right">-${formatCurrency(data.discount)}</td>
|
||||
</tr>` : ''}
|
||||
${vatRate > 0 ? `
|
||||
<tr>
|
||||
<td style="font-size: 11px;">Tax Incl. (${vatRate}%) / ${tr['Tax Included']}</td>
|
||||
<td style="text-align: right">${formatCurrency(vatAmount)}</td>
|
||||
</tr>` : ''}
|
||||
<tr style="font-weight: bold; font-size: 18px;">
|
||||
<td style="padding-top: 10px;">TOTAL / ${tr['TOTAL']}</td>
|
||||
<td style="text-align: right; padding-top: 10px;">${formatCurrency(data.total)}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="thick-divider"></div>
|
||||
|
||||
<div class="footer">
|
||||
<div style="font-weight: bold; font-size: 14px; margin-bottom: 2px;">THANK YOU FOR YOUR VISIT!</div>
|
||||
<div style="font-weight: bold; font-size: 14px; margin-bottom: 5px;" class="rtl">${tr['THANK YOU FOR YOUR VISIT!']}</div>
|
||||
<div>Please come again.</div>
|
||||
<div class="rtl">${tr['Please come again.']}</div>
|
||||
${COMPANY_SETTINGS.email ? `<div style="margin-top: 5px; font-size: 10px;">${COMPANY_SETTINGS.email}</div>` : ''}
|
||||
<div style="margin-top: 15px; font-size: 10px; color: #888;">Powered by Flatlogic POS</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
window.print();
|
||||
setTimeout(function() { window.close(); }, 1500);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
win.document.write(html);
|
||||
win.document.close();
|
||||
}
|
||||
</script>
|
||||
@ -42,37 +42,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
// Update purchase
|
||||
// Before updating, if it was completed and now it's not, we might need to revert stock.
|
||||
// But for simplicity, we'll only increase stock when status changes TO completed.
|
||||
$old_status = $purchase['status'];
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE purchases SET supplier_id = ?, purchase_date = ?, status = ?, notes = ?, total_amount = ? WHERE id = ?");
|
||||
$stmt->execute([$supplier_id, $purchase_date, $status, $notes, $total_amount, $id]);
|
||||
|
||||
// Re-fetch items to handle stock reversal if needed
|
||||
$stmt = $pdo->prepare("SELECT * FROM purchase_items WHERE purchase_id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$old_items = $stmt->fetchAll();
|
||||
|
||||
// If status was completed, revert old stock
|
||||
if ($old_status === 'completed') {
|
||||
foreach ($old_items as $oi) {
|
||||
$pdo->prepare("UPDATE products SET stock_quantity = stock_quantity - ? WHERE id = ?")
|
||||
->execute([$oi['quantity'], $oi['product_id']]);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete old items
|
||||
$pdo->prepare("DELETE FROM purchase_items WHERE purchase_id = ?")->execute([$id]);
|
||||
} else {
|
||||
// Create purchase
|
||||
$stmt = $pdo->prepare("INSERT INTO purchases (supplier_id, purchase_date, status, notes, total_amount) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$supplier_id, $purchase_date, $status, $notes, $total_amount]);
|
||||
$id = $pdo->lastInsertId();
|
||||
}
|
||||
|
||||
// Insert items
|
||||
foreach ($product_ids as $index => $pid) {
|
||||
$qty = $quantities[$index];
|
||||
$cost = $cost_prices[$index];
|
||||
@ -81,7 +71,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$stmt = $pdo->prepare("INSERT INTO purchase_items (purchase_id, product_id, quantity, cost_price, total_price) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$id, $pid, $qty, $cost, $total_item_price]);
|
||||
|
||||
// If status is completed, increase stock
|
||||
if ($status === 'completed') {
|
||||
$pdo->prepare("UPDATE products SET stock_quantity = stock_quantity + ?, cost_price = ? WHERE id = ?")
|
||||
->execute([$qty, $cost, $pid]);
|
||||
@ -98,7 +87,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
|
||||
$suppliers = $pdo->query("SELECT * FROM suppliers ORDER BY name")->fetchAll();
|
||||
$products = $pdo->query("SELECT * FROM products ORDER BY name")->fetchAll();
|
||||
$products = $pdo->query("SELECT id, name, cost_price FROM products ORDER BY name")->fetchAll();
|
||||
$products_json = json_encode($products);
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
@ -114,53 +104,56 @@ include 'includes/header.php';
|
||||
|
||||
<form method="POST" id="purchaseForm">
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-4">
|
||||
<div class="card border-0 shadow-sm" style="border-radius: 15px;">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-4">General Information</h5>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small fw-bold">SUPPLIER</label>
|
||||
<select name="supplier_id" class="form-select" style="border-radius: 10px;">
|
||||
<option value="">Direct Purchase / None</option>
|
||||
<?php foreach ($suppliers as $s): ?>
|
||||
<option value="<?= $s['id'] ?>" <?= ($purchase && $purchase['supplier_id'] == $s['id']) ? 'selected' : '' ?>><?= htmlspecialchars($s['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small fw-bold">PURCHASE DATE</label>
|
||||
<input type="date" name="purchase_date" class="form-control" value="<?= $purchase['purchase_date'] ?? date('Y-m-d') ?>" required style="border-radius: 10px;">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small fw-bold">STATUS</label>
|
||||
<select name="status" class="form-select" style="border-radius: 10px;">
|
||||
<option value="pending" <?= ($purchase && $purchase['status'] == 'pending') ? 'selected' : '' ?>>Pending</option>
|
||||
<option value="completed" <?= ($purchase && $purchase['status'] == 'completed') ? 'selected' : '' ?>>Completed (Updates Stock)</option>
|
||||
<option value="cancelled" <?= ($purchase && $purchase['status'] == 'cancelled') ? 'selected' : '' ?>>Cancelled</option>
|
||||
</select>
|
||||
<div class="form-text mt-2 small text-info">Stock is only updated when status is set to <strong>Completed</strong>.</div>
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<label class="form-label text-muted small fw-bold">NOTES</label>
|
||||
<textarea name="notes" class="form-control" rows="3" placeholder="Reference No, delivery details..." style="border-radius: 10px;"><?= htmlspecialchars($purchase['notes'] ?? '') ?></textarea>
|
||||
<h5 class="fw-bold mb-4">Purchase Details</h5>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small fw-bold">SUPPLIER</label>
|
||||
<select name="supplier_id" class="form-select">
|
||||
<option value="">Direct Purchase / None</option>
|
||||
<?php foreach ($suppliers as $s): ?>
|
||||
<option value="<?= $s['id'] ?>" <?= ($purchase && $purchase['supplier_id'] == $s['id']) ? 'selected' : '' ?>><?= htmlspecialchars($s['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small fw-bold">PURCHASE DATE</label>
|
||||
<input type="date" name="purchase_date" class="form-control" value="<?= $purchase['purchase_date'] ?? date('Y-m-d') ?>" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small fw-bold">STATUS</label>
|
||||
<select name="status" class="form-select">
|
||||
<option value="pending" <?= ($purchase && $purchase['status'] == 'pending') ? 'selected' : '' ?>>Pending</option>
|
||||
<option value="completed" <?= ($purchase && $purchase['status'] == 'completed') ? 'selected' : '' ?>>Completed (Updates Stock)</option>
|
||||
<option value="cancelled" <?= ($purchase && $purchase['status'] == 'cancelled') ? 'selected' : '' ?>>Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label text-muted small fw-bold">NOTES</label>
|
||||
<textarea name="notes" class="form-control" rows="2" placeholder="Reference No, delivery details..."><?= htmlspecialchars($purchase['notes'] ?? '') ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8">
|
||||
<div class="card border-0 shadow-sm" style="border-radius: 15px;">
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="fw-bold mb-0">Purchase Items</h5>
|
||||
<button type="button" class="btn btn-soft-primary btn-sm" id="addItemBtn">
|
||||
<i class="bi bi-plus-lg me-1"></i> Add Item
|
||||
</button>
|
||||
<h5 class="fw-bold mb-0">Products to Purchase</h5>
|
||||
<div class="position-relative" style="width: 350px;">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-white border-end-0 text-muted"><i class="bi bi-search"></i></span>
|
||||
<input type="text" id="productSearch" class="form-control border-start-0" placeholder="Search products to add...">
|
||||
</div>
|
||||
<div id="searchResults" class="dropdown-menu w-100 shadow-sm mt-1" style="max-height: 300px; overflow-y: auto;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle" id="itemsTable">
|
||||
<thead class="text-muted small fw-bold">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 45%;">PRODUCT</th>
|
||||
<th style="width: 20%;">QTY</th>
|
||||
@ -170,40 +163,22 @@ include 'includes/header.php';
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="itemsBody">
|
||||
<?php if (empty($items)): ?>
|
||||
<tr class="item-row">
|
||||
<td>
|
||||
<select name="product_id[]" class="form-select product-select" required>
|
||||
<option value="">Select Product</option>
|
||||
<?php foreach ($products as $p): ?>
|
||||
<option value="<?= $p['id'] ?>" data-price="<?= $p['cost_price'] ?>"><?= htmlspecialchars($p['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="number" name="quantity[]" class="form-control qty-input" min="1" value="1" required></td>
|
||||
<td><input type="number" step="0.01" name="cost_price[]" class="form-control cost-input" value="0.00" required></td>
|
||||
<td class="text-end fw-bold row-total"><?= format_currency(0) ?></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php if (!empty($items)): ?>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<tr class="item-row">
|
||||
<tr class="item-row" data-product-id="<?= $item['product_id'] ?>">
|
||||
<td>
|
||||
<select name="product_id[]" class="form-select product-select" required>
|
||||
<?php foreach ($products as $p): ?>
|
||||
<option value="<?= $p['id'] ?>" data-price="<?= $p['cost_price'] ?>" <?= $item['product_id'] == $p['id'] ? 'selected' : '' ?>><?= htmlspecialchars($p['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="fw-bold"><?= htmlspecialchars($item['product_name']) ?></div>
|
||||
<input type="hidden" name="product_id[]" value="<?= $item['product_id'] ?>">
|
||||
</td>
|
||||
<td><input type="number" name="quantity[]" class="form-control qty-input" min="1" value="<?= $item['quantity'] ?>" required></td>
|
||||
<td><input type="number" step="0.01" name="cost_price[]" class="form-control cost-input" value="<?= $item['cost_price'] ?>" required></td>
|
||||
<td class="text-end fw-bold row-total"><?= format_currency($item['total_price']) ?></td>
|
||||
<td><button type="button" class="btn btn-link text-danger remove-item p-0"><i class="bi bi-x-circle-fill"></i></button></td>
|
||||
<td class="text-end"><button type="button" class="btn btn-link text-danger remove-item p-0"><i class="bi bi-trash"></i></button></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tfoot id="tableFooter" class="<?= empty($items) ? 'd-none' : '' ?>">
|
||||
<tr>
|
||||
<td colspan="3" class="text-end fw-bold pt-4">Grand Total:</td>
|
||||
<td class="text-end fw-bold pt-4 text-primary fs-5" id="grandTotal"><?= format_currency($purchase['total_amount'] ?? 0) ?></td>
|
||||
@ -211,12 +186,17 @@ include 'includes/header.php';
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<div id="noItemsMessage" class="text-center py-5 text-muted <?= !empty($items) ? 'd-none' : '' ?>">
|
||||
<div class="mb-2 display-6"><i class="bi bi-search"></i></div>
|
||||
<div>Use the search bar above to add products to this purchase.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<button type="submit" class="btn btn-primary btn-lg px-5 shadow-sm" style="border-radius: 10px;">
|
||||
<button type="submit" class="btn btn-primary btn-lg px-5 shadow-sm">
|
||||
<i class="bi bi-check-lg me-1"></i> Save Purchase
|
||||
</button>
|
||||
</div>
|
||||
@ -226,9 +206,13 @@ include 'includes/header.php';
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const products = <?= $products_json ?>;
|
||||
const productSearch = document.getElementById('productSearch');
|
||||
const searchResults = document.getElementById('searchResults');
|
||||
const itemsBody = document.getElementById('itemsBody');
|
||||
const addItemBtn = document.getElementById('addItemBtn');
|
||||
const grandTotalElement = document.getElementById('grandTotal');
|
||||
const noItemsMessage = document.getElementById('noItemsMessage');
|
||||
const tableFooter = document.getElementById('tableFooter');
|
||||
|
||||
function formatCurrency(amount) {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
|
||||
@ -236,7 +220,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
function calculateTotals() {
|
||||
let grandTotal = 0;
|
||||
document.querySelectorAll('.item-row').forEach(row => {
|
||||
const rows = document.querySelectorAll('.item-row');
|
||||
rows.forEach(row => {
|
||||
const qty = parseFloat(row.querySelector('.qty-input').value) || 0;
|
||||
const cost = parseFloat(row.querySelector('.cost-input').value) || 0;
|
||||
const total = qty * cost;
|
||||
@ -244,38 +229,98 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
grandTotal += total;
|
||||
});
|
||||
grandTotalElement.textContent = formatCurrency(grandTotal);
|
||||
|
||||
if (rows.length > 0) {
|
||||
noItemsMessage.classList.add('d-none');
|
||||
tableFooter.classList.remove('d-none');
|
||||
} else {
|
||||
noItemsMessage.classList.remove('d-none');
|
||||
tableFooter.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
addItemBtn.addEventListener('click', function() {
|
||||
const firstRow = document.querySelector('.item-row');
|
||||
const newRow = firstRow.cloneNode(true);
|
||||
productSearch.addEventListener('input', function() {
|
||||
const query = this.value.toLowerCase().trim();
|
||||
searchResults.innerHTML = '';
|
||||
|
||||
// Reset values
|
||||
newRow.querySelector('.qty-input').value = 1;
|
||||
newRow.querySelector('.cost-input').value = 0.00;
|
||||
newRow.querySelector('.product-select').value = "";
|
||||
newRow.querySelector('.row-total').textContent = formatCurrency(0);
|
||||
|
||||
// Add remove button if not exists
|
||||
if (!newRow.querySelector('.remove-item')) {
|
||||
const td = document.createElement('td');
|
||||
td.innerHTML = '<button type="button" class="btn btn-link text-danger remove-item p-0"><i class="bi bi-x-circle-fill"></i></button>';
|
||||
newRow.appendChild(td);
|
||||
if (query.length < 1) {
|
||||
searchResults.classList.remove('show');
|
||||
return;
|
||||
}
|
||||
|
||||
itemsBody.appendChild(newRow);
|
||||
const filtered = products.filter(p => p.name.toLowerCase().includes(query));
|
||||
|
||||
if (filtered.length > 0) {
|
||||
filtered.forEach(p => {
|
||||
const item = document.createElement('a');
|
||||
item.className = 'dropdown-item py-2 border-bottom';
|
||||
item.href = '#';
|
||||
item.innerHTML = `
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="fw-bold">${p.name}</div>
|
||||
<small class="text-muted">Cost: ${formatCurrency(p.cost_price)}</small>
|
||||
</div>
|
||||
<i class="bi bi-plus-circle text-primary"></i>
|
||||
</div>
|
||||
`;
|
||||
item.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
addProductToTable(p);
|
||||
productSearch.value = '';
|
||||
searchResults.classList.remove('show');
|
||||
});
|
||||
searchResults.appendChild(item);
|
||||
});
|
||||
searchResults.classList.add('show');
|
||||
} else {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'dropdown-item disabled text-center py-3';
|
||||
item.textContent = 'No products found';
|
||||
searchResults.appendChild(item);
|
||||
searchResults.classList.add('show');
|
||||
}
|
||||
});
|
||||
|
||||
itemsBody.addEventListener('change', function(e) {
|
||||
if (e.target.classList.contains('product-select')) {
|
||||
const selectedOption = e.target.options[e.target.selectedIndex];
|
||||
const price = selectedOption.dataset.price || 0;
|
||||
const row = e.target.closest('.item-row');
|
||||
row.querySelector('.cost-input').value = price;
|
||||
calculateTotals();
|
||||
// Close search results on click outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!productSearch.contains(e.target) && !searchResults.contains(e.target)) {
|
||||
searchResults.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
function addProductToTable(product) {
|
||||
// Check if already in table
|
||||
const existingRow = Array.from(document.querySelectorAll('.item-row')).find(row => row.dataset.productId == product.id);
|
||||
if (existingRow) {
|
||||
const qtyInput = existingRow.querySelector('.qty-input');
|
||||
qtyInput.value = parseInt(qtyInput.value) + 1;
|
||||
calculateTotals();
|
||||
return;
|
||||
}
|
||||
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'item-row';
|
||||
row.dataset.productId = product.id;
|
||||
row.innerHTML = `
|
||||
<td>
|
||||
<div class="fw-bold">${product.name}</div>
|
||||
<input type="hidden" name="product_id[]" value="${product.id}">
|
||||
</td>
|
||||
<td><input type="number" name="quantity[]" class="form-control qty-input" min="1" value="1" required></td>
|
||||
<td><input type="number" step="0.01" name="cost_price[]" class="form-control cost-input" value="${product.cost_price}" required></td>
|
||||
<td class="text-end fw-bold row-total">${format_currency(product.cost_price)}</td>
|
||||
<td class="text-end"><button type="button" class="btn btn-link text-danger remove-item p-0"><i class="bi bi-trash"></i></button></td>
|
||||
`;
|
||||
itemsBody.appendChild(row);
|
||||
calculateTotals();
|
||||
}
|
||||
|
||||
// Helper to call from JS
|
||||
function format_currency(amount) {
|
||||
return formatCurrency(amount);
|
||||
}
|
||||
|
||||
itemsBody.addEventListener('input', function(e) {
|
||||
if (e.target.classList.contains('qty-input') || e.target.classList.contains('cost-input')) {
|
||||
calculateTotals();
|
||||
@ -284,35 +329,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
itemsBody.addEventListener('click', function(e) {
|
||||
if (e.target.closest('.remove-item')) {
|
||||
const rows = document.querySelectorAll('.item-row');
|
||||
if (rows.length > 1) {
|
||||
e.target.closest('.item-row').remove();
|
||||
calculateTotals();
|
||||
} else {
|
||||
alert("At least one item is required.");
|
||||
}
|
||||
e.target.closest('.item-row').remove();
|
||||
calculateTotals();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.btn-soft-primary {
|
||||
background-color: rgba(13, 110, 253, 0.1);
|
||||
color: #0d6efd;
|
||||
border: none;
|
||||
}
|
||||
.btn-soft-primary:hover {
|
||||
background-color: #0d6efd;
|
||||
color: #fff;
|
||||
}
|
||||
.form-control, .form-select {
|
||||
border-color: #e9ecef;
|
||||
.dropdown-menu.show {
|
||||
display: block;
|
||||
}
|
||||
.form-control:focus, .form-select:focus {
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.05);
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
.dropdown-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../includes/functions.php";
|
||||
require_permission("purchases_view"); // You might need to add this permission to users table if permissions are enforced strictly
|
||||
require_permission("purchases_view");
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
@ -11,7 +11,6 @@ if (isset($_GET['delete'])) {
|
||||
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to delete purchases.</div>';
|
||||
} else {
|
||||
$id = $_GET['delete'];
|
||||
// Logic to revert stock could be added here, but usually deletions are just deletions.
|
||||
$pdo->prepare("DELETE FROM purchases WHERE id = ?")->execute([$id]);
|
||||
header("Location: purchases.php");
|
||||
exit;
|
||||
@ -64,7 +63,7 @@ include 'includes/header.php';
|
||||
<p class="text-muted mb-0">Manage inventory restocks and supplier invoices</p>
|
||||
</div>
|
||||
<?php if (has_permission('purchases_add')): ?>
|
||||
<a href="purchase_edit.php" class="btn btn-primary btn-lg shadow-sm" style="border-radius: 10px;">
|
||||
<a href="purchase_edit.php" class="btn btn-primary shadow-sm">
|
||||
<i class="bi bi-plus-lg me-1"></i> New Purchase
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
@ -72,17 +71,17 @@ include 'includes/header.php';
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm mb-4" style="border-radius: 15px;">
|
||||
<div class="card-body p-4">
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<form method="GET" class="row g-3 align-items-center">
|
||||
<div class="col-md-4">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-white border-end-0 text-muted ps-3" style="border-radius: 10px 0 0 10px;"><i class="bi bi-search"></i></span>
|
||||
<input type="text" name="search" class="form-control border-start-0 ps-0" placeholder="Search notes..." value="<?= htmlspecialchars($search) ?>" style="border-radius: 0 10px 10px 0;">
|
||||
<span class="input-group-text bg-white border-end-0 text-muted"><i class="bi bi-search"></i></span>
|
||||
<input type="text" name="search" class="form-control border-start-0" placeholder="Search notes..." value="<?= htmlspecialchars($search) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="supplier_filter" class="form-select" style="border-radius: 10px;">
|
||||
<select name="supplier_filter" class="form-select">
|
||||
<option value="">All Suppliers</option>
|
||||
<?php foreach ($suppliers as $s): ?>
|
||||
<option value="<?= $s['id'] ?>" <?= $supplier_filter == $s['id'] ? 'selected' : '' ?>><?= htmlspecialchars($s['name']) ?></option>
|
||||
@ -90,7 +89,7 @@ include 'includes/header.php';
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="status_filter" class="form-select" style="border-radius: 10px;">
|
||||
<select name="status_filter" class="form-select">
|
||||
<option value="">All Status</option>
|
||||
<option value="pending" <?= $status_filter == 'pending' ? 'selected' : '' ?>>Pending</option>
|
||||
<option value="completed" <?= $status_filter == 'completed' ? 'selected' : '' ?>>Completed</option>
|
||||
@ -98,20 +97,20 @@ include 'includes/header.php';
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary px-4 w-100" style="border-radius: 10px;">Filter</button>
|
||||
<button type="submit" class="btn btn-primary px-4 w-100">Filter</button>
|
||||
<?php if ($search || $supplier_filter || $status_filter): ?>
|
||||
<a href="purchases.php" class="btn btn-light text-muted px-3" style="border-radius: 10px;"><i class="bi bi-x-lg"></i></a>
|
||||
<a href="purchases.php" class="btn btn-light text-muted px-3"><i class="bi bi-x-lg"></i></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm" style="border-radius: 15px; overflow: hidden;">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="friendly-table mb-0">
|
||||
<thead>
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="ps-4">Date</th>
|
||||
<th>Supplier</th>
|
||||
@ -139,18 +138,18 @@ include 'includes/header.php';
|
||||
<span class="badge <?= $status_badge ?> rounded-pill px-3"><?= ucfirst($p['status']) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-bold text-primary" style="font-size: 1.1rem;"><?= format_currency($p['total_amount']) ?></div>
|
||||
<div class="fw-bold text-primary"><?= format_currency($p['total_amount']) ?></div>
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
<div class="d-inline-flex gap-2">
|
||||
<?php if (has_permission('purchases_add')): ?>
|
||||
<a href="purchase_edit.php?id=<?= $p['id'] ?>" class="btn-icon-soft edit" title="Edit/View">
|
||||
<i class="bi bi-eye-fill"></i>
|
||||
<a href="purchase_edit.php?id=<?= $p['id'] ?>" class="btn btn-sm btn-outline-primary" title="Edit/View">
|
||||
<i class="bi bi-pencil-square me-1"></i> Edit
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if (has_permission('purchases_del')): ?>
|
||||
<a href="?delete=<?= $p['id'] ?>" class="btn-icon-soft delete" onclick="return confirm('Are you sure you want to delete this purchase record?')" title="Delete">
|
||||
<i class="bi bi-trash-fill"></i>
|
||||
<a href="?delete=<?= $p['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this purchase record?')" title="Delete">
|
||||
<i class="bi bi-trash me-1"></i> Delete
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@ -8,39 +8,70 @@ if (function_exists('require_permission')) {
|
||||
|
||||
$pdo = db();
|
||||
$tab = $_GET['tab'] ?? 'staff';
|
||||
$date_from = $_GET['date_from'] ?? '';
|
||||
$date_to = $_GET['date_to'] ?? '';
|
||||
|
||||
$params = [];
|
||||
$where_clauses = [];
|
||||
|
||||
if ($date_from) {
|
||||
$where_clauses[] = "r.created_at >= :date_from";
|
||||
$params['date_from'] = $date_from . ' 00:00:00';
|
||||
}
|
||||
if ($date_to) {
|
||||
$where_clauses[] = "r.created_at <= :date_to";
|
||||
$params['date_to'] = $date_to . ' 23:59:59';
|
||||
}
|
||||
|
||||
$where_sql = !empty($where_clauses) ? "WHERE " . implode(" AND ", $where_clauses) : "";
|
||||
|
||||
// Fetch Staff summary stats
|
||||
$summaryStmt = $pdo->query("
|
||||
$summaryQuery = "
|
||||
SELECT u.id, u.full_name, u.username, u.profile_pic,
|
||||
AVG(r.rating) as avg_rating, COUNT(r.id) as total_ratings
|
||||
FROM users u
|
||||
JOIN staff_ratings r ON u.id = r.user_id
|
||||
$where_sql
|
||||
GROUP BY u.id
|
||||
ORDER BY avg_rating DESC
|
||||
");
|
||||
";
|
||||
$summaryStmt = $pdo->prepare($summaryQuery);
|
||||
$summaryStmt->execute($params);
|
||||
$summaries = $summaryStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch Service summary stats
|
||||
$serviceSummaryStmt = $pdo->query("
|
||||
SELECT AVG(rating) as avg_rating, COUNT(id) as total_ratings FROM service_ratings
|
||||
");
|
||||
$serviceWhereSql = str_replace('r.created_at', 'created_at', $where_sql);
|
||||
$serviceParams = $params;
|
||||
|
||||
$serviceSummaryQuery = "SELECT AVG(rating) as avg_rating, COUNT(id) as total_ratings FROM service_ratings $serviceWhereSql";
|
||||
$serviceSummaryStmt = $pdo->prepare($serviceSummaryQuery);
|
||||
$serviceSummaryStmt->execute($serviceParams);
|
||||
$serviceSummary = $serviceSummaryStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($tab === 'service') {
|
||||
$query = "SELECT * FROM service_ratings ORDER BY created_at DESC";
|
||||
$query = "SELECT * FROM service_ratings $serviceWhereSql ORDER BY created_at DESC";
|
||||
$list_params = $serviceParams;
|
||||
} else {
|
||||
$query = "
|
||||
SELECT r.*, u.full_name, u.username, u.profile_pic
|
||||
FROM staff_ratings r
|
||||
JOIN users u ON r.user_id = u.id
|
||||
$where_sql
|
||||
ORDER BY r.created_at DESC
|
||||
";
|
||||
$list_params = $params;
|
||||
}
|
||||
|
||||
$pagination = paginate_query($pdo, $query);
|
||||
$pagination = paginate_query($pdo, $query, $list_params);
|
||||
$ratings = $pagination['data'];
|
||||
?>
|
||||
|
||||
<style>
|
||||
.extra-small { font-size: 0.7rem; }
|
||||
.table-ratings { font-size: 0.8rem; }
|
||||
.table-ratings th { font-weight: 600; text-transform: uppercase; font-size: 0.7rem; letter-spacing: 0.03em; }
|
||||
</style>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="h3 mb-0 text-gray-800">Ratings & Feedback</h2>
|
||||
<div class="d-flex gap-2">
|
||||
@ -53,14 +84,38 @@ $ratings = $pagination['data'];
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm rounded-4 mb-4">
|
||||
<div class="card-body py-3">
|
||||
<form method="GET" class="row g-3 align-items-end">
|
||||
<input type="hidden" name="tab" value="<?= htmlspecialchars($tab) ?>">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold mb-1">From Date</label>
|
||||
<input type="date" name="date_from" class="form-control form-control-sm" value="<?= htmlspecialchars($date_from) ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold mb-1">To Date</label>
|
||||
<input type="date" name="date_to" class="form-control form-control-sm" value="<?= htmlspecialchars($date_to) ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button type="submit" class="btn btn-primary btn-sm px-3">
|
||||
<i class="bi bi-filter"></i> Filter
|
||||
</button>
|
||||
<a href="ratings.php?tab=<?= htmlspecialchars($tab) ?>" class="btn btn-light btn-sm px-3 border">
|
||||
<i class="bi bi-arrow-clockwise"></i> Reset
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-pills mb-4 bg-white p-2 rounded-4 shadow-sm d-inline-flex">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link rounded-pill <?= $tab === 'staff' ? 'active' : '' ?>" href="?tab=staff">
|
||||
<a class="nav-link rounded-pill <?= $tab === 'staff' ? 'active' : '' ?>" href="?tab=staff&date_from=<?= $date_from ?>&date_to=<?= $date_to ?>">
|
||||
<i class="bi bi-people me-1"></i> Staff Performance
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link rounded-pill <?= $tab === 'service' ? 'active' : '' ?>" href="?tab=service">
|
||||
<a class="nav-link rounded-pill <?= $tab === 'service' ? 'active' : '' ?>" href="?tab=service&date_from=<?= $date_from ?>&date_to=<?= $date_to ?>">
|
||||
<i class="bi bi-shop me-1"></i> Restaurant Service
|
||||
</a>
|
||||
</li>
|
||||
@ -71,7 +126,7 @@ $ratings = $pagination['data'];
|
||||
<?php if (empty($summaries)): ?>
|
||||
<div class="alert alert-info border-0 shadow-sm rounded-4 text-center py-4 mb-5">
|
||||
<i class="bi bi-info-circle fs-2 mb-2"></i>
|
||||
<p class="mb-0">No staff members have been rated yet.</p>
|
||||
<p class="mb-0">No staff members have been rated yet for the selected period.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="row g-4 mb-5">
|
||||
@ -80,25 +135,25 @@ $ratings = $pagination['data'];
|
||||
<div class="card border-0 shadow-sm rounded-4 h-100">
|
||||
<div class="card-body text-center">
|
||||
<?php if ($summary['profile_pic']): ?>
|
||||
<img src="../<?= htmlspecialchars($summary['profile_pic']) ?>" alt="Staff" class="rounded-circle mb-3" style="width: 64px; height: 64px; object-fit: cover;">
|
||||
<img src="../<?= htmlspecialchars($summary['profile_pic']) ?>" alt="Staff" class="rounded-circle mb-3" style="width: 56px; height: 56px; object-fit: cover;">
|
||||
<?php else: ?>
|
||||
<div class="rounded-circle bg-light d-inline-flex align-items-center justify-content-center mb-3" style="width: 64px; height: 64px;">
|
||||
<i class="bi bi-person fs-2 text-primary"></i>
|
||||
<div class="rounded-circle bg-light d-inline-flex align-items-center justify-content-center mb-3" style="width: 56px; height: 56px;">
|
||||
<i class="bi bi-person fs-3 text-primary"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<h5 class="card-title mb-1 small fw-bold text-truncate" title="<?= htmlspecialchars($summary['full_name'] ?: $summary['username']) ?>">
|
||||
<?= htmlspecialchars($summary['full_name'] ?: $summary['username']) ?>
|
||||
</h5>
|
||||
<div class="text-warning mb-2">
|
||||
<div class="text-warning mb-2" style="font-size: 0.8rem;">
|
||||
<?php
|
||||
$avg = round($summary['avg_rating']);
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
echo $i <= $avg ? '<i class="bi bi-star-fill"></i>' : '<i class="bi bi-star"></i>';
|
||||
}
|
||||
?>
|
||||
<span class="text-muted ms-1">(<?= number_format($summary['avg_rating'], 1) ?>)</span>
|
||||
<span class="text-muted ms-1 small">(<?= number_format($summary['avg_rating'], 1) ?>)</span>
|
||||
</div>
|
||||
<p class="text-muted small mb-0"><?= $summary['total_ratings'] ?> total ratings</p>
|
||||
<p class="text-muted extra-small mb-0"><?= $summary['total_ratings'] ?> ratings</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -120,8 +175,8 @@ $ratings = $pagination['data'];
|
||||
N/A
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
Based on <?= $serviceSummary['total_ratings'] ?> customer reviews
|
||||
<div class="text-white-50 extra-small">
|
||||
Based on <?= $serviceSummary['total_ratings'] ?> reviews
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -131,14 +186,14 @@ $ratings = $pagination['data'];
|
||||
|
||||
<!-- Detailed List -->
|
||||
<div class="card border-0 shadow-sm rounded-4">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="card-title mb-0"><?= $tab === 'service' ? 'Service Feedback' : 'Staff Feedback' ?></h5>
|
||||
<div class="card-header bg-white py-3 border-bottom-0">
|
||||
<h5 class="card-title mb-0 small fw-bold"><?= $tab === 'service' ? 'Service Feedback' : 'Staff Feedback' ?></h5>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<table class="table table-hover align-middle mb-0 table-ratings">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th class="ps-3">Date</th>
|
||||
<?php if ($tab === 'staff'): ?><th>Staff Member</th><?php endif; ?>
|
||||
<th>Rating</th>
|
||||
<th>Comment</th>
|
||||
@ -147,21 +202,25 @@ $ratings = $pagination['data'];
|
||||
<tbody>
|
||||
<?php if (empty($ratings)): ?>
|
||||
<tr>
|
||||
<td colspan="<?= $tab === 'staff' ? 4 : 3 ?>" class="text-center py-4 text-muted">No ratings found yet.</td>
|
||||
<td colspan="<?= $tab === 'staff' ? 4 : 3 ?>" class="text-center py-4 text-muted small">No ratings found yet.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($ratings as $rating): ?>
|
||||
<tr>
|
||||
<td class="small text-muted">
|
||||
<td class="ps-3 text-muted">
|
||||
<?= date('M d, Y H:i', strtotime($rating['created_at'])) ?>
|
||||
</td>
|
||||
<?php if ($tab === 'staff'): ?>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<?php if ($rating['profile_pic']): ?>
|
||||
<img src="../<?= htmlspecialchars($rating['profile_pic']) ?>" class="rounded-circle me-2" style="width: 32px; height: 32px; object-fit: cover;">
|
||||
<img src="../<?= htmlspecialchars($rating['profile_pic']) ?>" class="rounded-circle me-2 shadow-sm" style="width: 24px; height: 24px; object-fit: cover;">
|
||||
<?php else: ?>
|
||||
<div class="rounded-circle bg-light d-flex align-items-center justify-content-center me-2 shadow-sm" style="width: 24px; height: 24px;">
|
||||
<i class="bi bi-person text-primary" style="font-size: 0.7rem;"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<span><?= htmlspecialchars($rating['full_name'] ?: $rating['username']) ?></span>
|
||||
<span class="fw-medium text-dark"><?= htmlspecialchars($rating['full_name'] ?: $rating['username']) ?></span>
|
||||
</div>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
@ -169,13 +228,13 @@ $ratings = $pagination['data'];
|
||||
<div class="text-warning">
|
||||
<?php
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
echo $i <= $rating['rating'] ? '<i class="bi bi-star-fill"></i>' : '<i class="bi bi-star text-muted"></i>';
|
||||
echo $i <= $rating['rating'] ? '<i class="bi bi-star-fill"></i>' : '<i class="bi bi-star text-muted" style="opacity: 0.3;"></i>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-muted small"><?= htmlspecialchars($rating['comment'] ?: '-') ?></span>
|
||||
<td class="text-wrap" style="max-width: 300px;">
|
||||
<span class="text-muted italic"><?= htmlspecialchars($rating['comment'] ?: '-') ?></span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@ -183,28 +242,28 @@ $ratings = $pagination['data'];
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-footer bg-white">
|
||||
<?php render_pagination_controls($pagination); ?>
|
||||
<div class="card-footer bg-white py-3">
|
||||
<?php render_pagination_controls($pagination, ['date_from' => $date_from, 'date_to' => $date_to]); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rating QR Modal -->
|
||||
<div class="modal fade" id="ratingQRModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<div class="modal-header bg-warning">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<div class="modal-header bg-warning border-0">
|
||||
<h5 class="modal-title fw-bold text-dark"><i class="bi bi-star-fill me-2"></i> Customer Rating QR</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body text-center py-4">
|
||||
<p class="text-muted mb-4">Show this QR code to the customer to rate our staff and service.</p>
|
||||
<p class="text-muted mb-4 extra-small px-4">Show this QR code to the customer to rate our staff and service.</p>
|
||||
<div class="mb-4">
|
||||
<img id="rating-qr-img" src="" alt="Rating QR Code" class="img-fluid border p-3 bg-white shadow-sm" style="max-width: 250px;">
|
||||
<img id="rating-qr-img" src="" alt="Rating QR Code" class="img-fluid border p-3 bg-white shadow-sm rounded-3" style="max-width: 180px;">
|
||||
</div>
|
||||
<div class="alert alert-light border small text-break text-center mb-0" id="rating-url-text"></div>
|
||||
<div class="alert alert-light border extra-small text-break text-center mx-4 mb-0" id="rating-url-text"></div>
|
||||
</div>
|
||||
<div class="modal-footer border-top-0 justify-content-center pb-4">
|
||||
<button class="btn btn-primary px-4 fw-bold" onclick="window.print()">
|
||||
<button class="btn btn-primary px-4 fw-bold btn-sm rounded-pill" onclick="window.print()">
|
||||
<i class="bi bi-printer me-2"></i> Print QR Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -1,93 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
if (!isset($_GET['id'])) {
|
||||
header("Location: suppliers.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'];
|
||||
$message = '';
|
||||
|
||||
// Fetch Supplier
|
||||
$stmt = $pdo->prepare("SELECT * FROM suppliers WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$supplier = $stmt->fetch();
|
||||
|
||||
if (!$supplier) {
|
||||
header("Location: suppliers.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle Update
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$name = $_POST['name'];
|
||||
$contact_person = $_POST['contact_person'];
|
||||
$email = $_POST['email'];
|
||||
$phone = $_POST['phone'];
|
||||
$vat_no = $_POST['vat_no'];
|
||||
$address = $_POST['address'];
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE suppliers SET name = ?, contact_person = ?, email = ?, phone = ?, vat_no = ?, address = ? WHERE id = ?");
|
||||
if ($stmt->execute([$name, $contact_person, $email, $phone, $vat_no, $address, $id])) {
|
||||
$message = '<div class="alert alert-success">Supplier updated successfully!</div>';
|
||||
// Refresh data
|
||||
$stmt = $pdo->prepare("SELECT * FROM suppliers WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$supplier = $stmt->fetch();
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error updating supplier.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="mb-4">
|
||||
<a href="suppliers.php" class="text-decoration-none text-muted mb-2 d-inline-block"><i class="bi bi-arrow-left"></i> Back to Suppliers</a>
|
||||
<h2 class="fw-bold mb-0">Edit Supplier: <?= htmlspecialchars($supplier['name']) ?></h2>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Company Name</label>
|
||||
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($supplier['name']) ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Contact Person</label>
|
||||
<input type="text" name="contact_person" class="form-control" value="<?= htmlspecialchars($supplier['contact_person']) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($supplier['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($supplier['phone']) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">VAT No</label>
|
||||
<input type="text" name="vat_no" class="form-control" value="<?= htmlspecialchars($supplier['vat_no']) ?>" placeholder="e.g. GB123456789">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Address</label>
|
||||
<textarea name="address" class="form-control" rows="3"><?= htmlspecialchars($supplier['address']) ?></textarea>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="suppliers.php" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -6,23 +6,44 @@ $pdo = db();
|
||||
|
||||
$message = '';
|
||||
|
||||
// Handle Add Supplier
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_supplier') {
|
||||
if (!has_permission('suppliers_add')) {
|
||||
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to add suppliers.</div>';
|
||||
} else {
|
||||
$name = $_POST['name'];
|
||||
$contact_person = $_POST['contact_person'];
|
||||
$email = $_POST['email'];
|
||||
$phone = $_POST['phone'];
|
||||
$address = $_POST['address'];
|
||||
$vat_no = $_POST['vat_no'];
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO suppliers (name, contact_person, email, phone, address, vat_no) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
if ($stmt->execute([$name, $contact_person, $email, $phone, $address, $vat_no])) {
|
||||
$message = '<div class="alert alert-success">Supplier added successfully!</div>';
|
||||
// Handle Add/Edit Supplier
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
if ($_POST['action'] === 'add_supplier') {
|
||||
if (!has_permission('suppliers_add')) {
|
||||
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to add suppliers.</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error adding supplier.</div>';
|
||||
$name = $_POST['name'];
|
||||
$contact_person = $_POST['contact_person'];
|
||||
$email = $_POST['email'];
|
||||
$phone = $_POST['phone'];
|
||||
$address = $_POST['address'];
|
||||
$vat_no = $_POST['vat_no'];
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO suppliers (name, contact_person, email, phone, address, vat_no) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
if ($stmt->execute([$name, $contact_person, $email, $phone, $address, $vat_no])) {
|
||||
$message = '<div class="alert alert-success">Supplier added successfully!</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error adding supplier.</div>';
|
||||
}
|
||||
}
|
||||
} elseif ($_POST['action'] === 'edit_supplier') {
|
||||
if (!has_permission('suppliers_add')) {
|
||||
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to edit suppliers.</div>';
|
||||
} else {
|
||||
$id = $_POST['id'];
|
||||
$name = $_POST['name'];
|
||||
$contact_person = $_POST['contact_person'];
|
||||
$email = $_POST['email'];
|
||||
$phone = $_POST['phone'];
|
||||
$address = $_POST['address'];
|
||||
$vat_no = $_POST['vat_no'];
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE suppliers SET name = ?, contact_person = ?, email = ?, phone = ?, address = ?, vat_no = ? WHERE id = ?");
|
||||
if ($stmt->execute([$name, $contact_person, $email, $phone, $address, $vat_no, $id])) {
|
||||
$message = '<div class="alert alert-success">Supplier updated successfully!</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error updating supplier.</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -50,7 +71,7 @@ include 'includes/header.php';
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Suppliers</h2>
|
||||
<?php if (has_permission('suppliers_add')): ?>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addSupplierModal">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#supplierModal" onclick="openAddModal()">
|
||||
<i class="bi bi-plus-lg"></i> Add Supplier
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
@ -88,7 +109,11 @@ include 'includes/header.php';
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<?php if (has_permission('suppliers_add')): ?>
|
||||
<a href="supplier_edit.php?id=<?= $supplier['id'] ?>" class="btn btn-sm btn-outline-primary" title="Edit Supplier"><i class="bi bi-pencil"></i></a>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#supplierModal"
|
||||
onclick="openEditModal(<?= htmlspecialchars(json_encode($supplier)) ?>)"
|
||||
title="Edit Supplier"><i class="bi bi-pencil"></i></button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (has_permission('suppliers_del')): ?>
|
||||
@ -113,43 +138,44 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Supplier Modal -->
|
||||
<!-- Supplier Modal -->
|
||||
<?php if (has_permission('suppliers_add')): ?>
|
||||
<div class="modal fade" id="addSupplierModal" tabindex="-1">
|
||||
<div class="modal fade" id="supplierModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add New Supplier</h5>
|
||||
<h5 class="modal-title" id="supplierModalTitle">Add New Supplier</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<form method="POST" id="supplierForm">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" value="add_supplier">
|
||||
<input type="hidden" name="action" id="supplierAction" value="add_supplier">
|
||||
<input type="hidden" name="id" id="supplierId">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Company Name</label>
|
||||
<input type="text" name="name" class="form-control" required>
|
||||
<input type="text" name="name" id="supplierName" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Contact Person</label>
|
||||
<input type="text" name="contact_person" class="form-control">
|
||||
<input type="text" name="contact_person" id="supplierContactPerson" class="form-control">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control">
|
||||
<input type="email" name="email" id="supplierEmail" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Phone</label>
|
||||
<input type="text" name="phone" class="form-control">
|
||||
<input type="text" name="phone" id="supplierPhone" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">VAT No</label>
|
||||
<input type="text" name="vat_no" class="form-control" placeholder="e.g. GB123456789">
|
||||
<input type="text" name="vat_no" id="supplierVatNo" class="form-control" placeholder="e.g. GB123456789">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Address</label>
|
||||
<textarea name="address" class="form-control" rows="3"></textarea>
|
||||
<textarea name="address" id="supplierAddress" class="form-control" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@ -160,6 +186,27 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openAddModal() {
|
||||
document.getElementById('supplierModalTitle').innerText = 'Add New Supplier';
|
||||
document.getElementById('supplierAction').value = 'add_supplier';
|
||||
document.getElementById('supplierForm').reset();
|
||||
document.getElementById('supplierId').value = '';
|
||||
}
|
||||
|
||||
function openEditModal(supplier) {
|
||||
document.getElementById('supplierModalTitle').innerText = 'Edit Supplier';
|
||||
document.getElementById('supplierAction').value = 'edit_supplier';
|
||||
document.getElementById('supplierId').value = supplier.id;
|
||||
document.getElementById('supplierName').value = supplier.name;
|
||||
document.getElementById('supplierContactPerson').value = supplier.contact_person || '';
|
||||
document.getElementById('supplierEmail').value = supplier.email || '';
|
||||
document.getElementById('supplierPhone').value = supplier.phone || '';
|
||||
document.getElementById('supplierVatNo').value = supplier.vat_no || '';
|
||||
document.getElementById('supplierAddress').value = supplier.address || '';
|
||||
}
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
BIN
assets/images/company/favicon_699d0d4e7a2f6.png
Normal file
BIN
assets/images/company/favicon_699d0d4e7a2f6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
BIN
assets/images/company/logo_699d0d4e79490.png
Normal file
BIN
assets/images/company/logo_699d0d4e79490.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@ -23,6 +23,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const quickOrderBtn = document.getElementById('quick-order-btn');
|
||||
const placeOrderBtn = document.getElementById('place-order-btn');
|
||||
const recallBtn = document.getElementById('recall-bill-btn');
|
||||
const reprintReceiptBtn = document.getElementById('reprint-receipt-btn');
|
||||
|
||||
// Recall Modal
|
||||
const recallModalEl = document.getElementById('recallOrderModal');
|
||||
@ -504,6 +505,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
};
|
||||
|
||||
function updateCart() {
|
||||
if (reprintReceiptBtn) {
|
||||
if (currentOrderId) reprintReceiptBtn.classList.remove('d-none');
|
||||
else reprintReceiptBtn.classList.add('d-none');
|
||||
}
|
||||
|
||||
if (cart.length === 0) {
|
||||
cartItemsContainer.innerHTML = `
|
||||
<div class="text-center text-muted mt-5">
|
||||
@ -514,14 +520,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
cartTotalPrice.innerText = formatCurrency(0);
|
||||
if (quickOrderBtn) quickOrderBtn.disabled = true;
|
||||
if (placeOrderBtn) placeOrderBtn.disabled = true;
|
||||
|
||||
// RESET current Order ID if cart becomes empty?
|
||||
// Actually, if we empty the cart, we might want to "cancel" the update?
|
||||
// No, user can add items back. But if they leave it empty, we can't submit.
|
||||
// If they start adding items, it's still the recalled order.
|
||||
// What if they want to START NEW? They should reload or we should provide a Clear button.
|
||||
// For now, let's assume they continue working on it.
|
||||
// If they want new, they can refresh or we can add a "New Order" button later.
|
||||
return;
|
||||
}
|
||||
|
||||
@ -782,101 +780,229 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
}
|
||||
|
||||
window.reprintCurrentReceipt = function() {
|
||||
if (!currentOrderId) return;
|
||||
|
||||
const subtotal = cart.reduce((acc, item) => acc + (item.price * item.quantity), 0);
|
||||
const discount = parseFloat(cartDiscountInput.value) || 0;
|
||||
const totalAmount = Math.max(0, subtotal - discount);
|
||||
const orderType = document.querySelector('input[name="order_type"]:checked').value;
|
||||
|
||||
printReceipt({
|
||||
orderId: currentOrderId,
|
||||
customer: currentCustomer,
|
||||
items: [...cart],
|
||||
total: totalAmount,
|
||||
discount: discount,
|
||||
orderType: orderType,
|
||||
tableNumber: (orderType === 'dine-in') ? currentTableName : null,
|
||||
date: new Date().toLocaleString() + ' (Reprint)',
|
||||
paymentMethod: 'Previously Settled',
|
||||
loyaltyRedeemed: isLoyaltyRedemption
|
||||
});
|
||||
};
|
||||
|
||||
function printReceipt(data) {
|
||||
const width = 300;
|
||||
const height = 600;
|
||||
const settings = (typeof COMPANY_SETTINGS !== "undefined") ? COMPANY_SETTINGS : {};
|
||||
const width = 400;
|
||||
const height = 800;
|
||||
const left = (screen.width - width) / 2;
|
||||
const top = (screen.height - height) / 2;
|
||||
|
||||
const win = window.open('', 'Receipt', `width=${width},height=${height},top=${top},left=${left}`);
|
||||
|
||||
const tr = {
|
||||
'Order': 'الطلب',
|
||||
'Type': 'النوع',
|
||||
'Date': 'التاريخ',
|
||||
'Staff': 'الموظف',
|
||||
'Table': 'طاولة',
|
||||
'Payment': 'الدفع',
|
||||
'ITEM': 'الصنف',
|
||||
'TOTAL': 'المجموع',
|
||||
'Subtotal': 'المجموع الفرعي',
|
||||
'Discount': 'الخصم',
|
||||
'Tax Included': 'شامل الضريبة',
|
||||
'THANK YOU FOR YOUR VISIT!': 'شكراً لزيارتكم!',
|
||||
'Please come again.': 'يرجى زيارتنا مرة أخرى.',
|
||||
'Customer Details': 'تفاصيل العميل',
|
||||
'Tel': 'هاتف',
|
||||
'takeaway': 'سفري',
|
||||
'dine-in': 'محلي',
|
||||
'delivery': 'توصيل',
|
||||
'VAT No': 'الرقم الضريبي',
|
||||
'CTR No': 'رقم السجل التجاري'
|
||||
};
|
||||
|
||||
const itemsHtml = data.items.map(item => `
|
||||
<tr>
|
||||
<td style="padding: 2px 0;">
|
||||
${item.name} <br>
|
||||
${item.variant_name ? `<small>(${item.variant_name})</small>` : ''}
|
||||
<td style="padding: 5px 0; border-bottom: 1px solid #eee;">
|
||||
<div style="font-weight: bold;">${item.name}</div>
|
||||
${item.variant_name ? `<div style="font-size: 10px; color: #555;">(${item.variant_name})</div>` : ''}
|
||||
<div style="font-size: 11px;">${item.quantity} x ${formatCurrency(item.price)}</div>
|
||||
</td>
|
||||
<td style="text-align: right; vertical-align: top;">${item.quantity} x ${formatCurrency(item.price)}</td>
|
||||
<td style="text-align: right; vertical-align: middle; font-weight: bold;">${formatCurrency(item.quantity * item.price)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
const customerHtml = data.customer ? `
|
||||
<div style="margin-bottom: 10px; border-bottom: 1px dashed #000; padding-bottom: 5px;">
|
||||
<strong>Customer:</strong><br>
|
||||
${data.customer.name}<br>
|
||||
${data.customer.phone || ''}
|
||||
<div style="margin-bottom: 10px; border: 1px solid #eee; padding: 8px; border-radius: 4px;">
|
||||
<div style="font-weight: bold; text-transform: uppercase; font-size: 10px; color: #666; margin-bottom: 3px;">
|
||||
<span style="float: left;">Customer Details</span>
|
||||
<span style="float: right;">${tr['Customer Details']}</span>
|
||||
<div style="clear: both;"></div>
|
||||
</div>
|
||||
<div style="font-weight: bold;">${data.customer.name}</div>
|
||||
${data.customer.phone ? `<div>Tel: ${data.customer.phone}</div>` : ''}
|
||||
${data.customer.address ? `<div style="font-size: 11px;">${data.customer.address}</div>` : ''}
|
||||
</div>
|
||||
` : '';
|
||||
|
||||
const tableHtml = data.tableNumber ? `<div>Table: ${data.tableNumber}</div>` : '';
|
||||
const paymentHtml = data.paymentMethod ? `<div>Payment: ${data.paymentMethod}</div>` : '';
|
||||
const loyaltyHtml = data.loyaltyRedeemed ? `<div><strong>* Free Meal Redeemed *</strong></div>` : '';
|
||||
const tableHtml = data.tableNumber ? `
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span><strong>Table:</strong> ${data.tableNumber}</span>
|
||||
<span><strong>${tr['Table']}:</strong> ${data.tableNumber}</span>
|
||||
</div>` : '';
|
||||
|
||||
const paymentHtml = data.paymentMethod ? `
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span><strong>Payment:</strong> ${data.paymentMethod}</span>
|
||||
<span><strong>${tr['Payment']}:</strong> ${data.paymentMethod}</span>
|
||||
</div>` : '';
|
||||
|
||||
const loyaltyHtml = data.loyaltyRedeemed ? `<div style="color: #d63384; font-weight: bold; margin: 5px 0; text-align: center;">* Loyalty Reward Applied *</div>` : '';
|
||||
|
||||
const vatRate = parseFloat(settings.vat_rate) || 0;
|
||||
const subtotal = data.total + data.discount;
|
||||
const vatAmount = vatRate > 0 ? (data.total * (vatRate / (100 + vatRate))) : 0;
|
||||
|
||||
const logoHtml = settings.logo_url ? `<img src="${BASE_URL}${settings.logo_url}" style="max-height: 80px; max-width: 150px; margin-bottom: 10px;">` : '';
|
||||
|
||||
const html = `
|
||||
<html>
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<title>Receipt #${data.orderId}</title>
|
||||
<style>
|
||||
body { font-family: 'Courier New', monospace; font-size: 12px; margin: 0; padding: 10px; }
|
||||
.header { text-align: center; margin-bottom: 10px; }
|
||||
.header h2 { margin: 0; font-size: 16px; }
|
||||
body {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
padding: 15px;
|
||||
color: #000;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.header { text-align: center; margin-bottom: 15px; }
|
||||
.header h2 { margin: 0 0 5px 0; font-size: 20px; font-weight: bold; }
|
||||
.header div { font-size: 12px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
.totals { margin-top: 10px; border-top: 1px dashed #000; padding-top: 5px; }
|
||||
.footer { text-align: center; margin-top: 20px; font-size: 10px; }
|
||||
.divider { border-bottom: 2px dashed #000; margin: 10px 0; }
|
||||
.thick-divider { border-bottom: 2px solid #000; margin: 10px 0; }
|
||||
.totals td { padding: 3px 0; }
|
||||
.footer { text-align: center; margin-top: 25px; font-size: 12px; }
|
||||
.order-info { font-size: 11px; margin-bottom: 10px; }
|
||||
.order-info-row { display: flex; justify-content: space-between; margin-bottom: 2px; }
|
||||
.rtl { direction: rtl; unicode-bidi: embed; }
|
||||
@media print {
|
||||
.no-print { display: none; }
|
||||
body { width: 80mm; padding: 5mm; }
|
||||
@page { margin: 0; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h2>FLATLOGIC POS</h2>
|
||||
<div>123 Main St, City</div>
|
||||
<div>Tel: 123-456-7890</div>
|
||||
${logoHtml}
|
||||
<h2>${settings.company_name}</h2>
|
||||
<div style="font-weight: bold;">${CURRENT_OUTLET.name}</div>
|
||||
<div>${settings.address}</div>
|
||||
<div>Tel: ${settings.phone}</div>
|
||||
${settings.vat_number ? `<div style="margin-top: 4px;">VAT No / الرقم الضريبي: ${settings.vat_number}</div>` : ''}
|
||||
${settings.ctr_number ? `<div>CTR No / رقم السجل: ${settings.ctr_number}</div>` : ''}
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<div>Order #${data.orderId}</div>
|
||||
<div>${data.date}</div>
|
||||
<div>Type: ${data.orderType.toUpperCase()}</div>
|
||||
${tableHtml}
|
||||
${paymentHtml}
|
||||
${loyaltyHtml}
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="order-info">
|
||||
<div class="order-info-row">
|
||||
<span><strong>Order:</strong> #${data.orderId}</span>
|
||||
<span><strong>${tr['Order']}:</strong> #${data.orderId}</span>
|
||||
</div>
|
||||
<div class="order-info-row">
|
||||
<span><strong>Type:</strong> ${data.orderType.toUpperCase()}</span>
|
||||
<span><strong>${tr['Type']}:</strong> ${tr[data.orderType] || data.orderType}</span>
|
||||
</div>
|
||||
<div class="order-info-row">
|
||||
<span><strong>Date:</strong> ${data.date}</span>
|
||||
<span><strong>${tr['Date']}:</strong> ${data.date}</span>
|
||||
</div>
|
||||
<div class="order-info-row">
|
||||
<span><strong>Staff:</strong> ${CURRENT_USER.name}</span>
|
||||
<span><strong>${tr['Staff']}:</strong> ${CURRENT_USER.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${tableHtml}
|
||||
${paymentHtml}
|
||||
${loyaltyHtml}
|
||||
|
||||
<div class="thick-divider"></div>
|
||||
${customerHtml}
|
||||
|
||||
<table>
|
||||
${itemsHtml}
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: left; padding-bottom: 5px;">
|
||||
ITEM / الصنف
|
||||
</th>
|
||||
<th style="text-align: right; padding-bottom: 5px;">
|
||||
TOTAL / المجموع
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${itemsHtml}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="totals">
|
||||
<table style="width: 100%">
|
||||
<tr>
|
||||
<td>Subtotal</td>
|
||||
<td style="text-align: right">${formatCurrency(data.total + data.discount)}</td>
|
||||
<td>Subtotal / ${tr['Subtotal']}</td>
|
||||
<td style="text-align: right">${formatCurrency(subtotal)}</td>
|
||||
</tr>
|
||||
${data.discount > 0 ? `
|
||||
<tr>
|
||||
<td>Discount</td>
|
||||
<td>Discount / ${tr['Discount']}</td>
|
||||
<td style="text-align: right">-${formatCurrency(data.discount)}</td>
|
||||
</tr>` : ''}
|
||||
<tr style="font-weight: bold; font-size: 14px;">
|
||||
<td>Total</td>
|
||||
<td style="text-align: right">${formatCurrency(data.total)}</td>
|
||||
${vatRate > 0 ? `
|
||||
<tr>
|
||||
<td style="font-size: 11px;">Tax Incl. (${vatRate}%) / ${tr['Tax Included']}</td>
|
||||
<td style="text-align: right">${formatCurrency(vatAmount)}</td>
|
||||
</tr>` : ''}
|
||||
<tr style="font-weight: bold; font-size: 18px;">
|
||||
<td style="padding-top: 10px;">TOTAL / ${tr['TOTAL']}</td>
|
||||
<td style="text-align: right; padding-top: 10px;">${formatCurrency(data.total)}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="thick-divider"></div>
|
||||
|
||||
<div class="footer">
|
||||
Thank you for your visit!<br>
|
||||
Please come again.
|
||||
<div style="font-weight: bold; font-size: 14px; margin-bottom: 2px;">THANK YOU FOR YOUR VISIT!</div>
|
||||
<div style="font-weight: bold; font-size: 14px; margin-bottom: 5px;" class="rtl">${tr['THANK YOU FOR YOUR VISIT!']}</div>
|
||||
<div>Please come again.</div>
|
||||
<div class="rtl">${tr['Please come again.']}</div>
|
||||
${settings.email ? `<div style="margin-top: 5px; font-size: 10px;">${settings.email}</div>` : ''}
|
||||
<div style="margin-top: 15px; font-size: 10px; color: #888;">Powered by Flatlogic POS</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
window.print();
|
||||
setTimeout(function() { window.close(); }, 500);
|
||||
setTimeout(function() { window.close(); }, 1500);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
2
db/migrations/023_add_discount_to_orders.sql
Normal file
2
db/migrations/023_add_discount_to_orders.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- Add discount column to orders table
|
||||
ALTER TABLE orders ADD COLUMN discount DECIMAL(10, 2) DEFAULT 0.00 AFTER total_amount;
|
||||
18
db/migrations/024_add_address_to_customers.sql
Normal file
18
db/migrations/024_add_address_to_customers.sql
Normal file
@ -0,0 +1,18 @@
|
||||
-- Add address column to customers table
|
||||
SET @dbname = DATABASE();
|
||||
SET @tablename = "customers";
|
||||
SET @columnname = "address";
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(table_name = @tablename)
|
||||
AND (table_schema = @dbname)
|
||||
AND (column_name = @columnname)
|
||||
) > 0,
|
||||
"SELECT 1",
|
||||
"ALTER TABLE customers ADD COLUMN address TEXT;"
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
2
db/migrations/025_add_display_layout_to_ads.sql
Normal file
2
db/migrations/025_add_display_layout_to_ads.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- Add display_layout column to ads_images table
|
||||
ALTER TABLE ads_images ADD COLUMN IF NOT EXISTS display_layout ENUM('both', 'split', 'fullscreen') DEFAULT 'both' AFTER is_active;
|
||||
1
pos.php
1
pos.php
@ -485,6 +485,7 @@ if (!$loyalty_settings) {
|
||||
const LOYALTY_SETTINGS = <?= json_encode($loyalty_settings) ?>;
|
||||
const PRODUCT_VARIANTS = <?= json_encode($variants_by_product) ?>;
|
||||
const PAYMENT_TYPES = <?= json_encode($payment_types) ?>;
|
||||
const CURRENT_USER = <?= json_encode(['id' => $currentUser['id'], 'name' => $currentUser['name']]) ?>;
|
||||
const CURRENT_OUTLET = <?= json_encode(['id' => $outlet_id, 'name' => $current_outlet_name]) ?>;
|
||||
const BASE_URL = '<?= get_base_url() ?>';
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user