From 1a952b42dd0f83514a2357727d25c7d7c814cb97 Mon Sep 17 00:00:00 2001 From: Thanakorn S Date: Tue, 26 May 2026 13:10:54 +0700 Subject: [PATCH] Seal transaction limit coverage gaps --- .../api/engine/manage_purchase_request.php | 2 + .../engine/manage_supplier_credit_note.php | 2 + .../api/engine/manage_payment_billing.php | 2 + .../api/engine/manage_receipt_billing.php | 2 + app/ics/api/engine/manage_stock_in.php | 4 +- app/ics/api/engine/manage_stock_out.php | 4 +- app/ics/api/engine/manage_stock_transfer.php | 4 +- app/order/api/engine/confirm_order.php | 2 + app/order/api/engine/confirm_return.php | 2 + app/order/api/engine/manage_return.php | 2 + .../api/engine/proceed_to_credit_note.php | 2 + app/order/api/engine/proceed_to_invoice.php | 2 + app/po/api/engine/confirm_supplier_return.php | 4 + app/po/api/engine/manage_supplier_return.php | 8 +- .../engine/proceed_to_purchase_invoice.php | 2 + .../proceed_to_supplier_credit_note.php | 2 + app/revenue/api/engine/manage_credit_note.php | 2 + app/revenue/api/engine/proceed_to_invoice.php | 2 + docs/reviewed/README.md | 53 +++ docs/reviewed/coverage-matrix.md | 138 ++++++++ docs/reviewed/live-dashboard.md | 206 ++++++++++++ docs/reviewing/accounting.md | 308 ++++++++++++++++++ docs/reviewing/helper-endpoints.md | 97 ++++++ docs/reviewing/runtime-architecture.md | 108 ++++++ docs/reviewing/setup-landing.md | 75 +++++ docs/reviewing/system-settings.md | 224 +++++++++++++ docs/reviewing/transaction-limits.md | 143 ++++++++ 27 files changed, 1397 insertions(+), 5 deletions(-) create mode 100644 docs/reviewed/README.md create mode 100644 docs/reviewed/coverage-matrix.md create mode 100644 docs/reviewed/live-dashboard.md create mode 100644 docs/reviewing/accounting.md create mode 100644 docs/reviewing/helper-endpoints.md create mode 100644 docs/reviewing/runtime-architecture.md create mode 100644 docs/reviewing/setup-landing.md create mode 100644 docs/reviewing/system-settings.md create mode 100644 docs/reviewing/transaction-limits.md diff --git a/app/expense/api/engine/manage_purchase_request.php b/app/expense/api/engine/manage_purchase_request.php index bc6a6bf..1499492 100644 --- a/app/expense/api/engine/manage_purchase_request.php +++ b/app/expense/api/engine/manage_purchase_request.php @@ -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; diff --git a/app/expense/api/engine/manage_supplier_credit_note.php b/app/expense/api/engine/manage_supplier_credit_note.php index 2cf9a9e..3241d16 100644 --- a/app/expense/api/engine/manage_supplier_credit_note.php +++ b/app/expense/api/engine/manage_supplier_credit_note.php @@ -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.'; diff --git a/app/finance/api/engine/manage_payment_billing.php b/app/finance/api/engine/manage_payment_billing.php index 23b4992..bc3c0c1 100644 --- a/app/finance/api/engine/manage_payment_billing.php +++ b/app/finance/api/engine/manage_payment_billing.php @@ -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)); } diff --git a/app/finance/api/engine/manage_receipt_billing.php b/app/finance/api/engine/manage_receipt_billing.php index 197d559..e535e5a 100644 --- a/app/finance/api/engine/manage_receipt_billing.php +++ b/app/finance/api/engine/manage_receipt_billing.php @@ -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)); } diff --git a/app/ics/api/engine/manage_stock_in.php b/app/ics/api/engine/manage_stock_in.php index 8e0706b..15e64b0 100644 --- a/app/ics/api/engine/manage_stock_in.php +++ b/app/ics/api/engine/manage_stock_in.php @@ -30,7 +30,9 @@ $answer['success'] = 1; $answer['auto_approved'] = $auto_approve; - (new UsageGuard($pdo1, $company_id, $packages))->increment(); + if ($new_id > 0) { + (new UsageGuard($pdo1, $company_id, $packages))->increment(); + } } catch (PDOException $e) { $answer['success'] = 0; diff --git a/app/ics/api/engine/manage_stock_out.php b/app/ics/api/engine/manage_stock_out.php index 0d750d6..c344f87 100644 --- a/app/ics/api/engine/manage_stock_out.php +++ b/app/ics/api/engine/manage_stock_out.php @@ -30,7 +30,9 @@ $answer['success'] = 1; $answer['auto_approved'] = $auto_approve; - (new UsageGuard($pdo1, $company_id, $packages))->increment(); + if ($new_id > 0) { + (new UsageGuard($pdo1, $company_id, $packages))->increment(); + } } catch (PDOException $e) { $answer['success'] = 0; diff --git a/app/ics/api/engine/manage_stock_transfer.php b/app/ics/api/engine/manage_stock_transfer.php index 2afc44b..b63b209 100644 --- a/app/ics/api/engine/manage_stock_transfer.php +++ b/app/ics/api/engine/manage_stock_transfer.php @@ -31,7 +31,9 @@ $answer['success'] = 1; $answer['auto_approved'] = $auto_approve; - (new UsageGuard($pdo1, $company_id, $packages))->increment(); + if ($new_id > 0) { + (new UsageGuard($pdo1, $company_id, $packages))->increment(); + } } catch (PDOException $e) { $answer['success'] = 0; diff --git a/app/order/api/engine/confirm_order.php b/app/order/api/engine/confirm_order.php index 7c5e2c6..4a3a785 100644 --- a/app/order/api/engine/confirm_order.php +++ b/app/order/api/engine/confirm_order.php @@ -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); } diff --git a/app/order/api/engine/confirm_return.php b/app/order/api/engine/confirm_return.php index bde481c..6cb47e7 100644 --- a/app/order/api/engine/confirm_return.php +++ b/app/order/api/engine/confirm_return.php @@ -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); } diff --git a/app/order/api/engine/manage_return.php b/app/order/api/engine/manage_return.php index ba75d2d..68d502a 100644 --- a/app/order/api/engine/manage_return.php +++ b/app/order/api/engine/manage_return.php @@ -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); diff --git a/app/order/api/engine/proceed_to_credit_note.php b/app/order/api/engine/proceed_to_credit_note.php index 53e3190..3e8dedc 100644 --- a/app/order/api/engine/proceed_to_credit_note.php +++ b/app/order/api/engine/proceed_to_credit_note.php @@ -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.'; diff --git a/app/order/api/engine/proceed_to_invoice.php b/app/order/api/engine/proceed_to_invoice.php index 3055a92..5d7c724 100644 --- a/app/order/api/engine/proceed_to_invoice.php +++ b/app/order/api/engine/proceed_to_invoice.php @@ -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) { diff --git a/app/po/api/engine/confirm_supplier_return.php b/app/po/api/engine/confirm_supplier_return.php index ee09570..c0396c7 100644 --- a/app/po/api/engine/confirm_supplier_return.php +++ b/app/po/api/engine/confirm_supplier_return.php @@ -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.'; diff --git a/app/po/api/engine/manage_supplier_return.php b/app/po/api/engine/manage_supplier_return.php index 20bc67c..e1edd52 100644 --- a/app/po/api/engine/manage_supplier_return.php +++ b/app/po/api/engine/manage_supplier_return.php @@ -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) { diff --git a/app/po/api/engine/proceed_to_purchase_invoice.php b/app/po/api/engine/proceed_to_purchase_invoice.php index e8cb9c6..d01566f 100644 --- a/app/po/api/engine/proceed_to_purchase_invoice.php +++ b/app/po/api/engine/proceed_to_purchase_invoice.php @@ -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.'; diff --git a/app/po/api/engine/proceed_to_supplier_credit_note.php b/app/po/api/engine/proceed_to_supplier_credit_note.php index 8ddccf4..7a444cb 100644 --- a/app/po/api/engine/proceed_to_supplier_credit_note.php +++ b/app/po/api/engine/proceed_to_supplier_credit_note.php @@ -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.'; diff --git a/app/revenue/api/engine/manage_credit_note.php b/app/revenue/api/engine/manage_credit_note.php index c237224..91a5c7a 100644 --- a/app/revenue/api/engine/manage_credit_note.php +++ b/app/revenue/api/engine/manage_credit_note.php @@ -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.'; diff --git a/app/revenue/api/engine/proceed_to_invoice.php b/app/revenue/api/engine/proceed_to_invoice.php index cb92042..99e7563 100644 --- a/app/revenue/api/engine/proceed_to_invoice.php +++ b/app/revenue/api/engine/proceed_to_invoice.php @@ -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) { diff --git a/docs/reviewed/README.md b/docs/reviewed/README.md new file mode 100644 index 0000000..16a64c0 --- /dev/null +++ b/docs/reviewed/README.md @@ -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. + diff --git a/docs/reviewed/coverage-matrix.md b/docs/reviewed/coverage-matrix.md new file mode 100644 index 0000000..0e85db8 --- /dev/null +++ b/docs/reviewed/coverage-matrix.md @@ -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. diff --git a/docs/reviewed/live-dashboard.md b/docs/reviewed/live-dashboard.md new file mode 100644 index 0000000..08c26e6 --- /dev/null +++ b/docs/reviewed/live-dashboard.md @@ -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 `