Commit 251285ca by HlQ

[add]

1.项目资料文件报表
2.新增工单20分钟未确认,会再次钉钉提醒功能
3.升级 SpringBoot 版本,升级 JDK 版本为 21,开启虚拟线程
4.新项目管理页面添加项目经理字段
[fix] 修复合同详情和合同列表显示销售不一致的bug
1 parent a6cfb4b8
...@@ -8,12 +8,13 @@ ...@@ -8,12 +8,13 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version> <version>3.3.1</version>
</parent> </parent>
<artifactId>Vion-DevOps</artifactId> <artifactId>Vion-DevOps</artifactId>
<version>1</version> <version>1</version>
<properties> <properties>
<java.version>21</java.version>
<hutool.version>6.0.0-M13</hutool.version> <hutool.version>6.0.0-M13</hutool.version>
<redisson.verion>3.32.0</redisson.verion> <redisson.verion>3.32.0</redisson.verion>
<mapstruct-plus.version>1.4.3</mapstruct-plus.version> <mapstruct-plus.version>1.4.3</mapstruct-plus.version>
...@@ -105,8 +106,8 @@ ...@@ -105,8 +106,8 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<configuration> <configuration>
<source>17</source> <source>21</source>
<target>17</target> <target>21</target>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
<annotationProcessorPaths> <annotationProcessorPaths>
<path> <path>
......
...@@ -241,6 +241,14 @@ public class StoreController { ...@@ -241,6 +241,14 @@ public class StoreController {
return storeService.fileNotify(storeName, contractNo, sourceName, fileNameArr, userIdStr) ? "钉钉提醒成功" : "钉钉提醒失败"; return storeService.fileNotify(storeName, contractNo, sourceName, fileNameArr, userIdStr) ? "钉钉提醒成功" : "钉钉提醒失败";
} }
@PostMapping("/store/file/table")
@SaCheckPermission(value = "store:file:table", orRole = "admin")
public Map<String, Map<String, Map<String, Long>>> storeFileTable(StoreDTO data,
@RequestBody(required = false) List<OrderItem> orderItemList,
HttpServletResponse response) {
return storeService.storeFileTable(data, orderItemList, response);
}
@PostMapping("/store/file/export") @PostMapping("/store/file/export")
@SaCheckPermission(value = "store:file:export", orRole = "admin") @SaCheckPermission(value = "store:file:export", orRole = "admin")
public void storeFileExport(StoreDTO data, @RequestBody(required = false) List<OrderItem> orderItemList, public void storeFileExport(StoreDTO data, @RequestBody(required = false) List<OrderItem> orderItemList,
......
...@@ -14,6 +14,7 @@ import java.util.Date; ...@@ -14,6 +14,7 @@ import java.util.Date;
@Setter @Setter
public class StoreDTO extends BaseDTO { public class StoreDTO extends BaseDTO {
private Long id; private Long id;
private Long[] ids;
/** 门店名称 */ /** 门店名称 */
private String name ; private String name ;
/** 销售人 */ /** 销售人 */
......
...@@ -39,6 +39,8 @@ public interface IStoreService extends MPJBaseService<Store> { ...@@ -39,6 +39,8 @@ public interface IStoreService extends MPJBaseService<Store> {
Boolean fileNotify(String storeName, String contractNo, String sourceName, String[] fileNameArr, String[] userIdStr); Boolean fileNotify(String storeName, String contractNo, String sourceName, String[] fileNameArr, String[] userIdStr);
Map<String, Map<String, Map<String, Long>>> storeFileTable(StoreDTO data, List<OrderItem> orderItemList, HttpServletResponse response);
void storeFileExport(StoreDTO data, List<OrderItem> orderItemList, HttpServletResponse response); void storeFileExport(StoreDTO data, List<OrderItem> orderItemList, HttpServletResponse response);
......
...@@ -70,6 +70,7 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont ...@@ -70,6 +70,7 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont
private final IRContractTeamService contractTeamService; private final IRContractTeamService contractTeamService;
private final IRContractUserService contractUserService; private final IRContractUserService contractUserService;
private final IRContractProductService contractProductService; private final IRContractProductService contractProductService;
private final IRStoreUserService storeUserService;
// 引入 settlementDiffService 会循环依赖 // 引入 settlementDiffService 会循环依赖
private final SettlementDiffMapper settlementDiffMapper; private final SettlementDiffMapper settlementDiffMapper;
private final DingMod dingMod; private final DingMod dingMod;
...@@ -154,9 +155,12 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont ...@@ -154,9 +155,12 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont
var idList = Opt.ofEmptyAble(contractVOList.getRecords()) var idList = Opt.ofEmptyAble(contractVOList.getRecords())
.map(r -> r.stream().map(ContractVO::getId).collect(Collectors.toList())) .map(r -> r.stream().map(ContractVO::getId).collect(Collectors.toList()))
.orElse(ListUtil.empty()); .orElse(List.of());
var contractStoreList = contractStoreService.lambdaQuery().in(RContractStore::getContractId, idList).list(); // 根据合同关联的项目,获取项目的相关信息
var contractStoreList = Opt.ofEmptyAble(idList)
.map(ids -> contractStoreService.lambdaQuery().in(RContractStore::getContractId, ids).list())
.orElse(List.of());
var contractId2StoresMap = Opt.ofEmptyAble(contractStoreList) var contractId2StoresMap = Opt.ofEmptyAble(contractStoreList)
.map(l -> l.stream().collect(Collectors.groupingBy(RContractStore::getContractId, .map(l -> l.stream().collect(Collectors.groupingBy(RContractStore::getContractId,
Collectors.mapping(RContractStore::getStoreId, Collectors.toList())))) Collectors.mapping(RContractStore::getStoreId, Collectors.toList()))))
...@@ -165,13 +169,12 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont ...@@ -165,13 +169,12 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont
.map(cs -> cs.stream().map(RContractStore::getStoreId).toList()) .map(cs -> cs.stream().map(RContractStore::getStoreId).toList())
.map(storeIds -> Db.listByIds(storeIds, Store.class).stream().collect(Collectors.toMap(Store::getId, Function.identity()))) .map(storeIds -> Db.listByIds(storeIds, Store.class).stream().collect(Collectors.toMap(Store::getId, Function.identity())))
.orElse(Map.of()); .orElse(Map.of());
var storeId2UserIdMap = Opt.ofEmptyAble(contractStoreList)
.map(cs -> cs.stream().map(RContractStore::getStoreId).toList())
.map(storeIds -> storeUserService.lambdaQuery().in(RStoreUser::getStoreId, storeIds).eq(RStoreUser::getIsMain, 1).list())
.map(suList -> suList.stream().collect(Collectors.toMap(RStoreUser::getStoreId, RStoreUser::getUserId)))
.orElse(MapUtil.empty());
// 结算差异
Opt.ofEmptyAble(idList)
.map(l -> settlementDiffMapper.selectList(Wrappers.<SettlementDiff>lambdaQuery()
.in(SettlementDiff::getContractId, l)))
.map(list -> list.stream().collect(Collectors.groupingBy(SettlementDiff::getContractId, Collectors.reducing(BigDecimal.ZERO, SettlementDiff::getSettlementDiff, BigDecimal::add))))
.ifPresent(map -> contractVOList.getRecords().forEach(vo -> vo.setDiffAmount(map.getOrDefault(vo.getId(), BigDecimal.ZERO))));
// 合同绑定销售 // 合同绑定销售
Opt.ofEmptyAble(idList) Opt.ofEmptyAble(idList)
.map(l -> contractUserService.lambdaQuery() .map(l -> contractUserService.lambdaQuery()
...@@ -191,10 +194,12 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont ...@@ -191,10 +194,12 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont
vo.setOutstandingAmount(null); vo.setOutstandingAmount(null);
vo.setInvoiceAmount(null); vo.setInvoiceAmount(null);
vo.setDiffAmount(null); vo.setDiffAmount(null);
var collect = contractId2StoresMap.getOrDefault(vo.getId(), List.of()).stream() var storeList = contractId2StoresMap.getOrDefault(vo.getId(), List.of()).stream()
.map(storeMap::get) .map(storeMap::get)
.collect(Collectors.toList()); .collect(Collectors.toList());
vo.setStoreVOS(converter.convert(collect, StoreVO.class)); var storeVOList = converter.convert(storeList, StoreVO.class);
storeVOList.forEach(storeVO -> storeVO.setMainUser(storeId2UserIdMap.getOrDefault(storeVO.getId(), null)));
vo.setStoreVOS(storeVOList);
}); });
completeStoreName(contractVOList.getRecords()); completeStoreName(contractVOList.getRecords());
return contractVOList; return contractVOList;
...@@ -248,6 +253,12 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont ...@@ -248,6 +253,12 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont
var contractProducts = contractProductService.lambdaQuery() var contractProducts = contractProductService.lambdaQuery()
.eq(RContractProduct::getContractNo, contractVO.getContractNo()) .eq(RContractProduct::getContractNo, contractVO.getContractNo())
.list(); .list();
// 合同绑定销售
Opt.ofEmptyAble(contractUserService.lambdaQuery().eq(RContractUser::getContractId, contractVO.getId()).list())
.ifPresent(l -> l.stream()
.max(Comparator.comparing(RContractUser::getEnterDate))
.map(RContractUser::getUsername)
.ifPresent(contractVO::setSaleName));
contractVO.setContractProducts(contractProducts); contractVO.setContractProducts(contractProducts);
return contractVO; return contractVO;
} }
......
...@@ -110,6 +110,7 @@ public class StoreServiceImpl extends MPJBaseServiceImpl<StoreMapper, Store> imp ...@@ -110,6 +110,7 @@ public class StoreServiceImpl extends MPJBaseServiceImpl<StoreMapper, Store> imp
.in(ObjUtil.isNotNull(data.getTagId()), Store::getId, storeIdListByTagId) .in(ObjUtil.isNotNull(data.getTagId()), Store::getId, storeIdListByTagId)
.in(ObjUtil.isNotNull(data.getMainUser()), Store::getId, storeIdListByMainUser) .in(ObjUtil.isNotNull(data.getMainUser()), Store::getId, storeIdListByMainUser)
.gt(ObjUtil.isNotNull(estDate), Store::getEstDate, estDate) .gt(ObjUtil.isNotNull(estDate), Store::getEstDate, estDate)
.in(ArrayUtil.isNotEmpty(data.getIds()), Store::getId, data.getIds())
.page(page); .page(page);
// todo 缓存 // todo 缓存
List<Account> accountList = accountService.list(); List<Account> accountList = accountService.list();
...@@ -549,6 +550,39 @@ public class StoreServiceImpl extends MPJBaseServiceImpl<StoreMapper, Store> imp ...@@ -549,6 +550,39 @@ public class StoreServiceImpl extends MPJBaseServiceImpl<StoreMapper, Store> imp
} }
@Override @Override
public Map<String, Map<String, Map<String, Long>>> storeFileTable(StoreDTO data, List<OrderItem> orderItemList,
HttpServletResponse response) {
Page<StoreVO> voPage = this.getStoreList(data, orderItemList);
var records = voPage.getRecords();
var storeIds = records.stream().map(StoreVO::getId).toList();
var id2NameMap = records.stream().collect(Collectors.toMap(StoreVO::getId, StoreVO::getName));
var sourceTypeMap = redissonClient.<Integer, String>getMap(RedisKeyEnum.DICT_PREFIX.getVal() + RedisKeyEnum.SOURCE_TYPE.getVal());
var files = Opt.ofEmptyAble(storeIds)
.map(ids -> fileService.lambdaQuery()
.in(FileInfo::getSourceId, ids)
.in(FileInfo::getStoreId, ids)
.in(FileInfo::getSourceType, 1, 24, 25, 26, 27, 28, 29, 30)
.list())
.orElse(List.of());
var contractId2NoMap = Opt.ofEmptyAble(files)
.map(f -> f.stream().map(FileInfo::getContractId).toList())
.map(contractService::listByIds)
.filter(CollUtil::isNotEmpty)
.map(contractList -> contractList.stream().collect(Collectors.toMap(Contract::getId, Contract::getContractNo)))
.orElse(Map.of());
// storeName -> <contractName -> <sourceTypeName -> cnt>>
return Opt.ofEmptyAble(files)
.map(fs -> fs.stream().collect(Collectors.groupingBy(f -> id2NameMap.getOrDefault(f.getStoreId(),
"未知项目"),
Collectors.groupingBy(f -> Opt.ofNullable(f.getContractId()).map(id -> contractId2NoMap.getOrDefault(id, "暂无关联合同")).orElse("暂无关联合同"),
Collectors.groupingBy(f -> sourceTypeMap.getOrDefault(f.getSourceType(), "未知文件来源"),
Collectors.counting())))))
.orElse(Map.of());
}
@Override
public void storeFileExport(StoreDTO data, List<OrderItem> orderItemList, HttpServletResponse response) { public void storeFileExport(StoreDTO data, List<OrderItem> orderItemList, HttpServletResponse response) {
UserVO user = (UserVO) StpUtil.getTokenSession().get("curLoginUser"); UserVO user = (UserVO) StpUtil.getTokenSession().get("curLoginUser");
Page<StoreVO> voPage = this.getStoreList(data, orderItemList); Page<StoreVO> voPage = this.getStoreList(data, orderItemList);
...@@ -561,11 +595,13 @@ public class StoreServiceImpl extends MPJBaseServiceImpl<StoreMapper, Store> imp ...@@ -561,11 +595,13 @@ public class StoreServiceImpl extends MPJBaseServiceImpl<StoreMapper, Store> imp
"项目总结", "巡检报告", "项目总结", "巡检报告",
"结算资料", "其它"); "结算资料", "其它");
var files = fileService.lambdaQuery() var files = Opt.ofEmptyAble(storeIds)
.in(FileInfo::getSourceId, storeIds) .map(ids -> fileService.lambdaQuery()
.in(FileInfo::getStoreId, storeIds) .in(FileInfo::getSourceId, ids)
.in(FileInfo::getSourceType, 1, 24, 25, 26, 27, 28, 29, 30) .in(FileInfo::getStoreId, ids)
.list(); .in(FileInfo::getSourceType, 1, 24, 25, 26, 27, 28, 29, 30)
.list())
.orElse(List.of());
var contractId2NoMap = Opt.ofEmptyAble(files) var contractId2NoMap = Opt.ofEmptyAble(files)
.map(f -> f.stream().map(FileInfo::getContractId).toList()) .map(f -> f.stream().map(FileInfo::getContractId).toList())
.map(contractService::listByIds) .map(contractService::listByIds)
......
...@@ -6,7 +6,9 @@ import com.baomidou.mybatisplus.extension.toolkit.Db; ...@@ -6,7 +6,9 @@ import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.github.yulichang.base.MPJBaseServiceImpl; import com.github.yulichang.base.MPJBaseServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper; import com.github.yulichang.wrapper.MPJLambdaWrapper;
import io.github.linpeilie.Converter; import io.github.linpeilie.Converter;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.bean.BeanUtil; import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.collection.CollUtil; import org.dromara.hutool.core.collection.CollUtil;
...@@ -14,10 +16,13 @@ import org.dromara.hutool.core.date.DateUtil; ...@@ -14,10 +16,13 @@ import org.dromara.hutool.core.date.DateUtil;
import org.dromara.hutool.core.io.file.FileNameUtil; import org.dromara.hutool.core.io.file.FileNameUtil;
import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.lang.Opt; import org.dromara.hutool.core.lang.Opt;
import org.dromara.hutool.core.math.NumberUtil;
import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.crypto.SecureUtil; import org.dromara.hutool.crypto.SecureUtil;
import org.dromara.hutool.json.JSONObject; import org.dromara.hutool.json.JSONObject;
import org.dromara.hutool.json.JSONUtil; import org.dromara.hutool.json.JSONUtil;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import vion.dto.TaskTempDTO; import vion.dto.TaskTempDTO;
...@@ -34,16 +39,19 @@ import java.io.IOException; ...@@ -34,16 +39,19 @@ import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, TaskTemp> implements ITaskTempService { public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, TaskTemp> implements ITaskTempService {
private final IFileService fileService; private final IFileService fileService;
private final IUserService userService; private final IUserService userService;
private final DingMod dingMod; private final DingMod dingMod;
private final Converter converter; private final Converter converter;
private final RedissonClient redissonClient;
@Value("${fileUrl:}") @Value("${fileUrl:}")
private String fileUrl; private String fileUrl;
...@@ -84,7 +92,6 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task ...@@ -84,7 +92,6 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task
Opt.ofNullable(data.getFiles()) Opt.ofNullable(data.getFiles())
.ifPresent(fileList -> .ifPresent(fileList ->
Arrays.stream(fileList).forEach(infile -> { Arrays.stream(fileList).forEach(infile -> {
//上传url地址
String orgName = infile.getOriginalFilename(); String orgName = infile.getOriginalFilename();
String mainName = FileNameUtil.mainName(orgName); String mainName = FileNameUtil.mainName(orgName);
String fileExt = FileNameUtil.extName(orgName); String fileExt = FileNameUtil.extName(orgName);
...@@ -109,7 +116,14 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task ...@@ -109,7 +116,14 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task
fileService.save(fileInfo); fileService.save(fileInfo);
})); }));
// todo 异步发送钉钉消息通知 try {
var bd = redissonClient.<TaskTemp>getBlockingDeque("task_temp_queue");
var delayedQueue = redissonClient.getDelayedQueue(bd);
delayedQueue.offer(taskTemp, 20, TimeUnit.MINUTES);
} catch (Exception e) {
log.error("预工单添加延时队列出错,{}", e.getMessage());
}
var userList = userService.lambdaQuery().eq(User::getPreWorkOrder, 1).list(); var userList = userService.lambdaQuery().eq(User::getPreWorkOrder, 1).list();
String userids = userList.stream().map(User::getUserid).collect(Collectors.joining(",")); String userids = userList.stream().map(User::getUserid).collect(Collectors.joining(","));
dingMod.workMsg(buildMsg(userids, taskTemp)); dingMod.workMsg(buildMsg(userids, taskTemp));
...@@ -132,7 +146,7 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task ...@@ -132,7 +146,7 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task
var filterList = voList.stream().filter(BeanUtil::isNotEmpty).collect(Collectors.toList()); var filterList = voList.stream().filter(BeanUtil::isNotEmpty).collect(Collectors.toList());
if (CollUtil.isNotEmpty(filterList) && filterList.size() > 1) { if (CollUtil.isNotEmpty(filterList) && filterList.size() > 1) {
return Map.of("accountId", filterList.get(0).getAccountId(), "storeId", filterList.get(0).getStoreId()); return Map.of("accountId", filterList.getFirst().getAccountId(), "storeId", filterList.getFirst().getStoreId());
} }
return Map.of(); return Map.of();
} }
...@@ -168,5 +182,33 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task ...@@ -168,5 +182,33 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task
jsonObj.set("msg", msg); jsonObj.set("msg", msg);
return jsonObj; return jsonObj;
} }
/**
* 处理延时队列里的20分钟后未确认的工单,再次推送钉钉消息提醒
*/
@PostConstruct
void processTaskTempInQueue() {
Thread.startVirtualThread(() -> {
var bd = redissonClient.<TaskTemp>getBlockingDeque("task_temp_queue");
var delayedQueue = redissonClient.getDelayedQueue(bd);
while (true) {
var taskTemp = bd.poll();
if (ObjUtil.isNotNull(taskTemp)) {
var taskTempByDB = this.getById(taskTemp.getId());
if (ObjUtil.isNotNull(taskTempByDB) && NumberUtil.equals(taskTempByDB.getStatus(), 1)) {
var userids = userService.lambdaQuery()
.eq(User::getPreWorkOrder, 1)
.list().stream()
.map(User::getUserid)
.collect(Collectors.joining(","));
dingMod.workMsg(buildMsg(userids, taskTemp));
delayedQueue.offer(taskTemp, 20, TimeUnit.MINUTES);
}
}
}
});
}
} }
...@@ -12,6 +12,9 @@ spring: ...@@ -12,6 +12,9 @@ spring:
prefix: / prefix: /
suffix: .html suffix: .html
static-path-pattern: /** static-path-pattern: /**
threads:
virtual:
enabled: true
wx: wx:
mp: mp:
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!