Commit Graph

26 Commits

Author SHA1 Message Date
Developer
c4cbdb42c7 feat(store): complete partner store overhaul with mapping, household search, financial analytics, and resident notification system 2026-07-02 13:55:08 +08:00
Developer
a1470eb85e feat: implement team standby status, helper persistence, and store portal foundations 2026-07-02 12:03:50 +08:00
Developer
4a4b88ffe4 feat: partner store financial settlements, qr replacements, and tenant resolution fix 2026-07-01 16:38:49 +08:00
Developer
92f8ee95fc feat: Add Geofence Leaflet drawing tool for DOPs and Dumpsites 2026-06-30 13:31:36 +08:00
Developer
7dc074e3c3 Implement household, QR sale, team helpers assignment, and helper endpoints 2026-06-29 16:56:33 +08:00
Developer
b85fc255cf feat: implement barangay and household management with associated tests 2026-06-29 15:02:52 +08:00
ramram1515
9ac2ea7bda chore: apply code review checklist and web design guidelines fixes 2026-06-29 13:54:14 +08:00
bb2c574f5c feat: multi-LGU tenancy — Phase B (full data scoping)
Backend models + DB:
- Adds tenant_id to 13 more parent tables: service_areas, routes,
  collection_teams, trucks, trips, qr_code_batches, qr_codes,
  collection_logs, dumpsite_releases, payments, daily_collection_stats,
  weekly_route_performance, monthly_store_sales.
  Child tables (route_stops, trip_stops, team_members,
  truck_location_history, etc.) inherit scoping via their parent
  relation — they aren't queried directly in production code.
- HasTenant trait applied to all 13 models, plus tenant_id added
  to each fillable. Creating any one of these now auto-fills
  tenant_id from Tenancy::current().
- BatchGenerator's bulk QrCode insert was bypassing model events
  (and therefore the HasTenant creating hook). Now sets tenant_id
  inline from the just-created batch's tenant_id, which itself
  goes through the trait. Fixes "0 codes returned for resident in
  same tenant" regression.

Backfill:
- SanPascualTenantSeeder extended to backfill all 18 tenant-aware
  tables. Idempotent — only updates rows where tenant_id is null.
  Production live data: backfilled 1 route, 1 team, 1 truck, 3
  trips, 1 batch, 200 codes, 5 collection logs.

Tests:
- TestCase base now creates a default "TEST-LGU" tenant in setUp,
  sets it as Tenancy::current(), and attaches X-Tenant-Code on
  every JSON request. Tear-down clears the tenant.
- UserFactory defaults tenant_id to Tenancy::current()->id, so
  factory-created users land in the test tenant.
- Net result: 202 tests still passing, no per-test changes
  required.

Phase A scope (5 tables) + Phase B scope (13 tables) = 18 of the
~22 tenant-aware tables. The remaining child tables (route_stops,
trip_stops, team_members, truck_location_history, household_members,
trip_timeline_events, store_purchases, store_sales) are scoped
implicitly through their parent.

What's still un-tenant-aware:
- Admin web (Blade-based) — single-tenant context for now
- Driver/scanner endpoints — they currently rely on the bearer
  token's user.tenant_id, which works correctly via ResolveTenant's
  fallback. No action needed.
- Super-admin onboarding UI — Phase C.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 22:57:56 +08:00
fcf2f9d0bf feat: multi-LGU tenancy — Phase A (foundation + customer-web)
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>
2026-05-01 22:38:35 +08:00
0883d63abb chore(backend): demo resident seeder for customer-web walkthrough
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>
2026-05-01 21:11:56 +08:00
86ea731eb0 feat(backend): finish all deferred items — Sentry, audit, bulk, FCM, Reverb, PSGC
- **Sentry** wired in bootstrap/app.php via Sentry\Laravel\Integration.
  No-op when SENTRY_LARAVEL_DSN is empty.

- **Audit logging** broadened: LogsActivity applied to Household, QrCode,
  Trip, Payment with tight logOnly whitelists and named log channels
  ('household', 'qr_code', 'trip', 'payment') to keep the activity
  stream useful.

- **Bulk admin actions**:
  POST /admin/households/bulk-approve — skips already-approved /
  no-proof / unknown ids, reports per-id outcomes
  POST /admin/bulk/users/{suspend,activate} — admins protected
  POST /admin/bulk/qr-codes/void — respects state machine transitions

- **FCM push scaffold**: PushDriver contract with LogPushDriver
  (default) and FcmPushDriver (activates when FCM_SERVER_KEY is set).
  PushChannel adapts notifications. RoutesByPreferences now also
  routes via push when push_enabled + fcm_token. toPush() payloads
  added to HouseholdApproved / QrBalanceLow / PickupImminent.

- **Reverb WebSocket broadcast**: laravel/reverb installed.
  TruckLocationBroadcast fires on every TruckTracker::record().
  routes/channels.php authenticates: admins → private-admin.live,
  residents → area.{id}.trucks (only if their household barangay is
  covered by the service area).

- **PSGC seeder** expanded: all 17 PH regions, 4 NCR districts,
  all 17 NCR cities. Sample barangays still carry rectangular
  boundaries for point-in-polygon resolution tests.

- **Conditional SPATIAL INDEX migration** for barangays.boundary —
  safe no-op until every row has a polygon (i.e., after full PSA
  dataset import). Re-running `php artisan migrate` after import
  flips the column to NOT NULL and adds the index.

196 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 17:22:43 +08:00
15d56d0e98 feat(backend): finish Module 13 sub-modules + flow fixes
Notifications: notification_preferences + Laravel notifications inbox.
SmsChannel adapter for our SmsService. RoutesByPreferences trait reads
per-user toggles. HouseholdApproved/Rejected, QrBalanceLow, and
CodesPurchased notifications wired in via auto-discovered listeners
or direct dispatch from controllers/StoreOperations.

Payments: payments table + PaymentDriver interface. ManualPaymentDriver
works out of the box; PayMongoDriver activates when
PAYMONGO_SECRET_KEY is set, falls back to manual otherwise. Resident
initiates code-purchase, admin can mark paid manually, webhook applies
real provider events. Fulfillment runs StoreOperations::sellToHousehold.

Live tracking (HTTP polling): truck_location_history (with SPATIAL
INDEX + 7-day retention plan). Driver POST /driver/trucks/{uuid}/location
writes history, updates trucks.last_known_coordinates, caches in Redis,
flags geofence-trigger when entering active trip dumpsite. Admin
GET /admin/live/trucks returns active truck positions. Reverb broadcast
deferred.

Flow corrections:
- QrAllocator now idempotent — re-approving a household no longer
  re-dispenses free codes.
- arrive-dumpsite enforces dumpsite geofence via ST_Contains; can be
  bypassed with override_geofence: true.

171 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:01:38 +08:00
0a2e187f45 feat(backend): complete Module 13 reports + analytics
daily_collection_stats, weekly_route_performance, monthly_store_sales
aggregation tables. Aggregator service is idempotent — wipes the slice
and re-inserts. php artisan reports:aggregate (default: yesterday) for
the nightly cron. Admin endpoints: daily-collection / trip-performance
/ store-sales chart series + totals, compliance.csv stream of dumpsite
releases (DENR-style), POST rebuild for on-demand aggregation.

Payments, notifications, and live tracking sub-modules of Module 13
are deferred per scope.

164 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 02:58:09 +08:00
989f4b87b9 feat(backend): complete Module 12 (partner stores)
partner_stores, store_inventories, store_purchases, store_sales tables.
Promotes qr_code_batches.target_store_id and qr_codes.assigned_to_store_id
to real FKs. StoreOperations service handles wholesale issuance
(generates fresh batch -> codes go allocated to store -> inventory tops
up -> StorePurchase recorded) and resident sales (codes flip allocated
-> active to a household, commission computed at the store's rate).
Admin endpoints: store CRUD, issue-inventory, record-sale.

160 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 02:55:29 +08:00
8643057c81 feat(backend): complete Module 11 (scanning + collection logs)
collection_logs table. ScanService validates code state (active only;
used = duplicate, expired = expired, unassigned/allocated = invalid),
geo proximity (≤200m from DOP via ST_Distance_Sphere), then atomically
transitions code -> Used and writes a CollectionLog. Scans inside a
trip context bump trip_stop.total_scans + emit qr_scanned timeline event.
Bulk scan endpoint processes each scan independently for offline sync.
Triggers QrBalanceLow when household active count drops below threshold.

155 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 02:53:25 +08:00
13148fab97 feat(backend): complete Module 10 (trips + timeline)
trips, trip_stops, trip_timeline_events. Admin: schedule/show/cancel.
Driver: start, arrive/depart/skip stops, report incident, arrive at
dumpsite, release load (creates dumpsite_release row + tallies trip
load), complete. Every action writes a typed timeline event with GPS.
Trip number auto-generated TRIP-YYYYMMDD-NNN. Promotes
dumpsite_releases.trip_id to a real FK now that trips exists.

148 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 02:51:52 +08:00
15f9295d34 feat(backend): complete Module 9 (teams + trucks)
trucks (with optional last_known_coordinates POINT 4326),
collection_teams, team_members. Admin CRUD for both. TeamConflictDetector
flags double-assignment of driver/scanner/truck/helper across active
teams; override_conflicts: true bypasses for emergencies. Promotes
routes.default_team_id to a real FK now that collection_teams exists.

144 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 02:48:40 +08:00
6643b0b1da feat(web): add login + dashboard scaffold with Verde branding
Modern minimalist split-layout login (left brand panel, right form) at
/login. Sign-in posts to /api/v1/auth/login, stores the bearer token in
localStorage, redirects to /dashboard. Demo-account expander auto-fills
the form. Placeholder dashboard fetches /me, shows top nav + stat cards
(values pending), and supports sign-out.

AdminUserSeeder now seeds 4 demo admins (admin/ops/audit/demo @verde.local,
all "password"). Tailwind config gets a Verde green palette + Inter font;
component classes for buttons + form inputs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 02:35:39 +08:00
de77503cdd feat(backend): complete Module 8 (routes)
routes + route_stops tables. Admin CRUD with stops submitted as an
ordered array (drag-reorder is client-side; sequence is array-index).
Cloning produces an inactive copy with -COPY-XXXX suffix. RouteCalculator
recomputes total_distance_km via ST_Distance_Sphere across stop
coordinates + dumpsite, and estimated_duration_minutes from dwell time
+ travel time at config-driven avg speed (default 25 km/h).
default_team_id stays nullable bigint until Module 9 adds the FK.

139 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 02:25:04 +08:00
d458e74301 feat(backend): complete Module 7 — QR code system (core)
qr_code_batches + qr_codes tables. State machine via
spatie/laravel-model-states (unassigned → allocated → active → used,
plus expired/voided). Serial format PH-{area}-{YYMM}-{batch}-{code}-
{checksum} with 2-char SHA-256-derived checksum. BatchGenerator
bulk-inserts in chunks. QrAllocator allocates free codes to verified
households (prefers area-targeted batch, falls back to any). Real
HouseholdVerified listener replaces the placeholder. QrBalanceLow
event for Module 11.

Admin: batch CRUD, mark-printed, void code, lifecycle search, PDF
print sheet (24/A4 via dompdf + endroid/qr-code + picqer/barcode).
Resident: list own codes, balance with low_balance flag, activate
allocated codes.

Removed explicit Event::listen for HouseholdVerified — Laravel 11
auto-discovers it via handle() type-hint, and double-registering was
causing double-dispatch.

130 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 02:12:41 +08:00
474b1cfe0b feat(backend): complete Module 6 (dumpsites + geofence)
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>
2026-04-30 00:39:14 +08:00
1150a518e8 feat(backend): complete Module 5 (drop-off points + auto-assign)
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>
2026-04-30 00:11:22 +08:00
4eb2d14e52 feat(backend): complete Module 4 (household registration)
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>
2026-04-30 00:06:37 +08:00
c8404564fe feat(backend): complete Modules 2 + 3 (geo + user management)
Module 2 — Geographic Data: PSGC tables (regions/provinces/cities/
barangays) with native geometry(polygon|point, 4326) columns; cascading
dropdown endpoints; ST_Contains-based GPS resolution; service-area CRUD
with barangay attach/detach; SamplePsgcSeeder + psgc:import command.

Module 3 — User Management: 5 role-specific profile tables (driver/
helper/scanner/store_partner with verification_status + rejection
reason); profile auto-created on register; admin user CRUD with
filters/pagination, suspend/activate, profile approve/reject;
self-service /me + /me/profile with role-aware validation that blocks
self-approval and resets rejected profiles to pending on resubmit.

69 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:59:56 +08:00
dc297d4cd7 feat(backend): complete Module 1 auth (register/login/OTP/reset)
Closes Module 1: 9 auth endpoints under /api/v1/auth, OTP via SMS
(Semaphore + log + fake drivers), role middleware, role + admin seeders,
27 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:46:53 +08:00
a58e5eb60e feat(backend): scaffold Laravel 11 API with Module 1 foundation
- 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>
2026-04-29 14:34:40 +08:00