Skip to main content

Harvest Time Tracking Integration

The ticket system integrates with Harvest for time tracking, project management, and budget monitoring. Time entries from Harvest are synced back to tickets and displayed in the timeline. Staff can also log time directly from the ticket page via per-staff OAuth2.


Overview

ComponentDetails
API Clientjolicode/harvest-php-api (Composer — sync cron), direct REST (OAuth time entry)
Sync Fileinclude/custom/class-harvest.php (Suma\Harvest)
OAuth Fileinclude/class.harvest-oauth.php (HarvestOAuth)
Time Entry Fileinclude/class.harvest-time-entry.php (HarvestTimeEntry)
AJAX Controllerinclude/ajax.harvest.php (HarvestAjaxAPI)
Sync Crontools/harvest-times-used-import.php
Panel Templateinclude/staff/harvest-time-panel.inc.php
Frontend JScustom/src/js/HarvestTimeEntry.js
Budget FieldsCustom form fields on tickets

Direct Time Entry (Per-Staff OAuth)

Architecture

Staff authenticate via OAuth2 to get their own Harvest tokens. Time is logged directly from the reply/note forms on the ticket page.

Staff opens ticket → checks harvest_project_id field
→ If project exists AND staff has OAuth connection → show harvest panel
→ Panel renders in both reply and note forms

Timer flow:
Start Timer → POST /v2/time_entries (no hours = running timer)
Stop Timer → PATCH /v2/time_entries/{id}/stop

Manual flow:
Staff enters hours → POST /v2/time_entries (with hours field)

On reply/note submit:
→ scp/tickets.php processes harvest POST data
→ Timer: stopEntry() → updateEntry() (push notes) → finalizeEntry()
→ Manual: submitCompletedEntry() → finalizeEntry()

OAuth2 Flow

Staff clicks "Connect to Harvest"
→ GET /scp/ajax.php/harvest/connect-url
→ Returns Harvest authorization URL with state token
→ Staff authenticates at Harvest → redirects to callback
→ scp/harvest-oauth-callback.php exchanges code for tokens
→ Tokens stored in ost_staff_harvest_oauth

Token refresh is automatic — getAccessToken() refreshes within 600s of expiry.

Configuration (namespace: harvest_api)

KeyDefaultDescription
enabledfalseMaster on/off switch
client_id''Harvest OAuth app Client ID
client_secret''Harvest OAuth app Client Secret
redirect_uri''OAuth callback URL
account_id'593667'Harvest Account ID
default_task_id'5611181'Default task (Development)
gemini_note_enabledtrueEnable AI note optimization

Database Tables

ost_staff_harvest_oauth — Per-staff OAuth tokens

ColumnTypeDescription
staff_idINT UNIQUEStaff member
access_tokenTEXTHarvest OAuth access token
refresh_tokenTEXTRefresh token for renewal
token_expires_atDATETIMEWhen access token expires
harvest_user_idINTUser's Harvest user ID

ost_staff_harvest_time_log — Local log of all time entries submitted

ColumnTypeDescription
ticket_idINTosTicket ticket ID
staff_idINTStaff who logged time
harvest_entry_idBIGINT UNIQUEHarvest time entry ID
hoursDECIMAL(8,2) NULLHours logged (NULL while running)
notesTEXT NULLTime entry note text
ai_optimizedTINYINT(1)1 = note was AI-generated
thread_entry_idINT NULLAssociated thread entry (reply/note)
is_runningTINYINT(1)1 = timer currently running

AJAX Endpoints

EndpointMethodDescription
GET /scp/ajax.php/harvest/statusoauthStatus()Check OAuth connection status
GET /scp/ajax.php/harvest/connect-urlconnectUrl()Get OAuth authorization URL
POST /scp/ajax.php/harvest/disconnectdisconnect()Remove OAuth connection
GET /scp/ajax.php/harvest/tasks/{project_id}getTaskAssignments()List project tasks
POST /scp/ajax.php/harvest/optimize-noteoptimizeNote()AI note optimization
POST /scp/ajax.php/harvest/timer/startstartTimer()Start running timer
POST /scp/ajax.php/harvest/timer/{entry_id}/stopstopTimer()Stop running timer
GET /scp/ajax.php/harvest/timer/{project_id}getRunningTimer()Check for running timer
PATCH /scp/ajax.php/harvest/timer/{entry_id}updateTimerEntry()Update timer notes

AI Note Optimization

Uses shared gemini_api_key from AI Assistant config. Summarizes reply/note text into a concise 2–8 word time entry description.

Migration

-- Run once to create tables and config
deploy/deploy-harvest-oauth.sql

Features

Project Creation

When a ticket is assigned, a Harvest project can be created:

  • Project name derived from ticket subject
  • Client association based on organization
  • Budget hours set from custom field budget_hours

Time Entry Sync

The import cron pulls time entries from Harvest and correlates them with tickets:

Harvest API → GET /time_entries (filtered by project)
→ Match entries to ticket via project ID
→ Store in ost_ticket_harvest_entries
→ Display on ticket timeline
→ Calculate total hours used vs budget

Budget Tracking

Each ticket can have budget-related custom fields:

  • Budget Hours — Total allocated hours
  • Quoted Hours — Original quote given to client
  • Hours Used — Calculated from Harvest entries

Timeline Events

Harvest time entries appear as events in the ticket timeline alongside thread entries. Events are ordered by timestamp (not ID) to maintain chronological accuracy.


Configuration

Harvest API Credentials

Configured in include/ost-config.php or via the admin panel:

SettingPurpose
Account IDHarvest account identifier
Access TokenPersonal access token for API
User AgentRequired by Harvest API (app-name (email))

Custom Form Fields

FieldPurpose
harvest_project_idLinks ticket to Harvest project
budget_hoursAllocated budget hours
quoted_hoursHours quoted to client

Sync Process

Import Cron (tools/harvest-times-used-import.php)

Runs periodically to sync time data:

  1. Fetches active Harvest projects
  2. For each project with a linked ticket:
    • Pulls all time entries
    • Calculates total hours used
    • Updates ticket budget fields
    • Inserts new entries into ost_ticket_harvest_entries
  3. Triggers over-budget check (see Over-Budget Locking)

Database Table

-- ost_ticket_harvest_entries
CREATE TABLE ost_ticket_harvest_entries (
id INT AUTO_INCREMENT PRIMARY KEY,
ticket_id INT NOT NULL,
harvest_entry_id BIGINT NOT NULL,
user_id INT,
hours DECIMAL(8,2),
notes TEXT,
spent_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY (harvest_entry_id)
);

Staff Panel Integration

Timeline View (scp/timeline/)

The timeline page shows a combined view of:

  • Thread entries (messages, responses, notes)
  • Harvest time entries (hours logged, who, when, notes)
  • Status changes and assignments

Entries are interleaved chronologically.

Harvester UI (scp/harvester/)

Staff can:

  • View project details for a ticket
  • See time entries logged against the project
  • Check budget utilization percentage
  • Create new Harvest projects from tickets

Over-Budget Integration

When synced hours exceed the budget threshold, the over-budget lock system activates:

Hours Used > Budget Hours × Threshold Multiplier
→ Ticket locked for further work
→ PM notification sent
→ PM must unlock to continue

See Over-Budget Locking for the tiered threshold system.


Troubleshooting

IssueSolution
Time entries not syncingRun php tools/harvest-times-used-import.php manually
Project not linked to ticketCheck harvest_project_id custom field is populated
Budget showing 0Verify budget_hours field is set on the ticket
API rate limitingHarvest allows 100 requests/15 seconds — cron respects this
Duplicate entriesUNIQUE key on harvest_entry_id prevents duplicates

Key Files

FilePurpose
include/custom/class-harvest.phpHarvest API wrapper, project creation, rename, budget checks
include/class.harvest-oauth.phpPer-staff OAuth2 token management
include/class.harvest-time-entry.phpDirect time entry API calls, logging, Gemini note optimization
include/ajax.harvest.phpAJAX controller (9 endpoints: OAuth, timer, tasks, notes)
include/staff/harvest-time-panel.inc.phpShared panel partial for reply + note forms
scp/harvest-oauth-callback.phpOAuth callback handler
include/staff/settings-harvest-api.inc.phpAdmin settings page
custom/src/js/HarvestTimeEntry.jsFrontend JS component (timer, tasks, AI notes, OAuth popup)
tools/harvest-times-used-import.phpCron: imports time entries, updates budgets, syncs timeline events
tools/harvest-time-entries-backfill.phpOne-time historical backfill script
deploy/deploy-harvest-oauth.sqlMigration: OAuth + time log tables
deploy/deploy-harvest-time-entries.sqlMigration: timeline event tracking table