Express.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  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. private bool $enabledFullFree = false;
  36. /**
  37. * 构造方法
  38. * Express constructor.
  39. * @param $cityId
  40. * @param $goodsList
  41. * @throws \think\db\exception\DataNotFoundException
  42. * @throws \think\db\exception\DbException
  43. * @throws \think\db\exception\ModelNotFoundException
  44. */
  45. public function __construct($cityId, $goodsList)
  46. {
  47. parent::__construct();
  48. // 赋值传参
  49. $this->cityId = $cityId;
  50. $this->goodsList = $goodsList;
  51. // 整合运费模板
  52. $this->initDeliveryTemplate();
  53. }
  54. /**
  55. * 验证用户收货地址是否在配送范围
  56. * @return bool
  57. */
  58. public function isIntraRegion(): bool
  59. {
  60. if (!$this->cityId) {
  61. return false;
  62. }
  63. foreach ($this->data as $item) {
  64. $cityIds = [];
  65. foreach ($item['delivery']['rule'] as $ruleItem) {
  66. $cityIds = \array_merge($cityIds, $ruleItem['region']);
  67. }
  68. if (!in_array($this->cityId, $cityIds)) {
  69. $this->notInRuleGoodsId = current($item['goodsList'])['goods_id'];
  70. return false;
  71. }
  72. }
  73. return true;
  74. }
  75. /**
  76. * 获取不在配送范围的商品名称
  77. * @return null
  78. */
  79. public function getNotInRuleGoodsName()
  80. {
  81. $item = helper::getArrayItemByColumn($this->goodsList, 'goods_id', $this->notInRuleGoodsId);
  82. return !empty($item) ? $item['goods_name'] : null;
  83. }
  84. /**
  85. * 获取订单的配送费用
  86. * @param bool $allowFullFree 是否参与满额包邮
  87. * @return string
  88. * @throws \think\db\exception\DataNotFoundException
  89. * @throws \think\db\exception\DbException
  90. * @throws \think\db\exception\ModelNotFoundException
  91. */
  92. public function getDeliveryFee(bool $allowFullFree): string
  93. {
  94. if (empty($this->cityId) || empty($this->goodsList) || $this->notInRuleGoodsId > 0) {
  95. return helper::number2(0.00);
  96. }
  97. // 处理满额包邮
  98. $this->freeShipping($allowFullFree);
  99. // 计算配送金额
  100. foreach ($this->data as &$item) {
  101. // 计算当前配送模板的运费
  102. $item['delivery_fee'] = $this->calcDeliveryAmount($item);
  103. }
  104. // 根据运费组合策略获取最终运费金额
  105. return helper::number2($this->getFinalFreight());
  106. }
  107. /**
  108. * 获取是否启用了满额包邮
  109. * @return bool
  110. */
  111. public function getEnabledFullFree(): bool
  112. {
  113. return $this->enabledFullFree;
  114. }
  115. /**
  116. * 根据运费组合策略 计算最终运费
  117. * @return float|int|mixed
  118. * @throws \think\db\exception\DataNotFoundException
  119. * @throws \think\db\exception\DbException
  120. * @throws \think\db\exception\ModelNotFoundException
  121. */
  122. private function getFinalFreight()
  123. {
  124. // 运费合集
  125. $expressPriceArr = helper::getArrayColumn($this->data, 'delivery_fee');
  126. if (empty($expressPriceArr)) {
  127. return 0.00;
  128. }
  129. // 最终运费金额
  130. $expressPrice = 0.00;
  131. // 判断运费组合策略
  132. switch (SettingModel::getItem('trade')['freight_rule']) {
  133. case '10': // 策略1: 叠加
  134. $expressPrice = \array_sum($expressPriceArr);
  135. break;
  136. case '20': // 策略2: 以最低运费结算
  137. $expressPrice = \min($expressPriceArr);
  138. break;
  139. case '30': // 策略3: 以最高运费结算
  140. $expressPrice = \max($expressPriceArr);
  141. break;
  142. }
  143. return $expressPrice;
  144. }
  145. /**
  146. * 商品满额包邮
  147. * @param bool $allowFullFree 是否参与满额包邮
  148. * @return void
  149. * @throws \think\db\exception\DataNotFoundException
  150. * @throws \think\db\exception\DbException
  151. * @throws \think\db\exception\ModelNotFoundException
  152. */
  153. private function freeShipping(bool $allowFullFree): void
  154. {
  155. // 设置默认数据:包邮的商品列表
  156. helper::setDataAttribute($this->data, ['free_goods_list' => []], true);
  157. // 订单商品总金额
  158. $orderTotalPrice = helper::getArrayColumnSum($this->goodsList, 'total_price');
  159. // 获取满额包邮设置
  160. $options = SettingModel::getItem(SettingEnum::FULL_FREE);
  161. // 判断是否满足条件
  162. if (
  163. !$allowFullFree
  164. || !$options['is_open']
  165. || $orderTotalPrice < $options['money']
  166. || \in_array($this->cityId, $options['excludedRegions']['cityIds'])
  167. ) {
  168. return;
  169. }
  170. // 记录包邮的商品
  171. foreach ($this->data as &$item) {
  172. $item['free_goods_list'] = [];
  173. foreach ($item['goodsList'] as $goodsItem) {
  174. if (!\in_array($goodsItem['goods_id'], $options['excludedGoodsIds'])) {
  175. $item['free_goods_list'][] = $goodsItem['goods_id'];
  176. }
  177. }
  178. }
  179. // 记录已启用满额包邮
  180. $this->enabledFullFree = true;
  181. }
  182. /**
  183. * 计算当前配送模板的运费
  184. * @param $item
  185. * @return float|mixed|string
  186. */
  187. private function calcDeliveryAmount($item)
  188. {
  189. // 获取运费模板下商品总数量or总重量
  190. if (!$totality = $this->getItemGoodsTotal($item)) {
  191. return 0.00;
  192. }
  193. // 当前收货城市配送规则
  194. $deliveryRule = $this->getCityDeliveryRule($item['delivery']);
  195. if ($totality <= $deliveryRule['first']) {
  196. return $deliveryRule['first_fee'];
  197. }
  198. // 续件or续重 数量
  199. $additional = helper::bcsub($totality, $deliveryRule['first']);
  200. if ($additional <= $deliveryRule['additional']) {
  201. return helper::bcadd($deliveryRule['first_fee'], $deliveryRule['additional_fee']);
  202. }
  203. // 续重总费用
  204. $additionalFee = 0.00;
  205. // 计算续重/件金额
  206. if ($deliveryRule['additional'] > 0) {
  207. $additionalFee = \ceil($additional / $deliveryRule['additional']) * $deliveryRule['additional_fee'];
  208. }
  209. return helper::bcadd($deliveryRule['first_fee'], $additionalFee);
  210. }
  211. /**
  212. * 获取运费模板下商品总数量or总重量
  213. * @param $item
  214. * @return int|string
  215. */
  216. private function getItemGoodsTotal($item)
  217. {
  218. $totalWeight = 0; // 总重量
  219. $totalNum = 0; // 总数量
  220. foreach ($item['goodsList'] as $goodsItem) {
  221. // 如果商品为包邮,则不计算总量中
  222. if (!\in_array($goodsItem['goods_id'], $item['free_goods_list'])) {
  223. $goodsWeight = helper::bcmul($goodsItem['skuInfo']['goods_weight'], $goodsItem['total_num']);
  224. $totalWeight = helper::bcadd($totalWeight, $goodsWeight);
  225. $totalNum = helper::bcadd($totalNum, $goodsItem['total_num']);
  226. }
  227. }
  228. return $item['delivery']['method'] == DeliveryMethodEnum::QUANTITY ? $totalNum : $totalWeight;
  229. }
  230. /**
  231. * 根据城市id获取规则信息
  232. * @param
  233. * @return array|false
  234. */
  235. private function getCityDeliveryRule($delivery)
  236. {
  237. foreach ($delivery['rule'] as $item) {
  238. if (\in_array($this->cityId, $item['region'])) {
  239. return $item;
  240. }
  241. }
  242. return false;
  243. }
  244. /**
  245. * 整合运费模板
  246. * @return void
  247. * @throws \think\db\exception\DataNotFoundException
  248. * @throws \think\db\exception\DbException
  249. * @throws \think\db\exception\ModelNotFoundException
  250. */
  251. private function initDeliveryTemplate(): void
  252. {
  253. // 运费模板ID集
  254. $deliveryIds = $this->getDeliveryIds();
  255. if (empty($deliveryIds)) {
  256. return;
  257. }
  258. // 运费模板列表
  259. $deliveryList = (new DeliveryModel)->getListByIds($deliveryIds);
  260. // 整理数据集
  261. foreach ($deliveryList as $item) {
  262. $this->data[$item['delivery_id']]['delivery'] = $item;
  263. $this->data[$item['delivery_id']]['goodsList'] = $this->getGoodsListByDeliveryId($item['delivery_id']);
  264. }
  265. }
  266. /**
  267. * 运费模板ID集
  268. * @return array
  269. */
  270. private function getDeliveryIds(): array
  271. {
  272. $deliveryIds = helper::getArrayColumn($this->goodsList, 'delivery_id');
  273. return \array_values(array_unique(array_filter($deliveryIds)));
  274. }
  275. /**
  276. * 根据运费模板id整理商品集
  277. * @param $deliveryId
  278. * @return array
  279. */
  280. private function getGoodsListByDeliveryId($deliveryId): array
  281. {
  282. $data = [];
  283. foreach ($this->goodsList as $item) {
  284. $item['delivery_id'] == $deliveryId && $data[] = $item;
  285. }
  286. return $data;
  287. }
  288. }