CouponService.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. <?php
  2. namespace addons\TinyShop\services\marketing;
  3. use Yii;
  4. use yii\db\ActiveRecord;
  5. use yii\db\Exception;
  6. use yii\web\NotFoundHttpException;
  7. use yii\web\UnprocessableEntityHttpException;
  8. use common\enums\StatusEnum;
  9. use common\helpers\ArrayHelper;
  10. use common\helpers\StringHelper;
  11. use common\enums\UseStateEnum;
  12. use common\components\Service;
  13. use common\helpers\EchantsHelper;
  14. use common\helpers\BcHelper;
  15. use addons\TinyShop\common\models\marketing\Coupon;
  16. use addons\TinyShop\common\enums\RangeTypeEnum;
  17. use addons\TinyShop\merchant\modules\marketing\forms\CouponTypeForm;
  18. use addons\TinyShop\common\enums\SubscriptionActionEnum;
  19. use addons\TinyShop\common\models\marketing\CouponType;
  20. use addons\TinyShop\common\enums\CouponGetTypeEnum;
  21. use addons\TinyShop\common\enums\DiscountTypeEnum;
  22. use addons\TinyShop\common\enums\MarketingEnum;
  23. /**
  24. * Class CouponService
  25. * @package addons\TinyShop\services\marketing
  26. */
  27. class CouponService extends Service
  28. {
  29. /**
  30. * @return array|ActiveRecord[]
  31. */
  32. public function findStateCount($member_id = '')
  33. {
  34. $list = [
  35. 'get' => 0, // 已领取
  36. 'un_sed' => 0, // 已使用
  37. 'past_due' => 0 // 已过期
  38. ];
  39. $data = Coupon::find()
  40. ->select(['state', 'count(id) as count'])
  41. ->where(['member_id' => $member_id, 'status' => StatusEnum::ENABLED])
  42. ->andFilterWhere(['in', 'state', [UseStateEnum::GET, UseStateEnum::USE, UseStateEnum::PAST_DUE]])
  43. ->andFilterWhere(['merchant_id' => $this->getMerchantId()])
  44. ->groupBy('state')
  45. ->asArray()
  46. ->all();
  47. $data = ArrayHelper::map($data, 'state', 'count');
  48. isset($data[UseStateEnum::GET]) && $list['get'] = $data[UseStateEnum::GET];
  49. isset($data[UseStateEnum::USE]) && $list['un_sed'] = $data[UseStateEnum::USE];
  50. isset($data[UseStateEnum::PAST_DUE]) && $list['past_due'] = $data[UseStateEnum::PAST_DUE];
  51. return $list;
  52. }
  53. /**
  54. * @return array|\yii\db\ActiveRecord[]
  55. */
  56. public function findCountByState($member_id = '')
  57. {
  58. $default = [
  59. [
  60. 'state' => 0,
  61. 'count' => 0,
  62. ],
  63. [
  64. 'state' => 1,
  65. 'count' => 0,
  66. ],
  67. [
  68. 'state' => 2,
  69. 'count' => 0,
  70. ],
  71. [
  72. 'state' => 3,
  73. 'count' => 0,
  74. ],
  75. ];
  76. $data = Coupon::find()
  77. ->select(['state', 'count(id) as count'])
  78. ->andFilterWhere(['merchant_id' => $this->getMerchantId()])
  79. ->andFilterWhere(['member_id' => $member_id])
  80. ->groupBy('state')
  81. ->asArray()
  82. ->all();
  83. !isset($data[0]) && $data[0] = $default[0];
  84. !isset($data[1]) && $data[1] = $default[1];
  85. !isset($data[2]) && $data[2] = $default[2];
  86. !isset($data[3]) && $data[3] = $default[3];
  87. return $data;
  88. }
  89. /**
  90. * @param $type
  91. * @param $state
  92. * @return array
  93. */
  94. public function getBetweenCountStatToEchant($type, $state)
  95. {
  96. $fields = [
  97. 'count' => '数量',
  98. ];
  99. $fieldMap = [
  100. UseStateEnum::GET => 'fetch_time',
  101. UseStateEnum::USE => 'use_time',
  102. UseStateEnum::PAST_DUE => 'end_time',
  103. ];
  104. $condition = [];
  105. $field = $fieldMap[$state];
  106. $state == UseStateEnum::PAST_DUE && $condition = ['state' => UseStateEnum::PAST_DUE];
  107. // 获取时间和格式化
  108. list($time, $format) = EchantsHelper::getFormatTime($type);
  109. // 获取数据
  110. return EchantsHelper::lineOrBarInTime(function ($start_time, $end_time, $formatting) use (
  111. $state,
  112. $field,
  113. $condition
  114. ) {
  115. return Coupon::find()
  116. ->select([
  117. 'count(id) as count',
  118. "from_unixtime(".$field.", '$formatting') as time",
  119. ])
  120. ->andWhere(['between', $field, $start_time, $end_time])
  121. ->andFilterWhere($condition)
  122. ->andFilterWhere(['merchant_id' => $this->getMerchantId()])
  123. ->groupBy(['time'])
  124. ->asArray()
  125. ->all();
  126. }, $fields, $time, $format);
  127. }
  128. /**
  129. * 查看优惠券
  130. *
  131. * @param $member_id
  132. * @return array|\yii\db\ActiveRecord[]
  133. */
  134. public function getReadByMemberId($member_id)
  135. {
  136. $data = Coupon::find()
  137. ->where([
  138. 'member_id' => $member_id,
  139. 'state' => UseStateEnum::GET,
  140. 'status' => StatusEnum::ENABLED,
  141. 'is_read' => StatusEnum::DISABLED,
  142. ])
  143. ->andWhere(['between', 'start_time', 'end_time', time()])
  144. ->andWhere(['in', 'get_type', CouponGetTypeEnum::getShowMap()])
  145. ->andFilterWhere(['merchant_id' => $this->getMerchantId()])
  146. ->asArray()
  147. ->all();
  148. if (empty($data)) {
  149. return [];
  150. }
  151. $ids = ArrayHelper::getColumn($data, 'id');
  152. Coupon::updateAll(['is_read' => StatusEnum::ENABLED], ['in', 'id', $ids]);
  153. return [
  154. 'total' => count($data),
  155. 'list' => array_slice($data, 0, 10),
  156. ];
  157. }
  158. /**
  159. * 创建优惠券
  160. *
  161. * @param CouponTypeForm $couponType
  162. * @param $count
  163. * @throws Exception
  164. */
  165. public function create(CouponTypeForm $couponType, $count)
  166. {
  167. $codes = StringHelper::randomList($count);
  168. $rows = [];
  169. foreach ($codes as $code) {
  170. $rows[] = [
  171. 'coupon_type_id' => $couponType->id,
  172. 'merchant_id' => $couponType->merchant_id,
  173. 'code' => $code,
  174. ];
  175. }
  176. $field = ['coupon_type_id', 'merchant_id', 'code'];
  177. !empty($rows) && Yii::$app->db->createCommand()->batchInsert(Coupon::tableName(), $field, $rows)->execute();
  178. }
  179. /**
  180. * 赠送从已有优惠券中领取
  181. *
  182. * @param CouponType $couponType
  183. * @param $member_id
  184. * @param int $get_type
  185. * @param string $coupon
  186. * @return Coupon
  187. * @throws NotFoundHttpException
  188. * @throws UnprocessableEntityHttpException
  189. */
  190. public function give(CouponType $couponType, $member_id, $get_type = CouponGetTypeEnum::ONESELF, $coupon = '')
  191. {
  192. /** @var Coupon $model */
  193. if (!empty($coupon)) {
  194. $model = $coupon;
  195. } else {
  196. $model = Coupon::find()
  197. ->where(['coupon_type_id' => $couponType->id, 'state' => UseStateEnum::UNCLAIMED])
  198. ->andFilterWhere(['merchant_id' => $this->getMerchantId()])
  199. ->one();
  200. }
  201. if (!$model) {
  202. throw new NotFoundHttpException('优惠券已被领取完');
  203. }
  204. $model->member_id = $member_id;
  205. $model->state = UseStateEnum::GET;
  206. $model->get_type = $get_type;
  207. $model->title = $couponType->title;
  208. $model->discount_type = $couponType->discount_type;
  209. $model->discount = $couponType->discount;
  210. $model->at_least = $couponType->at_least;
  211. $model->single_type = $couponType->single_type;
  212. $model->fetch_time = time();
  213. // 领到券当日开始 N 天内有效
  214. if ($couponType->term_of_validity_type == StatusEnum::ENABLED) {
  215. $model->start_time = time();
  216. $model->end_time = time() + $couponType->fixed_term * 60 * 60 * 24;
  217. } else {
  218. $model->start_time = $couponType->start_time;
  219. $model->end_time = $couponType->end_time;
  220. }
  221. if (!$model->save()) {
  222. throw new UnprocessableEntityHttpException($this->getError($model));
  223. }
  224. Yii::$app->tinyShopService->marketingCouponType->deductionRepertory($couponType);
  225. // 赠送优惠券提醒
  226. Yii::$app->tinyShopService->notify->createRemindByReceiver(
  227. $model->id,
  228. SubscriptionActionEnum::COUPON_GIVE,
  229. $member_id,
  230. [
  231. 'couponType' => ArrayHelper::merge(
  232. ArrayHelper::toArray($couponType),
  233. ['get_type' => $get_type]
  234. ),
  235. ]
  236. );
  237. return $model;
  238. }
  239. /**
  240. * 赠送新的优惠券
  241. *
  242. * @param CouponType $couponType
  243. * @param $member_id
  244. * @param $map_id
  245. * @param $get_type
  246. * @param $number
  247. * @return Coupon
  248. * @throws UnprocessableEntityHttpException
  249. */
  250. public function giveByNewRecord(
  251. CouponType $couponType,
  252. $member_id,
  253. $map_id = 0,
  254. $get_type = CouponGetTypeEnum::ONESELF,
  255. $number = 1
  256. ) {
  257. $model = new Coupon();
  258. $model = $model->loadDefaultValues();
  259. $model->map_id = $map_id;
  260. $model->member_id = $member_id;
  261. $model->coupon_type_id = $couponType->id;
  262. $model->merchant_id = $couponType->merchant_id;
  263. $model->state = UseStateEnum::GET;
  264. $model->get_type = $get_type;
  265. $model->title = $couponType['title'];
  266. $model->discount_type = $couponType['discount_type'];
  267. $model->discount = $couponType['discount'];
  268. $model->at_least = $couponType['at_least'];
  269. $model->single_type = $couponType['single_type'];
  270. $model->fetch_time = time();
  271. // 领到券当日开始N天内有效
  272. if ($couponType->term_of_validity_type == StatusEnum::ENABLED) {
  273. $model->start_time = time();
  274. $model->end_time = time() + $couponType->fixed_term * 60 * 60 * 24;
  275. } else {
  276. $model->start_time = $couponType->start_time;
  277. $model->end_time = $couponType->end_time;
  278. }
  279. if (!$model->save()) {
  280. throw new UnprocessableEntityHttpException($this->getError($model));
  281. }
  282. // 增加发送数量
  283. CouponType::updateAllCounters(['count' => 1], ['id' => $couponType->id]);
  284. // 赠送优惠券提醒
  285. if (!in_array($get_type, [CouponGetTypeEnum::ONESELF]) ) {
  286. Yii::$app->tinyShopService->notify->createRemindByReceiver(
  287. $model->id,
  288. SubscriptionActionEnum::COUPON_GIVE,
  289. $member_id,
  290. ['couponType' => ArrayHelper::merge(ArrayHelper::toArray($couponType), ['get_type' => $get_type])]
  291. );
  292. }
  293. if ($number > 1) {
  294. $number--;
  295. return $this->giveByNewRecord($couponType, $member_id, $map_id, $get_type, $number);
  296. }
  297. return $model;
  298. }
  299. /**
  300. * 获取用户所有可用优惠券
  301. *
  302. * @param int $member_id
  303. * @param int $merchant_id
  304. * @param array $orderProducts
  305. * @return array
  306. */
  307. public function getUsableByMemberId(int $member_id, int $merchant_id, array $groupOrderProducts)
  308. {
  309. $models = Coupon::find()
  310. ->where([
  311. 'and',
  312. ['member_id' => $member_id],
  313. ['state' => UseStateEnum::GET],
  314. ['status' => StatusEnum::ENABLED],
  315. ['<', 'start_time', time()],
  316. ['>', 'end_time', time()],
  317. ])
  318. ->andWhere(['merchant_id' => $merchant_id])
  319. ->with(['couponType', 'product', 'cate'])
  320. ->limit(100)
  321. ->asArray()
  322. ->all();
  323. $coupons = [];
  324. foreach ($models as $model) {
  325. if ($coupon = $this->getPredictDiscountByCoupon($model, $groupOrderProducts)) {
  326. $coupons[] = $coupon;
  327. }
  328. }
  329. return $coupons;
  330. }
  331. /**
  332. * @param $coupon
  333. * @param $groupOrderProducts
  334. * @return mixed
  335. */
  336. public function getPredictDiscountByCoupon($coupon, $groupOrderProducts)
  337. {
  338. // 参与的商品ID
  339. $productIds = [];
  340. // 商品总金额
  341. $totalMoney = 0;
  342. $maxMoney = 0;
  343. $maxProductId = 0;
  344. $rangeType = $coupon['couponType']['range_type'];
  345. // 可用商品ID和不可用
  346. $usableProductIds = $disabledProductIds = [];
  347. foreach ($coupon['product'] as $item) {
  348. if ($item['marketing_type'] == MarketingEnum::COUPON_IN) {
  349. $usableProductIds[] = $item['product_id'];
  350. } else {
  351. $disabledProductIds[] = $item['product_id'];
  352. }
  353. }
  354. // 可用分类ID和不可用
  355. $usableCateIds = $disabledCateIds = [];
  356. foreach ($coupon['cate'] as $item) {
  357. if ($item['marketing_type'] == MarketingEnum::COUPON_IN) {
  358. $usableCateIds[] = $item['cate_id'];
  359. } else {
  360. $disabledCateIds[] = $item['cate_id'];
  361. }
  362. }
  363. switch ($rangeType) {
  364. // 全部商品参加
  365. case RangeTypeEnum::ALL;
  366. $productIds = array_keys($groupOrderProducts);
  367. break;
  368. // 指定商品参加
  369. case RangeTypeEnum::ASSIGN_PRODUCT;
  370. foreach ($usableProductIds as $usableProductId) {
  371. if (isset($groupOrderProducts[$usableProductId])) {
  372. $productIds[] = $usableProductId;
  373. }
  374. }
  375. break;
  376. // 指定商品不参加
  377. case RangeTypeEnum::NOT_ASSIGN_PRODUCT;
  378. foreach ($groupOrderProducts as $productId => $groupOrderProduct) {
  379. if (!in_array($productId, $disabledProductIds)) {
  380. $productIds[] = $productId;
  381. }
  382. }
  383. break;
  384. // 指定分类参加
  385. case RangeTypeEnum::ASSIGN_CATE;
  386. foreach ($groupOrderProducts as $productId => $groupOrderProduct) {
  387. if (array_intersect($groupOrderProduct['cateIds'], $usableCateIds)) {
  388. $productIds[] = $productId;
  389. }
  390. }
  391. break;
  392. // 指定分类不参加
  393. case RangeTypeEnum::NOT_ASSIGN_CATE;
  394. foreach ($groupOrderProducts as $productId => $groupOrderProduct) {
  395. $tmpStatus = true;
  396. foreach ($groupOrderProduct['cateIds'] as $cateId) {
  397. if (in_array($cateId, $disabledCateIds)) {
  398. $tmpStatus = false;
  399. break;
  400. }
  401. }
  402. $tmpStatus == true && $productIds[] = $productId;
  403. }
  404. break;
  405. }
  406. if (empty($productIds)) {
  407. return false;
  408. }
  409. // 计算最终优惠金额
  410. foreach ($productIds as $id) {
  411. $money = $groupOrderProducts[$id]['product_money'];
  412. if ($money > $maxMoney) {
  413. $maxMoney = $money;
  414. $maxProductId = $id;
  415. }
  416. $totalMoney = BcHelper::add($totalMoney, $money);
  417. }
  418. // 金额不满足
  419. if ($coupon['at_least'] > $totalMoney) {
  420. return false;
  421. }
  422. // 单品卷
  423. if ($coupon['single_type'] == StatusEnum::ENABLED) {
  424. $totalMoney = $maxMoney;
  425. $productIds = [$maxProductId];
  426. }
  427. // 减钱
  428. if ($coupon['discount_type'] == DiscountTypeEnum::MONEY) {
  429. $predictDiscount = $coupon['discount'];
  430. } else {
  431. $predictDiscount = BcHelper::mul($totalMoney, $coupon['discount']);
  432. $predictDiscount = BcHelper::sub($totalMoney, BcHelper::div($predictDiscount, 10));
  433. }
  434. $coupon = ArrayHelper::toArray($coupon);
  435. unset($coupon['map_id'], $coupon['use_order_id'], $coupon['couponType'], $coupon['product'], $coupon['cate']);
  436. $coupon['predictTotalMoney'] = $totalMoney;
  437. $coupon['predictDiscount'] = $predictDiscount;
  438. $coupon['productIds'] = $productIds;
  439. return $coupon;
  440. }
  441. /**
  442. * 关闭所有过期的优惠券
  443. */
  444. public function closeAll()
  445. {
  446. Coupon::updateAll(['state' => UseStateEnum::PAST_DUE], [
  447. 'and',
  448. ['state' => UseStateEnum::GET],
  449. ['<', 'end_time', time()],
  450. ]);
  451. }
  452. /**
  453. * 使用优惠券
  454. *
  455. * @param Coupon $coupon
  456. * @param $order_id
  457. */
  458. public function used(Coupon $coupon, $order_id)
  459. {
  460. $coupon->use_order_id = $order_id;
  461. $coupon->use_time = time();
  462. $coupon->state = UseStateEnum::USE;
  463. $coupon->save();
  464. }
  465. /**
  466. * 关闭退回
  467. *
  468. * @param $id
  469. * @param $member_id
  470. */
  471. public function back($id, $member_id)
  472. {
  473. Coupon::updateAll([
  474. 'use_order_id' => 0,
  475. 'use_time' => 0,
  476. 'state' => UseStateEnum::GET,
  477. ], [
  478. 'id' => $id,
  479. 'member_id' => $member_id,
  480. 'state' => UseStateEnum::USE,
  481. ]);
  482. }
  483. /**
  484. * 未使用撤回
  485. *
  486. * @param $id
  487. * @param $member_id
  488. */
  489. public function revocation($id, $member_id)
  490. {
  491. Coupon::updateAll([
  492. 'use_order_id' => 0,
  493. 'use_time' => 0,
  494. 'get_type' => 0,
  495. 'member_id' => 0,
  496. 'state' => UseStateEnum::UNCLAIMED,
  497. ], [
  498. 'id' => $id,
  499. 'member_id' => $member_id,
  500. 'state' => UseStateEnum::GET,
  501. ]);
  502. }
  503. /**
  504. * 查询用户优惠劵总数
  505. *
  506. * @param $member_id
  507. * @return false|string|null
  508. */
  509. public function findCountByMemberId($member_id)
  510. {
  511. return Coupon::find()
  512. ->select('count(id) as count')
  513. ->where(['member_id' => $member_id, 'state' => UseStateEnum::GET, 'status' => StatusEnum::ENABLED])
  514. ->andWhere(['between', 'start_time', 'end_time', time()])
  515. ->andFilterWhere(['merchant_id' => $this->getMerchantId()])
  516. ->scalar();
  517. }
  518. /**
  519. * @param $id
  520. * @param $member_id
  521. * @return false|string|null
  522. */
  523. public function findCountById($id, $member_id, $time = 0)
  524. {
  525. return Coupon::find()
  526. ->select('count(id) as count')
  527. ->where(['coupon_type_id' => $id, 'member_id' => $member_id])
  528. ->andFilterWhere(['>=', 'fetch_time', $time])
  529. ->scalar();
  530. }
  531. /**
  532. * 优惠券
  533. *
  534. * @param $code
  535. * @return array|ActiveRecord|null
  536. */
  537. public function findByCode($code)
  538. {
  539. return Coupon::find()
  540. ->where(['code' => $code])
  541. ->with('couponType')
  542. ->one();
  543. }
  544. /**
  545. * @param $id
  546. * @return array|ActiveRecord|null|Coupon
  547. */
  548. public function findByMemberId($id, $member_id)
  549. {
  550. return Coupon::find()
  551. ->where(['id' => $id, 'member_id' => $member_id])
  552. ->with('couponType')
  553. ->one();
  554. }
  555. }
粤ICP备19079148号