Multiple accounts payable cause bug#216
Open
bernhardF1984 wants to merge 3 commits into
Open
Conversation
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
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. |
Author
|
Hi ,
Please wait with this, i see the fix is much more complicated.
We also need to support cases where a journal entry created a credit and a
bill payment debited it.
I plan to implement more fixes that will help more use cases.
thanks
Bernhard
…On Thu, Jul 16, 2026 at 8:22 PM fivetran-catfritz ***@***.***> wrote:
*fivetran-catfritz* left a comment (fivetran/dbt_quickbooks#216)
<#216 (comment)>
Thank you @bernhardF1984 <https://github.com/bernhardF1984> for opening
this PR. I created an issue #217
<#217> to help our team
track progress.
I will review this PR and let you know the next steps.
—
Reply to this email directly, view it on GitHub
<#216?email_source=notifications&email_token=A7XPLJYH4MZTRRFJW4WL2Z35FEFMXA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTIOJZGQ3TCMJQGEZ2M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-4994711013>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/A7XPLJ6C6ZGDNWUYU3LBG2L5FEFMXAVCNFSNUABFKJSXA33TNF2G64TZHMZTAMJYGU4TQMRYHNEXG43VMU5TIOBZGY2TKMJWHEYKC5QC>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/A7XPLJZ3RD7SXLLLH24YWG35FEFMXA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTIOJZGQ3TCMJQGEZ2M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJKTGN5XXIZLSL5UW64Y>
and Android
<https://github.com/notifications/mobile/android/A7XPLJ3M454UBFJCDAPSOPL5FEFMXA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTIOJZGQ3TCMJQGEZ2M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>.
Download it today!
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.