adding cash register
This commit is contained in:
parent
b608d55567
commit
038a4de94d
12
db/migrations/20260218_pos_payments.sql
Normal file
12
db/migrations/20260218_pos_payments.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
-- Create pos_payments table to support split payments and detailed reporting
|
||||||
|
CREATE TABLE IF NOT EXISTS pos_payments (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
transaction_id INT NOT NULL,
|
||||||
|
payment_method VARCHAR(50) NOT NULL,
|
||||||
|
amount DECIMAL(15, 3) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (transaction_id) REFERENCES pos_transactions(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Add register_session_id to pos_transactions if missing (already exists but for idempotency)
|
||||||
|
-- ALTER TABLE pos_transactions ADD COLUMN IF NOT EXISTS register_session_id INT NULL;
|
||||||
845
index.php
845
index.php
@ -137,6 +137,121 @@ if (isset($_GET['action']) && $_GET['action'] === 'logout') {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- POS AJAX Handlers ---
|
||||||
|
if (isset($_GET['action']) || isset($_POST['action'])) {
|
||||||
|
$action = $_GET['action'] ?? $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
if ($action === 'validate_discount') {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$code = $_GET['code'] ?? '';
|
||||||
|
$stmt = db()->prepare("SELECT * FROM discount_codes WHERE code = ? AND status = 'active' AND (expiry_date IS NULL OR expiry_date >= CURDATE())");
|
||||||
|
$stmt->execute([$code]);
|
||||||
|
$discount = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($discount) {
|
||||||
|
echo json_encode(['success' => true, 'discount' => $discount]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid or expired discount code']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'get_held_carts') {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$stmt = db()->query("SELECT h.*, c.name as customer_name FROM pos_held_carts h LEFT JOIN customers c ON h.customer_id = c.id ORDER BY h.created_at DESC");
|
||||||
|
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'hold_pos_cart') {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$name = $_POST['cart_name'] ?? 'Untitled Cart';
|
||||||
|
$items = $_POST['items'] ?? '[]';
|
||||||
|
$customer_id = !empty($_POST['customer_id']) ? (int)$_POST['customer_id'] : null;
|
||||||
|
$stmt = db()->prepare("INSERT INTO pos_held_carts (cart_name, items_json, customer_id) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$name, $items, $customer_id]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'delete_held_cart') {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$id = (int)$_POST['id'];
|
||||||
|
$stmt = db()->prepare("DELETE FROM pos_held_carts WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'save_pos_transaction') {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$db = db();
|
||||||
|
try {
|
||||||
|
$db->beginTransaction();
|
||||||
|
|
||||||
|
$customer_id = !empty($_POST['customer_id']) ? (int)$_POST['customer_id'] : null;
|
||||||
|
$payments = json_decode($_POST['payments'] ?? '[]', true);
|
||||||
|
$items = json_decode($_POST['items'] ?? '[]', true);
|
||||||
|
$total_amount = (float)($_POST['total_amount'] ?? 0);
|
||||||
|
$discount_code_id = !empty($_POST['discount_code_id']) ? (int)$_POST['discount_code_id'] : null;
|
||||||
|
$discount_amount = (float)($_POST['discount_amount'] ?? 0);
|
||||||
|
$loyalty_redeemed = (float)($_POST['loyalty_redeemed'] ?? 0);
|
||||||
|
$net_amount = $total_amount - $discount_amount - $loyalty_redeemed;
|
||||||
|
|
||||||
|
$transaction_no = 'POS-' . time() . rand(10, 99);
|
||||||
|
$session_id = $_SESSION['register_session_id'] ?? null;
|
||||||
|
|
||||||
|
// Insert Transaction
|
||||||
|
$stmt = $db->prepare("INSERT INTO pos_transactions (transaction_no, customer_id, total_amount, discount_code_id, discount_amount, loyalty_points_redeemed, net_amount, register_session_id, created_by, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'completed')");
|
||||||
|
$stmt->execute([$transaction_no, $customer_id, $total_amount, $discount_code_id, $discount_amount, $loyalty_redeemed, $net_amount, $session_id, $_SESSION['user_id']]);
|
||||||
|
$transaction_id = (int)$db->lastInsertId();
|
||||||
|
|
||||||
|
// Insert Items & Update Stock
|
||||||
|
$stmtItem = $db->prepare("INSERT INTO pos_items (transaction_id, product_id, quantity, unit_price, subtotal) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
$stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$sub = (float)$item['price'] * (float)$item['qty'];
|
||||||
|
$stmtItem->execute([$transaction_id, $item['id'], $item['qty'], $item['price'], $sub]);
|
||||||
|
$stmtStock->execute([$item['qty'], $item['id']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert Payments
|
||||||
|
$stmtPay = $db->prepare("INSERT INTO pos_payments (transaction_id, payment_method, amount) VALUES (?, ?, ?)");
|
||||||
|
foreach ($payments as $p) {
|
||||||
|
$stmtPay->execute([$transaction_id, $p['method'], $p['amount']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Loyalty Points if customer exists
|
||||||
|
if ($customer_id) {
|
||||||
|
// Earn points
|
||||||
|
$points_earned = floor($net_amount);
|
||||||
|
$stmtPoints = $db->prepare("UPDATE customers SET loyalty_points = loyalty_points - ? + ? WHERE id = ?");
|
||||||
|
$stmtPoints->execute([$loyalty_redeemed * 100, $points_earned, $customer_id]);
|
||||||
|
|
||||||
|
// Record transactions
|
||||||
|
if ($points_earned > 0) {
|
||||||
|
$db->prepare("INSERT INTO loyalty_transactions (customer_id, transaction_id, points_change, transaction_type, description) VALUES (?, ?, ?, 'earned', ?)")
|
||||||
|
->execute([$customer_id, $transaction_id, $points_earned, "Earned from POS order #$transaction_no"]);
|
||||||
|
}
|
||||||
|
if ($loyalty_redeemed > 0) {
|
||||||
|
$db->prepare("INSERT INTO loyalty_transactions (customer_id, transaction_id, points_change, transaction_type, description) VALUES (?, ?, ?, 'redeemed', ?)")
|
||||||
|
->execute([$customer_id, $transaction_id, -$loyalty_redeemed * 100, "Redeemed for POS order #$transaction_no"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update transaction with points earned
|
||||||
|
$db->prepare("UPDATE pos_transactions SET loyalty_points_earned = ? WHERE id = ?")->execute([$points_earned, $transaction_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->commit();
|
||||||
|
echo json_encode(['success' => true, 'invoice_id' => $transaction_id, 'transaction_no' => $transaction_no]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$db->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Redirect to login if not authenticated
|
// Redirect to login if not authenticated
|
||||||
if (!isset($_SESSION['user_id'])) {
|
if (!isset($_SESSION['user_id'])) {
|
||||||
?>
|
?>
|
||||||
@ -535,6 +650,44 @@ if (isset($_POST['add_hr_department'])) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- POS Devices Handlers ---
|
||||||
|
if (isset($_POST['add_pos_device'])) {
|
||||||
|
$name = $_POST['device_name'] ?? '';
|
||||||
|
$type = $_POST['device_type'] ?? 'scale';
|
||||||
|
$conn = $_POST['connection_type'] ?? 'usb';
|
||||||
|
$ip = $_POST['ip_address'] ?? '';
|
||||||
|
$port = $_POST['port'] ? (int)$_POST['port'] : null;
|
||||||
|
$baud = $_POST['baud_rate'] ? (int)$_POST['baud_rate'] : null;
|
||||||
|
if ($name) {
|
||||||
|
$stmt = db()->prepare("INSERT INTO pos_devices (device_name, device_type, connection_type, ip_address, port, baud_rate) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$name, $type, $conn, $ip, $port, $baud]);
|
||||||
|
$message = "Device added successfully!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($_POST['edit_pos_device'])) {
|
||||||
|
$id = (int)$_POST['id'];
|
||||||
|
$name = $_POST['device_name'] ?? '';
|
||||||
|
$type = $_POST['device_type'] ?? 'scale';
|
||||||
|
$conn = $_POST['connection_type'] ?? 'usb';
|
||||||
|
$ip = $_POST['ip_address'] ?? '';
|
||||||
|
$port = $_POST['port'] ? (int)$_POST['port'] : null;
|
||||||
|
$baud = $_POST['baud_rate'] ? (int)$_POST['baud_rate'] : null;
|
||||||
|
$status = $_POST['status'] ?? 'active';
|
||||||
|
if ($id && $name) {
|
||||||
|
$stmt = db()->prepare("UPDATE pos_devices SET device_name = ?, device_type = ?, connection_type = ?, ip_address = ?, port = ?, baud_rate = ?, status = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$name, $type, $conn, $ip, $port, $baud, $status, $id]);
|
||||||
|
$message = "Device updated successfully!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($_POST['delete_pos_device'])) {
|
||||||
|
$id = (int)$_POST['id'];
|
||||||
|
if ($id) {
|
||||||
|
$stmt = db()->prepare("DELETE FROM pos_devices WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$message = "Device deleted successfully!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($_POST['update_profile'])) {
|
if (isset($_POST['update_profile'])) {
|
||||||
$id = $_SESSION['user_id'];
|
$id = $_SESSION['user_id'];
|
||||||
$username = $_POST['username'] ?? '';
|
$username = $_POST['username'] ?? '';
|
||||||
@ -606,6 +759,76 @@ if (isset($_POST['add_hr_department'])) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Cash Register & Session Handlers ---
|
||||||
|
if (isset($_POST['add_cash_register'])) {
|
||||||
|
$name = $_POST['name'] ?? '';
|
||||||
|
if ($name) {
|
||||||
|
$stmt = db()->prepare("INSERT INTO cash_registers (name) VALUES (?)");
|
||||||
|
$stmt->execute([$name]);
|
||||||
|
$message = "Cash Register added successfully!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($_POST['edit_cash_register'])) {
|
||||||
|
$id = (int)$_POST['id'];
|
||||||
|
$name = $_POST['name'] ?? '';
|
||||||
|
$status = $_POST['status'] ?? 'active';
|
||||||
|
if ($id && $name) {
|
||||||
|
$stmt = db()->prepare("UPDATE cash_registers SET name = ?, status = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$name, $status, $id]);
|
||||||
|
$message = "Cash Register updated successfully!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($_POST['delete_cash_register'])) {
|
||||||
|
$id = (int)$_POST['id'];
|
||||||
|
if ($id) {
|
||||||
|
$stmt = db()->prepare("DELETE FROM cash_registers WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$message = "Cash Register deleted successfully!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_POST['open_register'])) {
|
||||||
|
$register_id = (int)$_POST['register_id'];
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
$opening_balance = (float)$_POST['opening_balance'];
|
||||||
|
|
||||||
|
// Check if user already has an open session
|
||||||
|
$check = db()->prepare("SELECT id FROM register_sessions WHERE user_id = ? AND status = 'open'");
|
||||||
|
$check->execute([$user_id]);
|
||||||
|
if ($check->fetch()) {
|
||||||
|
$message = "Error: You already have an open register session.";
|
||||||
|
} else {
|
||||||
|
$stmt = db()->prepare("INSERT INTO register_sessions (register_id, user_id, opening_balance, status) VALUES (?, ?, ?, 'open')");
|
||||||
|
$stmt->execute([$register_id, $user_id, $opening_balance]);
|
||||||
|
$_SESSION['register_session_id'] = db()->lastInsertId();
|
||||||
|
$message = "Register opened successfully!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_POST['close_register'])) {
|
||||||
|
$session_id = (int)$_POST['session_id'];
|
||||||
|
$cash_in_hand = (float)$_POST['cash_in_hand'];
|
||||||
|
$notes = $_POST['notes'] ?? '';
|
||||||
|
|
||||||
|
// Calculate expected closing balance
|
||||||
|
// Opening + Sum of POS Transactions (Cash) - Any cash outflows (if any)
|
||||||
|
$session = db()->prepare("SELECT opening_balance FROM register_sessions WHERE id = ?");
|
||||||
|
$session->execute([$session_id]);
|
||||||
|
$opening = (float)$session->fetchColumn();
|
||||||
|
|
||||||
|
$sales = db()->prepare("SELECT SUM(p.amount) FROM pos_payments p JOIN pos_transactions t ON p.transaction_id = t.id WHERE t.register_session_id = ? AND t.status = 'completed' AND p.payment_method = 'cash'");
|
||||||
|
$sales->execute([$session_id]);
|
||||||
|
$cash_sales = (float)$sales->fetchColumn();
|
||||||
|
|
||||||
|
$expected = $opening + $cash_sales;
|
||||||
|
|
||||||
|
$stmt = db()->prepare("UPDATE register_sessions SET closing_balance = ?, cash_in_hand = ?, closed_at = CURRENT_TIMESTAMP, status = 'closed', notes = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$expected, $cash_in_hand, $notes, $session_id]);
|
||||||
|
|
||||||
|
unset($_SESSION['register_session_id']);
|
||||||
|
$message = "Register closed successfully!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Routing & Data Fetching
|
// Routing & Data Fetching
|
||||||
$page = $_GET['page'] ?? 'dashboard';
|
$page = $_GET['page'] ?? 'dashboard';
|
||||||
@ -642,8 +865,11 @@ $page_permissions = [
|
|||||||
'hr_payroll' => 'hr_view',
|
'hr_payroll' => 'hr_view',
|
||||||
'role_groups' => 'users_view',
|
'role_groups' => 'users_view',
|
||||||
'users' => 'users_view',
|
'users' => 'users_view',
|
||||||
|
'scale_devices' => 'users_view',
|
||||||
'backups' => 'users_view',
|
'backups' => 'users_view',
|
||||||
'logs' => 'users_view',
|
'logs' => 'users_view',
|
||||||
|
'cash_registers' => 'users_view',
|
||||||
|
'register_sessions' => 'pos_view',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isset($page_permissions[$page]) && !can($page_permissions[$page])) {
|
if (isset($page_permissions[$page]) && !can($page_permissions[$page])) {
|
||||||
@ -1189,6 +1415,30 @@ switch ($page) {
|
|||||||
case 'devices':
|
case 'devices':
|
||||||
$data['devices'] = db()->query("SELECT * FROM hr_biometric_devices ORDER BY id DESC")->fetchAll();
|
$data['devices'] = db()->query("SELECT * FROM hr_biometric_devices ORDER BY id DESC")->fetchAll();
|
||||||
break;
|
break;
|
||||||
|
case 'scale_devices':
|
||||||
|
$data['scale_devices'] = db()->query("SELECT * FROM pos_devices ORDER BY id DESC")->fetchAll();
|
||||||
|
break;
|
||||||
|
case 'cash_registers':
|
||||||
|
$data['cash_registers'] = db()->query("SELECT * FROM cash_registers ORDER BY id DESC")->fetchAll();
|
||||||
|
break;
|
||||||
|
case 'register_sessions':
|
||||||
|
$where = ["1=1"];
|
||||||
|
$params = [];
|
||||||
|
if (!can('users_view')) {
|
||||||
|
$where[] = "s.user_id = ?";
|
||||||
|
$params[] = $_SESSION['user_id'];
|
||||||
|
}
|
||||||
|
$whereSql = implode(" AND ", $where);
|
||||||
|
$stmt = db()->prepare("SELECT s.*, r.name as register_name, u.username
|
||||||
|
FROM register_sessions s
|
||||||
|
JOIN cash_registers r ON s.register_id = r.id
|
||||||
|
JOIN users u ON s.user_id = u.id
|
||||||
|
WHERE $whereSql
|
||||||
|
ORDER BY s.id DESC");
|
||||||
|
$stmt->execute($params);
|
||||||
|
$data['sessions'] = $stmt->fetchAll();
|
||||||
|
$data['cash_registers'] = db()->query("SELECT * FROM cash_registers WHERE status = 'active'")->fetchAll();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$data['customers'] = db()->query("SELECT * FROM customers WHERE type = 'customer' ORDER BY id DESC LIMIT 5")->fetchAll();
|
$data['customers'] = db()->query("SELECT * FROM customers WHERE type = 'customer' ORDER BY id DESC LIMIT 5")->fetchAll();
|
||||||
// Dashboard stats
|
// Dashboard stats
|
||||||
@ -1465,6 +1715,15 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<a href="index.php?page=users" class="nav-link <?= $page === 'users' ? 'active' : '' ?>">
|
<a href="index.php?page=users" class="nav-link <?= $page === 'users' ? 'active' : '' ?>">
|
||||||
<i class="fas fa-users-gear"></i> <span data-en="Users" data-ar="المستخدمين">Users</span>
|
<i class="fas fa-users-gear"></i> <span data-en="Users" data-ar="المستخدمين">Users</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="index.php?page=cash_registers" class="nav-link <?= $page === 'cash_registers' ? 'active' : '' ?>">
|
||||||
|
<i class="fas fa-cash-register"></i> <span data-en="Cash Registers" data-ar="خزائن الكاشير">Cash Registers</span>
|
||||||
|
</a>
|
||||||
|
<a href="index.php?page=register_sessions" class="nav-link <?= $page === 'register_sessions' ? 'active' : '' ?>">
|
||||||
|
<i class="fas fa-clock-rotate-left"></i> <span data-en="Register Sessions" data-ar="جلسات الكاشير">Register Sessions</span>
|
||||||
|
</a>
|
||||||
|
<a href="index.php?page=scale_devices" class="nav-link <?= $page === 'scale_devices' ? 'active' : '' ?>">
|
||||||
|
<i class="fas fa-microchip"></i> <span data-en="Devices" data-ar="الأجهزة">Devices</span>
|
||||||
|
</a>
|
||||||
<a href="index.php?page=backups" class="nav-link <?= $page === 'backups' ? 'active' : '' ?>">
|
<a href="index.php?page=backups" class="nav-link <?= $page === 'backups' ? 'active' : '' ?>">
|
||||||
<i class="fas fa-database"></i> <span data-en="Backups" data-ar="نسخ احتياطي">Backups</span>
|
<i class="fas fa-database"></i> <span data-en="Backups" data-ar="نسخ احتياطي">Backups</span>
|
||||||
</a>
|
</a>
|
||||||
@ -1521,6 +1780,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
'users' => ['en' => 'User Management', 'ar' => 'إدارة المستخدمين'],
|
'users' => ['en' => 'User Management', 'ar' => 'إدارة المستخدمين'],
|
||||||
'backups' => ['en' => 'Database Backups', 'ar' => 'نسخ قاعدة البيانات'],
|
'backups' => ['en' => 'Database Backups', 'ar' => 'نسخ قاعدة البيانات'],
|
||||||
'role_groups' => ['en' => 'Role Groups', 'ar' => 'مجموعات الأدوار'],
|
'role_groups' => ['en' => 'Role Groups', 'ar' => 'مجموعات الأدوار'],
|
||||||
|
'scale_devices' => ['en' => 'POS Devices', 'ar' => 'أجهزة نقاط البيع'],
|
||||||
|
'cash_registers' => ['en' => 'Cash Registers', 'ar' => 'خزائن الكاشير'],
|
||||||
|
'register_sessions' => ['en' => 'Register Sessions', 'ar' => 'جلسات الكاشير'],
|
||||||
];
|
];
|
||||||
$currTitle = $titles[$page] ?? $titles['dashboard'];
|
$currTitle = $titles[$page] ?? $titles['dashboard'];
|
||||||
?>
|
?>
|
||||||
@ -5174,6 +5436,188 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php elseif ($page === 'scale_devices'): ?>
|
||||||
|
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
|
||||||
|
<div class="card-header bg-white border-0 py-3 d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0 fw-bold" data-en="POS Devices" data-ar="أجهزة نقاط البيع">POS Devices</h5>
|
||||||
|
<button class="btn btn-primary rounded-3" data-bs-toggle="modal" data-bs-target="#addScaleDeviceModal">
|
||||||
|
<i class="fas fa-plus me-1"></i> <span data-en="Add Device" data-ar="إضافة جهاز">Add Device</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th data-en="Device Name" data-ar="اسم الجهاز">Device Name</th>
|
||||||
|
<th data-en="Type" data-ar="النوع">Type</th>
|
||||||
|
<th data-en="Connection" data-ar="الاتصال">Connection</th>
|
||||||
|
<th data-en="Details" data-ar="التفاصيل">Details</th>
|
||||||
|
<th data-en="Status" data-ar="الحالة">Status</th>
|
||||||
|
<th class="text-end" data-en="Actions" data-ar="الإجراءات">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($data['scale_devices'] as $d): ?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold"><?= htmlspecialchars($d['device_name']) ?></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-info-subtle text-info text-capitalize"><?= $d['device_type'] ?></span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-secondary-subtle text-secondary text-uppercase"><?= $d['connection_type'] ?></span>
|
||||||
|
</td>
|
||||||
|
<td class="small text-muted">
|
||||||
|
<?php if ($d['connection_type'] === 'network'): ?>
|
||||||
|
<?= htmlspecialchars((string)$d['ip_address']) ?>:<?= $d['port'] ?>
|
||||||
|
<?php elseif ($d['connection_type'] === 'serial'): ?>
|
||||||
|
Baud: <?= $d['baud_rate'] ?>
|
||||||
|
<?php else: ?>
|
||||||
|
USB Interface
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-<?= $d['status'] === 'active' ? 'success' : 'danger' ?>-subtle text-<?= $d['status'] === 'active' ? 'success' : 'danger' ?> text-capitalize"><?= $d['status'] ?></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<button class="btn btn-sm btn-light rounded-pill px-3" data-bs-toggle="modal" data-bs-target="#editScaleDeviceModal<?= $d['id'] ?>">
|
||||||
|
<i class="fas fa-edit me-1"></i> <span data-en="Edit" data-ar="تعديل">Edit</span>
|
||||||
|
</button>
|
||||||
|
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
|
||||||
|
<input type="hidden" name="id" value="<?= $d['id'] ?>">
|
||||||
|
<button type="submit" name="delete_pos_device" class="btn btn-sm btn-outline-danger border-0 rounded-pill">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Edit Device Modal -->
|
||||||
|
<div class="modal fade" id="editScaleDeviceModal<?= $d['id'] ?>" tabindex="-1">
|
||||||
|
<div class="modal-dialog border-0">
|
||||||
|
<div class="modal-content shadow border-0 rounded-4">
|
||||||
|
<div class="modal-header border-0">
|
||||||
|
<h5 class="modal-title fw-bold" data-en="Edit Device" data-ar="تعديل الجهاز">Edit Device</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="id" value="<?= $d['id'] ?>">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" data-en="Device Name" data-ar="اسم الجهاز">Device Name</label>
|
||||||
|
<input type="text" name="device_name" class="form-control" value="<?= htmlspecialchars($d['device_name']) ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" data-en="Device Type" data-ar="نوع الجهاز">Device Type</label>
|
||||||
|
<select name="device_type" class="form-select">
|
||||||
|
<option value="scale" <?= $d['device_type'] === 'scale' ? 'selected' : '' ?>>Weight Scale</option>
|
||||||
|
<option value="printer" <?= $d['device_type'] === 'printer' ? 'selected' : '' ?>>Receipt Printer</option>
|
||||||
|
<option value="display" <?= $d['device_type'] === 'display' ? 'selected' : '' ?>>Customer Display</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" data-en="Connection Type" data-ar="نوع الاتصال">Connection Type</label>
|
||||||
|
<select name="connection_type" class="form-select">
|
||||||
|
<option value="usb" <?= $d['connection_type'] === 'usb' ? 'selected' : '' ?>>USB</option>
|
||||||
|
<option value="network" <?= $d['connection_type'] === 'network' ? 'selected' : '' ?>>Network (TCP/IP)</option>
|
||||||
|
<option value="serial" <?= $d['connection_type'] === 'serial' ? 'selected' : '' ?>>Serial (RS232)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label class="form-label" data-en="IP Address" data-ar="عنوان IP">IP Address</label>
|
||||||
|
<input type="text" name="ip_address" class="form-control" value="<?= htmlspecialchars((string)$d['ip_address']) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label" data-en="Port" data-ar="المنفذ">Port</label>
|
||||||
|
<input type="number" name="port" class="form-control" value="<?= $d['port'] ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" data-en="Baud Rate" data-ar="معدل الباود">Baud Rate</label>
|
||||||
|
<input type="number" name="baud_rate" class="form-control" value="<?= $d['baud_rate'] ?>">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" data-en="Status" data-ar="الحالة">Status</label>
|
||||||
|
<select name="status" class="form-select">
|
||||||
|
<option value="active" <?= $d['status'] === 'active' ? 'selected' : '' ?>>Active</option>
|
||||||
|
<option value="inactive" <?= $d['status'] === 'inactive' ? 'selected' : '' ?>>Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0">
|
||||||
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
|
||||||
|
<button type="submit" name="edit_pos_device" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Scale Device Modal -->
|
||||||
|
<div class="modal fade" id="addScaleDeviceModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog border-0">
|
||||||
|
<div class="modal-content shadow border-0 rounded-4">
|
||||||
|
<div class="modal-header border-0">
|
||||||
|
<h5 class="modal-title fw-bold" data-en="Add POS Device" data-ar="إضافة جهاز">Add POS Device</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" data-en="Device Name" data-ar="اسم الجهاز">Device Name</label>
|
||||||
|
<input type="text" name="device_name" class="form-control" required placeholder="e.g. Counter 1 Scale">
|
||||||
|
</div>
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" data-en="Device Type" data-ar="نوع الجهاز">Device Type</label>
|
||||||
|
<select name="device_type" class="form-select">
|
||||||
|
<option value="scale">Weight Scale</option>
|
||||||
|
<option value="printer">Receipt Printer</option>
|
||||||
|
<option value="display">Customer Display</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" data-en="Connection Type" data-ar="نوع الاتصال">Connection Type</label>
|
||||||
|
<select name="connection_type" class="form-select">
|
||||||
|
<option value="usb">USB</option>
|
||||||
|
<option value="network">Network (TCP/IP)</option>
|
||||||
|
<option value="serial">Serial (RS232)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label class="form-label" data-en="IP Address" data-ar="عنوان IP">IP Address</label>
|
||||||
|
<input type="text" name="ip_address" class="form-control" placeholder="192.168.1.50">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label" data-en="Port" data-ar="المنفذ">Port</label>
|
||||||
|
<input type="number" name="port" class="form-control" placeholder="9100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" data-en="Baud Rate" data-ar="معدل الباود">Baud Rate</label>
|
||||||
|
<input type="number" name="baud_rate" class="form-control" placeholder="9600">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0">
|
||||||
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
|
||||||
|
<button type="submit" name="add_pos_device" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<?php elseif ($page === 'my_profile'): ?>
|
<?php elseif ($page === 'my_profile'): ?>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
@ -5660,6 +6104,407 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php elseif ($page === 'cash_registers'): ?>
|
||||||
|
<div class="card p-4 rounded-4 shadow-sm border-0">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<h5 class="m-0 fw-bold text-primary" data-en="Cash Registers Management" data-ar="إدارة خزائن الكاشير">Cash Registers Management</h5>
|
||||||
|
<p class="text-muted small mb-0">Define your shop counters and registers.</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#addRegisterModal">
|
||||||
|
<i class="bi bi-plus-lg me-2"></i> <span data-en="Add Register" data-ar="إضافة خزينة">Add Register</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-en="ID" data-ar="المعرف">ID</th>
|
||||||
|
<th data-en="Name" data-ar="الاسم">Name</th>
|
||||||
|
<th data-en="Status" data-ar="الحالة">Status</th>
|
||||||
|
<th data-en="Created At" data-ar="تاريخ الإنشاء">Created At</th>
|
||||||
|
<th class="text-end" data-en="Actions" data-ar="الإجراءات">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($data['cash_registers'] as $r): ?>
|
||||||
|
<tr>
|
||||||
|
<td>#<?= $r['id'] ?></td>
|
||||||
|
<td class="fw-bold"><?= htmlspecialchars($r['name']) ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge rounded-pill <?= $r['status'] === 'active' ? 'bg-success' : 'bg-danger' ?>">
|
||||||
|
<?= ucfirst($r['status']) ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td><?= $r['created_at'] ?></td>
|
||||||
|
<td class="text-end">
|
||||||
|
<button class="btn btn-sm btn-light rounded-circle" data-bs-toggle="modal" data-bs-target="#editRegisterModal<?= $r['id'] ?>"><i class="bi bi-pencil text-primary"></i></button>
|
||||||
|
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
|
||||||
|
<input type="hidden" name="id" value="<?= $r['id'] ?>">
|
||||||
|
<button type="submit" name="delete_cash_register" class="btn btn-sm btn-light rounded-circle"><i class="bi bi-trash text-danger"></i></button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
<div class="modal fade" id="editRegisterModal<?= $r['id'] ?>" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Edit Register</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-body text-start">
|
||||||
|
<input type="hidden" name="id" value="<?= $r['id'] ?>">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Register Name</label>
|
||||||
|
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($r['name']) ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Status</label>
|
||||||
|
<select name="status" class="form-select">
|
||||||
|
<option value="active" <?= $r['status'] === 'active' ? 'selected' : '' ?>>Active</option>
|
||||||
|
<option value="inactive" <?= $r['status'] === 'inactive' ? 'selected' : '' ?>>Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" name="edit_cash_register" class="btn btn-primary">Save Changes</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Modal -->
|
||||||
|
<div class="modal fade" id="addRegisterModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Add Cash Register</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Register Name</label>
|
||||||
|
<input type="text" name="name" class="form-control" placeholder="Counter 1" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" name="add_cash_register" class="btn btn-primary">Add Register</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php elseif ($page === 'register_sessions'): ?>
|
||||||
|
<div class="card p-4 rounded-4 shadow-sm border-0 mb-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<h5 class="m-0 fw-bold text-primary" data-en="Register Sessions" data-ar="جلسات الكاشير">Register Sessions</h5>
|
||||||
|
<p class="text-muted small mb-0">Manage daily opening and closing of cash registers.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<?php
|
||||||
|
$active_session = db()->prepare("SELECT s.*, r.name as register_name FROM register_sessions s JOIN cash_registers r ON s.register_id = r.id WHERE s.user_id = ? AND s.status = 'open'");
|
||||||
|
$active_session->execute([$_SESSION['user_id']]);
|
||||||
|
$session = $active_session->fetch();
|
||||||
|
?>
|
||||||
|
<?php if (!$session): ?>
|
||||||
|
<button class="btn btn-success rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#openRegisterModal">
|
||||||
|
<i class="bi bi-unlock me-2"></i> <span data-en="Open Register" data-ar="فتح الخزينة">Open Register</span>
|
||||||
|
</button>
|
||||||
|
<?php else: ?>
|
||||||
|
<button class="btn btn-danger rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#closeRegisterModal">
|
||||||
|
<i class="bi bi-lock me-2"></i> <span data-en="Close Register" data-ar="إغلاق الخزينة">Close Register</span>
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($session): ?>
|
||||||
|
<div class="alert alert-info border-0 shadow-sm d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<i class="bi bi-info-circle-fill me-2"></i>
|
||||||
|
Current Open Register: <strong><?= htmlspecialchars($session['register_name']) ?></strong> |
|
||||||
|
Opened At: <strong><?= $session['opened_at'] ?></strong> |
|
||||||
|
Opening Balance: <strong>OMR <?= number_format((float)$session['opening_balance'], 3) ?></strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="index.php?page=pos" class="btn btn-sm btn-primary rounded-pill px-3">Go to POS</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-en="Register" data-ar="الخزينة">Register</th>
|
||||||
|
<th data-en="Cashier" data-ar="الكاشير">Cashier</th>
|
||||||
|
<th data-en="Opened At" data-ar="وقت الفتح">Opened At</th>
|
||||||
|
<th data-en="Closed At" data-ar="وقت الإغلاق">Closed At</th>
|
||||||
|
<th data-en="Opening Bal." data-ar="رصيد الافتتاح">Opening Bal.</th>
|
||||||
|
<th data-en="Expected Bal." data-ar="الرصيد المتوقع">Expected Bal.</th>
|
||||||
|
<th data-en="Cash in Hand" data-ar="النقد الفعلي">Cash in Hand</th>
|
||||||
|
<th data-en="Difference" data-ar="الفرق">Difference</th>
|
||||||
|
<th data-en="Status" data-ar="الحالة">Status</th>
|
||||||
|
<th class="text-end" data-en="Report" data-ar="تقرير">Report</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($data['sessions'] as $s): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="fw-bold"><?= htmlspecialchars($s['register_name']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($s['username']) ?></td>
|
||||||
|
<td><?= $s['opened_at'] ?></td>
|
||||||
|
<td><?= $s['closed_at'] ?? '---' ?></td>
|
||||||
|
<td>OMR <?= number_format((float)$s['opening_balance'], 3) ?></td>
|
||||||
|
<td>OMR <?= number_format((float)($s['closing_balance'] ?? 0), 3) ?></td>
|
||||||
|
<td>OMR <?= number_format((float)($s['cash_in_hand'] ?? 0), 3) ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($s['status'] === 'closed'):
|
||||||
|
$diff = $s['cash_in_hand'] - $s['closing_balance'];
|
||||||
|
$color = $diff == 0 ? 'text-success' : ($diff > 0 ? 'text-info' : 'text-danger');
|
||||||
|
?>
|
||||||
|
<span class="<?= $color ?> fw-bold">OMR <?= number_format($diff, 3) ?></span>
|
||||||
|
<?php else: ?>---<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge rounded-pill <?= $s['status'] === 'open' ? 'bg-success' : 'bg-secondary' ?>">
|
||||||
|
<?= ucfirst($s['status']) ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<button class="btn btn-sm btn-light rounded-circle" data-bs-toggle="modal" data-bs-target="#sessionReportModal<?= $s['id'] ?>"><i class="bi bi-file-earmark-text text-primary"></i></button>
|
||||||
|
|
||||||
|
<!-- Session Report Modal -->
|
||||||
|
<div class="modal fade" id="sessionReportModal<?= $s['id'] ?>" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Session Cashflow Report (#<?= $s['id'] ?>)</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body text-start">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p class="mb-1 text-muted small">Register</p>
|
||||||
|
<h6><?= htmlspecialchars($s['register_name']) ?></h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p class="mb-1 text-muted small">Cashier</p>
|
||||||
|
<h6><?= htmlspecialchars($s['username']) ?></h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p class="mb-1 text-muted small">Opened At</p>
|
||||||
|
<h6><?= $s['opened_at'] ?></h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p class="mb-1 text-muted small">Closed At</p>
|
||||||
|
<h6><?= $s['closed_at'] ?? 'Still Open' ?></h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex justify-content-between mb-2">
|
||||||
|
<span>Opening Balance:</span>
|
||||||
|
<span>OMR <?= number_format((float)$s['opening_balance'], 3) ?></span>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
// Breakdown of sales by payment method
|
||||||
|
$breakdown = db()->prepare("SELECT p.payment_method, SUM(p.amount) as total FROM pos_payments p JOIN pos_transactions t ON p.transaction_id = t.id WHERE t.register_session_id = ? AND t.status = 'completed' GROUP BY p.payment_method");
|
||||||
|
$breakdown->execute([$s['id']]);
|
||||||
|
$methods = $breakdown->fetchAll();
|
||||||
|
$total_sales = 0;
|
||||||
|
foreach ($methods as $m): $total_sales += $m['total']; ?>
|
||||||
|
<div class="d-flex justify-content-between mb-2">
|
||||||
|
<span class="text-uppercase">Sales (<?= htmlspecialchars($m['payment_method']) ?>):</span>
|
||||||
|
<span>OMR <?= number_format((float)$m['total'], 3) ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<div class="d-flex justify-content-between mb-2 fw-bold border-top pt-2">
|
||||||
|
<span>Total Sales:</span>
|
||||||
|
<span>OMR <?= number_format($total_sales, 3) ?></span>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex justify-content-between mb-2 h5 text-primary">
|
||||||
|
<span>Expected Cash in Hand:</span>
|
||||||
|
<span>OMR <?= number_format((float)($s['closing_balance'] ?? 0), 3) ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between mb-2 h5 text-success">
|
||||||
|
<span>Actual Cash in Hand:</span>
|
||||||
|
<span>OMR <?= number_format((float)($s['cash_in_hand'] ?? 0), 3) ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between mb-2 h5 <?= ($s['cash_in_hand'] - $s['closing_balance']) >= 0 ? 'text-info' : 'text-danger' ?>">
|
||||||
|
<span>Difference:</span>
|
||||||
|
<span>OMR <?= number_format((float)(($s['cash_in_hand'] ?? 0) - ($s['closing_balance'] ?? 0)), 3) ?></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<h6 class="fw-bold mb-3">Detailed Transactions by Method</h6>
|
||||||
|
<?php
|
||||||
|
foreach ($methods as $m):
|
||||||
|
$details = db()->prepare("SELECT t.transaction_no, t.created_at, p.amount FROM pos_payments p JOIN pos_transactions t ON p.transaction_id = t.id WHERE t.register_session_id = ? AND t.status = 'completed' AND p.payment_method = ?");
|
||||||
|
$details->execute([$s['id'], $m['payment_method']]);
|
||||||
|
$trans = $details->fetchAll();
|
||||||
|
?>
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="bg-light p-2 mb-2 d-flex justify-content-between align-items-center rounded border-start border-4 <?= strtolower($m['payment_method']) === 'cash' ? 'border-success' : 'border-primary' ?>">
|
||||||
|
<span class="text-uppercase fw-bold small"><?= htmlspecialchars($m['payment_method']) ?></span>
|
||||||
|
<span class="badge bg-white text-dark border rounded-pill">OMR <?= number_format((float)$m['total'], 3) ?></span>
|
||||||
|
</div>
|
||||||
|
<table class="table table-sm table-hover small mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-muted border-bottom">
|
||||||
|
<th>TX #</th>
|
||||||
|
<th>Time</th>
|
||||||
|
<th class="text-end">Amount</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($trans as $tr): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= htmlspecialchars($tr['transaction_no']) ?></td>
|
||||||
|
<td><?= date('H:i', strtotime($tr['created_at'])) ?></td>
|
||||||
|
<td class="text-end">OMR <?= number_format((float)$tr['amount'], 3) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php if ($s['notes']): ?>
|
||||||
|
<hr>
|
||||||
|
<p class="mb-1 text-muted small">Notes:</p>
|
||||||
|
<p class="mb-0 small bg-light p-2 rounded"><?= nl2br(htmlspecialchars($s['notes'])) ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="window.print()">Print Report</button>
|
||||||
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Open Register Modal -->
|
||||||
|
<div class="modal fade" id="openRegisterModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Open Register Session</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Select Register / Counter</label>
|
||||||
|
<select name="register_id" class="form-select" required>
|
||||||
|
<?php foreach ($data['cash_registers'] as $reg): ?>
|
||||||
|
<option value="<?= $reg['id'] ?>"><?= htmlspecialchars($reg['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Opening Cash Balance (OMR)</label>
|
||||||
|
<input type="number" step="0.001" name="opening_balance" class="form-control" value="0.000" required>
|
||||||
|
<div class="form-text">Enter the amount of cash already in the drawer.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" name="open_register" class="btn btn-success">Open Register</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Close Register Modal -->
|
||||||
|
<?php if ($session): ?>
|
||||||
|
<div class="modal fade" id="closeRegisterModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Close Register Session (#<?= $session['id'] ?>)</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="session_id" value="<?= $session['id'] ?>">
|
||||||
|
<div class="alert alert-warning py-2 small border-0 shadow-sm">
|
||||||
|
<i class="bi bi-exclamation-triangle me-2"></i> Before closing, please count all cash in your register.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$curBreakdown = db()->prepare("SELECT p.payment_method, SUM(p.amount) as total FROM pos_payments p JOIN pos_transactions t ON p.transaction_id = t.id WHERE t.register_session_id = ? AND t.status = 'completed' GROUP BY p.payment_method");
|
||||||
|
$curBreakdown->execute([$session['id']]);
|
||||||
|
$curMethods = $curBreakdown->fetchAll();
|
||||||
|
$curCashSales = 0;
|
||||||
|
foreach($curMethods as $cm) if(strtolower($cm['payment_method']) === 'cash') $curCashSales = (float)$cm['total'];
|
||||||
|
?>
|
||||||
|
<div class="card bg-light border-0 mb-3 small">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title fw-bold small text-uppercase mb-2">Session Summary</h6>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<span class="text-muted">Opening Cash:</span>
|
||||||
|
<span class="fw-bold">OMR <?= number_format((float)$session['opening_balance'], 3) ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<span class="text-muted">Cash Sales:</span>
|
||||||
|
<span class="fw-bold">OMR <?= number_format($curCashSales, 3) ?></span>
|
||||||
|
</div>
|
||||||
|
<hr class="my-2">
|
||||||
|
<div class="d-flex justify-content-between fw-bold text-primary mb-2">
|
||||||
|
<span>Expected Cash in Hand:</span>
|
||||||
|
<span>OMR <?= number_format((float)$session['opening_balance'] + $curCashSales, 3) ?></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php $hasOther = false; foreach($curMethods as $cm): if(strtolower($cm['payment_method']) !== 'cash'): $hasOther = true; ?>
|
||||||
|
<div class="d-flex justify-content-between mt-1 text-muted border-top pt-1">
|
||||||
|
<span><?= htmlspecialchars($cm['payment_method']) ?> Total:</span>
|
||||||
|
<span>OMR <?= number_format((float)$cm['total'], 3) ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Total Cash in Hand (Actual Counted)</label>
|
||||||
|
<input type="number" step="0.001" name="cash_in_hand" class="form-control" required placeholder="0.000">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Closing Notes / Comments</label>
|
||||||
|
<textarea name="notes" class="form-control" rows="3" placeholder="Any discrepancies or remarks..."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" name="close_register" class="btn btn-danger">Close Register & Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php elseif ($page === 'logs'): ?>
|
<?php elseif ($page === 'logs'): ?>
|
||||||
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
|
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
|
||||||
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center border-0">
|
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center border-0">
|
||||||
|
|||||||
@ -90,41 +90,48 @@ class LicenseService {
|
|||||||
* Remote API Caller
|
* Remote API Caller
|
||||||
*/
|
*/
|
||||||
private static function callRemoteApi($endpoint, $params) {
|
private static function callRemoteApi($endpoint, $params) {
|
||||||
// In a real production environment, this would hit your licensing server.
|
|
||||||
// For this demonstration, we simulate the request logic.
|
|
||||||
|
|
||||||
$url = self::$remote_api_url . $endpoint;
|
$url = self::$remote_api_url . $endpoint;
|
||||||
|
|
||||||
/*
|
// Check if we are in local development / simulation mode
|
||||||
// Real implementation would look like this:
|
// If the URL is still the default placeholder, we use simulation
|
||||||
|
if (strpos(self::$remote_api_url, 'your-domain.com') !== false) {
|
||||||
|
return self::simulateApi($endpoint, $params);
|
||||||
|
}
|
||||||
|
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||||
$resp = curl_exec($ch);
|
$resp = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
$data = json_decode($resp, true);
|
|
||||||
return $data;
|
|
||||||
*/
|
|
||||||
|
|
||||||
// SIMULATION: If the key starts with 'FLAT-' (case-insensitive), we treat it as valid.
|
if ($http_code !== 200) {
|
||||||
$clean_key = strtoupper(trim($params['license_key'] ?? ''));
|
return ['success' => false, 'error' => "Remote server returned error code $http_code."];
|
||||||
if (strpos($clean_key, 'FLAT-') === 0) {
|
|
||||||
// Check if it's a verification request
|
|
||||||
if ($endpoint === '/verify') {
|
|
||||||
return ['success' => true];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => true,
|
|
||||||
'activation_token' => hash('sha256', $params['license_key'] . $params['fingerprint'] . 'SECRET_SALT')
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
$data = json_decode($resp, true);
|
||||||
'success' => false,
|
if (!$data) {
|
||||||
'error' => 'License key invalid or expired. Please contact support.'
|
return ['success' => false, 'error' => "Invalid response from remote server."];
|
||||||
];
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local Simulation for development purposes
|
||||||
|
*/
|
||||||
|
private static function simulateApi($endpoint, $params) {
|
||||||
|
$clean_key = strtoupper(trim($params['license_key'] ?? ''));
|
||||||
|
if (strpos($clean_key, 'FLAT-') === 0) {
|
||||||
|
if ($endpoint === '/verify') return ['success' => true];
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'activation_token' => hash('sha256', $params['license_key'] . $params['fingerprint'] . 'DEBUG_SALT')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'error' => 'License key invalid or expired (Simulation Mode).'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
license_manager/config.php
Normal file
21
license_manager/config.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
// Configuration for the SEPARATE Licensing Server
|
||||||
|
// This file should be configured on the server where you host the License Manager.
|
||||||
|
|
||||||
|
define('DB_HOST', '127.0.0.1');
|
||||||
|
define('DB_NAME', 'license_manager_db');
|
||||||
|
define('DB_USER', 'root');
|
||||||
|
define('DB_PASS', '');
|
||||||
|
define('SERVER_SECRET', 'CHANGE_THIS_TO_A_RANDOM_STRING_FOR_SECURITY');
|
||||||
|
|
||||||
|
function db_manager() {
|
||||||
|
static $pdo;
|
||||||
|
if (!$pdo) {
|
||||||
|
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4";
|
||||||
|
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return $pdo;
|
||||||
|
}
|
||||||
26
license_manager/database.sql
Normal file
26
license_manager/database.sql
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
-- SQL for the License Manager Database
|
||||||
|
-- Create a new database called 'license_manager_db' and run this script.
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS licenses (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
license_key VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
max_activations INT DEFAULT 1,
|
||||||
|
status ENUM('active', 'suspended', 'expired') DEFAULT 'active',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS activations (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
license_id INT NOT NULL,
|
||||||
|
fingerprint VARCHAR(255) NOT NULL,
|
||||||
|
domain VARCHAR(255),
|
||||||
|
product VARCHAR(255),
|
||||||
|
activated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (license_id) REFERENCES licenses(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE KEY (license_id, fingerprint)
|
||||||
|
) ENGINE=InnoDB;
|
||||||
|
|
||||||
|
-- Seed some test data
|
||||||
|
INSERT INTO licenses (license_key, max_activations) VALUES ('FLAT-8822-1192-3301', 1);
|
||||||
|
INSERT INTO licenses (license_key, max_activations) VALUES ('FLAT-TEST-KEY-0001', 5);
|
||||||
|
INSERT INTO licenses (license_key, max_activations) VALUES ('FLAT-DEV-UNLIMITED', 999);
|
||||||
115
license_manager/index.php
Normal file
115
license_manager/index.php
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* LICENSE MANAGER SERVER (Standalone Module)
|
||||||
|
*
|
||||||
|
* This is the central authority that manages license keys and activations.
|
||||||
|
* It should be hosted on a secure, separate server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/config.php';
|
||||||
|
|
||||||
|
// Simple Router
|
||||||
|
$request_uri = $_SERVER['REQUEST_URI'] ?? '';
|
||||||
|
$endpoint = '';
|
||||||
|
|
||||||
|
if (strpos($request_uri, '/activate') !== false) $endpoint = 'activate';
|
||||||
|
if (strpos($request_uri, '/verify') !== false) $endpoint = 'verify';
|
||||||
|
if (strpos($request_uri, '/deactivate') !== false) $endpoint = 'deactivate';
|
||||||
|
|
||||||
|
// If running as a simple script without proper URL rewriting
|
||||||
|
if (empty($endpoint)) {
|
||||||
|
$endpoint = $_GET['action'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db_manager();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Database connection failed.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($endpoint === 'activate') {
|
||||||
|
$key = strtoupper(trim($input['license_key'] ?? ''));
|
||||||
|
$fingerprint = $input['fingerprint'] ?? '';
|
||||||
|
$domain = $input['domain'] ?? '';
|
||||||
|
$product = $input['product'] ?? '';
|
||||||
|
|
||||||
|
if (empty($key) || empty($fingerprint)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Missing required parameters.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Find License
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM licenses WHERE license_key = ? LIMIT 1");
|
||||||
|
$stmt->execute([$key]);
|
||||||
|
$license = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$license) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid license key.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($license['status'] !== 'active') {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'License is ' . $license['status'] . '.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check current activations
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM activations WHERE license_id = ?");
|
||||||
|
$stmt->execute([$license['id']]);
|
||||||
|
$current_activations = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
// 3. Check if this machine is already activated
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM activations WHERE license_id = ? AND fingerprint = ?");
|
||||||
|
$stmt->execute([$license['id'], $fingerprint]);
|
||||||
|
$existing = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$existing) {
|
||||||
|
if ($current_activations >= $license['max_activations']) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Maximum activation limit reached.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record new activation
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO activations (license_id, fingerprint, domain, product) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$license['id'], $fingerprint, $domain, $product]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success: Return signed token
|
||||||
|
$token = hash_hmac('sha256', $key . $fingerprint, SERVER_SECRET);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'activation_token' => $token
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($endpoint === 'verify') {
|
||||||
|
$key = strtoupper(trim($input['license_key'] ?? ''));
|
||||||
|
$fingerprint = $input['fingerprint'] ?? '';
|
||||||
|
$token = $input['token'] ?? '';
|
||||||
|
|
||||||
|
// Simple validation: re-calculate token and check DB status
|
||||||
|
$expected_token = hash_hmac('sha256', $key . $fingerprint, SERVER_SECRET);
|
||||||
|
|
||||||
|
if ($token !== $expected_token) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid activation token.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT status FROM licenses WHERE license_key = ?");
|
||||||
|
$stmt->execute([$key]);
|
||||||
|
$status = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
if ($status === 'active') {
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'License is no longer active.']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid endpoint.']);
|
||||||
@ -1,53 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SAMPLE LICENSE MANAGER SERVER
|
|
||||||
* This would be hosted on your own server (e.g., https://licensing.your-domain.com/api/v1)
|
|
||||||
*/
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
// 1. Mock Database of valid keys
|
|
||||||
$valid_keys = [
|
|
||||||
'FLAT-8822-1192-3301' => ['status' => 'active', 'max_activations' => 1],
|
|
||||||
'FLAT-TEST-KEY-0001' => ['status' => 'active', 'max_activations' => 3],
|
|
||||||
];
|
|
||||||
|
|
||||||
// 2. Mock Database of current activations (Key => Fingerprint)
|
|
||||||
$activations = [
|
|
||||||
// 'FLAT-8822-1192-3301' => 'hash_from_client'
|
|
||||||
];
|
|
||||||
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
|
||||||
$endpoint = $_SERVER['PATH_INFO'] ?? '';
|
|
||||||
|
|
||||||
if ($endpoint === '/activate') {
|
|
||||||
$key = strtoupper(trim($input['license_key'] ?? ''));
|
|
||||||
$fingerprint = $input['fingerprint'] ?? '';
|
|
||||||
|
|
||||||
if (!isset($valid_keys[$key])) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Invalid license key.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($valid_keys[$key]['status'] !== 'active') {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'License is expired or suspended.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if already activated on another machine
|
|
||||||
if (isset($activations[$key]) && $activations[$key] !== $fingerprint) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'License already in use on another server.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success: Return a signed token
|
|
||||||
echo json_encode([
|
|
||||||
'success' => true,
|
|
||||||
'activation_token' => hash_hmac('sha256', $key . $fingerprint, 'YOUR_SECRET_SERVER_SALT')
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($endpoint === '/verify') {
|
|
||||||
// Similar logic to check if the token is still valid or if the key was revoked
|
|
||||||
echo json_encode(['success' => true]);
|
|
||||||
}
|
|
||||||
@ -9,3 +9,7 @@
|
|||||||
2026-02-18 10:46:04 - POST: {"license_key":"Flat-8822-1192-3301","activate":""}
|
2026-02-18 10:46:04 - POST: {"license_key":"Flat-8822-1192-3301","activate":""}
|
||||||
2026-02-18 10:46:14 - POST: {"license_key":": FLAT-8822-1192-3301","activate":""}
|
2026-02-18 10:46:14 - POST: {"license_key":": FLAT-8822-1192-3301","activate":""}
|
||||||
2026-02-18 10:48:04 - POST: {"license_key":" FLAT-8822-1192-3301","activate":""}
|
2026-02-18 10:48:04 - POST: {"license_key":" FLAT-8822-1192-3301","activate":""}
|
||||||
|
2026-02-18 11:33:56 - POST: {"name":"Counter 1","add_cash_register":""}
|
||||||
|
2026-02-18 11:34:15 - POST: {"name":"Counter 2","add_cash_register":""}
|
||||||
|
2026-02-18 11:43:14 - POST: {"register_id":"1","opening_balance":"0.000","open_register":""}
|
||||||
|
2026-02-18 11:50:03 - POST: {"register_id":"1","opening_balance":"0.000","open_register":""}
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
user_id|i:1;username|s:5:"admin";user_role_name|s:13:"Administrator";user_permissions|s:3:"all";profile_pic|s:32:"uploads/profile_1_1771401598.png";
|
user_id|i:1;username|s:5:"admin";user_role_name|s:13:"Administrator";user_permissions|s:3:"all";profile_pic|s:32:"uploads/profile_1_1771401598.png";register_session_id|s:1:"1";
|
||||||
Loading…
x
Reference in New Issue
Block a user