531 lines
17 KiB
PHP
531 lines
17 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands\Dealer;
|
||
|
||
use App\Enums\DealerEarningStatus;
|
||
use App\Enums\DealerLvl;
|
||
use App\Enums\DealerOrderSettleState;
|
||
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 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()
|
||
{
|
||
while (true) {
|
||
$page = 0;
|
||
|
||
DealerOrder::settlePending()->chunkById(200, function ($orders) use (&$page) {
|
||
$orders->load([
|
||
'userInfo',
|
||
'dealer.userInfo',
|
||
'products.productManageSubsidyRules',
|
||
]);
|
||
|
||
foreach ($orders as $order) {
|
||
try {
|
||
DB::transaction(function () use ($order) {
|
||
$this->handleDealerOrder($order);
|
||
});
|
||
} catch (Throwable $e) {
|
||
report($e);
|
||
}
|
||
}
|
||
|
||
$page += 1;
|
||
}, 'paied_time');
|
||
|
||
if ($page === 0) {
|
||
sleep(60);
|
||
} else {
|
||
sleep(5);
|
||
}
|
||
};
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* 处理经销商订单
|
||
*
|
||
* @param \App\Models\DealerOrder $dealerOrder
|
||
* @return void
|
||
*/
|
||
protected function handleDealerOrder(DealerOrder $dealerOrder)
|
||
{
|
||
$tz = now()->toDateTimeString();
|
||
|
||
// 上级经销商
|
||
$ancestors = $dealerOrder->dealer->getDealers();
|
||
|
||
// 签约经销商的进货日志
|
||
$this->handlePurchaseLogsOfContractedDealer($dealerOrder, $tz);
|
||
|
||
// 一级签约经销商和二级经销商的管理津贴
|
||
$this->handleManageSubsidyLogs($dealerOrder, $ancestors, $tz);
|
||
|
||
// 管理者的销售业绩
|
||
$this->handleManagerSalesLogs($dealerOrder, $ancestors, $tz);
|
||
|
||
// 渠道补贴
|
||
$this->handleChannelSubsidy($dealerOrder);
|
||
|
||
if ($dealerOrder->dealer->wasChanged('lvl')) {
|
||
$dealers = [
|
||
$dealerOrder->dealer,
|
||
...$ancestors,
|
||
];
|
||
|
||
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);
|
||
|
||
if ($lvl->value >= DealerLvl::Contracted->value) {
|
||
$upgradeAmount = app_settings('dealer.upgrade_amount_'.DealerLvl::Contracted->value);
|
||
} else {
|
||
$upgradeAmount = app_settings('dealer.upgrade_amount_'.$lvl->value);
|
||
}
|
||
|
||
// 手续费比例
|
||
$feeRate = app_settings('dealer.fee_rate');
|
||
|
||
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($channelSubsidyLog->total_amount, bcdiv($feeRate, '100', 5), 3);
|
||
$fee = round($fee, 2);
|
||
|
||
$channelSubsidyLog->earning()->create([
|
||
'user_id' => $channelSubsidyLog->user_id,
|
||
'lvl' => $channelSubsidyLog->lvl,
|
||
'total_amount' => $channelSubsidyLog->total_amount,
|
||
'total_earnings' => bcsub($channelSubsidyLog->total_amount, $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;
|
||
// 前一个直属邀请人
|
||
$previous = null;
|
||
|
||
while (true) {
|
||
if ($previous) {
|
||
$_dealer = $previous->userInfo->realInviterInfo?->dealer;
|
||
$_dealer?->setRelation('userInfo', $previous->userInfo->realInviterInfo);
|
||
} else {
|
||
$_dealer = $dealer->userInfo->realInviterInfo?->dealer;
|
||
$_dealer?->setRelation('userInfo', $dealer->userInfo->realInviterInfo);
|
||
}
|
||
|
||
$previous = $_dealer;
|
||
|
||
if ($_dealer === null) {
|
||
break;
|
||
}
|
||
|
||
// 如果经销商等级小于金牌, 那么跳过
|
||
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 (! in_array($dealer->lvl, [DealerLvl::Secondary, DealerLvl::Top])) {
|
||
continue;
|
||
}
|
||
|
||
// 如果当前经销商等级没有对应的管理津贴分配规则,则忽略
|
||
if (is_null($rule = $rules->get($dealer->lvl->value))) {
|
||
continue;
|
||
}
|
||
|
||
// 同等级管理津贴最多给三次
|
||
if ($last === null || $dealer->lvl->value > $last->lvl->value) {
|
||
if ($ranking < 3 && $dealer->lvl === DealerLvl::Top) {
|
||
if ($secondarySubsidyRule = $rules->get(DealerLvl::Secondary)) {
|
||
$key = 'price_'.(1 + $ranking).'st';
|
||
|
||
$secondarySubsidy = $secondarySubsidyRule->{$key};
|
||
|
||
if (bccomp($secondarySubsidy, '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, $secondarySubsidy, 2),
|
||
'created_at' => $tz,
|
||
'updated_at' => $tz,
|
||
];
|
||
}
|
||
}
|
||
}
|
||
|
||
$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;
|
||
}
|
||
}
|