Browse Source

feat(check): 新增盘点模式管理及自动盘盈盘亏处理功能

- 添加盘点模式开关接口,支持获取和更新系统盘点模式状态
- 实现自动盘盈盘亏处理服务,支持批量处理盘点差异结果
- 创建盘点调整相关实体类,包括调整项、标签明细、请求参数和结果实体
- 集成事务处理机制,自动生成盘亏(PK)和盘盈(PY)事务单据
- 提供盘盈盘亏事务记录查询接口,支持查看处理明细和子明细
- 完善库存更新逻辑,处理完成后自动更新标签数量和库存状态
master
常熟吴彦祖 3 weeks ago
parent
commit
2d6be7737b
  1. 131
      src/main/java/com/gaotao/common/utils/CountModeChecker.java
  2. 84
      src/main/java/com/gaotao/modules/check/controller/PhysicalInventoryController.java
  3. 102
      src/main/java/com/gaotao/modules/check/entity/CountAdjustmentItem.java
  4. 119
      src/main/java/com/gaotao/modules/check/entity/CountAdjustmentLabelItem.java
  5. 39
      src/main/java/com/gaotao/modules/check/entity/CountAdjustmentRequest.java
  6. 75
      src/main/java/com/gaotao/modules/check/entity/CountAdjustmentResult.java
  7. 116
      src/main/java/com/gaotao/modules/check/entity/CountAdjustmentTransData.java
  8. 85
      src/main/java/com/gaotao/modules/check/entity/CountAdjustmentTransSubData.java
  9. 116
      src/main/java/com/gaotao/modules/check/mapper/CountAdjustmentMapper.java
  10. 84
      src/main/java/com/gaotao/modules/check/service/CountAdjustmentService.java
  11. 502
      src/main/java/com/gaotao/modules/check/service/impl/CountAdjustmentServiceImpl.java
  12. 152
      src/main/resources/mapper/check/CountAdjustmentMapper.xml

131
src/main/java/com/gaotao/common/utils/CountModeChecker.java

@ -0,0 +1,131 @@
package com.gaotao.common.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* 盘点模式检查工具类 - rqrq
*
* <p>用于快速判断当前是否处于盘点模式如果是盘点模式则抛出异常阻止其他业务操作</p>
*
* <p><b>使用方式</b></p>
* <pre>
* // 1. 注入工具类
* &#64;Autowired
* private CountModeChecker countModeChecker;
*
* // 2. 在业务方法开头调用检查
* public void yourBusinessMethod(String site) {
* countModeChecker.checkNotInCountMode(site); // 如果是盘点模式会抛出异常
* // ... 业务逻辑
* }
*
* // 3. 也可以只获取状态不抛异常
* if (countModeChecker.isInCountMode(site)) {
* // 处理盘点模式的情况
* }
* </pre>
*
* @author rqrq
* @date 2025/12/23
*/
@Component
public class CountModeChecker {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 检查当前是否在盘点模式如果是则抛出异常 - rqrq
*
* <p>业务方法开头调用此方法可以快速阻止盘点模式下的其他操作</p>
*
* @param site 工厂编码
* @throws RuntimeException 如果当前处于盘点模式
* @author rqrq
*/
public void checkNotInCountMode(String site) {
if (isInCountMode(site)) {
throw new RuntimeException("当前处于盘点模式,无法进行此操作,请先关闭盘点模式");
}
}
/**
* 检查当前是否在盘点模式如果是则抛出自定义异常信息 - rqrq
*
* @param site 工厂编码
* @param customMessage 自定义错误信息
* @throws RuntimeException 如果当前处于盘点模式
* @author rqrq
*/
public void checkNotInCountMode(String site, String customMessage) {
if (isInCountMode(site)) {
throw new RuntimeException(customMessage);
}
}
/**
* 判断当前是否处于盘点模式 - rqrq
*
* @param site 工厂编码
* @return true=盘点模式 false=非盘点模式
* @author rqrq
*/
public boolean isInCountMode(String site) {
if (!StringUtils.hasText(site)) {
return false;
}
String countModeValue = getCountModeValue(site);
return "Y".equals(countModeValue);
}
/**
* 获取盘点模式参数值 - rqrq
*
* @param site 工厂编码
* @return Y=盘点模式 N=非盘点模式 null=未配置
* @author rqrq
*/
public String getCountModeValue(String site) {
try {
String sql = "SELECT parameter_value FROM wms_sys_parameter WHERE site = ? AND parameter_key = 'IF_COUNT'";
return jdbcTemplate.queryForObject(sql, String.class, site);
} catch (Exception e) {
// 查询失败或无数据时返回N默认非盘点模式
return "N";
}
}
/**
* 检查当前必须在盘点模式如果不是则抛出异常 - rqrq
*
* <p>盘点相关业务方法开头调用此方法确保只有盘点模式下才能执行</p>
*
* @param site 工厂编码
* @throws RuntimeException 如果当前不在盘点模式
* @author rqrq
*/
public void checkMustInCountMode(String site) {
if (!isInCountMode(site)) {
throw new RuntimeException("当前不在盘点模式,无法进行此操作,请先开启盘点模式");
}
}
/**
* 检查当前必须在盘点模式如果不是则抛出自定义异常信息 - rqrq
*
* @param site 工厂编码
* @param customMessage 自定义错误信息
* @throws RuntimeException 如果当前不在盘点模式
* @author rqrq
*/
public void checkMustInCountMode(String site, String customMessage) {
if (!isInCountMode(site)) {
throw new RuntimeException(customMessage);
}
}
}

84
src/main/java/com/gaotao/modules/check/controller/PhysicalInventoryController.java

@ -428,4 +428,88 @@ public class PhysicalInventoryController {
physicalInventoryService.manualHandleCount(query); physicalInventoryService.manualHandleCount(query);
return R.ok(); return R.ok();
} }
// ==================== 盘点模式管理 ==================== - rqrq
/**
* @Description 获取当前盘点模式状态 - rqrq
* @param query 查询条件包含site
* @return R 返回结果 Y=盘点模式 N=非盘点模式
* @author rqrq
*/
@PostMapping("/getSysIfCount")
public R getSysIfCount(@RequestBody java.util.Map<String, String> query) {
String site = query.get("site");
String result = physicalInventoryService.getSysIfCount(site);
return R.ok().put("value", result);
}
/**
* @Description 开启或关闭盘点模式 - rqrq
* @param query 参数包含sitevalue
* @return R
* @author rqrq
*/
@PostMapping("/updateSysIfCount")
public R updateSysIfCount(@RequestBody java.util.Map<String, String> query) {
String site = query.get("site");
String value = query.get("value");
physicalInventoryService.updateSysIfCount(site, value);
return R.ok();
}
// ==================== 自动盘盈盘亏处理 ==================== - rqrq
@Autowired
private com.gaotao.modules.check.service.CountAdjustmentService countAdjustmentService;
/**
* @Description 检查是否有需要系统处理的异常结果 - rqrq
* @param request 请求参数包含sitecountNo
* @return R 返回需要处理的记录数
* @author rqrq
*/
@PostMapping("/checkSystemHandleCount")
public R checkSystemHandleCount(@RequestBody CountAdjustmentRequest request) {
int count = countAdjustmentService.checkSystemHandleCount(request);
return R.ok().put("count", count);
}
/**
* @Description 执行自动盘盈盘亏处理 - rqrq
* @param request 请求参数包含sitecountNousername
* @return R 返回处理结果
* @author rqrq
*/
@PostMapping("/executeSystemAdjustment")
public R executeSystemAdjustment(@RequestBody CountAdjustmentRequest request) {
CountAdjustmentResult result = countAdjustmentService.executeSystemAdjustment(request);
return R.ok().put("row", result);
}
/**
* @Description 查询盘盈盘亏事务记录 - rqrq
* @param request 请求参数包含sitecountNo
* @return R 返回事务列表
* @author rqrq
*/
@PostMapping("/queryAdjustmentTransList")
public R queryAdjustmentTransList(@RequestBody CountAdjustmentRequest request) {
java.util.List<CountAdjustmentTransData> rows = countAdjustmentService.queryAdjustmentTransList(request);
return R.ok().put("rows", rows);
}
/**
* @Description 查询盘盈盘亏事务子明细 - rqrq
* @param query 请求参数包含sitetransNo
* @return R 返回子明细列表
* @author rqrq
*/
@PostMapping("/queryAdjustmentTransSubList")
public R queryAdjustmentTransSubList(@RequestBody java.util.Map<String, String> query) {
String site = query.get("site");
String transNo = query.get("transNo");
java.util.List<CountAdjustmentTransSubData> rows = countAdjustmentService.queryAdjustmentTransSubList(site, transNo);
return R.ok().put("rows", rows);
}
} }

102
src/main/java/com/gaotao/modules/check/entity/CountAdjustmentItem.java

@ -0,0 +1,102 @@
package com.gaotao.modules.check.entity;
import lombok.Data;
import org.apache.ibatis.type.Alias;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 盘点调整明细项实体类 - 按ERP维度汇总后的盘盈盘亏数据 - rqrq
*
* <p><b>用途</b>用于盘盈盘亏处理时按维度汇总的库存数据</p>
*
* <p><b>汇总维度</b></p>
* <ul>
* <li>site - 工厂编码</li>
* <li>warehouseId - 仓库ID</li>
* <li>locationId - 库位ID</li>
* <li>partNo - 物料号</li>
* <li>batchNo - 批号</li>
* <li>wdr - WDR</li>
* <li>expiredDate - 过期日期</li>
* <li>engChgLevel - 工程变更级别</li>
* </ul>
*
* <p><b>业务逻辑</b></p>
* <pre>
* // 根据diffQty判断盘盈盘亏
* if (diffQty > 0) {
* // 盘盈库存增加
* } else if (diffQty < 0) {
* // 盘亏库存减少
* }
* </pre>
*
* @author rqrq
* @date 2025/12/23
*/
@Data
@Alias("CountAdjustmentItem")
public class CountAdjustmentItem {
// ==================== 维度字段 ====================
/**
* 工厂编码
*/
private String site;
/**
* 仓库ID
*/
private String warehouseId;
/**
* 库位ID
*/
private String locationId;
/**
* 物料号
*/
private String partNo;
/**
* 批号
*/
private String batchNo;
/**
* WDR物料追溯号
*/
private String wdr;
/**
* 过期日期
*/
private Date expiredDate;
/**
* 工程变更级别
*/
private String engChgLevel;
// ==================== 数量字段 ====================
/**
* 汇总后的差异数量
* <p>正数=盘盈负数=盘亏</p>
*/
private BigDecimal diffQty;
// ==================== 关联明细 ====================
/**
* 该维度下的所有标签明细列表
* <p>用于生成trans_detail_sub</p>
*/
private List<CountAdjustmentLabelItem> labelItems;
}

119
src/main/java/com/gaotao/modules/check/entity/CountAdjustmentLabelItem.java

@ -0,0 +1,119 @@
package com.gaotao.modules.check.entity;
import lombok.Data;
import org.apache.ibatis.type.Alias;
import java.math.BigDecimal;
import java.util.Date;
/**
* 盘点调整标签明细实体类 - 标签级别的盘盈盘亏数据 - rqrq
*
* <p><b>用途</b>用于盘盈盘亏处理时记录每个标签的调整信息</p>
*
* <p><b>数据来源</b></p>
* <ul>
* <li>count_result表盘点结果信息</li>
* <li>handling_unit表标签详细信息</li>
* </ul>
*
* <p><b>业务逻辑</b></p>
* <pre>
* // 1. 用于生成trans_detail_sub记录
* // 2. 用于更新handling_unit.qty
* if (newQty <= 0) {
* // 设置in_stock_flag = 'N'
* }
* </pre>
*
* @author rqrq
* @date 2025/12/23
*/
@Data
@Alias("CountAdjustmentLabelItem")
public class CountAdjustmentLabelItem {
// ==================== 盘点结果字段 ====================
/**
* 盘点结果ID
*/
private Long countResultId;
/**
* 盘点单号
*/
private String countNo;
/**
* 盘点结果类型
* <p>QTY_DIFF=数量差异MISSING=缺失</p>
*/
private String countResult;
/**
* 差异数量
* <p>正数=盘盈负数=盘亏</p>
*/
private BigDecimal diffQty;
// ==================== 标签基本信息 ====================
/**
* 工厂编码
*/
private String site;
/**
* 标签号handling_unit.unit_id
*/
private String unitId;
/**
* 栈板号
*/
private String palletId;
// ==================== 库存维度字段 ====================
/**
* 物料号
*/
private String partNo;
/**
* 标签当前数量
*/
private BigDecimal qty;
/**
* 批号
*/
private String batchNo;
/**
* 库位ID
*/
private String locationId;
/**
* 仓库ID
*/
private String warehouseId;
/**
* WDR物料追溯号
*/
private String wdr;
/**
* 过期日期
*/
private Date expiredDate;
/**
* 工程变更级别
*/
private String engChgLevel;
}

39
src/main/java/com/gaotao/modules/check/entity/CountAdjustmentRequest.java

@ -0,0 +1,39 @@
package com.gaotao.modules.check.entity;
import lombok.Data;
import org.apache.ibatis.type.Alias;
/**
* 盘点调整请求参数实体类 - rqrq
*
* <p><b>用途</b>用于自动处理盈亏功能的请求参数传递</p>
*
* <p><b>使用场景</b></p>
* <ul>
* <li>Controller接收前端请求参数</li>
* <li>Service层方法入参</li>
* </ul>
*
* @author rqrq
* @date 2025/12/23
*/
@Data
@Alias("CountAdjustmentRequest")
public class CountAdjustmentRequest {
/**
* 工厂编码必填
*/
private String site;
/**
* 盘点单号必填
*/
private String countNo;
/**
* 操作用户名必填
*/
private String username;
}

75
src/main/java/com/gaotao/modules/check/entity/CountAdjustmentResult.java

@ -0,0 +1,75 @@
package com.gaotao.modules.check.entity;
import lombok.Data;
import org.apache.ibatis.type.Alias;
import java.util.List;
/**
* 盘点调整处理结果实体类 - rqrq
*
* <p><b>用途</b>用于返回自动处理盈亏的执行结果</p>
*
* @author rqrq
* @date 2025/12/23
*/
@Data
@Alias("CountAdjustmentResult")
public class CountAdjustmentResult {
/**
* 处理是否成功
*/
private boolean success;
/**
* 结果消息
*/
private String message;
/**
* 生成的盘亏单据号列表
*/
private List<String> lossTransNoList;
/**
* 生成的盘盈单据号列表
*/
private List<String> profitTransNoList;
/**
* 处理的盘亏记录数
*/
private int lossCount;
/**
* 处理的盘盈记录数
*/
private int profitCount;
/**
* 处理的标签数
*/
private int labelCount;
/**
* 创建成功结果 - rqrq
*/
public static CountAdjustmentResult success(String message) {
CountAdjustmentResult result = new CountAdjustmentResult();
result.setSuccess(true);
result.setMessage(message);
return result;
}
/**
* 创建失败结果 - rqrq
*/
public static CountAdjustmentResult fail(String message) {
CountAdjustmentResult result = new CountAdjustmentResult();
result.setSuccess(false);
result.setMessage(message);
return result;
}
}

116
src/main/java/com/gaotao/modules/check/entity/CountAdjustmentTransData.java

@ -0,0 +1,116 @@
package com.gaotao.modules.check.entity;
import lombok.Data;
import org.apache.ibatis.type.Alias;
import java.math.BigDecimal;
import java.util.Date;
/**
* 盘盈盘亏事务明细实体类 - 用于查询展示 - rqrq
*
* <p><b>用途</b>用于前端盘盈盘亏记录页签左侧列表展示</p>
*
* @author rqrq
* @date 2025/12/23
*/
@Data
@Alias("CountAdjustmentTransData")
public class CountAdjustmentTransData {
// ==================== trans_header字段 ====================
/**
* 工厂编码
*/
private String site;
/**
* 事务号
*/
private String transNo;
/**
* 事务日期
*/
private Date transDate;
/**
* 事务类型
* <p>PK=盘亏PY=盘盈</p>
*/
private String transTypeDb;
/**
* 事务类型描述
*/
private String transTypeDesc;
/**
* 仓库ID
*/
private String warehouseId;
/**
* 关联盘点单号存储在order_ref1
*/
private String orderRef1;
/**
* 备注
*/
private String remark;
/**
* 操作用户
*/
private String userName;
// ==================== trans_detail字段 ====================
/**
* 行号
*/
private Double itemNo;
/**
* 物料号
*/
private String partNo;
/**
* 事务数量
*/
private BigDecimal transQty;
/**
* 批号
*/
private String batchNo;
/**
* 库位ID
*/
private String locationId;
/**
* 方向IN=入库OUT=出库
*/
private String direction;
/**
* 方向描述
*/
private String directionDesc;
/**
* WDR号
*/
private String wdrNo;
/**
* 工程变更级别
*/
private String engChgLevel;
}

85
src/main/java/com/gaotao/modules/check/entity/CountAdjustmentTransSubData.java

@ -0,0 +1,85 @@
package com.gaotao.modules.check.entity;
import lombok.Data;
import org.apache.ibatis.type.Alias;
import java.math.BigDecimal;
/**
* 盘盈盘亏事务子明细实体类 - 用于查询展示 - rqrq
*
* <p><b>用途</b>用于前端盘盈盘亏记录页签右侧列表展示</p>
*
* @author rqrq
* @date 2025/12/23
*/
@Data
@Alias("CountAdjustmentTransSubData")
public class CountAdjustmentTransSubData {
/**
* 工厂编码
*/
private String site;
/**
* 事务号
*/
private String transNo;
/**
* 行号
*/
private Double itemNo;
/**
* 子行号
*/
private String subNo;
/**
* 子数量
*/
private Double subQty;
/**
* 方向IN=入库OUT=出库
*/
private String direction;
/**
* 方向描述
*/
private String directionDesc;
/**
* 标签号
*/
private String handlingUnitId;
/**
* 物料号
*/
private String partNo;
/**
* 批号
*/
private String batchNo;
/**
* 库位ID
*/
private String locationId;
/**
* 工程变更级别
*/
private String engChgLevel;
/**
* 栈板号存储在order_ref1
*/
private String palletId;
}

116
src/main/java/com/gaotao/modules/check/mapper/CountAdjustmentMapper.java

@ -0,0 +1,116 @@
package com.gaotao.modules.check.mapper;
import com.gaotao.modules.check.entity.CountAdjustmentLabelItem;
import com.gaotao.modules.trans.entity.TransDetail;
import com.gaotao.modules.trans.entity.TransDetailSub;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.gaotao.modules.check.entity.CountAdjustmentTransData;
import com.gaotao.modules.check.entity.CountAdjustmentTransSubData;
import java.math.BigDecimal;
import java.util.List;
/**
* 盘点调整Mapper接口 - rqrq
*
* <p><b>功能</b>提供盘盈盘亏处理相关的数据库操作</p>
*
* @author rqrq
* @date 2025/12/23
*/
@Mapper
public interface CountAdjustmentMapper {
/**
* 统计需要系统处理的异常结果数量 - rqrq
*
* @param site 工厂编码
* @param countNo 盘点单号
* @return int 记录数
* @author rqrq
*/
int countSystemHandleRecords(@Param("site") String site, @Param("countNo") String countNo);
/**
* 查询待系统处理的标签明细排除SURPLUS类型- rqrq
*
* <p>关联handling_unit获取标签详细信息</p>
*
* @param site 工厂编码
* @param countNo 盘点单号
* @return List<CountAdjustmentLabelItem> 标签明细列表
* @author rqrq
*/
List<CountAdjustmentLabelItem> querySystemHandleLabels(@Param("site") String site, @Param("countNo") String countNo);
/**
* 插入事务明细 - rqrq
*
* @param detail 事务明细
* @return int 影响行数
* @author rqrq
*/
int insertTransDetail(TransDetail detail);
/**
* 插入事务子明细 - rqrq
*
* @param sub 事务子明细
* @return int 影响行数
* @author rqrq
*/
int insertTransDetailSub(TransDetailSub sub);
/**
* 更新标签数量 - rqrq
*
* @param site 工厂编码
* @param unitId 标签号
* @param qty 新数量
* @param inStockFlag 在库标记
* @param username 操作用户
* @return int 影响行数
* @author rqrq
*/
int updateHandlingUnitQty(@Param("site") String site,
@Param("unitId") String unitId,
@Param("qty") BigDecimal qty,
@Param("inStockFlag") String inStockFlag,
@Param("username") String username);
/**
* 更新盘点结果处理标记 - rqrq
*
* @param countResultId 盘点结果ID
* @param username 操作用户
* @return int 影响行数
* @author rqrq
*/
int updateCountResultHandleFlag(@Param("countResultId") Long countResultId,
@Param("username") String username);
// ==================== 盘盈盘亏记录查询 ==================== - rqrq
/**
* 查询盘盈盘亏事务头和明细 - rqrq
*
* @param site 工厂编码
* @param countNo 盘点单号存储在order_ref1中
* @return List<CountAdjustmentTransData> 事务列表
* @author rqrq
*/
List<CountAdjustmentTransData> queryAdjustmentTransList(@Param("site") String site, @Param("countNo") String countNo);
/**
* 查询事务子明细 - rqrq
*
* @param site 工厂编码
* @param transNo 事务号
* @return List<CountAdjustmentTransSubData> 子明细列表
* @author rqrq
*/
List<CountAdjustmentTransSubData> queryAdjustmentTransSubList(@Param("site") String site, @Param("transNo") String transNo);
}

84
src/main/java/com/gaotao/modules/check/service/CountAdjustmentService.java

@ -0,0 +1,84 @@
package com.gaotao.modules.check.service;
import com.gaotao.modules.check.entity.CountAdjustmentRequest;
import com.gaotao.modules.check.entity.CountAdjustmentResult;
import com.gaotao.modules.check.entity.CountAdjustmentTransData;
import com.gaotao.modules.check.entity.CountAdjustmentTransSubData;
import java.util.List;
/**
* 盘点调整服务接口 - 用于自动处理盘盈盘亏 - rqrq
*
* <p><b>功能说明</b></p>
* <ul>
* <li>自动处理系统处理方式的盘点异常结果</li>
* <li>生成盘亏PK和盘盈PY事务单据</li>
* <li>更新库存和标签数量</li>
* </ul>
*
* <p><b>处理范围</b></p>
* <ul>
* <li>QTY_DIFF数量差异差异数量正数=盘盈负数=盘亏</li>
* <li>MISSING标签缺失差异数量为负数=盘亏</li>
* <li>SURPLUS多出标签不处理需手工处理</li>
* </ul>
*
* @author rqrq
* @date 2025/12/23
*/
public interface CountAdjustmentService {
/**
* 检查是否有需要系统处理的异常结果 - rqrq
*
* @param request 请求参数包含site和countNo
* @return int 需要处理的记录数0表示无需处理
* @author rqrq
*/
int checkSystemHandleCount(CountAdjustmentRequest request);
/**
* 执行自动盘盈盘亏处理 - rqrq
*
* <p><b>处理流程</b></p>
* <ol>
* <li>查询所有待系统处理的盘点结果handle_type='SYSTEM' AND handle_flag='N'</li>
* <li>排除SURPLUS类型多出标签不处理</li>
* <li>关联handling_unit获取标签详细信息</li>
* <li>按ERP维度汇总差异数量</li>
* <li>分离盘盈和盘亏数据</li>
* <li>按仓库分组处理盘亏生成PK单据更新库存</li>
* <li>按仓库分组处理盘盈生成PY单据更新库存</li>
* <li>逐个标签更新handling_unit.qty</li>
* <li>更新盘点结果handle_flag='Y'</li>
* </ol>
*
* @param request 请求参数包含sitecountNousername
* @return CountAdjustmentResult 处理结果
* @author rqrq
*/
CountAdjustmentResult executeSystemAdjustment(CountAdjustmentRequest request);
// ==================== 盘盈盘亏记录查询 ==================== - rqrq
/**
* 查询盘盈盘亏事务记录 - rqrq
*
* @param request 请求参数包含site和countNo
* @return List<CountAdjustmentTransData> 事务列表
* @author rqrq
*/
List<CountAdjustmentTransData> queryAdjustmentTransList(CountAdjustmentRequest request);
/**
* 查询盘盈盘亏事务子明细 - rqrq
*
* @param site 工厂编码
* @param transNo 事务号
* @return List<CountAdjustmentTransSubData> 子明细列表
* @author rqrq
*/
List<CountAdjustmentTransSubData> queryAdjustmentTransSubList(String site, String transNo);
}

502
src/main/java/com/gaotao/modules/check/service/impl/CountAdjustmentServiceImpl.java

@ -0,0 +1,502 @@
package com.gaotao.modules.check.service.impl;
import com.gaotao.modules.check.entity.*;
import com.gaotao.modules.check.mapper.CountAdjustmentMapper;
import com.gaotao.modules.check.service.CountAdjustmentService;
import com.gaotao.modules.trans.entity.TransHeader;
import com.gaotao.modules.trans.entity.TransDetail;
import com.gaotao.modules.trans.entity.TransDetailSub;
import com.gaotao.modules.trans.entity.TransNoControl;
import com.gaotao.modules.trans.service.TransNoControlService;
import com.gaotao.modules.trans.service.TransHeaderService;
import com.gaotao.modules.warehouse.service.InventoryStockService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
/**
* 盘点调整服务实现类 - 自动处理盘盈盘亏 - rqrq
*
* <p><b>处理流程</b></p>
* <ol>
* <li>查询待系统处理的盘点结果handle_type='SYSTEM' AND handle_flag='N'</li>
* <li>排除SURPLUS类型多出标签需手工处理</li>
* <li>关联handling_unit获取标签详细信息</li>
* <li>按ERP维度汇总差异数量</li>
* <li>分离盘盈diffQty>0和盘亏diffQty<0数据</li>
* <li>处理盘亏生成PK单据减少库存</li>
* <li>处理盘盈生成PY单据增加库存</li>
* <li>逐个标签更新handling_unit.qty</li>
* <li>更新盘点结果handle_flag='Y'</li>
* </ol>
*
* @author rqrq
* @date 2025/12/23
*/
@Slf4j
@Service
public class CountAdjustmentServiceImpl implements CountAdjustmentService {
@Autowired
private CountAdjustmentMapper countAdjustmentMapper;
@Autowired
private TransNoControlService transNoService;
@Autowired
private TransHeaderService transHeaderService;
@Autowired
private InventoryStockService inventoryStockService;
/**
* 检查是否有需要系统处理的异常结果 - rqrq
*/
@Override
public int checkSystemHandleCount(CountAdjustmentRequest request) {
log.info("开始检查系统处理异常数量 - rqrq,site={}, countNo={}", request.getSite(), request.getCountNo());
// 参数校验 - rqrq
if (!StringUtils.hasText(request.getSite())) {
throw new RuntimeException("站点不能为空");
}
if (!StringUtils.hasText(request.getCountNo())) {
throw new RuntimeException("盘点单号不能为空");
}
int count = countAdjustmentMapper.countSystemHandleRecords(request.getSite(), request.getCountNo());
log.info("检查系统处理异常数量完成,共{}条记录 - rqrq", count);
return count;
}
/**
* 执行自动盘盈盘亏处理 - rqrq
*/
@Override
@Transactional(rollbackFor = Exception.class)
public CountAdjustmentResult executeSystemAdjustment(CountAdjustmentRequest request) {
log.info("开始执行自动盘盈盘亏处理 - rqrq,site={}, countNo={}, username={}",
request.getSite(), request.getCountNo(), request.getUsername());
// 1. 参数校验 - rqrq
validateRequest(request);
// 2. 查询待系统处理的盘点结果排除SURPLUS类型- rqrq
List<CountAdjustmentLabelItem> labelItems = countAdjustmentMapper.querySystemHandleLabels(
request.getSite(), request.getCountNo());
if (labelItems == null || labelItems.isEmpty()) {
log.info("没有需要系统处理的异常结果 - rqrq");
return CountAdjustmentResult.fail("无异常结果需要系统处理");
}
log.info("查询到待处理标签明细:{}条 - rqrq", labelItems.size());
// 3. 按ERP维度汇总差异数量 - rqrq
List<CountAdjustmentItem> summaryItems = summarizeByErpDimension(labelItems);
log.info("按ERP维度汇总后:{}条记录 - rqrq", summaryItems.size());
// 4. 分离盘盈和盘亏数据 - rqrq
List<CountAdjustmentItem> lossItems = summaryItems.stream()
.filter(item -> item.getDiffQty().compareTo(BigDecimal.ZERO) < 0)
.collect(Collectors.toList());
List<CountAdjustmentItem> profitItems = summaryItems.stream()
.filter(item -> item.getDiffQty().compareTo(BigDecimal.ZERO) > 0)
.collect(Collectors.toList());
log.info("盘亏记录:{}条,盘盈记录:{}条 - rqrq", lossItems.size(), profitItems.size());
// 5. 创建结果对象 - rqrq
CountAdjustmentResult result = new CountAdjustmentResult();
result.setSuccess(true);
result.setLossTransNoList(new ArrayList<>());
result.setProfitTransNoList(new ArrayList<>());
result.setLossCount(lossItems.size());
result.setProfitCount(profitItems.size());
result.setLabelCount(labelItems.size());
// 6. 处理盘亏按仓库分组- rqrq
if (!lossItems.isEmpty()) {
processLossItems(lossItems, request, result);
}
// 7. 处理盘盈按仓库分组- rqrq
if (!profitItems.isEmpty()) {
processProfitItems(profitItems, request, result);
}
// 8. 逐个标签更新handling_unit.qty - rqrq
updateHandlingUnitQty(labelItems, request.getUsername());
// 9. 更新盘点结果handle_flag='Y' - rqrq
updateCountResultHandleFlag(labelItems, request.getUsername());
result.setMessage("处理完成,盘亏" + result.getLossCount() + "条,盘盈" + result.getProfitCount() + "条");
log.info("自动盘盈盘亏处理完成 - rqrq,{}", result.getMessage());
return result;
}
/**
* 参数校验 - rqrq
*/
private void validateRequest(CountAdjustmentRequest request) {
if (!StringUtils.hasText(request.getSite())) {
throw new RuntimeException("站点不能为空");
}
if (!StringUtils.hasText(request.getCountNo())) {
throw new RuntimeException("盘点单号不能为空");
}
if (!StringUtils.hasText(request.getUsername())) {
throw new RuntimeException("操作用户不能为空");
}
}
/**
* 按ERP维度汇总差异数量 - rqrq
*
* <p>汇总维度site + warehouseId + locationId + partNo + batchNo + wdr + expiredDate + engChgLevel</p>
*/
private List<CountAdjustmentItem> summarizeByErpDimension(List<CountAdjustmentLabelItem> labelItems) {
// 使用Map按维度汇总 - rqrq
Map<String, CountAdjustmentItem> summaryMap = new HashMap<>();
for (CountAdjustmentLabelItem label : labelItems) {
// 构建维度Key - rqrq
String key = buildDimensionKey(label);
CountAdjustmentItem item = summaryMap.get(key);
if (item == null) {
item = new CountAdjustmentItem();
item.setSite(label.getSite());
item.setWarehouseId(label.getWarehouseId());
item.setLocationId(label.getLocationId());
item.setPartNo(label.getPartNo());
item.setBatchNo(label.getBatchNo());
item.setWdr(label.getWdr());
item.setExpiredDate(label.getExpiredDate());
item.setEngChgLevel(label.getEngChgLevel());
item.setDiffQty(BigDecimal.ZERO);
item.setLabelItems(new ArrayList<>());
summaryMap.put(key, item);
}
// 累加差异数量 - rqrq
item.setDiffQty(item.getDiffQty().add(label.getDiffQty()));
item.getLabelItems().add(label);
}
return new ArrayList<>(summaryMap.values());
}
/**
* 构建维度Key - rqrq
*/
private String buildDimensionKey(CountAdjustmentLabelItem label) {
return String.join("||",
nullToEmpty(label.getSite()),
nullToEmpty(label.getWarehouseId()),
nullToEmpty(label.getLocationId()),
nullToEmpty(label.getPartNo()),
nullToEmpty(label.getBatchNo()),
nullToEmpty(label.getWdr()),
label.getExpiredDate() != null ? label.getExpiredDate().toString() : "",
nullToEmpty(label.getEngChgLevel())
);
}
private String nullToEmpty(String str) {
return str != null ? str : "";
}
/**
* 处理盘亏按仓库分组- rqrq
*
* <p><b>处理流程</b></p>
* <ol>
* <li>按site+warehouseId分组</li>
* <li>每组生成一个盘亏单据PK</li>
* <li>创建trans_headerorder_ref1=盘点单号</li>
* <li>创建trans_detail按维度</li>
* <li>创建trans_detail_sub按标签</li>
* <li>减少库存</li>
* </ol>
*/
private void processLossItems(List<CountAdjustmentItem> lossItems,
CountAdjustmentRequest request,
CountAdjustmentResult result) {
log.info("开始处理盘亏,共{}条记录 - rqrq", lossItems.size());
// 按site+warehouseId分组 - rqrq
Map<String, List<CountAdjustmentItem>> groupedItems = lossItems.stream()
.collect(Collectors.groupingBy(item -> item.getSite() + "||" + item.getWarehouseId()));
for (Map.Entry<String, List<CountAdjustmentItem>> entry : groupedItems.entrySet()) {
String[] keys = entry.getKey().split("\\|\\|");
String site = keys[0];
String warehouseId = keys.length > 1 ? keys[1] : "";
List<CountAdjustmentItem> items = entry.getValue();
// 生成盘亏单据号 - rqrq
TransNoControl transData = transNoService.getTransNo(site, "PK", 10);
String transNo = transData.getNewTransNo();
log.info("生成盘亏单据号:{} - rqrq", transNo);
// 创建trans_header - rqrq
TransHeader header = createTransHeader(site, transNo, warehouseId, "PK",
request.getCountNo(), request.getUsername());
transHeaderService.save(header);
// 创建trans_detail和trans_detail_sub - rqrq
int itemNo = 1;
for (CountAdjustmentItem item : items) {
// 创建trans_detail - rqrq
TransDetail detail = createTransDetail(transNo, itemNo, item, "OUT");
countAdjustmentMapper.insertTransDetail(detail);
// 创建trans_detail_sub按标签- rqrq
int subNo = 1;
for (CountAdjustmentLabelItem label : item.getLabelItems()) {
TransDetailSub sub = createTransDetailSub(transNo, itemNo, subNo, label, "OUT");
countAdjustmentMapper.insertTransDetailSub(sub);
subNo++;
}
// 减少库存 - rqrq
inventoryStockService.reduceStockWithLock(
item.getSite(),
item.getWarehouseId(),
item.getPartNo(),
item.getBatchNo(),
item.getLocationId(),
item.getDiffQty().abs(),
item.getWdr(),
item.getEngChgLevel()
);
itemNo++;
}
result.getLossTransNoList().add(transNo);
}
log.info("盘亏处理完成,生成{}个单据 - rqrq", result.getLossTransNoList().size());
}
/**
* 处理盘盈按仓库分组- rqrq
*/
private void processProfitItems(List<CountAdjustmentItem> profitItems,
CountAdjustmentRequest request,
CountAdjustmentResult result) {
log.info("开始处理盘盈,共{}条记录 - rqrq", profitItems.size());
// 按site+warehouseId分组 - rqrq
Map<String, List<CountAdjustmentItem>> groupedItems = profitItems.stream()
.collect(Collectors.groupingBy(item -> item.getSite() + "||" + item.getWarehouseId()));
for (Map.Entry<String, List<CountAdjustmentItem>> entry : groupedItems.entrySet()) {
String[] keys = entry.getKey().split("\\|\\|");
String site = keys[0];
String warehouseId = keys.length > 1 ? keys[1] : "";
List<CountAdjustmentItem> items = entry.getValue();
// 生成盘盈单据号 - rqrq
TransNoControl transData = transNoService.getTransNo(site, "PY", 10);
String transNo = transData.getNewTransNo();
log.info("生成盘盈单据号:{} - rqrq", transNo);
// 创建trans_header - rqrq
TransHeader header = createTransHeader(site, transNo, warehouseId, "PY",
request.getCountNo(), request.getUsername());
transHeaderService.save(header);
// 创建trans_detail和trans_detail_sub - rqrq
int itemNo = 1;
for (CountAdjustmentItem item : items) {
// 创建trans_detail - rqrq
TransDetail detail = createTransDetail(transNo, itemNo, item, "IN");
countAdjustmentMapper.insertTransDetail(detail);
// 创建trans_detail_sub按标签- rqrq
int subNo = 1;
for (CountAdjustmentLabelItem label : item.getLabelItems()) {
TransDetailSub sub = createTransDetailSub(transNo, itemNo, subNo, label, "IN");
countAdjustmentMapper.insertTransDetailSub(sub);
subNo++;
}
// 增加库存 - rqrq
inventoryStockService.updateStockWithLock(
item.getSite(),
item.getWarehouseId(),
item.getPartNo(),
item.getBatchNo(),
item.getLocationId(),
item.getWdr(),
item.getDiffQty(),
"N",
item.getEngChgLevel()
);
itemNo++;
}
result.getProfitTransNoList().add(transNo);
}
log.info("盘盈处理完成,生成{}个单据 - rqrq", result.getProfitTransNoList().size());
}
/**
* 创建事务头 - rqrq
*/
private TransHeader createTransHeader(String site, String transNo, String warehouseId,
String transType, String countNo, String username) {
TransHeader header = new TransHeader();
header.setSite(site);
header.setTransNo(transNo);
header.setTransDate(new Date());
header.setTransTypeDb(transType);
header.setWarehouseId(warehouseId);
header.setUserId(username);
header.setUserName(username);
header.setOrderRef1(countNo); // 关联盘点单号 - rqrq
header.setRemark(transType.equals("PK") ? "盘亏处理" : "盘盈处理");
header.setStatus("COMPLETED");
header.setStatusDb("COMPLETED");
header.setEnterDate(new Date());
header.setIfsFlag("N");
return header;
}
/**
* 创建事务明细 - rqrq
*/
private TransDetail createTransDetail(String transNo, int itemNo,
CountAdjustmentItem item, String direction) {
TransDetail detail = new TransDetail();
detail.setSite(item.getSite());
detail.setTransNo(transNo);
detail.setItemNo((double) itemNo);
detail.setPartNo(item.getPartNo());
detail.setTransQty(item.getDiffQty().abs());
detail.setBatchNo(item.getBatchNo());
detail.setLocationId(item.getLocationId());
detail.setDirection(direction);
detail.setWdrNo(item.getWdr());
detail.setEngChgLevel(item.getEngChgLevel());
return detail;
}
/**
* 创建事务子明细 - rqrq
*/
private TransDetailSub createTransDetailSub(String transNo, int itemNo, int subNo,
CountAdjustmentLabelItem label, String direction) {
TransDetailSub sub = new TransDetailSub();
sub.setSite(label.getSite());
sub.setTransNo(transNo);
sub.setItemNo((double) itemNo);
sub.setSubNo(String.valueOf(subNo));
sub.setSubQty(label.getDiffQty().abs().doubleValue());
sub.setDirection(direction);
sub.setHandlingUnitId(label.getUnitId());
sub.setPartNo(label.getPartNo());
sub.setBatchNo(label.getBatchNo());
sub.setLocationId(label.getLocationId());
sub.setEngChgLevel(label.getEngChgLevel());
sub.setOrderRef1(label.getPalletId()); // 记录栈板号 - rqrq
return sub;
}
/**
* 更新标签数量 - rqrq
*
* <p>如果更新后qty<=0设置in_stock_flag='N'</p>
*/
private void updateHandlingUnitQty(List<CountAdjustmentLabelItem> labelItems, String username) {
log.info("开始更新标签数量,共{}个标签 - rqrq", labelItems.size());
for (CountAdjustmentLabelItem label : labelItems) {
// 计算新数量 - rqrq
BigDecimal newQty = label.getQty().add(label.getDiffQty());
String inStockFlag = newQty.compareTo(BigDecimal.ZERO) > 0 ? "Y" : "N";
// 如果新数量小于0设为0 - rqrq
if (newQty.compareTo(BigDecimal.ZERO) < 0) {
newQty = BigDecimal.ZERO;
}
// 更新handling_unit - rqrq
countAdjustmentMapper.updateHandlingUnitQty(
label.getSite(),
label.getUnitId(),
newQty,
inStockFlag,
username
);
}
log.info("标签数量更新完成 - rqrq");
}
/**
* 更新盘点结果处理标记 - rqrq
*/
private void updateCountResultHandleFlag(List<CountAdjustmentLabelItem> labelItems, String username) {
log.info("开始更新盘点结果处理标记 - rqrq");
for (CountAdjustmentLabelItem label : labelItems) {
countAdjustmentMapper.updateCountResultHandleFlag(label.getCountResultId(), username);
}
log.info("盘点结果处理标记更新完成 - rqrq");
}
// ==================== 盘盈盘亏记录查询 ==================== - rqrq
/**
* 查询盘盈盘亏事务记录 - rqrq
*/
@Override
public List<CountAdjustmentTransData> queryAdjustmentTransList(CountAdjustmentRequest request) {
log.info("开始查询盘盈盘亏事务记录 - rqrq,site={}, countNo={}", request.getSite(), request.getCountNo());
if (!StringUtils.hasText(request.getSite())) {
throw new RuntimeException("站点不能为空");
}
if (!StringUtils.hasText(request.getCountNo())) {
throw new RuntimeException("盘点单号不能为空");
}
List<CountAdjustmentTransData> list = countAdjustmentMapper.queryAdjustmentTransList(
request.getSite(), request.getCountNo());
log.info("查询盘盈盘亏事务记录完成,共{}条记录 - rqrq", list != null ? list.size() : 0);
return list;
}
/**
* 查询盘盈盘亏事务子明细 - rqrq
*/
@Override
public List<CountAdjustmentTransSubData> queryAdjustmentTransSubList(String site, String transNo) {
log.info("开始查询盘盈盘亏事务子明细 - rqrq,site={}, transNo={}", site, transNo);
if (!StringUtils.hasText(site)) {
throw new RuntimeException("站点不能为空");
}
if (!StringUtils.hasText(transNo)) {
throw new RuntimeException("事务号不能为空");
}
List<CountAdjustmentTransSubData> list = countAdjustmentMapper.queryAdjustmentTransSubList(site, transNo);
log.info("查询盘盈盘亏事务子明细完成,共{}条记录 - rqrq", list != null ? list.size() : 0);
return list;
}
}

152
src/main/resources/mapper/check/CountAdjustmentMapper.xml

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gaotao.modules.check.mapper.CountAdjustmentMapper">
<!-- rqrq - 统计需要系统处理的异常结果数量 -->
<select id="countSystemHandleRecords" resultType="int">
SELECT COUNT(1)
FROM count_result
WHERE site = #{site}
AND count_no = #{countNo}
AND handle_flag = 'N'
AND handle_type = 'SYSTEM'
AND count_result != 'SURPLUS'
AND count_result != 'OK'
</select>
<!-- rqrq - 查询待系统处理的标签明细(排除SURPLUS类型)-->
<select id="querySystemHandleLabels" resultType="CountAdjustmentLabelItem">
SELECT
r.id AS countResultId,
r.count_no AS countNo,
r.count_result AS countResult,
r.diff_qty AS diffQty,
r.site AS site,
r.unit_id AS unitId,
r.pallet_id AS palletId,
h.part_no AS partNo,
h.qty AS qty,
h.batch_no AS batchNo,
h.location_id AS locationId,
h.warehouse_id AS warehouseId,
h.wdr AS wdr,
h.expired_date AS expiredDate,
h.eng_chg_level AS engChgLevel
FROM count_result r
LEFT JOIN handling_unit h ON r.site = h.site AND r.unit_id = h.unit_id
WHERE r.site = #{site}
AND r.count_no = #{countNo}
AND r.handle_flag = 'N'
AND r.handle_type = 'SYSTEM'
AND r.count_result != 'SURPLUS'
AND r.count_result != 'OK'
ORDER BY r.id
</select>
<!-- rqrq - 插入事务明细 -->
<insert id="insertTransDetail">
INSERT INTO trans_detail (
site, trans_no, item_no, part_no, trans_qty,
batch_no, location_id, direction, wdr_no, eng_chg_level
) VALUES (
#{site}, #{transNo}, #{itemNo}, #{partNo}, #{transQty},
#{batchNo}, #{locationId}, #{direction}, #{wdrNo}, #{engChgLevel}
)
</insert>
<!-- rqrq - 插入事务子明细 -->
<insert id="insertTransDetailSub">
INSERT INTO trans_detail_sub (
site, trans_no, item_no, sub_no, sub_qty, direction,
handling_unit_id, part_no, batch_no, location_id, eng_chg_level, order_ref1
) VALUES (
#{site}, #{transNo}, #{itemNo}, #{subNo}, #{subQty}, #{direction},
#{handlingUnitId}, #{partNo}, #{batchNo}, #{locationId}, #{engChgLevel}, #{orderRef1}
)
</insert>
<!-- rqrq - 更新标签数量 -->
<update id="updateHandlingUnitQty">
UPDATE handling_unit
SET qty = #{qty},
in_stock_flag = #{inStockFlag},
modified_by = #{username},
modified_date = GETDATE()
WHERE site = #{site}
AND unit_id = #{unitId}
</update>
<!-- rqrq - 更新盘点结果处理标记 -->
<update id="updateCountResultHandleFlag">
UPDATE count_result
SET handle_flag = 'Y',
handle_time = GETDATE(),
handle_by = #{username}
WHERE id = #{countResultId}
</update>
<!-- rqrq - 查询盘盈盘亏事务头和明细 -->
<select id="queryAdjustmentTransList" resultType="CountAdjustmentTransData">
SELECT
h.site,
h.trans_no AS transNo,
h.trans_date AS transDate,
h.trans_type_db AS transTypeDb,
CASE h.trans_type_db
WHEN 'PK' THEN '盘亏'
WHEN 'PY' THEN '盘盈'
ELSE h.trans_type_db
END AS transTypeDesc,
h.warehouse_id AS warehouseId,
h.order_ref1 AS orderRef1,
h.remark,
h.user_name AS userName,
d.item_no AS itemNo,
d.part_no AS partNo,
d.trans_qty AS transQty,
d.batch_no AS batchNo,
d.location_id AS locationId,
d.direction,
CASE d.direction
WHEN 'IN' THEN '入库'
WHEN 'OUT' THEN '出库'
ELSE d.direction
END AS directionDesc,
d.wdr_no AS wdrNo,
d.eng_chg_level AS engChgLevel
FROM trans_header h
INNER JOIN trans_detail d ON h.site = d.site AND h.trans_no = d.trans_no
WHERE h.site = #{site}
AND h.order_ref1 = #{countNo}
AND h.trans_type_db IN ('PK', 'PY')
ORDER BY h.trans_no, d.item_no
</select>
<!-- rqrq - 查询事务子明细 -->
<select id="queryAdjustmentTransSubList" resultType="CountAdjustmentTransSubData">
SELECT
site,
trans_no AS transNo,
item_no AS itemNo,
sub_no AS subNo,
sub_qty AS subQty,
direction,
CASE direction
WHEN 'IN' THEN '入库'
WHEN 'OUT' THEN '出库'
ELSE direction
END AS directionDesc,
handling_unit_id AS handlingUnitId,
part_no AS partNo,
batch_no AS batchNo,
location_id AS locationId,
eng_chg_level AS engChgLevel,
order_ref1 AS palletId
FROM trans_detail_sub
WHERE site = #{site}
AND trans_no = #{transNo}
ORDER BY item_no, sub_no
</select>
</mapper>
Loading…
Cancel
Save