Browse Source

feat(check): 新增PDA手工盘点功能

- 新增PDA扫描栈板接口,支持验证并返回盘点信息
- 新增PDA扫描标签接口,支持获取标签详情
- 新增PDA一键提交盘点接口,默认全部标记为OK
- 新增PDA提交盘点结果接口,支持自定义盘点状态
- 新增相关Mapper方法及XML查询语句,支持PDA盘点数据查询
- 优化创建盘点标签逻辑,不再自动附加同托盘其他物料
- 新增盘点完成后任务单处理逻辑,与RFID接口保持一致
master
常熟吴彦祖 3 weeks ago
parent
commit
c411561973
  1. 66
      src/main/java/com/gaotao/modules/check/controller/PhysicalInventoryController.java
  2. 40
      src/main/java/com/gaotao/modules/check/mapper/PhysicalInventoryMapper.java
  3. 72
      src/main/java/com/gaotao/modules/check/service/PhysicalInventoryService.java
  4. 281
      src/main/java/com/gaotao/modules/check/service/impl/PhysicalInventoryServiceImpl.java
  5. 59
      src/main/resources/mapper/check/PhysicalInventoryMapper.xml

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

@ -310,4 +310,70 @@ public class PhysicalInventoryController {
String taskNo = physicalInventoryService.createReviewTask(site, countNo, reviewType, palletIds, username);
return R.ok().put("taskNo", taskNo);
}
// ==================== PDA手工盘点 ====================
/**
* @Description PDA扫描栈板 - 验证并返回盘点信息 - rqrq
* @param query 包含sitepalletId
* @return R
* @author rqrq
*/
@PostMapping("/pda/scanPallet")
public R pdaScanPallet(@RequestBody java.util.Map<String, String> query) {
String site = query.get("site");
String palletId = query.get("palletId");
java.util.Map<String, Object> result = physicalInventoryService.pdaScanPallet(site, palletId);
return R.ok().put("data", result);
}
/**
* @Description PDA扫描标签 - 获取标签信息 - rqrq
* @param query 包含siteunitId
* @return R
* @author rqrq
*/
@PostMapping("/pda/scanLabel")
public R pdaScanLabel(@RequestBody java.util.Map<String, String> query) {
String site = query.get("site");
String unitId = query.get("unitId");
CountLabelData result = physicalInventoryService.pdaScanLabel(site, unitId);
return R.ok().put("row", result);
}
/**
* @Description PDA一键提交盘点默认全部OK- rqrq
* @param query 包含sitecountNopalletIdusername
* @return R
* @author rqrq
*/
@PostMapping("/pda/quickSubmitCount")
public R pdaQuickSubmitCount(@RequestBody java.util.Map<String, String> query) {
String site = query.get("site");
String countNo = query.get("countNo");
String palletId = query.get("palletId");
String username = query.get("username");
int result = physicalInventoryService.pdaQuickSubmitCount(site, countNo, palletId, username);
return R.ok().put("result", result);
}
/**
* @Description PDA提交盘点结果 - rqrq
* @param query 包含sitecountNopalletIdscannedLabelsusername
* @return R
* @author rqrq
*/
@PostMapping("/pda/submitCount")
@SuppressWarnings("unchecked")
public R pdaSubmitCount(@RequestBody java.util.Map<String, Object> query) {
String site = (String) query.get("site");
String countNo = (String) query.get("countNo");
String palletId = (String) query.get("palletId");
java.util.List<java.util.Map<String, Object>> scannedLabels =
(java.util.List<java.util.Map<String, Object>>) query.get("scannedLabels");
String username = (String) query.get("username");
int result = physicalInventoryService.pdaSubmitCount(site, countNo, palletId, scannedLabels, username);
return R.ok().put("result", result);
}
}

40
src/main/java/com/gaotao/modules/check/mapper/PhysicalInventoryMapper.java

@ -336,6 +336,36 @@ public interface PhysicalInventoryMapper extends BaseMapper<CountHeader> {
*/
int batchUpdateHandlingUnitCountFlagByCountNo(@Param("site") String site, @Param("countNo") String countNo, @Param("countDate") String countDate);
// ==================== PDA手工盘点 ====================
/**
* @Description 根据栈板号查询盘点栈板信息 - rqrq
* @param site 工厂编码
* @param palletId 栈板号
* @return CountPalletData
* @author rqrq
*/
CountPalletData getCountPalletByPalletId(@Param("site") String site, @Param("palletId") String palletId);
/**
* @Description 根据栈板号查询盘点标签明细列表 - rqrq
* @param site 工厂编码
* @param countNo 盘点单号
* @param palletId 栈板号
* @return List<CountLabelData>
* @author rqrq
*/
List<CountLabelData> getCountLabelsByPalletId(@Param("site") String site, @Param("countNo") String countNo, @Param("palletId") String palletId);
/**
* @Description 根据标签号查询标签信息 - rqrq
* @param site 工厂编码
* @param unitId 标签号
* @return CountLabelData
* @author rqrq
*/
CountLabelData getLabelInfoByUnitId(@Param("site") String site, @Param("unitId") String unitId);
// ==================== 物料汇总查询 ====================
/**
@ -497,4 +527,14 @@ public interface PhysicalInventoryMapper extends BaseMapper<CountHeader> {
* @author rqrq
*/
String getCountType(@Param("site") String site, @Param("countNo") String countNo);
/**
* @Description 根据栈板号查询关联的任务单号 - rqrq
* @param site 工厂编码
* @param countNo 盘点单号
* @param palletId 栈板号
* @return String 任务号可能为空
* @author rqrq
*/
String getTaskNoByPallet(@Param("site") String site, @Param("countNo") String countNo, @Param("palletId") String palletId);
}

72
src/main/java/com/gaotao/modules/check/service/PhysicalInventoryService.java

@ -254,4 +254,76 @@ public interface PhysicalInventoryService {
* @author rqrq
*/
boolean hasUncompletedTask(String site, String countNo);
// ==================== PDA手工盘点 ====================
/**
* @Description PDA扫描栈板 - 验证并返回盘点信息 - rqrq
*
* <p><b>业务逻辑</b></p>
* <pre>
* 1. 清洗栈板编码去掉末尾L/R
* 2. 检查是否有状态为CHECKING的盘点单
* 3. 验证栈板是否在盘点栈板明细中
* 4. 返回栈板的盘点标签明细
* </pre>
*
* @param site 工厂编码
* @param palletId 栈板号可能带L/R后缀
* @return Map 包含cleanPalletId, countNo, labelList
* @author rqrq
*/
java.util.Map<String, Object> pdaScanPallet(String site, String palletId);
/**
* @Description PDA扫描标签 - 获取标签信息 - rqrq
* @param site 工厂编码
* @param unitId 标签号
* @return CountLabelData 标签信息
* @author rqrq
*/
CountLabelData pdaScanLabel(String site, String unitId);
/**
* @Description PDA一键提交盘点默认全部OK- rqrq
*
* <p><b>业务逻辑</b></p>
* <pre>
* 1. 查询栈板的盘点标签明细
* 2. 删除本栈板已有的盘点结果
* 3. 插入全部OK的盘点结果
* 4. 更新标签和栈板的盘点状态
* </pre>
*
* @param site 工厂编码
* @param countNo 盘点单号
* @param palletId 栈板号
* @param username 操作人
* @return int 处理的标签数
* @author rqrq
*/
int pdaQuickSubmitCount(String site, String countNo, String palletId, String username);
/**
* @Description PDA提交盘点结果 - rqrq
*
* <p><b>业务逻辑</b></p>
* <pre>
* 1. 查询栈板的盘点标签明细
* 2. 对比本地扫描记录忽略不在明细中的标签
* 3. 删除本栈板已有的盘点结果
* 4. 插入盘点结果OK/MISSING/SURPLUS
* 5. 更新标签和栈板的盘点状态
* </pre>
*
* @param site 工厂编码
* @param countNo 盘点单号
* @param palletId 栈板号
* @param scannedLabels 扫描的标签列表包含unitId, partNo, qty
* @param username 操作人
* @return int 处理的标签数
* @author rqrq
*/
int pdaSubmitCount(String site, String countNo, String palletId,
java.util.List<java.util.Map<String, Object>> scannedLabels, String username);
}

281
src/main/java/com/gaotao/modules/check/service/impl/PhysicalInventoryServiceImpl.java

@ -470,23 +470,7 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl<PhysicalInventoryM
log.info("新增标签数: {},涉及托盘数: {}", selectedLabels.size(), selectedPallets.size());
// 5. 查询同托盘的其他标签EXTRA类型- rqrq
Set<String> assignedUnitIds = selectedLabels.stream()
.map(CountLabelInfo::getUnitId)
.collect(Collectors.toSet());
// 合并已存在的标签ID - rqrq
assignedUnitIds.addAll(existingUnitIds);
List<CountLabelInfo> extraLabels = new ArrayList<>();
for (String palletId : selectedPallets) {
List<CountLabelInfo> extras = baseMapper.queryExtraLabelsByPallet(
query.getSite(), palletId, new ArrayList<>(assignedUnitIds));
extraLabels.addAll(extras);
}
log.info("同托盘关联标签数: {}", extraLabels.size());
// 6. 创建盘点标签子表 - rqrq
// 5. 创建盘点标签子表只添加勾选的物料不再附带同托盘其他物料- rqrq
List<CountLabel> countLabels = new ArrayList<>();
// 添加指定物料标签ASSIGNED- rqrq
@ -496,13 +480,6 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl<PhysicalInventoryM
countLabels.add(countLabel);
}
// 添加同托盘关联标签EXTRA- rqrq
for (CountLabelInfo labelInfo : extraLabels) {
CountLabel countLabel = createCountLabel(query.getSite(), query.getCountNo(), labelInfo,
CountLabel.LABEL_TYPE_EXTRA, query.getUsername());
countLabels.add(countLabel);
}
if (!countLabels.isEmpty()) {
baseMapper.batchInsertCountLabel(countLabels);
}
@ -514,7 +491,7 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl<PhysicalInventoryM
// 合并所有标签用于获取栈板层数 - rqrq
List<CountLabelInfo> allLabelInfos = new ArrayList<>();
allLabelInfos.addAll(selectedLabels);
allLabelInfos.addAll(extraLabels);
// allLabelInfos.addAll(extraLabels);
List<CountPallet> countPallets = new ArrayList<>();
for (String palletId : selectedPallets) {
@ -1501,4 +1478,258 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl<PhysicalInventoryM
log.info("createReviewTask 结束,taskNo: {}", taskNo);
return taskNo;
}
// ==================== PDA手工盘点 ====================
@Override
public java.util.Map<String, Object> pdaScanPallet(String site, String palletId) {
log.info("pdaScanPallet 开始,site: {}, palletId: {}", site, palletId);
// 1. 清洗栈板编码去掉末尾L/R- rqrq
String cleanPalletId = palletId;
if (palletId != null && palletId.length() > 0) {
char lastChar = palletId.charAt(palletId.length() - 1);
if (lastChar == 'R' || lastChar == 'L' || lastChar == 'r' || lastChar == 'l') {
cleanPalletId = palletId.substring(0, palletId.length() - 1);
log.info("栈板码最后一位是R或L,已自动去除,原始={}, 处理后={}", palletId, cleanPalletId);
}
}
// 2. 检查是否有状态为CHECKING的盘点单 - rqrq
CountHeaderData activeCount = getCurrentActiveCount(site);
if (activeCount == null || !CountHeader.STATUS_CHECKING.equals(activeCount.getStatus())) {
throw new RuntimeException("当前没有进行中的盘点单据");
}
String countNo = activeCount.getCountNo();
log.info("找到进行中的盘点单: {}", countNo);
// 3. 验证栈板是否在盘点栈板明细中 - rqrq
CountPalletData countPallet = baseMapper.getCountPalletByPalletId(site, cleanPalletId);
if (countPallet == null) {
throw new RuntimeException("该栈板不在当前盘点单的盘点范围内");
}
// 4. 查询栈板的盘点标签明细 - rqrq
List<CountLabelData> labelList = baseMapper.getCountLabelsByPalletId(site, countNo, cleanPalletId);
log.info("pdaScanPallet 结束,栈板: {}, 标签数: {}", cleanPalletId, labelList.size());
java.util.Map<String, Object> result = new java.util.HashMap<>();
result.put("cleanPalletId", cleanPalletId);
result.put("countNo", countNo);
result.put("labelList", labelList);
result.put("countPallet", countPallet);
return result;
}
@Override
public CountLabelData pdaScanLabel(String site, String unitId) {
log.info("pdaScanLabel 开始,site: {}, unitId: {}", site, unitId);
CountLabelData label = baseMapper.getLabelInfoByUnitId(site, unitId);
if (label == null) {
throw new RuntimeException("标签不存在: " + unitId);
}
log.info("pdaScanLabel 结束,unitId: {}, partNo: {}", unitId, label.getPartNo());
return label;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int pdaQuickSubmitCount(String site, String countNo, String palletId, String username) {
log.info("pdaQuickSubmitCount 开始,site: {}, countNo: {}, palletId: {}", site, countNo, palletId);
// 1. 查询栈板的盘点标签明细 - rqrq
List<CountLabelData> labelList = baseMapper.getCountLabelsByPalletId(site, countNo, palletId);
if (labelList.isEmpty()) {
throw new RuntimeException("该栈板没有盘点标签明细");
}
Date now = new Date();
// 2. 删除本栈板已有的盘点结果 - rqrq
baseMapper.deleteCountResultByPallet(site, countNo, palletId);
log.info("已删除栈板已有的盘点结果");
// 3. 插入全部OK的盘点结果 - rqrq
List<CountResult> resultList = new ArrayList<>();
for (CountLabelData label : labelList) {
CountResult result = new CountResult();
result.setSite(site);
result.setCountNo(countNo);
result.setUnitId(label.getUnitId());
result.setPalletId(palletId);
result.setPartNo(label.getPartNo());
result.setQty(label.getQty());
result.setBatchNo(label.getBatchNo());
result.setLocationId(label.getLocationId());
result.setWarehouseId(label.getWarehouseId());
result.setWdr(label.getWdr());
result.setCountResult(CountResult.RESULT_OK);
result.setCountDate(now);
result.setCountUser(username);
result.setRemark("PDA一键提交-全部OK");
result.setCreatedBy(username);
resultList.add(result);
}
if (!resultList.isEmpty()) {
baseMapper.batchInsertCountResult(resultList);
log.info("已插入盘点结果,数量: {}", resultList.size());
}
// 4. 更新标签盘点状态 - rqrq
baseMapper.updateCountLabelFlagByPallet(site, countNo, palletId, username);
// 5. 更新栈板盘点状态 - rqrq
baseMapper.updateCountPalletFlag(site, countNo, palletId, username);
// 6. 处理任务单与RFID接口一致- rqrq
handleTaskAfterCount(site, countNo, palletId, username);
log.info("pdaQuickSubmitCount 结束,处理标签数: {}", labelList.size());
return labelList.size();
}
@Override
@Transactional(rollbackFor = Exception.class)
public int pdaSubmitCount(String site, String countNo, String palletId,
java.util.List<java.util.Map<String, Object>> scannedLabels, String username) {
log.info("pdaSubmitCount 开始,site: {}, countNo: {}, palletId: {}, 扫描标签数: {}",
site, countNo, palletId, scannedLabels != null ? scannedLabels.size() : 0);
// 1. 查询栈板的盘点标签明细 - rqrq
List<CountLabelData> labelList = baseMapper.getCountLabelsByPalletId(site, countNo, palletId);
if (labelList.isEmpty()) {
throw new RuntimeException("该栈板没有盘点标签明细");
}
// 2. 构建明细unitId集合 - rqrq
Set<String> expectedUnitIds = labelList.stream()
.map(CountLabelData::getUnitId)
.collect(Collectors.toSet());
// 3. 构建扫描记录Map只保留在明细中的- rqrq
java.util.Map<String, java.util.Map<String, Object>> scannedMap = new java.util.HashMap<>();
if (scannedLabels != null) {
for (java.util.Map<String, Object> scanned : scannedLabels) {
String unitId = (String) scanned.get("unitId");
if (expectedUnitIds.contains(unitId)) {
scannedMap.put(unitId, scanned);
} else {
log.info("忽略不在明细中的标签(多扫了): {}", unitId);
}
}
}
Date now = new Date();
// 4. 删除本栈板已有的盘点结果 - rqrq
baseMapper.deleteCountResultByPallet(site, countNo, palletId);
log.info("已删除栈板已有的盘点结果");
// 5. 生成盘点结果 - rqrq
List<CountResult> resultList = new ArrayList<>();
for (CountLabelData label : labelList) {
CountResult result = new CountResult();
result.setSite(site);
result.setCountNo(countNo);
result.setUnitId(label.getUnitId());
result.setPalletId(palletId);
result.setPartNo(label.getPartNo());
result.setQty(label.getQty());
result.setBatchNo(label.getBatchNo());
result.setLocationId(label.getLocationId());
result.setWarehouseId(label.getWarehouseId());
result.setWdr(label.getWdr());
result.setCountDate(now);
result.setCountUser(username);
result.setCreatedBy(username);
if (scannedMap.containsKey(label.getUnitId())) {
// 扫描到了OK
result.setCountResult(CountResult.RESULT_OK);
result.setRemark("PDA扫描确认存在");
} else {
// 没扫描到MISSING
result.setCountResult(CountResult.RESULT_MISSING);
result.setRemark("PDA扫描未发现该标签(盘亏)");
}
resultList.add(result);
}
if (!resultList.isEmpty()) {
baseMapper.batchInsertCountResult(resultList);
log.info("已插入盘点结果,数量: {}", resultList.size());
}
// 6. 更新标签盘点状态 - rqrq
baseMapper.updateCountLabelFlagByPallet(site, countNo, palletId, username);
// 7. 更新栈板盘点状态 - rqrq
baseMapper.updateCountPalletFlag(site, countNo, palletId, username);
// 8. 处理任务单与RFID接口一致- rqrq
handleTaskAfterCount(site, countNo, palletId, username);
log.info("pdaSubmitCount 结束,处理标签数: {}", labelList.size());
return labelList.size();
}
/**
* @Description 盘点完成后处理任务单与RFID接口逻辑一致- rqrq
*
* <p><b>处理逻辑</b></p>
* <ol>
* <li>查询该栈板关联的任务号task_no</li>
* <li>如果有任务号更新wms_order_task_detail状态为已完成</li>
* <li>检查该任务的所有明细是否都已完成</li>
* <li>如果都完成了更新主表状态为"已完成"并触发下一批推送</li>
* </ol>
*
* @param site 工厂编码
* @param countNo 盘点单号
* @param palletId 栈板号
* @param username 操作人
* @author rqrq
*/
private void handleTaskAfterCount(String site, String countNo, String palletId, String username) {
// 1. 查询该栈板关联的任务号 - rqrq
String taskNo = baseMapper.getTaskNoByPallet(site, countNo, palletId);
if (!StringUtils.hasText(taskNo)) {
// 没有任务号说明不是通过推送出库的不需要处理任务单 - rqrq
log.info("该栈板没有关联任务号,跳过任务单处理,palletId: {}", palletId);
return;
}
log.info("开始处理任务单,taskNo: {}, palletId: {}", taskNo, palletId);
// 2. 更新wms_order_task_detail状态为已完成 - rqrq
baseMapper.updateTaskDetailStatusByPallet(site, taskNo, palletId);
log.info("已更新wms_order_task_detail状态为已完成,palletId: {}", palletId);
// 3. 检查该任务的所有明细是否都已完成且主表状态未完成时才触发下一批推送 - rqrq
String taskStatus = baseMapper.getWmsOrderTaskStatus(site, taskNo);
if ("已完成".equals(taskStatus)) {
// 任务已完成说明是重复调用不再触发下一批推送 - rqrq
log.info("wms_order_task已是已完成状态,跳过触发下一批推送(可能是重复调用)");
} else {
int uncompleteCount = baseMapper.checkAllTaskDetailCompletedByTaskNo(site, taskNo);
if (uncompleteCount == 0) {
// 所有明细都已完成更新主表状态 - rqrq
baseMapper.updateTaskStatusCompleted(site, taskNo);
log.info("该任务所有明细已完成,主表状态已更新为已完成");
// 4. 触发下一批推送 - rqrq
CountHeaderData query = new CountHeaderData();
query.setSite(site);
query.setCountNo(countNo);
query.setUsername(username);
int pushedCount = continuePushCount(query);
log.info("触发下一批推送,推送数量: {}", pushedCount);
}
}
}
}

59
src/main/resources/mapper/check/PhysicalInventoryMapper.xml

@ -544,6 +544,58 @@
WHERE cr.site = #{site} AND cr.count_no = #{countNo}
)
</update>
<!-- rqrq - 根据栈板号查询盘点栈板信息(用于PDA手工盘点) -->
<select id="getCountPalletByPalletId" resultType="CountPalletData">
SELECT
cp.site,
cp.count_no AS countNo,
cp.pallet_id AS palletId,
cp.seq_no AS seqNo,
cp.label_count AS labelCount,
cp.checked_count AS checkedCount,
cp.count_flag AS countFlag,
cp.location_z AS locationZ
FROM count_pallet cp
WHERE cp.site = #{site} AND cp.pallet_id = #{palletId}
AND EXISTS (
SELECT 1 FROM count_header ch
WHERE ch.site = cp.site AND ch.count_no = cp.count_no AND ch.status = 'CHECKING'
)
</select>
<!-- rqrq - 根据栈板号查询盘点标签明细列表(用于PDA手工盘点) -->
<select id="getCountLabelsByPalletId" resultType="CountLabelData">
SELECT
cl.site,
cl.count_no AS countNo,
cl.unit_id AS unitId,
cl.part_no AS partNo,
h.part_desc AS partDesc,
cl.qty,
cl.batch_no AS batchNo,
cl.pallet_id AS palletId,
cl.count_flag AS countFlag,
CASE cl.count_flag WHEN 'Y' THEN '已盘点' ELSE '未盘点' END AS countFlagDesc
FROM count_label cl
LEFT JOIN handling_unit h ON h.site = cl.site AND h.unit_id = cl.unit_id
WHERE cl.site = #{site} AND cl.count_no = #{countNo} AND cl.pallet_id = #{palletId}
ORDER BY cl.unit_id
</select>
<!-- rqrq - 根据标签号查询标签信息(用于PDA扫描标签) -->
<select id="getLabelInfoByUnitId" resultType="CountLabelData">
SELECT
h.site,
h.unit_id AS unitId,
h.part_no AS partNo,
h.part_desc AS partDesc,
h.qty,
h.batch_no AS batchNo,
h.wdr
FROM handling_unit h
WHERE h.site = #{site} AND h.unit_id = #{unitId}
</select>
<!-- rqrq - 查询物料汇总(按物料+批号+WDR+仓库+库位汇总)-->
<select id="searchMaterialSummary" resultType="CountMaterialSummary">
@ -861,5 +913,12 @@
<select id="getCountType" resultType="java.lang.String">
SELECT count_type FROM count_header WHERE site = #{site} AND count_no = #{countNo}
</select>
<!-- rqrq - 根据栈板号查询关联的任务单号 -->
<select id="getTaskNoByPallet" resultType="java.lang.String">
SELECT task_no
FROM count_pallet
WHERE site = #{site} AND count_no = #{countNo} AND pallet_id = #{palletId}
</select>
</mapper>
Loading…
Cancel
Save