diff --git a/api/inventory.php b/api/inventory.php index 8e0f531..be45850 100644 --- a/api/inventory.php +++ b/api/inventory.php @@ -5,12 +5,20 @@ require_once __DIR__ . '/../includes/auth.php'; header('Content-Type: application/json'); -if (!is_logged_in()) { - http_response_code(403); +// Check if user is logged in +if (!isset($_SESSION['user_id'])) { + http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; } +// Check permission +if (!has_permission('inventory')) { + http_response_code(403); + echo json_encode(['error' => 'Forbidden: Stock Management permission required']); + exit; +} + $action = $_GET['action'] ?? ''; $db = db(); @@ -29,4 +37,4 @@ if ($action === 'get_batches') { exit; } -echo json_encode(['error' => 'Invalid action']); +echo json_encode(['error' => 'Invalid action']); \ No newline at end of file diff --git a/db/migrations/20260321_add_department_to_inventory_transactions.sql b/db/migrations/20260321_add_department_to_inventory_transactions.sql new file mode 100644 index 0000000..76d7bc6 --- /dev/null +++ b/db/migrations/20260321_add_department_to_inventory_transactions.sql @@ -0,0 +1,2 @@ +ALTER TABLE inventory_transactions ADD COLUMN IF NOT EXISTS department_id INT NULL; +ALTER TABLE inventory_transactions ADD CONSTRAINT fk_inventory_dept FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE SET NULL; diff --git a/includes/auth.php b/includes/auth.php index 2137e1a..1930cbc 100644 --- a/includes/auth.php +++ b/includes/auth.php @@ -77,3 +77,10 @@ function require_role($role_slug) { die("Access Denied: You do not have the required role."); } } + +function require_permission($permission) { + if (!has_permission($permission)) { + http_response_code(403); + die("Access Denied: You do not have the required permission: " . htmlspecialchars($permission)); + } +} \ No newline at end of file diff --git a/includes/pages/inventory_reports.php b/includes/pages/inventory_reports.php index d9e5c41..3433dc1 100644 --- a/includes/pages/inventory_reports.php +++ b/includes/pages/inventory_reports.php @@ -4,6 +4,7 @@ $report_type = $_GET['report'] ?? 'low_stock'; // Data Fetching based on report type $data = []; $columns = []; +$departments = $db->query("SELECT id, name_en FROM departments ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC); if ($report_type === 'low_stock') { $title = __('low_stock_report'); @@ -41,6 +42,56 @@ if ($report_type === 'low_stock') { GROUP BY i.id ORDER BY total_value DESC ")->fetchAll(PDO::FETCH_ASSOC); +} elseif ($report_type === 'consumption') { + $title = __('consumption_report'); + $columns = ['department', 'item_name', 'total_quantity', 'total_cost']; + + // Filters + $filter_department = $_GET['department_id'] ?? ''; + $filter_start = $_GET['start_date'] ?? ''; + $filter_end = $_GET['end_date'] ?? ''; + + $where = "WHERE t.transaction_type = 'out'"; + $params = []; + + if ($filter_department) { + $where .= " AND t.department_id = ?"; + $params[] = $filter_department; + } + if ($filter_start) { + $where .= " AND DATE(t.transaction_date) >= ?"; + $params[] = $filter_start; + } + if ($filter_end) { + $where .= " AND DATE(t.transaction_date) <= ?"; + $params[] = $filter_end; + } + + $sql = " + SELECT d.name_en as department, + i.name_en as item_name, + SUM(t.quantity) as total_quantity, + SUM(t.quantity * COALESCE(b.cost_price, 0)) as total_cost + FROM inventory_transactions t + JOIN departments d ON t.department_id = d.id + JOIN inventory_items i ON t.item_id = i.id + LEFT JOIN inventory_batches b ON t.batch_id = b.id + $where + GROUP BY d.id, i.id + ORDER BY d.name_en ASC, total_cost DESC + "; + + $stmt = $db->prepare($sql); + $stmt->execute($params); + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // Calculate totals + $grand_total_qty = 0; + $grand_total_cost = 0; + foreach ($data as $row) { + $grand_total_qty += $row['total_quantity']; + $grand_total_cost += $row['total_cost']; + } } ?> @@ -51,6 +102,7 @@ if ($report_type === 'low_stock') { + @@ -59,12 +111,47 @@ if ($report_type === 'low_stock') {
+ + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ +
- + @@ -81,7 +168,7 @@ if ($report_type === 'low_stock') { Out of Stock'; @@ -101,6 +188,15 @@ if ($report_type === 'low_stock') { + + + + + + + + +
:
@@ -108,4 +204,4 @@ if ($report_type === 'low_stock') {
- + \ No newline at end of file diff --git a/includes/pages/inventory_transactions.php b/includes/pages/inventory_transactions.php index c9548eb..f6e7c71 100644 --- a/includes/pages/inventory_transactions.php +++ b/includes/pages/inventory_transactions.php @@ -8,6 +8,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $notes = $_POST['notes'] ?? ''; $reference_type = $_POST['reference_type'] ?? 'manual_adjustment'; $reference_id = 0; // Or pass from somewhere + $department_id = !empty($_POST['department_id']) ? $_POST['department_id'] : null; if (!$item_id || $quantity <= 0) { $_SESSION['flash_message'] = '
' . __('fill_all_required_fields') . '
'; @@ -27,8 +28,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $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]); + $stmt = $db->prepare("INSERT INTO inventory_transactions (item_id, batch_id, transaction_type, quantity, reference_type, reference_id, user_id, notes, department_id) VALUES (?, ?, 'in', ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$item_id, $batch_id, $quantity, $reference_type, $reference_id, $_SESSION['user_id'], $notes, $department_id]); } elseif ($type === 'out') { // Deduct from Batch @@ -48,8 +49,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $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]); + $stmt = $db->prepare("INSERT INTO inventory_transactions (item_id, batch_id, transaction_type, quantity, reference_type, reference_id, user_id, notes, department_id) VALUES (?, ?, 'out', ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$item_id, $batch_id, $quantity, $reference_type, $reference_id, $_SESSION['user_id'], $notes, $department_id]); } $db->commit(); @@ -74,11 +75,12 @@ $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 + SELECT t.*, i.name_en as item_name, i.sku, b.batch_number, u.name as user_name, d.name_en as department_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 + LEFT JOIN departments d ON t.department_id = d.id ORDER BY t.transaction_date DESC LIMIT $per_page OFFSET $offset ")->fetchAll(PDO::FETCH_ASSOC); @@ -88,6 +90,9 @@ $items = $db->query("SELECT id, name_en, sku FROM inventory_items WHERE is_activ // Fetch Suppliers $suppliers = $db->query("SELECT id, name_en FROM suppliers ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC); + +// Fetch Departments +$departments = $db->query("SELECT id, name_en FROM departments ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC); ?>
@@ -108,6 +113,7 @@ $suppliers = $db->query("SELECT id, name_en FROM suppliers ORDER BY name_en ASC" + @@ -115,7 +121,7 @@ $suppliers = $db->query("SELECT id, name_en FROM suppliers ORDER BY name_en ASC" - + @@ -136,6 +142,7 @@ $suppliers = $db->query("SELECT id, name_en FROM suppliers ORDER BY name_en ASC" + @@ -222,12 +229,23 @@ $suppliers = $db->query("SELECT id, name_en FROM suppliers ORDER BY name_en ASC"