6
0
Fork 0

Merge branch 'develop' of gitee.com:zi-chunsheng-e-commerce/mall-server

release
李静 2022-03-31 10:59:22 +08:00
commit 78af7fd2cf
20 changed files with 383 additions and 81 deletions

View File

@ -70,7 +70,7 @@ class DealerEarningPay extends AbstractTool
$action = DealerWalletAction::PurchaseSubsidyIn;
break;
}
$walletService->changeBalance($earning->user, $earning->total_earnings, $action, '收入-'.$earning->type_name, $earning);
$walletService->changeBalance($earning->user, $earning->total_earnings, $action, '收入-'.$earning->earningable_type_text, $earning);
DB::commit();
} catch (Throwable $th) {
DB::rollBack();

View File

@ -59,10 +59,6 @@ class DealerChannelSubsidyController extends AdminController
return $this->settle_at?->toDateTimeString();
})->sortable();
$grid->column('status', '状态')->display(function ($v) {
if (! $this->isSettled()) {
return "<i class='fa fa-circle' style='font-size: 13px;color: #b9c3cd'></i>&nbsp;&nbsp;待结算";
}
return "<i class='fa fa-circle' style='font-size: 13px;color: {$v->color()}'></i>&nbsp;&nbsp;{$v->text()}";
});
$grid->column('pay_at', '付款时间')->display(function () {
@ -72,50 +68,35 @@ class DealerChannelSubsidyController extends AdminController
return $this->created_at?->toDateTimeString();
});
$grid->showRowSelector();
// $grid->showRowSelector();
// $grid->tools(function ($tools) {
// $tools->batch(function ($batch) {
// $batch->disableDelete();
$grid->tools(function ($tools) {
$tools->batch(function ($batch) {
$batch->disableDelete();
// if (Admin::user()->can('dcat.admin.dealer_channel_subsidies.batch_pay')) {
// $batch->add(new DealerChannelSubsidyBatchPay());
// }
// });
// });
if (Admin::user()->can('dcat.admin.dealer_channel_subsidies.batch_pay')) {
$batch->add(new DealerChannelSubsidyBatchPay());
}
});
});
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (
$actions->row->isSettled() &&
$actions->row->isPending() &&
Admin::user()->can('dcat.admin.dealer_channel_subsidies.pay')
) {
$actions->append(new DealerChannelSubsidyPay());
}
});
$grid->disableActions();
// $grid->actions(function (Grid\Displayers\Actions $actions) {
// if (
// $actions->row->isSettled() &&
// $actions->row->isPending() &&
// Admin::user()->can('dcat.admin.dealer_channel_subsidies.pay')
// ) {
// $actions->append(new DealerChannelSubsidyPay());
// }
// });
$grid->filter(function (Grid\Filter $filter) {
$filter->panel();
$filter->equal('user.phone', '手机号')->width(3);
$filter->where('status', function ($query) {
switch ($this->input) {
case 'pending':
$query->whereNull('settle_at')->where('status', DealerEarningStatus::Pending);
break;
case 'paying':
$query->whereNotNull('settle_at')->where('status', DealerEarningStatus::Pending);
break;
case 'completed':
$query->where('status', DealerEarningStatus::Completed);
break;
}
}, '状态')->select([
'pending' => '待结算',
'paying' => '待付款',
'completed' => '已完成',
$filter->equal('status', '状态')->select([
DealerEarningStatus::Pending->value => '待付款',
DealerEarningStatus::Completed->value => '已完成',
])->width(3);
$filter->between('settle_at', '结算时间')->datetime()->width(6);
});

View File

@ -120,6 +120,7 @@ class DealerEarningController extends AdminController
$totalAmount = (clone $query)->sum('total_amount');
$row->column(3, new InfoBox('金额', $totalAmount, 'fa fa-cny'));
$row->column(3, new InfoBox('手续费', (clone $query)->sum('fee'), 'fa fa-cny'));
});
});

View File

@ -99,6 +99,8 @@ class DealerPurchaseSubsidyController extends AdminController
});
$row->column(3, new InfoBox('进货业绩', (clone $query)->sum('total_purchase_amount'), 'fa fa-cny'));
$row->column(3, new InfoBox('补贴金额', (clone $query)->sum('total_amount'), 'fa fa-cny'));
$row->column(3, new InfoBox('手续费', (clone $query)->sum('fee'), 'fa fa-cny'));
});
});

View File

@ -68,7 +68,7 @@ class DealerEarningPay extends Form implements LazyRenderable
break;
}
$walletService->changeBalance($earning->user, $earning->total_earnings, $action, '收入-'.$earning->type_name, $earning);
$walletService->changeBalance($earning->user, $earning->total_earnings, $action, '收入-'.$earning->earningable_type_text, $earning);
DB::commit();
} catch (Throwable $th) {
DB::rollBack();

View File

@ -2,7 +2,11 @@
namespace App\Admin\Renderable;
use App\Models\DealerChannelSubsidyLog;
use App\Models\DealerEarning;
use App\Models\DealerManagerSubsidy;
use App\Models\DealerManageSubsidy;
use App\Models\DealerPurchaseSubsidy;
use Dcat\Admin\Grid;
use Dcat\Admin\Grid\LazyRenderable;
use Dcat\Admin\Widgets\Card;
@ -11,7 +15,7 @@ class DealerEarningSimpleTable extends LazyRenderable
{
public function grid(): Grid
{
$userId = $this->payload['id']??0;
$userId = $this->payload['id'] ?? 0;
$builder = DealerEarning::query();
// dd($userId);
$builder->with(['user', 'payer'])->where(function ($q) use ($userId) {
@ -22,9 +26,14 @@ class DealerEarningSimpleTable extends LazyRenderable
$grid->column('payer_id', '付款人')->display(function () {
return $this->payer_id ? $this->payer?->phone : '公司';
});
$grid->column('type_name', '资金类型')->display(function () {
return $this->type_name;
})->label();
$grid->column('earningable_type', '资金类型')->display(function () {
return $this->earningable_type_text;
})->label([
(new DealerManageSubsidy())->getMorphClass() => 'primary',
(new DealerManagerSubsidy())->getMorphClass() => 'success',
(new DealerPurchaseSubsidy())->getMorphClass() => 'danger',
(new DealerChannelSubsidyLog())->getMorphClass() => 'warning',
]);
$grid->column('remark', '备注')->display('详情') // 设置按钮名称
->expand(function () {
// 这里返回 content 字段内容,并用 Card 包裹起来
@ -39,14 +48,13 @@ class DealerEarningSimpleTable extends LazyRenderable
// $grid->column('fee_rate', '手续费率')->append('%');
// $grid->column('fee', '手续费')->prepend('¥');
$grid->column('total_earnings', '实际金额')->prepend('¥');
$grid->column('status_format', '状态')->display(function ($value) {
return $this->status_format;
})->using([
-1=> '待结算',
0 => '待打款',
1 => '待收款',
2 => '已完成',
])->dot();
$grid->column('status', '状态')->display(function ($v) {
if (! $this->isSettled()) {
return "<i class='fa fa-circle' style='font-size: 13px;color: #b9c3cd'></i>&nbsp;&nbsp;待结算";
}
return "<i class='fa fa-circle' style='font-size: 13px;color: {$v->color()}'></i>&nbsp;&nbsp;{$v->text()}";
});
$grid->column('created_at', '创建时间');
$grid->column('id', '操作')->display('查看')->link(function ($value) {
return admin_route('dealer_earnings.show', ['dealer_earning' => $this->id]);

View File

@ -0,0 +1,192 @@
<?php
namespace App\Console\Commands\Dealer;
use App\Admin\Services\DealerEarningService;
use App\Enums\DealerOrderSettleState;
use App\Enums\DealerSalesValueLogType;
use App\Models\Dealer;
use App\Models\DealerChannelSubsidyLog;
use App\Models\DealerEarning;
use App\Models\DealerManagerSalesLog;
use App\Models\DealerManageSubsidyLog;
use App\Models\DealerOrder;
use App\Models\DealerPurchaseLog;
use App\Models\DealerSalesValueLog;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Throwable;
class ChannelSubsidySettleCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dealer:channel-subsidy-settle';
/**
* The console command description.
*
* @var string
*/
protected $description = '结算签约经销商的渠道补贴';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
while (true) {
$page = 0;
DealerEarning::channelSubsidy()->withoutPayer()->onlyPending()->chunkById(200, function ($earnings) use (&$page) {
$earnings->load(['earningable', 'user']);
foreach ($earnings as $earning) {
try {
DB::beginTransaction();
$earning->update([
'settle_at' => now(),
]);
(new DealerEarningService())->pay($earning);
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
report($e);
}
}
$page++;
});
if ($page === 0) {
sleep(60);
} elseif ($page === 1) {
sleep(30);
} else {
sleep(15);
}
}
}
/**
* 处理经销商订单
*
* @param \App\Models\DealerOrder $order
* @return void
*/
protected function handleDealerOrder(DealerOrder $order)
{
$this->handleManagerSalesLogs($order);
$this->handleManageSubsidyLogs($order);
$this->handleChannelSubsidyLogs($order);
$this->handlePurchaseLogs($order);
$this->handleOrder($order);
}
protected function handleManagerSalesLogs(DealerOrder $order)
{
DealerManagerSalesLog::where('order_id', $order->id)->update([
'order_completed_at' => $order->shippinged_time,
]);
}
protected function handleManageSubsidyLogs(DealerOrder $order)
{
DealerManageSubsidyLog::where('order_id', $order->id)->update([
'order_completed_at' => $order->shippinged_time,
]);
}
protected function handleChannelSubsidyLogs(DealerOrder $order)
{
$channelSubsidyLogIds = DealerChannelSubsidyLog::where('order_id', $order->id)->get('id')->toArray();
DealerChannelSubsidyLog::whereIn('id', $channelSubsidyLogIds)->update([
'order_completed_at' => $order->shippinged_time,
]);
if ($order->consignor_id !== null) {
DealerEarning::where('earningable_type', 'dealer_channel_subsidy_log')->whereIn('earningable_id', $channelSubsidyLogIds)->update([
'settle_at' => now(),
]);
}
}
protected function handlePurchaseLogs(DealerOrder $order)
{
DealerPurchaseLog::where('order_id', $order->id)->update([
'order_completed_at' => $order->shippinged_time,
]);
}
protected function handleOrder(DealerOrder $order)
{
$salesValue = $order->total_amount;
if (bccomp($salesValue, '0', 2) < 1) {
return;
}
$salesValueLogs = [];
if (bccomp($salesValue, '0', 2) === 1) {
$ts = now()->toDateTimeString();
$order->dealer->update([
'self_sales_value' => DB::raw("self_sales_value+{$salesValue}"),
]);
$salesValueLogs[] = [
'user_id' => $order->user_id,
'loggable_type' => $order->getMorphClass(),
'loggable_id' => $order->id,
'type' => DealerSalesValueLogType::Personal,
'change_sales_value' => $salesValue,
'remark' => '个人进货',
'created_at' => $ts,
'updated_at' => $ts,
];
if (count($pids = $order->dealer->userInfo->parent_ids) > 0) {
foreach ($order->dealer->userInfo->parent_ids as $pid) {
$salesValueLogs[] = [
'user_id' => $pid,
'loggable_type' => $order->getMorphClass(),
'loggable_id' => $order->id,
'type' => DealerSalesValueLogType::Team,
'change_sales_value' => $salesValue,
'remarks' => '团队成员进货',
'created_at' => $ts,
'updated_at' => $ts,
];
}
// 更新上级的团队团队业绩
Dealer::whereIn('user_id', $pids)->update([
'team_sales_value' => DB::raw("team_sales_value + {$salesValue}"),
]);
}
}
// 保存销售值日志
DealerSalesValueLog::insert($salesValueLogs);
// 将订单标记未已结算
$order->forceFill([
'settle_state' => DealerOrderSettleState::Completed,
])->save();
}
}

View File

@ -117,9 +117,11 @@ class OrderSettleCommand extends Command
'order_completed_at' => $order->shippinged_time,
]);
DealerEarning::where('earningable_type', 'dealer_channel_subsidy_log')->whereIn('earningable_id', $channelSubsidyLogIds)->update([
'settle_at' => now(),
]);
if ($order->consignor_id !== null) {
DealerEarning::where('earningable_type', 'dealer_channel_subsidy_log')->whereIn('earningable_id', $channelSubsidyLogIds)->update([
'settle_at' => now(),
]);
}
}
protected function handlePurchaseLogs(DealerOrder $order)

View File

@ -56,17 +56,13 @@ class PurchaseSubsidySettleCommand extends Command
$tz = now();
if ($tz->day >= 20) {
// 结算当月5号-19号的管理津贴
$startAt = $tz->copy()->setDay(5)->startOfDay();
// 上月20号-当月19号的进货业绩
$startAt = $tz->copy()->subMonthNoOverflow()->setDay(20)->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();
// 上上月20号-上月19号的进货业绩
$startAt = $tz->copy()->subMonthsNoOverflow(2)->setDay(20)->startOfDay();
$endAt = $tz->copy()->subMonthNoOverflow()->setDay(19)->endOfDay();
}
$head = '【'.$startAt->format('Y/m/d').'-'.$endAt->format('Y/m/d').'】';

View File

@ -29,7 +29,7 @@ class Kernel extends ConsoleKernel
->runInBackground();
$schedule->command('dealer:purchase-subsidy-settle')
->twiceMonthly(5, 20, '3:00')
->twiceMonthly(20, '3:00')
->runInBackground();
$schedule->command('dealer:manager-subsidy-settle')

View File

@ -5,9 +5,12 @@ namespace App\Endpoint\Api\Http\Controllers\Auth;
use App\Constants\Device;
use App\Endpoint\Api\Http\Controllers\Controller;
use App\Exceptions\BizException;
use App\Models\SmsCode;
use App\Models\User;
use App\Models\UserInfo;
use App\Services\SmsCodeService;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class LoginController extends Controller
{
@ -19,21 +22,29 @@ class LoginController extends Controller
*/
public function __invoke(Request $request)
{
$validated = $request->validate([
'phone' => ['bail', 'required', 'string'],
'password' => ['bail', 'required', 'string'],
$request->validate([
'type' => ['bail', 'filled'],
], [], [
'type' => '登录类型',
]);
$user = User::where('phone', $validated['phone'])->first();
$type = strtolower($request->input('type', 'password'));
if (! $user?->verifyPassword($validated['password'])) {
throw new BizException(__('Incorrect account or password'));
$method = 'loginUsing'.Str::studly($type);
if (! method_exists($this, $method)) {
throw new BizException('登录类型 非法');
}
if ($user->old_password) {
$user->password = $validated['password'];
$user->old_password = null;
$user = $this->{$method}($request);
if ($type === 'password') {
if ($user->old_password) {
$user->password = $request->input('password');
$user->old_password = null;
}
}
$user->last_login_at = now();
$user->last_login_ip = $request->realIp();
$user->save();
@ -75,4 +86,42 @@ class LoginController extends Controller
'token' => $token->plainTextToken,
]);
}
protected function loginUsingPassword(Request $request)
{
$validated = $request->validate([
'phone' => ['bail', 'required', 'string'],
'password' => ['bail', 'required', 'string'],
], [], [
'phone' => '手机号',
'password' => '密码',
]);
$user = User::where('phone', $validated['phone'])->first();
if (! $user?->verifyPassword($validated['password'])) {
throw new BizException(__('Incorrect account or password'));
}
return $user;
}
protected function loginUsingVerifyCode(Request $request)
{
$validated = $request->validate([
'phone' => ['bail', 'required', 'string'],
'verify_code' => ['bail', 'required', 'string'],
], [], [
'phone' => '手机号',
'verify_code' => '验证码',
]);
app(SmsCodeService::class)->validate(
$validated['phone'],
SmsCode::TYPE_LOGIN,
$validated['verify_code']
);
return User::where('phone', $validated['phone'])->firstOrFail();
}
}

View File

@ -22,7 +22,7 @@ class ManageSubsidyLogController extends Controller
$perPage = Paginator::resolvePerPage('per_page', 20, 50);
$manageSubsidyLogs = DealerManageSubsidyLog::with(['product', 'order'])
$manageSubsidyLogs = DealerManageSubsidyLog::with(['product', 'order.user'])
->when($isComplted, function ($query) {
$tz = now();

View File

@ -7,10 +7,13 @@ use App\Endpoint\Api\Http\Resources\ProduckSkuResource;
use App\Endpoint\Api\Http\Resources\ProductFeatureResource;
use App\Endpoint\Api\Http\Resources\ProductSkuTinyResource;
use App\Events\ProductSkuViewed;
use App\Exceptions\BizException;
use App\Helpers\Paginator;
use App\Models\ProductSku;
use App\Models\ProductSkuFavorite;
use App\Models\ProductSpu;
use EasyWeChat\Factory as EasyWeChat;
use EasyWeChat\Kernel\Http\StreamResponse;
use Illuminate\Http\Request;
class ProductSkuController extends Controller
@ -152,4 +155,40 @@ class ProductSkuController extends Controller
return response()->noContent();
}
/**
* 分享商品
*
* @param int $id
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function share($id, Request $request)
{
$user = $request->user();
$sku = ProductSku::findOrFail($id);
$app = EasyWeChat::miniProgram(config('wechat.mini_program.default'));
$scene = http_build_query([
'product' => $sku->id,
'code' => $user->userInfo->code,
]);
$response = $app->app_code->getUnlimit($scene, [
'page' => 'pages/welcome/index',
'check_path' => false,
'env_version' => app()->isProduction() ? 'release' : 'trial',
'width' => $request->query('width', 200),
]);
if ($response instanceof StreamResponse) {
return response()->json([
'image' => 'data:image/png;base64,'.base64_encode($response->getBody()),
]);
}
throw new BizException('分享失败');
}
}

View File

@ -16,7 +16,7 @@ class DealerEarningSimpleResource extends JsonResource
{
return [
'id' => $this->id,
'type' => $this->type_name,
'type' => $this->earningable_type_text,
'created_at' => $this->created_at->toDateTimeString(),
'total_earnings'=> $this->total_earnings,
'status' => $this->status_format,

View File

@ -22,6 +22,7 @@ class OrderSimpleResource extends JsonResource
'status' => $this->status,
'is_consignor' => $request->user()->id == $this->consignor_id, //是否发货人身份
'products' => OrderProductResource::collection($this->whenLoaded('products')),
'user' => UserResource::make($this->whenLoaded('user')),
];
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Endpoint\Api\Http\Resources\Dealer;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'phone' => $this->phone,
'nickname' => (string) $this->userInfo->nickname,
'avatar' => (string) $this->userInfo->avatar,
'gender' => (string) $this->userInfo->gender,
'code' => (string) $this->userInfo->code,
];
}
}

View File

@ -122,6 +122,8 @@ Route::group([
Route::put('user-bank', [UserBankController::class, 'update']);
Route::get('banks-options', [UserBankController::class, 'options']);
// 商品分销
Route::get('product/products/{product}/share', [ProductSkuController::class, 'share']);
// 收藏商品
Route::post('product/products/{product}/collect', [ProductSkuController::class, 'collect']);
// 取消商品收藏

View File

@ -323,8 +323,8 @@ class Dealer extends Model
*/
public function getArchivedPurchaseAmountAttribute()
{
return Cache::remember('dealer_archived_purchase_amount:'.$this->user_id, 3600, function () {
return $this->purchaseSubsidies()->sum('total_purchase_amount');
return Cache::remember('dealer_archived_purchase_amount_v2:'.$this->user_id, 3600, function () {
return bcmul($this->purchaseSubsidies()->sum('total_purchase_amount'), '1', 2);
});
}

View File

@ -15,6 +15,7 @@ class SmsCode extends Model
public const TYPE_REGISTER = 1;
public const TYPE_RESET_PASSWORD = 2;
public const TYPE_SET_WALLET_PASSWORD = 3;
public const TYPE_LOGIN = 4;
/**
* @var array
@ -53,6 +54,7 @@ class SmsCode extends Model
self::TYPE_REGISTER,
self::TYPE_RESET_PASSWORD,
self::TYPE_SET_WALLET_PASSWORD,
self::TYPE_LOGIN,
];
/**

View File

@ -53,6 +53,7 @@ class SmsCodeService
break;
case SmsCode::TYPE_LOGIN:
case SmsCode::TYPE_RESET_PASSWORD:
case SmsCode::TYPE_SET_WALLET_PASSWORD:
if ($user === null) {