# Mileage / Vehicle Logbook (יומן נסיעות)

**Date:** 2026-06-01
**Status:** Draft
**Spec:** 166
**Tier:** All tiers
**Depends on:** `expenses-module`, `foundation-auth-rbac`, `settings-module`
**Referenced by:** `expenses-module`, `israeli-tax-reports`, `app-shell`

---

## Overview

Israeli tax law (Income Tax Ordinance, Section 18) allows deduction of vehicle expenses based on documented business mileage. Self-employed individuals and businesses can deduct either:
1. **Actual expenses method** — tracked via receipts (fuel, maintenance, insurance) with business/total ratio applied
2. **Standard rate method (שיעור קבוע)** — rate per kilometre set annually by ITA (currently ~₪2.05/km for standard vehicles); no receipts required beyond mileage log

This spec defines the Zync mileage logbook: a structured trip log, odometer tracking, annual report generation, and integration with the expenses module for tax deduction calculations.

### Navigation

The mileage logbook is **accessed as a tab within the expenses module** — it is not a standalone top-level page.

- Accessible from: `/expenses` page → **"Mileage"** tab
- Canonical route: `/expenses/mileage`
- Quick-log: "Log Trip" slide-over, reachable from the expenses secondary nav / Mileage tab

---

## Data Model

```sql
CREATE TABLE mileage_trips (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id       UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  user_id         UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  trip_date       DATE NOT NULL,
  origin          TEXT NOT NULL,                -- free text: "Tel Aviv office"
  destination     TEXT NOT NULL,               -- free text: "Haifa client site"
  purpose         TEXT NOT NULL,               -- business purpose: "Client meeting — Acme Corp"
  distance_km     NUMERIC(8,2) NOT NULL,
  vehicle_id      UUID REFERENCES mileage_vehicles(id) ON DELETE SET NULL,
  odometer_start  INTEGER,                     -- optional; km at trip start
  odometer_end    INTEGER,                     -- optional; km at trip end
  project_id      UUID REFERENCES projects(id) ON DELETE SET NULL,
  customer_id     UUID REFERENCES customers(id) ON DELETE SET NULL,
  is_billable     BOOLEAN NOT NULL DEFAULT false,
  notes           TEXT,
  created_at      TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_mileage_trips_user   ON mileage_trips(user_id, tenant_id, trip_date DESC);
CREATE INDEX idx_mileage_trips_tenant ON mileage_trips(tenant_id, trip_date DESC);

CREATE TABLE mileage_vehicles (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id     UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  name          TEXT NOT NULL,                 -- e.g. "Personal car — Mazda 3"
  plate_number  TEXT,                          -- vehicle registration number
  vehicle_type  TEXT NOT NULL DEFAULT 'car',   -- 'car' | 'motorcycle' | 'truck'
  engine_cc     INTEGER,                        -- engine displacement for ITA rate lookup
  is_default    BOOLEAN NOT NULL DEFAULT false, -- default vehicle for new trips
  created_at    TIMESTAMPTZ DEFAULT NOW()
);

-- Annual ITA mileage rates (admin-configurable per year)
CREATE TABLE mileage_rates (
  year          INTEGER PRIMARY KEY,
  rate_per_km   NUMERIC(6,4) NOT NULL,          -- ILS per km (e.g. 2.0500)
  source        TEXT DEFAULT 'ita_regulations', -- 'ita_regulations' | 'manual'
  updated_at    TIMESTAMPTZ DEFAULT NOW()
);
-- Seed:
INSERT INTO mileage_rates (year, rate_per_km) VALUES
  (2024, 2.05),
  (2025, 2.05),   -- update when ITA publishes 2025 rate
  (2026, 2.05);
```

---

## Features

### Trip Log (`/expenses/mileage`)

Rendered inside the expenses module's **Mileage** tab. Main view: calendar and list toggle.

**List view:**

```
┌──────────────────────────────────────────────────────────────────┐
│  Mileage Logbook                           [+ Log trip]          │
│                                                                  │
│  Filter: [This month ▾]  [All vehicles ▾]  [Export ▾]           │
│                                                                  │
│  Date       Origin → Destination           KM    Project         │
│  ─────────────────────────────────────────────────────────────   │
│  Jun 01     Tel Aviv → Haifa               95    Acme Corp       │
│  Jun 01     Haifa → Tel Aviv               95    Acme Corp       │
│  May 30     Home → Jerusalem office        65    —               │
│  ─────────────────────────────────────────────────────────────   │
│  Total: 255 km   Value: ₪522.75 (@ ₪2.05/km)                   │
└──────────────────────────────────────────────────────────────────┘
```

### Log Trip Modal

```
┌──────────────────────────────────────────────────────────────┐
│  Log trip                                           [✕]      │
│                                                              │
│  Date *            [2026-06-01_____]                         │
│  From *            [Tel Aviv office_______]                  │
│  To *              [Haifa — Acme Corp_____]                  │
│  Purpose *         [Client meeting_________]                 │
│  Distance *        [95___] km                                │
│                                                              │
│  Vehicle           [Personal car — Mazda 3 ▾]               │
│  Odometer start    [_______] km  (optional)                  │
│  Odometer end      [_______] km  (optional)                  │
│  Project           [Acme Corp redesign ▾]  (optional)        │
│  Customer          [Acme Corp ▾]            (optional)       │
│  Billable          ☐                                         │
│                                                              │
│  [Cancel]                        [Log trip]                  │
└──────────────────────────────────────────────────────────────┘
```

Distance can be entered manually or auto-calculated if odometer start + end provided.

### Annual Mileage Report

For year-end tax purposes. Accessible from the logbook header: "Export Annual Report".

Report contents (PDF + Excel):
- Total trips: N
- Total business km: X
- Rate per km: ₪2.05 (for year YYYY)
- Estimated deduction value: ₪X × 2.05
- Per-month breakdown
- Full trip list (date, from, to, purpose, km)
- Vehicle registration numbers

```
GET /api/mileage/report/annual?year=2026
    → {
        year, total_km, rate_per_km, deduction_value,
        months: [{ month, km, trips }],
        trips: [...]
      }
      Requires: expenses:read

GET /api/mileage/report/annual/xlsx?year=2026
    → Excel download; filename: "mileage-report-{year}.xlsx"
```

---

## Expense Integration

Business mileage can be added as a deductible expense automatically. When creating or editing a trip with `project_id` or `customer_id` set:

- An expense record is created (or updated) with:
  - `category = 'travel'`
  - `amount = distance_km × current_year_rate_per_km`
  - `description = "Mileage: {origin} → {destination}"`
  - `is_per_diem = false` (distinct from per-diem)
  - `source = 'mileage'` (new source value)
- Checkbox in Log Trip modal: "Add as billable expense" — only shown when project_id is set

```sql
ALTER TABLE expenses ADD COLUMN mileage_trip_id UUID REFERENCES mileage_trips(id) ON DELETE SET NULL;
-- Link expense to mileage trip; NULL for non-mileage expenses
```

---

## Settings

`/settings/time-tracking` → "Mileage & Vehicles" section (shown when mileage module enabled):

- Vehicle list (CRUD)
- Default vehicle selector
- Rate display: "Current ITA rate: ₪{rate}/km (2026)" with "Update rate" link for admins

---

## API

```
GET    /api/mileage                          → list trips (paginated, filterable: date range, user, vehicle)
POST   /api/mileage                          → log trip
GET    /api/mileage/:id                      → trip detail
PATCH  /api/mileage/:id                      → edit trip
DELETE /api/mileage/:id                      → delete trip

GET    /api/mileage/vehicles                 → list tenant vehicles
POST   /api/mileage/vehicles                 → create vehicle
PATCH  /api/mileage/vehicles/:id             → update vehicle
DELETE /api/mileage/vehicles/:id             → delete vehicle

GET    /api/mileage/summary?from=&to=        → summary stats for date range
GET    /api/mileage/report/annual?year=      → annual report data
GET    /api/mileage/report/annual/xlsx?year= → Excel download
```

All routes require: `expenses:write` (log/edit) or `expenses:read` (view/export).

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| Separate `mileage_trips` table | Not expense sub-type | Trips have unique fields (origin, destination, odometer, vehicle) that don't fit the expenses schema; creates a clean expense record via join |
| Rate in `mileage_rates` table | Not hardcoded | ITA updates rate annually; admin must be able to update without code deployment |
| Optional odometer fields | Not required | Many users don't track odometer; purpose + km is sufficient for ITA; odometer adds accuracy for inspection but is not legally required for standard-rate method |
| Auto-expense creation | Opt-in per trip | Not all mileage is a billable expense; some is just logbook data; opt-in reduces noise in expense module |
| Excel + data export | Not PDF only | IL accountants use Excel for tax year-end processing; trip list + totals in Excel format is standard practice |
