277 lines
13 KiB
PHP
277 lines
13 KiB
PHP
<?php
|
||
|
||
/**
|
||
* ShippingService.php
|
||
*
|
||
* @copyright 2022 beikeshop.com - All Rights Reserved
|
||
* @link https://beikeshop.com
|
||
* @author Edward Yang <yangjin@guangda.work>
|
||
* @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];
|
||
}
|
||
}
|