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:
214
.planning/phases/03-operational-modules/03-05-PLAN.md
Normal file
214
.planning/phases/03-operational-modules/03-05-PLAN.md
Normal file
@@ -0,0 +1,214 @@
|
||||
---
|
||||
phase: 03-operational-modules
|
||||
plan: "05"
|
||||
type: execute
|
||||
wave: 3
|
||||
depends_on: ["03-04"]
|
||||
files_modified:
|
||||
- prisma/schema.prisma
|
||||
- src/lib/prisma-tenant.ts
|
||||
- src/lib/services/technician-service.ts
|
||||
- src/lib/services/compensation-service.ts
|
||||
- src/app/api/technicians/route.ts
|
||||
- src/app/api/technicians/[id]/route.ts
|
||||
- src/app/api/technicians/[id]/compensation/route.ts
|
||||
- src/app/api/job-type-rates/route.ts
|
||||
- src/app/api/job-type-rates/[id]/route.ts
|
||||
- src/app/api/reports/compensation/route.ts
|
||||
- src/lib/__tests__/compensation-service.test.ts
|
||||
autonomous: true
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Admin can create technician profiles with contact info, skills, and assigned zone"
|
||||
- "Admin can set flat compensation rates per job type at the tenant level"
|
||||
- "Technician can have base monthly salary, per-job bonuses, or hybrid (both optional)"
|
||||
- "Only completed job orders count toward per-job compensation"
|
||||
- "System generates compensation summary per technician per period: total jobs, base salary, job bonuses, total"
|
||||
artifacts:
|
||||
- path: "prisma/schema.prisma"
|
||||
provides: "TechnicianProfile, JobTypeRate models"
|
||||
contains: "model TechnicianProfile"
|
||||
- path: "src/lib/services/technician-service.ts"
|
||||
provides: "Technician profile CRUD with zone and compensation config"
|
||||
exports: ["TechnicianService"]
|
||||
- path: "src/lib/services/compensation-service.ts"
|
||||
provides: "Period compensation calculation and summary report"
|
||||
exports: ["CompensationService"]
|
||||
- path: "src/lib/__tests__/compensation-service.test.ts"
|
||||
provides: "Integration tests for compensation calculation across models"
|
||||
min_lines: 80
|
||||
key_links:
|
||||
- from: "src/lib/services/compensation-service.ts"
|
||||
to: "prisma/schema.prisma"
|
||||
via: "Queries completed JobOrders by technician and joins with JobTypeRate for per-job amounts"
|
||||
pattern: "jobOrder\\.findMany|jobTypeRate\\.findMany"
|
||||
- from: "src/lib/services/compensation-service.ts"
|
||||
to: "src/lib/services/technician-service.ts"
|
||||
via: "Reads TechnicianProfile for base salary and compensation model"
|
||||
pattern: "technicianProfile|monthlySalary"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Build technician management with hybrid compensation model.
|
||||
|
||||
Purpose: Technicians need profiles with skills and zone assignments for proper job routing. The compensation system supports the real-world ISP pattern where technicians can be paid per-job, monthly salary, or a hybrid of both. The compensation summary report gives management visibility into technician costs.
|
||||
|
||||
Output: TechnicianProfile model, JobTypeRate model (tenant-level rates), TechnicianService for profile management, CompensationService for period calculation, compensation summary report API, 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
|
||||
@.planning/phases/03-operational-modules/03-04-SUMMARY.md
|
||||
@prisma/schema.prisma
|
||||
@src/lib/services/job-order-service.ts
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: TechnicianProfile and JobTypeRate Prisma models + migration</name>
|
||||
<files>prisma/schema.prisma, src/lib/prisma-tenant.ts</files>
|
||||
<action>
|
||||
**New enum:**
|
||||
- `CompensationModel { PER_JOB, SALARY, HYBRID }`
|
||||
|
||||
**TechnicianProfile model** — extends User with technician-specific data:
|
||||
- id (uuid PK), tenantId
|
||||
- userId (FK to User, unique) — one profile per user
|
||||
- phone (String?)
|
||||
- skills (String[]) — array of skill tags, e.g., ["fiber splicing", "router config", "installation"]
|
||||
- zoneId (String? FK to Zone) — primary assigned zone
|
||||
- compensationModel (CompensationModel default PER_JOB)
|
||||
- monthlySalary (Decimal? 10,2) — null if pure per-job
|
||||
- isActive (Boolean default true)
|
||||
- createdAt, updatedAt
|
||||
- @@unique([tenantId, userId]) — one profile per user per tenant
|
||||
- @@index([tenantId])
|
||||
|
||||
**JobTypeRate model** — tenant-level flat rates per job type:
|
||||
- id (uuid PK), tenantId
|
||||
- jobType (JobType enum — reuse from 03-04)
|
||||
- rate (Decimal 10,2) — flat amount per completed job of this type (e.g., 500.00 for INSTALLATION)
|
||||
- description (String?) — e.g., "Standard installation rate"
|
||||
- isActive (Boolean default true)
|
||||
- createdAt, updatedAt
|
||||
- @@unique([tenantId, jobType]) — one rate per job type per tenant
|
||||
- @@index([tenantId])
|
||||
|
||||
**Update relations:**
|
||||
- User: add `technicianProfile TechnicianProfile?`
|
||||
- Zone: add `technicianProfiles TechnicianProfile[]`
|
||||
|
||||
**Add to TENANT_SCOPED_MODELS:** "technicianProfile", "jobTypeRate"
|
||||
|
||||
Run `npx prisma migrate dev --name add-technician-compensation`
|
||||
</action>
|
||||
<verify>
|
||||
- `npx prisma migrate dev` completes without errors
|
||||
- `npx prisma generate` succeeds
|
||||
- Schema has TechnicianProfile and JobTypeRate models
|
||||
</verify>
|
||||
<done>TechnicianProfile and JobTypeRate models exist with proper relations, compensation model enum, and migration applied.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: TechnicianService, CompensationService + APIs + tests</name>
|
||||
<files>src/lib/services/technician-service.ts, src/lib/services/compensation-service.ts, src/app/api/technicians/route.ts, src/app/api/technicians/[id]/route.ts, src/app/api/technicians/[id]/compensation/route.ts, src/app/api/job-type-rates/route.ts, src/app/api/job-type-rates/[id]/route.ts, src/app/api/reports/compensation/route.ts, src/lib/__tests__/compensation-service.test.ts</files>
|
||||
<action>
|
||||
**TechnicianService** (`src/lib/services/technician-service.ts`):
|
||||
- `createProfile(db, { userId, phone?, skills?, zoneId?, compensationModel, monthlySalary? })`:
|
||||
1. Validate user has TECHNICIAN role
|
||||
2. Validate monthlySalary is set if compensationModel is SALARY or HYBRID
|
||||
3. Create TechnicianProfile
|
||||
4. Return profile with user info
|
||||
- `updateProfile(db, profileId, { phone?, skills?, zoneId?, compensationModel?, monthlySalary?, isActive? })`
|
||||
- `getProfile(db, userId)` — get profile by user ID
|
||||
- `listTechnicians(db, filters?)` — list all technician profiles for tenant. Filter by zoneId, isActive, compensationModel.
|
||||
- `setJobTypeRate(db, { jobType, rate, description? })` — create or update rate for a job type (upsert on @@unique)
|
||||
- `listJobTypeRates(db)` — list all job type rates for tenant
|
||||
- `deactivateJobTypeRate(db, rateId)` — soft-delete
|
||||
|
||||
**CompensationService** (`src/lib/services/compensation-service.ts`):
|
||||
- `calculateTechnicianCompensation(db, technicianUserId, { periodStart, periodEnd })`:
|
||||
1. Load technician profile (for compensationModel and monthlySalary)
|
||||
2. Query completed job orders assigned to this technician within date range
|
||||
3. For each completed job order, look up JobTypeRate for the job type
|
||||
4. Calculate:
|
||||
- jobCount: number of completed jobs
|
||||
- jobBonusTotal: sum of (rate for each job type * count of that type)
|
||||
- baseSalary: monthlySalary if SALARY or HYBRID model, else 0
|
||||
- totalCompensation: baseSalary + jobBonusTotal
|
||||
5. Return: { technicianId, technicianName, compensationModel, baseSalary, jobCount, jobBonusTotal, totalCompensation, jobDetails: [{ jobOrderId, jobType, completedAt, rate }] }
|
||||
|
||||
- `getCompensationSummary(db, { periodStart, periodEnd, technicianId? })`:
|
||||
1. If technicianId provided, calculate for one technician
|
||||
2. Otherwise, calculate for all active technicians
|
||||
3. Return array of per-technician summaries (same structure as above)
|
||||
4. Include grand totals: totalJobs, totalBaseSalary, totalBonuses, grandTotal
|
||||
|
||||
**API Routes:**
|
||||
- `GET /api/technicians` — list technician profiles. ADMIN, OFFICE_STAFF.
|
||||
- `POST /api/technicians` — create profile. ADMIN only. Body: { userId, phone?, skills?, zoneId?, compensationModel, monthlySalary? }
|
||||
- `GET /api/technicians/[id]` — get profile detail. ADMIN, OFFICE_STAFF, TECHNICIAN (own only).
|
||||
- `PUT /api/technicians/[id]` — update profile. ADMIN only.
|
||||
- `GET /api/technicians/[id]/compensation` — get compensation for a technician for a period. ADMIN. Query params: periodStart, periodEnd.
|
||||
- `GET /api/job-type-rates` — list rates. ADMIN.
|
||||
- `POST /api/job-type-rates` — set rate (upsert). ADMIN. Body: { jobType, rate, description? }
|
||||
- `PUT /api/job-type-rates/[id]` — update rate. ADMIN.
|
||||
- `GET /api/reports/compensation` — compensation summary for all technicians. ADMIN. Query params: periodStart, periodEnd, technicianId?
|
||||
|
||||
**Integration Tests** (`src/lib/__tests__/compensation-service.test.ts`):
|
||||
- Create technician profile (validates TECHNICIAN role)
|
||||
- PER_JOB model: 3 completed installations at 500 each = 1500 total
|
||||
- SALARY model: monthly salary of 15000, job count tracked but no per-job bonus
|
||||
- HYBRID model: 15000 salary + 3 installations at 500 = 16500 total
|
||||
- Only COMPLETED job orders count (PENDING, IN_PROGRESS, CANCELLED excluded)
|
||||
- Jobs outside date range excluded
|
||||
- Job type with no configured rate: 0 bonus for that job (not an error)
|
||||
- Compensation summary across multiple technicians with grand totals
|
||||
- Drill-down detail: each job with type, date, rate
|
||||
- Job type rate CRUD (create, update, upsert by jobType)
|
||||
</action>
|
||||
<verify>
|
||||
- `npx vitest run src/lib/__tests__/compensation-service.test.ts` — all tests pass
|
||||
- `npx vitest run` — full suite passes (no regressions)
|
||||
</verify>
|
||||
<done>TechnicianService handles profiles with compensation config. CompensationService calculates per-period compensation for per-job, salary, and hybrid models. Summary report shows per-technician totals with job-by-job drill-down. All models tested.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- Technician profiles: CRUD with skills, zone, compensation model
|
||||
- Job type rates: admin sets flat rates per job type
|
||||
- PER_JOB compensation: sum of rates for completed jobs only
|
||||
- SALARY compensation: monthly base only
|
||||
- HYBRID compensation: base + per-job bonuses
|
||||
- Period summary: per-technician totals + grand totals
|
||||
- Drill-down: per-job detail (type, date, rate)
|
||||
- Only completed jobs count — no partial credit
|
||||
- All existing tests pass (no regressions)
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- TechnicianProfile and JobTypeRate models with migration applied
|
||||
- TechnicianService manages profiles with hybrid compensation config
|
||||
- CompensationService correctly calculates for PER_JOB, SALARY, and HYBRID models
|
||||
- Period compensation summary with drill-down to individual jobs
|
||||
- Job type rates are tenant-level and admin-configurable
|
||||
- Integration tests prove all three compensation models with correct calculations
|
||||
- Full test suite passes with no regressions
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/03-operational-modules/03-05-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user