Files
NetForge/.planning/phases/03-operational-modules/03-05-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

10 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 05 execute 3
03-04
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
true
truths artifacts key_links
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
path provides contains
prisma/schema.prisma TechnicianProfile, JobTypeRate models model TechnicianProfile
path provides exports
src/lib/services/technician-service.ts Technician profile CRUD with zone and compensation config
TechnicianService
path provides exports
src/lib/services/compensation-service.ts Period compensation calculation and summary report
CompensationService
path provides min_lines
src/lib/__tests__/compensation-service.test.ts Integration tests for compensation calculation across models 80
from to via pattern
src/lib/services/compensation-service.ts prisma/schema.prisma Queries completed JobOrders by technician and joins with JobTypeRate for per-job amounts jobOrder.findMany|jobTypeRate.findMany
from to via pattern
src/lib/services/compensation-service.ts src/lib/services/technician-service.ts Reads TechnicianProfile for base salary and compensation model technicianProfile|monthlySalary
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.

<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 @.planning/phases/03-operational-modules/03-04-SUMMARY.md @prisma/schema.prisma @src/lib/services/job-order-service.ts Task 1: TechnicianProfile and JobTypeRate Prisma models + migration prisma/schema.prisma, src/lib/prisma-tenant.ts **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 - npx prisma migrate dev completes without errors - npx prisma generate succeeds - Schema has TechnicianProfile and JobTypeRate models TechnicianProfile and JobTypeRate models exist with proper relations, compensation model enum, and migration applied.

Task 2: TechnicianService, CompensationService + APIs + tests 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 **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)
    • npx vitest run src/lib/__tests__/compensation-service.test.ts — all tests pass
    • npx vitest run — full suite passes (no regressions) 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.
- 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)

<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>
After completion, create `.planning/phases/03-operational-modules/03-05-SUMMARY.md`