wyyl/beike/Shop/Services/TotalServices/ShippingService.php

277 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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];
}
}