DrbService.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. <?php
  2. namespace App\Service;
  3. use App\Model\DDEmployee;
  4. use App\Model\Record;
  5. use Illuminate\Support\Facades\Redis;
  6. class DrbService extends Service
  7. {
  8. const RedisKey = 'DRBACCESSTOKENKEY';
  9. public function getAccessToken()
  10. {
  11. $token = Redis::get(self::RedisKey);
  12. if(! empty($token)) return [true, ['access_token' => $token]];
  13. $appKey = config('dingtalk.app_key');
  14. $appSecret = config('dingtalk.app_secret');
  15. $url = "https://api.dingtalk.com/v1.0/oauth2/accessToken";
  16. $resp = $this->curlOpen1($url, [
  17. 'request' => 'post',
  18. 'header' => ['Content-Type: application/json'],
  19. 'json' => [
  20. "appKey" => $appKey,
  21. "appSecret" => $appSecret
  22. ]
  23. ]);
  24. $res = json_decode($resp, true);
  25. $accessToken = $res['accessToken'] ?? "";
  26. $expires_in = $res['expires_in'] ?? 0;
  27. if(empty($accessToken)) return [false, 'AccessToken获取失败'];
  28. Redis::setex(self::RedisKey, $expires_in, $accessToken);
  29. return [true, ['access_token' => $accessToken]];
  30. }
  31. /**
  32. * 根据前端传来的免登 code 获取用户信息
  33. * @param string $code 前端 dd.getAuthCode 获取的 code
  34. * @return array [bool, data] bool 表示成功与否,data 成功返回用户信息,失败返回错误信息
  35. */
  36. public function getUserByCode($data)
  37. {
  38. $code = $data['code'] ?? "";
  39. if (empty($code)) return [false, '钉钉授权code不能为空'];
  40. // 1. 获取 access_token
  41. [$success, $tokenData] = $this->getAccessToken();
  42. if (! $success) return [false, $tokenData]; // tokenData 是错误信息
  43. $accessToken = $tokenData['access_token'];
  44. // 2. 用 code 换取用户信息(v2 接口)
  45. $url = "https://oapi.dingtalk.com/topapi/v2/user/getuserinfo?access_token={$accessToken}";
  46. $resp = $this->curlOpen1($url, [
  47. 'request' => 'post',
  48. 'header' => [
  49. "Content-Type: application/json",
  50. ],
  51. 'json' => [
  52. "code" => $code
  53. ]
  54. ]);
  55. $res = json_decode($resp, true);
  56. if (!isset($res['errcode'])) {
  57. return [false, '接口返回异常: ' . $resp];
  58. }
  59. if ($res['errcode'] !== 0) {
  60. return [false, '获取用户信息失败: ' . $res['errmsg']];
  61. }
  62. if(! empty($res['result'])){
  63. $result = $res['result'];
  64. DDEmployee::updateOrCreate(
  65. ['userid' => $result['userid']],
  66. ['name' => $result['name'], 'userid' => $result['userid']]
  67. );
  68. }
  69. return [true, $res];
  70. }
  71. private function getManDetail($user, $accessToken){
  72. // 3. 根据 userid 获取详细用户信息(包括部门)
  73. $urlDetail = "https://oapi.dingtalk.com/topapi/v2/user/get?access_token={$accessToken}";
  74. $respDetail = $this->curlOpen1($urlDetail, [
  75. 'request' => 'post',
  76. 'header' => ["Content-Type: application/json"],
  77. 'json' => ["userid" => $user['userId']]
  78. ]);
  79. $detail = json_decode($respDetail, true);
  80. if (!isset($detail['errcode'])) return [false, '获取用户详情接口异常: ' . $respDetail];
  81. if ($detail['errcode'] !== 0) return [false, '获取用户详情失败: ' . $detail['errmsg']];
  82. if (empty($detail['result'])) return [false, '获取用户详情失败,结果为空'];
  83. // 返回完整用户信息
  84. return [true, $detail['result']];
  85. }
  86. public function createProcessInstance($data, $user)
  87. {
  88. if(empty($data['type'])) return [false, '单据类型不能为空'];
  89. $type = $data['type'];
  90. if(empty($data['order_number'])) return [false,'订单号不能为空'];
  91. [$success, $msg] = $this->checkCreateProcessInstance($data, $user);
  92. if(! $success) return [false, $msg];
  93. //获取模板id
  94. $code = $this->getModelCode($type);
  95. //获取模板数据
  96. [$success, $formData] = $this->getFormData($data, $user);
  97. if(! $success) return [false, $formData];
  98. dd($formData);
  99. // 1. 获取 access_token
  100. [$success, $tokenData] = $this->getAccessToken();
  101. if (!$success) return [false, $tokenData];
  102. $accessToken = $tokenData['access_token'];
  103. $userId = $user['userId'];
  104. [$success, $userDetail] = $this->getManDetail($user, $accessToken);
  105. if(!$success) return [false, $userDetail];
  106. //创建审批
  107. [$success, $msg] = $this->createFlow($accessToken, $code, $userId, $userDetail, $formData);
  108. if(! $success) return [false, $msg];
  109. //记录信息
  110. $this->recordDatabase($data, $user, $msg);
  111. return [true, ''];
  112. }
  113. private function recordDatabase($data, $user, $process_instance_id){
  114. $type = $data['type'];
  115. Record::insert([
  116. 'type' => $type,
  117. 'database' => $user['zt_database'],
  118. 'order_number'=> $data['order_number'],
  119. 'crt_time' => time(),
  120. 'process_instance_id' => $process_instance_id
  121. ]);
  122. return [true, ''];
  123. }
  124. private function checkCreateProcessInstance($data, $user){
  125. list($status,$msg) = $this->limitingSendRequestBackgExpire($data['order_number'].$data['type'].$user['zt_database']);
  126. if(! $status) return [false,$msg];
  127. $type = $data['type'];
  128. $bool = Record::where('del_time',0)
  129. ->where('type', $type)
  130. ->where('database', $user['zt_database'])
  131. ->where('order_number',$data['order_number'])
  132. ->exists();
  133. if($bool) return [false, '单号' . $data['order_number'] . '已创建审批流'];
  134. return [true, ''];
  135. }
  136. private function createFlow($accessToken, $code, $userId, $userDetail, $formData){
  137. // 2. 请求 URL
  138. $url = "https://oapi.dingtalk.com/topapi/processinstance/create?access_token={$accessToken}";
  139. // 3. 请求体
  140. $payload = [
  141. "process_code" => $code, // 审批模板编码
  142. "originator_user_id" => $userId, // 发起人 userId
  143. "dept_id" => $userDetail['dept_id_list'][0], // 发起人部门 ID
  144. "form_component_values" => $formData, // 表单数据
  145. ];
  146. // 4. 发送请求
  147. $resp = $this->curlOpen1($url, [
  148. 'request' => 'post',
  149. 'header' => [
  150. "Content-Type: application/json",
  151. ],
  152. 'json' => $payload
  153. ]);
  154. $res = json_decode($resp, true);
  155. if (!isset($res['errcode'])) {
  156. return [false, "接口返回异常: " . $resp];
  157. }
  158. if ($res['errcode'] !== 0) {
  159. return [false, "创建审批实例失败: " . $res['errmsg']];
  160. }
  161. return [true, $res['process_instance_id']];
  162. }
  163. private function getModelCode($type){
  164. if($type == 1){
  165. // 采购单
  166. $code = "PROC-61E1D916-C7BE-4DE2-9D25-63505D8573B1";
  167. }elseif ($type == 2){
  168. // 请购单
  169. $code = "PROC-4FECC44D-993C-48E3-8623-CDC75B467622";
  170. }else{
  171. // 付款单
  172. $code = "PROC-F6D0C212-D8C6-4662-9AFD-EA9DE85A1F14";
  173. }
  174. return $code;
  175. }
  176. private function getFormData($data, $user){
  177. //cs
  178. // $formData = [ [ "name" => "订单日期", "value" => "2025-09-23" ], [ "name" => "订单编号", "value" => "PO20250923001" ], [ "name" => "业务类型", "value" => "标准采购" ], [ "name" => "供应商", "value" => "XX供应商有限公司" ], [ "name" => "制单人", "value" => "陈庆鹏" ], [ "name" => "表体", "value" => json_encode([ [ [ "name" => "存货名称", "value" => "打印机" ], [ "name" => "数量", "value" => "2" ], [ "name" => "主计量单位", "value" => "台" ], [ "name" => "原币价税合计", "value" => "3000" ] ], [ [ "name" => "存货名称", "value" => "显示器" ], [ "name" => "数量", "value" => "5" ], [ "name" => "主计量单位", "value" => "个" ], [ "name" => "原币价税合计", "value" => "5000" ] ] ], JSON_UNESCAPED_UNICODE) ] ];
  179. // return [true, $formData];
  180. //cs
  181. $service = new U8ServerService($user);
  182. $error = $service->getError();
  183. if(! empty($error)) return [false, $error];
  184. [$success, $order] = $service->getOrderDetails($data, $user);
  185. if(! $success) return [false, $order];
  186. $type = $data['type'];
  187. if($type == 1){
  188. // 采购单
  189. $formData = $this->typeOne($order);
  190. }elseif ($type == 2){
  191. // 请购单
  192. $formData = $this->typeTwo($order);
  193. }else{
  194. // 付款单
  195. $formData = $this->typeThree($order);
  196. }
  197. if(empty($formData)) return [false, '审批参数不能为空'];
  198. return [true, $formData];
  199. }
  200. private function typeOne($userOrder){
  201. if (empty($userOrder)) return [];
  202. $formData = [
  203. [
  204. "name" => "订单日期",
  205. "value" => date('Y-m-d', strtotime($userOrder['order_date'] ?? ''))
  206. ],
  207. [
  208. "name" => "订单编号",
  209. "value" => $userOrder['order_number'] ?? ''
  210. ],
  211. [
  212. "name" => "业务类型",
  213. "value" => $userOrder['business_type'] ?? ''
  214. ],
  215. [
  216. "name" => "供应商",
  217. "value" => $userOrder['supplier_title'] ?? ''
  218. ],
  219. [
  220. "name" => "制单人",
  221. "value" => $userOrder['crt_name'] ?? ''
  222. ],
  223. [
  224. "name" => "表体", // 对应 TableField 的 label
  225. "value" => json_encode(
  226. array_map(function($item){
  227. return [
  228. [
  229. "name" => "存货名称",
  230. "value" => $item['product_title'] ?? ''
  231. ],
  232. [
  233. "name" => "数量",
  234. "value" => $item['quantity'] ?? 0
  235. ],
  236. [
  237. "name" => "主计量", // 修改这里,对应模板字段
  238. "value" => $item['unit_title'] ?? ''
  239. ],
  240. [
  241. "name" => "原币价税合计",
  242. "value" => $item['amount'] ?? 0
  243. ]
  244. ];
  245. }, $userOrder['detail'] ?? []),
  246. JSON_UNESCAPED_UNICODE
  247. )
  248. ]
  249. ];
  250. return $formData;
  251. }
  252. private function typeTwo($userOrder){
  253. if (empty($userOrder)) return [];
  254. $formData = [
  255. [
  256. "name" => "单据号",
  257. "value" => $userOrder['order_number'] ?? ''
  258. ],
  259. [
  260. "name" => "日期",
  261. "value" => date('Y-m-d', strtotime($userOrder['order_date'] ?? ''))
  262. ],
  263. [
  264. "name" => "业务类型",
  265. "value" => $userOrder['business_type'] ?? ''
  266. ],
  267. [
  268. "name" => "请购人",
  269. "value" => $userOrder['purchase_name'] ?? ''
  270. ],
  271. [
  272. "name" => "制单人",
  273. "value" => $userOrder['crt_name'] ?? ''
  274. ],
  275. [
  276. "name" => "表体", // 对应 TableField 的 label
  277. "value" => json_encode(
  278. array_map(function($item){
  279. return [
  280. [
  281. "name" => "存货名称",
  282. "value" => $item['product_title'] ?? ''
  283. ],
  284. [
  285. "name" => "数量",
  286. "value" => $item['quantity'] ?? 0
  287. ],
  288. [
  289. "name" => "主计量", // 修改这里,对应模板字段
  290. "value" => $item['unit_title'] ?? ''
  291. ],
  292. [
  293. "name" => "要求到货日期",
  294. "value" => $item['need_arrived_date'] ?? ''
  295. ]
  296. ];
  297. }, $userOrder['detail'] ?? []),
  298. JSON_UNESCAPED_UNICODE
  299. )
  300. ]
  301. ];
  302. return $formData;
  303. }
  304. private function typeThree($userOrder){
  305. if (empty($userOrder)) return [];
  306. $formData = [
  307. [
  308. "name" => "单据编号",
  309. "value" => $userOrder['order_number'] ?? ''
  310. ],
  311. [
  312. "name" => "日期",
  313. "value" => date('Y-m-d', strtotime($userOrder['order_date'] ?? ''))
  314. ],
  315. [
  316. "name" => "供应商",
  317. "value" => $userOrder['supplier_title'] ?? ''
  318. ],
  319. [
  320. "name" => "制单人",
  321. "value" => $userOrder['crt_name'] ?? ''
  322. ],
  323. [
  324. "name" => "表体", // 对应 TableField 的 label
  325. "value" => json_encode(
  326. array_map(function($item){
  327. return [
  328. [
  329. "name" => "来源",
  330. "value" => $item['source'] ?? ''
  331. ],
  332. [
  333. "name" => "来源单据号",
  334. "value" => $item['source_order_number'] ?? ''
  335. ],
  336. [
  337. "name" => "主计量", // 修改这里,对应模板字段
  338. "value" => $item['unit_title'] ?? ''
  339. ],
  340. [
  341. "name" => "申请金额",
  342. "value" => $item['amount'] ?? 0
  343. ]
  344. ];
  345. }, $userOrder['detail'] ?? []),
  346. JSON_UNESCAPED_UNICODE
  347. )
  348. ]
  349. ];
  350. return $formData;
  351. }
  352. public function getTemplateFields($data)
  353. {
  354. $processCode = $data['code'] ?? "";
  355. if (empty($processCode)) {
  356. return [false, '模板编号 process_code 不能为空'];
  357. }
  358. [$ok, $tokenData] = $this->getAccessToken();
  359. if (! $ok) return [false, $tokenData];
  360. $accessToken = $tokenData['access_token'];
  361. // 注意这里是 GET,并且 processCode 是 query 参数
  362. $url = "https://api.dingtalk.com/v1.0/workflow/forms/schemas/processCodes?processCode={$processCode}";
  363. $resp = $this->curlOpen1($url, [
  364. 'request' => 'get',
  365. 'header' => [
  366. "Content-Type: application/json",
  367. "x-acs-dingtalk-access-token: {$accessToken}"
  368. ],
  369. ]);
  370. $res = json_decode($resp, true);
  371. if (isset($res['schemas'])) {
  372. return [true, $res['schemas']];
  373. } else {
  374. return [false, $res];
  375. }
  376. }
  377. protected function curlOpen1($url, $config = [])
  378. {
  379. $default = [
  380. 'post' => false,
  381. 'request' => 'get',
  382. 'header' => [],
  383. 'json' => null,
  384. 'timeout' => 30
  385. ];
  386. $arr = array_merge($default, $config);
  387. $ch = curl_init();
  388. curl_setopt($ch, CURLOPT_URL, $url);
  389. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  390. curl_setopt($ch, CURLOPT_TIMEOUT, $arr['timeout']);
  391. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  392. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  393. if (!empty($arr['header'])) {
  394. curl_setopt($ch, CURLOPT_HTTPHEADER, $arr['header']);
  395. }
  396. if ($arr['post'] || $arr['request'] !== 'get') {
  397. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($arr['request']));
  398. if ($arr['json'] !== null) {
  399. curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($arr['json'], JSON_UNESCAPED_UNICODE));
  400. }
  401. }
  402. $result = curl_exec($ch);
  403. if ($result === false) {
  404. $err = curl_error($ch);
  405. curl_close($ch);
  406. return json_encode(['error' => $err]);
  407. }
  408. curl_close($ch);
  409. return $result;
  410. }
  411. }