From 73e8632e1082843e1736d86d3e5fa6a4ee0b4e7d Mon Sep 17 00:00:00 2001
From: liding <756868258@qq.com>
Date: 星期五, 08 五月 2026 13:35:42 +0800
Subject: [PATCH] fix:1.领料,补料,退料,增加库存出入库记录
---
src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java | 1148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1,142 insertions(+), 6 deletions(-)
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
index b4e3826..598ae6b 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
@@ -1,20 +1,1156 @@
package com.ruoyi.production.service.impl;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.enums.ReviewStatusEnum;
+import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
+import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.production.bean.dto.ProductionOrderPickDto;
+import com.ruoyi.production.bean.vo.ProductionOrderPickVo;
+import com.ruoyi.production.mapper.ProductionOperationTaskMapper;
+import com.ruoyi.production.mapper.ProductionOrderMapper;
import com.ruoyi.production.mapper.ProductionOrderPickMapper;
+import com.ruoyi.production.mapper.ProductionOrderPickRecordMapper;
+import com.ruoyi.production.pojo.ProductionOrder;
import com.ruoyi.production.pojo.ProductionOrderPick;
+import com.ruoyi.production.pojo.ProductionOrderPickRecord;
import com.ruoyi.production.service.ProductionOrderPickService;
+import com.ruoyi.stock.dto.StockInventoryDto;
+import com.ruoyi.stock.mapper.StockInventoryMapper;
+import com.ruoyi.stock.pojo.StockInRecord;
+import com.ruoyi.stock.pojo.StockInventory;
+import com.ruoyi.stock.pojo.StockOutRecord;
+import com.ruoyi.stock.service.StockInventoryService;
+import com.ruoyi.stock.service.StockInRecordService;
+import com.ruoyi.stock.service.StockOutRecordService;
+import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
/**
- * <p>
- * 璁㈠崟棰嗘枡绾胯竟浠� 鏈嶅姟瀹炵幇绫�
- * </p>
- *
- * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
- * @since 2026-04-21 03:55:52
+ * 鐢熶骇璁㈠崟棰嗘枡鏈嶅姟瀹炵幇銆�
+ * 璐熻矗棰嗘枡鏂板銆佹洿鏂般�佽ˉ鏂欍�侀��鏂欏強搴撳瓨鑱斿姩銆�
*/
@Service
+@RequiredArgsConstructor
public class ProductionOrderPickServiceImpl extends ServiceImpl<ProductionOrderPickMapper, ProductionOrderPick> implements ProductionOrderPickService {
+ private static final byte PICK_TYPE_NORMAL = 1;
+ private static final byte PICK_TYPE_FEEDING = 2;
+ private static final String PICK_STOCK_OUT_RECORD_TYPE = StockOutQualifiedRecordTypeEnum.PICK_STOCK_OUT.getCode();
+ private static final String FEED_STOCK_OUT_RECORD_TYPE = StockOutQualifiedRecordTypeEnum.FEED_STOCK_OUT.getCode();
+ private static final String PICK_RETURN_IN_RECORD_TYPE = StockInQualifiedRecordTypeEnum.PICK_RETURN_IN.getCode();
+ private static final String FEED_RETURN_IN_RECORD_TYPE = StockInQualifiedRecordTypeEnum.FEED_RETURN_IN.getCode();
+
+ private final ProductionOrderMapper productionOrderMapper;
+ private final ProductionOperationTaskMapper productionOperationTaskMapper;
+ private final ProductionOrderPickRecordMapper productionOrderPickRecordMapper;
+ private final StockInventoryMapper stockInventoryMapper;
+ private final StockInventoryService stockInventoryService;
+ private final StockInRecordService stockInRecordService;
+ private final StockOutRecordService stockOutRecordService;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean savePick(ProductionOrderPickDto dto) {
+ // 棰嗘枡鏂板鎬绘祦绋嬶細
+ // 1) 瑙f瀽鍓嶇琛屾暟鎹苟閫愯鍚堝苟鍙傛暟锛�
+ // 2) 鏍¢獙鍙傛暟涓庢壒娆★紱
+ // 3) 鍏堜繚瀛橀鏂欎富璁板綍锛�
+ // 4) 鍐嶈蛋鈥滃嚭搴撶敵璇� + 瀹℃壒閫氳繃鈥濆畬鎴愬簱瀛樻墸鍑忥紱
+ // 5) 鍐欏叆棰嗘枡娴佹按锛岃褰曟暟閲忓彉鍖栬建杩广��
+ List<ProductionOrderPickDto> pickItems = resolvePickItems(dto);
+ // 閫愯澶勭悊棰嗘枡鏁版嵁锛岃鍙风敤浜庢嫾瑁呯簿纭殑鎶ラ敊淇℃伅銆�
+ for (int i = 0; i < pickItems.size(); i++) {
+ int rowNo = i + 1;
+ ProductionOrderPickDto resolvedDto = mergeDto(dto, pickItems.get(i));
+ // 姣忚閮藉仛瀹屾暣鏍¢獙锛屽紓甯镐俊鎭甫琛屽彿銆�
+ validatePickParam(resolvedDto, rowNo);
+
+ // 缁熶竴澶勭悊鎵规锛堟敮鎸佸崟鎵规/澶氭壒娆★級銆�
+ List<String> batchNoList = resolveBatchNoList(resolvedDto);
+ String inventoryBatchNo = pickInventoryBatchNo(batchNoList);
+ String storedBatchNo = formatBatchNoStorage(batchNoList);
+
+ // 淇濆瓨棰嗘枡涓昏褰曞揩鐓с��
+ ProductionOrderPick orderPick = new ProductionOrderPick();
+ orderPick.setProductionOrderId(resolvedDto.getProductionOrderId());
+ orderPick.setProductModelId(resolvedDto.getProductModelId());
+ orderPick.setBatchNo(storedBatchNo);
+ orderPick.setQuantity(resolvedDto.getPickQuantity());
+ orderPick.setRemark(resolvedDto.getRemark());
+ orderPick.setOperationName(resolvedDto.getOperationName());
+ orderPick.setTechnologyOperationId(resolvedDto.getTechnologyOperationId());
+ orderPick.setDemandedQuantity(resolvedDto.getDemandedQuantity());
+ orderPick.setBom(resolvedDto.getBom());
+ orderPick.setReturned(false);
+ // 鏂板涓昏褰曘��
+ baseMapper.insert(orderPick);
+
+ // 鍏堟柊澧炲嚭搴撶敵璇凤紝鍐嶅鎵归�氳繃锛屽畬鎴愬簱瀛樻墸鍑忋��
+ subtractInventory(orderPick.getId(), resolvedDto.getProductModelId(), storedBatchNo, resolvedDto.getPickQuantity(), rowNo, PICK_STOCK_OUT_RECORD_TYPE);
+
+ // 璁板綍鏈棰嗘枡娴佹按锛坆efore=0锛宎fter=鏈棰嗘枡閲忥級銆�
+ insertPickRecord(orderPick.getId(),
+ resolvedDto.getProductionOrderId(),
+ resolvedDto.getProductionOperationTaskId(),
+ resolvedDto.getProductModelId(),
+ inventoryBatchNo,
+ resolvedDto.getPickQuantity(),
+ BigDecimal.ZERO,
+ resolvedDto.getPickQuantity(),
+ resolvedDto.getPickType(),
+ resolvedDto.getRemark(),
+ resolvedDto.getFeedingReason());
+ }
+ return true;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean updatePick(ProductionOrderPickDto dto) {
+ // 棰嗘枡鏇存柊鍏ュ彛锛堝悓鎺ュ彛鍏煎涓夌被涓氬姟锛夛細
+ // 1) 鏅�氶鏂欐敼閲�/澧炲垹锛�
+ // 2) 琛ユ枡锛坧ickType=2锛夛紱
+ // 3) 閫�鏂欙紙returned=true锛夈��
+ if (dto == null) {
+ throw new ServiceException("鍙傛暟涓嶈兘涓虹┖");
+ }
+ Long productionOrderId = resolveProductionOrderId(dto);
+ if (productionOrderId == null) {
+ throw new ServiceException("鐢熶骇璁㈠崟ID涓嶈兘涓虹┖");
+ }
+ ProductionOrder productionOrder = productionOrderMapper.selectById(productionOrderId);
+ if (productionOrder == null) {
+ throw new ServiceException("鐢熶骇璁㈠崟涓嶅瓨鍦�");
+ }
+
+ // 鏌ヨ璁㈠崟涓嬬幇鏈夐鏂欒褰曞苟鏋勫缓ID绱㈠紩銆�
+ List<ProductionOrderPick> existingPickList = baseMapper.selectList(
+ Wrappers.<ProductionOrderPick>lambdaQuery()
+ .eq(ProductionOrderPick::getProductionOrderId, productionOrderId));
+ // 杞垚Map渚夸簬鍚庣画鎸塈D蹇�熸牎楠屼笌鏇存柊銆�
+ Map<Long, ProductionOrderPick> existingPickMap = existingPickList.stream()
+ .filter(item -> item.getId() != null)
+ .collect(Collectors.toMap(ProductionOrderPick::getId, Function.identity(), (a, b) -> a));
+
+ // 琛ユ枡璇锋眰鍗曠嫭璧拌ˉ鏂欏垎鏀��
+ if (isFeedingRequest(dto)) {
+ processFeedingPickItems(dto, existingPickMap, productionOrderId);
+ return true;
+ }
+ // 閫�鏂欒姹傚崟鐙蛋閫�鏂欏垎鏀��
+ if (isReturnRequest(dto)) {
+ processReturnPickItems(dto, existingPickMap, productionOrderId);
+ return true;
+ }
+
+ // 鏅�氭洿鏂板満鏅厛澶勭悊鏄惧紡鍒犻櫎銆�
+ processDeletePickIds(dto, existingPickMap, productionOrderId);
+
+ List<ProductionOrderPickDto> pickItems = resolveUpdateItems(dto);
+ Set<Long> keepPickIdSet = new HashSet<>();
+ // keepPickIdSet 鐢ㄤ簬鏍囪鏈鍓嶇浠嶇劧淇濈暀鐨勬棫璁板綍锛屽悗缁敤浜庤瘑鍒�滄湭鍥炰紶鍗冲垹闄も�濈殑琛屻��
+ for (int i = 0; i < pickItems.size(); i++) {
+ int rowNo = i + 1;
+ ProductionOrderPickDto resolvedDto = mergeDto(dto, pickItems.get(i));
+ if (isEmptyUpdateItem(resolvedDto)) {
+ continue;
+ }
+ if (resolvedDto.getProductionOrderId() == null) {
+ resolvedDto.setProductionOrderId(productionOrderId);
+ }
+ validatePickParam(resolvedDto, rowNo);
+
+ if (resolvedDto.getId() == null) {
+ addNewPickInUpdate(resolvedDto, rowNo);
+ continue;
+ }
+ keepPickIdSet.add(resolvedDto.getId());
+ updateExistingPick(resolvedDto, rowNo, existingPickMap);
+ }
+ // 娓呯悊鍓嶇鏈洖浼犳棫琛屽苟鍥炶ˉ搴撳瓨銆�
+ processMissingPickItems(dto, existingPickMap, productionOrderId, keepPickIdSet);
+ return true;
+ }
+
+ @Override
+ public List<ProductionOrderPickVo> listPickedDetail(Long productionOrderId) {
+ // 鏌ヨ璁㈠崟棰嗘枡鏄庣粏锛屽苟琛ラ綈鎵规灞曠ず瀛楁銆�
+ if (productionOrderId == null) {
+ return Collections.emptyList();
+ }
+ List<ProductionOrderPickVo> detailList = baseMapper.listPickedDetailByOrderId(productionOrderId);
+ fillBatchNoList(detailList);
+ fillSelectableBatchNoList(detailList);
+ return detailList;
+ }
+
+ private void processDeletePickIds(ProductionOrderPickDto rootDto,
+ Map<Long, ProductionOrderPick> existingPickMap,
+ Long productionOrderId) {
+ // 澶勭悊鍓嶇鏄惧紡鍒犻櫎ID锛�
+ // 1) 鏍¢獙鍒犻櫎鐩爣鏄惁灞炰簬褰撳墠璁㈠崟锛�
+ // 2) 鍥炶ˉ搴撳瓨锛�
+ // 3) 鍒犻櫎涓昏褰曪紱
+ // 4) 璁板綍鍒犻櫎娴佹按銆�
+ if (rootDto.getDeletePickIds() == null || rootDto.getDeletePickIds().isEmpty()) {
+ return;
+ }
+ Set<Long> deleteIdSet = new LinkedHashSet<>(rootDto.getDeletePickIds());
+ for (Long deleteId : deleteIdSet) {
+ if (deleteId == null) {
+ continue;
+ }
+ ProductionOrderPick existingPick = existingPickMap.get(deleteId);
+ if (existingPick == null || !Objects.equals(existingPick.getProductionOrderId(), productionOrderId)) {
+ throw new ServiceException("鍒犻櫎澶辫触锛氶鏂欒褰曚笉瀛樺湪鎴栦笉灞炰簬褰撳墠璁㈠崟锛孖D=" + deleteId);
+ }
+ String oldBatchNo = resolveInventoryBatchNoFromStored(existingPick.getBatchNo());
+ BigDecimal oldQuantity = defaultDecimal(existingPick.getQuantity());
+ addInventory(existingPick.getId(), existingPick.getProductModelId(), oldBatchNo, oldQuantity, PICK_RETURN_IN_RECORD_TYPE);
+ // 鍒犻櫎鍏宠仈棰嗘枡娴佹按锛岄伩鍏嶉仐鐣欐棤涓昏褰曘��
+ productionOrderPickRecordMapper.delete(
+ Wrappers.<ProductionOrderPickRecord>lambdaQuery()
+ .eq(ProductionOrderPickRecord::getPickId, existingPick.getId())
+ );
+ int affected = baseMapper.deleteById(deleteId);
+ if (affected <= 0) {
+ throw new ServiceException("鍒犻櫎棰嗘枡璁板綍澶辫触锛孖D=" + deleteId);
+ }
+ insertPickRecord(existingPick.getId(),
+ existingPick.getProductionOrderId(),
+ rootDto.getProductionOperationTaskId(),
+ existingPick.getProductModelId(),
+ oldBatchNo,
+ oldQuantity,
+ oldQuantity,
+ BigDecimal.ZERO,
+ rootDto.getPickType(),
+ rootDto.getRemark(),
+ rootDto.getFeedingReason());
+ existingPickMap.remove(deleteId);
+ }
+ }
+
+ private void processMissingPickItems(ProductionOrderPickDto rootDto,
+ Map<Long, ProductionOrderPick> existingPickMap,
+ Long productionOrderId,
+ Set<Long> keepPickIdSet) {
+ // 澶勭悊鈥滃墠绔湭鍥炰紶鈥濈殑鏃ц锛�
+ // 瀵瑰簲鍦烘櫙鏄敤鎴峰湪鍓嶇鍒犻櫎琛屼絾鏈斁鍏� deletePickIds銆�
+ // 杩欓噷鍏滃簳璇嗗埆骞舵墽琛屽洖琛ュ簱瀛� + 鍒犻櫎涓昏褰� + 鍐欐祦姘淬��
+ if (rootDto.getPickList() == null) {
+ return;
+ }
+ List<ProductionOrderPick> missingPickList = existingPickMap.values().stream()
+ .filter(Objects::nonNull)
+ .filter(item -> item.getId() != null)
+ .filter(item -> Objects.equals(item.getProductionOrderId(), productionOrderId))
+ .filter(item -> !keepPickIdSet.contains(item.getId()))
+ .toList();
+ for (ProductionOrderPick missingPick : missingPickList) {
+ String oldBatchNo = resolveInventoryBatchNoFromStored(missingPick.getBatchNo());
+ BigDecimal oldQuantity = defaultDecimal(missingPick.getQuantity());
+ addInventory(missingPick.getId(), missingPick.getProductModelId(), oldBatchNo, oldQuantity, PICK_RETURN_IN_RECORD_TYPE);
+ // 鍒犻櫎鍏宠仈棰嗘枡娴佹按锛岄伩鍏嶉仐鐣欐棤涓昏褰曘��
+ productionOrderPickRecordMapper.delete(
+ Wrappers.<ProductionOrderPickRecord>lambdaQuery()
+ .eq(ProductionOrderPickRecord::getPickId, missingPick.getId())
+ );
+ int affected = baseMapper.deleteById(missingPick.getId());
+ if (affected <= 0) {
+ throw new ServiceException("鍒犻櫎鏈洖浼犻鏂欒褰曞け璐ワ紝ID=" + missingPick.getId());
+ }
+ insertPickRecord(missingPick.getId(),
+ missingPick.getProductionOrderId(),
+ rootDto.getProductionOperationTaskId(),
+ missingPick.getProductModelId(),
+ oldBatchNo,
+ oldQuantity,
+ oldQuantity,
+ BigDecimal.ZERO,
+ rootDto.getPickType(),
+ rootDto.getRemark(),
+ rootDto.getFeedingReason());
+ existingPickMap.remove(missingPick.getId());
+ }
+ }
+
+ private void addNewPickInUpdate(ProductionOrderPickDto dto, int rowNo) {
+ // 鏇存柊鍦烘櫙涓嬫柊澧炰竴鏉¢鏂欙細
+ // 鏂板涓昏褰� -> 鍑哄簱鐢宠骞跺鎵� -> 鍐欐祦姘淬��
+ List<String> batchNoList = resolveBatchNoList(dto);
+ String inventoryBatchNo = pickInventoryBatchNo(batchNoList);
+ String storedBatchNo = formatBatchNoStorage(batchNoList);
+
+ ProductionOrderPick orderPick = new ProductionOrderPick();
+ orderPick.setProductionOrderId(dto.getProductionOrderId());
+ orderPick.setProductModelId(dto.getProductModelId());
+ orderPick.setBatchNo(storedBatchNo);
+ orderPick.setQuantity(dto.getPickQuantity());
+ orderPick.setRemark(dto.getRemark());
+ orderPick.setOperationName(dto.getOperationName());
+ orderPick.setTechnologyOperationId(dto.getTechnologyOperationId());
+ orderPick.setDemandedQuantity(dto.getDemandedQuantity());
+ orderPick.setBom(dto.getBom());
+ orderPick.setReturned(false);
+ baseMapper.insert(orderPick);
+
+ // 鍏堟柊澧炲嚭搴撶敵璇凤紝鍐嶅鎵归�氳繃锛屽畬鎴愬簱瀛樻墸鍑忋��
+ subtractInventory(orderPick.getId(), dto.getProductModelId(), storedBatchNo, dto.getPickQuantity(), rowNo, PICK_STOCK_OUT_RECORD_TYPE);
+
+ insertPickRecord(orderPick.getId(),
+ dto.getProductionOrderId(),
+ dto.getProductionOperationTaskId(),
+ dto.getProductModelId(),
+ inventoryBatchNo,
+ dto.getPickQuantity(),
+ BigDecimal.ZERO,
+ dto.getPickQuantity(),
+ dto.getPickType(),
+ dto.getRemark(),
+ dto.getFeedingReason());
+ }
+
+ private void processFeedingPickItems(ProductionOrderPickDto rootDto,
+ Map<Long, ProductionOrderPick> existingPickMap,
+ Long productionOrderId) {
+ // 琛ユ枡娴佺▼鍏ュ彛锛�
+ // 閫愯鏍¢獙琛ユ枡鍙傛暟锛屾牎楠屽師棰嗘枡褰掑睘锛屽啀鎵ц琛ユ枡搴撳瓨鎵e噺鍜屼富璁板綍鍥炲啓銆�
+ List<ProductionOrderPickDto> pickItems = resolveUpdateItems(rootDto);
+ for (int i = 0; i < pickItems.size(); i++) {
+ int rowNo = i + 1;
+ ProductionOrderPickDto resolvedDto = mergeDto(rootDto, pickItems.get(i));
+ if (isEmptyUpdateItem(resolvedDto)) {
+ continue;
+ }
+ if (!isFeedingPick(resolvedDto)) {
+ throw new ServiceException("琛ユ枡璇锋眰涓瓨鍦ㄩ潪琛ユ枡绫诲瀷鏁版嵁");
+ }
+ if (resolvedDto.getProductionOrderId() == null) {
+ resolvedDto.setProductionOrderId(productionOrderId);
+ }
+ validateFeedingParam(resolvedDto, rowNo);
+
+ ProductionOrderPick oldPick = existingPickMap.get(resolvedDto.getId());
+ if (oldPick == null || !Objects.equals(oldPick.getProductionOrderId(), productionOrderId)) {
+ throw new ServiceException("绗�" + rowNo + "琛岃ˉ鏂欏け璐ワ細鏈壘鍒板搴旂殑棰嗘枡璁板綍");
+ }
+ addFeedingPick(resolvedDto, oldPick, rowNo);
+ }
+ }
+
+ private void addFeedingPick(ProductionOrderPickDto dto, ProductionOrderPick oldPick, int rowNo) {
+ // 琛ユ枡鏍稿績锛�
+ // 1) 鏍¢獙瑙勬牸涓�鑷达紱
+ // 2) 鎵e噺琛ユ枡搴撳瓨锛�
+ // 3) 鍐欒ˉ鏂欐祦姘达紱
+ // 4) 鍥炲啓涓诲崟绱琛ユ枡閲忓拰瀹為檯閲忋��
+ if (dto.getProductModelId() != null && !Objects.equals(dto.getProductModelId(), oldPick.getProductModelId())) {
+ throw new ServiceException("绗�" + rowNo + "琛岃ˉ鏂欏け璐ワ細浜у搧瑙勬牸涓庡師棰嗘枡璁板綍涓嶄竴鑷�");
+ }
+ Long productModelId = oldPick.getProductModelId();
+ List<String> batchNoList = resolveBatchNoList(dto);
+ String inventoryBatchNo = batchNoList.isEmpty()
+ ? resolveInventoryBatchNoFromStored(oldPick.getBatchNo())
+ : formatBatchNoStorage(batchNoList);
+ BigDecimal feedingQuantity = dto.getFeedingQuantity();
+
+ subtractInventory(oldPick.getId(), productModelId, inventoryBatchNo, feedingQuantity, rowNo, FEED_STOCK_OUT_RECORD_TYPE);
+
+ // 璁$畻琛ユ枡鍓嶅悗鏁伴噺骞跺啓琛ユ枡娴佹按銆�
+ BigDecimal beforeFeedingQty = sumFeedingQuantity(dto.getProductionOrderId(), oldPick.getId());
+ BigDecimal afterFeedingQty = beforeFeedingQty.add(feedingQuantity);
+ insertPickRecord(oldPick.getId(),
+ dto.getProductionOrderId(),
+ dto.getProductionOperationTaskId(),
+ productModelId,
+ inventoryBatchNo,
+ feedingQuantity,
+ beforeFeedingQty,
+ afterFeedingQty,
+ PICK_TYPE_FEEDING,
+ dto.getRemark(),
+ dto.getFeedingReason());
+
+ ProductionOrderPick updatePick = new ProductionOrderPick();
+ updatePick.setId(oldPick.getId());
+ updatePick.setFeedingQty(afterFeedingQty);
+ updatePick.setActualQty(calculateActualQty(oldPick, afterFeedingQty));
+ // 鍥炲啓涓昏褰曠殑琛ユ枡绱鍊间笌瀹為檯鐢ㄩ噺銆�
+ int affected = baseMapper.updateById(updatePick);
+ if (affected <= 0) {
+ throw new ServiceException("绗�" + rowNo + "琛岃ˉ鏂欏け璐ワ細鏇存柊棰嗘枡涓昏褰曞け璐�");
+ }
+ oldPick.setFeedingQty(afterFeedingQty);
+ oldPick.setActualQty(updatePick.getActualQty());
+ }
+
+ private void processReturnPickItems(ProductionOrderPickDto rootDto,
+ Map<Long, ProductionOrderPick> existingPickMap,
+ Long productionOrderId) {
+ // 閫�鏂欐祦绋嬪叆鍙o細
+ // 閫愯鏍¢獙閫�鏂欏弬鏁颁笌棰嗘枡褰掑睘锛屽啀鏇存柊閫�鏂欓噺涓庡疄闄呴噺瀛楁銆�
+ List<ProductionOrderPickDto> pickItems = resolveUpdateItems(rootDto);
+ for (int i = 0; i < pickItems.size(); i++) {
+ int rowNo = i + 1;
+ ProductionOrderPickDto resolvedDto = mergeDto(rootDto, pickItems.get(i));
+ if (isEmptyUpdateItem(resolvedDto)) {
+ continue;
+ }
+ if (resolvedDto.getProductionOrderId() == null) {
+ resolvedDto.setProductionOrderId(productionOrderId);
+ }
+ validateReturnParam(resolvedDto, rowNo);
+
+ ProductionOrderPick oldPick = existingPickMap.get(resolvedDto.getId());
+ if (oldPick == null || !Objects.equals(oldPick.getProductionOrderId(), productionOrderId)) {
+ throw new ServiceException("绗�" + rowNo + "琛岄��鏂欏け璐ワ細鏈壘鍒板搴旂殑棰嗘枡璁板綍");
+ }
+ updateReturnPick(resolvedDto, oldPick, rowNo);
+ }
+ }
+
+ private void updateReturnPick(ProductionOrderPickDto dto, ProductionOrderPick oldPick, int rowNo) {
+ // 閫�鏂欐洿鏂帮細
+ // 1) returnQty 鎸夆�滄湰娆¢��鏂欓噺鈥濆鐞嗭紱
+ // 2) 鏈閫�鏂欓噺鍥炶ˉ鍒扳�滅敓浜ч��鏂欏叆搴撯�濓紱
+ // 3) 绱姞涓昏褰曢��鏂欐�婚噺骞堕噸绠楀疄闄呴噺銆�
+ BigDecimal oldReturnQty = defaultDecimal(oldPick.getReturnQty());
+ BigDecimal currentReturnQty = defaultDecimal(dto.getReturnQty());
+ BigDecimal totalReturnQty = oldReturnQty.add(currentReturnQty);
+ if (currentReturnQty.compareTo(BigDecimal.ZERO) > 0) {
+ String returnBatchNo = resolveInventoryBatchNoFromStored(oldPick.getBatchNo());
+ addInventory(oldPick.getId(), oldPick.getProductModelId(), returnBatchNo, currentReturnQty, FEED_RETURN_IN_RECORD_TYPE);
+ }
+
+ BigDecimal actualQty = defaultDecimal(oldPick.getQuantity())
+ .add(defaultDecimal(oldPick.getFeedingQty()))
+ .subtract(totalReturnQty);
+ if (actualQty.compareTo(BigDecimal.ZERO) < 0) {
+ throw new ServiceException("绗�" + rowNo + "琛岄��鏂欏け璐ワ細绱閫�鏂欐暟閲忎笉鑳藉ぇ浜庡彲鐢ㄦ暟閲�");
+ }
+ ProductionOrderPick updatePick = new ProductionOrderPick();
+ updatePick.setId(oldPick.getId());
+ updatePick.setReturnQty(totalReturnQty);
+ updatePick.setActualQty(actualQty);
+ updatePick.setReturned(totalReturnQty.compareTo(BigDecimal.ZERO) > 0);
+ int affected = baseMapper.updateById(updatePick);
+ if (affected <= 0) {
+ throw new ServiceException("绗�" + rowNo + "琛岄��鏂欏け璐ワ細鏇存柊棰嗘枡涓昏褰曞け璐�");
+ }
+ oldPick.setReturnQty(updatePick.getReturnQty());
+ oldPick.setActualQty(updatePick.getActualQty());
+ oldPick.setReturned(updatePick.getReturned());
+ }
+
+ private void updateExistingPick(ProductionOrderPickDto dto,
+ int rowNo,
+ Map<Long, ProductionOrderPick> existingPickMap) {
+ // 鏅�氭洿鏂板崟琛屾牳蹇冩祦绋嬶細
+ // 1) 鏍¢獙鏃ц褰曞瓨鍦ㄤ笖灞炰簬褰撳墠璁㈠崟锛�
+ // 2) 姣旇緝鏂版棫鈥滆鏍�+鎵规鈥濓紝鍐冲畾搴撳瓨澶勭悊绛栫暐锛�
+ // 3) 鏇存柊涓昏褰曪紱
+ // 4) 鍐欏彉鏇存祦姘达紙璁板綍鍓嶅悗鏁伴噺鍙樺寲锛夈��
+ ProductionOrderPick oldPick = existingPickMap.get(dto.getId());
+ if (oldPick == null || !Objects.equals(oldPick.getProductionOrderId(), dto.getProductionOrderId())) {
+ throw new ServiceException("绗�" + rowNo + "琛屾洿鏂板け璐ワ細鏈壘鍒板搴旂殑棰嗘枡璁板綍");
+ }
+
+ Long oldProductModelId = oldPick.getProductModelId();
+ String oldBatchNo = resolveInventoryBatchNoFromStored(oldPick.getBatchNo());
+ BigDecimal oldQuantity = defaultDecimal(oldPick.getQuantity());
+
+ Long newProductModelId = dto.getProductModelId();
+ List<String> newBatchNoList = resolveBatchNoList(dto);
+ String newBatchNo = pickInventoryBatchNo(newBatchNoList);
+ String newStoredBatchNo = formatBatchNoStorage(newBatchNoList);
+ BigDecimal newQuantity = dto.getPickQuantity();
+
+ // 鍒ゆ柇瑙勬牸+鎵规鎴栨暟閲忔槸鍚﹀彉鍖栵紝骞舵寜鍦烘櫙澶勭悊搴撳瓨锛�
+ // 1) 鍚岃鏍煎悓鎵规锛氭寜宸�煎鐞嗭紙澧為噺鎵e噺 / 鍑忛噺鍥為��锛夛紱
+ // 2) 瑙勬牸鎴栨壒娆″彉鍖栵細鍥為��鏃ч鏂欏悗鍐嶉噸鎻愭柊棰嗘枡銆�
+ boolean sameStockKey = Objects.equals(oldProductModelId, newProductModelId)
+ && Objects.equals(oldBatchNo, newBatchNo);
+ boolean quantityChanged = oldQuantity.compareTo(newQuantity) != 0;
+ boolean needReissuePickRecord = !sameStockKey || quantityChanged;
+ if (sameStockKey) {
+ BigDecimal deltaQuantity = newQuantity.subtract(oldQuantity);
+ if (deltaQuantity.compareTo(BigDecimal.ZERO) > 0) {
+ // 鏁伴噺澧炲姞锛屽彧鎵e噺鏂板閮ㄥ垎銆�
+ subtractInventory(oldPick.getId(), newProductModelId, newStoredBatchNo, deltaQuantity, rowNo, PICK_STOCK_OUT_RECORD_TYPE);
+ } else if (deltaQuantity.compareTo(BigDecimal.ZERO) < 0) {
+ // 鏁伴噺鍑忓皯锛屽彧鍥為��宸�奸儴鍒嗐��
+ addInventory(oldPick.getId(), oldProductModelId, oldBatchNo, deltaQuantity.abs(), PICK_RETURN_IN_RECORD_TYPE);
+ }
+ } else {
+ // 瑙勬牸鎴栨壒娆″彉鍖栵細鍏堝叏閲忓洖閫�鏃ч鏂欙紝鍐嶅叏閲忔墸鍑忔柊棰嗘枡銆�
+ addInventory(oldPick.getId(), oldProductModelId, oldBatchNo, oldQuantity, PICK_RETURN_IN_RECORD_TYPE);
+ subtractInventory(oldPick.getId(), newProductModelId, newStoredBatchNo, newQuantity, rowNo, PICK_STOCK_OUT_RECORD_TYPE);
+ }
+ if (needReissuePickRecord) {
+ // 姝e父棰嗘枡娴佹按鎸夆�滄渶鏂伴鏂欓噺鈥濋噸寤猴紝閬垮厤淇濈暀鍘嗗彶鏃у�笺��
+ deleteNormalPickRecord(oldPick.getId());
+ }
+
+ oldPick.setProductModelId(newProductModelId);
+ oldPick.setBatchNo(newStoredBatchNo);
+ oldPick.setQuantity(newQuantity);
+ oldPick.setRemark(dto.getRemark());
+ oldPick.setOperationName(dto.getOperationName());
+ oldPick.setTechnologyOperationId(dto.getTechnologyOperationId());
+ // 鏅�氭洿鏂颁篃瑕佸悓姝ラ噸绠楀疄闄呯敤閲忥紝閬垮厤娌跨敤鏃у�笺��
+ // 瑙勫垯锛氬疄闄呯敤閲� = 棰嗘枡鏁伴噺 + 琛ユ枡鏁伴噺 - 閫�鏂欐暟閲忋��
+ oldPick.setActualQty(calculateActualQty(oldPick, oldPick.getFeedingQty()));
+ if (dto.getDemandedQuantity() != null) {
+ oldPick.setDemandedQuantity(dto.getDemandedQuantity());
+ }
+ if (dto.getBom() != null) {
+ oldPick.setBom(dto.getBom());
+ }
+ int affected = baseMapper.updateById(oldPick);
+ if (affected <= 0) {
+ throw new ServiceException("绗�" + rowNo + "琛屾洿鏂板け璐ワ細鏇存柊棰嗘枡璁板綍澶辫触");
+ }
+
+ // 濡傛灉鍙戠敓棰嗘枡閲嶆彁锛岃ˉ鍐欎竴鏉℃柊鐨勬甯搁鏂欐祦姘淬��
+ if (needReissuePickRecord) {
+ insertPickRecord(oldPick.getId(),
+ dto.getProductionOrderId(),
+ dto.getProductionOperationTaskId(),
+ newProductModelId,
+ newBatchNo,
+ newQuantity,
+ BigDecimal.ZERO,
+ newQuantity,
+ dto.getPickType(),
+ dto.getRemark(),
+ dto.getFeedingReason());
+ }
+ }
+
+ private void insertPickRecord(Long pickId,
+ Long productionOrderId,
+ Long productionOperationTaskId,
+ Long productModelId,
+ String batchNo,
+ BigDecimal pickQuantity,
+ BigDecimal beforeQuantity,
+ BigDecimal afterQuantity,
+ Byte pickType,
+ String remark,
+ String feedingReason) {
+ // 鍐欓鏂欐祦姘磋褰曪細缁熶竴璁板綍棰嗘枡/琛ユ枡/閫�鏂欐暟閲忓彉鍖栬建杩广��
+ ProductionOrderPickRecord pickRecord = new ProductionOrderPickRecord();
+ pickRecord.setPickId(pickId);
+ pickRecord.setProductionOrderId(productionOrderId);
+ pickRecord.setProductionOperationTaskId(productionOperationTaskId);
+ pickRecord.setProductModelId(productModelId);
+ pickRecord.setBatchNo(batchNo);
+ pickRecord.setPickQuantity(defaultDecimal(pickQuantity));
+ pickRecord.setBeforeQuantity(defaultDecimal(beforeQuantity));
+ pickRecord.setAfterQuantity(defaultDecimal(afterQuantity));
+ pickRecord.setPickType(pickType == null ? PICK_TYPE_NORMAL : pickType);
+ pickRecord.setRemark(remark);
+ pickRecord.setFeedingReason(feedingReason);
+ productionOrderPickRecordMapper.insert(pickRecord);
+ }
+
+ private void deleteNormalPickRecord(Long pickId) {
+ // 鍒犻櫎璇ラ鏂欏崟鍘嗗彶涓婄殑鈥滄甯搁鏂欌�濇祦姘达紝淇濈暀琛ユ枡/閫�鏂欐祦姘淬��
+ if (pickId == null) {
+ return;
+ }
+ productionOrderPickRecordMapper.delete(
+ Wrappers.<ProductionOrderPickRecord>lambdaQuery()
+ .eq(ProductionOrderPickRecord::getPickId, pickId)
+ .eq(ProductionOrderPickRecord::getPickType, PICK_TYPE_NORMAL)
+ );
+ }
+
+ private void subtractInventory(Long recordId,
+ Long productModelId,
+ String batchNo,
+ BigDecimal quantity,
+ int rowNo,
+ String stockOutRecordType) {
+ // 鎵e噺搴撳瓨鎬绘祦绋嬶細
+ // 1) 瑙f瀽鎵规鍒楄〃锛�
+ // 2) 璁$畻姣忎釜鎵规鍙敤閲忎笌鎬诲彲鐢ㄩ噺锛�
+ // 3) 鎸夋壒娆¢『搴忛�愮瑪鈥滄柊澧炲嚭搴撹褰曞苟瀹℃壒閫氳繃鈥濓紝鐩村埌鎵e畬鐩爣鏁伴噺锛�
+ // 4) 浠讳竴姝ュけ璐ュ嵆鎶涢敊骞跺洖婊氫簨鍔°��
+ BigDecimal deductQuantity = defaultDecimal(quantity);
+ // 棰嗘枡鏁伴噺灏忎簬绛変簬0鏃讹紝涓嶉渶瑕佹墽琛屽簱瀛樻墸鍑忋��
+ if (deductQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+ return;
+ }
+
+ List<String> batchNoList = parseBatchNoValue(batchNo);
+ if (batchNoList.isEmpty()) {
+ batchNoList = Collections.singletonList(null);
+ }
+
+ // 鍏堣绠楀悇鎵规鍙敤閲忥紝閬垮厤杈规墸杈圭畻瀵艰嚧鍒ゆ柇涓嶄竴鑷淬��
+ Map<String, BigDecimal> availableQuantityMap = new LinkedHashMap<>();
+ BigDecimal totalAvailableQuantity = BigDecimal.ZERO;
+ // 閬嶅巻鎵规锛岃绠楁瘡涓壒娆″彲鐢ㄥ簱瀛樸��
+ for (String currentBatchNo : batchNoList) {
+ // 鏌ヨ褰撳墠瑙勬牸+鎵规鐨勫簱瀛樿褰曘��
+ StockInventory stockInventory = stockInventoryMapper.selectOne(buildStockWrapper(productModelId, currentBatchNo));
+ BigDecimal availableQuantity = BigDecimal.ZERO;
+ if (stockInventory != null) {
+ availableQuantity = defaultDecimal(stockInventory.getQualitity())
+ .subtract(defaultDecimal(stockInventory.getLockedQuantity()));
+ if (availableQuantity.compareTo(BigDecimal.ZERO) < 0) {
+ availableQuantity = BigDecimal.ZERO;
+ }
+ }
+ availableQuantityMap.put(currentBatchNo, availableQuantity);
+ totalAvailableQuantity = totalAvailableQuantity.add(availableQuantity);
+ }
+
+ if (deductQuantity.compareTo(totalAvailableQuantity) > 0) {
+ BigDecimal shortQuantity = deductQuantity.subtract(totalAvailableQuantity);
+ throw new ServiceException("绗�" + rowNo + "琛屾墸鍑忓簱瀛樺け璐ワ細鍙敤搴撳瓨涓嶈冻锛屽綋鍓嶅彲鐢�"
+ + formatQuantity(totalAvailableQuantity) + "锛屼粛缂哄皯" + formatQuantity(shortQuantity));
+ }
+
+ // 鎸夋壒娆¢『搴忛�愮瑪鎵e噺搴撳瓨銆�
+ BigDecimal remainingQuantity = deductQuantity;
+ for (Map.Entry<String, BigDecimal> entry : availableQuantityMap.entrySet()) {
+ if (remainingQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+ break;
+ }
+ BigDecimal availableQuantity = defaultDecimal(entry.getValue());
+ if (availableQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+ continue;
+ }
+ BigDecimal currentDeductQuantity = remainingQuantity.min(availableQuantity);
+ createAndApproveStockOutRecord(recordId, productModelId, entry.getKey(), currentDeductQuantity, rowNo, stockOutRecordType);
+ remainingQuantity = remainingQuantity.subtract(currentDeductQuantity);
+ }
+
+ if (remainingQuantity.compareTo(BigDecimal.ZERO) > 0) {
+ throw new ServiceException("绗�" + rowNo + "琛屾墸鍑忓簱瀛樺け璐ワ細浠嶆湁鏈墸鍑忔暟閲�" + formatQuantity(remainingQuantity));
+ }
+ }
+
+ private void createAndApproveStockOutRecord(Long recordId,
+ Long productModelId,
+ String batchNo,
+ BigDecimal quantity,
+ int rowNo,
+ String stockOutRecordType) {
+ // 搴撳瓨鎵e噺鏀逛负涓ゆ锛�
+ // 1) 鍏堣皟鐢� addStockOutRecordOnly 鏂板寰呭鎵瑰嚭搴撹褰曪紱
+ // 2) 鍐嶈皟鐢ㄥ嚭搴撳鎵癸紝瀹℃壒鐘舵�佸浐瀹氫紶 1锛堥�氳繃锛夈��
+ try {
+ StockInventoryDto stockInventoryDto = new StockInventoryDto();
+ stockInventoryDto.setRecordId(recordId == null ? 0L : recordId);
+ stockInventoryDto.setRecordType(stockOutRecordType);
+ stockInventoryDto.setProductModelId(productModelId);
+ stockInventoryDto.setBatchNo(batchNo);
+ stockInventoryDto.setQualitity(quantity);
+ stockInventoryService.addStockOutRecordOnly(stockInventoryDto);
+
+ LambdaQueryWrapper<StockOutRecord> recordWrapper = Wrappers.<StockOutRecord>lambdaQuery()
+ .eq(StockOutRecord::getRecordId, stockInventoryDto.getRecordId())
+ .eq(StockOutRecord::getRecordType, stockOutRecordType)
+ .eq(StockOutRecord::getProductModelId, productModelId)
+ .eq(StockOutRecord::getType, "0")
+ .orderByDesc(StockOutRecord::getId)
+ .last("limit 1");
+ if (StringUtils.isEmpty(batchNo)) {
+ recordWrapper.isNull(StockOutRecord::getBatchNo);
+ } else {
+ recordWrapper.eq(StockOutRecord::getBatchNo, batchNo);
+ }
+ StockOutRecord stockOutRecord = stockOutRecordService.getOne(recordWrapper, false);
+ if (stockOutRecord == null || stockOutRecord.getId() == null) {
+ throw new ServiceException("绗�" + rowNo + "琛屾墸鍑忓簱瀛樺け璐ワ細鏈壘鍒板搴斿嚭搴撶敵璇疯褰�");
+ }
+ stockOutRecordService.batchApprove(
+ Collections.singletonList(stockOutRecord.getId()),
+ ReviewStatusEnum.APPROVED.getCode()
+ );
+ } catch (ServiceException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ throw new ServiceException("绗�" + rowNo + "琛屾墸鍑忓簱瀛樺け璐ワ細" + ex.getMessage());
+ }
+ }
+
+ private void addInventory(Long recordId,
+ Long productModelId,
+ String batchNo,
+ BigDecimal quantity,
+ String stockInRecordType) {
+ // 鍥炶ˉ搴撳瓨鏀逛负涓ゆ锛�
+ // 1) 鍏堟柊澧炲叆搴撶敵璇凤紱
+ // 2) 鍐嶅鎵归�氳繃锛岀‘淇濆簱瀛樼珛鍒诲洖琛ョ敓鏁堛��
+ BigDecimal addQuantity = defaultDecimal(quantity);
+ if (addQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+ return;
+ }
+ try {
+ StockInventoryDto stockInventoryDto = new StockInventoryDto();
+ stockInventoryDto.setProductModelId(productModelId);
+ stockInventoryDto.setBatchNo(batchNo);
+ stockInventoryDto.setQualitity(addQuantity);
+ stockInventoryDto.setRecordType(stockInRecordType);
+ stockInventoryDto.setRecordId(recordId == null ? 0L : recordId);
+ stockInventoryService.addStockInRecordOnly(stockInventoryDto);
+
+ LambdaQueryWrapper<StockInRecord> recordWrapper = Wrappers.<StockInRecord>lambdaQuery()
+ .eq(StockInRecord::getRecordId, stockInventoryDto.getRecordId())
+ .eq(StockInRecord::getRecordType, stockInventoryDto.getRecordType())
+ .eq(StockInRecord::getProductModelId, productModelId)
+ .eq(StockInRecord::getType, "0")
+ .orderByDesc(StockInRecord::getId)
+ .last("limit 1");
+ if (StringUtils.isEmpty(batchNo)) {
+ recordWrapper.isNull(StockInRecord::getBatchNo);
+ } else {
+ recordWrapper.eq(StockInRecord::getBatchNo, batchNo);
+ }
+ StockInRecord stockInRecord = stockInRecordService.getOne(recordWrapper, false);
+ if (stockInRecord == null || stockInRecord.getId() == null) {
+ throw new ServiceException("鍥炶ˉ搴撳瓨澶辫触锛氭湭鎵惧埌瀵瑰簲鍏ュ簱鐢宠璁板綍");
+ }
+ stockInRecordService.batchApprove(
+ Collections.singletonList(stockInRecord.getId()),
+ ReviewStatusEnum.APPROVED.getCode()
+ );
+ } catch (ServiceException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ throw new ServiceException("鍥炶ˉ搴撳瓨澶辫触锛�" + ex.getMessage());
+ }
+ }
+
+ private List<ProductionOrderPickDto> resolvePickItems(ProductionOrderPickDto dto) {
+ // 瑙f瀽鏂板鍦烘櫙鐨勯鏂欐槑缁嗛泦鍚堛��
+ if (dto == null) {
+ throw new ServiceException("鍙傛暟涓嶈兘涓虹┖");
+ }
+ if (dto.getPickList() != null && !dto.getPickList().isEmpty()) {
+ return dto.getPickList();
+ }
+ return Collections.singletonList(dto);
+ }
+
+ private List<ProductionOrderPickDto> resolveUpdateItems(ProductionOrderPickDto dto) {
+ // 瑙f瀽鏇存柊鍦烘櫙鐨勯鏂欐槑缁嗛泦鍚堛��
+ if (dto.getPickList() != null) {
+ return dto.getPickList();
+ }
+ if (isEmptyUpdateItem(dto)) {
+ return Collections.emptyList();
+ }
+ return Collections.singletonList(dto);
+ }
+
+ private boolean isEmptyUpdateItem(ProductionOrderPickDto dto) {
+ // 鍒ゆ柇鏇存柊琛屾槸鍚︿负绌虹櫧鍗犱綅琛屻��
+ return dto.getId() == null
+ && dto.getProductModelId() == null
+ && dto.getPickQuantity() == null
+ && StringUtils.isEmpty(dto.getBatchNo())
+ && (dto.getBatchNoList() == null || dto.getBatchNoList().isEmpty())
+ && dto.getPickType() == null
+ && dto.getFeedingQuantity() == null
+ && StringUtils.isEmpty(dto.getFeedingReason())
+ && dto.getReturnQty() == null
+ && dto.getActualQty() == null
+ && dto.getReturned() == null
+ && dto.getProductionOperationTaskId() == null
+ && dto.getTechnologyOperationId() == null
+ && StringUtils.isEmpty(dto.getOperationName())
+ && dto.getDemandedQuantity() == null
+ && dto.getBom() == null
+ && StringUtils.isEmpty(dto.getRemark());
+ }
+
+ private Long resolveProductionOrderId(ProductionOrderPickDto dto) {
+ // 浼樺厛浠庝富DTO瑙f瀽璁㈠崟ID锛屼笉瀛樺湪鏃跺啀浠庡瓙椤逛腑鍥為��鏌ユ壘銆�
+ if (dto.getProductionOrderId() != null) {
+ return dto.getProductionOrderId();
+ }
+ if (dto.getPickList() == null || dto.getPickList().isEmpty()) {
+ return null;
+ }
+ return dto.getPickList().stream()
+ .filter(Objects::nonNull)
+ .map(ProductionOrderPickDto::getProductionOrderId)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(null);
+ }
+
+ private ProductionOrderPickDto mergeDto(ProductionOrderPickDto rootDto, ProductionOrderPickDto itemDto) {
+ // 鍚堝苟瑙勫垯锛�
+ // - itemDto 浼樺厛鎵胯浇琛岀骇杈撳叆锛�
+ // - itemDto 缂哄け瀛楁浠� rootDto 鍏滃簳缁ф壙锛�
+ // - 杈撳嚭 merged 浣滀负缁熶竴涓氬姟鍏ュ弬銆�
+ ProductionOrderPickDto merged = new ProductionOrderPickDto();
+ // 鍏堟嫹璐濊绾у瓧娈点��
+ if (itemDto != null) {
+ merged.setId(itemDto.getId());
+ merged.setProductionOrderId(itemDto.getProductionOrderId());
+ merged.setProductionOperationTaskId(itemDto.getProductionOperationTaskId());
+ merged.setProductModelId(itemDto.getProductModelId());
+ merged.setBatchNo(itemDto.getBatchNo());
+ merged.setBatchNoList(itemDto.getBatchNoList());
+ merged.setPickQuantity(itemDto.getPickQuantity());
+ merged.setPickType(itemDto.getPickType());
+ merged.setRemark(itemDto.getRemark());
+ merged.setFeedingReason(itemDto.getFeedingReason());
+ merged.setFeedingQuantity(itemDto.getFeedingQuantity());
+ merged.setTechnologyOperationId(itemDto.getTechnologyOperationId());
+ merged.setOperationName(itemDto.getOperationName());
+ merged.setDemandedQuantity(itemDto.getDemandedQuantity());
+ merged.setBom(itemDto.getBom());
+ merged.setReturnQty(itemDto.getReturnQty());
+ merged.setActualQty(itemDto.getActualQty());
+ merged.setReturned(itemDto.getReturned());
+ }
+ if (merged.getId() == null) {
+ merged.setId(rootDto.getId());
+ }
+ if (merged.getProductionOrderId() == null) {
+ merged.setProductionOrderId(rootDto.getProductionOrderId());
+ }
+ if (merged.getProductionOperationTaskId() == null) {
+ merged.setProductionOperationTaskId(rootDto.getProductionOperationTaskId());
+ }
+ if (merged.getProductModelId() == null) {
+ merged.setProductModelId(rootDto.getProductModelId());
+ }
+ if (merged.getBatchNo() == null) {
+ merged.setBatchNo(rootDto.getBatchNo());
+ }
+ if (merged.getBatchNoList() == null || merged.getBatchNoList().isEmpty()) {
+ merged.setBatchNoList(rootDto.getBatchNoList());
+ }
+ if (merged.getPickQuantity() == null) {
+ merged.setPickQuantity(rootDto.getPickQuantity());
+ }
+ if (merged.getPickType() == null) {
+ merged.setPickType(rootDto.getPickType());
+ }
+ if (merged.getRemark() == null) {
+ merged.setRemark(rootDto.getRemark());
+ }
+ if (merged.getFeedingReason() == null) {
+ merged.setFeedingReason(rootDto.getFeedingReason());
+ }
+ if (merged.getFeedingQuantity() == null) {
+ merged.setFeedingQuantity(rootDto.getFeedingQuantity());
+ }
+ if (merged.getTechnologyOperationId() == null) {
+ merged.setTechnologyOperationId(rootDto.getTechnologyOperationId());
+ }
+ if (merged.getOperationName() == null) {
+ merged.setOperationName(rootDto.getOperationName());
+ }
+ if (merged.getDemandedQuantity() == null) {
+ merged.setDemandedQuantity(rootDto.getDemandedQuantity());
+ }
+ if (merged.getBom() == null) {
+ merged.setBom(rootDto.getBom());
+ }
+ if (merged.getReturnQty() == null) {
+ merged.setReturnQty(rootDto.getReturnQty());
+ }
+ if (merged.getActualQty() == null) {
+ merged.setActualQty(rootDto.getActualQty());
+ }
+ if (merged.getReturned() == null) {
+ merged.setReturned(rootDto.getReturned());
+ }
+ return merged;
+ }
+
+ private void validatePickParam(ProductionOrderPickDto dto, int rowNo) {
+ // 鏍¢獙鏅�氶鏂欏弬鏁帮紙璁㈠崟銆佽鏍笺�佹暟閲忋�佺被鍨嬶級銆�
+ if (dto.getProductionOrderId() == null) {
+ throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細鐢熶骇璁㈠崟ID涓嶈兘涓虹┖");
+ }
+ if (dto.getProductModelId() == null) {
+ throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細浜у搧瑙勬牸涓嶈兘涓虹┖");
+ }
+ if (dto.getPickQuantity() == null || dto.getPickQuantity().compareTo(BigDecimal.ZERO) < 0) {
+ throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細棰嗘枡鏁伴噺涓嶈兘灏忎簬0");
+ }
+ if (dto.getPickType() != null && dto.getPickType() != PICK_TYPE_NORMAL && dto.getPickType() != PICK_TYPE_FEEDING) {
+ throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細棰嗘枡绫诲瀷浠呮敮鎸�1(棰嗘枡)鎴�2(琛ユ枡)");
+ }
+ }
+
+ private void validateFeedingParam(ProductionOrderPickDto dto, int rowNo) {
+ // 鏍¢獙琛ユ枡鍙傛暟锛堣鍗曘�侀鏂橧D銆佽ˉ鏂欐暟閲忋�佺被鍨嬶級銆�
+ if (dto.getProductionOrderId() == null) {
+ throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細鐢熶骇璁㈠崟ID涓嶈兘涓虹┖");
+ }
+ if (dto.getId() == null) {
+ throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細棰嗘枡璁板綍ID涓嶈兘涓虹┖");
+ }
+ if (dto.getFeedingQuantity() == null || dto.getFeedingQuantity().compareTo(BigDecimal.ZERO) < 0) {
+ throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細琛ユ枡鏁伴噺涓嶈兘灏忎簬0");
+ }
+ if (!isFeedingPick(dto)) {
+ throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細琛ユ枡鍦烘櫙涓嬮鏂欑被鍨嬪繀椤讳负2");
+ }
+ }
+
+ private void validateReturnParam(ProductionOrderPickDto dto, int rowNo) {
+ // 鏍¢獙閫�鏂欏弬鏁帮紙璁㈠崟銆侀鏂橧D銆侀��鏂欓噺锛夈��
+ if (dto.getProductionOrderId() == null) {
+ throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細鐢熶骇璁㈠崟ID涓嶈兘涓虹┖");
+ }
+ if (dto.getId() == null) {
+ throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細棰嗘枡璁板綍ID涓嶈兘涓虹┖");
+ }
+ if (dto.getReturnQty() == null || dto.getReturnQty().compareTo(BigDecimal.ZERO) < 0) {
+ throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細閫�鏂欐暟閲忎笉鑳藉皬浜�0");
+ }
+ }
+
+ private boolean isFeedingRequest(ProductionOrderPickDto dto) {
+ // 鍒ゆ柇褰撳墠璇锋眰鏄惁灞炰簬琛ユ枡娴佺▼銆�
+ if (isFeedingPick(dto)) {
+ return true;
+ }
+ if (dto.getPickList() == null || dto.getPickList().isEmpty()) {
+ return false;
+ }
+ return dto.getPickList().stream()
+ .filter(Objects::nonNull)
+ .anyMatch(this::isFeedingPick);
+ }
+
+ private boolean isFeedingPick(ProductionOrderPickDto dto) {
+ // 鍒ゆ柇褰撳墠琛屾槸鍚︿负琛ユ枡绫诲瀷銆�
+ return dto != null && Objects.equals(dto.getPickType(), PICK_TYPE_FEEDING);
+ }
+
+ private boolean isReturnRequest(ProductionOrderPickDto dto) {
+ // 鍒ゆ柇褰撳墠璇锋眰鏄惁灞炰簬閫�鏂欐祦绋嬨��
+ if (isReturnPick(dto)) {
+ return true;
+ }
+ if (dto.getPickList() == null || dto.getPickList().isEmpty()) {
+ return false;
+ }
+ return dto.getPickList().stream()
+ .filter(Objects::nonNull)
+ .anyMatch(this::isReturnPick);
+ }
+
+ private boolean isReturnPick(ProductionOrderPickDto dto) {
+ // 鍒ゆ柇褰撳墠琛屾槸鍚︿负閫�鏂欑被鍨嬨��
+ return dto != null && Boolean.TRUE.equals(dto.getReturned());
+ }
+
+ private BigDecimal sumFeedingQuantity(Long productionOrderId, Long pickId) {
+ // 姹囨�绘寚瀹氶鏂欏崟鐨勫巻鍙茶ˉ鏂欐�婚噺銆�
+ List<ProductionOrderPickRecord> feedingRecords = productionOrderPickRecordMapper.selectList(
+ Wrappers.<ProductionOrderPickRecord>lambdaQuery()
+ .eq(ProductionOrderPickRecord::getProductionOrderId, productionOrderId)
+ .eq(ProductionOrderPickRecord::getPickId, pickId)
+ .eq(ProductionOrderPickRecord::getPickType, PICK_TYPE_FEEDING));
+ return feedingRecords.stream()
+ .map(ProductionOrderPickRecord::getPickQuantity)
+ .map(this::defaultDecimal)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ private BigDecimal calculateActualQty(ProductionOrderPick pick, BigDecimal feedingQty) {
+ // 鎸夆�滈鏂�+琛ユ枡-閫�鏂欌�濊绠楀疄闄呯敤閲忋��
+ return defaultDecimal(pick.getQuantity())
+ .add(defaultDecimal(feedingQty))
+ .subtract(defaultDecimal(pick.getReturnQty()));
+ }
+
+ private String normalizeBatchNo(String batchNo) {
+ // 鏍囧噯鍖栨壒娆″彿锛堝幓绌虹櫧銆佺┖涓茶浆null锛夈��
+ if (StringUtils.isEmpty(batchNo)) {
+ return null;
+ }
+ String trimBatchNo = batchNo.trim();
+ return trimBatchNo.isEmpty() ? null : trimBatchNo;
+ }
+ private List<String> resolveBatchNoList(ProductionOrderPickDto dto) {
+ // 浼樺厛瑙f瀽 batchNoList锛岀┖鍒欏洖閫�瑙f瀽 batchNo 瀛楃涓层��
+ List<String> normalizedBatchNoList = normalizeBatchNoList(dto.getBatchNoList());
+ if (!normalizedBatchNoList.isEmpty()) {
+ return normalizedBatchNoList;
+ }
+ return parseBatchNoValue(dto.getBatchNo());
+ }
+
+ private String pickInventoryBatchNo(List<String> batchNoList) {
+ // 浠庢壒娆¢泦鍚堜腑鍙栧簱瀛樻墸鍑忎娇鐢ㄧ殑鎵规銆�
+ if (batchNoList == null || batchNoList.isEmpty()) {
+ return null;
+ }
+ return batchNoList.get(0);
+ }
+
+ private String resolveInventoryBatchNoFromStored(String storedBatchNo) {
+ // 浠庢暟鎹簱瀛樺偍鎵规瀛楁涓弽瑙e彲鐢ㄦ壒娆°��
+ return pickInventoryBatchNo(parseBatchNoValue(storedBatchNo));
+ }
+
+ private String formatBatchNoStorage(List<String> batchNoList) {
+ // 灏嗘壒娆¢泦鍚堟牸寮忓寲涓烘暟鎹簱瀛樺偍鍊笺��
+ if (batchNoList == null || batchNoList.isEmpty()) {
+ return null;
+ }
+ if (batchNoList.size() == 1) {
+ return batchNoList.get(0);
+ }
+ return String.join(",", batchNoList);
+ }
+
+ private List<String> normalizeBatchNoList(List<String> batchNoList) {
+ // 鎵归噺鏍囧噯鍖栨壒娆″彿骞跺幓閲嶃��
+ if (batchNoList == null || batchNoList.isEmpty()) {
+ return Collections.emptyList();
+ }
+ LinkedHashSet<String> normalizedSet = new LinkedHashSet<>();
+ for (String batchNo : batchNoList) {
+ String normalizedBatchNo = normalizeBatchNo(batchNo);
+ if (!StringUtils.isEmpty(normalizedBatchNo)) {
+ normalizedSet.add(normalizedBatchNo);
+ }
+ }
+ return new ArrayList<>(normalizedSet);
+ }
+
+ private void fillBatchNoList(List<ProductionOrderPickVo> detailList) {
+ // 灏嗗悓璁㈠崟+鍚岃鏍�+鍚屽伐搴忕殑鏁版嵁鎸夌粍鑱氬悎鎵规锛屼究浜庡墠绔粺涓�灞曠ず銆�
+ if (detailList == null || detailList.isEmpty()) {
+ return;
+ }
+ Map<String, LinkedHashSet<String>> batchNoGroupMap = new HashMap<>();
+ for (ProductionOrderPickVo detail : detailList) {
+ String key = buildBatchNoGroupKey(detail);
+ LinkedHashSet<String> batchSet = batchNoGroupMap.computeIfAbsent(key, k -> new LinkedHashSet<>());
+ batchSet.addAll(parseBatchNoValue(detail.getBatchNo()));
+ if (detail.getBatchNoList() != null && !detail.getBatchNoList().isEmpty()) {
+ batchSet.addAll(normalizeBatchNoList(detail.getBatchNoList()));
+ }
+ }
+ for (ProductionOrderPickVo detail : detailList) {
+ String key = buildBatchNoGroupKey(detail);
+ LinkedHashSet<String> batchSet = batchNoGroupMap.get(key);
+ detail.setBatchNoList(batchSet == null ? Collections.emptyList() : new ArrayList<>(batchSet));
+ }
+ }
+
+ private void fillSelectableBatchNoList(List<ProductionOrderPickVo> detailList) {
+ // 鍚堝苟鈥滃凡閫夋壒娆♀�濆拰鈥滃簱瀛樺彲閫夋壒娆♀�濓紝鐢ㄤ簬鍓嶇涓嬫媺銆�
+ if (detailList == null || detailList.isEmpty()) {
+ return;
+ }
+ // 鍏堟敹闆嗘槑缁嗕腑娑夊強鐨勮鏍糏D锛屾壒閲忔煡璇㈠簱瀛樻壒娆°��
+ Set<Long> productModelIdSet = detailList.stream()
+ .map(ProductionOrderPickVo::getProductModelId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ if (productModelIdSet.isEmpty()) {
+ return;
+ }
+ List<StockInventory> stockBatchList = stockInventoryMapper.listSelectableBatchNoByProductModelIds(
+ new ArrayList<>(productModelIdSet));
+ Map<Long, LinkedHashSet<String>> stockBatchMap = new HashMap<>();
+ for (StockInventory stockInventory : stockBatchList) {
+ if (stockInventory == null || stockInventory.getProductModelId() == null) {
+ continue;
+ }
+ String normalizedBatchNo = normalizeBatchNo(stockInventory.getBatchNo());
+ if (StringUtils.isEmpty(normalizedBatchNo)) {
+ continue;
+ }
+ stockBatchMap.computeIfAbsent(stockInventory.getProductModelId(), k -> new LinkedHashSet<>())
+ .add(normalizedBatchNo);
+ }
+ for (ProductionOrderPickVo detail : detailList) {
+ LinkedHashSet<String> mergedBatchSet = new LinkedHashSet<>();
+ mergedBatchSet.addAll(normalizeBatchNoList(detail.getBatchNoList()));
+ LinkedHashSet<String> selectableBatchSet = stockBatchMap.get(detail.getProductModelId());
+ if (selectableBatchSet != null) {
+ mergedBatchSet.addAll(selectableBatchSet);
+ }
+ detail.setBatchNoList(new ArrayList<>(mergedBatchSet));
+ }
+ }
+
+ private String buildBatchNoGroupKey(ProductionOrderPickVo detail) {
+ // 鏋勫缓鎵规鑱氬悎鍒嗙粍閿��
+ return detail.getProductionOrderId() + "|"
+ + detail.getProductModelId() + "|"
+ + detail.getTechnologyOperationId() + "|"
+ + detail.getOperationName();
+ }
+
+ private List<String> parseBatchNoValue(String rawBatchNoValue) {
+ // 鎵规瑙f瀽鍏煎涓夌鏍煎紡锛�
+ // 1) 鍗曞�硷細A001
+ // 2) 閫楀彿鍒嗛殧锛欰001,A002
+ // 3) 绫籎SON鏁扮粍瀛楃涓诧細["A001","A002"]
+ String normalizedValue = normalizeBatchNo(rawBatchNoValue);
+ if (StringUtils.isEmpty(normalizedValue)) {
+ return Collections.emptyList();
+ }
+ if (normalizedValue != null && normalizedValue.startsWith("[") && normalizedValue.endsWith("]")) {
+ String value = normalizedValue.substring(1, normalizedValue.length() - 1);
+ if (StringUtils.isEmpty(value)) {
+ return Collections.emptyList();
+ }
+ List<String> parsed = Arrays.stream(value.split(","))
+ .map(item -> item.trim().replace("\"", "").replace("'", ""))
+ .collect(Collectors.toList());
+ return normalizeBatchNoList(parsed);
+ }
+ if (normalizedValue != null && normalizedValue.contains(",")) {
+ List<String> parsed = Arrays.stream(normalizedValue.split(","))
+ .map(item -> item.trim())
+ .collect(Collectors.toList());
+ return normalizeBatchNoList(parsed);
+ }
+ return Collections.singletonList(normalizedValue);
+ }
+
+ private LambdaQueryWrapper<StockInventory> buildStockWrapper(Long productModelId, String batchNo) {
+ // 鏋勫缓搴撳瓨鏌ヨ鏉′欢锛堣鏍� + 鎵规锛夈��
+ LambdaQueryWrapper<StockInventory> wrapper = Wrappers.<StockInventory>lambdaQuery()
+ .eq(StockInventory::getProductModelId, productModelId);
+ if (StringUtils.isEmpty(batchNo)) {
+ wrapper.isNull(StockInventory::getBatchNo);
+ } else {
+ wrapper.eq(StockInventory::getBatchNo, batchNo);
+ }
+ return wrapper;
+ }
+
+ private BigDecimal defaultDecimal(BigDecimal value) {
+ // BigDecimal 绌哄�煎厹搴曪紝缁熶竴鎸�0澶勭悊銆�
+ return value == null ? BigDecimal.ZERO : value;
+ }
+
+ private String formatQuantity(BigDecimal value) {
+ // 鏁伴噺鏍煎紡鍖栬緭鍑猴紙鍘婚櫎鏈熬鏃犳晥0锛夈��
+ return defaultDecimal(value).stripTrailingZeros().toPlainString();
+ }
}
--
Gitblit v1.9.3