Commit 251285ca by HlQ

[add]

1.项目资料文件报表
2.新增工单20分钟未确认,会再次钉钉提醒功能
3.升级 SpringBoot 版本,升级 JDK 版本为 21,开启虚拟线程
4.新项目管理页面添加项目经理字段
[fix] 修复合同详情和合同列表显示销售不一致的bug
1 parent a6cfb4b8
......@@ -8,12 +8,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
<version>3.3.1</version>
</parent>
<artifactId>Vion-DevOps</artifactId>
<version>1</version>
<properties>
<java.version>21</java.version>
<hutool.version>6.0.0-M13</hutool.version>
<redisson.verion>3.32.0</redisson.verion>
<mapstruct-plus.version>1.4.3</mapstruct-plus.version>
......@@ -105,8 +106,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
<source>21</source>
<target>21</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
......
......@@ -241,6 +241,14 @@ public class StoreController {
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")
@SaCheckPermission(value = "store:file:export", orRole = "admin")
public void storeFileExport(StoreDTO data, @RequestBody(required = false) List<OrderItem> orderItemList,
......
......@@ -14,6 +14,7 @@ import java.util.Date;
@Setter
public class StoreDTO extends BaseDTO {
private Long id;
private Long[] ids;
/** 门店名称 */
private String name ;
/** 销售人 */
......
......@@ -39,6 +39,8 @@ public interface IStoreService extends MPJBaseService<Store> {
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);
......
......@@ -70,6 +70,7 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont
private final IRContractTeamService contractTeamService;
private final IRContractUserService contractUserService;
private final IRContractProductService contractProductService;
private final IRStoreUserService storeUserService;
// 引入 settlementDiffService 会循环依赖
private final SettlementDiffMapper settlementDiffMapper;
private final DingMod dingMod;
......@@ -154,9 +155,12 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont
var idList = Opt.ofEmptyAble(contractVOList.getRecords())
.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)
.map(l -> l.stream().collect(Collectors.groupingBy(RContractStore::getContractId,
Collectors.mapping(RContractStore::getStoreId, Collectors.toList()))))
......@@ -165,13 +169,12 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont
.map(cs -> cs.stream().map(RContractStore::getStoreId).toList())
.map(storeIds -> Db.listByIds(storeIds, Store.class).stream().collect(Collectors.toMap(Store::getId, Function.identity())))
.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)
.map(l -> contractUserService.lambdaQuery()
......@@ -191,10 +194,12 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont
vo.setOutstandingAmount(null);
vo.setInvoiceAmount(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)
.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());
return contractVOList;
......@@ -248,6 +253,12 @@ public class ContractServiceImpl extends MPJBaseServiceImpl<ContractMapper, Cont
var contractProducts = contractProductService.lambdaQuery()
.eq(RContractProduct::getContractNo, contractVO.getContractNo())
.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);
return contractVO;
}
......
......@@ -110,6 +110,7 @@ public class StoreServiceImpl extends MPJBaseServiceImpl<StoreMapper, Store> imp
.in(ObjUtil.isNotNull(data.getTagId()), Store::getId, storeIdListByTagId)
.in(ObjUtil.isNotNull(data.getMainUser()), Store::getId, storeIdListByMainUser)
.gt(ObjUtil.isNotNull(estDate), Store::getEstDate, estDate)
.in(ArrayUtil.isNotEmpty(data.getIds()), Store::getId, data.getIds())
.page(page);
// todo 缓存
List<Account> accountList = accountService.list();
......@@ -549,6 +550,39 @@ public class StoreServiceImpl extends MPJBaseServiceImpl<StoreMapper, Store> imp
}
@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) {
UserVO user = (UserVO) StpUtil.getTokenSession().get("curLoginUser");
Page<StoreVO> voPage = this.getStoreList(data, orderItemList);
......@@ -561,11 +595,13 @@ public class StoreServiceImpl extends MPJBaseServiceImpl<StoreMapper, Store> imp
"项目总结", "巡检报告",
"结算资料", "其它");
var files = fileService.lambdaQuery()
.in(FileInfo::getSourceId, storeIds)
.in(FileInfo::getStoreId, storeIds)
.in(FileInfo::getSourceType, 1, 24, 25, 26, 27, 28, 29, 30)
.list();
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)
......
......@@ -6,7 +6,9 @@ import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.github.yulichang.base.MPJBaseServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import io.github.linpeilie.Converter;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.collection.CollUtil;
......@@ -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.FileUtil;
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.util.ObjUtil;
import org.dromara.hutool.crypto.SecureUtil;
import org.dromara.hutool.json.JSONObject;
import org.dromara.hutool.json.JSONUtil;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import vion.dto.TaskTempDTO;
......@@ -34,16 +39,19 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, TaskTemp> implements ITaskTempService {
private final IFileService fileService;
private final IUserService userService;
private final DingMod dingMod;
private final Converter converter;
private final RedissonClient redissonClient;
@Value("${fileUrl:}")
private String fileUrl;
......@@ -84,7 +92,6 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task
Opt.ofNullable(data.getFiles())
.ifPresent(fileList ->
Arrays.stream(fileList).forEach(infile -> {
//上传url地址
String orgName = infile.getOriginalFilename();
String mainName = FileNameUtil.mainName(orgName);
String fileExt = FileNameUtil.extName(orgName);
......@@ -109,7 +116,14 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task
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();
String userids = userList.stream().map(User::getUserid).collect(Collectors.joining(","));
dingMod.workMsg(buildMsg(userids, taskTemp));
......@@ -132,7 +146,7 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task
var filterList = voList.stream().filter(BeanUtil::isNotEmpty).collect(Collectors.toList());
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();
}
......@@ -168,5 +182,33 @@ public class TaskTempServiceImpl extends MPJBaseServiceImpl<TaskTempMapper, Task
jsonObj.set("msg", msg);
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:
prefix: /
suffix: .html
static-path-pattern: /**
threads:
virtual:
enabled: true
wx:
mp:
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!