Merge #37 into master from cc_20251208_visit

feat:拜访字段调整

* cc_20251208_visit: (19 commits squashed)

  - feat:拜访记录

  - fix:拜访详情接口新增字段

  - fix:新增状态转义字段

  - fix:惩处单查询提供惩处待处理、惩处已处理状态筛选

  - fix

  - fix:管理员能查看所有加盟商

  - fix:去掉部分字段非空校验

  - fix:空校验

  - Merge branch 'master' into cc_20251208_visit
    
    # Conflicts:
    #	coolstore-partner-common/src/main/java/com/cool/store/enums/ErrorCodeEnum.java
    #	coolstore-partner-dao/src/main/java/com/cool/store/dao/store/StoreMasterSignerInfoDAO.java
    #	coolstore-partner-service/src/main/java/com/cool/store/service/impl/UserAuthMappingServiceImpl.java

  - fix

  - fix:拜访记录失效延时消息

  - fix:拜访记录统计

  - fix:拜访记录统计新增筛选条件

  - fix:字段转义

  - feat:拜访调整

  - feat:拜访调整_过滤我的

  - feat:拜访调整_过滤我的

  - feat:拜访字段调整

  - feat:拜访字段调整

Signed-off-by: 正新 <accounts_6964c7bcd2a2c377c5bbd01b@mail.teambition.com>
Merged-by: 正新 <accounts_6964c7bcd2a2c377c5bbd01b@mail.teambition.com>

CR-link: https://codeup.aliyun.com/692ea314dec569489f6f167c/hangzhou/java/custom_zxjp/change/37
This commit is contained in:
正新
2026-02-02 09:56:33 +00:00
parent 88e7d99f85
commit 83fcb84f9d
37 changed files with 1821 additions and 1 deletions

View File

@@ -10,6 +10,7 @@ import com.cool.store.enums.RocketMqGroupEnum;
import com.cool.store.mq.RocketMqConfig;
import com.cool.store.mq.consumer.listener.ShopDecorationAssignListener;
import com.cool.store.mq.consumer.listener.StoreUserUpdateListener;
import com.cool.store.mq.consumer.listener.VisitRecordListener;
import com.cool.store.mq.consumer.listener.XfsgTrainingPersonSyncListener;
import com.google.common.collect.Maps;
import org.springframework.context.annotation.Bean;
@@ -38,6 +39,8 @@ public class ConsumerClient {
private StoreUserUpdateListener storeUserUpdateListener;
@Resource
private ShopDecorationAssignListener shopDecorationAssignListener;
@Resource
private VisitRecordListener visitRecordListener;
/**
* 获取通用配置
@@ -117,4 +120,17 @@ public class ConsumerClient {
return consumerBean;
}
@Bean(initMethod = "start", destroyMethod = "shutdown")
public ConsumerBean visitRecordInvalid() {
RocketMqGroupEnum groupEnum = RocketMqGroupEnum.VISIT_RECORD_INVALID;
ConsumerBean consumerBean = new ConsumerBean();
//配置文件
Properties properties = getCommonProperties(groupEnum);
consumerBean.setProperties(properties);
Map<Subscription, MessageListener> commonSubscriptionTable = getCommonSubscriptionTable(groupEnum, visitRecordListener);
//订阅多个topic如上面设置
consumerBean.setSubscriptionTable(commonSubscriptionTable);
return consumerBean;
}
}

View File

@@ -0,0 +1,54 @@
package com.cool.store.mq.consumer.listener;
import com.aliyun.openservices.ons.api.Action;
import com.aliyun.openservices.ons.api.ConsumeContext;
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.MessageListener;
import com.cool.store.constants.CommonConstants;
import com.cool.store.service.visit.VisitRecordService;
import com.cool.store.utils.RedisUtilPool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
/**
* <p>
* 拜访记录失效listener
* </p>
*
* @author wangff
* @since 2026/1/26
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class VisitRecordListener implements MessageListener {
private final RedisUtilPool redisUtilPool;
private final VisitRecordService visitRecordService;
@Override
public Action consume(Message message, ConsumeContext consumeContext) {
String text = new String(message.getBody());
if (StringUtils.isBlank(text)) {
log.info("消息体为空,tag:{},messageId:{}", message.getTag(), message.getMsgID());
return Action.CommitMessage;
}
String lockKey = "VisitRecordListener:" + message.getMsgID();
boolean lock = redisUtilPool.setNxExpire(lockKey, message.getMsgID(), CommonConstants.NORMAL_LOCK_TIMES);
if (lock) {
try {
visitRecordService.recordInvalid(Long.valueOf(text));
} catch (Exception e) {
log.error("VisitRecordListener consume error", e);
return Action.ReconsumeLater;
} finally {
redisUtilPool.delKey(lockKey);
}
log.info("消费成功,tag:{},messageId:{},reqBody={}", message.getTag(), message.getMsgID(), text);
return Action.CommitMessage;
}
return Action.ReconsumeLater;
}
}

View File

@@ -34,6 +34,8 @@ public interface UserAuthMappingService {
*/
List<String> getAuthRegionIdAndSubRegionIdByUserId(String userId);
List<String> getAuthRegionIdAndSubRegionIdExcludeStoreByUserId(String userId);
/**
* 获取所有管辖下的区域ID 不包含门店
* @param userId

View File

@@ -6,6 +6,7 @@ import com.cool.store.constants.CommonConstants;
import com.cool.store.constants.RedisConstant;
import com.cool.store.context.LoginUserInfo;
import com.cool.store.dao.*;
import com.cool.store.dao.visit.VisitRecordDAO;
import com.cool.store.entity.*;
import com.cool.store.enums.*;
import com.cool.store.enums.point.PayBusinessTypeEnum;
@@ -91,6 +92,8 @@ public class LineServiceImpl implements LineService {
QualificationsInfoDAO qualificationsInfoDAO;
@Resource
EnterpriseService enterpriseService;
@Resource
VisitRecordDAO visitRecordDAO;
@Override
@@ -186,6 +189,7 @@ public class LineServiceImpl implements LineService {
}
}
result.setDevelopmentManager(developmentManagerName.toString());
result.setVisitNum(visitRecordDAO.getCompleteVisitNum(result.getId()));
return result;
}

View File

@@ -119,6 +119,20 @@ public class UserAuthMappingServiceImpl implements UserAuthMappingService {
}
@Override
public List<String> getAuthRegionIdAndSubRegionIdExcludeStoreByUserId(String userId) {
List<UserAuthMappingDO> userAuthMapping = listUserAuthMappingByUserId(userId);
if (CollectionUtils.isEmpty(userAuthMapping)) {
return Lists.newArrayList();
}
List<String> regionIds = userAuthMapping.stream().map(UserAuthMappingDO::getMappingId).collect(Collectors.toList());
List<String> subRegionIds = regionMapper.getSubRegionIdsExcludeStore(regionIds);
if (CollectionUtils.isNotEmpty(subRegionIds)) {
regionIds.addAll(subRegionIds);
}
return regionIds;
}
@Override
public List<String> getAuthStoreIdAndSubRegionIdByUserId(String userId) {
List<UserAuthMappingDO> userAuthMapping = listUserAuthMappingByUserId(userId);

View File

@@ -0,0 +1,84 @@
package com.cool.store.service.visit;
import com.cool.store.request.visit.*;
import com.cool.store.vo.visit.*;
import com.github.pagehelper.PageInfo;
/**
* <p>
* 拜访记录Service
* </p>
*
* @author wangff
* @since 2025/12/8
*/
public interface VisitRecordService {
/**
* 签到
* @param request 拜访记录签到Request
* @return 拜访记录id
*/
String signIn(VisitSignInRequest request);
/**
* 签退
* @param request 拜访记录签到Request
* @return 拜访记录id
*/
String signOut(VisitSignOutRequest request);
/**
* 编辑拜访记录
* @param request 拜访记录编辑Request
* @return 是否成功
*/
Boolean editRecord(VisitEditRequest request);
/**
* 拜访加盟商列表
* @param request 拜访加盟商查询Request
* @return 拜访加盟商列表VO
*/
PageInfo<VisitPartnerListVO> visitPartnerList(VisitPartnerQueryRequest request);
/**
* 查询拜访记录简单信息
* @param request 拜访记录查询Request
* @return 拜访记录列表VO
*/
PageInfo<VisitRecordSimpleListVO> recordSimpleList(VisitRecordSimpleQueryRequest request);
/**
* 获取拜访记录详情
* @param id 拜访记录id
* @return 拜访记录详情VO
*/
VisitRecordDetailVO getDetail(Long id);
/**
* 取消拜访
* @param request 拜访记录取消Request
* @return 是否成功
*/
Boolean cancelVisit(VisitCancelRequest request);
/**
* 获取拜访记录列表
* @param request 拜访记录查询Request
* @return 拜访记录列表VO
*/
PageInfo<VisitRecordListVO> recordList(VisitRecordQueryRequest request);
/**
* 记录失效
* @param id id
*/
void recordInvalid(Long id);
/**
* 拜访记录状态统计
* @return 拜访记录状态统计VO
*/
VisitRecordStatusStatisticsVO countStatusStatistics(VisitRecordQueryRequest request);
}

View File

@@ -0,0 +1,289 @@
package com.cool.store.service.visit.impl;
import cn.hutool.core.collection.CollStreamUtil;
import com.cool.store.constants.CommonConstants;
import com.cool.store.context.CurrentUserHolder;
import com.cool.store.dao.*;
import com.cool.store.dao.ad.AdDistrictDAO;
import com.cool.store.dao.store.StoreMasterSignerInfoDAO;
import com.cool.store.dao.visit.VisitRecordDAO;
import com.cool.store.dto.region.BigRegionDTO;
import com.cool.store.entity.LineInfoDO;
import com.cool.store.entity.QualificationsInfoDO;
import com.cool.store.entity.StoreDO;
import com.cool.store.entity.store.StoreMasterSignerInfoDO;
import com.cool.store.entity.visit.VisitRecordDO;
import com.cool.store.enums.ErrorCodeEnum;
import com.cool.store.enums.RocketMqTagEnum;
import com.cool.store.enums.visit.VisitStatusEnum;
import com.cool.store.exception.ServiceException;
import com.cool.store.mq.producer.SimpleMessageService;
import com.cool.store.request.visit.*;
import com.cool.store.service.SysRoleService;
import com.cool.store.service.UserAuthMappingService;
import com.cool.store.service.dict.impl.DictService;
import com.cool.store.service.visit.VisitRecordService;
import com.cool.store.utils.BeanUtil;
import com.cool.store.utils.poi.StringUtils;
import com.cool.store.vo.visit.*;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* <p>
* 拜访记录服务实现类
* </p>
*
* @author wangff
* @since 2025/12/8
*/
@Service
@RequiredArgsConstructor
public class VisitRecordServiceImpl implements VisitRecordService {
private final VisitRecordDAO visitRecordDAO;
private final UserAuthMappingService userAuthMappingService;
private final QualificationsInfoDAO qualificationsInfoDAO;
private final StoreDao storeDao;
private final StoreMasterSignerInfoDAO storeSignerDAO;
private final LineInfoDAO lineInfoDAO;
private final BigRegionDAO bigRegionDAO;
private final DictService dictService;
private final SysRoleService sysRoleService;
private final SimpleMessageService simpleMessageService;
private final AdDistrictDAO adDistrictDAO;
private final EnterpriseUserDAO enterpriseUserDAO;
@Override
public String signIn(VisitSignInRequest request) {
VisitRecordDO record = VisitRecordDO.builder()
.visitNo(generateVisitNo())
.lineId(request.getLineId())
.signInLonLat(request.getSignInLonLat())
.signInAddress(request.getSignInAddress())
.status(VisitStatusEnum.N_SIGN_OUT.getStatus())
.signInTime(new Date())
.userId(CurrentUserHolder.getUserId())
.build();
visitRecordDAO.insertSelective(record);
simpleMessageService.send(String.valueOf(record.getId()), RocketMqTagEnum.VISIT_RECORD_INVALID, System.currentTimeMillis() + 12 * 60 * 60 * 1000);
return record.getId().toString();
}
@Override
public String signOut(VisitSignOutRequest request) {
VisitRecordDO old = visitRecordDAO.getById(request.getId());
verify(old);
if (CommonConstants.INDEX_ZERO.equals(old.getCompleteEdit())) {
throw new ServiceException(ErrorCodeEnum.VISIT_RECORD_NOT_COMPLETE_EDIT);
}
VisitRecordDO record = VisitRecordDO.builder()
.id(request.getId())
.signOutTime(new Date())
.signOutLonLat(request.getSignOutLonLat())
.signOutAddress(request.getSignOutAddress())
.status(VisitStatusEnum.COMPLETE.getStatus())
.build();
visitRecordDAO.updateSelective(record);
return record.getId().toString();
}
@Override
public Boolean editRecord(VisitEditRequest request) {
VisitRecordDO old = visitRecordDAO.getById(request.getId());
verify(old);
VisitRecordDO record = BeanUtil.toBean(request, VisitRecordDO.class);
record.setCompleteEdit(CommonConstants.INDEX_ONE);
return visitRecordDAO.updateSelective(record);
}
@Override
public PageInfo<VisitPartnerListVO> visitPartnerList(VisitPartnerQueryRequest request) {
List<Long> authRegionIds = null;
String currentUserId = CurrentUserHolder.getUserId();
if (!sysRoleService.checkIsAdmin(currentUserId)) {
authRegionIds = userAuthMappingService.getAuthRegionIdAndSubRegionIdExcludeStoreByUserId(currentUserId)
.stream()
.map(Long::valueOf)
.collect(Collectors.toList());
List<BigRegionDTO> bigRegionList = bigRegionDAO.queryAllBigRegion(null, null, null);
Set<Long> bigRegionIds = CollStreamUtil.toSet(bigRegionList, BigRegionDTO::getRegionId);
authRegionIds.retainAll(bigRegionIds);
if (CollectionUtils.isEmpty(authRegionIds)) {
return new PageInfo<>();
}
}
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<Long> lineIds = visitRecordDAO.getPartnerLineId(authRegionIds, request.getKeyword(), request.getUserId());
PageInfo<Long> page = new PageInfo<>(lineIds);
List<VisitPartnerListVO> list = visitRecordDAO.getVisitRecordListByLineIds(lineIds, request.getUserId());
PageInfo<VisitPartnerListVO> result = BeanUtil.toPage(page, VisitPartnerListVO.class);
result.setList(list);
List<LineInfoDO> lineList = lineInfoDAO.getByLineIds(lineIds);
Map<Long, LineInfoDO> lineMap = CollStreamUtil.toMap(lineList, LineInfoDO::getId, v -> v);
Map<Long, Integer> storeNumMap = getStoreNumMap(lineIds);
list.forEach(v -> {
v.setOpenNum(storeNumMap.getOrDefault(v.getLineId(), 0));
v.setIsVeteran(v.getOpenNum() > 0 ? 1 : 0);
LineInfoDO lineInfoDO = lineMap.get(v.getLineId());
if (Objects.nonNull(lineInfoDO)) {
v.setName(lineInfoDO.getUsername());
v.setMobile(lineInfoDO.getMobile());
}
});
return result;
}
private Map<Long, Integer> getStoreNumMap(List<Long> lineIds) {
List<QualificationsInfoDO> qualificationList = qualificationsInfoDAO.getByLineIds(lineIds);
Map<Long, String> idCardMap = CollStreamUtil.toMap(qualificationList, QualificationsInfoDO::getLineId, QualificationsInfoDO::getIdCardNo);
// 根据身份证查询对应签约人开店数量
List<StoreMasterSignerInfoDO> storeSignerList = storeSignerDAO.getListByIdCard(idCardMap.values());
List<String> storeIds = CollStreamUtil.toList(storeSignerList, StoreMasterSignerInfoDO::getStoreId);
List<StoreDO> effectiveStoreList = storeDao.getEffectiveStoreByStoreIds(storeIds);
Set<String> effectiveStoreIds = CollStreamUtil.toSet(effectiveStoreList, StoreDO::getStoreId);
Map<String, Integer> storeNumMap = new HashMap<>();
for (StoreMasterSignerInfoDO signerInfoDO : storeSignerList) {
if (effectiveStoreIds.contains(signerInfoDO.getStoreId())) {
storeNumMap.compute(signerInfoDO.getSigner1IdCardNo(), (k, v) -> Objects.isNull(v) ? 1 : v + 1);
storeNumMap.compute(signerInfoDO.getSigner2IdCardNo(), (k, v) -> Objects.isNull(v) ? 1 : v + 1);
}
}
return CollStreamUtil.toMap(lineIds, v -> v, v -> storeNumMap.getOrDefault(idCardMap.get(v), 0));
}
@Override
public PageInfo<VisitRecordSimpleListVO> recordSimpleList(VisitRecordSimpleQueryRequest request) {
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<VisitRecordDO> list = visitRecordDAO.getByLineId(request.getLineId(), request.getVisitStartDate(), request.getVisitEndDate(), request.getUserId());
PageInfo<VisitRecordDO> page = new PageInfo<>(list);
Set<Long> lineIds = CollStreamUtil.toSet(list, VisitRecordDO::getLineId);
Set<String> userIdList = CollStreamUtil.toSet(list, VisitRecordDO::getUserId);
Map<String, String> userNameMap = new HashMap<>();
if (CollectionUtils.isNotEmpty(userIdList)){
userNameMap = enterpriseUserDAO.getUserNameMap(new ArrayList<>(userIdList));
}
List<LineInfoDO> lineList = lineInfoDAO.getByLineIds(new ArrayList<>(lineIds));
Map<Long, LineInfoDO> lineMap = CollStreamUtil.toMap(lineList, LineInfoDO::getId, v -> v);
PageInfo<VisitRecordSimpleListVO> result = BeanUtil.toPage(page, VisitRecordSimpleListVO.class);
Map<String, String> finalUserNameMap = userNameMap;
result.getList().forEach(v -> {
LineInfoDO lineInfoDO = lineMap.get(v.getLineId());
String userName = finalUserNameMap.getOrDefault(v.getUserId(), "");
v.setUserName(userName);
if (Objects.nonNull(lineInfoDO)) {
v.setName(lineInfoDO.getUsername());
v.setMobile(lineInfoDO.getMobile());
}
});
return result;
}
@Override
public VisitRecordDetailVO getDetail(Long id) {
VisitRecordDO record = visitRecordDAO.getById(id);
if (Objects.isNull(record)) {
throw new ServiceException(ErrorCodeEnum.VISIT_RECORD_NOT_EXIST);
}
VisitRecordDetailVO vo = BeanUtil.toBean(record, VisitRecordDetailVO.class);
LineInfoDO lineInfo = lineInfoDAO.getLineInfo(record.getLineId());
if (Objects.nonNull(lineInfo)) {
vo.setName(lineInfo.getUsername());
vo.setMobile(lineInfo.getMobile());
}
if (StringUtils.isNotBlank(record.getUserId())){
String userName = enterpriseUserDAO.getUserName(record.getUserId());
vo.setUserId(record.getUserId());
vo.setUserName(userName);
}
Map<Long, Integer> storeNumMap = getStoreNumMap(Collections.singletonList(record.getLineId()));
vo.setOpenNum(storeNumMap.getOrDefault(record.getLineId(), 0));
vo.setIsVeteran(storeNumMap.getOrDefault(record.getLineId(), 0) > 0 ? 1 : 0);
List<VisitPartnerListVO> list = visitRecordDAO.getVisitRecordListByLineIds(Collections.singletonList(record.getLineId()), null);
Map<String, String> adNameMap = adDistrictDAO.getNameByCodes(Arrays.asList(vo.getDesireCity(), vo.getDesireDistrict()));
if (CollectionUtils.isNotEmpty(list)) {
VisitPartnerListVO partner = list.get(0);
vo.setVisitNum(partner.getVisitNum());
vo.setDesireCityName(adNameMap.get(vo.getDesireCity()));
vo.setDesireDistrictName(adNameMap.get(vo.getDesireDistrict()));
}
dictService.fillDictField(vo);
return vo;
}
@Override
public Boolean cancelVisit(VisitCancelRequest request) {
return visitRecordDAO.deleteById(request.getId());
}
@Override
public PageInfo<VisitRecordListVO> recordList(VisitRecordQueryRequest request) {
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<VisitRecordListVO> list = visitRecordDAO.getVisitRecordList(request);
PageInfo<VisitRecordListVO> page = new PageInfo<>(list);
Set<Long> lineIds = CollStreamUtil.toSet(list, VisitRecordListVO::getLineId);
Set<String> userIdList = CollStreamUtil.toSet(list, VisitRecordListVO::getVisitUserId);
Map<String, String> userNameMap = new HashMap<>();
if (CollectionUtils.isNotEmpty(userIdList)){
userNameMap = enterpriseUserDAO.getUserNameMap(new ArrayList<>(userIdList));
}
List<String> adCodes = list.stream()
.flatMap(v -> Stream.of(v.getDesireCity(), v.getDesireDistrict()))
.distinct()
.collect(Collectors.toList());
Map<String, String> adNameMap = adDistrictDAO.getNameByCodes(adCodes);
Map<Long, Integer> storeNumMap = getStoreNumMap(new ArrayList<>(lineIds));
Map<String, String> finalUserNameMap = userNameMap;
list.forEach(v -> {
v.setIsVeteran(storeNumMap.getOrDefault(v.getLineId(), 0) > 0 ? 1 : 0);
v.setDesireCityName(adNameMap.get(v.getDesireCity()));
v.setDesireDistrictName(adNameMap.get(v.getDesireDistrict()));
v.setVisitUserName(finalUserNameMap.get(v.getVisitUserId()));
});
return page;
}
@Override
public void recordInvalid(Long id) {
VisitRecordDO record = visitRecordDAO.getById(id);
if (Objects.nonNull(record) && VisitStatusEnum.N_SIGN_OUT.getStatus().equals(record.getStatus())) {
VisitRecordDO update = VisitRecordDO.builder().id(record.getId()).status(VisitStatusEnum.INVALID.getStatus()).build();
visitRecordDAO.updateSelective(update);
}
}
@Override
public VisitRecordStatusStatisticsVO countStatusStatistics(VisitRecordQueryRequest request) {
return visitRecordDAO.countStatusStatistics(request);
}
/**
* 校验操作权限及拜访状态
*/
private void verify(VisitRecordDO record) {
if (Objects.isNull(record)) {
throw new ServiceException(ErrorCodeEnum.VISIT_RECORD_NOT_EXIST);
}
if (VisitStatusEnum.INVALID.getStatus().equals(record.getStatus())) {
throw new ServiceException(ErrorCodeEnum.VISIT_RECORD_INVALID);
}
if (VisitStatusEnum.COMPLETE.getStatus().equals(record.getStatus())) {
throw new ServiceException(ErrorCodeEnum.VISIT_RECORD_COMPLETE);
}
if (!record.getUserId().equals(CurrentUserHolder.getUserId())) {
throw new ServiceException(ErrorCodeEnum.VISIT_RECORD_NOT_YOUR_OWN);
}
}
private String generateVisitNo() {
return "V" + System.currentTimeMillis();
}
}