From 76b9b2a2cba51123c112f24f90f3b38c75fe5bf7 Mon Sep 17 00:00:00 2001 From: Thanakorn S Date: Sat, 25 Apr 2026 16:02:55 +0700 Subject: [PATCH] reports --- app/assets/utils/classes/Reports.php | 244 ++++-- app/assets/utils/classes/WarehouseManager.php | 9 +- .../api/engine_report/lot_stock_log.php | 60 ++ .../api/engine_report/product_lot.php | 61 ++ app/dashboard/api/engine_report/rack_log.php | 38 + .../api/engine_report/rack_occupancy.php | 45 ++ .../api/engine_report/reports_stats.php | 24 + app/dashboard/index.php | 727 ++++++++---------- app/dashboard/occupy_rack.php | 525 +++++++++++++ app/dashboard/product_lot.php | 364 +++++++++ app/dashboard/reports.php | 186 ----- app/include_sidebar.php | 11 +- 12 files changed, 1625 insertions(+), 669 deletions(-) create mode 100644 app/dashboard/api/engine_report/lot_stock_log.php create mode 100644 app/dashboard/api/engine_report/product_lot.php create mode 100644 app/dashboard/api/engine_report/rack_log.php create mode 100644 app/dashboard/api/engine_report/rack_occupancy.php create mode 100644 app/dashboard/api/engine_report/reports_stats.php create mode 100644 app/dashboard/occupy_rack.php create mode 100644 app/dashboard/product_lot.php delete mode 100644 app/dashboard/reports.php diff --git a/app/assets/utils/classes/Reports.php b/app/assets/utils/classes/Reports.php index 2a70a2f..b7964d8 100644 --- a/app/assets/utils/classes/Reports.php +++ b/app/assets/utils/classes/Reports.php @@ -44,10 +44,7 @@ class Reports } /** - * Master Data Summary: - * - Total Categories: Count of all categories in md_product_category for the company. - * - Active Categories: Count of categories where status = 1. - * - Inactive Categories: Count of categories where status = 0. + * Master Data Summary */ public function getTotalCategory(): int { @@ -93,10 +90,6 @@ class Reports return (int) $this->fetchScalar($sql); } - /** - * Total capacity = total count of active racks across all warehouses. - * One rack = one storage position (holds one product unit). - */ public function getTotalCapacity(): int { $sql = "SELECT COUNT(*) @@ -105,9 +98,6 @@ class Reports return (int) $this->fetchScalar($sql); } - /** - * Space Used = count of racks currently occupied (product_sku IS NOT NULL). - */ public function getSpaceUsed(): int { $sql = "SELECT COUNT(*) @@ -117,10 +107,6 @@ class Reports return (int) $this->fetchScalar($sql); } - /** - * Product Count = total count of distinct products in the md_product table for the company. - * Low Stock Count = count of products where current stock balance (total_in - total_out) is less than the min_stock defined in md_product. - */ public function getTotalProduct(): int { $sql = "SELECT COUNT(*) @@ -130,7 +116,7 @@ class Reports } public function getTotalProductInStock(): int -{ + { $sql = "SELECT COUNT(DISTINCT product_sku) FROM warehouse_balance WHERE company_id = :company_id @@ -167,17 +153,6 @@ class Reports return $this->fetchAll($sql); } - /** - * Low Stock Items - * - * Returns per-warehouse rows where balance has fallen to/below reorder_point. - * - * Status tiers: - * critical — balance <= min_stock (safety floor breached) - * warning — balance <= reorder_point (reorder trigger hit, still above min) - * - * Products with reorder_point = 0 are skipped (no threshold defined). - */ public function getLowStockItems(): array { $sql = "SELECT @@ -210,9 +185,6 @@ class Reports foreach ($rows as $row) { $balance = (float) $row['balance']; $min_stock = (float) $row['min_stock']; - - $is_critical = ($balance <= $min_stock); - $items[] = [ 'warehouse_id' => (int) $row['warehouse_id'], 'warehouse_name' => $row['warehouse_name'], @@ -222,25 +194,18 @@ class Reports 'balance' => max(0, round($balance, 2)), 'min_stock' => (float) $row['min_stock'], 'reorder_point' => (float) $row['reorder_point'], - 'status' => $is_critical ? 'critical' : 'warning', + 'status' => ($balance <= $min_stock) ? 'critical' : 'warning', ]; } - return $items; } - /** - * Count of low-stock items at CRITICAL level (balance <= min_stock). - */ public function getCriticalStockCount(): int { $items = $this->getLowStockItems(); return count(array_filter($items, fn($i) => $i['status'] === 'critical')); } - /** - * Count of low-stock items at WARNING level (balance <= reorder_point, > min_stock). - */ public function getWarningStockCount(): int { $items = $this->getLowStockItems(); @@ -282,6 +247,180 @@ class Reports return (int) $this->fetchScalar($sql); } + // DASHBOARD REPORTS (cross-warehouse, month-filtered via warehouse_balance) + + public function getDashboardStats(string $month): array + { + $sth = $this->pdo->prepare( + "SELECT + ROUND(COALESCE(SUM(total_in), 0), 2) AS total_in, + ROUND(COALESCE(SUM(total_out), 0), 2) AS total_out, + COUNT(DISTINCT product_sku) AS active_products + FROM warehouse_balance + WHERE company_id = :company_id + AND month = :month" + ); + $sth->execute([':company_id' => $this->companyId, ':month' => $month]); + return $sth->fetch(PDO::FETCH_ASSOC) ?: [ + 'total_in' => 0, 'total_out' => 0, 'active_products' => 0 + ]; + } + + public function getDashboardLowStockCount(): int + { + $sth = $this->pdo->prepare( + "SELECT wb.product_sku, + ROUND(SUM(wb.total_in) - SUM(wb.total_out), 2) AS balance, + mp.min_stock + FROM warehouse_balance wb + INNER JOIN md_product mp + ON mp.company_id = wb.company_id + AND mp.sku = wb.product_sku + WHERE wb.company_id = :company_id + GROUP BY wb.product_sku, mp.min_stock" + ); + $sth->execute([':company_id' => $this->companyId]); + $rows = $sth->fetchAll(PDO::FETCH_ASSOC); + $count = 0; + foreach ($rows as $row) { + if ((float)$row['balance'] < (float)$row['min_stock']) $count++; + } + return $count; + } + + public function getStockMovementChart(int $months = 12): array + { + $now = new DateTime(); + $start = (clone $now)->modify("-" . ($months - 1) . " months")->format('Y-m'); + $end = $now->format('Y-m'); + + $sth = $this->pdo->prepare( + "SELECT + month, + ROUND(SUM(total_in), 2) AS stock_in, + ROUND(SUM(total_out), 2) AS stock_out + FROM warehouse_balance + WHERE company_id = :company_id + AND month BETWEEN :start AND :end + GROUP BY month + ORDER BY month ASC" + ); + $sth->execute([ + ':company_id' => $this->companyId, + ':start' => $start, + ':end' => $end, + ]); + $rows = $sth->fetchAll(PDO::FETCH_ASSOC); + + $dataByMonth = []; + foreach ($rows as $row) { + $dataByMonth[$row['month']] = $row; + } + + $labels = []; $stock_in = []; $stock_out = []; + for ($i = $months - 1; $i >= 0; $i--) { + $key = (clone $now)->modify("-{$i} months")->format('Y-m'); + $labels[] = (clone $now)->modify("-{$i} months")->format('M Y'); + $stock_in[] = isset($dataByMonth[$key]) ? (float)$dataByMonth[$key]['stock_in'] : 0; + $stock_out[] = isset($dataByMonth[$key]) ? (float)$dataByMonth[$key]['stock_out'] : 0; + } + + return ['labels' => $labels, 'stock_in' => $stock_in, 'stock_out' => $stock_out]; + } + + public function getMostMovedProducts(string $month, int $limit = 10): array + { + $limit = (int) $limit; + $sth = $this->pdo->prepare( + "SELECT + wb.product_sku, + ROUND(SUM(wb.total_in), 2) AS total_in, + ROUND(SUM(wb.total_out), 2) AS total_out, + p.product_name, + pc.category AS category_name + FROM warehouse_balance wb + INNER JOIN md_product p + ON p.company_id = wb.company_id + AND p.sku = wb.product_sku + LEFT JOIN md_product_category pc + ON pc.company_id = wb.company_id + AND pc.id = p.category + WHERE wb.company_id = :company_id + AND wb.month = :month + GROUP BY wb.product_sku, p.product_name, pc.category + ORDER BY total_out DESC + LIMIT {$limit}" + ); + $sth->execute([ + ':company_id' => $this->companyId, + ':month' => $month, + ]); + return $sth->fetchAll(PDO::FETCH_ASSOC); + } + + public function getAvailableMonths(): array + { + $sth = $this->pdo->prepare( + "SELECT DISTINCT month + FROM warehouse_balance + WHERE company_id = :company_id + ORDER BY month DESC" + ); + $sth->execute([':company_id' => $this->companyId]); + return $sth->fetchAll(PDO::FETCH_COLUMN); + } + + /** + * Recent stock activity across all warehouses — last N transactions. + */ + public function getRecentActivity(int $limit = 10): array + { + $sth = $this->pdo->prepare( + "SELECT id, warehouse_name FROM md_warehouse + WHERE company_id = :company_id AND status = 1" + ); + $sth->execute([':company_id' => $this->companyId]); + $warehouses = $sth->fetchAll(PDO::FETCH_ASSOC); + + if (empty($warehouses)) return []; + + $unions = implode("\nUNION ALL\n", array_map( + fn($wh) => "SELECT s.date, s.product_sku, s.type, + ROUND(COALESCE(s.`in`, 0), 2) AS stock_in, + ROUND(COALESCE(s.`out`, 0), 2) AS stock_out, + p.product_name, + '{$wh['warehouse_name']}' AS warehouse_name + FROM `td_stock_{$wh['warehouse_name']}` s + LEFT JOIN md_product p + ON p.company_id = s.company_id + AND p.sku = s.product_sku + WHERE s.company_id = {$this->companyId}", + $warehouses + )); + + $limit = (int) $limit; + $sth = $this->pdo->query( + "SELECT * FROM ({$unions}) AS all_stock + ORDER BY date DESC + LIMIT {$limit}" + ); + + $items = []; + foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) { + $is_in = (float)$row['stock_in'] > 0; + $items[] = [ + 'product_name' => $row['product_name'] ?: $row['product_sku'], + 'product_sku' => $row['product_sku'], + 'warehouse_name' => $row['warehouse_name'], + 'direction' => $is_in ? 'in' : 'out', + 'qty' => $is_in ? (float)$row['stock_in'] : (float)$row['stock_out'], + 'type' => $row['type'], + 'date' => $row['date'], + ]; + } + return $items; + } + // WAREHOUSE OVERVIEW public function getWarehouseCapacity(int $warehouse_id): int { @@ -386,7 +525,6 @@ class Reports $start = (clone $now)->modify('-12 months')->format('Y-m-d 00:00:00'); $end = $now->format('Y-m-d 23:59:59'); - // Prior balance before the 12-month window $sth = $this->pdo->query( "SELECT ROUND(COALESCE(SUM(`in`) - SUM(`out`), 0), 2) AS balance FROM `{$table}` @@ -461,9 +599,6 @@ class Reports // EXPIRED / NEAR EXPIRY STOCK public function getExpiredStock(int $warehouse_id = 0): array { - // Step 1: get warehouses from md_warehouse — fast, indexed, company-scoped - // avoids hitting information_schema on every request - // if warehouse_id is passed, restrict to that warehouse only $where_wh = $warehouse_id > 0 ? "AND id = {$warehouse_id}" : ''; $sth = $this->pdo->prepare( "SELECT id, warehouse_name @@ -477,8 +612,6 @@ class Reports if (empty($warehouses)) return []; - // Step 2: build UNION only across known warehouse tables - // warehouse_id (integer) carried through for a fast indexed JOIN later $unions = implode("\nUNION ALL\n", array_map( fn($wh) => "SELECT s.id, @@ -496,8 +629,6 @@ class Reports $warehouses )); - // Step 3: join md_lot by (company_id, product_sku, lot_number) - // join md_warehouse by integer warehouse_id — no string concat, index-friendly $sql = "SELECT stock.id, stock.product_sku, @@ -509,7 +640,7 @@ class Reports l.expiry_date, DATEDIFF(l.expiry_date, CURDATE()) AS days_remaining, p.product_name, - pc.category as category_name, + pc.category AS category_name, mw.warehouse_name FROM ($unions) AS stock INNER JOIN md_lot l @@ -537,17 +668,11 @@ class Reports foreach ($rows as $row) { $days = (int) $row['days_remaining']; - if ($days < 0) { - $status = 'expired'; - } elseif ($days <= 7) { - $status = 'critical'; - } elseif ($days <= 14) { - $status = 'warning'; - } elseif ($days <= 30) { - $status = 'caution'; - } else { - continue; // beyond 30 days — not relevant for this report - } + if ($days < 0) { $status = 'expired'; } + elseif ($days <= 7) { $status = 'critical'; } + elseif ($days <= 14) { $status = 'warning'; } + elseif ($days <= 30) { $status = 'caution'; } + else { continue; } $items[] = [ 'id' => (int) $row['id'], @@ -571,7 +696,4 @@ class Reports } - - - ?> \ No newline at end of file diff --git a/app/assets/utils/classes/WarehouseManager.php b/app/assets/utils/classes/WarehouseManager.php index e39cebd..da640f4 100644 --- a/app/assets/utils/classes/WarehouseManager.php +++ b/app/assets/utils/classes/WarehouseManager.php @@ -23,7 +23,7 @@ class WarehouseManager { // ───────────────────────────────────────────────────────────── // Private helpers // ───────────────────────────────────────────────────────────── - + /** * Resolve the td_stock_ table name for a given warehouse_id. * Returns null if warehouse not found or inactive. @@ -145,13 +145,15 @@ class WarehouseManager { public function adjustBalance($type, $warehouse_id, $product_sku, $old_qty, $new_qty) { // Upsert the balance row (atomic, no race condition) + // Partitioned by month (YYYY-MM) for efficient monthly reporting $column = $type === 'in' ? 'total_in' : 'total_out'; $delta = $new_qty - $old_qty; + $month = date('Y-m'); $sql = "INSERT INTO warehouse_balance - (company_id, warehouse_id, product_sku, `$column`) + (company_id, warehouse_id, product_sku, month, `$column`) VALUES - (:company_id, :warehouse_id, :product_sku, :delta) + (:company_id, :warehouse_id, :product_sku, :month, :delta) ON DUPLICATE KEY UPDATE `$column` = `$column` + VALUES(`$column`)"; @@ -160,6 +162,7 @@ class WarehouseManager { ":company_id" => $this->company_id, ":warehouse_id" => $warehouse_id, ":product_sku" => $product_sku, + ":month" => $month, ":delta" => $delta ]); } diff --git a/app/dashboard/api/engine_report/lot_stock_log.php b/app/dashboard/api/engine_report/lot_stock_log.php new file mode 100644 index 0000000..5c6d746 --- /dev/null +++ b/app/dashboard/api/engine_report/lot_stock_log.php @@ -0,0 +1,60 @@ +prepare( + "SELECT id, warehouse_name FROM md_warehouse + WHERE company_id = :company_id AND status = 1" + ); + $sth->execute([':company_id' => $company_id]); + $warehouses = $sth->fetchAll(PDO::FETCH_ASSOC); + + $rows = []; + foreach ($warehouses as $wh) { + $safe = preg_replace('/[^a-zA-Z0-9_]/', '', $wh['warehouse_name']); + $table = "td_stock_{$safe}"; + + $sth = $pdo2->prepare( + "SELECT + s.id, + s.date, + s.type, + s.zone, + s.aisle, + s.rack, + ROUND(COALESCE(s.`in`, 0), 2) AS stock_in, + ROUND(COALESCE(s.`out`, 0), 2) AS stock_out, + s.serial_number, + s.description, + '{$wh['warehouse_name']}' AS warehouse_name + FROM `{$table}` s + WHERE s.company_id = :company_id + AND s.product_sku = :product_sku + AND s.lot_number = :lot_number + ORDER BY s.date DESC" + ); + $sth->execute([ + ':company_id' => $company_id, + ':product_sku' => $product_sku, + ':lot_number' => $lot_number, + ]); + $rows = array_merge($rows, $sth->fetchAll(PDO::FETCH_ASSOC)); + } + + // Sort all rows by date DESC + usort($rows, fn($a, $b) => strcmp($b['date'], $a['date'])); + + $answer['output'] = $rows; + $answer['success'] = 1; + exit(json_encode($answer)); +?> \ No newline at end of file diff --git a/app/dashboard/api/engine_report/product_lot.php b/app/dashboard/api/engine_report/product_lot.php new file mode 100644 index 0000000..b6eab06 --- /dev/null +++ b/app/dashboard/api/engine_report/product_lot.php @@ -0,0 +1,61 @@ +prepare( + "SELECT + l.id, + l.product_sku, + l.lot_number, + l.expiry_date, + l.description, + p.product_name, + DATEDIFF(l.expiry_date, CURDATE()) AS days_remaining, + COALESCE( + (SELECT ROUND(SUM(wb.total_in) - SUM(wb.total_out), 2) + FROM warehouse_balance wb + WHERE wb.company_id = l.company_id + AND wb.product_sku = l.product_sku), + 0 + ) AS balance + FROM md_lot l + INNER JOIN md_product p + ON p.company_id = l.company_id + AND p.sku = l.product_sku + WHERE l.company_id = :company_id + ORDER BY l.product_sku, l.expiry_date ASC" + ); + $sth->execute([':company_id' => $company_id]); + db_check($sth, $answer); + + $rows = $sth->fetchAll(PDO::FETCH_ASSOC); + $total = count($rows); + $active = 0; + $expired = 0; + $near = 0; + + foreach ($rows as &$row) { + $days = (int) $row['days_remaining']; + $balance = (float) $row['balance']; + + if ($balance > 0) $active++; + if ($days < 0) $expired++; + if ($days >= 0 && $days <= 30) $near++; + + // Status + if ($days < 0) $row['status'] = 'expired'; + elseif ($days <= 7) $row['status'] = 'critical'; + elseif ($days <= 30) $row['status'] = 'near'; + else $row['status'] = 'ok'; + } + unset($row); + + $answer['output'] = $rows; + $answer['total'] = $total; + $answer['active'] = $active; + $answer['expired'] = $expired; + $answer['near'] = $near; + $answer['success'] = 1; + exit(json_encode($answer)); +?> \ No newline at end of file diff --git a/app/dashboard/api/engine_report/rack_log.php b/app/dashboard/api/engine_report/rack_log.php new file mode 100644 index 0000000..8a48be6 --- /dev/null +++ b/app/dashboard/api/engine_report/rack_log.php @@ -0,0 +1,38 @@ +prepare($sql); + $sth->execute([ + ':company_id' => $company_id, + ':rack_id' => $rack_id, + ]); + db_check($sth, $answer); + + $answer['output'] = $sth->fetchAll(PDO::FETCH_ASSOC); + $answer['success'] = 1; + exit(json_encode($answer)); +?> \ No newline at end of file diff --git a/app/dashboard/api/engine_report/rack_occupancy.php b/app/dashboard/api/engine_report/rack_occupancy.php new file mode 100644 index 0000000..892d96b --- /dev/null +++ b/app/dashboard/api/engine_report/rack_occupancy.php @@ -0,0 +1,45 @@ +getTotalCapacity(); + $occupied = $report->getSpaceUsed(); + $empty = $total - $occupied; + + // All racks with warehouse name, product info and stock info + $sql = "SELECT + r.id, + r.zone, + r.aisle, + r.rack, + r.product_sku, + r.td_stock_id, + mw.warehouse_name, + mw.id AS warehouse_id, + p.product_name, + CASE WHEN r.product_sku IS NOT NULL THEN 'occupied' ELSE 'empty' END AS status + FROM md_rack r + INNER JOIN md_warehouse mw + ON mw.company_id = r.company_id + AND mw.id = r.warehouse + LEFT JOIN md_product p + ON p.company_id = r.company_id + AND p.sku = r.product_sku + WHERE r.company_id = :company_id + ORDER BY mw.warehouse_name, r.zone, CAST(r.aisle AS UNSIGNED), r.aisle, CAST(r.rack AS UNSIGNED), r.rack"; + + $sth = $pdo2->prepare($sql); + $sth->execute([':company_id' => $company_id]); + db_check($sth, $answer); + + $answer['output'] = $sth->fetchAll(PDO::FETCH_ASSOC); + $answer['total'] = $total; + $answer['occupied'] = $occupied; + $answer['empty'] = $empty; + $answer['success'] = 1; + exit(json_encode($answer)); +?> \ No newline at end of file diff --git a/app/dashboard/api/engine_report/reports_stats.php b/app/dashboard/api/engine_report/reports_stats.php new file mode 100644 index 0000000..125ebad --- /dev/null +++ b/app/dashboard/api/engine_report/reports_stats.php @@ -0,0 +1,24 @@ +getDashboardStats($month); + + $answer['output'] = [ + 'total_in' => $stats['total_in'], + 'total_out' => $stats['total_out'], + 'active_products' => $stats['active_products'], + 'low_stock' => $report->getDashboardLowStockCount(), + 'stock_movement' => $report->getStockMovementChart(12), + 'most_moved' => $report->getMostMovedProducts($month), + 'low_stock_items' => $report->getLowStockItems(), + 'recent_activity' => $report->getRecentActivity(10), + 'months' => $report->getAvailableMonths(), + ]; + $answer['success'] = 1; + exit(json_encode($answer)); +?> \ No newline at end of file diff --git a/app/dashboard/index.php b/app/dashboard/index.php index 92c0bc7..0d6728c 100644 --- a/app/dashboard/index.php +++ b/app/dashboard/index.php @@ -2,496 +2,389 @@ session_start(); require '../config.php'; require '../include_header.php'; - ?> -
-
+ + +
-
-

Dashboard

-

Your main content goes here…

+
+
+

Dashboard

+

Inventory overview across all warehouses

+
+
+ + +
+ +
-
-
-
+
+
+
- +
-

Total Sales

-

$25,000

-

+5% since last month

+

Total Stock In

+

—

+

Units received this month

+
+
+
- -
-
- -
- -
+
+
+
+
+ +
+
+

Total Stock Out

+

—

+

Units dispatched this month

+
+
+
+
+
+
+
+
+
+
- +
-

Total Purchase

-

$18,000

-

+22% since last month

+

Active Products

+

—

+

SKUs with movement

-
- - -
-
- -
- -
-
- -
-
-

Total Expenses

-

$9,000

-

+10% since last month

-
+
+
- -
-
- -
- -
+
+
+
- +
-

Invoice Due

-

$25,000

-

+35% since last month

+

Low Stock Alerts

+

—

+

Products below min stock

+
+
+
- -
-
+ +
-
-
-
-
-
-

$25,458

- Total Profit -
-
- -
-
-
-
+35% vs Last Month
- -
+
+
+
+

Stock Movement

+

Last 12 months across all warehouses

+
-
- -
-
-
-
-
-
-

$45,458

- Total Payment Returns -
-
- -
-
-
-
-20% vs Last Month
- -
-
-
- -
-
-
-
-
-
-

$34,458

- Total Expenses -
-
- -
-
-
-
-20% vs Last Month
- -
-
-
- -
- -
-
-
-
-
-

Sales vs Purchase

-
- -
-
-
- -
-
-
-
- - -
-
-
-

Overall Information

-
- -
-
-
-

Customers Overview

-
-
-
- -
-
-
-
-
-
-

5.5K

-

First Time

- 25% -
-
-
-
-

3.5K

-

Return

- 21% -
-
-
-
- - -
-
-
-

6987

- Suppliers -
-
-

4896

- Customers -
-
-

487

- Orders -
-
+
+
+ +
- -
-
+ +
+
-

Top Selling Products

- +

Most Moved Products

+
- -
    - - -
  • - -
    -

    Wireless Earphones

    -
    - $89 - • - 1,250 Units -
    -
    - 18% -
  • - - -
  • - -
    -

    Gaming Joy Stick

    -
    - $49 - • - 5,420 Units -
    - -
    - 32% -
  • - -
  • - -
    -

    Smart Watch Pro

    -
    - $98 - • - 862 Units -
    - -
    - 22% -
  • -
  • - -
    -

    USB-C Fast Charger

    -
    - $35 - • - 3,200 Units -
    - -
    - 28% -
  • -
  • - -
    -

    Portable Bluetooth Speaker

    -
    - $65 - • - 2,890 Units -
    - -
    - 25% -
  • +
      +
    • Loading...
    +
    +
    +
- -
-
+ +
+
-
- -

Low Stock Products

-
- View All +

Low Stock Products

+ View All
- -
    - -
  • - -
    -

    Wireless Headphones

    - ID: #554433 -
    -
    - 06 - In Stock -
    -
  • - -
  • - -
    -

    USB-C Cable Pack

    - ID: #887766 -
    -
    - 09 - In Stock -
    -
  • - -
  • - -
    -

    Phone Screen Protector

    - ID: #332211 -
    -
    - 03 - In Stock -
    -
  • -
  • - -
    -

    Portable Charger 20000mAh

    - ID: #998877 -
    -
    - 07 - In Stock -
    -
  • -
  • - -
    -

    Mechanical Keyboard RGB

    - ID: #665544 -
    -
    - 02 - In Stock -
    -
  • +
      +
    • Loading...
    +
    +
    +
- -
-
+ +
+
-

Recent Sales

- +

Recent Activity

+ All warehouses
- -
    - -
  • - -
    -

    MacBook Pro 16"

    -
    - Computers - • - 2,$2,499 -
    - -
    - Completed -
  • - -
  • - -
    -

    AirPods Pro Max

    -
    - Audio - • - $549 -
    - -
    - Processing -
  • - -
  • - -
    -

    iPad Air 11"

    -
    - Tablets - • - $799 -
    -
    - Completed -
  • - -
  • - -
    -

    Apple Watch Ultra

    -
    - Wearables - • - $799 -
    -
    - Pending -
  • - -
  • - -
    -

    Magic Keyboard

    -
    - Accessories - • - $299 -
    - -
    - Cancelled -
  • +
      +
    • Loading...
    +
    +
    +
+
- + + \ No newline at end of file diff --git a/app/dashboard/occupy_rack.php b/app/dashboard/occupy_rack.php new file mode 100644 index 0000000..0a7fd7b --- /dev/null +++ b/app/dashboard/occupy_rack.php @@ -0,0 +1,525 @@ + + + + + + + +
+
+ + +
+
+
+
+

Rack Occupancy

+

View occupied and available rack positions across all warehouses

+
+
+
+
+ + +
+
+
+
+
+
+ +
+
+ Total Racks +

—

+
+
+
+
+
+
+
+
+
+
+ +
+
+ Occupied +

—

+
+
+
+
+
+
+
+
+
+
+ +
+
+ Available +

—

+
+
+
+
+
+
+
+
+
+
+ +
+
+ Occupancy Rate +

—

+
+
+
+
+
+
+ + + + + +
+
+
+
+
+
+

All Racks

+

Complete rack list across all warehouses

+
+
+
+
+ + + + +
+
+
+ + + + + + + + + + + + + + + +
WarehouseZoneAisleRackSKUProductStatusAction
+
+
+
+
+
+ + +
+
+
+
+
+
+

Occupied Racks

+

Racks currently holding stock

+
+
+
+
+ + + + +
+
+
+ + + + + + + + + + + + + + +
WarehouseZoneAisleRackSKUProductAction
+
+
+
+
+
+ + +
+
+
+
+
+
+

Available Racks

+

Empty racks ready to receive stock

+
+
+
+
+ + + + +
+
+
+ + + + + + + + + + + + +
WarehouseZoneAisleRackAction
+
+
+
+
+
+ +
+ + + + +
+ + + + + + + + \ No newline at end of file diff --git a/app/dashboard/product_lot.php b/app/dashboard/product_lot.php new file mode 100644 index 0000000..3c2b8bb --- /dev/null +++ b/app/dashboard/product_lot.php @@ -0,0 +1,364 @@ + + + + + + + +
+
+ + +
+
+
+
+

Product Lots

+

Track lot numbers, expiry dates and stock transactions

+
+
+
+
+ + +
+
+
+
+
+ +
+
+ — + Total Lots + All registered lots +
+
+ + + +
+
+ +
+
+ — + Active Lots + Balance > 0 +
+
+ + + +
+
+ +
+
+ — + Expired Lots + Past expiry date +
+
+ + + +
+
+ +
+
+ — + Near Expiry + Within 30 days +
+
+ + + +
+
+ +
+
+
+
+
+ + +
+
+
+
+ +
+
+

Lot List

+

All lot numbers with expiry and stock balance

+
+
+ + +
+
+ + + + +
+
+ + +
+ + + + + + + + + + + + + + + +
ProductSKULot NumberExpiry DateDays RemainingBalanceStatusAction
+
+ +
+
+
+
+ + + + +
+
+ + + + + + + + \ No newline at end of file diff --git a/app/dashboard/reports.php b/app/dashboard/reports.php deleted file mode 100644 index d022cb9..0000000 --- a/app/dashboard/reports.php +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - -
-
-
-
-
-
-

Reports

-

View your inventory analytics and reports

-
- -
-
-
- -
- -
-
-
-
Total Revenue
-

$45,231

-

12% from last month

-
-
-
- -
-
-
-
Products Sold
-

1,234

-

8% from last month

-
-
-
- -
-
-
-
Low Stock Items
-

23

-

3% from last month

-
-
-
- -
-
-
-
Out of Stock
-

5

-

2% from last month

-
-
-
-
- -
- -
-
-
-
-
-

Sales Overview

- -
-
- - -
-
- - - -
- - - -
-
-
-
- -
- -
-
-
-
-
-

Top Products

- -
-
- - -
-
-
- Product A -
-
-
-
-
Gaming Joy Stick
- 156 units sold -
-
- $3,120 -
-
-
-
- -
-
- Product B -
-
-
-
-
Wireless Headphones
- 134 units sold -
-
- $2,680 -
-
-
-
- -
-
- Product C -
-
-
-
-
Smartwatch
- 98 units sold -
-
- $1,960 -
-
-
-
-
- -
-
-
-
- -
-
- - - - - - - - - - - \ No newline at end of file diff --git a/app/include_sidebar.php b/app/include_sidebar.php index 5d2e7c9..9c81471 100644 --- a/app/include_sidebar.php +++ b/app/include_sidebar.php @@ -27,9 +27,16 @@
  • - + - Reports + Occupied Racks + +
  • + +
  • + + + Product Lots