From 251be431ad12399fe4ebce599c353d05dcec7888 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 29 十月 2026 12:28:34 +0800
Subject: [PATCH] feat(mes): 支持追溯二维码解析功能
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesServiceImpl.java | 182 ++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 160 insertions(+), 22 deletions(-)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesServiceImpl.java
index 3435611..557e623 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesServiceImpl.java
@@ -5,21 +5,30 @@
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.MesWmReturnableProductSalesLineRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.MesWmReturnSalesPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.MesWmReturnSalesSaveReqVO;
-import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesLineDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesMapper;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
-import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnSalesStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
-import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
+import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
+import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesLineService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import jakarta.annotation.Resource;
@@ -29,17 +38,20 @@
import java.math.BigDecimal;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
* MES 閿�鍞��璐у崟 Service 瀹炵幇绫�
*
- * @author 鑺嬮亾婧愮爜
+ * @author 瓒呯骇绠$悊鍛�
*/
@Service
@Validated
@@ -55,9 +67,15 @@
@Resource
private CrmCustomerApi customerApi;
@Resource
- private MesMdmItemService mdmItemService;
+ private MdmItemApi mdmItemApi;
+ @Resource
+ private MdmUnitMeasureApi mdmUnitMeasureApi;
@Resource
private MesWmTransactionService wmTransactionService;
+ @Resource
+ private MesWmProductSalesMapper productSalesMapper;
+ @Resource
+ private MesWmProductSalesLineService productSalesLineService;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -76,9 +94,14 @@
@Transactional(rollbackFor = Exception.class)
public void updateReturnSales(MesWmReturnSalesSaveReqVO updateReqVO) {
// 1.1 鏍¢獙瀛樺湪 + 鍑嗗涓姸鎬�
- validateReturnSalesExistsAndPrepare(updateReqVO.getId());
+ MesWmReturnSalesDO oldReturnSales = validateReturnSalesExistsAndPrepare(updateReqVO.getId());
// 1.2 鏍¢獙鍏宠仈鏁版嵁
validateSaveData(updateReqVO);
+ // 1.3 宸叉湁閫�璐ц鏃讹紝涓嶅厑璁告洿鎹㈤攢鍞嚭搴撳崟
+ if (ObjUtil.notEqual(oldReturnSales.getProductSalesId(), updateReqVO.getProductSalesId())
+ && CollUtil.isNotEmpty(returnSalesLineService.getReturnSalesLineListByReturnId(updateReqVO.getId()))) {
+ throw exception(WM_RETURN_SALES_PRODUCT_SALES_IMMUTABLE);
+ }
// 2. 鏇存柊涓昏〃
MesWmReturnSalesDO updateObj = BeanUtils.toBean(updateReqVO, MesWmReturnSalesDO.class);
@@ -111,24 +134,113 @@
}
@Override
+ public PageResult<MesWmProductSalesDO> getReturnableProductSalesPage(MesWmProductSalesPageReqVO pageReqVO) {
+ List<Long> permittedClientIds = customerApi.getPermittedCustomerIds();
+ return productSalesMapper.selectReturnablePage(pageReqVO,
+ MesWmProductSalesStatusEnum.FINISHED.getStatus(),
+ MesWmReturnSalesStatusEnum.CANCELED.getStatus(), permittedClientIds);
+ }
+
+ @Override
+ public List<MesWmReturnableProductSalesLineRespVO> getReturnableProductSalesLineList(Long productSalesId) {
+ if (productSalesId == null) {
+ return Collections.emptyList();
+ }
+ MesWmProductSalesDO productSales = productSalesMapper.selectById(productSalesId);
+ if (productSales == null) {
+ throw exception(WM_RETURN_SALES_PRODUCT_SALES_NOT_EXISTS);
+ }
+ List<MesWmProductSalesLineDO> lines = productSalesLineService.getProductSalesLineListBySalesId(productSalesId);
+ if (CollUtil.isEmpty(lines)) {
+ return Collections.emptyList();
+ }
+ Map<Long, BigDecimal> returnedMap = returnSalesLineService.getReturnedQuantityMap(
+ convertList(lines, MesWmProductSalesLineDO::getId), null);
+ // 浠呬繚鐣欏彲閫�鏁伴噺澶т簬 0 鐨勫嚭搴撹
+ List<MesWmProductSalesLineDO> returnableLines = lines.stream()
+ .filter(line -> line.getQuantity() != null
+ && line.getQuantity().subtract(returnedMap.getOrDefault(line.getId(), BigDecimal.ZERO))
+ .compareTo(BigDecimal.ZERO) > 0)
+ .toList();
+ if (CollUtil.isEmpty(returnableLines)) {
+ return Collections.emptyList();
+ }
+ Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
+ convertSet(returnableLines, MesWmProductSalesLineDO::getItemId));
+ Map<Long, MdmUnitMeasureRespDTO> unitMap = CollectionUtils.convertMap(
+ mdmUnitMeasureApi.getUnitMeasureList(convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId))
+ .getCheckedData(),
+ MdmUnitMeasureRespDTO::getId);
+ return returnableLines.stream().map(line -> {
+ MesWmReturnableProductSalesLineRespVO vo = new MesWmReturnableProductSalesLineRespVO();
+ BigDecimal returned = returnedMap.getOrDefault(line.getId(), BigDecimal.ZERO);
+ vo.setProductSalesLineId(line.getId());
+ vo.setSalesId(line.getSalesId());
+ vo.setItemId(line.getItemId());
+ vo.setBatchId(line.getBatchId());
+ vo.setBatchCode(line.getBatchCode());
+ vo.setOutboundQuantity(line.getQuantity());
+ vo.setReturnedQuantity(returned);
+ vo.setReturnableQuantity(line.getQuantity().subtract(returned));
+ MapUtils.findAndThen(itemMap, line.getItemId(), item -> {
+ vo.setItemCode(item.getCode());
+ vo.setItemName(item.getName());
+ vo.setItemSpecification(item.getSpecification());
+ MapUtils.findAndThen(unitMap, item.getUnitMeasureId(), unit -> vo.setItemUnit(unit.getName()));
+ });
+ return vo;
+ }).toList();
+ }
+
+ @Override
@Transactional(rollbackFor = Exception.class)
public void submitReturnSales(Long id) {
// 1.1 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
- validateReturnSalesExistsAndPrepare(id);
+ MesWmReturnSalesDO returnSales = validateReturnSalesExistsAndPrepare(id);
// 1.2 鏍¢獙鑷冲皯鏈変竴鏉¤
List<MesWmReturnSalesLineDO> lines = returnSalesLineService.getReturnSalesLineListByReturnId(id);
if (CollUtil.isEmpty(lines)) {
throw exception(WM_RETURN_SALES_NO_LINE);
}
+ // 1.3 鍏宠仈鍑哄敭搴撳崟鏃讹紝閲嶆柊鏍¢獙琛屽綊灞炰笌鍙��鏁伴噺锛堣崏绋挎湡闂村彲鑳藉凡琚叾瀹冮��璐у崟鍗犵敤锛�
+ if (returnSales.getProductSalesId() != null) {
+ validateLinesAgainstProductSales(returnSales, lines);
+ }
- // 2.1 纭畾鐩爣鐘舵�侊細1锛夋湁寰呮楠岀墿鏂� 鈫� 寰呮楠岀姸鎬侊紱2锛夋棤寰呮楠岀墿鏂� 鈫� 寰呮墽琛岀姸鎬�
- boolean hasPendingQc = CollUtil.contains(lines,
- line -> MesWmQualityStatusEnum.PENDING.getStatus().equals(line.getQualityStatus()));
- Integer targetStatus = hasPendingQc ? MesWmReturnSalesStatusEnum.CONFIRMED.getStatus()
- : MesWmReturnSalesStatusEnum.APPROVING.getStatus();
- // 2.2 鎻愪氦锛堣崏绋� -> 寰呮楠�/寰呮墽琛岋級
+ // 2. 鎻愪氦锛堣崏绋� -> 寰呮墽琛岋級锛涢攢鍞粨搴撴祦杞笉缁忚繃璐ㄦ
returnSalesMapper.updateById(new MesWmReturnSalesDO()
- .setId(id).setStatus(targetStatus));
+ .setId(id).setStatus(MesWmReturnSalesStatusEnum.APPROVING.getStatus()));
+ }
+
+ /**
+ * 鏍¢獙閫�璐у崟鎵�鏈夎鍧囨潵鑷墍閫夌殑閿�鍞嚭搴撳崟锛屼笖鏈崟鍚勮鏁伴噺涔嬪拰涓嶈秴杩囧彲閫�鏁伴噺
+ */
+ private void validateLinesAgainstProductSales(MesWmReturnSalesDO returnSales,
+ List<MesWmReturnSalesLineDO> lines) {
+ if (CollUtil.contains(lines, line -> line.getProductSalesLineId() == null)) {
+ throw exception(WM_RETURN_SALES_LINE_PRODUCT_SALES_REQUIRED);
+ }
+ Map<Long, MesWmProductSalesLineDO> outboundLineMap = convertMap(
+ productSalesLineService.getProductSalesLineListBySalesId(returnSales.getProductSalesId()),
+ MesWmProductSalesLineDO::getId);
+ // 姹囨�绘湰鍗曟瘡涓嚭搴撹鐨勯��璐ф暟閲�
+ Map<Long, BigDecimal> lineQuantityMap = new HashMap<>();
+ for (MesWmReturnSalesLineDO line : lines) {
+ if (!outboundLineMap.containsKey(line.getProductSalesLineId())) {
+ throw exception(WM_RETURN_SALES_LINE_PRODUCT_SALES_NOT_MATCH);
+ }
+ lineQuantityMap.merge(line.getProductSalesLineId(), line.getQuantity(), BigDecimal::add);
+ }
+ // 宸查��鏁伴噺鎺掗櫎鏈崟锛岄伩鍏嶆妸鏈崟鑷韩璁″叆
+ Map<Long, BigDecimal> returnedMap = returnSalesLineService.getReturnedQuantityMap(
+ convertSet(lines, MesWmReturnSalesLineDO::getProductSalesLineId), returnSales.getId());
+ for (Map.Entry<Long, BigDecimal> entry : lineQuantityMap.entrySet()) {
+ BigDecimal returnable = outboundLineMap.get(entry.getKey()).getQuantity()
+ .subtract(returnedMap.getOrDefault(entry.getKey(), BigDecimal.ZERO));
+ if (entry.getValue().compareTo(returnable) > 0) {
+ throw exception(WM_RETURN_SALES_LINE_QUANTITY_EXCEED);
+ }
+ }
}
@Override
@@ -171,7 +283,7 @@
MesWmReturnSalesDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO);
// 瀵规瘮琛屾暟閲忎笌鏄庣粏鎬绘暟閲忥紝涓嶆弧瓒崇洿鎺ユ姏鍑�
if (line.getQuantity().compareTo(totalDetailQuantity) > 0) {
- MesMdmItemDO item = mdmItemService.validateItemExistsAndEnable(line.getItemId());
+ MdmItemRespDTO item = mdmItemApi.validateItemExistsAndEnable(line.getItemId()).getCheckedData();
throw exception(WM_RETURN_SALES_DETAIL_QUANTITY_MISMATCH,
item.getCode() + " " + item.getName() + " 鏈畬鎴愪笂鏋�");
}
@@ -188,13 +300,24 @@
private void createTransactionList(MesWmReturnSalesDO returnSales) {
List<MesWmReturnSalesDetailDO> details = returnSalesDetailService.getReturnSalesDetailListByReturnId(returnSales.getId());
- wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO()
- .setType(MesWmTransactionTypeEnum.IN.getType()).setItemId(detail.getItemId())
- .setQuantity(detail.getQuantity()) // 鍏ュ簱鏁伴噺涓烘鏁�
- .setBatchId(detail.getBatchId()).setBatchCode(detail.getBatchCode())
- .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
- .setBizType(MesBizTypeConstants.WM_RETURN_SALES).setBizId(returnSales.getId())
- .setBizCode(returnSales.getCode()).setBizLineId(detail.getLineId())));
+ // 鏄庣粏鏈甫鎵规鏃跺洖閫�鍒伴��璐ц鎵规锛岄伩鍏嶅巻鍙叉暟鎹叆搴撲涪鎵规
+ Map<Long, MesWmReturnSalesLineDO> lineMap = convertMap(
+ returnSalesLineService.getReturnSalesLineListByReturnId(returnSales.getId()),
+ MesWmReturnSalesLineDO::getId);
+ wmTransactionService.createTransactionList(convertList(details, detail -> {
+ MesWmReturnSalesLineDO line = lineMap.get(detail.getLineId());
+ Long batchId = detail.getBatchId() != null ? detail.getBatchId()
+ : line != null ? line.getBatchId() : null;
+ String batchCode = detail.getBatchCode() != null ? detail.getBatchCode()
+ : line != null ? line.getBatchCode() : null;
+ return new MesWmTransactionSaveReqDTO()
+ .setType(MesWmTransactionTypeEnum.IN.getType()).setItemId(detail.getItemId())
+ .setQuantity(detail.getQuantity()) // 鍏ュ簱鏁伴噺涓烘鏁�
+ .setBatchId(batchId).setBatchCode(batchCode)
+ .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
+ .setBizType(MesBizTypeConstants.WM_RETURN_SALES).setBizId(returnSales.getId())
+ .setBizCode(returnSales.getCode()).setBizLineId(detail.getLineId());
+ }));
}
@Override
@@ -274,6 +397,21 @@
private void validateSaveData(MesWmReturnSalesSaveReqVO reqVO) {
validateCodeUnique(reqVO.getId(), reqVO.getCode());
customerApi.validateCustomer(reqVO.getClientId());
+ // 鍏宠仈閿�鍞嚭搴撳崟鏃讹紝鏍¢獙鍑哄簱鍗曠姸鎬佷笌瀹㈡埛涓�鑷存�э紝骞跺洖濉嚭搴撳崟鍙�
+ if (reqVO.getProductSalesId() == null) {
+ return;
+ }
+ MesWmProductSalesDO productSales = productSalesMapper.selectById(reqVO.getProductSalesId());
+ if (productSales == null) {
+ throw exception(WM_RETURN_SALES_PRODUCT_SALES_NOT_EXISTS);
+ }
+ if (ObjUtil.notEqual(MesWmProductSalesStatusEnum.FINISHED.getStatus(), productSales.getStatus())) {
+ throw exception(WM_RETURN_SALES_PRODUCT_SALES_NOT_FINISHED);
+ }
+ if (ObjUtil.notEqual(productSales.getClientId(), reqVO.getClientId())) {
+ throw exception(WM_RETURN_SALES_CLIENT_MISMATCH);
+ }
+ reqVO.setProductSalesCode(productSales.getCode());
}
}
--
Gitblit v1.9.3