Seal transaction limit coverage gaps

This commit is contained in:
Thanakorn S
2026-05-26 13:10:54 +07:00
parent b4b1f5cbec
commit d93abb0b81
27 changed files with 1397 additions and 5 deletions
@@ -2,6 +2,7 @@
session_start();
require_once '../../../assets/utils/db_auth.php';
require_once '../../../assets/utils/classes/PurchaseRequestManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
require_role($user_role, ['owner', 'admin', 'staff']);
$action = $data['action'] ?? '';
@@ -16,6 +17,7 @@ try {
$answer['success'] = 1;
$answer['message'] = 'Purchase request created.';
$answer['new_id'] = $new_id;
(new UsageGuard($pdo1, $company_id, $packages))->increment();
} elseif ($action === 'update') {
$prm->save(array_merge($data, ['id' => $id, 'items' => $items]), $logging);
$answer['success'] = 1;
@@ -3,6 +3,7 @@
require_once '../../../assets/utils/db_auth.php';
require_role($user_role, ['owner', 'admin', 'staff']);
require_once '../../../assets/utils/classes/InvoiceManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
try {
$new_id = null;
@@ -15,6 +16,7 @@
$answer['success'] = 1;
$answer['new_id'] = $new_id;
$answer['message'] = 'Supplier credit note created.';
(new UsageGuard($pdo1, $company_id, $packages))->increment();
} catch (PDOException $e) {
$answer['message'] = 'Database error, please try again.';
@@ -3,6 +3,7 @@
require_once '../../../assets/utils/db_auth.php';
require_role($user_role, ['owner', 'admin', 'staff']);
require_once '../../../assets/utils/classes/PaymentBillingManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
try {
$mgr = new PaymentBillingManager($pdo2, $company_id);
@@ -49,6 +50,7 @@
$answer['success'] = 1;
$answer['new_id'] = $new_id;
$answer['message'] = 'Payment billing created.';
(new UsageGuard($pdo1, $company_id, $packages))->increment();
exit(json_encode($answer));
}
@@ -3,6 +3,7 @@
require_once '../../../assets/utils/db_auth.php';
require_role($user_role, ['owner', 'admin', 'staff']);
require_once '../../../assets/utils/classes/ReceiptBillingManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
try {
$mgr = new ReceiptBillingManager($pdo2, $company_id);
@@ -49,6 +50,7 @@
$answer['success'] = 1;
$answer['new_id'] = $new_id;
$answer['message'] = 'Receipt billing created.';
(new UsageGuard($pdo1, $company_id, $packages))->increment();
exit(json_encode($answer));
}
+2
View File
@@ -30,7 +30,9 @@
$answer['success'] = 1;
$answer['auto_approved'] = $auto_approve;
if ($new_id > 0) {
(new UsageGuard($pdo1, $company_id, $packages))->increment();
}
} catch (PDOException $e) {
$answer['success'] = 0;
+2
View File
@@ -30,7 +30,9 @@
$answer['success'] = 1;
$answer['auto_approved'] = $auto_approve;
if ($new_id > 0) {
(new UsageGuard($pdo1, $company_id, $packages))->increment();
}
} catch (PDOException $e) {
$answer['success'] = 0;
@@ -31,7 +31,9 @@
$answer['success'] = 1;
$answer['auto_approved'] = $auto_approve;
if ($new_id > 0) {
(new UsageGuard($pdo1, $company_id, $packages))->increment();
}
} catch (PDOException $e) {
$answer['success'] = 0;
+2
View File
@@ -7,6 +7,7 @@
require_once '../../../assets/utils/classes/WarehouseManager.php';
require_once '../../../assets/utils/classes/InvoiceManager.php';
require_once '../../../assets/utils/classes/CompanySettingManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
require_once '../../../assets/utils/notify_node.php';
$id = (int)($data['id'] ?? 0);
@@ -38,6 +39,7 @@
$answer['auto_invoice'] = $auto_invoice;
notify_node('order_updated', [], $company_id);
if ($auto_invoice) {
(new UsageGuard($pdo1, $company_id, $packages))->increment();
notify_node('invoice_updated', [], $company_id);
}
+2
View File
@@ -6,6 +6,7 @@
require_once '../../../assets/utils/classes/InvoiceManager.php';
require_once '../../../assets/utils/classes/WarehouseManager.php';
require_once '../../../assets/utils/classes/CompanySettingManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
require_once '../../../assets/utils/notify_node.php';
$id = (int)($data['id'] ?? 0);
@@ -36,6 +37,7 @@
notify_node('stock_updated', ['warehouse_id' => 0, 'type' => 'in'], $company_id);
}
if ($auto_cn) {
(new UsageGuard($pdo1, $company_id, $packages))->increment();
notify_node('invoice_updated', [], $company_id);
}
+2
View File
@@ -3,6 +3,7 @@
require_once '../../../assets/utils/db_auth.php';
require_role($user_role, ['owner', 'admin', 'staff']);
require_once '../../../assets/utils/classes/ReturnManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
require_once '../../../assets/utils/notify_node.php';
$action = $data['action'] ?? 'save';
@@ -30,6 +31,7 @@
$answer['success'] = 1;
if ($new_id) {
$answer['new_id'] = $new_id;
(new UsageGuard($pdo1, $company_id, $packages))->increment();
}
notify_node('return_updated', [], $company_id);
@@ -3,6 +3,7 @@
require_once '../../../assets/utils/db_auth.php';
require_role($user_role, ['owner', 'admin', 'staff']);
require_once '../../../assets/utils/classes/InvoiceManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
$return_id = (int)($data['return_id'] ?? 0);
@@ -22,6 +23,7 @@
$answer['success'] = 1;
$answer['new_id'] = $new_id;
(new UsageGuard($pdo1, $company_id, $packages))->increment();
} catch (PDOException $e) {
$answer['message'] = 'Database error, please try again.';
@@ -3,6 +3,7 @@
require_once '../../../assets/utils/db_auth.php';
require_role($user_role, ['owner', 'admin', 'staff']);
require_once '../../../assets/utils/classes/InvoiceManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
require_once '../../../assets/utils/notify_node.php';
$order_id = (int)($data['order_id'] ?? 0);
@@ -23,6 +24,7 @@
$answer['success'] = 1;
$answer['new_id'] = $new_id;
(new UsageGuard($pdo1, $company_id, $packages))->increment();
notify_node('invoice_updated', [], $company_id);
} catch (PDOException $e) {
@@ -6,6 +6,7 @@
require_once '../../../assets/utils/classes/WarehouseManager.php';
require_once '../../../assets/utils/classes/InvoiceManager.php';
require_once '../../../assets/utils/classes/CompanySettingManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
require_once '../../../assets/utils/notify_node.php';
$return_id = (int)($data['id'] ?? 0);
@@ -33,6 +34,9 @@
if ($auto_approve) {
notify_node('stock_updated', ['warehouse_id' => 0, 'type' => 'out'], $company_id);
}
if ($auto_dn) {
(new UsageGuard($pdo1, $company_id, $packages))->increment();
}
} catch (PDOException $e) {
$answer['message'] = 'Database error, please try again.';
+6 -2
View File
@@ -3,11 +3,12 @@
require_once '../../../assets/utils/db_auth.php';
require_role($user_role, ['owner', 'admin', 'staff']);
require_once '../../../assets/utils/classes/SupplierReturnManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
$mgr = new SupplierReturnManager($pdo2, $company_id);
try {
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, $action, &$answer) {
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, $action, $pdo1, $packages, &$answer) {
$mgr = new SupplierReturnManager($pdo, $company_id);
// Tracking-only update for confirmed returns
@@ -27,7 +28,10 @@
$new_id = $mgr->saveReturn($payload, $logging);
$answer['success'] = 1;
if ($new_id) $answer['new_id'] = $new_id;
if ($new_id) {
$answer['new_id'] = $new_id;
(new UsageGuard($pdo1, $company_id, $packages))->increment();
}
});
} catch (PDOException $e) {
@@ -3,6 +3,7 @@
require_once '../../../assets/utils/db_auth.php';
require_role($user_role, ['owner', 'admin', 'staff']);
require_once '../../../assets/utils/classes/InvoiceManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
$po_id = (int)($data['po_id'] ?? 0);
@@ -22,6 +23,7 @@
$answer['success'] = 1;
$answer['new_id'] = $new_id;
(new UsageGuard($pdo1, $company_id, $packages))->increment();
} catch (PDOException $e) {
$answer['message'] = 'Database error, please try again.';
@@ -3,6 +3,7 @@
require_once '../../../assets/utils/db_auth.php';
require_role($user_role, ['owner', 'admin', 'staff']);
require_once '../../../assets/utils/classes/InvoiceManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
$return_id = (int)($data['return_id'] ?? 0);
@@ -22,6 +23,7 @@
$answer['success'] = 1;
$answer['new_id'] = $new_id;
(new UsageGuard($pdo1, $company_id, $packages))->increment();
} catch (PDOException $e) {
$answer['message'] = 'Database error, please try again.';
@@ -3,6 +3,7 @@
require_once '../../../assets/utils/db_auth.php';
require_role($user_role, ['owner', 'admin', 'staff']);
require_once '../../../assets/utils/classes/InvoiceManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
try {
$new_id = null;
@@ -15,6 +16,7 @@
$answer['success'] = 1;
$answer['new_id'] = $new_id;
$answer['message'] = 'Credit note created.';
(new UsageGuard($pdo1, $company_id, $packages))->increment();
} catch (PDOException $e) {
$answer['message'] = 'Database error, please try again.';
@@ -4,6 +4,7 @@
require_role($user_role, ['owner', 'admin', 'staff']);
require_once '../../../assets/utils/classes/OrderManager.php';
require_once '../../../assets/utils/classes/InvoiceManager.php';
require_once '../../../assets/utils/classes/UsageGuard.php';
require_once '../../../assets/utils/notify_node.php';
$order_id = (int)($data['order_id'] ?? 0);
@@ -27,6 +28,7 @@
$answer['success'] = 1;
$answer['new_id'] = $new_id;
(new UsageGuard($pdo1, $company_id, $packages))->increment();
notify_node('invoice_updated', [], $company_id);
} catch (PDOException $e) {
+53
View File
@@ -0,0 +1,53 @@
# MN3 WMS Feature Documentation
This folder documents the main product features discovered from the current PHP codebase.
The app is split into three operating areas:
- WMS: warehouse operations, stock movement, sales and purchase workflows.
- Accounting: revenue, expense, finance, journals, batch GL posting, and financial reports.
- Master Data: shared setup for products, locations, contacts, chart of accounts, departments, formulas, and posting rules.
## Documents
- [Coverage Matrix](coverage-matrix.md) — codebase surface mapped to documentation status
- [WMS Features](../reviewing/wms.md)
- [Accounting Features](../reviewing/accounting.md)
- [Master Data Features](../reviewing/master-data.md)
- [System And Settings Features](../reviewing/system-settings.md)
- [Runtime Architecture](../reviewing/runtime-architecture.md)
- [Setup And Landing Pages](../reviewing/setup-landing.md)
- [Helper Endpoint Contracts](../reviewing/helper-endpoints.md)
- [Document Lifecycle And Status](../reviewing/document-lifecycle.md)
- [Transaction Limits](../reviewing/transaction-limits.md) — package tiers, daily/weekly quotas, report gating
## Architecture
- Architecture coverage is currently split between [System And Settings Features](../reviewing/system-settings.md), [Accounting Features](../reviewing/accounting.md), and [Coverage Matrix](coverage-matrix.md).
## Cross-Cutting Specs
- [Running Number](running-number.md) — document number format, sequences, manual entry, gap policy
- [Session Concurrency](session-concurrency.md) — single-session enforcement, heartbeat, PHP GC stale detection, single-factor auth
- [Live Dashboard](live-dashboard.md) — real-time Socket.IO events, section reload map, flash card effect
- [Role Guards](role-guards.md) — CRUD access matrix per role
- [Soft Delete](soft-delete.md) — mechanism, downstream blocks, stock/GL side effects, deletion order
## Core Architecture
The UI is mostly PHP pages under `app/`, with AJAX endpoints under each module's `api/engine` or `api/engine_report` folder.
Common backend behavior is concentrated in manager classes:
- `app/assets/utils/classes/*Manager.php` handles WMS, document, contact, product, finance, and report logic.
- `app/assets/utils/classes_ac/*` handles accounting setup, GL posting, financial statements, posting windows, and tax reports.
- `app/assets/js/custom.js` contains shared AJAX, pagination, account autocomplete, locks, batch processing, and display formatting helpers.
## Cross-Cutting Rules
- Company scoping is applied through `company_id` from the authenticated session.
- Most write APIs load `app/assets/utils/db_auth.php`, which validates session, OTP freshness, CSRF token, and request payload.
- Role checks use `require_role()` where a route is limited to owners/admins.
- Audit logs are stored as JSON in many business tables through the `$logging` object from `db_auth.php`.
- Numeric display uses shared frontend formatting to suppress floating point noise and avoid scientific notation in the UI.
+138
View File
@@ -0,0 +1,138 @@
# Documentation Coverage Matrix
This matrix compares the current codebase surface area with the feature specs in `docs/`.
Status legend:
- `Covered` - behavior is described by a reviewed spec.
- `Draft-covered` - behavior is described in `docs/reviewing/` and still needs review sign-off.
- `Partial` - behavior is mentioned, but important routes, events, or edge cases are missing.
- `Missing` - no product/spec coverage was found.
- `Internal` - infrastructure or shared code that is not a user-facing feature by itself.
## Summary
| Area | Code surface | Coverage |
|---|---:|---|
| App PHP pages/routes | 108 | Mostly draft-covered |
| App API endpoints | 192 | Mostly draft-covered |
| Reviewed specs | 6 | Cross-cutting behavior only |
| Reviewing specs | 10 | Main feature families plus runtime/setup/helper coverage |
The docs cover the main product domains, but the full feature set is not all reviewed. The main business specs remain in `docs/reviewing/`.
## Specification Map
| Spec | Status | Primary coverage |
|---|---|---|
| `docs/reviewing/wms.md` | Draft-covered | WMS dashboard, stock overview, stock in/out/transfer, labels, sales orders/returns/invoices, purchase orders/supplier returns/purchase invoices, stock reports |
| `docs/reviewing/accounting.md` | Draft-covered | Accounting dashboard, revenue/expense documents, finance, chart of accounts, departments, formulas, product account mapping, manual journals, GL posting, financial reports |
| `docs/reviewing/master-data.md` | Draft-covered | Products, categories, warehouses/storage, contacts, chart of accounts, departments, formulas, posting window |
| `docs/reviewing/system-settings.md` | Draft-covered | Authentication, sessions/security, users, profile/password, company settings, SMTP, branch switching, shared JS, locks, ETL maintenance, file uploads |
| `docs/reviewing/document-lifecycle.md` | Draft-covered | Document statuses, void strategy, soft delete strategy, GL versioning |
| `docs/reviewing/transaction-limits.md` | Draft-covered | Package tiers, quota counting, report/dashboard gating |
| `docs/reviewing/invited-onboarding.md` | Draft-covered | Invited user activation flow |
| `docs/reviewed/role-guards.md` | Covered | Role access matrix |
| `docs/reviewed/soft-delete.md` | Covered | Soft delete and downstream deletion guards |
| `docs/reviewed/session-concurrency.md` | Covered | Single-session login enforcement and heartbeat |
| `docs/reviewed/live-dashboard.md` | Covered | Socket.IO dashboard event behavior |
| `docs/reviewed/running-number.md` | Covered | Document numbering configuration and generation |
| `docs/reviewing/runtime-architecture.md` | Draft-covered | App shell, config, database connections, Node.js realtime server, cron scheduler, setup script |
| `docs/reviewing/setup-landing.md` | Draft-covered | Root redirect, landing page, legal pages, CLI setup behavior |
| `docs/reviewing/helper-endpoints.md` | Draft-covered | Helper/search/retrieve/stats endpoint contracts |
## Page Coverage
| Code area | Pages | Coverage | Spec |
|---|---|---|---|
| WMS dashboard | `app/dashboard/index.php`, `app/dashboard/low_stock_products.php` | Draft-covered | `docs/reviewing/wms.md`, `docs/reviewed/live-dashboard.md` |
| Stock operations | `app/ics/stock_in.php`, `app/ics/manage_stock_in.php`, `app/ics/stock_out.php`, `app/ics/manage_stock_out.php`, `app/ics/stock_transfer.php`, `app/ics/manage_stock_transfer.php`, `app/ics/stock_overview.php` | Draft-covered | `docs/reviewing/wms.md`, `docs/reviewing/document-lifecycle.md` |
| Barcode labels | `app/ics/sku_barcode_label.php`, `app/ics/location_barcode_label.php` | Draft-covered | `docs/reviewing/wms.md` |
| Inventory master data | `app/inventory/product.php`, `app/inventory/manage_product.php`, `app/inventory/warehouse.php`, `app/inventory/manage_warehouse.php`, `app/inventory/manage_category.php`, `app/inventory/manage_storage.php` | Draft-covered | `docs/reviewing/master-data.md` |
| Contacts | `app/contact/contact.php`, `app/contact/manage_contact.php`, `app/contact/manage_contact_type.php` | Draft-covered | `docs/reviewing/master-data.md` |
| WMS sales | `app/order/order.php`, `app/order/manage_order.php`, `app/order/confirm_order.php`, `app/order/return.php`, `app/order/manage_return.php`, `app/order/invoice.php`, `app/order/manage_invoice.php`, `app/order/print_invoice.php` | Draft-covered | `docs/reviewing/wms.md`, `docs/reviewing/document-lifecycle.md` |
| WMS purchase | `app/po/po.php`, `app/po/manage_po.php`, `app/po/supplier_returns.php`, `app/po/manage_supplier_return.php`, `app/po/invoice.php`, `app/po/manage_purchase_invoice.php` | Draft-covered | `docs/reviewing/wms.md`, `docs/reviewing/document-lifecycle.md` |
| Revenue documents | `app/revenue/quotation.php`, `app/revenue/manage_quotation.php`, `app/revenue/order.php`, `app/revenue/manage_order.php`, `app/revenue/invoice.php`, `app/revenue/manage_invoice.php`, `app/revenue/manage_credit_note.php`, `app/revenue/view_invoice.php`, `app/revenue/receipt.php` | Draft-covered | `docs/reviewing/accounting.md` |
| Expense documents | `app/expense/purchase_request.php`, `app/expense/manage_purchase_request.php`, `app/expense/purchase_order.php`, `app/expense/manage_purchase_order.php`, `app/expense/purchase_invoice.php`, `app/expense/manage_purchase_invoice.php`, `app/expense/manage_supplier_credit_note.php`, `app/expense/payment.php` | Draft-covered | `docs/reviewing/accounting.md` |
| Finance | `app/finance/receipt_billing.php`, `app/finance/manage_receipt_billing.php`, `app/finance/receipt.php`, `app/finance/manage_receipt.php`, `app/finance/payment_billing.php`, `app/finance/manage_payment_billing.php`, `app/finance/payment.php`, `app/finance/manage_payment.php` | Draft-covered | `docs/reviewing/accounting.md` |
| Accounting dashboard | `app/ac_dashboard/index.php` | Draft-covered | `docs/reviewing/accounting.md`, `docs/reviewed/live-dashboard.md` |
| Accounting setup | `app/accounting/chart_of_accounts.php`, `app/accounting/manage_account.php`, `app/accounting/departments.php`, `app/accounting/manage_department.php`, `app/accounting/account_formulas.php`, `app/accounting/posting_window.php` | Draft-covered | `docs/reviewing/accounting.md`, `docs/reviewing/master-data.md` |
| Accounting journals and reports | `app/accounting/gl_entries.php`, `app/accounting/journal_listing.php`, `app/journal/index.php`, `app/journal/new.php`, `app/accounting/trial_balance.php`, `app/accounting/pl_statement.php`, `app/accounting/balance_sheet.php`, `app/accounting/gl_movement.php`, `app/accounting/vat_report.php` | Draft-covered | `docs/reviewing/accounting.md` |
| WMS reports | `app/reports/stock_movement.php`, `app/reports/expired_stock.php`, `app/reports/occupy_rack.php`, `app/reports/product_lot.php` | Draft-covered | `docs/reviewing/wms.md`, `docs/reviewing/transaction-limits.md` |
| Authentication | `app/login/index.php`, `app/login/register.php`, `app/login/verify.php`, `app/login/forgot_password.php`, `app/login/onboarding.php`, `app/login/invited_onboarding.php` | Draft-covered | `docs/reviewing/system-settings.md`, `docs/reviewing/invited-onboarding.md`, `docs/reviewed/session-concurrency.md` |
| Settings | `app/setting/users.php`, `app/setting/profile.php`, `app/setting/company.php`, `app/setting/system_config.php`, `app/setting/smtp.php`, `app/setting/document_types.php`, `app/setting/gl_maintenance.php`, `app/setting/stock_maintenance.php` | Draft-covered | `docs/reviewing/system-settings.md`, `docs/reviewed/running-number.md` |
| Cron | `app/cron/etl_gl_maintenance.php`, `app/cron/etl_stock_maintenance.php` | Draft-covered | `docs/reviewing/system-settings.md` |
| Shared app shell | `app/index.php`, `app/session.php`, `app/preset.php`, `app/dbconn.php`, `app/config.php`, `app/config.example.php`, `app/include_*.php` | Draft-covered | `docs/reviewing/system-settings.md`, `docs/reviewing/runtime-architecture.md` |
| Landing/setup | `index.php`, `setup.php`, `landing/index.php`, `landing/privacy.php`, `landing/terms.php`, `SESSION.php` | Draft-covered | `docs/reviewing/setup-landing.md`, `docs/reviewing/runtime-architecture.md` |
## API Coverage By Module
| Module | Endpoints | Coverage | Notes |
|---|---:|---|---|
| `ac_dashboard/api/engine` | 6 | Draft-covered | Dashboard APIs are `by_source`, `journals`, `pl`, `posting_window`, `recent`, `trend` |
| `accounting/api/engine` | 25 | Draft-covered | CRUD, journal, posting, report, lock, and batch-log APIs are described by accounting/system specs |
| `contact/api/engine` | 9 | Draft-covered | Contact and type CRUD plus stats are covered at feature level |
| `dashboard/api/engine_report` | 2 | Draft-covered | WMS dashboard stats and low-stock APIs |
| `expense/api/engine` | 6 | Draft-covered | Purchase request and supplier credit note APIs; purchase invoice behavior is shared through invoice managers/routes |
| `finance/api/engine` | 8 | Draft-covered | Receipt/payment billing and receipt/payment CRUD/delete APIs |
| `ics/api/engine` | 27 | Draft-covered | Core stock and label flows are covered by WMS; helper lookup endpoints are covered by `helper-endpoints.md` |
| `ics/api/engine_report` | 7 | Draft-covered | Stock overview and activity/report APIs |
| `inventory/api/engine` | 17 | Draft-covered | Product/category/warehouse/storage CRUD and stats |
| `login/api/engine` | 9 | Draft-covered | Login, OTP, registration, onboarding, password reset, logout |
| `order/api/engine` | 18 | Draft-covered | Sales order, return, invoice lifecycle |
| `po/api/engine` | 17 | Draft-covered | PO, receive, supplier return, purchase invoice/credit note lifecycle |
| `reports/api/engine_report` | 7 | Draft-covered | Stock reports and quota gating |
| `revenue/api/engine` | 10 | Draft-covered | Quotation, revenue order, invoice conversion, credit note APIs |
| `setting/api/engine` | 18 | Draft-covered | Users, company/profile/SMTP/system settings, document types, ETL maintenance, branch switching |
## API Helper Endpoint Coverage
These endpoints are implemented and used by forms. They are now covered at contract level by `docs/reviewing/helper-endpoints.md`:
- `app/ics/api/engine/contact_search.php`
- `app/ics/api/engine/product_search.php`
- `app/ics/api/engine/retrieve_active_lot.php`
- `app/ics/api/engine/retrieve_active_serial.php`
- `app/ics/api/engine/retrieve_aisle.php`
- `app/ics/api/engine/retrieve_lot.php`
- `app/ics/api/engine/retrieve_rack.php`
- `app/ics/api/engine/retrieve_warehouse.php`
- `app/ics/api/engine/retrieve_zone.php`
- `app/ics/api/engine/validate_scan_location.php`
- `app/inventory/api/engine/manager.php`
- `app/accounting/api/engine/account_stats.php`
- `app/accounting/api/engine/department_stats.php`
- `app/contact/api/engine/contact_stats.php`
- `app/inventory/api/engine/product_stats.php`
- `app/inventory/api/engine/warehouse_stats.php`
## Cross-Cutting Coverage
| Behavior | Coverage | Spec |
|---|---|---|
| Role authorization | Covered | `docs/reviewed/role-guards.md` |
| Soft delete and deletion order | Covered | `docs/reviewed/soft-delete.md` |
| Running numbers | Covered | `docs/reviewed/running-number.md` |
| Session concurrency | Covered | `docs/reviewed/session-concurrency.md` |
| Dashboard socket events | Covered | `docs/reviewed/live-dashboard.md` |
| Transaction quotas | Draft-covered | `docs/reviewing/transaction-limits.md` |
| Posting window | Draft-covered | `docs/reviewing/accounting.md`, `docs/reviewing/master-data.md` |
| Document statuses | Draft-covered | `docs/reviewing/document-lifecycle.md` |
| Operation locks | Draft-covered | `docs/reviewing/system-settings.md`, `docs/reviewing/accounting.md` |
| ETL maintenance | Draft-covered | `docs/reviewing/system-settings.md` |
| File uploads | Draft-covered | `docs/reviewing/system-settings.md` |
| Node.js server and cron runtime | Draft-covered | `docs/reviewed/live-dashboard.md`, `docs/reviewing/system-settings.md`, `docs/reviewing/runtime-architecture.md` |
| Installation/setup and landing site | Draft-covered | `docs/reviewing/setup-landing.md`, `docs/reviewing/runtime-architecture.md` |
## Known Stale Or Corrected References
- `docs/reviewed/README.md` previously linked reviewing specs as if they lived in `docs/reviewed/`.
- `docs/reviewed/README.md` previously referenced `docs/architecture.md`, which is not present.
- `docs/reviewing/accounting.md` previously referenced `app/ac_dashboard/api/engine/stats.php`; the actual accounting dashboard endpoints are split by section.
- `docs/reviewing/system-settings.md` previously referenced `migrations/*.sql`; schema setup wording now reflects this repository snapshot.
## Remaining Documentation Work
1. Promote or revise the `docs/reviewing/` specs after review.
2. Add exact request/response payload tables for helper endpoints where frontend code depends on field-level contracts.
3. Reconcile `schema_migrations` usage with the absence of a `migrations/` directory if file-based migrations are reintroduced.
+206
View File
@@ -0,0 +1,206 @@
# Live Dashboard — Real-Time Event Specification
## Mechanism
PHP engine files emit named events to a Node.js process over HTTP after a successful DB commit. Node.js broadcasts the event to all browser tabs in the same company room via Socket.IO. Each dashboard page listens for relevant events and reloads only the sections that changed — without a full page refresh.
```
PHP (engine file)
└─ notify_node(event, payload, company_id) // fire-and-forget POST to Node
└─ Node.js /emit endpoint
└─ io.to('company_{id}').emit(event, payload)
└─ Browser (dashboard JS)
└─ targeted section reload + flash effect
```
---
## `notify_node()` (`app/assets/utils/notify_node.php`)
Fire-and-forget HTTP POST from PHP to the local Node.js `/emit` endpoint. Called **after** `$pdo->commit()` so the event is never sent for rolled-back transactions.
```php
notify_node(string $event, array $payload, int $company_id): void
```
Failure is silently ignored — the DB write is authoritative; the live update is a UX enhancement only.
---
## Socket.IO Room
Each company gets its own room: `company_{company_id}`. The browser joins this room on page load via `window._socket` (initialized in `include_ending.php`, always before page `<script>` blocks).
---
## Events
### `stock_updated`
Fired when stock inventory changes.
| Trigger file | Action |
|---|---|
| `ics/api/engine/approve_stock.php` | Stock In / Out / Transfer approved |
Payload: `{ warehouse_id, type }`
Dashboard sections reloaded: stock-in total, stock-out total, low stock count, stock movement chart, stock health donut, most-moved list, low-stock list, recent activity list.
> `stock_overview.php` does **not** listen — it is an analytical report page, not a live monitor.
---
### `order_updated`
Fired when a sales order is created, confirmed, cancelled, or deleted.
| Trigger file | Action |
|---|---|
| `order/api/engine/manage_order.php` | Order saved (create or update) |
| `order/api/engine/confirm_order.php` | Order confirmed |
| `order/api/engine/cancel_order.php` | Order cancelled |
| `order/api/engine/delete_order.php` | Order soft-deleted |
Payload: `{}`
Dashboard sections reloaded: total orders card.
---
### `invoice_updated`
Fired when any invoice-type document changes status or is created/deleted. Also fired as a side-effect from `confirm_order` when `auto_invoice = true`, and from `confirm_return` when `auto_cn = true`.
| Trigger file | Action |
|---|---|
| `order/api/engine/proceed_to_invoice.php` | Invoice created from order |
| `order/api/engine/manage_invoice.php` | Invoice saved (create or update) |
| `order/api/engine/void_invoice.php` | Invoice voided |
| `order/api/engine/delete_invoice.php` | Invoice soft-deleted |
| `order/api/engine/update_invoice_status.php` | Invoice status updated |
| `order/api/engine/confirm_order.php` | Auto-invoice on order confirm (conditional) |
| `order/api/engine/confirm_return.php` | Auto credit note on return confirm (conditional) |
| `revenue/api/engine/proceed_to_invoice.php` | Revenue invoice created |
| `revenue/api/engine/delete_credit_note.php` | Credit note deleted |
| `expense/api/engine/delete_supplier_credit_note.php` | Supplier credit note deleted |
Payload: `{}`
Dashboard sections reloaded: total revenue card, unpaid invoices card.
---
### `return_updated`
Fired when a customer return is created, confirmed, cancelled, or deleted.
| Trigger file | Action |
|---|---|
| `order/api/engine/manage_return.php` | Return saved (create or update) |
| `order/api/engine/confirm_return.php` | Return confirmed |
| `order/api/engine/cancel_return.php` | Return cancelled |
| `order/api/engine/delete_return.php` | Return soft-deleted |
Payload: `{}`
Dashboard sections reloaded: pending returns card.
---
### `gl_posted`
Fired when a GL entry is posted, replaced, or saved as a manual journal.
| Trigger file | Action |
|---|---|
| `accounting/api/engine/post_gl_entry.php` | Source document GL posted or replaced |
| `accounting/api/engine/save_manual_journal.php` | Manual journal saved or replaced |
| `order/api/engine/issue_invoice.php` | Invoice issued and posted to GL |
| `finance/api/engine/manage_receipt.php` | Receipt saved and posted to GL |
| `finance/api/engine/manage_payment.php` | Payment saved and posted to GL |
Payload is built by `gl_posted_payload()` for most emitters and includes document type, source id, action, and journal lines where available.
Accounting dashboard sections reloaded: dashboard cards and charts handled by `ac_dashboard/index.php`.
---
### `receipt_posted` and `payment_posted`
Fired as finance-specific signals immediately after receipt or payment creation.
| Event | Trigger file | Payload |
|---|---|---|
| `receipt_posted` | `finance/api/engine/manage_receipt.php` | `{ id }` |
| `payment_posted` | `finance/api/engine/manage_payment.php` | `{ id }` |
The current accounting dashboard refresh is driven by `gl_posted`; these finance events are available for targeted listeners if a page adds them later.
---
## WMS Dashboard Section Map (`dashboard/index.php`)
| Event | Sections updated | DOM IDs | Reload function |
|---|---|---|---|
| `stock_updated` | Stock in/out cards, low stock card, movement chart, health donut, most-moved list, low-stock list, activity list | `stat_total_in`, `stat_total_out`, `stat_low_stock_count`, `#ics_movementChart`, `#overallChart`, `#list_most_moved`, `#list_low_stock`, `#list_recent_activity` | `retrieve_stock_sections()` |
| `order_updated` | Total orders card | `stat_total_orders` | `retrieve_order_sections()` |
| `invoice_updated` | Revenue + unpaid invoices cards | `stat_total_revenue`, `stat_unpaid_invoices` | `retrieve_invoice_sections()` |
| `return_updated` | Pending returns card | `stat_pending_returns` | `retrieve_return_sections()` |
All four section functions call the same API endpoint (`dashboard/api/engine_report/reports_stats.php`) but update only the relevant DOM elements.
---
## Accounting Dashboard (`ac_dashboard/index.php`)
The accounting dashboard listens for `gl_posted` and reloads its accounting KPI sections. It does not listen to the WMS events unless a future page explicitly adds those listeners.
---
## Flash Card Effect
When a silent socket-triggered section reload completes, each updated card receives a 1-second yellow glow animation to signal the change without disrupting the user.
**CSS:**
```css
@keyframes card-flash {
0% { box-shadow: 0 0 0 3px rgba(250,204,21,0.8); }
100% { box-shadow: none; }
}
.card-flash { animation: card-flash 1s ease-out; }
```
**JS helper (`flash_card`):**
```javascript
function flash_card(el) {
if (!el) return;
el.classList.remove('card-flash');
void el.offsetWidth; // force reflow so re-trigger works
el.classList.add('card-flash');
setTimeout(function() { el.classList.remove('card-flash'); }, 1000);
}
```
Called immediately after the overlay `.hide()` for each updated card element. Flash does **not** fire on the initial full-page load — only on silent socket-triggered background refreshes.
---
## Pages That Do NOT Listen
| Page | Reason |
|---|---|
| `ics/stock_overview.php` | Analytical report — user loads on demand, no live monitoring needed |
| All other non-dashboard pages | Point-in-time views; manual refresh is sufficient |
---
## Key Files
| File | Role |
|---|---|
| `app/assets/utils/notify_node.php` | PHP → Node.js HTTP POST helper |
| `nodejs/server.js` | Node.js Socket.IO server, `/emit` endpoint, room broadcast |
| `app/include_ending.php` | Initializes `window._socket` before page scripts run |
| `app/dashboard/index.php` | WMS dashboard: 4 socket listeners, 4 section-reload functions, `flash_card()` |
| `app/ac_dashboard/index.php` | Accounting dashboard: own listener pattern, `flash_card()` |
+308
View File
@@ -0,0 +1,308 @@
# Accounting Features
## Accounting Dashboard
Routes:
- `app/ac_dashboard/index.php`
- `app/ac_dashboard/api/engine/by_source.php`
- `app/ac_dashboard/api/engine/journals.php`
- `app/ac_dashboard/api/engine/pl.php`
- `app/ac_dashboard/api/engine/posting_window.php`
- `app/ac_dashboard/api/engine/recent.php`
- `app/ac_dashboard/api/engine/trend.php`
The accounting dashboard summarizes accounting KPIs, posting window status, and high-level financial indicators.
Backend support:
- `ac_dashboard/api/engine/*`
- `FinancialReports`
- `CompanySettingManager`
## Revenue Documents
Routes:
- `app/revenue/quotation.php`
- `app/revenue/manage_quotation.php`
- `app/revenue/order.php`
- `app/revenue/manage_order.php`
- `app/revenue/invoice.php`
- `app/revenue/manage_invoice.php`
- `app/revenue/manage_credit_note.php`
- `app/revenue/view_invoice.php`
- `app/revenue/receipt.php`
Revenue workflows include quotations, sales orders, sales invoices, and customer credit notes. Quotations can be converted forward, sales documents support inline tax, and invoices can be posted to the GL.
Backend support:
- `QuotationManager`
- `OrderManager`
- `InvoiceManager`
- `revenue/api/engine/*`
## Expense Documents
Routes:
- `app/expense/purchase_request.php`
- `app/expense/manage_purchase_request.php`
- `app/expense/purchase_order.php`
- `app/expense/manage_purchase_order.php`
- `app/expense/purchase_invoice.php`
- `app/expense/manage_purchase_invoice.php`
- `app/expense/manage_supplier_credit_note.php`
- `app/expense/payment.php`
Expense workflows include purchase requests, purchase orders, purchase invoices, and supplier credit notes. Purchase requests can be submitted, approved, rejected, reopened, cancelled, and converted to purchase orders. Purchase documents support inline tax and accounting formula assignment.
Backend support:
- `PurchaseRequestManager`
- `PurchaseOrderManager`
- `InvoiceManager`
- `expense/api/engine/*`
## Receipt Billing And Receipts
Routes:
- `app/finance/receipt_billing.php`
- `app/finance/manage_receipt_billing.php`
- `app/finance/receipt.php`
- `app/finance/manage_receipt.php`
Receipt Billing groups customer invoices for collection. Receipts allocate cash against open customer documents, refresh invoice settlement status, and can be assigned formulas for GL posting.
Backend support:
- `ReceiptBillingManager`
- `ReceiptManager`
## Payment Billing And Payments
Routes:
- `app/finance/payment_billing.php`
- `app/finance/manage_payment_billing.php`
- `app/finance/payment.php`
- `app/finance/manage_payment.php`
Payment Billing groups supplier invoices for payment. Payments allocate cash against open supplier documents, refresh settlement status, and can be assigned formulas for GL posting.
Backend support:
- `PaymentBillingManager`
- `PaymentManager`
## Chart Of Accounts
Routes:
- `app/accounting/chart_of_accounts.php`
- `app/accounting/manage_account.php`
- `app/accounting/api/engine/account.php`
- `app/accounting/api/engine/manage_account.php`
The chart of accounts manages account code, account name, account type, account category, parent account, posting flag, and status. Account categories are used by reports such as VAT.
Backend support:
- `ChartOfAccounts::getAll()`
- `ChartOfAccounts::create()`
- `ChartOfAccounts::update()`
- `ChartOfAccounts::isPostingAccount()`
## Departments
Routes:
- `app/accounting/departments.php`
- `app/accounting/manage_department.php`
- `app/accounting/api/engine/department.php`
Departments are optional dimensions on journal lines and accounting reports. Reports can filter by department where supported.
Backend support:
- `DepartmentManager`
## Account Formulas
Routes:
- `app/accounting/account_formulas.php`
- `app/accounting/api/engine/account_formula.php`
Account Formulas define GL posting templates by document type. A formula consists of debit/credit lines, account codes, amount keys, descriptions, status, and default flag.
Supported amount keys:
- `grand_total`: final document total.
- `total`: pre-tax/subtotal-style amount, split by SKU when Product Account Mapping applies.
- `tax`: document tax amount.
- `amount`: receipt or payment amount.
Backend support:
- `AccountFormulaManager`
- `BasePosting::resolveFormula()`
## Product Account Mapping
Routes:
- `app/accounting/account_formulas.php`
- `app/accounting/api/engine/product_account_mapping.php`
- `app/inventory/manage_product.php`
Product Account Mapping assigns sales and purchase GL accounts to each product/SKU. It is used when an account formula line uses `total`.
The `Product Accounts` tab inside Account Formulas is an accounting-only maintenance view. The pencil action opens a modal that edits only `sales_account_code` and `purchase_account_code`; full product master data remains in the product maintenance page.
Posting behavior:
- Sales invoice and credit note `total` lines use `sales_account_code`.
- Purchase invoice and supplier credit note `total` lines use `purchase_account_code`.
- If a product mapping is blank, posting falls back to the formula account for that line.
- Batch GL posting checks loaded invoice documents and blocks Run when product mappings are missing for formulas that use `total`.
Backend support:
- `ProductManager::updateAccountMapping()`
- `BasePosting::buildSkuTotalLines()`
- `InvoicePosting`
- `CreditNotePosting`
- `PurchaseInvoicePosting`
- `SupplierCreditNotePosting`
## Manual GL Journal
Routes:
- `app/accounting/journal_listing.php`
- `app/journal/index.php`
- `app/journal/new.php`
- `app/accounting/api/engine/save_manual_journal.php`
- `app/accounting/api/engine/get_journal_listing.php`
- `app/accounting/api/engine/get_journal_detail.php`
Manual GL Journals allow users to create or edit journal entries outside source documents. Journal lines include account code, department, debit, credit, and description.
Backend support:
- `GlManager::postManual()`
- `GlManager::replaceManual()`
## Source Document GL Posting
Routes:
- `app/accounting/api/engine/post_gl_entry.php`
Source document posting builds journal lines from a source document and selected/default formula, then writes or replaces `td_gl` and `td_gl_item`.
Supported source types:
- `invoice`
- `credit_note`
- `purchase_invoice`
- `supplier_credit_note`
- `receipt`
- `payment`
Posting behavior:
- First post: `GlManager::post()` creates `td_gl` and `td_gl_item` rows.
- Re-post: `GlManager::replace()` snapshots the current lines as JSON into `td_gl.history`, increments `td_gl.current_version`, then writes new lines. Full version history is preserved.
- Void: calls `GlManager::delete()` which hard-deletes the `td_gl` and `td_gl_item` rows. No reversal entry is created (future work).
Backend support:
- `BasePosting`
- `InvoicePosting`
- `CreditNotePosting`
- `PurchaseInvoicePosting`
- `SupplierCreditNotePosting`
- `ReceiptPosting`
- `PaymentPosting`
- `GlManager::post()`
- `GlManager::replace()`
> **Planned — not yet implemented:** `PostingManager` (`app/assets/utils/classes/PostingManager.php`) is a stub intended as the sole bridge between WMS engine files and the accounting engine. WMS endpoints would call `PostingManager::postInvoice()` etc. instead of importing accounting classes directly. All methods are TODO — GL posting is currently triggered manually from `gl_entries.php`, not automatically from WMS saves.
## Batch GL Entries
Routes:
- `app/accounting/gl_entries.php`
- `app/accounting/api/engine/get_gl_documents.php`
- `app/accounting/api/engine/post_gl_entry.php`
- `app/accounting/api/engine/acquire_op_lock.php`
- `app/accounting/api/engine/release_op_lock.php`
- `app/accounting/api/engine/log_batch_action.php`
Batch GL Entries load documents by date range and formula, then post or replace GL entries one document at a time. A DB lock prevents overlapping batch posting jobs across tabs/users.
Current safeguards:
- Requires formula selection.
- Uses operation locks for `gl_post` via `OperationLockManager` — prevents two users running overlapping batch jobs.
- Uses retry behavior through `batch_process()`.
- Blocks Run when Product Account Mapping is incomplete for selected formula/document set.
- Shows Product Accounts status per loaded invoice document.
- Logs batch totals and failed IDs via `BatchActionManager` — records start time, end time, total processed, failed IDs, and result message into `batch_action_log`.
Backend support:
- `OperationLockManager`
- `BatchActionManager::log(array $data)`
## Posting Window
Routes:
- `app/accounting/posting_window.php`
- `app/accounting/api/engine/posting_window.php`
The posting window controls which dates are open for accounting postings and approved inventory entries. It supports open-from and open-to boundaries.
Backend support:
- `CompanySettingManager`
- `PostingWindowGuard::assertOpenDate()`
- `GlManager` posting validation
- `ReceiptManager` and `PaymentManager` date validation
## Financial Reports
Routes:
- `app/accounting/trial_balance.php`
- `app/accounting/pl_statement.php`
- `app/accounting/balance_sheet.php`
- `app/accounting/gl_movement.php`
- `app/accounting/vat_report.php`
- `app/accounting/api/engine/get_trial_balance.php`
- `app/accounting/api/engine/get_pl_statement.php`
- `app/accounting/api/engine/get_balance_sheet.php`
- `app/accounting/api/engine/get_gl_movement.php`
- `app/accounting/api/engine/get_vat_report.php`
Reports are generated from `td_gl`, `td_gl_item`, `md_account`, and optional departments.
Report behavior:
- Date filters use full dates (`YYYY-MM-DD` payload, `dd/mm/yyyy` UI).
- Trial Balance shows opening, movement, and closing balances.
- P&L shows revenue, expenses, and net profit.
- Balance Sheet shows asset, liability, equity, and retained earnings.
- GL Movement shows account-level journal movement.
- VAT Report uses account categories such as `sales_tax` and `purchase_tax`.
Backend support:
- `FinancialReports`
- `TaxReportManager`
+97
View File
@@ -0,0 +1,97 @@
# Helper Endpoint Contracts
This spec covers helper, lookup, search, and stats endpoints that support forms and dashboards. The main business save/delete/status endpoints are covered in the WMS, Accounting, Master Data, System Settings, and Document Lifecycle specs.
## Common Contract
Unless a route explicitly differs, helper endpoints:
- Load authenticated context through `db_auth.php`.
- Scope reads by the active `company_id`.
- Return JSON.
- Are consumed by AJAX from PHP pages.
- Should not mutate business documents unless their name says `manage`, `remove`, `delete`, `approve`, `confirm`, `cancel`, `void`, `issue`, `post`, `receive`, or `update`.
## ICS Search And Lookup
| Endpoint | Purpose | Main consumers |
|---|---|---|
| `app/ics/api/engine/contact_search.php` | Search contacts for stock/document forms. | Stock in/out/transfer and document source forms. |
| `app/ics/api/engine/product_search.php` | Search active products/SKUs for stock forms. | Stock in/out/transfer forms. |
| `app/ics/api/engine/barcode_lookup.php` | Resolve scanned barcode into product, lot/serial, or location context. | Scanner workflows and barcode-enabled forms. |
| `app/ics/api/engine/validate_scan_location.php` | Validate scanned warehouse/zone/aisle/rack context. | Scanner workflows. |
| `app/ics/api/engine/retrieve_stock_by_source.php` | Return stock rows linked to a source document. | WMS document detail and approval/deletion context. |
## ICS Location Helpers
| Endpoint | Purpose |
|---|---|
| `app/ics/api/engine/retrieve_warehouse.php` | Return active warehouses available to the company. |
| `app/ics/api/engine/retrieve_zone.php` | Return zones for a warehouse. |
| `app/ics/api/engine/retrieve_aisle.php` | Return aisles for a warehouse/zone. |
| `app/ics/api/engine/retrieve_rack.php` | Return racks for a warehouse/zone/aisle. |
These endpoints are driven by warehouse/storage setup and company location mode. They are used to populate dependent dropdowns and validate manual location selection.
## ICS Lot And Serial Helpers
| Endpoint | Purpose |
|---|---|
| `app/ics/api/engine/retrieve_lot.php` | Return known lots for a product/SKU. |
| `app/ics/api/engine/retrieve_active_lot.php` | Return lots with active stock availability. |
| `app/ics/api/engine/retrieve_active_serial.php` | Return serial numbers with active stock availability. |
| `app/ics/api/engine/sku_label_lots.php` | Return lots available for SKU label generation. |
| `app/ics/api/engine/sku_label_products.php` | Return products available for SKU label generation. |
## Stats Endpoints
| Endpoint | Purpose |
|---|---|
| `app/accounting/api/engine/account_stats.php` | Chart of accounts summary cards/statistics. |
| `app/accounting/api/engine/department_stats.php` | Department summary cards/statistics. |
| `app/contact/api/engine/contact_stats.php` | Contact summary cards/statistics. |
| `app/inventory/api/engine/product_stats.php` | Product summary cards/statistics. |
| `app/inventory/api/engine/warehouse_stats.php` | Warehouse summary cards/statistics. |
| `app/ics/api/engine_report/product_stats.php` | Stock/product operational statistics. |
| `app/ics/api/engine_report/warehouse_stats.php` | Stock overview warehouse statistics. |
| `app/dashboard/api/engine_report/reports_stats.php` | WMS dashboard cards and charts; gated by transaction limits. |
Stats endpoints are read-only dashboard/list-page support routes. They should follow the same quota behavior as their parent dashboard/report area where `UsageGuard` is applied.
## Retrieve Endpoints
Retrieve endpoints return one record or a bounded list needed by an edit form.
| Module | Endpoints |
|---|---|
| Contact | `retrieve_contact.php`, `retrieve_contact_type.php` |
| Inventory | `retrieve_category.php`, `retrieve_product.php`, `retrieve_storage.php`, `retrieve_warehouse.php` |
| Stock | `retrieve_stock_in.php`, `retrieve_stock_out.php`, `retrieve_stock_transfer.php` |
| Order | `retrieve_order.php`, `retrieve_invoice.php`, `retrieve_return.php` |
| PO | `retrieve_po.php`, `retrieve_supplier_return.php` |
| Revenue | `retrieve_quotation.php` |
| Expense | `retrieve_purchase_request.php` |
| Accounting | `retrieve_account.php`, `retrieve_department.php`, `get_journal_detail.php` |
| Settings | `retrieve_company.php`, `retrieve_profile.php`, `retrieve_smtp.php`, `retrieve_users.php` |
Expected behavior:
- Validate active company scope.
- Return only active positive-company rows unless the feature explicitly needs historical/deleted records.
- Avoid exposing rows from another company even when an ID is guessed.
## Generic Inventory Manager Endpoint
Endpoint: `app/inventory/api/engine/manager.php`
This route is an inventory utility endpoint guarded to owner/admin roles. It belongs with product/category/warehouse/storage maintenance and should not be used for unauthenticated or cross-module access.
## Endpoint Documentation Rule
When adding a new helper endpoint, update this file if the endpoint is not already clearly covered by a feature spec. Include:
- Endpoint path.
- Read/write behavior.
- Main consumer page.
- Important company/role/limit guards.
- Payload shape when frontend code depends on exact fields.
+108
View File
@@ -0,0 +1,108 @@
# Runtime Architecture
This spec covers runtime files that are not owned by a single business module.
## Entry Points
| File | Role |
|---|---|
| `index.php` | Root redirect to `landing/` using `$base_url` from `app/config.php`. |
| `app/index.php` | Authenticated app landing/router entry after login. |
| `app/session.php` | Session bootstrap and session guard helpers. |
| `app/dbconn.php` | Database connection setup for auth and business databases. |
| `app/preset.php` | Shared app initialization values used by pages. |
| `app/include_header.php` | Shared HTML head, CSS/JS includes, and page shell dependencies. |
| `app/include_topbar.php` | Topbar, branch/company switcher, usage warning badge, and session-visible user context. |
| `app/include_sidebar.php` | WMS navigation. |
| `app/include_sidebar_ac.php` | Accounting navigation. |
| `app/include_master_sidebar.php` | Master-data navigation. |
| `app/include_setting_sidebar.php` | Settings navigation. |
| `app/include_ending.php` | Shared closing scripts and Socket.IO initialization. |
## Configuration
`app/config.php` defines database credentials, base URLs, encryption settings, package tiers, and runtime secrets. `app/config.example.php` is the template for new environments.
Important runtime values:
- `$base_url` - public app base URL used for redirects and links.
- `$server_url` - app URL prefix used by PHP pages for AJAX routes.
- `$db_database` - auth/identity database.
- `$db_database2` - business data database.
- `$packages` - package tier limits used by `UsageGuard`.
- Node emit/cron secrets - shared with the Node.js runtime through environment variables.
## Database Connections
The app uses two MySQL databases:
| Database | Purpose |
|---|---|
| Auth database | Users, companies, company-user mapping, package usage, SMTP, company settings, schema migration records. |
| Business database | Products, warehouses, contacts, stock, sales/purchase/finance documents, GL, reports, barcode labels, operation locks. |
Most authenticated endpoints load `app/assets/utils/db_auth.php`, which establishes session context, validates company access, validates CSRF for writes, and exposes `$pdo1`, `$pdo2`, `$company_id`, `$user_id`, and `$user_role`.
## Node.js Realtime Server
File: `nodejs/server.js`
The Node.js process hosts the Socket.IO server and a protected HTTP `/emit` endpoint.
Flow:
1. PHP calls `/emit` after a successful database commit.
2. The request must include the `x-emit-secret` header matching `EMIT_SECRET`.
3. Body contains `{ event, data, company_id }`.
4. Node broadcasts to room `company_{company_id}`.
5. Browser tabs join their company room through the Socket.IO client initialized in `app/include_ending.php`.
Environment variables:
| Variable | Purpose |
|---|---|
| `PORT` | Node HTTP/Socket.IO port; defaults to `3000`. |
| `EMIT_SECRET` | Shared secret for PHP-to-Node event emission and cron calls. |
## Node.js Cron Scheduler
File: `nodejs/scheduler.js`
The scheduler runs alongside the realtime server process and calls PHP cron endpoints over HTTP. It authenticates with the `x-cron-secret` header using `EMIT_SECRET`.
Environment variables:
| Variable | Purpose |
|---|---|
| `PHP_BASE_URL` | Base URL for the PHP host; defaults to `http://127.0.0.1`. |
| `PHP_WEBROOT` | PHP app path; defaults to `/mn3wms/app`. |
| `EMIT_SECRET` | Shared cron secret. |
Schedules:
| Schedule | Endpoint | Purpose |
|---|---|---|
| `0 * * * *` | `/cron/etl_gl_maintenance.php` | Repair GL summary gaps for companies in the current hourly slot. |
| `30 * * * *` | `/cron/etl_stock_maintenance.php` | Repair stock summary gaps for companies in the current hourly slot. |
Both jobs use `company_id % 24` slotting so each company is checked at most once per hour and work is spread across the day.
## Setup Script
File: `setup.php`
`setup.php` is a CLI-only production bootstrap script. HTTP requests receive `403` and the script exits.
Behavior:
1. Loads database credentials from `app/config.php`.
2. Connects to MySQL without selecting a database.
3. Creates the auth and business databases if they do not exist.
4. Creates core tables with `CREATE TABLE IF NOT EXISTS`.
5. Prints a terminal status report.
Dynamic stock tables named `td_stock_<warehouse_id>` are not created by `setup.php`; the app creates them when a warehouse is used.
## Migration State
The auth database includes a `schema_migrations` table, but this repository snapshot does not contain a `migrations/` directory. Schema setup is currently embedded in `setup.php` and runtime manager code.
+75
View File
@@ -0,0 +1,75 @@
# Setup And Landing Pages
This spec covers public entry, setup, and static marketing/legal pages outside the authenticated app.
## Root Redirect
Route: `index.php`
The repository root index loads `app/config.php`, reads `$base_url`, and redirects users to `landing/`.
## Landing Home
Route: `landing/index.php`
The landing page is a public marketing page for MN3 WMS. It uses shared landing includes and assets under `landing/includes`, `landing/css`, `landing/js`, and `landing/images`.
Primary behavior:
- Builds `$base` from `$base_url . '/landing'`.
- Builds `$app_url` from `$base_url . '/app'`.
- Shows product positioning for warehouse, stock, fulfillment, barcode, and reporting workflows.
- Links primary calls to action to `app/login/index.php`.
- Includes public navigation, footer, and landing scripts.
## Legal Pages
Routes:
- `landing/privacy.php`
- `landing/terms.php`
Both pages load `app/config.php`, landing head/nav/footer includes, and render static legal copy.
Privacy page sections:
- Information collected.
- How information is used.
- Session/authentication data.
- Company data isolation.
- Data retention.
- Security.
- Cookies.
- User rights.
- Policy changes.
- Contact email.
Terms page sections:
- Use of service.
- Account security.
- Data ownership.
- Acceptable use.
- Service availability.
- Limitation of liability.
- Term changes.
- Contact email.
## Setup Script
Route/file: `setup.php`
`setup.php` is not a web page. It is intentionally CLI-only:
```bash
php setup.php
```
If accessed over HTTP, it returns `403` with the message `Run via CLI only: php setup.php`.
The setup script creates the configured databases and core tables if they do not already exist. It is safe to rerun for table creation because it uses `CREATE TABLE IF NOT EXISTS`, but it is not a migration runner for arbitrary schema changes.
## Out Of Scope
- Authenticated app routing is covered by `runtime-architecture.md` and the feature specs for each module.
- Business setup after registration/onboarding is covered by `system-settings.md` and the master-data/accounting specs.
+224
View File
@@ -0,0 +1,224 @@
# System And Settings Features
## Authentication
Routes:
- `app/login/index.php`
- `app/login/register.php`
- `app/login/verify.php`
- `app/login/forgot_password.php`
- `app/login/onboarding.php`
- `app/login/api/engine/*`
Authentication supports registration, login, OTP confirmation, password reset, onboarding, and app branch selection.
Backend support:
- `PasswordManager`
- `PasswordResetManager`
- Login APIs under `app/login/api/engine`
## Session And Security
Core files:
- `app/session.php`
- `app/assets/utils/db_auth.php`
- `app/assets/utils/db_helpers.php`
Security behavior:
- Authenticated session required for normal API routes.
- POST routes validate CSRF token.
- OTP freshness is checked against the user's current password.
- Company access is validated through `company_map_user`.
- API payloads are accepted as JSON-wrapped POST or FormData.
- Role checks use `require_role()`.
## User Management
Routes:
- `app/setting/users.php`
- `app/setting/api/engine/manage_users.php`
- `app/setting/api/engine/search_users.php`
- `app/setting/api/engine/retrieve_users.php`
User management supports company users, invitation, role updates, and user removal.
Backend support:
- `UserManager::getCompanyUsers()`
- `UserManager::searchUsers()`
- `UserManager::inviteUser()`
- `UserManager::updateRole()`
- `UserManager::removeUser()`
## Profile And Password
Routes:
- `app/setting/profile.php`
- `app/setting/api/engine/manage_profile.php`
- `app/setting/api/engine/change_password.php`
- `app/setting/api/engine/request_reset_otp.php`
- `app/setting/api/engine/reset_password_otp.php`
Users can maintain profile details and change/reset passwords.
Backend support:
- `UserManager::getProfile()`
- Password reset managers and setting APIs
## Company Settings
Routes:
- `app/setting/company.php`
- `app/setting/system_config.php`
- `app/setting/api/engine/company_setting.php`
- `app/setting/api/engine/manage_company.php`
- `app/setting/api/engine/retrieve_company.php`
Company settings store transaction configuration, posting windows, system behavior toggles, branch/company details, and setup values.
System configuration includes:
- Advanced Location: simple location mode or three-level zone/aisle/rack mode.
- Location Labels: custom labels for warehouse location levels.
- Default Stock Status: draft or auto-approved stock entries.
- Auto-Complete on Shipped: whether shipped orders become completed automatically.
- Invoice and Credit Note Generation: automatic invoice on order confirmation and credit note on return confirmation.
- Setting Locks: selected settings become locked after dependent transactions exist.
Backend support:
- `CompanySettingManager`
## SMTP Settings
Routes:
- `app/setting/smtp.php`
- `app/setting/api/engine/manage_smtp.php`
- `app/setting/api/engine/retrieve_smtp.php`
- `app/setting/api/engine/test_smtp.php`
- `app/assets/utils/module/mailer.php`
SMTP settings configure outbound email and include a test route.
## Branch Switching
Routes:
- `app/setting/api/engine/switch_branch.php`
- `app/include_topbar.php`
Branch switching changes the active company/context for a logged-in user when they have access to more than one company.
Backend support:
- `UserManager::getCompanyList()`
## Shared JavaScript Utilities
File:
- `app/assets/js/custom.js`
Shared frontend utilities include:
- `ajax_request()` with CSRF headers, loading overlay, queue lock, JSON payload preparation, and FormData handling.
- `generate_pagination()` for table paging.
- `batch_process()` for retryable sequential batch jobs.
- Operation lock helpers (`acquire_op_lock` / `release_op_lock`) for long-running cross-tab operations — wraps `OperationLockManager` (see below).
- Account autocomplete for posting-account selection.
- Number formatting that avoids scientific notation and suppresses tiny floating point noise.
## Operation Locks
Routes:
- `app/accounting/api/engine/acquire_op_lock.php`
- `app/accounting/api/engine/release_op_lock.php`
Operation locks prevent two users (or two browser tabs) from running the same long-running operation simultaneously. A lock is a DB row in `operation_locks` with a `company_id`, `operation_type`, `user_id`, and expiry timestamp.
Behavior:
- `acquire()` inserts or refreshes a lock row. Returns `{ acquired: true }` if successful or `{ acquired: false, held_by: '...' }` if another user holds it.
- `release()` deletes the lock row for the calling user.
- Locks have a configurable TTL (default 120 minutes). Expired locks are treated as unowned — any user can acquire them even without an explicit release.
Currently used for:
- `gl_post` — Batch GL posting in `accounting/gl_entries.php`. Prevents two users from running overlapping batch jobs against the same company's GL.
Backend support:
- `OperationLockManager::acquire(string $operation_type, int $ttl_minutes = 120)`
- `OperationLockManager::release(string $operation_type)`
## ETL Summary Maintenance
Routes:
- `app/setting/gl_maintenance.php`
- `app/setting/stock_maintenance.php`
- `app/setting/api/engine/gl_maintenance.php`
- `app/setting/api/engine/stock_maintenance.php`
- `app/cron/etl_gl_maintenance.php`
- `app/cron/etl_stock_maintenance.php`
Two pre-aggregated summary tables accelerate dashboard and report queries:
- `etl_gl_summary` — monthly GL totals per account, fed by every GL post/delete. Used by the accounting dashboard.
- `etl_stock_summary` — monthly stock in/out totals per warehouse/product, fed by `WarehouseManager::adjustBalance()`. Used by the WMS dashboard and stock overview.
Each summary table is maintained write-through on every transaction. The maintenance system provides a safety net for periods that drift out of sync due to crashes, rollbacks, or manual DB edits.
**Gap detection** compares `MAX(source_updated_at)` in the raw source table against the summary table's `source_updated_at` per period. A mismatch means the summary is stale and needs repair.
**Automatic repair** runs via the Node.js cron scheduler once per hour. Companies are spread across 24 hourly slots using `company_id % 24` to distribute load. GL runs at `:00` and stock runs at `:30` past each hour.
**Manual rebuild** is available in the maintenance UI. The page shows the cron schedule slot, last run result, and a live count of current gaps. A full rebuild drops and regenerates all periods for the company.
Backend support:
- `EtlManager` — GL ETL (gap detection, period repair, full rebuild, status)
- `EtlStockManager` — Stock ETL (same interface, works across dynamic `td_stock_<warehouse_id>` tables)
## Node.js Cron Scheduler
File:
- `nodejs/scheduler.js`
The scheduler runs inside the Node.js process alongside the Socket.IO server. It uses `node-cron` to call PHP cron endpoints over HTTP on a fixed schedule. The shared `x-cron-secret` header authenticates each call.
Current schedules:
| Schedule | Endpoint | Purpose |
|---|---|---|
| `0 * * * *` (every hour, `:00`) | `/cron/etl_gl_maintenance.php` | GL summary gap repair for slot = current hour |
| `30 * * * *` (every hour, `:30`) | `/cron/etl_stock_maintenance.php` | Stock summary gap repair for slot = current hour |
Each cron run processes only the companies whose `company_id % 24` equals the current hour slot — no company is processed more than once per hour.
## Database Schema Setup
Files:
- `setup.php`
The auth database includes a `schema_migrations` table, but this repository snapshot does not contain a `migrations/` directory or `migrate.php`. Fresh installation schema creation is currently handled by the CLI-only `setup.php` script, with some runtime-created structures such as dynamic `td_stock_<warehouse_id>` tables.
## File Uploads
File:
- `app/assets/utils/classes/FileUploader.php`
File upload support is used by product/contact/company/profile flows. It handles directory creation, uploaded file lists, cleanup of replaced files, and rollback on failure.
+143
View File
@@ -0,0 +1,143 @@
# Transaction Limits
## Overview
Each company is assigned a package tier that defines how many documents they can create per day and per week. When a quota is hit, report endpoints return HTTP 402 — operations (create, edit, approve) are never blocked.
---
## Package Tiers
Defined in `app/config.php` under `$packages`. Edit the array directly to adjust quotas or add tiers — no migration required.
| Package | Daily limit | Weekly limit | Locked on limit |
|----------|-------------|--------------|---------------------|
| `free` | 10 | 30 | dashboard, reports |
| `starter`| 30 | 100 | dashboard, reports |
| `growth` | 150 | 500 | dashboard, reports |
| `pro` | unlimited | unlimited | nothing |
`lock_on_limit` is an array of feature group keys. When either limit is hit, any endpoint that calls `assertFeatureAccessible('<key>')` returns 402 for that company until the quota resets.
A company's package is stored in `wms.company_list.package` (default `'starter'`). Change it with:
```sql
UPDATE company_list SET package = 'growth' WHERE company_id = 1;
```
---
## What Counts as a Transaction
One document created = one count. Updates to existing documents do not count.
| Endpoint | Document type |
|---|---|
| `ics/api/engine/manage_stock_in.php` | Stock In |
| `ics/api/engine/manage_stock_out.php` | Stock Out |
| `ics/api/engine/manage_stock_transfer.php` | Stock Transfer |
| `order/api/engine/manage_order.php` | WMS Sales Order |
| `order/api/engine/confirm_order.php` | Auto-created WMS Invoice on confirm |
| `order/api/engine/manage_invoice.php` | WMS Invoice / Credit Note |
| `order/api/engine/proceed_to_invoice.php` | WMS Invoice from Sales Order |
| `order/api/engine/manage_return.php` | Customer Return |
| `order/api/engine/confirm_return.php` | Auto-created Credit Note on return confirm |
| `order/api/engine/proceed_to_credit_note.php` | Credit Note from Customer Return |
| `po/api/engine/manage_po.php` | Purchase Order |
| `po/api/engine/manage_supplier_return.php` | Supplier Return |
| `po/api/engine/confirm_supplier_return.php` | Auto-created Supplier Credit Note on supplier return confirm |
| `po/api/engine/proceed_to_purchase_invoice.php` | Purchase Invoice from Purchase Order |
| `po/api/engine/proceed_to_supplier_credit_note.php` | Supplier Credit Note from Supplier Return |
| `revenue/api/engine/manage_order.php` | Revenue Sales Order |
| `revenue/api/engine/proceed_to_invoice.php` | Revenue Invoice from Sales Order |
| `revenue/api/engine/manage_credit_note.php` | Customer Credit Note |
| `revenue/api/engine/manage_quotation.php` | Quotation |
| `finance/api/engine/manage_receipt_billing.php` | Receipt Billing |
| `finance/api/engine/manage_receipt.php` | Receipt |
| `finance/api/engine/manage_payment_billing.php` | Payment Billing |
| `finance/api/engine/manage_payment.php` | Payment |
| `expense/api/engine/manage_purchase_request.php` | Purchase Request |
| `expense/api/engine/manage_supplier_credit_note.php` | Supplier Credit Note |
---
## What Gets Locked
Two feature group keys are defined:
**`dashboard`** — gated on dashboard/report summary endpoints. The low-stock detail endpoint and accounting posting-window endpoint are not gated:
- `dashboard/api/engine_report/reports_stats.php` (WMS dashboard)
- `ac_dashboard/api/engine/by_source.php`
- `ac_dashboard/api/engine/journals.php`
- `ac_dashboard/api/engine/pl.php`
- `ac_dashboard/api/engine/recent.php`
- `ac_dashboard/api/engine/trend.php`
**`reports`** — gated on all report endpoints:
- `accounting/api/engine/get_trial_balance.php`
- `accounting/api/engine/get_pl_statement.php`
- `accounting/api/engine/get_balance_sheet.php`
- `accounting/api/engine/get_gl_movement.php`
- `accounting/api/engine/get_vat_report.php`
- `reports/api/engine_report/stock_movement.php`
- `reports/api/engine_report/stock_movement_sku.php`
- `reports/api/engine_report/expired_stock.php`
- `reports/api/engine_report/lot_stock_log.php`
- `reports/api/engine_report/product_lot.php`
- `reports/api/engine_report/rack_log.php`
- `reports/api/engine_report/rack_occupancy.php`
---
## How Quotas Reset
- **Daily** — resets at midnight each day. `company_usage` stores one row per company per `day_date`; a new day is a new row with count 0.
- **Weekly** — resets at 00:00 Monday. The weekly count is the SUM of `daily_count` for all rows from Monday to today.
---
## Database
`wms.company_usage` — one row per company per day:
```sql
CREATE TABLE company_usage (
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
company_id INT(11) NOT NULL,
day_date DATE NOT NULL,
daily_count INT(11) NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY uq_company_day (company_id, day_date),
KEY idx_company_id (company_id)
);
```
---
## Key Files
| File | Role |
|---|---|
| `app/config.php` | `$packages` array — all tier definitions |
| `app/assets/utils/classes/UsageGuard.php` | `increment()`, `assertFeatureAccessible()`, `getStatus()` |
| `app/include_topbar.php` | Reads `getStatus()` on every page load; renders warning badge |
| `app/assets/js/custom.js` | Global 402 handler in `ajax_request` — shows usage detail alert |
---
## Topbar Warning Badge
`include_topbar.php` calls `UsageGuard::getStatus()` on every page load and renders a badge in the nav bar:
- **≥ 80% of either limit** — amber badge showing percentage
- **≥ 100%** — red badge "Limit reached — reports locked"
Hovering the badge shows raw counts: `Daily: 28/30 | Weekly: 87/100`.
---
## Adding a New Gated Feature
1. Pick a key name (e.g. `'export'`).
2. Add it to `lock_on_limit` arrays in the relevant package tiers in `config.php`.
3. Add `(new UsageGuard($pdo1, $company_id, $packages))->assertFeatureAccessible('export');` at the top of the target endpoint(s).