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 用). + * + *

逻辑: + *

    + *
  1. 按 body.phone 查 sys_user (del_flag='0')
  2. + *
  3. 查不到 → 创建 sys_user (userName=phone, nickName=body.name, password=phone, roleType='doctor', status='0')
  4. + *
  5. 查到 → 复用其 userId
  6. + *
  7. 检查 (meetingId, userId) 是否已存在 → 抛"该参会人已在会议中"
  8. + *
  9. insertWithProfile 写完整档案行
  10. + *
  11. 返回新 attendee.id (用于 #5 会议邀请触发 — 由 controller 决定是否推, 这里不推, 业务解耦)
  12. + *
+ * + * @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