Connected Close — GL Stitching
| Owner | Classification | Version | Effective | Next review | Status |
|---|---|---|---|---|---|
| Platform Engineering | Internal | 0.3 | 2026-09-17 | 2026-12-17 | Draft |
Purpose. Operating guide for how a CloseHub task resolves to the correct GL account and stays tied to the source module — the account-key rule, the control-variant resolution that makes the tie deterministic, and the reconciliation system as system-of-record. The companion Run a Close Task as a Module Automation covers how the step dispatches the engine; this page covers what account it runs against and why the numbers agree across modules.
The tie (close task → GL account → module)
A close step carries a GL account code. That code resolves to exactly one reconciliation control account, whose periodsummary is the account × period reconciliation every module ties to.
closehub.task
│ glaccountcode (+ segment1) e.g. 100001 / 7000
▼ attachReconPeriodSummaryId (control-variant resolution)
reconciliation.account ← the account master (System-of-Record chart)
▼
reconciliation.periodsummary ← account × period: glbalance, bankbalance,
│ subledgerbalance, reconciliationitembalance,
│ unidentifieddifference
▼
SOURCE MODULE ← cash (AR), invoice (AP), journal (JE) tie hereThe number on the close checklist is the number in the reconciliation module — it is resolved, not copied.
The account key — number + segment
An account's identity is accountnumber + segment1, never the number alone. The same accountnumber may have several active reconciliation.account rows that differ only by segment1; they are distinct accounts, not duplicates. The key is encoded in accountuniqueidentifier (e.g. 100001-7000-100001 vs 100001-6000-100001).
A close task must therefore pin which variant it reconciles. Where a task omits segment1, the number alone is ambiguous and resolution must select the control deterministically.
Control-variant resolution
The recon data set carries one control variant per balance-sheet number: the variant holding the live balance (non-zero glbalance), with its sibling variants zeroed. attachReconPeriodSummaryId resolves the task's account to that control:
ORDER BY
($3 <> '' AND a.segment1 = $3) DESC, -- 1. the task's OWN segment, when set
(glbalance <> 0) DESC, -- 2. else the non-zero CONTROL variant
(status = 'Not Prepared') DESC,
periodsummary.idThe single-control shape is produced upstream by the reconciliation data generator (one non-zero control per number; siblings zeroed), which is what makes step 2 unambiguous. Resolving on the number alone — or on a fakeable field such as bankbalance — selects the wrong sibling and silently breaks the tie.
Where the segment comes from — the onboarding mapping
The account key is authored once, at onboarding, from the reconciliation account master (the account System-of-Record — see Account Model & Chart of Accounts) — it is never hand-typed onto a task. Two artifacts meet:
| Role | Table | Carries |
|---|---|---|
| Source (SoR) | reconciliation.account | accountnumber, segment1..10, accountcategory (CASH / AR / AP / …), accounttype |
| Consumer (the work) | closehub.tasktemplate | glaccountcode, segment1..10, the step sequence |
The recon account master is loaded first (the customer's chart of accounts → reconciliation.account, one row per number + segment variant). CloseHub templates then bind to it. The join key is the account category/class, not the raw number:
reconciliation.account.accountcategory → closehub.tasktemplate
CASH (100001-6000, 100001-7000) → Bank Reconciliation
AR (100051-7000) → Close AR Sub-ledger
AP (100043-6000) → Close AP Sub-ledgerAt task generation (rollover / onboarding kit) the template's glaccountcode — and, when the class needs it, segment1 — are copied onto closehub.task from the recon account, so the task inherits the account key from the SoR rather than a hand-entered value. The carry-through is automated (tasktemplateeprocessor.razor → taskrollover_createtasks_V016.razor's MapTaskTemplateToTask); what is not automated is sourcing the value from the recon master — today glaccountcode / segment1 are free-text on the import workbook and the admin/task-templates/[id] editor, with no recon-account picker or validation. That picker/validation is the open onboarding affordance.
Two binding shapes
| Shape | When | How segment1 is set |
|---|---|---|
| Single-control (today) | A number has exactly one live balance-sheet variant (one non-zero control, siblings zeroed). | Left empty; runtime attachReconPeriodSummaryId resolves to the non-zero control (rule above). No mapping needed beyond the number. |
| Multi-live-segment | A number carries genuinely distinct live balances on more than one segment (multi-entity, per-cost-centre control). | Stamped at generation from reconciliation.account.segment1. Generate one task per live recon account in the class; each pins its own segment. Number-alone resolution is ambiguous here and must not be relied on. |
Onboarding rule. Map recon-account class → close template; at generation, emit one task per live recon account in that class and copy accountnumber (+ segment1) from reconciliation.account. The segment is a projection of the recon master — never keyed by hand, and never inferred from the number when more than one segment is live. Recon owns account identity; CloseHub owns the template→task generation that carries it.
Reconciliation as System-of-Record
Every module reconciles to the recon GL. The relationship is proven by a runnable contract (verify-fig-alignment.sql): the trial balance foots each period; cash AR sub-ledger equals open AR; invoice AP sub-ledger equals open AP; journal lines and posting-determination roles resolve to real recon accounts. A close task reading a periodsummary therefore reads the same figure the source module reports.
unidentifieddifference is computed by the engine as:
unidentifieddifference = glbalance − bankbalance − subledgerbalance − reconciliationitembalanceA reconciled account nets to zero; a residual is the account's explained (or unexplained) close item.
The exception loop — drafting the true-up JV
Where Validate & Compare leaves a residual unidentifieddifference, the task's Post Adjustment JE step runs postreconadjustment (registry id 56, application journal) to close it. The step reads the variance for the same control periodsummary the reconciliation resolved (via periodSummaryId), so the adjustment targets the exact account variant — never a zeroed sibling.
It builds a balanced two-line true-up against the account and an offset:
| Variance | Interpretation | Lines |
|---|---|---|
> 0 | GL overstated vs sub-ledger | CR account · DR offset |
< 0 | GL understated vs sub-ledger | DR account · CR offset |
0 | Account ties | No JV — step returns skipped. |
The offset account is the admin-fillable input (offsetAccount / offsetAccountName, declared NULL in automationparameter) — an AR write-down offsets to Bad Debt (GL6410), an AP timing break to GRNI (GL2050).
The JV is advisory, not auto-posted. It flows through the canonical journal intake (documentreceipt → extractedjournalvoucher + lines → processjournal) with assignApprove: false, so it lands as a preparer Draft in the journal intelligence queue — carrying a data-derived reason (the account, period, direction, magnitude, and recommended treatment, composed from the reconciliation facts). The preparer reviews the treatment, adjusts if needed, and confirms to submit for approval. The automation proposes the entry; a human commits it.
Link-back to the module
closehub.taskinformationlink.hyperlinkurl deep-links a task to the live module screen (the recon account workspace, the invoice queue, the collections queue), so the close checklist navigates into the source-of-truth record rather than a copy.
Worked example — the operating-bank bank reconciliation
The bank-rec step (account 100001, control segment 7000) ties three sides by construction:
| Side | Value | Source |
|---|---|---|
| Book (GL) | glbalance 933,632.11 | recon trial balance |
| Bank statement | bankbalance 1,018,632.11 | Bank-Statement-<period>.csv → bankbalanceimport |
| Reconciling item | −85,000 (outstanding check, off-statement) | recon item (Bank template, Timing) |
933,632.11 − 1,018,632.11 − 0 − (−85,000) = 0. The statement fixture's running balance ends exactly on bankbalance, so runaccountreconciliation clears the account to a zero difference.
The bank-import variant hazard
bankbalanceimport resolves reconciliation.account by accountnumber hash, so for a number with multiple segment variants it can attach the bankbalance to a zeroed sibling instead of the control. runreconciliation then joins periodsummary.accountid = bankbalance.accountid and places the balance on the wrong row — the control reads "no statement" and the sibling shows a large false break. The correction repoints reconciliation.bankbalance.accountid to the control variant at the source, so every subsequent engine run (including the automated bank-rec task) pulls it onto the correct account.
Operational constraints
- A task without
segment1relies on the control variant existing (one non-zero variant per number). Regenerate/reconcile so the single-control shape holds before depending on resolution. - Zeroed sibling accounts still exist as
$0rows; they roll up to zero and are harmless, but they are why number-only matching (e.g. the bank import) needs the control repoint. - The tie holds only while the FIG contract passes — treat a
verify-fig-alignment.sqlfailure as a module out of alignment.
Related
- Run a Close Task as a Module Automation → how the step dispatches the engine that reconciles this account.
- Run the Reconciliation Matching Engine → how matched items land on an account as reconciliation items.
- Account Model & Chart of Accounts → where account identity lives (recon SoR), the per-module tables, and the modular-shipping stance.
Revision history
| Version | Date | Author | Change |
|---|---|---|---|
| 0.1 | 2026-09-01 | Platform Engineering | Initial draft — account key, control-variant resolution, SoR contract, operating-bank worked example. |
| 0.2 | 2026-09-05 | Platform Engineering | Added the exception loop — postreconadjustment drafting a balanced true-up JV (variance-direction lines, admin-fillable offset, data-derived reason, advisory Draft into the journal intelligence queue). |
| 0.3 | 2026-09-17 | Platform Engineering | Restored Where the segment comes from — the onboarding mapping (recon master → close template by class; segment stamped at generation; single-control vs multi-live-segment binding shapes; the razor carry-through vs the missing recon-account picker/validation). Cross-linked to the new Account Model doc. |