Billing Methodology

How invoice amounts are calculated for each rate type.


All current rate types use actual workdays (Mon–Fri) in the specific month as the fundamental unit. This keeps proration (partial months) and deductions (absences, holidays, vacation) calculated against the same daily value — no inconsistency between how we charge for a partial month and how we credit for a missed day.

One historical exception: monthly invoices dated before PRORATION_CUTOFF (2026-04-01) that didn't opt out of paid holidays/vacation use a legacy calendar-day proration with hourly × hours absence deductions, matching the formula that QuickBooks invoices were generated under historically. Reconciliation against pre-cutoff QB invoices needs to know about this — see "Pre-cutoff legacy formula" below.


Monthly contracts (current)

The monthly rate is prorated by workdays, and the same per-workday rate drives every deduction:

effective_daily_rate = monthly_rate / workdays_in_month
base_amount          = workdays_in_intersection × effective_daily_rate
ScenarioFormula
Full monthmonthly_rate
Partial month (mid-month start/end, including revised_end)workdays_in_intersection × effective_daily_rate
Absence (always deducts)−effective_daily_rate per absence row
Holiday — only when paid_holidays = false−effective_daily_rate per weekday holiday
Vacation — only when paid_vacation = false−effective_daily_rate per weekday vacation day
Example: April 2026 has 22 workdays. A $10,000/month contract:
  • Effective daily rate = $10,000 / 22 = $454.55
  • Full month = $10,000.00
  • Start Apr 16 (11 workdays) = 11 × $454.55 = $5,000.00
  • 1 absence = −$454.55
  • 1 unpaid holiday (paid_holidays=false) = −$454.55
  • Start Apr 16 + 1 absence + 1 unpaid holiday = 9 × $454.55 = $4,090.91
Pre-cutoff legacy formula

Monthly invoices dated before 2026-04-01 with both paid_holidays = true and paid_vacation = true (the historical default) use the legacy formula instead — calendar-day proration plus hourly × hours absence deductions:

base_amount         = monthly_rate × (calendar_days_in_intersection / calendar_days_in_period)
hourly_rate         = monthly_rate × 12 / 2080
absence_deduction   = absence.hours × hourly_rate
net_amount          = base_amount − sum(absence_deductions)

Holidays and vacation are not deducted here — they were always considered baked into the monthly rate under this model. Setting paid_holidays = false or paid_vacation = false on a monthly contract bypasses the legacy branch entirely (it falls through to the workday-based model regardless of invoice date), since the legacy formula has no concept of per-day deductions.


Daily contracts

Billing is hours-first: compute hours_worked, then convert to days × rate:

weekday_hours    = workdays_in_intersection × daily_hours
hours_worked     = weekday_hours − holiday_hours − absence_hours − vacation_hours
days_worked      = hours_worked / daily_hours
amount           = days_worked × daily_rate

Where daily_hours = weekly_hours / 5 (40h/week → 8h/day). Holidays count as full days (holiday_hours = holidays.length × daily_hours); absences and vacation read hours directly from each row, so partial-day records are preserved.

DeductionSourceApplied when
Holiday hoursResource contract's holiday schedule (country-specific)paid_holidays = false
Vacation hoursResource Vacation rows for the periodpaid_vacation = false
Absence hoursResource Absence rows for the periodAlways
Example: April 2026 (22 workdays), $400/day, 40h/week, 1 holiday, 1 vacation day, 4 hours of absence, both flags false:
  • Weekday hours = 22 × 8 = 176
  • Hours worked = 176 − 8 − 8 − 4 = 156
  • Days worked = 156 / 8 = 19.5
  • Amount = 19.5 × $400 = $7,800.00

Hourly contracts

Same hours-first computation as daily, but the final step skips the days conversion:

amount = hours_worked × hourly_rate

Same flag semantics: holidays / vacations only deduct when their respective flag is false; absences always deduct.

Example: April 2026 (22 workdays), $50/hr, 40h/week, 1 absence (8h), paid_holidays = true, paid_vacation = true:
  • Hours worked = (22 × 8) − 8 = 168
  • Amount = 168 × $50 = $8,400.00

paid_holidays / paid_vacation flags

Both flags live on ClientContract and apply to all three rate types:

FlagDefaultEffect when trueEffect when false
paid_holidays true (existing contracts migrated to true) Holidays are paid at the daily rate. Monthly: no deduction (covered by monthly rate). Daily/hourly: holiday hours are not subtracted from hours_worked. Holidays unpaid. Monthly: each weekday holiday deducts one effective_daily_rate as a deduction row. Daily/hourly: holiday hours subtracted from hours_worked.
paid_vacation true Same as above: vacation paid at the daily rate; not deducted. Vacation hours / days deducted using the same formula as holidays.

Setting either flag to false on a monthly contract also forces the workday-based model even for pre-cutoff invoice dates, since the legacy calendar-day formula can't express per-day deductions.


Absence cost (display-time)

The amount shown for each absence on an invoice is recomputed at display time by computeAbsenceCost(contract, invoiceDate, absence), which picks the formula the invoice was originally billed under:

FrequencyInvoice dateAbsence cost formula
MonthlyBefore PRORATION_CUTOFFabsence.hours × (cost × 12 / 2080)
MonthlyOn/after PRORATION_CUTOFFcost / workdays_in_month per absence row
Daily / hourlyAny0 — already absorbed into hours_worked (no separate deduction row)

For monthly contracts under the post-cutoff model, holiday and vacation deductions use the same formula as absences (with hours = 8 per weekday), so all three deduction types are consistent within a given invoice month.


Invoice adjustments & passthroughs

Beyond the base computed amount, a ClientContractInvoice can carry adjustments of two kinds, both stored in ClientContractInvoiceAdjustment with a signed amount:

  • Deductions — emitted by the billing routine for monthly holiday / vacation deductions when the corresponding flag is false. amount is negative.
  • Passthroughs — billable items from the resource side (e.g., a billable resource.InvoiceAdjustment) mirrored onto the client invoice. amount is whatever the billable_amount was; usually positive.

Both are added into net_amount alongside baseAmount:

net_amount = base_amount + sum(deductions) − sum(absence_costs) + sum(passthroughs)

(Deduction amounts are already negative, so the formula reads correctly.)


Billed absences

Absences are stored on the resource side (resource.Absence). When a client invoice is generated that includes the absence, two pointers are set:

  • billed_at — the timestamp of billing.
  • billed_by_invoice_id — the ClientContractInvoice that absorbed it. Prevents the same absence from being billed twice.

Absences with billed_at = NULL are eligible for the next invoice in the period. The display-time cost shown on a billed absence is recomputed each render via computeAbsenceCost, not stored, so the cost is always self-consistent with the invoice date.


Contract end dates

If revised_end is set on a contract, it overrides contract_end for the intersection calculation. This is how mid-period contract shortenings flow into billing without losing the original end date.


QB-vs-DB reconciliation note

When matching against QuickBooks invoices, expect occasional small per-month residuals from:

  • Bonus / catchall lines — QB sometimes adds line items (e.g. weekend bonuses) directly to a customer invoice that the DB billing model doesn't generate. The QB-reconciliation report annotates these as data_gap.
  • Same-numbered supplemental QB invoices — see #908. Some QB doc numbers represent supplemental adjustments that don't have a 1:1 DB counterpart; reconciliation pairs by doc_number first then date, and unmatched ones surface as Missing in DB.
  • Pre-cutoff calendar-day vs workday proration — partial-month invoices generated under the legacy formula may differ from a regen under the current formula by a few cents to a few dollars per resource per month.

Key definitions

WorkdaysMonday through Friday (excludes weekends)
Workdays in monthVaries per month (e.g. Feb = 20, Apr = 22, Jul = 23)
IntersectionThe overlap between the contract's active period (using revised_end if set) and the invoice month
Holiday scheduleCountry-specific holiday calendar assigned to each resource contract
AbsenceAn unplanned period the resource did not work, recorded in hours (partial-day allowed)
VacationA planned period off, drawn from the resource's vacation allotment, also recorded in hours
PRORATION_CUTOFF2026-04-01. Invoice dates < cutoff use the legacy calendar-day formula (monthly only, both flags true). On/after = workday-based for all rate types.
paid_holidays / paid_vacationPer-contract flags controlling whether holidays / vacation are baked into the rate (true) or deducted (false).