Seal transaction limit coverage gaps
This commit is contained in:
@@ -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).
|
||||
Reference in New Issue
Block a user