提交 31c41f3b authored 作者: douxy's avatar douxy

增加店内执行计划导出/入功能:修复更新功能

上级 03bcdf82
package com.sfa.operation.pojo.sales.vo;
import lombok.Data;
/**
* @Author: DouXinYu
* @Date: 2025-12-10 15:31
* @Description: 店内执行-填报要更新到表的字段
*/
@Data
public class SalesApDisplayVo {
/**
* 主键ID
*/
private Long sadId;
/**
* 主货架形式(实际)
*/
private String actualMainShelfType;
/**
* 主货架数量(实际)
*/
private Integer actualMainShelfQty;
/**
* 实际-主货架是否执行
* 执行主货架形式 >= 计划主货架形式 && 执行主货架数量 >= 计划主货架数量"
* 执行/未执行
*/
private String actualMainShelfExecuted;
/**
* 端架数量(实际)
*/
private Integer actualEndCapQty;
/**
* 实际-架是否执行
* 执行端架数量 >= 计划端架数量
*/
private String actualEndCapExecuted;
/**
* 地堆平米数(实际)
*/
private Double actualFloorStackArea;
/**
* 地堆数量(实际)
*/
private Integer actualFloorStackQty;
/**
* 实际-地堆是否执行
* 执行平米数 >= 计划平米数 && 执行数量 >= 计划数量"
*/
private String actualFloorStackExecuted;
/**
* 多点陈列数量+形式(实际)
*/
private String actualMultiDisplay;
/**
* 实际-多点陈列是否执行
*
* actualMultiDisplay的值如下时:
* 执行与计划一致:执行
* 执行与计划不一致:未执行
*/
private String actualMultiDisplayExecuted;
/**
* 挂条数量+形式(实际)
*/
private String actualHangingStripQuantityForm;
/**
* 实际-挂条是否执行
*
* actualHangingStripQuantityForm的值如下时:
* 执行与计划一致:执行
* 执行与计划不一致:未执行
*/
private String hangingStripExecuted;
/**
* 备注信息
*
*/
private String remark ;
}
package com.sfa.operation.service.sales.export.impl; package com.sfa.operation.service.sales.export.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.sfa.common.core.domain.R; import com.sfa.common.core.domain.R;
import com.sfa.operation.factory.ApImportExcelStrategyFactory; import com.sfa.operation.factory.ApImportExcelStrategyFactory;
...@@ -13,6 +14,7 @@ import org.springframework.data.redis.core.StringRedisTemplate; ...@@ -13,6 +14,7 @@ import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
...@@ -61,9 +63,9 @@ public class ImportExcelServiceImpl implements IImportExcelService { ...@@ -61,9 +63,9 @@ public class ImportExcelServiceImpl implements IImportExcelService {
//failCount>0 时 返回错误信息 //failCount>0 时 返回错误信息
if (failCount>0 ) { if (failCount > 0 ) {
log.error("导入失败,失败条数:{}",failCount); log.error("导入失败,失败条数:{}",failCount);
return R.fail(0, request); return R.fail(0, result);
} else { } else {
String uuid = (String) result.getOrDefault("uuid", ""); String uuid = (String) result.getOrDefault("uuid", "");
String redisKey = REDIS_KEY_PREFIX + uuid; String redisKey = REDIS_KEY_PREFIX + uuid;
...@@ -83,32 +85,70 @@ public class ImportExcelServiceImpl implements IImportExcelService { ...@@ -83,32 +85,70 @@ public class ImportExcelServiceImpl implements IImportExcelService {
*/ */
@Override @Override
public R updateApEntity(ImportApExcelRequest request) { public R updateApEntity(ImportApExcelRequest request) {
if (request.getImportApType() == null || request.getImportApType().trim().isEmpty()){ if (request.getImportApType() == null || request.getImportApType().trim().isEmpty()) {
return R.fail("导入类型不能为空!"); return R.fail("导入类型不能为空!");
} }
if (request.getUuid() == null || request.getUuid().trim().isEmpty()){ if (request.getUuid() == null || request.getUuid().trim().isEmpty()) {
return R.fail("导入数据标识不能为空!"); return R.fail("导入数据标识不能为空!");
} }
// 获取策略 // 获取策略
IImportApExcelStrategy strategy = apImportExcelStrategyFactory.getStrategy(request.getImportApType()); IImportApExcelStrategy strategy = apImportExcelStrategyFactory.getStrategy(request.getImportApType());
if (strategy == null){ if (strategy == null) {
return R.fail("未找到对应的导入策略!"); return R.fail("未找到对应的导入策略!");
} }
//从redis获取数据
String redisKey = REDIS_KEY_PREFIX+ request.getUuid();
String redisValue = stringRedisTemplate.opsForValue().get(redisKey);
//解析jsonToDtoList // 从redis获取数据
List list = strategy.getTransactionJsonToObject(redisValue); String redisKey = REDIS_KEY_PREFIX + request.getUuid();
String redisDataJson = stringRedisTemplate.opsForValue().get(redisKey);
log.info("从Redis获取数据,redisKey={}, redisDataJson={}", redisKey, redisDataJson);
//批量更新 // 1校验Redis数据是否存在
String result = strategy.updateDisplay(list); if (redisDataJson == null || redisDataJson.trim().isEmpty()) {
log.error("Redis中无有效数据,key={}", redisKey);
return R.fail("导入数据已过期或不存在,请重新导入!");
}
if ("更新失败".equals(result)) { // 解析Redis数据为JSONObject,仅提取table数组(核心修改点,适配JDK8)
return R.fail(result); List<?> tableList;
try {
// 先解析整个Redis数据为JSONObject
JSONObject redisDataObj = JSONObject.parseObject(redisDataJson);
// 提取table字段对应的JSON数组
JSONArray tableArray = redisDataObj.getJSONArray("table");
if (tableArray == null) {
tableList = Collections.emptyList();
} else {
// 根据策略指定的DTO类型解析table数组
Class<?> dtoClass = strategy.getTargetDtoClass();
// 解析为策略对应的DTO类型列表(而非Object)
tableList = tableArray.toList(dtoClass);
}
} catch (Exception e) {
log.error("解析Redis中的table数组失败,redisDataJson={}", redisDataJson, e);
return R.fail("数据解析失败,请重新导入!");
} }
return R.ok(result); // 校验table数组非空
if (CollectionUtils.isEmpty(tableList)) {
log.error("解析出的table数组为空,redisDataJson={}", redisDataJson);
return R.fail("更新数据为空,请重新导入!");
}
for (Object o : tableList) {
System.out.println( o.toString());
}
// 批量更新(传入table数组,适配不同策略处理)
String updateResult = strategy.updateDisplay(tableList);
log.info("批量更新结果:{}", updateResult);
if ("更新失败".equals(updateResult)) {
return R.fail(updateResult);
}
return R.ok(updateResult);
} }
} }
...@@ -58,6 +58,12 @@ public interface IImportApExcelStrategy<T> { ...@@ -58,6 +58,12 @@ public interface IImportApExcelStrategy<T> {
*/ */
Map<String, Object> execute(String flePathUrl); Map<String, Object> execute(String flePathUrl);
/**
* 获取导入目标DTO类
* @return 目标DTO类
*/
Class<?> getTargetDtoClass();
/** /**
* 获取导入Sheet名称(默认方法) * 获取导入Sheet名称(默认方法)
*/ */
......
...@@ -8,7 +8,6 @@ import com.google.gson.JsonParser; ...@@ -8,7 +8,6 @@ import com.google.gson.JsonParser;
import com.sfa.operation.config.ExportColumnConfig; import com.sfa.operation.config.ExportColumnConfig;
import com.sfa.operation.domain.sales.entity.SalesApDisplay; import com.sfa.operation.domain.sales.entity.SalesApDisplay;
import com.sfa.operation.pojo.sales.excel.SalesApDisplayImportExcelDto; import com.sfa.operation.pojo.sales.excel.SalesApDisplayImportExcelDto;
import com.sfa.operation.pojo.sales.vo.SalesApDisplayVo;
import com.sfa.operation.service.sales.impl.ApDisplayCoreServiceImpl; import com.sfa.operation.service.sales.impl.ApDisplayCoreServiceImpl;
import com.sfa.operation.service.sales.impl.ApDisplayQueryServiceImpl; import com.sfa.operation.service.sales.impl.ApDisplayQueryServiceImpl;
import com.sfa.operation.strategy.IImportApExcelStrategy; import com.sfa.operation.strategy.IImportApExcelStrategy;
...@@ -20,7 +19,6 @@ import org.apache.commons.lang3.StringUtils; ...@@ -20,7 +19,6 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.io.InputStream; import java.io.InputStream;
import java.util.*; import java.util.*;
...@@ -166,74 +164,126 @@ public class NormalDisplayImportStrategyImpl implements IImportApExcelStrategy<S ...@@ -166,74 +164,126 @@ public class NormalDisplayImportStrategyImpl implements IImportApExcelStrategy<S
List<SalesApDisplay> salesApDisplayList = queryData(dtoList); List<SalesApDisplay> salesApDisplayList = queryData(dtoList);
Map<String, SalesApDisplay> displayMap = new HashMap<>(); Map<String, SalesApDisplay> displayMap = new HashMap<>();
for (SalesApDisplay display : salesApDisplayList) { for (SalesApDisplay display : salesApDisplayList) {
String matchKey = buildMatchKey( // 优化点:仅用主键sadId作为匹配键
display.getSadId(), String matchKey = buildMatchKey(display.getSadId());
display.getRegionName(),
display.getDealerName(),
display.getLineName()
);
displayMap.put(matchKey, display); displayMap.put(matchKey, display);
} }
for (SalesApDisplayImportExcelDto dto : dtoList) { for (SalesApDisplayImportExcelDto dto : dtoList) {
SalesApDisplayVo salesApDisplayVo = new SalesApDisplayVo(); // 跳过sadId为空的数据(兜底防护)
BeanUtils.copyProperties(dto, salesApDisplayVo); if (dto.getSadId() == null) {
log.warn("DTO序号为空,跳过处理");
continue;
}
// 从Map中获取当前DTO对应的计划数据 // 从Map中获取当前DTO对应的计划数据
SalesApDisplay salesApDisplay = displayMap.get(buildMatchKey( String dtoMatchKey = buildMatchKey(dto.getSadId());
dto.getSadId(), SalesApDisplay dbDisplay = displayMap.get(dtoMatchKey);
dto.getRegionName(), if (dbDisplay == null) {
dto.getDealerName(),
dto.getLineName()
));
if (salesApDisplay == null) {
log.warn("序号为{}的DTO无匹配计划数据,跳过处理", dto.getSadId()); log.warn("序号为{}的DTO无匹配计划数据,跳过处理", dto.getSadId());
continue; continue;
} }
// 主货架执行状态计算 // 优化点:复制新实体,避免ORM会话关联的并发/只读异常
if (salesApDisplay.getPlannedMainShelfType() != null && salesApDisplay.getPlannedMainShelfQty() != null SalesApDisplay updateDisplay = new SalesApDisplay();
&& salesApDisplayVo.getActualMainShelfType() != null && salesApDisplayVo.getActualMainShelfQty() != null) { BeanUtils.copyProperties(dbDisplay, updateDisplay);
boolean mainShelfTypeMatch = salesApDisplayVo.getActualMainShelfType().equals(salesApDisplay.getPlannedMainShelfType());
boolean mainShelfQtySufficient = salesApDisplayVo.getActualMainShelfQty() >= salesApDisplay.getPlannedMainShelfQty(); // ========== 核心优化:赋值DTO中的基础实际值到新实体 ==========
salesApDisplayVo.setActualMainShelfExecuted((mainShelfTypeMatch && mainShelfQtySufficient) ? "执行" : "未执行"); // 1. 主货架基础值(实际)
if (dto.getActualMainShelfType() != null) {
updateDisplay.setActualMainShelfType(dto.getActualMainShelfType());
}
if (dto.getActualMainShelfQty() != null) {
updateDisplay.setActualMainShelfQty(dto.getActualMainShelfQty());
}
// 2. 端架基础值(实际)
if (dto.getActualEndCapQty() != null) {
updateDisplay.setActualEndCapQty(dto.getActualEndCapQty());
} }
// 端架执行状态计算 // 3. 地堆基础值(实际)
if (salesApDisplayVo.getActualEndCapQty() != null && salesApDisplay.getPlannedEndCapQty() != null) { if (dto.getActualFloorStackArea() != null) {
salesApDisplayVo.setActualEndCapExecuted( updateDisplay.setActualFloorStackArea(dto.getActualFloorStackArea());
salesApDisplayVo.getActualEndCapQty() >= salesApDisplay.getPlannedEndCapQty() ? "执行" : "未执行" }
if (dto.getActualFloorStackQty() != null) {
updateDisplay.setActualFloorStackQty(dto.getActualFloorStackQty());
}
// 4. 多点陈列基础值(实际)
String actualMultiDisplay = StringUtils.trimToNull(dto.getActualMultiDisplay());
if (actualMultiDisplay != null) {
updateDisplay.setActualMultiDisplay(actualMultiDisplay);
}
// 5. 挂条基础值(实际)
String actualHangingStrip = StringUtils.trimToNull(dto.getActualHangingStripQuantityForm());
if (actualHangingStrip != null) {
updateDisplay.setActualHangingStripQuantityForm(actualHangingStrip);
}
// 6. 备注
if (dto.getRemark() != null) {
updateDisplay.setRemark(dto.getRemark());
}
// ========== 执行状态计算(按业务规则处理空值) ==========
// 1. 主货架执行状态计算
if (updateDisplay.getPlannedMainShelfType() != null && updateDisplay.getPlannedMainShelfQty() != null
&& dto.getActualMainShelfType() != null && dto.getActualMainShelfQty() != null) {
boolean mainShelfTypeMatch = dto.getActualMainShelfType().equals(updateDisplay.getPlannedMainShelfType());
boolean mainShelfQtySufficient = dto.getActualMainShelfQty() >= updateDisplay.getPlannedMainShelfQty();
updateDisplay.setActualMainShelfExecuted((mainShelfTypeMatch && mainShelfQtySufficient) ? "执行" : "未执行");
} else {
// 业务规则:计划/实际值不全时,清空执行状态
updateDisplay.setActualMainShelfExecuted(null);
}
// 2. 端架执行状态计算
if (dto.getActualEndCapQty() != null && updateDisplay.getPlannedEndCapQty() != null) {
updateDisplay.setActualEndCapExecuted(
dto.getActualEndCapQty() >= updateDisplay.getPlannedEndCapQty() ? "执行" : "未执行"
); );
} else {
updateDisplay.setActualEndCapExecuted(null);
} }
// 地堆执行状态计算 // 3. 地堆执行状态计算
if (salesApDisplay.getPlannedFloorStackArea() != null && salesApDisplay.getPlannedFloorStackQty() != null if (updateDisplay.getPlannedFloorStackArea() != null && updateDisplay.getPlannedFloorStackQty() != null
&& salesApDisplayVo.getActualFloorStackArea() != null && salesApDisplayVo.getActualFloorStackQty() != null) { && dto.getActualFloorStackArea() != null && dto.getActualFloorStackQty() != null) {
boolean areaSufficient = salesApDisplayVo.getActualFloorStackArea() >= salesApDisplay.getPlannedFloorStackArea(); boolean areaSufficient = dto.getActualFloorStackArea() >= updateDisplay.getPlannedFloorStackArea();
boolean qtySufficient = salesApDisplayVo.getActualFloorStackQty() >= salesApDisplay.getPlannedFloorStackQty(); boolean qtySufficient = dto.getActualFloorStackQty() >= updateDisplay.getPlannedFloorStackQty();
salesApDisplayVo.setActualFloorStackExecuted((areaSufficient && qtySufficient) ? "执行" : "未执行"); updateDisplay.setActualFloorStackExecuted((areaSufficient && qtySufficient) ? "执行" : "未执行");
} else {
updateDisplay.setActualFloorStackExecuted(null);
} }
// 多点陈列执行状态 // 4. 多点陈列执行状态(处理空字符串)
if (salesApDisplay.getPlannedMultiDisplay() != null && salesApDisplayVo.getActualMultiDisplay() != null) { if (updateDisplay.getPlannedMultiDisplay() != null && actualMultiDisplay != null) {
salesApDisplayVo.setActualMultiDisplayExecuted( updateDisplay.setActualMultiDisplayExecuted(
StringUtils.equals("执行与计划一致", salesApDisplayVo.getActualMultiDisplay()) ? "执行" : "未执行" StringUtils.equals("执行与计划一致", actualMultiDisplay) ? "执行" : "未执行"
); );
} else {
updateDisplay.setActualMultiDisplayExecuted(null);
} }
// 挂条执行状态字段赋值错误 // 5. 挂条执行状态(处理空字符串)
if (salesApDisplay.getPlannedHangingStripQuantityForm() != null && salesApDisplayVo.getActualHangingStripQuantityForm() != null) { if (updateDisplay.getPlannedHangingStripQuantityForm() != null && actualHangingStrip != null) {
salesApDisplayVo.setHangingStripExecuted( updateDisplay.setHangingStripExecuted(
StringUtils.equals("执行与计划一致", salesApDisplayVo.getActualHangingStripQuantityForm()) ? "执行" : "未执行" StringUtils.equals("执行与计划一致", actualHangingStrip) ? "执行" : "未执行"
); );
} else {
updateDisplay.setHangingStripExecuted(null);
} }
// VO转实体逻辑 // 添加到更新列表
SalesApDisplay updatedEntity = new SalesApDisplay(); updateEntityList.add(updateDisplay);
BeanUtils.copyProperties(salesApDisplayVo, updatedEntity);
updateEntityList.add(updatedEntity);
} }
// 日志打印更新实体(调试用,生产可注释)
for (SalesApDisplay salesApDisplay : updateEntityList) {
log.info("待更新实体:{}", salesApDisplay.toString());
}
return updateEntityList; return updateEntityList;
} }
...@@ -267,7 +317,7 @@ public class NormalDisplayImportStrategyImpl implements IImportApExcelStrategy<S ...@@ -267,7 +317,7 @@ public class NormalDisplayImportStrategyImpl implements IImportApExcelStrategy<S
//批量验证/操作) //批量验证/操作)
int successCount = queryAndBatchOperate(dtoList); int successCount = queryAndBatchOperate(dtoList);
// 5封装返回结果 // 封装返回结果
resultMap.put("uuid", UUID.randomUUID().toString()); resultMap.put("uuid", UUID.randomUUID().toString());
resultMap.put("table", dtoList); resultMap.put("table", dtoList);
resultMap.put("successCount", successCount); resultMap.put("successCount", successCount);
...@@ -343,6 +393,10 @@ public class NormalDisplayImportStrategyImpl implements IImportApExcelStrategy<S ...@@ -343,6 +393,10 @@ public class NormalDisplayImportStrategyImpl implements IImportApExcelStrategy<S
} }
} }
@Override
public Class<?> getTargetDtoClass() {
return SalesApDisplayImportExcelDto.class;
}
/** /**
* 统一构建组合匹配键(保证DTO和查询结果的键规则一致) * 统一构建组合匹配键(保证DTO和查询结果的键规则一致)
...@@ -354,6 +408,13 @@ public class NormalDisplayImportStrategyImpl implements IImportApExcelStrategy<S ...@@ -354,6 +408,13 @@ public class NormalDisplayImportStrategyImpl implements IImportApExcelStrategy<S
+ (lineName == null ? "" : lineName.trim()); + (lineName == null ? "" : lineName.trim());
} }
/**
* 统一构建组合匹配键(保证DTO和查询结果的键规则一致)
*/
private String buildMatchKey(Long sadId) {
return sadId == null ? "" : sadId.toString();
}
/** /**
* 构建错误DTO(仅用于参数异常场景) * 构建错误DTO(仅用于参数异常场景)
*/ */
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论