feat(backend): finish Module 13 sub-modules + flow fixes

Notifications: notification_preferences + Laravel notifications inbox.
SmsChannel adapter. RoutesByPreferences trait reads per-user toggles.
HouseholdApproved/Rejected, QrBalanceLow, CodesPurchased notifications
wired via auto-discovered listeners or direct dispatch.

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.
Driver POST 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 positions.
Reverb broadcast deferred.

Flow fixes:
- QrAllocator now idempotent — re-approving a household won't
  re-dispense 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>
This commit is contained in:
2026-05-01 14:02:05 +08:00
parent 15d56d0e98
commit c62ad3cc08

View File

@@ -266,7 +266,47 @@ bind `FakeSmsService` via `$this->app->instance(SmsService::class, ...)` in
`GET /admin/reports/compliance.csv?from=&to=` streams a DENR-style
dumpsite-release export, `POST /admin/reports/rebuild` triggers
aggregation on demand.
- All 164 feature tests passing
- [x] Module 13b: Notifications — complete
- `notification_preferences` table + `notifications` (Laravel inbox).
- `App\Notifications\Channels\SmsChannel` adapts our SmsService.
- `RoutesByPreferences` trait routes via database + sms based on
per-user toggles. Notifications: `HouseholdApproved`,
`HouseholdRejected`, `QrBalanceLowNotification`, `CodesPurchased`.
Listeners auto-route via Laravel 11 discovery; rejection +
purchase notifications dispatched directly from the controller /
`StoreOperations`.
- `GET/PATCH /me/notification-preferences`. Defaults seeded on
register.
- [x] Module 13c: Payments — scaffolded (PayMongo + Manual)
- `payments` table.
- `PaymentDriver` interface with `ManualPaymentDriver` (admin marks
paid; default) and `PayMongoDriver` (activates when
`PAYMONGO_SECRET_KEY` is set; HMAC-SHA256 webhook verification).
- Resident `POST /me/payments/code-purchase` → driver returns
checkout URL. Webhook `POST /api/v1/webhooks/paymongo` applies
paid/failed events. Admin `POST /admin/live/payments/{uuid}/mark-paid`
forces fulfillment for cash-paid or stuck payments.
- Fulfillment runs `StoreOperations::sellToHousehold()` — codes
allocated → active for the household + `CodesPurchased` notification.
- [x] Module 13d: Live Tracking (HTTP polling) — complete
- `truck_location_history` (POINT 4326 + SPATIAL INDEX).
- `TruckTracker::record()` writes history, updates
`trucks.last_known_coordinates`, Redis-caches latest position,
flags geofence-trigger when entering the active trip's dumpsite.
- Driver `POST /driver/trucks/{uuid}/location` (15-second cadence;
only the assigned-team driver may post).
- Admin `GET /admin/live/trucks` returns positions for active trucks
updated within the last hour.
- Reverb broadcast deferred — polling is the data source for now.
- Flow corrections from this batch:
- `QrAllocator::allocateFreeToHousehold()` is now **idempotent**
if the household already has any QR codes assigned, the listener
skips. Re-approving a rejected→approved cycle no longer dispenses
a second free batch.
- `arrive-dumpsite` validates the GPS is **inside** the dumpsite
boundary via `Dumpsite::containsPoint`. `override_geofence: true`
bypasses for known-bad GPS — admin can audit later.
- All 171 feature tests passing
- [ ] Deferred sub-modules: PayMongo payments, FCM notifications,
Reverb live tracking