Skip to content

Tax Filings — GraphQL Relations

Date: 2026-06-15

Problem / motivation

The existing TaxRate, TaxPeriod, and RetentionCertificate types exposed FK id fields (accountPayableId, declarationFileId, pdfFileId, etc.) but not the resolved objects behind them. The frontend had to perform separate queries to fetch the related account, file, user, or ledger-entry records, and there was no way to eager-load them in a single GraphQL request.

In scope

  • Expose resolved relation fields on TaxRate, TaxPeriod, and RetentionCertificate (detailed below).
  • Create per-entity dataloaders where none existed: FileLoader, UserLoader, LedgerEntryLoader, TaxPeriodLoader.

Out of scope

  • No DB schema changes — FK columns already exist.
  • No new queries or mutations.
  • No changes to TaxRate/TaxPeriod/RetentionCertificate creation or update logic.

Data model changes

None — this is a pure GraphQL layer change on top of existing FK columns.

GraphQL surface

TaxRate — new fields

type TaxRate {
  # existing …
  accountPayable: Account         # resolves accountPayableId
  accountReceivable: Account      # resolves accountReceivableId
  withholding: Account            # resolves withholdingAccountId
}

TaxPeriod — new fields

type TaxPeriod {
  # existing …
  declarationFile: FileLite       # resolves declarationFileId
  paymentLedgerEntry: LedgerEntry # resolves paymentLedgerEntryId
  filedBy: User                   # resolves filedById
}

RetentionCertificate — new fields

type RetentionCertificate {
  # existing …
  pdfFile: FileLite               # resolves pdfFileId
  period: TaxPeriod!              # resolves periodId
}

What was implemented

  • New dataloaders
  • app/graphql/files/loaders/file_loader.py
  • app/graphql/users/loaders/user_loader.py
  • app/graphql/ledger/loaders/ledger_entry_loader.py
  • app/graphql/tax_filings/loaders/tax_period_loader.py
  • Updated response types in app/graphql/tax_filings/strawberry/tax_rate_response.py and app/graphql/tax_filings/strawberry/tax_filing_response.py — added @strawberry.field async resolvers for every relation.
  • Tests in tests/graphql/tax_filings/test_relations.py covering None-guards and successful load paths.

RBAC

No change — all new fields live on existing types behind the existing TAX_FILINGS path.

Frontend contract

All new fields are optional (nullable) except RetentionCertificate.period which is non-null.

  • To display the GL accounts on a tax rate: select accountPayable { id accountCode accountNumber }, etc.
  • To display the declaration file on a period: select declarationFile { id name url }.
  • To display the payment journal: select paymentLedgerEntry { id entryNumber entryDate memo }.
  • To display who filed the period: select filedBy { id firstName lastName email }.
  • On a certificate detail page: select period { id periodType startDate endDate status } and pdfFile { id name url }.

Future additions

  • RetentionCertificate.entity — the entity relation (client or supplier) is polymorphic (based on entityKind); deferred because it requires a union type or separate resolver per kind.