Schedule icon
OutputValues icon
If icon
Fail icon
Queries icon
Log icon
UploadFiles icon
Switch icon
Set icon

Reconcile intercompany balances across entities and generate consolidation eliminations

Month-end intercompany reconciliation in Kestra and DuckDB. Counterparty matching, goods in transit, eliminations, CTA and a period lock.

Categories
BusinessData

Any group with more than one legal entity has to agree its intercompany balances before it can consolidate. Each entity says what it owes the others, in its own books, currency and timing. Before the eliminations can be posted, the group must find out why the two sides differ. Usually this happens in a spreadsheet exchanged by email between entity accountants. This flow pairs every document across entities, says why each difference exists and what to do about it, books the timing items, and builds the eliminations.

It runs with no setup and no network. DuckDB generates five entities in EUR, GBP, USD and INR, with these intercompany flows:

  • management fees and royalties,
  • goods shipped between entities, booked by the receiver on arrival,
  • services,
  • a EUR 2,000,000 loan with monthly interest.

This blueprint was created by zkasuran.

How a difference is explained

Every intercompany document of the month, and the loan, is looked up on both sides by its document number.

Cause Meaning Effect
IN_TRANSIT The receiver books it after period end: goods shipped on the last day Booked as purchases in transit in the receiver. Needs approved_by
NOT_BOOKED_BY_RECEIVER Only the sender booked it Blocks
WRONG_COUNTERPARTY The receiver booked it against another entity Blocks
AMOUNT_MISMATCH Both sides booked it, for different amounts Blocks
UNEXPLAINED_PAIR_DIFFERENCE A pair is still apart and no document explains why Blocks

Each blocker carries the action to take, for example E200 booked it against E400 instead of E300. Change the counterparty.

Eliminations

  • Balance sheet: receivables, payables and the loan are eliminated as each entity carries them. The rate is the group closing rate, moved by the entity's own revaluation drift when the balance is in a foreign currency, because a local bank rate never quite matches the group rate. The residual is the translation difference. It goes to the translation reserve (CTA) and must stay within fx_tolerance.
  • Income and expense: eliminated at the average rate, by document month. Last month's goods in transit are received this month, but the in-transit entry booked last month reverses on day one, so the receipt nets out.
  • After the CTA line, the eliminations must net to exactly zero.

The counterparty matrix shows who owes whom in group currency, entity by entity, for the review.

How it works

  1. period_control and period_gate refuse a closed period or one whose previous period is open.
  2. reconcile (io.kestra.plugin.jdbc.duckdb.Queries) builds the lines, explains each document and proposes the timing entries. It then checks the pairs and builds the eliminations, the CTA and the matrix.
  3. log_close prints the close. evidence stores pairs.csv, differences.csv, proposed-entries.csv, eliminations.csv, counterparty-matrix.csv and blockers.csv.
  4. decide (io.kestra.plugin.core.flow.Switch):
    • READY: locks the period.
    • Otherwise the run fails with the reason: BLOCKED, OUT_OF_TOLERANCE (CTA too large or P&L not netting), or NEEDS_APPROVAL (goods in transit).

Tested end to end

On Kestra 2.0.3 OSS, in this order.

Run Inputs Result
1 2026-09 12 documents, 9 pairs. One shipment from E300 to E200 (115,640.00 EUR) shipped on the 30th and received on October 3rd: in transit. All 9 pairs agree once it is booked. Balance sheet 2,513,011.08 and P&L 512,526.87 eliminated, CTA -1,353.15, group nets to 0.00. Stops for approval
2 same, approved_by Closed
3 2026-09 again Refused: already closed
4 2026-11 Refused: 2026-10 must be closed first
5 2026-10 ISSUES Blocked by the 3 issues: E500 never booked the 12,360.00 management fee; E200 booked a UK goods invoice against E400; the loan interest was booked at 7,500.00 by the lender and 6,666.67 by the borrower
6 2026-10, fx_tolerance: 1000 Blocked: CTA -1,349.94 is above the tolerance
7 2026-10 Closed

Problems found while testing

  • Last month's shipments broke this month's P&L. Income and expense were first selected by booking date, so the receiver's September booking of an August shipment appeared without its August revenue: a 119,180.00 P&L difference. They are now selected by document month, which matches reversing the in-transit entry on day one.
  • Translation differences were always zero. The first version translated both sides with the same rate, so no FX difference could ever appear. Each entity now carries the drift of its own revaluation rate, and the 2,000,000 loan alone produces a 1,200.00 difference, as it would in practice.

Inputs

Input Default Purpose
period 2026-09 YYYY-MM, validated
scenario CLEAN Demo only
group_currency EUR Label for the CTA line
fx_tolerance 2500.0 Largest acceptable translation difference
approved_by empty Needed to book goods in transit

Use it with your data

  • Replace entities and rates with your entity master and the group rates (the FX revaluation blueprint stores closing rates in KV).
  • Replace ic_lines with the intercompany-flagged lines of each ledger: entity, counterparty, type, document number, date, transaction currency and amount. The receiver must reference the sender's document number. If your entities do not share document numbers, match on amount and date first.
  • Set drift to 0 if you translate each entity's local currency balance directly, and add a local amount column.

Things to know

  • Unrealized profit on intercompany inventory still held at period end is not eliminated here. Add the margin of the goods in transit and in stock as a separate step.
  • Dividends and capital transactions between entities need their own eliminations.
  • Reopening a period is deliberate: delete the KV key intercompany_close.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.