v1.0.31 #43
@@ -3,6 +3,7 @@ package com.imyeyu.api.modules.common.mapper;
|
||||
import com.imyeyu.api.modules.common.entity.Notify;
|
||||
import com.imyeyu.api.modules.common.vo.NotifyManagementItem;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
|
||||
import java.util.List;
|
||||
@@ -15,6 +16,10 @@ import java.util.List;
|
||||
*/
|
||||
public interface NotifyMapper extends BaseMapper<Notify, String> {
|
||||
|
||||
/// 查询业务提醒是否已创建
|
||||
@Select("SELECT EXISTS (SELECT 1 FROM `notify` WHERE `biz_type` = #{bizType} AND `biz_reminded_type` = #{bizRemindedType} AND `biz_reminded_id` = #{bizRemindedId} AND `deleted_at` IS NULL)")
|
||||
boolean existsByBizReminded(@Param("bizType") String bizType, @Param("bizRemindedType") String bizRemindedType, @Param("bizRemindedId") String bizRemindedId);
|
||||
|
||||
/// 分页查询管理端通知
|
||||
///
|
||||
/// @param userId 管理员用户 ID
|
||||
|
||||
@@ -14,6 +14,14 @@ import com.imyeyu.spring.service.BaseService;
|
||||
*/
|
||||
public interface NotifyService extends BaseService<Notify, String> {
|
||||
|
||||
/// 查询业务提醒是否已创建
|
||||
///
|
||||
/// @param bizType 业务类型
|
||||
/// @param bizRemindedType 被提醒业务类型
|
||||
/// @param bizRemindedId 被提醒业务 ID
|
||||
/// @return true 为已创建
|
||||
boolean existsByBizReminded(String bizType, String bizRemindedType, String bizRemindedId);
|
||||
|
||||
/// 分页查询当前用户创建的通知
|
||||
///
|
||||
/// @param page 分页参数
|
||||
|
||||
+5
@@ -46,6 +46,11 @@ public class NotifyServiceImplement extends AbstractEntityService<Notify, String
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByBizReminded(String bizType, String bizRemindedType, String bizRemindedId) {
|
||||
return mapper.existsByBizReminded(bizType, bizRemindedType, bizRemindedId);
|
||||
}
|
||||
|
||||
/// 创建通知并级联创建通知详情
|
||||
///
|
||||
/// @param notify 通知
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.imyeyu.api.modules.gao.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.gao.vo.GaoOrderReminderCustomer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// GAO 订单提醒 Mapper
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
public interface GaoOrderReminderMapper {
|
||||
|
||||
/// 查询需要收款的订单客户
|
||||
///
|
||||
/// @return 订单客户列表
|
||||
List<GaoOrderReminderCustomer> selectOrderReceiveCustomers();
|
||||
|
||||
/// 查询待核销收款客户
|
||||
///
|
||||
/// @return 收款客户列表
|
||||
List<GaoOrderReminderCustomer> selectReceiveVerifyCustomers();
|
||||
|
||||
/// 查询待核销履约客户
|
||||
///
|
||||
/// @return 履约客户列表
|
||||
List<GaoOrderReminderCustomer> selectFulfillmentVerifyCustomers();
|
||||
}
|
||||
@@ -15,6 +15,12 @@ import java.util.List;
|
||||
/// @since 2026-07-30
|
||||
public interface GaoUserMapper extends BaseMapper<GaoUser, String> {
|
||||
|
||||
/// 查询门店启用中的店长
|
||||
///
|
||||
/// @param storeId 门店 ID
|
||||
/// @return 店长列表
|
||||
List<GaoUser> selectEnabledStoreManagers(@Param("storeId") String storeId);
|
||||
|
||||
List<GaoUser> selectByUserIdList(Collection<String> userIdList);
|
||||
|
||||
List<GaoUser> selectRecipientPage(@Param("page") Page<GaoUser> page);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.imyeyu.api.modules.gao.service;
|
||||
|
||||
/// GAO 订单提醒服务
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
public interface GaoOrderReminderService {
|
||||
|
||||
/// 发送订单收款提醒
|
||||
void sendOrderReceiveReminder();
|
||||
|
||||
/// 发送核销提醒
|
||||
void sendVerifyReminder();
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package com.imyeyu.api.modules.gao.service.implement;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imyeyu.api.modules.common.entity.Notify;
|
||||
import com.imyeyu.api.modules.common.entity.NotifyDetail;
|
||||
import com.imyeyu.api.modules.common.service.NotifyService;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoUser;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoOrderReminderMapper;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoUserMapper;
|
||||
import com.imyeyu.api.modules.gao.service.GaoOrderReminderService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoOrderReminderCustomer;
|
||||
import com.imyeyu.api.modules.user.entity.User;
|
||||
import com.imyeyu.api.modules.user.service.UserService;
|
||||
import com.imyeyu.utils.Time;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/// GAO 订单提醒服务实现
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GaoOrderReminderServiceImplement implements GaoOrderReminderService {
|
||||
|
||||
private static final String NOTIFY_BIZ_TYPE = "GAO_ORDER_REMINDER";
|
||||
private static final String SMS_TEMPLATE_ID = "2725097";
|
||||
private static final String ZONE_ID = "Asia/Shanghai";
|
||||
|
||||
private final GaoOrderReminderMapper mapper;
|
||||
private final GaoUserMapper gaoUserMapper;
|
||||
private final UserService userService;
|
||||
private final NotifyService notifyService;
|
||||
private final ObjectMapper jackson;
|
||||
|
||||
@Override
|
||||
public void sendOrderReceiveReminder() {
|
||||
send(mapper.selectOrderReceiveCustomers(), ReminderType.ORDER_RECEIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendVerifyReminder() {
|
||||
Map<String, Map<String, GaoOrderReminderCustomer>> storeCustomerMap = new LinkedHashMap<>();
|
||||
Map<String, Set<String>> storeActionMap = new LinkedHashMap<>();
|
||||
mergeCustomers(storeCustomerMap, storeActionMap, mapper.selectReceiveVerifyCustomers(), "核销收款");
|
||||
mergeCustomers(storeCustomerMap, storeActionMap, mapper.selectFulfillmentVerifyCustomers(), "核销履约");
|
||||
for (Map.Entry<String, Map<String, GaoOrderReminderCustomer>> entry : storeCustomerMap.entrySet()) {
|
||||
String action = String.join("、", storeActionMap.get(entry.getKey()));
|
||||
sendStoreReminder(entry.getKey(), new ArrayList<>(entry.getValue().values()), ReminderType.VERIFY.name(), action);
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeCustomers(Map<String, Map<String, GaoOrderReminderCustomer>> storeCustomerMap,
|
||||
Map<String, Set<String>> storeActionMap, List<GaoOrderReminderCustomer> customerList, String action) {
|
||||
if (customerList == null || customerList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (GaoOrderReminderCustomer customer : customerList) {
|
||||
storeCustomerMap.computeIfAbsent(customer.getStoreId(), key -> new LinkedHashMap<>())
|
||||
.putIfAbsent(customer.getCustomerId(), customer);
|
||||
storeActionMap.computeIfAbsent(customer.getStoreId(), key -> new LinkedHashSet<>()).add(action);
|
||||
}
|
||||
}
|
||||
|
||||
private void send(List<GaoOrderReminderCustomer> customerList, ReminderType reminderType) {
|
||||
if (customerList == null || customerList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<String, List<GaoOrderReminderCustomer>> storeCustomerMap = customerList.stream()
|
||||
.collect(Collectors.groupingBy(GaoOrderReminderCustomer::getStoreId, LinkedHashMap::new, Collectors.toList()));
|
||||
for (Map.Entry<String, List<GaoOrderReminderCustomer>> entry : storeCustomerMap.entrySet()) {
|
||||
sendStoreReminder(entry.getKey(), entry.getValue(), reminderType.name(), reminderType.label);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendStoreReminder(String storeId, List<GaoOrderReminderCustomer> customerList, String reminderType,
|
||||
String action) {
|
||||
String customerNames = formatCustomerNames(customerList);
|
||||
String content = "您的客户 %s 的订单需要 %s,请留意订单状态。".formatted(customerNames, action);
|
||||
List<GaoUser> managerList = gaoUserMapper.selectEnabledStoreManagers(storeId);
|
||||
if (managerList == null || managerList.isEmpty()) {
|
||||
log.warn("GAO 订单提醒未找到启用中的店长: storeId[{}], reminderType[{}]", storeId, reminderType);
|
||||
return;
|
||||
}
|
||||
List<String> userIdList = managerList.stream().map(GaoUser::getUserId).distinct().toList();
|
||||
Map<String, User> userMap = userService.mapByIdList(userIdList);
|
||||
for (GaoUser manager : managerList) {
|
||||
try {
|
||||
User user = userMap.get(manager.getUserId());
|
||||
if (user == null) {
|
||||
log.warn("GAO 订单提醒跳过不存在的店长用户: storeId[{}], userId[{}]", storeId, manager.getUserId());
|
||||
continue;
|
||||
}
|
||||
String remindedId = "%s:%s:%s:%s".formatted(LocalDate.now(ZoneId.of(ZONE_ID)), storeId, manager.getUserId(), reminderType);
|
||||
if (notifyService.existsByBizReminded(NOTIFY_BIZ_TYPE, reminderType, remindedId)) {
|
||||
continue;
|
||||
}
|
||||
long sendAt = Time.now();
|
||||
Notify notify = new Notify();
|
||||
notify.setBizType(NOTIFY_BIZ_TYPE);
|
||||
notify.setBizRemindedType(reminderType);
|
||||
notify.setBizRemindedId(remindedId);
|
||||
notify.setDetailList(List.of(
|
||||
createInternalDetail(manager.getUserId(), content, sendAt),
|
||||
createSmsDetail(user.getPhoneNo(), customerNames, action, content, sendAt)
|
||||
));
|
||||
notifyService.create(notify);
|
||||
} catch (Exception e) {
|
||||
log.error("GAO 订单提醒发送失败: storeId[{}], userId[{}], reminderType[{}]", storeId, manager.getUserId(), reminderType, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NotifyDetail createInternalDetail(String userId, String content, long sendAt) {
|
||||
NotifyDetail detail = new NotifyDetail();
|
||||
detail.setMsgType(NotifyDetail.MsgType.INTERNAL);
|
||||
detail.setTemplateId(SMS_TEMPLATE_ID);
|
||||
detail.setSubject("订单状态提醒");
|
||||
detail.setContent(content);
|
||||
detail.setContentType(NotifyDetail.ContentType.TEXT);
|
||||
detail.setSendTo(userId);
|
||||
detail.setSendAt(sendAt);
|
||||
return detail;
|
||||
}
|
||||
|
||||
private NotifyDetail createSmsDetail(String phoneNo, String customerNames, String action, String content, long sendAt) {
|
||||
NotifyDetail detail = new NotifyDetail();
|
||||
detail.setMsgType(NotifyDetail.MsgType.SMS);
|
||||
detail.setTemplateId(SMS_TEMPLATE_ID);
|
||||
detail.setContent(content);
|
||||
detail.setArgs(jackson.valueToTree(List.of(customerNames, action)));
|
||||
detail.setSendTo(phoneNo);
|
||||
detail.setSendAt(sendAt);
|
||||
return detail;
|
||||
}
|
||||
|
||||
private String formatCustomerNames(List<GaoOrderReminderCustomer> customerList) {
|
||||
List<String> nameList = customerList.stream()
|
||||
.map(GaoOrderReminderCustomer::getCustomerName)
|
||||
.map(name -> name == null || name.isBlank() ? "未知客户" : name.trim())
|
||||
.toList();
|
||||
if (customerList.size() <= 2) {
|
||||
return String.join("、", nameList);
|
||||
}
|
||||
return "%s、%s等 %d 位".formatted(nameList.getFirst(), nameList.get(1), customerList.size());
|
||||
}
|
||||
|
||||
private enum ReminderType {
|
||||
|
||||
/// 订单收款
|
||||
ORDER_RECEIVE("收款"),
|
||||
|
||||
/// 统一核销
|
||||
VERIFY("核销收款、核销履约");
|
||||
|
||||
private final String label;
|
||||
|
||||
ReminderType(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.imyeyu.api.modules.gao.task;
|
||||
|
||||
import com.imyeyu.api.modules.gao.service.GaoOrderReminderService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// GAO 订单提醒定时任务
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GaoOrderReminderTask {
|
||||
|
||||
private static final String ZONE_ID = "Asia/Shanghai";
|
||||
|
||||
private final GaoOrderReminderService service;
|
||||
|
||||
/// 每天早上八点发送订单收款提醒
|
||||
@Scheduled(cron = "0 0 8 * * ?", zone = ZONE_ID)
|
||||
public void sendOrderReceiveReminder() {
|
||||
execute("订单收款", service::sendOrderReceiveReminder);
|
||||
}
|
||||
|
||||
/// 每天晚上九点发送核销提醒
|
||||
@Scheduled(cron = "0 0 21 * * ?", zone = ZONE_ID)
|
||||
public void sendVerifyReminder() {
|
||||
execute("核销", service::sendVerifyReminder);
|
||||
}
|
||||
|
||||
private void execute(String reminderName, Runnable action) {
|
||||
try {
|
||||
action.run();
|
||||
} catch (Exception e) {
|
||||
log.error("GAO {}提醒任务执行失败", reminderName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/// GAO 订单提醒客户
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
@Data
|
||||
public class GaoOrderReminderCustomer {
|
||||
|
||||
/// 门店 ID
|
||||
private String storeId;
|
||||
|
||||
/// 客户 ID
|
||||
private String customerId;
|
||||
|
||||
/// 客户名称
|
||||
private String customerName;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="com.imyeyu.api.modules.gao.mapper.GaoOrderReminderMapper">
|
||||
<select id="selectOrderReceiveCustomers" resultType="com.imyeyu.api.modules.gao.vo.GaoOrderReminderCustomer">
|
||||
SELECT
|
||||
o.`store_id` AS `storeId`,
|
||||
o.`customer_id` AS `customerId`,
|
||||
c.`name` AS `customerName`
|
||||
FROM `gao_sales_order` o
|
||||
INNER JOIN `gao_customer` c ON c.`id` = o.`customer_id` AND c.`deleted_at` IS NULL
|
||||
WHERE o.`deleted_at` IS NULL
|
||||
AND o.`status` IN ('ACTIVE', 'FULFILLED')
|
||||
AND o.`payment_status` IN ('UNPAID', 'PARTIALLY_PAID')
|
||||
GROUP BY o.`store_id`, o.`customer_id`, c.`name`
|
||||
ORDER BY o.`store_id`, c.`name`, o.`customer_id`
|
||||
</select>
|
||||
|
||||
<select id="selectReceiveVerifyCustomers" resultType="com.imyeyu.api.modules.gao.vo.GaoOrderReminderCustomer">
|
||||
SELECT
|
||||
f.`store_id` AS `storeId`,
|
||||
f.`customer_id` AS `customerId`,
|
||||
c.`name` AS `customerName`
|
||||
FROM `gao_fund_record` f
|
||||
INNER JOIN `gao_customer` c ON c.`id` = f.`customer_id` AND c.`deleted_at` IS NULL
|
||||
WHERE f.`deleted_at` IS NULL
|
||||
AND f.`biz_type` = 'SALES_ORDER'
|
||||
AND f.`operation_type` = 'RECEIVE'
|
||||
AND f.`status` = 'PENDING'
|
||||
GROUP BY f.`store_id`, f.`customer_id`, c.`name`
|
||||
ORDER BY f.`store_id`, c.`name`, f.`customer_id`
|
||||
</select>
|
||||
|
||||
<select id="selectFulfillmentVerifyCustomers" resultType="com.imyeyu.api.modules.gao.vo.GaoOrderReminderCustomer">
|
||||
SELECT
|
||||
f.`store_id` AS `storeId`,
|
||||
f.`customer_id` AS `customerId`,
|
||||
c.`name` AS `customerName`
|
||||
FROM `gao_order_fulfillment` f
|
||||
INNER JOIN `gao_customer` c ON c.`id` = f.`customer_id` AND c.`deleted_at` IS NULL
|
||||
WHERE f.`status` = 'PENDING'
|
||||
GROUP BY f.`store_id`, f.`customer_id`, c.`name`
|
||||
ORDER BY f.`store_id`, c.`name`, f.`customer_id`
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -1,6 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="com.imyeyu.api.modules.gao.mapper.GaoUserMapper">
|
||||
<select id="selectEnabledStoreManagers" resultType="com.imyeyu.api.modules.gao.entity.GaoUser">
|
||||
SELECT DISTINCT gu.*
|
||||
FROM `gao_user` gu
|
||||
INNER JOIN `user_role` ur ON ur.`user_id` = gu.`user_id` AND ur.`deleted_at` IS NULL
|
||||
INNER JOIN `role` r ON r.`id` = ur.`role_id` AND r.`deleted_at` IS NULL
|
||||
WHERE gu.`store_id` = #{storeId}
|
||||
AND gu.`enabled` = 1
|
||||
AND gu.`deleted_at` IS NULL
|
||||
AND r.`module_code` = 'GAO'
|
||||
AND r.`code` = 'STORE_MANAGER'
|
||||
</select>
|
||||
|
||||
<select id="selectByUserIdList" resultType="com.imyeyu.api.modules.gao.entity.GaoUser">
|
||||
SELECT
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user