6
0
Fork 0

积分充值/积分扣减

base
Jing Li 2023-10-13 19:51:54 +08:00
parent c23990f7d3
commit 6c39e98976
24 changed files with 472 additions and 257 deletions

View File

@ -0,0 +1,31 @@
<?php
namespace App\Admin\Actions\Grid;
use App\Admin\Forms\PointChange as PointChangeForm;
use Dcat\Admin\Grid\RowAction;
use Dcat\Admin\Widgets\Modal;
class PointChange extends RowAction
{
public function title()
{
return '<i class="feather grid-action-icon icon-credit-card"></i>&nbsp;变更积分';
}
protected function authorize($user): bool
{
return $user->can('dcat.admin.users.change_points');
}
public function render()
{
$form = PointChangeForm::make()->payload(['id'=>$this->getKey()]);
return Modal::make()
->lg()
->title($this->title())
->body($form)
->button($this->title());
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Repositories\PointLog as PointLogRepository;
use App\Admin\Widgets\InfoBox;
use App\Enums\PointLogAction;
use App\Models\PointLog;
use Dcat\Admin\Admin;
use Dcat\Admin\Grid;
use Dcat\Admin\Http\Controllers\AdminController;
use Dcat\Admin\Layout\Row;
class PointLogController extends AdminController
{
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
Admin::style(
<<<CSS
.card-header {
margin-top: 1.5rem !important;
margin-bottom: -1rem !important;
}
CSS
);
$builder = PointLogRepository::with(['user', 'administrator']);
return Grid::make($builder, function (Grid $grid) {
$grid->model()->orderBy('id', 'desc');
$grid->column('id')->sortable();
$grid->column('user.phone')->copyable();
$grid->column('action')->display(fn ($action) => $action->label())->label();;
$grid->column('change_points');
$grid->column('before_points');
$grid->column('after_points');
$grid->column('remark');
$grid->column('created_at');
$grid->disableActions();
$grid->header(function ($collection) use ($grid) {
return tap(new Row(), function ($row) use ($grid) {
$query = PointLog::query();
$grid->model()->getQueries()->unique()->each(function ($value) use (&$query) {
if (in_array($value['method'], ['paginate', 'get', 'orderBy', 'orderByDesc'], true)) {
return;
}
$query = call_user_func_array([$query, $value['method']], $value['arguments'] ?? []);
});
$totalPoints = (clone $query)->sum('change_points');
$row->column(2, new InfoBox('积分总数', $totalPoints, 'fa fa-ticket'));
});
});
$grid->filter(function (Grid\Filter $filter) {
$filter->panel(false);
$filter->equal('user.phone')->width(3);
$filter->in('action')->multipleSelect(PointLogAction::options())->width(3);
$filter->between('created_at')->dateTime()->width(3);
});
return $grid;
});
}
}

View File

@ -1,81 +0,0 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Repositories\PointsLog;
use Dcat\Admin\Form;
use Dcat\Admin\Grid;
use Dcat\Admin\Http\Controllers\AdminController;
use Dcat\Admin\Show;
class PointsLogController extends AdminController
{
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
$builder = PointsLog::with('user');
return Grid::make($builder, function (Grid $grid) {
// $grid->column('id')->sortable();
$grid->column('user.phone')->copyable();
$grid->column('desc');
// $grid->column('type');
$grid->column('points');
$grid->column('old_points');
$grid->column('created_at')->sortable();
$grid->model()->orderBy('created_at', 'desc');
// $grid->column('updated_at')->sortable();
$grid->disableActions();
$grid->filter(function (Grid\Filter $filter) {
$filter->panel(false);
$filter->equal('user.phone')->width(3);
});
});
}
/**
* Make a show builder.
*
* @param mixed $id
*
* @return Show
*/
protected function detail($id)
{
return Show::make($id, new PointsLog(), function (Show $show) {
$show->field('id');
$show->field('user_id');
$show->field('type');
$show->field('points');
$show->field('old_points');
$show->field('desc');
$show->field('created_at');
$show->field('updated_at');
});
}
/**
* Make a form builder.
*
* @return Form
*/
protected function form()
{
return Form::make(new PointsLog(), function (Form $form) {
$form->display('id');
$form->text('user_id');
$form->text('type');
$form->text('points');
$form->text('old_points');
$form->text('desc');
$form->display('created_at');
$form->display('updated_at');
});
}
}

View File

@ -3,19 +3,12 @@
namespace App\Admin\Controllers;
use App\Admin\Actions\Grid\DisableUser;
use App\Admin\Actions\Grid\PointChange;
use App\Admin\Actions\Grid\{EnableUser, UserCompany};
use App\Admin\Actions\Grid\Frozen;
use App\Admin\Actions\Grid\UserResetAccountPassword;
use App\Admin\Actions\Grid\UserResetPassword;
use App\Admin\Actions\Show\{UserEditBank, UserCompany as UserCompanyShow};
use App\Admin\Actions\Show\UserEditPhone;
use App\Admin\Actions\Show\UserCompany as UserCompanyShow;
use App\Admin\Actions\Show\UserEditAgent;
use App\Admin\Renderable\Grid\Filter\PriceBetween;
use App\Admin\Renderable\UserBalanceLogSimpleTable;
use App\Admin\Renderable\UserFansSimpleTable;
use App\Admin\Renderable\UserInviterSimpleTable;
use App\Admin\Actions\Show\UserEditPhone;
use App\Admin\Renderable\UserSalesValueLogSimpleTable;
use App\Admin\Renderable\UserWalletLogSimpleTable;
use App\Admin\Repositories\User;
use App\Exceptions\BizException;
use App\Models\Order;
@ -61,6 +54,13 @@ class UserController extends AdminController
$grid->column('userInfo.inviterInfo.user.phone')->display(function ($v) {
return $v ?: $this->userInfo?->inviter_id;
})->copyable();
$grid->column('userInfo.points')->sortable()->if(function () {
return Admin::user()->can('dcat.admin.point_logs.index');
})->then(function (Grid\Column $column) {
$column->link(function ($value) {
return route('dcat.admin.point_logs.index', ['user[phone]' => $this->phone]);
});
});
$grid->column('userInfo.growth_value')->filter(
Grid\Column\Filter\Between::make()
)->modal(function ($modal) {
@ -89,6 +89,10 @@ class UserController extends AdminController
$grid->actions(function (Grid\Displayers\Actions $actions) {
$actions->disableView(Admin::user()->cannot('dcat.admin.users.show'));
$actions->disableDelete(Admin::user()->cannot('dcat.admin.users.destroy'));
if (Admin::user()->can('dcat.admin.users.change_points')) {
$actions->append(new PointChange());
}
if ($actions->row->status == 1) {
if (Admin::user()->can('dcat.admin.users.disable')) {
$actions->append(new DisableUser());

View File

@ -0,0 +1,75 @@
<?php
namespace App\Admin\Forms;
use App\Enums\PointLogAction;
use App\Models\User;
use App\Services\PointService;
use Dcat\Admin\Admin;
use Dcat\Admin\Contracts\LazyRenderable;
use Dcat\Admin\Traits\LazyWidget;
use Dcat\Admin\Widgets\Form;
use Illuminate\Support\Facades\DB;
use Throwable;
class PointChange extends Form implements LazyRenderable
{
use LazyWidget;
protected function authorize($user): bool
{
return $user->can('dcat.admin.users.change_points');
}
public function handle(array $input)
{
$changePoints = (int) $input['change_points'];
if ($changePoints < 1) {
return $this->response()->error('积分必须大于1');
}
$action = PointLogAction::from($input['action']);
if ($action === PointLogAction::Deduction) {
$changePoints = -$changePoints;
}
$user = User::findOrFail($this->payload['id']);
try {
DB::beginTransaction();
(new PointService())->change($user, $changePoints, $action, $input['remark'], null, Admin::user());
DB::commit();
} catch (Throwable $th) {
DB::rollBack();
report($th);
return $this->response()->error('操作失败:'.$th->getMessage());
}
return $this->response()
->success(__('admin.update_succeeded'))
->refresh();
}
/**
* Build a form here.
*/
public function form()
{
$this->radio('action', '操作')
->options([
PointLogAction::Recharge->value => '充值',
PointLogAction::Deduction->value => '扣减',
])
->default(PointLogAction::Recharge->value)
->required();
$this->number('change_points', '积分')->min(1)->required();
$this->textarea('remark', '备注')->required();
$this->confirm('是否确认变更?', '提交后该动作无法逆转');
}
}

View File

@ -2,15 +2,10 @@
namespace App\Admin\Repositories;
use App\Models\PointsLog as Model;
use App\Models\PointLog as Model;
use Dcat\Admin\Repositories\EloquentRepository;
class PointsLog extends EloquentRepository
class PointLog extends EloquentRepository
{
/**
* Model.
*
* @var string
*/
protected $eloquentClass = Model::class;
}

View File

@ -139,9 +139,9 @@ Route::group([
'index',
])->names('balance_logs');
$router->resource('points-logs', 'PointsLogController')->only([
$router->resource('point-logs', 'PointLogController')->only([
'index',
])->names('points_logs');
])->names('point_logs');
$router->resource('order-refunds', 'OrderRefundLogController')->only([
'index',

View File

@ -10,14 +10,10 @@ use App\Helpers\Paginator as PaginatorHelper;
use App\Models\Article;
use App\Models\ArticleCategory;
use App\Models\ArticleLikesLog;
use App\Models\ArticlePointsLog;
use App\Models\PointsLog;
use App\Services\PointsService;
use Dcat\Admin\Support\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Throwable;
use Dcat\Admin\Support\Helper;
class ArticleController extends Controller
{
@ -123,29 +119,10 @@ class ArticleController extends Controller
* @param [type] $id
* @return void
*/
public function read($id, Request $request, PointsService $pointsService)
public function read($id, Request $request)
{
$article = Article::where('is_show', 1)->findOrFail($id);
if ($article->points > 0) {
if (ArticlePointsLog::where('user_id', $request->user()->id)->whereDate('created_at', now())->exists()) {
throw new BizException('您今天已拿过积分了,请明天再来');
}
try {
DB::beginTransaction();
ArticlePointsLog::create([
'user_id' => $request->user()->id,
'article_id' => $article->id,
]);
$pointsService->sendPoints(PointsLog::TYPE_READ, $request->user(), $article->points, '阅读文章送积分');
DB::commit();
} catch (Throwable $th) {
DB::rollBack();
report($th);
throw new BizException('系统繁忙,请稍后再试');
}
}
return response()->json([
'points'=>$article->points,
'points' => 0,
]);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Endpoint\Api\Http\Controllers;
use App\Endpoint\Api\Http\Controllers\Controller;
use App\Endpoint\Api\Http\Resources\PointLogResource;
use App\Helpers\Paginator;
use App\Models\PointLog;
use Illuminate\Http\Request;
class PointLogController extends Controller
{
/**
* 积分流水
*
* @param \Illuminate\Http\Request $request
*/
public function index(Request $request)
{
$pointLogs = PointLog::where('user_id', $request->user()->id)
->latest('id')
->simplePaginate(Paginator::resolvePerPage('per_page', 20, 50));
return PointLogResource::collection($pointLogs);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Endpoint\Api\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PointLogResource 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,
'action' => $this->action,
'change_points' => $this->change_points,
'remark' => (string) $this->remark,
'created_at' => $this->created_at->toDateTimeString(),
];
}
}

View File

@ -20,7 +20,8 @@ class UserInfoResource extends JsonResource
'gender' => (string) $this->gender,
'birthday' => (string) $this->birthday?->toDateString(),
'code' => (string) $this->code,
'is_company' => (boolean) $this->is_company
'is_company' => (boolean) $this->is_company,
'points' => $this->points,
];
}
}

View File

@ -13,6 +13,7 @@ use App\Endpoint\Api\Http\Controllers\ArticleController;
use App\Endpoint\Api\Http\Controllers\Auth;
use App\Endpoint\Api\Http\Controllers\Auth\LoginController;
use App\Endpoint\Api\Http\Controllers\Auth\LogoutController;
use App\Endpoint\Api\Http\Controllers\Auth\MiniprogramController;
use App\Endpoint\Api\Http\Controllers\Auth\RegisterController;
use App\Endpoint\Api\Http\Controllers\Auth\ResetPasswordController;
use App\Endpoint\Api\Http\Controllers\CaptchaController;
@ -21,6 +22,7 @@ use App\Endpoint\Api\Http\Controllers\MessageController;
use App\Endpoint\Api\Http\Controllers\Order\OrderController;
use App\Endpoint\Api\Http\Controllers\Order\OrderVerifyController;
use App\Endpoint\Api\Http\Controllers\Order\StatisticsController;
use App\Endpoint\Api\Http\Controllers\PointLogController;
use App\Endpoint\Api\Http\Controllers\Product\HotController;
use App\Endpoint\Api\Http\Controllers\Product\PartController;
use App\Endpoint\Api\Http\Controllers\Product\ProductCategoryController;
@ -33,11 +35,10 @@ use App\Endpoint\Api\Http\Controllers\ShareBgController;
use App\Endpoint\Api\Http\Controllers\ShippingAddressController;
use App\Endpoint\Api\Http\Controllers\ShoppingCartItemController;
use App\Endpoint\Api\Http\Controllers\SmsCodeController;
use App\Endpoint\Api\Http\Controllers\StoreController;
use App\Endpoint\Api\Http\Controllers\UserBankController;
use App\Endpoint\Api\Http\Controllers\UserCouponController;
use App\Endpoint\Api\Http\Controllers\ZoneController;
use App\Endpoint\Api\Http\Controllers\Auth\MiniprogramController;
use App\Endpoint\Api\Http\Controllers\StoreController;
use Illuminate\Support\Facades\Route;
Route::group([
@ -127,6 +128,8 @@ Route::group([
Route::post('wallet/wallet-to-balance', [WalletController::class, 'walletToBalance']);
Route::post('wallet/balance-transfer', [WalletController::class, 'balanceTransfer']);
Route::get('point-logs', [PointLogController::class, 'index']);
//银行卡
Route::get('user-bank', [UserBankController::class, 'show']);
Route::put('user-bank', [UserBankController::class, 'update']);

View File

@ -0,0 +1,25 @@
<?php
namespace App\Enums;
use Illuminate\Support\Arr;
enum PointLogAction: int {
case Recharge = 1;
case Deduction = 2;
case Consumption = 3;
public function label(): string
{
return Arr::get(self::options(), $this->value, 'Unknown');
}
public static function options(): array
{
return [
self::Recharge->value => '充值',
self::Deduction->value => '扣减',
self::Consumption->value => '消费',
];
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Models;
use App\Enums\PointLogAction;
use App\Models\Admin\Administrator;
use Dcat\Admin\Traits\HasDateTimeFormatter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PointLog extends Model
{
use HasFactory, HasDateTimeFormatter;
protected $fillable = [
'loggable_id',
'loggable_type',
'user_id',
'action',
'change_points',
'before_points',
'after_points',
'remark',
'adminstrator_id',
];
protected $casts = [
'action' => PointLogAction::class,
];
public function user()
{
return $this->belongsTo(User::class);
}
public function administrator()
{
return $this->belongsTo(Administrator::class);
}
}

View File

@ -1,25 +0,0 @@
<?php
namespace App\Models;
use Dcat\Admin\Traits\HasDateTimeFormatter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PointsLog extends Model
{
use HasFactory;
use HasDateTimeFormatter;
public const TYPE_CLICK = 1;//签到
public const TYPE_READ = 2;//阅读文章
protected $fillable = [
'type', 'user_id', 'points', 'old_points', 'desc',
];
public function user()
{
return $this->belongsTo(User::class);
}
}

View File

@ -42,6 +42,7 @@ class UserInfo extends Model
'birthday',
'depth',
'path',
'points',
'growth_value',
'real_inviter_id',
'is_company',
@ -117,7 +118,7 @@ class UserInfo extends Model
{
return $this->hasMany(Order::class, 'inviter_id', 'user_id')->where('status', Order::STATUS_COMPLETED);
}
public function inviteUserInfos()
{
return $this->hasMany(UserInfo::class, 'inviter_id', 'user_id');

View File

@ -0,0 +1,72 @@
<?php
namespace App\Services;
use App\Enums\PointLogAction;
use App\Exceptions\BizException;
use App\Models\Admin\Administrator;
use App\Models\PointLog;
use App\Models\User;
use App\Models\UserInfo;
use Illuminate\Database\Eloquent\Model;
class PointService
{
/**
* 变更积分
*
* @throws \App\Exceptions\BizException
*/
public function change(
User $user,
int $points,
PointLogAction $action,
?string $remark = null,
?Model $loggable = null,
?Administrator $administrator = null,
) {
if ($points === 0) {
return;
}
$userinfo = UserInfo::lockForUpdate()->where('user_id', $user->id)->firstOrFail();
switch ($action) {
case PointLogAction::Recharge:
if ($points < 0) {
throw new BizException('积分不能小于 0');
}
break;
case PointLogAction::Deduction:
case PointLogAction::Consumption:
if ($points > 0) {
throw new BizException('积分不能大于 0');
}
break;
}
$before = $userinfo->points;
$after = $before + $points;
if ($after < 0) {
throw new BizException('积分不足');
}
$userinfo->update([
'points' => $after,
]);
PointLog::create([
'loggable_id' => $loggable?->id,
'loggable_type' => $loggable?->getMorphClass(),
'user_id' => $user->id,
'action' => $action,
'change_points' => $points,
'before_points' => $before,
'after_points' => $after,
'remark' => $remark,
'administrator_id' => $administrator?->id,
]);
}
}

View File

@ -1,40 +0,0 @@
<?php
namespace App\Services;
use App\Models\PointsLog;
use App\Models\User;
class PointsService
{
/**
* 发放积分
*
* @param integer $type
* @param User $user
* @param integer $points
* @param string $desc
* @return bool
*/
public function sendPoints(int $type, User $user, int $points, string $desc)
{
$res = false;
if ($points != 0) {
PointsLog::create([
'type' => $type,
'user_id' => $user->id,
'points' => $points,
'old_points'=> $user->userInfo->points,
'desc' => $desc,
]);
if ($points > 0) {
$user->userInfo()->increment('points', abs($points));
} else {
$user->userInfo()->decrement('points', abs($points));
}
$res = true;
}
return $res;
}
}

View File

@ -1,36 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePointsLogsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('points_logs', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id')->comment('用户ID');
$table->unsignedTinyInteger('type')->default(0)->comment('类别1签到2阅读');
$table->bigInteger('points')->default(0)->comment('积分:可以为负数');
$table->unsignedBigInteger('old_points')->default(0)->comment('变动前');
$table->string('desc')->nullable()->comment('备注');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('points_logs');
}
}

View File

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePointLogsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('point_logs', function (Blueprint $table) {
$table->id();
$table->nullableMorphs('loggable');
$table->unsignedBigInteger('user_id')->comment('用户ID');
$table->tinyInteger('action')->comment('操作');
$table->bigInteger('change_points')->comment('变更的积分');
$table->unsignedBigInteger('before_points')->comment('变更前的积分');
$table->unsignedBigInteger('after_points')->comment('变更后的积分');
$table->string('remark')->nullable()->comment('备注');
$table->unsignedBigInteger('administrator_id')->nullable()->comment('管理员ID');
$table->timestamps();
$table->index('user_id');
$table->index('action');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('point_logs');
}
}

View File

@ -34,7 +34,8 @@ class AdminMenuSeeder extends Seeder
'uri' => 'users',
],
['title' => '代理等级', 'icon' => '', 'uri' => 'agent'],
['title' => '成长值流水', 'icon' => '', 'uri' => 'sales-value-logs']
['title' => '积分流水', 'icon' => '', 'uri' => 'point-logs'],
['title' => '成长值流水', 'icon' => '', 'uri' => 'sales-value-logs'],
],
],
[

View File

@ -87,6 +87,7 @@ class AdminPermissionSeeder extends Seeder
'edit_phone'=>['name' =>'修改手机号'],
'edit_bank'=>['name'=>'修改银行卡'],
'agent'=>['name'=>'设置代理'],
'change_points' => ['name'=>'变更积分'],
],
],
'agent' => [
@ -298,10 +299,6 @@ class AdminPermissionSeeder extends Seeder
'deduction'=>['name' =>'扣减'],
],
],
'points_logs'=>[
'name' =>'积分账户',
'curd' => ['index'],
],
'wallet_to_bank_logs' => [
'name' => '提现审核',
'curd' => ['index'],
@ -373,7 +370,12 @@ class AdminPermissionSeeder extends Seeder
'sales_value_logs' => [
'name' => '成长值记录',
'curd' => ['index']
]
],
'point_logs' => [
'name' => '积分流水',
'curd' => ['index'],
'children' => [],
],
];
// try {
// DB::begintransaction();

View File

@ -0,0 +1,21 @@
<?php
return [
'labels' => [
'PointLog' => '积分流水',
'point-logs' => '积分流水',
],
'fields' => [
'user_id' => '用户ID',
'user'=>[
'phone' => '手机号',
],
'action' => '操作',
'change_points' => '变更积分',
'before_points' => '变更前',
'after_points' => '变更后',
'remark' => '备注',
],
'options' => [
],
];

View File

@ -1,20 +0,0 @@
<?php
return [
'labels' => [
'PointsLog' => '积分账户',
'points-logs' => '积分账户',
],
'fields' => [
'user_id' => '用户ID',
'user'=>[
'phone' => '手机号',
],
'type' => '类别',
'points' => '变动积分',
'old_points' => '变动前',
'desc' => '备注',
],
'options' => [
],
];