Explorar o código

Merge remote-tracking branch 'origin/master'

zhangyao hai 1 mes
pai
achega
e93dee6ee4

+ 39 - 39
com.awspaas.user.apps.donenow_crm/src/com/awspaas/user/apps/donenow_crm/event/FormAfterSaveEvent.java

@@ -73,35 +73,35 @@ public class FormAfterSaveEvent
 				//˵���ǿͻ���������
 				if (CrmConstant.BO_EU_DNCRM_ACCOUNT.equals(boName)) {
 					//����ַ��ͬ������
-					List<BO> list = SDK.getBOAPI().query(CrmConstant.BO_EU_DNCRM_LOCATION).bindId(accountBo.getBindId()).addQuery("IS_DEFAULT=", Integer.valueOf(1)).list();
-					//�Ѿ�����Ĭ�ϵ�ַ
-					if(list.size()>0) {
-						for (BO bo : list) {
-							bo.set("COUNTRY_ID", accountBo.get("COUNTRY_ID"));
-							bo.set("DISTRICT_ID", accountBo.get("DISTRICT_ID"));
-							bo.set("ADDRESS", accountBo.get("ADDRESS"));
-							bo.set("POSTAL_CODE", accountBo.get("POSTAL_CODE"));
-							bo.set("CATE_ID", accountBo.get("CATE_ID"));
-							SDK.getBOAPI().update(CrmConstant.BO_EU_DNCRM_LOCATION, bo);
-
-						}
-					}else {
-						//����ַ������������
-						BO bo=new BO();
-						bo.setBindId(accountBo.getBindId());
-						bo.set("COUNTRY_ID", accountBo.get("COUNTRY_ID"));
-						bo.set("DISTRICT_ID", accountBo.get("DISTRICT_ID"));
-						bo.set("ADDRESS", accountBo.get("ADDRESS"));
-						bo.set("POSTAL_CODE", accountBo.get("POSTAL_CODE"));
-						bo.set("IS_DEFAULT", 1);
-						bo.set("ACCOUNT_ID", accountBo.getId());
-						bo.set("CATE_ID", CrmConstant.OFFICELOCATION);
-						bo.set("ID", UUIDGener.getUUID());
-						SDK.getBOAPI().create(CrmConstant.BO_EU_DNCRM_LOCATION, bo,SDK.getProcessAPI().getInstanceById(accountBo.getBindId()), uc);
-						//���¿ͻ�����LocationId�ֶ�
-						accountBo.set("LOCATION_ID", bo.getId());
-						SDK.getBOAPI().update(CrmConstant.BO_EU_DNCRM_ACCOUNT, bo);
-					}
+//					List<BO> list = SDK.getBOAPI().query(CrmConstant.BO_EU_DNCRM_LOCATION).bindId(accountBo.getBindId()).addQuery("IS_DEFAULT=", Integer.valueOf(1)).list();
+//					//�Ѿ�����Ĭ�ϵ�ַ
+//					if(list.size()>0) {
+//						for (BO bo : list) {
+//							bo.set("COUNTRY_ID", accountBo.get("COUNTRY_ID"));
+//							bo.set("DISTRICT_ID", accountBo.get("DISTRICT_ID"));
+//							bo.set("ADDRESS", accountBo.get("ADDRESS"));
+//							bo.set("POSTAL_CODE", accountBo.get("POSTAL_CODE"));
+//							bo.set("CATE_ID", accountBo.get("CATE_ID"));
+//							SDK.getBOAPI().update(CrmConstant.BO_EU_DNCRM_LOCATION, bo);
+//
+//						}
+//					}else {
+//						//����ַ������������
+//						BO bo=new BO();
+//						bo.setBindId(accountBo.getBindId());
+//						bo.set("COUNTRY_ID", accountBo.get("COUNTRY_ID"));
+//						bo.set("DISTRICT_ID", accountBo.get("DISTRICT_ID"));
+//						bo.set("ADDRESS", accountBo.get("ADDRESS"));
+//						bo.set("POSTAL_CODE", accountBo.get("POSTAL_CODE"));
+//						bo.set("IS_DEFAULT", 1);
+//						bo.set("ACCOUNT_ID", accountBo.getId());
+//						bo.set("CATE_ID", CrmConstant.OFFICELOCATION);
+//						bo.set("ID", UUIDGener.getUUID());
+//						SDK.getBOAPI().create(CrmConstant.BO_EU_DNCRM_LOCATION, bo,SDK.getProcessAPI().getInstanceById(accountBo.getBindId()), uc);
+//						//���¿ͻ�����LocationId�ֶ�
+//						accountBo.set("LOCATION_ID", bo.getId());
+//						SDK.getBOAPI().update(CrmConstant.BO_EU_DNCRM_ACCOUNT, bo);
+//					}
 
 					//����ϵ�˱�ͬ������
 					List<BO> mainList = SDK.getBOAPI().query(CrmConstant.BO_EU_DNCRM_CONTACT).addQuery("ACCOUNT_ID=", accountBo.getId()).addQuery("IS_PRIMARY_CONTACT=", 1).list();
@@ -137,16 +137,16 @@ public class FormAfterSaveEvent
 				}
 
 				//˵���ǵ�ַ��������
-				else if (CrmConstant.BO_EU_DNCRM_LOCATION.equals(boName)) {
-					BO locationBo = process.getBO(CrmConstant.BO_EU_DNCRM_LOCATION);
-					if (locationBo != null && "1".equals(locationBo.getString("IS_DEFAULT"))) {
-						accountBo.set("COUNTRY_ID", locationBo.get("COUNTRY_ID"));
-						accountBo.set("DISTRICT_ID", locationBo.get("DISTRICT_ID"));
-						accountBo.set("ADDRESS", locationBo.get("ADDRESS"));
-						accountBo.set("POSTAL_CODE", locationBo.get("POSTAL_CODE"));
-						SDK.getBOAPI().update(CrmConstant.BO_EU_DNCRM_ACCOUNT, accountBo);
-					}
-				}
+//				else if (CrmConstant.BO_EU_DNCRM_LOCATION.equals(boName)) {
+//					BO locationBo = process.getBO(CrmConstant.BO_EU_DNCRM_LOCATION);
+//					if (locationBo != null && "1".equals(locationBo.getString("IS_DEFAULT"))) {
+//						accountBo.set("COUNTRY_ID", locationBo.get("COUNTRY_ID"));
+//						accountBo.set("DISTRICT_ID", locationBo.get("DISTRICT_ID"));
+//						accountBo.set("ADDRESS", locationBo.get("ADDRESS"));
+//						accountBo.set("POSTAL_CODE", locationBo.get("POSTAL_CODE"));
+//						SDK.getBOAPI().update(CrmConstant.BO_EU_DNCRM_ACCOUNT, accountBo);
+//					}
+//				}
 			}
 		} catch (Exception e) {
 			// TODO Auto-generated catch block

+ 101 - 27
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/controller/contractApproveController.java

@@ -1611,31 +1611,35 @@ public class contractApproveController {
             if (totalReceiveAmount == null || totalReceiveAmount.compareTo(BigDecimal.ZERO) <= 0) {
                 return ResponseObject.newErrResponse("总收款金额不能为空且需大于0");
             }
+            // 强制总收款金额保留2位小数,符合财务金额规范,杜绝前端传入多位小数
+            totalReceiveAmount = totalReceiveAmount.setScale(2, BigDecimal.ROUND_HALF_UP);
 
-            // 2. 查询bindid关联的明细记录(核心修改:查询字段改为INVOICE_AMOUNT)
+            // 2. 查询bindid关联的明细记录(核心:查询字段为INVOICE_AMOUNT)
             String querySql = "SELECT ID, INVOICE_AMOUNT FROM BO_EU_DNCTT_INVOICE_PLAN_DETAIL WHERE BINDID = ?";
             List<Object> queryParams = new ArrayList<>();
             queryParams.add(bindid.trim());
             List<RowMap> detailList = DBSql.getMaps(querySql, queryParams.toArray());
 
             // 3. 预处理:统计总发票金额 + 校验数据(变量名同步改为发票金额相关)
-            Map<String, BigDecimal> idInvoiceAmountMap = new HashMap<>(); // 原idBaseAmountMap → 改为发票金额映射
-            BigDecimal totalInvoiceAmount = BigDecimal.ZERO; // 原totalBaseAmount → 改为总发票金额(比例计算基数)
+            Map<String, BigDecimal> idInvoiceAmountMap = new HashMap<>(); // 明细ID-发票金额映射
+            BigDecimal totalInvoiceAmount = BigDecimal.ZERO; // 总发票金额(比例计算基数)
             for (RowMap row : detailList) {
                 String id = row.getString("ID");
-                // 处理发票金额:从INVOICE_AMOUNT字段获取(核心修改)
+                // 处理发票金额:从INVOICE_AMOUNT字段获取
                 String invoiceAmountStr = row.getString("INVOICE_AMOUNT");
                 BigDecimal invoiceAmount = null;
                 if (invoiceAmountStr != null && !invoiceAmountStr.trim().isEmpty()) {
                     try {
                         invoiceAmount = new BigDecimal(invoiceAmountStr.trim());
+                        // 强制发票金额保留2位小数,杜绝多位小数导致的比例偏差、单条固定少0.02
+                        invoiceAmount = invoiceAmount.setScale(2, BigDecimal.ROUND_HALF_UP);
                     } catch (NumberFormatException e) {
-                        System.out.println("发票金额格式错误,ID:" + id + ",值:" + invoiceAmountStr); // 日志同步修改
+                        System.out.println("发票金额格式错误,ID:" + id + ",值:" + invoiceAmountStr);
                         continue;
                     }
                 }
                 if (invoiceAmount == null || invoiceAmount.compareTo(BigDecimal.ZERO) <= 0) {
-                    System.out.println("发票金额非法(空或<=0),ID:" + id); // 日志同步修改
+                    System.out.println("发票金额非法(空或<=0),ID:" + id);
                     continue;
                 }
                 idInvoiceAmountMap.put(id, invoiceAmount);
@@ -1647,7 +1651,7 @@ public class contractApproveController {
                 Map<String, Object> result = new HashMap<>();
                 result.put("totalMatched", detailList.size());
                 result.put("updateCount", 0);
-                result.put("message", "bindid关联明细的总发票金额为0,无法计算比例"); // 提示信息修改
+                result.put("message", "bindid关联明细的总发票金额为0,无法计算比例");
                 ResponseObject responseObject = ResponseObject.newOkResponse();
                 responseObject.setData(result);
                 return responseObject;
@@ -1658,30 +1662,36 @@ public class contractApproveController {
             connUpdate.setAutoCommit(false);
 
             int updateCount = 0;
-
-            // 5. 循环按比例分配并更新 RECEIVE_AMOUNT 字段(使用总发票金额计算比例)
-            for (String id : idInvoiceAmountMap.keySet()) {
-                // 按比例计算:使用发票金额作为基数(变量名同步修改)
+            // 转换为有序列表,保证最后一条兜底逻辑生效
+            List<String> detailIdList = new ArrayList<>(idInvoiceAmountMap.keySet());
+            int detailSize = detailIdList.size();
+            BigDecimal sumAllocatedAmount = BigDecimal.ZERO; // 前N-1条累计分配金额
+
+            // 5. 循环按比例分配并更新 RECEIVE_AMOUNT 字段(核心修复:最后一条兜底,彻底解决金额偏差)
+            for (int i = 0; i < detailSize; i++) {
+                String id = detailIdList.get(i);
                 BigDecimal invoiceAmount = idInvoiceAmountMap.get(id);
-                BigDecimal ratio = invoiceAmount.divide(totalInvoiceAmount, 6, BigDecimal.ROUND_HALF_UP);
-                BigDecimal allocatedReceiveAmount = totalReceiveAmount.multiply(ratio).setScale(2, BigDecimal.ROUND_HALF_UP);
+                BigDecimal allocatedReceiveAmount;
 
-                // 构建更新SQL(仅更新RECEIVE_AMOUNT)
-                StringBuilder updateSql = new StringBuilder("UPDATE BO_EU_DNCTT_INVOICE_PLAN_DETAIL SET ");
-                List<Object> updateParams = new ArrayList<>();
+                if (i == detailSize - 1) {
+                    // ========== 核心:最后1条兜底 ==========
+                    // 用总收款金额 - 前N-1条累计金额,补足所有差额,总额100%精准无损耗
+                    allocatedReceiveAmount = totalReceiveAmount.subtract(sumAllocatedAmount);
+                } else {
+                    // 前N-1条:比例精度拉满至12位,最小化单条偏差(从0.02→0.01)
+                    BigDecimal ratio = invoiceAmount.divide(totalInvoiceAmount, 12, BigDecimal.ROUND_HALF_UP);
+                    allocatedReceiveAmount = totalReceiveAmount.multiply(ratio).setScale(2, BigDecimal.ROUND_HALF_UP);
+                    sumAllocatedAmount = sumAllocatedAmount.add(allocatedReceiveAmount);
+                }
 
-                updateSql.append("RECEIVE_AMOUNT = ?, ");
+                // 构建更新SQL(无拼接冗余逗号,彻底解决SQL语法错误)
+                String updateSql = "UPDATE BO_EU_DNCTT_INVOICE_PLAN_DETAIL SET RECEIVE_AMOUNT = ? WHERE ID = ?";
+                List<Object> updateParams = new ArrayList<>();
                 updateParams.add(allocatedReceiveAmount);
-
-                // 移除多余逗号
-                if (updateSql.charAt(updateSql.length() - 2) == ',') {
-                    updateSql.setLength(updateSql.length() - 2);
-                }
-                updateSql.append(" WHERE ID = ?");
                 updateParams.add(id.trim());
 
                 // 执行更新
-                int affectedRows = DBSql.update(connUpdate, updateSql.toString(), updateParams.toArray());
+                int affectedRows = DBSql.update(connUpdate, updateSql, updateParams.toArray());
                 if (affectedRows > 0) {
                     updateCount++;
                 }
@@ -1690,19 +1700,21 @@ public class contractApproveController {
             // 6. 提交事务
             connUpdate.commit();
 
-            // 7. 返回结果(字段同步改为总发票金额)
+            // 7. 返回结果(字段同步改为总发票金额,返回精准分配数据
             Map<String, Object> result = new HashMap<>();
             result.put("totalMatched", detailList.size());
             result.put("updateCount", updateCount);
             result.put("totalReceiveAmount", totalReceiveAmount);
-            result.put("totalInvoiceAmount", totalInvoiceAmount); // 原totalBaseAmount → 改为totalInvoiceAmount
-            result.put("message", "按比例更新RECEIVE_AMOUNT完成,共更新" + updateCount + "条明细记录");
+            result.put("totalInvoiceAmount", totalInvoiceAmount);
+            result.put("actualAllocatedTotal", totalReceiveAmount); // 实际分配总额=总收款,绝对一致
+            result.put("message", "按比例更新RECEIVE_AMOUNT完成,共更新" + updateCount + "条明细记录,金额无差额");
 
             ResponseObject responseObject = ResponseObject.newOkResponse();
             responseObject.setData(result);
             return responseObject;
 
         } catch (SQLException e) {
+            // 事务回滚:增强容错,确保异常时数据回滚
             if (connUpdate != null && !connUpdate.isClosed()) {
                 try {
                     connUpdate.rollback();
@@ -1713,6 +1725,7 @@ public class contractApproveController {
             e.printStackTrace();
             return ResponseObject.newErrResponse("更新RECEIVE_AMOUNT失败:" + e.getMessage());
         } finally {
+            // 关闭连接:安全兜底,避免连接泄漏
             if (connUpdate != null && !connUpdate.isClosed()) {
                 DBSql.close(connUpdate);
             }
@@ -2684,6 +2697,67 @@ public class contractApproveController {
         return BigDecimal.ZERO;
     }
 
+    @Mapping(value = "com.awspaas.user.apps.donenow_ctt.updateContractEndDate")
+    public ResponseObject updateContractEndDate(UserContext uc, String ids, String periodEndDate) {
+        if (StringUtils.isBlank(ids)) {
+            return ResponseObject.newErrResponse("ids 不能为空");
+        }
+        if (StringUtils.isBlank(periodEndDate)) {
+            return ResponseObject.newErrResponse("PERIOD_END_DATE 不能为空");
+        }
+
+        try {
+            String[] idArray = ids.split(",");
+            StringBuilder validIdSb = new StringBuilder();
+            int validIdCount = 0;
+            for (String id : idArray) {
+                if (StringUtils.isNotBlank(id)) {
+                    if (validIdSb.length() > 0) {
+                        validIdSb.append(",");
+                    }
+                    validIdSb.append("?");
+                    validIdCount++;
+                }
+            }
+            if (validIdCount == 0) {
+                return ResponseObject.newErrResponse("无有效合同ID");
+            }
+
+            String updateSql = String.format(
+                    "UPDATE BO_EU_DNCTT_CONTRACT SET CONTRACT_END_DATE = ?, END_DATE = ? WHERE ID IN (%s)",
+                    validIdSb.toString()
+            );
+            System.out.println("最终执行的SQL:" + updateSql);
+
+            Object[] params = new Object[2 + validIdCount];
+            params[0] = periodEndDate;
+            params[1] = periodEndDate;
+            int paramIndex = 2;
+            for (String id : idArray) {
+                if (StringUtils.isNotBlank(id)) {
+                    params[paramIndex++] = id.trim();
+                }
+            }
+            System.out.println("参数数组长度:" + params.length + ",占位符数量:" + (2 + validIdCount)); // 调试用
+
+            int updatedRows = DBSql.update(updateSql, params);
+
+            Map<String, Object> result = new HashMap<>();
+            result.put("ids", ids);
+            result.put("periodEndDate", periodEndDate);
+            result.put("updatedRows", updatedRows);
+            result.put("validIdCount", validIdCount);
+
+            ResponseObject responseObject = ResponseObject.newOkResponse();
+            responseObject.setData(result);
+            return responseObject;
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            return ResponseObject.newErrResponse("更新失败:" + e.getMessage());
+        }
+    }
+
 
 }
 

+ 65 - 20
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/controller/contractCreateController.java

@@ -87,34 +87,78 @@ public class contractCreateController {
     @Mapping(value = "com.awspaas.user.apps.donenow_ctt.contract_delete")
     public ResponseObject contractDelete(UserContext uc, String bindid, String id) throws SQLException {
 
-        int cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNPRO_PROJECT WHERE CONTRACT_ID=? AND CLOSED=0", new Object[]{id});
-        if (cnt > 0) {
-            return ResponseObject.newErrResponse("该合同关联了" + cnt + "个项目,无法删除!");
+        StringBuilder errorMsg = new StringBuilder();
+
+// 1. 查询“项目”关联数
+        String projectSql = "SELECT COUNT(0) FROM BO_EU_DNPRO_PROJECT WHERE CONTRACT_ID=? AND CLOSED=0";
+        String projectCountStr = DBSql.getString(projectSql, new Object[]{id});
+        int projectCount = StringUtils.isBlank(projectCountStr) ? 0 : Integer.parseInt(projectCountStr);
+        if (projectCount > 0) {
+            errorMsg.append(projectCount).append("个项目");
         }
-        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNSDK_TASK WHERE CONTRACT_ID=? ", new Object[]{id});
-        if (cnt > 0) {
-            return ResponseObject.newErrResponse("该合同关联了" + cnt + "个任务,无法删除!");
+
+// 2. 查询“工单/任务”关联数
+        String taskSql = "SELECT COUNT(0) FROM BO_EU_DNSDK_TASK WHERE CONTRACT_ID=?";
+        String taskCountStr = DBSql.getString(taskSql, new Object[]{id});
+        int taskCount = StringUtils.isBlank(taskCountStr) ? 0 : Integer.parseInt(taskCountStr);
+        if (taskCount > 0) {
+            if (errorMsg.length() > 0) errorMsg.append("\n"); // 替换为纯文本换行符
+            errorMsg.append(taskCount).append("个工单/任务");
         }
-        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNCRM_INSTALLED_PRODUCT WHERE CONTRACT_ID=? AND CLOSED=0", new Object[]{id});
-        if (cnt > 0) {
-            return ResponseObject.newErrResponse("该合同关联了" + cnt + "个产品,无法删除!");
+
+// 3. 查询“配置项”关联数
+        String productSql = "SELECT COUNT(0) FROM BO_EU_DNCRM_INSTALLED_PRODUCT WHERE CONTRACT_ID=? AND CLOSED=0";
+        String productCountStr = DBSql.getString(productSql, new Object[]{id});
+        int productCount = StringUtils.isBlank(productCountStr) ? 0 : Integer.parseInt(productCountStr);
+        if (productCount > 0) {
+            if (errorMsg.length() > 0) errorMsg.append("\n"); // 替换为纯文本换行符
+            errorMsg.append(productCount).append("个配置项");
         }
-        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNCTT_CONTRACT_COST WHERE CONTRACT_ID=? AND CLOSED=0", new Object[]{id});
-        if (cnt > 0) {
-            return ResponseObject.newErrResponse("该合同关联了" + cnt + "个合同成本,无法删除!");
+
+// 4. 查询“合同产品”关联数
+        String contractCostSql = "SELECT COUNT(0) FROM BO_EU_DNCTT_CONTRACT_COST WHERE CONTRACT_ID=? AND CLOSED=0";
+        String contractCostCountStr = DBSql.getString(contractCostSql, new Object[]{id});
+        int contractCostCount = StringUtils.isBlank(contractCostCountStr) ? 0 : Integer.parseInt(contractCostCountStr);
+        if (contractCostCount > 0) {
+            if (errorMsg.length() > 0) errorMsg.append("\n"); // 替换为纯文本换行符
+            errorMsg.append(contractCostCount).append("个合同产品");
         }
-        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNCRM_ACCOUNT_DEDUCTION WHERE CONTRACT_ID=? ", new Object[]{id});
-        if (cnt > 0) {
-            return ResponseObject.newErrResponse("该合同关联了" + cnt + "个已计费条目,无法删除!");
+
+// 5. 查询“已计费条目”关联数
+        String deductionSql = "SELECT COUNT(0) FROM BO_EU_DNCRM_ACCOUNT_DEDUCTION WHERE CONTRACT_ID=?";
+        String deductionCountStr = DBSql.getString(deductionSql, new Object[]{id});
+        int deductionCount = StringUtils.isBlank(deductionCountStr) ? 0 : Integer.parseInt(deductionCountStr);
+        if (deductionCount > 0) {
+            if (errorMsg.length() > 0) errorMsg.append("\n"); // 替换为纯文本换行符
+            errorMsg.append(deductionCount).append("个已计费条目");
         }
-        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNSDK_WORK_ENTRY WHERE CONTRACT_ID=? ", new Object[]{id});
-        if (cnt > 0) {
-            return ResponseObject.newErrResponse("该合同关联了" + cnt + "个工时,无法删除!");
+
+// 6. 查询“工时”关联数
+        String workEntrySql = "SELECT COUNT(0) FROM BO_EU_DNSDK_WORK_ENTRY WHERE CONTRACT_ID=?";
+        String workEntryCountStr = DBSql.getString(workEntrySql, new Object[]{id});
+        int workEntryCount = StringUtils.isBlank(workEntryCountStr) ? 0 : Integer.parseInt(workEntryCountStr);
+        if (workEntryCount > 0) {
+            if (errorMsg.length() > 0) errorMsg.append("\n"); // 替换为纯文本换行符
+            errorMsg.append(workEntryCount).append("个工时");
+        }
+
+        String contractServiceSql = "SELECT COUNT(0) FROM BO_EU_DNCTT_CONTRACT_SERVICE WHERE CONTRACT_ID=? AND CLOSED=0";
+        String contractServiceCountStr = DBSql.getString(contractServiceSql, new Object[]{id});
+        int contractServiceCount = StringUtils.isBlank(contractServiceCountStr) ? 0 : Integer.parseInt(contractServiceCountStr);
+        if (contractServiceCount > 0) {
+            if (errorMsg.length() > 0) errorMsg.append("\n"); // 替换为纯文本换行符
+            errorMsg.append(contractServiceCount).append("个合同服务");
+        }
+
+        if (errorMsg.length() > 0) {
+            return ResponseObject.newErrResponse("该合同关联了:\n" + errorMsg.toString() + ",无法删除!");
+            // 开头也添加\n,让第一个关联项也换行显示(可选,根据排版需求调整)
         }
 
+
         //如果服务已经审批并提交,则不能删除
         //合同初始费用审批
-        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNCTT_CONTRACT WHERE (ID=? OR BINDID=?) AND SETUP_FEE!=0 AND SETUP_FEE IS NOT NULL AND CLOSED=0", new Object[]{id, bindid});
+        int cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNCTT_CONTRACT WHERE (ID=? OR BINDID=?) AND SETUP_FEE!=0 AND SETUP_FEE IS NOT NULL AND CLOSED=0", new Object[]{id, bindid});
         if (cnt > 0) {
             return ResponseObject.newErrResponse("该合同合同初始费用已审批并提交,无法删除!");
         }
@@ -144,7 +188,8 @@ public class contractCreateController {
             updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_SERVICE", params, conn);
             updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_COST_DEFAULT", params, conn);
 
-            updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST", params, conn);
+            updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_SERVICE_PURCHASE_ADJUST", params, conn);
+            updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_SERVICE_SALE_ADJUST", params, conn);
             updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", params, conn);
 
             updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_SERVICE_BUNDLE_SERVICE", " CONTRACT_SERVICE_ID IN (SELECT ID FROM BO_EU_DNCTT_CONTRACT_SERVICE WHERE (CONTRACT_ID=:CONTRACT_ID OR BINDID=:BINDID)) ", params, conn);

+ 1 - 0
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/event/PeriodForm.java

@@ -43,6 +43,7 @@ public class PeriodForm implements DataWindowFormatSQLEventInterface {
                     standardSql += " " + newCondition;
                 }
 
+
                 LOGGER.info("周期查询替换完成,中间SQL:" + standardSql);
             }
 

+ 45 - 8
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/event/costProductShipFormAfterSave.java

@@ -10,6 +10,7 @@ import com.actionsoft.sdk.local.SDK;
 import org.apache.commons.lang3.StringUtils;
 
 import java.sql.Connection;
+import java.sql.SQLException;
 import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.List;
@@ -198,15 +199,29 @@ public class costProductShipFormAfterSave extends ExecuteListener {
                 //生成运费成本
                 // 输入运费的物料计费代码,就会生成新的成本ctt_contract_cost
                 if (StringUtils.isNotBlank(productBO.getString("COST_CODE_ID"))) {
+
                     BO costShipBO = null;
                     ProcessInstance costShipProcessIns = null;
-                    if (StringUtils.isNotBlank(productBO.getString("SHIPPING_CONTRACT_COST_ID"))) {
-                        costShipProcessIns = SDK.getProcessAPI().createBOProcessInstance("obj_87c6ef75d18f44cc8e85d3d4e818c303", processExecutionContext.getUserContext().getUID(), "合同产品-生成运费成本");
+                    String shippingContractCostId = productBO.getString("SHIPPING_CONTRACT_COST_ID");
+                    if (StringUtils.isBlank(shippingContractCostId)) {
+                        // 场景1:无SHIPPING_CONTRACT_COST_ID,创建新流程和BO
+                        costShipProcessIns = SDK.getProcessAPI().createBOProcessInstance("obj_87c6ef75d18f44cc8e85d3d4e818c303",
+                                processExecutionContext.getUserContext().getUID(), "合同产品-生成运费成本");
                         costShipBO = new BO();
                         costShipBO.setBindId(costShipProcessIns.getId());
                     } else {
-                        costShipBO = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT_COST", productBO.getString("SHIPPING_CONTRACT_COST_ID"));
-                        costShipBO.set("CLOSED", 0);
+                        // 场景2:有SHIPPING_CONTRACT_COST_ID,先查询
+                        costShipBO = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT_COST", shippingContractCostId);
+                        if (costShipBO == null) {
+                            // 查询结果为空,仍创建新流程和BO
+                            costShipProcessIns = SDK.getProcessAPI().createBOProcessInstance("obj_87c6ef75d18f44cc8e85d3d4e818c303",
+                                    processExecutionContext.getUserContext().getUID(), "合同产品-生成运费成本");
+                            costShipBO = new BO();
+                            costShipBO.setBindId(costShipProcessIns.getId());
+                        } else {
+                            // 查询结果不为空,重置CLOSED状态
+                            costShipBO.set("CLOSED", 0);
+                        }
                     }
 
                     costShipBO.set("PRODUCT_ID", null);
@@ -268,11 +283,33 @@ public class costProductShipFormAfterSave extends ExecuteListener {
 
                 }
                 conn.commit();
-            } catch (Exception e) {
-                conn.rollback();
+            }catch (Exception e) {
+                // 1. 回滚事务(保持原有逻辑)
+                if (conn != null) {
+                    try {
+                        conn.rollback();
+                    } catch (SQLException rollbackEx) {
+                        // 打印回滚失败日志
+                        System.err.println("事务回滚失败:" + rollbackEx.getMessage());
+                        rollbackEx.printStackTrace();
+                    }
+                }
+
+                // 2. 打印详细异常信息(关键:输出异常类型、消息、堆栈,以及关键业务参数)
+                System.err.println("===== 库存转移异常详情 =====");
+                System.err.println("异常类型:" + e.getClass().getName());
+                System.err.println("异常消息:" + e.getMessage());
+                System.err.println("关键业务参数:");
+                System.err.println("  产品ID:" + (productBO != null ? productBO.getString("PRODUCT_ID") : "null"));
+                System.err.println("  配送数量:" + (productBO != null ? productBO.get("QUANTITY") : "null"));
+                System.err.println("  仓库ID:" + (productBO != null ? productBO.get("WAREHOUSE_ID") : "null"));
+                System.err.println("  产品序列号:" + (productBO != null ? productBO.getString("SHIPPING_SN") : "null"));
+                System.err.println("==========================");
                 e.printStackTrace();
-                throw new BPMNError("550", "库存转移失败");
-            } finally {
+
+                // 3. 抛出兜底异常(保持原有逻辑,可补充具体异常消息)
+                throw new BPMNError("550", "库存转移失败:" + e.getMessage());
+            }finally {
                 DBSql.close(conn);
             }
         }

+ 138 - 47
com.awspaas.user.apps.donenow_ivt/src/com/awspaas/user/apps/donenow_ivt/controller/IVTController.java

@@ -14,6 +14,7 @@ import com.actionsoft.sdk.local.SDK;
 import com.alibaba.fastjson.JSONObject;
 import com.awspaas.user.apps.donenow_ivt.constant.IVTConstant;
 import com.awspaas.user.apps.donenow_ivt.utils.BigDecimalUtil;
+import com.ibm.icu.math.BigDecimal;
 import me.chanjar.weixin.common.util.StringUtils;
 
 import java.sql.Connection;
@@ -164,18 +165,60 @@ public class IVTController extends BaseController {
         return success("");
     }
 
+    @Mapping("com.awspaas.user.apps.donenow_ivt.queryExistPurchasereceive")
+    public String queryExistPurchasereceive(String ids, String bindId, UserContext uc) {
+        // 处理入参:优先用bindId查询采购项ID
+        String pureIds = "";
+        if (StringUtils.isNotBlank(bindId)) {
+            // 核心:通过bindId获取关联的采购项ID(和创建接口逻辑一致)
+            pureIds = DBSql.getString("SELECT GROUP_CONCAT(ID) from bo_eu_dnivt_order_product WHERE BINDID=?", new Object[]{bindId});
+        } else if (StringUtils.isNotBlank(ids)) {
+            pureIds = ids.trim();
+            if (pureIds.endsWith(",")) {
+                pureIds = pureIds.substring(0, pureIds.length() - 1);
+            }
+        } else {
+            return "{\"code\":-1,\"msg\":\"请传入bindId或ids\"}";
+        }
+
+        if (StringUtils.isBlank(pureIds)) {
+            return "{\"code\":-1,\"msg\":\"无已存在的待接收接收单\"}";
+        }
 
+        String quotedIds = "'" + pureIds.replace(",", "','") + "'";
+        String checkSql = "SELECT DISTINCT s.BINDID FROM BO_EU_DNIVT_RECEIVE_SUB s " +
+                "WHERE s.ORDER_PRODUCT_ID IN (" + quotedIds + ") ";
+        String existBindId = DBSql.getString(checkSql, new Object[]{});
+
+        if (StringUtils.isNotBlank(existBindId)) {
+            String url = SDK.getFormAPI().getFormURL("", uc.getSessionId(), existBindId, "", 1, "", "", "");
+            url = SDK.getPortalAPI().getPortalUrl() + "/r" + url.substring(1);
+            return "{\"code\":0,\"msg\":\"操作成功\",\"data\":\"" + url + "\"}";
+        } else {
+            return "{\"code\":-1,\"msg\":\"无已存在的待接收接收单\"}";
+        }
+    }
+    /**
+     *
+     *采购项接收
+     */
     /**
      *
      *采购项接收
      */
+
+
     @Mapping("com.awspaas.user.apps.donenow_ivt.purchasereceive")
     public String purchasereceive(String ids, UserContext uc, String bindId) {
         System.out.print("idsids:" + ids);
         System.out.print("bindId:" + bindId);
 
+        // ===== 1. 订单项ID预处理 + 非空校验 =====
         if (StringUtils.isNotBlank(ids)) {
-            ids = ids.substring(0, ids.length() - 1);
+            ids = ids.trim();
+            if (ids.endsWith(",")) {
+                ids = ids.substring(0, ids.length() - 1);
+            }
         } else if (StringUtils.isNotBlank(bindId)) {
             ids = DBSql.getString("SELECT GROUP_CONCAT(ID) from bo_eu_dnivt_order_product WHERE BINDID=?", new Object[]{bindId});
         } else {
@@ -184,7 +227,7 @@ public class IVTController extends BaseController {
         ids = "'" + ids.replace(",", "','") + "'";
         System.out.print("idsids:" + ids);
 
-        //采购接收查询SQL
+        // ===== 2. 采购订单项及关联数据查询 =====
         String sql = "  select op.CONTRACT_COST_ID,a.IS_SERIALIZED,o.CREATEDATE,o.FREIGHT_COST,op.WAREHOUSE_ID,op.product_id,op.id,c.id as contract_id,p.id as project_id,t.id as task_id,op.product_id," +
                 "   ifnull(c.account_id,ifnull(p.account_id,t.account_id))as account_id,b.opportunity_id," +
                 "  o.vendor_account_id,if(c.name is not null,'contract',if(p.name is not null,'project'," +
@@ -220,90 +263,138 @@ public class IVTController extends BaseController {
         if (maps.size() == 0) {
             return fail("请检查采购订单是否未审批!");
         }
-        ProcessInstance createBOProcessInstance = SDK.getProcessAPI().createBOProcessInstance(IVTConstant.obj_3121af9039dc454aa776f9bdadf6b239, uc.getUID(), "");
-        //校验是否是同一个采购订单
+
+        // ===== 3. 提取采购订单号 + 校验:单次只能处理同一个采购订单 =====
+        String purchaseOrderNo = null;
         Set<String> set = new HashSet<String>();
-        List<BO> list = new ArrayList<BO>();
         for (RowMap map : maps) {
-            if (StringUtils.isNotBlank(map.getString("PURCHASE_ORDER_NO")) && StringUtils.isNotBlank(map.getString("PURCHASE_ORDER_NO").trim())) {
-                set.add(map.getString("PURCHASE_ORDER_NO"));
+            String poNo = map.getString("purchase_order_no");
+            if (StringUtils.isNotBlank(poNo) && StringUtils.isNotBlank(poNo.trim())) {
+                set.add(poNo);
+                purchaseOrderNo = poNo;
             }
-
         }
         if (set.size() > 1) {
             System.out.println("setsetset:" + set);
-
             return fail("一次只能接收同一个采购订单的采购项");
         }
-        for (RowMap orgMap : maps) {
 
-            System.out.println("map:" + orgMap);
+        // ===== 4. 核心修复:防重复创建【适配主表无STATUS字段】✅✅
+        // 关键改动:主表仅按采购单号判断是否存在,彻底删除STATUS查询条件,杜绝报错
+        BO existingMainBO = null;
+        if (StringUtils.isNotBlank(purchaseOrderNo)) {
+            long count = SDK.getBOAPI().query(IVTConstant.BO_EU_DNIVT_RECEIVE_MAIN)
+                    .addQuery("PURCHASE_ORDER_NO=", purchaseOrderNo)
+                    .count();
+            // 只要存在同采购单号的主单,直接获取并返回,不区分状态
+            if (count > 0) {
+                existingMainBO = SDK.getBOAPI().query(IVTConstant.BO_EU_DNIVT_RECEIVE_MAIN)
+                        .addQuery("PURCHASE_ORDER_NO=", purchaseOrderNo)
+                        .detail();
+            }
+        }
 
-            Map<String, Object> map = convertKeysToUppercase(orgMap);
+        // ===== 5. 存在已有单据,直接返回URL,终止创建(重复打开不报错)✅✅
+        if (existingMainBO != null) {
+            String processInstanceId = existingMainBO.getBindId();
+            String url = SDK.getFormAPI().getFormURL("", uc.getSessionId(), processInstanceId, "", 1, "", "", "");
+            url = SDK.getPortalAPI().getPortalUrl() + "/r" + url.substring(1);
+            return success(url);
+        }
 
-            //采购数量不能为空,也不能小于0
-            Object purchaseQuantity = map.get("QUANTITY");
+        // ===== 6. 初始化流程实例ID(主/子表绑定)=====
+        ProcessInstance createBOProcessInstance = SDK.getProcessAPI().createBOProcessInstance(IVTConstant.obj_3121af9039dc454aa776f9bdadf6b239, uc.getUID(), "");
 
-            System.out.println("purchaseQuantity:" + purchaseQuantity);
-            if (purchaseQuantity == null || StringUtils.isBlank(purchaseQuantity.toString()) || Double.parseDouble(purchaseQuantity.toString()) <= 0) {
+        // ===== 7. 构建采购接收子表数据(子表独有STATUS,初始待接收)✅✅
+        List<BO> list = new ArrayList<BO>();
+        for (RowMap orgMap : maps) {
+            System.out.println("map:" + orgMap);
+            // 原生RowMap取值,无方法歧义、零报错
+            String opId = orgMap.getString("id");
+            String productName = orgMap.getString("product_name");
+            String warehouseId = orgMap.getString("warehouse_id");
+            String accountName = orgMap.getString("account_name");
+            String contracName = orgMap.getString("contrac_project_task_name");
+            String productId = orgMap.getString("product_id");
+            String contractCostId = orgMap.getString("contract_cost_id");
+            String isSerialized = orgMap.getString("IS_SERIALIZED");
+            Object quantityObj = orgMap.get("quantity");
+            Object onHandObj = orgMap.get("on_hand");
+
+            // 过滤无效项:采购数量为空/≤0 跳过
+            if (quantityObj == null || StringUtils.isBlank(quantityObj.toString()) || Double.parseDouble(quantityObj.toString()) <= 0) {
                 continue;
             }
 
-            String unBack = "0";
+            int totalQty = Integer.parseInt(quantityObj.toString());
             BO subBo = new BO();
-            set.add(getString(map, "PURCHASE_ORDER_NO"));
-            RowMap rowMap = DBSql.getMap("select ORDER_PRODUCT_ID,sum(QUANTITY_RECEIVED) QUANTITY_RECEIVED from BO_EU_DNIVT_RECEIVE WHERE  ORDER_PRODUCT_ID=? group by ORDER_PRODUCT_ID", new Object[]{getString(map, "ID")});
-            if (null != rowMap) {
-                unBack = getUnBack(getString(map, "QUANTITY"), rowMap.getString("QUANTITY_RECEIVED"));
-                if (Integer.valueOf(unBack) <= 0) {
-                    // System.out.println("采购数量小于已接收数量:" +getString(map,"QUANTITY") + "-----" + rowMap.getString("QUANTITY_RECEIVED"));
-                    // continue;
-                }
-                subBo.set("QUANTITY_RECEIVED", rowMap.get("QUANTITY_RECEIVED"));
-                subBo.set("QUANTITY_BACKORDERED", unBack);
-            } else {
-                subBo.set("QUANTITY_RECEIVED", 0);
-                subBo.set("QUANTITY_BACKORDERED", map.get("QUANTITY"));
+            int historyReceive = 0;
+
+            // 查询子表历史累计接收数(子表有数据统计能力)
+            RowMap rowMap = DBSql.getMap(
+                    "select sum(QUANTITY_RECEIVED) QUANTITY_RECEIVED from BO_EU_DNIVT_RECEIVE_SUB WHERE ORDER_PRODUCT_ID=? group by ORDER_PRODUCT_ID",
+                    new Object[]{opId}
+            );
+            if (null != rowMap && rowMap.get("QUANTITY_RECEIVED") != null) {
+                historyReceive = Integer.parseInt(rowMap.get("QUANTITY_RECEIVED").toString());
             }
-            subBo.set("PRODUCT_NAME", map.get("PRODUCT_NAME"));
-            subBo.set("IS_SERIALIZED", map.get("IS_SERIALIZED"));
-            subBo.set("WAREHOUSE_ID", map.get("WAREHOUSE_ID"));
-            subBo.set("ACCOUNT_NAME", map.get("ACCOUNT_NAME"));
-            subBo.set("CONTRAC_PROJECT_TASK_NAME", map.get("CONTRAC_PROJECT_TASK_NAME"));
-            subBo.set("QUANTITY", map.get("QUANTITY"));
-            subBo.set("ON_HAND", map.get("ON_HAND"));
-            subBo.set("PRODUCT_ID", map.get("PRODUCT_ID"));
-            subBo.set("ORDER_PRODUCT_ID", map.get("ID"));
-            subBo.set("NOW_COUNT", 0);
-            subBo.set("CONTRACT_COST_ID", map.get("CONTRACT_COST_ID"));
+
+            // 正确计算字段初始值
+            int totalReceive = historyReceive;  // 累计接收数=历史数,初始0
+            String unBack = String.valueOf(totalQty - totalReceive); // 未收货=采购数-历史数
+
+            // ✅ 核心:子表设置STATUS,初始强制为【待接收】,手动填数后由业务端更新
             subBo.set("STATUS", "待接收");
-            subBo.set("WHOLE", DBSql.getInt("select count(1) AS CNT from BO_EU_DNCTT_CONTRACT_COST where ID=? AND IS_SERVICE_PRODUCT=1", new Object[]{map.get("CONTRACT_COST_ID")}));//判断是否整体接收
+            // ✅ 本次接收数初始0,必须手动输入保存后生效
+            subBo.set("NOW_COUNT", 0);
+
+            // 子表字段赋值(完整、无遗漏)
+            subBo.set("QUANTITY_RECEIVED", totalReceive);
+            subBo.set("QUANTITY_BACKORDERED", unBack);
+            subBo.set("PRODUCT_NAME", productName);
+            subBo.set("IS_SERIALIZED", isSerialized);
+            subBo.set("WAREHOUSE_ID", warehouseId);
+            subBo.set("ACCOUNT_NAME", accountName);
+            subBo.set("CONTRAC_PROJECT_TASK_NAME", contracName);
+            subBo.set("QUANTITY", totalQty);
+            subBo.set("ON_HAND", onHandObj);
+            subBo.set("PRODUCT_ID", productId);
+            subBo.set("ORDER_PRODUCT_ID", opId);
+            subBo.set("CONTRACT_COST_ID", contractCostId);
+
+            // 查询WHOLE字段值
+            int whole = DBSql.getInt("select count(1) AS CNT from BO_EU_DNCTT_CONTRACT_COST where ID=? AND IS_SERVICE_PRODUCT=1", new Object[]{contractCostId});
+            subBo.set("WHOLE", whole);
+
             subBo.setBindId(createBOProcessInstance.getId());
             list.add(subBo);
         }
+
+        // 无有效子表数据,返回失败
         if (list.size() == 0) {
             return fail("采购订单项都已经接收完毕");
         }
-        //采购接收主表添加数据
+
+        // ===== 8. 创建采购接收主表数据(无STATUS字段,适配表结构)✅✅
         RowMap rowMap = maps.get(0);
         BO detail = SDK.getBOAPI().query(IVTConstant.BO_EU_DNIVT_ORDER).addQuery("PURCHASE_ORDER_NO=", rowMap.getString("purchase_order_no")).detail();
         if (null == detail) {
             return fail("未找到对应的订单");
         }
-        //主表数据
+
         BO bo = new BO();
         bo.setBindId(createBOProcessInstance.getId());
-        bo.setId(UUIDGener.getUUID());
+        bo.setId(UUID.randomUUID().toString().replace("-",""));
         bo.set("COMPANY", rowMap.getString("vendor"));
         bo.set("PURCHASE_ORDER_NO", rowMap.getString("purchase_order_no"));
         bo.set("SUBMIT_TIME", rowMap.get("CREATEDATE"));
-        bo.set("VENDOR_INVOICE_NO", rowMap.get("VENDOR_INVOICE_NO"));
         bo.set("FREIGHT_COST", rowMap.get("FREIGHT_COST"));
         SDK.getBOAPI().create(IVTConstant.BO_EU_DNIVT_RECEIVE_MAIN, bo, createBOProcessInstance, uc);
 
-        //子表数据
+        // ===== 9. 创建采购接收子表数据(携带STATUS字段)=====
         SDK.getBOAPI().create(IVTConstant.BO_EU_DNIVT_RECEIVE_SUB, list, createBOProcessInstance, uc);
 
+        // ===== 10. 生成单据URL并返回 =====
         String url = SDK.getFormAPI().getFormURL("", uc.getSessionId(), createBOProcessInstance.getId(), "", 1, "", "", "");
         url = SDK.getPortalAPI().getPortalUrl() + "/r" + url.substring(1);
         return success(url);

+ 74 - 0
com.awspaas.user.apps.donenow_ivt/src/com/awspaas/user/apps/donenow_ivt/controller/caiController.java

@@ -9,6 +9,7 @@ import com.actionsoft.bpms.server.bind.annotation.Controller;
 import com.actionsoft.bpms.server.bind.annotation.Mapping;
 import com.actionsoft.bpms.util.DBSql;
 import com.actionsoft.sdk.local.SDK;
+import com.awspaas.user.apps.donenow_ivt.constant.IVTConstant;
 import org.apache.commons.lang3.StringUtils;
 
 import java.util.*;
@@ -421,5 +422,78 @@ public class caiController {
         return responseObject;
     }
 
+    @Mapping(value = "com.awspaas.user.apps.donenow_ivt.checkDeliveryTimeRequired")
+    public ResponseObject checkDeliveryTimeRequired(UserContext uc, String purchaseOrderId) {
+        if (StringUtils.isBlank(purchaseOrderId)) {
+            return ResponseObject.newErrResponse("采购订单ID不能为空");
+        }
+
+        String countSql = "select count(1) from `bo_eu_dnivt_order_product` where order_id = ?";
+        String countStr = DBSql.getString(countSql, new Object[]{purchaseOrderId});
+        if (StringUtils.isBlank(countStr) || "0".equals(countStr.trim())) {
+            ResponseObject responseObject = ResponseObject.newOkResponse();
+            responseObject.put("message", "未找到ORDER_ID为【" + purchaseOrderId + "】的采购项信息");
+            responseObject.put("deliveryTimeRequired", false);
+            responseObject.setData(Collections.emptyList());
+            return responseObject;
+        }
+
+        boolean deliveryTimeRequired = false;
+        String errorMsg = "";
+
+        String productSql = "select CONTRACT_COST_ID from `bo_eu_dnivt_order_product` where order_id = ?";
+        List<String> contractCostIdList = new ArrayList<>();
+        String costIdSql = "select GROUP_CONCAT(CONTRACT_COST_ID) from `bo_eu_dnivt_order_product` where order_id = ?";
+        String costIdsStr = DBSql.getString(costIdSql, new Object[]{purchaseOrderId});
+        if (StringUtils.isNotBlank(costIdsStr)) {
+            String[] costIdArray = costIdsStr.split(",");
+            for (String costId : costIdArray) {
+                if (StringUtils.isNotBlank(costId)) {
+                    contractCostIdList.add(costId.trim());
+                }
+            }
+        }
+
+        for (String contractCostId : contractCostIdList) {
+            if (StringUtils.isBlank(contractCostId)) {
+                deliveryTimeRequired = true;
+                errorMsg = "存在成本ID为空的采购项,配送日期必填";
+                break;
+            }
+
+            String serviceSql = "select IS_SERVICE_PRODUCT from `bo_eu_dnctt_contract_cost` where ID = ?";
+            String isServiceProduct = DBSql.getString(serviceSql, new Object[]{contractCostId});
+
+            if (StringUtils.isBlank(isServiceProduct)) {
+                deliveryTimeRequired = true;
+                errorMsg = "成本ID【" + contractCostId + "】无对应合同产品,配送日期必填";
+                break;
+            }
+
+            if (!"1".equals(isServiceProduct.trim())) {
+                deliveryTimeRequired = true;
+                errorMsg = "存在非服务创建的合同产品(成本ID:" + contractCostId + "),配送日期必填";
+                break;
+            }
+        }
+
+        List<Map<String, Object>> resultList = new ArrayList<>();
+        Map<String, Object> checkInfo = new HashMap<>();
+        checkInfo.put("purchaseOrderId", purchaseOrderId);
+        checkInfo.put("deliveryTimeRequired", deliveryTimeRequired);
+        checkInfo.put("message", deliveryTimeRequired ? errorMsg : "所有采购项均符合要求,配送日期非必填");
+        resultList.add(checkInfo);
+
+        ResponseObject responseObject = ResponseObject.newOkResponse();
+        if (deliveryTimeRequired) {
+            responseObject.put("message", errorMsg);
+        } else {
+            responseObject.put("message", "未找到需要必填配送日期的场景,配送日期非必填");
+        }
+        responseObject.setData(resultList);
+        return responseObject;
+    }
+
+
 
 }

+ 12 - 7
com.awspaas.user.apps.donenow_ivt/src/com/awspaas/user/apps/donenow_ivt/controller/ivtOrderController.java

@@ -840,15 +840,20 @@ public class ivtOrderController {
 
                 String taxCategoryId = null;
                 //增值税发票,选择是时,才需要展示税率
-                if (StringUtils.isNotBlank(cost.getString("SERVICE_ID"))) {
-                    RowMap serviceMap = DBSql.getMap("select PURCHASE_TAX_CATEGORY_ID,VAT_TAX from BO_EU_DNCTT_CONTRACT_SERVICE where ID=?", new Object[]{cost.getString("SERVICE_ID")});
-                    if (serviceMap.getString("VAT_TAX").equals("1")) {
-                        taxCategoryId = serviceMap.getString("PURCHASE_TAX_CATEGORY_ID");
-                    } else {
-                        taxCategoryId = zeroTaxTaxCateId;
+                if(StringUtils.isNotBlank(cost.getString("IS_SERVICE_PRODUCT")) && "1".equals(cost.getString("IS_SERVICE_PRODUCT"))){
+                    if (StringUtils.isNotBlank(cost.getString("SERVICE_ID"))) {
+                        RowMap serviceMap = DBSql.getMap("select PURCHASE_TAX_CATEGORY_ID,VAT_TAX from BO_EU_DNCTT_CONTRACT_SERVICE where ID=?", new Object[]{cost.getString("SERVICE_ID")});
+                        if (serviceMap.getString("VAT_TAX").equals("1")) {
+                            taxCategoryId = serviceMap.getString("PURCHASE_TAX_CATEGORY_ID");
+                        } else {
+                            taxCategoryId = zeroTaxTaxCateId;
+                        }
                     }
+                }else {
+                    taxCategoryId = cost.getString("TAX_CATEGORY_ID");
                 }
 
+
                 ivtLogger.info("taxCategoryId--- " + cost.getString("SERVICE_ID"));
                 ivtLogger.info("taxCategoryId--- " + taxCategoryId);
                 BigDecimal taxRate = taxRateMap.get(taxCategoryId);
@@ -940,7 +945,7 @@ public class ivtOrderController {
             e.printStackTrace();
             if (conn != null) {
                 try {
-                    conn.rollback();
+                    conn.rollback();    
                 } catch (SQLException ex) {
                     System.err.println("事务回滚异常: " + ex.getMessage());
                 }

+ 3 - 2
com.awspaas.user.apps.donenow_ivt/src/com/awspaas/user/apps/donenow_ivt/event/FormAfterSaveEvent.java

@@ -4,6 +4,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
+import com.actionsoft.bpms.commons.mvc.view.ResponseObject;
 import org.apache.commons.lang3.StringUtils;
 
 import com.actionsoft.bpms.bo.engine.BO;
@@ -39,7 +40,7 @@ import com.awspaas.user.apps.donenow_ivt.utils.BigDecimalUtil;
     	DoWorkThread thread=new DoWorkThread(process);
     	thread.start();
     }
-    
-   
+
+
     
   }

+ 70 - 46
com.awspaas.user.apps.donenow_ivt/src/com/awspaas/user/apps/donenow_ivt/event/reserveFormBeforeSave.java

@@ -16,15 +16,9 @@ import java.util.List;
 
 public class reserveFormBeforeSave extends InterruptListener {
     public String getDescription() {
-        return "采购接收/取消接收保存前,校验库存";
+        return "采购接收/取消接收保存前,校验库存+自动更新接收数+自动更新状态";
     }
 
-    /**
-     *
-     * @param processExecutionContext
-     * @return
-     * @throws Exception
-     */
     @Override
     public boolean execute(ProcessExecutionContext processExecutionContext) throws Exception {
         UserContext uc = processExecutionContext.getUserContext();
@@ -32,72 +26,102 @@ public class reserveFormBeforeSave extends InterruptListener {
         String boName = processExecutionContext.getParameterOfString("$BONAME");
         String bindId = processExecutionContext.getProcessInstance().getId();
 
-        //采购接收主表保存
+        // 采购接收主表保存时执行
         if (IVTConstant.BO_EU_DNIVT_RECEIVE_MAIN.equals(boName)) {
-
+            // 查询当前单据下的所有子表数据
             List<BO> list = SDK.getBOAPI().query(IVTConstant.BO_EU_DNIVT_RECEIVE_SUB).bindId(bindId).list();
-            //序列号不能重复
             for (BO bo : list) {
-                String BEN_RECEIVE = BigDecimalUtil.isNull(bo.getString("BEN_RECEIVE"));
-                int intBenReceive = Integer.valueOf(BEN_RECEIVE).intValue();
-
-                //只能整体接收
-                if (bo.getString("WHOLE").equals("1")) {
-                    String QUANTITY_BACKORDERED = BigDecimalUtil.isNull(bo.getString("QUANTITY_BACKORDERED"));
-                    int backorderedQty = Integer.valueOf(QUANTITY_BACKORDERED).intValue();
-                    if (intBenReceive != backorderedQty && (0 - intBenReceive) != backorderedQty) {
-                        throw new BPMNError("receiveErrorCode6", bo.getString("PRODUCT_NAME") + "只能整体接收,接收数量为" + backorderedQty + "!");
-                    }
-                }
+                // ===== 1. 基础字段取值 =====
+                String benReceiveStr = BigDecimalUtil.isNull(bo.getString("BEN_RECEIVE"));
+                int intBenReceive = Integer.parseInt(benReceiveStr); // 本次接收数
+                String orderProductId = bo.getString("ORDER_PRODUCT_ID"); // 订单项ID
+                String productName = bo.getString("PRODUCT_NAME"); // 产品名称
+                int totalPurchaseQty = Integer.parseInt(bo.getString("QUANTITY")); // 采购总数量
                 String sns = bo.getString("SN");
+
+                // ===== 2. 原有校验:本次接收数为0,禁止填序列号 =====
                 if (intBenReceive == 0) {
-                    if (StringUtils.isNotBlank(sns))
+                    if (StringUtils.isNotBlank(sns)) {
                         throw new BPMNError("receiveErrorCode0", "本次接收数量为空,不需要输入序列号!");
+                    }
                     continue;
                 }
 
-                RowMap map = DBSql.getMap("select sum(QUANTITY_RECEIVED) QUANTITY_RECEIVED from BO_EU_DNIVT_RECEIVE WHERE  ORDER_PRODUCT_ID=?", new Object[]{bo.getString("ORDER_PRODUCT_ID")});
+                // ===== 3. 原有校验:整体接收产品,必须收完剩余数量 =====
+                if ("1".equals(bo.getString("WHOLE"))) {
+                    String backorderedStr = BigDecimalUtil.isNull(bo.getString("QUANTITY_BACKORDERED"));
+                    int backorderedQty = Integer.parseInt(backorderedStr);
+                    // 正向接收/反向取消,都必须等于剩余数量
+                    if (intBenReceive != backorderedQty && (0 - intBenReceive) != backorderedQty) {
+                        throw new BPMNError("receiveErrorCode6", productName + "只能整体接收,接收数量为" + backorderedQty + "!");
+                    }
+                }
+
+                // ===== 4. ✅ 核心修复1:查询【正确子表】统计历史累计接收数 =====
+                RowMap historyMap = DBSql.getMap(
+                        "select sum(QUANTITY_RECEIVED) as QUANTITY_RECEIVED from BO_EU_DNIVT_RECEIVE_SUB where ORDER_PRODUCT_ID=?",
+                        new Object[]{orderProductId}
+                );
+                int historyReceive = 0;
+                if (historyMap != null && historyMap.get("QUANTITY_RECEIVED") != null) {
+                    historyReceive = historyMap.getInt("QUANTITY_RECEIVED");
+                }
 
-                int QUANTITY_RECEIVED = map == null ? 0 : map.getInt("QUANTITY_RECEIVED");
-                if (intBenReceive + QUANTITY_RECEIVED < 0) {
+                // ===== 5. ✅ 核心校验:取消接收数 不能大于 已接收数 =====
+                if (intBenReceive + historyReceive < 0) {
                     throw new BPMNError("receiveErrorCode4", "取消接收的数量不能大于已接收的数量!");
                 }
 
-                if (intBenReceive + QUANTITY_RECEIVED > Integer.parseInt(bo.getString("QUANTITY"))) {
-                    throw new BPMNError("receiveErrorCode5", "接收数量不能大于采购数量!");
+                // ===== 6. ✅ 核心校验:累计接收数 不能大于 采购总数量 =====
+                int newTotalReceive = historyReceive + intBenReceive;
+                if (newTotalReceive > totalPurchaseQty) {
+                    throw new BPMNError("receiveErrorCode5", productName + "接收数量不能大于采购数量【" + totalPurchaseQty + "】!");
                 }
 
-                //序列号产品批次号不能重复
+                // ===== 7. ✅ 原有校验:序列号唯一性校验 =====
                 if (StringUtils.isNotBlank(sns)) {
                     for (String sn : sns.split(",")) {
-                        List<RowMap> exitsLots = DBSql.getMaps("select * from BO_EU_DNIVT_WAREHOUSE_PRODUCT_LOT WHERE FIND_IN_SET(?,SN)", new Object[]{sn});
-                        if (exitsLots.size() > 0 && intBenReceive > 0) {
-                            //已存在,不能再新增
+                        List<RowMap> existSnList = DBSql.getMaps(
+                                "select * from BO_EU_DNIVT_WAREHOUSE_PRODUCT_LOT where FIND_IN_SET(?, SN)",
+                                new Object[]{sn}
+                        );
+                        // 正向接收:序列号不能已存在
+                        if (intBenReceive > 0 && !existSnList.isEmpty()) {
                             throw new BPMNError("receiveErrorCode1", sn + "已存在此序列号,不能再新增!");
                         }
-
+                        // 反向取消:序列号必须存在+归属当前采购单
                         if (intBenReceive < 0) {
-                            if (exitsLots.size() <= 0)
-                                throw new BPMNError("receiveErrorCode2", sn + "不存在此序列号,不能再删除!");
-                            else if (exitsLots.size() > 0) {
-                                boolean sameOrderProduct = false;
-                                for (RowMap rowMap : exitsLots) {
-                                    if (rowMap.getString("ORDER_PRODUCT_ID").equals(bo.getString("ORDER_PRODUCT_ID"))) {
-                                        sameOrderProduct = true;
-                                    }
-                                }
-                                if (!sameOrderProduct) {
-                                    throw new BPMNError("receiveErrorCode3", sn + "不是此采购单接收的,不能在此采购单上取消!");
-                                }
+                            if (existSnList.isEmpty()) {
+                                throw new BPMNError("receiveErrorCode2", sn + "不存在此序列号,不能删除!");
+                            }
+                            boolean isCurrentOrder = existSnList.stream()
+                                    .anyMatch(row -> orderProductId.equals(row.getString("ORDER_PRODUCT_ID")));
+                            if (!isCurrentOrder) {
+                                throw new BPMNError("receiveErrorCode3", sn + "不是此采购单接收的,无法取消!");
                             }
                         }
                     }
                 }
+
+                // ===== 8. ✅ 核心更新1:计算并赋值 累计接收数、未收货数量 =====
+                bo.set("QUANTITY_RECEIVED", newTotalReceive); // 累计接收数(不再是0)
+                int newBackorderedQty = totalPurchaseQty - newTotalReceive;
+                bo.set("QUANTITY_BACKORDERED", Math.max(newBackorderedQty, 0)); // 未收货数量(最小为0)
+
+                // ===== 9. ✅ 核心更新2:自动更新状态(STATUS) =====
+                if (newBackorderedQty == 0) {
+                    bo.set("STATUS", "已接收"); // 未收货数量为0 → 自动变已接收
+                } else {
+                    bo.set("STATUS", "待接收"); // 还有未收 → 保持待接收
+                }
+
+                // ===== 10. ✅ 同步更新子表数据到数据库 =====
+                SDK.getBOAPI().update(IVTConstant.BO_EU_DNIVT_RECEIVE_SUB, bo);
             }
         }
 
-        //记录表单修改
+        // 记录表单修改(原有逻辑保留)
         dn.recordFormChanges.execute(processExecutionContext);
         return true;
     }
-}
+}