Skip to content

Multiple accounts payable cause bug#216

Open
bernhardF1984 wants to merge 3 commits into
fivetran:mainfrom
bernhardF1984:main
Open

Multiple accounts payable cause bug#216
bernhardF1984 wants to merge 3 commits into
fivetran:mainfrom
bernhardF1984:main

Conversation

@bernhardF1984

@bernhardF1984 bernhardF1984 commented Jul 15, 2026

Copy link
Copy Markdown

Bernhard Feldmann- Twelvegrow

issue with multiple accounts payable.
Bug: payable_account_id is dropped in stg_quickbooks__bill_payment, causing data fanout in int_quickbooks__bill_payment_double_entry when more than one account shares account_type = 'Accounts Payable'
Package
fivetran/dbt_quickbooks (also present in the deprecated fivetran/dbt_quickbooks_source)
Summary
int_quickbooks__bill_payment_double_entry.sql posts the debit side of every Bill Payment to every account whose account_type = 'Accounts Payable', instead of to the one specific AP account each payment actually clears. This silently multiplies real accounts-payable activity onto any other account in the chart of accounts that happens to share that account_type — which QuickBooks allows, even though it isn't the intended one-AP-account-per-currency design the package assumes.

The root cause is that the source bill_payment object does carry a payable_account_id field (confirmed present in the raw Fivetran-synced bill_payment table), but stg_quickbooks__bill_payment.sql never selects it through to the staging layer. Because that field isn't available downstream, int_quickbooks__bill_payment_double_entry.sql has no way to join precisely, and falls back to matching by account_type + currency_id alone.

This is inconsistent with int_quickbooks__bill_double_entry.sql, which handles the equivalent case correctly by using bills.payable_account_id directly for its credit-side entry — that model is not affected by this bug.
Where the bug is
models/staging/stg_quickbooks__bill_payment.sql — final select omits payable_account_id even though it exists on the source table:

final as (
select
cast(id as {{ dbt.type_string() }}) as bill_payment_id,
cast(check_bank_account_id as {{ dbt.type_string() }}) as check_bank_account_id,
check_print_status,
cast(credit_card_account_id as {{ dbt.type_string() }}) as credit_card_account_id,
exchange_rate,
currency_id,
cast(department_id as {{ dbt.type_string() }}) as department_id,
pay_type,
total_amount,
cast( {{ dbt.date_trunc('day', 'transaction_date') }} as date) as transaction_date,
cast(vendor_id as {{ dbt.type_string() }}) as vendor_id,
created_at,
updated_at,
_fivetran_deleted,
source_relation
from fields
)

models/double_entry_transactions/int_quickbooks__bill_payment_double_entry.sql — because payable_account_id isn't available, the join falls back to matching every account of the configured account_type:

ap_accounts as (
select
account_id,
currency_id,
source_relation
from accounts
where account_type = '{{ var('quickbooks__accounts_payable_reference', 'Accounts Payable') }}'
and is_active
and not is_sub_account
),

bill_payment_join as (
select
...
coalesce(bill_payments.credit_card_account_id, bill_payments.check_bank_account_id) as payment_account_id,
ap_accounts.account_id,
...
from bill_payments
left join ap_accounts
on ap_accounts.currency_id = bill_payments.currency_id
and ap_accounts.source_relation = bill_payments.source_relation
),

Since the join only matches on currency_id + source_relation, every Bill Payment fans out into one debit row per matching AP-type account, not one debit row for the specific account it actually pays down.

The DECISIONLOG does document that a single AP account per currency is a requirement and that multiple AP-typed accounts cause fanout, but since payable_account_id is already present on the source object, this looks like an unintentional oversight rather than an inherent limitation — the fix is available in the data, it's just not being used.
Reproduction
In a company file with 4 accounts sharing account_type = 'Accounts Payable' (one real "Accounts Payable" control account, plus three other liability-type accounts that were misconfigured with that same account_type in QuickBooks), querying quickbooks__general_ledger shows:

Three unrelated accounts show a nearly identical transaction count and dollar total to the real AP account — because they're all receiving a copy of the same underlying Bill Payment activity. This produced multi-million-dollar phantom balances on quickbooks__balance_sheet for accounts that should be at or near $0.
Impact
Any customer with more than one account carrying account_type = 'Accounts Payable' (which QuickBooks Online permits, even if not best practice) will see their non-AP account(s) silently inflated by the full company-wide AP payment volume, with no error or warning — the totals are close enough to the real AP account's totals that it can look plausible rather than obviously broken.
Suggested fix
Add payable_account_id to the final select in stg_quickbooks__bill_payment.sql:

cast(payable_account_id as {{ dbt.type_string() }}) as payable_account_id,

In int_quickbooks__bill_payment_double_entry.sql, join bill_payment_join to ap_accounts on the specific account instead of (or in addition to) account_type:

left join ap_accounts
on ap_accounts.account_id = bill_payments.payable_account_id
and ap_accounts.source_relation = bill_payments.source_relation

This mirrors how int_quickbooks__bill_double_entry.sql already handles the Bill side correctly via bills.payable_account_id.

Bug: payable_account_id is dropped in stg_quickbooks__bill_payment, causing data fanout in int_quickbooks__bill_payment_double_entry when more than one account shares account_type = 'Accounts Payable'
Package
fivetran/dbt_quickbooks (also present in the deprecated fivetran/dbt_quickbooks_source)
Summary
int_quickbooks__bill_payment_double_entry.sql posts the debit side of every Bill Payment to every account whose account_type = 'Accounts Payable', instead of to the one specific AP account each payment actually clears. This silently multiplies real accounts-payable activity onto any other account in the chart of accounts that happens to share that account_type — which QuickBooks allows, even though it isn't the intended one-AP-account-per-currency design the package assumes.

The root cause is that the source bill_payment object does carry a payable_account_id field (confirmed present in the raw Fivetran-synced bill_payment table), but stg_quickbooks__bill_payment.sql never selects it through to the staging layer. Because that field isn't available downstream, int_quickbooks__bill_payment_double_entry.sql has no way to join precisely, and falls back to matching by account_type + currency_id alone.

This is inconsistent with int_quickbooks__bill_double_entry.sql, which handles the equivalent case correctly by using bills.payable_account_id directly for its credit-side entry — that model is not affected by this bug.
Where the bug is
models/staging/stg_quickbooks__bill_payment.sql — final select omits payable_account_id even though it exists on the source table:

final as (
    select 
        cast(id as {{ dbt.type_string() }}) as bill_payment_id,
        cast(check_bank_account_id as {{ dbt.type_string() }}) as check_bank_account_id,
        check_print_status,
        cast(credit_card_account_id as {{ dbt.type_string() }}) as credit_card_account_id,
        exchange_rate,
        currency_id,
        cast(department_id as {{ dbt.type_string() }}) as department_id,
        pay_type,
        total_amount,
        cast( {{ dbt.date_trunc('day', 'transaction_date') }} as date) as transaction_date,
        cast(vendor_id as {{ dbt.type_string() }}) as vendor_id,
        created_at,
        updated_at,
        _fivetran_deleted,
        source_relation
    from fields
)

models/double_entry_transactions/int_quickbooks__bill_payment_double_entry.sql — because payable_account_id isn't available, the join falls back to matching every account of the configured account_type:

ap_accounts as (
    select
        account_id,
        currency_id,
        source_relation
    from accounts
    where account_type = '{{ var('quickbooks__accounts_payable_reference', 'Accounts Payable') }}'
        and is_active
        and not is_sub_account
),

bill_payment_join as (
    select
        ...
        coalesce(bill_payments.credit_card_account_id, bill_payments.check_bank_account_id) as payment_account_id,
        ap_accounts.account_id,
        ...
    from bill_payments
    left join ap_accounts
        on ap_accounts.currency_id = bill_payments.currency_id
        and ap_accounts.source_relation = bill_payments.source_relation
),

Since the join only matches on currency_id + source_relation, every Bill Payment fans out into one debit row per matching AP-type account, not one debit row for the specific account it actually pays down.

The DECISIONLOG does document that a single AP account per currency is a requirement and that multiple AP-typed accounts cause fanout, but since payable_account_id is already present on the source object, this looks like an unintentional oversight rather than an inherent limitation — the fix is available in the data, it's just not being used.
Reproduction
In a company file with 4 accounts sharing account_type = 'Accounts Payable' (one real "Accounts Payable" control account, plus three other liability-type accounts that were misconfigured with that same account_type in QuickBooks), querying quickbooks__general_ledger shows:

account_id
account_name
debit txn count
debit total
131
Accounts Payable (real)
2,769
$19,734,953.91
211
Loan Payable
2,758
$19,726,244.63
286
Loan Payable-Equipment
2,758
$19,726,244.63
178
Payroll Taxes Pay
2,758
$19,726,244.63


Three unrelated accounts show a nearly identical transaction count and dollar total to the real AP account — because they're all receiving a copy of the same underlying Bill Payment activity. This produced multi-million-dollar phantom balances on quickbooks__balance_sheet for accounts that should be at or near $0.
Impact
Any customer with more than one account carrying account_type = 'Accounts Payable' (which QuickBooks Online permits, even if not best practice) will see their non-AP account(s) silently inflated by the full company-wide AP payment volume, with no error or warning — the totals are close enough to the real AP account's totals that it can look plausible rather than obviously broken.
Suggested fix
Add payable_account_id to the final select in stg_quickbooks__bill_payment.sql:

cast(payable_account_id as {{ dbt.type_string() }}) as payable_account_id,

In int_quickbooks__bill_payment_double_entry.sql, join bill_payment_join to ap_accounts on the specific account instead of (or in addition to) account_type:

left join ap_accounts
    on ap_accounts.account_id = bill_payments.payable_account_id
    and ap_accounts.source_relation = bill_payments.source_relation

This mirrors how int_quickbooks__bill_double_entry.sql already handles the Bill side correctly via bills.payable_account_id.
Workaround
Until this is fixed upstream, we're overriding both models locally (defining models of the same name in our own dbt project, which take precedence over the installed package version) with the change above. We're also correcting the account_type of the misclassified accounts directly in QuickBooks, since that's the proper long-term fix regardless of this package behavior — but the package should not silently fan out data even when a customer's chart of accounts is misconfigured this way.
Bug: payable_account_id is dropped in stg_quickbooks__bill_payment, causing data fanout in int_quickbooks__bill_payment_double_entry when more than one account shares account_type = 'Accounts Payable'
Package
fivetran/dbt_quickbooks (also present in the deprecated fivetran/dbt_quickbooks_source)
Summary
int_quickbooks__bill_payment_double_entry.sql posts the debit side of every Bill Payment to every account whose account_type = 'Accounts Payable', instead of to the one specific AP account each payment actually clears. This silently multiplies real accounts-payable activity onto any other account in the chart of accounts that happens to share that account_type — which QuickBooks allows, even though it isn't the intended one-AP-account-per-currency design the package assumes.

The root cause is that the source bill_payment object does carry a payable_account_id field (confirmed present in the raw Fivetran-synced bill_payment table), but stg_quickbooks__bill_payment.sql never selects it through to the staging layer. Because that field isn't available downstream, int_quickbooks__bill_payment_double_entry.sql has no way to join precisely, and falls back to matching by account_type + currency_id alone.

This is inconsistent with int_quickbooks__bill_double_entry.sql, which handles the equivalent case correctly by using bills.payable_account_id directly for its credit-side entry — that model is not affected by this bug.
Where the bug is
models/staging/stg_quickbooks__bill_payment.sql — final select omits payable_account_id even though it exists on the source table:

final as (
    select 
        cast(id as {{ dbt.type_string() }}) as bill_payment_id,
        cast(check_bank_account_id as {{ dbt.type_string() }}) as check_bank_account_id,
        check_print_status,
        cast(credit_card_account_id as {{ dbt.type_string() }}) as credit_card_account_id,
        exchange_rate,
        currency_id,
        cast(department_id as {{ dbt.type_string() }}) as department_id,
        pay_type,
        total_amount,
        cast( {{ dbt.date_trunc('day', 'transaction_date') }} as date) as transaction_date,
        cast(vendor_id as {{ dbt.type_string() }}) as vendor_id,
        created_at,
        updated_at,
        _fivetran_deleted,
        source_relation
    from fields
)

models/double_entry_transactions/int_quickbooks__bill_payment_double_entry.sql — because payable_account_id isn't available, the join falls back to matching every account of the configured account_type:

ap_accounts as (
    select
        account_id,
        currency_id,
        source_relation
    from accounts
    where account_type = '{{ var('quickbooks__accounts_payable_reference', 'Accounts Payable') }}'
        and is_active
        and not is_sub_account
),

bill_payment_join as (
    select
        ...
        coalesce(bill_payments.credit_card_account_id, bill_payments.check_bank_account_id) as payment_account_id,
        ap_accounts.account_id,
        ...
    from bill_payments
    left join ap_accounts
        on ap_accounts.currency_id = bill_payments.currency_id
        and ap_accounts.source_relation = bill_payments.source_relation
),

Since the join only matches on currency_id + source_relation, every Bill Payment fans out into one debit row per matching AP-type account, not one debit row for the specific account it actually pays down.

The DECISIONLOG does document that a single AP account per currency is a requirement and that multiple AP-typed accounts cause fanout, but since payable_account_id is already present on the source object, this looks like an unintentional oversight rather than an inherent limitation — the fix is available in the data, it's just not being used.
Reproduction
In a company file with 4 accounts sharing account_type = 'Accounts Payable' (one real "Accounts Payable" control account, plus three other liability-type accounts that were misconfigured with that same account_type in QuickBooks), querying quickbooks__general_ledger shows:

account_id
account_name
debit txn count
debit total
131
Accounts Payable (real)
2,769
$19,734,953.91
211
Loan Payable
2,758
$19,726,244.63
286
Loan Payable-Equipment
2,758
$19,726,244.63
178
Payroll Taxes Pay
2,758
$19,726,244.63


Three unrelated accounts show a nearly identical transaction count and dollar total to the real AP account — because they're all receiving a copy of the same underlying Bill Payment activity. This produced multi-million-dollar phantom balances on quickbooks__balance_sheet for accounts that should be at or near $0.
Impact
Any customer with more than one account carrying account_type = 'Accounts Payable' (which QuickBooks Online permits, even if not best practice) will see their non-AP account(s) silently inflated by the full company-wide AP payment volume, with no error or warning — the totals are close enough to the real AP account's totals that it can look plausible rather than obviously broken.
Suggested fix
Add payable_account_id to the final select in stg_quickbooks__bill_payment.sql:

cast(payable_account_id as {{ dbt.type_string() }}) as payable_account_id,

In int_quickbooks__bill_payment_double_entry.sql, join bill_payment_join to ap_accounts on the specific account instead of (or in addition to) account_type:

left join ap_accounts
    on ap_accounts.account_id = bill_payments.payable_account_id
    and ap_accounts.source_relation = bill_payments.source_relation

This mirrors how int_quickbooks__bill_double_entry.sql already handles the Bill side correctly via bills.payable_account_id.
Workaround
Until this is fixed upstream, we're overriding both models locally (defining models of the same name in our own dbt project, which take precedence over the installed package version) with the change above. We're also correcting the account_type of the misclassified accounts directly in QuickBooks, since that's the proper long-term fix regardless of this package behavior — but the package should not silently fan out data even when a customer's chart of accounts is misconfigured this way.
bug in case of multiple accounts payable
@fivetran-catfritz

Copy link
Copy Markdown
Contributor

Thank you @bernhardF1984 for opening this PR. I created an issue #217 to help our team track progress.

I will review this PR and let you know the next steps.

@bernhardF1984

bernhardF1984 commented Jul 17, 2026 via email

Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants