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>
Three personas covering the main resident states the customer-web
needs to render:
juan@verde.local → approved household, DOP assigned, 10 free
codes already allocated. Tests full dashboard,
/codes, /pickups, /tracker, /settings.
maria@verde.local → household submitted, verification_status =
pending. Tests the awaiting-verification banner
+ admin-approval flow (set status to approved
in /admin/households and the page auto-flips).
pedro@verde.local → user only. Tests the /onboarding wizard from
scratch.
All passwords: "password". Phone + email pre-verified so login works
without OTP loops.
Idempotent — safe to re-run via:
php artisan db:seed --class=DemoResidentSeeder
Also generates a 200-code free QR batch when none exists, so the
QrAllocator has something to draw from when seeding Juan. Hooked
into DatabaseSeeder so `migrate:fresh --seed` includes it.
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>
drop_off_points (with SPATIAL INDEX on coordinates) +
drop_off_capacity_logs. Public nearby query via ST_Distance_Sphere
returns DOPs sorted by distance with distance_meters in payload.
Admin CRUD plus capacity-log endpoint. Household creation auto-assigns
to the nearest active DOP within 25km; resident can re-run the lookup
via POST /households/{uuid}/reassign-drop-off.
97 feature tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Laravel 11.51 + PHP 8.4 in backend/
- MySQL 9 connection (DBngin, db: verde)
- Installed Sanctum, Spatie permission/activitylog/model-states,
matanyadaev/laravel-eloquent-spatial
- API routes prefixed /api/v1 in bootstrap/app.php
- Standard response envelope { success, data, message, errors, meta }
via ApiResponse + ApiController base class
- Global exception handlers for validation/auth/not-found/http errors
on api/* routes (always JSON, never redirect to login)
- Extended users migration: uuid, phone+verified_at, role enum
(admin/resident/driver/helper/scanner/store_partner), status,
first/middle/last name, avatar_path, preferred_language, fcm_token,
last_login_at, soft deletes
- User model: HasApiTokens, HasRoles, LogsActivity, SoftDeletes,
role/status constants, auto-uuid on create
- Health endpoint at GET /api/v1/health verifies DB connection
- backend/CLAUDE.md documenting backend conventions
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>