Files
NetForge/.planning/phases/03-operational-modules/03-01-PLAN.md
kevin-asprec ef0150654b docs(03): create phase plan
Phase 03: Operational Modules
- 5 plans in 3 waves
- Wave 1: 03-01 (zones), 03-03 (tickets) — parallel
- Wave 2: 03-02 (collector collections), 03-04 (job orders) — parallel
- Wave 3: 03-05 (technician compensation) — sequential
- Ready for execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 06:05:55 +08:00

9.1 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
phase plan type wave depends_on files_modified autonomous must_haves
03-operational-modules 01 execute 1
prisma/schema.prisma
src/lib/prisma-tenant.ts
src/lib/services/zone-service.ts
src/app/api/zones/route.ts
src/app/api/zones/[id]/route.ts
src/app/api/zones/[id]/subscribers/route.ts
src/app/api/collectors/[id]/subscribers/route.ts
src/lib/__tests__/zone-service.test.ts
true
truths artifacts key_links
Admin can create, update, and deactivate zones for their tenant
Admin can assign subscribers to a zone
Admin can assign a collector user to a zone
A collector can only see subscribers assigned to their zone(s)
path provides contains
prisma/schema.prisma Zone model with name, description, isActive; ZoneAssignment linking collector users to zones model Zone
path provides exports
src/lib/services/zone-service.ts Zone CRUD, subscriber zone assignment, collector zone assignment, getCollectorSubscribers
ZoneService
path provides exports
src/app/api/zones/route.ts GET list zones, POST create zone
GET
POST
path provides min_lines
src/lib/__tests__/zone-service.test.ts Integration tests for zone CRUD, assignment, collector scoping 80
from to via pattern
src/lib/services/zone-service.ts prisma/schema.prisma Prisma client queries on Zone and ZoneAssignment prisma.zone.
from to via pattern
src/app/api/collectors/[id]/subscribers/route.ts src/lib/services/zone-service.ts getCollectorSubscribers returns only zone-scoped subscribers getCollectorSubscribers
Create the zone/territory system that scopes collectors to specific subscriber groups.

Purpose: Zones are the foundation for collector workflow — a collector can only collect from subscribers in their assigned zones. This must exist before collector field collection (03-02) can enforce proper scoping.

Output: Zone Prisma model, zone CRUD service and APIs, collector-to-zone and subscriber-to-zone assignment, scoped subscriber list for collectors, integration tests.

<execution_context> @C:\Users\KevinAsprec.claude/get-shit-done/workflows/execute-plan.md @C:\Users\KevinAsprec.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-operational-modules/03-CONTEXT.md @prisma/schema.prisma @src/lib/prisma-tenant.ts @src/lib/services/subscriber-service.ts @src/lib/middleware/with-permission.ts Task 1: Zone and ZoneAssignment Prisma models + migration prisma/schema.prisma, src/lib/prisma-tenant.ts Add two new models to Prisma schema:
  1. Zone model:

    • id (uuid PK), tenantId, name (String), description (String?), isActive (Boolean default true)
    • createdAt, updatedAt
    • @@unique([tenantId, name]) — zone names unique per tenant
    • @@index([tenantId])
    • Relation: subscribers Subscriber[] (via Subscriber.zoneId — update Subscriber to add zoneId optional FK)
    • Relation: assignments ZoneAssignment[]
  2. ZoneAssignment model:

    • id (uuid PK), tenantId, zoneId (FK to Zone), userId (FK to User — the collector)
    • createdAt
    • @@unique([tenantId, zoneId, userId]) — prevent duplicate assignments
    • @@index([tenantId]), @@index([userId]), @@index([zoneId])
  3. Update Subscriber model:

    • The Subscriber already has zone String? field. Replace it with a proper FK:
    • Add zoneId String? and zone Zone? @relation(fields: [zoneId], references: [id])
    • Remove the old zone String? field (it was a placeholder for Phase 3)
    • Add @@index([tenantId, zoneId])
  4. Update User model:

    • Add relation: zoneAssignments ZoneAssignment[]
  5. Add "zone" and "zoneAssignment" to TENANT_SCOPED_MODELS in src/lib/prisma-tenant.ts and extend the withTenantContext() $extends block following the existing pattern.

  6. Run npx prisma migrate dev --name add-zones to create the migration.

Important: The old zone String? on Subscriber is being replaced with zoneId String? (FK). The migration needs to handle this — drop the old column, add new column. No data migration needed (no production data). - npx prisma migrate dev completes without errors - npx prisma generate succeeds - Schema has Zone, ZoneAssignment models - Subscriber has zoneId FK instead of zone String Zone and ZoneAssignment models exist in schema, Subscriber.zoneId replaces zone String, TENANT_SCOPED_MODELS updated, migration applied.

Task 2: ZoneService + API routes + integration tests src/lib/services/zone-service.ts, src/app/api/zones/route.ts, src/app/api/zones/[id]/route.ts, src/app/api/zones/[id]/subscribers/route.ts, src/app/api/collectors/[id]/subscribers/route.ts, src/lib/__tests__/zone-service.test.ts **ZoneService** (`src/lib/services/zone-service.ts`): - `createZone(db, { name, description })` — creates zone, returns zone - `updateZone(db, zoneId, { name?, description?, isActive? })` — updates zone - `listZones(db)` — returns all zones for tenant (active and inactive) - `assignSubscriberToZone(db, subscriberId, zoneId)` — updates subscriber.zoneId - `removeSubscriberFromZone(db, subscriberId)` — sets subscriber.zoneId to null - `assignCollectorToZone(db, userId, zoneId)` — creates ZoneAssignment (validates user has COLLECTOR role) - `removeCollectorFromZone(db, userId, zoneId)` — deletes ZoneAssignment - `getCollectorZones(db, userId)` — returns zones assigned to a collector - `getCollectorSubscribers(db, userId)` — returns subscribers in all zones assigned to this collector (the key scoping query). Include subscriber status and outstanding invoice count for the collector's field view. - `getZoneSubscribers(db, zoneId)` — returns subscribers in a specific zone

Follow existing service patterns: take tenantPrisma client as first arg (same as PaymentService, SubscriberService). Use as any cast pattern for tenantId injection (documented in 02-03 decision).

API Routes:

  • GET /api/zones — list zones (ADMIN, OFFICE_STAFF, COLLECTOR can read)
  • POST /api/zones — create zone (ADMIN only)
  • GET /api/zones/[id] — get zone detail with subscriber count
  • PUT /api/zones/[id] — update zone (ADMIN only)
  • POST /api/zones/[id]/subscribers — assign subscriber to zone, body: { subscriberId }. ADMIN, OFFICE_STAFF.
  • DELETE /api/zones/[id]/subscribers — remove subscriber from zone, body: { subscriberId }. ADMIN, OFFICE_STAFF.
  • GET /api/collectors/[id]/subscribers — get subscribers for a specific collector (scoped by zone assignments). ADMIN, OFFICE_STAFF can query any collector; COLLECTOR can only query self.

Use withPermission() HOF pattern from existing API routes. For dynamic [id] routes, use the closure pattern documented in 02-01 decision (withPermission doesn't support dynamic params directly).

Integration Tests (src/lib/__tests__/zone-service.test.ts):

  • Zone CRUD (create, update, list, deactivate)
  • Zone name uniqueness within tenant
  • Subscriber zone assignment and removal
  • Collector zone assignment and removal
  • getCollectorSubscribers returns only subscribers in collector's zones
  • getCollectorSubscribers returns empty for collector with no zone assignments
  • Collector cannot be assigned to zone if they don't have COLLECTOR role
  • Cross-tenant isolation (zone from tenant A not visible to tenant B)

Follow existing test patterns: beforeAll creates tenant+user+accounts, afterAll cleans up in correct order. Add Zone and ZoneAssignment to cleanup order. - npx vitest run src/lib/__tests__/zone-service.test.ts — all tests pass - npx vitest run — full suite passes (no regressions) ZoneService with zone CRUD, subscriber/collector assignment, and collector-scoped subscriber queries all working. API routes enforce RBAC. Integration tests prove zone scoping and tenant isolation.

- Zone CRUD: create, update, deactivate zones - Subscriber assignment: assign/remove subscriber to/from zone - Collector assignment: assign/remove collector to/from zone - Collector scoping: collector sees only their zone's subscribers - Tenant isolation: zones are tenant-scoped - All existing tests still pass (no regressions from Subscriber.zone -> zoneId migration)

<success_criteria>

  • Zone and ZoneAssignment models in Prisma schema with migration applied
  • ZoneService handles zone CRUD, subscriber assignment, collector assignment, and scoped queries
  • API routes enforce RBAC (admin creates zones, collectors query their subscribers)
  • Integration tests prove collector can only see subscribers in their assigned zones
  • Full test suite passes with no regressions </success_criteria>
After completion, create `.planning/phases/03-operational-modules/03-01-SUMMARY.md`