FreeShippingMarketing.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. <?php
  2. namespace addons\Happiness\common\logic\order\marketing;
  3. use addons\Happiness\common\enums\HappinessEnum;
  4. use common\enums\GoodsTypeEnum;
  5. use common\enums\ShippingTypeEnum;
  6. use common\models\goods\FreeShippingRules;
  7. use common\models\goods\FreightRules;
  8. use common\models\goods\Goods;
  9. use common\models\user\UserAddress;
  10. use Yii;
  11. use yii\helpers\Json;
  12. /**
  13. * 运费减免计算
  14. * @Author bing
  15. * @DateTime 2021-09-27 19:07:32
  16. * @copyright: Copyright (c) 2020 广东七件事集团
  17. */
  18. class FreeShippingMarketing extends BaseMarketing{
  19. public function execute( $form){}
  20. /**
  21. * 计算运费
  22. * @Author: lun
  23. * @DateTime: 2021/10/6 0006 10:37
  24. * @Copyright: copyright (c) 2021 广东七件事集团
  25. * @param $item
  26. * @param $form
  27. * @param $extra
  28. * @return mixed
  29. */
  30. public static function compute($item, &$form, &$extra)
  31. {
  32. if (isset($form->data['shipping_type']) && $form->data['shipping_type']==ShippingTypeEnum::PICKUP){
  33. return $item;
  34. }
  35. // 获取用户收货地址信息
  36. if (empty($extra['address'])) {
  37. $address = UserAddress::getOne([['id' => $form['address_id']], ['user_id' => $form['user_id']]], true);
  38. $extra['address'] = $address;
  39. } else {
  40. $address = $extra['address'];
  41. }
  42. // 收货地址
  43. if(empty($address)){
  44. $address['province_id'] = $address['city_id'] = $address['district_id'] = $address['town_id'] = $address['community_id'] = 0;
  45. $address['province'] = $address['city'] = $address['district'] = $address['town'] = $address['community'] = $address['detail'] = $address['name'] = $address['mobile'] ='';
  46. }
  47. // 收货地址
  48. $item['mch_order_info']['province'] = $address['province_id'];
  49. $item['mch_order_info']['city'] = $address['city_id'];
  50. $item['mch_order_info']['area'] = $address['district_id'];
  51. $item['mch_order_info']['town'] = $address['town_id'];
  52. $item['mch_order_info']['community'] = $address['community_id'];
  53. $item['mch_order_info']['region_name'] = $address['province'] .' '. $address['city'] .' '. $address['district'].' '.$address['town'].' '.$address['community'];
  54. $item['mch_order_info']['address'] = $address['detail'];
  55. $item['mch_order_info']['receiver_name'] = $address['name'];
  56. $item['mch_order_info']['mobile'] = $address['mobile'];
  57. $shipping_fee = 0;// 订单总运费初始化
  58. $group_order_fee = 0;// 单个商户订单运费
  59. $is_free = false;// 是否免邮
  60. $compute_shipping = [];// 运费计算,配合运费规则compute_type == 2使用
  61. // 获取包邮地区
  62. $feeShippingRules = FreeShippingRules::getFreeShippingRulesArea($item['mch_order_info']['mall_id'], $item['mch_order_info']['mch_id']);
  63. // 获取运费模板
  64. $freightRules = FreightRules::getFreightRulesArea($item['mch_order_info']['mall_id'], $item['mch_order_info']['mch_id']);
  65. // 包邮计算
  66. foreach ($feeShippingRules as $fee) {
  67. // 如果包邮金额<=购买商品金额,并且地区符合收货地址则免邮
  68. if ($fee['money'] <= $item['mch_order_info']['goods_price'] && self::isInArea($address, $fee['district_ids'])) {
  69. $is_free = true;
  70. continue;
  71. }
  72. }
  73. if ($form->data['shipping_type'] == ShippingTypeEnum::LOGISTICS) {
  74. // 不免邮处理
  75. if ($is_free == false) {
  76. // 阶梯价格情况下 存储哪个运费模板已经计算了价格(因为如果是开启了阶梯,并且是同一个模板来计算的话只拿一次价格)
  77. $calculatedFreight = [];
  78. $freightValue = self::getFreightValue($item['goods'], $form, $freightRules);
  79. foreach ($item['goods'] as $good_id => $good) {
  80. $good_info = Goods::findOne(['id' => $good_id]);
  81. if($good_info['goods_type'] == GoodsTypeEnum::GoodsTypeVirtual) continue;
  82. $good_num = 0;// 第N个商品
  83. if ($good['num'] > 1) {
  84. $good_num+= $good['num'];
  85. } else {
  86. $good_num++;
  87. }
  88. // 物流方式
  89. if (!in_array(ShippingTypeEnum::LOGISTICS, explode(',', $form['sku'][$good_id]['goods']['freight_type']))) continue;
  90. // 计算满件包邮
  91. if ($form['sku'][$good_id]['goods']['free_shipping_num'] > 0 && $good['num'] >= $form['sku'][$good_id]['goods']['free_shipping_num']) continue;
  92. // 计算满元包邮
  93. if ($form['sku'][$good_id]['goods']['free_shipping_money'] > 0 && $good['goods_price'] >= $form['sku'][$good_id]['goods']['free_shipping_money']) continue;
  94. // 运费规则
  95. if ($form['sku'][$good_id]['goods']['freight_rules_type'] == 1) {
  96. // 根据商品id获取对应的规则
  97. $thisRules = isset($freightRules[$form['sku'][$good_id]['goods']['freight_id']])
  98. ? $freightRules[$form['sku'][$good_id]['goods']['freight_id']]
  99. : [];
  100. // 运费模板ID
  101. $rule_id = $thisRules['id'] ?? 0;
  102. if(!empty($thisRules['rule'])){
  103. foreach ($thisRules['rule'] as $rule) {
  104. // 获取该模板的所有地区id
  105. $areaArr = array_column($rule['list'], 'id');
  106. // 判断收货地址是否在模板中
  107. if (self::isInArea($address, $areaArr)) {
  108. if ($thisRules['type'] == 1) {// 按重计费
  109. $new_num_weight = $form['sku'][$good_id]['weight'] * $good['num'];
  110. } else {// 按件计费
  111. $new_num_weight = $good['num'];
  112. }
  113. // 判断是否开启阶梯
  114. if (!empty($rule['is_enable_ladder_condition']) && !empty($rule['ladder_condition'])) {
  115. // 根据商品计算方式来决定使用哪个字段来确定唯一值
  116. switch ($thisRules['compute_type']) {
  117. case 1: // 按不同商品计算
  118. $calculatedFreightKey = $good['goods_id'];
  119. break;
  120. case 2: // 按同件商品计算
  121. $calculatedFreightKey = $form['sku'][$good['goods_id']]['goods']['freight_id'];
  122. break;
  123. }
  124. if (isset($calculatedFreightKey)) {
  125. if (!empty($calculatedFreight[$thisRules['compute_type']][$calculatedFreightKey])) {
  126. continue;
  127. }
  128. // 验证是否满足阶梯
  129. $computeLadderCondition = self::computeLadderCondition(
  130. $freightValue,
  131. $good,
  132. $thisRules['type'],
  133. $thisRules['compute_type'],
  134. $rule,
  135. $form
  136. );
  137. if ($computeLadderCondition['is_success']) {
  138. $shipping_fee += $computeLadderCondition['money'];
  139. $group_order_fee += $computeLadderCondition['money'];
  140. $calculatedFreight[$thisRules['compute_type']][$calculatedFreightKey] = true;
  141. continue;
  142. }
  143. }
  144. }
  145. // 计算[首件/首重]收费,只要商品有重量并且运费有首重设置就需要给运费
  146. if ($new_num_weight > 0 && $rule['first'] > 0) {
  147. if ($thisRules['compute_type'] == 1) {
  148. $group_order_fee += $rule['firstPrice'];
  149. $shipping_fee += $rule['firstPrice'];
  150. }
  151. }
  152. if ($thisRules['type'] == 1) {// 计算续重收费
  153. if ($thisRules['compute_type'] == 2) {// 按同件商品计算,扣除首重后,剩余不同商品都按续重计算
  154. self::getShopFee($rule_id, $thisRules['type'], $rule, $new_num_weight, $compute_shipping);
  155. } else {
  156. $surplus_weight = $new_num_weight - $rule['first'];// 剩余重量 = 总重量 - 首重重量
  157. // 续重运费 = (剩余重量 / 续件重量)[向上取整] * 续重费用 [向下取整]
  158. if($surplus_weight > 0 && isset($rule['second'])&&$rule['second']>0) {
  159. $fee = floor(ceil($surplus_weight / $rule['second']) * $rule['secondPrice']);
  160. $shipping_fee += $fee;
  161. $group_order_fee += $fee;
  162. }
  163. }
  164. } else {// 计算续件收费
  165. if ($thisRules['compute_type'] == 2) {// 按同件商品计算,扣除首件后,剩余不同商品都按续件计算
  166. self::getShopFee($rule_id, $thisRules['type'], $rule, $new_num_weight, $compute_shipping);
  167. } else {
  168. $surplus_num = $good_num - $rule['first'];// 减扣首件数量
  169. if ($surplus_num > 0 && $rule['second'] > 0) {
  170. // 续费 * (减扣首件数量 / 续件数量)[向下取整]
  171. if(isset($rule['second']) && $rule['second'] > 0){
  172. $fee = $rule['secondPrice'] * floor($surplus_num / $rule['second']);
  173. $shipping_fee += $fee;
  174. $group_order_fee += $fee;
  175. }
  176. }
  177. }
  178. }
  179. continue;// 已计算好运费则跳出循环
  180. }
  181. }
  182. }
  183. } else {
  184. // 自定义运费
  185. if ($form['sku'][$good_id]['goods']['shipping_fee'] >= 0) {
  186. $shipping_fee+= $form['sku'][$good_id]['goods']['shipping_fee'];
  187. $group_order_fee+= $form['sku'][$good_id]['goods']['shipping_fee'];
  188. }
  189. }
  190. }
  191. // 按同件商品运费计算
  192. if ($compute_shipping) {
  193. $shop_fee_assignment = self::shopFeeAssignment($compute_shipping);
  194. $shipping_fee += $shop_fee_assignment['shipping_fee'] ?? 0;
  195. $group_order_fee += $shop_fee_assignment['group_order_fee'] ?? 0;
  196. }
  197. // 单个商户订单运费计算
  198. $item['mch_order_info']['shipping_type'] = $form['data']['shipping_type'] ?? ShippingTypeEnum::LOGISTICS;
  199. $item['mch_order_info']['shipping_money'] = $group_order_fee;
  200. }
  201. } else {
  202. //快递方式为送货上门
  203. $store_info = $form->store_info;
  204. //不存在门店信息时,跳过
  205. if (empty($store_info)) return $item;
  206. //门店为不在范围时,不进行运费计算
  207. if ($store_info['disabled']) return $item;
  208. //判断门店是否使用独立配置
  209. if ($store_info['intra_city_distribution']['is_alone'] == 1) {
  210. //当前门店是否免配送费
  211. if ($store_info['intra_city_distribution']['delivery_fee'] == 0) {
  212. $group_order_fee += 0;
  213. } else {
  214. $group_order_fee += $store_info['intra_city_distribution']['distance_charge'];
  215. //如果配送距离超出初始配送距离,则需累计计算每次增加的公里数对应的配送费
  216. if ($store_info['intra_city_distribution']['distance'] < $store_info['distance']) {
  217. $distance = $store_info['distance'] - $store_info['intra_city_distribution']['distance'] ;
  218. if ($store_info['intra_city_distribution']['every_distance'] > 0) {
  219. $group_order_fee += ceil($distance/$store_info['intra_city_distribution']['every_distance']) * $store_info['intra_city_distribution']['every_distance_charge'];
  220. }
  221. }
  222. }
  223. } else {
  224. //获取幸福小店插件基础配置
  225. $setting = \Yii::$app->services->setting->addons->get($item['mch_order_info']['mall_id'], HappinessEnum::ADDONS_NAME, 'basic');
  226. //基础配置是否免配送
  227. if ($setting['delivery_fee'] == 0) {
  228. $group_order_fee += 0;
  229. } else {
  230. $group_order_fee += $setting['start_amount'];
  231. if ($setting['start_limit'] < $store_info['distance']) {
  232. $distance = $store_info['distance'] - $setting['start_limit'];
  233. if ($setting['add_limit'] > 0) {
  234. $group_order_fee += ceil($distance/$setting['add_limit']) * $setting['add_amount'];
  235. }
  236. }
  237. }
  238. }
  239. $shipping_fee = $group_order_fee;
  240. $item['mch_order_info']['shipping_money'] = $group_order_fee;
  241. }
  242. // 运费赋值
  243. $item['mch_order_info']['pay_money'] = $item['mch_order_info']['pay_money'] + $shipping_fee;
  244. $prev_shipping_fee = $form->marketing['deduct']['shipping_fee']['money'] ?? 0;
  245. $form->marketing['deduct']['shipping_fee'] = ['name' => '运费', 'money' => $prev_shipping_fee + $shipping_fee];
  246. return $item;
  247. }
  248. /**
  249. * 判断收货地址是否在地区中
  250. * @Author: lun
  251. * @DateTime: 2021/9/30 0030 14:45
  252. * @Copyright: copyright (c) 2021 广东七件事集团
  253. * @param $address
  254. * @param $areaArr
  255. * @return bool
  256. */
  257. private static function isInArea($address, $areaArr)
  258. {
  259. $return = false;
  260. if(in_array($address['province_id'], $areaArr) || in_array($address['city_id'], $areaArr) || in_array($address['district_id'], $areaArr)) $return = true;
  261. return $return;
  262. }
  263. /**
  264. * 运费模板为:按同件商品计算
  265. * 目的:不同商品使用不同的模板情况下的混合计算,主要算出:首件/重 + 续件/重 运费费用
  266. * @Author: lun
  267. * @DateTime: 2023/8/12 0012 17:11
  268. * @Copyright: copyright (c) 2021 广东七件事集团
  269. * @param $rule_id
  270. * @param $rule
  271. * @param $goods_weight_or_num
  272. * @param array $data
  273. */
  274. private static function getShopFee($rule_id, $type, $rule, $goods_weight_or_num, &$data = [])
  275. {
  276. $data[$rule_id]['total_weight_or_num'] = empty($data[$rule_id]['total_weight_or_num']) ? $goods_weight_or_num : $data[$rule_id]['total_weight_or_num'] + $goods_weight_or_num;
  277. if (empty($data[$rule_id]['first_fee'])) $data[$rule_id]['first_fee'] = $rule['firstPrice'];// 首重/件 费用
  278. if (empty($data[$rule_id]['group_first_fee'])) $data[$rule_id]['group_first_fee'] = $rule['firstPrice'];// 首重/件 费用
  279. if (empty($data[$rule_id]['first_weight_or_num'])) $data[$rule_id]['first_weight_or_num'] = $rule['first'];// 同模板商品首重
  280. if (empty($data[$rule_id]['renew_price'])) $data[$rule_id]['renew_price'] = 0;// 续重/件 费用
  281. $surplus_weight_or_num = $data[$rule_id]['total_weight_or_num'] - $data[$rule_id]['first_weight_or_num'];// 总重量/总数量 - 首重/件 = 续重/件
  282. // 续重/件运费 = (剩余重量 / 续件重量)[向上取整] * 续重费用 [向下取整]
  283. if($surplus_weight_or_num > 0 && isset($rule['second']) && $rule['second'] > 0) {
  284. if ($type == 2) {// 按件计费
  285. $fee = $rule['secondPrice'] * floor($surplus_weight_or_num / $rule['second']);
  286. } else {// 按重计费
  287. $fee = floor(ceil($surplus_weight_or_num / $rule['second']) * $rule['secondPrice']);
  288. }
  289. $data[$rule_id]['renew_price'] = $fee;// 每次都覆盖之前的值,为了重新算出最后的续重/件费用
  290. }
  291. }
  292. /**
  293. * 将计算好的不同运费模板运费加起来
  294. * @Author: lun
  295. * @DateTime: 2023/8/12 0012 17:13
  296. * @Copyright: copyright (c) 2021 广东七件事集团
  297. * @param $shopFeeList
  298. * @return array
  299. */
  300. private static function shopFeeAssignment($shopFeeList)
  301. {
  302. $shop_fee = 0;
  303. $group_order_fee = 0;
  304. foreach ($shopFeeList as $item) {
  305. $shop_fee += $item['first_fee'] + $item['renew_price'];
  306. $group_order_fee += $item['group_first_fee'] + $item['renew_price'];
  307. }
  308. return ['shipping_fee' => $shop_fee, 'group_order_fee' => $group_order_fee];
  309. }
  310. /**
  311. * 计算阶梯价格
  312. *
  313. * @param array $freightValue
  314. * @param array $good
  315. * @param int $type
  316. * @param int $computeType
  317. * @param array $rule
  318. * @param $form
  319. *
  320. * @return array
  321. */
  322. private static function computeLadderCondition(
  323. array $freightValue,
  324. array $good,
  325. int $type,
  326. int $computeType,
  327. array $rule,
  328. $form
  329. ): array
  330. {
  331. $res = ['is_success' => false, 'money' => 0];
  332. !isset($freightValue[$computeType]) && $freightValue[$computeType] = [];
  333. // 如果是按重,则需要转为kg
  334. if ($type == 1) {
  335. foreach ($freightValue[$computeType] as &$value) {
  336. $value = bcdiv($value, 1000, 2);
  337. }
  338. }
  339. switch ($computeType) {
  340. case 1: // 按不同商品计算
  341. $goodsId = $good['goods_id'];
  342. $resValue = $freightValue[$computeType][$goodsId] ?? 0;
  343. break;
  344. case 2: // 按同件商品计算
  345. $freightId = $form['sku'][$good['goods_id']]['goods']['freight_id'];
  346. $resValue = $freightValue[$computeType][$freightId] ?? 0;
  347. break;
  348. default:
  349. return $res;
  350. }
  351. $checkLadderConditionRes = self::checkLadderCondition($rule['ladder_condition'], $resValue);
  352. if ($checkLadderConditionRes['res']) {
  353. $res['is_success'] = true;
  354. $res['money'] = $checkLadderConditionRes['money'];
  355. }
  356. return $res;
  357. }
  358. /**
  359. * @param array $ladderCondition
  360. * @param $value
  361. *
  362. * @return array
  363. */
  364. protected static function checkLadderCondition(array $ladderCondition, $value): array
  365. {
  366. // 根据$ladderCondition的condition_value字段按从大到小排序
  367. usort($ladderCondition, function ($a, $b) {
  368. return $b['condition_value'] <=> $a['condition_value'];
  369. });
  370. foreach ($ladderCondition as $condition) {
  371. if ($value >= $condition['condition_value']) {
  372. return ['res' => true, 'money' => $condition['money']];
  373. }
  374. }
  375. return ['res' => false];
  376. }
  377. /**
  378. * 按计费方式和计算方式将值归类
  379. *
  380. * @param $goods
  381. * @param $form
  382. * @param $freightRules
  383. *
  384. * @return array
  385. */
  386. private static function getFreightValue($goods, $form, $freightRules): array
  387. {
  388. $freightValue = [];
  389. // 把相同运费模板的商品合并,根据选择的计费方式
  390. // 如果是按相同商品计算时,就需要保存选择了当前模板的商品
  391. foreach ($goods as $good_id => $good) {
  392. $freightId = $form['sku'][$good_id]['goods']['freight_id'];
  393. // 根据商品id获取对应的规则
  394. $thisRules = $freightRules[$freightId] ?? [];
  395. if (empty($thisRules)) {
  396. continue;
  397. }
  398. if ($thisRules['type'] == 1) {// 按重计费
  399. // 查询重量
  400. $new_num_weight = $form['sku'][$good_id]['weight'] * $good['num'];
  401. } else {// 按件计费
  402. $new_num_weight = $good['num'];
  403. }
  404. switch ($thisRules['compute_type']) {
  405. case 1: // 按不同商品计算
  406. !isset($freightValue[$thisRules['compute_type']][$good_id]) && $freightValue[$thisRules['compute_type']][$good_id] = 0;
  407. $freightValue[$thisRules['compute_type']][$good_id] += $new_num_weight;
  408. break;
  409. case 2: // 按同件商品计算
  410. !isset($freightValue[$thisRules['compute_type']][$freightId]) && $freightValue[$thisRules['compute_type']][$freightId] = 0;
  411. $freightValue[$thisRules['compute_type']][$freightId] += $new_num_weight;
  412. break;
  413. }
  414. }
  415. return $freightValue;
  416. }
  417. }