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>
215 lines
10 KiB
Markdown
215 lines
10 KiB
Markdown
---
|
|
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>
|