Skip to content

Employee Bank Accounts — align with the clients/suppliers pattern

Date: 2026-06-19 Domain: app/graphql/payroll/ Migration: drop_employee_bank_account_id

Problem / motivation

Employee shipped with a single direct bank_account_id FK, which only models one account per employee and is inconsistent with how every other entity in the system holds bank accounts. Clients, suppliers, and the company don't carry a bank-account column — their accounts live on the BankAccount table via source_type + source_id, and the UI renders them in a dedicated tab. The 6edaaf64 commit had already added the EMPLOYEES source type and both sides of the relationship, leaving the model with two competing ways to attach an account. This change removes the FK and standardizes employees on the source-based pattern so an employee can have zero, one, or many accounts, managed from their own tab.

In scope

  • Drop employees.bank_account_id (column + FK) and the singular Employee.bank_account relationship.
  • Remove bankAccountId from Employee, EmployeeInput, EmployeeUpdateInput in the GraphQL schema, and the bankAccount resolver from the Employee type.
  • Keep the Employee.employee_bank_accounts reverse relationship (source_type=EMPLOYEES).
  • Migration repoints any existing employees.bank_account_id link into the new model before dropping the column.

Out of scope

  • No employeeBankAccounts convenience resolver on the Employee type — the frontend uses the existing findBankAccountsBySourceId(sourceId, EMPLOYEES) query, exactly like clients.
  • No changes to the bank-account module itself (its queries/mutations already accept sourceType: EMPLOYEES).

What shipped

Data model

  • employee.py — removed bank_account_id column and the bank_account (singular) relationship; employee_bank_accounts overlaps trimmed to client, supplier, company.
  • Migration 20260619_drop_employee_bank_account_id.pydown_revision = expand_unified_tax_rate. upgrade() repoints referenced bank accounts to source_type='EMPLOYEES', source_id=<employee id>, drops the FK employees_bank_account_id_fkey, then drops the column. downgrade() re-adds the column + FK (empty — prior values now live on bank_accounts).

GraphQL / service

  • payroll_input.pybankAccountId removed from EmployeeInput and EmployeeUpdateInput.
  • payroll_response.pybankAccountId field and bankAccount resolver removed from EmployeeResponse.
  • payroll_service.pybank_account_id param dropped from create_employee / update_employee.
  • payroll_mutations.pybank_account_id wiring removed from createEmployee / updateEmployee.

Frontend contract

updateEmployee(employee: EmployeeUpdateInput!) exists (the earlier payroll doc note saying "no update mutation yet" was stale). EmployeeUpdateInput no longer carries bankAccountId.

Manage an employee's bank accounts through the existing bank-account surface, scoped by source — the same components/calls the client and supplier bank-account tabs already use:

# list
findBankAccountsBySourceId(sourceId: $employeeId, sourceType: EMPLOYEES): [BankAccount!]!

# create / update / delete — BankAccountInput.sourceType = EMPLOYEES, sourceId = employeeId
createBankAccount(bankAccount: BankAccountInput!): BankAccount!
updateBankAccount(bankAccount: BankAccountInput!): BankAccount!
deleteBankAccount(bankAccountId: UUID!): Boolean!

Render accounts in an "Bank accounts" tab on the employee detail page. Drop any reads of employee.bankAccountId / employee.bankAccount.

Future additions

  • employeeBankAccounts resolver on Employee — deferred; clients don't expose one either, so for consistency the list is fetched via findBankAccountsBySourceId. Add only if a single round-trip becomes a real need.
  • Payroll test coverage — the payroll module still has no test suite; adding employee CRUD + bank-account-by-source tests is a separate follow-up.