PoolCommand.php 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. <?php
  2. /**
  3. * 使用定时器将符合条件的用户回收到公海池
  4. *
  5. * @author fanqi
  6. * @since 2021-03-31
  7. */
  8. namespace app\common\command;
  9. use think\Config;
  10. use think\console\Command;
  11. use think\console\Input;
  12. use think\console\input\Argument;
  13. use think\console\input\Option;
  14. use think\console\Output;
  15. use think\Db;
  16. use think\response\Json;
  17. use Workerman\Lib\Timer;
  18. use Workerman\Worker;
  19. class PoolCommand extends Command
  20. {
  21. protected $timer;
  22. protected $interval = 10;
  23. protected function configure()
  24. {
  25. $this->setName('pool')
  26. ->addArgument('status', Argument::REQUIRED, 'start/stop/reload/status/connections')
  27. ->addOption('d', null, Option::VALUE_NONE, 'daemon(守护进程)方式启动')
  28. ->setDescription('公海回收定时器');
  29. // 读取数据库配置文件
  30. $filename = ROOT_PATH . 'config' . DS . 'database.php';
  31. // 重新加载数据库配置文件
  32. Config::load($filename, 'database');
  33. }
  34. /**
  35. * 初始化
  36. *
  37. * @param Input $input
  38. * @param Output $output
  39. */
  40. protected function init(Input $input, Output $output)
  41. {
  42. global $argv;
  43. $argv[1] = $input->getArgument('status') ?: 'start';
  44. if ($input->hasOption('d')) {
  45. $argv[2] = '-d';
  46. } else {
  47. unset($argv[2]);
  48. }
  49. }
  50. /**
  51. * 停止定时器
  52. */
  53. public function stop()
  54. {
  55. Timer::del($this->timer);
  56. }
  57. /**
  58. * 启动定时器
  59. */
  60. public function start()
  61. {
  62. $this->timer = Timer::add($this->interval, function () {
  63. # 只在凌晨12点至6点间执行
  64. if ((int)date('H') >= 0 && (int)date('H') < 6) {
  65. # 公海规则
  66. $ruleList = db('crm_customer_pool_rule')->alias('rule')->field('rule.*')
  67. ->join('__CRM_CUSTOMER_POOL__ pool', 'pool.pool_id = rule.pool_id', 'LEFT')->where('pool.status', 1)->select();
  68. if (!empty($ruleList)) {
  69. # 符合公海条件的客户IDS
  70. $customerIds = $this->getQueryCondition($ruleList);
  71. # 整理客户公海关联数据
  72. $poolRelationData = $this->getCustomerPoolRelationData($customerIds);
  73. # 整理修改客户数据的条件(进入公海时间,前负责人...)
  74. $customerWhere = $this->getCustomerQueryCondition($customerIds);
  75. Db::startTrans();
  76. try {
  77. # 将客户退回公海
  78. if (!empty($poolRelationData)) Db::name('crm_customer_pool_relation')->insertAll($poolRelationData);
  79. # 修改客户数据
  80. if (!empty($customerWhere)) {
  81. Db::name('crm_customer')->whereIn('customer_id', $customerWhere)->exp('before_owner_user_id', 'owner_user_id')->update([
  82. 'ro_user_id' => '',
  83. 'rw_user_id' => '',
  84. 'owner_user_id' => 0,
  85. 'into_pool_time' => time()
  86. ]);
  87. }
  88. $this->updateInfo($ruleList,$customerWhere);
  89. # 删除联系人的负责人
  90. Db::name('crm_contacts')->whereIn('customer_id', $customerWhere)->update(['owner_user_id' => '']);
  91. Db::commit();
  92. } catch (\Exception $e) {
  93. Db::rollback();
  94. }
  95. }
  96. }
  97. });
  98. }
  99. /**
  100. * 自动入公海操作记录
  101. * @param $ruleList
  102. * @param $customerWhere
  103. *
  104. * @author alvin guogaobo
  105. * @version 1.0 版本号
  106. * @since 2021/6/3 0003 10:38
  107. */
  108. private function updateInfo($ruleList, $customerWhere)
  109. {
  110. foreach ($ruleList as $k => $v) {
  111. $levels = json_decode($v['level'], true);
  112. foreach ($levels as $k1 => $v1) {
  113. if (!empty($v1['limit_day'])) {
  114. $time = $v1['limit_day'];
  115. } else {
  116. $time = $this->getMinDay($levels);
  117. }
  118. }
  119. foreach ($customerWhere as $val) {
  120. if ($v['type'] == 1) updateActionLog(0, 'crm_customer', $val, '', '', '超过' . $time . '天无新建跟进记录自动进入公海');
  121. if ($v['type'] == 2) updateActionLog(0, 'crm_customer', $val, '', '', '超过' . $time . '天无新建商机自动进入公海');
  122. if ($v['type'] == 3) updateActionLog(0, 'crm_customer', $val, '', '', '超过' . $time . '天未成交自动进入公海');
  123. }
  124. }
  125. }
  126. protected function execute(Input $input, Output $output)
  127. {
  128. # 动态修改运行时参数
  129. set_time_limit(0);
  130. ini_set('memory_limit', '512M');
  131. $this->init($input, $output);
  132. # 创建定时器任务
  133. $task = new Worker();
  134. $task->name = 'pool';
  135. $task->count = 1;
  136. $task->onWorkerStart = [$this, 'start'];
  137. $task->runAll();
  138. }
  139. /**
  140. * 整理修改客户数据的条件
  141. *
  142. * @param array $customerIds 客户ID
  143. * @return array
  144. * @since 2021-04-01
  145. * @author fanqi
  146. */
  147. private function getCustomerQueryCondition($customerIds)
  148. {
  149. $result = [];
  150. foreach ($customerIds as $k1 => $v1) {
  151. foreach ($v1 as $k2 => $v2) {
  152. $result[] = $v2;
  153. }
  154. }
  155. return array_unique($result);
  156. }
  157. /**
  158. * 客户公海关联数据
  159. *
  160. * @param array $customerIds 客户ID
  161. * @return array
  162. * @since 2021-04-01
  163. * @author fanqi
  164. */
  165. private function getCustomerPoolRelationData($customerIds)
  166. {
  167. $result = [];
  168. # 用于排重
  169. $repeat = [];
  170. foreach ($customerIds as $k1 => $v1) {
  171. $customerArray = array_unique($v1);
  172. foreach ($customerArray as $k2 => $v2) {
  173. if (!empty($repeat[$k1][$v2])) continue;
  174. $result[] = [
  175. 'pool_id' => $k1,
  176. 'customer_id' => $v2
  177. ];
  178. $repeat[$k1][$v2] = $v2;
  179. }
  180. }
  181. return $result;
  182. }
  183. /**
  184. * 获取符合公海条件的客户
  185. *
  186. * @param array $rules 公海规则数据
  187. * @return array
  188. * @since 2021-04-01
  189. * @author fanqi
  190. */
  191. private function getQueryCondition($rules)
  192. {
  193. $result = [];
  194. foreach ($rules as $k => $v) {
  195. if (!isset($result[$v['pool_id']])) $result[$v['pool_id']] = [];
  196. if ($v['type'] == 1) $result[$v['pool_id']] = array_merge($result[$v['pool_id']], $this->getFollowUpQueryResult($v['level_conf'], $v['level'], $v['deal_handle'], $v['business_handle']));
  197. if ($v['type'] == 2) $result[$v['pool_id']] = array_merge($result[$v['pool_id']], $this->getBusinessQueryResult($v['level_conf'], $v['level'], $v['deal_handle']));
  198. if ($v['type'] == 3) $result[$v['pool_id']] = array_merge($result[$v['pool_id']], $this->getDealQueryResult($v['level_conf'], $v['level'], $v['business_handle']));
  199. }
  200. return $result;
  201. }
  202. /**
  203. * N天内无新建跟进记录的客户
  204. *
  205. * @param int $type 类型:1 所有用户,不分级别,2 根据用户级别区分
  206. * @param Json $levels 级别数据
  207. * @param int $dealStatus 是否排除成交用户:1 排除,0 不排除
  208. * @param int $businessStatus 是否排除有商机用户:1 排除,0 不排除
  209. * @return array
  210. * @since 2021-04-01
  211. * @author fanqi
  212. */
  213. private function getFollowUpQueryResult($type, $levels, $dealStatus, $businessStatus)
  214. {
  215. # 转换格式
  216. $levels = json_decode($levels, true);
  217. # 默认条件
  218. $where = "`customer`.`owner_user_id` > 0";
  219. # 所有用户,不区分级别
  220. if ($type == 1) {
  221. foreach ($levels as $k1 => $v1) {
  222. if (!empty($v1['limit_day'])) {
  223. $time = time() - 24 * 60 * 60 * $v1['limit_day'];
  224. $where .= " AND ((`customer`.`last_time` < " . $time . " AND `customer`.`last_time` > `customer`.`obtain_time`) OR (`customer`.`obtain_time` < " . $time . " AND `customer`.`obtain_time` > `customer`.`last_time`) OR (`customer`.`obtain_time` < " . $time . " AND ISNULL(`customer`.`last_time`)))";
  225. }
  226. }
  227. }
  228. # 根据用户级别设置条件
  229. if ($type == 2) {
  230. foreach ($levels as $k1 => $v1) {
  231. if (!empty($v1['level']) && !empty($v1['limit_day'])) {
  232. $time = (time() - 24 * 60 * 60 * $v1['limit_day']);
  233. if ($k1 == 0) {
  234. $where .= " AND ( ((`customer`.`level` = '" . $v1['level'] . "' AND `customer`.`last_time` < " . $time . " AND `customer`.`last_time` > `customer`.`obtain_time`) OR (`customer`.`level` = '" . $v1['level'] . "' AND `customer`.`obtain_time` < " . $time . " AND `customer`.`obtain_time` > `customer`.`last_time`) OR (`customer`.`level` = '" . $v1['level'] . "' AND `customer`.`obtain_time` < " . $time . " AND ISNULL(`customer`.`last_time`)))";
  235. } else {
  236. $where .= " OR ((`customer`.`level` = '" . $v1['level'] . "' AND `customer`.`last_time` < " . $time . " AND `customer`.`last_time` > `customer`.`obtain_time`) OR (`customer`.`level` = '" . $v1['level'] . "' AND `customer`.`obtain_time` < " . $time . " AND `customer`.`obtain_time` > `customer`.`last_time`) OR (`customer`.`level` = '" . $v1['level'] . "' AND `customer`.`obtain_time` < " . $time . " AND ISNULL(`customer`.`last_time`)))";
  237. }
  238. }
  239. }
  240. # 获取最小天数,对于没有设置级别的客户数据使用
  241. $minLimit = $this->getMinDay($levels);
  242. $minTime = (time() - 24 * 60 * 60 * $minLimit);
  243. $where .= " OR ((!`customer`.`level` AND `customer`.`last_time` < " . $minTime . " AND `customer`.`last_time` > `customer`.`obtain_time`) OR (!`customer`.`level` AND `customer`.`obtain_time` < " . $minTime . " AND `customer`.`obtain_time` > `customer`.`last_time`) OR (!`customer`.`level` AND `customer`.`obtain_time` < " . $minTime . " AND ISNULL(`customer`.`last_time`))) )";
  244. }
  245. # 选择不进入公海的客户(已成交客户)
  246. if (!empty($dealStatus)) $where .= " AND (`customer`.`deal_status` <> '已成交' OR ISNULL(`customer`.`deal_status`))";
  247. # 选择不进入公海的客户(有商机客户)
  248. if (!empty($businessStatus)) $where .= " AND ISNULL(`business`.`customer_id`)";
  249. # 锁定的客户不提醒
  250. $where .= " AND `customer`.`is_lock` = 0";
  251. # 查询符合条件的客户
  252. return db('crm_customer')
  253. ->alias('customer')->join('__CRM_BUSINESS__ business', 'business.customer_id = customer.customer_id', 'LEFT')
  254. ->where($where)->column('customer.customer_id');
  255. }
  256. /**
  257. * N天内无新建商机的客户
  258. *
  259. * @param int $type 类型:1 所有用户,不分级别,2 根据用户级别区分
  260. * @param Json $levels 级别数据
  261. * @param int $dealStatus 是否排除成交用户:1 排除,0 不排除
  262. * @return array|false|string
  263. * @since 2021-04-01
  264. * @author fanqi
  265. */
  266. private function getBusinessQueryResult($type, $levels, $dealStatus)
  267. {
  268. # 转换格式
  269. $levels = json_decode($levels, true);
  270. # 默认条件
  271. $where = "`customer`.`owner_user_id` > 0";
  272. # 所有用户,不区分级别
  273. if ($type == 1) {
  274. foreach ($levels as $k1 => $v1) {
  275. if (!empty($v1['limit_day'])) {
  276. $time = time() - 24 * 60 * 60 * $v1['limit_day'];
  277. $where .= " AND ( (ISNULL(`business`.`customer_id`) AND `customer`.`obtain_time` < " . $time . ") OR (`customer`.`obtain_time` < " . $time . " AND `customer`.`obtain_time` > `business`.`create_time`) OR (`business`.`create_time` < " . $time . " AND `business`.`create_time` > `customer`.`obtain_time`) )";
  278. }
  279. }
  280. }
  281. # 根据用户级别设置条件
  282. if ($type == 2) {
  283. foreach ($levels as $k1 => $v1) {
  284. if (!empty($v1['level']) && !empty($v1['limit_day'])) {
  285. $time = time() - 24 * 60 * 60 * $v1['limit_day'];
  286. if ($k1 == 0) {
  287. $where .= " AND ( ((ISNULL(`business`.`customer_id`) AND `customer`.`obtain_time` < " . $time . " AND `customer`.`level` = '" . $v1['level'] . "') OR (`customer`.`obtain_time` < " . $time . " AND `customer`.`obtain_time` > `business`.`create_time` AND `customer`.`level` = '" . $v1['level'] . "') OR (`business`.`create_time` < " . $time . " AND `business`.`create_time` > `customer`.`obtain_time` AND `customer`.`level` = '" . $v1['level'] . "'))";
  288. } else {
  289. $where .= " OR ((ISNULL(`business`.`customer_id`) AND `customer`.`obtain_time` < " . $time . " AND `customer`.`level` = '" . $v1['level'] . "') OR (`customer`.`obtain_time` < " . $time . " AND `customer`.`obtain_time` > `business`.`create_time` AND `customer`.`level` = '" . $v1['level'] . "') OR (`business`.`create_time` < " . $time . " AND `business`.`create_time` > `customer`.`obtain_time` AND `customer`.`level` = '" . $v1['level'] . "'))";
  290. }
  291. }
  292. }
  293. # 获取最小天数,对于没有设置级别的客户数据使用
  294. $minLimit = $this->getMinDay($levels);
  295. $minTime = (time() - 24 * 60 * 60 * $minLimit);
  296. $where .= " OR ((ISNULL(`business`.`customer_id`) AND `customer`.`obtain_time` < " . $minTime . " AND !`customer`.`level`) OR (`customer`.`obtain_time` < " . $minTime . " AND `customer`.`obtain_time` > `business`.`create_time` AND !`customer`.`level`) OR (`business`.`create_time` < " . $minTime . " AND `business`.`create_time` > `customer`.`obtain_time` AND !`customer`.`level`)) )";
  297. }
  298. # 选择不进入公海的客户(已成交客户)
  299. if (!empty($dealStatus)) $where .= " AND (`customer`.`deal_status` <> '已成交' OR ISNULL(`customer`.`deal_status`))";
  300. # 锁定的客户不提醒
  301. $where .= " AND `customer`.`is_lock` = 0";
  302. # 查询匹配条件的客户
  303. return db('crm_customer')->alias('customer')
  304. ->join('__CRM_BUSINESS__ business', 'business.customer_id = customer.customer_id', 'LEFT')
  305. ->where($where)->column('customer.customer_id');
  306. }
  307. /**
  308. * N天内没有成交的客户
  309. *
  310. * @param int $type 类型:1 所有用户,不分级别,2 根据用户级别区分
  311. * @param Json $levels 级别数据
  312. * @param int $businessStatus 是否排除有商机用户:1 排除,0 不排除
  313. * @return array|false|string
  314. * @since 2021-04-01
  315. * @author fanqi
  316. */
  317. private function getDealQueryResult($type, $levels, $businessStatus)
  318. {
  319. # 转换格式
  320. $levels = json_decode($levels, true);
  321. # 默认条件
  322. $where = "`customer`.`owner_user_id` > 0";
  323. # 所有用户,不区分级别
  324. if ($type == 1) {
  325. foreach ($levels as $k1 => $v1) {
  326. if (!empty($v1['limit_day'])) {
  327. $time = time() - 24 * 60 * 60 * $v1['limit_day'];
  328. $where .= " AND ( (ISNULL(`contract`.`customer_id`) AND `customer`.`obtain_time` < " . $time . ") OR (`customer`.`obtain_time` < " . $time . " AND `customer`.`obtain_time` > `contract`.`create_time`) OR (`contract`.`create_time` < " . $time . " AND `contract`.`create_time` > `customer`.`obtain_time`) )";
  329. }
  330. }
  331. }
  332. # 根据用户级别设置条件
  333. if ($type == 2) {
  334. foreach ($levels as $k1 => $v1) {
  335. if (!empty($v1['level']) && !empty($v1['limit_day'])) {
  336. $time = time() - 24 * 60 * 60 * $v1['limit_day'];
  337. if ($k1 == 0) {
  338. $where .= " AND ( ((ISNULL(`contract`.`customer_id`) AND `customer`.`obtain_time` < " . $time . " AND `customer`.`level` = '" . $v1['level'] . "') OR (`customer`.`obtain_time` < " . $time . " AND `customer`.`obtain_time` > `contract`.`create_time` AND `customer`.`level` = '" . $v1['level'] . "') OR (`contract`.`create_time` < " . $time . " AND `contract`.`create_time` > `customer`.`obtain_time` AND `customer`.`level` = '" . $v1['level'] . "'))";
  339. } else {
  340. $where .= " OR ((ISNULL(`contract`.`customer_id`) AND `customer`.`obtain_time` < " . $time . " AND `customer`.`level` = '" . $v1['level'] . "') OR (`customer`.`obtain_time` < " . $time . " AND `customer`.`obtain_time` > `contract`.`create_time` AND `customer`.`level` = '" . $v1['level'] . "') OR (`contract`.`create_time` < " . $time . " AND `contract`.`create_time` > `customer`.`obtain_time` AND `customer`.`level` = '" . $v1['level'] . "'))";
  341. }
  342. }
  343. }
  344. # 获取最小天数,对于没有设置级别的客户数据使用
  345. $minLimit = $this->getMinDay($levels);
  346. $minTime = (time() - 24 * 60 * 60 * $minLimit);
  347. $where .= " OR ((ISNULL(`contract`.`customer_id`) AND `customer`.`obtain_time` < " . $minTime . " AND !`customer`.`level`) OR (`customer`.`obtain_time` < " . $minTime . " AND `customer`.`obtain_time` > `contract`.`create_time` AND !`customer`.`level`) OR (`contract`.`create_time` < " . $minTime . " AND `contract`.`create_time` > `customer`.`obtain_time` AND !`customer`.`level`)) )";
  348. }
  349. # 选择不进入公海的客户(有商机客户)
  350. if (!empty($businessStatus)) $where .= " AND ISNULL(`business`.`customer_id`)";
  351. # 锁定的客户不提醒
  352. $where .= " AND `customer`.`is_lock` = 0";
  353. # 查询符合条件的客户
  354. return db('crm_customer')->alias('customer')
  355. ->join('__CRM_BUSINESS__ business', 'business.customer_id = customer.customer_id', 'LEFT')
  356. ->join('__CRM_CONTRACT__ contract', 'contract.customer_id = customer.customer_id', 'LEFT')
  357. ->where($where)->column('customer.customer_id');
  358. }
  359. /**
  360. * 获取公海规则最小数字(最快进入公海天数)
  361. *
  362. * @param $data
  363. * @return int
  364. * @since 2021-04-19
  365. * @author fanqi
  366. */
  367. private function getMinDay($data)
  368. {
  369. $number = 1;
  370. foreach ($data as $k1 => $v1) {
  371. if (empty($number) || $v1['limit_day'] < $number) $number = $v1['limit_day'];
  372. }
  373. return $number;
  374. }
  375. }