WorkFlowService.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. <?php
  2. namespace App\Service;
  3. use App\Model\Draft;
  4. use App\Model\Employee;
  5. use App\Model\SysMenu;
  6. use App\Model\WorkFlowInstances;
  7. use App\Model\WorkFlowInstancesNodes;
  8. use App\Model\WorkFlowTemplates;
  9. use Illuminate\Support\Facades\DB;
  10. class WorkFlowService extends Service
  11. {
  12. public function workFlowEdit($data,$user){
  13. list($status,$msg) = $this->workFlowRule($data, $user, false);
  14. if(!$status) return [$status,$msg];
  15. try {
  16. DB::beginTransaction();
  17. $model = WorkFlowTemplates::where('id', $data['id'])->first();
  18. $model->code = $data['code'] ?? '';
  19. $model->title = $data['title'] ?? '';
  20. $model->content = $data['content'] ?? '';
  21. $model->save();
  22. DB::commit();
  23. }catch (\Exception $exception){
  24. DB::rollBack();
  25. return [false,$exception->getMessage()];
  26. }
  27. return [true, ''];
  28. }
  29. public function workFlowAdd($data,$user){
  30. list($status,$msg) = $this->workFlowRule($data, $user);
  31. if(!$status) return [$status,$msg];
  32. try {
  33. DB::beginTransaction();
  34. $model = new WorkFlowTemplates();
  35. $model->code = $data['code'] ?? '';
  36. $model->title = $data['title'] ?? '';
  37. $model->content = $data['content'] ?? '';
  38. $model->crt_id = $user['id'];
  39. $model->top_depart_id = $user['top_depart_id'];
  40. $model->save();
  41. DB::commit();
  42. }catch (\Exception $exception){
  43. DB::rollBack();
  44. return [false,$exception->getMessage()];
  45. }
  46. return [true, ''];
  47. }
  48. public function workFlowDetail($data, $user){
  49. if($this->isEmpty($data,'id')) return [false,'请选择数据!'];
  50. $customer = WorkFlowTemplates::where('del_time',0)
  51. ->where('id',$data['id'])
  52. ->first();
  53. if(empty($customer)) return [false,'审批流不存在或已被删除'];
  54. $customer = $customer->toArray();
  55. $customer['crt_name'] = Employee::where('id',$customer['crt_id'])->value('title');
  56. $customer['crt_time'] = $customer['crt_time'] ? date("Y-m-d H:i:s",$customer['crt_time']): '';
  57. return [true, $customer];
  58. }
  59. public function workFlowDel($data, $user){
  60. if($this->isEmpty($data,'id')) return [false,'请选择数据!'];
  61. $customer = WorkFlowTemplates::where('del_time',0)
  62. ->where('id',$data['id'])
  63. ->first();
  64. if(empty($customer)) return [false,'审批流不存在或已被删除'];
  65. $customer->del_time = time();
  66. $customer->save();
  67. return [true, ''];
  68. }
  69. public function workFlowCommon($data,$user, $field = []){
  70. if(empty($field)) $field = WorkFlowTemplates::$field;
  71. $model = WorkFlowTemplates::TopClear($user,$data);
  72. $model = $model->where('del_time',0)
  73. ->select($field)
  74. ->orderby('id', 'desc');
  75. if(! empty($data['title'])) $model->where('title', 'LIKE', '%'.$data['title'].'%');
  76. if(! empty($data['code'])) $model->where('code', 'LIKE', '%'.$data['code'].'%');
  77. if(! empty($data['id'])) $model->whereIn('id', $data['id']);
  78. if(! empty($data['crt_time'][0]) && ! empty($data['crt_time'][1])) {
  79. $return = $this->changeDateToTimeStampAboutRange($data['crt_time']);
  80. $model->where('crt_time','>=',$return[0]);
  81. $model->where('crt_time','<=',$return[1]);
  82. }
  83. return $model;
  84. }
  85. public function workFlowList($data,$user){
  86. $model = $this->workFlowCommon($data, $user);
  87. $list = $this->limit($model,'',$data);
  88. $list = $this->fillData($list);
  89. return [true, $list];
  90. }
  91. public function workFlowRule(&$data, $user, $is_add = true) {
  92. if(empty($data['title'])) return [false, '审批流名称不能为空'];
  93. if(empty($data['code'])) return [false, '审批流编码不能为空'];
  94. if(empty($data['content'])) return [false, '审批流内容不能为空'];
  95. // 1. JSON 格式校验与转换
  96. $content = $data['content'];
  97. if (is_string($content)) {
  98. $content = json_decode($content, true);
  99. if (json_last_error() !== JSON_ERROR_NONE) {
  100. return [false, '审批流内容格式错误,不是有效的JSON'];
  101. }
  102. }
  103. // 2. 结构完整性校验
  104. if (!isset($content['nodes']) || !is_array($content['nodes'])) {
  105. return [false, '审批流配置缺少节点(nodes)信息'];
  106. }
  107. if (!isset($content['edges']) || !is_array($content['edges'])) {
  108. return [false, '审批流配置缺少连线(edges)信息'];
  109. }
  110. $data['content'] = $content;
  111. // 4. 业务唯一性校验
  112. $query = WorkFlowTemplates::where('code', $data['code'])
  113. ->where('top_depart_id', $user['top_depart_id'])
  114. ->where('del_time', 0);
  115. if (!$is_add) {
  116. if (empty($data['id'])) return [false, 'ID不能为空'];
  117. $query->where('id', '<>', $data['id']);
  118. }
  119. if ($query->exists()) return [false, '审批流编码已存在'];
  120. return [true, ''];
  121. }
  122. public function fillData($data){
  123. if(empty($data['data'])) return $data;
  124. $emp = (new EmployeeService())->getEmployeeMap(array_unique(array_merge_recursive(array_column($data['data'],'charge_id'), array_column($data['data'],'crt_id'))));
  125. foreach ($data['data'] as $key => $value){
  126. $data['data'][$key]['crt_time'] = $value['crt_time'] ? date('Y-m-d H:i:s',$value['crt_time']) : '';
  127. $data['data'][$key]['crt_name'] = $emp[$value['crt_id']] ?? '';
  128. }
  129. return $data;
  130. }
  131. /**
  132. * 触发审批流实例
  133. * * @param string $templateCode 审批流唯一编码
  134. * @param int $documentId 业务单据ID
  135. * @param string $documentType 业务模型类名
  136. * @param array $user 当前操作用户信息
  137. */
  138. public function triggerWorkflow($templateID, $documentId, $documentType, $user)
  139. {
  140. if($user['select_tree_type'] == SysMenu::tree_type_zero) return [true, ''];
  141. // 1. 获取对应的审批流模板
  142. $template = WorkFlowTemplates::where('id', $templateID)
  143. ->where('top_depart_id', $user['top_depart_id'])
  144. ->where('del_time', 0)
  145. ->first();
  146. if (!$template) return [false, '审批模板不存在'];
  147. // 2. 创建流程主实例
  148. $instance = WorkFlowInstances::create([
  149. 'template_id' => $template->id,
  150. 'document_id' => $documentId,
  151. 'document_type' => $documentType,
  152. 'status' => WorkFlowInstances::status_two, // 激活
  153. 'crt_id' => $user['id'],
  154. 'crt_time' => time(),
  155. 'upd_time' => time(),
  156. 'top_depart_id' => $user['top_depart_id']
  157. ]);
  158. dd($template->content);
  159. // 获取前端原始 JSON 中的节点和连线 [cite: 1]
  160. $nodes = $template->content['nodes'];
  161. $edges = $template->content['edges'];
  162. // 3. 准备批量插入的节点数据
  163. $insertNodes = [];
  164. foreach ($nodes as $node) {
  165. $nodeKey = $node['id'];
  166. // 从 edges 中寻找当前节点的前置 (source 为谁) 和 后置 (target 为谁) [cite: 1]
  167. // 这里的 source 和 target 是 edges 顶层的字符串 ID
  168. $prev = collect($edges)->where('target', $nodeKey)->pluck('source')->first();
  169. $next = collect($edges)->where('source', $nodeKey)->pluck('target')->first();
  170. $tempAssignees = $node['data']['assignees'] ?? [];
  171. foreach($tempAssignees as $k => $person) {
  172. $tempAssignees[$k]['is_pass'] = 0; // 0-未审,1-通过,2-驳回
  173. $tempAssignees[$k]['mark'] = '';
  174. $tempAssignees[$k]['pass_time'] = 0;
  175. }
  176. $insertNodes[] = [
  177. 'instance_id' => $instance->id,
  178. 'node_key' => $nodeKey,
  179. 'label' => $node['label'] ?? '未命名节点',
  180. 'prev_node_key' => $prev, // 上级节点 ID
  181. 'next_node_key' => $next, // 下级节点 ID
  182. // 审批人快照存储 [cite: 1]
  183. 'assignees' => json_encode($tempAssignees),
  184. // 审批类型:1 会签,2 或签 [cite: 1]
  185. 'approval_type' => $node['data']['type'] ?? 2,
  186. // 初始状态逻辑:如果没有前置节点,说明是首节点,直接进入“审批中”状态
  187. 'status' => $prev ? 0 : 1,
  188. 'handled_time' => 0,
  189. 'crt_time' => time(),
  190. 'upd_time' => time(),
  191. 'crt_id' => $user['id'],
  192. 'top_depart_id' => $user['top_depart_id']
  193. ];
  194. }
  195. // 4. 写入节点实例表
  196. WorkFlowInstancesNodes::insert($insertNodes);
  197. return [true, $instance->id];
  198. }
  199. /**
  200. * 获取当前用户的待办审核列表
  201. */
  202. public function getMyPendingApprovals($data, $user)
  203. {
  204. $userId = $user['id'];
  205. $topDepartId = $user['top_depart_id'];
  206. // 1. 构造查询对象
  207. $model = WorkFlowInstancesNodes::where('del_time',0)
  208. ->where('top_depart_id', $topDepartId)
  209. ->where('status', 1) // 1 = 审批中
  210. ->whereRaw('JSON_CONTAINS(assignees, JSON_OBJECT("id", ?))', [(int)$userId])
  211. // 如果我是“或签”,只要我还没审,节点状态就是1,我能看到;
  212. // 如果我是“会签”,我审过了但别人没审,节点状态还是1,但通过这个排除条件,我看不到了。
  213. ->whereRaw('NOT JSON_CONTAINS(assignees, JSON_OBJECT("id", ?, "is_pass", 1))', [$userId])
  214. ->orderBy('id', 'desc');
  215. $pageData = $this->limit($model, '', $data);
  216. if (empty($pageData['data'])) return [true, $pageData];
  217. // 3. 提取当前页的所有 instance_id
  218. $instanceIds = array_unique(array_column($pageData['data'], 'instance_id'));
  219. // 4. 获取关联信息
  220. $instances = WorkFlowInstances::with(['template'])
  221. ->whereIn('id', $instanceIds)
  222. ->get()
  223. ->keyBy('id');
  224. // 5. 组装返回数据 (遍历当前页的数据进行填充)
  225. foreach ($pageData['data'] as $key => $node) {
  226. $instance = $instances[$node['instance_id']] ?? null;
  227. $pageData['data'][$key] = [
  228. 'node_instance_id' => $node['id'],
  229. 'instance_id' => $node['instance_id'],
  230. 'node_label' => $node['label'],
  231. 'document_id' => $instance->document_id ?? 0, //业务单据id
  232. 'document_type' => $instance->document_type ?? '', //业务单据类型
  233. 'flow_title' => $instance->template->title ?? '未知流程',
  234. 'apply_time_fmt' => isset($instance->crt_time->timestamp) ? date('Y-m-d H:i:s', $instance->crt_time->timestamp) : '',
  235. 'approval_type' => $node['approval_type'],
  236. ];
  237. }
  238. return [true, $pageData];
  239. }
  240. public function approval($data, $user){
  241. list($status, $msg, $result) = $this->approve($data, $user);
  242. if(! $status) return [false, $msg];
  243. if($result == 1 || $result == 2){
  244. $instance_id = $msg;
  245. $this->businessSettle($result, $instance_id);
  246. }
  247. return [true, ''];
  248. }
  249. public function businessSettle($result, $instance_id){
  250. $w = WorkFlowInstances::where('id', $instance_id)->first();
  251. if(empty($w)) return;
  252. $w = $w->toArray();
  253. if($result == 1){
  254. $draft = Draft::where('del_time',0)
  255. ->where('document_type', $w['document_type'])
  256. ->where('document_id', $w['document_id'])
  257. ->where('top_depart_id', $w['top_depart_id'])
  258. ->latest()
  259. ->first();
  260. if(! empty($draft)){
  261. $draft = $draft->toArray();
  262. if($draft['opt_type'] == 1){
  263. //调用编辑接口 覆盖数据
  264. if($draft['document_type'] == 'item'){
  265. (new ItemService())->itemEditSave($draft['content'], $draft['user']);
  266. }
  267. }else{
  268. //单纯更新某个字段
  269. }
  270. }
  271. }else{
  272. //审核状态标记改回去
  273. DB::table($w['document_type'])->where('id', $w['document_id'])->update(['approval_state' => 0]);
  274. }
  275. }
  276. /**
  277. * @param int $nodeInstanceId 节点实例ID
  278. * @param int $status 结果:2-通过,3-驳回
  279. * @param string $remark 意见
  280. * @param array $user 当前操作人
  281. *
  282. * 业务结果说明:0-审批中(继续等待), 1-最终通过(业务单据可改为已完成), 2-已驳回(业务单据可改为已拒绝)
  283. */
  284. public function approve($data, $user)
  285. {
  286. $nodeInstanceId = $data['node_instance_id'] ?? 0;
  287. $status = $data['status'] ?? 2;
  288. $remark = $data['mark'] ?? '';
  289. $currentNode = WorkFlowInstancesNodes::where('del_time',0)
  290. ->where('id', $nodeInstanceId)
  291. ->where('top_depart_id', $user['top_depart_id'])
  292. ->first();
  293. if (!$currentNode || $currentNode->status != 1) return [false, '环节无效或已处理', 0];
  294. $assignees = is_string($currentNode->assignees) ? json_decode($currentNode->assignees, true) : $currentNode->assignees;
  295. $myIndex = collect($assignees)->search(fn($item) => $item['id'] == $user['id']);
  296. if ($myIndex === false) return [false, '您不在审批人名单中', 0];
  297. try {
  298. DB::beginTransaction();
  299. $time = time();
  300. $assignees[$myIndex]['mark'] = $remark;
  301. $assignees[$myIndex]['pass_time'] = $time;
  302. // --- 情况 A:驳回 ---
  303. if ($status == 3) {
  304. $currentNode->status = 3;
  305. $currentNode->handled_time = $time;
  306. $assignees[$myIndex]['is_pass'] = 2;
  307. $currentNode->assignees = $assignees;
  308. $currentNode->save();
  309. WorkFlowInstances::where('id', $currentNode->instance_id)->update(['status' => WorkFlowInstances::status_four]);
  310. DB::commit();
  311. return [true, $currentNode->instance_id, 2];
  312. }
  313. // --- 情况 B:通过 ---
  314. $assignees[$myIndex]['is_pass'] = 1;
  315. $currentNode->assignees = $assignees;
  316. $shouldMoveToNext = false;
  317. if ($currentNode->approval_type == 2) {
  318. $shouldMoveToNext = true;
  319. } else {
  320. $unPassedCount = collect($assignees)->where('is_pass', '!=', 1)->count();
  321. if ($unPassedCount == 0) {
  322. $shouldMoveToNext = true;
  323. }
  324. }
  325. $finalResult = 0; // 默认:还在审批中
  326. if ($shouldMoveToNext) {
  327. $currentNode->status = 2;
  328. $currentNode->handled_time = $time;
  329. $currentNode->save();
  330. if (!empty($currentNode->next_node_key)) {
  331. $hasActiveNext = $this->activateNextNode($currentNode->instance_id, $currentNode->next_node_key);
  332. if (!$hasActiveNext) {
  333. // 如果逻辑上虽然有key但没找到下个节点,也视为终结
  334. WorkFlowInstances::where('id', $currentNode->instance_id)->update(['status' => WorkFlowInstances::status_three]);
  335. $finalResult = 1;
  336. }
  337. } else {
  338. // 没有下个节点,流程最终终结
  339. WorkFlowInstances::where('id', $currentNode->instance_id)->update(['status' => WorkFlowInstances::status_three]);
  340. $finalResult = 1; // 返回 1,表示业务层面可以执行“生效”动作
  341. }
  342. } else {
  343. // 会签中,还有人没审
  344. $currentNode->save();
  345. $finalResult = 0;
  346. }
  347. DB::commit();
  348. return [true, $currentNode->instance_id, $finalResult];
  349. } catch (\Exception $e) {
  350. DB::rollBack();
  351. return [false, $e->getMessage(), 0];
  352. }
  353. }
  354. private function activateNextNode($instanceId, $nextNodeKey)
  355. {
  356. $nextNode = WorkFlowInstancesNodes::where('instance_id', $instanceId)
  357. ->where('node_key', $nextNodeKey)
  358. ->where('del_time',0)
  359. ->first();
  360. if ($nextNode) {
  361. $nextNode->status = 1;
  362. $nextNode->save();
  363. return true;
  364. }
  365. return false;
  366. }
  367. }