Files
timebank-cc-public/references/BANK_CLIENTS_BACKFILL_COMMAND.md
T

5.3 KiB

bank-clients:backfill-by-location — Bank Client Assignment Backfill

Links existing Users and Organizations to a local (level 2) Bank via the bank_clients pivot table, matched by shared city. This populates the client relationship that bank-manager features (e.g. the "Manage Profiles" scoped view and the transaction reversal feature) rely on to determine "which profiles belong to my bank."

Why this exists

bank_clients is not populated during normal registration — there is no signup flow that assigns a new User/Organization to a bank. Historically the table was only ever written to by one admin-only "Create Profile" form. As a result, on any environment where bank-manager features are newly enabled, most (or all) profiles have no bank client link at all, and a bank-manager's scoped views will appear empty even though the profiles clearly belong to that bank's city.

This command derives the missing links after the fact: it matches each profile's own Location (city) against a level-2 bank's own Location (city), and inserts a bank_clients row with relationship_type = 'local' for every match that doesn't already have one.

Usage

php artisan bank-clients:backfill-by-location [options]

Options

Option Description
--dry-run Preview the assignments — prints counts and the bank/city mapping, writes nothing to the database

How matching works

  1. Finds every level 2 Bank that has a Location record with a city_id set. Banks without a location, or banks at level 0 (central) / level 1 (system/bridge), are never targets — client relationships only ever point to the public-facing local bank for a city.
  2. For each such bank, builds a city_id → Bank map.
  3. For every User and Organization that has a Location record whose city_id matches one of those banks, links the profile to that bank via attachBankClient($bank, 'local') — unless the profile already has a local bank client link, in which case it's skipped.
  4. Profiles whose city doesn't match any bank's city are left unlinked. This is expected — not every registered profile has a nearby bank onboarded yet.

Only User and Organization are considered. Banks and Admins are never assigned as someone else's client.

Idempotency

Safe to re-run at any time:

  • Already-linked profiles (checked via $client->banksClient()->exists()) are always skipped, never duplicated or reassigned.
  • Newly registered profiles, or newly added banks with a location, will pick up correct links on the next run without disturbing existing ones.
  • Never deletes or modifies an existing bank_clients row.

Examples

# Preview only — see how many users/organizations would be linked, and to which banks
php artisan bank-clients:backfill-by-location --dry-run

# Apply the backfill for real
php artisan bank-clients:backfill-by-location

Output

Bank #5 "Timebank Amsterdam" -> city_id 188
Bank #6 "Timebank Den Haag" -> city_id 305
Bank #8 "Timebank BXL" -> city_id 345

DRY RUN — no changes will be written.

Users matched by city: 1812
 1812/1812 [============================] 100%

Organizations matched by city: 4
 4/4 [============================] 100%

Linked: 1816
Skipped (already linked): 0
Run without --dry-run to apply these changes.

On a subsequent run (or the real run after a dry-run), previously-linked profiles show up under "Skipped (already linked)" instead of "Linked".

Running this on a production server

  1. Always dry-run first and sanity-check the bank/city list and counts before writing anything — this command performs a real, potentially large (thousands of rows) database write.
    php artisan bank-clients:backfill-by-location --dry-run
    
  2. Confirm every bank you expect to receive clients actually appears in the "Bank #… -> city_id …" list. If a bank you expected is missing, its Location record likely has no city_id set — fix that first (via the bank's profile location settings) and re-run the dry-run.
  3. If the numbers look right, run it for real:
    php artisan bank-clients:backfill-by-location
    
  4. Re-running later (e.g. after onboarding a new bank, or after a batch of new registrations) is safe — it only adds new links, never touches existing ones.

What this command does NOT do

  • Does not create or update Bank Location records — a bank with no city set is silently skipped from the mapping.
  • Does not resolve conflicts if a profile is later moved to a different city — the unique constraint on bank_clients (client_id, client_type, relationship_type) means a profile can only have one local bank client link at a time; changing a profile's city does not automatically re-link them, this command only fills in missing links.
  • Does not link Banks or Admins to other banks.
  • App\Models\User::attachBankClient() / App\Models\Organization::attachBankClient() — the underlying pivot-sync method this command calls.
  • App\Models\Bank::userClients() / App\Models\Bank::organizationClients() — read-side relations used by Profiles\Manage.php to scope a bank-manager's view to their own clients.
  • bank_clients table (migration 2025_04_30_001906_create_bank_clients_table.php) — polymorphic pivot: bank_id, client_type, client_id, relationship_type (unique per client + relationship_type).