| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- <?php
- namespace App\Services\Sharing;
- use App\Models\User;
- use App\Models\Wallet;
- use App\Services\BalanceLogService;
- use App\Services\OperationAuditService;
- use Carbon\Carbon;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Validator;
- use Illuminate\Validation\Rule;
- use Illuminate\Validation\ValidationException;
- class ShareClaimAuditService
- {
- private const AUDIT_TYPE = 'agent_commission';
- public function options(): array
- {
- return ['audit_types' => [['value' => self::AUDIT_TYPE, 'label' => '代理佣金']]];
- }
- public function list(array $filters): array
- {
- $params = Validator::make($filters, [
- 'page' => ['nullable', 'integer', 'min:1'], 'limit' => ['nullable', 'integer', 'min:1', 'max:200'],
- 'status' => ['nullable', Rule::in(['pending', 'approved', 'rejected'])],
- // This endpoint currently reads only agent-commission claims.
- 'audit_type' => ['nullable', 'string', Rule::in([self::AUDIT_TYPE])],
- 'account' => ['nullable', 'string', 'max:100'], 'user_id' => ['nullable', 'integer', 'min:1'],
- 'start_date' => ['nullable', 'date_format:Y-m-d'],
- 'end_date' => ['nullable', 'date_format:Y-m-d', 'after_or_equal:start_date'],
- ])->validate();
- $query = DB::table('share_claims as c')->leftJoin('users as u', 'u.id', '=', 'c.user_id');
- if (!empty($params['status'])) $query->where('c.status', $params['status']);
- if (!empty($params['account'])) $query->where('u.account', 'like', '%' . $params['account'] . '%');
- if (!empty($params['user_id'])) $query->where('c.user_id', $params['user_id']);
- if (!empty($params['start_date'])) $query->where('c.created_at', '>=', $params['start_date'] . ' 00:00:00');
- if (!empty($params['end_date'])) $query->where('c.created_at', '<=', $params['end_date'] . ' 23:59:59');
- $total = $query->count();
- $rows = $query->select(['c.*', 'u.account', 'u.member_id', 'u.first_name as nickname'])->orderByDesc('c.id')
- ->forPage((int)($params['page'] ?? 1), (int)($params['limit'] ?? 20))->get();
- return ['total' => $total, 'page' => (int)($params['page'] ?? 1), 'limit' => (int)($params['limit'] ?? 20),
- 'data' => $rows->map(fn ($row) => $this->claim($row))->all()];
- }
- public function detail(int $id): array
- {
- $row = DB::table('share_claims as c')->leftJoin('users as u', 'u.id', '=', 'c.user_id')
- ->where('c.id', $id)->select(['c.*', 'u.account', 'u.member_id', 'u.first_name as nickname'])->first();
- if (!$row) $this->invalid('id', '领取申请不存在');
- $account = DB::table('share_accounts')->where('user_id', $row->user_id)->first();
- $earned = (string)($account->earned_amount ?? '0');
- $paid = (string)($account->paid_amount ?? '0');
- $reserved = (string)($account->reserved_amount ?? '0');
- $balance = bcsub($earned, $paid, 10);
- $spendable = bcsub($balance, $reserved, 10);
- return $this->claim($row) + ['commission_account' => [
- 'earned_amount' => $this->money($earned), 'paid_amount' => $this->money($paid), 'reserved_amount' => $this->money($reserved),
- 'pending_amount' => $this->money($this->positive($balance)), 'spendable_amount' => $this->money($this->positive($spendable)),
- 'debt_amount' => $this->money($this->positive(bcsub($paid, $earned, 10))),
- ], 'can_approve' => $row->status === 'pending' && bccomp($balance, (string)$row->amount, 10) >= 0];
- }
- public function audit(int $id, string $action, int $adminId, string $remark = ''): array
- {
- Validator::make(compact('id', 'action', 'remark'), ['id' => ['required', 'integer', 'min:1'],
- 'action' => ['required', Rule::in(['approve', 'reject'])], 'remark' => ['string', 'max:2000']])->validate();
- if ($adminId <= 0) $this->invalid('admin', '请使用后台管理员身份审核');
- $userId = DB::table('share_claims')->where('id', $id)->value('user_id');
- if (!$userId) $this->invalid('id', '领取申请不存在');
- return DB::transaction(function () use ($id, $action, $adminId, $remark, $userId) {
- // Every claim mutation uses the same lock order as member requests and settlement corrections.
- $account = DB::table('share_accounts')->where('user_id', $userId)->lockForUpdate()->first();
- if (!$account) $this->invalid('id', '会员佣金账户不存在');
- $claim = DB::table('share_claims')->where('id', $id)->lockForUpdate()->first();
- if (!$claim || (int)$claim->user_id !== (int)$userId) $this->invalid('id', '领取申请不存在');
- $targetStatus = $action === 'approve' ? 'approved' : 'rejected';
- if ($claim->status === $targetStatus) return $this->detail($id);
- if ($claim->status !== 'pending') $this->invalid('id', '领取申请已审核,不可更改结果');
- $amount = (string)$claim->amount;
- if (bccomp($amount, '0.01', 10) < 0 || bccomp((string)$account->reserved_amount, $amount, 10) < 0) {
- $this->invalid('id', '申请金额或预留佣金异常,请核对');
- }
- $now = Carbon::now(ShareDefaults::TIMEZONE)->format('Y-m-d H:i:s');
- $changes = ['reserved_amount' => bcsub((string)$account->reserved_amount, $amount, 10), 'updated_at' => $now];
- $claimChanges = ['status' => $targetStatus, 'active_user_id' => null, 'audit_admin_id' => $adminId,
- 'audit_remark' => $remark, 'audited_at' => $now, 'updated_at' => $now];
- if ($action === 'approve') {
- if (bccomp(bcsub((string)$account->earned_amount, (string)$account->paid_amount, 10), $amount, 10) < 0) {
- $this->invalid('amount', '佣金修正后余额不足,请驳回原申请后由会员重新申请');
- }
- $user = User::query()->find($userId);
- if (!$user) $this->invalid('user_id', '会员不存在');
- $wallet = Wallet::query()->where('member_id', $user->member_id)->orderBy('id')->lockForUpdate()->first();
- if (!$wallet) $this->invalid('user_id', '会员钱包不存在');
- $before = (string)$wallet->available_balance;
- $after = bcadd($before, $amount, 10);
- $wallet->available_balance = $after;
- $wallet->save();
- $log = BalanceLogService::addLog($user->member_id, $amount, $before, $after, '分享佣金领取', $id, $remark);
- $claimChanges['credited_log_id'] = (int)$log->id;
- $changes['paid_amount'] = bcadd((string)$account->paid_amount, $amount, 10);
- }
- DB::table('share_accounts')->where('user_id', $userId)->update($changes);
- DB::table('share_claims')->where('id', $id)->update($claimChanges);
- OperationAuditService::record('share_claim', $id, $action, (array)$claim, (array)DB::table('share_claims')->where('id', $id)->first());
- return $this->detail($id);
- });
- }
- private function claim(object $row): array
- {
- return ['id' => (int)$row->id, 'audit_type' => self::AUDIT_TYPE, 'activity_name' => '代理佣金',
- 'user_id' => (int)$row->user_id, 'member_id' => (string)($row->member_id ?? ''),
- 'account' => (string)($row->account ?? ''), 'nickname' => (string)($row->nickname ?? ''), 'request_id' => (string)$row->request_id,
- 'amount' => $this->money($row->amount), 'status' => $row->status,
- 'audit_admin_id' => $row->audit_admin_id === null ? null : (int)$row->audit_admin_id,
- 'audit_remark' => (string)($row->audit_remark ?? ''), 'audited_at' => $row->audited_at,
- 'credited_log_id' => $row->credited_log_id === null ? null : (int)$row->credited_log_id,
- 'created_at' => $row->created_at, 'updated_at' => $row->updated_at];
- }
- private function positive(string $value): string { return bccomp($value, '0', 10) > 0 ? $value : '0'; }
- private function money($value): string { return bcadd((string)$value, '0', 2); }
- private function invalid(string $key, string $message): void { throw ValidationException::withMessages([$key => $message]); }
- }
|