# 学校主子表导入导出接口文档（art/school）

## 1. 目标

面向前端提供“学校主表 + 子表（详情/校区/学院/专业/招生计划）”的一体化导出与导入能力，支持：

- 多 Sheet 标准模板导出
- 导入前冲突预检
- 按学校编码选择性替换（原子事务）
- 子表导入按“业务键优先 + ID映射兜底”自动重建关联（避免替换后ID变化导致错绑）
- 导入明细反馈（成功/失败/跳过与原因）

---

## 2. 接口清单

### 2.1 导出模板数据（含业务数据）

- **URL**: `POST /art/school/export`
- **权限**: `art:school:export`
- **Content-Type**: `application/x-www-form-urlencoded`
- **响应**: Excel 文件下载（`.xlsx`）

#### 查询参数（同学校列表）

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| mainCode | string | 否 | 学校编码 |
| mainName | string | 否 | 学校名称（模糊） |
| shortName | string | 否 | 学校简称（模糊） |
| province/city/district | string | 否 | 行政区 |
| universityType | string | 否 | 大学类型 |
| educationLevel | string | 否 | 学历层次 |
| schoolNature | string | 否 | 办学性质 |

#### 导出 Sheet 结构

1. `学校基本信息`
2. `学校详情信息`
3. `校区信息`
4. `学院信息`
5. `专业信息`
6. `招生计划信息`
7. `模板说明`

> 子表均包含 `学校编码*`、`学校名称*` 用于与主表关联。

---

### 2.2 导出空模板

- **URL**: `POST /art/school/importTemplate`
- **权限**: `art:school:export`
- **响应**: 空模板 Excel（仅表头 + 模板说明）

---

### 2.3 导入预检

- **URL**: `POST /art/school/importPreview`
- **权限**: `art:school:import`
- **Content-Type**: `multipart/form-data`
- **入参**:
  - `file`（Excel 文件，必填）

#### 返回示例

```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "totalSchoolCount": 10,
    "conflictCount": 3,
    "invalidCount": 1,
    "details": [
      {
        "mainCode": "1001",
        "mainName": "测试学校01",
        "status": "CONFLICT",
        "message": "系统已存在该学校数据，导入时需确认是否替换"
      },
      {
        "mainCode": "",
        "mainName": "",
        "status": "INVALID",
        "message": "必填字段缺失：学校编码/学校名称不能为空"
      }
    ]
  }
}
```

#### 状态说明

- `CONFLICT`: 系统已存在（按学校编码/名称命中）
- `INVALID`: 模板数据无效（缺失、重复、子表关联不到主表）

---

### 2.4 导入执行

- **URL**: `POST /art/school/importData`
- **权限**: `art:school:import`
- **Content-Type**: `multipart/form-data`
- **入参**:
  - `file`（Excel 文件，必填）
  - `replaceAll`（boolean，选填，默认 `false`）
  - `replaceMainCodes`（string[]，选填，可多值传参）

#### 前端推荐调用逻辑

1. 先调 `importPreview`。
2. 对 `CONFLICT` 学校弹窗询问“是否替换”。
3. 将用户同意替换的学校编码组装为 `replaceMainCodes`，再调 `importData`。
4. 用户全量替换时可直接传 `replaceAll=true`。

#### 返回示例

```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "totalSchoolCount": 10,
    "successCount": 8,
    "failCount": 1,
    "skippedCount": 1,
    "details": [
      {
        "mainCode": "1001",
        "mainName": "测试学校01",
        "status": "SUCCESS",
        "message": "替换成功"
      },
      {
        "mainCode": "1002",
        "mainName": "测试学校02",
        "status": "SKIPPED",
        "message": "学校名称重复且用户取消替换"
      },
      {
        "mainCode": "1003",
        "mainName": "测试学校03",
        "status": "FAILED",
        "message": "学院保存失败"
      }
    ]
  }
}
```

#### 状态说明

- `SUCCESS`: 导入/替换成功
- `SKIPPED`: 冲突且用户未选择替换
- `FAILED`: 导入失败（事务回滚）
- `INVALID`: 文件预校验失败

---

## 3. 原子性与一致性

- 导入按“单学校”为最小事务单元：
  - 该学校主表 + 子表（详情/校区/学院/专业/招生计划）要么全部成功，要么全部回滚。
- 替换逻辑：
  1. 删除该学校历史主表及上述子表数据
  2. 插入新主表数据
  3. 插入新子表数据

---

## 4. 模板必填规则（简表）

- `学校基本信息`: `学校编码*`、`学校名称*`
- `学校详情信息`: `学校编码*`、`学校名称*`
- `校区信息`: `学校编码*`、`学校名称*`、`校区名称*`
- `学院信息`: `学校编码*`、`学校名称*`、`学院名称*`
- `专业信息`: `学校编码*`、`学校名称*`、`专业名称*`，且需提供 `学院ID` 或 `学院编码/学院名称`
- `招生计划信息`: `学校编码*`、`学校名称*`、`年份*`、`招生省份*`、`专业名称*`、`计划数*`

> `*` 表示模板必填字段。

---

## 5. 兼容说明

- 现有 `POST /art/school` 与 `PUT /art/school` 仍用于单学校编辑（`school + detail` 统一结构）。
- 本文档新增导入导出接口可与现有列表/编辑功能并行使用。

---

## 6. 子表关联规则（导入执行）

- 学院：导入时重新生成 `college_id`，并建立 `旧学院ID -> 新学院ID` 映射。
- 专业：优先使用 `学院ID` 映射；若无效则按 `学院编码+学院名称`（或仅学院名称）匹配学院，再插入专业并建立 `旧专业ID -> 新专业ID` 映射。
- 招生计划：优先使用 `专业ID` 映射；若无效则按 `专业名称+学历层次` 匹配；仍匹配不到则 `major_id` 置空，仅保留 `major_name` 入库。
