添加:物流运费计算方式

This commit is contained in:
wuhui_zzw 2023-08-22 16:00:34 +08:00
parent f1f2a7d1bf
commit 18546a254c
9 changed files with 259 additions and 53 deletions

View File

@ -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');
}
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);

View File

@ -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,

View File

@ -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;
}
/**
* 通过配送方式获取插件编码

View File

@ -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',
];

View File

@ -76,4 +76,5 @@ return [
'add_num' => '每增加件',
'continuation_num_fee' => '续件运费',
'freight_calculation_method' => '运费计算方式',
'logistics_duration' => '发货后:day个工作日内交货',
];

View File

@ -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);