* @created 2022-07-22 17:58:14 * @modified 2022-07-22 17:58:14 */ namespace Beike\Shop\Services\TotalServices; use Beike\Libraries\Weight; use Beike\Models\Country; use Beike\Models\Logistics; use Beike\Models\Product; use Beike\Repositories\LogisticsRepo; use Beike\Shop\Services\CheckoutService; use Illuminate\Support\Str; class ShippingService{ protected static $country;// 国家信息 protected static $productsList;// 商品分组统计列表 // 运费计算 public static function getTotal(CheckoutService $checkout): ?array{ // 计算运费 $totalService = $checkout->totalService; $shippingMethod = $totalService->getShippingMethod(); $amount = 0; $logisticsInfo = [];// 物流信息 self::$country = LogisticsRepo::getDefaultCountries(true);// 国家信息 $showTips = 0;// 是否显示【待协商】提示 if($shippingMethod && !is_int($checkout->cart->shipping_method_code)){ // 通过插件(固定运费) 进行计算 if(empty($shippingMethod)) return NULL; $shippingPluginCode = self::parseShippingPluginCode($shippingMethod); $pluginCode = Str::studly($shippingPluginCode); if(!app('plugin')->checkActive($shippingPluginCode)){ $cart = $checkout->cart; $cart->shipping_method_code = ''; $cart->saveOrFail(); return []; } $className = "Plugin\\{$pluginCode}\\Bootstrap"; if(!method_exists($className,'getShippingFee')){ throw new \Exception("请在插件 {$className} 实现方法: public function getShippingFee(CheckoutService \$checkout)"); } $amount = (float)(new $className)->getShippingFee($checkout); } else{ // 商品信息处理 获取同一个商品的数量、重量 $products = $totalService->getCartProducts(); self::$productsList = collect($products)->groupBy('product_id')->map(function($group){ $firstInfo = $group->first(); $productWeightInfo = Product::query() ->select(['id','weight','weight_class','length','width','height']) ->find($firstInfo['product_id']); if($productWeightInfo) $productWeightInfo = $productWeightInfo->toArray(); // 重量转换 - 物流重量单位为 千克;商品重量需要进行转换 $weight = $productWeightInfo['weight'] ?? 0; $sumQuantity = $group->sum('quantity'); // 判断:如果是按批卖 则数量需要除以每批的数量 在计算总重量。按件卖则直接计算 $weightQuantity = $firstInfo['sales_method'] == 'batches' ? ($sumQuantity / $firstInfo['piece_to_batch']) : $sumQuantity;// 多少件/多少批 根据销售方式获取 $sumWeight = (float)sprintf("%.2f",($weight * $weightQuantity)); $weightClass = $productWeightInfo['weight_class']; $sumWeight = Weight::convert($sumWeight,$weightClass);// 总重量 单位:克 // 获取 体积重 (长cm*宽cm*高cm)*数量/抛比=体积重 $volumeWeight = ($productWeightInfo['length'] * $productWeightInfo['width'] * $productWeightInfo['height']) * $weightQuantity;// 还需要除以计抛比 return [ 'product_id' => $firstInfo['product_id'], 'sum_quantity' => $sumQuantity, 'sum_weight' => $sumWeight, 'volume_weight' => $volumeWeight, 'weight' => $weight, 'weight_class' => $weightClass, ]; }); // 通过物流进行计算 $logisticsId = (int)$checkout->cart->shipping_method_code; $logisticsInfo = self::getLogistics($logisticsId,$checkout->cart); if($logisticsInfo) $amount = $logisticsInfo['amount'];// 存在物流 获取运费 else $showTips = 1;// 如果还是不能获取物流信息 则显示 待协商 } $totalData = [ 'code' => 'shipping', 'title' => trans('shop/carts.shipping_fee'), 'amount' => $amount, 'amount_format' => currency_format($amount), 'amount_tips' => trans('shop/carts.to_be_negotiated'), 'show_tips' => $showTips, 'country' => self::$country,// 国家信息 'logistics' => $logisticsInfo,// 物流信息 ]; $totalService->amount += $totalData['amount']; $totalService->totals[] = $totalData; return $totalData; } /** * Common: 运费计算 - 物流运费 - 获取使用的物流 * Author: wu-hui * Time: 2023/08/22 11:55 * @param $logisticsId * @param $cart * @return array|mixed|mixed[] */ private static function getLogistics($logisticsId,$cart){ $logisticsField = ['id','type', 'name','first_weight','first_weight_fee','continuation_weight_max','add_weight','continuation_weight_fee','num_fee','throwing_ratio']; // 判断:客服端是否切换国家 如果存在切换的国家id 则使用切换的国家id;不存在则继续执行 $countryId = (int)request()->input('products_country_id'); if($countryId > 0) { // 切换国家或者物流 // 仅存在国家ID,直接获取当前国家全部物流信息,取其中一个 // 同时存在国家ID和物流ID 则获取对应物流ID;如果未查询到该物流 则执行仅存在国家ID时的操作 if($logisticsId) { // 同时存在国家ID 获取物流ID $logisticsInfo = Logistics::query() ->whereRaw(\DB::raw('FIND_IN_SET('.$countryId.',country_ids)')) ->with(['weights']) ->where('id',$logisticsId) ->select($logisticsField) ->first(); if($logisticsInfo) { // 刷新国家信息 self::refreshCurrentCountry($countryId); $logisticsList = [$logisticsInfo->toArray()]; goto logisticsListHandle; } } // 仅存在国家ID时 goto CountryGetLogistics; } // 不存在切换国家id 正常流程 if($logisticsId <= 0){ // 获取当前收货地址国家ID $cartArr = $cart->toArray(); $address = $cart->guest_shipping_address ?? $cart->guest_payment_address ?? $cartArr['payment_address'] ?? $cartArr['shipping_address']; if($address) $countryId = (int)$address['country_id'];// 存在收货地址(用户已经登录 || 用户设置收货地址) else $countryId = (int)self::$country['id'];// 不存在收货地址(用户未登录 || 用户已登录但是未设置收货地址) 获取默认国家收货地址 // 获取全部物流 返回第一个物流信息ID CountryGetLogistics: $logisticsList = Logistics::getAll($countryId,$logisticsField); // 刷新国家信息 self::refreshCurrentCountry($countryId); }else{ // 获取物流信息 $logisticsInfo = Logistics::query() ->with(['weights']) ->select($logisticsField) ->find($logisticsId); $logisticsList = [$logisticsInfo ? $logisticsInfo->toArray() : []]; } // 根据已查询的物流列表 筛选出符合条件的物流并且返回第一个 logisticsListHandle: $eligibleLogisticsList = Logistics::LogisticsFiltering($logisticsList,self::$productsList); // 循环计算物流运费 foreach($eligibleLogisticsList as &$eligibleLogisticsListItem){ // weight:按重量计费,num:按数量计费,free:卖家包邮 $logisticsHandleFun = 'compute_'.$eligibleLogisticsListItem['type']; $eligibleLogisticsListItem['amount'] = (float)self::$logisticsHandleFun($eligibleLogisticsListItem); } // 存在多个物流 需要获取运费最小的物流 if(count($eligibleLogisticsList) > 1){ $amounts = array_column($eligibleLogisticsList,'amount'); array_multisort($amounts,SORT_ASC,$eligibleLogisticsList); } return $eligibleLogisticsList[0] ?? []; } /** * Common: 运费计算 - 物流运费 - 运费计算(按重量计费) * Author: wu-hui * Time: 2023/08/22 15:34 * @param $logisticsInfo * @return float|int */ private static function compute_weight($logisticsInfo){ /** * 计算规则: * 总重量 - 低于首重;则运费 = 【首重运费】 * 总重量 - 高于首重;则运费 = 首重按照【首重运费】计算;(剩余重量 / 每增加重量 ) * 续重运费 */ $amount = (int)0; $firstWeight = (float)$logisticsInfo['first_weight'];// 首重 $firstWeightFee = (float)$logisticsInfo['first_weight_fee'];// 首重运费 $addWeight = (float)$logisticsInfo['add_weight'];// 每增加重量 $continuationWeightFee = (float)$logisticsInfo['continuation_weight_fee'];// 续重运费 // 循环处理商品 foreach(self::$productsList as $productInfo){ // 计算体积重 并且取数值大的作为重量计算 $volumeWeight = $productInfo['volume_weight'] / $logisticsInfo['throwing_ratio']; $volumeWeight = (float)sprintf("%.2f",Weight::convert($volumeWeight,$productInfo['weight_class'])); $sumWeight = $productInfo['sum_weight'] > $volumeWeight ? $productInfo['sum_weight'] : $volumeWeight; // 首重运费 $amount += (float)sprintf("%.2f",$firstWeightFee); // 判断:如果总重量超过首重 则计算续重运费 $surplus = (float)$sumWeight - $firstWeight; if($surplus > 0) $amount += (float)sprintf("%.2f",(ceil($surplus / $addWeight)) * $continuationWeightFee); } return $amount; } /** * Common: 运费计算 - 物流运费 - 运费计算(按数量计费) * Author: wu-hui * Time: 2023/08/22 14:47 * @param $logisticsInfo * @return float|int */ private static function compute_num($logisticsInfo){ /** * 计算规则: * 购买数量 - 低于首件数量;则运费 = 【首件运费】 * 购买数量 - 高于首件数量;则运费 = 首件数量按照【首件运费】计算;(剩余购买数量 / 每增加件数量 ) * 续件运费 */ $amount = (int)0; $firstWeight = (int)$logisticsInfo['first_weight'];// 首件数量 $firstWeightFee = (float)$logisticsInfo['first_weight_fee'];// 首件运费 $addWeight = (int)$logisticsInfo['add_weight'];// 每增加件 $continuationWeightFee = (float)$logisticsInfo['continuation_weight_fee'];// 续件运费 // 循环处理商品 foreach(self::$productsList as $productInfo){ // 首件运费 $amount += (float)sprintf("%.2f",$firstWeightFee); // 判断:如果数量超过首件数量 则计算续件运费 $surplus = (int)$productInfo['sum_quantity'] - $firstWeight; if($surplus > 0) $amount += (float)sprintf("%.2f",($surplus / $addWeight) * $continuationWeightFee); } return $amount; } /** * Common: 运费计算 - 物流运费 - 运费计算(卖家包邮) * Author: wu-hui * Time: 2023/08/22 11:59 * @param $logisticsInfo * @return int */ private static function compute_free($logisticsInfo){ return 0; } /** * Common: 刷新当前选择国家信息 * Author: wu-hui * Time: 2023/08/28 14:50 */ private static function refreshCurrentCountry($countryId){ $newCountry = Country::query()->where('id',$countryId)->first(); self::$country = $newCountry ? $newCountry->toArray() : []; } /** * 通过配送方式获取插件编码 * * @param $shippingMethod * @return string */ public static function parseShippingPluginCode($shippingMethod): string { $methodArray = explode('.', $shippingMethod); return $methodArray[0]; } }