From 1a565dba0d54411ad15e88b940f4c8520fd2fcfe Mon Sep 17 00:00:00 2001 From: wenshijun Date: Wed, 14 Jan 2026 08:14:04 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=9F=E6=88=90=E5=8D=95=E6=8D=AE=E5=8F=B7?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=BF=AE=E6=94=B9=20=E7=BA=BF=E7=A8=8B?= =?UTF-8?q?=E6=B1=A0=E7=BA=BF=E7=A8=8B=E6=95=B0=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/zg/common/utils/poi/ExcelUtil.java | 43 +++++++- .../zg/framework/config/ThreadPoolConfig.java | 6 +- .../controller/AgvTaskResultController.java | 2 + .../wisdom/controller/RkInfoController.java | 11 ++- .../com/zg/project/wisdom/domain/RkInfo.java | 12 +++ .../wisdom/domain/dto/OutGoodsDTO.java | 8 ++ .../impl/AgvTaskResultServiceImpl.java | 15 ++- .../service/impl/RkInfoServiceImpl.java | 54 +++------- .../zg/project/wisdom/utils/BillNoUtil.java | 72 +++++++++----- .../resources/mybatis/wisdom/RkInfoMapper.xml | 98 ++++++++++++++----- 10 files changed, 212 insertions(+), 109 deletions(-) diff --git a/src/main/java/com/zg/common/utils/poi/ExcelUtil.java b/src/main/java/com/zg/common/utils/poi/ExcelUtil.java index 19df517..e2bb110 100644 --- a/src/main/java/com/zg/common/utils/poi/ExcelUtil.java +++ b/src/main/java/com/zg/common/utils/poi/ExcelUtil.java @@ -1116,6 +1116,9 @@ public class ExcelUtil } } + /** + * 添加单元格 + */ /** * 添加单元格 */ @@ -1126,11 +1129,13 @@ public class ExcelUtil { // 设置行高 row.setHeight(maxHeight); - // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列. + + // 根据Excel中设置情况决定是否导出 if (attr.isExport()) { // 创建cell cell = row.createCell(column); + if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) { if (subMergedLastRowNum >= subMergedFirstRowNum) @@ -1138,14 +1143,45 @@ public class ExcelUtil sheet.addMergedRegion(new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column)); } } + cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType(), attr.wrapText()))); // 用于读取对象中的属性 Object value = getTargetValue(vo, field, attr); + + // ========================= + // ✅ BigDecimal:按“实际几位显示几位”,去掉末尾0(核心修改点) + // ========================= + if (value instanceof BigDecimal) + { + BigDecimal bd = (BigDecimal) value; + + if (-1 != attr.scale()) + { + bd = bd.setScale(attr.scale(), attr.roundingMode()); + } + + // 去掉末尾0:1.000000 -> 1,1.2300 -> 1.23 + String text = bd.stripTrailingZeros().toPlainString(); + + // 强制按字符串写入,避免 Excel 数值格式导致显示成固定小数位 + cell.setCellType(CellType.STRING); + cell.setCellValue(StringUtils.isEmpty(text) ? attr.defaultValue() : text + attr.suffix()); + + // 统计 + addStatisticsData(column, text, attr); + + return cell; + } + + // ========================= + // 原有逻辑(非 BigDecimal)保持不变 + // ========================= String dateFormat = attr.dateFormat(); String readConverterExp = attr.readConverterExp(); String separator = attr.separator(); String dictType = attr.dictType(); + if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) { cell.getCellStyle().setDataFormat(this.wb.getCreationHelper().createDataFormat().getFormat(dateFormat)); @@ -1164,10 +1200,6 @@ public class ExcelUtil } cell.setCellValue(sysDictMap.get(dictType + value)); } - else if (value instanceof BigDecimal && -1 != attr.scale()) - { - cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue()); - } else if (!attr.handler().equals(ExcelHandlerAdapter.class)) { cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell)); @@ -1177,6 +1209,7 @@ public class ExcelUtil // 设置列类型 setCellVo(value, attr, cell); } + addStatisticsData(column, Convert.toStr(value), attr); } } diff --git a/src/main/java/com/zg/framework/config/ThreadPoolConfig.java b/src/main/java/com/zg/framework/config/ThreadPoolConfig.java index 9932ce2..70dedcd 100644 --- a/src/main/java/com/zg/framework/config/ThreadPoolConfig.java +++ b/src/main/java/com/zg/framework/config/ThreadPoolConfig.java @@ -18,16 +18,16 @@ import java.util.concurrent.ThreadPoolExecutor; public class ThreadPoolConfig { // 核心线程池大小 - private int corePoolSize = 50; + private int corePoolSize = 16; // 最大可创建的线程数 - private int maxPoolSize = 200; + private int maxPoolSize = 32; // 队列最大长度 private int queueCapacity = 1000; // 线程池维护线程所允许的空闲时间 - private int keepAliveSeconds = 300; + private int keepAliveSeconds = 120; @Bean(name = "threadPoolTaskExecutor") public ThreadPoolTaskExecutor threadPoolTaskExecutor() diff --git a/src/main/java/com/zg/project/wisdom/controller/AgvTaskResultController.java b/src/main/java/com/zg/project/wisdom/controller/AgvTaskResultController.java index dbea512..4a78a64 100644 --- a/src/main/java/com/zg/project/wisdom/controller/AgvTaskResultController.java +++ b/src/main/java/com/zg/project/wisdom/controller/AgvTaskResultController.java @@ -80,11 +80,13 @@ public class AgvTaskResultController extends BaseController { */ @PostMapping("/upGoods") public AjaxResult upGoods(@RequestBody OutGoodsDTO dto) { + if (StringUtils.isBlank(dto.getTaskNo())) { throw new ServiceException("taskNo 不能为空"); } // 返回 taskId String taskId = agvTaskResultService.handleUpGoods(dto.getTaskNo(), dto.getMaterialStatus()); + return AjaxResult.success("上架任务已提交至WCS").put("taskId", taskId); } diff --git a/src/main/java/com/zg/project/wisdom/controller/RkInfoController.java b/src/main/java/com/zg/project/wisdom/controller/RkInfoController.java index 2395f91..86ccccd 100644 --- a/src/main/java/com/zg/project/wisdom/controller/RkInfoController.java +++ b/src/main/java/com/zg/project/wisdom/controller/RkInfoController.java @@ -71,13 +71,14 @@ public class RkInfoController extends BaseController @PostMapping("/pageStatistics") public Map pageStatistics(@RequestBody RkInfoQueryDTO dto) { - // 使用 PageHelper 分页 + // 分页(如果这里不需要总数,后面你可以改成 startPage(..., false)) PageHelper.startPage(dto.getPageNum(), dto.getPageSize()); - List list = rkInfoService.selectAllRkInfo(dto); - Map dataInfo = new HashMap<>(); - dataInfo.put("dataList",getDataTable(list)); - return dataInfo; + List list = rkInfoService.selectAllRkInfo(dto); + + Map dataInfo = new HashMap<>(); + dataInfo.put("dataList", getDataTable(list)); + return dataInfo; } /** diff --git a/src/main/java/com/zg/project/wisdom/domain/RkInfo.java b/src/main/java/com/zg/project/wisdom/domain/RkInfo.java index 80975ca..1983edb 100644 --- a/src/main/java/com/zg/project/wisdom/domain/RkInfo.java +++ b/src/main/java/com/zg/project/wisdom/domain/RkInfo.java @@ -193,6 +193,10 @@ public class RkInfo extends BaseEntity { @Excel(name = "实际入库数量") private BigDecimal realQty; + /** 总金额 = 合同单价 × 实际入库数量(导出专用) */ + @Excel(name = "总金额") + private BigDecimal totalAmount; + /** 库位码(编码) */ @Excel(name = "库位码") private String pcode; @@ -488,6 +492,14 @@ public class RkInfo extends BaseEntity { public String getFycde2() { return fycde2; } public void setFycde2(String fycde2) { this.fycde2 = fycde2; } + public BigDecimal getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(BigDecimal totalAmount) { + this.totalAmount = totalAmount; + } + public String getIsDelete() { return isDelete; } public void setIsDelete(String isDelete) { this.isDelete = isDelete; } diff --git a/src/main/java/com/zg/project/wisdom/domain/dto/OutGoodsDTO.java b/src/main/java/com/zg/project/wisdom/domain/dto/OutGoodsDTO.java index a1dd79f..caaa292 100644 --- a/src/main/java/com/zg/project/wisdom/domain/dto/OutGoodsDTO.java +++ b/src/main/java/com/zg/project/wisdom/domain/dto/OutGoodsDTO.java @@ -1,9 +1,17 @@ package com.zg.project.wisdom.domain.dto; +import com.zg.framework.aspectj.lang.annotation.Excel; import lombok.Data; @Data public class OutGoodsDTO { private String taskNo; private Integer materialStatus; // 0=空托,1=有货(影响是否传物料信息) + /** + * 调度模式 + * 1 = 仅立库(WCS) + * 2 = 立库 + AGV + */ + @Excel(name = "调度模式", readConverterExp = "1=仅立库,2=立库+AGV") + private Integer dispatchMode; } \ No newline at end of file diff --git a/src/main/java/com/zg/project/wisdom/service/impl/AgvTaskResultServiceImpl.java b/src/main/java/com/zg/project/wisdom/service/impl/AgvTaskResultServiceImpl.java index cda0c0a..24431ce 100644 --- a/src/main/java/com/zg/project/wisdom/service/impl/AgvTaskResultServiceImpl.java +++ b/src/main/java/com/zg/project/wisdom/service/impl/AgvTaskResultServiceImpl.java @@ -135,8 +135,12 @@ public class AgvTaskResultServiceImpl implements IAgvTaskResultService { * 货物上架 * @param */ + /** + * 货物上架 + */ @Override public String handleUpGoods(String taskNo, Integer materialStatus) { + Date now = DateUtils.getNowDate(); // 1. 查询调度任务 @@ -172,13 +176,11 @@ public class AgvTaskResultServiceImpl implements IAgvTaskResultService { // 3. 发起调用 String wcsResp = OkHttpUtils.postJson(wcsJobCreateUrl, wcsParam.toJSONString()); - if (StringUtils.isBlank(wcsResp)) { throw new ServiceException("WCS 接口无响应"); } JSONObject json = JSON.parseObject(wcsResp); - if (!json.containsKey("result")) { throw new ServiceException("WCS 返回格式异常:" + wcsResp); } @@ -203,12 +205,19 @@ public class AgvTaskResultServiceImpl implements IAgvTaskResultService { wcs.setUpdateTime(now); wcsTaskResultMapper.insertWcsTaskResult(wcs); - log.info("[上架] 执行成功,任务 {} 状态已更新为已完成", taskNo); + // ✅ 5. 根据 dispatchMode 判断:仅立库(WCS)模式,WCS成功即任务完成 + if (ddTask.getDispatchMode() != null && ddTask.getDispatchMode() == 1) { + ddTaskMapper.updateTaskStatusByTaskNo(taskNo, 2); // 2=已完成 + log.info("[上架] dispatchMode=1,仅立库模式:任务 {} 已更新为已完成(task_status=2)", taskNo); + } else { + log.info("[上架] dispatchMode!=1,非仅立库模式:任务 {} 不在此处置完成,后续流程继续", taskNo); + } // ✅ 返回 taskId return taskId; } + /** * 货物下架 * @param diff --git a/src/main/java/com/zg/project/wisdom/service/impl/RkInfoServiceImpl.java b/src/main/java/com/zg/project/wisdom/service/impl/RkInfoServiceImpl.java index 2b3a349..e491e86 100644 --- a/src/main/java/com/zg/project/wisdom/service/impl/RkInfoServiceImpl.java +++ b/src/main/java/com/zg/project/wisdom/service/impl/RkInfoServiceImpl.java @@ -32,12 +32,15 @@ import com.zg.project.wisdom.utils.BillNoUtil; import com.zg.project.wisdom.utils.CodeConvertUtil; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import com.zg.project.wisdom.mapper.RkInfoMapper; import com.zg.project.wisdom.domain.RkInfo; import com.zg.project.wisdom.service.IRkInfoService; import org.springframework.transaction.annotation.Transactional; +import javax.annotation.Resource; + import static com.zg.common.utils.SecurityUtils.getUsername; /** @@ -91,43 +94,19 @@ public class RkInfoServiceImpl implements IRkInfoService * @return 库存单据主 */ @Override - public List selectRkInfoList(RkInfo rkInfo) { + public List selectRkInfoList(RkInfo rkInfo) + { boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled")); List list = rkInfoMapper.selectRkInfoList(rkInfo); - LocalDate today = LocalDate.now(); - for (RkInfo info : list) { - // 计算库龄 - if (info.getRkTime() != null) { - LocalDate rkDate = info.getRkTime().toInstant() - .atZone(ZoneId.systemDefault()) - .toLocalDate(); - long days = ChronoUnit.DAYS.between(rkDate, today); - info.setStockAge(days); - } - - // 查询现场图片 + 审核结果 - AuditSignature signature = auditSignatureMapper.selectPhotoUrlByRkId(info.getId()); - if (signature != null) { - info.setScenePhotoUrl(signature.getSignUrl()); - info.setAuditResult(signature.getAuditResult()); - info.setApproverId(signature.getApproverId()); - } else { - info.setScenePhotoUrl(null); - info.setAuditResult(null); - info.setApproverId(null); - } - } - - // 审核开启,过滤掉审核结果不是“通过” 且 审核人不为空(说明是审核失败) - if (needAudit) { + // 审核开启时,过滤掉“审核失败”的数据 + if (needAudit) + { list = list.stream() .filter(info -> - // ① 未审核过(approverId 为空)或 - info.getApproverId() == null || - // ② 审核通过 - "1".equals(info.getAuditResult()) + info.getApproverId() == null + || "1".equals(info.getAuditResult()) ) .collect(Collectors.toList()); } @@ -1380,18 +1359,7 @@ public class RkInfoServiceImpl implements IRkInfoService @Override public List selectAllRkInfo(RkInfo query) { - - List list = rkInfoMapper.selectAllRkInfo(query); - - Instant now = Instant.now(); - for (RkInfo ri : list) { - Date rkTime = ri.getRkTime(); - if (rkTime != null) { - long days = Duration.between(rkTime.toInstant(), now).toDays(); - ri.setStockAge(days); - } - } - return list; + return rkInfoMapper.selectAllRkInfo(query); } @Override diff --git a/src/main/java/com/zg/project/wisdom/utils/BillNoUtil.java b/src/main/java/com/zg/project/wisdom/utils/BillNoUtil.java index f5be4dd..e15544b 100644 --- a/src/main/java/com/zg/project/wisdom/utils/BillNoUtil.java +++ b/src/main/java/com/zg/project/wisdom/utils/BillNoUtil.java @@ -4,45 +4,63 @@ import com.zg.project.wisdom.mapper.RkInfoMapper; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.ThreadLocalRandom; +/** + * 单据号生成工具类 + * + * 目标: + * 1) 每次业务操作都生成全新单据号(不依赖历史,不查DB) + * 2) 单号短、可读 + * 3) 避免串单:不同出库操作绝不复用同一个 billNo + * + * 推荐格式: + * CK24011315384227 + * 前缀 + yyMMdd + HHmmss + 2位随机数 + */ public class BillNoUtil { - // 内存缓存:key = prefix + yyyyMMdd - private static final Map DAILY_SEQUENCE_MAP = new ConcurrentHashMap<>(); + private static final String DEFAULT_PREFIX = "RK"; /** - * 生成当天单据号(格式:RK20251222_1) + * 生成单据号(短号,不查数据库) + * 格式:PREFIX + yyMMdd + HHmmss + 2位随机数 + * 示例:CK24011315384227 + * + * @param prefix 单据前缀:RK/CK/... + * @param rkInfoMapper 兼容旧签名(不再使用,可传 null) */ public static synchronized String generateTodayBillNo(String prefix, RkInfoMapper rkInfoMapper) { - // ✅ 先得到最终前缀(不要让 lambda 捕获一个会被重新赋值的变量) - final String finalPrefix = (prefix == null || prefix.trim().isEmpty()) ? "RK" : prefix.trim(); + // 前缀处理 + final String p = (prefix == null || prefix.trim().isEmpty()) + ? DEFAULT_PREFIX + : prefix.trim().toUpperCase(); - // ✅ today / key 也用 final,确保 lambda 可用 - final String today = new SimpleDateFormat("yyyyMMdd").format(new Date()); - final String key = finalPrefix + today; - final String likePrefix = finalPrefix + today + "_"; + // 时间到秒:yyMMddHHmmss(12位) + String time = new SimpleDateFormat("yyMMddHHmmss").format(new Date()); - // 如果当天还没初始化,从数据库取最大号 - AtomicInteger counter = DAILY_SEQUENCE_MAP.computeIfAbsent(key, k -> { + // 2位随机数:10~99(避免同一秒内多次点击撞号) + int rnd = ThreadLocalRandom.current().nextInt(10, 100); - String maxBillNo = rkInfoMapper.selectMaxBillNo(likePrefix); + return p + time + rnd; + } - int start = 1; - if (maxBillNo != null) { - try { - String seqStr = maxBillNo.substring(maxBillNo.lastIndexOf("_") + 1); - start = Integer.parseInt(seqStr) + 1; - } catch (Exception ignored) { - } - } - return new AtomicInteger(start); - }); + /** + * 需要更短的版本(可选) + * 格式:PREFIX + yyMMdd + 4位随机数 + * 示例:CK2401134837 + * + * 注意:比带秒的版本更短,但理论碰撞概率略高(一般够用) + */ + public static synchronized String generateShortBillNo(String prefix) { + final String p = (prefix == null || prefix.trim().isEmpty()) + ? DEFAULT_PREFIX + : prefix.trim().toUpperCase(); - int seq = counter.getAndIncrement(); - return finalPrefix + today + "_" + seq; + String day = new SimpleDateFormat("yyMMdd").format(new Date()); + int rnd4 = ThreadLocalRandom.current().nextInt(1000, 10000); + + return p + day + rnd4; } } diff --git a/src/main/resources/mybatis/wisdom/RkInfoMapper.xml b/src/main/resources/mybatis/wisdom/RkInfoMapper.xml index d68b9dd..6d2f640 100644 --- a/src/main/resources/mybatis/wisdom/RkInfoMapper.xml +++ b/src/main/resources/mybatis/wisdom/RkInfoMapper.xml @@ -67,6 +67,10 @@ + + + + @@ -79,37 +83,85 @@ ri.wl_type, mt.type_name AS wl_type_name, ri.cangku, - wh.warehouse_name AS cangku_name, + wh.warehouse_name AS cangku_name, - -- 新增:大仓/小仓编码和名称 - wh.parent_warehouse_code AS parent_warehouse_code, - wh.parent_warehouse_name AS parent_warehouse_name, - wh.warehouse_code AS warehouse_code, - wh.warehouse_name AS warehouse_name, + wh.parent_warehouse_code AS parent_warehouse_code, + wh.parent_warehouse_name AS parent_warehouse_name, + wh.warehouse_code AS warehouse_code, + wh.warehouse_name AS warehouse_name, + + ri.rk_time, + ri.lihuo_y, + ri.is_chuku, + ri.is_borrowed, + ri.is_delivery, + ri.remark, + + ri.ck_lihuo_y, + ri.ck_type, + sot.type_name AS ck_type_name, + + ri.team_code, + ct.team_name, - ri.rk_time, ri.lihuo_y, ri.is_chuku, ri.is_borrowed, ri.is_delivery,ri.remark, - ri.ck_lihuo_y, ri.ck_type, sot.type_name AS ck_type_name, - ri.team_code, ct.team_name, ri.ck_remark, ri.ly_time, - ri.fycde_1, ri.fycde_2, - ri.borrow_time, ri.return_time, - ri.xj, ri.xm_no, ri.xm_ms, ri.wl_no, ri.wl_ms, ri.xm_no_ck, ri.xm_ms_ck, - ri.gys_no, ri.gys_mc, ri.jh_amt, ri.ht_dj, ri.sap_no, ri.xh, ri.gys_jh_id, - ri.jh_qty, ri.ht_qty, ri.dw, ri.real_qty, - ri.pcode, ri.pcode_id, ri.tray_code, ri.entity_id, - ri.status, ri.has_moved, - ri.create_by, ri.create_time, ri.update_by, ri.update_time, ri.is_delete, - u.user_name AS lihuo_y_name + ri.borrow_time, + ri.return_time, + + ri.xj, + ri.xm_no, + ri.xm_ms, + ri.xm_no_ck, + ri.xm_ms_ck, + + ri.wl_no, + ri.wl_ms, + ri.gys_no, + ri.gys_mc, + + ri.jh_amt, + ri.ht_dj, + ri.sap_no, + ri.xh, + ri.gys_jh_id, + + ri.jh_qty, + ri.ht_qty, + ri.dw, + ri.real_qty, + + ri.pcode, + ri.pcode_id, + ri.tray_code, + ri.entity_id, + + ri.status, + ri.has_moved, + + ri.create_by, + ri.create_time, + ri.update_by, + ri.update_time, + ri.is_delete, + + u.user_name AS lihuo_y_name, + + -- ✅ 库龄(天) + DATEDIFF(CURDATE(), ri.rk_time) AS stock_age, + + -- 总金额 = 单价 × 实际数量 + COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0) AS total_amount FROM rk_info ri - LEFT JOIN stock_in_type st ON ri.rk_type = st.type_code - LEFT JOIN material_type mt ON ri.wl_type = mt.type_code - LEFT JOIN warehouse_info wh ON ri.cangku = wh.warehouse_code - LEFT JOIN stock_out_type sot ON ri.ck_type = sot.type_code + LEFT JOIN stock_in_type st ON ri.rk_type = st.type_code + LEFT JOIN material_type mt ON ri.wl_type = mt.type_code + LEFT JOIN warehouse_info wh ON ri.cangku = wh.warehouse_code + LEFT JOIN stock_out_type sot ON ri.ck_type = sot.type_code LEFT JOIN construction_team ct ON ri.team_code = ct.team_code - LEFT JOIN sys_user u ON ri.lihuo_y = u.user_id + LEFT JOIN sys_user u ON ri.lihuo_y = u.user_id + SELECT