Going live with a Ledger in production often requires importing existing data. You can approach this in two ways:
Use a historical import when:
A balance import may be easier when:
A historical import requires you to produce a Ledger Entry for every historical financial event, which means individually dealing with any inconsistencies or missing data. With a balance import, you can handle issues in bulk. You only need to calculate a consistent set of balances at a chosen point in time.
A historical import involves processing all your existing data and posting Ledger Entries for every financial event. The approach:
With a balance import, you pick a go-live date and consolidate existing data before that date into a set of initial balances. The Ledger will only contain entries from that point forward.
The overall process:
Pick a specific moment in time to anchor your migration. Go-live timestamps are usually midnight before the start of a month or quarter, so that you can align starting balances with existing balance reports. If you are using balance reports from multiple sources like both your bank and internal systems, ensure their timezones match. If they do not, you need to calculate aligned initial balances from transaction level data.
While you develop your funds flows, direct live traffic to a temporary Ledger in your production workspace. This interim Ledger lets you test your Schema and verify that payments reconcile and balances update as expected. Using an interim Ledger reduces the number of entries you need to reverse later.
Close to your go-live date:
To support setting initial balances, add the following to your Ledger:
asset account called initial-balance-offsetincome account called initial-retained-earningsset-initial-balanceCalculate a set of starting balances for leaf asset and liability accounts in your Schema at the go-live timestamp, including account template instances. You do not need to calculate starting balances for income and expense accounts since they track deltas in your assets and liability accounts.
Your Ledger will typically have three categories of asset and liability accounts:
It's useful to automate this process so that you can run it multiple times as you learn more about transforming your data into a balance snapshot. You'll reuse the same code to validate balances once the import process is completed.
Bring together all asset and liability starting balances into a combined data set. To calculate the balance for the initial-retained-earnings account:
retained_earnings = total(asset_accounts) - total(liability_accounts)
Add a row to your data set to set this balance on this account.
To set an initial balance, post a set-initial-balance runtime entry with these lines:
initial-balance-offset accountDepending on the type of the target account, you may need to flip the sign when posting to the initial-balance-offset account:
| Target account type | Offset account amount |
|---|---|
| asset | Flipped amount |
| liability | Original amount |
| income | Original amount |
| expense | Flipped amount |
Ensure the posted timestamp is set to your go-live timestamp.
For unlinked accounts, use addLedgerEntry to set initial balances:
mutation AddLedgerEntry($ik: SafeString!, $entry: LedgerEntryInput!) {
addLedgerEntry(
ik: $ik
entry: $entry
) {
__typename
... on AddLedgerEntryResult {
entry {
id
ik
posted
}
}
}
}{
"ik": "initial-balance-unique-id",
"entry": {
"ledger": { "id": "ledger-id" },
"type": "set-initial-balance",
"posted": "2024-01-01T00:00:00.000Z",
"lines": [
{
"account": { "path": "assets/users/user-123" },
"amount": "10000"
},
{
"account": { "path": "assets/initial-balance-offset" },
"amount": "-10000"
}
]
}
}For linked accounts, you need to call syncCustomTxs before you can call reconcileTx to set the initial balance. Assuming you have fewer than 200 linked accounts in your Schema, you can make a single call to syncCustomTxs:
mutation SyncTransactions(
$link: LinkMatchInput!
$txs: [CustomTxInput!]!
) {
syncCustomTxs(link: $link, txs: $txs) {
__typename
... on SyncCustomTxsResult {
txs {
id
externalId
amount
date
description
}
}
... on Error {
code
message
}
}
}{
"link": { "id": "link-id" },
"txs": [
{
"id": "initial-balance-tx",
"accountId": "account-1",
"amount": "10000",
"currency": "USD",
"timestamp": "2024-01-01T00:00:00.000Z"
}
]
}This adds an initial balance transaction to each account.
Then for each linked account, post the set-initial-balance runtime entry with reconcileTx:
mutation ReconcileTx(
$entry: LedgerEntryInput!
) {
reconcileTx(
entry: $entry
) {
... on ReconcileTxResult {
entry {
type
created
posted
}
lines {
amount
key
description
account {
path
}
}
}
... on Error {
code
message
}
}
}{
"entry": {
"type": "set-initial-balance",
"ledger": {
"id": "ledger-id"
},
"posted": "2024-01-01T00:00:00.000Z",
"lines": [
{
"account": { "path": "assets/users/user-123" },
"amount": "10000"
},
{
"account": { "path": "assets/initial-balance-offset" },
"amount": "-10000"
}
]
}
}Once completed, ensure the balance on the initial-balance-offset account is zero to verify all balance setting entries were posted.
To avoid double-counting, any entries posted before the go-live timestamp must be reversed. Any balance impacts from these entries will already be included in the initial balances set.
To find entries to reverse, query:
query GetEntriesToReverse(
$ledger: LedgerMatchInput!
$filter: LedgerEntriesFilterSet
) {
ledger(ledger: $ledger) {
ledgerEntries(filter: $filter) {
nodes {
id
type
posted
}
}
}
}{
"ledger": { "id": "ledger-id" },
"filter": {
"posted": {
"lessThanOrEqualTo": "2024-01-01T00:00:00.000Z"
},
"type": {
"notEqualTo": "set-initial-balance"
}
}
}This query finds all entries posted on or before the cutoff timestamp, while filtering out the entries used to set initial balances.
Save the list of Ledger Entry IDs to reverse. For each, call reverseLedgerEntry:
mutation ReverseLedgerEntry(
$id: ID!
) {
reverseLedgerEntry(
id: $id
) {
__typename
... on ReverseLedgerEntryResult {
reversingLedgerEntry {
ik
id
type
posted
created
reverses {
id
created
}
reversalPosition
}
reversedLedgerEntry {
ik
id
type
posted
created
reversedBy {
id
created
}
reversalPosition
}
}
... on Error {
code
message
}
}
}{
"id": "entry-id-to-reverse"
}Once the import process is complete, balances in your Ledger will be a combination of initial balances and recently posted data. To validate, check the balances of your accounts:
Once the import process is complete you can:
set-initial-balance entry typeinitial-balance-offset and initial-retained-earnings accounts