All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.ps1.soar.service.TaskService Maven / Gradle / Ivy

package cn.ps1.soar.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.ps1.aolai.entity.User;
import cn.ps1.aolai.service.AolaiService;
import cn.ps1.aolai.service.UtilsService;
import cn.ps1.aolai.utils.ConfUtil;
import cn.ps1.aolai.utils.Const;
import cn.ps1.aolai.utils.FailedException;
import cn.ps1.soar.entity.Emp;
import cn.ps1.soar.entity.Mode;
import cn.ps1.soar.entity.Node;
import cn.ps1.soar.entity.Task;
import cn.ps1.soar.entity.Work;
import cn.ps1.soar.utils.FlowUtil;

/**
 * 审批任务处理
 * 
 * @author Aolai
 * @version 1.0 $Date: 2024.01.20
 * @since openjdk-1.8
 */
@Service
public class TaskService {

	private static Logger LOG = LoggerFactory.getLogger(TaskService.class);

	@Autowired
	private AolaiService aolai;
	@Autowired
	private UtilsService utils;

	@Autowired
	private HttpServletRequest req;
	
	private static final String PAGE_SIZE = "pageSize";
	private static final String PAGE_NO = "pageNo";

	private static final String TASK_IDS = "taskIds";
	private static final String NODE_TYPES = "nodeTypes";

	/**
	 * 公共:新建工作流(任务)的参数条件:默认当前用户或“秘书”
	 */
	Map newTaskParams(HttpServletRequest req) {
		Map params = utils.jsonParams(req);
		// 当前用户或“秘书”
		Map user = utils.userSelf(req);

		// 增加暂时参数empSecty,默认当前用户或“秘书”
		params.put(Emp.SECTY, user.get(User.ID));
		params.put(Task.COMP, user.get(User.COMP));

		if (!params.containsKey(Task.ID)) {
			// 以下是增加了taskMode参数的校验
			Object obj = params.get(Task.MODE);
			Map mode = utils.obj2Map(obj); // 转map对象
			String[] keys = ConfUtil.getValid(Task.MODE).split(ConfUtil.COMMA);
			if (!utils.availParams(mode, keys))
				throw new FailedException();
			params.put(Task.TITLE, mode.get(Mode.NAME));
			params.put(Task.MODE, utils.obj2Str(obj)); // 转字符串
		} else {
			// 任务重复保存发起时,taskMode保持不变
			params.remove(Task.MODE);
		}
		return params;
	}

	/**
	 * 批量保存审批过程中的节点,并更新节点工作状态
	 */
	Map saveWorkNodes(Map params,
			List> workNodes) {
		if (workNodes.size() == 0)
			throw new FailedException(FlowUtil.IS_LOST_WAY);

		// 当前审批节点(workNodes)的基本信息:
		// workComp,workTaskId,workNodeNo,workState,workComment
		// ,workESign,workIdx..[,workEmpId,workAgent,workESign,workUpdate]
		List> data = new ArrayList<>();

		// 已在pickCandidates()选新“候选人”时flowEngine.setNodeApprover()
		// 赋了4个值:workEmpId(empId)、workEmpName、workAgent、workOpUid(empUid)
		String[] workKeys = { Work.COMMENT, Work.ESIGN, Work.EMPID,
				Work.EMPNAME, Work.AGENT, Work.OPUID };

		// 当前提交审批的节点编号
		Object workNodeNo = params.get(Work.NODENO);
		Object approver = params.get(FlowUtil.APPROVER);

		// 原始node数据来源于getFlowNodes()
		for (Map node : workNodes) {
			// 这里需克隆一份审批节点数值(排除workUpdate)
			Map theNode = utils.map2Obj(node);
			theNode.remove(Work.UPDATE);

			// 待审批为空,需要新设置一个节点
			if (node.get(Work.NODENO) == null) {
				// 这里是新建节点(但taskId可能新建或携带)
				theNode.put(Work.TASKID, params.get(Task.ID));
				theNode.put(Work.NODENO, node.get(Node.NO));
				theNode.put(Work.COMP, node.get(Node.COMP));
				// 克隆数据,新建默认改为“0”,历史已自增“1++”
				theNode.put(Work.IDX, Const.STR_0);
				// 其中workState已赋值
			}

			// 注意workKeys的一致性(排除workUpdate):
			// 这里key与getFlowNodes()返回值(workField)一致
			String nodeNo = node.get(Node.NO);

			String workEmpId = node.get(Work.EMPID);
			// TODO: 需要补充加签的处理
			if (workEmpId != null && workEmpId.equals(approver)
					&& nodeNo.equals(workNodeNo)) {
				theNode.remove(Work.CREATE);
				data.add(theNode);
				continue;
			}
			for (String key : workKeys) {
				// 提交审批时携带“审批人”“审批意见”等页面数据如:workComment,workESign
				if (nodeNo.equals(workNodeNo) && params.containsKey(key)) {
					// 保留原node数据如:workEmpId,workEmpName,workAgent,workOpUid
					theNode.put(key, params.get(key));
				} else if (node.get(key) == null) {
					// 新设候选人数据:workEmpId,workEmpName,workAgent,workOpUid
					// 其他节点默认置为空
					theNode.put(key, "");
				}
				// 保留其他已有theNode属性
			}
			data.add(theNode);
		}
		LOG.debug("> addWorkNodes...{}", data);

		if (utils.isFailed(aolai.batchAdd(null, Work.TABLE, data, null, true)))
			throw new RuntimeException();
		return utils.success();
	}

	/**
	 * 添加一条新审批流程(任务):如果携带taskState=0时,保存为草稿
	 * 

* 根据前台提交的数据params,过程中又补充了taskOpUid,taskSecty[,taskState] */ Map addNewTask(Map params, Map sponsor, String taskState) { // data={taskFlowNo,taskEmpId,taskDept,taskTitle,taskFormData,..candidates} // 新发起一个流程,设置任务发起人的姓名 params.put(Task.EMPNAME, sponsor.get(Emp.NAME)); // 便于查询待办任务: // 这里必须把“taskOpUid”设置为发起人“empUid” params.put(Task.OPUID, sponsor.get(Emp.UID)); params.put(Task.STATE, taskState); // 登录用户“empSecty”可能是“发起人”,也可能“秘书”发起 // 如果由“秘书”发起,则“秘书”可以操作,否则“秘书”不可操作 if (params.get(Emp.SECTY).equals(sponsor.get(Emp.SECTY))) params.put(Task.SECTY, params.get(Emp.SECTY)); // 如果非“秘书”发起,则“秘书”不可操作 // 已移到前面处理:只在新发起任务时isNewTask // 这里必须把“taskOpUid”设置为发起人“empUid” // params.put(Task.OPUID, sponsor.get(Emp.UID)); LOG.debug("> addNewTask...{}", params); return aolai.addRecord(null, Task.TABLE, params, true); } /** * 根据taskId获取工作流程审批(任务)信息 */ Map getTaskInfo(Map where) { // taskComp,taskId,workNodeNo,workIdx Map cond = utils.sameId(where, Task.KEY); return aolai.findOne(null, Task.TABLE, cond, "getTaskInfo"); } /** * 获取当前用户(指定节点)工作流程审批(任务)信息 */ Map getReadyTask(Map params) { // taskId,workNodeNo Map where = utils.sameId(params, Task.KEY); where.put(Work.NODENO, params.get(Work.NODENO)); where.put(Work.STATE, FlowUtil.STATE_READY); // 就绪“0” String uid = utils.sqlVal(params.get(Work.AGENT)); String[] keys = { Work.OPUID, Work.AGENT }, values = { uid, uid }; // 权限校验用,有权限的用户才能查询到数据 where.put(utils.sqlOr(keys), values); Map> tables = workCond(where); List> list = aolai.findAll(null, tables, "getTaskList", where, null); if (list.size() == 0) throw new FailedException(ConfUtil.DENY_ACCESS); // 无权限操作 // 待办任务 Map task = list.get(0); // 这里的workEmpId用作在findNextNode()中鉴权 params.put(Work.EMPID, task.get(Work.EMPID)); // 当前用户提交的工作流 params.put(Task.FLOWNO, task.get(Task.FLOWNO)); // 返回当前任务信息 return task; } /** * 更新工作流程(任务)状态:“完成”或“驳回” */ Map setTaskState(Map data) { // taskId\taskComp Map where = utils.sameId(data, Task.KEY); // 限定条件:状态=0\1草稿或处理中的,才可以更新, where.put(utils.pHolder(Task.STATE, "<"), FlowUtil.STATE_COMPLETE); // 更新任务状态 return aolai.update(null, Task.TABLE, data, where); } /** * 会签“父”节点jointlyNode != null、未签节点却不存在(为“零”)时,则并发冲突 */ boolean jointlyCompleted(String jointlyNode) { if (jointlyNode == null) return false; Map where = new HashMap<>(); String key = utils.pHolder(Work.STATE, "<>"); where.put(key, FlowUtil.STATE_COMPLETE); key = utils.pHolder(Work.NODENO, "LIKE"); where.put(key, jointlyNode + "%"); key = utils.pHolder(Work.NODENO, "<>"); where.put(key, jointlyNode); // 不含父节点自己 // 不存在即“会签”完成,需要重新处理 return !aolai.exists(null, Work.TABLE, where); } /** * 根据taskId撤销一个审批中的任务,取消“审批中”的审批流程 */ Map cancelTask(Map params) { Map where = utils.sameId(params, Task.KEY); where.put(Task.STATE, FlowUtil.STATE_WAITING); // 1.处理中的任务 // 将要删除的数据存在才行 if (aolai.exists(null, Task.TABLE, where)) { // 先撤销任务 Map fields = new HashMap<>(); fields.put(Task.STATE, FlowUtil.STATE_CANCEL); // 5.撤回 // 撤销 aolai.update(null, Task.TABLE, fields, utils.sameId(where, Task.KEY)); // 再撤销审批节点 Map cond = new HashMap<>(); cond.put(Work.COMP, where.get(Task.COMP)); cond.put(Work.TASKID, where.get(Task.ID)); cond.put(Work.STATE, FlowUtil.STATE_READY); // 0.就绪节点 // 再撤销审批节点 fields.put(Work.STATE, FlowUtil.STATE_CANCEL); // 5.撤回 // 设置当前节点的执行人是当前操作用户本人 fields.put(Work.OPUID, req.getAttribute(User.ID)); // 获取当前操作员工编号 // 撤销 aolai.update(null, Work.TABLE, fields, cond); // 撤销成功 return utils.success(); } // 撤销失败 return utils.invalidParams(); } /** * 我的待办(两种视角)、已办(审批人视角)、办结事项(发起人视角) *

* 如果是我的办结(含驳回的和已完成的taskState=2、4) */ public Map getTaskList(HttpServletRequest req) { // workState,taskState[,taskSdate,taskEdate] Map params = utils.jsonParams(req); // 当前登录用户、“代理人”或“秘书”岗位职责 Map user = utils.userSelf(req); // 分页 String keys[] = { null, PAGE_SIZE, PAGE_NO }; Map where = utils.sameIf(params, keys); where.put(Task.COMP, user.get(User.COMP)); if (!utils.isEmpty(params.get(TASK_IDS))) { where.put(pHolderIn(Task.ID), params.get(TASK_IDS)); } // 节点类型,支持传递数组格式的参数 if (params.containsKey(Node.TYPE)) { String nodeType = String.valueOf(params.get(Node.TYPE)); where.put(pHolderIn(Node.TYPE), nodeType.split(Const.COMMA)); } else if (!utils.isEmpty(params.get(NODE_TYPES))) { where.put(pHolderIn(Node.TYPE), params.get(NODE_TYPES)); } // 查询任务表的组合条件 Map> tables; tables = workTaskCond(params, where, user.get(User.ID)); // 拓展支持json表达式,把参数传入where条件中 putJsonExtParams(params, where); // 按“发起时间”倒排序,最新的任务在上面 Map order = new HashMap<>(); order.put(Task.CREATE, "DESC"); // 按发起时间倒序 // 根据状态(workState,taskState)查询的结果 LOG.debug("> getTaskList...{}", tables); Map result = aolai.queryList(null, tables, "getTaskList", where, order, ConfUtil.limitRows()); // 分页数据 return utils.success(result); } /** * 拓展支持json表达式参数传递,注意此参数非前端传入,而是saor作为jar注入到父工程,父工程组装好传入,没有sql注入风险。 * 前端传入,拦截器会拦截到,没有sql注入风险 * @param params * @param where */ private void putJsonExtParams(Map params, Map where) { // 支持两种JSON类型数据: taskFormData\taskMode // String[] fields = { Task.FORMDATA, Task.MODE }; String jsonExtKey = "json_extract"; for (Map.Entry e: params.entrySet()) { if (e.getKey().contains(jsonExtKey)) { where.put(e.getKey(), e.getValue()); } } } /** * 我的待办(两种视角)、已办(审批人视角)、办结事项(发起人视角)的查询条件 */ private Map> workTaskCond( Map params, Map where, String userId) { // 参数必须携带其中一个参数 String taskState = (String) params.get(Task.STATE); String workState = (String) params.get(Work.STATE); if (utils.isEmpty(taskState) && utils.isEmpty(workState)) throw new FailedException(); String uid = utils.sqlVal(userId); String[] keys, values = { uid, uid }; // 审批人视角:查询workState=“0”待办的节点(含“代理人”视角) if (utils.isEmpty(taskState)) { // 待办任务:处理中 if (FlowUtil.STATE_READY.equals(workState)) { where.put(Work.STATE, FlowUtil.STATE_READY); // 同时满足条件:taskState=“1”处理中 where.put(Task.STATE, FlowUtil.STATE_WAITING); // 1.处理中 } else if (FlowUtil.STATE_COMPLETE.equals(workState)) { // 已完成的需要有个开始时间(什么时间处理的) setPeriodCond(params, where, Work.UPDATE); // 已办:workState=2.完成(4.驳回) String[] vs = { FlowUtil.STATE_COMPLETE, FlowUtil.STATE_REJECT }; where.put(pHolderIn(Work.STATE), vs); // where.remove(Work.STATE); } else if (!utils.isEmpty(workState)) { // workState=4.驳回,需要有个开始时间(什么时间驳回的) setPeriodCond(params, where, Work.UPDATE); } else { // 其他情况参数无效 throw new FailedException(); } // 必须是普通节点 where.put(Node.STYLE, FlowUtil.STYLE_NORMAL); // 0.普通 // 审批人岗位职责、或“代理”岗位职责 keys = new String[] { Work.OPUID, Work.AGENT }; where.put(utils.sqlOr(keys), values); return workCond(where); } else { // 发起人视角:查询taskState=“0” if (FlowUtil.STATE_READY.equals(taskState)) { // 1.处理中 0.草稿 5.撤回 4.驳回 where.put(pHolderIn(Work.STATE), new String[] { FlowUtil.STATE_WAITING, FlowUtil.STATE_READY, FlowUtil.STATE_CANCEL, FlowUtil.STATE_REJECT }); } else if (Const.ALL.equals(taskState)) { // 已完成的需要有个开始时间 setPeriodCond(params, where, Task.CREATE); } else if (!utils.isEmpty(taskState)) { // 单条件状态查询:传入什么状态,查询什么状态。 where.put(Task.STATE, taskState); setPeriodCond(params, where, Task.CREATE); } else { throw new FailedException(); } // where.remove(Task.STATE); // “发起人”或“秘书” keys = new String[] { Task.OPUID, Task.SECTY }; where.put(utils.sqlOr(keys), values); return taskCond(where); } } private String pHolderIn(String key) { return utils.pHolder(key, "in"); } /** * 日期条件 */ private void setPeriodCond(Map params, Map where, String key) { String startDate = (String) params.get("startDate"); String endDate = (String) params.get("endDate"); // 默认三个月之内的数据 if (!utils.isDatetime(startDate, Const.SDF)) startDate = utils.prevMonth("", 3) + "01"; else startDate += " 00:00:00"; where.put(utils.pHolder(key, ">="), startDate); if (utils.isDatetime(endDate, Const.SDF)) { where.put(utils.pHolder(key, "<="), endDate + " 23:59:59"); } else if (endDate != null) { throw new FailedException(); } LOG.debug("setPeriodCond...{}", where); } /** * 重新梳理的审批节点查询条件 */ private Map> workCond(Map where) { // 根据请求参数查询数据并返回查询结果 // 注意这里用LinkedHashMap确保三个关联表的顺序 Map> tables = new LinkedHashMap<>(); String taskComp = (String) where.get(Task.COMP); // “审批节点”表 tables.put(Work.TABLE, null); // 关联“工作任务”表 Map cond1 = new HashMap<>(); cond1.put(Task.COMP, taskComp); cond1.put(Task.ID, Work.TASKID); // 对应一条对应任务 tables.put(Task.TABLE, cond1); // 关联“流程节点”名称 Map cond2 = new HashMap<>(); // 有参数workNodeNo时,nodeStyle参数传递null cond2.put(Node.STYLE, (String) where.get(Node.STYLE)); cond2.put(Node.COMP, taskComp); cond2.put(Node.NO, Work.NODENO); // 当前任务节点 cond2.put(Node.FLOWNO, Task.FLOWNO); tables.put(Node.TABLE, cond2); // 取消此限制条件,否则“驳回”的记录就查不到了 // where.put(Work.IDX, Const.STR_0); // 审批次序“0” return tables; } /** * 重新梳理的审批节点查询条件 */ private Map> taskCond(Map where) { // 根据请求参数查询数据并返回查询结果 // 注意这里用LinkedHashMap确保三个关联表的顺序 Map> tables = new LinkedHashMap<>(); String taskComp = (String) where.get(Task.COMP); // “工作任务”表 tables.put(Task.TABLE, null); // 关联“审批节点”表 Map cond1 = new HashMap<>(); cond1.put(Work.COMP, taskComp); // 只有一个对应节点 cond1.put(Work.NODENO, Task.NODENO); cond1.put(Work.TASKID, Task.ID); // 默认选择审批次序必须为“0”的待审批节点 cond1.put(Work.IDX, Const.STR_0); // “审批节点”表(workIdx=‘0’) tables.put(Work.TABLE, cond1); // 关联“流程节点”表 Map cond2 = new HashMap<>(); cond2.put(Node.STYLE, (String) where.get(Node.STYLE)); cond2.put(Node.COMP, taskComp); cond2.put(Node.NO, Work.NODENO); // 当前任务节点 cond2.put(Node.FLOWNO, Task.FLOWNO); // 只有一个对应流程 // “流程节点”表 tables.put(Node.TABLE, cond2); return tables; } /** * 单独获取当前选中任务的表单信息 */ public Map getTaskInfo(HttpServletRequest req) { Map where = getTaskParams(req); if (aolai.exists(null, Task.TABLE, where)) { // 因为where条件中含有{taskOpUid}={V} or {taskSecty}={V}参数 // 所以这里使用findOne时,去除一个参数 Map cond = utils.sameId(where, Task.KEY); return aolai.findOne(null, Task.TABLE, cond); } throw new FailedException(); } /** * 删除一个草稿(已撤回)工作任务(流程) *

* 限定条件:状态=5.已撤回的 0.草稿中的,才可以删除 */ @Transactional(rollbackFor = { Throwable.class }) public Map delTask(HttpServletRequest req) { return delTask(getTaskParams(req)); } /** * 根据历史taskId删除任务 */ private Map delTask(Map params) { Map where = utils.sameId(params, Task.KEY); // 可删除任务:主动撤回任务、草稿任务、被驳回任务 String[] vals = { FlowUtil.STATE_CANCEL, FlowUtil.STATE_READY, FlowUtil.STATE_REJECT }; where.put(pHolderIn(Task.STATE), vals); // 将要删除的数据必须存在 if (!aolai.exists(null, Task.TABLE, where)) return utils.invalidParams(); // 无效参数 // 先删除任务 aolai.delete(null, Task.TABLE, utils.sameId(where, Task.KEY)); // 再删除过程中的审批节点 Map cond = new HashMap<>(); cond.put(Work.COMP, where.get(Task.COMP)); cond.put(Work.TASKID, where.get(Task.ID)); aolai.delete(null, Work.TABLE, cond); // 返回成功 return utils.success(); } /** * 公共:工作流任务的参数条件 */ private Map getTaskParams(HttpServletRequest req) { Map where = utils.jsonParams(req); Map user = utils.userSelf(req); where.put(Task.COMP, user.get(User.COMP)); // taskOpUid、taskSecty // 根据taskId获取审批节点,这里userId可能是“代理人” String uid = utils.sqlVal(user.get(User.ID)); String[] values = { uid, uid }; // 仅允许当前用户或“秘书”可以撤销 String[] keys = { Task.OPUID, Task.SECTY }; where.put(utils.sqlOr(keys), values); return where; } /** * 获取审批流中的普通审批节点一览,查询条件:workTaskId */ public Map getWorkNodes(HttpServletRequest req) { Map where = utils.jsonParams(req); utils.setUserComp(req, where, Work.COMP); where.put(utils.pHolder(Work.EMPID, Const.NEQ), ""); // 优先以更新时间排序 Map order = new LinkedHashMap<>(); order.put(Work.UPDATE, "DESC"); order.put(Work.CREATE, "DESC"); // 根据条件查询数据并返回结果 String[] args = { null, Work.TABLE, null, Const.STR_0 }; return utils.success(aolai.findAll(where, order, args)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy