', 0); $totalCount = (int) $query->count(); $totalCommission = (float) $query->sum('commission'); $sampleList = UserModel::where('commission', '>', 0) ->field('id,nickname,mobile,money,commission') ->order('commission', 'desc') ->limit(20) ->select(); $samples = []; foreach ($sampleList as $u) { $samples[] = [ 'id' => (int) $u->id, 'nickname' => (string) ($u->nickname ?: '用户' . $u->id), 'mobile' => (string) ($u->mobile ?: ''), 'current_money' => number_format((float) $u->money, 2, '.', ''), 'current_commission' => number_format((float) $u->commission, 2, '.', ''), 'target_money' => number_format((float) bcadd((string) $u->money, (string) $u->commission, 2), 2, '.', ''), ]; } return [ 'total_users' => $totalCount, 'total_commission' => number_format($totalCommission, 2, '.', ''), 'samples' => $samples, ]; } /** * 执行全量结转:将所有会员的 commission 余额转入 money 钱包 * * @param bool $isDryRun 是否只读模拟 * @param callable|null $progressCallback 进度回调 * @param array $operator 操作人上下文 * @return array */ public function transferAll($isDryRun = false, $progressCallback = null, $operator = []) { if ($isDryRun) { $preview = $this->preview(); return [ 'status' => 'dry_run', 'dry_run' => true, 'total_users' => $preview['total_users'], 'transferred_amount' => $preview['total_commission'], 'success_count' => 0, 'fail_count' => 0, 'errors' => [], 'preview' => $preview, ]; } $userIds = UserModel::where('commission', '>', 0)->order('id', 'asc')->column('id'); $totalUsers = count($userIds); $successCount = 0; $failCount = 0; $transferredAmount = '0.00'; $errors = []; foreach ($userIds as $idx => $userId) { try { $transferResult = Db::transaction(function () use ($userId) { $user = UserModel::where('id', $userId)->lock(true)->find(); if (!$user) { return 0.0; } $amount = floatval($user->commission); if ($amount <= 0) { return 0.0; } // 1. 扣减佣金钱包 Wallet::change($user, 'commission', -$amount, 'transfer_to_money', [ 'batch_migration' => 1, 'reason' => '全量历史收益结转至钱包余额', ], '收益结转至钱包余额'); // 2. 增加钱包余额 Wallet::change($user, 'money', $amount, 'transfer_by_commission', [ 'batch_migration' => 1, 'reason' => '全量历史收益结转至钱包余额', ], '收益结转至钱包余额'); return $amount; }); if ($transferResult > 0) { $successCount++; $transferredAmount = bcadd($transferredAmount, (string) $transferResult, 2); } if (is_callable($progressCallback)) { $progressCallback($idx + 1, $totalUsers, $userId, $transferResult, null); } } catch (\Throwable $e) { $failCount++; $errItem = [ 'user_id' => $userId, 'error' => $e->getMessage(), ]; $errors[] = $errItem; if (is_callable($progressCallback)) { $progressCallback($idx + 1, $totalUsers, $userId, 0.0, $e->getMessage()); } } } return [ 'status' => 'completed', 'dry_run' => false, 'total_users' => $totalUsers, 'success_count' => $successCount, 'fail_count' => $failCount, 'transferred_amount' => number_format((float) $transferredAmount, 2, '.', ''), 'errors' => $errors, ]; } }