- C1: verify.php now filters license='owner' — invite tokens no longer accepted
- C1: onboarding API rejects non-owner sessions
- C2: Existing-user invite requires explicit acceptance via accept_invite.php
- C2: New accept_invite.php page and API engine added
- C2: inviteUser() generates token + expiry for existing users; resendInvite() handles active users
- C3: session_regenerate_id(true) before writing invite session keys on both invite pages
- C4: invited_onboarding API wraps activation in transaction with SELECT FOR UPDATE; rowCount check added; SQLSTATE 23000 caught cleanly
- C5: inviteUser() and resendInvite() two-table writes wrapped in transactions
- M2: removeUser() wrapped in transaction with FOR UPDATE; clears default_company on active user removal
- M4: Logged-in user guard added to invited_onboarding.php and accept_invite.php
- M5: manage_users.php uses $server_url instead of HTTP_HOST for invite URLs
- M6: Username regex enforces 3-32 chars; reserved name blocklist added
- N5: searchUsers() changed from LIKE fuzzy search to exact email match only
- N7: resendInvite() rate-limited to once per 60s via invite_resent_at column
- Schema: company_map_user gains invite_expires_at and invite_resent_at columns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Upgraded all plain `require` to `require_once` across 172 api/engine
and api/engine_report files to prevent class-redeclaration errors
- Added issue button, issue_invoice() with GL toastr, and delete_invoice()
to expense/manage_purchase_invoice.php, bringing it in line with
po/manage_purchase_invoice.php
- Added can_delete role guard (admin/owner only) to trash icons on
revenue/invoice.php and expense/purchase_invoice.php, matching the
existing pattern in finance/receipt.php and finance/payment.php
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates wms + wms2 databases and all 54 tables from scratch using
CREATE TABLE IF NOT EXISTS. Reads credentials from app/config.php.
Safe to re-run (idempotent). CLI-only guard prevents web access.
Dynamic td_stock_<warehouse_id> tables are excluded — the app creates
them automatically on first warehouse use.
Run: php setup.php
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- StockManager: coerce lot_number/serial_number to trimmed string (fixes
Array-to-string warnings); validate quantity > 0 on insert; tighten
transfer pair lookup to match in/out side by column value
- PostingWindowGuard: strip time component from datetime strings before
date-format validation
- docs/tests/doc_flow_test.php: 32-assertion document flow suite covering
Stock In create + approve, Sales Order draft/confirm, Invoice from Order
(with duplicate-block check), PO create/confirm, Quotation create, and
usage increment wiring check; all 32/32 PASS against wms_codex_test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>