516 lines
16 KiB
PHP
516 lines
16 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands\Dealer;
|
||
|
||
use App\Enums\DealerEarningStatus;
|
||
use App\Enums\DealerLvl;
|
||
use App\Enums\DealerOrderSettleState;
|
||
use App\Enums\DealerOrderStatus;
|
||
use App\Models\Dealer;
|
||
use App\Models\DealerChannelSubsidyLog;
|
||
use App\Models\DealerManagerSalesLog;
|
||
use App\Models\DealerManageSubsidyLog;
|
||
use App\Models\DealerOrder;
|
||
use App\Models\DealerPurchaseLog;
|
||
use App\Models\UserInfo;
|
||
use Illuminate\Console\Command;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Throwable;
|
||
|
||
class OrderProcessCommand extends Command
|
||
{
|
||
/**
|
||
* The name and signature of the console command.
|
||
*
|
||
* @var string
|
||
*/
|
||
protected $signature = 'dealer:order-process';
|
||
|
||
/**
|
||
* The console command description.
|
||
*
|
||
* @var string
|
||
*/
|
||
protected $description = '处理结算状态是待处理的已付款经销商订单';
|
||
|
||
/**
|
||
* Execute the console command.
|
||
*
|
||
* @return int
|
||
*/
|
||
public function handle()
|
||
{
|
||
do {
|
||
$page = 0;
|
||
|
||
DealerOrder::where(
|
||
'settle_state',
|
||
DealerOrderSettleState::Pending
|
||
)->whereIn('status', [
|
||
DealerOrderStatus::Paid,
|
||
DealerOrderStatus::Shipped,
|
||
DealerOrderStatus::Completed,
|
||
])->chunkById(200, function ($orders) use (&$page) {
|
||
$orders->load([
|
||
'dealer.userInfo',
|
||
'products.productManageSubsidyRules',
|
||
]);
|
||
|
||
foreach ($orders as $order) {
|
||
try {
|
||
DB::transaction(function () use ($order) {
|
||
$this->handleDealerOrder($order);
|
||
});
|
||
} catch (Throwable $e) {
|
||
report($e);
|
||
}
|
||
}
|
||
|
||
$page += 1;
|
||
});
|
||
|
||
if ($page === 0) {
|
||
sleep(30);
|
||
} elseif ($page === 1) {
|
||
sleep(5);
|
||
}
|
||
} while (true);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* 处理经销商订单
|
||
*
|
||
* @param \App\Models\DealerOrder $dealerOrder
|
||
* @return void
|
||
*/
|
||
protected function handleDealerOrder(DealerOrder $dealerOrder)
|
||
{
|
||
$tz = now()->toDateTimeString();
|
||
|
||
// 当前链上的全部经销商(含下单经销商)
|
||
$dealers = [
|
||
$dealerOrder->dealer,
|
||
...$dealerOrder->dealer->getDealers(),
|
||
];
|
||
|
||
// 签约经销商的进货日志
|
||
$this->handlePurchaseLogsOfContractedDealer($dealerOrder, $tz);
|
||
|
||
// 管理者的销售业绩
|
||
$this->handleManagerSalesLogs($dealerOrder, $dealers, $tz);
|
||
|
||
// 一级签约经销商和二级经销商的管理津贴
|
||
$this->handleManageSubsidyLogs($dealerOrder, $dealers, $tz);
|
||
|
||
// 渠道补贴
|
||
$this->handleChannelSubsidy($dealerOrder);
|
||
|
||
if ($dealerOrder->dealer->wasChanged('lvl')) {
|
||
foreach ($dealers as $dealer) {
|
||
$dealer->attemptUpgrade();
|
||
}
|
||
}
|
||
|
||
// 将订单标记为已处理
|
||
$dealerOrder->forceFill([
|
||
'settle_state' => DealerOrderSettleState::Processed,
|
||
])->save();
|
||
}
|
||
|
||
/**
|
||
* 计算渠道补贴
|
||
*
|
||
* @param \App\Models\DealerOrder $dealerOrder
|
||
* @return void
|
||
*/
|
||
protected function handleChannelSubsidy(DealerOrder $dealerOrder)
|
||
{
|
||
$lvl = $dealerOrder->dealer->lvl;
|
||
|
||
if ($dealerOrder->total_amount >= app_settings('dealer.upgrade_amount_'.DealerLvl::Contracted->value)) {
|
||
// 升级为签约
|
||
if ($lvl->value < DealerLvl::Contracted->value) {
|
||
$lvl = DealerLvl::Contracted;
|
||
}
|
||
} elseif ($dealerOrder->total_amount >= app_settings('dealer.upgrade_amount_'.DealerLvl::Special->value)) {
|
||
// 升级为特约
|
||
if ($lvl->value < DealerLvl::Special->value) {
|
||
$lvl = DealerLvl::Special;
|
||
}
|
||
} elseif ($dealerOrder->total_amount >= app_settings('dealer.upgrade_amount_'.DealerLvl::Gold->value)) {
|
||
// 升级为金牌
|
||
if ($lvl->value < DealerLvl::Gold->value) {
|
||
$lvl = DealerLvl::Gold;
|
||
}
|
||
}
|
||
|
||
// 如果经销商等级小于金牌,则没有渠道补贴
|
||
if ($lvl->value < DealerLvl::Gold->value) {
|
||
return;
|
||
}
|
||
|
||
[$dealers, $rule] = $this->mapDealersAndRuleOfChannel($dealerOrder->dealer, $lvl);
|
||
|
||
// 升级金额
|
||
$upgradeAmount = app_settings('dealer.upgrade_amount_'.$lvl->value);
|
||
// 手续费比例
|
||
$feeRate = bcdiv(app_settings('dealer.fee_rate'), '100', 10);
|
||
|
||
foreach ($dealers as $key => $dealer) {
|
||
$ruleKey = $dealer->lvl->value.'_'.$key;
|
||
|
||
if ($dealer->lvl->value >= DealerLvl::Contracted->value) {
|
||
$ruleKey = DealerLvl::Contracted->value.'_'.$key;
|
||
}
|
||
|
||
// 补贴金额
|
||
$subsidyAmount = $rule[$ruleKey];
|
||
|
||
$totalAmount = bcmul($subsidyAmount, $dealerOrder->total_amount, 10);
|
||
$totalAmount = bcdiv($totalAmount, $upgradeAmount, 3);
|
||
$totalAmount = round($totalAmount, 2);
|
||
|
||
$channelSubsidyLog = DealerChannelSubsidyLog::create([
|
||
'user_id' => $dealer->user_id,
|
||
'lvl' => $dealer->lvl,
|
||
'order_id' => $dealerOrder->id,
|
||
'total_amount' => $totalAmount,
|
||
'order_id' => $dealerOrder->id,
|
||
'remark' => "补贴总额={$dealerOrder->total_amount}/{$upgradeAmount}*{$subsidyAmount}",
|
||
]);
|
||
|
||
$fee = bcmul($totalAmount, bcdiv($feeRate, '100', 5), 3);
|
||
$fee = round($fee, 2);
|
||
|
||
$channelSubsidyLog->earning()->create([
|
||
'user_id' => $dealer->user_id,
|
||
'lvl' => $dealer->lvl,
|
||
'total_amount' => $totalAmount,
|
||
'total_earnings' => bcsub($totalAmount, $fee, 2),
|
||
'fee' => $fee,
|
||
'fee_rate' => $feeRate,
|
||
'payer_id' => $dealerOrder->consignor_id,
|
||
'status' => DealerEarningStatus::Pending,
|
||
'remark' => "订单号: {$dealerOrder->sn}",
|
||
]);
|
||
}
|
||
|
||
$dealerOrder->dealer->upgrade($lvl, '进货升级');
|
||
}
|
||
|
||
/**
|
||
* 获取渠道补贴经销商和补贴规则
|
||
*
|
||
* @param \App\Models\Dealer $dealer
|
||
* @param \App\Enums\DealerLvl $lvl
|
||
* @return array
|
||
*/
|
||
protected function mapDealersAndRuleOfChannel(Dealer $dealer, DealerLvl $lvl): array
|
||
{
|
||
// 渠道补贴经销商
|
||
$dealers = [];
|
||
// 渠道补贴规则
|
||
$rule = null;
|
||
// 是否升级
|
||
$isUp = $lvl->value > $dealer->lvl->value;
|
||
// 最后参与渠道补贴的经销商
|
||
$last = null;
|
||
|
||
foreach ($dealer->getRealDealers() as $_dealer) {
|
||
// 如果经销商等级小于金牌, 那么跳过
|
||
if ($_dealer->lvl->value < DealerLvl::Gold->value) {
|
||
continue;
|
||
}
|
||
|
||
if ($lvl->value >= DealerLvl::Contracted->value) {
|
||
if ($last === null) {
|
||
if ($_dealer->lvl->value >= DealerLvl::Contracted->value) {
|
||
// 渠道补贴规则: 签约 -> 签约 -> 签约
|
||
$rule = app_settings(sprintf(
|
||
'dealer.channel_rules.%s_%s',
|
||
DealerLvl::Contracted->value,
|
||
DealerLvl::Contracted->value
|
||
));
|
||
|
||
$dealers[] = $_dealer;
|
||
|
||
$last = $_dealer;
|
||
} elseif ($isUp && $_dealer->isSpecialDealer()) {
|
||
// 渠道补贴规则: 签约 -> 特邀 -> 签约 -> 签约
|
||
$rule = app_settings(sprintf(
|
||
'dealer.channel_rules.%s_%s',
|
||
DealerLvl::Contracted->value,
|
||
DealerLvl::Special->value
|
||
));
|
||
|
||
$dealers[] = $_dealer;
|
||
|
||
$last = $_dealer;
|
||
}
|
||
} elseif ($_dealer->lvl->value >= DealerLvl::Contracted->value) {
|
||
$dealers[] = $_dealer;
|
||
|
||
// 如果最后参与渠道补贴的经销商是签约, 那么已经找到所有参与渠道补贴的经销商
|
||
if ($last->lvl->value >= DealerLvl::Contracted->value) {
|
||
break;
|
||
}
|
||
|
||
$last = $_dealer;
|
||
}
|
||
} elseif ($lvl === DealerLvl::Special) {
|
||
if ($_dealer->lvl->value > DealerLvl::Special->value) {
|
||
break;
|
||
}
|
||
|
||
if ($last === null) {
|
||
if ($_dealer->isSpecialDealer()) {
|
||
// 渠道补贴规则: 特邀 -> 特邀 -> 特邀
|
||
$rule = app_settings(sprintf(
|
||
'dealer.channel_rules.%s_%s',
|
||
DealerLvl::Special->value,
|
||
DealerLvl::Special->value
|
||
));
|
||
|
||
$dealers[] = $_dealer;
|
||
|
||
$last = $_dealer;
|
||
} elseif ($isUp && $_dealer->isGoldDealer()) {
|
||
// 渠道补贴规则: 特邀 -> 金牌 -> 特邀 -> 特邀
|
||
$rule = app_settings(sprintf(
|
||
'dealer.channel_rules.%s_%s',
|
||
DealerLvl::Special->value,
|
||
DealerLvl::Gold->value
|
||
));
|
||
|
||
$dealers[] = $_dealer;
|
||
|
||
$last = $_dealer;
|
||
}
|
||
} elseif ($_dealer->isSpecialDealer()) {
|
||
$dealers[] = $_dealer;
|
||
|
||
if ($last->isSpecialDealer()) {
|
||
break;
|
||
}
|
||
|
||
$last = $_dealer;
|
||
}
|
||
} elseif ($lvl === DealerLvl::Gold) {
|
||
if ($_dealer->lvl->value >= DealerLvl::Gold->value) {
|
||
if ($_dealer->isGoldDealer()) {
|
||
// 渠道补贴规则: 金牌 -> 金牌
|
||
$rule = app_settings(sprintf(
|
||
'dealer.channel_rules.%s_%s',
|
||
DealerLvl::Gold->value,
|
||
DealerLvl::Gold->value
|
||
));
|
||
|
||
$dealers[] = $_dealer;
|
||
}
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return [$dealers, $rule];
|
||
}
|
||
|
||
/**
|
||
* 生成签约经销商的进货日志
|
||
*
|
||
* @param \App\Models\DealerOrder $dealerOrder
|
||
* @param array $dealers
|
||
* @param string $tz
|
||
* @return void
|
||
*/
|
||
protected function handlePurchaseLogsOfContractedDealer(DealerOrder $dealerOrder, string $tz)
|
||
{
|
||
$dealer = $dealerOrder->userInfo->dealer;
|
||
|
||
// 采购业绩是否算自己的业绩
|
||
$valid = true;
|
||
|
||
if ($dealer->lvl->value < DealerLvl::Contracted->value) {
|
||
// 如果订单金额小于升级签约经销商的金额,则结束
|
||
if ($dealerOrder->total_amount < app_settings('dealer.upgrade_amount_'.DealerLvl::Contracted->value)) {
|
||
return;
|
||
}
|
||
|
||
$valid = false;
|
||
}
|
||
|
||
$log = new DealerPurchaseLog([
|
||
'user_id' => $dealer->user_id,
|
||
'lvl' => $dealer->lvl,
|
||
'order_id' => $dealerOrder->id,
|
||
'total_amount' => $dealerOrder->total_amount,
|
||
'path' => $valid ? $dealerOrder->userInfo->full_path : $dealerOrder->userInfo->path,
|
||
'remark' => $valid ? null : '升级签约',
|
||
]);
|
||
$log->setCreatedAt($tz);
|
||
$log->setUpdatedAt($tz);
|
||
$log->save();
|
||
}
|
||
|
||
/**
|
||
* 分配一级签约经销商和二级经销商的管理津贴
|
||
*
|
||
* @param \App\Models\DealerOrder $dealerOrder
|
||
* @param array $dealers
|
||
* @param string $tz
|
||
* @return void
|
||
*/
|
||
protected function handleManageSubsidyLogs(DealerOrder $dealerOrder, array $dealers, string $tz)
|
||
{
|
||
$logs = [];
|
||
|
||
foreach ($dealerOrder->products as $product) {
|
||
if ($product->productManageSubsidyRules->isEmpty()) {
|
||
continue;
|
||
}
|
||
|
||
// 管理津贴分配规则
|
||
$rules = $product->productManageSubsidyRules->keyBy('lvl');
|
||
|
||
$last = null;
|
||
$ranking = 0;
|
||
|
||
foreach ($dealers as $dealer) {
|
||
// 如果当前经销商等级没有对应的管理津贴分配规则,则忽略
|
||
if (is_null($rule = $rules->get($dealer->lvl->value))) {
|
||
continue;
|
||
}
|
||
|
||
// 同等级管理津贴最多给三次
|
||
if ($last === null || $dealer->lvl->value > $last->lvl->value) {
|
||
$ranking = 1;
|
||
} elseif ($ranking < 3 && $dealer->lvl->value === $last->lvl->value) {
|
||
$ranking++;
|
||
} else {
|
||
continue;
|
||
}
|
||
|
||
$subsidy = $rule->{"price_{$ranking}st"};
|
||
|
||
if (bccomp($subsidy, '0') === 1) {
|
||
$logs[] = [
|
||
'user_id' => $dealer->user_id,
|
||
'order_id' => $product->order_id,
|
||
'product_id' => $product->product_id,
|
||
'lvl' => $dealer->lvl,
|
||
'sales_volume' => $product->qty,
|
||
'total_amount' => bcmul($product->qty, $subsidy, 2),
|
||
'created_at' => $tz,
|
||
'updated_at' => $tz,
|
||
];
|
||
}
|
||
|
||
$last = $dealer;
|
||
}
|
||
}
|
||
|
||
DealerManageSubsidyLog::insert($logs);
|
||
}
|
||
|
||
/**
|
||
* 过滤出可能会享受管理津贴的经销商(每个等级最多3人)
|
||
*
|
||
* @param array $dealers
|
||
* @return array
|
||
*/
|
||
protected function mapManageSubsidyDealers(array $dealers): array
|
||
{
|
||
$map = [];
|
||
$last = null;
|
||
$ranking = 1;
|
||
|
||
foreach ($dealers as $dealer) {
|
||
if ($last === null || $dealer->lvl->value > $last->lvl->value) {
|
||
$last = $dealer;
|
||
$map[] = $last;
|
||
$ranking = 1;
|
||
} elseif ($ranking < 3 && $dealer->lvl->value === $last->lvl->value) {
|
||
$last = $dealer;
|
||
$map[] = $last;
|
||
$ranking++;
|
||
}
|
||
}
|
||
|
||
return $map;
|
||
}
|
||
|
||
/**
|
||
* 生成管理者的销售业绩
|
||
*
|
||
* @param \App\Models\DealerOrder $dealerOrder
|
||
* @param array $dealers
|
||
* @param string $tz
|
||
* @return void
|
||
*/
|
||
protected function handleManagerSalesLogs(DealerOrder $dealerOrder, array $dealers, string $tz): void
|
||
{
|
||
if (is_null($manager = $this->firstManager($dealers))) {
|
||
return;
|
||
}
|
||
|
||
$logs = [];
|
||
|
||
foreach ($dealerOrder->products as $product) {
|
||
$logs[] = [
|
||
'user_id' => $manager->user_id,
|
||
'lvl' => $manager->lvl,
|
||
'order_id' => $product->order_id,
|
||
'product_id' => $product->product_id,
|
||
'sales_volume' => $product->qty,
|
||
'created_at' => $tz,
|
||
'updated_at' => $tz,
|
||
];
|
||
}
|
||
|
||
DealerManagerSalesLog::insert($logs);
|
||
}
|
||
|
||
/**
|
||
* 从给定的经销商中获取第一个管理者
|
||
*
|
||
* @param array $dealers
|
||
* @return \App\Models\Dealer|null
|
||
*/
|
||
protected function firstManager(array $dealers): ?Dealer
|
||
{
|
||
foreach ($dealers as $dealer) {
|
||
if ($dealer->is_manager) {
|
||
return $dealer;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 获取给定用户的实际上级经销商
|
||
*
|
||
* @param \App\Models\UserInfo $userInfo
|
||
* @return array
|
||
*/
|
||
protected function getRealDealers(UserInfo $userInfo): array
|
||
{
|
||
$ancestors = [];
|
||
|
||
if (empty($pids = $userInfo->real_parent_ids)) {
|
||
return $ancestors;
|
||
}
|
||
|
||
$ancestors = UserInfo::with(['dealer'])
|
||
->whereIn('user_id', $pids)
|
||
->latest('depth')
|
||
->get(['user_id']);
|
||
|
||
return $ancestors->map(function ($item) {
|
||
return $item->dealer;
|
||
})->all();
|
||
}
|
||
}
|