添加:物流运费计算方式
This commit is contained in:
parent
f1f2a7d1bf
commit
18546a254c
|
|
@ -5,8 +5,7 @@ namespace Beike\Models;
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Logistics extends Base
|
||||
{
|
||||
class Logistics extends Base{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
|
|
@ -27,9 +26,26 @@ class Logistics extends Base
|
|||
'day_max',
|
||||
'position'
|
||||
];
|
||||
/**
|
||||
* Common: 根据国家ID 获取所有关联的物流列表
|
||||
* Author: wu-hui
|
||||
* Time: 2023/08/22 10:31
|
||||
* @param int $countryId
|
||||
* @param string[] $field
|
||||
* @return array
|
||||
*/
|
||||
public static function getAll(int $countryId,$field = ['id','name','warehouse_name','type','day_min','day_max']){
|
||||
$list = self::select($field)
|
||||
->whereRaw(\DB::raw('FIND_IN_SET('.$countryId.',country_ids)'))
|
||||
->orderBy('position','ASC')
|
||||
->orderBy('id','ASC')
|
||||
->get();
|
||||
|
||||
public function country()
|
||||
{
|
||||
return $this->belongsTo(Country::class, 'country_id', 'id');
|
||||
|
||||
return $list ? $list->toArray() : [];
|
||||
}
|
||||
|
||||
public function country(){
|
||||
return $this->belongsTo(Country::class,'country_id','id');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class CountryRepo
|
|||
$builder = Country::query()
|
||||
->where('name', 'like', "$name%")
|
||||
->select('id', 'name', 'status', 'code')
|
||||
->orderBy('sort_order','DESC')
|
||||
->orderBy('sort_order','ASC')
|
||||
->orderBy('id','ASC');
|
||||
// if ($onlyActive) {
|
||||
// $builder->where('status', 1);
|
||||
|
|
@ -156,7 +156,7 @@ class CountryRepo
|
|||
$list = Country::query()
|
||||
->whereIn('id', $ids)
|
||||
->select('id', 'name', 'code')
|
||||
->orderBy('sort_order','DESC')
|
||||
->orderBy('sort_order','ASC')
|
||||
->orderBy('id','ASC')
|
||||
->get();
|
||||
if($list) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
namespace Beike\Services;
|
||||
|
||||
use Beike\Admin\Http\Resources\PluginResource;
|
||||
use Beike\Models\Logistics;
|
||||
use Beike\Repositories\PluginRepo;
|
||||
use Beike\Shop\Services\CheckoutService;
|
||||
use Illuminate\Support\Str;
|
||||
|
|
@ -43,12 +44,26 @@ class ShippingMethodService
|
|||
if ($quotes) {
|
||||
$pluginResource = (new PluginResource($plugin))->jsonSerialize();
|
||||
$shippingMethods[] = [
|
||||
'is_logistics' => 0,
|
||||
'code' => $pluginCode,
|
||||
'name' => $pluginResource['name'],
|
||||
'quotes' => $quotes,
|
||||
];
|
||||
}
|
||||
}
|
||||
// 获取根据收货地址 获取物流信息
|
||||
if($checkout->cart->guest_shipping_address || $checkout->cart->guest_payment_address){
|
||||
$address = $checkout->cart->guest_shipping_address ?? $checkout->cart->guest_payment_address;
|
||||
$logisticsList = Logistics::getAll($address['country_id']);
|
||||
foreach($logisticsList as $logisticsItem){
|
||||
$shippingMethods[] = [
|
||||
'is_logistics' => 1,
|
||||
'code' => $logisticsItem['id'],
|
||||
'name' => $logisticsItem['name'],
|
||||
'quotes' => $logisticsItem,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $shippingMethods;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,8 +89,8 @@ class ProductController extends Controller
|
|||
'product_id' => $item['product_id'],
|
||||
'product_sku_id' => $item['product_sku_id'],
|
||||
'quantity' => $item['quantity'],
|
||||
'product' => Product::where('id',40)->first()->toArray(),
|
||||
'sku' => ProductSku::where('id',641)->first()->toArray(),
|
||||
'product' => Product::where('id',$item['product_id'])->first()->toArray(),
|
||||
'sku' => ProductSku::where('id',$item['product_sku_id'])->first()->toArray(),
|
||||
];
|
||||
}
|
||||
$cartItems = collect($diyData)->mapInto(CartProduct::class);
|
||||
|
|
|
|||
|
|
@ -252,16 +252,19 @@ class CheckoutService
|
|||
foreach ($shipments as $shipment) {
|
||||
$shipmentCodes = array_merge($shipmentCodes, array_column($shipment['quotes'], 'code'));
|
||||
}
|
||||
if (! in_array($currentCart->shipping_method_code, $shipmentCodes)) {
|
||||
// 判断:如果运费方式不存在 并且不是物流则设置第一个物流信息
|
||||
if (!in_array($currentCart->shipping_method_code, $shipmentCodes) && !is_int($currentCart->shipping_method_code)) {
|
||||
$this->updateShippingMethod($shipmentCodes[0] ?? '');
|
||||
$this->totalService->setShippingMethod($shipmentCodes[0] ?? '');
|
||||
}
|
||||
|
||||
$shippingMethodCode = !empty($currentCart->shipping_method_code) ? $currentCart->shipping_method_code : ($shipments[0]['code'] ?? '');
|
||||
|
||||
$data = [
|
||||
'current' => [
|
||||
'shipping_address_id' => $currentCart->shipping_address_id,
|
||||
'guest_shipping_address' => $currentCart->guest_shipping_address,
|
||||
'shipping_method_code' => $currentCart->shipping_method_code,
|
||||
'shipping_method_code' => $shippingMethodCode,
|
||||
'payment_address_id' => $currentCart->payment_address_id,
|
||||
'guest_payment_address' => $currentCart->guest_payment_address,
|
||||
'payment_method_code' => $currentCart->payment_method_code,
|
||||
|
|
|
|||
|
|
@ -12,52 +12,176 @@
|
|||
|
||||
namespace Beike\Shop\Services\TotalServices;
|
||||
|
||||
use Beike\Libraries\Weight;
|
||||
use Beike\Models\Logistics;
|
||||
use Beike\Shop\Services\CheckoutService;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ShippingService
|
||||
{
|
||||
/**
|
||||
* @param CheckoutService $checkout
|
||||
* @return array|null
|
||||
* @throws \Exception|\Throwable
|
||||
*/
|
||||
public static function getTotal(CheckoutService $checkout): ?array
|
||||
{
|
||||
class ShippingService{
|
||||
// 运费计算
|
||||
public static function getTotal(CheckoutService $checkout): ?array{
|
||||
$totalService = $checkout->totalService;
|
||||
$shippingMethod = $totalService->getShippingMethod();
|
||||
if (empty($shippingMethod)) {
|
||||
return null;
|
||||
$amount = 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{
|
||||
// 通过物流进行计算
|
||||
$logisticsId = (int)$checkout->cart->shipping_method_code;
|
||||
$logisticsInfo = self::getLogistics($logisticsId,$checkout->cart);
|
||||
if($logisticsInfo) {
|
||||
// 商品信息处理 获取同一个商品的数量
|
||||
$products = $checkout->selectedProducts->toArray() ?? [];
|
||||
$productsList = collect($products)->groupBy('product_id')->map(function($group){
|
||||
$firstInfo = $group->first();
|
||||
// 重量转换 - 物流重量单位为 千克;商品重量需要进行转换
|
||||
$weight = $firstInfo['product']['weight'] ?? 0;
|
||||
$sumQuantity = $group->sum('quantity');
|
||||
$sumWeight = (float)sprintf("%.2f",($weight * $sumQuantity));
|
||||
$weightClass = $firstInfo['product']['weight_class'];
|
||||
$sumWeight = Weight::convert($sumWeight,$weightClass);// 总重量 单位:克
|
||||
|
||||
return [
|
||||
'product_id' => $firstInfo['product_id'],
|
||||
'sum_quantity' => $sumQuantity,
|
||||
'sum_weight' => $sumWeight,
|
||||
'weight' => $weight,
|
||||
'weight_class' => $weightClass,
|
||||
];
|
||||
});
|
||||
// weight:按重量计费,num:按数量计费,free:卖家包邮
|
||||
$logisticsHandleFun = 'compute_'.$logisticsInfo['type'];
|
||||
$amount = self::$logisticsHandleFun($logisticsInfo,$productsList);
|
||||
}
|
||||
}
|
||||
|
||||
$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);
|
||||
$totalData = [
|
||||
'code' => 'shipping',
|
||||
'title' => trans('shop/carts.shipping_fee'),
|
||||
'amount' => $amount,
|
||||
'amount_format' => currency_format($amount),
|
||||
];
|
||||
|
||||
$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){
|
||||
if($logisticsId <= 0){
|
||||
// 获取当前收货地址国家ID
|
||||
$address = $cart->guest_shipping_address ?? $cart->guest_payment_address;
|
||||
$countryId = $address['country_id'];
|
||||
// 获取全部物流 返回第一个物流信息ID
|
||||
$logisticsList = Logistics::getAll($countryId,['type','first_weight','first_weight_fee','continuation_weight_max','add_weight','continuation_weight_fee','num_fee']);
|
||||
|
||||
return $logisticsList[0] ?? [];
|
||||
}else{
|
||||
// 获取物流信息
|
||||
$logisticsInfo = Logistics::query()
|
||||
->select(['type','first_weight','first_weight_fee','continuation_weight_max','add_weight','continuation_weight_fee','num_fee'])
|
||||
->find($logisticsId)
|
||||
->toArray();
|
||||
|
||||
return $logisticsInfo;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Common: 运费计算 - 物流运费 - 运费计算(按重量计费)
|
||||
* Author: wu-hui
|
||||
* Time: 2023/08/22 15:34
|
||||
* @param $logisticsInfo
|
||||
* @param $productsList
|
||||
* @return float|int
|
||||
*/
|
||||
private static function compute_weight($logisticsInfo,$productsList){
|
||||
/**
|
||||
* 计算规则:
|
||||
* 总重量 - 低于首重;则运费 = 【首重运费】
|
||||
* 总重量 - 高于首重;则运费 = 首重按照【首重运费】计算;(剩余重量 / 每增加重量 ) * 续重运费
|
||||
*/
|
||||
$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($productsList as $productInfo){
|
||||
// 首重运费
|
||||
$amount += (float)sprintf("%.2f",$firstWeightFee);
|
||||
// 判断:如果总重量超过首重 则计算续重运费
|
||||
$surplus = (int)$productInfo['sum_weight'] - $firstWeight;
|
||||
if($surplus > 0) $amount += (float)sprintf("%.2f",($surplus / $addWeight) * $continuationWeightFee);
|
||||
}
|
||||
|
||||
return $amount;
|
||||
}
|
||||
/**
|
||||
* Common: 运费计算 - 物流运费 - 运费计算(按数量计费)
|
||||
* Author: wu-hui
|
||||
* Time: 2023/08/22 14:47
|
||||
* @param $logisticsInfo
|
||||
* @param $productsList
|
||||
* @return float|int
|
||||
*/
|
||||
private static function compute_num($logisticsInfo,$productsList){
|
||||
/**
|
||||
* 计算规则:
|
||||
* 购买数量 - 低于首件数量;则运费 = 【首件运费】
|
||||
* 购买数量 - 高于首件数量;则运费 = 首件数量按照【首件运费】计算;(剩余购买数量 / 每增加件数量 ) * 续件运费
|
||||
*/
|
||||
$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($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
|
||||
* @param $productsList
|
||||
* @return int
|
||||
*/
|
||||
private static function compute_free($logisticsInfo,$productsList){
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通过配送方式获取插件编码
|
||||
|
|
|
|||
|
|
@ -77,4 +77,5 @@ return [
|
|||
'add_num'=>'Add num',
|
||||
'continuation_num_fee'=>'Continuation num fee',
|
||||
'freight_calculation_method'=>'Freight calculation method',
|
||||
'logistics_duration' => 'Delivery within :day working days after shipment',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -76,4 +76,5 @@ return [
|
|||
'add_num' => '每增加件',
|
||||
'continuation_num_fee' => '续件运费',
|
||||
'freight_calculation_method' => '运费计算方式',
|
||||
'logistics_duration' => '发货后:day个工作日内交货',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -47,19 +47,42 @@
|
|||
<h5 class="checkout-title">{{ __('shop/checkout.delivery_method') }}</h5>
|
||||
<div class="radio-line-wrap" id="shipping-methods-wrap">
|
||||
@foreach ($shipping_methods as $methods)
|
||||
@foreach ($methods['quotes'] as $shipping)
|
||||
<div class="radio-line-item {{ $shipping['code'] == $current['shipping_method_code'] ? 'active':'' }}" data-key="shipping_method_code" data-value="{{ $shipping['code'] }}">
|
||||
<div class="left">
|
||||
<span class="radio"></span>
|
||||
<img src="{{ $shipping['icon'] }}" class="img-fluid">
|
||||
@if(!$methods['is_logistics'])
|
||||
@foreach ($methods['quotes'] as $shipping)
|
||||
<div class="radio-line-item {{ $shipping['code'] == $current['shipping_method_code'] ? 'active':'' }}" data-key="shipping_method_code" data-value="{{ $shipping['code'] }}">
|
||||
<div class="left">
|
||||
<span class="radio"></span>
|
||||
<img src="{{ $shipping['icon'] }}" class="img-fluid">
|
||||
</div>
|
||||
<div class="right ms-2">
|
||||
<div class="title">{{ $shipping['name'] }}</div>
|
||||
<div class="sub-title">{!! $shipping['description'] !!}</div>
|
||||
<div class="mt-2">{!! $shipping['html'] ?? '' !!}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right ms-2">
|
||||
<div class="title">{{ $shipping['name'] }}</div>
|
||||
<div class="sub-title">{!! $shipping['description'] !!}</div>
|
||||
<div class="mt-2">{!! $shipping['html'] ?? '' !!}</div>
|
||||
@endforeach
|
||||
@else
|
||||
<div class="radio-line-item {{ $methods['code'] == $current['shipping_method_code'] ? 'active':'' }}" data-key="shipping_method_code" data-value="{{ $methods['code'] }}">
|
||||
<div class="left">
|
||||
<span class="radio"></span>
|
||||
</div>
|
||||
<div class="right ms-2">
|
||||
<div class="title">{{ $methods['name'] }}({{ $methods['quotes']['warehouse_name'] }})</div>
|
||||
<div class="title">
|
||||
@if($methods['quotes']['type'] == 'weight')
|
||||
{{ __('admin/logistics.type_weight') }}
|
||||
@elseif ($methods['quotes']['type'] == 'num')
|
||||
{{ __('admin/logistics.type_num') }}
|
||||
@elseif ($methods['quotes']['type'] == 'free')
|
||||
{{ __('admin/logistics.type_free') }}
|
||||
@endif
|
||||
<span style="margin-left: 10px;">
|
||||
{{ __('admin/logistics.logistics_duration',['day'=> $methods['quotes']['day_min'].'-'.$methods['quotes']['day_max'] ]) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -194,8 +217,9 @@
|
|||
let html = '';
|
||||
|
||||
data.forEach((methods) => {
|
||||
methods.quotes.forEach((quote) => {
|
||||
html += `<div class="radio-line-item d-flex align-items-center ${shipping_method_code == quote.code ? 'active' : ''}" data-key="shipping_method_code" data-value="${quote.code}">
|
||||
if(!methods.is_logistics){
|
||||
methods.quotes.forEach((quote) => {
|
||||
html += `<div class="radio-line-item d-flex align-items-center ${shipping_method_code == quote.code ? 'active' : ''}" data-key="shipping_method_code" data-value="${quote.code}">
|
||||
<div class="left">
|
||||
<span class="radio"></span>
|
||||
<img src="${quote.icon}" class="img-fluid">
|
||||
|
|
@ -206,7 +230,29 @@
|
|||
<div class="mt-2">${quote.html || ''}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
})
|
||||
})
|
||||
}
|
||||
else{
|
||||
// 获取计费方式
|
||||
let typeText = '{{ __('admin/logistics.type_weight') }}';
|
||||
if(methods.quotes.type === 'num') typeText = '{{ __('admin/logistics.type_num') }}';
|
||||
else if(methods.quotes.type === 'free') typeText = '{{ __('admin/logistics.type_free') }}';
|
||||
// 获取配送时间
|
||||
let logistics_duration = '{{ __('admin/logistics.logistics_duration') }}'
|
||||
logistics_duration = logistics_duration.replace(":day", methods.quotes.day_min + '-' + methods.quotes.day_max);
|
||||
html += `<div class="radio-line-item d-flex align-items-center ${shipping_method_code == methods.code ? 'active' : ''}" data-key="shipping_method_code" data-value="${methods.code}">
|
||||
<div class="left">
|
||||
<span class="radio"></span>
|
||||
</div>
|
||||
<div class="right ms-3">
|
||||
<div class="title">${methods.name}(${methods.quotes.warehouse_name} }})</div>
|
||||
<div class="title">
|
||||
${typeText}
|
||||
<span style="margin-left: 10px;">${logistics_duration}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
})
|
||||
|
||||
$('#shipping-methods-wrap').html(html);
|
||||
|
|
|
|||
Loading…
Reference in New Issue