Express.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2017~2024 https://www.yiovo.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
  8. // +----------------------------------------------------------------------
  9. // | Author: 萤火科技 <admin@yiovo.com>
  10. // +----------------------------------------------------------------------
  11. declare (strict_types=1);
  12. namespace app\common\service\delivery;
  13. use app\common\library\helper;
  14. use app\common\model\Delivery as DeliveryModel;
  15. use app\common\model\store\Setting as SettingModel;
  16. use app\common\enum\Setting as SettingEnum;
  17. use app\common\enum\delivery\Method as DeliveryMethodEnum;
  18. use app\common\service\BaseService;
  19. /**
  20. * 快递配送服务类
  21. * Class Delivery
  22. * @package app\common\service
  23. */
  24. class Express extends BaseService
  25. {
  26. // 用户收货城市id
  27. private int $cityId;
  28. // 订单商品列表
  29. private iterable $goodsList;
  30. // 不在配送范围的商品ID
  31. private ?int $notInRuleGoodsId = null;
  32. // 运费模板数据集
  33. private array $data = [];
  34. /**
  35. * 构造方法
  36. * Express constructor.
  37. * @param $cityId
  38. * @param $goodsList
  39. * @throws \think\db\exception\DataNotFoundException
  40. * @throws \think\db\exception\DbException
  41. * @throws \think\db\exception\ModelNotFoundException
  42. */
  43. public function __construct($cityId, $goodsList)
  44. {
  45. parent::__construct();
  46. // 赋值传参
  47. $this->cityId = $cityId;
  48. $this->goodsList = $goodsList;
  49. // 整合运费模板
  50. $this->initDeliveryTemplate();
  51. }
  52. /**
  53. * 验证用户收货地址是否在配送范围
  54. * @return bool
  55. */
  56. public function isIntraRegion(): bool
  57. {
  58. if (!$this->cityId) {
  59. return false;
  60. }
  61. foreach ($this->data as $item) {
  62. $cityIds = [];
  63. foreach ($item['delivery']['rule'] as $ruleItem) {
  64. $cityIds = array_merge($cityIds, $ruleItem['region']);
  65. }
  66. if (!in_array($this->cityId, $cityIds)) {
  67. $this->notInRuleGoodsId = current($item['goodsList'])['goods_id'];
  68. return false;
  69. }
  70. }
  71. return true;
  72. }
  73. /**
  74. * 获取不在配送范围的商品名称
  75. * @return null
  76. */
  77. public function getNotInRuleGoodsName()
  78. {
  79. $item = helper::getArrayItemByColumn($this->goodsList, 'goods_id', $this->notInRuleGoodsId);
  80. return !empty($item) ? $item['goods_name'] : null;
  81. }
  82. /**
  83. * 获取订单的配送费用
  84. * @return string
  85. * @throws \think\db\exception\DataNotFoundException
  86. * @throws \think\db\exception\DbException
  87. * @throws \think\db\exception\ModelNotFoundException
  88. */
  89. public function getDeliveryFee(): string
  90. {
  91. if (empty($this->cityId) || empty($this->goodsList) || $this->notInRuleGoodsId > 0) {
  92. return helper::number2(0.00);
  93. }
  94. // 处理商品包邮
  95. $this->freeshipping();
  96. // 计算配送金额
  97. foreach ($this->data as &$item) {
  98. // 计算当前配送模板的运费
  99. $item['delivery_fee'] = $this->calcDeliveryAmount($item);
  100. }
  101. // 根据运费组合策略获取最终运费金额
  102. return helper::number2($this->getFinalFreight());
  103. }
  104. /**
  105. * 根据运费组合策略 计算最终运费
  106. * @return float|int|mixed
  107. * @throws \think\db\exception\DataNotFoundException
  108. * @throws \think\db\exception\DbException
  109. * @throws \think\db\exception\ModelNotFoundException
  110. */
  111. private function getFinalFreight()
  112. {
  113. // 运费合集
  114. $expressPriceArr = helper::getArrayColumn($this->data, 'delivery_fee');
  115. if (empty($expressPriceArr)) {
  116. return 0.00;
  117. }
  118. // 最终运费金额
  119. $expressPrice = 0.00;
  120. // 判断运费组合策略
  121. switch (SettingModel::getItem('trade')['freight_rule']) {
  122. case '10': // 策略1: 叠加
  123. $expressPrice = array_sum($expressPriceArr);
  124. break;
  125. case '20': // 策略2: 以最低运费结算
  126. $expressPrice = min($expressPriceArr);
  127. break;
  128. case '30': // 策略3: 以最高运费结算
  129. $expressPrice = max($expressPriceArr);
  130. break;
  131. }
  132. return $expressPrice;
  133. }
  134. /**
  135. * 商品满额包邮
  136. * @return void
  137. * @throws \think\db\exception\DataNotFoundException
  138. * @throws \think\db\exception\DbException
  139. * @throws \think\db\exception\ModelNotFoundException
  140. */
  141. private function freeshipping(): void
  142. {
  143. // 订单商品总金额
  144. $orderTotalPrice = helper::getArrayColumnSum($this->goodsList, 'total_price');
  145. // 获取满额包邮设置
  146. $options = SettingModel::getItem(SettingEnum::FULL_FREE);
  147. foreach ($this->data as &$item) {
  148. $item['free_goods_list'] = [];
  149. foreach ($item['goodsList'] as $goodsItem) {
  150. if (
  151. $options['is_open']
  152. && $orderTotalPrice >= $options['money']
  153. && !in_array($goodsItem['goods_id'], $options['excludedGoodsIds'])
  154. && !in_array($this->cityId, $options['excludedRegions']['cityIds'])
  155. ) {
  156. $item['free_goods_list'][] = $goodsItem['goods_id'];
  157. }
  158. }
  159. }
  160. }
  161. /**
  162. * 计算当前配送模板的运费
  163. * @param $item
  164. * @return float|mixed|string
  165. */
  166. private function calcDeliveryAmount($item)
  167. {
  168. // 获取运费模板下商品总数量or总重量
  169. if (!$totality = $this->getItemGoodsTotal($item)) {
  170. return 0.00;
  171. }
  172. // 当前收货城市配送规则
  173. $deliveryRule = $this->getCityDeliveryRule($item['delivery']);
  174. if ($totality <= $deliveryRule['first']) {
  175. return $deliveryRule['first_fee'];
  176. }
  177. // 续件or续重 数量
  178. $additional = $totality - $deliveryRule['first'];
  179. if ($additional <= $deliveryRule['additional']) {
  180. return helper::bcadd($deliveryRule['first_fee'], $deliveryRule['additional_fee']);
  181. }
  182. // 计算续重/件金额
  183. if ($deliveryRule['additional'] < 1) {
  184. // 配送规则中续件为0
  185. $additionalFee = 0.00;
  186. } else {
  187. $additionalFee = helper::bcdiv($deliveryRule['additional_fee'], $deliveryRule['additional']) * $additional;
  188. }
  189. return helper::bcadd($deliveryRule['first_fee'], $additionalFee);
  190. }
  191. /**
  192. * 获取运费模板下商品总数量or总重量
  193. * @param $item
  194. * @return int|string
  195. */
  196. private function getItemGoodsTotal($item)
  197. {
  198. $totalWeight = 0; // 总重量
  199. $totalNum = 0; // 总数量
  200. foreach ($item['goodsList'] as $goodsItem) {
  201. // 如果商品为包邮,则不计算总量中
  202. if (!in_array($goodsItem['goods_id'], $item['free_goods_list'])) {
  203. $goodsWeight = helper::bcmul($goodsItem['skuInfo']['goods_weight'], $goodsItem['total_num']);
  204. $totalWeight = helper::bcadd($totalWeight, $goodsWeight);
  205. $totalNum = helper::bcadd($totalNum, $goodsItem['total_num']);
  206. }
  207. }
  208. return $item['delivery']['method'] == DeliveryMethodEnum::QUANTITY ? $totalNum : $totalWeight;
  209. }
  210. /**
  211. * 根据城市id获取规则信息
  212. * @param
  213. * @return array|false
  214. */
  215. private function getCityDeliveryRule($delivery)
  216. {
  217. foreach ($delivery['rule'] as $item) {
  218. if (in_array($this->cityId, $item['region'])) {
  219. return $item;
  220. }
  221. }
  222. return false;
  223. }
  224. /**
  225. * 整合运费模板
  226. * @return void
  227. * @throws \think\db\exception\DataNotFoundException
  228. * @throws \think\db\exception\DbException
  229. * @throws \think\db\exception\ModelNotFoundException
  230. */
  231. private function initDeliveryTemplate(): void
  232. {
  233. // 运费模板ID集
  234. $deliveryIds = $this->getDeliveryIds();
  235. if (empty($deliveryIds)) {
  236. return;
  237. }
  238. // 运费模板列表
  239. $deliveryList = (new DeliveryModel)->getListByIds($deliveryIds);
  240. // 整理数据集
  241. foreach ($deliveryList as $item) {
  242. $this->data[$item['delivery_id']]['delivery'] = $item;
  243. $this->data[$item['delivery_id']]['goodsList'] = $this->getGoodsListByDeliveryId($item['delivery_id']);
  244. }
  245. }
  246. /**
  247. * 运费模板ID集
  248. * @return array
  249. */
  250. private function getDeliveryIds(): array
  251. {
  252. $deliveryIds = helper::getArrayColumn($this->goodsList, 'delivery_id');
  253. return array_values(array_unique(array_filter($deliveryIds)));
  254. }
  255. /**
  256. * 根据运费模板id整理商品集
  257. * @param $deliveryId
  258. * @return array
  259. */
  260. private function getGoodsListByDeliveryId($deliveryId): array
  261. {
  262. $data = [];
  263. foreach ($this->goodsList as $item) {
  264. $item['delivery_id'] == $deliveryId && $data[] = $item;
  265. }
  266. return $data;
  267. }
  268. }