## Context

See proposal.md - Why. Relevant existing mechanics:
- `Account belongsToMany Position` via the `account_position` pivot (`Account::positions()`); "current position" is read elsewhere in the codebase as `$account->positions()->first()` (e.g. `Account::getCompany()`, `EmployeeExport`). There is no ordering column on the pivot, so "first" is whatever the DB returns first for that account.
- `Field::$defaultKeyByCodes` maps template placeholder codes (e.g. `employee_position`) to a dot-path walked against a `Contract` instance in `Field::getDefaultValueByCode()`. This same map backs contract preview and contract generation for every contract type, so it is a shared, cross-cutting mapping, not a single-controller change.
- `Employee belongsTo Account`; `Employee` already holds several denormalized/self-standing text fields (`working_email`, `bank_account_number`, `tax_code`) alongside its FK relations, so adding another plain text column follows existing convention.

## Goals / Non-Goals

**Goals:**
- Add `contract_position` as a plain nullable string column on `employees`.
- Seed existing rows once at migration time from each employee's current position.
- Repoint the `employee_position` template code to read the new field.

**Non-Goals:**
- No FK/relationship to `positions`.
- No change to `account_position`, `Account::positions()`, or any other current-position derivation used outside contract-document rendering (e.g. `EmployeeExport`'s "position" column keeps reading the live position).
- No UI/automatic re-sync of `contract_position` when an employee's live position changes later — it is a manually maintained field after the initial backfill.

## Decisions

- **Column placement**: add `contract_position` to `employees` (not `accounts`, not a new table). Rationale: the field is employee-profile data conceptually parallel to `working_email`/`tax_code`, which already live on `employees`; the `Field` mapping already reaches employee-only data through `account->employee->...` (e.g. `employee_code`), so this is consistent with the existing pattern.
- **Backfill source**: `contract_position` is set from each employee's current position name, matching the same lookup already used by `Account::getCompany()` and `EmployeeExport` for "current position," so the backfilled value matches what contracts generated the day before this migration would have shown. If an account has no position, `contract_position` stays `null`.
- **Backfill execution**: a single set-based SQL `UPDATE` (via `DB::statement()`/`DB::unprepared()` in the migration), not a PHP loop over employees. Each employee currently belongs to exactly one department, so a correlated subquery per employee is enough — no need to page through records in application code:

  ```sql
  UPDATE employees e
  SET contract_position = (
      SELECT p.name
      FROM account_position ap
      JOIN positions p ON p.id = ap.position_id
      WHERE ap.account_id = e.account_id
      ORDER BY ap.id ASC
      LIMIT 1
  )
  WHERE e.contract_position IS NULL;
  ```

  `ORDER BY ap.id ASC LIMIT 1` reproduces `positions()->first()` deterministically for a single statement — a plain `UPDATE ... JOIN account_position` would apply nondeterministically if an account ever has more than one `account_position` row. The `WHERE e.contract_position IS NULL` guard makes the statement idempotent (safe to rerun without clobbering values HR has already edited).
- **Template mapping change**: change one line in `Field::$defaultKeyByCodes['employee_position']` from `'account->position->name'` to `'account->employee->contract_position'`. No change to `getDefaultValueByCode()` itself — the dot-path walker already supports this path shape (see `employee_code` using `account->employee->code`).
- **Editability**: expose `contract_position` through the existing employee create/update admin flow as a plain optional string input (same validation tier as other free-text employee fields, e.g. max length consistent with `working_email`/`tax_code`). No new endpoint needed.
- **List filter**: add a `contract_position` query param to `AccountTrait::getEmployeeListByRequest()`, matched with `where('contract_position', 'like', '%'.$value.'%')` scoped through `whereHas('employee', ...)` — same partial-match convention already used by `Employee::scopeSearchCode($key, false)` and the `q` search, rather than an exact match like `position_id` (which filters on a real FK, not free text).
- **Export column**: add `'contract_position' => 'Vị trí HĐ'` to `EmployeeExport::$exportFields`, and a `case 'contract_position': return $account->employee->contract_position ?? '';` branch in `getDataByKey()`, next to the existing `'position'` case. This is a new, separate column — it does not change what the existing `'position'` (live position) column returns.

## Risks / Trade-offs

- [Accounts with more than one current position] `positions()->first()` picks an unordered first row → backfilled `contract_position` may not match the position an admin would expect for multi-position accounts. Mitigation: this is the same ambiguity every existing "current position" call site already accepts; no new risk introduced, and HR can correct the value manually post-backfill since the field is editable.
- [Silent drift after backfill] Once seeded, `contract_position` no longer tracks live position changes, so contracts generated long after a position change will show stale text unless HR updates it. Mitigation: this is the explicit intent of the change (freeze what prints on contracts); call it out in release notes so HR knows to update it when a position genuinely changes.
- [Shared mapping blast radius] `Field::$defaultKeyByCodes` feeds every contract type's preview/generation, so the one-line change affects all of them at once, not just newly created contracts. Mitigation: this is the intended behavior per proposal.md (all contract documents should use the stored field going forward); no partial/conditional rollout is in scope.

## Migration Plan

1. Add migration: `contract_position` nullable string column on `employees`.
2. In the same migration, run the single backfill `UPDATE` statement above (see Decisions) to seed `contract_position` for all existing employees in one pass.
3. Update `Field::$defaultKeyByCodes['employee_position']` to point at `account->employee->contract_position`.
4. Expose `contract_position` as an editable field on the employee admin create/update endpoints.

Rollback: drop the `contract_position` column and revert the `Field` mapping line; no other state depends on the new column.
