diff --git a/.gitignore b/.gitignore index d1e3a0c..377cf52 100755 --- a/.gitignore +++ b/.gitignore @@ -145,5 +145,5 @@ vite.config.ts.timestamp-* # custom files AGENTS.md CLAUDE.md -ROLES.md -SESSION.php \ No newline at end of file +SESSION.php +docs/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..df8cf01 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,87 @@ +# Changelog + +All notable changes to TRx WMS are documented here. +Format: `## [version] — YYYY-MM-DD` with sections Added / Changed / Fixed / Removed. + +--- + +## [Unreleased] + +### Planned +- Role-based access control enforcement across all API endpoints and UI pages (see `ROLES.md`) + +--- + +## [0.6.0] — 2026-05-06 + +### Added +- Barcode system — `md_barcode` table, SKU label page (`ics/sku_barcode_label.php`), Location label page (`ics/location_barcode_label.php`) +- `scanner.js` — unified scanner module supporting USB scanner, handheld, phone camera (Html5Qrcode), and clipboard paste +- Scan-driven stock flows — scan SKU label → scan location label → F2 to save (no mouse required) +- `ics/api/engine/barcode_lookup.php` — validates barcode string, returns typed result (`sku` / `loc` / `raw`) +- `ics/api/engine/validate_scan_location.php` — pre-save location validation for stock in/out/transfer +- `ics/api/engine/sku_label_lots.php`, `sku_label_products.php` — lot and product data feeds for SKU label page +- Cost, price, and margin fields on `md_product` +- Contact module linked to setting sidebar + +### Fixed +- `location_barcode_label.php` — `ON DUPLICATE KEY UPDATE` now includes `status = 1` so previewing a previously-disabled location barcode re-enables it + +--- + +## [0.5.0] — 2026-04 + +### Added +- Purchase Orders (`po/`) — create, confirm, receive, cancel, payment tracking +- Order module (`order/`) — sales orders, returns, invoices (invoice / credit note / debit note), order confirmation, payment status +- `td_order`, `td_invoice`, `td_return`, `td_purchase_order` tables +- Invoice print view (`order/print_invoice.php`) + +--- + +## [0.4.0] — 2026-03 + +### Added +- Warehouse location modes — simple (warehouse + rack) and advanced (warehouse + zone + aisle + rack) +- `company_setting` keys: `advanced_location`, `location_label_rack/zone/aisle`, `default_stock_status`, `auto_complete_on_ship`, `auto_invoice_and_credit_note` +- Per-warehouse stock tables (`td_stock_{warehouse_id}`) — balance = `SUM(in) - SUM(out)` where `status = 1` +- Stock approval flow — transactions created as `status = 0` (draft) and approved separately +- Rack occupancy report (`reports/occupy_rack.php`) +- Expired / near-expiry report (`reports/expired_stock.php`) + +### Changed +- Warehouse layers made switchable via `advanced_location` setting + +--- + +## [0.3.0] — 2026-02 + +### Added +- Contact module (`contact/`) — supplier/customer contacts, contact types +- Stock movement report with date and SKU filters +- Product lot report with expiry date tracking +- Low-stock dashboard widget with restock shortcut + +--- + +## [0.2.0] — 2026-01 + +### Added +- Multi-tenant branch support — `company_list`, `company_map_user`, branch switcher in topbar +- User management (`setting/users.php`) — invite by email, role assignment, remove member +- SMTP configuration (`setting/smtp.php`) +- OTP validation on every API request (HMAC-SHA1 based on user password + session time) +- CSRF token enforcement on all POST requests + +--- + +## [0.1.0] — 2025-12 + +### Added +- Initial project scaffold — PHP + MySQL + Bootstrap 5 + jQuery +- Login / registration / onboarding flow +- Product master (`inventory/product.php`) — SKU, name, UOM, category, barcode +- Warehouse master (`inventory/warehouse.php`) — warehouses and rack definitions +- Stock In / Out / Transfer pages with manual form entry +- `td_stock` base table structure +- Dashboard with basic stock summary diff --git a/README.md b/README.md index 256b4d3..911c282 100755 --- a/README.md +++ b/README.md @@ -1,2 +1,136 @@ -# inapp-pro -inapp-pro Inventory Dashboard Admin Template +# TRx WMS + +A multi-tenant Warehouse Management System (WMS) built with PHP, MySQL, and vanilla JavaScript. Designed for small-to-medium operations that need barcode-driven stock control, lot/serial traceability, and multi-warehouse support — accessible from any browser without installing a native app. + +--- + +## Features + +### Dashboard +- Live stock overview with key metrics (total SKUs, low-stock count, near-expiry alerts) +- Low-stock product list with one-click restock shortcut to Stock In +- Dashboard widgets per warehouse + +### Inventory Management +- **Product master** — SKU, name, unit of measure, barcode, category, cost/price/margin tracking +- **Categories** — group products for filtering and reporting +- **Warehouse master** — multi-warehouse support; simple or advanced location mode (warehouse → rack, or warehouse → zone → aisle → rack) +- **Storage / rack master** — define physical rack locations per warehouse +- **Product lots** — lot number, expiry date, per-lot traceability across all warehouses + +### Stock Control (ICS) +- **Stock In** — receive stock into a specific location; supports lot, expiry, serial, unit price, and contact (supplier) +- **Stock Out** — remove stock from a location; cascaded lot/serial dropdowns filtered to available stock +- **Stock Transfer** — move stock between any two locations (same or different warehouse) +- **Stock Overview** — real-time balance per SKU across all warehouses and locations +- Two-step approval flow: transactions created as `pending` and approved separately + +### Barcode System +- **SKU barcode labels** — generate `SKU|{sku}|{lot}|{serial}` labels; print, disable, re-enable per serial +- **Location barcode labels** — generate `LOC|{warehouse}|{rack}` (simple) or `LOC|{warehouse}|{zone}|{aisle}|{rack}` (advanced) labels +- **Scanner support** — USB scanner, handheld scanner, phone camera (Html5Qrcode), and clipboard paste all handled by a unified `scanner.js` module +- Scan-driven stock flows: scan SKU label → scan location label → press F2 to save (no mouse required) +- Disabled barcodes are rejected at scan time with a clear error message + +### Orders +- **Sales orders** — create, confirm, and track customer orders +- **Returns** — manage product returns linked to original orders +- **Invoices** — generate and print invoices per order +- **Purchase orders (PO)** — create and track supplier purchase orders + +### Contacts +- Supplier and customer contact management +- Contact types (supplier, customer, other) +- Linked to stock-in, stock-out, orders, and POs + +### Reports +- **Stock Movement** — full transaction history with in/out/net summary; filterable by date, SKU, warehouse +- **Product Lots** — lot-level stock balance with expiry dates across all warehouses +- **Expired / Near Expiry** — products approaching or past their expiry date +- **Rack Occupancy** — visual overview of which racks are occupied, empty, or locked + +### Settings +- **Company profile** — name, logo, branch details +- **System config** — location mode (simple/advanced), custom zone/aisle/rack labels, stock uniqueness rules +- **User management** — invite users by email, assign roles (admin / staff / viewer), remove members +- **SMTP** — configure outbound email for notifications +- **Profile** — per-user name, username, password, profile picture + +### Multi-Tenant / Branch Support +- Each company is isolated; users can belong to multiple companies +- Branch switcher in the topbar for users with access to more than one company +- All data (products, stock, orders, contacts) is scoped to the active company + +### Security +- Session-based authentication with TOTP-style OTP validation on every API request +- CSRF token enforcement on all POST requests +- Role-based access control: `owner`, `admin`, `staff`, `viewer` (see `ROLES.md`) +- Passwords hashed; profile picture uploads sandboxed to `uploads/profile/` + +--- + +## Tech Stack + +| Layer | Technology | +|-------|-----------| +| Backend | PHP 8.x, Apache | +| Databases | MySQL — `wms` (system/auth), `wms2` (operational data) | +| Frontend | Bootstrap 5, jQuery, Flatpickr, ApexCharts, JsBarcode, Html5Qrcode | +| Build | Vite (`npm run dev` / `npm run build`) | +| Auth | Session + HMAC-SHA1 OTP + CSRF tokens | + +--- + +## Module Layout + +``` +app/ +├── dashboard/ # Dashboard and low-stock widgets +├── ics/ # Stock In / Out / Transfer, barcode labels +├── inventory/ # Product, warehouse, rack, category masters +├── order/ # Sales orders, returns, invoices +├── po/ # Purchase orders +├── reports/ # Stock movement, lots, expiry, rack occupancy +├── contact/ # Supplier / customer contacts +├── setting/ # Company, users, SMTP, system config, profile +├── assets/ +│ ├── js/ # main.js, scanner.js, custom.js +│ ├── css/ # main.css, custom.css +│ └── utils/ # db_auth.php, db_helpers.php, shared classes +└── login/ # Login, OTP, onboarding +``` + +--- + +## Local Development + +```bash +# Front-end assets (Vite) +npm run dev +npm run build + +# Syntax-check a PHP file +php -l app/ics/manage_stock_in.php + +# Apply a schema migration to the client database +mysql -uroot -p2618 wms2 < migration.sql +``` + +App is served by Apache at `http://localhost/wms/app/`. + +--- + +## Documentation + +| File | Contents | +|------|----------| +| `CHANGELOG.md` | Version history and notable changes | +| `docs/ROLES.md` | Role-based access control spec (admin / staff / viewer) | +| `docs/DATABASE.md` | Full schema for both databases — tables, columns, relationships | +| `docs/API.md` | All API endpoints — request fields, response format, error codes | +| `docs/DEPLOYMENT.md` | Installation, Apache/PHP/MySQL setup, environment checklist | +| `docs/TESTING.md` | Manual test checklists — barcode flow, stock ops, regression | +| `docs/SECURITY.md` | Auth model, OTP flow, CSRF, session management, XSS/SQL rules | +| `docs/STOCK.md` | Stock ledger, approval flow, rack lifecycle, StockManager/WarehouseManager | +| `docs/SCANNER.md` | scanner.js internals, device support, integrating scanning into new pages | +| `docs/CONTRIBUTING.md` | Patterns for adding new APIs, pages, modules, settings, and schema changes | diff --git a/app/assets/css/custom.css b/app/assets/css/custom.css index 10c0dc4..f4ee4c6 100644 --- a/app/assets/css/custom.css +++ b/app/assets/css/custom.css @@ -88,4 +88,15 @@ justify-content: center; z-index: 2; border-radius: inherit; -} \ No newline at end of file +} + +.logo-area > a > img:first-child { + width: 40px; + height: 40px; + transition: width 0.2s ease, height 0.2s ease; +} + +.sidebar.collapsed .logo-area > a > img:first-child { + width: 24px !important; + height: 24px !important; +} diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png new file mode 100644 index 0000000..960c441 Binary files /dev/null and b/app/assets/images/logo.png differ diff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg deleted file mode 100644 index cd648df..0000000 --- a/app/assets/images/logo.svg +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/app/assets/js/custom.js b/app/assets/js/custom.js index e232ac8..7b23855 100644 --- a/app/assets/js/custom.js +++ b/app/assets/js/custom.js @@ -785,4 +785,26 @@ function set_btn_state(selector, enabled, hint) { .prop('disabled', true) .attr('title', hint || ''); } -} \ No newline at end of file +} + +var ROLE_RANK = { viewer: 0, staff: 1, admin: 2, owner: 3 }; + +function apply_role_ui() { + var role = document.getElementById('session-context')?.dataset.role || 'viewer'; + var rank = ROLE_RANK[role] ?? 0; + + $('[data-min-role]').each(function() { + var required = ROLE_RANK[$(this).data('min-role')] ?? 99; + if (rank < required) $(this).hide(); + }); +} + +$(function() { + apply_role_ui(); + if (window.MutationObserver) { + var roleObserver = new MutationObserver(function() { + apply_role_ui(); + }); + roleObserver.observe(document.body, { childList: true, subtree: true }); + } +}); diff --git a/app/assets/utils/db_auth.php b/app/assets/utils/db_auth.php index bb06419..dd3c277 100644 --- a/app/assets/utils/db_auth.php +++ b/app/assets/utils/db_auth.php @@ -9,6 +9,15 @@ require_once __DIR__."/db_helpers.php"; // xhr.responseJSON from being populated automatically in ajax_request(). header('Content-Type: application/json; charset=utf-8'); +if (!function_exists('require_role')) { + function require_role(string $user_role, array $allowed): void { + if (!in_array($user_role, $allowed, true)) { + http_response_code(403); + exit(json_encode(['success' => 0, 'message' => 'Access denied.'])); + } + } +} + if(!empty($_SESSION["login_company_id"])){ // CSRF Validation — add right at the top of the logged-in block @@ -63,6 +72,9 @@ if(!empty($_SESSION["login_company_id"])){ exit(json_encode($answer)); } + $user_role = $map[0]['role'] ?? 'viewer'; + $_SESSION['login_role'] = $user_role; + } // set up ANSWER @@ -86,6 +98,7 @@ if(!empty($_SESSION["login_company_id"])){ $company_id = (int)$_SESSION["login_company_id"]; $user_id = (int)$_SESSION["login_user_id"]; + $user_role = $_SESSION['login_role'] ?? ($user_role ?? 'viewer'); $uuid = bin2hex(random_bytes(16)); // create json for table logging @@ -98,4 +111,4 @@ if(!empty($_SESSION["login_company_id"])){ } -?> \ No newline at end of file +?> diff --git a/app/ics/api/engine/location_barcode_label.php b/app/ics/api/engine/location_barcode_label.php index 9d4c577..71eb837 100644 --- a/app/ics/api/engine/location_barcode_label.php +++ b/app/ics/api/engine/location_barcode_label.php @@ -33,6 +33,10 @@ $aisle = trim($data['aisle'] ?? ''); $rack = trim($data['rack'] ?? ''); + if ($label_action !== 'list') { + require_role($user_role, ['owner', 'admin', 'staff']); + } + if ($label_action === 'list') { $sth = $pdo2->prepare( "SELECT barcode, warehouse_id, zone, aisle, rack, status, print_count, last_printed_dt diff --git a/app/ics/api/engine/manage_stock_in.php b/app/ics/api/engine/manage_stock_in.php index 876cde4..bd8b509 100644 --- a/app/ics/api/engine/manage_stock_in.php +++ b/app/ics/api/engine/manage_stock_in.php @@ -5,6 +5,8 @@ require '../../../assets/utils/classes/WarehouseManager.php'; require '../../../assets/utils/classes/CompanySettingManager.php'; + require_role($user_role, ['owner', 'admin', 'staff']); + $csm = new CompanySettingManager($pdo1, $company_id); $auto_approve = (int)$csm->get('default_stock_status') === 1; diff --git a/app/ics/api/engine/manage_stock_out.php b/app/ics/api/engine/manage_stock_out.php index bb96e50..ce4ae06 100644 --- a/app/ics/api/engine/manage_stock_out.php +++ b/app/ics/api/engine/manage_stock_out.php @@ -5,6 +5,8 @@ require '../../../assets/utils/classes/WarehouseManager.php'; require '../../../assets/utils/classes/CompanySettingManager.php'; + require_role($user_role, ['owner', 'admin', 'staff']); + $csm = new CompanySettingManager($pdo1, $company_id); $auto_approve = (int)$csm->get('default_stock_status') === 1; diff --git a/app/ics/api/engine/manage_stock_transfer.php b/app/ics/api/engine/manage_stock_transfer.php index e424e5f..3a1dbc9 100644 --- a/app/ics/api/engine/manage_stock_transfer.php +++ b/app/ics/api/engine/manage_stock_transfer.php @@ -5,6 +5,8 @@ require '../../../assets/utils/classes/WarehouseManager.php'; require '../../../assets/utils/classes/CompanySettingManager.php'; + require_role($user_role, ['owner', 'admin', 'staff']); + $csm = new CompanySettingManager($pdo1, $company_id); $auto_approve = (int)$csm->get('default_stock_status') === 1; diff --git a/app/ics/api/engine/sku_barcode_label.php b/app/ics/api/engine/sku_barcode_label.php index a0bc5d1..49acedf 100644 --- a/app/ics/api/engine/sku_barcode_label.php +++ b/app/ics/api/engine/sku_barcode_label.php @@ -127,6 +127,10 @@ $product_sku = trim($data['product_sku'] ?? ''); $lot_number = trim($data['lot_number'] ?? ''); + if (in_array($label_action, ['create', 'print', 'disable', 'enable'], true)) { + require_role($user_role, ['owner', 'admin', 'staff']); + } + if ($label_action === 'print') { $barcode = trim($data['barcode'] ?? ''); if ($barcode === '') { diff --git a/app/ics/api/engine/validate_scan_location.php b/app/ics/api/engine/validate_scan_location.php index 0a710dc..639a472 100644 --- a/app/ics/api/engine/validate_scan_location.php +++ b/app/ics/api/engine/validate_scan_location.php @@ -3,6 +3,8 @@ require '../../../assets/utils/db_auth.php'; require '../../../assets/utils/classes/WarehouseManager.php'; + require_role($user_role, ['owner', 'admin', 'staff']); + $whMgmt = new WarehouseManager($pdo2, $company_id); $mode = $data['mode'] ?? ''; diff --git a/app/ics/location_barcode_label.php b/app/ics/location_barcode_label.php index 0051a0d..5347a81 100644 --- a/app/ics/location_barcode_label.php +++ b/app/ics/location_barcode_label.php @@ -1,6 +1,10 @@ @@ -17,13 +21,13 @@
Print or reprint labels from the rack master list.
Print or reprint product identity labels from the lot master list.