Great—time-accurate historical data is exactly what the “effective-dated assignment” approach is for.

## Recommended flow (historical accuracy)
### 1) Model “which branch the user belonged to when”
Use a history table, e.g. `user_branch_assignments`:

- `user_id`
- `branch_id`
- `starts_at`
- `ends_at` (nullable; `null` = current)
- timestamps

Your rule for history:
- For any `user_id`, at a given timestamp, exactly one assignment row is “active” (`starts_at <= t` and (`ends_at` is null or `t < ends_at`)).

### 2) Model branch-specific activities against the assignment
For any activity that should be tied to the user’s branch “at the time it happened”, store:

- `user_branch_assignment_id` (FK)
- `occurred_at` (the activity time)
- activity fields

When creating an activity at time `occurred_at`, you must pick the assignment row that was active at that time.

## Transfer logic (what happens at transfer time)
At transfer time `T`:
1. Close current assignment (branch A):
   - set `ends_at = T`
2. Create new assignment (branch B):
   - set `starts_at = T`
   - `ends_at = null`

From then on:
- old activities remain linked to the old assignment
- new activities link to the new assignment

## Query pattern you’ll use
- “Current branch for a user”:
  - assignment where `ends_at is null`
- “Activities for a user in branch X at time range” (historical):
  - join activities → `user_branch_assignments` and filter by `branch_id`

## Laravel-specific implementation note (important)
When inserting an activity with `occurred_at`, do this in a transaction:
- read the assignment active at `occurred_at`
- create the activity pointing to `user_branch_assignment_id`

Optionally lock the user’s assignment rows to avoid edge-case races around the transfer timestamp.

If you share your current activity table name + the column you use for the activity time (e.g. `created_at` vs `occurred_at`), I can sketch the exact Laravel query/transaction structure.