Autosave: 20260318-094533
This commit is contained in:
parent
9a2273fa5b
commit
a410151a7c
@ -149,6 +149,7 @@ body {
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
font-weight: 400; /* Normal font for sub items */
|
||||
color: rgba(255, 255, 255, 0.9); /* Lighter text for blue bg */
|
||||
padding: 0.6rem 1rem;
|
||||
display: flex;
|
||||
@ -169,8 +170,8 @@ body {
|
||||
|
||||
.nav-section-title {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.7) !important; /* Lighter text for blue bg */
|
||||
font-weight: 700 !important; /* Bold for main category */
|
||||
color: #38bdf8 !important; /* Sky Blue for distinction */
|
||||
letter-spacing: 0.05em;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
@ -713,4 +714,4 @@ body:not(.theme-default) .form-select:focus {
|
||||
.form-grid-3 .form-select,
|
||||
.form-grid-3 .input-group {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
2
db/migrations/20260318_add_outlet_id_to_purchases.sql
Normal file
2
db/migrations/20260318_add_outlet_id_to_purchases.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE purchases ADD COLUMN outlet_id INT DEFAULT 1;
|
||||
UPDATE purchases SET outlet_id = 1 WHERE outlet_id IS NULL;
|
||||
12
db/migrations/20260318_create_outlets_table.sql
Normal file
12
db/migrations/20260318_create_outlets_table.sql
Normal file
@ -0,0 +1,12 @@
|
||||
CREATE TABLE IF NOT EXISTS outlets (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
address TEXT NULL,
|
||||
phone VARCHAR(50) NULL,
|
||||
status ENUM('active', 'inactive') DEFAULT 'active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO outlets (id, name, address, phone, status, created_at)
|
||||
SELECT 1, 'Main Outlet', 'Head Office', '', 'active', NOW()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM outlets WHERE id = 1);
|
||||
65
db/migrations/20260318_multi_outlet_schema.sql
Normal file
65
db/migrations/20260318_multi_outlet_schema.sql
Normal file
@ -0,0 +1,65 @@
|
||||
-- Multi-Outlet Implementation
|
||||
|
||||
-- 1. Create outlet_stock table
|
||||
CREATE TABLE IF NOT EXISTS outlet_stock (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
outlet_id INT NOT NULL,
|
||||
item_id INT NOT NULL,
|
||||
quantity DECIMAL(15, 2) DEFAULT 0.00,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY unique_item_outlet (outlet_id, item_id),
|
||||
CONSTRAINT fk_outlet_stock_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_outlet_stock_item FOREIGN KEY (item_id) REFERENCES stock_items(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 2. Migrate existing stock to Default Outlet (ID 1)
|
||||
-- We assume Outlet 1 exists (created in previous migration)
|
||||
INSERT INTO outlet_stock (outlet_id, item_id, quantity)
|
||||
SELECT 1, id, stock_quantity FROM stock_items
|
||||
ON DUPLICATE KEY UPDATE quantity = stock_items.stock_quantity;
|
||||
|
||||
-- 3. Add outlet_id to tables
|
||||
-- Invoices
|
||||
ALTER TABLE invoices ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT NULL;
|
||||
ALTER TABLE invoices ADD CONSTRAINT fk_invoices_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE SET NULL;
|
||||
UPDATE invoices SET outlet_id = 1 WHERE outlet_id IS NULL;
|
||||
|
||||
-- POS Transactions
|
||||
ALTER TABLE pos_transactions ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT NULL;
|
||||
ALTER TABLE pos_transactions ADD CONSTRAINT fk_pos_trans_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE SET NULL;
|
||||
UPDATE pos_transactions SET outlet_id = 1 WHERE outlet_id IS NULL;
|
||||
|
||||
-- POS Held Carts
|
||||
ALTER TABLE pos_held_carts ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT NULL;
|
||||
ALTER TABLE pos_held_carts ADD CONSTRAINT fk_pos_carts_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE SET NULL;
|
||||
UPDATE pos_held_carts SET outlet_id = 1 WHERE outlet_id IS NULL;
|
||||
|
||||
-- Quotations
|
||||
ALTER TABLE quotations ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT NULL;
|
||||
ALTER TABLE quotations ADD CONSTRAINT fk_quotations_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE SET NULL;
|
||||
UPDATE quotations SET outlet_id = 1 WHERE outlet_id IS NULL;
|
||||
|
||||
-- LPOs
|
||||
ALTER TABLE lpos ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT NULL;
|
||||
-- Note: lpos might already have outlet_id from a previous failed attempt or partial migration, checking IF NOT EXISTS is good.
|
||||
-- We need to check if the foreign key exists before adding it to avoid errors, or just try adding it.
|
||||
-- For simplicity in this environment, we'll try to add it. If it fails, it might be due to duplicate name.
|
||||
-- Let's use a safe procedure for FKs if possible, or just standard ALTER.
|
||||
-- safe bet:
|
||||
ALTER TABLE lpos ADD CONSTRAINT fk_lpos_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE SET NULL;
|
||||
UPDATE lpos SET outlet_id = 1 WHERE outlet_id IS NULL;
|
||||
|
||||
-- Sales Returns
|
||||
ALTER TABLE sales_returns ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT NULL;
|
||||
ALTER TABLE sales_returns ADD CONSTRAINT fk_sales_returns_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE SET NULL;
|
||||
UPDATE sales_returns SET outlet_id = 1 WHERE outlet_id IS NULL;
|
||||
|
||||
-- Purchase Returns
|
||||
ALTER TABLE purchase_returns ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT NULL;
|
||||
ALTER TABLE purchase_returns ADD CONSTRAINT fk_purchase_returns_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE SET NULL;
|
||||
UPDATE purchase_returns SET outlet_id = 1 WHERE outlet_id IS NULL;
|
||||
|
||||
-- Expenses
|
||||
ALTER TABLE expenses ADD COLUMN IF NOT EXISTS outlet_id INT DEFAULT NULL;
|
||||
ALTER TABLE expenses ADD CONSTRAINT fk_expenses_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE SET NULL;
|
||||
UPDATE expenses SET outlet_id = 1 WHERE outlet_id IS NULL;
|
||||
7
db/migrations/20260318_user_outlets_table.sql
Normal file
7
db/migrations/20260318_user_outlets_table.sql
Normal file
@ -0,0 +1,7 @@
|
||||
CREATE TABLE IF NOT EXISTS user_outlets (
|
||||
user_id INT(11) NOT NULL,
|
||||
outlet_id INT(11) NOT NULL,
|
||||
PRIMARY KEY (user_id, outlet_id),
|
||||
CONSTRAINT fk_user_outlets_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_user_outlets_outlet FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE
|
||||
);
|
||||
58
fix_index_py.py
Normal file
58
fix_index_py.py
Normal file
@ -0,0 +1,58 @@
|
||||
|
||||
with open('index.php', 'r') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# The corrupted block starts around line 50 (index 50 is line 51)
|
||||
# And ends around line 65 where "// Timezone Setup" is.
|
||||
# Let's find "require_once 'db/config.php';" which I added, and "// Timezone Setup"
|
||||
|
||||
start_idx = -1
|
||||
end_idx = -1
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if "require_once 'db/config.php';" in line:
|
||||
start_idx = i
|
||||
if "// Timezone Setup" in line:
|
||||
end_idx = i
|
||||
break
|
||||
|
||||
if start_idx != -1 and end_idx != -1:
|
||||
print(f"Replacing lines {start_idx+1} to {end_idx}")
|
||||
|
||||
new_block = """require_once 'db/config.php';
|
||||
require_once 'includes/stock_helper.php';
|
||||
|
||||
// Helper for current outlet
|
||||
if (!function_exists('current_outlet_id')) {
|
||||
function current_outlet_id() {
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
return (int)($_SESSION['outlet_id'] ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Outlet Switch
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'switch_outlet' && isset($_GET['id'])) {
|
||||
$target_id = (int)$_GET['id'];
|
||||
$allowed_outlets = $_SESSION['user_outlets'] ?? [1];
|
||||
$is_admin = ($_SESSION['user_role_name'] ?? '') === 'Administrator';
|
||||
|
||||
if ($is_admin || in_array($target_id, $allowed_outlets)) {
|
||||
$stmt = db()->prepare("SELECT id FROM outlets WHERE id = ? AND status = 'active'");
|
||||
$stmt->execute([$target_id]);
|
||||
if ($stmt->fetchColumn()) {
|
||||
$_SESSION['outlet_id'] = $target_id;
|
||||
}
|
||||
}
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
"""
|
||||
# Replace the slice
|
||||
lines[start_idx:end_idx] = [new_block]
|
||||
|
||||
with open('index.php', 'w') as f:
|
||||
f.writelines(lines)
|
||||
print("Fixed index.php")
|
||||
else:
|
||||
print("Could not find block boundaries.")
|
||||
32
includes/stock_helper.php
Normal file
32
includes/stock_helper.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// includes/stock_helper.php
|
||||
|
||||
if (!function_exists('current_outlet_id')) {
|
||||
function current_outlet_id() {
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
return (int)($_SESSION['outlet_id'] ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('update_stock')) {
|
||||
function update_stock($item_id, $qty, $outlet_id = null) {
|
||||
if ($outlet_id === null) {
|
||||
$outlet_id = current_outlet_id();
|
||||
}
|
||||
$db = db();
|
||||
|
||||
// 1. Update/Insert into outlet_stock (Per-Outlet Source of Truth)
|
||||
$check = $db->prepare("SELECT id FROM outlet_stock WHERE outlet_id = ? AND item_id = ?");
|
||||
$check->execute([$outlet_id, $item_id]);
|
||||
if (!$check->fetchColumn()) {
|
||||
$db->prepare("INSERT INTO outlet_stock (outlet_id, item_id, quantity) VALUES (?, ?, 0)")->execute([$outlet_id, $item_id]);
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE outlet_stock SET quantity = quantity + ? WHERE outlet_id = ? AND item_id = ?");
|
||||
$stmt->execute([$qty, $outlet_id, $item_id]);
|
||||
|
||||
// 2. Update global stock_items (Legacy/Aggregate Cache)
|
||||
$stmtGlobal = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
|
||||
$stmtGlobal->execute([$qty, $item_id]);
|
||||
}
|
||||
}
|
||||
399
index.php
399
index.php
@ -49,6 +49,32 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
file_put_contents('post_debug.log', date('Y-m-d H:i:s') . " - POST: " . json_encode($_POST) . "\n", FILE_APPEND);
|
||||
}
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/stock_helper.php';
|
||||
|
||||
// Helper for current outlet
|
||||
if (!function_exists('current_outlet_id')) {
|
||||
function current_outlet_id() {
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
return (int)($_SESSION['outlet_id'] ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Outlet Switch
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'switch_outlet' && isset($_GET['id'])) {
|
||||
$target_id = (int)$_GET['id'];
|
||||
$allowed_outlets = $_SESSION['user_outlets'] ?? [1];
|
||||
$is_admin = ($_SESSION['user_role_name'] ?? '') === 'Administrator';
|
||||
|
||||
if ($target_id === -1) { $_SESSION['outlet_id'] = -1; } elseif ($is_admin || in_array($target_id, $allowed_outlets)) {
|
||||
$stmt = db()->prepare("SELECT id FROM outlets WHERE id = ? AND status = 'active'");
|
||||
$stmt->execute([$target_id]);
|
||||
if ($stmt->fetchColumn()) {
|
||||
$_SESSION['outlet_id'] = $target_id;
|
||||
}
|
||||
}
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Timezone Setup
|
||||
try {
|
||||
@ -195,7 +221,6 @@ if ($page === 'activate') {
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'db/BackupService.php';
|
||||
require_once 'includes/accounting_helper.php';
|
||||
|
||||
// Helper to check permissions
|
||||
@ -357,6 +382,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
|
||||
|
||||
$_SESSION['profile_pic'] = $u['profile_pic'];
|
||||
$_SESSION['theme'] = $u['theme'] ?? 'default';
|
||||
|
||||
// --- Multi-Outlet Login Logic ---
|
||||
// Fetch assigned outlets
|
||||
$outletStmt = db()->prepare("SELECT outlet_id FROM user_outlets WHERE user_id = ?");
|
||||
$outletStmt->execute([$u['id']]);
|
||||
$user_outlets = $outletStmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (empty($user_outlets)) {
|
||||
if (($u['role_name'] ?? '') === 'Administrator') {
|
||||
$allOutlets = db()->query("SELECT id FROM outlets WHERE status = 'active'")->fetchAll(PDO::FETCH_COLUMN);
|
||||
$user_outlets = $allOutlets ?: [1];
|
||||
} else {
|
||||
$user_outlets = [1];
|
||||
}
|
||||
}
|
||||
$_SESSION['user_outlets'] = $user_outlets;
|
||||
$_SESSION['outlet_id'] = $user_outlets[0];
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
} else {
|
||||
@ -401,7 +443,7 @@ if (isset($_GET['action']) || isset($_POST['action'])) {
|
||||
exit;
|
||||
}
|
||||
$searchTerm = "%$q%";
|
||||
$stmt = db()->prepare("SELECT * FROM stock_items WHERE name_en LIKE ? OR name_ar LIKE ? OR sku LIKE ? LIMIT 15");
|
||||
$oid = current_outlet_id(); $stmt = db()->prepare("SELECT i.*, COALESCE(os.quantity, 0) as stock_quantity FROM stock_items i LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $oid WHERE (i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?) LIMIT 15");
|
||||
$stmt->execute([$searchTerm, $searchTerm, $searchTerm]);
|
||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
exit;
|
||||
@ -491,19 +533,19 @@ if (isset($_GET['action']) || isset($_POST['action'])) {
|
||||
$items_for_journal[] = ['id' => $item['id'], 'qty' => $item['qty']];
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO invoices (transaction_no, customer_id, invoice_date, payment_type, total_amount, vat_amount, total_with_vat, paid_amount, status, register_session_id, is_pos, discount_amount, loyalty_points_redeemed, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'paid', ?, 1, ?, ?, ?)");
|
||||
$stmt = $db->prepare("INSERT INTO invoices (transaction_no, customer_id, invoice_date, payment_type, total_amount, vat_amount, total_with_vat, paid_amount, status, register_session_id, is_pos, discount_amount, loyalty_points_redeemed, created_by, outlet_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'paid', ?, 1, ?, ?, ?, ?)");
|
||||
$stmt->execute([$transaction_no, $customer_id, date('Y-m-d'), 'pos', $total_amount, $tax_amount, $net_amount, $net_amount, $session_id, $discount_amount, $loyalty_redeemed, $_SESSION['user_id']]);
|
||||
$transaction_id = (int)$db->lastInsertId();
|
||||
|
||||
// Insert Items & Update Stock
|
||||
$stmtItem = $db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
|
||||
// $stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
|
||||
|
||||
foreach ($items as $item) {
|
||||
$sub = (float)$item['price'] * (float)$item['qty'];
|
||||
$va = (float)($item['vat_amount'] ?? 0);
|
||||
$stmtItem->execute([$transaction_id, $item['id'], $item['qty'], $item['price'], $va, $sub]);
|
||||
$stmtStock->execute([$item['qty'], $item['id']]);
|
||||
update_stock($item['id'], -$item['qty']);
|
||||
}
|
||||
|
||||
// Insert Payments
|
||||
@ -946,7 +988,7 @@ function getPromotionalPrice($item) {
|
||||
|
||||
// Update stock
|
||||
$change = ($type === 'sale') ? -$qty : $qty;
|
||||
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$change, $item_id]);
|
||||
update_stock($item_id, $change);
|
||||
$items_for_journal[] = ['id' => $item_id, 'qty' => $qty];
|
||||
}
|
||||
|
||||
@ -1120,7 +1162,7 @@ function getPromotionalPrice($item) {
|
||||
|
||||
$total_with_vat = $total_subtotal + $total_vat;
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO lpos (supplier_id, lpo_date, delivery_date, status, total_amount, vat_amount, total_with_vat, terms_conditions) VALUES (?, ?, ?, 'pending', ?, ?, ?, ?)");
|
||||
$stmt = $db->prepare("INSERT INTO lpos (supplier_id, lpo_date, delivery_date, status, total_amount, vat_amount, total_with_vat, terms_conditions, outlet_id) VALUES (?, ?, ?, 'pending', ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$supp_id, $lpo_date, $delivery_date, $total_subtotal, $total_vat, $total_with_vat, $terms]);
|
||||
$lpo_id = $db->lastInsertId();
|
||||
|
||||
@ -1225,8 +1267,8 @@ function getPromotionalPrice($item) {
|
||||
|
||||
// Create Invoice
|
||||
$inv_date = date('Y-m-d');
|
||||
$stmtInv = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0)");
|
||||
$stmtInv->execute([$quot['customer_id'], $inv_date, $quot['total_amount'], $quot['vat_amount'], $quot['total_with_vat']]);
|
||||
$stmtInv = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount, outlet_id) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0, ?)");
|
||||
$stmtInv->execute([$quot['customer_id'], $inv_date, $quot['total_amount'], $quot['vat_amount'], $quot['total_with_vat'], current_outlet_id()]);
|
||||
$inv_id = $db->lastInsertId();
|
||||
|
||||
$items_for_journal = [];
|
||||
@ -1234,7 +1276,7 @@ function getPromotionalPrice($item) {
|
||||
$db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$inv_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['vat_amount'], $item['total_price']]);
|
||||
|
||||
// Update stock
|
||||
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?")->execute([$item['quantity'], $item['item_id']]);
|
||||
update_stock($item['item_id'], -$item['quantity']);
|
||||
$items_for_journal[] = ['id' => $item['item_id'], 'qty' => $item['quantity']];
|
||||
}
|
||||
|
||||
@ -1268,8 +1310,8 @@ function getPromotionalPrice($item) {
|
||||
|
||||
// Create Purchase Invoice
|
||||
$pur_date = date('Y-m-d');
|
||||
$stmtPur = $db->prepare("INSERT INTO purchases (supplier_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0)");
|
||||
$stmtPur->execute([$lpo['supplier_id'], $pur_date, $lpo['total_amount'], $lpo['vat_amount'], $lpo['total_with_vat']]);
|
||||
$stmtPur = $db->prepare("INSERT INTO purchases (supplier_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount, outlet_id) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0, ?)");
|
||||
$stmtPur->execute([$lpo['supplier_id'], $pur_date, $lpo['total_amount'], $lpo['vat_amount'], $lpo['total_with_vat'], current_outlet_id()]);
|
||||
$pur_id = $db->lastInsertId();
|
||||
|
||||
$items_for_journal = [];
|
||||
@ -1277,7 +1319,7 @@ function getPromotionalPrice($item) {
|
||||
$db->prepare("INSERT INTO purchase_items (purchase_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$pur_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['vat_amount'], $item['total_amount']]);
|
||||
|
||||
// Update stock
|
||||
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$item['quantity'], $item['item_id']]);
|
||||
update_stock($item['item_id'], $item['quantity']);
|
||||
$items_for_journal[] = ['id' => $item['item_id'], 'qty' => $item['quantity']];
|
||||
}
|
||||
|
||||
@ -1461,7 +1503,7 @@ function getPromotionalPrice($item) {
|
||||
|
||||
$total_with_vat = $total_subtotal + $total_vat;
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO lpos (supplier_id, lpo_date, delivery_date, status, total_amount, vat_amount, total_with_vat, terms_conditions) VALUES (?, ?, ?, 'pending', ?, ?, ?, ?)");
|
||||
$stmt = $db->prepare("INSERT INTO lpos (supplier_id, lpo_date, delivery_date, status, total_amount, vat_amount, total_with_vat, terms_conditions, outlet_id) VALUES (?, ?, ?, 'pending', ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$supp_id, $lpo_date, $delivery_date, $total_subtotal, $total_vat, $total_with_vat, $terms]);
|
||||
$lpo_id = $db->lastInsertId();
|
||||
|
||||
@ -1569,8 +1611,8 @@ function getPromotionalPrice($item) {
|
||||
|
||||
// Create Invoice
|
||||
$inv_date = date('Y-m-d');
|
||||
$stmtInv = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0)");
|
||||
$stmtInv->execute([$quot['customer_id'], $inv_date, $quot['total_amount'], $quot['vat_amount'], $quot['total_with_vat']]);
|
||||
$stmtInv = $db->prepare("INSERT INTO invoices (customer_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount, outlet_id) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0, ?)");
|
||||
$stmtInv->execute([$quot['customer_id'], $inv_date, $quot['total_amount'], $quot['vat_amount'], $quot['total_with_vat'], current_outlet_id()]);
|
||||
$inv_id = $db->lastInsertId();
|
||||
|
||||
$items_for_journal = [];
|
||||
@ -1578,7 +1620,7 @@ function getPromotionalPrice($item) {
|
||||
$db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$inv_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['vat_amount'], $item['total_price']]);
|
||||
|
||||
// Update stock
|
||||
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?")->execute([$item['quantity'], $item['item_id']]);
|
||||
update_stock($item['item_id'], -$item['quantity']);
|
||||
$items_for_journal[] = ['id' => $item['item_id'], 'qty' => $item['quantity']];
|
||||
}
|
||||
|
||||
@ -1612,8 +1654,8 @@ function getPromotionalPrice($item) {
|
||||
|
||||
// Create Purchase Invoice
|
||||
$inv_date = date('Y-m-d');
|
||||
$stmtPur = $db->prepare("INSERT INTO purchases (supplier_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0)");
|
||||
$stmtPur->execute([$lpo['supplier_id'], $inv_date, $lpo['total_amount'], $lpo['vat_amount'], $lpo['total_with_vat']]);
|
||||
$stmtPur = $db->prepare("INSERT INTO purchases (supplier_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount, outlet_id) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0, ?)");
|
||||
$stmtPur->execute([$lpo['supplier_id'], $inv_date, $lpo['total_amount'], $lpo['vat_amount'], $lpo['total_with_vat'], current_outlet_id()]);
|
||||
$pur_id = $db->lastInsertId();
|
||||
|
||||
$items_for_journal = [];
|
||||
@ -1621,7 +1663,7 @@ function getPromotionalPrice($item) {
|
||||
$db->prepare("INSERT INTO purchase_items (purchase_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$pur_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['vat_amount'], $item['total_amount']]);
|
||||
|
||||
// Update stock
|
||||
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$item['quantity'], $item['item_id']]);
|
||||
update_stock($item['item_id'], $item['quantity']);
|
||||
$items_for_journal[] = ['id' => $item['item_id'], 'qty' => $item['quantity']];
|
||||
}
|
||||
|
||||
@ -1840,7 +1882,7 @@ function getPromotionalPrice($item) {
|
||||
$oldItems = $stmtOld->fetchAll();
|
||||
foreach ($oldItems as $old) {
|
||||
$change = ($type === 'sale') ? (float)$old['quantity'] : -(float)$old['quantity'];
|
||||
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$change, $old['item_id']]);
|
||||
update_stock($old['item_id'], $change);
|
||||
}
|
||||
|
||||
// Delete old items
|
||||
@ -1861,7 +1903,7 @@ function getPromotionalPrice($item) {
|
||||
$db->prepare("INSERT INTO $item_table ($fk_col, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$id, $item_id, $qty, $price, $vatAmount, $subtotal]);
|
||||
|
||||
$change = ($type === 'sale') ? -$qty : $qty;
|
||||
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$change, $item_id]);
|
||||
update_stock($item_id, $change);
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
@ -2056,7 +2098,7 @@ if (isset($_POST['add_hr_department'])) {
|
||||
|
||||
// Insert Return Items and Update Stock
|
||||
$stmtItem = $db->prepare("INSERT INTO sales_return_items (return_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
|
||||
// $stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
|
||||
|
||||
foreach ($item_ids as $i => $item_id) {
|
||||
$qty = (float)$quantities[$i];
|
||||
@ -2064,7 +2106,7 @@ if (isset($_POST['add_hr_department'])) {
|
||||
$price = (float)$prices[$i];
|
||||
$line_total = $qty * $price;
|
||||
$stmtItem->execute([$return_id, $item_id, $qty, $price, $line_total]);
|
||||
$stmtStock->execute([$qty, $item_id]);
|
||||
update_stock($item_id, $qty);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2107,7 +2149,7 @@ if (isset($_POST['add_hr_department'])) {
|
||||
|
||||
// Insert Return Items and Update Stock
|
||||
$stmtItem = $db->prepare("INSERT INTO purchase_return_items (return_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
|
||||
// $stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
|
||||
|
||||
foreach ($item_ids as $i => $item_id) {
|
||||
$qty = (float)$quantities[$i];
|
||||
@ -2115,7 +2157,7 @@ if (isset($_POST['add_hr_department'])) {
|
||||
$price = (float)$prices[$i];
|
||||
$line_total = $qty * $price;
|
||||
$stmtItem->execute([$return_id, $item_id, $qty, $price, $line_total]);
|
||||
$stmtStock->execute([$qty, $item_id]);
|
||||
update_stock($item_id, -$qty);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2250,28 +2292,7 @@ if (isset($_POST['add_hr_department'])) {
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['add_user'])) {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$phone = $_POST['phone'] ?? '';
|
||||
$group_id = (int)($_POST['group_id'] ?? 0) ?: null;
|
||||
if ($username && $password) {
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = db()->prepare("INSERT INTO users (username, password, email, phone, group_id) VALUES (?, ?, ?, ?, ?)");
|
||||
try {
|
||||
$stmt->execute([$username, $hashed_password, $email, $phone, $group_id]);
|
||||
$message = "User added successfully!";
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') {
|
||||
$message = "Error: Username already exists.";
|
||||
} else {
|
||||
$message = "Error adding user: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($_POST['edit_role_group'])) {
|
||||
if (isset($_POST['edit_role_group'])) {
|
||||
$id = (int)$_POST['id'];
|
||||
$name = $_POST['name'] ?? '';
|
||||
$permissions = isset($_POST['permissions']) ? $_POST['permissions'] : [];
|
||||
@ -2308,34 +2329,7 @@ if (isset($_POST['add_hr_department'])) {
|
||||
$message = "Role Group deleted successfully!";
|
||||
}
|
||||
}
|
||||
if (isset($_POST['edit_user'])) {
|
||||
$id = (int)$_POST['id'];
|
||||
$username = $_POST['username'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$phone = $_POST['phone'] ?? '';
|
||||
$group_id = (int)($_POST['group_id'] ?? 0) ?: null;
|
||||
$status = $_POST['status'] ?? 'active';
|
||||
if ($id && $username) {
|
||||
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, phone = ?, group_id = ?, status = ? WHERE id = ?");
|
||||
$stmt->execute([$username, $email, $phone, $group_id, $status, $id]);
|
||||
|
||||
if (!empty($_POST['password'])) {
|
||||
$hashed_password = password_hash($_POST['password'], PASSWORD_DEFAULT);
|
||||
$stmt = db()->prepare("UPDATE users SET password = ? WHERE id = ?");
|
||||
$stmt->execute([$hashed_password, $id]);
|
||||
}
|
||||
$message = "User updated successfully!";
|
||||
}
|
||||
}
|
||||
if (isset($_POST['delete_user'])) {
|
||||
$id = (int)$_POST['id'];
|
||||
if ($id) {
|
||||
$stmt = db()->prepare("DELETE FROM users WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$message = "User deleted successfully!";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- POS Devices Handlers ---
|
||||
if (isset($_POST['add_pos_device'])) {
|
||||
$name = $_POST['device_name'] ?? '';
|
||||
@ -3332,7 +3326,7 @@ switch ($page) {
|
||||
}
|
||||
unset($inv);
|
||||
|
||||
$items_list_raw = db()->query("SELECT id, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate, is_promotion, promotion_start, promotion_end, promotion_percent FROM stock_items ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$oid = current_outlet_id(); $items_list_raw = db()->query("SELECT i.id, i.name_en, i.name_ar, i.sale_price, i.purchase_price, COALESCE(os.quantity, 0) as stock_quantity, i.vat_rate, i.is_promotion, i.promotion_start, i.promotion_end, i.promotion_percent FROM stock_items i LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $oid ORDER BY i.name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($items_list_raw as &$item) {
|
||||
$item['sale_price'] = getPromotionalPrice($item);
|
||||
}
|
||||
@ -3479,8 +3473,7 @@ switch ($page) {
|
||||
$data['role_groups'] = db()->query("SELECT * FROM role_groups ORDER BY name ASC")->fetchAll();
|
||||
break;
|
||||
case 'users':
|
||||
$data['users'] = db()->query("SELECT u.*, g.name as group_name FROM users u LEFT JOIN role_groups g ON u.group_id = g.id ORDER BY u.username ASC")->fetchAll();
|
||||
$data['role_groups'] = db()->query("SELECT id, name FROM role_groups ORDER BY name ASC")->fetchAll();
|
||||
require 'pages/users_logic.php';
|
||||
break;
|
||||
case 'backups':
|
||||
$data['backups'] = BackupService::getBackups();
|
||||
@ -3679,16 +3672,31 @@ switch ($page) {
|
||||
default:
|
||||
if (can('dashboard_view')) {
|
||||
$data['customers'] = db()->query("SELECT * FROM customers ORDER BY id DESC LIMIT 5")->fetchAll();
|
||||
// Statistics with Outlet Filter
|
||||
$current_oid = current_outlet_id();
|
||||
$inv_cond = ($current_oid > 0) ? " WHERE outlet_id = $current_oid " : "";
|
||||
$pos_cond = " WHERE status = 'completed' " . (($current_oid > 0) ? " AND outlet_id = $current_oid " : "");
|
||||
|
||||
$pay_inv_cond = ($current_oid > 0) ? " WHERE i.outlet_id = $current_oid " : "";
|
||||
$pay_pos_cond = ($current_oid > 0) ? " WHERE t.outlet_id = $current_oid " : "";
|
||||
|
||||
$pur_cond = ($current_oid > 0) ? " WHERE outlet_id = $current_oid " : "";
|
||||
$pay_pur_cond = ($current_oid > 0) ? " WHERE p.outlet_id = $current_oid " : "";
|
||||
|
||||
$low_stock_query = ($current_oid > 0)
|
||||
? "SELECT COUNT(*) FROM stock_items i LEFT JOIN outlet_stock os ON i.id = os.item_id AND os.outlet_id = $current_oid WHERE COALESCE(os.quantity, 0) <= i.min_stock_level"
|
||||
: "SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level";
|
||||
|
||||
$data['stats'] = [
|
||||
'total_customers' => db()->query("SELECT COUNT(*) FROM customers")->fetchColumn(),
|
||||
'total_items' => db()->query("SELECT COUNT(*) FROM stock_items")->fetchColumn(),
|
||||
'total_sales' => (db()->query("SELECT SUM(total_with_vat) FROM invoices")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(net_amount) FROM pos_transactions WHERE status = 'completed'")->fetchColumn() ?: 0),
|
||||
'total_received' => (db()->query("SELECT SUM(amount) FROM payments")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(amount) FROM pos_payments")->fetchColumn() ?: 0),
|
||||
'total_purchases' => db()->query("SELECT SUM(total_with_vat) FROM purchases")->fetchColumn() ?: 0,
|
||||
'total_paid' => db()->query("SELECT SUM(amount) FROM purchase_payments")->fetchColumn() ?: 0,
|
||||
'total_sales' => (db()->query("SELECT SUM(total_with_vat) FROM invoices $inv_cond")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(net_amount) FROM pos_transactions $pos_cond")->fetchColumn() ?: 0),
|
||||
'total_received' => (db()->query("SELECT SUM(p.amount) FROM payments p JOIN invoices i ON p.invoice_id = i.id $pay_inv_cond")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(pp.amount) FROM pos_payments pp JOIN pos_transactions t ON pp.transaction_id = t.id $pay_pos_cond")->fetchColumn() ?: 0),
|
||||
'total_purchases' => db()->query("SELECT SUM(total_with_vat) FROM purchases $pur_cond")->fetchColumn() ?: 0,
|
||||
'total_paid' => db()->query("SELECT SUM(pp.amount) FROM purchase_payments pp JOIN purchases p ON pp.purchase_id = p.id $pay_pur_cond")->fetchColumn() ?: 0,
|
||||
'expired_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE()")->fetchColumn(),
|
||||
'near_expiry_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)")->fetchColumn(),
|
||||
'low_stock_items_count' => db()->query("SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level")->fetchColumn(),
|
||||
'low_stock_items_count' => db()->query($low_stock_query)->fetchColumn(),
|
||||
];
|
||||
$data['stats']['total_receivable'] = $data['stats']['total_sales'] - $data['stats']['total_received'];
|
||||
$data['stats']['total_payable'] = $data['stats']['total_purchases'] - $data['stats']['total_paid'];
|
||||
@ -4211,7 +4219,41 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
<div class="text-muted" style="font-size: 0.7rem;"><?= htmlspecialchars((string)($_SESSION['user_role_name'] ?? '')) ?></div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<a href="index.php?page=my_profile" class="btn btn-light rounded-circle p-0 overflow-hidden shadow-sm d-inline-block position-relative" style="width: 40px; height: 40px;" title="<?= __('edit') ?>">
|
||||
<!-- Outlet Switcher -->
|
||||
<?php
|
||||
$user_outlets_list = $_SESSION['user_outlets'] ?? [1];
|
||||
$is_admin = ($_SESSION['user_role_name'] ?? '') === 'Administrator';
|
||||
|
||||
if (count($user_outlets_list) > 1 || $is_admin):
|
||||
$current_oid = current_outlet_id();
|
||||
$current_oname = $current_oid === -1 ? (__('All Outlets') ?: 'All Outlets') : (db()->query("SELECT name FROM outlets WHERE id = $current_oid")->fetchColumn() ?: 'Outlet ' . $current_oid);
|
||||
?>
|
||||
<div class="dropdown d-inline-block me-3">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle btn-sm" type="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-store me-1"></i> <?= htmlspecialchars($current_oname) ?>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item <?= $current_oid == -1 ? 'active' : '' ?>" href="index.php?action=switch_outlet&id=-1"><?= __('All Outlets') ?: 'All Outlets' ?></a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<?php
|
||||
$availOutlets = $user_outlets_list;
|
||||
if ($is_admin) {
|
||||
$availOutlets = db()->query("SELECT id FROM outlets WHERE status='active'")->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
foreach($availOutlets as $oid):
|
||||
$oname = db()->query("SELECT name FROM outlets WHERE id=$oid")->fetchColumn();
|
||||
?>
|
||||
<li>
|
||||
<a class="dropdown-item <?= $oid == $current_oid ? 'active' : '' ?>" href="index.php?action=switch_outlet&id=<?= $oid ?>">
|
||||
<?= htmlspecialchars($oname) ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="index.php?page=my_profile" class="btn btn-light rounded-circle p-0 overflow-hidden shadow-sm d-inline-block position-relative" style="width: 40px; height: 40px;" title="<?= __('edit') ?>">
|
||||
<?php if (!empty($_SESSION['profile_pic'])): ?>
|
||||
<img src="<?= htmlspecialchars($_SESSION['profile_pic']) ?>?v=<?= time() ?>" alt="Profile" style="width: 100%; height: 100%; object-fit: cover;">
|
||||
<?php else: ?>
|
||||
@ -8534,7 +8576,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require "outlets_html.php"; ?>
|
||||
<?php elseif ($page === "outlets" && ($_SESSION["user_role_name"] ?? "") === "Administrator"): ?>
|
||||
<?php require "outlets_html.php"; ?>
|
||||
<?php elseif ($page === 'settings'): ?>
|
||||
<div class="card p-4">
|
||||
<h5 class="mb-4" data-en="Company Profile" data-ar="ملف الشركة">Company Profile</h5>
|
||||
@ -8984,145 +9027,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
|
||||
<?php elseif ($page === 'users'): ?>
|
||||
<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>
|
||||
<h5 class="m-0 fw-bold text-primary" data-en="User Management" data-ar="إدارة المستخدمين">User Management</h5>
|
||||
<p class="text-muted small mb-0" data-en="Maintain your team accounts and security" data-ar="صيانة حسابات فريقك وأمنها">Maintain your team accounts and security</p>
|
||||
</div>
|
||||
<?php if (can('users_add')): ?>
|
||||
<button class="btn btn-primary rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#addUserModal">
|
||||
<i class="bi bi-person-plus me-1"></i> <span data-en="Invite User" data-ar="دعوة مستخدم">Invite User</span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4" data-en="User Info" data-ar="معلومات المستخدم">User Info</th>
|
||||
<th data-en="Access Level" data-ar="مستوى الوصول">Access Level</th>
|
||||
<th data-en="Contact" data-ar="الاتصال">Contact</th>
|
||||
<th data-en="Status" data-ar="الحالة">Status</th>
|
||||
<th data-en="Actions" data-ar="الإجراءات" class="text-end pe-4">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($data['users'] as $u): ?>
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<?php if (!empty($u['profile_pic'])): ?>
|
||||
<img src="<?= htmlspecialchars((string)$u['profile_pic']) ?>?v=<?= time() ?>" alt="Avatar" class="rounded-circle me-3 shadow-sm" style="width: 35px; height: 35px; object-fit: cover;">
|
||||
<?php else: ?>
|
||||
<div class="avatar-sm bg-gradient-primary rounded-circle me-3 text-white d-flex align-items-center justify-content-center fw-bold" style="width: 35px; height: 35px; background: linear-gradient(135deg, #6e8efb, #a777e3);">
|
||||
<?= strtoupper(substr((string)$u['username'], 0, 1)) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div>
|
||||
<div class="fw-bold text-dark"><?= htmlspecialchars((string)$u['username']) ?></div>
|
||||
<div class="text-muted small">ID: #<?= str_pad((string)$u['id'], 4, '0', STR_PAD_LEFT) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill bg-info bg-opacity-10 text-info px-3">
|
||||
<?= htmlspecialchars((string)($u['group_name'] ?? 'No Role Assigned')) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="text-dark small mb-1"><i class="bi bi-envelope me-1"></i> <?= htmlspecialchars((string)($u['email'] ?? '')) ?></div>
|
||||
<div class="text-muted small"><i class="bi bi-phone me-1"></i> <?= htmlspecialchars((string)($u['phone'] ?? '-')) ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($u['status'] === 'active'): ?>
|
||||
<span class="badge rounded-pill bg-success bg-opacity-10 text-success px-3">Active</span>
|
||||
<?php else: ?>
|
||||
<span class="badge rounded-pill bg-secondary bg-opacity-10 text-secondary px-3">Suspended</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-light btn-sm rounded-circle" type="button" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow-sm border-0">
|
||||
<?php if (can('users_edit')): ?>
|
||||
<li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#editUserModal<?= $u['id'] ?>"><i class="bi bi-pencil me-2 text-primary"></i> Edit Profile</a></li>
|
||||
<?php endif; ?>
|
||||
<?php if (can('users_delete')): ?>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form method="POST" onsubmit="return confirm('Deactivate this user account?')">
|
||||
<input type="hidden" name="id" value="<?= $u['id'] ?>">
|
||||
<button type="submit" name="delete_user" class="dropdown-item text-danger"><i class="bi bi-trash me-2"></i> Remove Access</button>
|
||||
</form>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Edit User Modal -->
|
||||
<div class="modal fade" id="editUserModal<?= $u['id'] ?>" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content border-0 shadow text-start">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title fw-bold" data-en="Edit User Account" data-ar="تعديل حساب المستخدم">Edit User Account</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="id" value="<?= $u['id'] ?>">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="Username" data-ar="اسم المستخدم">Username</label>
|
||||
<input type="text" name="username" class="form-control" value="<?= htmlspecialchars((string)$u['username']) ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="Email Address" data-ar="البريد الإلكتروني">Email Address</label>
|
||||
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars((string)($u['email'] ?? '')) ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="Phone Number" data-ar="رقم الهاتف">Phone Number</label>
|
||||
<input type="text" name="phone" class="form-control" value="<?= htmlspecialchars((string)($u['phone'] ?? '')) ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="Assign Role Group" data-ar="تعيين مجموعة الأدوار">Assign Role Group</label>
|
||||
<select name="group_id" class="form-select">
|
||||
<option value="">--- No Group ---</option>
|
||||
<?php foreach (($data['role_groups'] ?? []) as $g): ?>
|
||||
<option value="<?= $g['id'] ?>" <?= ($u['group_id'] ?? null) == $g['id'] ? 'selected' : '' ?>><?= htmlspecialchars((string)$g['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="Account Status" data-ar="حالة الحساب">Account Status</label>
|
||||
<select name="status" class="form-select">
|
||||
<option value="active" <?= $u['status'] === 'active' ? 'selected' : '' ?>>Active</option>
|
||||
<option value="inactive" <?= $u['status'] === 'inactive' ? 'selected' : '' ?>>Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="New Password" data-ar="كلمة مرور جديدة">New Password</label>
|
||||
<input type="password" name="password" class="form-control" placeholder="Leave blank to keep current" autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-light rounded-pill px-3" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
|
||||
<button type="submit" name="edit_user" class="btn btn-primary rounded-pill px-4" data-en="Save Changes" data-ar="حفظ التغييرات">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
|
||||
|
||||
</div>
|
||||
<?php require 'pages/users_view.php'; ?>
|
||||
<?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">
|
||||
@ -10008,49 +9913,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
|
||||
<!-- Add User Modal -->
|
||||
<div class="modal fade" id="addUserModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" data-en="Add New User" data-ar="إضافة مستخدم جديد">Add New User</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="Username" data-ar="اسم المستخدم">Username</label>
|
||||
<input type="text" name="username" class="form-control" required autocomplete="username">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" data-en="Email" data-ar="البريد الإلكتروني">Email</label>
|
||||
<input type="email" name="email" class="form-control" autocomplete="email">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" data-en="Phone" data-ar="الهاتف">Phone</label>
|
||||
<input type="text" name="phone" class="form-control" autocomplete="tel">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" data-en="Password" data-ar="كلمة المرور">Password</label>
|
||||
<input type="password" name="password" class="form-control" required autocomplete="new-password">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" data-en="Role Group" data-ar="مجموعة الأدوار">Role Group</label>
|
||||
<select name="group_id" class="form-select">
|
||||
<option value="">--- Select Group ---</option>
|
||||
<?php foreach (($data['role_groups'] ?? []) as $g): ?>
|
||||
<option value="<?= $g['id'] ?>"><?= htmlspecialchars($g['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
|
||||
<button type="submit" name="add_user" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Add Customer Modal -->
|
||||
<div class="modal fade" id="addCustomerModal" tabindex="-1">
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
<?php elseif ($page === 'outlets' && ($_SESSION['user_role_name'] ?? '') === 'Administrator'): ?>
|
||||
<div class="card border-0 shadow-sm rounded-4 mb-4">
|
||||
<div class="card-header bg-white border-bottom-0 pt-4 pb-0 px-4 d-flex justify-content-between align-items-center">
|
||||
<h5 class="fw-bold mb-0"><i class="bi bi-shop text-primary me-2"></i> Manage Outlets</h5>
|
||||
|
||||
106
pages/users_logic.php
Normal file
106
pages/users_logic.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
// pages/users_logic.php
|
||||
|
||||
// Handle Actions
|
||||
if (isset($_POST['add_user'])) {
|
||||
if (can('users_add')) {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$phone = $_POST['phone'] ?? '';
|
||||
$group_id = (int)($_POST['group_id'] ?? 0) ?: null;
|
||||
$outlet_ids = $_POST['outlet_ids'] ?? [];
|
||||
|
||||
if ($username && $password) {
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = db()->prepare("INSERT INTO users (username, password, email, phone, group_id) VALUES (?, ?, ?, ?, ?)");
|
||||
try {
|
||||
$stmt->execute([$username, $hashed_password, $email, $phone, $group_id]);
|
||||
$user_id = db()->lastInsertId();
|
||||
|
||||
if (!empty($outlet_ids)) {
|
||||
$stmtOut = db()->prepare("INSERT INTO user_outlets (user_id, outlet_id) VALUES (?, ?)");
|
||||
foreach ($outlet_ids as $oid) {
|
||||
$stmtOut->execute([$user_id, $oid]);
|
||||
}
|
||||
}
|
||||
|
||||
$message = "User added successfully!";
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') {
|
||||
$message = "Error: Username already exists.";
|
||||
} else {
|
||||
$message = "Error adding user: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['edit_user'])) {
|
||||
if (can('users_edit')) {
|
||||
$id = (int)$_POST['id'];
|
||||
$username = $_POST['username'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$phone = $_POST['phone'] ?? '';
|
||||
$group_id = (int)($_POST['group_id'] ?? 0) ?: null;
|
||||
$status = $_POST['status'] ?? 'active';
|
||||
$outlet_ids = $_POST['outlet_ids'] ?? [];
|
||||
|
||||
if ($id && $username) {
|
||||
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, phone = ?, group_id = ?, status = ? WHERE id = ?");
|
||||
$stmt->execute([$username, $email, $phone, $group_id, $status, $id]);
|
||||
|
||||
if (!empty($_POST['password'])) {
|
||||
$hashed_password = password_hash($_POST['password'], PASSWORD_DEFAULT);
|
||||
$stmt = db()->prepare("UPDATE users SET password = ? WHERE id = ?");
|
||||
$stmt->execute([$hashed_password, $id]);
|
||||
}
|
||||
|
||||
// Update Outlets
|
||||
db()->prepare("DELETE FROM user_outlets WHERE user_id = ?")->execute([$id]);
|
||||
if (!empty($outlet_ids)) {
|
||||
$stmtOut = db()->prepare("INSERT INTO user_outlets (user_id, outlet_id) VALUES (?, ?)");
|
||||
foreach ($outlet_ids as $oid) {
|
||||
$stmtOut->execute([$id, $oid]);
|
||||
}
|
||||
}
|
||||
|
||||
$message = "User updated successfully!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['delete_user'])) {
|
||||
if (can('users_delete')) {
|
||||
$id = (int)$_POST['id'];
|
||||
if ($id) {
|
||||
$stmt = db()->prepare("DELETE FROM users WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$message = "User deleted successfully!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch Data
|
||||
$page_num = isset($_GET['p']) ? max(1, (int)$_GET['p']) : 1;
|
||||
$items_per_page = 20;
|
||||
$offset = ($page_num - 1) * $items_per_page;
|
||||
|
||||
$total_users = db()->query("SELECT COUNT(*) FROM users")->fetchColumn();
|
||||
$total_pages = ceil($total_users / $items_per_page);
|
||||
|
||||
$data['users'] = db()->query("
|
||||
SELECT u.*, g.name as group_name, GROUP_CONCAT(uo.outlet_id) as outlet_ids
|
||||
FROM users u
|
||||
LEFT JOIN role_groups g ON u.group_id = g.id
|
||||
LEFT JOIN user_outlets uo ON u.id = uo.user_id
|
||||
GROUP BY u.id
|
||||
ORDER BY u.username ASC
|
||||
LIMIT $items_per_page OFFSET $offset
|
||||
")->fetchAll();
|
||||
|
||||
$data['role_groups'] = db()->query("SELECT id, name FROM role_groups ORDER BY name ASC")->fetchAll();
|
||||
$data['outlets'] = db()->query("SELECT * FROM outlets ORDER BY name ASC")->fetchAll();
|
||||
$data['current_page'] = $page_num;
|
||||
$data['total_pages'] = $total_pages;
|
||||
201
pages/users_view.php
Normal file
201
pages/users_view.php
Normal file
@ -0,0 +1,201 @@
|
||||
<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>
|
||||
<h5 class="m-0 fw-bold text-primary" data-en="User Management" data-ar="إدارة المستخدمين">User Management</h5>
|
||||
<p class="text-muted small mb-0" data-en="Maintain your team accounts and security" data-ar="صيانة حسابات فريقك وأمنها">Maintain your team accounts and security</p>
|
||||
</div>
|
||||
<?php if (can('users_add')): ?>
|
||||
<button class="btn btn-primary rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#addUserModal">
|
||||
<i class="bi bi-person-plus me-1"></i> <span data-en="Invite User" data-ar="دعوة مستخدم">Invite User</span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4" data-en="User Info" data-ar="معلومات المستخدم">User Info</th>
|
||||
<th data-en="Access Level" data-ar="مستوى الوصول">Access Level</th>
|
||||
<th data-en="Contact" data-ar="الاتصال">Contact</th>
|
||||
<th data-en="Status" data-ar="الحالة">Status</th>
|
||||
<th data-en="Actions" data-ar="الإجراءات" class="text-end pe-4">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($data['users'] as $u): ?>
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<?php if (!empty($u['profile_pic'])): ?>
|
||||
<img src="<?= htmlspecialchars((string)$u['profile_pic']) ?>?v=<?= time() ?>" alt="Avatar" class="rounded-circle me-3 shadow-sm" style="width: 35px; height: 35px; object-fit: cover;">
|
||||
<?php else: ?>
|
||||
<div class="avatar-sm bg-gradient-primary rounded-circle me-3 text-white d-flex align-items-center justify-content-center fw-bold" style="width: 35px; height: 35px; background: linear-gradient(135deg, #6e8efb, #a777e3);">
|
||||
<?= strtoupper(substr((string)$u['username'], 0, 1)) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div>
|
||||
<div class="fw-bold text-dark"><?= htmlspecialchars((string)$u['username']) ?></div>
|
||||
<div class="text-muted small">ID: #<?= str_pad((string)$u['id'], 4, '0', STR_PAD_LEFT) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill bg-info bg-opacity-10 text-info px-3">
|
||||
<?= htmlspecialchars((string)($u['group_name'] ?? 'No Role Assigned')) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="text-dark small mb-1"><i class="bi bi-envelope me-1"></i> <?= htmlspecialchars((string)($u['email'] ?? '')) ?></div>
|
||||
<div class="text-muted small"><i class="bi bi-phone me-1"></i> <?= htmlspecialchars((string)($u['phone'] ?? '-')) ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($u['status'] === 'active'): ?>
|
||||
<span class="badge rounded-pill bg-success bg-opacity-10 text-success px-3">Active</span>
|
||||
<?php else: ?>
|
||||
<span class="badge rounded-pill bg-secondary bg-opacity-10 text-secondary px-3">Suspended</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
<?php if (can('users_edit')): ?>
|
||||
<button class="btn btn-sm btn-light text-primary rounded-circle" data-bs-toggle="modal" data-bs-target="#editUserModal<?= $u['id'] ?>" title="Edit">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (can('users_delete')): ?>
|
||||
<form method="POST" class="d-inline" onsubmit="return confirm('Deactivate this user account?')">
|
||||
<input type="hidden" name="id" value="<?= $u['id'] ?>">
|
||||
<button type="submit" name="delete_user" class="btn btn-sm btn-light text-danger rounded-circle" title="Delete">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Edit User Modal -->
|
||||
<div class="modal fade" id="editUserModal<?= $u['id'] ?>" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content border-0 shadow text-start">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title fw-bold" data-en="Edit User Account" data-ar="تعديل حساب المستخدم">Edit User Account</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="id" value="<?= $u['id'] ?>">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="Username" data-ar="اسم المستخدم">Username</label>
|
||||
<input type="text" name="username" class="form-control" value="<?= htmlspecialchars((string)$u['username']) ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="Email Address" data-ar="البريد الإلكتروني">Email Address</label>
|
||||
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars((string)($u['email'] ?? '')) ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="Phone Number" data-ar="رقم الهاتف">Phone Number</label>
|
||||
<input type="text" name="phone" class="form-control" value="<?= htmlspecialchars((string)($u['phone'] ?? '')) ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="Assign Role Group" data-ar="تعيين مجموعة الأدوار">Assign Role Group</label>
|
||||
<select name="group_id" class="form-select">
|
||||
<option value="">--- No Group ---</option>
|
||||
<?php foreach (($data['role_groups'] ?? []) as $g): ?>
|
||||
<option value="<?= $g['id'] ?>" <?= ($u['group_id'] ?? null) == $g['id'] ? 'selected' : '' ?>><?= htmlspecialchars((string)$g['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="Assigned Outlets" data-ar="المنافذ المعينة">Assigned Outlets</label>
|
||||
<select name="outlet_ids[]" class="form-select" multiple size="3">
|
||||
<?php
|
||||
$u_outlets = explode(',', $u['outlet_ids'] ?? '');
|
||||
foreach ($data['outlets'] as $out):
|
||||
?>
|
||||
<option value="<?= $out['id'] ?>" <?= in_array($out['id'], $u_outlets) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($out['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="form-text small" data-en="Hold Ctrl/Cmd to select multiple" data-ar="اضغط Ctrl/Cmd لتحديد متعدد">Hold Ctrl/Cmd to select multiple.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="Account Status" data-ar="حالة الحساب">Account Status</label>
|
||||
<select name="status" class="form-select">
|
||||
<option value="active" <?= $u['status'] === 'active' ? 'selected' : '' ?>>Active</option>
|
||||
<option value="inactive" <?= $u['status'] === 'inactive' ? 'selected' : '' ?>>Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold" data-en="New Password" data-ar="كلمة مرور جديدة">New Password</label>
|
||||
<input type="password" name="password" class="form-control" placeholder="Leave blank to keep current" autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-light rounded-pill px-3" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
|
||||
<button type="submit" name="edit_user" class="btn btn-primary rounded-pill px-4" data-en="Save Changes" data-ar="حفظ التغييرات">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
|
||||
</div>
|
||||
|
||||
<!-- Add User Modal -->
|
||||
<div class="modal fade" id="addUserModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" data-en="Add New User" data-ar="إضافة مستخدم جديد">Add New User</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="Username" data-ar="اسم المستخدم">Username</label>
|
||||
<input type="text" name="username" class="form-control" required autocomplete="username">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" data-en="Email" data-ar="البريد الإلكتروني">Email</label>
|
||||
<input type="email" name="email" class="form-control" autocomplete="email">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" data-en="Phone" data-ar="الهاتف">Phone</label>
|
||||
<input type="text" name="phone" class="form-control" autocomplete="tel">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" data-en="Password" data-ar="كلمة المرور">Password</label>
|
||||
<input type="password" name="password" class="form-control" required autocomplete="new-password">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" data-en="Role Group" data-ar="مجموعة الأدوار">Role Group</label>
|
||||
<select name="group_id" class="form-select">
|
||||
<option value="">--- Select Group ---</option>
|
||||
<?php foreach (($data['role_groups'] ?? []) as $g): ?>
|
||||
<option value="<?= $g['id'] ?>"><?= htmlspecialchars($g['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" data-en="Assigned Outlets" data-ar="المنافذ المعينة">Assigned Outlets</label>
|
||||
<select name="outlet_ids[]" class="form-select" multiple size="3">
|
||||
<?php foreach ($data['outlets'] as $out): ?>
|
||||
<option value="<?= $out['id'] ?>"><?= htmlspecialchars($out['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="form-text small" data-en="Hold Ctrl/Cmd to select multiple" data-ar="اضغط Ctrl/Cmd لتحديد متعدد">Hold Ctrl/Cmd to select multiple.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
|
||||
<button type="submit" name="add_user" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -67,3 +67,4 @@
|
||||
2026-03-18 06:31:00 - POST: {"type":"sale","customer_id":"7","invoice_date":"2026-03-18","due_date":"","payment_type":"cash","status":"unpaid","paid_amount":"0.000","item_ids":["7"],"quantities":["1"],"prices":["0.250"],"add_invoice":""}
|
||||
2026-03-18 06:42:39 - POST: {"type":"sale","customer_id":"7","invoice_date":"2026-03-18","due_date":"","payment_type":"cash","status":"unpaid","paid_amount":"0.000","item_ids":["7"],"quantities":["1"],"prices":["0.250"],"add_invoice":""}
|
||||
2026-03-18 06:43:05 - POST: {"invoice_id":"31","customer_id":"7","invoice_date":"2026-03-18","due_date":"","payment_type":"cash","status":"paid","paid_amount":"0.000","item_ids":["7"],"quantities":["1.00"],"prices":["0.250"],"edit_invoice":""}
|
||||
2026-03-18 07:19:13 - POST: {"id":"","name":"Nizwa Outlet","phone":"","address":"","status":"active","add_outlet":""}
|
||||
|
||||
28
summary.txt
28
summary.txt
@ -1,18 +1,18 @@
|
||||
Plan:
|
||||
I have restored the "Manage Outlets" feature.
|
||||
|
||||
1. Identified a critical HTML structure error: a missing `</tbody>` tag in the `accounting` journal table (index.php) was causing the browser to render the page incorrectly, potentially hiding the main content area.
|
||||
2. Found a JavaScript logic error in the `renderPagination` function where `document.currentScript` was being accessed inside a `DOMContentLoaded` listener (where it is null), causing the "Rows per page" dropdown to remain at the bottom instead of moving above the table.
|
||||
3. Fixed both issues by patching `index.php`.
|
||||
**Plan:**
|
||||
1. **Controller Logic:** Added the backend logic to handle `outlets` page requests (listing, adding, editing, deleting outlets) in `index.php`.
|
||||
2. **Navigation:** Added a "Manage Outlets" link to the sidebar under the **Admin** section.
|
||||
3. **View Integration:** Re-enabled the `outlets_html.php` view file to display the outlet management interface.
|
||||
4. **Correction:** Cleaned up some accidental code insertions that occurred during the process.
|
||||
|
||||
Changed:
|
||||
**Changed:**
|
||||
* `index.php`: Added `case 'outlets':` logic, sidebar link, and view inclusion.
|
||||
|
||||
* `index.php`:
|
||||
* Added the missing `</tbody>` tag to the accounting journal table.
|
||||
* Updated the `renderPagination` inline script to correctly capture `document.currentScript` immediately and safely move the "Rows per page" dropdown above the table wrapper.
|
||||
**Notes:**
|
||||
* You can now access **Manage Outlets** from the sidebar (under the Admin section).
|
||||
* The system supports multiple outlets as requested.
|
||||
* Only Administrators can access this page.
|
||||
|
||||
Notes:
|
||||
|
||||
* The missing `</tbody>` tag likely caused the browser to treat the entire main content area as invalid or hidden, explaining why only the sidebar was visible.
|
||||
* The pagination dropdown should now appear correctly above the table.
|
||||
|
||||
Next: Please verify that the accounting page (`/index.php?page=accounting`) now loads correctly with the sidebar AND the main content table. Also check if the "Rows per page" dropdown is visible above the table.
|
||||
**Next:**
|
||||
Please verify if you can now see the "Manage Outlets" link and manage your outlets successfully.
|
||||
Loading…
x
Reference in New Issue
Block a user