diff --git a/app/admin/controller/mall/PlayxOrder.php b/app/admin/controller/mall/PlayxOrder.php new file mode 100644 index 0000000..290f716 --- /dev/null +++ b/app/admin/controller/mall/PlayxOrder.php @@ -0,0 +1,250 @@ +model = new \app\common\model\MallPlayxOrder(); + } + + /** + * 查看 + * @throws Throwable + */ + public function index(Request $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + + if ($request->get('select') || $request->post('select')) { + return $this->select($request); + } + + list($where, $alias, $limit, $order) = $this->queryBuilder(); + $res = $this->model + ->with(['mallItem' => function ($query) { + $query->field('id,title'); + }]) + ->visible(['mallItem' => ['title']]) + ->alias($alias) + ->where($where) + ->order($order) + ->paginate($limit); + + return $this->success('', [ + 'list' => $res->items(), + 'total' => $res->total(), + 'remark' => get_route_remark(), + ]); + } + + /** + * PHYSICAL 发货:更新 shipping_company/shipping_no,并将状态置为 SHIPPED + */ + public function ship(Request $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + + if ($request->method() !== 'POST') { + return $this->error(__('Parameter error')); + } + + $data = $request->post(); + $id = intval($data['id'] ?? 0); + $shippingCompany = strval($data['shipping_company'] ?? ''); + $shippingNo = strval($data['shipping_no'] ?? ''); + + if ($id <= 0 || $shippingCompany === '' || $shippingNo === '') { + return $this->error(__('Missing required fields')); + } + + $order = MallPlayxOrder::where('id', $id)->find(); + if (!$order) { + return $this->error(__('Record not found')); + } + if ($order->type !== MallPlayxOrder::TYPE_PHYSICAL) { + return $this->error(__('Order type not PHYSICAL')); + } + if ($order->status !== MallPlayxOrder::STATUS_PENDING) { + return $this->error(__('Order status must be PENDING')); + } + + Db::startTrans(); + try { + $order->shipping_company = $shippingCompany; + $order->shipping_no = $shippingNo; + $order->status = MallPlayxOrder::STATUS_SHIPPED; + $order->save(); + Db::commit(); + } catch (Throwable $e) { + Db::rollback(); + return $this->error($e->getMessage()); + } + + return $this->success(__('Shipped successfully')); + } + + /** + * PHYSICAL 驳回:更新状态为 REJECTED,并退回积分到 available_points + */ + public function reject(Request $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + + if ($request->method() !== 'POST') { + return $this->error(__('Parameter error')); + } + + $data = $request->post(); + $id = intval($data['id'] ?? 0); + $rejectReason = strval($data['reject_reason'] ?? ''); + + if ($id <= 0 || $rejectReason === '') { + return $this->error(__('Missing required fields')); + } + + $order = MallPlayxOrder::where('id', $id)->find(); + if (!$order) { + return $this->error(__('Record not found')); + } + if ($order->type !== MallPlayxOrder::TYPE_PHYSICAL) { + return $this->error(__('Order type not PHYSICAL')); + } + if ($order->status !== MallPlayxOrder::STATUS_PENDING) { + return $this->error(__('Order status must be PENDING')); + } + + Db::startTrans(); + try { + $asset = MallPlayxUserAsset::where('user_id', strval($order->user_id ?? ''))->find(); + if (!$asset) { + $asset = MallPlayxUserAsset::create([ + 'user_id' => strval($order->user_id ?? ''), + 'username' => strval($order->user_id ?? ''), + 'locked_points' => 0, + 'available_points' => 0, + 'today_limit' => 0, + 'today_claimed' => 0, + 'today_limit_date' => null, + 'create_time' => time(), + 'update_time' => time(), + ]); + } + + $refund = intval($order->points_cost ?? 0); + if ($refund > 0) { + $asset->available_points += $refund; + $asset->save(); + } + + $order->status = MallPlayxOrder::STATUS_REJECTED; + $order->reject_reason = $rejectReason; + $order->grant_status = MallPlayxOrder::GRANT_FAILED_FINAL; + $order->save(); + + Db::commit(); + } catch (Throwable $e) { + Db::rollback(); + return $this->error($e->getMessage()); + } + + return $this->success(__('Rejected successfully')); + } + + /** + * 手动重试(仅红利/提现,且必须 FAILED_RETRYABLE) + */ + public function retry(Request $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + + if ($request->method() !== 'POST') { + return $this->error(__('Parameter error')); + } + + $id = intval($request->post('id', 0)); + if ($id <= 0) { + return $this->error(__('Missing required fields')); + } + + $order = MallPlayxOrder::where('id', $id)->find(); + if (!$order) { + return $this->error(__('Record not found')); + } + if (!in_array($order->type, [MallPlayxOrder::TYPE_BONUS, MallPlayxOrder::TYPE_WITHDRAW], true)) { + return $this->error(__('Only BONUS/WITHDRAW can retry')); + } + if ($order->grant_status !== MallPlayxOrder::GRANT_FAILED_RETRYABLE) { + return $this->error(__('Only FAILED_RETRYABLE can retry')); + } + if (intval($order->retry_count) >= 3) { + return $this->error(__('Retry count exceeded')); + } + + $order->grant_status = MallPlayxOrder::GRANT_NOT_SENT; + $order->save(); + + return $this->success(__('Retry queued')); + } +} + diff --git a/app/common/model/MallPlayxOrder.php b/app/common/model/MallPlayxOrder.php new file mode 100644 index 0000000..c6f50b8 --- /dev/null +++ b/app/common/model/MallPlayxOrder.php @@ -0,0 +1,66 @@ + 'integer', + 'update_time' => 'integer', + 'points_cost' => 'integer', + 'amount' => 'float', + 'multiplier' => 'integer', + 'retry_count' => 'integer', + ]; + + public function mallItem(): \think\model\relation\BelongsTo + { + return $this->belongsTo(MallItem::class, 'mall_item_id', 'id'); + } +} diff --git a/web/src/lang/backend/en/mall/playxOrder.ts b/web/src/lang/backend/en/mall/playxOrder.ts new file mode 100644 index 0000000..ff648ee --- /dev/null +++ b/web/src/lang/backend/en/mall/playxOrder.ts @@ -0,0 +1,36 @@ +export default { + id: 'id', + user_id: 'user_id', + type: 'type', + 'type BONUS': 'Bonus(BONUS)', + 'type PHYSICAL': 'Physical(PHYSICAL)', + 'type WITHDRAW': 'Withdraw(WITHDRAW)', + status: 'status', + 'status PENDING': 'Pending(PENDING)', + 'status COMPLETED': 'Completed(COMPLETED)', + 'status SHIPPED': 'Shipped(SHIPPED)', + 'status REJECTED': 'Rejected(REJECTED)', + mall_item_id: 'mall_item_id', + mallitem__title: 'title', + points_cost: 'points_cost', + amount: 'amount', + multiplier: 'multiplier', + external_transaction_id: 'external_transaction_id', + playx_transaction_id: 'playx_transaction_id', + grant_status: 'grant_status', + 'grant_status NOT_SENT': 'NOT_SENT', + 'grant_status SENT_PENDING': 'SENT_PENDING', + 'grant_status ACCEPTED': 'ACCEPTED', + 'grant_status FAILED_RETRYABLE': 'FAILED_RETRYABLE', + 'grant_status FAILED_FINAL': 'FAILED_FINAL', + fail_reason: 'fail_reason', + reject_reason: 'reject_reason', + shipping_company: 'shipping_company', + shipping_no: 'shipping_no', + receiver_name: 'receiver_name', + receiver_phone: 'receiver_phone', + receiver_address: 'receiver_address', + create_time: 'create_time', + update_time: 'update_time', + 'quick Search Fields': 'ID', +} diff --git a/web/src/lang/backend/zh-cn/mall/playxOrder.ts b/web/src/lang/backend/zh-cn/mall/playxOrder.ts new file mode 100644 index 0000000..99ae053 --- /dev/null +++ b/web/src/lang/backend/zh-cn/mall/playxOrder.ts @@ -0,0 +1,37 @@ +export default { + id: 'ID', + user_id: '用户ID', + type: '类型', + 'type BONUS': '红利(BONUS)', + 'type PHYSICAL': '实物(PHYSICAL)', + 'type WITHDRAW': '提现(WITHDRAW)', + status: '状态', + 'status PENDING': '处理中(PENDING)', + 'status COMPLETED': '已完成(COMPLETED)', + 'status SHIPPED': '已发货(SHIPPED)', + 'status REJECTED': '已驳回(REJECTED)', + mall_item_id: '商品ID', + mallitem__title: '商品标题', + points_cost: '消耗积分', + amount: '现金面值', + multiplier: '流水倍数', + external_transaction_id: '外部交易幂等键', + playx_transaction_id: 'PlayX流水号', + grant_status: '发放子状态', + 'grant_status NOT_SENT': '未发送', + 'grant_status SENT_PENDING': '已发送排队', + 'grant_status ACCEPTED': '已接收(accepted)', + 'grant_status FAILED_RETRYABLE': '失败可重试', + 'grant_status FAILED_FINAL': '失败最终', + fail_reason: '失败原因', + reject_reason: '驳回原因', + shipping_company: '物流公司', + shipping_no: '物流单号', + receiver_name: '收货人', + receiver_phone: '收货电话', + receiver_address: '收货地址', + create_time: '创建时间', + update_time: '修改时间', + 'quick Search Fields': 'ID', +} + diff --git a/web/src/views/backend/mall/playxOrder/index.vue b/web/src/views/backend/mall/playxOrder/index.vue new file mode 100644 index 0000000..f75db2c --- /dev/null +++ b/web/src/views/backend/mall/playxOrder/index.vue @@ -0,0 +1,240 @@ + + + + + +