Files
backmanager-server/API.md

1319 lines
48 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 蜜雪冰城企业管理系统 —— 后端 API 文档
> 蜜雪冰城总部用于管理加盟商、合同、员工、产品、供应商及售后服务的企业信息管理系统后端服务。
## 目录
- [快速开始](#快速开始)
- [技术栈](#技术栈)
- [项目结构](#项目结构)
- [架构分层](#架构分层)
- [认证机制](#认证机制)
- [API 接口总览](#api-接口总览)
- [1. 用户认证](#1-用户认证)
- [2. 用户管理](#2-用户管理)
- [3. 加盟商管理](#3-加盟商管理)
- [4. 员工管理](#4-员工管理)
- [5. 合同管理](#5-合同管理)
- [6. 售后管理](#6-售后管理)
- [7. 产品管理](#7-产品管理)
- [8. 供应商管理](#8-供应商管理)
- [统一响应格式](#统一响应格式)
- [数据库设计](#数据库设计)
- [表结构详情](#表结构详情)
- [表关系 ER 图](#表关系-er-图)
- [部门权限系统](#部门权限系统)
- [部门定义](#部门定义)
- [权限列表](#权限列表)
- [部门-权限映射](#部门-权限映射)
- [数据范围隔离](#数据范围隔离)
- [中间件说明](#中间件说明)
- [预置账号](#预置账号)
---
## 快速开始
### 环境要求
- Node.js >= 18
- MySQL 5.7+ / MariaDB 10.2+
### 安装与运行
```bash
# 1. 安装依赖
npm install
# 2. 配置环境变量
cp .env.example .env
# 编辑 .env 填入数据库连接信息和 JWT 密钥
# 3. 启动开发服务(自动重启)
npm run dev
# 4. 启动生产服务
npm start
# 5. 运行集成测试(需先启动服务)
node test-all.js
```
### 环境变量 (.env)
| 变量 | 说明 | 默认值 |
|------|------|--------|
| `PORT` | 服务端口 | `3000` |
| `DB_HOST` | MySQL 主机 | — |
| `DB_PORT` | MySQL 端口 | `3306` |
| `DB_USER` | MySQL 用户名 | — |
| `DB_PASSWORD` | MySQL 密码 | — |
| `DB_NAME` | 数据库名 | `backmanager` |
| `JWT_SECRET` | JWT 签名密钥 | — |
| `JWT_EXPIRES_IN` | Token 有效期 | `2h` |
> 首次启动时,系统会自动创建数据库、建表、初始化部门权限和预置用户。
---
## 技术栈
| 技术 | 用途 |
|------|------|
| Express 5 | Web 框架 |
| mysql2/promise | MySQL 驱动(连接池 + Promise |
| jsonwebtoken | JWT 认证 |
| bcryptjs | 密码哈希加密 |
| dotenv | 环境变量管理 |
---
## 项目结构
```
backmanager-server/
├── server.js # 入口Express 应用、鉴权中间件、路由注册
├── db.js # 数据库:连接池、建表、种子数据
├── middleware/
│ ├── permissions.js # 权限检查 + 数据范围 + 合同类型校验
│ ├── users.js # 用户自保护规则(禁止自改部门、自删等)
│ └── employees.js # 员工创建时同步创建用户账号
├── routes/
│ ├── users.js # 用户登录/信息/CRUD
│ ├── customers.js # 加盟商 CRUD + 简易列表
│ ├── employees.js # 员工 CRUD + 简易列表
│ ├── contracts.js # 合同 CRUD
│ ├── afterSales.js # 售后 CRUD
│ ├── products.js # 产品 CRUD
│ └── suppliers.js # 供应商 CRUD + 简易列表
├── test-all.js # 集成测试(覆盖 6 个部门的权限与数据隔离131 条用例)
├── API.md # 本文档
├── .env.example # 环境变量模板
└── package.json
```
---
## 架构分层
本项目采用**三层分离架构**,各层职责清晰:
```
请求 → 中间件层(认证/权限/数据范围/业务规则)→ 路由层(纯 CRUD数据库操作→ 响应
```
| 分层 | 文件 | 职责 |
|------|------|------|
| **API 端点层** | `server.js` | 路由注册,串联中间件链 |
| **中间件层** | `middleware/*.js` | JWT 认证、权限校验、数据范围注入、业务规则校验 |
| **数据库查询层** | `routes/*.js` | 纯 SQL CRUD 操作,不做权限判断和业务逻辑 |
**路由层纯粹性要求:** 所有 `routes/*.js` 只包含数据库的增删改查操作,不参与权限判断、数据范围计算等逻辑。权限和数据范围由中间件通过 `req.user.permissions``req.scope` 注入。
---
## 认证机制
所有 API除登录外需要在请求头中携带 JWT Token
```
Authorization: Bearer <token>
```
### Token 获取
通过 `POST /api/user/login` 登录获取 Token。
### Token 有效载荷 (Payload)
```json
{
"id": 1,
"username": "admin",
"name": "系统管理员",
"department_id": 1,
"departmentName": "admin",
"departmentDesc": "信息技术部",
"permissions": ["customer:read", "customer:create", "customer:update", "customer:delete", "contract:read", "..."]
}
```
### 中间件执行流程
```
请求 → auth验证 Token→ checkPermission权限检查→ dataScope数据范围注入→ [业务中间件] → 路由处理 → 响应
```
| 中间件 | 文件 | 说明 |
|--------|------|------|
| `auth` | `server.js` | 解析并验证 JWT Token将用户信息挂载到 `req.user` |
| `checkPermission(resource, action)` | `middleware/permissions.js` | 检查 `req.user.permissions` 是否包含 `resource:action` |
| `dataScope(resource)` | `middleware/permissions.js` | 根据用户部门计算数据范围,挂载 `req.scope = { sql, params }` |
| `validateContractType` | `middleware/permissions.js` | 限制招商部只能操作加盟合同,采购部只能操作采购合同 |
| `protectSelfUpdate` | `middleware/users.js` | 禁止用户修改自己的部门或禁用自己 |
| `protectUserDelete` | `middleware/users.js` | 禁止删除自己或最后一个管理员 |
| `allowUserCreation` | `middleware/employees.js` | 允许有 `user:manage` 权限的用户在创建员工时同步创建账号 |
### 错误响应
| HTTP 状态码 | 场景 |
|------------|------|
| 401 | 未携带 Token 或 Token 无效/过期 |
| 403 | 权限不足(无对应操作权限或数据不在可见范围内) |
---
## API 接口总览
### 通用说明
- **分页参数**:所有列表接口支持 `page`(页码,从 1 开始,默认 1`pageSize`(每页条数,默认 10最大 100
- **分页响应**:列表接口返回 `{ list, total, page, pageSize, totalPages }`
- **搜索参数**:通过 URL Query String 传递,如 `GET /api/customers?name=张&phone=138`
- **部分更新**PUT 接口只传需要修改的字段即可,未传字段保持不变
- **权限要求**:每个接口所需的权限标注在接口标题下方
### 接口权限速查表
| 接口 | 方法 | 权限 | 数据范围 |
|------|------|------|---------|
| `/api/user/login` | POST | 无 | — |
| `/api/user/info` | GET | 登录即可 | 仅自己 |
| `/api/user/list` | GET | 登录即可 | 所有启用用户 |
| `/api/user/logout` | POST | 登录即可 | — |
| `/api/user/password` | PUT | 登录即可 | 仅自己 |
| `/api/users` | GET/POST | `user:manage` | 全部 |
| `/api/users/:id` | GET/PUT/DELETE | `user:manage` | 全部 |
| `/api/customers/simple` | GET | `customer:read` | 全部客户 |
| `/api/customers` | GET | `customer:read` | 过渡期全量 |
| `/api/customers` | POST | `customer:create` | — |
| `/api/customers/:id` | GET/PUT/DELETE | 对应权限 | 过渡期全量 |
| `/api/employees/simple` | GET | 登录即可 | 全部在职员工 |
| `/api/employees` | GET | `employee:read` | 按部门隔离 |
| `/api/employees` | POST | `employee:create` | — |
| `/api/employees/:id` | GET/PUT/DELETE | 对应权限 | 按部门隔离 |
| `/api/contracts` | GET | `contract:read` | 按合同类型隔离 |
| `/api/contracts` | POST | `contract:create` | 按部门限制类型 |
| `/api/contracts/:id` | GET/PUT/DELETE | 对应权限 | 按合同类型隔离 |
| `/api/after-sales` | GET | `after_sale:read` | 过渡期全量 |
| `/api/after-sales` | POST | `after_sale:create` | — |
| `/api/after-sales/:id` | GET/PUT/DELETE | 对应权限 | 过渡期全量 |
| `/api/products` | GET | `product:read` | 全部 |
| `/api/products/:id` | GET/PUT/DELETE | 对应权限 | 全部 |
| `/api/suppliers/simple` | GET | `supplier:read` | 全部正常供应商 |
| `/api/suppliers` | GET | `supplier:read` | 全部 |
| `/api/suppliers/:id` | GET/PUT/DELETE | 对应权限 | 全部 |
---
### 1. 用户认证
#### POST /api/user/login — 登录
无需 Token。
**请求体:**
```json
{
"username": "admin",
"password": "123456"
}
```
**成功响应:**
```json
{
"code": 200,
"message": "ok",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"userInfo": {
"id": 1,
"username": "admin",
"name": "系统管理员",
"department_id": 1,
"departmentName": "admin",
"departmentDesc": "信息技术部",
"permissions": ["customer:read", "customer:create", "..."]
}
}
}
```
> **注意**:登录成功返回 `code: 200`,其他接口统一返回 `code: 0`。前端通过 `res.code === 0 || res.code === 200` 兼容判断。
**错误响应:**
| code | message |
|------|---------|
| 400 | 用户名和密码必填 |
| 400 | 账号或密码错误 |
| 403 | 账号已被禁用,请联系管理员 |
#### GET /api/user/info — 获取当前用户信息
需要 Token。
返回当前登录用户的完整信息(含部门名称、员工姓名)。
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 用户 ID |
| `username` | string | 用户名 |
| `is_active` | number | 状态0-禁用, 1-启用 |
| `department_id` | number | 部门 ID |
| `employee_id` | number | 关联员工 ID |
| `dept_name` | string | 部门标识(如 `admin` |
| `dept_desc` | string | 部门中文名(如 信息技术部) |
| `real_name` | string | 真实姓名(来自员工表) |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### GET /api/user/list — 简易用户列表
需要 Token仅需登录无需特定权限
返回所有启用用户的基本信息,用于前端下拉选择负责人。
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 用户 ID |
| `username` | string | 用户名 |
| `real_name` | string | 真实姓名(来自员工表,可能为 null |
#### POST /api/user/logout — 登出
需要 Token。服务端无状态仅返回成功应答客户端自行清除 Token。
#### PUT /api/user/password — 修改密码
需要 Token。修改当前登录用户自己的密码。
**请求体:**
```json
{
"oldPassword": "123456",
"newPassword": "654321"
}
```
**校验规则:**
- 旧密码和新密码必填
- 新密码至少 6 位
- 新密码不能与旧密码相同
---
### 2. 用户管理
> 需要 `user:manage` 权限(仅系统管理员 / 信息技术部拥有)。
#### GET /api/users — 用户列表
**查询参数:**
| 参数 | 说明 | 示例 |
|------|------|------|
| `username` | 按用户名模糊搜索 | `?username=admin` |
| `real_name` | 按真实姓名模糊搜索 | `?real_name=张` |
| `id` | 按用户 ID 精确搜索 | `?id=1` |
| `department_id` | 按部门 ID 过滤 | `?department_id=2` |
| `is_active` | 按状态过滤0-禁用, 1-启用) | `?is_active=1` |
| `page` | 页码 | `?page=1` |
| `pageSize` | 每页条数 | `?pageSize=10` |
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 用户 ID |
| `username` | string | 用户名 |
| `is_active` | number | 状态0-禁用, 1-启用 |
| `department_id` | number | 部门 ID |
| `employee_id` | number | 关联员工 ID |
| `dept_name` | string | 部门标识(如 `admin` |
| `dept_desc` | string | 部门中文名(如 信息技术部) |
| `real_name` | string | 真实姓名(来自员工表) |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### GET /api/users/:id — 用户详情
返回单个用户完整信息。
#### POST /api/users — 创建用户
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `username` | ✅ | 用户名3-50 字符) |
| `password` | ✅ | 密码(至少 6 位) |
| `department_id` | ❌ | 部门 ID需为已存在的部门 |
| `employee_id` | ❌ | 关联员工 ID需为已存在的员工 |
| `is_active` | ❌ | 状态,默认 1 |
**校验规则:**
- `department_id` 必须存在于 `departments`
- `employee_id` 必须存在于 `employees`
- `username` 不可重复(返回 409
#### PUT /api/users/:id — 更新用户
**可更新字段:** `username``department_id``employee_id``is_active``password`
**自保护限制(`protectSelfUpdate` 中间件):**
- 不能修改自己的 `department_id`
- 不能禁用自己(`is_active` 不能设为 0
#### DELETE /api/users/:id — 删除用户
**自保护限制(`protectUserDelete` 中间件):**
- 不能删除自己
- 不能删除信息技术部的最后一个管理员账号
---
### 3. 加盟商管理
> 需要 `customer:read` / `customer:create` / `customer:update` / `customer:delete` 权限。
#### GET /api/customers/simple — 简易加盟商列表
需要 `customer:read` 权限。无数据范围限制,用于前端下拉选择。
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 加盟商 ID |
| `name` | string | 加盟商姓名 |
#### GET /api/customers — 加盟商列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `id` | 按 ID 精确搜索 |
| `name` | 按加盟商名称模糊搜索 |
| `phone` | 按电话模糊搜索 |
| `province` | 按省份精确匹配 |
| `city` | 按城市精确匹配 |
**响应数据字段(含 JOIN 字段):**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 加盟商 ID |
| `name` | string | 加盟商姓名 |
| `phone` | string | 联系电话 |
| `province` | string | 省 |
| `city` | string | 市 |
| `district` | string | 区 |
| `address` | string | 详细地址 |
| `email` | string | 电子邮箱 |
| `remark` | string | 备注信息 |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/customers — 新增加盟商
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `name` | ✅ | 加盟商姓名 |
| `phone` | ❌ | 联系电话 |
| `province` | ❌ | 省 |
| `city` | ❌ | 市 |
| `district` | ❌ | 区 |
| `address` | ❌ | 详细地址 |
| `email` | ❌ | 电子邮箱 |
| `remark` | ❌ | 备注 |
#### PUT /api/customers/:id — 更新加盟商
所有字段均可选,只传需要修改的字段。
#### DELETE /api/customers/:id — 删除加盟商
**限制:** 被合同或售后记录引用的客户无法删除(返回 400
---
### 4. 员工管理
> 需要 `employee:read` / `employee:create` / `employee:update` / `employee:delete` 权限。
#### GET /api/employees/simple — 简易员工列表
需要 Token仅需登录。无数据范围限制用于前端下拉选择。支持按部门过滤。
**查询参数:**
| 参数 | 说明 |
|------|------|
| `department` | 按部门精确过滤(如 `?department=运营部` |
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 员工 ID |
| `name` | string | 姓名 |
| `department` | string | 所属部门 |
| `position` | string | 职务/岗位 |
#### GET /api/employees — 员工列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `id` | 按 ID 精确搜索 |
| `name` | 按姓名模糊搜索 |
| `department` | 按部门模糊搜索 |
| `status` | 按在职状态过滤0-离职, 1-在职) |
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 员工 ID |
| `name` | string | 姓名 |
| `gender` | string | 性别(男/女) |
| `age` | number | 年龄 |
| `education` | string | 学历 |
| `department` | string | 所属部门 |
| `entry_date` | date | 入职时间 |
| `position` | string | 职务/岗位 |
| `salary` | number | 工资金额 |
| `phone` | string | 联系电话 |
| `email` | string | 电子邮箱 |
| `status` | number | 在职状态0-离职, 1-在职 |
| `remark` | string | 备注 |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/employees — 新增员工
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `name` | ✅ | 员工姓名 |
| `gender` | ❌ | 性别 |
| `age` | ❌ | 年龄 |
| `education` | ❌ | 学历 |
| `department` | ❌ | 部门 |
| `entry_date` | ❌ | 入职时间格式YYYY-MM-DD |
| `position` | ❌ | 职务 |
| `salary` | ❌ | 工资 |
| `phone` | ❌ | 电话 |
| `email` | ❌ | 邮箱 |
| `status` | ❌ | 在职状态,默认 1 |
| `remark` | ❌ | 备注 |
| `username` | ❌ | 需同步创建用户时填写(需 `user:manage` 权限) |
| `password` | ❌ | 需同步创建用户时填写(需 `user:manage` 权限) |
| `department_id` | ❌ | 需同步创建用户时填写(指定用户所属部门 ID |
**同步创建用户账号(`allowUserCreation` 中间件):**
当同时传入 `username``password``department_id` 三个字段时:
- 需要拥有 `user:manage` 权限
- 系统会在创建员工后自动创建关联的系统用户账号
- `username` 长度 3-50 字符,`password` 至少 6 位
#### PUT /api/employees/:id — 更新员工
所有字段均可选,只传需要修改的字段。
#### DELETE /api/employees/:id — 删除员工
**特殊行为:**
- 删除员工时会同步删除关联的用户账号(`users` 表中 `employee_id` 匹配的记录)
- 不能删除当前登录用户自己关联的员工账号
- 被合同或售后记录引用的员工无法删除(返回 400
---
### 5. 合同管理
> 需要 `contract:read` / `contract:create` / `contract:update` / `contract:delete` 权限。
#### GET /api/contracts — 合同列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `id` | 按 ID 精确搜索 |
| `status` | 按合同状态过滤(草稿/生效/完成/作废) |
| `customer_name` | 按客户名称模糊搜索 |
| `contract_no` | 按合同编号模糊搜索 |
| `contract_name` | 按合同名称模糊搜索 |
| `employee_name` | 按业务员姓名模糊搜索 |
| `effective_date_start` | 按生效日期起始过滤格式YYYY-MM-DD |
| `effective_date_end` | 按生效日期截止过滤格式YYYY-MM-DD |
**响应数据字段(含 JOIN 字段):**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 合同 ID |
| `customer_id` | number | 客户 ID加盟合同必填采购合同为 null |
| `customer_name` | string | 客户名称JOIN 自 customers |
| `supplier_id` | number | 供应商 ID采购合同必填加盟合同为 null |
| `supplier_name` | string | 供应商名称JOIN 自 suppliers |
| `contract_name` | string | 合同名称 |
| `contract_no` | string | 合同编号 |
| `contract_content` | string | 合同内容/条款 |
| `amount` | number | 合同金额 |
| `effective_date` | date | 生效日期 |
| `expiry_date` | date | 到期日期 |
| `employee_id` | number | 业务员 ID |
| `employee_name` | string | 业务员姓名JOIN 自 employees |
| `status` | string | 状态:草稿/生效/完成/作废 |
| `remark` | string | 备注 |
| `type` | string | 合同类型:`franchise`(加盟)/ `supply`(采购) |
| `responsible_user_id` | number | 负责人用户 ID |
| `responsible_user_name` | string | 负责人姓名JOIN 自 users → employees |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/contracts — 新增合同
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `customer_id` | 条件必填 | 关联客户 ID加盟合同必填 |
| `supplier_id` | 条件必填 | 关联供应商 ID采购合同必填 |
| `contract_name` | ✅ | 合同名称 |
| `contract_no` | ❌ | 合同编号 |
| `contract_content` | ❌ | 合同内容 |
| `amount` | ❌ | 合同金额 |
| `effective_date` | ❌ | 生效日期 |
| `expiry_date` | ❌ | 到期日期 |
| `employee_id` | ❌ | 业务员 ID |
| `status` | ❌ | 状态,默认 `生效中` |
| `remark` | ❌ | 备注 |
| `type` | ❌ | 合同类型(见下方说明) |
| `responsible_user_id` | ❌ | 负责人(默认为当前用户,仅管理员可在前端修改) |
**合同类型限制(`validateContractType` 中间件):**
| 部门 | 允许的合同类型 | 说明 |
|------|-------------|------|
| 招商部 (`franchise_manager`) | 仅 `franchise` | 只能创建加盟合同,且必须选择 `customer_id` |
| 采购部 (`procurement_manager`) | 仅 `supply` | 只能创建采购合同,且必须选择 `supplier_id` |
| 信息技术部/总经理/财务 | 不限制 | 默认 `franchise`,可手动指定 |
**校验规则:**
- `customer_id` 必须为已存在的客户
- `supplier_id` 必须为已存在的供应商
- `employee_id`(如填写)必须为已存在的员工
#### PUT /api/contracts/:id — 更新合同
所有字段均可选。可更新字段包括 `type``supplier_id``responsible_user_id`
**校验规则:** 更新 `customer_id``supplier_id``employee_id` 时会校验关联记录是否存在。合同类型限制同创建。
#### DELETE /api/contracts/:id — 删除合同
**限制:** 被售后记录引用的合同无法删除(返回 400
---
### 6. 售后管理
> 需要 `after_sale:read` / `after_sale:create` / `after_sale:update` / `after_sale:delete` 权限。
#### GET /api/after-sales — 售后列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `id` | 按 ID 精确搜索 |
| `customer_id` | 按客户 ID 精确搜索 |
| `customer_name` | 按客户名称模糊搜索 |
| `handle_status` | 按处理状态过滤(待处理/处理中/已完成) |
| `feedback` | 按反馈内容模糊搜索 |
**响应数据字段(含 JOIN 字段):**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 售后记录 ID |
| `customer_id` | number | 客户 ID |
| `customer_name` | string | 客户名称JOIN 自 customers |
| `feedback` | string | 客户反馈意见 |
| `employee_id` | number | 处理业务员 ID |
| `employee_name` | string | 业务员姓名JOIN 自 employees |
| `handle_method` | string | 处理方式/解决方案 |
| `handle_status` | string | 处理状态:待处理/处理中/已完成 |
| `service_date` | date | 售后日期 |
| `remark` | string | 备注 |
| `responsible_user_id` | number | 负责人用户 ID |
| `responsible_user_name` | string | 负责人姓名JOIN 自 users → employees |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/after-sales — 新增售后
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `customer_id` | ✅ | 关联客户 ID |
| `feedback` | ✅ | 售后反馈内容 |
| `employee_id` | ❌ | 处理业务员 ID前端仅显示运营部员工 |
| `handle_method` | ❌ | 处理方式 |
| `handle_status` | ❌ | 处理状态,默认 `待处理` |
| `service_date` | ❌ | 售后日期 |
| `remark` | ❌ | 备注 |
| `responsible_user_id` | ❌ | 负责人(默认为当前用户,仅管理员可在前端修改) |
**校验规则:**
- `customer_id` 必须为已存在的客户
- `employee_id`(如填写)必须为已存在的员工
#### PUT /api/after-sales/:id — 更新售后
所有字段均可选。
**校验规则:** 更新 `customer_id``employee_id` 时会校验关联记录是否存在。
#### DELETE /api/after-sales/:id — 删除售后
---
### 7. 产品管理
> 需要 `product:read` / `product:create` / `product:update` / `product:delete` 权限。
> 无数据范围隔离,拥有权限即可查看所有产品。
#### GET /api/products — 产品列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `id` | 按 ID 精确搜索 |
| `name` | 按产品名称模糊搜索 |
| `type` | 按产品类型模糊搜索 |
| `supplier` | 按供应商名称模糊搜索 |
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 产品 ID |
| `name` | string | 产品名称 |
| `type` | string | 产品类型/分类 |
| `quantity` | number | 库存数量 |
| `price` | number | 单价 |
| `unit` | string | 计量单位(默认"件" |
| `specification` | string | 规格/型号 |
| `supplier` | string | 供应商名称 |
| `remark` | string | 备注 |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/products — 新增产品
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `name` | ✅ | 产品名称 |
| `type` | ❌ | 类型 |
| `quantity` | ❌ | 库存数量,默认 0 |
| `price` | ❌ | 单价,默认 0.00 |
| `unit` | ❌ | 单位,默认"件" |
| `specification` | ❌ | 规格 |
| `supplier` | ❌ | 供应商名称 |
| `remark` | ❌ | 备注 |
#### PUT /api/products/:id — 更新产品
所有字段均可选。
#### DELETE /api/products/:id — 删除产品
---
### 8. 供应商管理
> 需要 `supplier:read` / `supplier:create` / `supplier:update` / `supplier:delete` 权限。
> 无数据范围隔离,拥有权限即可查看所有供应商。
#### GET /api/suppliers/simple — 简易供应商列表
需要 `supplier:read` 权限。返回所有正常状态供应商的基本信息,用于前端下拉选择。
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 供应商 ID |
| `name` | string | 供应商名称 |
#### GET /api/suppliers — 供应商列表
**查询参数:**
| 参数 | 说明 |
|------|------|
| `id` | 按 ID 精确搜索 |
| `name` | 按供应商名称模糊搜索 |
| `type` | 按类型模糊搜索 |
| `status` | 按状态过滤0-停用, 1-正常) |
**响应数据字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 供应商 ID |
| `name` | string | 供应商名称 |
| `contact` | string | 联系人 |
| `phone` | string | 联系电话 |
| `address` | string | 地址 |
| `type` | string | 类型(原材料/包装/设备) |
| `content` | string | 备注说明 |
| `status` | number | 状态0-停用, 1-正常 |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |
#### POST /api/suppliers — 新增供应商
**请求体:**
| 字段 | 必填 | 说明 |
|------|------|------|
| `name` | ✅ | 供应商名称 |
| `contact` | ❌ | 联系人 |
| `phone` | ❌ | 联系电话 |
| `address` | ❌ | 地址 |
| `type` | ❌ | 类型 |
| `content` | ❌ | 备注说明 |
| `status` | ❌ | 状态,默认 1 |
#### PUT /api/suppliers/:id — 更新供应商
所有字段均可选。
#### DELETE /api/suppliers/:id — 删除供应商
---
## 统一响应格式
### 成功响应
```json
{
"code": 0,
"message": "ok",
"data": { ... }
}
```
### 列表响应
```json
{
"code": 0,
"message": "ok",
"data": {
"list": [ ... ],
"total": 100,
"page": 1,
"pageSize": 10,
"totalPages": 10
}
}
```
### 错误响应
```json
{
"code": 403,
"message": "权限不足"
}
```
| HTTP 状态码 | code | 说明 |
|------------|------|------|
| 400 | 400 | 参数错误 / 校验失败 / 关联记录不存在 |
| 401 | 401 | 未登录 / Token 无效 |
| 403 | 403 | 权限不足 / 账号被禁用 / 合同类型不匹配 |
| 404 | 404 | 资源不存在 |
| 409 | 409 | 数据冲突(如用户名重复) |
| 500 | 500 | 服务器内部错误 |
---
## 数据库设计
### 表结构详情
#### 1. users — 系统用户表
存储系统登录账号,每个用户关联一个部门和一个员工。
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 用户 ID |
| `username` | VARCHAR(50) | NOT NULL, UNIQUE | 用户名(登录账号) |
| `password` | VARCHAR(255) | NOT NULL | 密码bcrypt 哈希) |
| `is_active` | TINYINT(1) | NOT NULL DEFAULT 1 | 账号状态0-禁用, 1-启用 |
| `department_id` | INT | NULLABLE | 部门 ID → departments.id |
| `employee_id` | INT | NULLABLE, UNIQUE | 员工 ID → employees.id |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 2. departments — 部门表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 部门 ID |
| `name` | VARCHAR(50) | NOT NULL, UNIQUE | 部门标识(英文) |
| `description` | VARCHAR(200) | NULLABLE | 部门名称(中文) |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
#### 3. permissions — 权限表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 权限 ID |
| `name` | VARCHAR(100) | NOT NULL, UNIQUE | 权限标识(`resource:action` 格式) |
| `description` | VARCHAR(200) | NULLABLE | 权限描述(中文) |
| `resource` | VARCHAR(50) | NOT NULL | 资源名称 |
| `action` | VARCHAR(50) | NOT NULL | 操作类型 |
#### 4. department_permissions — 部门-权限关联表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `department_id` | INT | PK (联合) | 部门 ID → departments.id |
| `permission_id` | INT | PK (联合) | 权限 ID → permissions.id |
#### 5. customers — 加盟商信息表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 加盟商 ID |
| `name` | VARCHAR(100) | NOT NULL, INDEX | 加盟商姓名 |
| `phone` | VARCHAR(20) | INDEX | 联系电话 |
| `province` | VARCHAR(50) | — | 省 |
| `city` | VARCHAR(50) | — | 市 |
| `district` | VARCHAR(50) | — | 区 |
| `address` | VARCHAR(200) | — | 详细地址 |
| `email` | VARCHAR(100) | — | 电子邮箱 |
| `remark` | TEXT | — | 备注信息 |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 6. employees — 员工信息表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 员工 ID |
| `name` | VARCHAR(100) | NOT NULL, INDEX | 姓名 |
| `gender` | VARCHAR(4) | — | 性别:男/女 |
| `age` | INT | — | 年龄 |
| `education` | VARCHAR(50) | — | 学历 |
| `department` | VARCHAR(100) | INDEX | 所属部门 |
| `entry_date` | DATE | — | 入职时间 |
| `position` | VARCHAR(100) | — | 职务/岗位 |
| `salary` | DECIMAL(10,2) | — | 工资金额 |
| `phone` | VARCHAR(20) | — | 联系电话 |
| `email` | VARCHAR(100) | — | 电子邮箱 |
| `status` | TINYINT(1) | NOT NULL DEFAULT 1 | 在职状态0-离职, 1-在职 |
| `remark` | TEXT | — | 备注 |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 7. contracts — 合同信息表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 合同 ID |
| `customer_id` | INT | NULLABLE, INDEX | 客户 ID → customers.id加盟合同必填采购合同为 null |
| `supplier_id` | INT | NULLABLE, INDEX | 供应商 ID → suppliers.id采购合同必填加盟合同为 null |
| `contract_name` | VARCHAR(200) | NOT NULL | 合同名称 |
| `contract_no` | VARCHAR(100) | — | 合同编号 |
| `contract_content` | TEXT | — | 合同内容/条款 |
| `amount` | DECIMAL(12,2) | — | 合同金额 |
| `effective_date` | DATE | INDEX | 生效日期 |
| `expiry_date` | DATE | — | 到期日期 |
| `employee_id` | INT | INDEX | 业务员 ID → employees.id |
| `status` | VARCHAR(20) | NOT NULL DEFAULT '生效' | 状态:草稿/生效/完成/作废 |
| `remark` | TEXT | — | 备注 |
| `type` | VARCHAR(20) | NOT NULL DEFAULT 'franchise' | 类型:`franchise`(加盟) / `supply`(采购) |
| `responsible_user_id` | INT | — | 负责人用户 ID → users.id |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 8. after_sales — 售后信息表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 售后记录 ID |
| `customer_id` | INT | NOT NULL, INDEX | 客户 ID → customers.id |
| `feedback` | TEXT | NOT NULL | 客户反馈意见 |
| `employee_id` | INT | INDEX | 处理业务员 ID → employees.id |
| `handle_method` | TEXT | — | 处理方式/解决方案 |
| `handle_status` | VARCHAR(20) | NOT NULL DEFAULT '待处理', INDEX | 状态:待处理/处理中/已完成 |
| `service_date` | DATE | — | 售后日期 |
| `remark` | TEXT | — | 备注 |
| `responsible_user_id` | INT | — | 负责人用户 ID → users.id |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 9. products — 产品信息表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 产品 ID |
| `name` | VARCHAR(200) | NOT NULL, INDEX | 产品名称 |
| `type` | VARCHAR(100) | INDEX | 产品类型/分类 |
| `quantity` | INT | NOT NULL DEFAULT 0 | 库存数量 |
| `price` | DECIMAL(10,2) | NOT NULL DEFAULT 0.00 | 单价 |
| `unit` | VARCHAR(20) | DEFAULT '件' | 计量单位 |
| `specification` | VARCHAR(200) | — | 规格/型号 |
| `supplier` | VARCHAR(200) | — | 供应商名称 |
| `remark` | TEXT | — | 备注 |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
#### 10. suppliers — 供应商信息表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INT | PK, AUTO_INCREMENT | 供应商 ID |
| `name` | VARCHAR(200) | NOT NULL, INDEX | 供应商名称 |
| `contact` | VARCHAR(100) | — | 联系人 |
| `phone` | VARCHAR(50) | — | 联系电话 |
| `address` | VARCHAR(500) | — | 地址 |
| `type` | VARCHAR(50) | — | 类型(原材料/包装/设备) |
| `content` | TEXT | — | 备注说明 |
| `status` | TINYINT(1) | NOT NULL DEFAULT 1 | 状态0-停用, 1-正常 |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
---
### 表关系 ER 图
```
┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐
│ departments │ │ permissions │ │ department_permissions │
│──────────────│ │──────────────│ │────────────────────────│
│ id (PK) │◄─┐ │ id (PK) │◄─┐ │ department_id (PK,FK) │
│ name │ │ │ name │ │ │ permission_id (PK,FK) │
│ description │ │ │ description │ └──│ → permissions.id │
└──────────────┘ │ │ resource │ └────────────────────────┘
│ │ action │
│ └──────────────┘
┌──────────────┐ │ ┌──────────────┐
│ users │ │ │ employees │
│──────────────│ │ │──────────────│
│ id (PK) │ │ │ id (PK) │
│ username │ │ │ name │
│ password │ │ │ gender │
│ is_active │ │ │ age │
│ department_id│──┘ │ education │
│ (FK) │ │ department │◄──── 数据范围隔离字段
│ employee_id │─ ─ ─│ entry_date │
│ (FK) ──────┼──→ │ position │
└──────┬───────┘ │ salary │
│ │ phone │
│ │ email │
│ │ status │
│ └──────┬───────┘
│ │
▼ ▼
┌──────────────────────────────────────────────┐
│ customers (加盟商) │
│──────────────────────────────────────────────│
│ id (PK) │
│ name, phone, province, city, district, ... │
└────────────────────┬─────────────────────────┘
┌──────────┴──────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ contracts │ │ after_sales │
│──────────────────│ │──────────────────│
│ id (PK) │ │ id (PK) │
│ customer_id (FK) │ │ customer_id (FK) │
│ → customers.id │ │ → customers.id │
│ supplier_id (FK) │ │ feedback │
│ → suppliers.id │ │ employee_id (FK) │
│ contract_name │ │ → employees.id │
│ contract_no │ │ handle_method │
│ amount │ │ handle_status │
│ employee_id (FK) │ │ service_date │
│ → employees.id │ │ responsible_user │
│ status │ │ _id → users.id │
│ type │ └──────────────────┘
│ franchise/ │
│ supply │
│ responsible_user │
│ _id → users.id │
└──────────────────┘
┌──────────────┐ ┌──────────────┐
│ products │ │ suppliers │
│──────────────│ │──────────────│
│ id (PK) │ │ id (PK) │
│ name │ │ name │
│ type │ │ contact │
│ quantity │ │ phone │
│ price │ │ address │
│ unit │ │ type │
│ specification│ │ content │
│ supplier │ │ status │
└──────────────┘ └──────────────┘
```
**关系说明:**
| 关系 | 类型 | 说明 |
|------|------|------|
| users → departments | 多对一 | 一个用户属于一个部门,一个部门可以有多个用户 |
| users → employees | 一对一 | 一个用户关联一个员工(通过 employee_id唯一约束 |
| departments ↔ permissions | 多对多 | 通过 department_permissions 中间表关联 |
| contracts → customers | 多对一 | 通过 customer_id 关联客户(加盟合同必填,采购合同为 null |
| contracts → suppliers | 多对一 | 通过 supplier_id 关联供应商(采购合同必填,加盟合同为 null |
| contracts → employees | 多对一 | 通过 employee_id 关联业务员 |
| contracts → users | 多对一 | 通过 responsible_user_id 指定负责人 |
| after_sales → customers | 多对一 | 通过 customer_id 关联客户 |
| after_sales → employees | 多对一 | 通过 employee_id 关联业务员 |
| after_sales → users | 多对一 | 通过 responsible_user_id 指定负责人 |
> **注意**:数据库未设置外键约束,引用完整性由应用代码保证(插入/更新前检查关联记录是否存在)。
---
## 部门权限系统
### 部门定义
系统预置 6 个部门,对应蜜雪冰城总部的组织架构:
| 部门标识 | 中文名 | 职责概述 |
|---------|--------|---------|
| `admin` | 信息技术部 | 系统全权管理,拥有所有权限 |
| `general_manager` | 总经理办公室 | 全局只读,查看所有业务数据 |
| `franchise_manager` | 招商部 | 管理加盟商和加盟合同 |
| `operations_manager` | 运营部 | 维护加盟商信息、处理售后 |
| `procurement_manager` | 采购部 | 管理产品、供应商和采购合同 |
| `finance` | 财务部 | 只读查看业务数据(加盟商、合同、售后、员工) |
### 权限列表
共 25 个权限,按 `资源:操作` 格式命名:
| 权限标识 | 中文描述 |
|---------|---------|
| **加盟商 (customer)** | |
| `customer:read` | 查看加盟商 |
| `customer:create` | 新增加盟商 |
| `customer:update` | 修改加盟商 |
| `customer:delete` | 删除加盟商 |
| **合同 (contract)** | |
| `contract:read` | 查看合同 |
| `contract:create` | 新增合同 |
| `contract:update` | 修改合同 |
| `contract:delete` | 删除合同 |
| **售后 (after_sale)** | |
| `after_sale:read` | 查看售后 |
| `after_sale:create` | 新增售后 |
| `after_sale:update` | 修改售后 |
| `after_sale:delete` | 删除售后 |
| **产品 (product)** | |
| `product:read` | 查看产品原料 |
| `product:create` | 新增产品原料 |
| `product:update` | 修改产品原料 |
| `product:delete` | 删除产品原料 |
| **供应商 (supplier)** | |
| `supplier:read` | 查看供应商 |
| `supplier:create` | 新增供应商 |
| `supplier:update` | 修改供应商 |
| `supplier:delete` | 删除供应商 |
| **员工 (employee)** | |
| `employee:read` | 查看员工 |
| `employee:create` | 新增员工 |
| `employee:update` | 修改员工 |
| `employee:delete` | 删除员工 |
| **用户 (user)** | |
| `user:manage` | 管理用户账号 |
### 部门-权限映射
| 权限 | admin | general_manager | franchise_manager | operations_manager | procurement_manager | finance |
|------|:-----:|:---------------:|:-----------------:|:------------------:|:-------------------:|:-------:|
| customer:read | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| customer:create | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| customer:update | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| customer:delete | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| contract:read | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| contract:create | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
| contract:update | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
| contract:delete | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
| after_sale:read | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
| after_sale:create | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| after_sale:update | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| after_sale:delete | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| product:read | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| product:create | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| product:update | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| product:delete | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| supplier:read | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| supplier:create | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| supplier:update | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| supplier:delete | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| employee:read | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| employee:create | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| employee:update | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| employee:delete | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| user:manage | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
### 数据范围隔离
拥有操作权限 ≠ 能看到所有数据。系统通过 `dataScope()` 中间件在 SQL 查询层面做数据隔离:
| 资源 | admin | general_manager | franchise_manager | operations_manager | procurement_manager | finance |
|------|-------|-----------------|-------------------|--------------------|--------------------|---------|
| customers | 全部 | 全部 | 全部(过渡期) | 全部(过渡期) | 全部 | 全部 |
| contracts | 全部 | 全部 | 仅加盟合同 | — | 仅采购合同 | 全部 |
| after_sales | 全部 | 全部 | — | 全部(过渡期) | — | 全部 |
| employees | 全部 | 全部 | 仅本部门 | 仅本部门 | 仅本部门 | 全部 |
| products | 全部 | 全部 | — | — | 全部 | — |
| suppliers | 全部 | 全部 | — | — | 全部 | — |
| users | 全部 | — | — | — | — | — |
> **"—"** 表示该部门无此资源的权限,请求会被 `checkPermission` 中间件直接拦截(返回 403
**隔离机制说明:**
| 隔离类型 | 资源 | 实现方式 |
|---------|------|---------|
| 合同类型过滤 | contracts | 招商部只能看到 `type = 'franchise'`,采购部只能看到 `type = 'supply'` |
| 部门过滤 | employees | 招商部/运营部/采购部只能看到本部门员工(`department = 本部门名称` |
| 过渡期全量 | customers, after_sales | 当前不注入数据范围,所有部门看到全量数据 |
| 无隔离 | products, suppliers | 无数据范围过滤,有权限即可看到全部 |
**过渡期说明:**
- `customers` 已移除 `responsible_user_id` 字段,不再需要负责人隔离
- `after_sales``dataScope()` 当前为占位状态(不注入任何 scope所有有权限的部门都能看到全量数据
- 后续可基于 `responsible_user_id` 对 contracts/after_sales 激活负责人隔离
**合同类型与部门限制(`validateContractType` 中间件):**
- 招商部(`franchise_manager`)只能创建/修改加盟合同(`type = 'franchise'`
- 采购部(`procurement_manager`)只能创建/修改采购合同(`type = 'supply'`
- 信息技术部/总经理/财务不限制
### 中间件说明
| 中间件 | 文件 | 类型 | 说明 |
|--------|------|------|------|
| `auth` | `server.js` | 认证 | 解析 JWT Token`{ id, username, name, department_id, departmentName, departmentDesc, permissions }` 挂到 `req.user` |
| `checkPermission(resource, action)` | `middleware/permissions.js` | 权限 | 检查 `req.user.permissions` 是否包含 `"resource:action"`,否 → 403 |
| `dataScope(resource)` | `middleware/permissions.js` | 数据范围 | 根据用户部门计算 SQL 过滤条件,挂到 `req.scope = { sql, params }`admin/general_manager 不注入(全量) |
| `validateContractType` | `middleware/permissions.js` | 业务规则 | 限制招商部/采购部只能操作对应类型的合同 |
| `protectSelfUpdate` | `middleware/users.js` | 自保护 | 禁止用户修改自己的部门或禁用自己 |
| `protectUserDelete` | `middleware/users.js` | 自保护 | 禁止用户删除自己或删除信息技术部最后一个管理员 |
| `allowUserCreation` | `middleware/employees.js` | 业务规则 | 允许有 `user:manage` 权限的用户在创建员工时同步创建系统账号 |
**`req.scope` 注入格式:**
```javascript
// 示例:招商部查看合同时注入
req.scope = {
sql: 'AND type = ?',
params: ['franchise']
}
// 路由层机械使用contracts list/detail 需加 c. 别名前缀)
where += ' ' + req.scope.sql.replace(/\btype\b/g, 'c.type')
params.push(...req.scope.params)
```
---
## 预置账号
系统首次启动时自动创建以下账号,密码均为 `123456`
| 用户名 | 姓名 | 部门标识 | 部门 | 职位 |
|--------|------|---------|------|------|
| `admin` | 系统管理员 | `admin` | 信息技术部 | 系统管理员 |
| `zhangchao` | 张超 | `general_manager` | 总经理办公室 | 总经理 |
| `liming` | 李明 | `franchise_manager` | 招商部 | 招商经理 |
| `wangli` | 王丽 | `operations_manager` | 运营部 | 运营经理 |
| `zhaoqiang` | 赵强 | `procurement_manager` | 采购部 | 采购经理 |
| `chenfang` | 陈芳 | `finance` | 财务部 | 财务主管 |
**额外预置账号**(业务员工,同样密码 `123456`
| 用户名 | 姓名 | 部门 | 职位 |
|--------|------|------|------|
| `liuyang` | 刘洋 | 招商部 | 招商专员 |
| `sunting` | 孙婷 | 招商部 | 招商专员 |
| `zhoujie` | 周杰 | 运营部 | 运营专员 |
| `wumin` | 吴敏 | 运营部 | 运营专员 |
| `zhengwei` | 郑伟 | 运营部 | 售后工程师 |
| `huanglei` | 黄磊 | 采购部 | 采购专员 |
| `mali` | 马丽 | 财务部 | 会计 |
| `linfeng` | 林峰 | 总经理办公室 | 副总经理 |
### 快速测试
```bash
# 以管理员身份登录
curl -X POST http://127.0.0.1:3000/api/user/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
# 以招商经理身份登录(只能看到加盟合同)
curl -X POST http://127.0.0.1:3000/api/user/login \
-H "Content-Type: application/json" \
-d '{"username":"liming","password":"123456"}'
```