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>
1. API docs via dedoc/scramble at /docs/api (scoped to api/v1).
Linked from the admin sidebar Settings group.
2. Scheduled commands registered in routes/console.php:
- reports:aggregate (02:00) — daily/weekly/monthly aggregations
- qr:expire (02:30) — flips past-due allocated/active codes to expired
- trucks:prune-locations (03:00) — drops history older than retention
window (default 7 days, config('verde.location_retention_days'))
All idempotent + withoutOverlapping. --dry flags on qr:expire and
trucks:prune-locations for safe inspection.
3. Trip double-booking validation: AdminTripController::store rejects
new trips when the team or truck already has a non-cancelled trip on
the same date. override_conflicts: true bypasses for emergencies.
Cancelled trips don't block rebooking.
4a. Email verification: User implements MustVerifyEmail.
VerifyEmailNotification overrides verificationUrl() for our
namespaced route. Register sends the link automatically (best
effort, won't block signup). POST /auth/email/resend (auth) +
GET /auth/email/verify/{id}/{hash} (signed URL).
4b. Password change while logged in: POST /me/password validates
current_password, requires the new password to differ, revokes
every other active token on success — current session stays.
5a. PickupImminent notification: when TripStop -> arrived,
TripExecutor::notifyAssignedHouseholds() finds households whose
assigned_drop_off_point_id matches and sends DB + SMS.
5b. Auto-geofence on truck location: TruckTracker::record() now
auto-fires TripExecutor::arriveAtDumpsite() when an in-progress
trip's truck pings inside its dumpsite boundary. The executor's
status guard prevents duplicate timeline events if the driver also
presses arrive-dumpsite manually.
190 feature tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
households + household_members tables. Resident endpoints to create one
household (auto-resolves barangay from GPS), upload proof of residency,
manage members. Admin endpoints to list/approve/reject. Approving fires
HouseholdVerified event with a placeholder listener; Module 7 replaces
the body with actual QR batch allocation. Approved households are
immutable to residents; resubmitting after rejection resets to pending.
84 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>