Skip to content

Per Diem Tracking

Extends timesheets with per diem tracking for field service engineers. Per diem records are computed automatically in real time when timesheets are saved on eligible tasks.

Why this exists

Nugget pays a flat daily cash payment to field service engineers who travel for work. Before Odoo, per diem was tracked manually through trip logs in the CRM. This module automates the entire flow: time gets logged, per diem records appear, accounting reviews and posts journal entries.

How it works

Data flow

Task (is_per_diem_eligible flag)
  → Timesheet (is_per_diem_eligible, computed from task)
    → Per Diem record (one per employee per calendar day)
      → Journal Entry (on Mark as Paid)

Real-time triggers

Per diem records are created, updated, or deleted automatically by overrides on account.analytic.line:

  • create — new timesheet on eligible task → per diem created/updated
  • write — hours changed, date moved, task reassigned → per diem recalculated
  • unlink — timesheet deleted → per diem recalculated or deleted

Additional triggers:

  • Task is_per_diem_eligible toggled → all timesheets on the task recomputed
  • Task analytic_account_id changed (via nugget_task_analytics) → unvalidated timesheets update analytic distribution → per diem distribution rebuilds

Design principle

Pending records always reflect current state (live reference). Paid records are frozen (audit trail). Validated timesheets are never modified by analytic propagation.

Business Rules

  • Full day: >= 8 hours = $100 (configurable)
  • Half day: >= 4 hours = $50 (configurable)
  • Below threshold: No per diem record created (or existing pending record deleted)
  • Calculation is per employee per calendar day, not per task
  • Per diem is calculated door-to-door — travel time counts, hotel downtime does not
  • Only timesheets on tasks marked "Per Diem Eligible" are eligible
  • Multiple eligible tasks on the same day sum together

GL Account Routing

Journal entries post to different GL accounts based on task type:

Task typeAccount
Service task (is_service_task = True)company.per_diem_service_account_id
Non-service taskcompany.per_diem_account_id

Falls back to the standard account if the service account is not configured. The is_service_task flag is stored on the per diem record for filtering.

Key Views

  • Per Diems list — Accounting > Reporting > Management > Per Diems
  • Timesheets list — "Per Diem Eligible" filter
  • Task form — "Per Diem Eligible" and "Service Task" toggles
  • Settings — Accounting > Per Diem Tracking (thresholds, amounts, accounts)

Configuration

SettingConfig Parameter / FieldDefault
Full Day Amountnugget_per_diem.full_day_amount$100.00
Half Day Amountnugget_per_diem.half_day_amount$50.00
Full Day Thresholdnugget_per_diem.full_day_threshold8.0 hours
Half Day Thresholdnugget_per_diem.half_day_threshold4.0 hours
Per Diem Accountres.company.per_diem_account_id(must be set)
Per Diem Account (Service)res.company.per_diem_service_account_id(falls back to standard)

Test Plan

Core flow

#TestExpected Result
01Create per-diem-eligible task, log 8+ hoursPer diem appears at $100
02Log 5 hours on per-diem-eligible taskPer diem appears at $50
03Log 3 hours on per-diem-eligible taskNo per diem record created
04Log 4h on task A + 4h on task B (different analytics, same employee, same day)Single per diem at $100, distribution splits 50/50

Edits and deletes

#TestExpected Result
05Start at 8h ($100), reduce to 5hPer diem updates to $50
06Start at 5h ($50), reduce to 2hPer diem record deleted
07Delete a timesheet entirelyPer diem record deleted
#TestExpected Result
08Mark per diem as paid, edit source timesheet hoursPaid record unchanged
09Mark per diem as paid, try to delete source timesheetError: cannot delete

Toggle behavior

#TestExpected Result
10Toggle Per Diem Eligible off on taskPending per diems removed
11Toggle Per Diem Eligible on (task has existing timesheets)Per diems created

GL account routing

#TestExpected Result
12Log time on service task and non-service task, mark both as paidDifferent GL accounts on journal entries
134h service + 4h non-service, same day, mark as paidis_service_task = True, uses service account

List view actions

#TestExpected Result
14Select pending per diems, gear > Mark as PaidJournal entries created, status moves to Paid

Cross-Module Dependencies

  • nugget_task_analytics — Owns the analytic_account_id field on tasks and propagates analytic changes to unvalidated timesheets. The timesheet write then cascades into per diem recomputation via nugget_per_diem's triggers.
  • nugget_service_requests — Owns the is_service_task field on tasks. Used for GL account routing on per diem journal entries.
  • timesheet_grid (Enterprise) — Provides validated field on timesheets. Used to protect approved timesheets from analytic propagation.

Nugget Scientific internal documentation