Backend:
- New tenants table with PSGC-derived code, links to
cities_municipalities, optional boundary_polygon override, theme
color, contact info, timezone.
- Adds nullable tenant_id to users / households / drop_off_points /
dumpsites / partner_stores. Foreign-keyed, indexed.
- Tenant model with deriveCode() helper + effectiveBoundary()
fallback chain.
- App\Tenancy\Tenancy — process-level current-tenant register with
withTenant() / withoutScope() helpers for jobs + super-admin.
- App\Tenancy\TenantScope — global Eloquent scope, no-op when no
tenant is set (so seeders + super-admin reads still work).
- App\Tenancy\HasTenant trait — applied to Household, DropOffPoint,
Dumpsite, PartnerStore. Auto-fills tenant_id on create from
Tenancy::current().
- ResolveTenant middleware — reads X-Tenant-Code (or X-Tenant-Id),
validates tenant exists + active, sets Tenancy::current(). Falls
back to authenticated user's tenant_id when header missing.
Registered globally on the api group.
- Login + register now require an active tenant (super-admin
bypasses). Cross-tenant credential reuse is rejected with a
403 + clear message.
- super_admin role added to RoleSeeder + users.role enum.
- Public GET /api/v1/tenants/lookup?code= — no auth, returns tenant
details for the pre-login screen.
Seeders:
- SuperAdminSeeder seeds super@verde.local (tenant_id = null).
- SanPascualTenantSeeder seeds Region IV-A → Batangas → San Pascual
municipality → sample Poblacion barangay → Tenant row with code
SAN-PASCUAL-BAT, then backfills every existing tenant-aware row
(13 users / 2 households / 5 DOPs / 1 dumpsite / 3 stores) so
the dev environment keeps working end-to-end.
- Wired into DatabaseSeeder so migrate:fresh --seed bootstraps
cleanly.
Customer-web:
- New /tenant page — text input, calls public lookup, confirms with
resolved tenant card, stores code + name in cookies (1 year).
"Pilot users: SAN-PASCUAL-BAT" hint as a clickable shortcut.
- /login + /register now redirect to /tenant?next= when no cookie,
show a verde "signing in to <LGU>" pill with a Switch link,
delegate the actual form to client components.
- /api/tenant route — POST sets cookie, DELETE clears.
- apiServer auto-attaches X-Tenant-Code on every API call when the
cookie is present.
- Tenant cookies are non-httpOnly so the client can echo them; the
session token stays httpOnly.
Build: 23 routes (added /tenant), 196 backend tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dumpsites table with both coordinates POINT and boundary_polygon
POLYGON (SRID 4326). Admin CRUD accepts boundary as a list of {lat,lng}
points; ring is auto-closed. Dumpsite::containsPoint() runs ST_Contains
for geofence checks (Module 10 will fire arrived_at_dumpsite from this).
dumpsite_releases schema in place — trip_id stays nullable bigint until
Module 10 adds the FK.
109 feature tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>