Browse Source

采购相关代码

ouyj 1 month ago
parent
commit
80aab019b9

+ 608 - 0
src/main/bpm/com/yw/bpm/client/action/BpmCommonProcessAction.java

@@ -0,0 +1,608 @@
+package com.yw.bpm.client.action;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.context.ApplicationContext;
+
+import com.yw.bpm.client.service.BpmClientFlowSubProcessService;
+import com.yw.bpm.core.model.ProcessResult;
+import com.yw.bpm.core.service.CoreService;
+import com.yw.bpm.form.dao.FormDao;
+import com.yw.bpm.form.dao.FormSealInfoDao;
+import com.yw.bpm.form.model.FormElement;
+import com.yw.bpm.form.model.FormInfo;
+import com.yw.bpm.form.model.FormSealInfo;
+import com.yw.bpm.instance.dao.FlowDao;
+import com.yw.bpm.instance.model.FileInfo;
+import com.yw.bpm.instance.model.FlowInstance;
+import com.yw.bpm.instance.model.FlowResult;
+import com.yw.bpm.instance.model.TacheInstance;
+import com.yw.bpm.instance.service.FlowService;
+import com.yw.bpm.instance.service.FormInstanceService;
+import com.yw.bpm.system.model.SystemGroupUser;
+import com.yw.bpm.system.util.StringUtil;
+import com.yw.bpm.tache.model.TacheModel;
+import com.yw.bpm.task.model.Task;
+import com.yw.contract.contract.model.ContractPurchaseDetail;
+import com.yw.contract.contract.model.ContractPurchaseForm;
+import com.yw.core.clientImpl.model.ResultVo;
+import com.yw.core.clientImpl.service.RequestAbs;
+import com.yw.core.framework.util.StringUtils;
+import com.yw.core.session.AppSession;
+
+import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+import net.sf.json.JsonConfig;
+
+/**
+ * 通用流程审批 Action(支持多种特殊流程)
+ * 小程序端通过 serviceId 调用此接口进行审批
+ * 参考 BpmClientFlowSubProcess3Action 实现
+ */
+public class BpmCommonProcessAction extends RequestAbs {
+	
+	public final String serviceId = "bpm_common_process_approval";
+	private ApplicationContext cxt = null;
+	private String userid = "";
+	private String returnParams = "";
+
+	@Override
+	public String getServiceId() {
+		return serviceId;
+	}
+	
+	@Override
+	public String getResult() {
+		// 获取请求的参数
+		String param = getParameters();
+		JSONObject json = JSONObject.fromObject(param);
+		
+		JSONObject jsonflow = JSONObject.fromObject(json.get("flow"));
+		JSONObject jsonform = JSONObject.fromObject(json.get("form"));
+		String control = json.getString("control");
+		String insId = jsonflow.getString("insId");
+		String tinsId = jsonflow.getString("tinsId");
+		
+		// 【差异 5】设置 userid(从 staffId 获取)
+		if (jsonflow.opt("staffId") != null) {
+			userid = jsonflow.getString("staffId");
+		}
+		
+		if (null == cxt) {
+			cxt = AppSession.getApplicationContext();
+		}
+		
+		// 返回数据
+		ResultVo rv = null;
+		try {
+			String responseJson = this.subProcess(jsonflow, jsonform, control);
+			if ("1".equals(responseJson)) {
+				rv = new ResultVo();
+				rv.setReturnCode("1");
+				rv.setReturnMsg("审批成功");
+				rv.setReturnParams(returnParams);
+			} else if ("2".equals(responseJson)) {
+				rv = new ResultVo();
+				rv.setReturnCode("2");
+				rv.setReturnMsg("退回上一级成功");
+				rv.setReturnParams(returnParams);
+			} else if ("3".equals(responseJson)) {
+				rv = new ResultVo();
+				rv.setReturnCode("3");
+				rv.setReturnMsg("退回至发起人成功");
+				rv.setReturnParams(returnParams);
+			} else if ("0".equals(responseJson)) {
+				rv = new ResultVo();
+				rv.setReturnCode("0");
+				rv.setReturnMsg("操作失败");
+				rv.setReturnParams("");
+			} else {
+				rv = new ResultVo();
+				rv.setReturnCode("0");
+				rv.setReturnMsg(responseJson);
+				rv.setReturnParams("");
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+			rv = new ResultVo();
+			rv.setReturnCode("0");
+			rv.setReturnMsg(e.getMessage());
+		}
+		
+		return JSONObject.fromObject(rv, config).toString();
+	}
+	
+	/**
+	 * 提交流程审批(参考 BpmClientFlowSubProcess3Action 的 subProcess 方法)
+	 */
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	private String subProcess(JSONObject jsonflow, JSONObject jsonform, String control) throws Exception {
+		if (null == cxt) {
+			cxt = AppSession.getApplicationContext();
+		}
+		
+		FlowService flowService = (FlowService) cxt.getBean("bpmFlowService");
+		CoreService bpmCoreService = (CoreService) cxt.getBean("bpmCoreService");
+		BpmClientFlowSubProcessService bpmClientFlowSubProcessService = 
+			(BpmClientFlowSubProcessService) cxt.getBean("bpmClientFlowSubProcessService");
+		FlowDao flowDao = (FlowDao) cxt.getBean("bpmFlowDao");
+		
+		String formId = jsonform.getString("formId");
+		String formInsId = jsonform.getString("formInsId");
+		JSONArray jsonformElements = JSONArray.fromObject(jsonform.get("formElements"));
+		List formElementsList = JSONArray.toList(jsonformElements, new HashMap(), new JsonConfig());
+		Map<String, Object> formIns = new HashMap<String, Object>();
+		formIns.put("formId", formId);
+		formIns.put("formInsId", formInsId);
+		formIns.put("formElementsList", formElementsList);
+		
+		String tmodeId = jsonflow.getString("tmodelId");
+		String modelId = jsonflow.getString("modelId");
+		String insId = jsonflow.getString("insId");
+		String tinsId = jsonflow.getString("tinsId");
+		String preTinsId = jsonflow.optString("preTinsId", "");
+		String fileIds = jsonflow.optString("fileIds", "");
+		if (fileIds.startsWith("[") && fileIds.endsWith("]")) {
+			fileIds = fileIds.substring(1, fileIds.length() - 1);
+		}
+		String groupId = jsonflow.optString("groupId", "");
+		String isMoreIns = jsonflow.optString("isMoreIns", "");
+		String pathJudgeType = jsonflow.optString("pathJudgeType", "");
+		String seModel = jsonflow.getString("seModel");
+		String nextTmodelId = jsonflow.optString("nextTmodelId", "");
+		String remark = jsonflow.optString("remark", "");
+		String result = jsonflow.getString("result"); // 1-通过,0-退回上一级,2-退回发起人
+		
+		String insName = "";
+		if (jsonflow.get("insName") != null) {
+			insName = jsonflow.getString("insName");
+		}
+		
+		// 【差异 1】处理手动分支路径
+		List<TacheModel> manualTacheModels = new ArrayList<TacheModel>();
+		if ("1".equals(pathJudgeType)) { // 手动分支
+			TacheModel nextTmodel = new TacheModel();
+			nextTmodel.setTmodelId(nextTmodelId);
+			manualTacheModels.add(nextTmodel);
+		}
+		
+		// 【差异 2】处理下一环节人员列表(usesNames)
+		List<TacheInstance> nextTinstanceList = new ArrayList<TacheInstance>();
+		if (jsonflow.optJSONArray("usesNames") != null) {
+			JSONArray jsonusesNames = jsonflow.getJSONArray("usesNames");
+			List<Map<String, String>> usesNamesList = JSONArray.toList(jsonusesNames, new HashMap<String, String>(),
+					new JsonConfig());
+			for (Map<String, String> userMap : usesNamesList) {
+				if (userMap != null) {
+					String userid = userMap.get("username");
+					TacheInstance tache = new TacheInstance();
+					String nextTmodelIdi = userMap.get("nextTmodelId");
+					tache.getTmodelId().setTmodelId(nextTmodelIdi);
+					if (!StringUtils.isEmpty(nextTmodelIdi) && !StringUtils.isEmpty(userid)) {
+						tache.getUser().setUserId(userid.replace("-", ""));
+						tache.getGroupxuserid().setId(userid.replace("-", ""));
+					}
+					nextTinstanceList.add(tache);
+				}
+			}
+		}
+		
+		// 根据 modelId 调用不同的业务处理逻辑
+		processByModelId(modelId, jsonflow, jsonform);
+		
+		// 更新表单数据
+		int i = 1;
+		/*if (!"".equals(formId) && !"0".equals(formId)) {
+			i = bpmClientFlowSubProcessService.updateInsForPhone(formIns);
+		}*/
+		
+		if (i != 0) {
+			// 构建流程实例和环节实例
+			FlowInstance finstance = flowDao.getFlowInstanceById(insId);
+			
+			TacheInstance tinstance = new TacheInstance();
+			tinstance.setTinsId(tinsId);
+			tinstance.setRemark(remark);
+			tinstance.setPreTinsId(preTinsId);
+			tinstance.getModelId().setModelId(modelId);
+			tinstance.getInsId().setInsId(finstance.getInsId());
+			tinstance.getTmodelId().setTmodelId(tmodeId);
+			tinstance.getTmodelId().getIsMoreIns().setValue(isMoreIns);
+			tinstance.getTmodelId().getPathJudgeType().setValue(pathJudgeType);
+			tinstance.getGroupid().setGroupId(groupId);
+			tinstance.getState().setValue("2");
+			tinstance.getSe().setValue(seModel);
+			tinstance.setHasNext("0");
+			tinstance.setParallel("0");
+			tinstance.setProcess_type("1");
+			
+			if (!StringUtil.stringIsNull(insName)) {
+				finstance.setInsName(insName);
+			}
+			
+			// 解析附件列表
+			String[] fileIdsArr = StringUtil.stringIsNull(fileIds) ? new String[]{} : fileIds.split(",");
+			List<FileInfo> fileList = new ArrayList<FileInfo>();
+			for (String file : fileIdsArr) {
+				if (!StringUtils.isEmpty(file)) {
+					FileInfo info = new FileInfo();
+					info.getFileId().setUniversalid(file);
+					info.setIfileId(String.valueOf(System.currentTimeMillis()));
+					info.getState().setValue("1");
+					info.getCreator().setUserId(finstance.getCreator().getUserId());
+					info.getInsId().setInsId(finstance.getInsId());
+					info.getTinsId().setTinsId(tinstance.getTinsId());
+					fileList.add(info);
+				}
+			}
+			
+			TacheModel nextTmodel = new TacheModel();
+			nextTmodel.setTmodelId(nextTmodelId);
+			 
+			FlowResult fr;
+			try {
+				if ("1".equals(result)) {
+					// 通过 - 调用 bpmCoreService.process()
+					// 【差异 3】处理通知用户和抄送用户
+					String noticeUsers = "";
+					if (jsonflow.opt("noticeUsers") != null) {
+						noticeUsers = jsonflow.getString("noticeUsers");
+					}
+					
+					Task task = new Task();
+					task.setInsId(finstance.getInsId());
+					task.setTinsId(tinstance.getTinsId());
+					if (jsonflow.opt("copytoUsers") != null) {
+						task.setUsers(jsonflow.getString("copytoUsers"));
+					}
+					task.setDistributedUser(userid);
+					
+					// 【差异 4】先预处理获取下一环节
+					ProcessResult pr = bpmCoreService.preProcess(finstance, tinstance, manualTacheModels, 
+						nextTinstanceList);
+					List<Map<String, Object>> nextTacheInstances = pr.getNextTacheInstances();
+					
+					// 如果 nextTinstanceList 为空,从预处理结果中获取
+					if (nextTinstanceList.size() == 0) {
+						for (Map<String, Object> nextTacheInstance : nextTacheInstances) {
+							TacheModel tacheModel = (TacheModel) nextTacheInstance.get("tacheModel");
+							List<SystemGroupUser> users = (List<SystemGroupUser>) nextTacheInstance.get("users");
+							String nextTmodelIds = tacheModel.getTmodelId();
+							String isCheck = nextTacheInstance.get("isCheck").toString();
+							if ("true".equals(isCheck)) {
+								for (SystemGroupUser user : users) {
+									TacheInstance tache = new TacheInstance();
+									tache.getTmodelId().setTmodelId(nextTmodelIds);
+									tache.getGroupxuserid().setId(user.getId());
+									nextTinstanceList.add(tache);
+								}
+							} else {
+								TacheInstance tache = new TacheInstance();
+								tache.getTmodelId().setTmodelId(nextTmodelIds);
+								nextTinstanceList.add(tache);
+							}
+						}
+					}
+					
+					// 正式处理
+					pr = bpmCoreService.process(finstance, tinstance, manualTacheModels, nextTinstanceList,
+							fileList, noticeUsers, task);
+					if (pr.isResultState()) {
+						returnParams = "{msg:\"" + pr.getResultInfo() + "\"}";
+						return "1";
+					} else {
+						return pr.getResultInfo();
+					}
+				} else if ("0".equals(result)) {
+					// 退回上一级
+					fr = flowService.returnTache(finstance, tinstance, nextTmodel, nextTinstanceList, fileList);
+					if (fr.isSuccess()) {
+						if (fr.isInfo()) {
+							returnParams = "{msg:\"" + fr.getTip() + fr.getAppendString() + "\"}";
+						} else {
+							returnParams = "{msg:\"" + fr.getTip() + "\"}";
+						}
+						return "2";
+					}
+				} else if ("2".equals(result)) {
+					// 退回发起人
+					fr = flowService.returnTacheToSender(finstance, tinstance, nextTmodel, nextTinstanceList, fileList);
+					if (fr.isSuccess()) {
+						if (fr.isInfo()) {
+							returnParams = "{msg:\"" + fr.getTip() + fr.getAppendString() + "\"}";
+						} else {
+							returnParams = "{msg:\"" + fr.getTip() + "\"}";
+						}
+						return "3";
+					}
+				}
+				return "0";
+			} catch (Exception e) {
+				System.out.println(e.getMessage());
+				return e.getMessage();
+			}
+		}
+		
+		return "0";
+	}
+	
+	/**
+	 * 根据 modelId 分发到不同的业务处理逻辑
+	 */
+	private void processByModelId(String modelId, JSONObject jsonflow, JSONObject jsonform) throws Exception {
+		if (StringUtil.stringIsNull(modelId)) {
+			return;
+		}
+		
+		// 采购流程(modelId = 200001)
+		if ("200001".equals(modelId)) {
+			processPurchaseFlow(jsonflow, jsonform);
+		}
+		// 其他流程类型...
+	}
+	
+	/**
+	 * 处理采购流程表单提交
+	 * 参考 PurchaseNoContractAction.processContract() 和 ContractFlowCustomAction.processContract() 方法
+	 */
+	@SuppressWarnings("unchecked")
+	private void processPurchaseFlow(JSONObject jsonflow, JSONObject jsonform) throws Exception {
+		// 获取表单实例 ID
+		String formInsId = jsonform.getString("formInsId");
+		if (StringUtil.stringIsNull(formInsId)) {
+			return;
+		}
+		
+		// 获取服务实例
+		if (null == cxt) {
+			cxt = AppSession.getApplicationContext();
+		}
+		
+		// 调用采购流程的业务逻辑
+		Object contractPurchaseService = null;
+		try {
+			contractPurchaseService = cxt.getBean("contractPurchaseService");
+		} catch (Exception e) {
+			System.out.println("未找到 contractPurchaseService Bean,跳过采购流程特殊处理");
+			return;
+		}
+		
+		// 解析表单元素
+		JSONArray formElements = JSONArray.fromObject(jsonform.get("formElements"));
+		Map<String, Object> formDataMap = new HashMap<String, Object>();
+		
+		// 将 formElements 转换为 Map,提取各个字段的值
+		for (int i = 0; i < formElements.size(); i++) {
+			JSONObject element = formElements.getJSONObject(i);
+			// 【修复】优先使用 name 字段,如果没有则使用 tableField 字段
+			String name = element.optString("name", null);
+			if (name == null) {
+				name = element.optString("tableField", null);
+			}
+			if (name == null) {
+				continue; // 跳过没有 name 和 tableField 的元素
+			}
+			// 【修复】优先使用 value 字段,如果没有则使用 defaultValue 字段
+			String value = element.optString("value", null);
+			if (value == null) {
+				value = element.optString("defaultValue", "");
+			}
+			formDataMap.put(name, value);
+		}
+		
+		// 获取审批意见字段(根据当前环节动态判断)
+		String seModel = jsonflow.getString("seModel");
+		String tinsId = jsonflow.getString("tinsId");
+		
+		// 如果是审批环节(seModel = '0'),需要处理审批意见和签名
+		if ("0".equals(seModel)) {
+			// 处理部门意见
+			processApprovalOpinion(formDataMap, formInsId, "departmental_opinion", "departmentalOpinion");
+			// 处理副总经理意见
+			processApprovalOpinion(formDataMap, formInsId, "deputy_general_manager_opinion", "dgmOpinion");
+			// 处理审计副总经理意见
+			processApprovalOpinion(formDataMap, formInsId, "audit_deputy_general_manager_opinion", "auditDgmOpinion");
+			// 处理总经理意见
+			processApprovalOpinion(formDataMap, formInsId, "general_manager_opinion", "gmOpinion");
+		}
+		
+		// 保存或更新采购表单数据
+		saveOrUpdatePurchaseForm(formDataMap, formInsId, contractPurchaseService, seModel);
+	}
+	
+	/**
+	 * 保存或更新采购表单数据(包含物料明细)
+	 * 参考 ContractFlowCustomAction.processContract() 方法
+	 */
+	private void saveOrUpdatePurchaseForm(Map<String, Object> formDataMap, String formInsId, Object contractPurchaseService, String seModel) throws Exception {
+		// 构建 ContractPurchaseForm 对象
+		ContractPurchaseForm purchaseForm = new ContractPurchaseForm();
+		
+		// 设置主键 ID(使用 formInsId)
+		try {
+			purchaseForm.setUniversalid(Long.valueOf(formInsId));
+			purchaseForm.setlFormInsId(Long.valueOf(formInsId));
+		} catch (NumberFormatException e) {
+			System.out.println("formInsId 转换失败:" + formInsId);
+		}
+		
+		// 使用 Apache BeanUtils 进行属性拷贝(简化字段映射)
+		// 从 formDataMap 复制到 purchaseForm,自动匹配同名字段
+		org.apache.commons.beanutils.BeanUtils.populate(purchaseForm, formDataMap);
+		
+		// 处理特殊字段(需要类型转换的)
+		if (formDataMap.containsKey("contractId")) {
+			try {
+				purchaseForm.setContractId(Long.valueOf(formDataMap.get("contractId").toString()));
+			} catch (Exception e) {}
+		}
+		if (formDataMap.containsKey("totalAmount")) {
+			try {
+				purchaseForm.setTotalAmount(new java.math.BigDecimal(formDataMap.get("totalAmount").toString()));
+			} catch (Exception e) {}
+		}
+		
+		// 解析并设置物料明细(detailList 是 JSON 字符串)
+		if (formDataMap.containsKey("detailList")) {
+			String detailListJson = (String) formDataMap.get("detailList");
+			if (!StringUtil.stringIsNull(detailListJson)) {
+				try {
+					JSONArray detailArray = JSONArray.fromObject(detailListJson);
+					List<ContractPurchaseDetail> details = new ArrayList<>();
+					
+					for (int i = 0; i < detailArray.size(); i++) {
+						JSONObject detailObj = detailArray.getJSONObject(i);
+						ContractPurchaseDetail detail = new ContractPurchaseDetail();
+										
+						// 【优化】手动设置字段,避免 BeanUtils 的类型转换警告
+						try {
+							if (detailObj.containsKey("universalid") && detailObj.get("universalid") != null) {
+								detail.setUniversalid(Long.valueOf(detailObj.getString("universalid")));
+							}
+							if (detailObj.containsKey("materialCode")) {
+								detail.setMaterialCode(detailObj.getString("materialCode"));
+							}
+							if (detailObj.containsKey("materialName")) {
+								detail.setMaterialName(detailObj.getString("materialName"));
+							}
+							if (detailObj.containsKey("materialModel")) {
+								detail.setMaterialModel(detailObj.getString("materialModel"));
+							}
+							if (detailObj.containsKey("measureName")) {
+								detail.setMeasureName(detailObj.getString("measureName"));
+							}
+							if (detailObj.containsKey("qty")) {
+								detail.setQty(new java.math.BigDecimal(detailObj.getString("qty")));
+							}
+							if (detailObj.containsKey("price")) {
+								detail.setPrice(new java.math.BigDecimal(detailObj.getString("price")));
+							}
+							// 设置关联信息
+							detail.setContractPurchaseFormId(purchaseForm.getUniversalid());
+							detail.setlFormInsId(purchaseForm.getUniversalid());
+							detail.setIsValid(9); // 初始状态
+							detail.setQtyLock(java.math.BigDecimal.ZERO);
+							detail.setQtyUsed(java.math.BigDecimal.ZERO);
+							detail.setQtyRemain(detail.getQty());
+						} catch (Exception e) {
+							System.out.println("解析物料明细第 " + (i+1) + " 条失败:" + e.getMessage());
+							e.printStackTrace();
+						}
+										
+						details.add(detail);
+					}
+					
+					purchaseForm.setDetails(details);
+					System.out.println("解析物料明细成功,数量:" + details.size());
+				} catch (Exception e) {
+					System.out.println("解析物料明细失败:" + e.getMessage());
+					e.printStackTrace();
+				}
+			}
+		}
+		
+		// 设置 seTache(审批环节标识)
+		purchaseForm.setSeTache(seModel);
+		
+		// 调用 service 保存或更新
+		try {
+			java.lang.reflect.Method editMethod = contractPurchaseService.getClass()
+				.getMethod("editContractPurchaseForm", ContractPurchaseForm.class);
+			editMethod.invoke(contractPurchaseService, purchaseForm);
+			System.out.println("采购表单保存成功,formInsId: " + formInsId + ", 物料数量:" + 
+				(purchaseForm.getDetails() != null ? purchaseForm.getDetails().size() : 0));
+		} catch (Exception e) {
+			System.out.println("保存采购表单失败:" + e.getMessage());
+			e.printStackTrace();
+			throw e; // 重新抛出异常,让上层知道保存失败
+		}
+	}
+	
+	/**
+	 * 处理单个审批意见字段(包含签名)
+	 * 参考 ContractFlowCustomAction.processContract() 方法
+	 */
+	private void processApprovalOpinion(Map<String, Object> formDataMap, String formInsId, String fieldName, String formProperty) throws Exception {
+		if (null == cxt) {
+			cxt = AppSession.getApplicationContext();
+		}
+		FormSealInfoDao bpmFormSealDao = (FormSealInfoDao) cxt.getBean("bpmFormSealDao");
+		// 检查当前环节是否包含该字段
+		String tableFields = (String) formDataMap.get("table_fields"); // 当前环节的字段列表
+		System.out.println("【处理审批意见】fieldName: " + fieldName + ", table_fields: " + tableFields);
+		
+		if (tableFields != null && tableFields.contains("," + fieldName + ",")) {
+			System.out.println("【找到字段】" + fieldName + " 在 table_fields 中");
+			// 获取签名字段值(imgval 格式:sealId_left_top)
+			String sealImgVal = (String) formDataMap.get(fieldName + "_imgval");
+			// 获取原有签名实例 ID(从 fieldName 获取,因为前端传递的是下划线命名)
+			String existingSealInsId = (String) formDataMap.get(fieldName);
+			
+			System.out.println("【签名信息】sealImgVal: " + sealImgVal + ", existingSealInsId: " + existingSealInsId);
+			
+			if (!StringUtil.stringIsNull(sealImgVal)) {
+				System.out.println("【保存签名】" + fieldName + " 有签名图片,准备保存...");
+				// 有签名图片,保存或更新签名
+				Map<String, String> sealMap = new HashMap<>();
+				sealMap.put("formId", "200001"); // 采购流程 formId
+				sealMap.put("formInsId", formInsId);
+				sealMap.put("imgval", sealImgVal);
+				
+				if (StringUtil.stringIsNull(existingSealInsId)) {
+					// 创建新签名 - 返回新的签名实例 ID
+					sealMap.put("imgvalseq", null);
+					String sealInsId = createOrUpdateSealForForm(sealMap, "create");
+					// 将新的签名实例 ID 放入 formDataMap,供后续更新采购单使用
+					formDataMap.put(formProperty, sealInsId);
+					System.out.println("【创建新签名】" + fieldName + ", sealInsId: " + sealInsId);
+				} else {
+					Map<String, String> conditions = new HashMap<String, String>();
+					conditions.put("sealId", existingSealInsId); 
+					FormSealInfo formSealInfo = bpmFormSealDao
+							.getFormSealInfoForPhoneByConditions(conditions);
+					if (formSealInfo == null) {
+						// 创建新签名 - 返回新的签名实例 ID
+						sealMap.put("imgvalseq", null);
+						String sealInsId = createOrUpdateSealForForm(sealMap, "create");
+						// 将新的签名实例 ID 放入 formDataMap,供后续更新采购单使用
+						formDataMap.put(formProperty, sealInsId);
+						System.out.println("【创建新签名】" + fieldName + ", sealInsId: " + sealInsId);
+					}else{ 
+						// 更新已有签名 - 使用原有的签名实例 ID
+						sealMap.put("imgvalseq", existingSealInsId);
+						createOrUpdateSealForForm(sealMap, "update");
+						formDataMap.put(formProperty, existingSealInsId);
+						System.out.println("【更新已有签名】" + fieldName + ", existingSealInsId: " + existingSealInsId);
+					} 
+				}
+			} else if (!StringUtil.stringIsNull(existingSealInsId)) {
+				System.out.println("【删除签名】" + fieldName + " 没有签名但有原有记录,删除...");
+				// 没有签名但有原有签名记录,删除签名
+				Map<String, String> sealMap = new HashMap<>();
+				sealMap.put("imgvalseq", existingSealInsId);
+				createOrUpdateSealForForm(sealMap, "delete");
+				formDataMap.put(formProperty, "");
+			} else {
+				System.out.println("【跳过签名】" + fieldName + " 既没有签名图片也没有原有记录");
+			}
+		} else {
+			System.out.println("【跳过字段】" + fieldName + " 不在 table_fields 中,不处理");
+		}
+	}
+	
+	/**
+	 * 创建或更新表单签名印章
+	 * 参考 BpmBaseAction.createOrUpdateSealForForm() 方法
+	 */
+	private String createOrUpdateSealForForm(Map<String, String> sealMap, String operType) throws Exception {
+		if (null == cxt) {
+			cxt = AppSession.getApplicationContext();
+		}
+		FormInstanceService bpmFormInstanceService = 
+			(FormInstanceService) cxt.getBean("bpmFormInstanceService");
+		return bpmFormInstanceService.createOrUpdateSealForForm(sealMap, operType);
+	}
+}

+ 425 - 0
src/main/contract/com/yw/contract/client/action/PurchaseNoContractMiniAppAction.java

@@ -0,0 +1,425 @@
+package com.yw.contract.client.action;
+
+import com.yw.bpm.system.model.SystemGroupUser;
+import com.yw.bpm.system.util.SystemUtil;
+import com.yw.contract.contract.model.ContractPurchaseDetail;
+import com.yw.contract.contract.model.ContractPurchaseForm;
+import com.yw.contract.contract.service.ContractPurchaseService;
+import com.yw.core.clientImpl.model.ResultVo;
+import com.yw.core.clientImpl.service.RequestAbs;
+import com.yw.core.session.AppSession;
+import com.yw.core.utils.Constant;
+import com.yw.core.utils.DateUtil;
+import com.yw.core.utils.StringUtil;
+import com.yw.eu.base.code.service.CodeRuleService;
+import com.yw.eu.base.user.model.GroupXUser;
+import com.yw.eu.base.user.model.User;
+import com.yw.eu.base.user.service.UserService;
+import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.context.ApplicationContext;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+// BPM 相关导入
+import com.yw.bpm.core.model.ProcessResult;
+import com.yw.bpm.core.service.CoreService;
+import com.yw.bpm.instance.model.FileInfo;
+import com.yw.bpm.instance.model.FlowInstance;
+import com.yw.bpm.instance.model.TacheInstance;
+import com.yw.bpm.tache.model.TacheModel;
+import com.yw.bpm.task.model.Task;
+
+/**
+ * 小程序端采购流程发起相关接口
+ */
+public class PurchaseNoContractMiniAppAction extends RequestAbs {
+    public final String serviceId = "miniapp_purchaseNoContract";
+    private ApplicationContext cxt = null;
+
+    @Override
+    public String getResult() {
+        String param = getParameters();
+        JSONObject json = JSONObject.fromObject(param);
+        String task = json.getString("task");
+
+        if (null == cxt) {
+            cxt = AppSession.getApplicationContext();
+        }
+
+        ResultVo rv = null;
+        try {
+            if ("getInitData".equals(task)) {
+                rv = getInitData(json);
+            } else if ("startContractPurchase".equals(task)) {
+                rv = startContractPurchase(json);
+            } else if ("getPurchaseFormData".equals(task)) {
+                rv = getPurchaseFormData(json);
+            } else if ("getPurchaseDataByInsId".equals(task)) {
+                rv = getPurchaseDataByInsId(json);
+            } else if ("selectMaterial".equals(task)) {
+                rv = selectMaterial(json);
+            } else {
+                rv = new ResultVo();
+                rv.setReturnCode("-1");
+                rv.setReturnMsg("未知操作:" + task);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            rv = new ResultVo();
+            rv.setReturnCode("-1");
+            rv.setReturnMsg("操作失败:" + e.getMessage());
+        }
+
+        return JSONObject.fromObject(rv, config).toString();
+    }
+
+    /**
+     * 获取初始化数据(流水号、申请人信息等)
+     */
+    private ResultVo getInitData(JSONObject json) throws Exception {
+        ResultVo rv = new ResultVo();
+        User user = getUserFromJson(json);
+
+        // 获取流水号
+        CodeRuleService codeRuleService = (CodeRuleService) cxt.getBean("codeRuleService");
+        String contractPurchaseNumber = codeRuleService.createCode(
+            "PURCHASE_CODE", 
+            user.getUnitId(),
+            null,
+            true,
+            null
+        );
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("contractPurchaseNumber", contractPurchaseNumber);
+        result.put("initiator", user.getName());
+        result.put("department", user.getGroupXUser().getGroupName());
+        result.put("applyDate", DateUtil.getCurrDate("yyyy-MM-dd"));
+        result.put("depid", user.getGroupXUser().getGroupID());
+        rv.setReturnCode("1");
+        rv.setReturnMsg("success");
+        rv.setReturnParams(result);
+        return rv;
+    }
+
+    /**
+     * 发起采购流程(包含表单、附件)
+     */
+    private ResultVo startContractPurchase(JSONObject json) throws Exception {
+        ResultVo rv = new ResultVo();
+        User user = getUserFromJson(json);
+        JSONObject formData = json.getJSONObject("formData");
+        
+        // 获取流程信息(参考 bpm_20150325001FlowStart)
+        JSONObject flowInfo = json.optJSONObject("flowInfo");
+        String modelId = "";
+        String tmodelId = "";
+        String insName = "";
+        String formId = "";
+        String creator = "";
+        String username = "";
+        String groupid = "";
+        String gxid = "";
+        if (flowInfo != null) {
+            modelId = flowInfo.optString("modelId", "");
+            tmodelId = flowInfo.optString("tmodelId", "");
+            insName = flowInfo.optString("insName", "");
+            formId = flowInfo.optString("formId", "");
+            creator = flowInfo != null ? flowInfo.optString("staffId", user.getUseId().toString()) : user.getUseId().toString();
+            username = flowInfo != null ? flowInfo.optString("staffName", user.getName()) : user.getName();
+            groupid = flowInfo != null ? flowInfo.optString("groupId", user.getGroupXUser().getGroupID().toString()) : user.getGroupXUser().getGroupID().toString();
+            gxid = flowInfo != null ? flowInfo.optString("gxId", user.getGroupXUser().getGroupID().toString()) : user.getGroupXUser().getGroupID().toString();
+        }
+        
+        // 获取附件 IDs(参考 bpm_20150325001FlowStart 和 work.js)
+        // fileIds 在 flowInfo 内部,可能是数组或空字符串
+        String fileIds = "";
+        if (flowInfo != null && flowInfo.has("fileIds")) {
+            Object fileIdsObj = flowInfo.get("fileIds");
+            if (fileIdsObj instanceof JSONArray) {
+                // 如果是数组格式
+                JSONArray fileIdsArray = (JSONArray) fileIdsObj;
+                List<String> fileIdList = new ArrayList<>();
+                for (int i = 0; i < fileIdsArray.size(); i++) {
+                    String fileId = fileIdsArray.getString(i);
+                    if (StringUtils.isNotEmpty(fileId)) {
+                        fileIdList.add(fileId);
+                    }
+                }
+                fileIds = StringUtils.join(fileIdList.toArray(), ",");
+            } else {
+                // 如果是字符串格式(可能为空字符串)
+                fileIds = flowInfo.optString("fileIds", "");
+            }
+        }
+
+        ContractPurchaseService contractPurchaseService = 
+            (ContractPurchaseService) cxt.getBean("contractPurchaseService");
+
+        // 构建主表数据(使用前端传递的 formId 作为 universalid)
+        ContractPurchaseForm contractPurchaseForm = new ContractPurchaseForm();
+        Long universalid = StringUtil.getSeq();
+        contractPurchaseForm.setUniversalid(universalid);
+        contractPurchaseForm.setlFormInsId(universalid);
+        contractPurchaseForm.setIsValid(9);
+        contractPurchaseForm.setCreateTime(new Date());
+        contractPurchaseForm.setCreator(user.getUseId());
+        contractPurchaseForm.setUnitId(user.getUnitId());
+
+        // 设置表单字段(安全的空值处理)
+        String contractPurchaseNumber = formData.optString("contractPurchaseNumber", "");
+        if (StringUtils.isNotEmpty(contractPurchaseNumber)) {
+            contractPurchaseForm.setContractPurchaseNumber(contractPurchaseNumber);
+        }
+        
+        String contractPurchaseName = formData.optString("contractPurchaseName", "");
+        if (StringUtils.isNotEmpty(contractPurchaseName)) {
+            contractPurchaseForm.setContractPurchaseName(contractPurchaseName);
+        }
+        
+        String supplierCode = formData.optString("supplierCode", "");
+        if (StringUtils.isNotEmpty(supplierCode)) {
+            contractPurchaseForm.setSupplierCode(supplierCode);
+        }
+        
+        String supplierName = formData.optString("supplierName", "");
+        if (StringUtils.isNotEmpty(supplierName)) {
+            contractPurchaseForm.setSupplierName(supplierName);
+        }
+        
+        String contractIdStr = formData.optString("contractId", "");
+        if (StringUtils.isNotEmpty(contractIdStr)) {
+            contractPurchaseForm.setContractId(StringUtil.strToLong(contractIdStr));
+        }
+        
+        String contractNumber = formData.optString("contractNumber", "");
+        if (StringUtils.isNotEmpty(contractNumber)) {
+            contractPurchaseForm.setContractNumber(contractNumber);
+        }
+        
+        String contractName = formData.optString("contractName", "");
+        if (StringUtils.isNotEmpty(contractName)) {
+            contractPurchaseForm.setContractName(contractName);
+        }
+        
+        String totalAmountStr = formData.optString("totalAmount", "");
+        if (StringUtils.isNotEmpty(totalAmountStr)) {
+            contractPurchaseForm.setTotalAmount(new BigDecimal(totalAmountStr));
+        }
+        
+        String applyDate = formData.optString("applyDate", "");
+        if (StringUtils.isNotEmpty(applyDate)) {
+            contractPurchaseForm.setApplyDate(applyDate);
+        }
+        
+        String department = formData.optString("department", "");
+        if (StringUtils.isNotEmpty(department)) {
+            contractPurchaseForm.setDepartment(department);
+        }
+        
+        String initiator = formData.optString("initiator", "");
+        if (StringUtils.isNotEmpty(initiator)) {
+            contractPurchaseForm.setInitiator(initiator);
+        }
+        
+        String depid = formData.optString("depid", "");
+        if (StringUtils.isNotEmpty(depid)) {
+            contractPurchaseForm.setDepid(depid);
+        }
+
+        // 构建明细列表(只保存数量,不保存单价和税率)
+        List<ContractPurchaseDetail> details = new ArrayList<>();
+        if (formData.has("detailList")) {
+            JSONArray detailArray = formData.getJSONArray("detailList");
+            for (int i = 0; i < detailArray.size(); i++) {
+                JSONObject detailJson = detailArray.getJSONObject(i);
+                ContractPurchaseDetail detail = new ContractPurchaseDetail();
+                detail.setlFormInsId(universalid);
+                
+                String materialCode = detailJson.optString("materialCode", "");
+                if (StringUtils.isNotEmpty(materialCode)) {
+                    detail.setMaterialCode(materialCode);
+                }
+                
+                String materialName = detailJson.optString("materialName", "");
+                if (StringUtils.isNotEmpty(materialName)) {
+                    detail.setMaterialName(materialName);
+                }
+                
+                String materialModel = detailJson.optString("materialModel", "");
+                if (StringUtils.isNotEmpty(materialModel)) {
+                    detail.setMaterialModel(materialModel);
+                }
+                
+                String measureName = detailJson.optString("measureName", "");
+                if (StringUtils.isNotEmpty(measureName)) {
+                    detail.setMeasureName(measureName);
+                }
+                
+                String qtyStr = detailJson.optString("qty", "");
+                if (StringUtils.isNotEmpty(qtyStr)) {
+                    detail.setQty(new BigDecimal(qtyStr));
+                }
+                
+                details.add(detail);
+            }
+        }
+        contractPurchaseForm.setDetails(details);
+
+        // 1. 先保存采购表单数据
+        int num = contractPurchaseService.addContractPurchaseForm(contractPurchaseForm);
+        if (num <= 0) {
+            rv.setReturnCode("-1");
+            rv.setReturnMsg("表单提交失败!");
+            return rv;
+        }
+
+        // 2. 如果有附件或需要提交流程,调用 BPM 流程启动逻辑
+        try {
+            String formInsId = String.valueOf(contractPurchaseForm.getlFormInsId()); // 表单实例 id
+            
+            // 调用通用流程启动方法(封装从初始化到启动的完整流程)
+            CoreService bpmCoreService = (CoreService) cxt.getBean("bpmCoreService");
+            ProcessResult pr = bpmCoreService.startFlowComplete(flowInfo, user, formInsId);
+            
+            if (pr.isResultState()) {
+                rv.setReturnCode("1");
+                rv.setReturnMsg("提交成功");
+                rv.setReturnParams("{\"universalid\":\"" + universalid + "\"}");
+            } else {
+                rv.setReturnCode("-1");
+                rv.setReturnMsg(pr.getResultInfo());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            rv.setReturnCode("-1");
+            rv.setReturnMsg("提交流程失败!" + e.getMessage());
+        }
+        
+        return rv;
+    }
+
+    /**
+     * 选择物料(支持分页和搜索)
+     */
+    private ResultVo selectMaterial(JSONObject json) throws Exception {
+        ResultVo rv = new ResultVo();
+        User user = getUserFromJson(json);
+        
+        // 获取分页参数
+        int page = json.optInt("page", 1);
+        int pageSize = json.optInt("pageSize", 20);
+        String itemName = json.optString("itemName", "");
+        
+        ContractPurchaseService contractPurchaseService = 
+            (ContractPurchaseService) cxt.getBean("contractPurchaseService");
+        
+        // 调用分页查询
+        Map<String, Object> result = contractPurchaseService.getMaterialsByPage(page, pageSize, itemName);
+
+        rv.setReturnCode("1");
+        rv.setReturnMsg("success");
+        rv.setReturnParams(result);
+        return rv;
+    }
+
+    /**
+     * 获取采购表单数据(用于审批页面展示)
+     * 根据 formInsId 获取采购单的详细信息
+     */
+    private ResultVo getPurchaseFormData(JSONObject json) throws Exception {
+        ResultVo rv = new ResultVo();
+        User user = getUserFromJson(json);
+        
+        // 获取表单实例 ID
+        String formInsId = json.optString("formInsId", "");
+        
+        if (StringUtil.isEmpty(formInsId)) {
+            rv.setReturnCode("-1");
+            rv.setReturnMsg("缺少必要参数:formInsId");
+            return rv;
+        }
+        
+        ContractPurchaseService contractPurchaseService = 
+            (ContractPurchaseService) cxt.getBean("contractPurchaseService");
+        
+        // 使用 formInsId 查询
+        ContractPurchaseForm purchaseForm = contractPurchaseService.getPurchaseFormByFormInsId(Long.parseLong(formInsId));
+        
+        if (purchaseForm == null) {
+            rv.setReturnCode("-1");
+            rv.setReturnMsg("未找到采购单数据");
+            return rv;
+        }
+        
+        // 使用 JSONObject 直接将对象转为 Map(自动序列化所有字段)
+        JSONObject purchaseFormJson = JSONObject.fromObject(purchaseForm);
+        
+        rv.setReturnCode("1");
+        rv.setReturnMsg("success");
+        rv.setReturnParams(purchaseFormJson);
+        return rv;
+    }
+
+    /**
+     * 获取采购表单数据(用于查看页面)
+     * 根据流程实例 ID(insId)获取采购单的详细信息
+     */
+    private ResultVo getPurchaseDataByInsId(JSONObject json) throws Exception {
+        ResultVo rv = new ResultVo();
+        User user = getUserFromJson(json);
+        
+        // 获取流程实例 ID
+        String insId = json.optString("insId", "");
+        
+        if (StringUtil.isEmpty(insId)) {
+            rv.setReturnCode("-1");
+            rv.setReturnMsg("缺少必要参数:insId");
+            return rv;
+        }
+        
+        ContractPurchaseService contractPurchaseService = 
+            (ContractPurchaseService) cxt.getBean("contractPurchaseService");
+        
+        // 使用流程实例 ID 查询采购单(通过 flow_ins_id 关联)
+        ContractPurchaseForm purchaseForm = contractPurchaseService.getPurchaseFormByFlowInsId(Long.parseLong(insId));
+        
+        if (purchaseForm == null) {
+            rv.setReturnCode("-1");
+            rv.setReturnMsg("未找到采购单数据");
+            return rv;
+        }
+        
+        // 使用 JSONObject 直接将对象转为 Map(自动序列化所有字段)
+        JSONObject purchaseFormJson = JSONObject.fromObject(purchaseForm);
+        
+        rv.setReturnCode("1");
+        rv.setReturnMsg("success");
+        rv.setReturnParams(purchaseFormJson);
+        return rv;
+    }
+
+    /**
+     * 从 JSON 获取用户信息
+     */
+    private User getUserFromJson(JSONObject json) throws Exception {
+        UserService userService = (UserService) cxt.getBean("userService");
+        String useId = json.getString("useId");
+        User user = userService.findUserById(useId);
+        GroupXUser groupXUser = userService.loadOwnGroupXuser(user.getUseId());
+        user.setGroupXUser(groupXUser);
+        return user;
+    }
+
+    @Override
+    public String getServiceId() {
+        return serviceId;
+    }
+}