From 2cef296b1ecbb6c74bc8b7d1d7d7531d5c1d673f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=83=AD=E5=BA=86=E6=B3=B0?=
<12369755+htcloud1@user.noreply.gitee.com>
Date: Sat, 22 Aug 2026 15:53:06 +0800
Subject: [PATCH] =?UTF-8?q?feat(meeting):=20=E4=BC=9A=E8=AE=AE=E8=AF=A6?=
=?UTF-8?q?=E6=83=85=E9=A1=B5=E5=8F=82=E4=BC=9A=E4=BA=BA=20CRUD=20+=20BizP?=
=?UTF-8?q?rojectPlan=20audit=20=E5=AD=97=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 后端: BizMeetingAttendee 增 list/add/update/delete 4 端点
按手机号定位 sys_user (无则新建, 用户名=密码=phone, role_type='doctor')
Mapper: insertWithProfile (一次写完整档案) + deleteByPrimaryKey
Service: insertByPhoneWithProfile (重复参会人抛友好异常)
- #4 配套: BizProjectPlan.auditOpinion/auditBy/auditTime + 关联项目名
BizProjectPlan.projectName (LEFT JOIN biz_project) 用于 #4 通知 content
- 前端 MeetingDetail.vue: 劳务材料 tab 内, 劳务明细表 file-row 下方
直接出表 (无表题), 含姓名/手机/单位/科室/职称/劳务费/签字状态/操作
新增/编辑 dialog 共用, 删除走 ElMessageBox.confirm 二次确认
- 触发 #5: 新参会人 POST 时也发会议邀请通知 (与 BizMeetingController.add 一致)
---
.../BizMeetingAttendeeController.java | 104 ++++++++-
.../controller/BizMeetingController.java | 21 +-
.../ruoyi/business/domain/BizProjectPlan.java | 10 +
.../mapper/BizMeetingAttendeeMapper.java | 13 ++
.../service/IBizMeetingAttendeeService.java | 26 +++
.../impl/BizMeetingAttendeeServiceImpl.java | 86 +++++++
.../impl/BizProjectPlanServiceImpl.java | 30 ++-
.../business/BizMeetingAttendeeMapper.xml | 25 +++
.../mapper/business/BizProjectPlanMapper.xml | 8 +-
ry-vue3/src/views/manager/MeetingDetail.vue | 212 +++++++++++++++++-
10 files changed, 522 insertions(+), 13 deletions(-)
diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingAttendeeController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingAttendeeController.java
index 7fa40ca..1e0392c 100644
--- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingAttendeeController.java
+++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingAttendeeController.java
@@ -8,19 +8,25 @@ import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.business.domain.BizMeeting;
import com.ruoyi.business.domain.BizMeetingAttendee;
+import com.ruoyi.business.notify.BizNotifyService;
import com.ruoyi.business.service.IBizMeetingAttendeeService;
+import com.ruoyi.business.service.IBizMeetingService;
/**
- * 会议参会人 Controller (劳务协议 / 手写签名)
+ * 会议参会人 Controller (劳务协议 / 手写签名 + 管理端 CRUD)
*
* 公开端点 (任意登录用户可调):
* - GET /business/meetingAttendee/unsigned 当前用户的"待签署"会议 (handsign 或 labor_protocol 为空)
* - PUT /business/meetingAttendee/{id}/handsign 更新手写签名 (Base64, 直接存 DB)
* - PUT /business/meetingAttendee/{id}/laborProtocol 更新劳务协议 URL (OSS)
*
- * 管理端点 (后续 BizMeetingController.add/edit 调用):
- * - /business/meetingAttendee (CRUD)
+ * 管理端点 (MeetingDetail 参会人 CRUD 用, admin/manager):
+ * - GET /business/meetingAttendee/list/{meetingId} 会议下所有参会人
+ * - POST /business/meetingAttendee 按手机号新增参会人 (定位/新建 sys_user)
+ * - PUT /business/meetingAttendee 更新参会人档案
+ * - DELETE /business/meetingAttendee/{id} 删除参会人 (单条)
*/
@RestController
@RequestMapping("/business/meetingAttendee")
@@ -28,6 +34,10 @@ public class BizMeetingAttendeeController extends BaseController {
@Autowired
private IBizMeetingAttendeeService attendeeService;
+ @Autowired
+ private IBizMeetingService bizMeetingService;
+ @Autowired
+ private BizNotifyService bizNotifyService;
/**
* 当前登录用户的"待签署"列表 (handsign 或 labor_protocol 任一为空)
@@ -40,6 +50,64 @@ public class BizMeetingAttendeeController extends BaseController {
return success(rows);
}
+ /**
+ * 管理端: 某会议的全部参会人 (MeetingDetail 参会人 CRUD 用).
+ * 返回全字段, 前端按需展示.
+ */
+ @GetMapping("/list/{meetingId}")
+ public AjaxResult listByMeetingId(@PathVariable("meetingId") Long meetingId) {
+ return success(attendeeService.selectByMeetingId(meetingId));
+ }
+
+ /**
+ * 管理端: 按手机号新增参会人.
+ *
+ *
后端流程: 按 body.phone 查 sys_user → 查到用之, 查不到新建 (用户名=密码=phone, role_type='doctor')
+ * → 写完整档案行 (含 name/work_unit/fee...) → 触发 #5 会议邀请通知.
+ *
+ *
请求体示例:
+ *
+ * { "meetingId": 123, "phone": "13800138000", "name": "张三",
+ * "workUnit": "协和医院", "department": "心内科", "title": "副主任医师",
+ * "laborForm": "授课", "feePreTax": 1000, "tax": 100, "fee": 900 }
+ *
+ */
+ @Log(title = "参会人", businessType = BusinessType.INSERT)
+ @PostMapping
+ public AjaxResult add(@RequestBody BizMeetingAttendee body) {
+ Long attendeeId = attendeeService.insertByPhoneWithProfile(body);
+ // #5 触发: 新参会人发邀请 (走 BizMeetingController.edit 同一路径, 不需要 dedup — 这是新行)
+ BizMeeting m = bizMeetingService.getById(body.getMeetingId());
+ bizNotifyService.meetingInvitation(body.getUserId(), body.getMeetingId(),
+ m != null ? m.getMeetingName() : null,
+ m != null ? m.getStartTime() : null);
+ return success(attendeeId);
+ }
+
+ /**
+ * 管理端: 更新参会人档案 (管理员在 MeetingDetail 改了姓名/单位/劳务费等).
+ * 不允许改 phone/userId (用 delete + add 来改手机号, 避免破坏 sys_user 关联).
+ */
+ @Log(title = "参会人", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public AjaxResult edit(@RequestBody BizMeetingAttendee body) {
+ if (body.getId() == null) {
+ return error("id 不能为空");
+ }
+ body.setUpdateBy(SecurityUtils.getUsername());
+ return toAjax(attendeeService.updateProfile(body));
+ }
+
+ /**
+ * 管理端: 按 id 删除参会人 (单条).
+ * 注意: 仅删 biz_meeting_attendee 行, 不删 sys_user (参会人可能是真实用户, 不能因参会关系删号).
+ */
+ @Log(title = "参会人", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{id}")
+ public AjaxResult remove(@PathVariable("id") Long id) {
+ return toAjax(attendeeService.deleteByPrimaryKey(id));
+ }
+
/** 更新手写签名 (Base64 字符串, 直接存 DB longtext) */
@Log(title = "手写签名", businessType = BusinessType.UPDATE)
@PutMapping("/{id}/handsign")
@@ -51,17 +119,35 @@ public class BizMeetingAttendeeController extends BaseController {
return toAjax(attendeeService.updateHandsign(entity));
}
- /** 更新劳务协议 URL (OSS 上传后调本接口) */
+ /**
+ * 更新劳务协议 URL (OSS 上传后调本接口).
+ *
+ * 同时承担 #6 通知触发: 当 labor_protocol 从空 → 非空 (或从无 → 有新 URL) 时,
+ * 给参会人 (a.user_id) 发一条"劳务协议待签"通知. 协议被替换 (URL 改了) 不再重发,
+ * 避免重复打扰 — 医生在 /doctor/home 列表里能直接看到 URL.
+ */
@Log(title = "劳务协议", businessType = BusinessType.UPDATE)
@PutMapping("/{id}/laborProtocol")
public AjaxResult updateLaborProtocol(@PathVariable("id") Long id, @RequestBody BizMeetingAttendee body) {
+ // #6 dedup: 先查旧值, 仅在"从空变有"时通知
+ BizMeetingAttendee before = attendeeService.selectById(id);
+ String newUrl = body.getLaborProtocol();
+ boolean wasEmpty = before == null
+ || before.getLaborProtocol() == null
+ || before.getLaborProtocol().isEmpty();
+
BizMeetingAttendee entity = new BizMeetingAttendee();
entity.setId(id);
- entity.setLaborProtocol(body.getLaborProtocol());
+ entity.setLaborProtocol(newUrl);
entity.setUpdateBy(SecurityUtils.getUsername());
- return toAjax(attendeeService.updateLaborProtocol(entity));
- }
+ int rows = attendeeService.updateLaborProtocol(entity);
- /* ====== 管理端点 (给后续 BizMeetingController.add 调用) ====== */
- // TODO: BizMeetingController.add 接受 attendeeUserIds: Long[], 批量 insert 中间表
+ // 仅在原 URL 为空 + 新 URL 非空时推通知, 避免协议替换产生噪音
+ if (rows > 0 && wasEmpty && newUrl != null && !newUrl.isEmpty() && before != null) {
+ BizMeeting m = bizMeetingService.getById(before.getMeetingId());
+ String meetingName = m != null ? m.getMeetingName() : null;
+ bizNotifyService.agreementAwaitingSign(before.getUserId(), id, before.getMeetingId(), meetingName);
+ }
+ return toAjax(rows);
+ }
}
\ No newline at end of file
diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingController.java
index daac11e..73ed48b 100644
--- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingController.java
+++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingController.java
@@ -20,6 +20,7 @@ import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.business.domain.BizMeeting;
+import com.ruoyi.business.notify.BizNotifyService;
import com.ruoyi.business.service.IBizMeetingService;
import com.ruoyi.business.service.IBizMeetingAttendeeService;
@@ -42,6 +43,8 @@ public class BizMeetingController extends BaseController {
private IBizMeetingSupervisorService bizMeetingSupervisorService;
@Autowired
private IBizMeetingExecutorService bizMeetingExecutorService;
+ @Autowired
+ private BizNotifyService bizNotifyService;
@GetMapping("/list")
public TableDataInfo list(BizMeeting bizMeeting) {
@@ -67,6 +70,13 @@ public class BizMeetingController extends BaseController {
Long[] attendeeUserIds = bizMeeting.getAttendeeUserIds();
if (attendeeUserIds != null && attendeeUserIds.length > 0) {
attendeeService.insertBatch(bizMeeting.getMeetingId(), attendeeUserIds);
+ // #5 会议邀请通知 (新增会议, 全部新参会人都要通知)
+ String meetingName = bizMeeting.getMeetingName();
+ Date startTime = bizMeeting.getStartTime();
+ for (Long uid : attendeeUserIds) {
+ if (uid == null) continue;
+ bizNotifyService.meetingInvitation(uid, bizMeeting.getMeetingId(), meetingName, startTime);
+ }
}
return toAjax(rows);
}
@@ -77,7 +87,16 @@ public class BizMeetingController extends BaseController {
int rows = bizMeetingService.updateByPrimaryKey(bizMeeting);
Long[] attendeeUserIds = bizMeeting.getAttendeeUserIds();
if (attendeeUserIds != null && attendeeUserIds.length > 0) {
- attendeeService.insertBatch(bizMeeting.getMeetingId(), attendeeUserIds);
+ Long meetingId = bizMeeting.getMeetingId();
+ // #5 dedup: 先拿已有的 userId 集合, 仅给"新增"的 userId 发通知, 避免重复打扰已参会医生
+ java.util.Set existingUids = new java.util.HashSet<>(attendeeService.selectUserIdsByMeetingId(meetingId));
+ attendeeService.insertBatch(meetingId, attendeeUserIds);
+ String meetingName = bizMeeting.getMeetingName();
+ Date startTime = bizMeeting.getStartTime();
+ for (Long uid : attendeeUserIds) {
+ if (uid == null || existingUids.contains(uid)) continue;
+ bizNotifyService.meetingInvitation(uid, meetingId, meetingName, startTime);
+ }
}
return toAjax(rows);
}
diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectPlan.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectPlan.java
index cd81c8b..03ab51f 100644
--- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectPlan.java
+++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectPlan.java
@@ -39,6 +39,8 @@ public class BizProjectPlan extends BaseEntity {
/** project_no */
@Excel(name = "project_no")
private String projectNo;
+ /** 关联项目名 (LEFT JOIN biz_project.project_name, 通知/列表展示用, 不持久化) */
+ private String projectName;
/** remark */
@Excel(name = "remark")
private String remark;
@@ -91,6 +93,8 @@ public class BizProjectPlan extends BaseEntity {
public void setIsSettled(String isSettled) { this.isSettled = isSettled; }
public String getProjectNo() { return projectNo; }
public void setProjectNo(String projectNo) { this.projectNo = projectNo; }
+ public String getProjectName() { return projectName; }
+ public void setProjectName(String projectName) { this.projectName = projectName; }
public String getRemark() { return remark; }
public void setRemark(String remark) { this.remark = remark; }
public String getIsFinished() { return isFinished; }
@@ -103,6 +107,12 @@ public class BizProjectPlan extends BaseEntity {
public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
public Date getUpdateTime() { return updateTime; }
public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
+ public String getAuditOpinion() { return auditOpinion; }
+ public void setAuditOpinion(String auditOpinion) { this.auditOpinion = auditOpinion; }
+ public String getAuditBy() { return auditBy; }
+ public void setAuditBy(String auditBy) { this.auditBy = auditBy; }
+ public String getAuditTime() { return auditTime; }
+ public void setAuditTime(String auditTime) { this.auditTime = auditTime; }
public Long getSubmitterId() { return submitterId; }
public void setSubmitterId(Long submitterId) { this.submitterId = submitterId; }
public String getSubmitterName() { return submitterName; }
diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizMeetingAttendeeMapper.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizMeetingAttendeeMapper.java
index 3d1dae3..edd6172 100644
--- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizMeetingAttendeeMapper.java
+++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizMeetingAttendeeMapper.java
@@ -8,6 +8,12 @@ public interface BizMeetingAttendeeMapper {
int insert(BizMeetingAttendee entity);
/** 批量插入参会人 (BizMeetingController.add 调用) */
int insertBatch(@Param("meetingId") Long meetingId, @Param("userIds") Long[] userIds, @Param("createBy") String createBy);
+ /**
+ * 管理端"新增参会人"用: 一次性插入完整档案 (含 name/phone/workUnit 等).
+ * 与 {@link #insert} 区别: insert 只写 meeting_id+user_id+create_by (医生端"刚被加入"零信息行),
+ * 本方法带档案用于 admin 在会议详情页手动加参会人.
+ */
+ int insertWithProfile(BizMeetingAttendee entity);
/** 医生填写信息保存草稿: 更新签字快照字段 + 劳务信息 (不含 handsign/PDF) */
int updateProfile(BizMeetingAttendee entity);
int updateHandsign(BizMeetingAttendee entity);
@@ -15,9 +21,16 @@ public interface BizMeetingAttendeeMapper {
int updateSign(BizMeetingAttendee entity);
int updateLaborProtocol(BizMeetingAttendee entity);
int deleteByMeetingId(Long meetingId);
+ /** 管理端按 attendee.id 单删 (MeetingDetail 参会人 CRUD 用) */
+ int deleteByPrimaryKey(Long id);
int deleteByMeetingIdAndUserId(BizMeetingAttendee entity);
List selectByMeetingId(Long meetingId);
List selectByUserId(Long userId);
BizMeetingAttendee selectById(Long id);
List selectUnsignedByUserId(Long userId);
+ /**
+ * 拿某会议已存在的参会人 userId 列表 (用于 add/edit 时 diff 新加入的人, 仅通知增量)
+ * 性能: 只查 user_id 一列, 走 meeting_id 索引; meeting 参会人通常 < 100, 无压力.
+ */
+ List selectUserIdsByMeetingId(Long meetingId);
}
\ No newline at end of file
diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizMeetingAttendeeService.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizMeetingAttendeeService.java
index fdab009..a902438 100644
--- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizMeetingAttendeeService.java
+++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizMeetingAttendeeService.java
@@ -14,9 +14,35 @@ public interface IBizMeetingAttendeeService {
int updateSign(BizMeetingAttendee entity);
int updateLaborProtocol(BizMeetingAttendee entity);
int deleteByMeetingId(Long meetingId);
+ /** MeetingDetail 参会人 CRUD 用: 按 id 单删 (管理端视角) */
+ int deleteByPrimaryKey(Long id);
int deleteByMeetingIdAndUserId(BizMeetingAttendee entity);
+
+ /**
+ * 管理端新增参会人 (MeetingDetail 参会人 CRUD 用).
+ *
+ * 逻辑:
+ *
+ * - 按 body.phone 查 sys_user (del_flag='0')
+ * - 查不到 → 创建 sys_user (userName=phone, nickName=body.name, password=phone, roleType='doctor', status='0')
+ * - 查到 → 复用其 userId
+ * - 检查 (meetingId, userId) 是否已存在 → 抛"该参会人已在会议中"
+ * - insertWithProfile 写完整档案行
+ * - 返回新 attendee.id (用于 #5 会议邀请触发 — 由 controller 决定是否推, 这里不推, 业务解耦)
+ *
+ *
+ * @param body 必填 meetingId/phone; 可选 name/workUnit/department/title/laborForm/feePreTax/tax/fee
+ * @return 新 attendee.id
+ * @throws ServiceException phone 为空 / 用户已在会议中
+ */
+ Long insertByPhoneWithProfile(BizMeetingAttendee body);
List selectByMeetingId(Long meetingId);
List selectByUserId(Long userId);
BizMeetingAttendee selectById(Long id);
List selectUnsignedByUserId(Long userId);
+ /**
+ * 拿某会议已存在的参会人 userId 列表 (#5 会议邀请 dedup 用).
+ * 列表实现层直接返 mapper 结果; 业务方通常用 {@code new HashSet<>(service.selectUserIdsByMeetingId(mid))} 做 contains 判断.
+ */
+ List selectUserIdsByMeetingId(Long meetingId);
}
\ No newline at end of file
diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingAttendeeServiceImpl.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingAttendeeServiceImpl.java
index d0165cc..3af3b42 100644
--- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingAttendeeServiceImpl.java
+++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingAttendeeServiceImpl.java
@@ -1,18 +1,31 @@
package com.ruoyi.business.service.impl;
+import java.util.Collections;
import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.business.domain.BizMeetingAttendee;
import com.ruoyi.business.mapper.BizMeetingAttendeeMapper;
import com.ruoyi.business.service.IBizMeetingAttendeeService;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.system.mapper.SysUserMapper;
+import com.ruoyi.system.service.ISysUserService;
@Service
public class BizMeetingAttendeeServiceImpl implements IBizMeetingAttendeeService {
+ private static final Logger log = LoggerFactory.getLogger(BizMeetingAttendeeServiceImpl.class);
+
@Autowired
private BizMeetingAttendeeMapper mapper;
+ @Autowired
+ private SysUserMapper sysUserMapper;
+ @Autowired
+ private ISysUserService sysUserService;
@Override
public int insert(BizMeetingAttendee entity) {
@@ -25,6 +38,69 @@ public class BizMeetingAttendeeServiceImpl implements IBizMeetingAttendeeService
return mapper.insertBatch(meetingId, userIds, SecurityUtils.getUsername());
}
+ /**
+ * 管理端"新增参会人"全流程: 按 phone 定位/新建 sys_user + 写入完整档案.
+ *
+ * 参照 {@link com.ruoyi.business.service.impl.BizExpertServiceImpl#insert} 的"phone → userId"
+ * 模式 (用户名=phone, 密码=phone, roleType='doctor'), 但本方法**只做 phone 查重+建账号**,
+ * 不做"是否已注册专家"等业务校验 (参会人 ≠ 专家, 只要有手机号就能加入会议).
+ *
+ *
异常: phone 为空 → 抛; (meetingId, userId) 已存在 → 抛 (用 mapper 的 insertWithProfile
+ * 在 PK 冲突时报 DuplicateEntry 兜底, 这里先 SELECT 给出友好提示).
+ */
+ @Override
+ public Long insertByPhoneWithProfile(BizMeetingAttendee body) {
+ String phone = body.getPhone();
+ if (phone == null || phone.trim().isEmpty()) {
+ throw new ServiceException("手机号不能为空");
+ }
+ phone = phone.trim();
+
+ // 1. 按 phone 查 sys_user (单条 IN 查, selectByPhoneList 接受 List)
+ List hits = sysUserMapper.selectByPhoneList(Collections.singletonList(phone));
+ Long userId;
+ SysUser existed = (hits != null && !hits.isEmpty()) ? hits.get(0) : null;
+ if (existed != null) {
+ userId = existed.getUserId();
+ } else {
+ // 2. 查不到 → 建 sys_user (用户名=phone, 密码=phone, role_type='doctor')
+ SysUser newUser = new SysUser();
+ newUser.setUserName(phone);
+ newUser.setNickName(body.getName() != null && !body.getName().isEmpty() ? body.getName() : phone);
+ newUser.setPhonenumber(phone);
+ newUser.setPassword(SecurityUtils.encryptPassword(phone));
+ newUser.setStatus("0");
+ newUser.setDelFlag("0");
+ newUser.setCreateBy(SecurityUtils.getUsername());
+ sysUserService.insertUser(newUser);
+ userId = newUser.getUserId();
+ if (userId == null) {
+ // 兜底: insertUser 用了 useGeneratedKeys, 正常能拿到; 拿不到时按 phone 再查一次
+ SysUser re = sysUserMapper.selectUserByUserName(phone);
+ if (re == null) throw new ServiceException("建账号失败, 请重试");
+ userId = re.getUserId();
+ }
+ // 显式设 role_type='doctor' (DB 默认 'executor', 参会人应为 doctor 视角)
+ sysUserService.updateRoleType(userId, "doctor");
+ log.info("[attendee] 新建 sys_user (phone={}, userId={}, roleType=doctor)", phone, userId);
+ }
+
+ // 3. 检查 (meetingId, userId) 是否已存在 → 友好提示
+ // 仅 SELECT, 不 DELETE (deleteByMeetingIdAndUserId 是破坏性的, 不能误用做探测)
+ java.util.Set existing = new java.util.HashSet<>(mapper.selectUserIdsByMeetingId(body.getMeetingId()));
+ if (existing.contains(userId)) {
+ throw new ServiceException("该手机号参会人已在会议中, 无需重复添加");
+ }
+
+ // 4. 写完整档案行
+ body.setUserId(userId);
+ body.setCreateBy(SecurityUtils.getUsername());
+ mapper.insertWithProfile(body);
+ Long newId = body.getId();
+ log.info("[attendee] 新增参会人 meetingId={} userId={} attendeeId={}", body.getMeetingId(), userId, newId);
+ return newId;
+ }
+
@Override
public int updateProfile(BizMeetingAttendee entity) {
return mapper.updateProfile(entity);
@@ -50,6 +126,11 @@ public class BizMeetingAttendeeServiceImpl implements IBizMeetingAttendeeService
return mapper.deleteByMeetingId(meetingId);
}
+ @Override
+ public int deleteByPrimaryKey(Long id) {
+ return mapper.deleteByPrimaryKey(id);
+ }
+
@Override
public int deleteByMeetingIdAndUserId(BizMeetingAttendee entity) {
return mapper.deleteByMeetingIdAndUserId(entity);
@@ -74,4 +155,9 @@ public class BizMeetingAttendeeServiceImpl implements IBizMeetingAttendeeService
public List selectUnsignedByUserId(Long userId) {
return mapper.selectUnsignedByUserId(userId);
}
+
+ @Override
+ public List selectUserIdsByMeetingId(Long meetingId) {
+ return mapper.selectUserIdsByMeetingId(meetingId);
+ }
}
\ No newline at end of file
diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizProjectPlanServiceImpl.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizProjectPlanServiceImpl.java
index 827e793..88f2d44 100644
--- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizProjectPlanServiceImpl.java
+++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizProjectPlanServiceImpl.java
@@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.business.domain.BizProjectPlan;
import com.ruoyi.business.mapper.BizProjectPlanMapper;
+import com.ruoyi.business.notify.BizNotifyService;
import com.ruoyi.business.service.IBizProjectPlanService;
@Service
@@ -12,6 +13,8 @@ public class BizProjectPlanServiceImpl implements IBizProjectPlanService
{
@Autowired
private BizProjectPlanMapper bizProjectPlanMapper;
+ @Autowired
+ private BizNotifyService bizNotifyService;
@Override
public BizProjectPlan getById(String planId)
@@ -21,9 +24,34 @@ public class BizProjectPlanServiceImpl implements IBizProjectPlanService
{ return bizProjectPlanMapper.selectList(entity); }
@Override
public int insert(BizProjectPlan entity) { com.ruoyi.common.utils.id.SnowflakeId.injectIfEmpty(entity, "planId"); return bizProjectPlanMapper.insert(entity); }
+
+ /**
+ * 通用 update. #4 触发点 (方案审核结果通知) 在这里插桩:
+ * - 先读旧 status
+ * - 执行 update
+ * - 仅当 status 真的变化 (1→2 通过 / 1→3 拒绝) 才发通知
+ * - 其它字段编辑 (plan_name/file/...) 不发, 避免噪音
+ */
@Override
public int updateByPrimaryKey(BizProjectPlan entity)
- { return bizProjectPlanMapper.updateByPrimaryKey(entity); }
+ {
+ String oldStatus = null;
+ if (entity.getPlanId() != null) {
+ BizProjectPlan existed = bizProjectPlanMapper.selectByPrimaryKey(entity.getPlanId());
+ if (existed != null) {
+ oldStatus = existed.getStatus();
+ }
+ }
+ int n = bizProjectPlanMapper.updateByPrimaryKey(entity);
+ if (n > 0 && entity.getStatus() != null && !entity.getStatus().equals(oldStatus)) {
+ // 重新读一次拿 submitterId/planName/auditOpinion (entity 可能只传了 planId+status+opinion)
+ BizProjectPlan after = bizProjectPlanMapper.selectByPrimaryKey(entity.getPlanId());
+ if (after != null) {
+ bizNotifyService.planAuditResult(after, oldStatus);
+ }
+ }
+ return n;
+ }
@Override
public int deleteByPrimaryKey(String planId)
{ return bizProjectPlanMapper.deleteByPrimaryKey(planId); }
diff --git a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizMeetingAttendeeMapper.xml b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizMeetingAttendeeMapper.xml
index ed5a410..7619993 100644
--- a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizMeetingAttendeeMapper.xml
+++ b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizMeetingAttendeeMapper.xml
@@ -40,6 +40,20 @@
insert into biz_meeting_attendee(meeting_id, user_id, create_by, create_time)
values(#{meetingId}, #{userId}, #{createBy}, sysdate())
+
+
+ insert into biz_meeting_attendee(meeting_id, user_id, name, phone, work_unit, department, title,
+ id_card, bank_card, bank_name, bank_branch, bank_region, bank_address, account_name,
+ id_card_attachments, labor_form, fee_pre_tax, tax, fee, create_by, create_time)
+ values(#{meetingId}, #{userId}, #{name}, #{phone}, #{workUnit}, #{department}, #{title},
+ #{idCard}, #{bankCard}, #{bankName}, #{bankBranch}, #{bankRegion}, #{bankAddress}, #{accountName},
+ #{idCardAttachments}, #{laborForm}, #{feePreTax}, #{tax}, #{fee}, #{createBy}, sysdate())
+
insert into biz_meeting_attendee(meeting_id, user_id, create_by, create_time)
@@ -103,6 +117,10 @@
delete from biz_meeting_attendee where meeting_id = #{meetingId}
+
+
+ delete from biz_meeting_attendee where id = #{id}
+
delete from biz_meeting_attendee where meeting_id = #{meetingId} and user_id = #{userId}
@@ -133,4 +151,11 @@
and (a.handsign is null or a.handsign = '' or a.labor_protocol is null or a.labor_protocol = '')
order by m.start_time asc
+
+
\ No newline at end of file
diff --git a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectPlanMapper.xml b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectPlanMapper.xml
index 32fbabb..76c3190 100644
--- a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectPlanMapper.xml
+++ b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectPlanMapper.xml
@@ -13,7 +13,11 @@
+
+
+
+
@@ -25,13 +29,15 @@
select p.plan_id, p.plan_name, p.plan_direction, p.plan_direction_id,
s.title as plan_direction_title,
p.plan_category, p.project_form, p.design_file_url, p.status, p.is_settled,
- p.project_no, p.remark, p.submitter_id,
+ p.project_no, p.remark, p.audit_opinion, p.audit_by, p.audit_time, p.submitter_id,
COALESCE(bp.name, u.user_name) as submitter_name,
+ proj.project_name as project_name,
p.create_by, p.create_time, p.update_by, p.update_time
from biz_project_plan p
left join biz_special_plan s on s.id = p.plan_direction_id
left join sys_user u on u.user_id = p.submitter_id
left join biz_person bp on bp.user_id = u.user_id
+ left join biz_project proj on proj.project_no = p.project_no