# Contractor Management UI

**Date:** 2026-05-31  
**Status:** Draft  
**Spec:** 134  
**Tier:** All tiers  
**Depends on:** `contractor-payouts`, `projects-module`, `time-management`, `foundation-auth-rbac`  
**Referenced by:** `contractor-payouts`, `projects-module`

---

## Overview

Spec 21 (`contractor-payouts`) defines the contractor data model and payout flow. Spec 87 (`contractor-portal`) defines the contractor self-service time entry portal. Neither defines the staff-facing management UI. This spec adds the top-level `/contractors` route for OWNER/ADMIN to manage contractor records, assign them to projects, review their submitted time, and initiate payouts.

---

## Route

`/contractors` — top-level route (matches spec 21's existing backend). Requires `payouts:read` minimum; write actions require `payouts:write`.

All `/contractors` routes require the tenant's `contractor_payouts` module to be enabled. Rationale: disabled modules must not remain reachable through direct navigation.

---

## Contractor List

```
┌──────────────────────────────────────────────────────────────┐
│  Contractors                                [+ Add]          │
│                                                              │
│  Name              Rate       Projects   Hours(Mo)  Status   │
│  ─────────────────────────────────────────────────────────  │
│  Dana Cohen        ₪120/h     3          42.5h      Active   │
│  Yossi Levi        ₪90/h      1          12.0h      Active   │
│  Roni Bar          ₪150/h     0          0h         Inactive │
│                                                              │
│  Filters: [Active ▾]  [All projects ▾]                       │
└──────────────────────────────────────────────────────────────┘
```

**Rate** = `contractors.hourly_rate` (default) or per-project override from `contractor_assignments.rate_override` (spec 21).

---

## Add Contractor

**[+ Add]** → slide-in sheet:

```
┌──────────────────────────────────────────────────────────────┐
│  Add contractor                                              │
│                                                              │
│  Name         [_____________________________]                │
│  Email        [_____________________________]                │
│               (used for magic link login — spec 87)          │
│  Phone        [_____________________________]                │
│  Default rate [₪___] per hour                                │
│                                                              │
│  Has Zync account:                                           │
│  ● No — time entered by staff on their behalf                │
│  ○ Yes — invite via magic link to contractor portal          │
│                                                              │
│  [Cancel]     [Add contractor]                               │
└──────────────────────────────────────────────────────────────┘
```

Creates `contractors` record (spec 21 `POST /api/contractors`).

---

## Contractor Detail

Click contractor row → detail sheet:

### Overview Tab

```
┌──────────────────────────────────────────────────────────────┐
│  Dana Cohen — Contractor                          [Edit] [✕] │
│                                                              │
│  Email:   dana@freelance.il                                  │
│  Rate:    ₪120/h (default)                                   │
│  Status:  Active  [Deactivate]                               │
│                                                              │
│  This month: 42.5h · ₪5,100                                  │
│  Pending payout: ₪3,200                                      │
│                                                              │
│  [Initiate payout →]                                         │
└──────────────────────────────────────────────────────────────┘
```

### Projects Tab

```
│  Assigned projects                       [+ Assign to project]│
│                                                              │
│  Website Redesign     Hourly: ₪120/h (default)   [Edit rate] │
│  Mobile App Dev       Hourly: ₪140/h (override)  [Edit rate] │
│  API Integration      Hourly: ₪120/h (default)   [Edit rate] │
```

**[+ Assign to project]** → project picker modal → on select, creates `contractor_assignments` record (spec 21 `POST /api/contractors/:id/assignments`).

**[Edit rate]** → inline input to set `contractor_assignments.rate_override` for this contractor on this project.

**[Remove]** → removes from project (soft; keeps time entries).

### Time Entries Tab

```
│  May 2026                          [← Apr]  [Jun →]  [Approve all]│
│                                                              │
│  Date       Project              Task             Hours  Status   │
│  2026-05-31  Website Redesign    Homepage design  2.5h   Pending  │
│  2026-05-30  Mobile App Dev      Auth flow        4.0h   Approved │
│  2026-05-29  Website Redesign    Responsive CSS   3.0h   Approved │
│                                                              │
│  May total: 42.5h · Approved: 40.0h · Pending: 2.5h          │
```

Time entries submitted via contractor portal (spec 87) or entered by staff. ADMIN can approve pending entries inline.

### Payouts Tab

```
│  Payout history                                              │
│                                                              │
│  Date         Amount    Method   Status                      │
│  2026-04-30   ₪4,800    Bank     Paid                        │
│  2026-03-31   ₪3,600    Bank     Paid                        │
│                                                              │
│  Pending: ₪3,200 (40.0 approved hours × ₪120/h, minus ₪4,600 prev) │
│  [Initiate payout for ₪3,200 →]                              │
```

---

## Project Hourly Rate Override

When contractor is assigned to multiple projects at different rates, the rate override is stored on `contractor_assignments.rate_override` (existing column from spec 21):

```sql
-- From spec 21 (already exists):
-- contractor_assignments.rate_override NUMERIC(10,2)
-- null = use contractors.hourly_rate; non-null = this project-specific rate
-- COALESCE(contractor_assignments.rate_override, contractors.hourly_rate)
```

Payout calculation uses per-project rate: `SUM(time_entries.duration_seconds / 3600 × effective_rate)`.

---

## Deactivate Contractor

**[Deactivate]** → confirmation:

```
Deactivate Dana Cohen?
Active project assignments will be removed.
Time entries and payout history are preserved.

[Cancel]    [Deactivate]
```

Calls `DELETE /api/contractors/:id` (spec 21 → deactivate endpoint). Removes from active project assignments (preserves time entry history).

---

## API

```
-- All routes below use spec 21's existing /api/contractors endpoints.
-- This spec adds the UI only; no new API routes needed.

GET /api/contractors
    → list contractors for tenant
      query: { status?, project_id?, page? }
      Requires: payouts:read

POST /api/contractors
     → add contractor
       body: { name, email, phone?, hourly_rate, invite_to_portal? }
       Requires: payouts:write

GET /api/contractors/:id
    → contractor detail + stats
      Requires: payouts:read

PATCH /api/contractors/:id
      → update contractor (name, email, hourly_rate)
        Requires: payouts:write

DELETE /api/contractors/:id
       → deactivate
         Requires: payouts:write

GET /api/contractors/:id/time
    → contractor time entries (paginated, filterable by month + status)
      Requires: payouts:read

GET /api/contractors/:id/assignments
    → projects this contractor is assigned to
      Requires: payouts:read

POST /api/contractors/:id/assignments
     → assign contractor to project
       body: { project_id, rate_override? }
       Requires: payouts:write

PATCH /api/contractors/:id/assignments/:aid
      → update project assignment (rate_override)
        Requires: payouts:write

DELETE /api/contractors/:id/assignments/:aid
       → remove from project
         Requires: payouts:write
```

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| Separate top-level `/contractors` route | Not merged into `/team/staff` | Contractors have different management actions (rate overrides, portal invite, payout) and match spec 21's existing `/api/contractors` backend; mixing with employees adds visual complexity |
| OWNER/ADMIN only | Not MEMBER | Contractors are financial entities; rate and payout access requires admin trust level |
| Per-project rate overrides | Not single global rate | Contractors are often engaged at different rates by project (e.g., higher for more complex work); global rate is the default, not the only rate |
| Deactivate vs delete | Not hard delete | Deactivated contractors have time/payout history; deletion would break invoice line items referencing them |
