From 1c029399c73ca6a9cdf7b9e8a500fc07a759aa7e Mon Sep 17 00:00:00 2001 From: yangk Date: Mon, 6 Jul 2026 16:17:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(asset):=20=E6=B7=BB=E5=8A=A0=E5=80=9F?= =?UTF-8?q?=E7=94=A8=E5=88=B0=E6=9C=9F=E5=BD=92=E8=BF=98=E6=8F=90=E9=86=92?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增AmsBorrowOrderMapper查询到期借用单据接口 - 实现BorrowReturnReminderService借用到期提醒服务 - 添加SysNoticeReceiver通知接收人相关实体和接口 - 集成系统通知公告模块支持定向消息推送 - 完善通知查看权限控制和防重复提醒机制 - 编写单元测试验证提醒逻辑和数据完整性 --- .../system/SysNoticeController.java | 20 ++- .../asset/mapper/AmsBorrowOrderMapper.java | 10 ++ .../service/IBorrowReturnReminderService.java | 16 +++ .../impl/BorrowReturnReminderServiceImpl.java | 134 ++++++++++++++++++ .../mapper/asset/AmsBorrowOrderMapper.xml | 17 +++ .../BorrowReturnReminderServiceImplTest.java | 126 ++++++++++++++++ .../SysNoticeReceiverServiceImplTest.java | 75 ++++++++++ .../system/domain/SysNoticeReceiver.java | 109 ++++++++++++++ .../mapper/SysNoticeReceiverMapper.java | 56 ++++++++ .../service/ISysNoticeReceiverService.java | 45 ++++++ .../impl/SysNoticeReadServiceImpl.java | 18 ++- .../impl/SysNoticeReceiverServiceImpl.java | 66 +++++++++ .../mapper/system/SysNoticeMapper.xml | 4 +- .../mapper/system/SysNoticeReadMapper.xml | 11 +- .../mapper/system/SysNoticeReceiverMapper.xml | 49 +++++++ 15 files changed, 751 insertions(+), 5 deletions(-) create mode 100644 ruoyi-asset/src/main/java/com/ruoyi/asset/service/IBorrowReturnReminderService.java create mode 100644 ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/BorrowReturnReminderServiceImpl.java create mode 100644 ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/BorrowReturnReminderServiceImplTest.java create mode 100644 ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/SysNoticeReceiverServiceImplTest.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNoticeReceiver.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeReceiverMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeReceiverService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeReceiverServiceImpl.java create mode 100644 ruoyi-system/src/main/resources/mapper/system/SysNoticeReceiverMapper.xml diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java index 9afb6bc..4dc11a7 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java @@ -14,11 +14,13 @@ import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.system.domain.SysNotice; +import com.ruoyi.system.service.ISysNoticeReceiverService; import com.ruoyi.system.service.ISysNoticeReadService; import com.ruoyi.system.service.ISysNoticeService; @@ -39,6 +41,9 @@ public class SysNoticeController extends BaseController @Autowired private ISysNoticeReadService noticeReadService; + @Autowired + private ISysNoticeReceiverService noticeReceiverService; + @RequiresPermissions("system:notice:view") @GetMapping() public String notice() @@ -112,7 +117,14 @@ public class SysNoticeController extends BaseController @GetMapping("/view/{noticeId}") public String view(@PathVariable("noticeId") Long noticeId, ModelMap mmap) { - mmap.put("notice", noticeService.selectNoticeById(noticeId)); + SysNotice notice = noticeService.selectNoticeById(noticeId); + SysUser currentUser = ShiroUtils.getSysUser(); + Long userId = currentUser == null ? null : currentUser.getUserId(); + if (notice == null || (!noticeReceiverService.canViewNotice(noticeId, userId) && !canManageNotice(userId))) + { + return "error/unauth"; + } + mmap.put("notice", notice); return prefix + "/view"; } @@ -189,7 +201,13 @@ public class SysNoticeController extends BaseController @ResponseBody public AjaxResult remove(String ids) { + noticeReceiverService.deleteByNoticeIds(ids); noticeReadService.deleteByNoticeIds(ids); return toAjax(noticeService.deleteNoticeByIds(ids)); } + + private boolean canManageNotice(Long userId) + { + return ShiroUtils.isAdmin(userId) || ShiroUtils.getSubject().isPermitted("system:notice:list"); + } } diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/mapper/AmsBorrowOrderMapper.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/mapper/AmsBorrowOrderMapper.java index ce5bf13..bb6a841 100644 --- a/ruoyi-asset/src/main/java/com/ruoyi/asset/mapper/AmsBorrowOrderMapper.java +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/mapper/AmsBorrowOrderMapper.java @@ -39,6 +39,16 @@ public interface AmsBorrowOrderMapper */ public List selectAmsBorrowOrderList(AmsBorrowOrder amsBorrowOrder); + /** + * 查询借用到期且仍有未归还明细的提醒候选单据 + * + * @param orderStatus 主单状态 + * @param detailStatus 明细归还状态 + * @return 借用单集合 + */ + public List selectOverdueBorrowReturnReminderCandidates(@Param("orderStatus") String orderStatus, + @Param("detailStatus") String detailStatus); + /** * 新增借用归还管理 * diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IBorrowReturnReminderService.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IBorrowReturnReminderService.java new file mode 100644 index 0000000..1957dfd --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IBorrowReturnReminderService.java @@ -0,0 +1,16 @@ +package com.ruoyi.asset.service; + +/** + * 借用到期归还提醒 服务层 + * + * @author Yangk + */ +public interface IBorrowReturnReminderService +{ + /** + * 扫描已到预计归还日期但仍未归还的借用单,并只给借用人生成定向提醒。 + * + * @return 新生成提醒数量 + */ + public int remindOverdueBorrowOrders(); +} diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/BorrowReturnReminderServiceImpl.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/BorrowReturnReminderServiceImpl.java new file mode 100644 index 0000000..c501668 --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/BorrowReturnReminderServiceImpl.java @@ -0,0 +1,134 @@ +package com.ruoyi.asset.service.impl; + +import java.text.SimpleDateFormat; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.util.HtmlUtils; +import com.ruoyi.asset.constant.BorrowOrderStatus; +import com.ruoyi.asset.domain.AmsBorrowOrder; +import com.ruoyi.asset.mapper.AmsBorrowOrderMapper; +import com.ruoyi.asset.service.IBorrowReturnReminderService; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.system.domain.SysNotice; +import com.ruoyi.system.domain.SysNoticeReceiver; +import com.ruoyi.system.service.ISysNoticeReceiverService; +import com.ruoyi.system.service.ISysNoticeService; + +/** + * 借用到期归还提醒 服务层实现 + * + * @author Yangk + */ +@Service("borrowReturnReminderService") +public class BorrowReturnReminderServiceImpl implements IBorrowReturnReminderService +{ + private static final String SOURCE_TYPE_BORROW_OVERDUE = "BORROW_OVERDUE"; + + private static final int NOTICE_TITLE_MAX_LENGTH = 50; + + private static final String NOTICE_TYPE_NOTICE = "1"; + + private static final String NOTICE_STATUS_NORMAL = "0"; + + private static final String SYSTEM_USER = "system"; + + @Autowired + private AmsBorrowOrderMapper amsBorrowOrderMapper; + + @Autowired + private ISysNoticeService noticeService; + + @Autowired + private ISysNoticeReceiverService noticeReceiverService; + + @Override + @Transactional(rollbackFor = Exception.class) + public int remindOverdueBorrowOrders() + { + List candidates = amsBorrowOrderMapper.selectOverdueBorrowReturnReminderCandidates( + BorrowOrderStatus.BORROWING, BorrowOrderStatus.BORROWING); + int createdCount = 0; + for (AmsBorrowOrder order : candidates) + { + if (order.getOrderId() == null || order.getBorrowUserId() == null) + { + continue; + } + if (noticeReceiverService.isSourceNotified(SOURCE_TYPE_BORROW_OVERDUE, order.getOrderId(), + order.getBorrowUserId())) + { + continue; + } + createdCount += createReminder(order); + } + return createdCount; + } + + private int createReminder(AmsBorrowOrder order) + { + SysNotice notice = new SysNotice(); + notice.setNoticeTitle(truncate("借用资产到期归还提醒:" + defaultString(order.getBorrowNo()), + NOTICE_TITLE_MAX_LENGTH)); + notice.setNoticeType(NOTICE_TYPE_NOTICE); + notice.setStatus(NOTICE_STATUS_NORMAL); + notice.setCreateBy(SYSTEM_USER); + notice.setNoticeContent(buildNoticeContent(order)); + if (noticeService.insertNotice(notice) != 1 || notice.getNoticeId() == null) + { + throw new ServiceException("借用到期提醒生成失败,通知公告写入异常"); + } + + SysNoticeReceiver receiver = new SysNoticeReceiver(); + receiver.setNoticeId(notice.getNoticeId()); + receiver.setUserId(order.getBorrowUserId()); + receiver.setSourceType(SOURCE_TYPE_BORROW_OVERDUE); + receiver.setSourceId(order.getOrderId()); + // 业务来源唯一键兜底防重,避免每天定时任务重复提醒同一借用单。 + if (noticeReceiverService.insertNoticeReceiver(receiver) != 1) + { + throw new ServiceException("借用到期提醒生成失败,接收人写入异常"); + } + return 1; + } + + private String buildNoticeContent(AmsBorrowOrder order) + { + String borrowNo = escape(order.getBorrowNo()); + String borrowUserName = escape(order.getBorrowUserName()); + String expectedReturnDate = formatDate(order); + return "

借用单号:" + borrowNo + "

" + + "

借用人:" + borrowUserName + "

" + + "

预计归还日期:" + expectedReturnDate + "

" + + "

该借用单已到预计归还日期,请及时发起归还申请或联系管理员处理。

"; + } + + private String formatDate(AmsBorrowOrder order) + { + if (order.getExpectedReturnDate() == null) + { + return ""; + } + return new SimpleDateFormat("yyyy-MM-dd").format(order.getExpectedReturnDate()); + } + + private String escape(String value) + { + return HtmlUtils.htmlEscape(defaultString(value)); + } + + private String defaultString(String value) + { + return value == null ? "" : value; + } + + private String truncate(String value, int maxLength) + { + if (value == null || value.length() <= maxLength) + { + return value; + } + return value.substring(0, maxLength); + } +} diff --git a/ruoyi-asset/src/main/resources/mapper/asset/AmsBorrowOrderMapper.xml b/ruoyi-asset/src/main/resources/mapper/asset/AmsBorrowOrderMapper.xml index 44d6afd..630339c 100644 --- a/ruoyi-asset/src/main/resources/mapper/asset/AmsBorrowOrderMapper.xml +++ b/ruoyi-asset/src/main/resources/mapper/asset/AmsBorrowOrderMapper.xml @@ -118,6 +118,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where order_id = #{orderId} and del_flag = '0' + + - + insert into sys_notice ( notice_title, notice_type, @@ -83,4 +83,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - \ No newline at end of file + diff --git a/ruoyi-system/src/main/resources/mapper/system/SysNoticeReadMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysNoticeReadMapper.xml index c0b3d22..ed13ea4 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysNoticeReadMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysNoticeReadMapper.xml @@ -20,7 +20,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" @@ -42,6 +47,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" left join sys_notice_read r on r.notice_id = n.notice_id and r.user_id = #{userId} where n.status = '0' + and ( + not exists (select 1 from sys_notice_receiver nr_all where nr_all.notice_id = n.notice_id) + or exists (select 1 from sys_notice_receiver nr_user where nr_user.notice_id = n.notice_id and nr_user.user_id = #{userId}) + ) order by n.notice_id desc limit #{limit} diff --git a/ruoyi-system/src/main/resources/mapper/system/SysNoticeReceiverMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysNoticeReceiverMapper.xml new file mode 100644 index 0000000..03327d5 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysNoticeReceiverMapper.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + insert into sys_notice_receiver (notice_id, user_id, source_type, source_id, create_time) + values (#{noticeId}, #{userId}, #{sourceType}, #{sourceId}, #{createTime}) + + + + + + + + + + delete from sys_notice_receiver where notice_id in + + #{noticeId} + + + +