Skip to main content
Checked against the product · 2026-10-05

Payroll Pay-Code Mapping — Controller Guide

When payroll comes in from your payroll provider, every line carries a pay code — a short label like REG, OT, PTO, or a numeric code like 103. Before that payroll can land in the general ledger, each code has to be told which GL account it belongs to and what kind of cost it is. That mapping is a controller decision, it's confirmed once per code, and any code that isn't mapped yet parks its payroll and raises the payroll flag on the month-end close checklist. This guide explains the idea, then shows you exactly where the mapping lives, how it's set up, and how to prove it's working — because unlike most Accounting features, pay-code mapping has no dedicated screen you click through; it's configured at onboarding and maintained through your implementation contact.

For: controllers · Time: ~10 minutes to read · You'll need: the Accounting module; your prior system's or payroll provider's pay-code list.

Where it lives

There is no dedicated "Pay-Code Mapping" page in the left nav. Mapping rows are created and maintained through the Accounting API (/api/accounting/payroll/pay-code-mappings), which your Arcvue implementation contact drives during onboarding. You verify the results in two places you can click to: the month-end close checklist at Accounting → General Ledger → Period Close (/accounting/close), whose payroll gate lists any code still needing a mapping, and the Pay Code Distribution report at Accounting → Reports (Payroll category), which shows every code that actually appeared in payroll. The payroll gate also carries a Review payroll button straight to Accounting → Cash → Feeds on its Payroll tab, where the parked runs show their status and a count of the lines that still need a mapping — that tab reviews and re-posts payroll, it is not a mapping editor.


Part 1 — The ideas you need first (read once)​

A pay code is a label; the GL needs an account​

Your payroll provider sends payroll as coded lines — "code 103, $4,200," "PTO, $600." The code is the provider's shorthand. Your books, though, need to know that 103 is (say) direct labor debiting a specific labor account, while PTO is a leave/fringe cost hitting a different one. Until a code is mapped, Arcvue doesn't know where its dollars go — so it parks the payroll line with a posting status of pending_mapping rather than guess.

The mapping is human-confirmed — the engine never infers it​

This is deliberate and DCAA-relevant: Arcvue will not invent a pay-code mapping. A wrong mapping silently misroutes labor cost (and can corrupt an indirect rate), so the system requires a person to confirm each code once. After it's confirmed, that mapping is reused automatically on every future payroll run — a code is mapped once, not every month.

Every mapping carries a cost classification​

Each mapping pins down two things: the GL account the line debits, and a cost classification, which is what actually drives routing. There are exactly eight valid classifications:

  • direct_labor — billable labor, distributed to contracts
  • indirect_labor_overhead — indirect labor routed to the overhead pool
  • indirect_labor_ga — indirect labor routed to the G&A pool
  • indirect_labor_fringe — indirect labor routed to the fringe pool
  • indirect_labor_fringe_sca — fringe labor for service-contract (SCA) work, routed to the SCA fringe pool
  • pto_accrual — leave / paid time off
  • unallowable — kept out of billed indirect rates (FAR 31)
  • pass_through — filtered out of the GL entirely

The classification is why the mapping matters beyond the account: it decides whether a dollar is billable, which indirect pool it feeds, or whether it's unallowable. (These eight values are enforced on the server — a mapping submitted with anything else is rejected.)

Descriptions let one code mean two things​

Occasionally the same code carries different meanings depending on its line description. A mapping can therefore be scoped to a code plus a description. When Arcvue resolves a payroll line it tries, in order: (1) an exact code + description match, then (2) a code-only mapping (description blank), then (3) the code's one active mapping, but only when the code has exactly one. A code carrying several description-specific mappings, with no code-only fallback, is never resolved by picking one of them: the line stays pending_mapping and the code appears on the worklist, because a guess would choose both the account and the classification. So a description-specific mapping is an override; you almost always also want a plain code-only mapping as the fallback.

Why unmapped codes block the close​

The month-end close checklist carries a payroll-posting-complete item. If any payroll line is still pending_mapping, payroll isn't fully in the books and that item is raised.

It is a flag, not a hard gate: it warns you and still lets you close — unless your company has chosen to make it a hard stop (see the note under Step 4). So by default the responsibility is yours — closing with unmapped labor leaves real cost unrecorded and throws off both your cost picture and any cost-reimbursable billing, and nothing stops you doing it. Clear the item before you close rather than relying on the checklist to refuse.


Part 2 — How to run it​

Because there's no mapping screen, "running it" means: getting the initial map set up, telling your implementation contact when a new code appears, and — the part you own every month — verifying that nothing is unmapped before you close. The steps below name the real controls you click to do that verification.

Step 1 — Get the initial map set up (onboarding, one time)​

During onboarding your Arcvue implementation contact loads your pay-code map for you. You supply the raw material: your payroll provider's (or your prior system's, pending prior-system confirmation) list of pay codes, and for each one the GL account it should debit and which of the eight classifications above it is. Each code is written once as a mapping row via POST /api/accounting/payroll/pay-code-mappings (fields: code, gl_account_id, cost_classification, and optional description). You don't type this into a form yourself — you review and confirm the list your contact prepares. This is the human-confirmation step the engine relies on.

Step 2 — Find the codes that still need a mapping​

Open the close checklist: in the left nav go to Accounting → General Ledger → Period Close (/accounting/close). Pick the month you're closing in the 12-month timeline, then read the Payroll group in the checklist. The payroll-posting row's detail message lists the distinct codes that appear in payroll but have no active mapping — that's your worklist, and every code on it is holding payroll out of the period.

If you want the same worklist independent of the close screen, it's also the unresolved_codes list returned by GET /api/accounting/payroll/pay-code-mappings (the endpoint returns your existing mappings and the codes that still need one). Your implementation contact can pull it on request.

Step 3 — Confirm a mapping for each unmapped code​

For every code on the worklist, decide the two facts — which GL account it debits and which of the eight classifications it is — and send them to your implementation contact to add. Adding a code is a single confirmed row; the same call updates an existing code if you're correcting one (it matches on code, refreshes the account/classification/description, re-activates the row, and stamps who confirmed it and when). Once the row exists, the payroll line that was sitting at pending_mapping can post on the next posting run, and the code is resolved for every future payroll — you never map it again.

Step 4 — Re-check the worklist, then close​

Confirming the mapping does not clear the row on its own, and this is the step people miss. The gate tests posting status, not whether a mapping exists: it passes only when no payroll line touching the period is still sitting at ingested, pending_mapping, pending_employee or suspense. The lines that were parked have to be posted after the mapping lands.

That happens on its own: the daily payroll sync (08:25 UTC) re-posts every ingested run, so a code confirmed today clears on tomorrow morning's run. If you need it sooner, your implementation contact can re-run the post immediately. Either way, watch it on Accounting → Cash → Feeds → Payroll — the run's status moves off Pending mapping once the lines post.

Then return to Accounting → General Ledger → Period Close and re-read the Payroll group. Once the parked lines are posted, the payroll-posting row clears (green). Continue the normal close from there — see Close a Month (Period Close).

It flags, it does not stop you

The payroll-posting row is a flag, not a hard stop — by default you can close a period with it still red, and Arcvue will let you. Treat it as the thing to resolve before you close rather than as a door that is locked, and if you close over it, know that the parked payroll is not in that period's numbers. A company can instead choose to make this check a hard stop, so that it refuses the close until the payroll posts. That is a per-company setting, which today your Arcvue implementation contact sets for you; see Close a Month, "Turning a warning into a hard stop for your company".

Step 5 — Sanity-check what actually posted​

As a final confidence check, open Accounting → Reports, choose the Payroll category, and run Pay Code Distribution — "Breakdown by pay code across the year." This shows every code that actually flowed through payroll and where it landed, so you can eyeball that a code you just mapped is now distributing to the account and pool you intended (e.g., a code you classified direct_labor is showing as billable, not sitting in an indirect pool).


Part 3 — When something looks wrong​

SymptomWhat it meansWhat to do
Payroll shows unposted / pending_mapping linesOne or more pay codes aren't mapped — the single most common reason the payroll row is red. It flags the close, it does not block it.On Accounting → General Ledger → Period Close, read the payroll-posting row's list of unmapped codes and get each one confirmed (Step 3).
I mapped the code and the payroll row is still redExpected, briefly. The gate tests posting status, so the lines that were parked still have to post.Wait for the daily payroll sync (08:25 UTC), or ask your implementation contact to re-run the post. Watch the run on Accounting → Cash → Feeds → Payroll.
I mapped a code but a new line still won't resolveLikely a description mismatch. If you only created description-specific mappings and the new line's description matches none of them, resolution falls through; with two or more of them, Arcvue will not pick one, so the line stays pending_mapping.Add a plain code-only mapping (blank description) as the fallback, in addition to any description-specific ones.
A code that used to resolve suddenly stoppedIts mapping was deactivated. Inactive (retired) mappings are treated as missing — by design, so you can supersede an old mapping.Re-confirm the code (re-POSTing the same code re-activates its row), or point the code at the correct current mapping.
Labor is landing in the wrong place (billable showing as indirect, or vice-versa)The cost classification on the mapping — not just the account — drives routing.Fix the classification on that code's mapping to the correct one of the eight values.
A new pay code appeared this month and nothing warned meA code only becomes visible once it shows up in payroll; the first run with it will surface it as unresolved.Treat the close payroll item as the safety net — it will list the new code. It reports; it does not refuse the close, so act on what it lists. Confirm it (Step 3), then close.
The classification I want to use is rejectedOnly the eight listed classifications are valid; anything else is refused server-side.Pick the closest correct value from the list in Part 1 (most "indirect labor" cases are one of the four indirect_labor_* pools).

One-line summary​

Every pay code must be confirmed once — to a GL account and one of eight cost classifications — before its payroll can post; Arcvue never guesses the mapping, and parks unmapped lines as pending_mapping, which raises (but does not block) the payroll row on the close checklist until those lines are mapped and posted. There's no dedicated screen: your implementation contact sets and maintains the map via the API, and you verify it on Accounting → General Ledger → Period Close (the payroll gate) and confirm results on the Pay Code Distribution report.

  • Where the payroll gate blocks the close → Close a Month (Period Close) (/accounting/close)
  • How labor feeds the pools → Indirect Rates (/accounting/rates)
  • Keeping unallowable labor out of billed rates → Concepts: unallowable cost

Also on the Period Close page​

The Accruals awaiting a bill panel (see Close a Month, Step 3) carries the decisions on an accrued payable whose bill never came: Link this bill, Re-accrue — with the fields Accrue into, Expense account and Amount — and Close — never coming, which requires the note Why the bill is not coming.