WorkClientServices.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2020 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\services\work;
  12. use app\dao\work\WorkClientDao;
  13. use app\jobs\work\WorkClientJob;
  14. use app\services\BaseServices;
  15. use app\services\user\label\UserLabelServices;
  16. use app\services\user\UserServices;
  17. use crmeb\services\wechat\config\WorkConfig;
  18. use crmeb\services\wechat\WechatResponse;
  19. use crmeb\services\wechat\Work;
  20. use crmeb\traits\ServicesTrait;
  21. use think\exception\ValidateException;
  22. use think\facade\Log;
  23. /**
  24. * 企业微信客户
  25. * Class WorkClientServices
  26. * @package app\services\work
  27. * @mixin WorkClientDao
  28. */
  29. class WorkClientServices extends BaseServices
  30. {
  31. use ServicesTrait;
  32. /**
  33. * WorkClientServices constructor.
  34. * @param WorkClientDao $dao
  35. */
  36. public function __construct(WorkClientDao $dao)
  37. {
  38. $this->dao = $dao;
  39. }
  40. /**
  41. * @param array $where
  42. * @param array $field
  43. * @param bool $isPage
  44. * @return array
  45. */
  46. public function getList(array $where, array $field = ['*'], bool $isPage = true)
  47. {
  48. $page = $limit = 0;
  49. if ($isPage) {
  50. [$page, $limit] = $this->getPageValue();
  51. }
  52. $list = $this->dao->getDataList($where, $field, $page, $limit, 'create_time', [
  53. 'followOne' => function ($query) {
  54. $query->with([
  55. 'member' => function ($query) {
  56. $query->field(['userid', 'id', 'name', 'main_department'])
  57. ->with(['mastareDepartment']);
  58. }
  59. ])->field(['userid', 'client_id', 'state', 'id']);
  60. },
  61. 'follow' => function ($query) {
  62. $query->field(['id', 'client_id'])->with(['tags']);
  63. },
  64. ]);
  65. foreach ($list as &$item) {
  66. if (!empty($item['follow'])) {
  67. $tags = [];
  68. foreach ($item['follow'] as $value) {
  69. if (!empty($value['tags'])) {
  70. $tags = array_merge($tags, $value['tags']);
  71. }
  72. }
  73. $newTags = [];
  74. foreach ($tags as $tag) {
  75. if (!in_array($tag['tag_name'], array_column($newTags, 'tag_name'))) {
  76. $newTags[] = $tag;
  77. }
  78. }
  79. $item['followOne']['tags'] = $newTags;
  80. }
  81. }
  82. $count = $this->dao->count($where);
  83. return compact('list', 'count');
  84. }
  85. /**
  86. * 自动同步客户
  87. * @param int $page
  88. * @param string $cursor
  89. * @return bool
  90. */
  91. public function authGetExternalcontact(int $page = 1, string $cursor = '')
  92. {
  93. /** @var WorkConfig $config */
  94. $config = app()->make(WorkConfig::class);
  95. $corpId = $config->get('corpId');
  96. if (!$corpId) {
  97. return true;
  98. }
  99. /** @var WorkMemberServices $memberService */
  100. $memberService = app()->make(WorkMemberServices::class);
  101. $menmberList = $memberService->getDataList(['corp_id' => $corpId], ['userid'], $page, 10);
  102. //没有数据就返回成功
  103. if (!$menmberList) {
  104. return true;
  105. }
  106. $userids = array_column($menmberList, 'userid');
  107. $response = Work::getBatchClientList($userids, $cursor);
  108. $externalContactList = $response['external_contact_list'] ?? [];
  109. $external = [];
  110. $followUser = [];//内部人员跟踪
  111. $externalUserids = [];//客户信息
  112. $this->transaction(function () use ($externalContactList, $corpId, $externalUserids, $followUser, $external) {
  113. foreach ($externalContactList as $item) {
  114. $externalContact = $item['external_contact'];
  115. $unionid = $externalContact['unionid'] ?? '';
  116. if (isset($externalContact['unionid'])) {
  117. unset($externalContact['unionid']);
  118. }
  119. $corpName = $corpFullName = $position = '';
  120. if (isset($externalContact['corp_name'])) {
  121. $corpName = $externalContact['corp_name'];
  122. unset($externalContact['corp_name']);
  123. }
  124. if (isset($externalContact['corp_full_name'])) {
  125. $corpFullName = $externalContact['corp_full_name'];
  126. unset($externalContact['corp_full_name']);
  127. }
  128. if (isset($externalContact['position'])) {
  129. $position = $externalContact['position'];
  130. unset($externalContact['position']);
  131. }
  132. $externalContact['position'] = $position;
  133. $externalContact['external_profile'] = json_encode($externalContact['external_profile'] ?? []);
  134. $followUserData = [
  135. 'userid' => $item['follow_info']['userid'],
  136. 'remark' => $item['follow_info']['remark'] ?? '',
  137. 'description' => $item['follow_info']['description'] ?? '',
  138. 'createtime' => $item['follow_info']['createtime'] ?? '',
  139. 'remark_corp_name' => $item['follow_info']['remark_corp_name'] ?? '',
  140. 'remark_mobiles' => json_encode($item['follow_info']['remark_mobiles'] ?? ''),
  141. 'add_way' => $item['follow_info']['add_way'] ?? '',
  142. 'oper_userid' => $item['follow_info']['oper_userid'] ?? '',
  143. 'create_time' => time(),
  144. 'tags' => [],
  145. ];
  146. if (!empty($item['follow_info']['tag_id'])) {
  147. $tagRes = Work::getCorpTags($item['follow_info']['tag_id']);
  148. foreach ($tagRes['tag_group'] ?? [] as $group) {
  149. foreach ($group['tag'] as $tag) {
  150. $followUserData['tags'][] = [
  151. 'group_name' => $group['group_name'] ?? '',
  152. 'tag_name' => $tag['name'] ?? '',
  153. 'type' => $tag['type'] ?? 1,
  154. 'tag_id' => $tag['id'],
  155. 'create_time' => time()
  156. ];
  157. }
  158. }
  159. }
  160. $followUser[$externalContact['external_userid']] = $followUserData;
  161. $externalUserids[] = $externalContact['external_userid'];
  162. $externalUserid = $externalContact['external_userid'];
  163. $externalContact['corp_id'] = $corpId;
  164. $externalContact['unionid'] = $unionid;
  165. $externalContact['corp_name'] = $corpName;
  166. $externalContact['corp_full_name'] = $corpFullName;
  167. if ($this->dao->count(['external_userid' => $externalUserid, 'corp_id' => $corpId])) {
  168. unset($externalContact['external_userid']);
  169. $this->dao->update(['external_userid' => $externalUserid], $externalContact);
  170. } else {
  171. $externalContact['create_time'] = time();
  172. $externalContact['update_time'] = time();
  173. $external[] = $externalContact;
  174. }
  175. }
  176. if ($external) {
  177. $this->dao->saveAll($external);
  178. }
  179. $clientList = $this->dao->getColumn([['external_userid', 'in', $externalUserids], ['corp_id', '=', $corpId]], 'id', 'external_userid');
  180. /** @var WorkClientFollowServices $followService */
  181. $followService = app()->make(WorkClientFollowServices::class);
  182. if ($followUser) {
  183. /** @var WorkClientFollowTagsServices $tagService */
  184. $tagService = app()->make(WorkClientFollowTagsServices::class);
  185. foreach ($followUser as $userid => $items) {
  186. $items['client_id'] = $clientList[$userid];
  187. if (($id = $followService->value(['client_id' => $clientList[$userid], 'userid' => $items['userid']], 'id'))) {
  188. $followService->update($id, [
  189. 'remark' => $items['remark'],
  190. 'description' => $items['description'],
  191. 'createtime' => $items['createtime'],
  192. 'remark_corp_name' => $items['remark_corp_name'],
  193. 'remark_mobiles' => $items['remark_mobiles'],
  194. 'add_way' => $items['add_way'],
  195. 'oper_userid' => $items['oper_userid'],
  196. ]);
  197. } else {
  198. $res = $followService->save($items);
  199. $id = $res->id;
  200. }
  201. if (!empty($items['tags'])) {
  202. $tagService->delete(['follow_id' => $id]);
  203. foreach ($items['tags'] as &$tag) {
  204. $tag['follow_id'] = $id;
  205. }
  206. $tagService->saveAll($items['tags']);
  207. }
  208. }
  209. }
  210. });
  211. if (isset($response['next_cursor']) && $response['next_cursor']) {
  212. WorkClientJob::dispatchDo('authClient', [$page, $response['next_cursor'] ?? '']);
  213. } else if (count($userids) >= 10 && empty($response['next_cursor'])) {
  214. WorkClientJob::dispatchDo('authClient', [$page + 1, '']);
  215. }
  216. return true;
  217. }
  218. public function saveClientTags(array $tagGroup)
  219. {
  220. }
  221. /**
  222. * 创建客户
  223. * @param array $payload
  224. * @return mixed
  225. */
  226. public function createClient(array $payload)
  227. {
  228. $corpId = $payload['ToUserName'];//企业id
  229. $externalUserID = $payload['ExternalUserID'];//外部企业userid
  230. $state = $payload['State'] ?? '';//扫码值
  231. $userId = $payload['UserID'];//成员userid
  232. //保存客户
  233. $clientId = $this->saveOrUpdateClient($corpId, $externalUserID, $userId);
  234. //发送欢迎语
  235. try {
  236. event('work.welcome', [$payload['WelcomeCode'] ?? '', $state, $clientId, $userId]);
  237. } catch (\Throwable $e) {
  238. Log::error([
  239. 'message' => '发送欢迎语失败:' . $e->getMessage(),
  240. 'file' => $e->getFile(),
  241. 'line' => $e->getLine()
  242. ]);
  243. }
  244. //设置欢客户标签
  245. try {
  246. event('work.label', [$state, $userId, $externalUserID]);
  247. } catch (\Throwable $e) {
  248. Log::error([
  249. 'message' => '设置欢客户标签失败:' . $e->getMessage(),
  250. 'file' => $e->getFile(),
  251. 'line' => $e->getLine()
  252. ]);
  253. }
  254. //关联客户与商城用户
  255. try {
  256. event('work.user', [$clientId]);
  257. } catch (\Throwable $e) {
  258. Log::error([
  259. 'message' => '关联客户与商城用户失败:' . $e->getMessage(),
  260. 'file' => $e->getFile(),
  261. 'line' => $e->getLine()
  262. ]);
  263. }
  264. return $clientId;
  265. }
  266. /**
  267. * 更新客户信息
  268. * @param array $payload
  269. * @return mixed
  270. */
  271. public function updateClient(array $payload)
  272. {
  273. $corpId = $payload['ToUserName'];
  274. $externalUserID = $payload['ExternalUserID'];
  275. $userId = $payload['UserID'];//成员serid
  276. $clientId = $this->saveOrUpdateClient($corpId, $externalUserID, $userId);
  277. //关联客户与商城用户
  278. try {
  279. event('work.user', [$clientId]);
  280. } catch (\Throwable $e) {
  281. Log::error([
  282. 'message' => '关联客户与商城用户失败:' . $e->getMessage(),
  283. 'file' => $e->getFile(),
  284. 'line' => $e->getLine()
  285. ]);
  286. }
  287. return $clientId;
  288. }
  289. /**
  290. * 企业成员删除客户
  291. * @param array $payload
  292. * @return bool
  293. * @throws \think\db\exception\DataNotFoundException
  294. * @throws \think\db\exception\DbException
  295. * @throws \think\db\exception\ModelNotFoundException
  296. */
  297. public function deleteClient(array $payload)
  298. {
  299. $corpId = $payload['ToUserName'];
  300. $externalUserID = $payload['ExternalUserID'];
  301. $userId = $payload['UserID'];//成员serid
  302. $clientInfo = $this->dao->get(['external_userid' => $externalUserID, 'corp_id' => $corpId], ['id']);
  303. if ($clientInfo) {
  304. $this->transaction(function () use ($clientInfo, $userId) {
  305. $this->dao->destroy($clientInfo->id);
  306. /** @var WorkClientFollowServices $followService */
  307. $followService = app()->make(WorkClientFollowServices::class);
  308. $followService->update(['client_id' => $clientInfo->id, 'userid' => $userId], ['is_del_user' => 1]);
  309. });
  310. }
  311. return true;
  312. }
  313. /**
  314. * 客户删除企业微信成员
  315. * @param array $payload
  316. * @return bool
  317. * @throws \think\db\exception\DataNotFoundException
  318. * @throws \think\db\exception\DbException
  319. * @throws \think\db\exception\ModelNotFoundException
  320. */
  321. public function deleteFollowClient(array $payload)
  322. {
  323. $corpId = $payload['ToUserName'];
  324. $externalUserID = $payload['ExternalUserID'];
  325. $userId = $payload['UserID'];//成员serid
  326. $clientInfo = $this->dao->get(['external_userid' => $externalUserID, 'corp_id' => $corpId], ['id']);
  327. /** @var WorkClientFollowServices $followService */
  328. $followService = app()->make(WorkClientFollowServices::class);
  329. if ($clientInfo) {
  330. $followService->update(['client_id' => $clientInfo->id, 'userid' => $userId], ['is_del_user' => 1]);
  331. }
  332. return true;
  333. }
  334. /**
  335. * 更新或者添加客户信息
  336. * @param string $corpId
  337. * @param string $externalUserID
  338. * @param string $userId
  339. * @return mixed
  340. */
  341. public function saveOrUpdateClient(string $corpId, string $externalUserID, string $userId)
  342. {
  343. $response = Work::getClientInfo($externalUserID);
  344. $externalContact = $response['external_contact'] ?? [];
  345. $followUser = $response['follow_user'] ?? [];
  346. $res = true;
  347. $externalContact['corp_id'] = $corpId;
  348. $externalContact['external_profile'] = json_encode($externalContact['external_profile'] ?? []);
  349. $clientId = $this->dao->value(['external_userid' => $externalContact['external_userid'], 'corp_id' => $corpId], 'id');
  350. try {
  351. $clientId = $this->transaction(function () use ($userId, $res, $clientId, $externalContact, $followUser) {
  352. if ($clientId) {
  353. $this->dao->update($clientId, $externalContact);
  354. } else {
  355. $res = $this->dao->save($externalContact);
  356. $clientId = $res->id;
  357. }
  358. $userids = [];
  359. $res1 = false;
  360. foreach ($followUser as &$item) {
  361. $item['create_time'] = time();
  362. if ($userId === $item['userid']) {
  363. $res1 = true;
  364. }
  365. $userids[] = $item['userid'];
  366. $item['client_id'] = $clientId;
  367. if (isset($item['wechat_channels'])) {
  368. unset($item['wechat_channels']);
  369. }
  370. }
  371. if (!$res1 && $userId) {
  372. $followUser[] = [
  373. 'client_id' => $clientId,
  374. 'userid' => $userId,
  375. 'createtime' => time(),
  376. 'tags' => []
  377. ];
  378. }
  379. //添加了此外部联系人的企业成员
  380. if ($followUser) {
  381. /** @var WorkClientFollowServices $followService */
  382. $followService = app()->make(WorkClientFollowServices::class);
  383. /** @var WorkClientFollowTagsServices $tagService */
  384. $tagService = app()->make(WorkClientFollowTagsServices::class);
  385. foreach ($followUser as $item) {
  386. if (($id = $followService->value(['client_id' => $clientId, 'userid' => $item['userid']], 'id'))) {
  387. $followService->update($id, [
  388. 'remark' => $item['remark'],
  389. 'description' => $item['description'],
  390. 'remark_corp_name' => $item['remark_corp_name'] ?? '',
  391. 'add_way' => $item['add_way'] ?? '',
  392. 'oper_userid' => $item['oper_userid'] ?? '',
  393. ]);
  394. } else {
  395. $res = $followService->save($item);
  396. $id = $res->id;
  397. }
  398. $tagService->delete(['follow_id' => $id]);
  399. if (!empty($item['tags'])) {
  400. $tagsNews = [];
  401. foreach ($item['tags'] as $tag) {
  402. $tag['follow_id'] = $id;
  403. $tagsNews[] = $tag;
  404. }
  405. $tagService->saveAll($tagsNews);
  406. }
  407. }
  408. }
  409. if (!$res) {
  410. throw new ValidateException('保存失败');
  411. }
  412. return $clientId;
  413. });
  414. } catch (\Throwable $e) {
  415. Log::error([
  416. 'message' => $e->getMessage(),
  417. 'file' => $e->getFile(),
  418. 'line' => $e->getLine()
  419. ]);
  420. }
  421. return $clientId;
  422. }
  423. /**
  424. * @param string $userid
  425. * @param array $clientInfo
  426. * @return array
  427. */
  428. public function getClientInfo(string $userid, array $clientInfo)
  429. {
  430. $clientInfo['userInfo'] = [];
  431. if ($clientInfo['uid']) {
  432. /** @var UserServices $make */
  433. $make = app()->make(UserServices::class);
  434. $userInfo = $make->get($clientInfo['uid'], ['*'], ['label', 'userGroup', 'spreadUser']);
  435. if ($userInfo) {
  436. $clientInfo['userInfo'] = $userInfo->toArray();
  437. $clientInfo['userInfo']['birthday'] = $clientInfo['userInfo']['birthday'] ? date('Y-m-d', $clientInfo['userInfo']['birthday']) : '';
  438. }
  439. }
  440. return $clientInfo;
  441. }
  442. /**
  443. * 异步批量设置标签
  444. * @param array $addTag
  445. * @param array $removeTag
  446. * @param array $userId
  447. * @param array $where
  448. * @param int $isAll
  449. * @return bool
  450. */
  451. public function synchBatchLabel(array $addTag, array $removeTag, array $userId, array $where, int $isAll = 0)
  452. {
  453. if ($isAll) {
  454. $clientList = $this->dao->getDataList($where, ['external_userid', 'id', 'unionid', 'uid'], 0, 0, null, ['followOne']);
  455. } else {
  456. $clientList = $this->dao->getDataList(['external_userid' => $userId], ['external_userid', 'id', 'unionid', 'uid'], 0, 0, null, ['followOne']);
  457. }
  458. $batchClient = [];
  459. foreach ($clientList as $item) {
  460. if (!empty($item['followOne'])) {
  461. $batchClient[] = [
  462. 'external_userid' => $item['external_userid'],
  463. 'userid' => $item['followOne']['userid'],
  464. 'add_tag' => $addTag,
  465. 'remove_tag' => $removeTag,
  466. ];
  467. }
  468. }
  469. if ($batchClient) {
  470. foreach ($batchClient as $item) {
  471. WorkClientJob::dispatchDo('setLabel', [$item]);
  472. }
  473. }
  474. return true;
  475. }
  476. /**
  477. * 设置客户标签
  478. * @param array $markTag
  479. * @return WechatResponse|false
  480. */
  481. public function setClientMarkTag(array $markTag)
  482. {
  483. try {
  484. $res = Work::markTags($markTag['userid'], $markTag['external_userid'], $markTag['add_tag'], $markTag['remove_tag']);
  485. $res = new WechatResponse($res);
  486. //同步标签后同步用户信息
  487. /** @var WorkConfig $config */
  488. $config = app()->make(WorkConfig::class);
  489. $corpId = $config->get('corpId');
  490. WorkClientJob::dispatchSece(2, 'saveClientInfo', [$corpId, $markTag['external_userid'], $markTag['userid']]);
  491. return $res;
  492. } catch (\Throwable $e) {
  493. Log::error([
  494. 'message' => '修改客户标签发生错误:' . $e->getMessage(),
  495. 'file' => $e->getFile(),
  496. 'line' => $e->getLine()
  497. ]);
  498. return false;
  499. }
  500. }
  501. /**
  502. * 查找成员下附带的客户人数
  503. * @param array $where
  504. * @return int
  505. */
  506. public function getUserIdsByCount(array $where)
  507. {
  508. if ($where['is_all']) {
  509. unset($where['time'], $where['label'], $where['notLabel']);
  510. }
  511. $where['timeKey'] = 'create_time';
  512. if (!empty($where['label'])) {
  513. /** @var UserLabelServices $service */
  514. $service = app()->make(UserLabelServices::class);
  515. $tagId = $service->getColumn([
  516. ['id', 'in', $where['label']],
  517. ], 'tag_id');
  518. $where['label'] = array_unique($tagId);
  519. }
  520. if (!empty($where['notLabel'])) {
  521. /** @var UserLabelServices $service */
  522. $service = app()->make(UserLabelServices::class);
  523. $tagId = $service->getColumn([
  524. ['id', 'in', $where['notLabel']],
  525. ], 'tag_id');
  526. $where['notLabel'] = array_unique($tagId);
  527. }
  528. return $this->dao->getClientCount($where);
  529. }
  530. /**
  531. * 解绑用户
  532. * @param int $uid
  533. */
  534. public function unboundUser(int $uid)
  535. {
  536. try {
  537. $this->dao->update(['uid' => $uid], ['uid' => 0]);
  538. } catch (\Throwable $e) {
  539. Log::error([
  540. 'message' => '解绑用户失败:' . $e->getMessage(),
  541. 'file' => $e->getFile(),
  542. 'line' => $e->getLine()
  543. ]);
  544. }
  545. }
  546. }