6
0
Fork 0

Merge branch 'hotfix/fix-dealer-settle' into develop

release
vine_liutk 2022-02-17 21:10:56 +08:00
commit df124a8dc8
3 changed files with 788 additions and 12 deletions

View File

@ -0,0 +1,722 @@
<?php
namespace App\Admin\Imports;
use App\Enums\DealerEarningStatus;
use App\Enums\DealerLvl;
use App\Enums\DealerOrderSettleState;
use App\Exceptions\BizException;
use App\Exceptions\ImportException;
use App\Models\Dealer;
use App\Models\DealerChannelSubsidyLog;
use App\Models\DealerManagerSalesLog;
use App\Models\DealerManageSubsidyLog;
use App\Models\DealerOrder;
use App\Models\DealerOrderAllocateLog;
use App\Models\DealerProduct;
use App\Models\DealerPurchaseLog;
use App\Models\DealerUserProduct;
use App\Models\User;
use App\Services\Dealer\OrderService;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
use Throwable;
class DealerOrderImport extends Import
{
public function loadRow($row)
{
/**校验行数据 **/
//获取下单用户
$phone = $row->getCellAtIndex(0)?->getValue();
if (empty($phone)) {
throw new ImportException('未输入下单用户手机号');
}
//获取下单盒数;
$qty = $row->getCellAtIndex(1)?->getValue();
if (empty($qty)) {
throw new ImportException('未输入下单盒数');
}
//获取下单金额;
$totalAmount = $row->getCellAtIndex(2)?->getValue();
if (empty($totalAmount)) {
throw new ImportException('未输入下单金额');
}
//获取目标等级
$toLv = $row->getCellAtIndex(3)?->getValue();
if (empty($toLv)) {
throw new ImportException('未输入目标等级');
}
$productId = $row->getCellAtIndex(4)?->getValue();
if (empty($productId)) {
$productId = 1;
}
$user = User::where('phone', $phone)->first();
dump('手机号:'.$phone.'开始执行');
if ($user) {
$orderService = new OrderService();
$product = DealerProduct::findOrFail($productId);
$shippingAddress = $user->shippingAddresses()->orderBy('is_default', 'desc')->first();
try {
DB::beginTransaction();
//执行自己升级
if ($toLv < 4) {
$user->dealer->upgrade(DealerLvl::from($toLv), '后台修改等级');
//执行上级尝试升级
foreach ($user->dealer->getDealers() as $parentDealer) {
$parentDealer->attemptUpgrade();
}
} else {
/**下单 -start **/
//没有地址
if (!$shippingAddress) {
$shippingAddress = $user->shippingAddresses()->create([
'zone_id'=>3,
'zone'=>'北京市 北京市 东城区',
'consignee'=>'测试',
'telephone'=>'18888888888',
'address'=>'1',
'is_default'=>true,
]);
}
$order = $orderService->quickCreateOrder($user, $product, $qty, $shippingAddress->id);
//重新按新规则给一次发货人
if ($order->consignor_id) {
$order->update([
'consignor_id'=>$this->getConsignor($user, $order->total_amount)?->user_id,
]);
$order->refresh();
if (!($order->consignor_id > 1)) {//如果是null或者1
//确认接单
$order = $orderService->confirmOrder($order);
}
}
/**下单 -end **/
dump('下单:'.$order->sn);
/**更新订单状态 -start **/
//【金牌】,【特邀】订单需要发货人有对应的库存,否则往上丢
if (in_array($toLv, [
'2', '3',
])) {
do {
if ($order->consignor_id > 1) {
$userProduct = DealerUserProduct::where('user_id', $order->consignor_id)->where('stock', '>', 0)->first();
if (!$userProduct) {
//没找到库存商品,则继续往上丢
$order = $this->updateOrderConsignor($order);
$order->refresh();
} else {
// if ($userProduct->stock < $qty) {
// //记录手机号
// }
}
} else {
break;
}
} while (!$userProduct);
}
if ($order->isPending()) {
//确认接单
$order = $orderService->confirmOrder($order);
}
//确认打款
$order = $orderService->payOrder($order, 'offline');
//确认收款
$order = $orderService->paidOrder($order);
//如果目标等级是【金牌】【特邀】则只到【待发货】。其余订单走到【已完成】
if (!in_array($toLv, [
'2', '3',
])) {
//确认发货
$order = $orderService->shippingOrder($order);
//确认收货
$order = $orderService->shippingedOrder($order);
}
/**更新订单状态 -end **/
//执行升级结算订单
$this->handleDealerOrder($order);
}
DB::commit();
} catch (QueryException $e) {
DB::rollBack();
if (strpos($e->getMessage(), 'Numeric value out of range') !== false) {
$e = new BizException('当前可发货库存不足');
}
throw $e;
} catch (Throwable $th) {
DB::rollBack();
report($th);
throw new BizException($th->getMessage());
}
} else {
throw new ImportException('未找到用户:'.$phone);
}
}
/**
* 更新订单发货人
*
* @return DealerOrder
*/
public function updateOrderConsignor(DealerOrder $order)
{
//只处理当前订单有发货人的情况
if ($order->consignor) {
$consignor = $this->getConsignor($order->user, $order->total_amount, $order->consignor);
$oldConsignor = $order->consignor;
$order->update([
'allocated_at' => now(),
'consignor_id' => $consignor?->user_id,
]);
//记录分配日志
DealerOrderAllocateLog::create([
'order_id'=>$order->id,
'last_consignor_id'=>$oldConsignor->id,
'new_consignor_id' =>$order->consignor_id,
]);
}
return $order;
}
private function getConsignor(User $user, $totalAmount, ?User $lastConsignor = null)
{
$rules = [
[
'amount' => '2640',
'lvl' => DealerLvl::Contracted,
],
[
'amount' => '860',
'lvl' => DealerLvl::Special,
],
[
'amount' => '630',
'lvl' => DealerLvl::Gold,
],
];
$lvl = $user->dealer->lvl;
//计算通过这个订单可能升级成为的身份
foreach ($rules as $rule) {
if ($totalAmount >= $rule['amount'] && $lvl->value < $rule['lvl']->value) {
$lvl = $rule['lvl'];
}
}
//如果是签约单,直接抛到公司后台发货
if ($lvl->value >= DealerLvl::Contracted->value) {
return null;
}
//新逻辑
$consignor = null;
$_lastConsignor = $lastConsignor;
do {
$query = User::with(['userInfo', 'dealer']);
if ($_lastConsignor) {
$query->where('id', $_lastConsignor->userInfo->real_inviter_id);
} else {
$query->where('id', $user->userInfo->real_inviter_id);
}
$consignor = $query->first();
if ($consignor) {//找到老上级
if ($consignor->dealer->is_sale == true && $consignor->dealer->lvl->value > $lvl->value) {
break;
} else {
$_lastConsignor = $consignor;
$consignor = null;
}
} else {//如果找不到人了
$consignor = $lastConsignor;
break;
}
} while (empty($consignor));
return $consignor?->userInfo;
}
/**
* 处理经销商订单
*
* @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;
$lvlValue = [
DealerLvl::Contracted->value => '2640',
DealerLvl::Special->value => '860',
DealerLvl::Gold->value => '630',
];
if ($dealerOrder->total_amount >= $lvlValue[DealerLvl::Contracted->value]) {
// 升级为签约
if ($lvl->value < DealerLvl::Contracted->value) {
$lvl = DealerLvl::Contracted;
}
} elseif ($dealerOrder->total_amount >= $lvlValue[DealerLvl::Special->value]) {
// 升级为特约
if ($lvl->value < DealerLvl::Special->value) {
$lvl = DealerLvl::Special;
}
} elseif ($dealerOrder->total_amount >= $lvlValue[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 = $lvlValue[DealerLvl::Contracted->value];
} else {
$upgradeAmount = $lvlValue[$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 = [
DealerLvl::Contracted->value.'_0' => '396',
DealerLvl::Contracted->value.'_1' => '79',
];
$dealers[] = $_dealer;
$last = $_dealer;
} elseif ($isUp && $_dealer->isSpecialDealer()) {
// 渠道补贴规则: 签约 -> 特邀 -> 签约 -> 签约
$rule = [
DealerLvl::Special->value.'_0' => '264',
DealerLvl::Contracted->value.'_1' => '132',
DealerLvl::Contracted->value.'_2' => '79',
];
$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 = [
DealerLvl::Special->value.'_0' => '80',
DealerLvl::Special->value.'_1' => '20',
];
$dealers[] = $_dealer;
$last = $_dealer;
} elseif ($isUp && $_dealer->isGoldDealer()) {
// 渠道补贴规则: 特邀 -> 金牌 -> 特邀 -> 特邀
$rule = [
DealerLvl::Gold->value.'_0' => '50',
DealerLvl::Special->value.'_1' => '30',
DealerLvl::Special->value.'_2' => '20',
];
$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 = [
DealerLvl::Gold->value.'_0' => '75',
];
$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)
{
if (! $this->isContractedDealerToPurchase($dealerOrder)) {
return;
}
$dealer = $dealerOrder->userInfo->dealer;
// 采购业绩是否算自己的业绩
$valid = $dealer->lvl->value >= DealerLvl::Contracted->value;
$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)
{
if (! $this->isContractedDealerToPurchase($dealerOrder)) {
return;
}
$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->value)) {
$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 (! $this->isContractedDealerToPurchase($dealerOrder)) {
return;
}
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 DealerOrder $dealerOrder
* @return bool
*/
protected function isContractedDealerToPurchase(DealerOrder $dealerOrder): bool
{
$dealer = $dealerOrder->userInfo->dealer;
if ($dealer->lvl->value < DealerLvl::Contracted->value) {
if ($dealerOrder->total_amount < '2640') {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Console\Commands\Dealer;
use App\Admin\Imports\DealerOrderImport;
use App\Models\ImportJobLog;
use Illuminate\Console\Command;
class ManagerSubsidySettleCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dealer:import-order {fileUri}';
protected $fileUri = '';
/**
* The console command description.
*
* @var string
*/
protected $description = '导入批零订单';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if ($this->hasArgument('fileUri')) {
$this->fileUri = $this->argument('fileUri');
}
$import = new DealerOrderImport();
$res = $import->readFileByUrl($this->fileUri);
ImportJobLog::insert(array_map(function ($value) {
return array_merge($value, [
'job_id'=> 0,
'created_at' => now(),
'updated_at' => now(),
]);
}, $res['errors']));
}
}

View File

@ -208,30 +208,31 @@ class OrderService
* 确认接单
*
* @param DealerOrder $order
* @return void
* @return DealerOrder $order
*/
public function confirmOrder(DealerOrder $order)
{
if (!$order->isPending()) {
throw new BizException('订单状态异常,请刷新后再试');
throw new BizException('无法接单:订单状态异常,请刷新后再试');
}
$order->update([
'status' => DealerOrderStatus::Paying,
]);
return $order;
}
/**
* 确认打款
*
* @return void
* @return DealerOrder $order
*/
public function payOrder(DealerOrder $order, string $payWay, ?string $payImage)
public function payOrder(DealerOrder $order, string $payWay, ?string $payImage = null)
{
if (empty($payWay)) {
throw new BizException('请选择付款方式');
}
if (!$order->isPendinged()) {
throw new BizException('订单状态异常,请刷新后再试');
throw new BizException('无法付款:订单状态异常,请刷新后再试');
}
switch ($payWay) {
case DealerOrder::PAY_WAY_WALLET:
@ -260,35 +261,37 @@ class OrderService
]);
break;
}
return $order;
}
/**
* 确认收款
*
* @param DealerOrder $order
* @return void
* @return DealerOrder $order
*/
public function paidOrder(DealerOrder $order)
{
if (!$order->isPay()) {
throw new BizException('订单状态异常,请刷新后再试');
throw new BizException('无法收款:订单状态异常,请刷新后再试');
}
$order->update([
'status' => DealerOrderStatus::Paid,
'paied_time' => now(),
]);
return $order;
}
/**
* 确认发货
*
* @param DealerOrder $order
* @return void
* @return DealerOrder $order
*/
public function shippingOrder(DealerOrder $order)
{
if (!$order->isPaid()) {
throw new BizException('订单状态异常,请刷新后再试');
throw new BizException('无法发货:订单状态异常,请刷新后再试');
}
//扣减发货人库存
if ($order->consignor) {
@ -299,12 +302,13 @@ class OrderService
'status' => DealerOrderStatus::Shipped,
'shipping_time' => now(),
]);
return $order;
}
public function shippingedOrder(DealerOrder $order)
{
if (!$order->isShipping()) {
throw new BizException('订单状态异常,请刷新后再试');
throw new BizException('无法收货:订单状态异常,请刷新后再试');
}
//增加自己的库存
$this->orderInQty($order);
@ -313,16 +317,18 @@ class OrderService
'status' => DealerOrderStatus::Completed,
'shippinged_time' => now(),
]);
return $order;
}
public function cancelOrder(DealerOrder $order)
{
if (!($order->isPending() || $order->isPendinged() || $order->isPay())) {
throw new BizException('订单状态异常,请刷新后再试');
throw new BizException('无法取消:订单状态异常,请刷新后再试');
}
$order->update([
'status' => DealerOrderStatus::Cancelled,
]);
return $order;
}
/**
@ -334,7 +340,7 @@ class OrderService
{
//只处理当前订单有发货人的情况
if ($order->consignor) {
$consignor = $this->getConsignor($order->user, $order->totalAmount, $order->consignor);
$consignor = $this->getConsignor($order->user, $order->total_amount, $order->consignor);
$oldConsignor = $order->consignor;
$order->update([
'allocated_at' => now(),
@ -347,6 +353,7 @@ class OrderService
'new_consignor_id' =>$order->consignor_id,
]);
}
return $order;
}
/**