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>
This commit is contained in:
187
.planning/phases/03-operational-modules/03-01-PLAN.md
Normal file
187
.planning/phases/03-operational-modules/03-01-PLAN.md
Normal file
@@ -0,0 +1,187 @@
|
||||
---
|
||||
phase: 03-operational-modules
|
||||
plan: "01"
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- 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
|
||||
autonomous: true
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "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)"
|
||||
artifacts:
|
||||
- path: "prisma/schema.prisma"
|
||||
provides: "Zone model with name, description, isActive; ZoneAssignment linking collector users to zones"
|
||||
contains: "model Zone"
|
||||
- path: "src/lib/services/zone-service.ts"
|
||||
provides: "Zone CRUD, subscriber zone assignment, collector zone assignment, getCollectorSubscribers"
|
||||
exports: ["ZoneService"]
|
||||
- path: "src/app/api/zones/route.ts"
|
||||
provides: "GET list zones, POST create zone"
|
||||
exports: ["GET", "POST"]
|
||||
- path: "src/lib/__tests__/zone-service.test.ts"
|
||||
provides: "Integration tests for zone CRUD, assignment, collector scoping"
|
||||
min_lines: 80
|
||||
key_links:
|
||||
- from: "src/lib/services/zone-service.ts"
|
||||
to: "prisma/schema.prisma"
|
||||
via: "Prisma client queries on Zone and ZoneAssignment"
|
||||
pattern: "prisma\\.zone\\."
|
||||
- from: "src/app/api/collectors/[id]/subscribers/route.ts"
|
||||
to: "src/lib/services/zone-service.ts"
|
||||
via: "getCollectorSubscribers returns only zone-scoped subscribers"
|
||||
pattern: "getCollectorSubscribers"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<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>
|
||||
|
||||
<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
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Zone and ZoneAssignment Prisma models + migration</name>
|
||||
<files>prisma/schema.prisma, src/lib/prisma-tenant.ts</files>
|
||||
<action>
|
||||
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).
|
||||
</action>
|
||||
<verify>
|
||||
- `npx prisma migrate dev` completes without errors
|
||||
- `npx prisma generate` succeeds
|
||||
- Schema has Zone, ZoneAssignment models
|
||||
- Subscriber has zoneId FK instead of zone String
|
||||
</verify>
|
||||
<done>Zone and ZoneAssignment models exist in schema, Subscriber.zoneId replaces zone String, TENANT_SCOPED_MODELS updated, migration applied.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: ZoneService + API routes + integration tests</name>
|
||||
<files>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</files>
|
||||
<action>
|
||||
**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.
|
||||
</action>
|
||||
<verify>
|
||||
- `npx vitest run src/lib/__tests__/zone-service.test.ts` — all tests pass
|
||||
- `npx vitest run` — full suite passes (no regressions)
|
||||
</verify>
|
||||
<done>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.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- 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)
|
||||
</verification>
|
||||
|
||||
<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>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/03-operational-modules/03-01-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user