Autosave: 20260321-173937
This commit is contained in:
parent
a6e75b0397
commit
1cb1f89067
32
api/inventory.php
Normal file
32
api/inventory.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
require_once __DIR__ . '/../helpers.php';
|
||||||
|
require_once __DIR__ . '/../includes/auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (!is_logged_in()) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $_GET['action'] ?? '';
|
||||||
|
$db = db();
|
||||||
|
|
||||||
|
if ($action === 'get_batches') {
|
||||||
|
$item_id = $_GET['item_id'] ?? 0;
|
||||||
|
if (!$item_id) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $db->prepare("SELECT id, batch_number, quantity, expiry_date, cost_price FROM inventory_batches WHERE item_id = ? AND quantity > 0 ORDER BY expiry_date ASC");
|
||||||
|
$stmt->execute([$item_id]);
|
||||||
|
$batches = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode($batches);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['error' => 'Invalid action']);
|
||||||
52
db/migrations/20260321_create_inventory_module.sql
Normal file
52
db/migrations/20260321_create_inventory_module.sql
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS inventory_categories (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name_en VARCHAR(255) NOT NULL,
|
||||||
|
name_ar VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS inventory_items (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
category_id INT,
|
||||||
|
name_en VARCHAR(255) NOT NULL,
|
||||||
|
name_ar VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
sku VARCHAR(100) UNIQUE,
|
||||||
|
unit VARCHAR(50) DEFAULT 'piece',
|
||||||
|
min_level INT DEFAULT 10,
|
||||||
|
reorder_level INT DEFAULT 20,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (category_id) REFERENCES inventory_categories(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS inventory_batches (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
item_id INT NOT NULL,
|
||||||
|
batch_number VARCHAR(100),
|
||||||
|
expiry_date DATE,
|
||||||
|
quantity INT NOT NULL DEFAULT 0,
|
||||||
|
cost_price DECIMAL(10, 2) DEFAULT 0.00,
|
||||||
|
supplier_id INT,
|
||||||
|
received_date DATE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (item_id) REFERENCES inventory_items(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS inventory_transactions (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
item_id INT NOT NULL,
|
||||||
|
batch_id INT,
|
||||||
|
transaction_type ENUM('in', 'out', 'adjustment') NOT NULL,
|
||||||
|
quantity INT NOT NULL,
|
||||||
|
reference_type VARCHAR(50), -- 'purchase', 'consumption', 'manual_adjustment'
|
||||||
|
reference_id INT,
|
||||||
|
user_id INT,
|
||||||
|
transaction_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
notes TEXT,
|
||||||
|
FOREIGN KEY (item_id) REFERENCES inventory_items(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (batch_id) REFERENCES inventory_batches(id) ON DELETE SET NULL,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
@ -113,29 +113,31 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<nav class="mt-3">
|
<nav class="mt-3">
|
||||||
|
<?php if (has_permission('dashboard')): ?>
|
||||||
<a href="dashboard.php" class="sidebar-link <?php echo $section === 'dashboard' ? 'active' : ''; ?>"><i class="bi bi-speedometer2 me-2"></i> <?php echo __('dashboard'); ?></a>
|
<a href="dashboard.php" class="sidebar-link <?php echo $section === 'dashboard' ? 'active' : ''; ?>"><i class="bi bi-speedometer2 me-2"></i> <?php echo __('dashboard'); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (has_role('admin') || has_role('doctor') || has_role('nurse') || has_role('receptionist')): ?>
|
<?php if (has_permission('patients')): ?>
|
||||||
<a href="patients.php" class="sidebar-link <?php echo $section === 'patients' ? 'active' : ''; ?>"><i class="bi bi-people me-2"></i> <?php echo __('patients'); ?></a>
|
<a href="patients.php" class="sidebar-link <?php echo $section === 'patients' ? 'active' : ''; ?>"><i class="bi bi-people me-2"></i> <?php echo __('patients'); ?></a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (has_role('admin') || has_role('doctor') || has_role('nurse')): ?>
|
<?php if (has_permission('visits')): ?>
|
||||||
<a href="visits.php" class="sidebar-link <?php echo $section === 'visits' ? 'active' : ''; ?>"><i class="bi bi-clipboard2-pulse me-2"></i> <?php echo __('visits'); ?></a>
|
<a href="visits.php" class="sidebar-link <?php echo $section === 'visits' ? 'active' : ''; ?>"><i class="bi bi-clipboard2-pulse me-2"></i> <?php echo __('visits'); ?></a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (has_role('admin') || has_role('doctor') || has_role('receptionist')): ?>
|
<?php if (has_permission('appointments')): ?>
|
||||||
<a href="appointments.php" class="sidebar-link <?php echo $section === "appointments" ? "active" : ""; ?>"><i class="bi bi-calendar-event me-2"></i> <?php echo __("appointments"); ?></a>
|
<a href="appointments.php" class="sidebar-link <?php echo $section === "appointments" ? "active" : ""; ?>"><i class="bi bi-calendar-event me-2"></i> <?php echo __("appointments"); ?></a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (has_role('admin') || has_role('doctor') || has_role('nurse')): ?>
|
<?php if (has_permission('home_visits')): ?>
|
||||||
<a href="home_visits.php" class="sidebar-link <?php echo $section === 'home_visits' ? 'active' : ''; ?>"><i class="bi bi-house-heart me-2"></i> <?php echo __('home_visits'); ?></a>
|
<a href="home_visits.php" class="sidebar-link <?php echo $section === 'home_visits' ? 'active' : ''; ?>"><i class="bi bi-house-heart me-2"></i> <?php echo __('home_visits'); ?></a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (has_role('admin') || has_role('receptionist') || has_role('nurse')): ?>
|
<?php if (has_permission('queue')): ?>
|
||||||
<a href="queue.php" class="sidebar-link <?php echo $section === 'queue' ? 'active' : ''; ?>"><i class="bi bi-list-ol me-2"></i> <?php echo __('queue_management'); ?></a>
|
<a href="queue.php" class="sidebar-link <?php echo $section === 'queue' ? 'active' : ''; ?>"><i class="bi bi-list-ol me-2"></i> <?php echo __('queue_management'); ?></a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (has_role('admin') || has_role('laboratorial')): ?>
|
<?php if (has_permission('laboratory')): ?>
|
||||||
<a href="#labSubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['laboratory_tests', 'test_groups', 'laboratory_inquiries']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
<a href="#labSubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['laboratory_tests', 'test_groups', 'laboratory_inquiries']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
||||||
<span><i class="bi bi-prescription2 me-2"></i> <?php echo __('laboratory'); ?></span>
|
<span><i class="bi bi-prescription2 me-2"></i> <?php echo __('laboratory'); ?></span>
|
||||||
<i class="bi bi-chevron-down small"></i>
|
<i class="bi bi-chevron-down small"></i>
|
||||||
@ -149,7 +151,7 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (has_role('admin') || has_role('radiologic')): ?>
|
<?php if (has_permission('xray')): ?>
|
||||||
<!-- X-Ray Module -->
|
<!-- X-Ray Module -->
|
||||||
<a href="#xraySubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['xray_tests', 'xray_groups', 'xray_inquiries']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
<a href="#xraySubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['xray_tests', 'xray_groups', 'xray_inquiries']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
||||||
<span><i class="bi bi-x-diamond me-2"></i> <?php echo __('xray'); ?></span>
|
<span><i class="bi bi-x-diamond me-2"></i> <?php echo __('xray'); ?></span>
|
||||||
@ -164,7 +166,7 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (has_role('admin') || has_role('doctor')): ?>
|
<?php if (has_permission('pharmacy')): ?>
|
||||||
<!-- Pharmacy Module -->
|
<!-- Pharmacy Module -->
|
||||||
<a href="#pharmacySubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['pharmacy_inventory', 'pharmacy_pos', 'pharmacy_sales', 'drugs', 'drugs_groups', 'suppliers', 'pharmacy_purchases', 'pharmacy_purchase_returns', 'pharmacy_alerts', 'pharmacy_reports']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
<a href="#pharmacySubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['pharmacy_inventory', 'pharmacy_pos', 'pharmacy_sales', 'drugs', 'drugs_groups', 'suppliers', 'pharmacy_purchases', 'pharmacy_purchase_returns', 'pharmacy_alerts', 'pharmacy_reports']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
||||||
<span><i class="bi bi-capsule me-2"></i> <?php echo __('pharmacy'); ?></span>
|
<span><i class="bi bi-capsule me-2"></i> <?php echo __('pharmacy'); ?></span>
|
||||||
@ -187,21 +189,55 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_permission('inventory')): ?>
|
||||||
|
<!-- Stock Management Module -->
|
||||||
|
<a href="#inventorySubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['inventory_dashboard', 'inventory_items', 'inventory_categories', 'inventory_transactions', 'inventory_reports']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
||||||
|
<span><i class="bi bi-box-seam me-2"></i> <?php echo __('stock_management'); ?></span>
|
||||||
|
<i class="bi bi-chevron-down small"></i>
|
||||||
|
</a>
|
||||||
|
<div class="collapse <?php echo in_array($section, ['inventory_dashboard', 'inventory_items', 'inventory_categories', 'inventory_transactions', 'inventory_reports']) ? 'show' : ''; ?>" id="inventorySubmenu">
|
||||||
|
<div class="sidebar-submenu">
|
||||||
|
<a href="inventory_dashboard.php" class="sidebar-link py-2 <?php echo $section === 'inventory_dashboard' ? 'active' : ''; ?>"><i class="bi bi-speedometer2 me-2"></i> <?php echo __('dashboard'); ?></a>
|
||||||
|
<a href="inventory_items.php" class="sidebar-link py-2 <?php echo $section === 'inventory_items' ? 'active' : ''; ?>"><i class="bi bi-boxes me-2"></i> <?php echo __('items'); ?></a>
|
||||||
|
<a href="inventory_categories.php" class="sidebar-link py-2 <?php echo $section === 'inventory_categories' ? 'active' : ''; ?>"><i class="bi bi-tags me-2"></i> <?php echo __('categories'); ?></a>
|
||||||
|
<a href="inventory_transactions.php" class="sidebar-link py-2 <?php echo $section === 'inventory_transactions' ? 'active' : ''; ?>"><i class="bi bi-arrow-left-right me-2"></i> <?php echo __('transactions'); ?></a>
|
||||||
|
<a href="inventory_reports.php" class="sidebar-link py-2 <?php echo $section === 'inventory_reports' ? 'active' : ''; ?>"><i class="bi bi-file-earmark-bar-graph me-2"></i> <?php echo __('reports'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (has_role('admin') || has_role('receptionist')): ?>
|
<?php if (has_permission('billing')): ?>
|
||||||
<a href="billing.php" class="sidebar-link <?php echo $section === 'billing' ? 'active' : ''; ?>"><i class="bi bi-receipt me-2"></i> <?php echo __('billing'); ?></a>
|
<a href="billing.php" class="sidebar-link <?php echo $section === 'billing' ? 'active' : ''; ?>"><i class="bi bi-receipt me-2"></i> <?php echo __('billing'); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_permission('insurance')): ?>
|
||||||
<a href="insurance.php" class="sidebar-link <?php echo $section === 'insurance' ? 'active' : ''; ?>"><i class="bi bi-shield-check me-2"></i> <?php echo __('insurance'); ?></a>
|
<a href="insurance.php" class="sidebar-link <?php echo $section === 'insurance' ? 'active' : ''; ?>"><i class="bi bi-shield-check me-2"></i> <?php echo __('insurance'); ?></a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (has_role('admin')): ?>
|
<?php if (has_permission('doctors')): ?>
|
||||||
<a href="doctors.php" class="sidebar-link <?php echo $section === 'doctors' ? 'active' : ''; ?>"><i class="bi bi-person-badge me-2"></i> <?php echo __('doctors'); ?></a>
|
<a href="doctors.php" class="sidebar-link <?php echo $section === 'doctors' ? 'active' : ''; ?>"><i class="bi bi-person-badge me-2"></i> <?php echo __('doctors'); ?></a>
|
||||||
<a href="doctor_holidays.php" class="sidebar-link <?php echo $section === 'doctor_holidays' ? 'active' : ''; ?>"><i class="bi bi-calendar-x me-2"></i> <?php echo __('doctor_holidays'); ?></a>
|
<a href="doctor_holidays.php" class="sidebar-link <?php echo $section === 'doctor_holidays' ? 'active' : ''; ?>"><i class="bi bi-calendar-x me-2"></i> <?php echo __('doctor_holidays'); ?></a>
|
||||||
<a href="nurses.php" class="sidebar-link <?php echo $section === 'nurses' ? 'active' : ''; ?>"><i class="bi bi-person-heart me-2"></i> <?php echo __('nurses'); ?></a>
|
<a href="nurses.php" class="sidebar-link <?php echo $section === 'nurses' ? 'active' : ''; ?>"><i class="bi bi-person-heart me-2"></i> <?php echo __('nurses'); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_permission('reports')): ?>
|
||||||
<a href="reports.php" class="sidebar-link <?php echo $section === 'reports' ? 'active' : ''; ?>"><i class="bi bi-bar-chart-line me-2"></i> <?php echo __('admin_reports'); ?></a>
|
<a href="reports.php" class="sidebar-link <?php echo $section === 'reports' ? 'active' : ''; ?>"><i class="bi bi-bar-chart-line me-2"></i> <?php echo __('admin_reports'); ?></a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_permission('users')): ?>
|
||||||
|
<a href="#usersSubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['users', 'roles']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
||||||
|
<span><i class="bi bi-people-fill me-2"></i> <?php echo __('users_management'); ?></span>
|
||||||
|
<i class="bi bi-chevron-down small"></i>
|
||||||
|
</a>
|
||||||
|
<div class="collapse <?php echo in_array($section, ['users', 'roles']) ? 'show' : ''; ?>" id="usersSubmenu">
|
||||||
|
<div class="sidebar-submenu">
|
||||||
|
<a href="users.php" class="sidebar-link py-2 <?php echo $section === 'users' ? 'active' : ''; ?>"><i class="bi bi-person me-2"></i> <?php echo __('users'); ?></a>
|
||||||
|
<a href="roles.php" class="sidebar-link py-2 <?php echo $section === 'roles' ? 'active' : ''; ?>"><i class="bi bi-shield-lock me-2"></i> <?php echo __('roles'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (has_role('admin')): ?>
|
<?php if (has_permission('settings')): ?>
|
||||||
<a href="#settingsSubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['employees', 'positions', 'company_profile', 'cities', 'services', 'departments', 'queue_ads']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
<a href="#settingsSubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['employees', 'positions', 'company_profile', 'cities', 'services', 'departments', 'queue_ads']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
||||||
<span><i class="bi bi-gear me-2"></i> <?php echo __('settings'); ?></span>
|
<span><i class="bi bi-gear me-2"></i> <?php echo __('settings'); ?></span>
|
||||||
<i class="bi bi-chevron-down small"></i>
|
<i class="bi bi-chevron-down small"></i>
|
||||||
|
|||||||
183
includes/pages/inventory_categories.php
Normal file
183
includes/pages/inventory_categories.php
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
// Handle Create/Update/Delete Logic
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['action'])) {
|
||||||
|
if ($_POST['action'] === 'add_category') {
|
||||||
|
$name_en = $_POST['name_en'] ?? '';
|
||||||
|
$name_ar = $_POST['name_ar'] ?? '';
|
||||||
|
$description = $_POST['description'] ?? '';
|
||||||
|
|
||||||
|
if ($name_en && $name_ar) {
|
||||||
|
$stmt = $db->prepare("INSERT INTO inventory_categories (name_en, name_ar, description) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$name_en, $name_ar, $description]);
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-success">' . __('category_added_successfully') . '</div>';
|
||||||
|
} else {
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-danger">' . __('fill_all_required_fields') . '</div>';
|
||||||
|
}
|
||||||
|
} elseif ($_POST['action'] === 'delete_category') {
|
||||||
|
$id = $_POST['id'] ?? 0;
|
||||||
|
// Check if used
|
||||||
|
$stmt = $db->prepare("SELECT COUNT(*) FROM inventory_items WHERE category_id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
if ($stmt->fetchColumn() > 0) {
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-danger">' . __('cannot_delete_category_in_use') . '</div>';
|
||||||
|
} else {
|
||||||
|
$stmt = $db->prepare("DELETE FROM inventory_categories WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-success">' . __('category_deleted_successfully') . '</div>';
|
||||||
|
}
|
||||||
|
} elseif ($_POST['action'] === 'edit_category') {
|
||||||
|
$id = $_POST['id'] ?? 0;
|
||||||
|
$name_en = $_POST['name_en'] ?? '';
|
||||||
|
$name_ar = $_POST['name_ar'] ?? '';
|
||||||
|
$description = $_POST['description'] ?? '';
|
||||||
|
|
||||||
|
if ($name_en && $name_ar) {
|
||||||
|
$stmt = $db->prepare("UPDATE inventory_categories SET name_en = ?, name_ar = ?, description = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$name_en, $name_ar, $description, $id]);
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-success">' . __('category_updated_successfully') . '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to avoid resubmission
|
||||||
|
header("Location: inventory_categories.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch Categories
|
||||||
|
$categories = $db->query("SELECT * FROM inventory_categories ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="mb-0 fw-bold text-dark"><?php echo __('inventory_categories'); ?></h2>
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCategoryModal">
|
||||||
|
<i class="bi bi-plus-lg me-1"></i> <?php echo __('add_category'); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th><?php echo __('name_en'); ?></th>
|
||||||
|
<th><?php echo __('name_ar'); ?></th>
|
||||||
|
<th><?php echo __('description'); ?></th>
|
||||||
|
<th class="text-end"><?php echo __('actions'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($categories)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center py-4 text-muted"><?php echo __('no_data_available'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($categories as $cat): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo $cat['id']; ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($cat['name_en']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($cat['name_ar']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($cat['description'] ?? ''); ?></td>
|
||||||
|
<td class="text-end">
|
||||||
|
<button class="btn btn-sm btn-outline-primary me-1"
|
||||||
|
onclick="editCategory(<?php echo htmlspecialchars(json_encode($cat)); ?>)">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<form method="POST" class="d-inline" onsubmit="return confirm('<?php echo __('are_you_sure'); ?>');">
|
||||||
|
<input type="hidden" name="action" value="delete_category">
|
||||||
|
<input type="hidden" name="id" value="<?php echo $cat['id']; ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-danger">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Category Modal -->
|
||||||
|
<div class="modal fade" id="addCategoryModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><?php echo __('add_category'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="add_category">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('name_en'); ?> <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" name="name_en" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('name_ar'); ?> <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" name="name_ar" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('description'); ?></label>
|
||||||
|
<textarea name="description" class="form-control" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?php echo __('save'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Category Modal -->
|
||||||
|
<div class="modal fade" id="editCategoryModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><?php echo __('edit_category'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="edit_category">
|
||||||
|
<input type="hidden" name="id" id="edit_id">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('name_en'); ?> <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" name="name_en" id="edit_name_en" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('name_ar'); ?> <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" name="name_ar" id="edit_name_ar" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('description'); ?></label>
|
||||||
|
<textarea name="description" id="edit_description" class="form-control" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?php echo __('update'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function editCategory(cat) {
|
||||||
|
document.getElementById('edit_id').value = cat.id;
|
||||||
|
document.getElementById('edit_name_en').value = cat.name_en;
|
||||||
|
document.getElementById('edit_name_ar').value = cat.name_ar;
|
||||||
|
document.getElementById('edit_description').value = cat.description || '';
|
||||||
|
|
||||||
|
var myModal = new bootstrap.Modal(document.getElementById('editCategoryModal'));
|
||||||
|
myModal.show();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
137
includes/pages/inventory_dashboard.php
Normal file
137
includes/pages/inventory_dashboard.php
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
// Statistics
|
||||||
|
$total_items = $db->query("SELECT COUNT(*) FROM inventory_items WHERE is_active = 1")->fetchColumn();
|
||||||
|
|
||||||
|
$total_value_stmt = $db->query("SELECT SUM(quantity * cost_price) FROM inventory_batches");
|
||||||
|
$total_value = $total_value_stmt->fetchColumn() ?: 0;
|
||||||
|
|
||||||
|
// Low Stock
|
||||||
|
$low_stock_count = $db->query("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM inventory_items i
|
||||||
|
WHERE (SELECT SUM(quantity) FROM inventory_batches b WHERE b.item_id = i.id) <= i.min_level
|
||||||
|
")->fetchColumn();
|
||||||
|
|
||||||
|
// Expiring Soon (30 days)
|
||||||
|
$expiring_count = $db->query("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM inventory_batches
|
||||||
|
WHERE expiry_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY)
|
||||||
|
AND quantity > 0
|
||||||
|
")->fetchColumn();
|
||||||
|
|
||||||
|
// Recent Transactions
|
||||||
|
$recent_transactions = $db->query("
|
||||||
|
SELECT t.*, i.name_en as item_name
|
||||||
|
FROM inventory_transactions t
|
||||||
|
JOIN inventory_items i ON t.item_id = i.id
|
||||||
|
ORDER BY t.transaction_date DESC
|
||||||
|
LIMIT 5
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<h2 class="mb-4 fw-bold text-dark"><?php echo __('inventory_dashboard'); ?></h2>
|
||||||
|
|
||||||
|
<div class="row g-4 mb-4">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card shadow-sm border-0 h-100">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="text-primary mb-2"><i class="bi bi-box-seam fs-1"></i></div>
|
||||||
|
<h5 class="card-title text-muted"><?php echo __('total_items'); ?></h5>
|
||||||
|
<h2 class="fw-bold"><?php echo number_format($total_items); ?></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card shadow-sm border-0 h-100">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="text-success mb-2"><i class="bi bi-cash-stack fs-1"></i></div>
|
||||||
|
<h5 class="card-title text-muted"><?php echo __('total_value'); ?></h5>
|
||||||
|
<h2 class="fw-bold"><?php echo number_format($total_value, 2); ?></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card shadow-sm border-0 h-100">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="text-warning mb-2"><i class="bi bi-exclamation-triangle fs-1"></i></div>
|
||||||
|
<h5 class="card-title text-muted"><?php echo __('low_stock_items'); ?></h5>
|
||||||
|
<h2 class="fw-bold"><?php echo number_format($low_stock_count); ?></h2>
|
||||||
|
<a href="inventory_items.php" class="small text-decoration-none stretched-link"><?php echo __('view_details'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card shadow-sm border-0 h-100">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="text-danger mb-2"><i class="bi bi-calendar-x fs-1"></i></div>
|
||||||
|
<h5 class="card-title text-muted"><?php echo __('expiring_soon'); ?></h5>
|
||||||
|
<h2 class="fw-bold"><?php echo number_format($expiring_count); ?></h2>
|
||||||
|
<a href="inventory_reports.php?report=expiry" class="small text-decoration-none stretched-link"><?php echo __('view_details'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-header bg-white">
|
||||||
|
<h5 class="mb-0 fw-bold text-dark"><?php echo __('recent_transactions'); ?></h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0 align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th><?php echo __('date'); ?></th>
|
||||||
|
<th><?php echo __('item'); ?></th>
|
||||||
|
<th><?php echo __('type'); ?></th>
|
||||||
|
<th><?php echo __('qty'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($recent_transactions as $t): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo date('M d, H:i', strtotime($t['transaction_date'])); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($t['item_name']); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($t['transaction_type'] === 'in'): ?>
|
||||||
|
<span class="badge bg-success-subtle text-success">IN</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-danger-subtle text-danger">OUT</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?php echo $t['quantity']; ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($recent_transactions)): ?>
|
||||||
|
<tr><td colspan="4" class="text-center text-muted py-3"><?php echo __('no_recent_transactions'); ?></td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer bg-white text-center">
|
||||||
|
<a href="inventory_transactions.php" class="text-decoration-none"><?php echo __('view_all_transactions'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
<div class="card shadow-sm border-0 mb-4">
|
||||||
|
<div class="card-header bg-white">
|
||||||
|
<h5 class="mb-0 fw-bold text-dark"><?php echo __('quick_actions'); ?></h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<a href="inventory_items.php" class="btn btn-outline-primary"><i class="bi bi-plus-lg me-2"></i> <?php echo __('add_new_item'); ?></a>
|
||||||
|
<a href="inventory_transactions.php" class="btn btn-outline-success"><i class="bi bi-box-arrow-in-down me-2"></i> <?php echo __('receive_stock'); ?></a>
|
||||||
|
<a href="inventory_transactions.php" class="btn btn-outline-danger"><i class="bi bi-box-arrow-up me-2"></i> <?php echo __('issue_stock'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
282
includes/pages/inventory_items.php
Normal file
282
includes/pages/inventory_items.php
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
<?php
|
||||||
|
// Handle Actions
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['action'])) {
|
||||||
|
if ($_POST['action'] === 'add_item') {
|
||||||
|
$name_en = $_POST['name_en'] ?? '';
|
||||||
|
$name_ar = $_POST['name_ar'] ?? '';
|
||||||
|
$category_id = $_POST['category_id'] ?? null;
|
||||||
|
$sku = $_POST['sku'] ?? '';
|
||||||
|
$unit = $_POST['unit'] ?? 'piece';
|
||||||
|
$min_level = $_POST['min_level'] ?? 10;
|
||||||
|
$reorder_level = $_POST['reorder_level'] ?? 20;
|
||||||
|
$description = $_POST['description'] ?? '';
|
||||||
|
|
||||||
|
if ($name_en && $category_id) {
|
||||||
|
try {
|
||||||
|
$stmt = $db->prepare("INSERT INTO inventory_items (name_en, name_ar, category_id, sku, unit, min_level, reorder_level, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$name_en, $name_ar, $category_id, $sku, $unit, $min_level, $reorder_level, $description]);
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-success">' . __('item_added_successfully') . '</div>';
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-danger">' . __('error_adding_item') . ': ' . $e->getMessage() . '</div>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-danger">' . __('fill_all_required_fields') . '</div>';
|
||||||
|
}
|
||||||
|
} elseif ($_POST['action'] === 'edit_item') {
|
||||||
|
$id = $_POST['id'] ?? 0;
|
||||||
|
$name_en = $_POST['name_en'] ?? '';
|
||||||
|
$name_ar = $_POST['name_ar'] ?? '';
|
||||||
|
$category_id = $_POST['category_id'] ?? null;
|
||||||
|
$sku = $_POST['sku'] ?? '';
|
||||||
|
$unit = $_POST['unit'] ?? 'piece';
|
||||||
|
$min_level = $_POST['min_level'] ?? 10;
|
||||||
|
$reorder_level = $_POST['reorder_level'] ?? 20;
|
||||||
|
$description = $_POST['description'] ?? '';
|
||||||
|
|
||||||
|
if ($name_en && $category_id) {
|
||||||
|
try {
|
||||||
|
$stmt = $db->prepare("UPDATE inventory_items SET name_en = ?, name_ar = ?, category_id = ?, sku = ?, unit = ?, min_level = ?, reorder_level = ?, description = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$name_en, $name_ar, $category_id, $sku, $unit, $min_level, $reorder_level, $description, $id]);
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-success">' . __('item_updated_successfully') . '</div>';
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-danger">' . __('error_updating_item') . ': ' . $e->getMessage() . '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($_POST['action'] === 'delete_item') {
|
||||||
|
$id = $_POST['id'] ?? 0;
|
||||||
|
// Check usage
|
||||||
|
$stmt = $db->prepare("SELECT COUNT(*) FROM inventory_batches WHERE item_id = ? AND quantity > 0");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
if ($stmt->fetchColumn() > 0) {
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-danger">' . __('cannot_delete_item_with_stock') . '</div>';
|
||||||
|
} else {
|
||||||
|
$stmt = $db->prepare("DELETE FROM inventory_items WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-success">' . __('item_deleted_successfully') . '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header("Location: inventory_items.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch Items with Stock
|
||||||
|
$items = $db->query("
|
||||||
|
SELECT i.*, c.name_en as category_name,
|
||||||
|
COALESCE((SELECT SUM(quantity) FROM inventory_batches b WHERE b.item_id = i.id), 0) as current_stock
|
||||||
|
FROM inventory_items i
|
||||||
|
LEFT JOIN inventory_categories c ON i.category_id = c.id
|
||||||
|
ORDER BY i.name_en ASC
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Fetch Categories for Dropdown
|
||||||
|
$categories = $db->query("SELECT * FROM inventory_categories ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="mb-0 fw-bold text-dark"><?php echo __('inventory_items'); ?></h2>
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal">
|
||||||
|
<i class="bi bi-plus-lg me-1"></i> <?php echo __('add_item'); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th><?php echo __('sku'); ?></th>
|
||||||
|
<th><?php echo __('item_name'); ?></th>
|
||||||
|
<th><?php echo __('category'); ?></th>
|
||||||
|
<th><?php echo __('unit'); ?></th>
|
||||||
|
<th><?php echo __('min_level'); ?></th>
|
||||||
|
<th><?php echo __('current_stock'); ?></th>
|
||||||
|
<th class="text-end"><?php echo __('actions'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($items)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center py-4 text-muted"><?php echo __('no_items_found'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($items as $item): ?>
|
||||||
|
<tr class="<?php echo ($item['current_stock'] <= $item['min_level']) ? 'table-warning' : ''; ?>">
|
||||||
|
<td><span class="badge bg-secondary"><?php echo htmlspecialchars($item['sku'] ?? '-'); ?></span></td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold"><?php echo htmlspecialchars($item['name_en']); ?></div>
|
||||||
|
<small class="text-muted"><?php echo htmlspecialchars($item['name_ar']); ?></small>
|
||||||
|
</td>
|
||||||
|
<td><?php echo htmlspecialchars($item['category_name'] ?? '-'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($item['unit']); ?></td>
|
||||||
|
<td><?php echo $item['min_level']; ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($item['current_stock'] == 0): ?>
|
||||||
|
<span class="badge bg-danger"><?php echo __('out_of_stock'); ?></span>
|
||||||
|
<?php elseif ($item['current_stock'] <= $item['min_level']): ?>
|
||||||
|
<span class="badge bg-warning text-dark"><?php echo $item['current_stock']; ?> (Low)</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-success"><?php echo $item['current_stock']; ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<button class="btn btn-sm btn-outline-primary me-1"
|
||||||
|
onclick='editItem(<?php echo json_encode($item, JSON_HEX_APOS | JSON_HEX_QUOT); ?>)'>
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<form method="POST" class="d-inline" onsubmit="return confirm('<?php echo __('are_you_sure'); ?>');">
|
||||||
|
<input type="hidden" name="action" value="delete_item">
|
||||||
|
<input type="hidden" name="id" value="<?php echo $item['id']; ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-danger">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Item Modal -->
|
||||||
|
<div class="modal fade" id="addItemModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><?php echo __('add_item'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="add_item">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label"><?php echo __('name_en'); ?> <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" name="name_en" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label"><?php echo __('name_ar'); ?></label>
|
||||||
|
<input type="text" name="name_ar" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label"><?php echo __('category'); ?> <span class="text-danger">*</span></label>
|
||||||
|
<select name="category_id" class="form-select" required>
|
||||||
|
<option value=""><?php echo __('select_category'); ?></option>
|
||||||
|
<?php foreach ($categories as $cat): ?>
|
||||||
|
<option value="<?php echo $cat['id']; ?>"><?php echo htmlspecialchars($cat['name_en']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label"><?php echo __('sku'); ?></label>
|
||||||
|
<input type="text" name="sku" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label"><?php echo __('unit'); ?></label>
|
||||||
|
<input type="text" name="unit" class="form-control" value="piece">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label"><?php echo __('min_level'); ?></label>
|
||||||
|
<input type="number" name="min_level" class="form-control" value="10">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label"><?php echo __('reorder_level'); ?></label>
|
||||||
|
<input type="number" name="reorder_level" class="form-control" value="20">
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label"><?php echo __('description'); ?></label>
|
||||||
|
<textarea name="description" class="form-control" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?php echo __('save'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Item Modal -->
|
||||||
|
<div class="modal fade" id="editItemModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><?php echo __('edit_item'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="edit_item">
|
||||||
|
<input type="hidden" name="id" id="edit_id">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label"><?php echo __('name_en'); ?> <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" name="name_en" id="edit_name_en" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label"><?php echo __('name_ar'); ?></label>
|
||||||
|
<input type="text" name="name_ar" id="edit_name_ar" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label"><?php echo __('category'); ?> <span class="text-danger">*</span></label>
|
||||||
|
<select name="category_id" id="edit_category_id" class="form-select" required>
|
||||||
|
<option value=""><?php echo __('select_category'); ?></option>
|
||||||
|
<?php foreach ($categories as $cat): ?>
|
||||||
|
<option value="<?php echo $cat['id']; ?>"><?php echo htmlspecialchars($cat['name_en']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label"><?php echo __('sku'); ?></label>
|
||||||
|
<input type="text" name="sku" id="edit_sku" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label"><?php echo __('unit'); ?></label>
|
||||||
|
<input type="text" name="unit" id="edit_unit" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label"><?php echo __('min_level'); ?></label>
|
||||||
|
<input type="number" name="min_level" id="edit_min_level" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label"><?php echo __('reorder_level'); ?></label>
|
||||||
|
<input type="number" name="reorder_level" id="edit_reorder_level" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label"><?php echo __('description'); ?></label>
|
||||||
|
<textarea name="description" id="edit_description" class="form-control" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?php echo __('update'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function editItem(item) {
|
||||||
|
document.getElementById('edit_id').value = item.id;
|
||||||
|
document.getElementById('edit_name_en').value = item.name_en;
|
||||||
|
document.getElementById('edit_name_ar').value = item.name_ar;
|
||||||
|
document.getElementById('edit_category_id').value = item.category_id;
|
||||||
|
document.getElementById('edit_sku').value = item.sku || '';
|
||||||
|
document.getElementById('edit_unit').value = item.unit || 'piece';
|
||||||
|
document.getElementById('edit_min_level').value = item.min_level;
|
||||||
|
document.getElementById('edit_reorder_level').value = item.reorder_level;
|
||||||
|
document.getElementById('edit_description').value = item.description || '';
|
||||||
|
|
||||||
|
var myModal = new bootstrap.Modal(document.getElementById('editItemModal'));
|
||||||
|
myModal.show();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
111
includes/pages/inventory_reports.php
Normal file
111
includes/pages/inventory_reports.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
$report_type = $_GET['report'] ?? 'low_stock';
|
||||||
|
|
||||||
|
// Data Fetching based on report type
|
||||||
|
$data = [];
|
||||||
|
$columns = [];
|
||||||
|
|
||||||
|
if ($report_type === 'low_stock') {
|
||||||
|
$title = __('low_stock_report');
|
||||||
|
$columns = ['item_name', 'sku', 'min_level', 'current_stock', 'status'];
|
||||||
|
$data = $db->query("
|
||||||
|
SELECT i.name_en as item_name, i.sku, i.min_level,
|
||||||
|
COALESCE((SELECT SUM(quantity) FROM inventory_batches b WHERE b.item_id = i.id), 0) as current_stock
|
||||||
|
FROM inventory_items i
|
||||||
|
HAVING current_stock <= min_level
|
||||||
|
ORDER BY current_stock ASC
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} elseif ($report_type === 'expiry') {
|
||||||
|
$title = __('expiry_report');
|
||||||
|
$columns = ['item_name', 'batch_number', 'expiry_date', 'quantity', 'days_remaining'];
|
||||||
|
$data = $db->query("
|
||||||
|
SELECT i.name_en as item_name, b.batch_number, b.expiry_date, b.quantity,
|
||||||
|
DATEDIFF(b.expiry_date, CURDATE()) as days_remaining
|
||||||
|
FROM inventory_batches b
|
||||||
|
JOIN inventory_items i ON b.item_id = i.id
|
||||||
|
WHERE b.quantity > 0 AND b.expiry_date IS NOT NULL
|
||||||
|
AND b.expiry_date <= DATE_ADD(CURDATE(), INTERVAL 90 DAY)
|
||||||
|
ORDER BY b.expiry_date ASC
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} elseif ($report_type === 'valuation') {
|
||||||
|
$title = __('stock_valuation_report');
|
||||||
|
$columns = ['item_name', 'total_quantity', 'avg_cost', 'total_value'];
|
||||||
|
$data = $db->query("
|
||||||
|
SELECT i.name_en as item_name,
|
||||||
|
SUM(b.quantity) as total_quantity,
|
||||||
|
AVG(b.cost_price) as avg_cost,
|
||||||
|
SUM(b.quantity * b.cost_price) as total_value
|
||||||
|
FROM inventory_batches b
|
||||||
|
JOIN inventory_items i ON b.item_id = i.id
|
||||||
|
WHERE b.quantity > 0
|
||||||
|
GROUP BY i.id
|
||||||
|
ORDER BY total_value DESC
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="mb-0 fw-bold text-dark"><?php echo __('inventory_reports'); ?></h2>
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="?report=low_stock" class="btn btn-outline-primary <?php echo $report_type === 'low_stock' ? 'active' : ''; ?>"><?php echo __('low_stock'); ?></a>
|
||||||
|
<a href="?report=expiry" class="btn btn-outline-primary <?php echo $report_type === 'expiry' ? 'active' : ''; ?>"><?php echo __('expiry_dates'); ?></a>
|
||||||
|
<a href="?report=valuation" class="btn btn-outline-primary <?php echo $report_type === 'valuation' ? 'active' : ''; ?>"><?php echo __('valuation'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-header bg-white">
|
||||||
|
<h5 class="mb-0 fw-bold"><?php echo htmlspecialchars($title); ?></h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<?php foreach ($columns as $col): ?>
|
||||||
|
<th><?php echo __(str_replace('_', ' ', $col)); ?></th>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($data)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="<?php echo count($columns); ?>" class="text-center py-4 text-muted"><?php echo __('no_data_available'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($data as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<?php foreach ($columns as $col): ?>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
if ($col === 'current_stock' || $col === 'total_quantity') {
|
||||||
|
echo number_format($row[$col]);
|
||||||
|
} elseif ($col === 'total_value' || $col === 'avg_cost') {
|
||||||
|
echo formatCurrency($row[$col]);
|
||||||
|
} elseif ($col === 'status') {
|
||||||
|
if ($row['current_stock'] == 0) echo '<span class="badge bg-danger">Out of Stock</span>';
|
||||||
|
else echo '<span class="badge bg-warning text-dark">Low Stock</span>';
|
||||||
|
} elseif ($col === 'days_remaining') {
|
||||||
|
$days = $row['days_remaining'];
|
||||||
|
if ($days < 0) echo '<span class="badge bg-danger">Expired</span>';
|
||||||
|
elseif ($days < 30) echo '<span class="badge bg-warning text-dark">' . $days . ' days</span>';
|
||||||
|
else echo '<span class="badge bg-info">' . $days . ' days</span>';
|
||||||
|
} else {
|
||||||
|
echo htmlspecialchars($row[$col]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 text-end">
|
||||||
|
<button onclick="window.print()" class="btn btn-outline-secondary"><i class="bi bi-printer me-2"></i> <?php echo __('print_report'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
316
includes/pages/inventory_transactions.php
Normal file
316
includes/pages/inventory_transactions.php
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
<?php
|
||||||
|
// Handle Create Transaction
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['action']) && $_POST['action'] === 'add_transaction') {
|
||||||
|
$type = $_POST['type'] ?? 'in';
|
||||||
|
$item_id = $_POST['item_id'] ?? 0;
|
||||||
|
$quantity = (int)($_POST['quantity'] ?? 0);
|
||||||
|
$notes = $_POST['notes'] ?? '';
|
||||||
|
$reference_type = $_POST['reference_type'] ?? 'manual_adjustment';
|
||||||
|
$reference_id = 0; // Or pass from somewhere
|
||||||
|
|
||||||
|
if (!$item_id || $quantity <= 0) {
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-danger">' . __('fill_all_required_fields') . '</div>';
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$db->beginTransaction();
|
||||||
|
|
||||||
|
if ($type === 'in') {
|
||||||
|
// Create New Batch
|
||||||
|
$batch_number = $_POST['batch_number'] ?? date('YmdHis');
|
||||||
|
$expiry_date = !empty($_POST['expiry_date']) ? $_POST['expiry_date'] : null;
|
||||||
|
$cost_price = $_POST['cost_price'] ?? 0;
|
||||||
|
$supplier_id = !empty($_POST['supplier_id']) ? $_POST['supplier_id'] : null;
|
||||||
|
|
||||||
|
$stmt = $db->prepare("INSERT INTO inventory_batches (item_id, batch_number, expiry_date, quantity, cost_price, supplier_id, received_date) VALUES (?, ?, ?, ?, ?, ?, NOW())");
|
||||||
|
$stmt->execute([$item_id, $batch_number, $expiry_date, $quantity, $cost_price, $supplier_id]);
|
||||||
|
$batch_id = $db->lastInsertId();
|
||||||
|
|
||||||
|
// Create Transaction Record
|
||||||
|
$stmt = $db->prepare("INSERT INTO inventory_transactions (item_id, batch_id, transaction_type, quantity, reference_type, reference_id, user_id, notes) VALUES (?, ?, 'in', ?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$item_id, $batch_id, $quantity, $reference_type, $reference_id, $_SESSION['user_id'], $notes]);
|
||||||
|
|
||||||
|
} elseif ($type === 'out') {
|
||||||
|
// Deduct from Batch
|
||||||
|
$batch_id = $_POST['batch_id'] ?? 0;
|
||||||
|
|
||||||
|
// Check availability
|
||||||
|
$stmt = $db->prepare("SELECT quantity FROM inventory_batches WHERE id = ? FOR UPDATE");
|
||||||
|
$stmt->execute([$batch_id]);
|
||||||
|
$current_qty = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
if ($current_qty < $quantity) {
|
||||||
|
throw new Exception(__('insufficient_stock_in_batch'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Batch
|
||||||
|
$stmt = $db->prepare("UPDATE inventory_batches SET quantity = quantity - ? WHERE id = ?");
|
||||||
|
$stmt->execute([$quantity, $batch_id]);
|
||||||
|
|
||||||
|
// Create Transaction Record
|
||||||
|
$stmt = $db->prepare("INSERT INTO inventory_transactions (item_id, batch_id, transaction_type, quantity, reference_type, reference_id, user_id, notes) VALUES (?, ?, 'out', ?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$item_id, $batch_id, $quantity, $reference_type, $reference_id, $_SESSION['user_id'], $notes]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->commit();
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-success">' . __('transaction_recorded_successfully') . '</div>';
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$db->rollBack();
|
||||||
|
$_SESSION['flash_message'] = '<div class="alert alert-danger">' . __('error_recording_transaction') . ': ' . $e->getMessage() . '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header("Location: inventory_transactions.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch Transactions
|
||||||
|
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||||
|
$per_page = 20;
|
||||||
|
$offset = ($page - 1) * $per_page;
|
||||||
|
|
||||||
|
$stmt = $db->query("SELECT COUNT(*) FROM inventory_transactions");
|
||||||
|
$total_records = $stmt->fetchColumn();
|
||||||
|
$total_pages = ceil($total_records / $per_page);
|
||||||
|
|
||||||
|
$transactions = $db->query("
|
||||||
|
SELECT t.*, i.name_en as item_name, i.sku, b.batch_number, u.name as user_name
|
||||||
|
FROM inventory_transactions t
|
||||||
|
JOIN inventory_items i ON t.item_id = i.id
|
||||||
|
LEFT JOIN inventory_batches b ON t.batch_id = b.id
|
||||||
|
LEFT JOIN users u ON t.user_id = u.id
|
||||||
|
ORDER BY t.transaction_date DESC
|
||||||
|
LIMIT $per_page OFFSET $offset
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Fetch Items for Dropdown
|
||||||
|
$items = $db->query("SELECT id, name_en, sku FROM inventory_items WHERE is_active = 1 ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Fetch Suppliers
|
||||||
|
$suppliers = $db->query("SELECT id, name_en FROM suppliers ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="mb-0 fw-bold text-dark"><?php echo __('inventory_transactions'); ?></h2>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="openNewTransactionModal()">
|
||||||
|
<i class="bi bi-plus-lg me-1"></i> <?php echo __('new_transaction'); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th><?php echo __('date'); ?></th>
|
||||||
|
<th><?php echo __('type'); ?></th>
|
||||||
|
<th><?php echo __('item'); ?></th>
|
||||||
|
<th><?php echo __('batch'); ?></th>
|
||||||
|
<th><?php echo __('quantity'); ?></th>
|
||||||
|
<th><?php echo __('user'); ?></th>
|
||||||
|
<th><?php echo __('notes'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($transactions)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center py-4 text-muted"><?php echo __('no_transactions_found'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($transactions as $t): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo date('Y-m-d H:i', strtotime($t['transaction_date'])); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($t['transaction_type'] === 'in'): ?>
|
||||||
|
<span class="badge bg-success"><i class="bi bi-arrow-down"></i> <?php echo __('in'); ?></span>
|
||||||
|
<?php elseif ($t['transaction_type'] === 'out'): ?>
|
||||||
|
<span class="badge bg-danger"><i class="bi bi-arrow-up"></i> <?php echo __('out'); ?></span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-warning text-dark"><?php echo __('adjustment'); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold"><?php echo htmlspecialchars($t['item_name']); ?></div>
|
||||||
|
<small class="text-muted"><?php echo htmlspecialchars($t['sku']); ?></small>
|
||||||
|
</td>
|
||||||
|
<td><?php echo htmlspecialchars($t['batch_number'] ?? '-'); ?></td>
|
||||||
|
<td class="fw-bold"><?php echo $t['quantity']; ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($t['user_name'] ?? 'System'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($t['notes'] ?? ''); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<?php if ($total_pages > 1): ?>
|
||||||
|
<nav class="mt-4">
|
||||||
|
<ul class="pagination justify-content-center">
|
||||||
|
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
|
||||||
|
<li class="page-item <?php echo $i === $page ? 'active' : ''; ?>">
|
||||||
|
<a class="page-link" href="?page=<?php echo $i; ?>"><?php echo $i; ?></a>
|
||||||
|
</li>
|
||||||
|
<?php endfor; ?>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- New Transaction Modal -->
|
||||||
|
<div class="modal fade" id="transactionModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><?php echo __('new_transaction'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="add_transaction">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('transaction_type'); ?></label>
|
||||||
|
<div class="btn-group w-100" role="group">
|
||||||
|
<input type="radio" class="btn-check" name="type" id="type_in" value="in" checked onchange="toggleType()">
|
||||||
|
<label class="btn btn-outline-success" for="type_in"><?php echo __('stock_in_purchase'); ?></label>
|
||||||
|
|
||||||
|
<input type="radio" class="btn-check" name="type" id="type_out" value="out" onchange="toggleType()">
|
||||||
|
<label class="btn btn-outline-danger" for="type_out"><?php echo __('stock_out_consumption'); ?></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('item'); ?> <span class="text-danger">*</span></label>
|
||||||
|
<select name="item_id" id="item_select" class="form-select" required onchange="fetchBatches()">
|
||||||
|
<option value=""><?php echo __('select_item'); ?></option>
|
||||||
|
<?php foreach ($items as $item): ?>
|
||||||
|
<option value="<?php echo $item['id']; ?>"><?php echo htmlspecialchars($item['name_en'] . ' (' . ($item['sku'] ?? '-') . ')'); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fields for IN -->
|
||||||
|
<div id="in_fields">
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label"><?php echo __('batch_number'); ?></label>
|
||||||
|
<input type="text" name="batch_number" class="form-control" placeholder="Leave empty for auto-generated">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label"><?php echo __('expiry_date'); ?></label>
|
||||||
|
<input type="date" name="expiry_date" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label"><?php echo __('cost_price'); ?></label>
|
||||||
|
<input type="number" step="0.01" name="cost_price" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label"><?php echo __('supplier'); ?></label>
|
||||||
|
<select name="supplier_id" class="form-select">
|
||||||
|
<option value=""><?php echo __('select_supplier'); ?></option>
|
||||||
|
<?php foreach ($suppliers as $sup): ?>
|
||||||
|
<option value="<?php echo $sup['id']; ?>"><?php echo htmlspecialchars($sup['name_en']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fields for OUT -->
|
||||||
|
<div id="out_fields" style="display: none;">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('select_batch_to_deduct_from'); ?> <span class="text-danger">*</span></label>
|
||||||
|
<select name="batch_id" id="batch_select" class="form-select">
|
||||||
|
<option value=""><?php echo __('select_item_first'); ?></option>
|
||||||
|
</select>
|
||||||
|
<div class="form-text text-muted" id="batch_info"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('quantity'); ?> <span class="text-danger">*</span></label>
|
||||||
|
<input type="number" name="quantity" class="form-control" min="1" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('notes'); ?></label>
|
||||||
|
<textarea name="notes" class="form-control" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?php echo __('save_transaction'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function openNewTransactionModal() {
|
||||||
|
var myModal = new bootstrap.Modal(document.getElementById('transactionModal'));
|
||||||
|
myModal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleType() {
|
||||||
|
const isOut = document.getElementById('type_out').checked;
|
||||||
|
document.getElementById('in_fields').style.display = isOut ? 'none' : 'block';
|
||||||
|
document.getElementById('out_fields').style.display = isOut ? 'block' : 'none';
|
||||||
|
|
||||||
|
// Toggle required attribute for batch_select
|
||||||
|
document.getElementById('batch_select').required = isOut;
|
||||||
|
|
||||||
|
// Clear batch selection if switching
|
||||||
|
if (isOut) {
|
||||||
|
fetchBatches();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchBatches() {
|
||||||
|
const itemId = document.getElementById('item_select').value;
|
||||||
|
const batchSelect = document.getElementById('batch_select');
|
||||||
|
const isOut = document.getElementById('type_out').checked;
|
||||||
|
|
||||||
|
if (!isOut || !itemId) return;
|
||||||
|
|
||||||
|
batchSelect.innerHTML = '<option value="">Loading...</option>';
|
||||||
|
|
||||||
|
fetch('api/inventory.php?action=get_batches&item_id=' + itemId)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
batchSelect.innerHTML = '<option value="">Select Batch</option>';
|
||||||
|
if (data.length === 0) {
|
||||||
|
batchSelect.innerHTML = '<option value="">No stock available</option>';
|
||||||
|
}
|
||||||
|
data.forEach(batch => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = batch.id;
|
||||||
|
option.text = `Batch: ${batch.batch_number} | Exp: ${batch.expiry_date || 'N/A'} | Qty: ${batch.quantity}`;
|
||||||
|
batchSelect.add(option);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Error fetching batches:', err);
|
||||||
|
batchSelect.innerHTML = '<option value="">Error loading batches</option>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize Select2 if available
|
||||||
|
if (typeof $().select2 === 'function') {
|
||||||
|
$('#item_select').select2({
|
||||||
|
dropdownParent: $('#transactionModal'),
|
||||||
|
theme: 'bootstrap-5'
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#item_select').on('select2:select', function (e) {
|
||||||
|
fetchBatches();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
256
includes/pages/roles.php
Normal file
256
includes/pages/roles.php
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
<?php
|
||||||
|
// Handle Actions
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['action']) && $_POST['action'] === 'update_permissions') {
|
||||||
|
try {
|
||||||
|
$role_id = $_POST['role_id'];
|
||||||
|
$perms = isset($_POST['permissions']) ? $_POST['permissions'] : [];
|
||||||
|
|
||||||
|
// Encode as JSON
|
||||||
|
$perms_json = json_encode($perms);
|
||||||
|
|
||||||
|
$stmt = $db->prepare("UPDATE roles SET permissions = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$perms_json, $role_id]);
|
||||||
|
|
||||||
|
$_SESSION['flash_message'] = __('permissions_updated');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$_SESSION['flash_message'] = "Error: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
header("Location: roles.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_POST['action']) && $_POST['action'] === 'add_role') {
|
||||||
|
try {
|
||||||
|
$name = trim($_POST['name']);
|
||||||
|
$perms = isset($_POST['permissions']) ? $_POST['permissions'] : [];
|
||||||
|
|
||||||
|
if (empty($name)) {
|
||||||
|
throw new Exception("Role name is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate slug
|
||||||
|
$slug = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $name)));
|
||||||
|
|
||||||
|
// Check if slug exists
|
||||||
|
$stmt = $db->prepare("SELECT COUNT(*) FROM roles WHERE slug = ?");
|
||||||
|
$stmt->execute([$slug]);
|
||||||
|
if ($stmt->fetchColumn() > 0) {
|
||||||
|
throw new Exception("Role with this name already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode permissions
|
||||||
|
$perms_json = json_encode($perms);
|
||||||
|
|
||||||
|
$stmt = $db->prepare("INSERT INTO roles (name, slug, permissions) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$name, $slug, $perms_json]);
|
||||||
|
|
||||||
|
$_SESSION['flash_message'] = "Role added successfully";
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$_SESSION['flash_message'] = "Error: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
header("Location: roles.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch Roles
|
||||||
|
$stmt = $db->query("SELECT * FROM roles ORDER BY name ASC");
|
||||||
|
$roles = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Define Available Permissions
|
||||||
|
$available_permissions = [
|
||||||
|
'dashboard',
|
||||||
|
'patients',
|
||||||
|
'visits',
|
||||||
|
'appointments',
|
||||||
|
'home_visits',
|
||||||
|
'queue',
|
||||||
|
'laboratory',
|
||||||
|
'xray',
|
||||||
|
'pharmacy',
|
||||||
|
'billing',
|
||||||
|
'insurance',
|
||||||
|
'doctors',
|
||||||
|
'reports',
|
||||||
|
'settings',
|
||||||
|
'users'
|
||||||
|
];
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="fw-bold text-dark mb-0"><?php echo __('roles_permissions'); ?></h2>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addRoleModal">
|
||||||
|
<i class="bi bi-plus-lg"></i> <?php echo __('add_role'); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th><?php echo __('role'); ?></th>
|
||||||
|
<th><?php echo __('permissions'); ?></th>
|
||||||
|
<th><?php echo __('actions'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($roles as $role): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo $role['id']; ?></td>
|
||||||
|
<td><span class="badge bg-primary fs-6"><?php echo htmlspecialchars($role['name']); ?></span></td>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
$role_perms = json_decode($role['permissions'], true) ?? [];
|
||||||
|
if (in_array('*', $role_perms)) {
|
||||||
|
echo '<span class="badge bg-success">All Access</span>';
|
||||||
|
} else {
|
||||||
|
$count = count($role_perms);
|
||||||
|
echo $count > 0 ? $count . ' modules' : 'None';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if ($role['slug'] !== 'admin'): ?>
|
||||||
|
<button class="btn btn-sm btn-outline-primary" onclick='editPermissions(<?php echo json_encode($role); ?>)'>
|
||||||
|
<i class="bi bi-shield-lock"></i> <?php echo __('permissions'); ?>
|
||||||
|
</button>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted"><i class="bi bi-lock-fill"></i> Full Access</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Role Modal -->
|
||||||
|
<div class="modal fade" id="addRoleModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
<input type="hidden" name="action" value="add_role">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><?php echo __('add_role'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="roleName" class="form-label"><?php echo __('role_name'); ?></label>
|
||||||
|
<input type="text" class="form-control" id="roleName" name="name" required placeholder="e.g. Senior Nurse">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h6 class="mt-4 mb-3"><?php echo __('permissions'); ?></h6>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-3">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="selectAllAdd">
|
||||||
|
<label class="form-check-label fw-bold" for="selectAllAdd"><?php echo __('select_all'); ?></label>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<?php foreach ($available_permissions as $perm): ?>
|
||||||
|
<div class="col-md-4 mb-2">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input perm-check-add" type="checkbox" name="permissions[]" value="<?php echo $perm; ?>" id="add_perm_<?php echo $perm; ?>">
|
||||||
|
<label class="form-check-label" for="add_perm_<?php echo $perm; ?>">
|
||||||
|
<?php echo __('permission_' . $perm); ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?php echo __('save'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Permissions Modal -->
|
||||||
|
<div class="modal fade" id="permissionsModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
<input type="hidden" name="action" value="update_permissions">
|
||||||
|
<input type="hidden" name="role_id" id="permRoleId">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><?php echo __('edit_user'); ?>: <span id="permRoleName"></span></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-3">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="selectAll">
|
||||||
|
<label class="form-check-label fw-bold" for="selectAll"><?php echo __('select_all'); ?></label>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<?php foreach ($available_permissions as $perm): ?>
|
||||||
|
<div class="col-md-4 mb-2">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input perm-check" type="checkbox" name="permissions[]" value="<?php echo $perm; ?>" id="perm_<?php echo $perm; ?>">
|
||||||
|
<label class="form-check-label" for="perm_<?php echo $perm; ?>">
|
||||||
|
<?php echo __('permission_' . $perm); ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?php echo __('save_permissions'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function editPermissions(role) {
|
||||||
|
document.getElementById('permRoleId').value = role.id;
|
||||||
|
document.getElementById('permRoleName').innerText = role.name;
|
||||||
|
|
||||||
|
// Reset all checkboxes
|
||||||
|
document.querySelectorAll('.perm-check').forEach(c => c.checked = false);
|
||||||
|
|
||||||
|
// Check active permissions
|
||||||
|
let perms = [];
|
||||||
|
try {
|
||||||
|
perms = JSON.parse(role.permissions);
|
||||||
|
} catch(e) {
|
||||||
|
perms = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (perms && Array.isArray(perms)) {
|
||||||
|
perms.forEach(p => {
|
||||||
|
let checkbox = document.getElementById('perm_' + p);
|
||||||
|
if (checkbox) checkbox.checked = true;
|
||||||
|
});
|
||||||
|
} else if (perms === '*') {
|
||||||
|
// Handle wildcard if needed, though mostly admin specific
|
||||||
|
}
|
||||||
|
|
||||||
|
new bootstrap.Modal(document.getElementById('permissionsModal')).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('selectAll').addEventListener('change', function() {
|
||||||
|
let checked = this.checked;
|
||||||
|
document.querySelectorAll('.perm-check').forEach(c => c.checked = checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('selectAllAdd').addEventListener('change', function() {
|
||||||
|
let checked = this.checked;
|
||||||
|
document.querySelectorAll('.perm-check-add').forEach(c => c.checked = checked);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
246
includes/pages/users.php
Normal file
246
includes/pages/users.php
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
<?php
|
||||||
|
// Handle Actions
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['action'])) {
|
||||||
|
try {
|
||||||
|
if ($_POST['action'] === 'add_user') {
|
||||||
|
$name = $_POST['name'];
|
||||||
|
$email = $_POST['email'];
|
||||||
|
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
|
||||||
|
$role_id = $_POST['role_id'];
|
||||||
|
$active = isset($_POST['active']) ? 1 : 0;
|
||||||
|
|
||||||
|
$stmt = $db->prepare("INSERT INTO users (name, email, password, role_id, active) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$name, $email, $password, $role_id, $active]);
|
||||||
|
$_SESSION['flash_message'] = __('user_created');
|
||||||
|
} elseif ($_POST['action'] === 'edit_user') {
|
||||||
|
$id = $_POST['id'];
|
||||||
|
$name = $_POST['name'];
|
||||||
|
$email = $_POST['email'];
|
||||||
|
$role_id = $_POST['role_id'];
|
||||||
|
$active = isset($_POST['active']) ? 1 : 0;
|
||||||
|
|
||||||
|
$sql = "UPDATE users SET name = ?, email = ?, role_id = ?, active = ? WHERE id = ?";
|
||||||
|
$params = [$name, $email, $role_id, $active, $id];
|
||||||
|
|
||||||
|
if (!empty($_POST['password'])) {
|
||||||
|
$sql = "UPDATE users SET name = ?, email = ?, role_id = ?, active = ?, password = ? WHERE id = ?";
|
||||||
|
$params = [$name, $email, $role_id, $active, password_hash($_POST['password'], PASSWORD_DEFAULT), $id];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $db->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$_SESSION['flash_message'] = __('user_updated');
|
||||||
|
} elseif ($_POST['action'] === 'delete_user') {
|
||||||
|
$id = $_POST['id'];
|
||||||
|
// Prevent deleting self
|
||||||
|
if ($id == $_SESSION['user_id']) {
|
||||||
|
throw new Exception("You cannot delete yourself.");
|
||||||
|
}
|
||||||
|
$stmt = $db->prepare("DELETE FROM users WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$_SESSION['flash_message'] = __('user_deleted');
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$_SESSION['flash_message'] = "Error: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
header("Location: users.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch Users
|
||||||
|
$stmt = $db->query("SELECT u.*, r.name as role_name FROM users u JOIN roles r ON u.role_id = r.id ORDER BY u.id DESC");
|
||||||
|
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Fetch Roles
|
||||||
|
$stmt = $db->query("SELECT * FROM roles ORDER BY name ASC");
|
||||||
|
$roles = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="fw-bold text-dark mb-0"><?php echo __('users_management'); ?></h2>
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addUserModal">
|
||||||
|
<i class="bi bi-plus-lg me-1"></i> <?php echo __('add_user'); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th><?php echo __('name'); ?></th>
|
||||||
|
<th><?php echo __('email'); ?></th>
|
||||||
|
<th><?php echo __('role'); ?></th>
|
||||||
|
<th><?php echo __('status'); ?></th>
|
||||||
|
<th><?php echo __('created_at'); ?></th>
|
||||||
|
<th><?php echo __('actions'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($users as $user): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo $user['id']; ?></td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<img src="https://ui-avatars.com/api/?name=<?php echo urlencode($user['name']); ?>&background=random&color=fff&size=32" class="rounded-circle me-2" width="32" height="32">
|
||||||
|
<span class="fw-medium"><?php echo htmlspecialchars($user['name']); ?></span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td><?php echo htmlspecialchars($user['email']); ?></td>
|
||||||
|
<td><span class="badge bg-secondary"><?php echo htmlspecialchars($user['role_name']); ?></span></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($user['active']): ?>
|
||||||
|
<span class="badge bg-success"><?php echo __('active'); ?></span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-danger"><?php echo __('inactive'); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?php echo date('Y-m-d', strtotime($user['created_at'])); ?></td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-outline-primary me-1" onclick="editUser(<?php echo htmlspecialchars(json_encode($user)); ?>)">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<?php if ($user['id'] != $_SESSION['user_id']): ?>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteUser(<?php echo $user['id']; ?>)">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add User Modal -->
|
||||||
|
<div class="modal fade" id="addUserModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
<input type="hidden" name="action" value="add_user">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><?php echo __('add_user'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('name'); ?></label>
|
||||||
|
<input type="text" name="name" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('email'); ?></label>
|
||||||
|
<input type="email" name="email" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('password'); ?></label>
|
||||||
|
<input type="password" name="password" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('role'); ?></label>
|
||||||
|
<select name="role_id" class="form-select" required>
|
||||||
|
<?php foreach ($roles as $role): ?>
|
||||||
|
<option value="<?php echo $role['id']; ?>"><?php echo htmlspecialchars($role['name']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" name="active" class="form-check-input" id="addActive" checked>
|
||||||
|
<label class="form-check-label" for="addActive"><?php echo __('active'); ?></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?php echo __('save'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit User Modal -->
|
||||||
|
<div class="modal fade" id="editUserModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
<input type="hidden" name="action" value="edit_user">
|
||||||
|
<input type="hidden" name="id" id="editUserId">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><?php echo __('edit_user'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('name'); ?></label>
|
||||||
|
<input type="text" name="name" id="editUserName" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('email'); ?></label>
|
||||||
|
<input type="email" name="email" id="editUserEmail" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('password'); ?> <small class="text-muted">(Leave blank to keep current)</small></label>
|
||||||
|
<input type="password" name="password" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label"><?php echo __('role'); ?></label>
|
||||||
|
<select name="role_id" id="editUserRole" class="form-select" required>
|
||||||
|
<?php foreach ($roles as $role): ?>
|
||||||
|
<option value="<?php echo $role['id']; ?>"><?php echo htmlspecialchars($role['name']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" name="active" class="form-check-input" id="editUserActive">
|
||||||
|
<label class="form-check-label" for="editUserActive"><?php echo __('active'); ?></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?php echo __('save'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Confirmation Modal -->
|
||||||
|
<div class="modal fade" id="deleteUserModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST" class="modal-content">
|
||||||
|
<input type="hidden" name="action" value="delete_user">
|
||||||
|
<input type="hidden" name="id" id="deleteUserId">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><?php echo __('delete_user'); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p><?php echo __('confirm_delete_user'); ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
||||||
|
<button type="submit" class="btn btn-danger"><?php echo __('delete'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function editUser(user) {
|
||||||
|
document.getElementById('editUserId').value = user.id;
|
||||||
|
document.getElementById('editUserName').value = user.name;
|
||||||
|
document.getElementById('editUserEmail').value = user.email;
|
||||||
|
document.getElementById('editUserRole').value = user.role_id;
|
||||||
|
document.getElementById('editUserActive').checked = user.active == 1;
|
||||||
|
|
||||||
|
new bootstrap.Modal(document.getElementById('editUserModal')).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteUser(id) {
|
||||||
|
document.getElementById('deleteUserId').value = id;
|
||||||
|
new bootstrap.Modal(document.getElementById('deleteUserModal')).show();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
25
inventory_categories.php
Normal file
25
inventory_categories.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
$section = 'inventory_categories';
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/helpers.php';
|
||||||
|
require_once __DIR__ . '/includes/auth.php';
|
||||||
|
|
||||||
|
check_auth();
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$lang = $_SESSION['lang'];
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/actions.php';
|
||||||
|
require_once __DIR__ . '/includes/common_data.php';
|
||||||
|
|
||||||
|
$is_ajax = isset($_GET['ajax_search']) || isset($_POST['ajax_search']);
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/header.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/pages/inventory_categories.php';
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/footer.php';
|
||||||
|
}
|
||||||
25
inventory_dashboard.php
Normal file
25
inventory_dashboard.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
$section = 'inventory_dashboard';
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/helpers.php';
|
||||||
|
require_once __DIR__ . '/includes/auth.php';
|
||||||
|
|
||||||
|
check_auth();
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$lang = $_SESSION['lang'];
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/actions.php';
|
||||||
|
require_once __DIR__ . '/includes/common_data.php';
|
||||||
|
|
||||||
|
$is_ajax = isset($_GET['ajax_search']) || isset($_POST['ajax_search']);
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/header.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/pages/inventory_dashboard.php';
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/footer.php';
|
||||||
|
}
|
||||||
25
inventory_items.php
Normal file
25
inventory_items.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
$section = 'inventory_items';
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/helpers.php';
|
||||||
|
require_once __DIR__ . '/includes/auth.php';
|
||||||
|
|
||||||
|
check_auth();
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$lang = $_SESSION['lang'];
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/actions.php';
|
||||||
|
require_once __DIR__ . '/includes/common_data.php';
|
||||||
|
|
||||||
|
$is_ajax = isset($_GET['ajax_search']) || isset($_POST['ajax_search']);
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/header.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/pages/inventory_items.php';
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/footer.php';
|
||||||
|
}
|
||||||
25
inventory_reports.php
Normal file
25
inventory_reports.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
$section = 'inventory_reports';
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/helpers.php';
|
||||||
|
require_once __DIR__ . '/includes/auth.php';
|
||||||
|
|
||||||
|
check_auth();
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$lang = $_SESSION['lang'];
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/actions.php';
|
||||||
|
require_once __DIR__ . '/includes/common_data.php';
|
||||||
|
|
||||||
|
$is_ajax = isset($_GET['ajax_search']) || isset($_POST['ajax_search']);
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/header.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/pages/inventory_reports.php';
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/footer.php';
|
||||||
|
}
|
||||||
25
inventory_transactions.php
Normal file
25
inventory_transactions.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
$section = 'inventory_transactions';
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/helpers.php';
|
||||||
|
require_once __DIR__ . '/includes/auth.php';
|
||||||
|
|
||||||
|
check_auth();
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$lang = $_SESSION['lang'];
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/actions.php';
|
||||||
|
require_once __DIR__ . '/includes/common_data.php';
|
||||||
|
|
||||||
|
$is_ajax = isset($_GET['ajax_search']) || isset($_POST['ajax_search']);
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/header.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/pages/inventory_transactions.php';
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/footer.php';
|
||||||
|
}
|
||||||
27
roles.php
Normal file
27
roles.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
$section = 'roles';
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/helpers.php';
|
||||||
|
require_once __DIR__ . '/includes/auth.php';
|
||||||
|
|
||||||
|
// Check permissions
|
||||||
|
check_auth();
|
||||||
|
require_role('admin');
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$lang = $_SESSION['lang'];
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/actions.php';
|
||||||
|
require_once __DIR__ . '/includes/common_data.php';
|
||||||
|
|
||||||
|
$is_ajax = isset($_GET['ajax_search']) || isset($_POST['ajax_search']);
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/header.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/pages/roles.php';
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/footer.php';
|
||||||
|
}
|
||||||
27
users.php
Normal file
27
users.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
$section = 'users';
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/helpers.php';
|
||||||
|
require_once __DIR__ . '/includes/auth.php';
|
||||||
|
|
||||||
|
// Check permissions
|
||||||
|
check_auth();
|
||||||
|
require_role('admin');
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$lang = $_SESSION['lang'];
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/actions.php';
|
||||||
|
require_once __DIR__ . '/includes/common_data.php';
|
||||||
|
|
||||||
|
$is_ajax = isset($_GET['ajax_search']) || isset($_POST['ajax_search']);
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/header.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/pages/users.php';
|
||||||
|
|
||||||
|
if (!$is_ajax) {
|
||||||
|
require_once __DIR__ . '/includes/layout/footer.php';
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user