Q-Putaway – Architecture
Stack
Core PHP (7.4–8.x, no framework), PDO/MySQL (InnoDB, utf8mb4), Bootstrap 5.3, jQuery 3.7, Chart.js 4, ZXing (camera decoding fallback), JsBarcode and qrcode-generator (labels). All front-end libraries are vendored under assets/vendor/ (MIT licences), so no CDN is needed and a strict Content-Security-Policy works. No Node.js, Redis, workers or WebSockets are required in production.
Directory layout (no public/ folder)
index.php Front controller (all requests rewritten here by .htaccess)
.htaccess Rewrite + blocks private folders
assets/ CSS, JS (app.js core, scanner.js, pages/*.js), vendor libs, icons
app/
bootstrap.php Constants, autoloader (PSR-4 QP\ → app/), config, error handler
version.php QP_VERSION / QP_SCHEMA_VERSION
Core/ Config, DB, Router, Request, Response, Session, Csrf, Auth, View, Validator,
Audit, Crypto, Settings, Totp, Mailer, Csv, Xlsx, Grid, DocNumber, Idempotency,
Migrator, Installer, Markdown, HtmlSanitizer, Logger, ErrorHandler
Controllers/ One controller per module (thin: input → service → JSON/view)
Services/ Business logic (InventoryService is the only code that changes stock)
Masters/definitions.php Definition-driven master data (forms, grids, validation, CSV)
Reports/definitions.php Report catalogue
routes/*.php Route tables per module (permissions declared per route)
navigation.php Menu
permissions.php Permission catalogue (module × action)
views/ PHP templates (layouts, components, module views, emails)
database/migrations/ Ordered SQL migrations (additive only)
database/seeds/ Default roles
config/ config.php (generated; private)
storage/ logs, uploads, exports, backups, temporary, cache (private)
cron/cron.php Scheduler entry point
tests/ unit, integration, security, concurrency, http
tools/ dev router, icon generator, update signing tools
docs/ Standalone documentation + help-articles/ (seeded into the Help Center)
Request flow
index.php → security headers → HTTPS redirect → session → (installer redirect if not installed) → Router matches the route → middleware (authentication, CSRF for POST, perm any-of, super, reauth) → controller → service → Response::ok() JSON or View page.
JSON responses: {"ok": true, "data": …, "message": "…"} or {"ok": false, "error": "…", "errors": {field: msg}} with HTTP 401/403/404/405/409/419/422/500. Production responses never include SQL errors or stack traces; errors are logged with a reference code.
Inventory model
stock_movements: append-only ledger. Each row movesqty(base units, > 0) from (from_location,from_status) to (to_location,to_status). A NULL source means inbound into the company and a NULL destination means outbound. Reservations are recorded as from = to rows (net zero).stock_balances: one row per (location, product, batch, status) withqty_on_handandqty_reserved, updated in the same transaction as the ledger row.stock_reservations: batch-level allocations to order lines (ACTIVE→PICKED/RELEASED).- Statuses:
AVAILABLE,QUARANTINE,DAMAGED,PICKED(staging, awaiting dispatch) andIN_TRANSIT(destination warehouse's TRANSIT location). Expired is derived from the batch expiry date. - Integrity: InnoDB transactions;
SELECT … FOR UPDATEon source balances, orders, order lines, reservations, tasks and document headers; idempotency keys (X-Idempotency-Key) on every stock-changing endpoint; negative stock refused unless theallow_negative_stocksetting is on (and even then only for counts and adjustments). InventoryService::reconcile()recomputes balances and reservations from the ledger. The tests and the Reconcile page use it.
See docs/inventory-and-fefo.md for states and FEFO rules.
Security architecture
See docs/security.md. In summary: prepared statements everywhere; output escaping (e() / QP.esc); CSRF synchronizer tokens; server-side permission + warehouse scope on every route and query; strict CSP (no inline scripts); AES-256-GCM for secrets with keys in config/config.php; password_hash; selector/validator remember-me tokens; TOTP 2FA; audit log.
Front-end conventions
QP.api(method, path, data, opts): fetch wrapper that sends the CSRF header and idempotency keys, disables buttons, shows field errors and never reports success before the server confirms.QP.grid: server-side paginated tables (search, filters, sorting, card layout on phones).QP.poll: visibility-aware polling with no overlapping requests.QP.Scanner: camera scanner (native BarcodeDetector → ZXing fallback).data-scan-inputfields support keyboard-wedge scanners.- Page data is passed in
<script type="application/json" id="page-data">. CSP forbids inline executable scripts.
PWA
/manifest.webmanifest and /service-worker.js are generated by PwaController (branding, version hash). Static assets are cached per version (cache-first). Pages and APIs are network-only and are never cached. Updates wait for the user (SKIP_WAITING message from the update banner).
Extending
- New master: add a definition to
app/Masters/definitions.php(plus a migration for the table) to get CRUD, CSV template, import and export. - New report: add an entry to
app/Reports/definitions.php. - Schema change: add
database/migrations/NNN_name.sql(additive). The updater and installer apply pending migrations.