生成单据号逻辑修改

线程池线程数修改
This commit is contained in:
2026-01-14 08:14:04 +08:00
parent 5d227b7b6b
commit 1a565dba0d
10 changed files with 212 additions and 109 deletions

View File

@@ -1116,6 +1116,9 @@ public class ExcelUtil<T>
}
}
/**
* 添加单元格
*/
/**
* 添加单元格
*/
@@ -1126,11 +1129,13 @@ public class ExcelUtil<T>
{
// 设置行高
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<T>
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());
}
// 去掉末尾01.000000 -> 11.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<T>
}
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<T>
// 设置列类型
setCellVo(value, attr, cell);
}
addStatisticsData(column, Convert.toStr(value), attr);
}
}

View File

@@ -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()

View File

@@ -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);
}

View File

@@ -71,13 +71,14 @@ public class RkInfoController extends BaseController
@PostMapping("/pageStatistics")
public Map<String, Object> pageStatistics(@RequestBody RkInfoQueryDTO dto) {
// 使用 PageHelper 分页
// 分页(如果这里不需要总数,后面你可以改成 startPage(..., false)
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
List<RkInfo> list = rkInfoService.selectAllRkInfo(dto);
Map<String,Object> dataInfo = new HashMap<>();
dataInfo.put("dataList",getDataTable(list));
return dataInfo;
List<RkInfo> list = rkInfoService.selectAllRkInfo(dto);
Map<String, Object> dataInfo = new HashMap<>();
dataInfo.put("dataList", getDataTable(list));
return dataInfo;
}
/**

View File

@@ -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; }

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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<RkInfo> selectRkInfoList(RkInfo rkInfo) {
public List<RkInfo> selectRkInfoList(RkInfo rkInfo)
{
boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled"));
List<RkInfo> 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<RkInfo> selectAllRkInfo(RkInfo query) {
List<RkInfo> 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

View File

@@ -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<String, AtomicInteger> 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 + "_";
// 时间到秒yyMMddHHmmss12位
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;
}
}

View File

@@ -67,6 +67,10 @@
<result property="fycde1" column="fycde_1"/>
<result property="fycde2" column="fycde_2"/>
<result property="isDelivery" column="is_delivery"/>
<!-- ✅ 库龄(天) -->
<result property="stockAge" column="stock_age"/>
<!-- 总金额 -->
<result property="totalAmount" column="total_amount"/>
</resultMap>
<!-- 明细查询SQL包含多表JOIN用于普通明细分页等 -->
@@ -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
</sql>
<!-- 轻量分组专用SQL仅rk_info字段不做JOIN供按单据分组内层使用 -->
<sql id="selectRkInfoForGroup">
SELECT