> For AI assistants: Read this entire file before writing any code. All decisions here are final. # Fonrey — 统一状态机规范 (STATE_MACHINE) **定位**:本文档是 Fonrey 项目所有实体生命周期与状态流转的唯一权威定义。 **变更历史**: | 日期 | 变更人 | 变更内容 | |---|---|---| | 2026-06-04 | Sisyphus | 初始版本:整合各模块状态机逻辑,对齐 ENUMS v2.2 | --- ## 1. 通用约定 - **状态值命名**:统一采用 `lower_snake_case`,必须与 `DATA_MODEL/ENUMS.md` 完全一致。 - **触发动作**:状态迁移必须由显式的动作(Action)或事件(Event)触发。 - **操作日志**:所有关键状态迁移(除系统内部中间态外)必须记录对应的 `status_log` 或 `audit_log` 表。 - **不可逆规则**:标注为“终态”的状态禁止迁回初始态或中间态。 - **权限要求**:状态迁移接口必须校验操作者的 DataScope 与 Function Permission。 --- ## 2. 租户生命周期 (Public.Tenant) **实体**: `public.tenants` | **字段**: `status` **参考**: `DATA_MODEL_PUBLIC.md` §2.1, §4.1 ```mermaid stateDiagram-v2 [*] --> creating creating --> active: 初始化完成 creating --> failed: 初始化失败 active --> suspended: 逾期/违规/申请 suspended --> active: 恢复条件满足 active --> pending_delete: 申请注销 suspended --> pending_delete: 申请注销 pending_delete --> deleted: 30天后/人工确认 deleted --> [*] ``` ### 状态枚举值 (domain: `public.tenant.status`) - `creating`: 创建中 - `active`: 正常 - `suspended`: 已挂起 - `pending_delete`: 待删除 - `deleted`: 已删除 - `failed`: 创建/初始化失败 ### 状态迁移表 | 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 | |---|---|---|---|---|---| | `creating` | `active` | 租户 Schema 初始化成功 | System | 写 `tenant_status_logs` | 否 | | `creating` | `failed` | 初始化脚本执行失败 | System | 记录错误日志 | 否 | | `active` | `suspended` | 欠费/违规/License过期 | System/SuperAdmin | 设置 `suspended_until`, 写日志 | 是 | | `suspended` | `active` | 缴费成功/解封/到期恢复 | System/SuperAdmin | 清除挂起原因 | 是 | | `active`/`suspended` | `pending_delete` | 提交注销申请 | TenantAdmin | 设置 `deleted_at` | 是 | | `pending_delete` | `deleted` | 确认注销/静默期结束 | SuperAdmin/System | 物理删除或深度标记 | 否 | **来源章节**: `DATA_MODEL_PUBLIC.md` §4.1 --- ## 3. 客源状态机 (Client) **实体**: `client.clients` | **字段**: `status` **参考**: `DATA_MODEL_CLIENT.md` §3.1, §4 | `ENUMS.md` §3.6 ```mermaid stateDiagram-v2 [*] --> buying [*] --> renting [*] --> buy_or_rent state "活跃态" as Active { buying renting buy_or_rent } Active --> suspended: 暂时无需求 suspended --> Active: 重新激活 Active --> public: 手动转公/超时自动 suspended --> public: 手动转公 Active --> bought: 转成交(我购) Active --> rented_done: 转成交(我租) Active --> invalid: 转无效 suspended --> invalid: 转无效 invalid --> Active: 经理审批恢复 bought --> [*] rented_done --> [*] public --> [*] ``` ### 状态枚举值 (domain: `client.status`) - `buying`: 求购 - `renting`: 求租 - `buy_or_rent`: 租购 - `suspended`: 暂缓 - `bought`: 已购 (我购) - `rented_done`: 已租 (我租) - `public`: 公客 - `invalid`: 无效 ### 状态迁移表 | 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 | |---|---|---|---|---|---| | `buying`/`renting`/`buy_or_rent` | `suspended` | 修改状态为暂缓 | 经纪人 | 写 `client_status_logs` | 是 | | `suspended` | `buying`/`renting`/`buy_or_rent` | 重新激活需求 | 经纪人 | 更新 `last_active_at` | 是 | | `Active`/`suspended` | `public` | 手动转公/超时自动转公 | 经纪人/System | 修改 `client_type='public'` | 否 | | `Active` | `bought`/`rented_done` | 录入成交合同 | 经纪人 | 修改 `client_type='transacted'` | 否 | | `Active`/`suspended` | `invalid` | 标记无效 | 经纪人 | 记录 `invalid_reason` | 需审批 | ### 禁迁规则 - `transacted` (bought/rented_done) 状态为绝对终态,禁止迁往任何其他状态。 - `public` 状态客源禁止直接修改为 `private` 状态下的需求状态,必须走领用流程。 **来源章节**: `DATA_MODEL_CLIENT.md` §4, `ENUMS.md` §3.6 --- ## 4. 房源状态机 (Property) **实体**: `property.properties` | **字段**: `status` **参考**: `DATA_MODEL_PROPERTY.md` §3, §4.1 ```mermaid stateDiagram-v2 [*] --> unlisted unlisted --> for_sale: 挂牌出售 unlisted --> for_rent: 挂牌出租 state "在盘态" as Listed { for_sale for_rent for_sale_rent } Listed --> suspended: 业主暂缓 suspended --> Listed: 重新上架 Listed --> sold: 本司成交 Listed --> sold_elsewhere: 他售 Listed --> rented_elsewhere: 他租 Listed --> unlisted: 下架 sold --> [*] sold_elsewhere --> [*] ``` ### 状态枚举值 (domain: `property.status`) - `for_sale`: 出售 - `for_rent`: 出租 - `for_sale_rent`: 租售 - `suspended`: 暂缓 - `sold_elsewhere`: 他售 - `rented_elsewhere`: 他租 - `sold`: 成交 - `unlisted`: 未挂牌 ### 状态迁移表 | 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 | |---|---|---|---|---|---| | `unlisted` | `for_sale`/`for_rent` | 房源挂牌 | 经纪人 | 新增 `listing_histories` | 是 | | `Listed` | `suspended` | 业主通知暂缓 | 经纪人 | 写跟进日志 | 是 | | `Listed` | `sold` | 本司录入成交 | 经纪人 | 自动结束挂牌历史 | 否 | | `Listed` | `sold_elsewhere`/`rented_elsewhere` | 核实他司已成交 | 经纪人 | | 否 | | `Listed` | `unlisted` | 强制下架 | 管理员 | | 是 | **来源章节**: `DATA_MODEL_PROPERTY.md` §3 --- ## 5. 客户/房源等级 (Grade) **实体**: `client.clients` / `property.properties` | **字段**: `grade` **参考**: `DATA_MODEL_CLIENT.md` §3.1 | `DATA_MODEL_PROPERTY.md` §4.1 ### 状态枚举值 (domain: `client.grade` / `property.grade`) - `A`: A (急迫) - `B`: B (较强) - `C`: C (一般) - `D`: D (较弱) - `E`: E (暂不关注) - 仅客源 ### 状态迁移表 | 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 | | ---- | ------------------- | ------- | ---- | ----------------------------- | ---- | | 任意状态 | `A`/`B`/`C`/`D`/`E` | 手动调整等级 | 经纪人 | 写 `status_log` / `follow_log` | 是 | **来源章节**: `DATA_MODEL_CLIENT.md` §4, `DATA_MODEL_PROPERTY.md` §3 --- ## 6. 账号状态机 (Account) **实体**: `login.user_accounts` | **字段**: `status` **参考**: `DATA_MODEL_LOGIN.md` §5.1 ```mermaid stateDiagram-v2 [*] --> active: 创建(初始密码) active --> locked: 密码错误≥5次 locked --> active: 30min到期/手动解锁 active --> disabled: 离职/手动停用 disabled --> active: 复职/手动恢复 ``` ### 状态枚举值 (domain: `login.user_account.status`) - `active`: 启用 - `disabled`: 停用 - `locked`: 锁定 ### 状态迁移表 | 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 | |---|---|---|---|---|---| | `active` | `locked` | 密码连续错误5次 | System | 设置 `locked_until` | 是 | | `locked` | `active` | 时间到期/管理员解锁 | System/Admin | | 是 | | `active` | `disabled` | 员工离职/手动停用 | Admin/System | 踢出所有 Session | 是 | **来源章节**: `DATA_MODEL_LOGIN.md` §5.1 --- ## 7. 升级事件状态机 (Upgrade Event) **实体**: `public.upgrade_events` | **字段**: `status` **参考**: `DATA_MODEL_PUBLIC.md` §4.2 ```mermaid stateDiagram-v2 [*] --> draft draft --> pre_check: 提交 pre_check --> pre_backup: 健康检查通过 pre_backup --> batch_running: 备份完成 batch_running --> batch_done: 批次租户成功 batch_done --> batch_running: 下一批次 batch_done --> succeeded: 全部完成 batch_running --> halted: 失败/门控拦截 halted --> batch_running: 人工继续 halted --> rollback_running: 人工回滚 rollback_running --> rolled_back: 回滚完成 batch_running --> failed: 严重故障 ``` ### 状态枚举值 (domain: `public.upgrade_event.status`) - `draft`: 草稿 - `pre_check`: 预检查 - `pre_backup`: 预备份 - `batch_running`: 批次执行中 - `batch_done`: 批次完成 - `halted`: 已暂停 - `succeeded`: 已成功 - `failed`: 失败 - `rollback_running`: 回滚中 - `rolled_back`: 已回滚 **来源章节**: `DATA_MODEL_PUBLIC.md` §4.2 --- ## 8. 其他辅助状态 ### 8.1 备份/导出任务 (Backup/Export) **字段**: `status` | **参考**: `ENUMS.md` §2.3 - `pending` -> `in_progress` -> `success` / `failed` ### 8.2 客户端发布 (Client Release) **字段**: `status` | **参考**: `ENUMS.md` §2.4 - `draft` -> `published` -> `archived` - **约束**: 同平台架构下仅允许一个 `published`。 ### 8.3 员工状态 (Staff) **字段**: `status` | **参考**: `DATA_MODEL_ORG.md` §4 - `probation` -> `active` -> `resigned` - `active` <-> `frozen` ### 8.4 审批流 (Approval) **实体**: `number_holder_approvals` | **字段**: `status` - `pending` -> `approved` / `rejected` --- ## X. 状态机一致性校验清单 1. **枚举同步**: 状态字段值是否在 `ENUMS.md` 中定义且拼写一致? 2. **日志完备**: 每次 `status` 变更是否伴随对应的 `status_log` 插入动作? 3. **并发控制**: 状态变更是否包含 `version` 乐观锁检查? 4. **终态拦截**: 业务逻辑是否显式禁止从终态(如 `sold`, `deleted`, `rolled_back`)向外迁移? 5. **副作用触发**: 状态变更相关的异步任务(如转公客通知、缓存清理)是否已在 Service 层注册? --- ## 待澄清问题 1. **Client Invalid 恢复路径**: `DATA_MODEL_CLIENT.md` §4 提到“需经理审批后可恢复”,但未定义恢复后的目标状态(是恢复到 `suspended` 还是之前的活跃态?)。 - *涉及文件*: `DATA_MODEL_CLIENT.md` 2. **Property Unlisted 属性**: 房源在 `unlisted` 状态下,其 `attribute` (公/私盘) 是否有意义?若从 `private` 挂牌变为 `unlisted`,再次挂牌时是否应默认保留 `private` 属性? - *涉及文件*: `DATA_MODEL_PROPERTY.md` 3. **Tenant Deleted 物理性**: `DATA_MODEL_PUBLIC.md` §4.1 提到“硬删除直接到 deleted”,但在 §2.1 又说“硬删除直接物理删除行”。需要明确 `deleted` 状态是代表软删除的终点还是物理删除前的标记。 - *涉及文件*: `DATA_MODEL_PUBLIC.md` 4. **Staff Resigned 恢复**: 离职员工复职(`rejoin`)后,其 `status` 是回到 `probation` 还是 `active`? - *涉及文件*: `DATA_MODEL_ORG.md`