doge 2 giorni fa
parent
commit
c3af633aff

+ 3 - 7
app/Services/ShareEarningReportService.php

@@ -160,16 +160,17 @@ class ShareEarningReportService
 
     public function summary(array $params): array
     {
-        [$start, $end] = $this->dateRange($params);
+        $range = !empty($params['start_date']) || !empty($params['end_date']) ? $this->dateRange($params) : null;
         $page = (int)($params['page'] ?? 1);
         $limit = (int)($params['limit'] ?? 20);
         $prefix = DB::getTablePrefix();
         $recordAlias = $prefix . 'share_record';
         $query = ShareCommissionRecord::query()->from('share_commission_records as share_record')
             ->leftJoin('users as promoter', 'promoter.id', '=', 'share_record.promoter_user_id')
-            ->whereBetween('share_record.stat_date', [$start, $end])
             ->where('share_record.status', 1);
+        if ($range !== null) $query->whereBetween('share_record.stat_date', $range);
         if (!empty($params['account'])) $query->where('promoter.account', 'like', '%' . $params['account'] . '%');
+        $totalQuery = clone $query;
         $grouped = $query->select([
             'share_record.stat_date', 'share_record.promoter_user_id',
             'promoter.member_id as promoter_member_id', 'promoter.account as promoter_account',
@@ -197,11 +198,6 @@ class ShareEarningReportService
                 'fee_waiver_rate' => number_format((float)$row->fee_waiver_rate, 4, '.', ''),
             ]);
 
-        $totalQuery = ShareCommissionRecord::query()->from('share_commission_records as share_record')
-            ->leftJoin('users as promoter', 'promoter.id', '=', 'share_record.promoter_user_id')
-            ->whereBetween('share_record.stat_date', [$start, $end])
-            ->where('share_record.status', 1);
-        if (!empty($params['account'])) $totalQuery->where('promoter.account', 'like', '%' . $params['account'] . '%');
         $totals = $totalQuery->selectRaw("SUM(CASE WHEN {$recordAlias}.type = 'commission' THEN {$recordAlias}.amount ELSE 0 END) AS total_commission")
             ->selectRaw("MAX(CASE WHEN {$recordAlias}.type = 'fee_waiver' THEN {$recordAlias}.rate ELSE 0 END) AS highest_fee_waiver_rate")->first();
         return [

+ 14 - 2
docs/前端接口/分享赚钱/推广汇总/接口.md

@@ -8,9 +8,21 @@
 
 | 参数 | 必填 | 中文含义 |
 |---|---|---|
-| `start_date/end_date` | 否 | 日期范围,默认最近 31 天 |
+| `start_date/end_date` | 否 | 均不传或均为空时不限制日期;选择日期后成对提交,按 `stat_date` 闭区间筛选 |
 | `account` | 否 | 推广账号,模糊查询 |
-| `page/limit` | 否 | 页码/每页条数 |
+| `page/limit` | 否 | 页码/每页条数,默认 `1/20`,`limit` 范围为 `1`~`200` |
+
+不传时间时查询全部日期的有效明细(`status=1`),仍按 `page/limit` 分页,不会一次返回所有行。列表、总数和合计使用同一筛选范围;`summary` 合计包含整个筛选结果,不仅是当前页。
+
+```text
+# 默认全部日期,返回第1页
+/admin/shareEarning/summary?account=&page=1&limit=10
+
+# 指定时间范围
+/admin/shareEarning/summary?start_date=2026-09-23&end_date=2026-10-23&page=1&limit=10
+```
+
+前端清空时间选择器时,省略两个日期参数,或都传空字符串;不要继续携带上次选中的日期。显式日期范围仍最多支持 366 天,无日期筛选时可跨年查询。该默认行为仅适用于 `summary`;推广会员列表及回填命令仍沿用各自原有的默认范围。
 
 响应:
 

+ 4 - 2
docs/后端/分享赚钱-推广汇总明细登记与历史回填.md

@@ -20,7 +20,7 @@
 
 ## 历史回填
 
-**省略日期仍然只查最近 31 天,不表示全历史。**若运行后所有计数为 `0`,先在发布本次命令更新后执行:
+**回填命令省略日期仍然只查最近 31 天,不表示全历史。**推广汇总 API 已改为未传日期时查询全部日期,两者默认行为不同。若回填命令所有计数为 `0`,先在发布本次命令更新后执行:
 
 ```bash
 php artisan share:backfill-commissions --diagnose --all
@@ -51,7 +51,7 @@ php artisan share:backfill-commissions --start-date=2026-09-23 --end-date=2026-1
 | `--diagnose` | 只读输出数据链路统计,不执行登记;可与 `--all` 同用,与 `--write` 互斥 |
 | `--write` | 实际登记;省略时仅预览 |
 
-指定日期时单次范围不超过 366 天;历史更长可按月份或年度分批执行。默认范围与汇总接口一致,但统计的是返现流水日期,不是会员注册日期。全历史预览使用 `--all`,实际登记再加 `--write`。历史明细保留原入账日期;回填旧日期后,汇总 API 仍需传对应日期才能查看,不会统一改记为今天。
+指定日期时单次范围不超过 366 天;历史更长可按月份或年度分批执行。命令统计的是返现流水日期,不是会员注册日期,默认最近 31 天。全历史预览使用 `--all`,实际登记再加 `--write`。历史明细保留原入账日期,不会统一改记为今天;汇总 API 不传日期时可按页查看这些历史记录,显式选择日期时仍会过滤范围外的数据。
 
 ### 诊断结果对应断点
 
@@ -97,6 +97,8 @@ php artisan share:backfill-commissions --start-date=2026-09-23 --end-date=2026-1
 
 成功码为 `0`,查看 `data.total`、`data.data` 及 `data.summary.total_commission`。如果命令 `scanned=0`,说明范围内没有已付返现可补;推荐会员有数据不等于已经产生佣金。
 
+检查全部日期的已登记明细时,请使用 `/admin/shareEarning/summary?account=&page=1&limit=10`,省略起止日期;返回仍按页限制,合计覆盖全部符合条件的记录。仅该汇总 API 改为默认全部日期,推广会员列表和回填命令的默认最近 31 天保持不变。
+
 `tests/Integration/ShareCommissionRecordingTest.php` 使用真实控制器、资金服务、登记服务和命令,连接隔离的 SQLite 内存库,覆盖新返现、接口汇总、原发放条件、故障回滚、历史日期、比例变更、缺失元数据、只读预览、重复回填及全历史诊断;不会连接生产库或执行真实充值。
 
 ```bash

+ 100 - 0
tests/Integration/ShareCommissionRecordingTest.php

@@ -449,4 +449,104 @@ class ShareCommissionRecordingTest extends TestCase
         $this->assertSame(0, $counts['linked_real_invitee_deposit_logs_all']);
         $this->assertSame(0, $counts['with_promoter_wallet_deposit_logs_all']);
     }
+
+    private function summaryHistory(): void
+    {
+        DB::table('users')->insert(['id' => 99, 'member_id' => '1003', 'account' => 'other', 'first_name' => '其他推广人', 'user_code' => 'OTHER']);
+        foreach ([
+            ['2020-01-01', 7, 'commission', '100.0000', '2.5000', '2.5000', 1],
+            ['2020-01-01', 7, 'fee_waiver', '40.0000', '2.5000', '1.0000', 1],
+            ['2026-10-01', 7, 'commission', '200.0000', '2.5000', '5.0000', 1],
+            ['2026-10-02', 7, 'commission', '1000.0000', '10.0000', '100.0000', 0],
+            ['2026-10-05', 99, 'commission', '1000.0000', '10.0000', '100.0000', 1],
+        ] as $i => [$day, $promoter, $type, $base, $rate, $amount, $status]) {
+            ShareCommissionRecord::create([
+                'stat_date' => $day, 'promoter_user_id' => $promoter, 'type' => $type,
+                'category' => 'recharge', 'base_amount' => $base, 'rate' => $rate, 'amount' => $amount,
+                'source_type' => 'summary_test', 'source_id' => (string)$i, 'status' => $status,
+            ]);
+        }
+    }
+
+    private function summaryResponse(array $params): array
+    {
+        $request = Request::create('/admin/shareEarning/summary', 'GET', $params);
+        return (new \App\Http\Middleware\TrimStrings())->handle($request, function ($request) {
+            return (new \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull())->handle($request, function ($request) {
+                $this->app->instance('request', $request);
+                return (new ShareEarning())->summary(new ShareEarningReportService());
+            });
+        })->getData(true);
+    }
+
+    public static function clearedSummaryDates(): array
+    {
+        return [
+            'omitted' => [[]],
+            'null' => [['start_date' => null, 'end_date' => null]],
+            'empty strings from date picker' => [['start_date' => '', 'end_date' => '']],
+        ];
+    }
+
+    /** @dataProvider clearedSummaryDates */
+    public function test_summary_without_dates_includes_all_history_and_keeps_pagination_and_global_totals(array $dates): void
+    {
+        $this->summaryHistory();
+        $before = DB::table('share_commission_records')->orderBy('id')->get()->toJson();
+        $result = $this->summaryResponse($dates + ['account' => '', 'page' => 1, 'limit' => 1]);
+        $this->assertSame(0, $result['code']);
+        $this->assertSame(3, $result['data']['total']);
+        $this->assertCount(1, $result['data']['data']);
+        $this->assertSame('2026-10-05', $result['data']['data'][0]['date']);
+        $this->assertSame('107.5000', $result['data']['summary']['total_commission']);
+        $this->assertSame('2.5000', $result['data']['summary']['highest_fee_waiver_rate']);
+        $last = $this->summaryResponse($dates + ['page' => 3, 'limit' => 1]);
+        $this->assertSame('2020-01-01', $last['data']['data'][0]['date']);
+        $this->assertSame('40.0000', $last['data']['data'][0]['sales_amount']);
+        $this->assertSame($before, DB::table('share_commission_records')->orderBy('id')->get()->toJson());
+    }
+
+    public static function selectedSummaryDates(): array
+    {
+        return [
+            ['2020-01-01', '2020-01-01', '2.5000', '2.5000'],
+            ['2026-10-01', '2026-10-02', '5.0000', '0.0000'],
+        ];
+    }
+
+    /** @dataProvider selectedSummaryDates */
+    public function test_summary_explicit_dates_apply_to_list_count_and_totals(string $start, string $end, string $commission, string $feeRate): void
+    {
+        $this->summaryHistory();
+        $result = $this->summaryResponse(['start_date' => $start, 'end_date' => $end, 'page' => 1, 'limit' => 10]);
+        $this->assertSame(0, $result['code']);
+        $this->assertSame(1, $result['data']['total']);
+        $this->assertCount(1, $result['data']['data']);
+        $this->assertSame($start, $result['data']['data'][0]['date']);
+        $this->assertSame($commission, $result['data']['summary']['total_commission']);
+        $this->assertSame($feeRate, $result['data']['summary']['highest_fee_waiver_rate']);
+    }
+
+    public function test_summary_default_all_history_still_applies_account_and_status_filters(): void
+    {
+        $this->summaryHistory();
+        $result = $this->summaryResponse(['account' => 'prom', 'page' => 1, 'limit' => 10]);
+        $this->assertSame(0, $result['code']);
+        $this->assertSame(2, $result['data']['total']);
+        $this->assertSame('7.5000', $result['data']['summary']['total_commission']);
+        $this->assertSame('2.5000', $result['data']['summary']['highest_fee_waiver_rate']);
+        $this->assertSame(['promoter', 'promoter'], array_column($result['data']['data'], 'promoter_account'));
+    }
+
+    public function test_summary_explicit_range_limits_and_other_report_defaults_remain_unchanged(): void
+    {
+        $this->summaryHistory();
+        $invalid = $this->summaryResponse(['start_date' => '2020-01-01', 'end_date' => '2026-10-01']);
+        $this->assertSame(-3, $invalid['code']);
+        $this->assertStringContainsString('366', $invalid['msg']);
+        DB::table('users')->update(['created_at' => '2020-01-01 00:00:00']);
+        $other = (new ShareEarningReportService())->memberDays([]);
+        $this->assertSame(0, $other['total']);
+        $this->assertSame([], $other['data']);
+    }
 }