Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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));
}
}