6
0
Fork 0
jiqu-library-server/app/Console/Commands/Dealer/PurchaseSubsidySettleComman...

406 lines
14 KiB
PHP

<?php
namespace App\Console\Commands\Dealer;
use App\Actions\Dealer\CalculatePurchaseAmount;
use App\Enums\DealerEarningStatus;
use App\Enums\DealerLvl;
use App\Enums\DealerOrderSettleState;
use App\Enums\DealerPurchaseSubsidySettleState;
use App\Enums\DealerPurchaseSubsidyStatus;
use App\Models\Dealer;
use App\Models\DealerOrder;
use App\Models\DealerPurchaseSubsidy;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
class PurchaseSubsidySettleCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dealer:purchase-subsidy-settle';
/**
* The console command description.
*
* @var string
*/
protected $description = '结算签约经销商的进货补贴';
/**
* @var \App\Actions\Dealer\CalculatePurchaseAmount
*/
protected $calculatePurchaseAmount;
/**
* @param \App\Actions\Dealer\CalculatePurchaseAmount $calculatePurchaseAmount
*/
public function __construct(CalculatePurchaseAmount $calculatePurchaseAmount)
{
parent::__construct();
$this->calculatePurchaseAmount = $calculatePurchaseAmount;
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$tz = now();
if ($tz->day >= 20) {
// 结算当月5号-19号的管理津贴
$startAt = $tz->copy()->setDay(5)->startOfDay();
$endAt = $tz->copy()->setDay(19)->endOfDay();
} elseif ($tz->day >= 5) {
// 结算上月20号-到当月4号的管理津贴
$startAt = $tz->copy()->subMonthNoOverflow()->set('day', 20)->startOfDay();
$endAt = $tz->copy()->set('day', 4)->endOfDay();
} else {
// 结算上月5号-到19号的管理津贴
$startAt = $tz->copy()->subMonthNoOverflow()->setDay(5)->startOfDay();
$endAt = $startAt->copy()->setDay(19)->endOfDay();
}
$head = '【'.$startAt->format('Y/m/d').'-'.$endAt->format('Y/m/d').'】';
$ordersCount = DealerOrder::onlyCompleted()
->where('settle_state', '!=', DealerOrderSettleState::Completed)
->whereNotNull('shippinged_time')
->where('shippinged_time', '<=', $endAt)
->count();
if ($ordersCount > 0) {
return $this->warn("{$head} 订单还没有结算完成!");
}
$this->info("{$head}------------[开始]进货补贴结算------------");
$this->info("{$head}进货补贴初始化...");
$this->initializePurchaseSubsidies($startAt, $endAt, 200);
$this->info("{$head}进货补贴初始化完成".PHP_EOL);
$this->info("{$head}扣除上级的进货补贴...");
$this->deductPurchaseSubsidies($startAt, $endAt, 200);
$this->info("{$head}扣除上级的进货补贴完成".PHP_EOL);
$this->info("{$head}计算手续费...");
$this->calculateFeeOfPurchaseSubsidies($startAt, $endAt, 200);
$this->info("{$head}计算手续费完成".PHP_EOL);
$this->info("{$head}Done! 总耗时: ".$this->formatDuration($tz->diffInMilliseconds(now(), false)));
$this->info("{$head}------------[结束]进货补贴结算------------".PHP_EOL);
return 0;
}
/**
* 计算进货补贴手续费
*
* @param \Illuminate\Support\Carbon $startAt
* @param \Illuminate\Support\Carbon $endAt
* @param int $count
* @return void
*/
protected function calculateFeeOfPurchaseSubsidies(Carbon $startAt, Carbon $endAt, int $count = 200): void
{
DealerPurchaseSubsidy::where([
'start_at' => $startAt,
'end_at' => $endAt,
'settle_state' => DealerPurchaseSubsidySettleState::Processed,
])->chunkById($count, function ($purchaseSubsidies) {
foreach ($purchaseSubsidies as $purchaseSubsidy) {
DB::transaction(function () use ($purchaseSubsidy) {
$this->calculateFeeOfPurchaseSubsidy($purchaseSubsidy);
});
}
});
}
/**
* 计算进货补贴手续费
*
* @param \App\Models\DealerPurchaseSubsidy $purchaseSubsidy
* @return void
*/
protected function calculateFeeOfPurchaseSubsidy(DealerPurchaseSubsidy $purchaseSubsidy)
{
if (bccomp($purchaseSubsidy->total_amount, '0') === 1) {
$feeRate = bcdiv($purchaseSubsidy->fee_rate, '100', 5);
$fee = bcmul($purchaseSubsidy->total_amount, $feeRate, 3);
$fee = round($fee, 2);
$purchaseSubsidy->fee = $fee;
$purchaseSubsidy->real_amount = bcsub($purchaseSubsidy->total_amount, $fee, 2);
} else {
$purchaseSubsidy->status = DealerPurchaseSubsidyStatus::Completed;
}
$purchaseSubsidy->settle_state = DealerPurchaseSubsidySettleState::Completed;
$purchaseSubsidy->save();
if (! $purchaseSubsidy->isCompleted()) {
$remark = sprintf(
"%s - %s\n销售业绩: %s\n补贴比例: %s%%",
$purchaseSubsidy->start_at->format('Y/m/d'),
$purchaseSubsidy->end_at->format('Y/m/d'),
$purchaseSubsidy->total_purchase_amount,
$purchaseSubsidy->subsidy_rate
);
$purchaseSubsidy->earning()->create([
'user_id' => $purchaseSubsidy->user_id,
'lvl' => $purchaseSubsidy->lvl,
'total_amount' => $purchaseSubsidy->total_amount,
'total_earnings' => $purchaseSubsidy->real_amount,
'fee' => $purchaseSubsidy->fee,
'fee_rate' => $purchaseSubsidy->fee_rate,
'settle_at' => now(),
'status' => DealerEarningStatus::Pending,
'remark' => $remark,
]);
}
}
/**
* 扣除上级的进货补贴
*
* @param \Illuminate\Support\Carbon $startAt
* @param \Illuminate\Support\Carbon $endAt
* @param int $count
* @return void
*/
protected function deductPurchaseSubsidies(Carbon $startAt, Carbon $endAt, int $count = 200): void
{
DealerPurchaseSubsidy::where([
'start_at' => $startAt,
'end_at' => $endAt,
'settle_state' => DealerPurchaseSubsidySettleState::Pending,
])->chunkById($count, function ($purchaseSubsidies) {
foreach ($purchaseSubsidies as $purchaseSubsidy) {
DB::transaction(function () use ($purchaseSubsidy) {
$this->deductPurchaseSubsidy($purchaseSubsidy);
});
}
});
}
/**
* 扣除上级的采购补贴总额
*
* @param \AppModels\DealerPurchaseSubsidy $purchaseSubsidy
* @return void
*/
protected function deductPurchaseSubsidy(DealerPurchaseSubsidy $purchaseSubsidy)
{
// 扣除上级的进货补贴
if ($purchaseSubsidy->payer_id && bccomp($purchaseSubsidy->total_subsidy, '0', 2) === 1) {
$payerPurchaseSubsidy = DealerPurchaseSubsidy::where([
'user_id' => $purchaseSubsidy->payer_id,
'start_at' => $purchaseSubsidy->start_at,
'end_at' => $purchaseSubsidy->end_at,
])->first();
if ($payerPurchaseSubsidy) {
$payerPurchaseSubsidy->decrement('total_amount', $purchaseSubsidy->total_subsidy);
$payerPurchaseSubsidy->logs()->create([
'purchase_subsidy_id' => $payerPurchaseSubsidy->id,
'change_from_purchase_subsidy_id' => $purchaseSubsidy->id,
'change_amount' => bcmul($purchaseSubsidy->total_subsidy, '-1', 2),
'remark' => '扣除下级的进货补贴',
]);
}
}
$purchaseSubsidy->update([
'settle_state' => DealerPurchaseSubsidySettleState::Processed,
]);
}
/**
* 初始化进货补贴
*
* @param \Illuminate\Support\Carbon $startAt
* @param \Illuminate\Support\Carbon $endAt
* @param int $count
* @return void
*/
protected function initializePurchaseSubsidies(Carbon $startAt, Carbon $endAt, int $count = 200)
{
// 手续费比例
$feeRate = app_settings('dealer.fee_rate');
// 采购补贴规则
$purchaseRules = (array) app_settings('dealer.purchase_rules');
$lastId = $this->getLastDealerId($startAt, $endAt);
do {
$dealers = Dealer::with(['userInfo'])
->where('contracted_lvl_at', '<=', $endAt)
->where('lvl', '>=', DealerLvl::Contracted->value)
->forPageAfterId($count, $lastId, 'id')
->get();
$dealersCount = $dealers->count();
if ($dealersCount == 0) {
break;
}
foreach ($dealers as $dealer) {
DB::transaction(function () use ($dealer, $startAt, $endAt, $feeRate, $purchaseRules) {
$this->initializePurchaseSubsidy($dealer, $startAt, $endAt, $feeRate, $purchaseRules);
});
$lastId = $dealer->id;
}
unset($dealers);
} while ($dealersCount == $count);
}
/**
* 初始化进货补贴
*
* @param Dealer $dealer
* @param Carbon $startAt
* @param Carbon $endAt
* @param float $feeRate
* @param array $purchaseRules
* @return void
*/
protected function initializePurchaseSubsidy(Dealer $dealer, Carbon $startAt, Carbon $endAt, $feeRate, array $purchaseRules)
{
// 进货总额
$totalPurchaseAmount = $this->calculatePurchaseAmount->handle($dealer, $startAt, $endAt);
// 如果没有进货总额,则返回
if (bccomp($totalPurchaseAmount, '0', 2) <= 0) {
return;
}
// 进货补贴比例
$subsidyRate = $this->filterSubsidyRate($totalPurchaseAmount, $purchaseRules);
// 补贴总额
$totalSubsidy = bcmul($totalPurchaseAmount, bcdiv($subsidyRate, '100', 5), 3);
$totalSubsidy = round($totalSubsidy, 2);
$purchaseSubsidy = DealerPurchaseSubsidy::create([
'user_id' => $dealer->user_id,
'payer_id' => $this->nearestContractedDealer($dealer, $endAt)?->user_id,
'lvl' => $dealer->lvl,
'total_purchase_amount' => $totalPurchaseAmount,
'subsidy_rate' => $subsidyRate,
'total_subsidy' => $totalSubsidy,
'total_amount' => $totalSubsidy,
'real_amount' => 0,
'fee' => 0,
'fee_rate' => $feeRate,
'start_at' => $startAt,
'end_at' => $endAt,
'settle_state' => DealerPurchaseSubsidySettleState::Pending,
'status' => DealerPurchaseSubsidyStatus::Pending,
]);
if (bccomp($purchaseSubsidy->total_subsidy, '0', 2) === 1) {
$purchaseSubsidy->logs()->create([
'purchase_subsidy_id' => $purchaseSubsidy->id,
'change_from_purchase_subsidy_id' => null,
'change_amount' => $purchaseSubsidy->total_subsidy,
'remark' => '进货补贴总额',
]);
}
}
/**
* 进货补贴比例
*
* @param float $totalPurchaseAmount
* @param array $purchaseRules
* @return float
*/
protected function filterSubsidyRate($totalPurchaseAmount, array $purchaseRules)
{
$rate = '0';
foreach ($purchaseRules as $rule) {
if (bccomp($totalPurchaseAmount, bcmul($rule['price'], '10000'), 2) === -1) {
continue;
}
if (bccomp($rule['rate'], $rate, 5) === 1) {
$rate = $rule['rate'];
}
}
return $rate;
}
/**
* 获取最近的上级签约经销商
*
* @param \App\Models\Dealer $dealer
* @param \Illuminate\Support\Carbon $startAt
* @return \App\Models\Dealer|null
*/
protected function nearestContractedDealer(Dealer $dealer, Carbon $endAt): ?Dealer
{
foreach ($dealer->getDealers() as $_dealer) {
// 如果当前经销商等级小于签约,则跳过
if ($_dealer->lvl->value < DealerLvl::Contracted->value) {
continue;
}
if ($_dealer->contracted_lvl_at?->lte($endAt)) {
return $_dealer;
}
}
return null;
}
/**
* 获取给定时间端内的最后一个进货补贴所属经销商的ID
*
* @param \Illuminate\Support\Carbon $startAt
* @param \Illuminate\Support\Carbon $endAt
* @return int|null
*/
protected function getLastDealerId(Carbon $startAt, Carbon $endAt): ?int
{
$lastPurchaseSubsidy = DealerPurchaseSubsidy::where('start_at', $startAt)
->where('end_at', $endAt)
->latest('id')
->first();
return $lastPurchaseSubsidy?->dealer?->id;
}
/**
* 格式化时间
*
* @param float $milliseconds
* @return string
*/
protected function formatDuration($milliseconds): string
{
if ($milliseconds < 0.01) {
return round($milliseconds * 1000) . 'μs';
} elseif ($milliseconds >= 1000) {
return round($milliseconds / 1000, 2) . 's';
}
return $milliseconds . 'ms';
}
}