Commit d30bf0601c92ade4c24a4c0e723164f77feb383f

Authored by jason
1 parent 075dd83b

工作流 Flowable 取消流程实例实现

yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
... ... @@ -2,10 +2,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO;
6   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceMyPageReqVO;
7   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageItemRespVO;
8   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
  5 +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
9 6 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
10 7 import io.swagger.annotations.Api;
11 8 import io.swagger.annotations.ApiImplicitParam;
... ... @@ -50,4 +47,12 @@ public class BpmProcessInstanceController {
50 47 public CommonResult<BpmProcessInstanceRespVO> getProcessInstance(@RequestParam("id") String id) {
51 48 return success(processInstanceService.getProcessInstanceVO(id));
52 49 }
  50 +
  51 + @DeleteMapping("/cancel")
  52 + @ApiOperation(value = "取消流程实例", notes = "撤回发起的流程")
  53 + @PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')")
  54 + public CommonResult<Boolean> cancelProcessInstance(@Valid @RequestBody BpmProcessInstanceCancelReqVO cancelReqVO) {
  55 + processInstanceService.cancelProcessInstance(getLoginUserId(), cancelReqVO);
  56 + return success(true);
  57 + }
53 58 }
... ...
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
... ... @@ -5,18 +5,23 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI
5 5 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
6 6 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
7 7 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
  8 +import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEvent;
  9 +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
8 10 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
9 11 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
10 12 import org.flowable.engine.history.HistoricProcessInstance;
11 13 import org.flowable.engine.repository.ProcessDefinition;
  14 +import org.flowable.engine.runtime.ProcessInstance;
12 15 import org.flowable.task.api.Task;
13 16 import org.mapstruct.Mapper;
14 17 import org.mapstruct.Mapping;
15 18 import org.mapstruct.MappingTarget;
  19 +import org.mapstruct.Mappings;
16 20 import org.mapstruct.factory.Mappers;
17 21  
18 22 import java.util.List;
19 23 import java.util.Map;
  24 +import java.util.Optional;
20 25  
21 26 /**
22 27 * 流程实例 Convert
... ... @@ -73,7 +78,22 @@ public interface BpmProcessInstanceConvert {
73 78  
74 79 BpmProcessInstanceRespVO.User convert2(AdminUserRespDTO bean);
75 80  
  81 + default BpmProcessInstanceResultEvent convert(Object source, HistoricProcessInstance instance, Integer result) {
  82 + BpmProcessInstanceResultEvent event = new BpmProcessInstanceResultEvent(source);
  83 + event.setId(instance.getId());
  84 + event.setProcessDefinitionKey(instance.getProcessDefinitionKey());
  85 + event.setBusinessKey(instance.getBusinessKey());
  86 + event.setResult(result);
  87 + return event;
  88 + }
76 89  
77   -
  90 + default BpmMessageSendWhenProcessInstanceApproveReqDTO convert2ApprovedReq(ProcessInstance instance){
  91 + Long startUserId = instance.getStartUserId() == null ? null : Long.valueOf(instance.getStartUserId());
  92 + BpmMessageSendWhenProcessInstanceApproveReqDTO reqDTO = new BpmMessageSendWhenProcessInstanceApproveReqDTO()
  93 + .setStartUserId(startUserId)
  94 + .setProcessInstanceId(instance.getId())
  95 + .setProcessInstanceName(instance.getName());
  96 + return reqDTO;
  97 + }
78 98  
79 99 }
... ...
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
... ... @@ -101,7 +101,17 @@ public interface BpmTaskConvert {
101 101 })
102 102 BpmTaskTodoPageItemRespVO.ProcessInstance convert(HistoricProcessInstance processInstance, AdminUserRespDTO startUser);
103 103  
104   -
  104 + default BpmTaskExtDO convert2TaskExt(Task task){
  105 + Long assigneeUserId = task.getAssignee() == null ? null : Long.valueOf(task.getAssignee());
  106 + BpmTaskExtDO taskExtDO = new BpmTaskExtDO()
  107 + .setTaskId(task.getId())
  108 + .setAssigneeUserId(assigneeUserId)
  109 + .setName(task.getName())
  110 + .setProcessDefinitionId(task.getProcessDefinitionId())
  111 + .setProcessInstanceId(task.getProcessInstanceId());
  112 + taskExtDO.setCreateTime(task.getCreateTime());
  113 + return taskExtDO;
  114 + }
105 115 }
106 116  
107 117  
... ...
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
... ... @@ -28,8 +28,8 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent
28 28  
29 29 public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
30 30 .add(FlowableEngineEventType.PROCESS_CREATED)
31   - .add(FlowableEngineEventType.PROCESS_STARTED)
32 31 .add(FlowableEngineEventType.PROCESS_CANCELLED)
  32 + .add(FlowableEngineEventType.PROCESS_COMPLETED)
33 33 .build();
34 34  
35 35 public BpmProcessInstanceEventListener(){
... ... @@ -42,13 +42,12 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent
42 42 }
43 43  
44 44 @Override
45   - protected void processStarted(FlowableProcessStartedEvent event) {
46   - super.processStarted(event);
  45 + protected void processCancelled(FlowableCancelledEvent event) {
  46 + processInstanceService.updateProcessInstanceExtCancel(event);
47 47 }
48 48  
49   -
50 49 @Override
51   - protected void processCancelled(FlowableCancelledEvent event) {
52   - super.processCancelled(event);
  50 + protected void processCompleted(FlowableEngineEntityEvent event) {
  51 + processInstanceService.updateProcessInstanceExtComplete((ProcessInstance)event.getEntity());
53 52 }
54 53 }
... ...
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
... ... @@ -39,4 +39,9 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
39 39 protected void taskCreated(FlowableEngineEntityEvent event) {
40 40 taskService.createTaskExt((Task) event.getEntity());
41 41 }
  42 +
  43 + @Override
  44 + protected void taskCompleted(FlowableEngineEntityEvent event) {
  45 + super.taskCompleted(event);
  46 + }
42 47 }
... ...
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
... ... @@ -2,11 +2,9 @@ package cn.iocoder.yudao.module.bpm.service.task;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO;
6   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceMyPageReqVO;
7   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageItemRespVO;
8   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
  5 +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
9 6 import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
  7 +import org.flowable.engine.delegate.event.FlowableCancelledEvent;
10 8 import org.flowable.engine.history.HistoricProcessInstance;
11 9 import org.flowable.engine.runtime.ProcessInstance;
12 10  
... ... @@ -23,6 +21,14 @@ import java.util.Set;
23 21 public interface BpmProcessInstanceService {
24 22  
25 23 /**
  24 + * 获得流程实例
  25 + *
  26 + * @param id 流程实例的编号
  27 + * @return 流程实例
  28 + */
  29 + ProcessInstance getProcessInstance(String id);
  30 +
  31 + /**
26 32 * 获得流程实例列表
27 33 *
28 34 * @param ids 流程实例的编号集合
... ... @@ -68,6 +74,14 @@ public interface BpmProcessInstanceService {
68 74 BpmProcessInstanceRespVO getProcessInstanceVO(String id);
69 75  
70 76 /**
  77 + * 取消流程实例
  78 + *
  79 + * @param userId 用户编号
  80 + * @param cancelReqVO 取消信息
  81 + */
  82 + void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO);
  83 +
  84 + /**
71 85 * 获得历史的流程实例
72 86 *
73 87 * @param id 流程实例的编号
... ... @@ -81,4 +95,20 @@ public interface BpmProcessInstanceService {
81 95 * @param instance 流程任务
82 96 */
83 97 void createProcessInstanceExt(ProcessInstance instance);
  98 +
  99 + /**
  100 + * 更新 ProcessInstance 拓展记录为取消
  101 + *
  102 + * @param event 流程取消事件
  103 + */
  104 + void updateProcessInstanceExtCancel(FlowableCancelledEvent event);
  105 +
  106 + /**
  107 + * 更新 ProcessInstance 拓展记录为完成
  108 + *
  109 + * @param instance 流程任务
  110 + */
  111 + void updateProcessInstanceExtComplete(ProcessInstance instance);
  112 +
  113 +
84 114 }
... ...
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
... ... @@ -4,17 +4,17 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
7   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO;
8   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceMyPageReqVO;
9   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageItemRespVO;
10   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
  7 +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
11 8 import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
12 9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
13 10 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
14 11 import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper;
  12 +import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum;
15 13 import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
16 14 import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
  15 +import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher;
17 16 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
  17 +import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
18 18 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
19 19 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
20 20 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
... ... @@ -22,6 +22,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
22 22 import lombok.extern.slf4j.Slf4j;
23 23 import org.flowable.engine.HistoryService;
24 24 import org.flowable.engine.RuntimeService;
  25 +import org.flowable.engine.delegate.event.FlowableCancelledEvent;
25 26 import org.flowable.engine.history.HistoricProcessInstance;
26 27 import org.flowable.engine.repository.ProcessDefinition;
27 28 import org.flowable.engine.runtime.ProcessInstance;
... ... @@ -32,15 +33,12 @@ import org.springframework.validation.annotation.Validated;
32 33  
33 34 import javax.annotation.Resource;
34 35 import javax.validation.Valid;
35   -import java.util.List;
36   -import java.util.Map;
37   -import java.util.Optional;
38   -import java.util.Set;
  36 +import java.util.*;
39 37  
40 38 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
41 39 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
42   -import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_IS_SUSPENDED;
43   -import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_NOT_EXISTS;
  40 +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
  41 +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF;
44 42  
45 43 /**
46 44 * 流程实例 Service 实现类
... ... @@ -74,6 +72,15 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
74 72 private AdminUserApi adminUserApi;
75 73 @Resource
76 74 private DeptApi deptApi;
  75 + @Resource
  76 + private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher;
  77 + @Resource
  78 + private BpmMessageService messageService;
  79 + @Override
  80 + public ProcessInstance getProcessInstance(String id) {
  81 + return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
  82 + }
  83 +
77 84 @Override
78 85 public List<ProcessInstance> getProcessInstances(Set<String> ids) {
79 86 return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
... ... @@ -134,6 +141,24 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
134 141 processDefinition, processDefinitionExt, bpmnXml, startUser, dept);
135 142 }
136 143  
  144 + @Override
  145 + public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {
  146 + // 校验流程实例存在
  147 + ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
  148 + if (instance == null) {
  149 + throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
  150 + }
  151 + // 只能取消自己的
  152 + if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {
  153 + throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);
  154 + }
  155 +
  156 + // 通过删除流程实例,实现流程实例的取消,
  157 + // 删除流程实例,正则执行任务ACT_RU_TASK. 任务会被删除。通过历史表查询
  158 + runtimeService.deleteProcessInstance(cancelReqVO.getId(),
  159 + BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason()));
  160 + }
  161 +
137 162 /**
138 163 * 获得历史的流程实例
139 164 *
... ... @@ -162,6 +187,51 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
162 187 processInstanceExtMapper.insert(instanceExtDO);
163 188 }
164 189  
  190 + @Override
  191 + public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) {
  192 + // 判断是否为 Reject 不通过。如果是,则不进行更新
  193 + if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String)event.getCause())) {
  194 + return;
  195 + }
  196 +
  197 + // 需要主动查询,因为 instance 只有 id 属性
  198 + // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance
  199 + HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId());
  200 + // 更新拓展表
  201 + BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO()
  202 + .setProcessInstanceId(event.getProcessInstanceId())
  203 + .setEndTime(new Date()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置
  204 + .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
  205 + .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult());
  206 +
  207 + processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
  208 +
  209 + // 发送流程实例的状态事件
  210 + processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
  211 + BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
  212 + }
  213 +
  214 + @Override
  215 + public void updateProcessInstanceExtComplete(ProcessInstance instance) {
  216 + // 需要主动查询,因为 instance 只有 id 属性
  217 + // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance
  218 + HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId());
  219 + // 更新拓展表
  220 + BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO()
  221 + .setProcessInstanceId(instance.getProcessInstanceId())
  222 + .setEndTime(new Date()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置
  223 + .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
  224 + .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过
  225 + processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
  226 +
  227 + // 发送流程被通过的消息
  228 + messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance));
  229 +
  230 + // 发送流程实例的状态事件
  231 + processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
  232 + BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
  233 + }
  234 +
165 235 private String createProcessInstance0(Long userId, ProcessDefinition definition,
166 236 Map<String, Object> variables, String businessKey) {
167 237 // 校验流程定义
... ... @@ -181,7 +251,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
181 251 processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId())
182 252 .setFormVariables(variables));
183 253  
184   -
185 254 return instance.getId();
186 255 }
187 256 }
... ...
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
... ... @@ -61,4 +61,11 @@ public interface BpmTaskService {
61 61 */
62 62 void createTaskExt(Task task);
63 63  
  64 + /**
  65 + * 更新 Task 拓展记录为完成
  66 + *
  67 + * @param task 任务实体
  68 + */
  69 + void updateTaskExtComplete(Task task);
  70 +
64 71 }
... ...
yudao-module-bpm/yudao-module-bpm-impl-flowable/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
... ... @@ -27,10 +27,7 @@ import org.flowable.task.api.history.HistoricTaskInstance;
27 27 import org.springframework.stereotype.Service;
28 28  
29 29 import javax.annotation.Resource;
30   -import java.util.Collections;
31   -import java.util.List;
32   -import java.util.Map;
33   -import java.util.Set;
  30 +import java.util.*;
34 31  
35 32 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
36 33 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
... ... @@ -128,13 +125,16 @@ public class BpmTaskServiceImpl implements BpmTaskService{
128 125  
129 126 @Override
130 127 public void createTaskExt(Task task) {
131   - BpmTaskExtDO taskExtDO = new BpmTaskExtDO()
132   - .setTaskId(task.getId())
133   - .setAssigneeUserId(task.getAssignee() == null ? null : Long.valueOf(task.getAssignee()))
134   - .setProcessDefinitionId(task.getProcessDefinitionId())
135   - .setProcessInstanceId(task.getProcessInstanceId())
136   - .setName(task.getName())
  128 + BpmTaskExtDO taskExtDO = BpmTaskConvert.INSTANCE.convert2TaskExt(task)
137 129 .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
138 130 taskExtMapper.insert(taskExtDO);
139 131 }
  132 +
  133 + @Override
  134 + public void updateTaskExtComplete(Task task) {
  135 + BpmTaskExtDO taskExtDO = BpmTaskConvert.INSTANCE.convert2TaskExt(task)
  136 + .setEndTime(new Date()) // 此时不能使用 task 的 completeData,因为还是空的。
  137 + .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult());
  138 + taskExtMapper.updateByTaskId(taskExtDO);
  139 + }
140 140 }
... ...