PoolCommand.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  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. protected function execute(Input $input, Output $output)
  100. {
  101. # 动态修改运行时参数
  102. set_time_limit(0);
  103. ini_set('memory_limit', '512M');
  104. $this->init($input, $output);
  105. # 创建定时器任务
  106. $task = new Worker();
  107. $task->name = 'pool';
  108. $task->count = 1;
  109. $task->onWorkerStart = [$this, 'start'];
  110. $task->runAll();
  111. }
  112. /**
  113. * 整理修改客户数据的条件
  114. *
  115. * @param array $customerIds 客户ID
  116. * @return array
  117. * @since 2021-04-01
  118. * @author fanqi
  119. */
  120. private function getCustomerQueryCondition($customerIds)
  121. {
  122. $result = [];
  123. foreach ($customerIds as $k1 => $v1) {
  124. foreach ($v1 as $k2 => $v2) {
  125. $result[] = $v2;
  126. }
  127. }
  128. return array_unique($result);
  129. }
  130. /**
  131. * 客户公海关联数据
  132. *
  133. * @param array $customerIds 客户ID
  134. * @return array
  135. * @since 2021-04-01
  136. * @author fanqi
  137. */
  138. private function getCustomerPoolRelationData($customerIds)
  139. {
  140. $result = [];
  141. # 用于排重
  142. $repeat = [];
  143. foreach ($customerIds as $k1 => $v1) {
  144. $customerArray = array_unique($v1);
  145. foreach ($customerArray as $k2 => $v2) {
  146. if (!empty($repeat[$k1][$v2])) continue;
  147. $result[] = [
  148. 'pool_id' => $k1,
  149. 'customer_id' => $v2
  150. ];
  151. $repeat[$k1][$v2] = $v2;
  152. }
  153. }
  154. return $result;
  155. }
  156. /**
  157. * 获取符合公海条件的客户
  158. *
  159. * @param array $rules 公海规则数据
  160. * @return array
  161. * @since 2021-04-01
  162. * @author fanqi
  163. */
  164. private function getQueryCondition($rules)
  165. {
  166. $result = [];
  167. foreach ($rules as $k => $v) {
  168. if (!isset($result[$v['pool_id']])) $result[$v['pool_id']] = [];
  169. 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']));
  170. if ($v['type'] == 2) $result[$v['pool_id']] = array_merge($result[$v['pool_id']], $this->getBusinessQueryResult($v['level_conf'], $v['level'], $v['deal_handle']));
  171. if ($v['type'] == 3) $result[$v['pool_id']] = array_merge($result[$v['pool_id']], $this->getDealQueryResult($v['level_conf'], $v['level'], $v['business_handle']));
  172. }
  173. return $result;
  174. }
  175. /**
  176. * N天内无新建跟进记录的客户
  177. *
  178. * @param int $type 类型:1 所有用户,不分级别,2 根据用户级别区分
  179. * @param Json $levels 级别数据
  180. * @param int $dealStatus 是否排除成交用户:1 排除,0 不排除
  181. * @param int $businessStatus 是否排除有商机用户:1 排除,0 不排除
  182. * @return array
  183. * @since 2021-04-01
  184. * @author fanqi
  185. */
  186. private function getFollowUpQueryResult($type, $levels, $dealStatus, $businessStatus)
  187. {
  188. # 转换格式
  189. $levels = json_decode($levels, true);
  190. # 默认条件
  191. $where = "`customer`.`owner_user_id` > 0";
  192. # 所有用户,不区分级别
  193. if ($type == 1) {
  194. foreach ($levels as $k1 => $v1) {
  195. if (!empty($v1['limit_day'])) {
  196. $time = time() - 24 * 60 * 60 * $v1['limit_day'];
  197. $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`)))";
  198. }
  199. }
  200. }
  201. # 根据用户级别设置条件
  202. if ($type == 2) {
  203. foreach ($levels as $k1 => $v1) {
  204. if (!empty($v1['level']) && !empty($v1['limit_day'])) {
  205. $time = (time() - 24 * 60 * 60 * $v1['limit_day']);
  206. if ($k1 == 0) {
  207. $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`)))";
  208. } else {
  209. $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`)))";
  210. }
  211. }
  212. }
  213. # 获取最小天数,对于没有设置级别的客户数据使用
  214. $minLimit = $this->getMinDay($levels);
  215. $minTime = (time() - 24 * 60 * 60 * $minLimit);
  216. $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`))) )";
  217. }
  218. # 选择不进入公海的客户(已成交客户)
  219. if (!empty($dealStatus)) $where .= " AND (`customer`.`deal_status` <> '已成交' OR ISNULL(`customer`.`deal_status`))";
  220. # 选择不进入公海的客户(有商机客户)
  221. if (!empty($businessStatus)) $where .= " AND ISNULL(`business`.`customer_id`)";
  222. # 锁定的客户不提醒
  223. $where .= " AND `customer`.`is_lock` = 0";
  224. # 查询符合条件的客户
  225. return db('crm_customer')
  226. ->alias('customer')->join('__CRM_BUSINESS__ business', 'business.customer_id = customer.customer_id', 'LEFT')
  227. ->where($where)->column('customer.customer_id');
  228. }
  229. /**
  230. * N天内无新建商机的客户
  231. *
  232. * @param int $type 类型:1 所有用户,不分级别,2 根据用户级别区分
  233. * @param Json $levels 级别数据
  234. * @param int $dealStatus 是否排除成交用户:1 排除,0 不排除
  235. * @return array|false|string
  236. * @since 2021-04-01
  237. * @author fanqi
  238. */
  239. private function getBusinessQueryResult($type, $levels, $dealStatus)
  240. {
  241. # 转换格式
  242. $levels = json_decode($levels, true);
  243. # 默认条件
  244. $where = "`customer`.`owner_user_id` > 0";
  245. # 所有用户,不区分级别
  246. if ($type == 1) {
  247. foreach ($levels as $k1 => $v1) {
  248. if (!empty($v1['limit_day'])) {
  249. $time = time() - 24 * 60 * 60 * $v1['limit_day'];
  250. $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`) )";
  251. }
  252. }
  253. }
  254. # 根据用户级别设置条件
  255. if ($type == 2) {
  256. foreach ($levels as $k1 => $v1) {
  257. if (!empty($v1['level']) && !empty($v1['limit_day'])) {
  258. $time = time() - 24 * 60 * 60 * $v1['limit_day'];
  259. if ($k1 == 0) {
  260. $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'] . "'))";
  261. } else {
  262. $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'] . "'))";
  263. }
  264. }
  265. }
  266. # 获取最小天数,对于没有设置级别的客户数据使用
  267. $minLimit = $this->getMinDay($levels);
  268. $minTime = (time() - 24 * 60 * 60 * $minLimit);
  269. $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`)) )";
  270. }
  271. # 选择不进入公海的客户(已成交客户)
  272. if (!empty($dealStatus)) $where .= " AND (`customer`.`deal_status` <> '已成交' OR ISNULL(`customer`.`deal_status`))";
  273. # 锁定的客户不提醒
  274. $where .= " AND `customer`.`is_lock` = 0";
  275. # 查询匹配条件的客户
  276. return db('crm_customer')->alias('customer')
  277. ->join('__CRM_BUSINESS__ business', 'business.customer_id = customer.customer_id', 'LEFT')
  278. ->where($where)->column('customer.customer_id');
  279. }
  280. /**
  281. * N天内没有成交的客户
  282. *
  283. * @param int $type 类型:1 所有用户,不分级别,2 根据用户级别区分
  284. * @param Json $levels 级别数据
  285. * @param int $businessStatus 是否排除有商机用户:1 排除,0 不排除
  286. * @return array|false|string
  287. * @since 2021-04-01
  288. * @author fanqi
  289. */
  290. private function getDealQueryResult($type, $levels, $businessStatus)
  291. {
  292. # 转换格式
  293. $levels = json_decode($levels, true);
  294. # 默认条件
  295. $where = "`customer`.`owner_user_id` > 0";
  296. # 所有用户,不区分级别
  297. if ($type == 1) {
  298. foreach ($levels as $k1 => $v1) {
  299. if (!empty($v1['limit_day'])) {
  300. $time = time() - 24 * 60 * 60 * $v1['limit_day'];
  301. $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`) )";
  302. }
  303. }
  304. }
  305. # 根据用户级别设置条件
  306. if ($type == 2) {
  307. foreach ($levels as $k1 => $v1) {
  308. if (!empty($v1['level']) && !empty($v1['limit_day'])) {
  309. $time = time() - 24 * 60 * 60 * $v1['limit_day'];
  310. if ($k1 == 0) {
  311. $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'] . "'))";
  312. } else {
  313. $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'] . "'))";
  314. }
  315. }
  316. }
  317. # 获取最小天数,对于没有设置级别的客户数据使用
  318. $minLimit = $this->getMinDay($levels);
  319. $minTime = (time() - 24 * 60 * 60 * $minLimit);
  320. $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`)) )";
  321. }
  322. # 选择不进入公海的客户(有商机客户)
  323. if (!empty($businessStatus)) $where .= " AND ISNULL(`business`.`customer_id`)";
  324. # 锁定的客户不提醒
  325. $where .= " AND `customer`.`is_lock` = 0";
  326. # 查询符合条件的客户
  327. return db('crm_customer')->alias('customer')
  328. ->join('__CRM_BUSINESS__ business', 'business.customer_id = customer.customer_id', 'LEFT')
  329. ->join('__CRM_CONTRACT__ contract', 'contract.customer_id = customer.customer_id', 'LEFT')
  330. ->where($where)->column('customer.customer_id');
  331. }
  332. /**
  333. * 获取公海规则最小数字(最快进入公海天数)
  334. *
  335. * @param $data
  336. * @return int
  337. * @since 2021-04-19
  338. * @author fanqi
  339. */
  340. private function getMinDay($data)
  341. {
  342. $number = 1;
  343. foreach ($data as $k1 => $v1) {
  344. if (empty($number) || $v1['limit_day'] < $number) $number = $v1['limit_day'];
  345. }
  346. return $number;
  347. }
  348. }