# Design: 用户和组织模型架构设计

## Context

### 背景

系统需要支持以下四种用户类型和对应的登录端口：

| 用户类型 | 登录端口 | 组织归属 | 角色数量 |
|---------|---------|---------|---------|
| 平台用户 | Web后台 | 无（平台级） | 可分配多个角色 |
| 代理账号 | Web后台 + H5 | 店铺 | 只能分配一种角色 |
| 企业账号 | H5 | 企业 | 只能分配一种角色 |
| 个人客户 | H5（个人端） | 无 | 无角色无权限 |

### 组织层级关系

```
平台（系统）
├── 店铺A（一级代理）
│   ├── 店铺B（二级代理，最多7级）
│   │   └── 企业X
│   └── 企业Y
├── 店铺C（一级代理）
│   └── ...
└── 企业Z（平台直属企业）
```

### 约束条件

- 代理层级最多 7 级
- 代理的上下级关系不可变更
- 一个店铺多个账号（账号权限相同）
- 一个企业目前只有一个账号
- 个人客户独立表，不参与 RBAC 体系
- 遵循项目的数据库设计原则：禁止外键、禁止 GORM 关联

## Goals / Non-Goals

### Goals

1. 设计清晰的用户和组织模型，支持四种用户类型
2. 建立店铺层级关系，支持 7 级代理
3. 支持店铺、企业、个人客户的数据归属
4. 为后续的角色权限体系打好基础
5. 为后续的数据权限过滤打好基础

### Non-Goals

1. 本提案不实现角色权限体系（后续提案）
2. 本提案不实现个人客户的微信登录（后续提案）
3. 本提案不实现数据权限过滤逻辑（已在 004-rbac-data-permission 中定义）
4. 本提案不处理资产绑定（未来功能）

## Decisions

### Decision 1: 账号统一存储 vs 分表存储

**决策**: 平台用户、代理账号、企业账号统一存储在 `tb_account` 表，个人客户独立存储在 `tb_personal_customer` 表。

**理由**:
- 平台/代理/企业账号都参与 RBAC 体系，有相似的字段结构
- 个人客户不参与 RBAC，有独特的微信绑定需求
- 统一存储便于账号管理和登录验证
- 通过 `user_type` 字段区分账号类型

### Decision 2: 代理层级关系的存储位置

**决策**: 代理层级关系存储在 `tb_shop`（店铺表）的 `parent_id` 字段，而非 `tb_account` 的 `parent_id`。

**理由**:
- 层级关系是店铺之间的关系，不是个人之间的关系
- 一个店铺有多个账号，账号之间不应该有上下级关系
- 现有 `tb_account.parent_id` 字段将重新定义用途或移除

**变更**:
- `tb_account.parent_id` 字段移除或废弃
- 新增 `tb_shop.parent_id` 表示店铺的上级店铺
- 递归查询下级改为查询店铺的下级，而非账号的下级

### Decision 3: 企业的归属关系

**决策**: 企业通过 `owner_shop_id` 字段表示归属于哪个店铺，`NULL` 表示平台直属。

**理由**:
- 企业可以归属于任意级别的代理（店铺）
- 企业也可以直接归属于平台
- 上级代理能看到下级代理的企业数据

### Decision 4: 账号与组织的关联方式

**决策**: 账号通过 `shop_id` 或 `enterprise_id` 字段关联到组织。

**实现**:
- 平台用户：`shop_id = NULL`, `enterprise_id = NULL`
- 代理账号：`shop_id = 店铺ID`, `enterprise_id = NULL`
- 企业账号：`shop_id = NULL`, `enterprise_id = 企业ID`

### Decision 5: 数据权限过滤的调整

**决策**: 数据权限过滤基于 `shop_id`（店铺归属）而非 `owner_id`（账号归属）。

**理由**:
- 同一店铺的所有账号应该能看到店铺的所有数据
- 上级店铺应该能看到下级店铺的数据
- `owner_id` 字段保留用于记录数据的创建者（审计用途）

**变更**:
- 递归查询改为查询店铺的下级店铺 ID 列表
- 数据过滤条件改为 `WHERE shop_id IN (当前店铺及下级店铺)`
- 平台用户（`user_type = 1` 或 `user_type = 2`）跳过过滤

## Data Models

### Shop（店铺）

```go
type Shop struct {
    gorm.Model
    BaseModel   `gorm:"embedded"`

    ShopName    string  `gorm:"not null;size:100"`     // 店铺名称
    ShopCode    string  `gorm:"uniqueIndex;size:50"`   // 店铺编号
    ParentID    *uint   `gorm:"index"`                 // 上级店铺ID（NULL表示一级代理）
    Level       int     `gorm:"not null;default:1"`    // 层级（1-7）
    ContactName string  `gorm:"size:50"`               // 联系人姓名
    ContactPhone string `gorm:"size:20"`               // 联系人电话
    Province    string  `gorm:"size:50"`               // 省份
    City        string  `gorm:"size:50"`               // 城市
    District    string  `gorm:"size:50"`               // 区县
    Address     string  `gorm:"size:255"`              // 详细地址
    Status      int     `gorm:"not null;default:1"`    // 状态 0=禁用 1=启用
}
```

### Enterprise（企业）

```go
type Enterprise struct {
    gorm.Model
    BaseModel       `gorm:"embedded"`

    EnterpriseName  string  `gorm:"not null;size:100"`  // 企业名称
    EnterpriseCode  string  `gorm:"uniqueIndex;size:50"` // 企业编号
    OwnerShopID     *uint   `gorm:"index"`              // 归属店铺ID（NULL表示平台直属）
    LegalPerson     string  `gorm:"size:50"`            // 法人代表
    ContactName     string  `gorm:"size:50"`            // 联系人姓名
    ContactPhone    string  `gorm:"size:20"`            // 联系人电话
    BusinessLicense string  `gorm:"size:100"`           // 营业执照号
    Province        string  `gorm:"size:50"`            // 省份
    City            string  `gorm:"size:50"`            // 城市
    District        string  `gorm:"size:50"`            // 区县
    Address         string  `gorm:"size:255"`           // 详细地址
    Status          int     `gorm:"not null;default:1"` // 状态 0=禁用 1=启用
}
```

### PersonalCustomer（个人客户）

```go
type PersonalCustomer struct {
    gorm.Model

    Phone       string  `gorm:"uniqueIndex;size:20"`   // 手机号（唯一标识）
    Nickname    string  `gorm:"size:50"`               // 昵称
    AvatarURL   string  `gorm:"size:255"`              // 头像URL
    WxOpenID    string  `gorm:"index;size:100"`        // 微信OpenID
    WxUnionID   string  `gorm:"index;size:100"`        // 微信UnionID
    Status      int     `gorm:"not null;default:1"`    // 状态 0=禁用 1=启用
}
```

### Account（账号）- 修改

```go
type Account struct {
    gorm.Model
    BaseModel    `gorm:"embedded"`

    Username     string  `gorm:"uniqueIndex;size:50"`  // 用户名
    Phone        string  `gorm:"uniqueIndex;size:20"`  // 手机号
    Password     string  `gorm:"not null;size:255" json:"-"` // 密码
    UserType     int     `gorm:"not null;index"`       // 用户类型 1=超级管理员 2=平台用户 3=代理账号 4=企业账号
    ShopID       *uint   `gorm:"index"`                // 店铺ID（代理账号必填）
    EnterpriseID *uint   `gorm:"index"`                // 企业ID（企业账号必填）
    Status       int     `gorm:"not null;default:1"`   // 状态 0=禁用 1=启用
    // 移除 ParentID 字段，层级关系由 Shop 表维护
}
```

## Risks / Trade-offs

### Risk 1: 现有数据迁移

- **风险**: 现有 `tb_account.parent_id` 字段被移除，可能影响现有数据
- **缓解**: 当前系统是空架子，无实际数据需要迁移

### Risk 2: 数据权限过滤逻辑变更

- **风险**: 从 `owner_id` 过滤改为 `shop_id` 过滤，需要调整现有代码
- **缓解**: 现有的数据权限过滤尚未完全实现，可以直接按新设计实现

### Risk 3: 店铺层级查询性能

- **风险**: 7 级店铺层级的递归查询可能影响性能
- **缓解**: 继续使用 Redis 缓存店铺的下级 ID 列表，30 分钟过期

## Migration Plan

1. 创建新表：`tb_shop`、`tb_enterprise`、`tb_personal_customer`
2. 修改 `tb_account` 表结构：
   - 添加 `enterprise_id` 字段
   - 移除 `parent_id` 字段（如果有数据则先迁移）
3. 更新 GORM 模型定义
4. 更新 Store 层实现
5. 更新常量定义

## Open Questions

1. ~~店铺层级关系是否需要记录完整路径（如 `/1/2/3/`）以优化查询？~~ - 暂不需要，使用递归查询 + Redis 缓存
2. ~~企业账号未来扩展为多账号时，是否需要区分主账号和子账号？~~ - 未来再设计
