# 工作流开发指南

### 工作流开发

#### 工作流开发整体思路
工作流的开发大致分为以下几步：  
* 流程设计
* 流程部署
* 流程启动
下面以付款申请流程为例。  

#### 一、流程设计
1. 开始设计流程之前，先在工作流配置界面进行新建模型操作，输入完整界面上各个字段信息后进行保存。  
**唯一标识**及**分类**两个字段需要特别注意，这两个字段用于后台业务代码区分具体是哪一个工作流信息。
界面如下图所示：  
![新建模型界面](/assets/wfl_payment_req_create.png)  
  
2. 模型新建之后，需要设计具体的业务流程  
![进入模型界面](/assets/wfl_payment_req_modify_btn.png)  
从左边选择相应的节点，描述出整个业务的流程  
![描述模型界面](/assets/wfl_payment_req_draw.png)  
**各个流程节点的相关配置信息将在后面进行详细的介绍**
#### 二、流程部署
流程设计好以后，点击流程设计页面操作栏的最后一个按钮(勾)即可发布流程  
`未发布过的流程或者改动过的流程，勾会以绿色展示，反之灰色`  
发布以后可以在流程部署页面查看部署情况  
`如果有旧的流程启动了，发布的新版不会影响旧流程的运行`  
部署按钮见下图  
![部署模型界面](/assets/wfl_payment_req_deploy.png)  

#### 三、流程启动
流程启动一般是在前端发送请求到后端，后端进行处理后，触发工作流启动。  
请求处理类：    
```java
 @Override
    public void paymentApproval(IRequest iRequest, HttpSession session, List<HlsCusConContractCashflow> list) {
        // ...
        Map<String, Object> params = new HashMap<String, Object>();
        // 注意此处的workFlowType，该参数将和另一个业务类中的Code对应
        params.put("workFlowType", "PAY_PAYMENT");
        params.put("cshPaymentReqLn", ss.get(0));
        // 启动工作流流程
        activitiStartService.start(iRequest, list, params);
        // ...
    }
```  
业务实现类：  
```java
@Service
@Transactional
public class PaymentActivitStartServiceImpl implements IActivitiCommonService {

// 该数据对应了请求响应启动工作流时传入的workFlowType
    private static final String workFlowType = "PAY_PAYMENT";
    @Override
    public String getWorkFlowType() {
        return workFlowType;
    }

    // 启动工作流时，会自动调用该方法
    @Override
    public void process(IRequest iRequest, List list,Map params) {

        ProcessInstanceCreateRequest createRequest = getProcessInstanceCreateRequest(list,iRequest,params);
        // 启动流程
        activitiService.startProcess(iRequest, createRequest);
    }

    public ProcessInstanceCreateRequest getProcessInstanceCreateRequest(List<HlsCusConContractCashflow> list, IRequest iRequest, Map params) {
        ProcessInstanceCreateRequest createRequest = new ProcessInstanceCreateRequest();
        // 此处的两个参数，分别对应了新建模型时输入的**唯一标识**以及**分类**
        ReProcdef reProcdefs = reProcdefService.queryReProcdef("CSH_PAYMENT_REQ_WORK_FLOW","CSH_PAYMENT");
        SysUser sysUser = sysUserService.selectUserById(iRequest.getUserId());
        String id = reProcdefs.getId_();
        String name = reProcdefs.getName_();
        createRequest.setProcessDefinitionId(id);
        createRequest.setBusinessKey(list.get(0).getContract_id().toString());

        List<RestVariable> variables = new ArrayList<RestVariable>();
        List<RestVariable> transientVariables = new ArrayList<RestVariable>();
        RestVariable restVariable1 = new RestVariable();
        RestVariable restVariable2 = new RestVariable();
        RestVariable restVariable3 = new RestVariable();
        RestVariable restVariable4 = new RestVariable();
        RestVariable restVariable5 = new RestVariable();
        RestVariable restVariable13 = new RestVariable();
        RestVariable restVariable20 = new RestVariable();
        RestVariable restVariable = new RestVariable();

        HlsCusCshPaymentReqLn cshPaymentReqLn =  (HlsCusCshPaymentReqLn)params.get("cshPaymentReqLn");
        restVariable.setName("paymentRedId");
        restVariable.setValue(cshPaymentReqLn.getPayment_req_id());
        variables.add(restVariable);

        restVariable20.setName("contractId");
        restVariable20.setValue(cshPaymentReqLn.getSource_doc_id());
        variables.add(restVariable20);

        restVariable1.setName("processDefinitionId");
        restVariable1.setValue(id);
        variables.add(restVariable1);

        restVariable2.setName("contractCashflows");
        restVariable2.setValue(list);
        variables.add(restVariable2);

        restVariable3.setName("iRequest");
        restVariable3.setValue(iRequest);
        variables.add(restVariable3);

        double sumAmount = 0L;
        for(HlsCusConContractCashflow contractCashflow:list){
            sumAmount = add(sumAmount,contractCashflow.getAmount());
        }
        // 注意此处的sumAmount，在流程设计的过程中将会被使用到
        restVariable4.setName("sumAmount");
        restVariable4.setValue(new BigDecimal(sumAmount).toPlainString());
        variables.add(restVariable4);

        restVariable5.setName("startUserName");
        restVariable5.setValue(iRequest.getEmployeeCode());
        variables.add(restVariable5);

        RestVariable restVariable6 = new RestVariable();
        RestVariable restVariable7 = new RestVariable();
        RestVariable restVariable8 = new RestVariable();

        restVariable6.setName("documentCategory");
        restVariable6.setValue("CSH_PAYMENT_REQ");
        variables.add(restVariable6);
        restVariable7.setName("documentType");
        restVariable7.setValue("PAYMENT_REQ");
        variables.add(restVariable7);
        restVariable8.setName("documentId");
        restVariable8.setValue(cshPaymentReqLn.getPayment_req_id());
        variables.add(restVariable8);

        RestVariable restVariable9 = new RestVariable();
        restVariable9.setName("pName");
        restVariable9.setValue(name);
        variables.add(restVariable9);

        RestVariable restVariable10 = new RestVariable();
        restVariable10.setName("cshPaymentReqLn");
        restVariable10.setValue(cshPaymentReqLn);
        variables.add(restVariable10);


        RestVariable restVariable11 = new RestVariable();
        restVariable11.setName("ContractId");
        restVariable11.setValue(cshPaymentReqLn.getContract_id());
        variables.add(restVariable11);

        restVariable13.setName("startUserDescription");
        restVariable13.setValue(sysUser.getDescription());
        variables.add(restVariable13);

        createRequest.setVariables(variables);
        createRequest.setTransientVariables(transientVariables);


        return createRequest;
    }
    private double add(Double a, Double b) {
        BigDecimal aa = new BigDecimal(a.toString());
        BigDecimal bb = new BigDecimal(b.toString());

        return aa.add(bb).setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
    }
}
```  
**在该业务实现代码中，所有通过variables.add进行添加的参数，都可以在工作流设计时直接通过${ PARAM_NAME }使用，详细的用法在后面会进行介绍**

--- ---

### 流程设计详细
上面介绍了整个工作流流程启动的流程，但是在流程的设计过程并没有进行介绍，接下来将对流程图的设计以及流程的监听事件等进行介绍。
- 节点  
首先进入流程的详细界面，选择一个人工节点，如下图：  
![查看人工节点](/assets/wfl_payment_req_detail.png)  
可以看到右侧有许多的配置属性，下面是几个常用的属性的用途：  
* ID:自动生成，节点唯一标识  
* 名称:当前节点中文描述  
* 文档：消息机制，可在上下文中取到，如：我是${name},name为实现类里面配置的参数  
* 审批方式：默认为全部通过  
* 审批方式变量：当审批方式选择一定比例时，可设置变量如0.4，表示当审批人数达到这个比例后将会通过  
* 审批规则：选择对应的审批人，可在审批规则中定义  
* 加签：若勾选，则当前审批者可添加审批人  
* 表单url：表示审批流程中可插入的页面，能让审判者读阅  
* 执行监听器：执行监听器start跟end两种分别代表在该节点之前跟在该节点之后，在代理表达式里面可调用对应自己写的java类，格式如下：${endActionEventTask}，endActionEventTask为监听类在spring容器中的名称，一般为首字母小写之后的类名。  
* 任务监听器：任务监听器可参考执行监听器  

1. 文档  
可以看到右侧有对该流程的一些配置信息，先看文档，文档中的内容最终可以通过消息机制，当流程完成该流程时会在界面上显示文档中的内容。  
![文档](/assets/wfl_payment_req_doc.png)  
在文档中的内容，可以通过前面介绍的${}方式，直接使用在**业务代码**中添加的参数信息。  
2. 审批方式  
![](/assets/wfl_demo_2-1.png)  
如上图，在选择审批方式的时候，一共有五种审批方式，这里面的审批方式可在下图中的功能里面配置，如下图所示，  
![](/assets/wfl_demo_2-2.png)  
![](/assets/wfl_demo_2-3.png)  
这里面配置的审批方式会在设计节点的时候查询出来，可在这里删除或者添加新的审批方式，审批方式中的通过条件中，里面的一个变量为系统内置变量，具体含义可在IActivitiConstants.java文件中查看，如下图所示，
![](/assets/wfl_demo_2-4.png)  
3. 审批规则  
在流程设计的时候，点击审批规则会弹出下面的选择框，如下图所示，  
![](/assets/wfl_demo_2-5.png)  
审批规则是一个combobox，这里面的选项是在审批规则的功能里面进行配置，如下图所示  
![](/assets/wfl_demo_2-6.png)  
![](/assets/wfl_demo_2-7.png)  
我们可以看到，这里我们需要配置三个选项，分别是代码，描述，表达式，这里表达式对应某个变量或者我们后台写的service，我们通过service取得对应这个审批规则下所有的审批人员，我们可以看到方法后面还跟了一个参数，这个参数在我们选择的时候会放到表单属性里面，在我们介绍表单属性的会具体介绍  
4. 表单属性  
在右侧点击表单属性可以弹出表单属性的详细信息。（表单属性可以通过审批方式及审批规则自动添加，也可以手动填写）如图：  
![表单属性](/assets/wfl_payment_req_form.png)  

| 表单属性id | 表单名 | 类型 |  描述  |
| --------- | ------ | ----  | ---- |
| APPROVAL_STRATEGY  | ALL_APPROVAL | String | 审批策略为全部通过，指定的所有的审批人全都同意 |
| APPROVAL_CANDIDATE_RULE | - | Enum | 审批候选人，使用Enum类型，可以指定职位或者指定人进行审批,在其中设置id为APPOINTED_EMPLOYEE或APPOINTED_POSITION,表单名为{"parameter":[{"employeeCode":"//指定的employeeCode"}],"businessRules":[]} |  
另外若是想在审批页面指定出现哪些按钮，可以通过下图的方式进行配置  
![](/assets/wfl_demo_2-11.png)  
这里需要注意的是对应的按钮代码是固定的，我们需要配置对应的代码才会出现对应的按钮  
5. 任务监听  
在右侧点击任务监听后，弹出任务监听详细信息,可以通过选择不同的事件（create创建、assignment分配、complete完成、delete删除）并且配置Delegate expression（这个代理表达式使用${}包裹类名，但是类名为spring容器中对应的处理类的名称，如${approveActionEventTask}，一般为对应处理类的名称首字母小写）  

- 网关  
网关相当于一个if条件判断，通过在网关延伸出的流程线条上可以指定在什么条件流程会朝该方向进行。  
通过选中网关延伸出的线条，点击右侧的跳转条件，可以设置跳转到该方向的条件。值得注意的是，在这里设置的条件可以使用上面描述的**业务代码**中设置的任何的参数，使用方式见下图：    
![](/assets/wfl_payment_req_condition.png)  
