| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- <?php
- namespace plugin\saiadmin\app\logic\fund;
- use DateTime;
- use plugin\saiadmin\app\model\system\SystemUser;
- use plugin\saiadmin\basic\think\BaseLogic;
- use plugin\saiadmin\exception\ApiException;
- use plugin\saiadmin\service\OpenSpoutWriter;
- use support\Response;
- use support\think\Db;
- /**
- * 小程序用户充值、扣减流水查询与导出。
- */
- class RechargeRecordLogic extends BaseLogic
- {
- private const BALANCE_TYPE_NAMES = [
- 'wallet' => '钱包充值',
- 'card' => '储值卡本金',
- 'card_gift' => '储值卡充值',
- ];
- private const EXPORT_LIMIT = 50000;
- public function indexList(array $filters): array
- {
- $page = max(1, (int) request()->input('page', 1));
- $limit = min(100, max(1, (int) request()->input('limit', 20)));
- $result = $this->buildQuery($filters)
- ->order('l.created_at', 'desc')
- ->order('l.id', 'desc')
- ->paginate(['page' => $page, 'list_rows' => $limit])
- ->toArray();
- $result['data'] = array_map(fn (array $row): array => $this->formatRecord($row), $result['data'] ?? []);
- $result['summary'] = $this->summary($filters);
- return $result;
- }
- public function export(array $filters): Response
- {
- $query = $this->buildQuery($filters);
- $count = (int) (clone $query)->count();
- if ($count > self::EXPORT_LIMIT) {
- throw new ApiException('导出记录超过 50000 条,请缩小筛选范围');
- }
- $rows = $query->order('l.created_at', 'desc')->order('l.id', 'desc')->select()->toArray();
- $data = [];
- foreach ($rows as $row) {
- $record = $this->formatRecord($row);
- $data[] = [
- 'id' => $record['id'],
- 'user_id' => $record['user_id'],
- 'nickname' => $this->excelSafe($record['nickname']),
- 'phone' => $this->excelSafe($record['phone']),
- 'user_source_name' => $record['user_source_name'],
- 'balance_type_name' => $record['balance_type_name'],
- 'direction_name' => $record['direction_name'],
- 'change_amount' => $record['change_amount'],
- 'before_balance' => $record['before_balance'],
- 'after_balance' => $record['after_balance'],
- 'source_name' => $record['source_name'],
- 'operator_name' => $this->excelSafe($record['operator_name']),
- 'created_at' => $record['created_at'],
- 'remark' => $this->excelSafe($record['remark']),
- ];
- }
- $fileName = '充值记录_' . date('Ymd_His') . '.xlsx';
- $writer = new OpenSpoutWriter($fileName);
- $writer->setWidth([10, 12, 22, 18, 12, 18, 12, 14, 14, 14, 14, 18, 22, 36]);
- $writer->setHeader([
- '记录ID', '用户ID', '用户昵称', '手机号', '用户来源', '账户类型', '变动方向',
- '变动金额', '变动前余额', '变动后余额', '充值方式', '操作人', '充值时间', '备注',
- ]);
- $writer->setData($data);
- return response()->download($writer->returnFile(), urlencode($fileName));
- }
- private function buildQuery(array $filters)
- {
- $query = Db::table('sa_rental_user_wallet_log')->alias('l')
- ->leftJoin('sa_rental_user u', 'u.id = l.user_id AND u.delete_time IS NULL')
- ->field(implode(',', [
- 'l.id', 'l.user_id', 'l.balance_type', 'l.direction', 'l.amount',
- 'l.before_balance', 'l.after_balance', 'l.source', 'l.remark',
- 'l.operator_id', 'l.operator_name', 'l.created_at',
- 'u.nickname', 'u.phone', 'u.avatar', 'u.source AS user_source',
- ]));
- $managerIds = $this->accessibleManagerIds();
- if (is_array($managerIds)) {
- $userIds = $managerIds === [] ? [] : Db::table('sa_rental_order')
- ->whereNull('delete_time')
- ->whereIn('manage_agent_id', $managerIds)
- ->where('user_id', '>', 0)
- ->distinct(true)
- ->column('user_id');
- if ($userIds === []) {
- $query->where('l.id', 0);
- } else {
- $query->whereIn('l.user_id', array_map('intval', $userIds));
- }
- }
- $balanceType = trim((string) ($filters['balance_type'] ?? ''));
- if ($balanceType !== '') {
- if (!isset(self::BALANCE_TYPE_NAMES[$balanceType])) {
- throw new ApiException('充值类型不支持');
- }
- $query->where('l.balance_type', $balanceType);
- }
- $direction = trim((string) ($filters['direction'] ?? ''));
- if ($direction !== '') {
- if (!in_array($direction, ['1', '-1'], true)) {
- throw new ApiException('变动方向不支持');
- }
- $query->where('l.direction', (int) $direction);
- }
- $userId = trim((string) ($filters['user_id'] ?? ''));
- if ($userId !== '') {
- if (!ctype_digit($userId) || (int) $userId <= 0) {
- throw new ApiException('用户 ID 格式不正确');
- }
- $query->where('l.user_id', (int) $userId);
- }
- $nickname = trim((string) ($filters['nickname'] ?? ''));
- if ($nickname !== '') {
- $query->whereLike('u.nickname', '%' . $nickname . '%');
- }
- [$startDate, $endDate] = $this->dateRange($filters);
- if ($startDate !== '') {
- $query->where('l.created_at', '>=', $startDate . ' 00:00:00');
- }
- if ($endDate !== '') {
- $query->where('l.created_at', '<=', $endDate . ' 23:59:59');
- }
- return $query;
- }
- private function summary(array $filters): array
- {
- $query = $this->buildQuery($filters);
- $query->removeOption('field');
- $rows = $query->field(implode(',', [
- 'COUNT(*) AS record_count',
- 'COALESCE(SUM(CASE WHEN l.amount > 0 THEN l.amount ELSE 0 END), 0) AS increase_amount',
- 'COALESCE(SUM(CASE WHEN l.amount < 0 THEN ABS(l.amount) ELSE 0 END), 0) AS decrease_amount',
- 'COALESCE(SUM(l.amount), 0) AS net_amount',
- ]))->select()->toArray();
- $row = $rows[0] ?? [];
- return [
- 'record_count' => (int) ($row['record_count'] ?? 0),
- 'increase_amount' => round((float) ($row['increase_amount'] ?? 0), 2),
- 'decrease_amount' => round((float) ($row['decrease_amount'] ?? 0), 2),
- 'net_amount' => round((float) ($row['net_amount'] ?? 0), 2),
- ];
- }
- private function formatRecord(array $row): array
- {
- $balanceType = (string) ($row['balance_type'] ?? '');
- $amount = round((float) ($row['amount'] ?? 0), 2);
- $direction = (int) ($row['direction'] ?? ($amount >= 0 ? 1 : -1));
- $operatorId = (int) ($row['operator_id'] ?? 0);
- $operatorName = trim((string) ($row['operator_name'] ?? ''));
- $source = (string) ($row['source'] ?? '');
- return [
- 'id' => (int) $row['id'],
- 'user_id' => (int) ($row['user_id'] ?? 0),
- 'nickname' => (string) (($row['nickname'] ?? '') ?: '未设置昵称'),
- 'phone' => (string) ($row['phone'] ?? ''),
- 'avatar' => (string) ($row['avatar'] ?? ''),
- 'user_source' => (int) ($row['user_source'] ?? 0),
- 'user_source_name' => (int) ($row['user_source'] ?? 0) === 2 ? '支付宝' : '微信',
- 'balance_type' => $balanceType,
- 'balance_type_name' => self::BALANCE_TYPE_NAMES[$balanceType] ?? '其他账户',
- 'direction' => $direction,
- 'direction_name' => $direction === 1 ? '增加' : '扣减',
- 'amount' => $amount,
- 'change_amount' => abs($amount),
- 'before_balance' => round((float) ($row['before_balance'] ?? 0), 2),
- 'after_balance' => round((float) ($row['after_balance'] ?? 0), 2),
- 'source' => $source,
- 'source_name' => $source === 'admin_recharge' ? '后台调整' : ($source ?: '其他'),
- 'remark' => (string) ($row['remark'] ?? ''),
- 'operator_id' => $operatorId,
- 'operator_name' => $operatorName !== '' ? $operatorName : '管理员#' . $operatorId,
- 'created_at' => (string) ($row['created_at'] ?? ''),
- 'status_name' => '已完成',
- ];
- }
- /** @return array{0:string,1:string} */
- private function dateRange(array $filters): array
- {
- $startDate = trim((string) ($filters['start_date'] ?? ''));
- $endDate = trim((string) ($filters['end_date'] ?? ''));
- foreach ([['开始', $startDate], ['结束', $endDate]] as [$label, $date]) {
- if ($date === '') {
- continue;
- }
- $parsed = DateTime::createFromFormat('!Y-m-d', $date);
- if (!$parsed || $parsed->format('Y-m-d') !== $date) {
- throw new ApiException($label . '日期格式不正确');
- }
- }
- if ($startDate !== '' && $endDate !== '' && $startDate > $endDate) {
- throw new ApiException('开始日期不能晚于结束日期');
- }
- return [$startDate, $endDate];
- }
- /** @return int[]|null */
- private function accessibleManagerIds(): ?array
- {
- $currentId = (int) ($this->adminInfo['id'] ?? 0);
- if ($currentId === 1) {
- return null;
- }
- if ($currentId <= 0) {
- return [];
- }
- $managerIds = [$currentId];
- $dept = $this->adminInfo['deptList'] ?? [];
- if (empty($dept['id']) || !isset($dept['level'])) {
- return $managerIds;
- }
- $prefix = (string) $dept['level'] . (int) $dept['id'] . ',';
- $deptIds = Db::table('sa_system_dept')->whereNull('delete_time')
- ->whereLike('level', $prefix . '%')->column('id');
- if ($deptIds !== []) {
- $managerIds = array_merge($managerIds, array_map('intval', SystemUser::whereIn('dept_id', $deptIds)
- ->whereNull('delete_time')->column('id')));
- }
- return array_values(array_unique($managerIds));
- }
- private function excelSafe(mixed $value): string
- {
- $value = (string) $value;
- return preg_match('/^[=+\-@]/u', $value) === 1 ? "'" . $value : $value;
- }
- }
|