BusinessBonusReward.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. <?php
  2. namespace addons\BusinessBonus\common\models;
  3. use addons\BusinessBonus\common\enums\BusinessBonusEnums;
  4. use addons\IssueCoupons\common\models\IssueCouponsRule;
  5. use addons\Mch\common\models\Mch;
  6. use addons\Store\common\enums\StoreEnum;
  7. use addons\Store\common\models\Store;
  8. use addons\Store\common\models\StoreOrderDetail;
  9. use addons\Store\common\models\StoreSettings;
  10. use addons\Mch\common\models\MchSettings;
  11. use common\components\export\CsvTool;
  12. use common\enums\AddonsEnum;
  13. use common\enums\DownloadEnum;
  14. use common\enums\StatusEnum;
  15. use common\models\common\Download;
  16. use common\models\order\OrderDetail;
  17. use common\enums\UserWalletEnum;
  18. use common\models\user\User;
  19. use services\common\UserWalletService;
  20. use addons\Store\common\models\StoreGoods;
  21. use Yii;
  22. use yii\db\Exception;
  23. /**
  24. * This is the model class for table "{{%addons_business_bonus_reward}}".
  25. *
  26. * @property int $id updated_at
  27. * @property int|null $mall_id 商城ID
  28. * @property int|null $user_id 会员ID
  29. * @property int|null order_id 订单ID
  30. * @property string|null $order_no 订单号
  31. * @property int|null $settle_type 结算依据:1订单金额,2利润金额
  32. * @property float|null $settle_amount 结算金额
  33. * @property float|null $bonus_rate 分红比例,5%,填5
  34. * @property float|null $bonus_amount 分红金额
  35. * @property int|null $bonus_status 结算状态:0待结算,1已结算
  36. * @property int|null $bonus_time 分红时间
  37. * @property int|null $settle_time 结算时间,跑任务用
  38. * @property int|null $bonus_type 分红类型:1招商分红
  39. * @property int|null $from_type 来源类型:1门店,2多商户
  40. * @property int|null $store_id 门店、多商户的ID
  41. * @property int|null $status 状态:-1删除,0禁用,1启用
  42. * @property int|null $created_at 创建时间
  43. * @property int|null $updated_at 更新时间
  44. */
  45. class BusinessBonusReward extends \common\models\base\BaseActiveRecord
  46. {
  47. /**
  48. * {@inheritdoc}
  49. */
  50. public static function tableName()
  51. {
  52. return '{{%addons_business_bonus_reward}}';
  53. }
  54. /**
  55. * {@inheritdoc}
  56. */
  57. public function rules()
  58. {
  59. return [
  60. [['mall_id', 'user_id', 'order_id', 'settle_base_on', 'bonus_status', 'bonus_time', 'settle_time', 'bonus_type', 'from_type', 'store_id', 'status', 'created_at', 'updated_at'], 'integer'],
  61. [['settle_amount', 'bonus_rate', 'bonus_amount'], 'number'],
  62. [['order_no'], 'string', 'max' => 32],
  63. ];
  64. }
  65. /**
  66. * {@inheritdoc}
  67. */
  68. public function attributeLabels()
  69. {
  70. return [
  71. 'id' => 'updated_at',
  72. 'mall_id' => '商城ID',
  73. 'user_id' => '会员ID',
  74. 'order_id' => '订单ID',
  75. 'order_no' => '订单号',
  76. 'settle_base_on' => '结算依据:1订单金额,2利润金额',
  77. 'settle_amount' => '结算金额',
  78. 'bonus_rate' => '分红比例,5%,填5',
  79. 'bonus_amount' => '分红金额',
  80. 'bonus_status' => '结算状态:0待结算,1已结算',
  81. 'bonus_time' => '分红时间',
  82. 'settle_time' => '结算时间,跑任务用',
  83. 'bonus_type' => '分红类型:1招商分红',
  84. 'from_type' => '来源类型:1门店,2多商户',
  85. 'store_id' => '门店、多商户的ID',
  86. 'status' => '状态:-1删除,0禁用,1启用',
  87. 'created_at' => '创建时间',
  88. 'updated_at' => '更新时间',
  89. ];
  90. }
  91. /**
  92. * 数据设置
  93. */
  94. public static function setData(array $params)
  95. {
  96. try{
  97. if (!isset($params['mall_id']) || empty($params['mall_id'])) throw new Exception('缺少商城ID');
  98. if (isset($params['id']) && !empty($params['id'])) {
  99. $model = self::findOne(['id' => $params['id'], 'mall_id' => $params['mall_id'], 'status' => [StatusEnum::ENABLED, StatusEnum::DISABLED]]);
  100. if(empty($model)) throw new Exception('分红记录不存在或已删除');
  101. } else {
  102. $model = new self();
  103. $model->loadDefaultValues();
  104. $model->mall_id = $params['mall_id'];
  105. }
  106. if (!$model->load($params,'') || !$model->save()) throw new Exception($model->getErrorMessage());
  107. return $model;
  108. }catch(Exception $e){
  109. self::setStaticError($e->getMessage());
  110. return false;
  111. }
  112. }
  113. /**
  114. * 关联用户
  115. * @return \yii\db\ActiveQuery
  116. */
  117. public function getUser()
  118. {
  119. return $this->hasOne(User::class, ['id' => 'user_id'])->select('id,mobile,nickname,avatar_url,parent_id,username');
  120. }
  121. /**
  122. * 结算门店(或多商户)订单分红
  123. * 注意:调用此方法必须用try {} catch() {} 和 事务,会抛出异常
  124. */
  125. public static function settleStoreOrderReward($store_order, $config, $store_type)
  126. {
  127. if ($store_type == BusinessBonusEnums::STORE_TYPE_STORE) { // 门店
  128. $from_type = BusinessBonusEnums::FROM_TYPE_STORE;
  129. } else { // 多商户
  130. $from_type = BusinessBonusEnums::FROM_TYPE_MCH;
  131. }
  132. // 防止重发 当结算方式改变后,有可能会再次执行
  133. $is_repeact = self::getOne([
  134. ['=', 'mall_id', $store_order['mall_id']],
  135. ['=', 'order_id', $store_order['id']],
  136. ['=', 'from_type', $from_type],
  137. ], true);
  138. if ($is_repeact) {
  139. self::setStaticError('该订单已发放分红');
  140. return false;
  141. }
  142. $detail_where = [];
  143. $detail_where['where'][] = ['order_id' => $store_order['id']];
  144. $detail_where['select'] = 'order_id,goods_id,goods_name,cost_price,goods_price,price,num,original_goods_price';
  145. if ($store_type == BusinessBonusEnums::STORE_TYPE_STORE) {
  146. $store_order_detail = StoreOrderDetail::lists($detail_where);
  147. } else {
  148. $store_order_detail = OrderDetail::lists($detail_where);
  149. }
  150. if (!$store_order_detail) {
  151. self::setStaticError('订单商品不存在');
  152. return false;
  153. }
  154. $goods_cost = 0; // 商品成本总价
  155. foreach ($store_order_detail as $detail) {
  156. $goods_cost += ($detail['cost_price'] * $detail['num']);
  157. }
  158. $settle_base_on = $config['settle_base_on'] ?? BusinessBonusEnums::SETTLE_BASE_ON_PAY;
  159. // 利润金额 = 实付金额 - 运费 - 退款金额 - 商品成本
  160. $order_earn = $store_order['pay_money'] - $store_order['shipping_money'] - $store_order['refund_money'] - $goods_cost;
  161. $settle_amount = 0; // 结算金额
  162. // 结算依据:订单金额(不包括运费和抵扣金额)
  163. if ($settle_base_on == BusinessBonusEnums::SETTLE_BASE_ON_PAY) {
  164. // 实付金额本身不包含抵扣金额
  165. // 订单金额 = 实付金额 - 运费 - 退款金额
  166. $settle_amount = $store_order['pay_money'] - $store_order['shipping_money'] - $store_order['refund_money'];
  167. }
  168. // 结算依据:利润金额(订单最终价格-成本)负数取0
  169. else if ($settle_base_on == BusinessBonusEnums::SETTLE_BASE_ON_EARN) {
  170. // 利润金额 = 实付金额 - 运费 - 退款金额 - 商品成本
  171. $settle_amount = $order_earn;
  172. }
  173. //结算依据:商品售价
  174. else if ($settle_base_on == BusinessBonusEnums::SETTLE_BASE_ON_GOODS_PRICE) {
  175. $goods_price = 0;
  176. foreach ($store_order_detail as $detail) {
  177. if ($store_type == BusinessBonusEnums::STORE_TYPE_STORE) {
  178. $storeGoods = StoreGoods::getOne([['id' => $detail['goods_id']]], true, ['extra']);
  179. if(!empty($storeGoods['extra']['is_alone_settle'])){
  180. $goods_price += $storeGoods['extra']['alone_settle_price'] * $detail['num'];
  181. }else{
  182. $goods_price += $detail['original_goods_price'];
  183. }
  184. } else {
  185. $goods_price += $detail['original_goods_price'];
  186. }
  187. }
  188. $settle_amount = $goods_price;
  189. } else if ($settle_base_on == BusinessBonusEnums::SETTLE_BASE_ON_SERVICE_CHARGE) {
  190. $settle_amount = self::computeServiceCharge($store_order, $store_type);
  191. }
  192. if ($settle_amount <= 0) {
  193. self::setStaticError('结算金额小于等于0');
  194. return false;
  195. }
  196. if ($store_type == BusinessBonusEnums::STORE_TYPE_STORE) {
  197. // 门店信息
  198. $store_info = Store::findOne(['id' => $store_order['store_id']]);
  199. } else {
  200. // 商户信息
  201. $store_info = Mch::findOne(['id' => $store_order['mch_id']]);
  202. }
  203. if (!$store_info) {
  204. self::setStaticError('门店或商户不存在');
  205. return false;
  206. }
  207. // 查找门店店主的上级是否是招商员
  208. $store_user = User::findOne(['id' => $store_info['user_id']]);
  209. if ($store_user['parent_id'] <= 0) {
  210. self::setStaticError('门店店主推荐人不存在');
  211. return false;
  212. }
  213. $parent_business_user = BusinessBonusUser::getOne([
  214. ['=', 'mall_id', $store_user['mall_id']],
  215. ['=', 'user_id', $store_user['parent_id']],
  216. ['>', 'status', StatusEnum::DISABLED], // 禁用后不给予奖励
  217. ], true);
  218. if (!$parent_business_user) {
  219. self::setStaticError('门店店主推荐人不是招商员或已禁用');
  220. return false;
  221. }
  222. if ($store_info['created_at'] < $parent_business_user['register_time']) {
  223. self::setStaticError('推荐人成为招商员之前门店或商户已存在,不予奖励');
  224. return false;
  225. }
  226. // 获取分红比例
  227. $business_level = BusinessBonusLevel::getOne([
  228. ['=', 'mall_id', $store_user['mall_id']],
  229. ['=', 'level', $parent_business_user['business_bonus_level']],
  230. ['>', 'status', StatusEnum::DISABLED], // 禁用的等级不给予奖励
  231. ], true);
  232. if (!$business_level) {
  233. self::setStaticError('招商员等级不存在或已禁用');
  234. return false;
  235. }
  236. // 计算出分红金额
  237. $bonus_amount = $settle_amount * $business_level['bonus_rate'] / 100;
  238. if ($bonus_amount < 0.01) { // 只精确2位数
  239. self::setStaticError('分红金额小于0.01');
  240. return false;
  241. }
  242. // 结算周期
  243. $settle_circle = $config['settle_circle'] ?? BusinessBonusEnums::SETTLE_CIRCLE_DAY;
  244. if ($settle_circle == BusinessBonusEnums::SETTLE_CIRCLE_DAY) { // 一天 (凌晨)
  245. $settle_time = strtotime ('tomorrow');
  246. } else if ($settle_circle == BusinessBonusEnums::SETTLE_CIRCLE_WEEK) { // 一周 (下周一)
  247. $settle_time = strtotime('next Monday');
  248. } else { // 一个月 (下月1号)
  249. $settle_time = strtotime('midnight first day of next month');
  250. }
  251. // 保存数据
  252. $settle_base_on = 1;
  253. $reward_data = [
  254. 'mall_id' => $store_user['mall_id'],
  255. 'user_id' => $parent_business_user['user_id'],
  256. 'order_id' => $store_order['id'],
  257. 'order_no' => $store_order['order_no'],
  258. 'settle_base_on' => $settle_base_on,
  259. 'settle_amount' => $settle_amount,
  260. 'bonus_rate' => $business_level['bonus_rate'],
  261. 'bonus_amount' => $bonus_amount,
  262. 'bonus_status' => BusinessBonusEnums::BONUS_STATUS_TODO,
  263. 'bonus_time' => 0,
  264. 'settle_time' => $settle_time, // 预结算时间
  265. 'bonus_type' => BusinessBonusEnums::BONUS_TYPE_BUSINESS,
  266. 'from_type' => $from_type,
  267. 'store_id' => $store_info['id'],
  268. 'status' => StatusEnum::ENABLED
  269. ];
  270. // 门店统计
  271. $store_data = [
  272. 'mall_id' => $store_user['mall_id'],
  273. 'store_id' => $store_info['id'],
  274. 'store_type' => $store_type,
  275. 'store_user_id' => $store_info['user_id'],
  276. 'business_user_id' => $parent_business_user['user_id'],
  277. 'history_income' => $store_order['pay_money'], // 累计收入
  278. 'history_earn' => $order_earn, // 累计利润
  279. 'store_created_at' => $store_info['created_at']
  280. ];
  281. $user_data = [
  282. 'id' => $parent_business_user['id'],
  283. 'mall_id' => $store_user['mall_id'],
  284. ];
  285. $store_exist = BusinessBonusStore::findOne(['mall_id' => $store_data['mall_id'], 'store_id' => $store_data['store_id'], 'store_type' => $store_data['store_type'], 'status' => [StatusEnum::DISABLED, StatusEnum::ENABLED]]);
  286. if (!$store_exist) {
  287. if ($store_type == BusinessBonusEnums::STORE_TYPE_STORE) {
  288. $user_data['store_number'] = 1;
  289. } else {
  290. $user_data['mch_number'] = 1;
  291. }
  292. }
  293. // 调用方法必须用try {} catch() {}
  294. $res = self::setData($reward_data);
  295. if ($res == false) throw new Exception(self::getStaticError());
  296. $res = BusinessBonusStore::setData($store_data);
  297. if ($res == false) throw new Exception(BusinessBonusStore::getStaticError());
  298. if (isset($user_data['mch_number']) || isset($user_data['store_number'])) {
  299. $res = BusinessBonusUser::setData($user_data);
  300. if ($res == false) throw new Exception(BusinessBonusUser::getStaticError());
  301. }
  302. return true;
  303. }
  304. public static function settleStoreOrderRewardByCashierOrder($store_order, $config, $store_type)
  305. {
  306. $from_type = BusinessBonusEnums::FROM_TYPE_STORE_CASHIER;
  307. // 防止重发 当结算方式改变后,有可能会再次执行
  308. $is_repeact = self::getOne([
  309. ['=', 'mall_id', $store_order['mall_id']],
  310. ['=', 'order_id', $store_order['id']],
  311. ['=', 'from_type', $from_type],
  312. ], true);
  313. if ($is_repeact) {
  314. self::setStaticError('该订单已发放分红');
  315. return false;
  316. }
  317. // 结算依据:订单实际支付金额
  318. $settle_amount = $store_order['pay_money'];
  319. if ($settle_amount <= 0) {
  320. self::setStaticError('结算金额小于等于0');
  321. return false;
  322. }
  323. $order_earn = $settle_amount;
  324. // 门店信息
  325. $store_info = Store::findOne(['id' => $store_order['store_id']]);
  326. if (!$store_info) {
  327. self::setStaticError('门店或商户不存在');
  328. return false;
  329. }
  330. // 查找门店店主的上级是否是招商员
  331. $store_user = User::findOne(['id' => $store_info['user_id']]);
  332. if ($store_user['parent_id'] <= 0) {
  333. self::setStaticError('门店店主推荐人不存在');
  334. return false;
  335. }
  336. $parent_business_user = BusinessBonusUser::getOne([
  337. ['=', 'mall_id', $store_user['mall_id']],
  338. ['=', 'user_id', $store_user['parent_id']],
  339. ['>', 'status', StatusEnum::DISABLED], // 禁用后不给予奖励
  340. ], true);
  341. if (!$parent_business_user) {
  342. self::setStaticError('门店店主推荐人不是招商员或已禁用');
  343. return false;
  344. }
  345. if ($store_info['created_at'] < $parent_business_user['register_time']) {
  346. self::setStaticError('推荐人成为招商员之前门店或商户已存在,不予奖励');
  347. return false;
  348. }
  349. // 获取分红比例
  350. $business_level = BusinessBonusLevel::getOne([
  351. ['=', 'mall_id', $store_user['mall_id']],
  352. ['=', 'level', $parent_business_user['business_bonus_level']],
  353. ['>', 'status', StatusEnum::DISABLED], // 禁用的等级不给予奖励
  354. ], true);
  355. if (!$business_level) {
  356. self::setStaticError('招商员等级不存在或已禁用');
  357. return false;
  358. }
  359. // 计算出分红金额
  360. $bonus_amount = $settle_amount * $business_level['bonus_rate'] / 100;
  361. if ($bonus_amount < 0.01) { // 只精确2位数
  362. self::setStaticError('分红金额小于0.01');
  363. return false;
  364. }
  365. // 结算周期
  366. $settle_circle = $config['settle_circle'] ?? BusinessBonusEnums::SETTLE_CIRCLE_DAY;
  367. if ($settle_circle == BusinessBonusEnums::SETTLE_CIRCLE_DAY) { // 一天 (凌晨)
  368. $settle_time = strtotime ('tomorrow');
  369. } else if ($settle_circle == BusinessBonusEnums::SETTLE_CIRCLE_WEEK) { // 一周 (下周一)
  370. $settle_time = strtotime('next Monday');
  371. } else { // 一个月 (下月1号)
  372. $settle_time = strtotime('midnight first day of next month');
  373. }
  374. // 保存数据
  375. $reward_data = [
  376. 'mall_id' => $store_user['mall_id'],
  377. 'user_id' => $parent_business_user['user_id'],
  378. 'order_id' => $store_order['id'],
  379. 'order_no' => $store_order['order_no'],
  380. 'settle_base_on' => 1,
  381. 'settle_amount' => $settle_amount,
  382. 'bonus_rate' => $business_level['bonus_rate'],
  383. 'bonus_amount' => $bonus_amount,
  384. 'bonus_status' => BusinessBonusEnums::BONUS_STATUS_TODO,
  385. 'bonus_time' => 0,
  386. 'settle_time' => $settle_time, // 预结算时间
  387. 'bonus_type' => BusinessBonusEnums::BONUS_TYPE_BUSINESS,
  388. 'from_type' => $from_type,
  389. 'store_id' => $store_info['id'],
  390. 'status' => StatusEnum::ENABLED
  391. ];
  392. // 门店统计
  393. $store_data = [
  394. 'mall_id' => $store_user['mall_id'],
  395. 'store_id' => $store_info['id'],
  396. 'store_type' => $store_type,
  397. 'store_user_id' => $store_info['user_id'],
  398. 'business_user_id' => $parent_business_user['user_id'],
  399. 'history_income' => $store_order['pay_money'], // 累计收入
  400. 'history_earn' => $order_earn, // 累计利润
  401. 'store_created_at' => $store_info['created_at']
  402. ];
  403. $user_data = [
  404. 'id' => $parent_business_user['id'],
  405. 'mall_id' => $store_user['mall_id'],
  406. ];
  407. $store_exist = BusinessBonusStore::findOne(['mall_id' => $store_data['mall_id'], 'store_id' => $store_data['store_id'], 'store_type' => $store_data['store_type'], 'status' => [StatusEnum::DISABLED, StatusEnum::ENABLED]]);
  408. if (!$store_exist) {
  409. if ($store_type == BusinessBonusEnums::STORE_TYPE_STORE) {
  410. $user_data['store_number'] = 1;
  411. } else {
  412. $user_data['mch_number'] = 1;
  413. }
  414. }
  415. // 调用方法必须用try {} catch() {}
  416. $res = self::setData($reward_data);
  417. if ($res == false) throw new Exception(self::getStaticError());
  418. $res = BusinessBonusStore::setData($store_data);
  419. if ($res == false) throw new Exception(BusinessBonusStore::getStaticError());
  420. if (isset($user_data['mch_number']) || isset($user_data['store_number'])) {
  421. $res = BusinessBonusUser::setData($user_data);
  422. if ($res == false) throw new Exception(BusinessBonusUser::getStaticError());
  423. }
  424. return true;
  425. }
  426. public static function computeServiceCharge($order, $store_type)
  427. {
  428. $platform_ratio= 0;
  429. $price = $order['pay_money'];
  430. if ($store_type == BusinessBonusEnums::STORE_TYPE_STORE) {
  431. // 门店
  432. $store_settings = StoreSettings::getOne([['store_id' => $order['store_id']]]);
  433. $basic = \Yii::$app->services->setting->addons->get($order['mall_id'], StoreEnum::ADDONS_NAME, 'basic');
  434. } else {
  435. // 多商户
  436. $store_settings = MchSettings::getOne([['mch_id' => $order['mch_id']]]);
  437. $basic = \Yii::$app->services->setting->addons->get($order['mall_id'], AddonsEnum::ADDONS_NAME_MCH, 'basic');
  438. }
  439. if ($store_type == BusinessBonusEnums::STORE_TYPE_STORE) {
  440. if (isset($store_settings['store_service_fee']) && !empty($store_settings['store_service_fee'])) {
  441. $platform_ratio = $store_settings['store_service_ratio'] ?? 0;
  442. $service_fee_settle_type = $store_settings['store_service_fee_settle_type'] ?? 1;
  443. } else {
  444. $platform_ratio = $basic['platform_ratio'] ?? 0;
  445. $service_fee_settle_type = $basic['store_service_fee_settle_type'] ?? 1;
  446. }
  447. } else {
  448. if (isset($store_settings['individual_fee_setting']) && !empty($store_settings['individual_fee_setting'])) {
  449. $platform_ratio = $store_settings['store_service_ratio'] ?? 0;
  450. $service_fee_settle_type = $store_settings['store_service_fee_settle_type'] ?? 1;
  451. } else {
  452. $platform_ratio = $basic['platform_ratio'] ?? 0;
  453. $service_fee_settle_type = $basic['store_service_fee_settle_type'] ?? 1;
  454. }
  455. }
  456. if($service_fee_settle_type == 2){
  457. $price = $order['original_goods_price'];
  458. }elseif($service_fee_settle_type == 3){
  459. $price = StoreOrderDetail::find()->where(['order_id' => $order['id'],'status' => 1])->sum('cost_price');
  460. }
  461. return bcdiv($price * $platform_ratio,100,2);
  462. }
  463. /**
  464. * 结算分红并发放
  465. * @return bool
  466. */
  467. public static function sendBonus()
  468. {
  469. $redis_key = BusinessBonusEnums::ADDONS_NAME . ':BusinessBonusReward:sendBonus:lock';
  470. $redis_lock = Yii::$app->redis->get($redis_key);
  471. if ($redis_lock) {
  472. return false;
  473. }
  474. Yii::$app->redis->setex($redis_key, 86400, 1);
  475. $trans = Yii::$app->db->beginTransaction();
  476. try {
  477. $where = ['bonus_status'=>BusinessBonusEnums::BONUS_STATUS_TODO,'status'=>StatusEnum::ENABLED];
  478. $reward_list = self::find()->where(['and',$where,['<=', 'settle_time', time()]]);
  479. foreach ($reward_list->batch(200) as $detail){//分批查询标签
  480. $insert_wallet = [];
  481. foreach ($detail as $k=>$list){
  482. // 发放到推广中心可提现
  483. $order_type = $list['from_type'] == 1 ? '门店订单' : '商户订单';
  484. $desc = '用户(' . $list['user_id'] . '),获得' . $order_type . '(' . $list['order_id'] . ')奖励,奖励类型:招商分红,金额:' . $list['bonus_amount'];
  485. $insert_wallet[] = [
  486. 'mall_id' => $list['mall_id'],
  487. 'user_id' => $list['user_id'],
  488. 'is_frozen' => false,
  489. 'wallet_type' => UserWalletService::WALLET_TYPE_COMMISSION,
  490. 'money' => $list['bonus_amount'],
  491. 'desc' => $desc,
  492. 'source_table' => self::tableName(),
  493. 'source_table_id' => $list['id'],
  494. 'from_type' => UserWalletEnum::REWARD,
  495. 'capital_type'=> UserWalletEnum::BOUNS
  496. ];
  497. // 招商员增加累计分红
  498. $business_user = BusinessBonusUser::getOne([
  499. ['=', 'mall_id', $list['mall_id']],
  500. ['=', 'user_id', $list['user_id']],
  501. ['>=', 'status', StatusEnum::DISABLED]
  502. ], true, '', '', 'id');
  503. $user_data = [
  504. 'id' => $business_user['id'],
  505. 'user_id' => $list['user_id'],
  506. 'mall_id' => $list['mall_id'],
  507. 'history_bonus_amount' => $list['bonus_amount'],
  508. ];
  509. $res = BusinessBonusUser::setData($user_data);
  510. if ($res == false) throw new Exception(BusinessBonusUser::getStaticError());
  511. // 更改记录状态
  512. $update_data = [
  513. 'id' => $list['id'],
  514. 'mall_id' => $list['mall_id'],
  515. 'bonus_status' => BusinessBonusEnums::BONUS_STATUS_DONE,
  516. 'bonus_time' => time()
  517. ];
  518. $res = self::setData($update_data);
  519. if ($res == false) throw new Exception(self::getStaticError());
  520. }
  521. // 异步处理佣金
  522. if ($insert_wallet) {
  523. $res = UserWalletService::batchHandleByQueue($insert_wallet);
  524. if (empty($res)) throw new Exception(UserWalletService::getStaticError());
  525. }
  526. }
  527. $trans->commit();
  528. } catch (\Exception $e) {
  529. $trans->rollBack();
  530. Yii::$app->redis->del($redis_key);
  531. return false;
  532. }
  533. Yii::$app->redis->del($redis_key);
  534. return true;
  535. }
  536. /**
  537. * 处理分红记录的列表数据,加上其他需要的信息
  538. * @param array $list
  539. * @param int $mall_id
  540. * @param bool $add_order_goods 是否包含商品信息
  541. * @return array $list
  542. */
  543. public static function dealRewardList(array $list, int $mall_id, $add_order_goods = false)
  544. {
  545. $store_ids = $mch_ids = [];
  546. $store_order_ids = $mch_order_ids = [];
  547. foreach ($list as $item) {
  548. if ( in_array($item['from_type'],[BusinessBonusEnums::FROM_TYPE_STORE,BusinessBonusEnums::FROM_TYPE_STORE_CASHIER]) ) { // 门店
  549. $store_ids[] = $item['store_id'];
  550. $store_order_ids[] = $item['order_id'];
  551. } else if ($item['from_type'] == BusinessBonusEnums::FROM_TYPE_MCH) { // 多商户
  552. $mch_ids[] = $item['store_id'];
  553. $mch_order_ids[] = $item['order_id'];
  554. }
  555. }
  556. $store_array = $mch_array = [];
  557. if ($store_ids) {
  558. $store_list = Store::find()
  559. ->select('id,user_id,store_name')
  560. ->andWhere(['>=', 'status', StatusEnum::DISABLED])
  561. ->andWhere(['in', 'id', $store_ids])
  562. ->andWhere(['=', 'mall_id', $mall_id])
  563. ->asArray()
  564. ->all();
  565. $store_array = array_column($store_list, null, 'id');
  566. }
  567. if ($mch_ids) {
  568. $mch_list = Mch::find()
  569. ->select('id,user_id,store_name')
  570. ->andWhere(['>=', 'status', StatusEnum::DISABLED])
  571. ->andWhere(['in', 'id', $mch_ids])
  572. ->andWhere(['=', 'mall_id', $mall_id])
  573. ->asArray()
  574. ->all();
  575. $mch_array = array_column($mch_list, null, 'id');
  576. }
  577. $store_order_goods = $mch_order_goods = [];
  578. $select = 'id,order_id,goods_name,goods_price,num,pic_url';
  579. $order = 'id asc';
  580. if ($store_order_ids && $add_order_goods) {
  581. $where = [
  582. ['=', 'mall_id', $mall_id],
  583. ['=', 'status', StatusEnum::ENABLED],
  584. ['in', 'order_id', $store_order_ids],
  585. ];
  586. $condition = compact('where', 'select', 'order');
  587. $store_order_detail = StoreOrderDetail::lists($condition, true);
  588. foreach ($store_order_detail as $item) {
  589. $store_order_goods[$item['order_id']][] = $item;
  590. }
  591. }
  592. if ($mch_order_ids && $add_order_goods) {
  593. $where = [
  594. ['=', 'mall_id', $mall_id],
  595. ['=', 'status', StatusEnum::ENABLED],
  596. ['in', 'order_id', $mch_order_ids]
  597. ];
  598. $condition = compact('where', 'select', 'order');
  599. $mch_order_detail = OrderDetail::lists($condition, true);
  600. foreach ($mch_order_detail as $item) {
  601. $mch_order_goods[$item['order_id']][] = $item;
  602. }
  603. }
  604. foreach ($list as $key => $val) {
  605. $val['created_at_'] = date('Y-m-d H:i:s', $val['created_at']);
  606. $val['created_ats'] = date('Y/m/d', $val['created_at']);
  607. $val['store_name'] = '';
  608. $add_order_goods && $val['order_goods'] = [];
  609. if ( in_array($val['from_type'],[BusinessBonusEnums::FROM_TYPE_STORE,BusinessBonusEnums::FROM_TYPE_STORE_CASHIER]) ) { // 门店
  610. $val['store_name'] = $store_array[$val['store_id']]['store_name'] ?? '';
  611. $add_order_goods && $val['order_goods'] = $store_order_goods[$val['order_id']] ?? [];
  612. } else if ($val['from_type'] == BusinessBonusEnums::FROM_TYPE_MCH) { // 多商户
  613. $val['store_name'] = $mch_array[$val['store_id']]['store_name'] ?? '';
  614. $add_order_goods && $val['order_goods'] = $mch_order_goods[$val['order_id']] ?? [];
  615. }
  616. $list[$key] = $val;
  617. }
  618. return $list;
  619. }
  620. /**
  621. * 统计用户某个时间段的分红
  622. * @param $mall_id
  623. * @param $user_id
  624. * @param $start_time
  625. * @param $end_time
  626. * @return float
  627. */
  628. public static function getCustomStatistic($mall_id, $user_id, $start_time, $end_time)
  629. {
  630. $number = self::find()
  631. ->andWhere(['=', 'mall_id', $mall_id])
  632. ->andWhere(['=', 'user_id', $user_id])
  633. ->andWhere(['>=', 'created_at', $start_time])
  634. ->andWhere(['<=', 'created_at', $end_time])
  635. ->andWhere(['=', 'status', StatusEnum::ENABLED])
  636. ->sum('bonus_amount');
  637. return (float) $number;
  638. }
  639. /**
  640. * 可导出的字段
  641. * @param null $field
  642. * @return array
  643. */
  644. public static function fieldsList($field = null)
  645. {
  646. $fields = [
  647. 'id' => '序号',
  648. 'order_no' => '订单编号',
  649. 'user_id' => '招商员ID',
  650. 'nickname' => '招商员昵称',
  651. 'mobile' => '招商员手机号',
  652. 'settle_base_on' => '结算依据',
  653. 'settle_amount' => '结算金额',
  654. 'bonus_rate' => '分红比例(%)',
  655. 'bonus_amount' => '分红金额',
  656. 'bonus_status' => '状态',
  657. 'bonus_time' => '结算时间',
  658. 'settle_time' => '预结算时间',
  659. 'store_name' => '所属门店',
  660. 'from_type' => '来源类型',
  661. 'created_at' => '创建时间',
  662. ];
  663. if ($field === null) return $fields;
  664. $temp = [];
  665. foreach ($field as $val) {
  666. $temp[$val] = $fields[$val] ?? $val;
  667. }
  668. return $temp;
  669. }
  670. /**
  671. * 导出处理手柄
  672. */
  673. public static function exportHandle(array $data)
  674. {
  675. $download_id = $data['download_id'] ?? 0;
  676. $download = Download::findOne(['id' => $download_id]);
  677. if ($download) {
  678. try {
  679. $params = json_decode($download->params, true);
  680. $condition = $params['condition'];
  681. $list = self::lists($condition, true);
  682. $list = self::dealRewardList($list, $params['mall_id'], false);
  683. $filename = $download->name;
  684. $type = $download->type;
  685. $fields = $params['fields'];
  686. $fields_arr = self::fieldsList();
  687. $have_select_field_arr = [];
  688. foreach ($fields as $value) {
  689. $have_select_field_arr[] = $fields_arr[$value];
  690. }
  691. $csv = new CsvTool();
  692. $csv->filename = $filename;
  693. $csv->file_dirname = DownloadEnum::getTypeStorageDirMap($type);
  694. $csv->export_mode = CsvTool::EXPORT_MODE_ASYNC;
  695. $csv->header = $have_select_field_arr;
  696. $csv_data = [];
  697. foreach ($list as $data) {
  698. $row = [];
  699. foreach ($fields as $field) {
  700. switch ($field) {
  701. case (in_array($field, ['bonus_time', 'settle_time', 'created_at'])):
  702. $row[] = ($data[$field] ?? '') ? date('Y-m-d H:i:s', $data[$field] ?? '') : '';
  703. break;
  704. case (in_array($field, ['mobile', 'nickname'])):
  705. $row[] = $data['user'][$field] ?? '';
  706. break;
  707. case 'settle_base_on':
  708. $value = $data[$field] ?? 0;
  709. $temp = '';
  710. if ($value == BusinessBonusEnums::SETTLE_BASE_ON_PAY) {
  711. $temp = '订单金额';
  712. } else if ($value == BusinessBonusEnums::SETTLE_BASE_ON_EARN) {
  713. $temp = '订单利润';
  714. }
  715. $row[] = $temp;
  716. break;
  717. case 'bonus_status':
  718. $value = $data[$field] ?? 0;
  719. $temp = '';
  720. if ($value == BusinessBonusEnums::BONUS_STATUS_TODO) {
  721. $temp = '待结算';
  722. } else if ($value == BusinessBonusEnums::BONUS_STATUS_DONE) {
  723. $temp = '已结算';
  724. }
  725. $row[] = $temp;
  726. break;
  727. case 'from_type':
  728. $value = $data[$field] ?? 0;
  729. $temp = '';
  730. if ($value == BusinessBonusEnums::FROM_TYPE_STORE) {
  731. $temp = '门店';
  732. } else if ($value == BusinessBonusEnums::FROM_TYPE_MCH) {
  733. $temp = '商户';
  734. }
  735. $row[] = $temp;
  736. break;
  737. default:
  738. $row[] = $data[$field] ?? '';
  739. break;
  740. }
  741. }
  742. $csv_data[] = $row;
  743. }
  744. $csv->data = $csv_data;
  745. $csv->export();
  746. $csv->updateDown($download, $params['mall_id']);
  747. return true;
  748. } catch (Exception $e) {
  749. $download->status = 3;
  750. $download->save();
  751. self::setStaticError($e->getMessage());
  752. return $e->getMessage();
  753. }
  754. }
  755. }
  756. }