Files
hzdyjs/退学、休学、复学、毕业/02-组织关系处理.md
T
2025-12-25 18:01:27 +08:00

393 lines
14 KiB
Markdown
Raw 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.
# 02-组织关系处理
## 📋 概述
本分文档详细说明学生退学、休学、毕业、复学处理中的组织关系处理内容,包括班级干部信息和社团成员关系的变更逻辑。
## 👥 组织关系处理
### 1. 班级干部信息处理
#### 📊 数据库表结构
**表名**: `xg_class_pos_msg`
**主键**: `id` (bigint(20), 雪花ID)
**核心关联字段**:
- `class_id`: 班级ID,关联 `xg_class_msg.id`
- `student_id`: 学生ID,关联 `xg_student_msg.id`
- `student_no`: 学号
- `student_name`: 学生姓名(冗余字段)
#### 🎯 关键业务字段
基于数据库表结构 `xg_class_pos_msg`,关键字段如下:
| 字段名 | 类型 | 说明 | 默认值 |
|--------|------|------|--------|
| `is_current` | tinyint(1) | 是否现任(0-非现任,1-现任) | 1 |
| `cadre_status` | char(1) | 职务任职状态(关联stu_cadre_status字典) | 1 |
| `appointment_date` | date | 任职日期 | - |
| `term_start_date` | date | 任期开始日期 | - |
| `term_end_date` | date | 任期结束日期 | NULL |
| `term_duration` | int(11) | 任期时长(月) | 12 |
| `dismissal_date` | date | 离任日期 | NULL |
| `dismissal_reason` | varchar(200) | 离任原因 | NULL |
| `appointment_method` | varchar(20) | 任命方式(appointment/election/recommendation) | appointment |
#### 🎯 干部状态字典
系统使用 `stu_cadre_status` 字典类型,具体定义如下:
| 状态值 | 状态名称 | 说明 | 样式类 |
|--------|----------|------|--------|
| `1` | 在任 | 正常任职状态 | primary |
| `2` | 到期 | 任期自然到期 | success |
| `3` | 辞职 | 干部主动辞职 | warning |
| `4` | 免职 | 被免去职务 | danger |
| `5` | 调任 | 职务调整调动 | info |
| `6` | 休学 | 因学生休学离任 | danger |
**关键字段说明** (基于实体类代码分析):
- `isCurrent`: 是否现任 (0-非现任,1-现任)
- `cadreStatus`: 学生职务任职状态
- `dismissalDate`: 离任日期
- `dismissalReason`: 离任原因
- `termStartDate`: 任期开始日期
- `termEndDate`: 任期结束日期
- `termDuration`: 任期时长(月)
#### 🔄 离任业务流程
##### 📋 完整离任流程
```
1. 验证干部信息存在性和状态
2. 计算任期时长 (term_start_date 到 term_end_date)
3. 更新干部状态为非现任 (is_current = 0)
4. 设置离任状态和原因
5. 更新任期结束日期和离任日期
6. 删除用户角色关联 (sys_user_role)
7. 记录操作日志
```
##### 💻 后端实现逻辑
**控制器接口**: `POST /xgClassPosMsg/xgClassPosMsg/dismiss/{id}`
**核心方法**: `XgClassPosMsgServiceImpl.dismissCadre()`
```java
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean dismissCadre(Long id, Date termEndDate, String cadreStatus, String dismissalReason) {
// 1. 查询并验证干部信息
XgClassPosMsg classPosMsg = baseMapper.selectById(id);
// 2. 计算任期时长
Long termDuration = calculateTermDuration(classPosMsg.getTermStartDate(), termEndDate);
// 3. 更新干部信息
XgClassPosMsg updateEntity = new XgClassPosMsg();
updateEntity.setId(id);
updateEntity.setTermEndDate(termEndDate);
updateEntity.setTermDuration(termDuration);
updateEntity.setCadreStatus(cadreStatus);
updateEntity.setDismissalReason(dismissalReason);
updateEntity.setDismissalDate(termEndDate);
updateEntity.setIsCurrent(0L); // 设置为非现任
baseMapper.updateById(updateEntity);
// 4. 删除用户角色关联
sysUserRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
.eq(SysUserRole::getUserId, classPosMsg.getStudentId()));
return true;
}
```
##### 🎨 前端离任界面
**文件位置**: `web/src/views/xg/xsjcxx/xgClassPosMsg/xgClassPosMsg/index.vue`
**离任按钮**: 仅对 `is_current = 1` 的干部显示
```vue
<!-- 离任按钮 -->
<el-tooltip content="离任" placement="top">
<el-button
link
type="warning"
icon="ArrowDown"
@click="handleDismissal(scope.row)"
v-if="scope.row.isCurrent === 1"
></el-button>
</el-tooltip>
<!-- 离任对话框 -->
<el-dialog title="班级干部离任" v-model="dismissalDialog.visible" width="500px" append-to-body>
<el-form ref="dismissalFormRef" :model="dismissalForm" :rules="dismissalRules" label-width="140px">
<!-- 当前信息(只读) -->
<el-form-item label="班级名称" prop="className">
<el-input v-model="dismissalForm.className" disabled />
</el-form-item>
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="dismissalForm.studentName" disabled />
</el-form-item>
<el-form-item label="任期开始日期" prop="termStartDate">
<el-input v-model="dismissalForm.termStartDate" disabled />
</el-form-item>
<!-- 离任信息(可填写) -->
<el-form-item label="任期结束日期" prop="termEndDate">
<el-date-picker
clearable
v-model="dismissalForm.termEndDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择任期结束日期"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="离任状态" prop="cadreStatus">
<el-select v-model="dismissalForm.cadreStatus" placeholder="请选择离任状态">
<el-option label="到期" value="2" />
<el-option label="辞职" value="3" />
<el-option label="免职" value="4" />
<el-option label="调任" value="5" />
<el-option label="休学" value="6" />
</el-select>
</el-form-item>
<el-form-item label="离任原因" prop="dismissalReason">
<el-input v-model="dismissalForm.dismissalReason" type="textarea" placeholder="请输入离任原因" />
</el-form-item>
</el-form>
</el-dialog>
```
#### 🔄 学生状态变更时的干部处理逻辑
- **休学**: `cadre_status = '6'`(休学),`is_current = 0`,`dismissal_reason = '学生休学'`
- **退学**: `cadre_status = '4'`(免职),`is_current = 0`,`dismissal_reason = '学生退学'`
- **毕业**: `cadre_status = '4'`(免职),`is_current = 0`,`dismissal_reason = '学生毕业'`
#### 💾 SQL批量处理脚本
```sql
-- 参数说明:
-- @student_id: 学生ID
-- @change_type: 变更类型 (XIU_XUE-休学, TUI_XUE-退学, BI_YE-毕业)
-- @operator_id: 操作员ID
-- @dismissal_date: 离任日期 (默认为当前日期)
SET @dismissal_date = COALESCE(@dismissal_date, CURDATE());
-- 1. 更新干部状态(休学)
UPDATE xg_class_pos_msg
SET is_current = 0,
cadre_status = '6', -- 休学状态
term_end_date = @dismissal_date,
dismissal_date = @dismissal_date,
dismissal_reason = '学生休学',
term_duration = TIMESTAMPDIFF(MONTH, term_start_date, @dismissal_date),
update_by = @operator_id,
update_time = NOW()
WHERE student_id = @student_id
AND is_current = 1
AND deleted = 0
AND @change_type = 'XIU_XUE';
-- 2. 更新干部状态(退学/毕业)
UPDATE xg_class_pos_msg
SET is_current = 0,
cadre_status = '4', -- 免职状态
term_end_date = @dismissal_date,
dismissal_date = @dismissal_date,
dismissal_reason = CASE
WHEN @change_type = 'TUI_XUE' THEN '学生退学'
WHEN @change_type = 'BI_YE' THEN '学生毕业'
ELSE '学生状态变更'
END,
term_duration = TIMESTAMPDIFF(MONTH, term_start_date, @dismissal_date),
update_by = @operator_id,
update_time = NOW()
WHERE student_id = @student_id
AND is_current = 1
AND deleted = 0
AND @change_type IN ('TUI_XUE', 'BI_YE');
```
### 2. 社团成员关系处理
#### 📊 数据库表结构
**表名**: `stgl_member` (原名 `club_member`,现已重构)
**主键**: `id` (bigint(20))
**核心关联字段**:
- `user_id`: 用户ID,关联学生用户ID
- `student_no`: 学号
- `student_name`: 学生姓名(冗余字段)
- `relation_id`: 关联ID(社团ID或志愿者队伍ID)
- `member_type`: 成员类型(1-社团成员,2-志愿者)
#### 🎯 成员状态字典
系统使用 `stgl_member_status` 字典类型,具体定义如下:
| 状态值 | 状态名称 | 说明 | 样式类 |
|--------|----------|------|--------|
| `0` | 待审核 | 申请加入,等待审核 | warning |
| `1` | 审核中 | 正在审核中 | info |
| `2` | 已加入 | 正式成员 | success |
| `3` | 已驳回 | 审核未通过 | danger |
| `4` | 已退出 | 已退出组织 | danger |
**关键字段说明** (基于实体类代码分析):
- `memberType`: 成员类型 (1-社团成员,2-志愿者)
- `userId`: 用户ID,对应学生ID
- `studentNo`: 学号
- `studentName`: 学生姓名
- `roleType`: 角色类型 (社长、副社长、部长、普通成员)
- `joinTime`: 加入时间
- `status`: 状态 (0-待审核,1-审核中,2-已加入,3-已驳回,4-已退出)
#### 🔄 退出处理逻辑
##### 📋 退出业务规则
- **适用范围**: 对所有状态为"待审核"(0)、"审核中"(1)、"已加入"(2)的成员
- **处理方式**: 将状态更新为"已退出"(4)
- **数据保留**: 保留历史记录,不物理删除
- **关联清理**: 不涉及其他关联数据的清理
##### 💻 后端实现逻辑
**所在模块**: `ruoyi-stgl` (社团管理模块)
**实体类**: `StglMember.java`
**服务类**: `StglMemberServiceImpl.java`
```java
// 社团成员实体类关键字段
@Data
@TableName("stgl_member")
public class StglMember extends TenantEntity {
private Long userId; // 用户ID
private String studentNo; // 学号
private String studentName; // 学生姓名
private Integer memberType; // 成员类型:1-社团成员,2-志愿者
private Long relationId; // 关联ID
private Integer status; // 状态:0-待审核,1-审核中,2-已加入,3-已驳回,4-已退出
private String roleType; // 角色类型
private Date joinTime; // 加入时间
}
```
##### 🎨 前端界面展示
**管理界面**: `stgl/member/index.vue`
**状态显示**: 使用字典标签显示成员状态
```vue
<!-- 成员状态列 -->
<el-table-column label="状态" align="center" prop="status" width="100">
<template #default="scope">
<dict-tag :options="stgl_member_status" :value="scope.row.status" />
</template>
</el-table-column>
```
#### 🔄 学生状态变更时的成员处理逻辑
- **休学**: 成员状态改为 `4`(已退出),退出原因为"学生休学"
- **退学**: 成员状态改为 `4`(已退出),退出原因为"学生退学"
- **毕业**: 成员状态改为 `4`(已退出),退出原因为"学生毕业"
#### 💾 SQL批量处理脚本
```sql
-- 参数说明:
-- @student_id: 学生ID (对应user_id)
-- @change_type: 变更类型 (XIU_XUE-休学, TUI_XUE-退学, BI_YE-毕业)
-- @operator_id: 操作员ID
-- 更新社团/志愿者成员状态为已退出
UPDATE stgl_member
SET status = 4, -- 已退出状态
update_by = @operator_id,
update_time = NOW(),
remark = CONCAT(COALESCE(remark, ''), ' | 退出原因:', CASE
WHEN @change_type = 'XIU_XUE' THEN '学生休学'
WHEN @change_type = 'TUI_XUE' THEN '学生退学'
WHEN @change_type = 'BI_YE' THEN '学生毕业'
ELSE '学生状态变更'
END, ' - ', NOW())
WHERE user_id = @student_id
AND status IN (0, 1, 2) -- 待审核、审核中、已加入
AND deleted = 0;
```
## 🔗 关联数据处理
### 1. 用户角色权限清理
#### 📋 角色关联表
**表名**: `sys_user_role`
**关联逻辑**: 当干部离任时,需要清理对应的用户角色关联
#### 🔄 清理逻辑
```java
// 在干部离任时执行角色清理
int deleteCount = sysUserRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
.eq(SysUserRole::getUserId, classPosMsg.getStudentId()));
```
#### ⚠️ 注意事项
- **仅限干部离任**: 普通社团成员退出时不清理角色权限
- **角色识别**: 根据用户ID清理所有相关角色关联
- **权限回收**: 确保干部权限及时回收,避免安全风险
### 2. 数据一致性保证
#### 🛡️ 事务处理要求
```sql
-- 建议在学生状态变更的大事务中包含组织关系处理
START TRANSACTION;
-- 1. 备份原始数据到his表
INSERT INTO xg_class_pos_msg_his SELECT * FROM xg_class_pos_msg WHERE student_id = ?;
INSERT INTO stgl_member_his SELECT * FROM stgl_member WHERE user_id = ?;
-- 2. 处理班级干部关系(仅退学、休学、毕业时)
UPDATE xg_class_pos_msg SET is_current = 0, cadre_status = ?, term_end_date = NOW(), dismissal_date = NOW(), dismissal_reason = ? WHERE student_id = ?;
-- 3. 处理社团成员关系(仅退学、休学、毕业时)
UPDATE stgl_member SET status = 4 WHERE user_id = ?;
-- 4. 清理用户角色(如果需要)
DELETE FROM sys_user_role WHERE user_id = ?;
COMMIT;
```
#### 📊 处理统计
| 处理类型 | 涉及表数量 | 主要操作 |
|----------|------------|----------|
| 班级干部 | 2 (干部表+角色表) | 更新状态+清理权限 |
| 社团成员 | 1 (成员表) | 更新状态 |
| **总计** | **3个表** | **状态更新+权限清理** |
## ⚠️ 重要注意事项
### 1. 业务规则约束
- **干部离任**: 必须通过专门的离任接口,计算任期时长
- **成员退出**: 直接更新状态,不涉及权限清理
- **数据保留**: 所有操作都保留历史记录,便于审计
- **状态验证**: 操作前验证当前状态的合法性
### 2. 权限与安全
- **操作权限**: 干部离任需要专门的权限验证
- **角色清理**: 确保干部权限及时回收,防止权限泄露
- **审计记录**: 记录所有组织关系变更的操作日志
- **数据完整性**: 保证关联数据的完整性约束
### 3. 性能优化建议
- **批量处理**: 支持对多个学生的组织关系批量处理
- **索引优化**: 确保学生ID相关的查询有适当索引
- **事务控制**: 使用合适的事务隔离级别保证数据一致性
- **异步处理**: 大批量操作考虑异步处理机制
---
## 📅 更新记录
- **2025-12-XX**: 分文档创建,包含组织关系处理逻辑
- **2025-12-XX**: 基于代码分析完善技术实现细节和业务逻辑
- **后续更新**: 根据实际业务需求持续完善
---
*本分文档详细说明学生状态变更时的组织关系处理逻辑和技术实现,包括班级干部职务和社团成员关系的清理。*