diff --git a/app/admin/controller/mall/PointsLog.php b/app/admin/controller/mall/PointsLog.php new file mode 100644 index 0000000..6eee773 --- /dev/null +++ b/app/admin/controller/mall/PointsLog.php @@ -0,0 +1,71 @@ +model = new MallPointsLog(); + return null; + } + + public function add(Request $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + + if ($request->method() === 'POST') { + return $this->_add(); + } + + $userAssetId = $request->get('userAssetId', $request->post('userAssetId', 0)); + $userAsset = MallUserAsset::where('id', $userAssetId)->find(); + if (!$userAsset) { + return $this->error(__('User asset not found')); + } + + return $this->success('', ['userAsset' => $userAsset]); + } + + public function edit(Request $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + + return $this->error(__('Points log cannot be modified')); + } + + public function del(Request $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + + return $this->error(__('Points log cannot be deleted')); + } +} diff --git a/app/admin/controller/mall/UserAsset.php b/app/admin/controller/mall/UserAsset.php index 897ee9e..7d7f156 100644 --- a/app/admin/controller/mall/UserAsset.php +++ b/app/admin/controller/mall/UserAsset.php @@ -17,7 +17,14 @@ class UserAsset extends Backend */ protected ?object $model = null; - protected array|string $preExcludeFields = ['id', 'create_time', 'update_time']; + protected array|string $preExcludeFields = [ + 'id', + 'locked_points', + 'available_points', + 'today_claimed', + 'create_time', + 'update_time', + ]; protected string|array $quickSearchField = [ 'playx_user_id', diff --git a/app/admin/lang/en/mall/pointslog.php b/app/admin/lang/en/mall/pointslog.php new file mode 100644 index 0000000..49a3cc0 --- /dev/null +++ b/app/admin/lang/en/mall/pointslog.php @@ -0,0 +1,13 @@ + 'User asset', + 'Points adjustment' => 'Points adjustment', + 'User asset not found' => 'User asset not found', + 'Points adjustment must be a non-zero integer' => 'Points adjustment must be a non-zero integer', + 'Insufficient available points' => 'Insufficient available points', + 'Administrator added %s points' => 'Administrator added %s points', + 'Administrator deducted %s points' => 'Administrator deducted %s points', + 'Points log cannot be modified' => 'Points log cannot be modified', + 'Points log cannot be deleted' => 'Points log cannot be deleted', +]; diff --git a/app/admin/lang/zh-cn/mall/pointslog.php b/app/admin/lang/zh-cn/mall/pointslog.php new file mode 100644 index 0000000..202607b --- /dev/null +++ b/app/admin/lang/zh-cn/mall/pointslog.php @@ -0,0 +1,13 @@ + '用户资产', + 'Points adjustment' => '调整积分', + 'User asset not found' => '用户资产不存在', + 'Points adjustment must be a non-zero integer' => '调整积分必须为非零整数', + 'Insufficient available points' => '可用积分不足', + 'Administrator added %s points' => '管理员增加积分 %s', + 'Administrator deducted %s points' => '管理员扣除积分 %s', + 'Points log cannot be modified' => '积分流水不允许修改', + 'Points log cannot be deleted' => '积分流水不允许删除', +]; diff --git a/app/admin/model/MallPointsLog.php b/app/admin/model/MallPointsLog.php new file mode 100644 index 0000000..62e4cab --- /dev/null +++ b/app/admin/model/MallPointsLog.php @@ -0,0 +1,61 @@ +user_asset_id, FILTER_VALIDATE_INT); + if ($userAssetId === false || $userAssetId <= 0) { + throw new \InvalidArgumentException(__('User asset not found')); + } + + $scoreValue = filter_var($model->score_value, FILTER_VALIDATE_INT); + if ($scoreValue === false || $scoreValue === 0) { + throw new \InvalidArgumentException(__('Points adjustment must be a non-zero integer')); + } + + $userAsset = MallUserAsset::where('id', $userAssetId)->lock(true)->find(); + if (!$userAsset) { + throw new \RuntimeException(__('User asset not found')); + } + + $before = $userAsset->available_points; + $after = $before + $scoreValue; + if ($after < 0) { + throw new \RuntimeException(__('Insufficient available points')); + } + + $model->user_asset_id = $userAssetId; + $model->score_value = $scoreValue; + $model->before = $before; + $model->after = $after; + $model->memo = $scoreValue > 0 + ? __('Administrator added %s points', [abs($scoreValue)]) + : __('Administrator deducted %s points', [abs($scoreValue)]); + + $userAsset->available_points = $after; + $userAsset->save(); + } + + public static function onBeforeDelete(): bool + { + return false; + } + + public function userAsset(): BelongsTo + { + return $this->belongsTo(MallUserAsset::class, 'user_asset_id'); + } + + public function admin(): BelongsTo + { + return $this->belongsTo(Admin::class, 'admin_id'); + } +} diff --git a/app/admin/validate/MallPointsLog.php b/app/admin/validate/MallPointsLog.php new file mode 100644 index 0000000..babe8a8 --- /dev/null +++ b/app/admin/validate/MallPointsLog.php @@ -0,0 +1,30 @@ + 'require|integer|gt:0', + 'score_value' => 'require|integer|notIn:0', + ]; + + protected $scene = [ + 'add' => ['user_asset_id', 'score_value'], + ]; + + public function __construct() + { + $this->field = [ + 'user_asset_id' => __('User asset'), + 'score_value' => __('Points adjustment'), + ]; + parent::__construct(); + } +} diff --git a/app/api/controller/v1/Playx.php b/app/api/controller/v1/Playx.php index 5939466..79bf216 100644 --- a/app/api/controller/v1/Playx.php +++ b/app/api/controller/v1/Playx.php @@ -847,7 +847,7 @@ class Playx extends Api } /** - * 积分流水(领取/兑换/退回) + * 积分流水(领取/兑换/退回/管理员调整) * GET /api/v1/mall/pointsLogs * * 鉴权:token / session_id / user_id(同 assets/orders) @@ -981,6 +981,28 @@ FROM ( FROM mall_order o LEFT JOIN mall_item i ON i.id = o.mall_item_id WHERE o.user_id = :user_id_refund AND o.status = 'REJECTED' AND o.points_cost > 0 AND o.update_time IS NOT NULL + + UNION ALL + + SELECT + 'ADMIN_ADJUST' AS biz_type, + CASE WHEN pl.score_value > 0 THEN 'IN' ELSE 'OUT' END AS direction, + ABS(pl.score_value) AS points, + pl.create_time AS ts, + pl.id AS ref_id, + '' AS order_no, + '' AS order_status, + 0 AS item_id, + '' AS item_title, + 0 AS item_type, + 0 AS item_score, + 0 AS item_amount, + 0 AS item_multiplier, + '' AS item_category, + '' AS item_category_title, + CONCAT(LPAD(pl.create_time, 10, '0'), '_4_', LPAD(pl.id, 10, '0')) AS sort_key + FROM mall_points_log pl + WHERE pl.user_asset_id = :user_asset_id_adjust ) t WHERE 1=1 SQL; @@ -989,6 +1011,7 @@ SQL; 'user_id_claim' => $playxUserId, 'user_id_redeem' => $playxUserId, 'user_id_refund' => $playxUserId, + 'user_asset_id_adjust' => $assetId, ]; if ($cursor !== '') { @@ -1016,10 +1039,18 @@ SQL; if (!is_array($row)) { continue; } + $memo = ''; + if (($row['biz_type'] ?? '') === 'ADMIN_ADJUST') { + $memoKey = ($row['direction'] ?? '') === 'OUT' + ? 'Administrator deducted %s points' + : 'Administrator added %s points'; + $memo = __($memoKey, [$row['points'] ?? 0]); + } $list[] = [ 'biz_type' => $row['biz_type'] ?? '', 'direction' => $row['direction'] ?? '', 'points' => $row['points'] ?? 0, + 'memo' => $memo, 'ts' => $row['ts'] ?? null, 'ref_id' => $row['ref_id'] ?? '', 'order_no' => $row['order_no'] ?? '', diff --git a/app/api/lang/en.php b/app/api/lang/en.php index 53a5fed..10c99ba 100644 --- a/app/api/lang/en.php +++ b/app/api/lang/en.php @@ -78,6 +78,8 @@ return [ 'item_id and user_id/session_id required' => 'item_id and user_id/session_id/token are required', 'Item not found or not available' => 'Item not found or not available', 'Insufficient points' => 'Insufficient points', + 'Administrator added %s points' => 'Administrator added %s points', + 'Administrator deducted %s points' => 'Administrator deducted %s points', 'Redeem submitted, please wait about 10 minutes' => 'Redeem submitted, please wait about 10 minutes', 'Missing required fields' => 'Missing required fields', 'Out of stock' => 'Out of stock', diff --git a/app/api/lang/ms.php b/app/api/lang/ms.php index a726638..dd40eec 100644 --- a/app/api/lang/ms.php +++ b/app/api/lang/ms.php @@ -79,6 +79,8 @@ return [ 'item_id and user_id/session_id required' => 'item_id dan user_id/session_id/token diperlukan', 'Item not found or not available' => 'Item tidak dijumpai atau tidak tersedia', 'Insufficient points' => 'Mata tidak mencukupi', + 'Administrator added %s points' => 'Pentadbir menambah %s mata', + 'Administrator deducted %s points' => 'Pentadbir menolak %s mata', 'Redeem submitted, please wait about 10 minutes' => 'Penebusan dihantar, sila tunggu kira-kira 10 minit', 'Missing required fields' => 'Medan wajib tiada', 'Out of stock' => 'Stok tidak mencukupi', diff --git a/app/api/lang/zh-cn.php b/app/api/lang/zh-cn.php index b35bbf6..c54eec3 100644 --- a/app/api/lang/zh-cn.php +++ b/app/api/lang/zh-cn.php @@ -80,6 +80,8 @@ return [ 'item_id and user_id/session_id required' => '缺少 item_id,或未提供有效的 user_id/session_id/token', 'Item not found or not available' => '商品不存在或已下架', 'Insufficient points' => '积分不足', + 'Administrator added %s points' => '管理员增加积分 %s', + 'Administrator deducted %s points' => '管理员扣除积分 %s', 'Redeem submitted, please wait about 10 minutes' => '兑换已提交,请等待约 10 分钟', 'Missing required fields' => '缺少必填字段', 'Out of stock' => '库存不足', diff --git a/app/common/model/MallPointsLog.php b/app/common/model/MallPointsLog.php new file mode 100644 index 0000000..67bef01 --- /dev/null +++ b/app/common/model/MallPointsLog.php @@ -0,0 +1,18 @@ + +