How the schedule is calculated

Live formulas from src/lib/deferred.ts — any change to those functions updates the rules, source snippets, and example results shown here.

1. Eligibility — which invoice lines are processed

Filtering is based on GL Account. Stock Codes / Tax Codes are not used for eligibility.

  • The GL Account comes from the Sales Invoice detail row (AccountId / expanded Account), not from the invoice header.
  • Codes are normalized (trim + uppercase) before matching.
  • A line is processed only when its GL Account exists in the setup list with active = true.
  • Lines without a GL Account (comments, sub-totals, descriptive rows) are skipped silently. Lines with an unknown / inactive GL are counted as Ignored.
function isProcessable(glAccount, rules) {
	if (!glAccount) return false;
	const c = normalizeCode(glAccount);
	const r = rules.find((x) => x.code === c);
	return !!r && r.active;
}
function lineGLAccount(d) {
	const code = d.account?.code ?? d.glAccount?.code ?? "";
	if (code) return code;
	if (d.accountId != null) return String(d.accountId);
	if (d.glAccountId != null) return String(d.glAccountId);
	return "";
}

Example: normalizeCode(" 4200-0000 ") → 4200-0000

2. Revenue amount — Ex Tax from the Sales Invoice Register

Revenue is taken directly from the N3 Sales Invoice Register's Ex Tax value. No tax rate is applied by this app.

revenue = amountLocal ?? amount
// Amount in the N3 Sales Invoice Detail is the Before Tax / Ex Tax value.
// Net Amount is ignored — Before Tax is never derived from it.
// Tax Amount is display-only and never enters any calculation.
function lineExTaxAmount(d) {
	const explicit = d.taxableAmountLocal ?? d.taxableAmount ?? d.beforeTaxAmountLocal ?? d.beforeTaxAmount ?? d.taxExclusiveAmountLocal ?? d.taxExclusiveAmount;
	if (explicit != null) return round2(explicit);
	const gross = d.amountLocal ?? d.amount ?? d.netAmountLocal ?? d.netAmount ?? 0;
	const tax = lineTaxAmount(d);
	return round2(tax > 0 && gross > tax ? gross - tax : gross);
}
Detail with Before Tax (Amount) 1,200.00 and Tax 144.00
Revenue = 1,056.00

Cancelled invoices (isCancelled = true) are excluded at the fetch layer and never reach the schedule.

3. Service period — DateRef1 / DateRef2

The service period is taken strictly from the Sales Invoice detail's DateRef1 (Start Date) and DateRef2 (End Date). There is no fallback to the invoice date or Qty × UOM.

start = dateRef1 ?? manualCorrection.start
end   = dateRef2 ?? manualCorrection.end

if (!start || !end || end < start):
  → Missing Service Period — the line is NOT processed
    (Accounting enters the Start/End Date in the app)

months = months_inclusive(start, end)

Lines with an incomplete service period are listed under Missing Service Period on the Revenue Recognition Schedule. Once the Start Date and End Date are entered there, the line is scheduled on the next calculation.

4. Monthly allocation

Revenue is divided evenly across months; rounding remainder lands on the final month so totals reconcile to the cent.

months_inclusive = (end.year - start.year) × 12 + (end.month - start.month)
                 + (end.day >= start.day ? 1 : 0)
monthly = round2(revenue / months)
for i in 0..months-1:
  amount[i]    = monthly                       (i < months-1)
  amount[last] = revenue - sum(previous)       // absorbs rounding

5. Journal Entry — consolidated by GL Account

One journal line per GL Account per side. Descriptions are auto-generated from the selected accounting period.

To Set Up Deferred Revenue for the month of {accounting period}
  Dr  Sales (per GL Account)
      Cr  Deferred Revenue

To Recognize Revenue for the month of {accounting period}
  Dr  Deferred Revenue
      Cr  Sales (per GL Account)
  • Amounts are consolidated by GL Account across all supporting invoice lines.
  • Set Up Deferred Revenue uses only invoiced amounts in the period.
  • Recognize Revenue uses system-scheduled recognition for the month.
  • Each journal amount is clickable and drills down to the underlying invoice breakdown.
  • Posting is blocked when Debit ≠ Credit, when Deferred Revenue would go negative, or when recognition exceeds the available deferred revenue for a GL.
  • After a successful post, the accounting period is locked and further sync / posting for that month is prevented until the period is unlocked.

6. Dashboard KPIs

What each dashboard card means for the selected accounting period.

  • Sales — total Ex Tax sales recorded in N3 for the selected accounting period.
  • Deferred Revenue to Set Up — total of the To Set Up Deferred Revenue journal side for the period.
  • Revenue to Recognize — total of the To Recognize Revenue journal side, i.e. system-scheduled recognition in the period.
  • Ending Deferred Revenue Balance — the prior deferred balance plus the period's deferred-revenue movement, less the revenue recognized from DateRef1/DateRef2 schedules.

Worked example (live)

Computed by running the real buildSchedule on a sample invoice — these numbers update if the formula changes.

Invoice INV-DEMO · 06/15/2026
Line: GL 4200-0000 · Ex Tax 1,200.00
DateRef1: 06/15/2026 · DateRef2: 06/14/2027
Revenue (Ex Tax)
1,056.00
Months
12
Monthly
88.00
MonthAmount
Jun 202688.00
Jul 202688.00
Aug 202688.00
Sep 202688.00
Oct 202688.00
Nov 202688.00
Dec 202688.00
Jan 202788.00
Feb 202788.00
Mar 202788.00
Apr 202788.00
May 202788.00
Total1,056.00

Source of truth: src/lib/deferred.ts. Edit the functions there and this page reflects the new rules on next load.