Explorar el Código

[task-5915] 试剂耗材入库登记增加订单预领量处理接口

huangws hace 4 semanas
padre
commit
9f2303dfd3

+ 5 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/components/reagent/api/IReagentConsumablesInventoryService.java

@@ -106,4 +106,9 @@ public interface IReagentConsumablesInventoryService {
 			@RequestParam(name = "type", required = true) String type,
 			@RequestParam(name = "operate", required = true) String operate);
 
+	@RequestMapping(value = "/orderWithhold", method = { RequestMethod.POST })
+	public APIResult<Void> orderWithhold(
+			@RequestParam(name = "id", required = true) String id,
+			@RequestParam(name = "operate", required = true) String operate,
+			@RequestParam(name = "deleteRecord", required = true) String deleteRecord);
 }

+ 45 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/components/reagent/provider/ReagentConsumablesInventoryProvider.java

@@ -241,4 +241,49 @@ public class ReagentConsumablesInventoryProvider extends GenericProvider impleme
 		return result;
 	}
 
+	@ApiOperation(value = "采购订单预扣量处理", notes = "采购订单预扣量处理",
+			extensions = {
+					@Extension(properties = {
+							@ExtensionProperty(name = "submitCtrl", value = StringPool.Y)
+					})
+			})
+	@Override
+	public APIResult<Void> orderWithhold(
+			@ApiParam(name = "id", value = "入库登记主表 id", required = true)
+			@RequestParam(name = "id", required = true) String id,
+			@ApiParam(name = "operate", value = "操作类型 (开始-start;退回-back;结束-end)", required = true)
+			@RequestParam(name = "operate", required = true) String operate,
+			@ApiParam(name = "deleteRecord", value = "失败后是否删除 y-删 n-留", required = true)
+			@RequestParam(name = "deleteRecord", required = true) String deleteRecord) {
+		APIResult<Void> result = new APIResult<>();
+		try {
+			String retVal = inventoryService.orderWithhold(id, operate, deleteRecord);
+			// 防御性编程:防止返回 null
+			if (retVal == null || retVal.trim().isEmpty()) {
+				result.setMessage("更新失败!服务返回为空");
+				return result;
+			}
+			// 【优化点】使用 indexOf 查找分隔符,避免 split 的正则问题
+			int separatorIndex = retVal.indexOf('^');
+			if (separatorIndex > 0) {
+				String codePart = retVal.substring(0, separatorIndex).trim(); // 提取 ^ 前的部分并去空格
+				String msgPart = retVal.substring(separatorIndex + 1);        // 提取 ^ 后的部分
+				if ("1".equals(codePart)) {
+					result.setMessage("更新成功!");
+					// 如果需要,可以在这里设置 result.setCode(StateEnum.SUCCESS.getCode());
+				} else {
+					// 拼接自定义错误消息
+					result.setMessage("更新失败!" + msgPart);
+				}
+			} else {
+				// 如果没有找到 ^,说明返回格式不对,直接将整个返回值当作错误信息
+				logger.error("服务返回格式错误,未找到分隔符 '^': {}", retVal);
+				result.setMessage("更新失败!系统返回格式异常:" + retVal);
+			}
+		} catch (Exception e) {
+			logger.error("orderWithhold 接口执行异常", e);
+			setExceptionResult(result, StateEnum.ERROR.getCode(), StateEnum.ERROR.getText(), e);
+		}
+		return result;
+	}
 }

+ 27 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/components/reagent/service/InventoryService.java

@@ -37,6 +37,9 @@ public class InventoryService extends GenericProvider {
     @Autowired
     private ReturnService returnService;
 
+    @Autowired
+    private OrderService orderService;
+
     public boolean sync(String id, String action) throws Exception {
         logger.warn("com.lc.ibps.components.reagent.service.InventoryService.sync()--->id={},action={}",  id,action);
         OperationService service = getService(action);
@@ -289,4 +292,28 @@ public class InventoryService extends GenericProvider {
         }
         return null;
     }
+
+    public String orderWithhold(String id, String operate, String deleteRecord) throws Exception {
+        logger.info("com.lc.ibps.components.reagent.service.InventoryService.orderWithhold()--->id={},operate={},deleteRecord={}, id, operate,deleteRecord");
+        String retVal = "-1";
+        retVal = orderService.processOrder(id, operate);
+        if ("y".equals(deleteRecord) && retVal != null && retVal.startsWith("-1^")) {
+            logger.warn("业务校验失败且开启自动清理模式 (deleteRecord=y)。开始删除入库记录主子表脏数据,ID: {}", id);
+            try {
+                // 调用删除方法
+                orderService.deleteErrorRecord(id);
+
+                // 可选:在返回信息中追加提示,告知用户已执行清理
+                retVal = retVal + " (系统已自动撤销并删除无效申请记录)";
+
+            } catch (Exception e) {
+                logger.error("自动清理失败! ID: {}, 错误: {}", id, e.getMessage(), e);
+                // 如果清理本身也报错,追加警告信息到返回值,但不中断流程(因为业务本来就失败了)
+                retVal = retVal + " (警告:自动清理执行异常,请手动检查数据!)" ;
+                // 如果希望清理失败时向上抛出异常,可以取消下面这行的注释
+                // throw new Exception("业务失败且自动清理也失败", e);
+            }
+        }
+        return retVal;
+    }
 }

+ 243 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/components/reagent/service/OrderService.java

@@ -0,0 +1,243 @@
+package com.lc.ibps.components.reagent.service;
+
+
+import cn.hutool.core.date.DateUtil;
+import com.lc.ibps.api.form.sql.util.BeanUtils;
+import com.lc.ibps.base.core.util.Collections;
+import com.lc.ibps.base.framework.id.UniqueIdUtil;
+import com.lc.ibps.base.framework.table.ICommonDao;
+import com.lc.ibps.cloud.provider.GenericProvider;
+import com.lc.ibps.untils.SqlUtil;
+import com.lc.ibps.untils.StrUtil;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+public class OrderService extends GenericProvider {
+
+    @Resource
+    private ICommonDao commonDao;
+
+
+    public String processOrder(String id, String operate) {
+        logger.warn("com.lc.ibps.components.reagent.service.OrderService.processOrder()--->id={}, operate={}", id, operate);
+
+        // 校验操作类型
+        if (!Arrays.asList("start", "back", "end").contains(operate)) {
+            return "-1^非法的操作类型: " + operate;
+        }
+
+        // ================= 1. 基础校验与主表查询 (公共逻辑) =================
+        if (id == null || id.trim().isEmpty()) {
+            return "-1^id为空";
+        }
+
+        List<String> idList = Arrays.stream(id.split(","))
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .collect(Collectors.toList());
+
+        if (idList.isEmpty()) {
+            return "-1^id非法";
+        }
+
+        String safeIdStr = idList.stream()
+                .map(s -> s.replace("'", "''"))
+                .collect(Collectors.joining("','"));
+
+        // 查询入库登记子表
+        String sqlMain = "SELECT * from t_sjhxhclrkysdjbzb where parent_id_ in('" + safeIdStr + "')";
+        List<Map<String, Object>> inSubList = commonDao.query(sqlMain);
+
+        if (inSubList == null || inSubList.isEmpty()) {
+            return "-1^未找到入库登记子表记录";
+        }
+
+        // ================= 2. 查询订单子表 (公共逻辑) =================
+        List<String> ddzIdList = inSubList.stream()
+                .map(m -> {
+                    Object val = m.get("ding_dan_zi_id_");
+                    return val != null ? val.toString().trim() : null;
+                })
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        List<Map<String, Object>> orderSubList = new ArrayList<>();
+        if (!ddzIdList.isEmpty()) {
+            String safeDdzIdStr = ddzIdList.stream()
+                    .map(s -> s.replace("'", "''"))
+                    .collect(Collectors.joining("','"));
+            String sqlSub = "SELECT * from t_sjhccgsqbhzzb where id_ in('" + safeDdzIdStr + "')";
+            orderSubList = commonDao.query(sqlSub);
+        }
+
+        Map<String, Map<String, Object>> orderSubMap = orderSubList.stream()
+                .collect(Collectors.toMap(
+                        m -> (String) m.get("id_"),
+                        m -> m,
+                        (v1, v2) -> v1
+                ));
+
+        // ================= 3. 【第一阶段】全量预校验与SQL构造 (核心差异点) =================
+        List<String> updateSqls = new ArrayList<>();
+
+        for (Map<String, Object> inSub : inSubList) {
+            String ddzId = (String) inSub.get("ding_dan_zi_id_");
+
+            if (ddzId == null || !orderSubMap.containsKey(ddzId)) {
+                return "-1^数据错误:找不到对应的订单子表记录 (dingDanZiId=" + ddzId + ")";
+            }
+
+            Map<String, Object> orderSub = orderSubMap.get(ddzId);
+
+            // 获取数值
+            double inQty = getDoubleValue(inSub.get("shu_liang_"));
+            double currentYuLing = getDoubleValue(orderSub.get("yu_ling_liang_"));
+            double currentDaiRuKu = getDoubleValue(orderSub.get("dai_ru_ku_shu_lia"));
+            double orderQty = getDoubleValue(orderSub.get("shu_liang_")); // 订单总量,start时用
+
+            if (inQty <= 0) {
+                return "-1^数量必须为正数 (当前值:" + inQty + ")";
+            }
+
+            // --- 根据 operate 计算新值和校验 ---
+            double newYuLing = 0.0;
+            Double newDaiRuKu = null; // null 表示该操作不更新此字段
+            boolean checkPassed = false;
+            String errorMsg = "";
+
+            if ("start".equals(operate)) {
+                // [START] 占用预留,减少待入库
+                double availableQty = orderQty - currentYuLing;
+                if (availableQty < inQty) {
+                    errorMsg = "已超过可入库数量! (可用:" + availableQty + ", 申请:" + inQty + ")";
+                } else {
+                    newYuLing = currentYuLing + inQty;
+                    newDaiRuKu = currentDaiRuKu - inQty;
+                    checkPassed = true;
+                }
+
+            } else if ("back".equals(operate)) {
+                // [BACK] 释放预留,恢复待入库 (撤销 start)
+                if (currentYuLing < inQty) {
+                    errorMsg = "当前预留量不足,无法撤销! (当前预留:" + currentYuLing + ", 需撤销:" + inQty + ")";
+                } else {
+                    newYuLing = currentYuLing - inQty;
+                    newDaiRuKu = currentDaiRuKu + inQty;
+                    checkPassed = true;
+                }
+
+            } else if ("end".equals(operate)) {
+                // [END] 消除本次预留 (不恢复待入库,因为已正式入库)
+                if (currentYuLing < inQty) {
+                    errorMsg = "当前预留量不足,确认失败! (当前总预留:" + currentYuLing + ", 本次需释放:" + inQty + ")";
+                } else {
+                    newYuLing = currentYuLing - inQty;
+                    // newDaiRuKu 保持 null,不更新该字段
+                    checkPassed = true;
+                }
+            }
+
+            // --- 处理校验结果 ---
+            if (!checkPassed) {
+                String mingCheng = Objects.toString(orderSub.get("ming_cheng_"), "");
+                String bianMa = Objects.toString(orderSub.get("bian_ma_"), "");
+                String operateStr = "";
+                if(operate.equals("start")){
+                    operateStr = "启动";
+                } else if (operate.equals("back")){
+                    operateStr = "撤回";
+                } else if (operate.equals("end")){
+                    operateStr = "结束";
+                }
+                // 统一错误消息格式
+                return "-1^" + mingCheng + bianMa + "操作(" + operateStr + ")失败!" + errorMsg;
+            }
+
+            // --- 构造 SQL ---
+            // 基础部分:更新预留量
+            StringBuilder sqlBuilder = new StringBuilder();
+            sqlBuilder.append("update t_sjhccgsqbhzzb set yu_ling_liang_ = ").append(newYuLing);
+
+            // 如果需要更新待入库数 (start 和 back 需要,end 不需要)
+            if (newDaiRuKu != null) {
+                if (newDaiRuKu < 0) {
+                    return "-1^计算错误:待入库数不能为负数";
+                }
+                sqlBuilder.append(", dai_ru_ku_shu_lia = ").append(newDaiRuKu);
+            }
+
+            sqlBuilder.append(" where id_ = '").append(ddzId.replace("'", "''")).append("'");
+            updateSqls.add(sqlBuilder.toString());
+        }
+
+        // ================= 4. 【第二阶段】执行批量更新 (公共逻辑) =================
+        if (!updateSqls.isEmpty()) {
+            logger.info("Operation [{}] check passed. Executing {} updates...", operate, updateSqls.size());
+            try {
+                for (String sql : updateSqls) {
+                    commonDao.execute(sql);
+                }
+                logger.info("Operation [{}] completed successfully.", operate);
+            } catch (Exception e) {
+                logger.error("Error executing SQL for operation: {}", operate, e);
+                return "-1^数据库更新失败:" + e.getMessage();
+            }
+        }
+
+        return "1^success!";
+    }
+
+    public void deleteErrorRecord(String id) throws Exception {
+        if (id == null || id.trim().isEmpty()) {
+            return;
+        }
+
+        // 解析ID列表 (支持逗号分隔)
+        List<String> idList = Arrays.stream(id.split(","))
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .distinct()
+                .collect(Collectors.toList());
+
+        for (String singleId : idList) {
+            String safeId = singleId.replace("'", "''"); // 简单的SQL防注入处理
+
+            // 1. 先删子表 (t_sjhxhclrkysdjbzb),因为通常有外键约束或逻辑关联
+            // 条件:parent_id_ = singleId
+            String delSubSql = "DELETE FROM t_sjhxhclrkysdjbzb WHERE parent_id_ = '" + safeId + "'";
+            logger.debug("Executing delete sub-table SQL: {}", delSubSql);
+            commonDao.execute(delSubSql);
+
+            // 2. 再删主表 (t_sjhxhclrkysdjb)
+            // 条件:id_ = singleId
+            String delMainSql = "DELETE FROM t_sjhxhclrkysdjb WHERE id_ = '" + safeId + "'";
+            logger.debug("Executing delete main-table SQL: {}", delMainSql);
+            commonDao.execute(delMainSql);
+
+            logger.info("Successfully cleaned up error records for ID: {}", singleId);
+        }
+    }
+
+// ================= 辅助方法 =================
+
+    private double getDoubleValue(Object obj) {
+        if (obj == null) return 0.0;
+        if (obj instanceof Number) return ((Number) obj).doubleValue();
+        try {
+            String str = obj.toString().trim();
+            if (str.isEmpty()) return 0.0;
+            return Double.parseDouble(str);
+        } catch (NumberFormatException e) {
+            logger.warn("Failed to parse number from: {}", obj);
+            return 0.0;
+        }
+    }
+}
+