6
0
Fork 0

结算订单

release
李静 2021-12-29 19:34:25 +08:00
parent ad5ee383ad
commit 4fca777935
12 changed files with 451 additions and 68 deletions

View File

@ -39,6 +39,7 @@ class Distribution extends Form
// dd(config('distribution'), app_settings('distribution'));
$this->number('settle_days', '订单结算时间(天)')->value($appSettings['settle_days'] ?? 0)->help('订单完成后,隔多少天可以结算');
$this->currency('price_diff_fee_rate', '会员差价手续费')->value($appSettings['price_diff_fee_rate'] ?? 0)->symbol('%');
$this->currency('lvl_same_bonus_fee_rate', '平级奖励手续费')->value($appSettings['lvl_same_bonus_fee_rate'] ?? 0)->symbol('%');
$this->currency('lvl_diff_bonus_fee_rate', '级差奖励手续费')->value($appSettings['lvl_diff_bonus_fee_rate'] ?? 0)->symbol('%');

View File

@ -0,0 +1,112 @@
<?php
namespace App\Console\Commands;
use App\Models\DistributionPreIncome;
use App\Models\Order;
use App\Models\UserInfo;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Throwable;
class OrderSettleCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'order:settle';
/**
* The console command description.
*
* @var string
*/
protected $description = '订单结算';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
// 只查询可结算的订单,并且没有处理中的售后单
Order::whereDoesntHave('afterSales', function ($query) {
return $query->processing();
})->settlable()->chunkById(200, function ($orders) {
$orders->load(['user', 'afterSales']);
foreach ($orders as $order) {
try {
DB::beginTransaction();
$this->settle($order);
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
report($e);
}
}
});
return 0;
}
/**
* 结算成长值
*
* @param \App\Models\Order $order
* @return void
*/
protected function settle(Order $order)
{
// 用户可得销售值
$salesValue = $order->sales_value;
foreach ($order->afterSales as $afterSale) {
if ($afterSale->isCancelled()) {
continue;
}
$salesValue = bcsub($salesValue, $afterSale->sales_value, 2);
}
$user = $order->user;
// 结算下单用户的成长值
$user->userInfo()->update([
'growth_value' => DB::raw("growth_value+{$salesValue}"),
'pre_growth_value' => DB::raw("pre_growth_value-{$salesValue}"),
]);
// 尝试提升用户的代理等级
$user->userInfo->attemptUpgradeAgentLevel();
if (count($pids = $user->userInfo->parent_ids) > 0) {
// 更新上级的团队销售值
UserInfo::whereIn('user_id', $pids)->update([
'group_sales_value' => DB::raw("group_sales_value + {$salesValue}"),
]);
$ancestors = UserInfo::whereIn('user_id', $pids)->latest('depth')->get();
foreach ($ancestors as $ancestor) {
$ancestor->attemptUpgradeAgentLevel();
}
}
// 将订单标记未已结算
$order->update([
'is_settle' => true,
]);
// 将预收益标记为结算中
DistributionPreIncome::where('order_id', $order->id)->update([
'status' => DistributionPreIncome::STATUS_PROCESSING,
]);
}
}

View File

@ -57,7 +57,7 @@ class WalletController extends Controller
$perPage = PaginatorHelper::resolvePerPage('per_page', 20, 50);
$distributionLogs = $request->user()->distributionPreIncomes()
->with('logs')
->pending()
->unsettlement()
->latest('id')
->simplePaginate($perPage);

View File

@ -66,6 +66,23 @@ class AfterSale extends Model
return $afterSaleState;
}
/**
* 仅查询处理中的售后订单
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeProcessing($query)
{
return $query->whereIn('state', [
static::STATE_APPLY,
static::STATE_VERIFY,
static::STATE_AGREE,
static::STATE_SHIPPING,
static::STATE_FINANCE,
]);
}
public function user()
{
return $this->belongsTo(User::class, 'user_id');
@ -101,6 +118,16 @@ class AfterSale extends Model
return $this->type === static::TYPE_CHANGE;
}
/**
* 确认此售后单是否已取消
*
* @return bool
*/
public function isCancelled(): bool
{
return $this->status === static::STATE_CANCEL;
}
/**
* 设置申请售后时的录入操作日志
*

View File

@ -0,0 +1,17 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AgentUpgradeLog extends Model
{
/**
* @var array
*/
protected $fillable = [
'user_id',
'before_agent_level',
'change_agent_level',
];
}

View File

@ -6,10 +6,21 @@ use Illuminate\Database\Eloquent\Model;
class DistributionPreIncome extends Model
{
public const STATUS_PENDING = 0;
public const STATUS_PROCESSING = 1;
public const STATUS_PROCESSED = 2;
public const TYPE_PRICE_DIFF = 1;
public const TYPE_LEVEL_SAME = 2;
public const TYPE_LEVEL_DIFF = 3;
/**
* @var array
*/
protected $attributes = [
'status' => self::STATUS_PENDING,
];
/**
* @var array
*/
@ -43,12 +54,23 @@ class DistributionPreIncome extends Model
];
/**
* 待结算预收益
*
* @var array
*/
public function scopePending($query)
public static $statusTexts = [
self::STATUS_PENDING => '待结算',
self::STATUS_PROCESSING => '结算中',
self::STATUS_PROCESSED => '已结算',
];
/**
* 仅查询未结算的预收益
*/
public function scopeUnsettlement($query)
{
return $query->where('status', '=', 0);
return $query->whereIn('status', [
static::STATUS_PENDING,
static::STATUS_PROCESSING,
]);
}
/**

View File

@ -41,6 +41,8 @@ class Order extends Model
*/
protected $attributes = [
'reduced_amount' => 0,
'is_settle' => false,
'is_change' => false,
'status' => self::STATUS_PENDING,
];
@ -52,6 +54,7 @@ class Order extends Model
'completed_at' => 'datetime',
'auto_complete_at' => 'datetime',
'status' => 'int',
'is_settle' => 'bool',
'is_change' => 'bool',
];
@ -83,6 +86,7 @@ class Order extends Model
'completed_at',
'auto_complete_at',
'is_change',
'is_settle',
];
/**
@ -104,6 +108,16 @@ class Order extends Model
->where('auto_complete_at', '<=', now());
}
/**
* 仅查询可结算的订单
*/
public function scopeSettlable()
{
return $this->where('status', static::STATUS_COMPLETED)
->where('completed_at', '<=', now()->subDays(app_settings('distribution.settle_days', 7)))
->where('is_settle', false);
}
/**
* 下单人
*

View File

@ -2,8 +2,8 @@
namespace App\Models;
use App\Helpers\Str;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class UserInfo extends Model
{
@ -157,6 +157,180 @@ class UserInfo extends Model
return $this->hasMany(DistributionPreIncome::class, 'user_id', 'user_id');
}
/**
* 获取此用户的代理升级日志
*/
public function agentUpgradeLogs()
{
return $this->hasMany(AgentUpgradeLog::class, 'user_id', 'user_id');
}
/**
* 获取此用户的直推店铺总数
*/
public function getVipAgentsCount()
{
return static::where('inviter_id', $this->getKey())
->where('agent_level', '>=', static::AGENT_LEVEL_VIP)
->count();
}
/**
* 获取此用户的不同线上的下级区级代理总数
*/
public function getDistrictAgentsCountOnDifferentLines()
{
$path = $this->full_path;
// 获取所有区级以上的代理
$agents = static::where('agent_level', '>=', static::AGENT_LEVEL_DISTRICT)
->where('path', 'like', "{$path}%")
->get();
$lines = [];
foreach ($agents as $agent) {
preg_match("#\A{$path}\d+/#", $agent->full_path, $matches);
$line = $matches[0];
if (! isset($lines[$line])) {
$lines[$line] = 1;
}
}
return count($lines);
}
/**
* 获取此用户的不同线上的下级市级代理总数
*/
public function getCityAgentsCountOnDifferentLines()
{
$path = $this->full_path;
// 获取所有市级以上的代理
$agents = static::where('agent_level', '>=', static::AGENT_LEVEL_CITY)
->where('path', 'like', "$path%")
->get();
$lines = [];
foreach ($agents as $agent) {
preg_match("#\A{$path}\d+/#", $agent->full_path, $matches);
$line = $matches[0];
if (! isset($lines[$line])) {
$lines[$line] = 1;
}
}
return count($lines);
}
/**
* 获取此用户的不同线上的下级省级代理总数
*/
public function getProvinceAgentsCountOnDifferentLines()
{
$path = $this->full_path;
// 获取所有省级以上的代理
$agents = static::where('agent_level', '>=', static::AGENT_LEVEL_PROVINCE)
->where('path', 'like', "$path%")
->get();
$lines = [];
foreach ($agents as $agent) {
preg_match("#\A{$path}\d+/#", $agent->full_path, $matches);
$line = $matches[0];
if (! isset($lines[$line])) {
$lines[$line] = 1;
}
}
return count($lines);
}
/**
* 尝试提升代理等级
*
* @return void
*/
public function attemptUpgradeAgentLevel(): void
{
$lvl = $this->agent_level;
// 如果代理等级是分公司或董事时,不能继续提升代理等级
if (in_array($lvl, [static::AGENT_LEVEL_BRANCH, static::AGENT_LEVEL_DIRECTOR])) {
return;
}
// 如果成长值不足650时不能升级
if (bccomp($this->group_sales_value, '650') < 0) {
return;
}
// 如果代理等级是粉丝,则可升级为店铺
if ($lvl === static::AGENT_LEVEL_CIVILIAN) {
$lvl = static::AGENT_LEVEL_VIP;
}
// 总团队销售值 = 团队销售值 + 个人成长值
$salesValue = bcadd($this->group_sales_value, $this->growth_value, 2);
if ($lvl === static::AGENT_LEVEL_VIP) {
$vipsCount = $this->getVipAgentsCount();
// 如果直推店铺人数>=6并且团队销售值>=65000则可升级为区级代理
// 或者直推店铺人数>=4则可升级为社区
if ($vipsCount >= 6 && bccomp($salesValue, '65000') >= 0) {
$lvl = static::AGENT_LEVEL_DISTRICT;
} elseif ($vipsCount >= 4) {
$lvl = static::AGENT_LEVEL_COMMUNITY;
}
} elseif ($lvl === static::AGENT_LEVEL_COMMUNITY && bccomp($salesValue, '65000') >= 0) {
if ($this->getVipAgentsCount() >= 6) {
$lvl = static::AGENT_LEVEL_DISTRICT;
}
}
if ($lvl === static::AGENT_LEVEL_DISTRICT && bccomp($salesValue, '780000') >= 0) {
if ($this->getDistrictAgentsCountOnDifferentLines() >= 3) {
$lvl = static::AGENT_LEVEL_CITY;
}
}
if ($lvl === static::AGENT_LEVEL_CITY && bccomp($salesValue, '7800000') >= 0) {
if ($this->getCityAgentsCountOnDifferentLines() >= 2) {
$lvl = static::AGENT_LEVEL_PROVINCE;
}
}
if ($lvl === static::AGENT_LEVEL_PROVINCE && bccomp($salesValue, '52000000') >= 0) {
if ($this->getProvinceAgentsCountOnDifferentLines() >= 2) {
$lvl = static::AGENT_LEVEL_BRANCH;
}
}
if ($this->agent_level !== $lvl) {
$beforeAgentLevel = $this->agent_level;
$this->update([
'agent_level' => $lvl,
]);
$this->agentUpgradeLogs()->create([
'before_agent_level' => $beforeAgentLevel,
'change_agent_level' => $lvl,
]);
}
}
/**
* 变更预收益成长值
*
@ -223,4 +397,14 @@ class UserInfo extends Model
{
return static::$agentLevelTexts[$this->agent_level] ?? '未知';
}
/**
* 获取完整的邀请路径
*
* @return string
*/
public function getFullPathAttribute(): string
{
return Str::finish($this->path.$this->getKey(), '/');
}
}

View File

@ -1,62 +0,0 @@
<?php
// 必须包含所有代理等级
return [
// 会员差价手续费
'price_diff_fee_rate' => '0.23',
// 平级奖励手续费
'lvl_same_bonus_fee_rate' => '0',
// 级差奖励手续费
'lvl_diff_bonus_fee_rate' => '0.10',
// 代理等级分润规则
'rules' => [
// 平民(粉丝)
'civilian' => [
'lvl_same_bonus_rate' => '0', // 平级奖励比例
'lvl_diff_bonus_rate' => '0', // 级差奖励比例
],
// 店铺
'vip' => [
'lvl_same_bonus_rate' => '0', // 平级奖励比例
'lvl_diff_bonus_rate' => '0.02', // 级差奖励比例
],
// 社区
'community' => [
'lvl_same_bonus_rate' => '0.01', // 平级奖励比例
'lvl_diff_bonus_rate' => '0.10', // 级差奖励比例
],
// 区级
'district' => [
'lvl_same_bonus_rate' => '0.02', // 平级奖励比例
'lvl_diff_bonus_rate' => '0.19', // 级差奖励比例
],
// 市级
'city' => [
'lvl_same_bonus_rate' => '0.01', // 平级奖励比例
'lvl_diff_bonus_rate' => '0.32', // 级差奖励比例
],
// 省级
'province' => [
'lvl_same_bonus_rate' => '0.01', // 平级奖励比例
'lvl_diff_bonus_rate' => '0.42', // 级差奖励比例
],
// 分公司
'branch' => [
'lvl_same_bonus_rate' => '0.01', // 平级奖励比例
'lvl_diff_bonus_rate' => '0.48', // 级差奖励比例
],
// 董事
'director' => [
'lvl_same_bonus_rate' => '0.01', // 平级奖励比例
'lvl_diff_bonus_rate' => '0.50', // 级差奖励
],
],
];

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAgentUpgradeLogsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('agent_upgrade_logs', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->tinyInteger('before_agent_level')->comment('变更前的代理等级');
$table->tinyInteger('change_agent_level')->comment('变更的代理等级');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('agent_upgrade_logs');
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddIsSettleToOrdersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('orders', function (Blueprint $table) {
$table->boolean('is_settle')->default(false)->comment('是否结算');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('orders', function (Blueprint $table) {
$table->dropColumn(['is_settle']);
});
}
}

View File

@ -98,6 +98,8 @@ class AppSettingSeeder extends Seeder
],
'distribution' => [
'value' => [
// 分销结算时间
'settle_days' => '7',
// 会员差价手续费
'price_diff_fee_rate' => '0.23',
// 平级奖励手续费