# Auth 模块业务逻辑

## 模块职责

Auth 模块负责登录页、手机号密码登录、多账号选择和账号选择后的正式登录确认。

该模块只处理登录流程本身：
- 表单输入、校验和协议确认。
- 调用 v9 登录接口获取临时 token 和账号列表。
- 单账号时自动调用 set-user。
- 多账号时展示账号选择页。
- 选择账号后调用 set-user 换取正式 token。

正式 token 存储、账号快照和全局登录态写入由 App 模块的 `AuthSessionCoordinator` 统一处理。

## 核心对象

- `LoginView`：登录页 UI，展示手机号、密码、协议确认和登录按钮。
- `AccountSelectionView`：多账号选择弹窗，展示景区账号和门店账号。
- `LoginViewModel`：维护表单状态、校验状态、登录请求状态和多账号选择状态。
- `AuthAPI`：封装 `/api/app/v9/login` 和 `/api/app/v9/set-user`。
- `LoginRequest`：登录请求体。
- `SetUserRequest`：账号选择请求体。
- `V9AuthResponse`：v9 登录和 set-user 响应。
- `V9ScenicUser` / `V9StoreUser`：后端返回的景区账号和门店账号。
- `AccountSwitchAccount`：统一后的可选择账号模型。

## 登录流程

1. 用户输入手机号和密码。
2. `LoginViewModel.validateForLogin` 校验手机号、密码和协议勾选状态。
3. 未勾选协议时，`LoginView` 弹出协议确认 Sheet。
4. 校验通过后，`LoginViewModel.login` 调用 `AuthAPI.login`。
5. 登录接口返回临时 token 和可用账号列表。
6. `LoginViewModel` 过滤掉业务账号 ID 无效的账号。
7. 没有可用账号时抛出 `LoginFlowError.noAvailableAccount`。
8. 只有一个账号时，自动调用 `AuthAPI.setUser`，并返回 `.completed`。
9. 多个账号时，保存 `AccountSelectionPayload`，并返回 `.needsAccountSelection`。
10. `LoginView` 展示 `AccountSelectionView`。
11. 用户确认账号后，`LoginViewModel.selectAccount` 使用临时 token 调用 set-user。
12. set-user 返回正式 token 后，`LoginView` 调用 `AuthSessionCoordinator.completeLogin` 写入 token，并同步用户资料、角色权限、景区和门店。

## 账号模型转换

后端景区账号和门店账号字段不完全一致，统一转换为 `AccountSwitchAccount`：
- 景区账号通过 `ssUserId`、`scenicUserId`、`userId`、`id` 兜底生成业务账号 ID。
- 门店账号通过 `storeUserId`、`userId`、`id` 兜底生成业务账号 ID。
- `AccountSwitchAccount.toSetUserRequest` 根据账号类型生成 `store_user_id` 或 `ss_user_id`。

`V9AuthResponse` 还会派生：
- `accounts`：合并后的账号选择列表。
- `primaryProfile`：登录后用于全局展示的账号资料。
- `scenicScopes`：当前账号可用景区作用域。
- `storeScopes`：当前账号可用门店作用域。

## 缓存规则

Auth 模块不直接写缓存。登录成功后的缓存由 `AuthSessionCoordinator` 负责：
- 正式 token 写 Keychain。
- 上次手机号和协议状态写 UserDefaults。
- 账号资料、当前角色和业务作用域写入账号快照。

临时 token 只存在于 `AccountSelectionPayload`，不落盘。
