# 03-数据库设计文档

Schema: `xlyweberp_vibe_erp_test`
Migration 清单: `sql/migrations/V*.sql`（由 Flyway 顺序 apply）
生成方式: 由 A3 `db-design-gen` 基于 `docs/01-需求清单/<module>/REQ-*.md` REQ 卡片正向设计生成（schema SSoT）。

## 项目标准列约定

下文每张业务表的字段清单都自动包含以下 5 个标准列（匈牙利前缀 `i` int / `s` varchar / `t` datetime）。渲染时由 `docs-03-table-template.md` 模板内置原样输出。

| 列名 | 类型 | 可空 | 主键 | 说明 |
|---|---|---|---|---|
| `iIncrement` | int | 否 | 是 | 整数主键 ID（自增方式由实现决定：DB `AUTO_INCREMENT` 或应用 / 触发器分配） |
| `sId` | varchar(100) | 是 | — | 业务 ID（对外暴露的字符串标识，如 UUID / 人类可读编号） |
| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID（多租户隔离） |
| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID（组织层级隔离） |
| `tCreateDate` | datetime | 否 | — | 记录创建时间 |

字典 / 辅助表如有豁免，在该表业务注记里注明豁免原因。

## ER 关系概览

本期共 5 张业务表，分布在 USR / MOD 两个模块：

- **`tUser`** 是用户账户主表，可选关联 `tStaff`（员工）；与 `tPermissionCategory` 通过 `tUserPermission` 形成多对多权限授权关系。登录失败计数走 Redis，不入库。
- **`tStaff`** 是职员维度表，作为 `tUser` 的员工来源（员工名 / 部门下拉）。
- **`tPermissionCategory`** 是权限分类树（自引用 `iParentId`），由 `tUserPermission` 关联到具体用户。
- **`tUserPermission`** 是 `tUser` × `tPermissionCategory` 的关联表（多对多）。
- **`tModule`** 是 ERP 业务模块定义表（自引用 `iParentId` 形成模块树），与本期其他表无外键关系。

软删除统一使用 `bDeleted` / `tDeletedDate` / `sDeletedBy` 三件套；查询接口默认过滤 `bDeleted = 0`。

## 表清单

- `tUser` — 系统用户账户与登录凭据
- `tStaff` — 职员维度（员工名 / 部门 / 编号）
- `tPermissionCategory` — 权限分类树
- `tUserPermission` — 用户与权限分类的关联
- `tModule` — ERP 业务模块元数据树

---

## `tUser` — 系统用户账户与登录凭据

### 字段

| 字段 | 类型 | Nullable | 默认 | 业务含义 |
|---|---|---|---|---|
| `iIncrement` | int | 否 | — | 整数主键 ID（标准列） |
| `sId` | varchar(100) | 是 | — | 业务 ID（标准列） |
| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID（多租户隔离，标准列） |
| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID（组织层级隔离，标准列） |
| `tCreateDate` | datetime | 否 | — | 创建时间（标准列） |
| `sUserNo` | varchar(50) | 否 | — | 用户号；系统内唯一 |
| `sUserName` | varchar(50) | 否 | — | 用户名（登录账号）；系统内唯一 |
| `iStaffId` | int | 是 | NULL | 关联职员 ID（可选，外键 → `tStaff.iIncrement`） |
| `sUserType` | varchar(20) | 否 | `普通用户` | 用户类型；枚举：`普通用户` / `超级管理员` |
| `sLanguage` | varchar(10) | 否 | `zh` | 语言偏好；枚举：`zh` / `en` / `zh-TW` |
| `bCanModifyDocs` | bit(1) | 否 | 0 | 单据修改权限；0 否 / 1 是 |
| `sPasswordHash` | varchar(255) | 否 | — | 密码哈希值（BCrypt 等强哈希算法），新增默认初始密码 `666666` 的哈希 |
| `tLastLoginDate` | datetime | 是 | NULL | 最后登录时间 |
| `sCreatedBy` | varchar(50) | 是 | — | 制单人（创建用户的操作员用户号） |
| `bDeleted` | bit(1) | 否 | 0 | 软删除标记；0 有效 / 1 已作废 |
| `tDeletedDate` | datetime | 是 | NULL | 软删除时间 |
| `sDeletedBy` | varchar(50) | 是 | NULL | 软删除操作人 |

### 索引

- `uk_user_no` (UNIQUE): (`sUserNo`)
- `uk_user_name` (UNIQUE): (`sUserName`)
- `idx_staff_id` (NORMAL): (`iStaffId`)
- `idx_brand_subsidiary` (NORMAL): (`sBrandsId`, `sSubsidiaryId`)
- `idx_deleted_login` (NORMAL): (`bDeleted`, `tLastLoginDate`)

### 外键

- `fk_user_staff`: `iStaffId` → `tStaff.iIncrement` (ON DELETE SET NULL, ON UPDATE CASCADE)

### 业务注记

- 用户名 / 用户号在系统内全局唯一（含已软删除记录是否计入唯一性，需在 service 层用部分唯一索引或代码校验决定，本设计取代码校验，唯一索引仅约束未删除部分由应用层保证）。
- 密码哈希存储；登录失败次数 / 临时锁定不入库，由 Redis 维护。
- REQ-USR-003 查询接口的「作废」字段直接映射 `bDeleted`；「制单日期」即 `tCreateDate`；「制单人」即 `sCreatedBy`。
- 未关联职员的用户允许 `iStaffId = NULL`（如系统管理员账号）。

---

## `tStaff` — 职员维度（员工名 / 部门 / 编号）

### 字段

| 字段 | 类型 | Nullable | 默认 | 业务含义 |
|---|---|---|---|---|
| `iIncrement` | int | 否 | — | 整数主键 ID（标准列） |
| `sId` | varchar(100) | 是 | — | 业务 ID（标准列） |
| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID（多租户隔离，标准列） |
| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID（组织层级隔离，标准列） |
| `tCreateDate` | datetime | 否 | — | 创建时间（标准列） |
| `sStaffNo` | varchar(50) | 是 | — | 职员编号；系统内唯一 |
| `sStaffName` | varchar(50) | 否 | — | 职员姓名 |
| `sDepartment` | varchar(100) | 是 | NULL | 所属部门（本期暂用字符串，未来如需独立 `tDepartment` 字典表再另行重构） |
| `sCreatedBy` | varchar(50) | 是 | — | 制单人 |
| `bDeleted` | bit(1) | 否 | 0 | 软删除标记 |
| `tDeletedDate` | datetime | 是 | NULL | 软删除时间 |
| `sDeletedBy` | varchar(50) | 是 | NULL | 软删除操作人 |

### 索引

- `uk_staff_no` (UNIQUE): (`sStaffNo`)
- `idx_staff_name` (NORMAL): (`sStaffName`)
- `idx_department` (NORMAL): (`sDepartment`)

### 外键

（无）

### 业务注记

- USR 模块通过 `tUser.iStaffId` 引用本表；REQ-USR-003 列表的「员工名 / 部门」均来自本表。
- 部门当前以字符串保存，便于本期快速落地；后续若需独立部门字典表（如审批流引用部门），改为 `iDepartmentId` 外键。

---

## `tPermissionCategory` — 权限分类树

### 字段

| 字段 | 类型 | Nullable | 默认 | 业务含义 |
|---|---|---|---|---|
| `iIncrement` | int | 否 | — | 整数主键 ID（标准列） |
| `sId` | varchar(100) | 是 | — | 业务 ID（标准列） |
| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID（多租户隔离，标准列） |
| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID（组织层级隔离，标准列） |
| `tCreateDate` | datetime | 否 | — | 创建时间（标准列） |
| `sCategoryCode` | varchar(50) | 否 | — | 权限分类编码；系统内唯一 |
| `sCategoryName` | varchar(100) | 否 | — | 权限分类名称（界面展示） |
| `iParentId` | int | 是 | NULL | 父分类 ID（自引用，根节点为 NULL） |
| `iSortOrder` | int | 否 | 0 | 同级排序号 |
| `sCreatedBy` | varchar(50) | 是 | — | 制单人 |
| `bDeleted` | bit(1) | 否 | 0 | 软删除标记 |
| `tDeletedDate` | datetime | 是 | NULL | 软删除时间 |
| `sDeletedBy` | varchar(50) | 是 | NULL | 软删除操作人 |

### 索引

- `uk_category_code` (UNIQUE): (`sCategoryCode`)
- `idx_parent` (NORMAL): (`iParentId`)

### 外键

- `fk_category_parent`: `iParentId` → `tPermissionCategory.iIncrement` (ON DELETE RESTRICT, ON UPDATE CASCADE)

### 业务注记

- REQ-USR-001 / 002 表 2「权限组 - 权限分类」下拉来源即本表（`bDeleted = 0` 过滤）。
- 自引用形成权限分类树；删除前须确保无子分类、无 `tUserPermission` 引用。

---

## `tUserPermission` — 用户与权限分类关联

### 字段

| 字段 | 类型 | Nullable | 默认 | 业务含义 |
|---|---|---|---|---|
| `iIncrement` | int | 否 | — | 整数主键 ID（标准列） |
| `sId` | varchar(100) | 是 | — | 业务 ID（标准列） |
| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID（多租户隔离，标准列） |
| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID（组织层级隔离，标准列） |
| `tCreateDate` | datetime | 否 | — | 创建时间（标准列） |
| `iUserId` | int | 否 | — | 关联用户 ID（外键 → `tUser.iIncrement`） |
| `iCategoryId` | int | 否 | — | 关联权限分类 ID（外键 → `tPermissionCategory.iIncrement`） |
| `sCreatedBy` | varchar(50) | 是 | — | 授权操作人 |

### 索引

- `uk_user_category` (UNIQUE): (`iUserId`, `iCategoryId`)
- `idx_category` (NORMAL): (`iCategoryId`)

### 外键

- `fk_up_user`: `iUserId` → `tUser.iIncrement` (ON DELETE CASCADE, ON UPDATE CASCADE)
- `fk_up_category`: `iCategoryId` → `tPermissionCategory.iIncrement` (ON DELETE RESTRICT, ON UPDATE CASCADE)

### 业务注记

- 关联表保留标准列以保持设计一致性，无独立软删除字段——授权撤销直接物理删除关联行（CASCADE 也会随用户删除而清理）。
- REQ-USR-001 / 002 提交时按表 2 选中行重建本表 `iUserId` 下的关联（先删后插或差异同步）。

---

## `tModule` — ERP 业务模块元数据树

### 字段

| 字段 | 类型 | Nullable | 默认 | 业务含义 |
|---|---|---|---|---|
| `iIncrement` | int | 否 | — | 整数主键 ID（标准列） |
| `sId` | varchar(100) | 是 | — | 业务 ID（标准列） |
| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID（多租户隔离，标准列） |
| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID（组织层级隔离，标准列） |
| `tCreateDate` | datetime | 否 | — | 创建时间（标准列） |
| `sDisplayType` | varchar(20) | 否 | `手机端` | 显示类型；枚举：`手机端` / `前端业务` / `系统配置` / `接口` |
| `sProcedureName` | varchar(100) | 否 | — | 存储过程（审核）名称；系统内唯一 |
| `sModuleType` | varchar(50) | 否 | — | 模块类型（本期按自由文本处理，VARCHAR(50)；如未来收敛到枚举再加 CHECK 约束） |
| `sManageDeptEn` | varchar(50) | 否 | — | 管理部门英文标识 |
| `bShowPermission` | bit(1) | 否 | 0 | 权限是否显示；0 否 / 1 是 |
| `sModuleNameZh` | varchar(100) | 否 | — | 界面名称（中文，模糊查询用） |
| `iParentId` | int | 是 | NULL | 父模块 ID（自引用，根节点为 NULL） |
| `iSortOrder` | int | 否 | 0 | 同级排序号 |
| `sCreatedBy` | varchar(50) | 是 | — | 制单人 |
| `bDeleted` | bit(1) | 否 | 0 | 软删除标记 |
| `tDeletedDate` | datetime | 是 | NULL | 软删除时间 |
| `sDeletedBy` | varchar(50) | 是 | NULL | 软删除操作人 |

### 索引

- `uk_procedure_name` (UNIQUE): (`sProcedureName`)
- `idx_module_name_zh` (NORMAL): (`sModuleNameZh`)
- `idx_parent` (NORMAL): (`iParentId`)
- `idx_display_type` (NORMAL): (`sDisplayType`)

### 外键

- `fk_module_parent`: `iParentId` → `tModule.iIncrement` (ON DELETE RESTRICT, ON UPDATE CASCADE)

### 业务注记

- REQ-MOD-004 查询按 `sModuleNameZh` 模糊匹配（`LIKE '%关键字%'`，`bDeleted = 0` 过滤），结果按 `iParentId` 拼装树形。
- REQ-MOD-003 删除策略：仅软删除（写入 `bDeleted` / `tDeletedDate` / `sDeletedBy`）；service 层先校验「无未删除子模块」「未被外部业务引用」再执行。
- `sProcedureName` 假定系统内唯一，若实际允许同名请在审阅时移除该唯一索引。
