Răsfoiți Sursa

去掉时间必填

doge 1 zi în urmă
părinte
comite
23a69f51ca

+ 35 - 12
app/Services/Reporting/ReportReader.php

@@ -29,13 +29,30 @@ class ReportReader
             'group_id' => ['nullable', 'string', 'max:40'], 'page' => ['nullable', 'integer', 'min:1', 'max:1000000'], 'limit' => ['nullable', 'integer', 'min:1', 'max:100'],
             'metric' => ['nullable', Rule::in(['deposit_amount', 'bet_amount', 'payout_amount', 'withdraw_amount', 'activity_minutes'])],
             'kind' => ['nullable', Rule::in(['deposit', 'withdraw', 'agent_withdraw', 'bonus', 'agent_commission', 'share_commission', 'agent_accrual', 'share_accrual', 'manual_credit', 'manual_debit'])]])->validate();
-        $p = array_filter($p, fn ($v) => $v !== null && $v !== '') + ['report_type' => 'business', 'platform' => 'all', 'category' => 'all', 'settlement_status' => 'all',
+        $p = array_filter($p, fn ($v) => $v !== null && $v !== '');
+        $allTime = !isset($p['start_date']) && !isset($p['end_date']);
+        $p += ['report_type' => 'business', 'platform' => 'all', 'category' => 'all', 'settlement_status' => 'all',
             'start_date' => now('Asia/Shanghai')->toDateString(), 'end_date' => now('Asia/Shanghai')->toDateString(), 'page' => 1, 'limit' => 20, 'metric' => 'deposit_amount'];
+        $p['all_time'] = (int) $allTime;
+        if ($allTime) $p['start_date'] = $this->firstReportDate($p['end_date']);
         $start = Carbon::parse($p['start_date'], 'Asia/Shanghai'); $end = Carbon::parse($p['end_date'], 'Asia/Shanghai')->addDay();
-        if ($end->lte($start) || $start->diffInDays($end) > 366 || $p['end_date'] > now('Asia/Shanghai')->toDateString()) throw ValidationException::withMessages(['end_date' => '日期范围必须有效、不超过366天且不能查询未来日期']);
+        if ($end->lte($start) || (!$allTime && $start->diffInDays($end) > 366) || $p['end_date'] > now('Asia/Shanghai')->toDateString()) throw ValidationException::withMessages(['end_date' => '日期范围必须有效、不超过366天且不能查询未来日期']);
         $p['start'] = $start->toDateTimeString(); $p['end'] = $end->toDateTimeString(); return $p;
     }
 
+    private function firstReportDate(string $today): string
+    {
+        $dates = [$today]; $grammar = DB::connection()->getQueryGrammar();
+        $effectiveAt = 'COALESCE(' . $grammar->wrap('r.occurred_at') . ', ' . $grammar->wrap('e.occurred_at') . ')';
+        $earliest = [DB::table('reporting_orders')->min('bet_at'), DB::table('reporting_orders')->min('settled_at'),
+            DB::table('reporting_events as e')->leftJoin('reporting_reconciliations as r', 'r.event_id', '=', 'e.id')
+                ->min(DB::raw($effectiveAt)),
+            DB::table('reporting_activity')->min('observed_at'),
+            DB::table('users')->where(function ($q) { $q->whereNull('from')->orWhere('from', '<>', 2); })->min('created_at')];
+        foreach ($earliest as $at) if ($at !== null) $dates[] = substr((string) $at, 0, 10);
+        return min($dates);
+    }
+
     public function summary(array $input): array
     {
         $p = $this->params($input); $result = $this->compute($p, 'summary');
@@ -63,7 +80,7 @@ class ReportReader
         $keep = array_flip(array_merge(['group_id', 'label', 'net_cash_flow', 'company_profit', 'known_net_cash_flow', 'known_company_profit', 'complete', 'quality'], self::AMOUNTS));
         $all = array_intersect_key($all, $keep);
         $days = array_map(fn ($row) => ['date' => $row['group_id']] + array_intersect_key($row, $keep), $days);
-        return $result['meta'] + ['summary' => $all, 'total' => count($days), 'data' => array_slice($days, ((int) $p['page'] - 1) * (int) $p['limit'], (int) $p['limit'])];
+        return $result['meta'] + ['summary' => $all, 'total' => (int) Carbon::parse($p['start'])->diffInDays(Carbon::parse($p['end'])), 'data' => $days];
     }
 
     public function orders(array $input): array
@@ -78,7 +95,7 @@ class ReportReader
             ++$total;
             if ($total > ((int) $p['page'] - 1) * (int) $p['limit'] && count($rows) < (int) $p['limit']) $rows[] = $row + ['member_account' => $this->account($users[$row['user_id']])];
         }
-        return ['total' => $total, 'data' => $rows, 'timezone' => 'Asia/Shanghai'];
+        return ['total' => $total, 'data' => $rows, 'timezone' => 'Asia/Shanghai', 'filters' => array_diff_key($p, ['start' => 1, 'end' => 1])];
     }
 
     public function events(array $input): array
@@ -93,18 +110,24 @@ class ReportReader
             ++$total;
             if ($total > ((int) $p['page'] - 1) * (int) $p['limit'] && count($rows) < (int) $p['limit']) $rows[] = $row;
         }
-        return ['total' => $total, 'data' => $rows, 'timezone' => 'Asia/Shanghai'];
+        return ['total' => $total, 'data' => $rows, 'timezone' => 'Asia/Shanghai', 'filters' => array_diff_key($p, ['start' => 1, 'end' => 1])];
     }
 
     private function compute(array $p, string $mode): array
     {
         [$users, $agents] = $this->people($p); $settings = (new ReportAdmin())->settings();
-        $qualification = (new ShareMemberQualification())->read($p['end_date']); $policy = $qualification['policy_meta'];
+        $qualification = (new ShareMemberQualification())->read($p['end_date'], $mode !== 'daily'); $policy = $qualification['policy_meta'];
         $groups = []; $unallocated = null; $now = now('Asia/Shanghai')->toDateTimeString();
         if ($mode === 'summary') foreach (['all' => '所有订单'] + MaintenancePolicy::CATEGORIES as $key => $label) $groups[$key] = $this->empty($key, $label);
         if ($mode === 'daily') {
             $groups['all'] = $this->empty('all', '总计');
-            for ($day = Carbon::parse($p['start']); $day->format('Y-m-d H:i:s') < $p['end']; $day->addDay()) $groups[$day->toDateString()] = $this->empty($day->toDateString(), $day->toDateString());
+            $offset = ((int) $p['page'] - 1) * (int) $p['limit'];
+            if ($offset < Carbon::parse($p['start'])->diffInDays(Carbon::parse($p['end']))) {
+                $day = Carbon::parse($p['end'])->subDay()->subDays($offset);
+                for ($i = 0; $i < (int) $p['limit'] && $day->toDateTimeString() >= $p['start']; ++$i, $day->subDay()) {
+                    $groups[$day->toDateString()] = $this->empty($day->toDateString(), $day->toDateString());
+                }
+            }
         }
         $globalMissingTime = 0;
         foreach ($this->orderQuery($p)->orderBy('id')->cursor() as $record) {
@@ -114,8 +137,8 @@ class ReportReader
             if (isset($p['group_id']) && $this->group($row, $p) !== $p['group_id']) continue;
             $keys = $mode === 'summary' ? ['all', $row['category']] : ($mode === 'details' ? [$this->group($row, $p)] : ['all']);
             if ($mode === 'daily') {
-                if ($this->within($row['bet_at'], $p)) $keys[] = substr($row['bet_at'], 0, 10);
-                if ($this->within($row['settled_at'], $p)) $keys[] = substr($row['settled_at'], 0, 10);
+                if ($this->within($row['bet_at'], $p) && isset($groups[substr($row['bet_at'], 0, 10)])) $keys[] = substr($row['bet_at'], 0, 10);
+                if ($this->within($row['settled_at'], $p) && isset($groups[substr($row['settled_at'], 0, 10)])) $keys[] = substr($row['settled_at'], 0, 10);
             }
             foreach (array_unique($keys) as $key) {
                 if ($key === null) continue;
@@ -133,7 +156,7 @@ class ReportReader
             if ($row['reconciliation_id'] !== null) { $row['amount'] = $row['verified_amount']; $row['occurred_at'] = $row['verified_at']; }
             $keys = $mode === 'summary' ? ['all'] : ($mode === 'details' ? [$this->group($row, $p)] : ['all']);
             if ($mode === 'summary' && $row['category'] !== null) $keys[] = $row['category'];
-            if ($mode === 'daily' && $row['occurred_at'] !== null) $keys[] = substr($row['occurred_at'], 0, 10);
+            if ($mode === 'daily' && $row['occurred_at'] !== null && isset($groups[substr($row['occurred_at'], 0, 10)])) $keys[] = substr($row['occurred_at'], 0, 10);
             if ($mode === 'daily' && $row['occurred_at'] === null) ++$globalMissingTime;
             foreach (array_unique($keys) as $key) {
                 if ($key === null) $key = 'unallocated';
@@ -142,7 +165,7 @@ class ReportReader
                 if ($p['report_type'] === 'promotion' && $row['user_id'] !== null && $row['attribution_quality'] === 'historical_current_agent') ++$groups[$key]['quality']['unverified_attribution'];
             }
         }
-        $activity = $this->activity($p, $users, $agents);
+        $activity = $mode === 'daily' ? ['members' => [], 'groups' => []] : $this->activity($p, $users, $agents);
         foreach ($activity['groups'] as $key => $members) {
             if ($mode === 'details' && !isset($groups[$key])) $groups[$key] = $this->empty($key, $this->label($key, $p, $users, $agents));
         }
@@ -150,7 +173,7 @@ class ReportReader
         $run = $stateRows->firstWhere('source', '__run__'); $ready = $run && (json_decode($run->state, true)['complete'] ?? false);
         $syncAt = $run->updated_at ?? null;
         $scopePopulation = []; $groupPopulation = [];
-        foreach ($users as $uid => $u) {
+        if ($mode !== 'daily') foreach ($users as $uid => $u) {
             $person = ['user_id' => $u['id'], 'agent_id' => $u['agent_id'] ?? null, 'inviter_id' => $u['inviter_id'] ?? null, 'kind' => 'activity', 'client_platform' => $p['platform']];
             if (!$this->scope($person, $p, $users, $agents)) continue;
             if (isset($p['group_id']) && $this->group($person, $p) !== $p['group_id']) continue;

+ 1 - 1
docs/前端交付/README.md

@@ -16,7 +16,7 @@
 - [资金流动](bot-28/后台/资金流动-前端接入.md)
 - [龙虎榜](bot-28/后台/龙虎榜-前端接入.md)
 
-三份文档已统一页面范围、有效会员规则和展示字段。
+三个页面默认全部时间,不传起止日期即可;选择日期后按区间查询,最多366天;页面删除筛选下方统计说明与元数据、黄色完整性提示、列表数据状态列,保留筛选、业务指标、明细和分页。金额或人数为 `null` 时仅显示“—”。
 
 ## 阅读入口
 

+ 37 - 157
docs/前端交付/bot-28/后台/报表汇总-前端接入.md

@@ -6,7 +6,9 @@
 
 页面保留三个页签、筛选、查询、重置、“所有订单”总卡、九类游戏卡片、分组明细、游戏订单和资金记录追溯。
 
-**删除“有效会员最低存款设置”“收付款核对”“数据质量”三个按钮、菜单、弹窗、表单及其专门请求,不创建替代入口。** 报表中的有效会员条件仅作只读说明,修改条件仍在既有“分享赚钱”配置页面完成。
+筛选区下方不显示统计说明或元数据整段、黄色数据完整性提示;列表不显示“数据状态/数据不完整”列。币种、数据更新时间、查询时间、资格政策、活动采集起点和佣金余额快照不另作说明行、角标或弹窗。`quality`、`complete`、`known_*` 等字段仍由接口返回,本页不渲染,也不用已知小计代替最终结果;最终值为 `null` 时只显示“—”。
+
+**删除“有效会员最低存款设置”“收付款核对”“数据质量”三个按钮、菜单、弹窗、表单及其专门请求,不创建替代入口。** 有效会员人数使用接口结果;条件修改仍在既有“分享赚钱”配置页面完成。
 
 前端自建报表汇总菜单,权限 URI 为 **`admin/reporting/summary`**,没有开头斜线。`options`、`details`、`orders`、`events` 复用此权限;菜单显示和角色授权沿用现有后台权限配置。
 
@@ -51,8 +53,8 @@ Accept: application/json
 
 | 参数 | 类型、默认值 | 允许值与含义 | 使用位置 |
 |---|---|---|---|
-| `start_date` | 日期字符串,默认北京时间当天 | 起始日,含当天00:00:00 | 所有统计/追溯 |
-| `end_date` | 日期字符串,默认北京时间当天 | 结束日,含结束日;不得晚于北京时间当天 | 所有统计/追溯 |
+| `start_date` | 可选日期字符串 | 与结束日期一起传;不传日期时查询全部 | 所有统计/追溯 |
+| `end_date` | 可选日期字符串 | 结束日,含结束日;不晚于北京时间当天 | 所有统计/追溯 |
 | `report_type` | 字符串,默认 `business` | `business` / `promotion` / `sharing` | 所有统计/追溯 |
 | `platform` | 字符串,默认 `all` | `all` / `H5` / `PC` / `APP` / `unknown` | 所有统计/追溯 |
 | `category` | 字符串,默认 `all` | `all` 或下表九类代码;游戏订单按分类筛选 | 明细、追溯;主页面为 `all` |
@@ -65,7 +67,7 @@ Accept: application/json
 | `limit` | 整数,默认20 | 1~100 | `details` / `orders` / `events` |
 | `kind` | 字符串,不传则全部 | 见第9节资金类型枚举 | 仅 `events` 使用 |
 
-日期必须满足 `start_date ≤ end_date`,含首尾最多366天。服务端按“起始日00:00:00 ≤ 时间 < 结束日次日00:00:00”取数。页面校验后仍以接口校验结果为准。分页参数不分页汇总卡;返回 `filters` 中出现的 `metric` 是公共默认参数,不需要在本页增加排名筛选,也不改变本页明细排序。
+默认查询全部时间:不传 `start_date`、`end_date`,日期为空时也按全部处理,覆盖最早历史日期至北京时间当天;无历史时覆盖当天。选择日期区间时同时传 `start_date`、`end_date`,按北京时间包含首尾日查询,最多366天,开始日不得晚于结束日,结束日不得晚于当天。 `filters.start_date`、`filters.end_date` 回显实际覆盖日期;全部时间的分页或追溯请求仍不传日期,不将回显日期自动填回筛选。分页参数不分页汇总卡。
 
 `platform` 是业务发生时的客户端装置,不能用第三方供应商名称、游戏分类或会员注册端替代。没有可靠装置证据的数据为 `unknown`,包括部分第三方回单。
 
@@ -89,7 +91,7 @@ Accept: application/json
 GET /admin/reporting/options
 ```
 
-`data.report_types` 和 `data.categories` 是“代码 → 中文标签”的 **对象**;`platforms`、`settlement_statuses`、`ranking_metrics` 是 **字符串数组**,不是 `{value,label}` 数组。前端需要自行转成组件需要的选项结构。`ranking_metrics` 供龙虎榜使用,本页不生成排名控件。
+`data.report_types` 和 `data.categories` 是“代码 → 中文标签”的 **对象**;`platforms`、`settlement_statuses` 是 **字符串数组**,不是 `{value,label}` 数组。前端需要自行转成组件需要的选项结构。
 
 | 字段 | 类型 | 说明 |
 |---|---|---|
@@ -97,12 +99,10 @@ GET /admin/reporting/options
 | `categories` | object<string,string> | `all` 加九类游戏代码和名称 |
 | `platforms` | string[] | 装置代码;`all` 展示“全部”,`unknown` 展示“未知” |
 | `settlement_statuses` | string[] | `all` 全部、`pending` 未结算、`settled` 已结算 |
-| `ranking_metrics` | string[] | 龙虎榜可选指标,本页不用 |
-| `timezone` / `currency` | string | 统计时区/本位币 |
-| `max_days` | number | 最大查询天数,366 |
-| `rules` | object<string,string> | 只读规则说明,键为 `profit`、`net_cash_flow`、`valid_member`、`online_rate`、`public_costs` |
+| `currency` | string | 金额格式使用的本位币 |
+| `max_days` | number | 自选日期最多366天;不限制全部时间 |
 
-以下为可解析的完整选项结构,币种以实际响应为准:
+以下 JSON 仅展示本页使用字段,是有效响应子集;实际响应可以包含其他字段:
 
 ```json
 {
@@ -139,38 +139,30 @@ GET /admin/reporting/options
       "pending",
       "settled"
     ],
-    "ranking_metrics": [
-      "deposit_amount",
-      "bet_amount",
-      "payout_amount",
-      "withdraw_amount",
-      "activity_minutes"
-    ],
-    "timezone": "Asia/Shanghai",
     "currency": "CNY",
-    "max_days": 366,
-    "rules": {
-      "profit": "已结算游戏毛利-实发优惠-代理佣金-分享佣金-实际平台手续费",
-      "net_cash_flow": "成功收款-实际对外打款-实际平台手续费",
-      "valid_member": "范围内期间非撤销投注会员,按查询结束日已生效的分享赚钱规则,累计成功存款及累计已结算非撤销同币种有效投注均大于或等于对应门槛;累计包含查询开始日前记录,不受装置及游戏分类筛选影响",
-      "online_rate": "期间活跃会员/范围内期末真实会员",
-      "public_costs": "公共资金和成本只在所有订单展示,未知值为null"
-    }
+    "max_days": 366
   }
 }
 ```
 
-`options` 不返回本次查询结束日的资格政策,政策说明须读 `summary` 或 `details` 的 `valid_member_policy`,不能从选项文本提取门槛。
 
 ## 5. 汇总接口及响应
 
+全部时间请求示例:
+
+```http
+GET /admin/reporting/summary?report_type=business&platform=all&category=all&settlement_status=all
+```
+
+自选日期请求示例:
+
 ```http
 GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date=2026-10-26&platform=all&category=all&settlement_status=all
 ```
 
 `data.summary` 是“所有订单”总卡,`data.categories` 是九类卡片数组,以 `group_id` 匹配分类代码,`label` 为中文名称。卡片固定保留,包括无业务的分类;无需依据金额是否为0决定卡片是否存在。
 
-以下为可解析响应示例,`categories` **仅保留 F1彩票 一行,其余八行省略**,实际响应仍返回九类。金额和人数仅为示例。
+以下 JSON 仅展示本页使用字段,是有效响应子集;`categories` 仅展示 F1彩票 一行,实际响应仍返回九类。金额和人数仅为示例。
 
 ```json
 {
@@ -184,35 +176,9 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
       "category": "all",
       "settlement_status": "all",
       "start_date": "2026-10-26",
-      "end_date": "2026-10-26",
-      "page": 1,
-      "limit": 20,
-      "metric": "deposit_amount"
+      "end_date": "2026-10-26"
     },
-    "timezone": "Asia/Shanghai",
     "currency": "CNY",
-    "data_updated_at": "2026-10-26 08:00:00",
-    "activity_started_at": "2026-10-25 00:00:00",
-    "valid_deposit_min": "20.0000000000",
-    "valid_effective_bet_min": "100.0000000000",
-    "valid_member_policy": {
-      "id": 1,
-      "effective_date": "2026-10-01",
-      "min_deposit": "20.0000000000",
-      "min_effective_bet": "100.0000000000",
-      "date": "2026-10-26",
-      "available": true,
-      "unavailable_reasons": []
-    },
-    "commission_balances": {
-      "date_basis": "current_account_balance",
-      "as_of": "2026-10-26 16:00:00",
-      "agent_unpaid": "0.0000000000",
-      "share_unclaimed": "0.0000000000",
-      "share_reserved": "0.0000000000",
-      "share_debt": "0.0000000000"
-    },
-    "server_time": "2026-10-26 16:00:00",
     "summary": {
       "group_id": "all",
       "label": "所有订单",
@@ -236,26 +202,12 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
       "payout_amount": "60.0000000000",
       "refund_amount": "0.0000000000",
       "game_gross_profit": "40.0000000000",
-      "quality": {
-        "missing_time": 0,
-        "missing_amount": 0,
-        "unverified_fees": 0,
-        "foreign_currency": 0,
-        "unverified_attribution": 0,
-        "sync_errors": 0,
-        "not_synchronized": false
-      },
       "member_count": 4,
       "valid_member_count": 1,
       "active_member_count": 0,
-      "activity_population_complete": true,
-      "known_activity_minutes": 0,
       "activity_minutes": 0,
       "active_rate": "0.000000",
       "order_count": 1,
-      "complete": true,
-      "known_net_cash_flow": "998.0000000000",
-      "known_company_profit": "38.0000000000",
       "net_cash_flow": "998.0000000000",
       "company_profit": "38.0000000000"
     },
@@ -283,26 +235,12 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
         "payout_amount": "60.0000000000",
         "refund_amount": "0.0000000000",
         "game_gross_profit": "40.0000000000",
-        "quality": {
-          "missing_time": 0,
-          "missing_amount": 0,
-          "unverified_fees": 0,
-          "foreign_currency": 0,
-          "unverified_attribution": 0,
-          "sync_errors": 0,
-          "not_synchronized": false
-        },
         "member_count": 4,
         "valid_member_count": 1,
         "active_member_count": 0,
-        "activity_population_complete": true,
-        "known_activity_minutes": 0,
         "activity_minutes": 0,
         "active_rate": "0.000000",
         "order_count": 1,
-        "complete": true,
-        "known_net_cash_flow": null,
-        "known_company_profit": null,
         "net_cash_flow": null,
         "company_profit": null
       }
@@ -311,21 +249,7 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 }
 ```
 
-汇总和明细共享下列元数据;订单、资金追溯仅返回 `total`、`data`、`timezone`,不要要求其重复返回这些字段。
-
-| 字段 | 类型 | 页面使用 |
-|---|---|---|
-| `filters` | object | 服务端校验后的参数及默认值,数字查询参数回显可能仍为字符串 |
-| `timezone` / `currency` | string | 时区/本位币 |
-| `data_updated_at` | string \| null | 最近统计同步时间,`null` 不伪造为查询时间 |
-| `server_time` | string | 当前服务器时间 |
-| `activity_started_at` | string | 活动观测开始时间,用于解释历史时长覆盖情况 |
-| `valid_member_policy` | object | 本次 `end_date` 对应的只读资格政策,见第7节 |
-| `valid_deposit_min` | string \| null | 与政策 `min_deposit` 相同的兼容字段 |
-| `valid_effective_bet_min` | string \| null | 与政策 `min_effective_bet` 相同的兼容字段 |
-| `commission_balances` | object | 查询时账户余额快照,见下文 |
-
-`commission_balances` 包含 `date_basis="current_account_balance"`、`as_of` 和金额字段 `agent_unpaid`、`share_unclaimed`、`share_reserved`、`share_debt`。依次为代理待发、分享未领(含预留部分)、分享预留、分享欠账。金额可以为 `null`,例如当前按装置或分类筛选而无法分配时;这是查询时快照,不是查询期间新增成本,不能再次计入净收益公式。
+汇总和明细通过 `filters` 回传生效筛选,`currency` 用于金额格式。订单与资金追溯同样返回 `filters`,翻页和追溯保留原始日期选择及其余筛选;全部时间时仍不传日期,`filters` 中的实际覆盖日期仅作返回信息。
 
 ## 6. 卡片与明细指标
 
@@ -339,7 +263,7 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 | `deposit_amount` | 期间成功收款本金,按钱包本位币计 |
 | `withdraw_amount` | 期间实际对外提款净额,含会员及代理成功出款,非预扣申请额;等于 `member_withdraw_amount + agent_withdraw_amount` |
 | `member_withdraw_amount` / `agent_withdraw_amount` | 会员/代理实际对外提款净额 |
-| `deposit_fee` / `withdraw_fee` | 已确认的实际外部收款/出款手续费;未知手续费会影响完整性,已知累计金额不能当成最终费用 |
+| `deposit_fee` / `withdraw_fee` | 已确认的实际外部收款/出款手续费 |
 | `customer_fee_income` | 向会员/代理内扣的手续费收入,单列;不作为实际外部成本重复扣除 |
 | `configured_fee` | 原订单按配置计算的参考费用,不是已确认的实际外部费用 |
 | `bet_amount` | 期间下单的投注本金,退款另列;不是有效投注额 |
@@ -352,17 +276,12 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 | `manual_credit` / `manual_debit` | 人工上分/扣分,独立展示,不直接当作外部现金存提款 |
 | `company_profit` | string \| null,公司净收益,公式见下文;`settlement_status=pending` 时为 `null` |
 | `net_cash_flow` | string \| null,实际净入金,公式见下文 |
-| `known_company_profit` / `known_net_cash_flow` | string \| null,按已知金额计算的小计;不完整时必须标“已知小计,未完成核对”,不得展示为最终结果 |
 | `member_count` | number,当前人口范围内、查询结束日前的真实会员参考人数,不按注册装置拆分 |
 | `valid_member_count` | number \| null,本次投注候选人中满足累计两项门槛的去重人数;规则不可用时为 `null` |
 | `active_member_count` | number,本次已观测到的区间活跃会员去重人数;历史覆盖不足时仅代表已知观测 |
 | `activity_minutes` | number \| null,区间观测估算时长,整数分钟;历史覆盖不足时为 `null` |
-| `known_activity_minutes` | number,已知观测时长,所有秒数合计后向下取整为分钟 |
 | `active_rate` | string \| null,区间活跃人数 / 范围内期末真实会员人数,6位小数;例如 `"0.250000"` 显示25% |
-| `activity_population_complete` | boolean,历史活动会员是否全部仍属于当前人口范围;改绑导致范围不一致时为 `false` |
 | `order_count` | number,期间下单的原始业务订单数,可能与逐条追溯列表行数不同 |
-| `complete` | boolean,统计完整性;为 `false` 时最终公司净收益和实际净入金为 `null` |
-| `quality` | object,只读完整性提示,见第6.3节 |
 
 返回金额的含义:
 
@@ -371,11 +290,11 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 实际净入金 = 成功收款本金 - 实际对外提款净额 - 实际收款手续费 - 实际出款手续费
 ```
 
-优惠、内部佣金、应计金额不直接作为外部现金再次从净入金扣除。客户内扣手续费收入、配置参考费用、佣金账户快照不额外塞入上述公式。`pending` 筛选下 `known_company_profit` 也不能作为最终公司净收益显示。
+优惠、内部佣金、应计金额不直接作为外部现金再次从净入金扣除。客户内扣手续费收入和配置参考费用不额外加入上述公式。
 
 ### 6.2 总卡与分类卡的差异
 
-分类卡保留可归属的游戏指标及分类佣金/应计。以下公共资金字段返回 `null`:`deposit_amount`、`withdraw_amount`、`member_withdraw_amount`、`agent_withdraw_amount`、`deposit_fee`、`withdraw_fee`、`customer_fee_income`、`configured_fee`、`bonus_amount`、`manual_credit`、`manual_debit`。分类卡的 `company_profit`、`net_cash_flow` 及两个 `known_*` 小计也均为 `null`。分类毛利用 `game_gross_profit`。
+分类卡保留可归属的游戏指标及分类佣金/应计。以下公共资金字段返回 `null`:`deposit_amount`、`withdraw_amount`、`member_withdraw_amount`、`agent_withdraw_amount`、`deposit_fee`、`withdraw_fee`、`customer_fee_income`、`configured_fee`、`bonus_amount`、`manual_credit`、`manual_debit`。分类卡的 `company_profit`、`net_cash_flow` 也均为 `null`。分类毛利用 `game_gross_profit`。
 
 明细请求 `category != all` 时,同样将上述公共资金和净收益字段置为 `null`。分类佣金是已带分类归属的资金记录,不得按投注比例摊分公共成本,也不得把总卡金额复制给九类卡片。
 
@@ -383,41 +302,9 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 
 各分类有效会员可重复,不能相加得到总卡人数。`member_count`、活动人数、活动分钟和活跃率采用人员/活动范围,当前不按游戏分类拆分;不要把分类卡上这些值解释为某游戏专属在线人数或时长。
 
-### 6.3 只读完整性提示
-
-| `quality` 子字段 | 类型 | 提示含义 |
-|---|---|---|
-| `missing_time` | number | 存在无法确认业务时间的记录 |
-| `missing_amount` | number | 存在未知金额 |
-| `unverified_fees` | number | 存在未确认实际外部手续费的收付款 |
-| `foreign_currency` | number | 存在非本位币记录,未计入本位币合计 |
-| `unverified_attribution` | number | 存在未核实的历史代理归属 |
-| `sync_errors` | number | 统计同步存在错误 |
-| `not_synchronized` | boolean | 尚未完成首次同步 |
-
-任一异常可导致 `complete=false`。`sync_errors` 等提示不要直接解释为当前卡片可逐笔定位的条数。页面只需显示“部分数据未完成核对”及只读提示;不生成“数据质量”入口、诊断列表、核对按钮或修改表单。金额 `"0.0000000000"` 显示0.00,`null` 显示“—”;已知小计必须带说明。
-
-## 7. 有效会员:只读展示分享赚钱的生效条件
-
-有效会员人数直接使用 `valid_member_count`。它表示当前筛选范围内,期间有正额且未撤销投注、累计成功充值及累计有效投注均达到对应门槛的去重会员数。累计金额包含查询开始日前的记录。
-
-页面使用 `valid_member_policy` 显示本次查询结束日的生效条件。两项门槛均为“大于或等于”,0按实际值显示;修改条件仍使用现有分享赚钱设置页面。
-
-`valid_member_policy` 总是一个对象,字段如下:
-
-| 字段 | 类型 | 含义 |
-|---|---|---|
-| `id` | number \| null | 本次政策版本 ID |
-| `effective_date` | string \| null | 所用政策的生效日,不是本次资格截止日 |
-| `min_deposit` | string \| null | 累计成功充值最低门槛 |
-| `min_effective_bet` | string \| null | 累计有效投注最低门槛 |
-| `date` | string | 资格截止日,与本次 `end_date` 一致 |
-| `available` | boolean | 政策及必要分享数据是否足以计算资格 |
-| `unavailable_reasons` | string[] | 不可计算原因;只读排查信息,不据此生成页面操作 |
-
-`available=false` 时所有相关 `valid_member_count` 为 `null`,显示“—”,并显示“有效会员条件暂不可计算”。不能显示0人,也不能沿用旧的独立20门槛。两项门槛仍可能有值,也可能为 `null`,都以本次响应为准。
+## 7. 有效会员人数
 
-`valid_deposit_min` 与 `valid_effective_bet_min` 分别是两项金额的兼容字段,和 `valid_member_policy.min_deposit` / `min_effective_bet` 一致。建议只读说明为“截至 {date},累计成功充值 ≥ {min_deposit},且累计有效投注 ≥ {min_effective_bet};采用 {effective_date} 生效规则”。不添加输入框、保存按钮或报表专属设置弹窗。
+`valid_member_count` 为当前筛选范围内,期间有正额且未撤销投注、累计成功充值和累计有效投注均满足分享赚钱条件的去重会员数。累计金额包含查询开始日前记录。页面直接显示人数;为 `null` 时显示“—”。
 
 ## 8. 区间活跃与活动分钟
 
@@ -426,10 +313,8 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 | 字段/返回情况 | 页面展示 |
 |---|---|
 | `activity_minutes` | 使用整数分钟;0显示“0分钟”,`null` 显示“—” |
-| `known_activity_minutes` | 已知部分,可显示“已采集时长:X分钟” |
 | `active_member_count` | 区间活跃会员人数 |
 | `active_rate` | 按百分比展示,如 `"0.250000"` 显示25%;`null` 显示“—” |
-| `activity_population_complete=false` | 活跃率显示“—”,时长和人数仍按各自字段展示 |
 
 总卡与分类卡分别读取对应返回值,活动指标不按分类相加。
 
@@ -437,13 +322,13 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 
 ### 9.1 点击卡片获取分组明细
 
-点击“所有订单”传 `category=all`;点击游戏卡片传该卡片 `group_id` 作为 `category`。保留当前日期、页签、装置、结算状态和账号筛选,页码回到1。**卡片 `group_id` 是分类代码,不能把它直接作为追溯 `group_id`。**
+点击“所有订单”传 `category=all`;点击游戏卡片传该卡片 `group_id` 作为 `category`。保留当前时间范围、页签、装置、结算状态和账号筛选,页码回到1。**卡片 `group_id` 是分类代码,不能把它直接作为追溯 `group_id`。**
 
 ```http
 GET /admin/reporting/details?report_type=promotion&start_date=2026-10-26&end_date=2026-10-26&platform=all&category=self:lottery&settlement_status=all&page=1&limit=20
 ```
 
-业务数据为 `{ "total": 0, "data": [], "unallocated": null }` 加第5节共享元数据。`data` 每行拥有第6节同组统计指标,并附以下身份字段:
+业务数据包括 `filters`、`currency`、`total`、`data`、`unallocated`;`total=0` 时列表 `data=[]`。`data` 每行拥有第6节同组统计指标,并附以下身份字段:
 
 | 字段 | 类型 | 说明 |
 |---|---|---|
@@ -466,7 +351,7 @@ GET /admin/reporting/details?report_type=promotion&start_date=2026-10-26&end_dat
 GET /admin/reporting/orders?report_type=promotion&start_date=2026-10-26&end_date=2026-10-26&platform=all&category=self:lottery&settlement_status=all&group_id=10&page=1&limit=20
 ```
 
-业务数据为 `{ "total": 0, "data": [], "timezone": "Asia/Shanghai" }`,外层仍有 `code/timestamp/msg/data`。按 `bet_at` 倒序、再按记录 `id` 倒序。一笔原始业务拆分时可有多行,列表行数可与 `order_count` 不同。
+业务数据包括 `filters`、`total`、`data`、`timezone`,外层仍有 `code/timestamp/msg/data`。按 `bet_at` 倒序、再按记录 `id` 倒序。一笔原始业务拆分时可有多行,列表行数可与 `order_count` 不同。
 
 | 行字段 | 类型/含义 |
 |---|---|
@@ -481,11 +366,8 @@ GET /admin/reporting/orders?report_type=promotion&start_date=2026-10-26&end_date
 | `settlement_candidate_at` | string \| null,候选时间,不能冒充确认结算时间 |
 | `bet_amount` / `payout_amount` / `refund_amount` / `net_profit` | 原始十进制金额;`net_profit` 为会员净输赢,平台毛利方向相反 |
 | `settled` / `voided` | 原始0/1标记,分别表示已结算/已撤销,不是前端状态名称 |
-| `time_quality` | string,当前可为 `confirmed` / `missing_settlement_time` |
-| `attribution_quality` | string,`event_snapshot` 事件快照、`historical_current_agent` 参考当前代理关系的历史记录、`verified` 已确认归属 |
-| `created_at` / `updated_at` | string,统计记录创建/更新时间,不作为业务成功时间 |
 
-日期命中下单时间或结算时间均可能返回该行;历史已结算/撤销但结算时间缺失的记录也可能为提示缺失而返回。显示未知时间为“—”;不要按记录创建/更新时间补齐。`historical_current_agent` 在推广报表中影响完整性,不能标成已确认的历史归属。
+日期命中下单时间或结算时间均可能返回该行。未知时间显示“—”。
 
 ### 9.3 资金记录追溯
 
@@ -508,14 +390,14 @@ GET /admin/reporting/events?report_type=business&start_date=2026-10-26&end_date=
 | `manual_credit` | 人工上分 |
 | `manual_debit` | 人工扣分 |
 
-业务数据结构同订单追溯,只含 `total`、`data`、`timezone`;按资金记录 `id` 倒序。行字段:
+业务数据结构同订单追溯,包含 `filters`、`total`、`data`、`timezone`;按资金记录 `id` 倒序。行字段:
 
 | 行字段 | 类型/含义 |
 |---|---|
 | `id` | 统计资金记录整数标识 |
 | `source` / `source_id` / `kind` | string,原始来源/标识/资金类型 |
 | `user_id` / `agent_id` / `inviter_id` | 整数标识,可为 `null`;代理独立账户记录可能没有会员 |
-| `attribution_quality` / `client_platform` / `currency` | string,含义同订单字段 |
+| `client_platform` / `currency` | string,含义同订单字段 |
 | `category` | string \| null;公共资金可无游戏分类 |
 | `amount` | 原始金额,可为 `null` |
 | `customer_fee` | 客户内扣手续费,原始十进制金额 |
@@ -525,15 +407,14 @@ GET /admin/reporting/events?report_type=business&start_date=2026-10-26&end_date=
 | `reconciliation_id` | 整数标识 \| null,是否存在已确认记录的只读标识 |
 | `verified_amount` / `external_fee` | 已确认金额/实际外部手续费,均可为 `null` |
 | `verified_at` | string \| null,已确认业务时间 |
-| `created_at` / `updated_at` | string,统计记录创建/更新时间 |
 
-追溯返回的 `amount`、`occurred_at` 保留原始值,**不会被已确认值覆盖**。`reconciliation_id != null` 时,汇总使用 `verified_amount`、`verified_at`,页面展示有效金额/时间也应以这两项为准,并可同时展示原值作对照。`external_fee` 为实际费用;`configured_fee` 不可替代它。未确认业务时间的行显示“时间待确认”,不能用 `candidate_at`、`created_at` 或申请时间代替,也不提供编辑/核对入口。
+追溯返回的 `amount`、`occurred_at` 保留原始值,**不会被已确认值覆盖**。`reconciliation_id != null` 时,汇总使用 `verified_amount`、`verified_at`,页面展示有效金额/时间也应以这两项为准,并可同时展示原值作对照。`external_fee` 为实际费用;`configured_fee` 不可替代它。未确认业务时间显示“—”。
 
 ## 10. 页面调用流程、错误与空状态
 
-1. 进入页面读取 `options`,建立三个页签、装置及结算状态下拉。默认营业、当天、全部装置、全部结算状态,账号为空,主页面 `category=all`。
-2. 查询后请求 `summary`;三个页签都使用同一接口,只切换 `report_type`。总卡、分类卡、政策说明、更新时间及完整性提示必须来自同一份响应。
-3. 重置恢复默认筛选并重新查询。切换页签/筛选后清空旧明细选中组;列表页码回1。避免较早请求覆盖较新的查询结果。
+1. 进入页面读取 `options`,建立三个页签、装置及结算状态下拉。默认营业、全部时间、全部装置、全部结算状态,账号为空,日期为空,主页面 `category=all`;选择日期后查询相应区间。
+2. 查询后请求 `summary`;三个页签都使用同一接口,只切换 `report_type`。总卡和分类卡使用同一份响应。
+3. 重置清空日期,恢复全部时间及其余默认筛选并重新查询。切换页签/筛选后清空旧明细选中组;列表页码回1。避免较早请求覆盖较新的查询结果。
 4. 点击卡片请求 `details`;分页只刷新该列表,保留筛选。点击明细行的订单/资金追溯操作,用该行 `group_id` 和当前完整筛选请求对应接口,各自独立分页。
 5. 返回或关闭明细后保留主页面筛选。总卡不由明细当前页重算。
 
@@ -546,7 +427,6 @@ GET /admin/reporting/events?report_type=business&start_date=2026-10-26&end_date=
 | 网络异常、其他错误码 | 展示请求失败及重试状态,不把旧数据改成全0 |
 | `total=0`、`data=[]` | 列表显示“暂无数据”,分页总数为0 |
 | 无订单业务 | 保留“所有订单”及九类卡片;金额0与公共字段 `null` 仍按各自含义显示。人口/活动统计可能仍有值 |
-| `available=false` 或 `complete=false` | 属于成功响应中的只读提示状态,按第6、7节展示,不能整体当接口失败或空列表 |
 
 失败响应示例:
 

+ 14 - 15
docs/前端交付/bot-28/后台/报表页面调整-移除三个入口.md

@@ -1,34 +1,33 @@
 # 报表页面调整:移除三个入口
 
-2026-10-26前端修正单,适用于 `bot-28` 后台报表页面。按本文件直接修改,无需对比旧文档。
+2026-10-26前端修正单,适用于 `bot-28` 后台报表汇总、资金流动和龙虎榜页面。本文件为当前调整要求,可直接修改页面。
 
-## 页面删除项
+## 功能入口
 
 删除以下三个入口及关联弹窗、表单、事件绑定、保存操作和专门请求:
 
 1. **有效会员最低存款设置**。
-2. **收付款核对**,包括截图中的核对列表、核对按钮及编辑弹窗。
+2. **收付款核对**,包括核对列表、核对按钮及编辑弹窗。
 3. **数据质量**,包括异常列表或来源状态弹窗。
 
-三个功能均不创建菜单、按钮或替代入口。保留营业报表/推广报表/分享赚钱页签、已有筛选项、查询/重置、统计卡片、明细追溯。
+三个功能均不创建菜单、按钮或替代入口。有效会员条件仍由现有“分享赚钱”设置管理;报表直接使用 `valid_member_count`,为 `null` 时显示“—”。菜单、前端路由和权限按钮由前端按接入文档配置。
 
-## 有效会员规则展示
+## 本轮页面展示删除项
 
-有效会员条件统一使用既有“分享赚钱”设置。报表不再维护独立门槛,不新增配置页面,不复制分享赚钱表单。
+三个页面统一删除:
 
-报表接口按照查询结束日的生效版本,使用累计成功存款和累计有效投注两项门槛;均为大于或等于,允许0。查询开始日前的累计数据仍参与资格,待生效配置不能提前使用。当前筛选只限定要统计的会员和分组,不将全局累计资格改成期间金额或单一分类金额。
+1. 筛选区下方整段统计说明与元数据,包括币种、数据更新时间、查询时间、资格门槛政策、活动采集起点、佣金余额快照。
+2. 黄色数据完整性提示区域。
+3. 列表右侧“数据状态/数据不完整”列。
 
-读取响应中的 `valid_member_policy`、`valid_deposit_min`、`valid_effective_bet_min` 回显实际规则。无法计算资格时,`valid_member_count=null` 展示“—”;不要继续显示旧的“期间存款超过20且有一笔投注”文案。
+不增加替代提示、角标、弹窗或独立说明行。接口仍返回 `quality`、`complete`、`known_*` 等字段,页面不渲染,不用已知小计代替最终值;最终值为 `null` 时只显示“—”。`currency` 可用于金额格式,不单独显示说明行。
 
-## 数据完整性提示
+页面保留筛选、查询/重置、卡片与业务指标、明细及分页。报表汇总保留营业/推广/分享赚钱页签、所有订单与游戏卡片、订单和资金追溯;原有效会员数、活动时长等业务列可保留。请求失败仍按 `code` 展示实际 `msg`。
 
-继续根据报表响应里的 `complete`、`quality` 显示简短只读提示。最终金额为null时用“—”;已知小计须标明未完成核实,不把未知金额变成0。提示不带“去核对”“查看数据质量”等操作入口,不为显示提示额外请求核对或诊断接口。
+## 默认全部时间
 
-## 验收
+三个页面默认显示全部时间,日期为空时不传 `start_date`、`end_date`。无需额外标记。全部时间覆盖最早历史日期至北京时间当天,不受366天限制;无历史时覆盖当天。
 
-- 报表页面及菜单中均看不到上述三个入口,旧弹窗无法通过页面操作打开。
-- 正常查询、页签切换、卡片和明细追溯仍可使用。
-- 修改有效会员条件只使用现有分享赚钱页面,报表展示已生效规则。
-- 无政策或数据不完整时保持真实提示和“—”,不生成额外处理页面。
+选择日期区间后同时传起止日期,含首尾最多366天。清空日期或重置恢复全部时间。分页、卡片明细及订单/资金追溯保留原始日期选择;查询全部时继续不传日期。`filters.start_date`、`filters.end_date` 仅回显实际覆盖日期。
 
 [报表汇总完整文档](报表汇总-前端接入.md) · [资金流动完整文档](资金流动-前端接入.md) · [龙虎榜完整文档](龙虎榜-前端接入.md)

+ 33 - 122
docs/前端交付/bot-28/后台/资金流动-前端接入.md

@@ -4,34 +4,44 @@
 
 ## 1. 页面与接口
 
-页面展示全站资金流动,包含日期范围、查询、重置、区间总计、每日列表和分页。筛选区只提供日期,不提供报表类型、装置、游戏、结算状态或账号筛选。
+页面展示全站资金流动,保留时间范围、查询、重置、区间总计、每日列表和分页。筛选区只提供时间,不提供报表类型、装置、游戏、结算状态或账号筛选。
+
+筛选区下方不显示统计说明或元数据整段、黄色数据完整性提示;列表不显示“数据状态/数据不完整”列。币种、数据更新时间、查询时间、资格政策、活动采集起点和佣金余额快照不另作说明行、角标或弹窗。`quality`、`complete`、`known_*` 等字段仍由接口返回,本页不渲染,也不用已知小计代替最终结果;最终值为 `null` 时只显示“—”。
 
 页面不包含“有效会员最低存款设置”“收付款核对”“数据质量”的菜单、按钮、弹窗和表单。
 
 | 方法 | 完整路径 | 用途 | 菜单/按钮权限 URI |
 |---|---|---|---|
-| GET | `/admin/reporting/fund-flow` | 日期区间总计及每日分页列表 | `admin/reporting/fund-flow` |
+| GET | `/admin/reporting/fund-flow` | 时间区间总计及每日分页列表 | `admin/reporting/fund-flow` |
 
 菜单、前端路由和按钮由前端配置,权限 URI 使用上表值。请求使用总后台 API 域名及后台管理员登录令牌。
 
 ## 2. 请求参数
 
+自选日期请求:
+
 ```http
 GET /admin/reporting/fund-flow?start_date=2026-10-26&end_date=2026-10-26&page=1&limit=20
 Authorization: Bearer <admin_token>
 Accept: application/json
 ```
 
-资金页按上述示例提交以下四个参数:
+全部时间请求:
+
+```http
+GET /admin/reporting/fund-flow?page=1&limit=20
+```
 
 | 参数 | 类型 | 默认值与约束 |
 |---|---|---|
-| `start_date` | string | `YYYY-MM-DD`;默认北京时间当天 |
-| `end_date` | string | `YYYY-MM-DD`;默认北京时间当天 |
+| `start_date` | string | 可选;与结束日期一起传,不传日期时查询全部 |
+| `end_date` | string | 可选;`YYYY-MM-DD`,包含结束日 |
 | `page` | integer | 默认 `1`,范围 `1`~`1000000` |
 | `limit` | integer | 默认 `20`,范围 `1`~`100` |
 
-日期采用 `Asia/Shanghai`(北京时间),包含开始日和结束日,含首尾最多 366 天。开始日不得晚于结束日,结束日不得晚于北京时间当天。两端日期一起提交;单日查询使用相同日期。
+默认查询全部时间:不传 `start_date`、`end_date`,日期为空时也按全部处理,覆盖最早历史日期至北京时间当天;无历史时覆盖当天。选择日期区间时同时传 `start_date`、`end_date`,按北京时间包含首尾日查询,最多366天,开始日不得晚于结束日,结束日不得晚于当天。
+
+`filters.start_date`、`filters.end_date` 回显实际覆盖日期。全部时间翻页时继续不传日期,不将回显日期填回筛选。
 
 ## 3. 返回结构与示例
 
@@ -41,19 +51,16 @@ Accept: application/json
 |---|---|---|
 | `code` | integer | `0` 成功,其他值失败 |
 | `timestamp` | integer | 响应 Unix 时间戳,单位秒 |
-| `msg` | string | 响应提示 |
-| `data.filters` | object | 本次查询条件及分页回显 |
-| `data.timezone` | string | 统计时区,当前为 `Asia/Shanghai` |
-| `data.currency` | string | 金额币种,如 `CNY` |
-| `data.data_updated_at` | string 或 null | 数据更新时间;为空显示“更新时间暂不可用” |
-| `data.server_time` | string | 本次响应的服务器北京时间 |
-| `data.summary` | object | 整个日期区间的总计,切换分页仍为全区间总计 |
+| `msg` | string | 失败请求的提示文案 |
+| `data.filters` | object | 本次生效时间范围及分页回显 |
+| `data.currency` | string | 金额格式使用的币种,如 `CNY` |
+| `data.summary` | object | 整个时间区间的总计,切换分页仍为全区间总计 |
 | `data.total` | integer | 区间天数,用于列表分页,包含无业务日 |
 | `data.data` | array | 当前页每日行,按日期倒序排列,包含无业务日 |
 
-每日行的 `date` 为日期列,格式 `YYYY-MM-DD`;`group_id`、`label` 也为对应日期。区间总计的 `group_id` 为 `all`、`label` 为“总计”。总计和日行的金额、完整性字段见下文。
+每日行的 `date` 为日期列,格式 `YYYY-MM-DD`;`group_id`、`label` 也为对应日期。区间总计的 `group_id` 为 `all`、`label` 为“总计”。
 
-完整成功示例:
+以下 JSON 仅展示本页使用字段,是有效响应子集;实际响应可以包含其他字段:
 
 ```json
 {
@@ -62,40 +69,12 @@ Accept: application/json
   "msg": "OK",
   "data": {
     "filters": {
-      "report_type": "business",
-      "platform": "all",
-      "category": "all",
-      "settlement_status": "all",
       "start_date": "2026-10-26",
       "end_date": "2026-10-26",
       "page": 1,
-      "limit": 20,
-      "metric": "deposit_amount"
+      "limit": 20
     },
-    "timezone": "Asia/Shanghai",
     "currency": "CNY",
-    "data_updated_at": "2026-10-26 08:00:00",
-    "activity_started_at": "2026-10-25 00:00:00",
-    "valid_deposit_min": "20.0000000000",
-    "valid_effective_bet_min": "100.0000000000",
-    "valid_member_policy": {
-      "id": 1,
-      "effective_date": "2026-10-01",
-      "min_deposit": "20.0000000000",
-      "min_effective_bet": "100.0000000000",
-      "date": "2026-10-26",
-      "available": true,
-      "unavailable_reasons": []
-    },
-    "commission_balances": {
-      "date_basis": "current_account_balance",
-      "as_of": "2026-10-26 16:00:00",
-      "agent_unpaid": "0.0000000000",
-      "share_unclaimed": "0.0000000000",
-      "share_reserved": "0.0000000000",
-      "share_debt": "0.0000000000"
-    },
-    "server_time": "2026-10-26 16:00:00",
     "summary": {
       "group_id": "all",
       "label": "总计",
@@ -119,18 +98,6 @@ Accept: application/json
       "payout_amount": "60.0000000000",
       "refund_amount": "0.0000000000",
       "game_gross_profit": "40.0000000000",
-      "quality": {
-        "missing_time": 0,
-        "missing_amount": 0,
-        "unverified_fees": 0,
-        "foreign_currency": 0,
-        "unverified_attribution": 0,
-        "sync_errors": 0,
-        "not_synchronized": false
-      },
-      "complete": true,
-      "known_net_cash_flow": "998.0000000000",
-      "known_company_profit": "38.0000000000",
       "net_cash_flow": "998.0000000000",
       "company_profit": "38.0000000000"
     },
@@ -160,18 +127,6 @@ Accept: application/json
         "payout_amount": "60.0000000000",
         "refund_amount": "0.0000000000",
         "game_gross_profit": "40.0000000000",
-        "quality": {
-          "missing_time": 0,
-          "missing_amount": 0,
-          "unverified_fees": 0,
-          "foreign_currency": 0,
-          "unverified_attribution": 0,
-          "sync_errors": 0,
-          "not_synchronized": false
-        },
-        "complete": true,
-        "known_net_cash_flow": "998.0000000000",
-        "known_company_profit": "38.0000000000",
         "net_cash_flow": "998.0000000000",
         "company_profit": "38.0000000000"
       }
@@ -184,7 +139,7 @@ Accept: application/json
 
 ## 4. 金额字段与展示
 
-总计和每日行使用相同金额字段。金额为十进制字符串,保留原始值,以十进制方式格式化为两位小数;负值保留负号。`null` 显示“—”,确认的零显示 `0.00`。汇总展示接口返回的 `summary`,不累计当前页行金额。
+总计和每日行使用相同金额字段。金额为十进制字符串,保留原始值,以十进制方式格式化为两位小数;负值保留负号。`null` 显示“—”,数值为零时显示 `0.00`。汇总展示接口返回的 `summary`,不累计当前页行金额。`currency` 仅用于金额格式,不另作说明行。
 
 | 字段 | 展示名称 | 含义 |
 |---|---|---|
@@ -225,63 +180,19 @@ Accept: application/json
 
 客户手续费收入、配置手续费参考值、应计佣金和人工余额调整均单独展示,不重复加入上述计算。投注总额按下单日统计,游戏毛利按实际结算日统计;各结果直接展示对应返回字段。
 
-## 5. 完整性与其他返回字段
-
-| 字段 | 类型 | 展示方式 |
-|---|---|---|
-| `complete` | boolean | 总计和每日行分别判断;`false` 显示“数据不完整” |
-| `net_cash_flow` / `company_profit` | string 或 null | 最终值;为 null 时显示“—” |
-| `known_net_cash_flow` | string | 已知净入金小计;数据不完整时可附“已知小计,数据不完整”展示 |
-| `known_company_profit` | string | 已知公司净收益小计;数据不完整时可附“已知小计,数据不完整”展示 |
-| `quality` | object | 当前总计或日行的只读缺失说明 |
-
-| `quality` 字段 | 类型 | 展示说明 |
-|---|---|---|
-| `missing_time` | integer | 缺少业务发生/结算时间的记录数 |
-| `missing_amount` | integer | 缺少金额的记录数 |
-| `unverified_fees` | integer | 实际平台手续费尚未确认的记录数 |
-| `foreign_currency` | integer | 无法直接计入的非本位币记录数 |
-| `unverified_attribution` | integer | 历史归属尚未确认的记录数 |
-| `sync_errors` | integer | 数据异常记录数 |
-| `not_synchronized` | boolean | `true` 显示“数据尚未准备完成” |
-
-`complete=false` 时,已返回的其他金额作为已知金额展示,并附缺失说明;零金额仍需保留对应的未知费用等状态。缺失说明在本页只读展示。总计的缺失状态使用 `summary.quality`,每日使用本行 `quality`,不累计每日计数。
-
-以下字段位于外层业务 `data` 中,资金页只兼容接收,不生成设置控件或每日统计列:
-
-| 字段 | 类型/含义 |
-|---|---|
-| `activity_started_at` | string,活动统计开始时间 |
-| `valid_deposit_min` | string 或 null,有效会员累计存款门槛 |
-| `valid_effective_bet_min` | string 或 null,有效会员累计有效投注门槛 |
-| `valid_member_policy` | object,资格规则:`id`、`effective_date`、`min_deposit`、`min_effective_bet`、`date`、`available`、`unavailable_reasons`;`date` 对应本次结束日 |
-| `commission_balances` | object,查询时佣金余额快照,结构见下表 |
-
-| `commission_balances` 字段 | 含义 |
-|---|---|
-| `date_basis` | 固定 `current_account_balance` |
-| `as_of` | 余额快照时间 |
-| `agent_unpaid` | 未付代理佣金,金额字符串或 null |
-| `share_unclaimed` | 未领取分享佣金,包含预留部分,金额字符串或 null |
-| `share_reserved` | 已预留分享佣金,金额字符串或 null |
-| `share_debt` | 分享佣金欠款,金额字符串或 null |
-
-佣金余额快照不分摊到每日,也不计入期间净入金或公司净收益。每日列表和区间总计均不提供在线人数、有效会员数、活跃人数、活跃率或活动时长列。
-
-## 6. 页面交互与错误处理
+## 5. 页面交互与错误处理
 
 | 操作/状态 | 页面行为 |
 |---|---|
-| 首次进入 | 北京时间当天,`page=1`、`limit=20`,加载总计和每日列表 |
-| 查询 | 校验日期后以新日期和 `page=1` 请求;结果对应已提交的日期 |
-| 重置 | 日期恢复北京时间当天,`page=1`、`limit=20`,重新查询 |
-| 分页 | 保持已提交日期,改变页码;调整每页条数后回到第 1 页 |
-| 刷新 | 重新请求当前已提交日期和页码,同时更新总计、列表及更新时间 |
+| 首次进入 | 日期为空,查询全部时间,`page=1`、`limit=20`,加载总计和每日列表 |
+| 查询 | 以已选择的时间模式和 `page=1` 请求;全部时间不传日期 |
+| 重置 | 清空日期,恢复全部时间、`page=1`、`limit=20`,重新查询 |
+| 分页 | 保持已提交的时间模式,改变页码;调整每页条数后回到第1页 |
+| 刷新 | 重新请求当前已提交条件和页码,同时更新总计、列表 |
 | 加载 | 显示加载状态并防止重复提交;快速连续查询以最后一次请求结果为准 |
-| 无业务日 | 保留每日行;完整且金额为零时展示 `0.00`,有缺失时展示相应状态 |
+| 无业务日 | 保留每日行;金额为零时展示 `0.00`,`null` 显示“—” |
 | 空页 | `data.data=[]` 时展示“当前页暂无数据”,保留区间总计,允许返回有效页 |
-| 首次请求失败 | 展示错误信息和重试操作,结束加载状态 |
-| 刷新失败 | 可保留上次结果,标明“刷新失败,当前为上次结果”,保留其日期及更新时间 |
+| 请求失败 | 展示失败文案和重试操作,结束加载状态;不将失败结果替换成零金额 |
 
 参数或服务异常的返回示例:
 
@@ -301,4 +212,4 @@ Accept: application/json
 | `code=-1` 且 `msg="无权限访问"` | 展示无权限状态 |
 | 网络失败、非 JSON 响应或 HTTP 错误 | 使用请求层错误状态,结束加载,提供重试 |
 
-服务异常的提示为“报表暂不可用,请稍后重试”。业务是否成功按外层 `code` 判断;失败结果不转换为零金额或空业务数据。
+服务异常的提示为“报表暂不可用,请稍后重试”。业务是否成功按外层 `code` 判断。

+ 27 - 87
docs/前端交付/bot-28/后台/龙虎榜-前端接入.md

@@ -1,6 +1,8 @@
 # 龙虎榜:前端接入
 
-项目:`bot-28` 总后台。页面展示会员排名,固定营业范围 `business`,筛选包含日期和排名项目,下方为列表与分页。
+项目:`bot-28` 总后台。页面展示会员排名,固定营业范围 `business`,筛选包含时间范围和排名项目,下方为列表与分页。
+
+筛选区下方不显示统计说明或元数据整段、黄色数据完整性提示;列表不显示“数据状态/数据不完整”列。币种、数据更新时间、查询时间、资格政策、活动采集起点和佣金余额快照不另作说明行、角标或弹窗。`quality`、`complete`、`known_*` 等字段仍由接口返回,本页不渲染,也不用已知小计代替最终结果;最终值为 `null` 时只显示“—”。
 
 ## 接口与认证
 
@@ -20,7 +22,7 @@ Accept: application/json
 | `GET /admin/reporting/orders` | 已有游戏交易追溯 | `admin/reporting/summary` |
 | `GET /admin/reporting/events` | 已有资金记录追溯 | `admin/reporting/summary` |
 
-本页主请求为 `leaderboard`;公共选项和已有追溯入口使用各自权限控制。
+本页主请求为 `leaderboard`;公共选项和已有追溯入口使用各自权限控制。若打开会员统计或订单、资金追溯,保留当前日期条件及会员分组;`details`、`orders`、`events` 均回传 `filters`,全部时间追溯不传日期。
 
 ## 请求参数
 
@@ -30,13 +32,19 @@ GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metri
 
 | 参数 | 类型 | 默认值 | 说明 |
 |---|---|---|---|
-| `start_date` | string | 北京时间当天 | `YYYY-MM-DD`,包含开始日 |
-| `end_date` | string | 北京时间当天 | `YYYY-MM-DD`,包含结束日 |
+| `start_date` | string | 不限定 | 与结束日期一起传;`YYYY-MM-DD` |
+| `end_date` | string | 不限定 | 与开始日期一起传,包含结束日 |
 | `metric` | string | `deposit_amount` | 排名项目,见下表 |
 | `page` | integer | `1` | 页码,范围 `1`~`1000000` |
 | `limit` | integer | `20` | 每页条数,范围 `1`~`100` |
 
-日期使用 `Asia/Shanghai`(UTC+8),起止日期一起提交;开始日不晚于结束日,结束日不晚于当天,包含首尾日最多366天。本页不传 `report_type`,响应固定为 `business`。
+默认查询全部时间:不传 `start_date`、`end_date`,日期为空时也按全部处理,覆盖最早历史日期至北京时间当天;无历史时覆盖当天。选择日期区间时同时传 `start_date`、`end_date`,按北京时间包含首尾日查询,最多366天,开始日不得晚于结束日,结束日不得晚于当天。
+
+```http
+GET /admin/reporting/leaderboard?metric=deposit_amount&page=1&limit=20
+```
+
+`filters.start_date`、`filters.end_date` 回显实际覆盖日期;全部时间翻页仍不传日期。本页不传 `report_type`,响应固定为 `business`。
 
 | `metric` | 项目/列标题 | 含义 |
 |---|---|---|
@@ -61,18 +69,12 @@ GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metri
 
 业务错误也可能返回 HTTP 200,以 `code` 判断结果。参数错误的 `msg` 是第一条校验文案;报表暂不可用的文案为“报表暂不可用,请稍后重试”。
 
-业务对象字段:
+本页使用的业务对象字段:
 
 | 字段 | 类型 | 说明 |
 |---|---|---|
-| `filters` | object | 本次生效的筛选;含日期、项目、分页和默认范围 `report_type=business`、`platform=all`、`category=all`、`settlement_status=all` |
-| `timezone` / `currency` | string | 时间区与钱包本位币,如 `Asia/Shanghai`、`CNY` |
-| `data_updated_at` | string/null | 数据更新时间;空值显示“尚未完成同步” |
-| `server_time` | string | 查询时间 |
-| `activity_started_at` | string | 活动时长采集开始时间 |
-| `valid_deposit_min` / `valid_effective_bet_min` | 金额字符串/null | 有效会员的累计存款/累计有效投注门槛,只读 |
-| `valid_member_policy` | object | 查询结束日生效的分享赚钱资格政策,只读;`available` 表示是否可用,`date` 为资格截止日,`effective_date` 为生效日,`min_deposit`/`min_effective_bet` 为两项门槛 |
-| `commission_balances` | object | 查询时佣金账户余额;`date_basis`、`as_of` 表示余额口径与时间,金额字段为 `agent_unpaid`、`share_unclaimed`、`share_reserved`、`share_debt`,可为 `null` |
+| `filters` | object | 本次生效的筛选;含实际覆盖日期、项目、分页和默认范围 `report_type=business`、`platform=all`、`category=all`、`settlement_status=all` |
+| `currency` | string | 金额格式使用的本位币,如 `CNY` |
 | `total` | integer | 当前项目的会员总数 |
 | `data` | array | 当前页列表,完整路径为外层 `data.data` |
 | `unallocated` | object/null | 未分配公共资金与成本;不进入会员列表,不计入 `total` |
@@ -91,7 +93,7 @@ GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metri
 | `withdraw_amount` | 金额字符串/null | 提款总额 |
 | `activity_minutes` | integer/null | 活动时长,单位分钟;未知为 `null` |
 
-其他返回字段:
+可保留的其他业务列:
 
 | 字段 | 含义 |
 |---|---|
@@ -102,15 +104,15 @@ GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metri
 | `agent_accrual`、`share_accrual` | 应计代理/分享佣金 |
 | `manual_credit`、`manual_debit` | 人工上分/扣分 |
 | `settled_bet_amount`、`refund_amount`、`game_gross_profit` | 已结算投注净额、退款、游戏毛利 |
-| `company_profit`、`net_cash_flow` | 公司净收益、实际净入金;数据不完整时为 `null` |
-| `known_company_profit`、`known_net_cash_flow` | 已知小计,展示名称带“未完成核实” |
+| `company_profit`、`net_cash_flow` | 公司净收益、实际净入金;`null` 显示“—” |
 | `member_count`、`valid_member_count`、`active_member_count` | 会员参考人数、有效会员人数、期间活跃会员数;有效会员无法计算时为 `null` |
-| `known_activity_minutes`、`active_rate`、`activity_population_complete` | 已采集分钟、期间活跃占比、活动人口范围完整性;占比 `"0.250000"` 表示25% |
+| `active_rate` | 期间活跃占比,如 `"0.250000"` 显示25%;`null` 显示“—” |
 | `order_count` | 订单数 |
-| `complete`、`quality` | 完整性状态;`complete=false` 时显示简短只读说明,`quality` 为具体状态标志 |
 
 ## 返回示例
 
+以下 JSON 仅展示本页使用字段,是有效响应子集;实际响应可以包含其他字段:
+
 ```json
 {
   "code": 0,
@@ -122,36 +124,13 @@ GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metri
       "platform": "all",
       "category": "all",
       "settlement_status": "all",
-      "start_date": "2026-10-26",
+      "start_date": "2024-01-01",
       "end_date": "2026-10-26",
       "page": 1,
       "limit": 20,
       "metric": "deposit_amount"
     },
-    "timezone": "Asia/Shanghai",
     "currency": "CNY",
-    "data_updated_at": "2026-10-26 08:00:00",
-    "activity_started_at": "2026-10-25 00:00:00",
-    "valid_deposit_min": "20.0000000000",
-    "valid_effective_bet_min": "100.0000000000",
-    "valid_member_policy": {
-      "id": 1,
-      "effective_date": "2026-10-01",
-      "min_deposit": "20.0000000000",
-      "min_effective_bet": "100.0000000000",
-      "date": "2026-10-26",
-      "available": true,
-      "unavailable_reasons": []
-    },
-    "commission_balances": {
-      "date_basis": "current_account_balance",
-      "as_of": "2026-10-26 16:00:00",
-      "agent_unpaid": "0.0000000000",
-      "share_unclaimed": "0.0000000000",
-      "share_reserved": "0.0000000000",
-      "share_debt": "0.0000000000"
-    },
-    "server_time": "2026-10-26 16:00:00",
     "total": 1,
     "data": [
       {
@@ -159,68 +138,29 @@ GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metri
         "label": "member2",
         "deposit_amount": "1000.0000000000",
         "withdraw_amount": "0.0000000000",
-        "member_withdraw_amount": "0.0000000000",
-        "agent_withdraw_amount": "0.0000000000",
-        "deposit_fee": "2.0000000000",
-        "withdraw_fee": "0.0000000000",
-        "customer_fee_income": "0.0000000000",
-        "configured_fee": "0.0000000000",
-        "bonus_amount": "0.0000000000",
-        "agent_commission": "0.0000000000",
-        "share_commission": "0.0000000000",
-        "agent_accrual": "0.0000000000",
-        "share_accrual": "0.0000000000",
-        "manual_credit": "0.0000000000",
-        "manual_debit": "0.0000000000",
         "bet_amount": "100.0000000000",
-        "settled_bet_amount": "100.0000000000",
         "payout_amount": "60.0000000000",
-        "refund_amount": "0.0000000000",
-        "game_gross_profit": "40.0000000000",
-        "quality": {
-          "missing_time": 0,
-          "missing_amount": 0,
-          "unverified_fees": 0,
-          "foreign_currency": 0,
-          "unverified_attribution": 0,
-          "sync_errors": 0,
-          "not_synchronized": false
-        },
         "member_account": "member2",
         "nickname": "昵称2",
-        "agent_account": "agent10",
-        "inviter_account": "member1",
-        "member_count": 1,
         "valid_member_count": 1,
         "active_member_count": 0,
-        "activity_population_complete": true,
-        "known_activity_minutes": 0,
         "activity_minutes": 0,
         "active_rate": "0.000000",
-        "order_count": 1,
-        "complete": true,
-        "known_net_cash_flow": "998.0000000000",
-        "known_company_profit": "38.0000000000",
-        "net_cash_flow": "998.0000000000",
-        "company_profit": "38.0000000000",
         "rank": 1
       }
-    ],
-    "unallocated": null
+    ]
   }
 }
 ```
 
 ## 页面交互与展示
 
-初次进入使用北京时间当天、存款总额、第一页、每页20条。查询或切换日期、排名项目、每页条数时回到第一页;翻页保持当前筛选。重置恢复初始值。项目切换显示加载状态,快速切换时以最后一次请求结果更新列表。
+初次进入日期为空,查询全部时间,使用存款总额、第一页、每页20条。选择日期后按区间查询;查询、切换排名项目或每页条数时回到第一页,翻页保留原始日期条件。重置清空日期并恢复初始值。快速切换时以最后一次请求结果更新列表。
 
 成功后使用 `data.data` 渲染列表、`data.total` 渲染分页,空数组显示“暂无数据”。请求失败显示 `msg` 和重试入口,登录失效与无权限使用后台对应状态。
 
-金额按 `currency` 展示为两位小数,真实0显示 `0.00`,`null` 显示“—”。表头使用“投注总额/派奖总额”,分别取 `bet_amount`/`payout_amount`。名次直接取 `rank`,页面保持接口顺序。
-
-活动时长使用“分钟”,真实0显示“0分钟”,`null` 显示“—”。查询范围早于 `activity_started_at` 时,`activity_minutes` 可能为 `null`;`known_activity_minutes` 仅表示已有采集部分。活动时长是区间估算值,页面名称使用“活动时长”。
+`currency` 仅用于金额格式,不另作说明行。金额展示为两位小数,数值为0时显示 `0.00`,`null` 显示“—”。表头使用“投注总额/派奖总额”,分别取 `bet_amount`/`payout_amount`。名次直接取 `rank`,页面保持接口顺序。
 
-列表上方显示 `data_updated_at` 为“数据更新时间”;如展示 `server_time`,标为“查询时间”。有效会员条件仅作只读回显,使用 `valid_member_policy` 中的实际值。
+活动时长使用“分钟”,数值为0时显示“0分钟”,`null` 显示“—”。活动时长是区间估算值,页面名称使用“活动时长”。
 
-页面范围为日期、项目、查询/重置、榜单和分页;不包含“有效会员最低存款设置”“收付款核对”“数据质量”入口。
+页面范围为时间范围、项目、查询/重置、榜单和分页;不包含“有效会员最低存款设置”“收付款核对”“数据质量”入口。

+ 3 - 1
docs/前端交付/bot-28/后台/龙虎榜资金流动与报表-接口.md

@@ -10,6 +10,8 @@
 
 服务项目为 `bot-28`,使用后台 API 域名和管理员 Token。
 
-三个页面均去掉“有效会员最低存款设置”“收付款核对”“数据质量”按钮及关联弹窗。有效会员规则由现有分享赚钱设置提供,页面按接口返回值展示。
+三个页面默认查询全部时间,不传起止日期;选择日期后按区间查询。页面保留筛选、查询/重置、卡片与业务指标、明细及分页;去掉筛选下方统计说明与元数据、黄色完整性提示、列表数据状态列,不新增替代提示。
+
+三个页面均去掉“有效会员最低存款设置”“收付款核对”“数据质量”按钮及关联弹窗。有效会员人数直接使用接口结果;条件管理仍在现有分享赚钱设置。
 
 页面菜单和权限按钮由前端按各文档中的权限 URI 配置。

+ 22 - 10
docs/前端交付/交付清单.json

@@ -70,8 +70,8 @@
       "role": "后台",
       "kind": "目录",
       "git_status": "M",
-      "source_sha256": "a035e243011e2091f0011bceac0122883aa20db97d3829b2b207df36b7da4fa6",
-      "delivery_sha256": "a035e243011e2091f0011bceac0122883aa20db97d3829b2b207df36b7da4fa6",
+      "source_sha256": "bc2e73e53cbf7859be218b0da0e4d7a03fe33f52268f716025d18f0a49d449e2",
+      "delivery_sha256": "bc2e73e53cbf7859be218b0da0e4d7a03fe33f52268f716025d18f0a49d449e2",
       "revised_on": "2026-10-26"
     },
     {
@@ -116,8 +116,8 @@
       "git_status": "??",
       "scope_reason": "按用户要求重新整理的完整前端接入文档",
       "revised_on": "2026-10-26",
-      "source_sha256": "20daefcfec91c6da2116dfb6e93b1388d6bde2975c3b50a1fffba9f9bc48844e",
-      "delivery_sha256": "20daefcfec91c6da2116dfb6e93b1388d6bde2975c3b50a1fffba9f9bc48844e"
+      "source_sha256": "8f604172df704a46c14217073bda6039391e1f701c1c135ce3b66706626f6ae1",
+      "delivery_sha256": "8f604172df704a46c14217073bda6039391e1f701c1c135ce3b66706626f6ae1"
     },
     {
       "project": "bot-28",
@@ -128,8 +128,8 @@
       "git_status": "??",
       "scope_reason": "按用户要求重新整理的完整前端接入文档",
       "revised_on": "2026-10-26",
-      "source_sha256": "ed90202c51f1faece78c06f3c5a40e23e6ec3fd1a50942825a56d3de09bef2eb",
-      "delivery_sha256": "ed90202c51f1faece78c06f3c5a40e23e6ec3fd1a50942825a56d3de09bef2eb"
+      "source_sha256": "90cf5736a5ec22b169962c5f61072de13902c7d86ebf7f1c7676b4058add30b4",
+      "delivery_sha256": "90cf5736a5ec22b169962c5f61072de13902c7d86ebf7f1c7676b4058add30b4"
     },
     {
       "project": "bot-28",
@@ -140,8 +140,8 @@
       "git_status": "??",
       "scope_reason": "按用户要求重新整理的完整前端接入文档",
       "revised_on": "2026-10-26",
-      "source_sha256": "15cd40eff4466ec9b2915e53b0f63d11b25a3238f75a0c8d53e4d2f8b86fb1cf",
-      "delivery_sha256": "15cd40eff4466ec9b2915e53b0f63d11b25a3238f75a0c8d53e4d2f8b86fb1cf"
+      "source_sha256": "fc2846111a9155b83e3603e81ecf714d5cd58328382c48ceec4f02a8b676fa05",
+      "delivery_sha256": "fc2846111a9155b83e3603e81ecf714d5cd58328382c48ceec4f02a8b676fa05"
     },
     {
       "project": "bot-28",
@@ -151,8 +151,8 @@
       "kind": "修正单",
       "git_status": "??",
       "scope_reason": "用户明确取消三个报表入口并统一有效会员规则的独立修正单",
-      "source_sha256": "cd8a6d99d9ffa09388311f5dc46ed5623538a065e4350d38edd2f05203fef314",
-      "delivery_sha256": "cd8a6d99d9ffa09388311f5dc46ed5623538a065e4350d38edd2f05203fef314",
+      "source_sha256": "021078202c13f747b6812664d5ad490a5e6aebc78608395a5eb48aaca6365724",
+      "delivery_sha256": "021078202c13f747b6812664d5ad490a5e6aebc78608395a5eb48aaca6365724",
       "revised_on": "2026-10-26"
     },
     {
@@ -314,6 +314,18 @@
       "project": "bot-28",
       "head_commit": "26f28536c7555cc4c8d888a5246181c6ee13bc5a",
       "description": "按用户要求重新提供报表汇总、资金流动、龙虎榜三份完整前端接入文档;只包含接口、请求、返回及页面交互;原合并文档作为目录保留"
+    },
+    {
+      "date": "2026-10-26",
+      "project": "bot-28",
+      "head_commit": "1af7230324a6ad5b753a02d3fd1a715e9e79ddf6",
+      "description": "报表汇总、资金流动、龙虎榜支持all_time=1全部时间;自选日期仍最多366天;统一移除筛选下方统计说明与元数据、黄色完整性提示和列表数据状态列,保留业务指标;同步三份现行接入文档、目录、修正单及交付副本和SHA-256"
+    },
+    {
+      "date": "2026-10-26",
+      "project": "bot-28",
+      "head_commit": "1af7230324a6ad5b753a02d3fd1a715e9e79ddf6",
+      "description": "按用户确认,报表默认查询全部时间,无需all_time请求参数;不传日期为全部,传入日期按区间筛选,前端默认与重置清空日期"
     }
   ]
 }

+ 1 - 1
docs/前端交付/交付清单.md

@@ -2,7 +2,7 @@
 
 按用户指定Git范围收录接口资料,并按后续要求补充后台公告、信息管理、维护以及报表修订文档。交付副本采用当前文档版本,链接均在本目录内。
 
-本轮重新整理的[报表汇总](bot-28/后台/报表汇总-前端接入.md)、[资金流动](bot-28/后台/资金流动-前端接入.md)、[龙虎榜](bot-28/后台/龙虎榜-前端接入.md)为三份完整前端接入文档,可直接转发。原合并文件作为目录入口保留。
+本轮重新整理的[报表汇总](bot-28/后台/报表汇总-前端接入.md)、[资金流动](bot-28/后台/资金流动-前端接入.md)、[龙虎榜](bot-28/后台/龙虎榜-前端接入.md)为三份完整前端接入文档,可直接转发。原合并文件作为目录入口保留。三页已补充全部时间查询,并统一移除统计说明与元数据、完整性提示和列表数据状态列。
 
 共21个来源文件:bot-28后台12个,melbet_sport-api前台9个;其中16份正文、3份目录、1份修正单、1份示例数据。状态列保留整理时的Git状态,后续修订见JSON的revisions。
 

+ 37 - 157
docs/总后台/报表汇总-前端接入.md

@@ -6,7 +6,9 @@
 
 页面保留三个页签、筛选、查询、重置、“所有订单”总卡、九类游戏卡片、分组明细、游戏订单和资金记录追溯。
 
-**删除“有效会员最低存款设置”“收付款核对”“数据质量”三个按钮、菜单、弹窗、表单及其专门请求,不创建替代入口。** 报表中的有效会员条件仅作只读说明,修改条件仍在既有“分享赚钱”配置页面完成。
+筛选区下方不显示统计说明或元数据整段、黄色数据完整性提示;列表不显示“数据状态/数据不完整”列。币种、数据更新时间、查询时间、资格政策、活动采集起点和佣金余额快照不另作说明行、角标或弹窗。`quality`、`complete`、`known_*` 等字段仍由接口返回,本页不渲染,也不用已知小计代替最终结果;最终值为 `null` 时只显示“—”。
+
+**删除“有效会员最低存款设置”“收付款核对”“数据质量”三个按钮、菜单、弹窗、表单及其专门请求,不创建替代入口。** 有效会员人数使用接口结果;条件修改仍在既有“分享赚钱”配置页面完成。
 
 前端自建报表汇总菜单,权限 URI 为 **`admin/reporting/summary`**,没有开头斜线。`options`、`details`、`orders`、`events` 复用此权限;菜单显示和角色授权沿用现有后台权限配置。
 
@@ -51,8 +53,8 @@ Accept: application/json
 
 | 参数 | 类型、默认值 | 允许值与含义 | 使用位置 |
 |---|---|---|---|
-| `start_date` | 日期字符串,默认北京时间当天 | 起始日,含当天00:00:00 | 所有统计/追溯 |
-| `end_date` | 日期字符串,默认北京时间当天 | 结束日,含结束日;不得晚于北京时间当天 | 所有统计/追溯 |
+| `start_date` | 可选日期字符串 | 与结束日期一起传;不传日期时查询全部 | 所有统计/追溯 |
+| `end_date` | 可选日期字符串 | 结束日,含结束日;不晚于北京时间当天 | 所有统计/追溯 |
 | `report_type` | 字符串,默认 `business` | `business` / `promotion` / `sharing` | 所有统计/追溯 |
 | `platform` | 字符串,默认 `all` | `all` / `H5` / `PC` / `APP` / `unknown` | 所有统计/追溯 |
 | `category` | 字符串,默认 `all` | `all` 或下表九类代码;游戏订单按分类筛选 | 明细、追溯;主页面为 `all` |
@@ -65,7 +67,7 @@ Accept: application/json
 | `limit` | 整数,默认20 | 1~100 | `details` / `orders` / `events` |
 | `kind` | 字符串,不传则全部 | 见第9节资金类型枚举 | 仅 `events` 使用 |
 
-日期必须满足 `start_date ≤ end_date`,含首尾最多366天。服务端按“起始日00:00:00 ≤ 时间 < 结束日次日00:00:00”取数。页面校验后仍以接口校验结果为准。分页参数不分页汇总卡;返回 `filters` 中出现的 `metric` 是公共默认参数,不需要在本页增加排名筛选,也不改变本页明细排序。
+默认查询全部时间:不传 `start_date`、`end_date`,日期为空时也按全部处理,覆盖最早历史日期至北京时间当天;无历史时覆盖当天。选择日期区间时同时传 `start_date`、`end_date`,按北京时间包含首尾日查询,最多366天,开始日不得晚于结束日,结束日不得晚于当天。 `filters.start_date`、`filters.end_date` 回显实际覆盖日期;全部时间的分页或追溯请求仍不传日期,不将回显日期自动填回筛选。分页参数不分页汇总卡。
 
 `platform` 是业务发生时的客户端装置,不能用第三方供应商名称、游戏分类或会员注册端替代。没有可靠装置证据的数据为 `unknown`,包括部分第三方回单。
 
@@ -89,7 +91,7 @@ Accept: application/json
 GET /admin/reporting/options
 ```
 
-`data.report_types` 和 `data.categories` 是“代码 → 中文标签”的 **对象**;`platforms`、`settlement_statuses`、`ranking_metrics` 是 **字符串数组**,不是 `{value,label}` 数组。前端需要自行转成组件需要的选项结构。`ranking_metrics` 供龙虎榜使用,本页不生成排名控件。
+`data.report_types` 和 `data.categories` 是“代码 → 中文标签”的 **对象**;`platforms`、`settlement_statuses` 是 **字符串数组**,不是 `{value,label}` 数组。前端需要自行转成组件需要的选项结构。
 
 | 字段 | 类型 | 说明 |
 |---|---|---|
@@ -97,12 +99,10 @@ GET /admin/reporting/options
 | `categories` | object<string,string> | `all` 加九类游戏代码和名称 |
 | `platforms` | string[] | 装置代码;`all` 展示“全部”,`unknown` 展示“未知” |
 | `settlement_statuses` | string[] | `all` 全部、`pending` 未结算、`settled` 已结算 |
-| `ranking_metrics` | string[] | 龙虎榜可选指标,本页不用 |
-| `timezone` / `currency` | string | 统计时区/本位币 |
-| `max_days` | number | 最大查询天数,366 |
-| `rules` | object<string,string> | 只读规则说明,键为 `profit`、`net_cash_flow`、`valid_member`、`online_rate`、`public_costs` |
+| `currency` | string | 金额格式使用的本位币 |
+| `max_days` | number | 自选日期最多366天;不限制全部时间 |
 
-以下为可解析的完整选项结构,币种以实际响应为准:
+以下 JSON 仅展示本页使用字段,是有效响应子集;实际响应可以包含其他字段:
 
 ```json
 {
@@ -139,38 +139,30 @@ GET /admin/reporting/options
       "pending",
       "settled"
     ],
-    "ranking_metrics": [
-      "deposit_amount",
-      "bet_amount",
-      "payout_amount",
-      "withdraw_amount",
-      "activity_minutes"
-    ],
-    "timezone": "Asia/Shanghai",
     "currency": "CNY",
-    "max_days": 366,
-    "rules": {
-      "profit": "已结算游戏毛利-实发优惠-代理佣金-分享佣金-实际平台手续费",
-      "net_cash_flow": "成功收款-实际对外打款-实际平台手续费",
-      "valid_member": "范围内期间非撤销投注会员,按查询结束日已生效的分享赚钱规则,累计成功存款及累计已结算非撤销同币种有效投注均大于或等于对应门槛;累计包含查询开始日前记录,不受装置及游戏分类筛选影响",
-      "online_rate": "期间活跃会员/范围内期末真实会员",
-      "public_costs": "公共资金和成本只在所有订单展示,未知值为null"
-    }
+    "max_days": 366
   }
 }
 ```
 
-`options` 不返回本次查询结束日的资格政策,政策说明须读 `summary` 或 `details` 的 `valid_member_policy`,不能从选项文本提取门槛。
 
 ## 5. 汇总接口及响应
 
+全部时间请求示例:
+
+```http
+GET /admin/reporting/summary?report_type=business&platform=all&category=all&settlement_status=all
+```
+
+自选日期请求示例:
+
 ```http
 GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date=2026-10-26&platform=all&category=all&settlement_status=all
 ```
 
 `data.summary` 是“所有订单”总卡,`data.categories` 是九类卡片数组,以 `group_id` 匹配分类代码,`label` 为中文名称。卡片固定保留,包括无业务的分类;无需依据金额是否为0决定卡片是否存在。
 
-以下为可解析响应示例,`categories` **仅保留 F1彩票 一行,其余八行省略**,实际响应仍返回九类。金额和人数仅为示例。
+以下 JSON 仅展示本页使用字段,是有效响应子集;`categories` 仅展示 F1彩票 一行,实际响应仍返回九类。金额和人数仅为示例。
 
 ```json
 {
@@ -184,35 +176,9 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
       "category": "all",
       "settlement_status": "all",
       "start_date": "2026-10-26",
-      "end_date": "2026-10-26",
-      "page": 1,
-      "limit": 20,
-      "metric": "deposit_amount"
+      "end_date": "2026-10-26"
     },
-    "timezone": "Asia/Shanghai",
     "currency": "CNY",
-    "data_updated_at": "2026-10-26 08:00:00",
-    "activity_started_at": "2026-10-25 00:00:00",
-    "valid_deposit_min": "20.0000000000",
-    "valid_effective_bet_min": "100.0000000000",
-    "valid_member_policy": {
-      "id": 1,
-      "effective_date": "2026-10-01",
-      "min_deposit": "20.0000000000",
-      "min_effective_bet": "100.0000000000",
-      "date": "2026-10-26",
-      "available": true,
-      "unavailable_reasons": []
-    },
-    "commission_balances": {
-      "date_basis": "current_account_balance",
-      "as_of": "2026-10-26 16:00:00",
-      "agent_unpaid": "0.0000000000",
-      "share_unclaimed": "0.0000000000",
-      "share_reserved": "0.0000000000",
-      "share_debt": "0.0000000000"
-    },
-    "server_time": "2026-10-26 16:00:00",
     "summary": {
       "group_id": "all",
       "label": "所有订单",
@@ -236,26 +202,12 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
       "payout_amount": "60.0000000000",
       "refund_amount": "0.0000000000",
       "game_gross_profit": "40.0000000000",
-      "quality": {
-        "missing_time": 0,
-        "missing_amount": 0,
-        "unverified_fees": 0,
-        "foreign_currency": 0,
-        "unverified_attribution": 0,
-        "sync_errors": 0,
-        "not_synchronized": false
-      },
       "member_count": 4,
       "valid_member_count": 1,
       "active_member_count": 0,
-      "activity_population_complete": true,
-      "known_activity_minutes": 0,
       "activity_minutes": 0,
       "active_rate": "0.000000",
       "order_count": 1,
-      "complete": true,
-      "known_net_cash_flow": "998.0000000000",
-      "known_company_profit": "38.0000000000",
       "net_cash_flow": "998.0000000000",
       "company_profit": "38.0000000000"
     },
@@ -283,26 +235,12 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
         "payout_amount": "60.0000000000",
         "refund_amount": "0.0000000000",
         "game_gross_profit": "40.0000000000",
-        "quality": {
-          "missing_time": 0,
-          "missing_amount": 0,
-          "unverified_fees": 0,
-          "foreign_currency": 0,
-          "unverified_attribution": 0,
-          "sync_errors": 0,
-          "not_synchronized": false
-        },
         "member_count": 4,
         "valid_member_count": 1,
         "active_member_count": 0,
-        "activity_population_complete": true,
-        "known_activity_minutes": 0,
         "activity_minutes": 0,
         "active_rate": "0.000000",
         "order_count": 1,
-        "complete": true,
-        "known_net_cash_flow": null,
-        "known_company_profit": null,
         "net_cash_flow": null,
         "company_profit": null
       }
@@ -311,21 +249,7 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 }
 ```
 
-汇总和明细共享下列元数据;订单、资金追溯仅返回 `total`、`data`、`timezone`,不要要求其重复返回这些字段。
-
-| 字段 | 类型 | 页面使用 |
-|---|---|---|
-| `filters` | object | 服务端校验后的参数及默认值,数字查询参数回显可能仍为字符串 |
-| `timezone` / `currency` | string | 时区/本位币 |
-| `data_updated_at` | string \| null | 最近统计同步时间,`null` 不伪造为查询时间 |
-| `server_time` | string | 当前服务器时间 |
-| `activity_started_at` | string | 活动观测开始时间,用于解释历史时长覆盖情况 |
-| `valid_member_policy` | object | 本次 `end_date` 对应的只读资格政策,见第7节 |
-| `valid_deposit_min` | string \| null | 与政策 `min_deposit` 相同的兼容字段 |
-| `valid_effective_bet_min` | string \| null | 与政策 `min_effective_bet` 相同的兼容字段 |
-| `commission_balances` | object | 查询时账户余额快照,见下文 |
-
-`commission_balances` 包含 `date_basis="current_account_balance"`、`as_of` 和金额字段 `agent_unpaid`、`share_unclaimed`、`share_reserved`、`share_debt`。依次为代理待发、分享未领(含预留部分)、分享预留、分享欠账。金额可以为 `null`,例如当前按装置或分类筛选而无法分配时;这是查询时快照,不是查询期间新增成本,不能再次计入净收益公式。
+汇总和明细通过 `filters` 回传生效筛选,`currency` 用于金额格式。订单与资金追溯同样返回 `filters`,翻页和追溯保留原始日期选择及其余筛选;全部时间时仍不传日期,`filters` 中的实际覆盖日期仅作返回信息。
 
 ## 6. 卡片与明细指标
 
@@ -339,7 +263,7 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 | `deposit_amount` | 期间成功收款本金,按钱包本位币计 |
 | `withdraw_amount` | 期间实际对外提款净额,含会员及代理成功出款,非预扣申请额;等于 `member_withdraw_amount + agent_withdraw_amount` |
 | `member_withdraw_amount` / `agent_withdraw_amount` | 会员/代理实际对外提款净额 |
-| `deposit_fee` / `withdraw_fee` | 已确认的实际外部收款/出款手续费;未知手续费会影响完整性,已知累计金额不能当成最终费用 |
+| `deposit_fee` / `withdraw_fee` | 已确认的实际外部收款/出款手续费 |
 | `customer_fee_income` | 向会员/代理内扣的手续费收入,单列;不作为实际外部成本重复扣除 |
 | `configured_fee` | 原订单按配置计算的参考费用,不是已确认的实际外部费用 |
 | `bet_amount` | 期间下单的投注本金,退款另列;不是有效投注额 |
@@ -352,17 +276,12 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 | `manual_credit` / `manual_debit` | 人工上分/扣分,独立展示,不直接当作外部现金存提款 |
 | `company_profit` | string \| null,公司净收益,公式见下文;`settlement_status=pending` 时为 `null` |
 | `net_cash_flow` | string \| null,实际净入金,公式见下文 |
-| `known_company_profit` / `known_net_cash_flow` | string \| null,按已知金额计算的小计;不完整时必须标“已知小计,未完成核对”,不得展示为最终结果 |
 | `member_count` | number,当前人口范围内、查询结束日前的真实会员参考人数,不按注册装置拆分 |
 | `valid_member_count` | number \| null,本次投注候选人中满足累计两项门槛的去重人数;规则不可用时为 `null` |
 | `active_member_count` | number,本次已观测到的区间活跃会员去重人数;历史覆盖不足时仅代表已知观测 |
 | `activity_minutes` | number \| null,区间观测估算时长,整数分钟;历史覆盖不足时为 `null` |
-| `known_activity_minutes` | number,已知观测时长,所有秒数合计后向下取整为分钟 |
 | `active_rate` | string \| null,区间活跃人数 / 范围内期末真实会员人数,6位小数;例如 `"0.250000"` 显示25% |
-| `activity_population_complete` | boolean,历史活动会员是否全部仍属于当前人口范围;改绑导致范围不一致时为 `false` |
 | `order_count` | number,期间下单的原始业务订单数,可能与逐条追溯列表行数不同 |
-| `complete` | boolean,统计完整性;为 `false` 时最终公司净收益和实际净入金为 `null` |
-| `quality` | object,只读完整性提示,见第6.3节 |
 
 返回金额的含义:
 
@@ -371,11 +290,11 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 实际净入金 = 成功收款本金 - 实际对外提款净额 - 实际收款手续费 - 实际出款手续费
 ```
 
-优惠、内部佣金、应计金额不直接作为外部现金再次从净入金扣除。客户内扣手续费收入、配置参考费用、佣金账户快照不额外塞入上述公式。`pending` 筛选下 `known_company_profit` 也不能作为最终公司净收益显示。
+优惠、内部佣金、应计金额不直接作为外部现金再次从净入金扣除。客户内扣手续费收入和配置参考费用不额外加入上述公式。
 
 ### 6.2 总卡与分类卡的差异
 
-分类卡保留可归属的游戏指标及分类佣金/应计。以下公共资金字段返回 `null`:`deposit_amount`、`withdraw_amount`、`member_withdraw_amount`、`agent_withdraw_amount`、`deposit_fee`、`withdraw_fee`、`customer_fee_income`、`configured_fee`、`bonus_amount`、`manual_credit`、`manual_debit`。分类卡的 `company_profit`、`net_cash_flow` 及两个 `known_*` 小计也均为 `null`。分类毛利用 `game_gross_profit`。
+分类卡保留可归属的游戏指标及分类佣金/应计。以下公共资金字段返回 `null`:`deposit_amount`、`withdraw_amount`、`member_withdraw_amount`、`agent_withdraw_amount`、`deposit_fee`、`withdraw_fee`、`customer_fee_income`、`configured_fee`、`bonus_amount`、`manual_credit`、`manual_debit`。分类卡的 `company_profit`、`net_cash_flow` 也均为 `null`。分类毛利用 `game_gross_profit`。
 
 明细请求 `category != all` 时,同样将上述公共资金和净收益字段置为 `null`。分类佣金是已带分类归属的资金记录,不得按投注比例摊分公共成本,也不得把总卡金额复制给九类卡片。
 
@@ -383,41 +302,9 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 
 各分类有效会员可重复,不能相加得到总卡人数。`member_count`、活动人数、活动分钟和活跃率采用人员/活动范围,当前不按游戏分类拆分;不要把分类卡上这些值解释为某游戏专属在线人数或时长。
 
-### 6.3 只读完整性提示
-
-| `quality` 子字段 | 类型 | 提示含义 |
-|---|---|---|
-| `missing_time` | number | 存在无法确认业务时间的记录 |
-| `missing_amount` | number | 存在未知金额 |
-| `unverified_fees` | number | 存在未确认实际外部手续费的收付款 |
-| `foreign_currency` | number | 存在非本位币记录,未计入本位币合计 |
-| `unverified_attribution` | number | 存在未核实的历史代理归属 |
-| `sync_errors` | number | 统计同步存在错误 |
-| `not_synchronized` | boolean | 尚未完成首次同步 |
-
-任一异常可导致 `complete=false`。`sync_errors` 等提示不要直接解释为当前卡片可逐笔定位的条数。页面只需显示“部分数据未完成核对”及只读提示;不生成“数据质量”入口、诊断列表、核对按钮或修改表单。金额 `"0.0000000000"` 显示0.00,`null` 显示“—”;已知小计必须带说明。
-
-## 7. 有效会员:只读展示分享赚钱的生效条件
-
-有效会员人数直接使用 `valid_member_count`。它表示当前筛选范围内,期间有正额且未撤销投注、累计成功充值及累计有效投注均达到对应门槛的去重会员数。累计金额包含查询开始日前的记录。
-
-页面使用 `valid_member_policy` 显示本次查询结束日的生效条件。两项门槛均为“大于或等于”,0按实际值显示;修改条件仍使用现有分享赚钱设置页面。
-
-`valid_member_policy` 总是一个对象,字段如下:
-
-| 字段 | 类型 | 含义 |
-|---|---|---|
-| `id` | number \| null | 本次政策版本 ID |
-| `effective_date` | string \| null | 所用政策的生效日,不是本次资格截止日 |
-| `min_deposit` | string \| null | 累计成功充值最低门槛 |
-| `min_effective_bet` | string \| null | 累计有效投注最低门槛 |
-| `date` | string | 资格截止日,与本次 `end_date` 一致 |
-| `available` | boolean | 政策及必要分享数据是否足以计算资格 |
-| `unavailable_reasons` | string[] | 不可计算原因;只读排查信息,不据此生成页面操作 |
-
-`available=false` 时所有相关 `valid_member_count` 为 `null`,显示“—”,并显示“有效会员条件暂不可计算”。不能显示0人,也不能沿用旧的独立20门槛。两项门槛仍可能有值,也可能为 `null`,都以本次响应为准。
+## 7. 有效会员人数
 
-`valid_deposit_min` 与 `valid_effective_bet_min` 分别是两项金额的兼容字段,和 `valid_member_policy.min_deposit` / `min_effective_bet` 一致。建议只读说明为“截至 {date},累计成功充值 ≥ {min_deposit},且累计有效投注 ≥ {min_effective_bet};采用 {effective_date} 生效规则”。不添加输入框、保存按钮或报表专属设置弹窗。
+`valid_member_count` 为当前筛选范围内,期间有正额且未撤销投注、累计成功充值和累计有效投注均满足分享赚钱条件的去重会员数。累计金额包含查询开始日前记录。页面直接显示人数;为 `null` 时显示“—”。
 
 ## 8. 区间活跃与活动分钟
 
@@ -426,10 +313,8 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 | 字段/返回情况 | 页面展示 |
 |---|---|
 | `activity_minutes` | 使用整数分钟;0显示“0分钟”,`null` 显示“—” |
-| `known_activity_minutes` | 已知部分,可显示“已采集时长:X分钟” |
 | `active_member_count` | 区间活跃会员人数 |
 | `active_rate` | 按百分比展示,如 `"0.250000"` 显示25%;`null` 显示“—” |
-| `activity_population_complete=false` | 活跃率显示“—”,时长和人数仍按各自字段展示 |
 
 总卡与分类卡分别读取对应返回值,活动指标不按分类相加。
 
@@ -437,13 +322,13 @@ GET /admin/reporting/summary?report_type=business&start_date=2026-10-26&end_date
 
 ### 9.1 点击卡片获取分组明细
 
-点击“所有订单”传 `category=all`;点击游戏卡片传该卡片 `group_id` 作为 `category`。保留当前日期、页签、装置、结算状态和账号筛选,页码回到1。**卡片 `group_id` 是分类代码,不能把它直接作为追溯 `group_id`。**
+点击“所有订单”传 `category=all`;点击游戏卡片传该卡片 `group_id` 作为 `category`。保留当前时间范围、页签、装置、结算状态和账号筛选,页码回到1。**卡片 `group_id` 是分类代码,不能把它直接作为追溯 `group_id`。**
 
 ```http
 GET /admin/reporting/details?report_type=promotion&start_date=2026-10-26&end_date=2026-10-26&platform=all&category=self:lottery&settlement_status=all&page=1&limit=20
 ```
 
-业务数据为 `{ "total": 0, "data": [], "unallocated": null }` 加第5节共享元数据。`data` 每行拥有第6节同组统计指标,并附以下身份字段:
+业务数据包括 `filters`、`currency`、`total`、`data`、`unallocated`;`total=0` 时列表 `data=[]`。`data` 每行拥有第6节同组统计指标,并附以下身份字段:
 
 | 字段 | 类型 | 说明 |
 |---|---|---|
@@ -466,7 +351,7 @@ GET /admin/reporting/details?report_type=promotion&start_date=2026-10-26&end_dat
 GET /admin/reporting/orders?report_type=promotion&start_date=2026-10-26&end_date=2026-10-26&platform=all&category=self:lottery&settlement_status=all&group_id=10&page=1&limit=20
 ```
 
-业务数据为 `{ "total": 0, "data": [], "timezone": "Asia/Shanghai" }`,外层仍有 `code/timestamp/msg/data`。按 `bet_at` 倒序、再按记录 `id` 倒序。一笔原始业务拆分时可有多行,列表行数可与 `order_count` 不同。
+业务数据包括 `filters`、`total`、`data`、`timezone`,外层仍有 `code/timestamp/msg/data`。按 `bet_at` 倒序、再按记录 `id` 倒序。一笔原始业务拆分时可有多行,列表行数可与 `order_count` 不同。
 
 | 行字段 | 类型/含义 |
 |---|---|
@@ -481,11 +366,8 @@ GET /admin/reporting/orders?report_type=promotion&start_date=2026-10-26&end_date
 | `settlement_candidate_at` | string \| null,候选时间,不能冒充确认结算时间 |
 | `bet_amount` / `payout_amount` / `refund_amount` / `net_profit` | 原始十进制金额;`net_profit` 为会员净输赢,平台毛利方向相反 |
 | `settled` / `voided` | 原始0/1标记,分别表示已结算/已撤销,不是前端状态名称 |
-| `time_quality` | string,当前可为 `confirmed` / `missing_settlement_time` |
-| `attribution_quality` | string,`event_snapshot` 事件快照、`historical_current_agent` 参考当前代理关系的历史记录、`verified` 已确认归属 |
-| `created_at` / `updated_at` | string,统计记录创建/更新时间,不作为业务成功时间 |
 
-日期命中下单时间或结算时间均可能返回该行;历史已结算/撤销但结算时间缺失的记录也可能为提示缺失而返回。显示未知时间为“—”;不要按记录创建/更新时间补齐。`historical_current_agent` 在推广报表中影响完整性,不能标成已确认的历史归属。
+日期命中下单时间或结算时间均可能返回该行。未知时间显示“—”。
 
 ### 9.3 资金记录追溯
 
@@ -508,14 +390,14 @@ GET /admin/reporting/events?report_type=business&start_date=2026-10-26&end_date=
 | `manual_credit` | 人工上分 |
 | `manual_debit` | 人工扣分 |
 
-业务数据结构同订单追溯,只含 `total`、`data`、`timezone`;按资金记录 `id` 倒序。行字段:
+业务数据结构同订单追溯,包含 `filters`、`total`、`data`、`timezone`;按资金记录 `id` 倒序。行字段:
 
 | 行字段 | 类型/含义 |
 |---|---|
 | `id` | 统计资金记录整数标识 |
 | `source` / `source_id` / `kind` | string,原始来源/标识/资金类型 |
 | `user_id` / `agent_id` / `inviter_id` | 整数标识,可为 `null`;代理独立账户记录可能没有会员 |
-| `attribution_quality` / `client_platform` / `currency` | string,含义同订单字段 |
+| `client_platform` / `currency` | string,含义同订单字段 |
 | `category` | string \| null;公共资金可无游戏分类 |
 | `amount` | 原始金额,可为 `null` |
 | `customer_fee` | 客户内扣手续费,原始十进制金额 |
@@ -525,15 +407,14 @@ GET /admin/reporting/events?report_type=business&start_date=2026-10-26&end_date=
 | `reconciliation_id` | 整数标识 \| null,是否存在已确认记录的只读标识 |
 | `verified_amount` / `external_fee` | 已确认金额/实际外部手续费,均可为 `null` |
 | `verified_at` | string \| null,已确认业务时间 |
-| `created_at` / `updated_at` | string,统计记录创建/更新时间 |
 
-追溯返回的 `amount`、`occurred_at` 保留原始值,**不会被已确认值覆盖**。`reconciliation_id != null` 时,汇总使用 `verified_amount`、`verified_at`,页面展示有效金额/时间也应以这两项为准,并可同时展示原值作对照。`external_fee` 为实际费用;`configured_fee` 不可替代它。未确认业务时间的行显示“时间待确认”,不能用 `candidate_at`、`created_at` 或申请时间代替,也不提供编辑/核对入口。
+追溯返回的 `amount`、`occurred_at` 保留原始值,**不会被已确认值覆盖**。`reconciliation_id != null` 时,汇总使用 `verified_amount`、`verified_at`,页面展示有效金额/时间也应以这两项为准,并可同时展示原值作对照。`external_fee` 为实际费用;`configured_fee` 不可替代它。未确认业务时间显示“—”。
 
 ## 10. 页面调用流程、错误与空状态
 
-1. 进入页面读取 `options`,建立三个页签、装置及结算状态下拉。默认营业、当天、全部装置、全部结算状态,账号为空,主页面 `category=all`。
-2. 查询后请求 `summary`;三个页签都使用同一接口,只切换 `report_type`。总卡、分类卡、政策说明、更新时间及完整性提示必须来自同一份响应。
-3. 重置恢复默认筛选并重新查询。切换页签/筛选后清空旧明细选中组;列表页码回1。避免较早请求覆盖较新的查询结果。
+1. 进入页面读取 `options`,建立三个页签、装置及结算状态下拉。默认营业、全部时间、全部装置、全部结算状态,账号为空,日期为空,主页面 `category=all`;选择日期后查询相应区间。
+2. 查询后请求 `summary`;三个页签都使用同一接口,只切换 `report_type`。总卡和分类卡使用同一份响应。
+3. 重置清空日期,恢复全部时间及其余默认筛选并重新查询。切换页签/筛选后清空旧明细选中组;列表页码回1。避免较早请求覆盖较新的查询结果。
 4. 点击卡片请求 `details`;分页只刷新该列表,保留筛选。点击明细行的订单/资金追溯操作,用该行 `group_id` 和当前完整筛选请求对应接口,各自独立分页。
 5. 返回或关闭明细后保留主页面筛选。总卡不由明细当前页重算。
 
@@ -546,7 +427,6 @@ GET /admin/reporting/events?report_type=business&start_date=2026-10-26&end_date=
 | 网络异常、其他错误码 | 展示请求失败及重试状态,不把旧数据改成全0 |
 | `total=0`、`data=[]` | 列表显示“暂无数据”,分页总数为0 |
 | 无订单业务 | 保留“所有订单”及九类卡片;金额0与公共字段 `null` 仍按各自含义显示。人口/活动统计可能仍有值 |
-| `available=false` 或 `complete=false` | 属于成功响应中的只读提示状态,按第6、7节展示,不能整体当接口失败或空列表 |
 
 失败响应示例:
 

+ 14 - 15
docs/总后台/报表页面调整-移除三个入口.md

@@ -1,34 +1,33 @@
 # 报表页面调整:移除三个入口
 
-2026-10-26前端修正单,适用于 `bot-28` 后台报表页面。按本文件直接修改,无需对比旧文档。
+2026-10-26前端修正单,适用于 `bot-28` 后台报表汇总、资金流动和龙虎榜页面。本文件为当前调整要求,可直接修改页面。
 
-## 页面删除项
+## 功能入口
 
 删除以下三个入口及关联弹窗、表单、事件绑定、保存操作和专门请求:
 
 1. **有效会员最低存款设置**。
-2. **收付款核对**,包括截图中的核对列表、核对按钮及编辑弹窗。
+2. **收付款核对**,包括核对列表、核对按钮及编辑弹窗。
 3. **数据质量**,包括异常列表或来源状态弹窗。
 
-三个功能均不创建菜单、按钮或替代入口。保留营业报表/推广报表/分享赚钱页签、已有筛选项、查询/重置、统计卡片、明细追溯。
+三个功能均不创建菜单、按钮或替代入口。有效会员条件仍由现有“分享赚钱”设置管理;报表直接使用 `valid_member_count`,为 `null` 时显示“—”。菜单、前端路由和权限按钮由前端按接入文档配置。
 
-## 有效会员规则展示
+## 本轮页面展示删除项
 
-有效会员条件统一使用既有“分享赚钱”设置。报表不再维护独立门槛,不新增配置页面,不复制分享赚钱表单。
+三个页面统一删除:
 
-报表接口按照查询结束日的生效版本,使用累计成功存款和累计有效投注两项门槛;均为大于或等于,允许0。查询开始日前的累计数据仍参与资格,待生效配置不能提前使用。当前筛选只限定要统计的会员和分组,不将全局累计资格改成期间金额或单一分类金额。
+1. 筛选区下方整段统计说明与元数据,包括币种、数据更新时间、查询时间、资格门槛政策、活动采集起点、佣金余额快照。
+2. 黄色数据完整性提示区域。
+3. 列表右侧“数据状态/数据不完整”列。
 
-读取响应中的 `valid_member_policy`、`valid_deposit_min`、`valid_effective_bet_min` 回显实际规则。无法计算资格时,`valid_member_count=null` 展示“—”;不要继续显示旧的“期间存款超过20且有一笔投注”文案。
+不增加替代提示、角标、弹窗或独立说明行。接口仍返回 `quality`、`complete`、`known_*` 等字段,页面不渲染,不用已知小计代替最终值;最终值为 `null` 时只显示“—”。`currency` 可用于金额格式,不单独显示说明行。
 
-## 数据完整性提示
+页面保留筛选、查询/重置、卡片与业务指标、明细及分页。报表汇总保留营业/推广/分享赚钱页签、所有订单与游戏卡片、订单和资金追溯;原有效会员数、活动时长等业务列可保留。请求失败仍按 `code` 展示实际 `msg`。
 
-继续根据报表响应里的 `complete`、`quality` 显示简短只读提示。最终金额为null时用“—”;已知小计须标明未完成核实,不把未知金额变成0。提示不带“去核对”“查看数据质量”等操作入口,不为显示提示额外请求核对或诊断接口。
+## 默认全部时间
 
-## 验收
+三个页面默认显示全部时间,日期为空时不传 `start_date`、`end_date`。无需额外标记。全部时间覆盖最早历史日期至北京时间当天,不受366天限制;无历史时覆盖当天。
 
-- 报表页面及菜单中均看不到上述三个入口,旧弹窗无法通过页面操作打开。
-- 正常查询、页签切换、卡片和明细追溯仍可使用。
-- 修改有效会员条件只使用现有分享赚钱页面,报表展示已生效规则。
-- 无政策或数据不完整时保持真实提示和“—”,不生成额外处理页面。
+选择日期区间后同时传起止日期,含首尾最多366天。清空日期或重置恢复全部时间。分页、卡片明细及订单/资金追溯保留原始日期选择;查询全部时继续不传日期。`filters.start_date`、`filters.end_date` 仅回显实际覆盖日期。
 
 [报表汇总完整文档](报表汇总-前端接入.md) · [资金流动完整文档](资金流动-前端接入.md) · [龙虎榜完整文档](龙虎榜-前端接入.md)

+ 33 - 122
docs/总后台/资金流动-前端接入.md

@@ -4,34 +4,44 @@
 
 ## 1. 页面与接口
 
-页面展示全站资金流动,包含日期范围、查询、重置、区间总计、每日列表和分页。筛选区只提供日期,不提供报表类型、装置、游戏、结算状态或账号筛选。
+页面展示全站资金流动,保留时间范围、查询、重置、区间总计、每日列表和分页。筛选区只提供时间,不提供报表类型、装置、游戏、结算状态或账号筛选。
+
+筛选区下方不显示统计说明或元数据整段、黄色数据完整性提示;列表不显示“数据状态/数据不完整”列。币种、数据更新时间、查询时间、资格政策、活动采集起点和佣金余额快照不另作说明行、角标或弹窗。`quality`、`complete`、`known_*` 等字段仍由接口返回,本页不渲染,也不用已知小计代替最终结果;最终值为 `null` 时只显示“—”。
 
 页面不包含“有效会员最低存款设置”“收付款核对”“数据质量”的菜单、按钮、弹窗和表单。
 
 | 方法 | 完整路径 | 用途 | 菜单/按钮权限 URI |
 |---|---|---|---|
-| GET | `/admin/reporting/fund-flow` | 日期区间总计及每日分页列表 | `admin/reporting/fund-flow` |
+| GET | `/admin/reporting/fund-flow` | 时间区间总计及每日分页列表 | `admin/reporting/fund-flow` |
 
 菜单、前端路由和按钮由前端配置,权限 URI 使用上表值。请求使用总后台 API 域名及后台管理员登录令牌。
 
 ## 2. 请求参数
 
+自选日期请求:
+
 ```http
 GET /admin/reporting/fund-flow?start_date=2026-10-26&end_date=2026-10-26&page=1&limit=20
 Authorization: Bearer <admin_token>
 Accept: application/json
 ```
 
-资金页按上述示例提交以下四个参数:
+全部时间请求:
+
+```http
+GET /admin/reporting/fund-flow?page=1&limit=20
+```
 
 | 参数 | 类型 | 默认值与约束 |
 |---|---|---|
-| `start_date` | string | `YYYY-MM-DD`;默认北京时间当天 |
-| `end_date` | string | `YYYY-MM-DD`;默认北京时间当天 |
+| `start_date` | string | 可选;与结束日期一起传,不传日期时查询全部 |
+| `end_date` | string | 可选;`YYYY-MM-DD`,包含结束日 |
 | `page` | integer | 默认 `1`,范围 `1`~`1000000` |
 | `limit` | integer | 默认 `20`,范围 `1`~`100` |
 
-日期采用 `Asia/Shanghai`(北京时间),包含开始日和结束日,含首尾最多 366 天。开始日不得晚于结束日,结束日不得晚于北京时间当天。两端日期一起提交;单日查询使用相同日期。
+默认查询全部时间:不传 `start_date`、`end_date`,日期为空时也按全部处理,覆盖最早历史日期至北京时间当天;无历史时覆盖当天。选择日期区间时同时传 `start_date`、`end_date`,按北京时间包含首尾日查询,最多366天,开始日不得晚于结束日,结束日不得晚于当天。
+
+`filters.start_date`、`filters.end_date` 回显实际覆盖日期。全部时间翻页时继续不传日期,不将回显日期填回筛选。
 
 ## 3. 返回结构与示例
 
@@ -41,19 +51,16 @@ Accept: application/json
 |---|---|---|
 | `code` | integer | `0` 成功,其他值失败 |
 | `timestamp` | integer | 响应 Unix 时间戳,单位秒 |
-| `msg` | string | 响应提示 |
-| `data.filters` | object | 本次查询条件及分页回显 |
-| `data.timezone` | string | 统计时区,当前为 `Asia/Shanghai` |
-| `data.currency` | string | 金额币种,如 `CNY` |
-| `data.data_updated_at` | string 或 null | 数据更新时间;为空显示“更新时间暂不可用” |
-| `data.server_time` | string | 本次响应的服务器北京时间 |
-| `data.summary` | object | 整个日期区间的总计,切换分页仍为全区间总计 |
+| `msg` | string | 失败请求的提示文案 |
+| `data.filters` | object | 本次生效时间范围及分页回显 |
+| `data.currency` | string | 金额格式使用的币种,如 `CNY` |
+| `data.summary` | object | 整个时间区间的总计,切换分页仍为全区间总计 |
 | `data.total` | integer | 区间天数,用于列表分页,包含无业务日 |
 | `data.data` | array | 当前页每日行,按日期倒序排列,包含无业务日 |
 
-每日行的 `date` 为日期列,格式 `YYYY-MM-DD`;`group_id`、`label` 也为对应日期。区间总计的 `group_id` 为 `all`、`label` 为“总计”。总计和日行的金额、完整性字段见下文。
+每日行的 `date` 为日期列,格式 `YYYY-MM-DD`;`group_id`、`label` 也为对应日期。区间总计的 `group_id` 为 `all`、`label` 为“总计”。
 
-完整成功示例:
+以下 JSON 仅展示本页使用字段,是有效响应子集;实际响应可以包含其他字段:
 
 ```json
 {
@@ -62,40 +69,12 @@ Accept: application/json
   "msg": "OK",
   "data": {
     "filters": {
-      "report_type": "business",
-      "platform": "all",
-      "category": "all",
-      "settlement_status": "all",
       "start_date": "2026-10-26",
       "end_date": "2026-10-26",
       "page": 1,
-      "limit": 20,
-      "metric": "deposit_amount"
+      "limit": 20
     },
-    "timezone": "Asia/Shanghai",
     "currency": "CNY",
-    "data_updated_at": "2026-10-26 08:00:00",
-    "activity_started_at": "2026-10-25 00:00:00",
-    "valid_deposit_min": "20.0000000000",
-    "valid_effective_bet_min": "100.0000000000",
-    "valid_member_policy": {
-      "id": 1,
-      "effective_date": "2026-10-01",
-      "min_deposit": "20.0000000000",
-      "min_effective_bet": "100.0000000000",
-      "date": "2026-10-26",
-      "available": true,
-      "unavailable_reasons": []
-    },
-    "commission_balances": {
-      "date_basis": "current_account_balance",
-      "as_of": "2026-10-26 16:00:00",
-      "agent_unpaid": "0.0000000000",
-      "share_unclaimed": "0.0000000000",
-      "share_reserved": "0.0000000000",
-      "share_debt": "0.0000000000"
-    },
-    "server_time": "2026-10-26 16:00:00",
     "summary": {
       "group_id": "all",
       "label": "总计",
@@ -119,18 +98,6 @@ Accept: application/json
       "payout_amount": "60.0000000000",
       "refund_amount": "0.0000000000",
       "game_gross_profit": "40.0000000000",
-      "quality": {
-        "missing_time": 0,
-        "missing_amount": 0,
-        "unverified_fees": 0,
-        "foreign_currency": 0,
-        "unverified_attribution": 0,
-        "sync_errors": 0,
-        "not_synchronized": false
-      },
-      "complete": true,
-      "known_net_cash_flow": "998.0000000000",
-      "known_company_profit": "38.0000000000",
       "net_cash_flow": "998.0000000000",
       "company_profit": "38.0000000000"
     },
@@ -160,18 +127,6 @@ Accept: application/json
         "payout_amount": "60.0000000000",
         "refund_amount": "0.0000000000",
         "game_gross_profit": "40.0000000000",
-        "quality": {
-          "missing_time": 0,
-          "missing_amount": 0,
-          "unverified_fees": 0,
-          "foreign_currency": 0,
-          "unverified_attribution": 0,
-          "sync_errors": 0,
-          "not_synchronized": false
-        },
-        "complete": true,
-        "known_net_cash_flow": "998.0000000000",
-        "known_company_profit": "38.0000000000",
         "net_cash_flow": "998.0000000000",
         "company_profit": "38.0000000000"
       }
@@ -184,7 +139,7 @@ Accept: application/json
 
 ## 4. 金额字段与展示
 
-总计和每日行使用相同金额字段。金额为十进制字符串,保留原始值,以十进制方式格式化为两位小数;负值保留负号。`null` 显示“—”,确认的零显示 `0.00`。汇总展示接口返回的 `summary`,不累计当前页行金额。
+总计和每日行使用相同金额字段。金额为十进制字符串,保留原始值,以十进制方式格式化为两位小数;负值保留负号。`null` 显示“—”,数值为零时显示 `0.00`。汇总展示接口返回的 `summary`,不累计当前页行金额。`currency` 仅用于金额格式,不另作说明行。
 
 | 字段 | 展示名称 | 含义 |
 |---|---|---|
@@ -225,63 +180,19 @@ Accept: application/json
 
 客户手续费收入、配置手续费参考值、应计佣金和人工余额调整均单独展示,不重复加入上述计算。投注总额按下单日统计,游戏毛利按实际结算日统计;各结果直接展示对应返回字段。
 
-## 5. 完整性与其他返回字段
-
-| 字段 | 类型 | 展示方式 |
-|---|---|---|
-| `complete` | boolean | 总计和每日行分别判断;`false` 显示“数据不完整” |
-| `net_cash_flow` / `company_profit` | string 或 null | 最终值;为 null 时显示“—” |
-| `known_net_cash_flow` | string | 已知净入金小计;数据不完整时可附“已知小计,数据不完整”展示 |
-| `known_company_profit` | string | 已知公司净收益小计;数据不完整时可附“已知小计,数据不完整”展示 |
-| `quality` | object | 当前总计或日行的只读缺失说明 |
-
-| `quality` 字段 | 类型 | 展示说明 |
-|---|---|---|
-| `missing_time` | integer | 缺少业务发生/结算时间的记录数 |
-| `missing_amount` | integer | 缺少金额的记录数 |
-| `unverified_fees` | integer | 实际平台手续费尚未确认的记录数 |
-| `foreign_currency` | integer | 无法直接计入的非本位币记录数 |
-| `unverified_attribution` | integer | 历史归属尚未确认的记录数 |
-| `sync_errors` | integer | 数据异常记录数 |
-| `not_synchronized` | boolean | `true` 显示“数据尚未准备完成” |
-
-`complete=false` 时,已返回的其他金额作为已知金额展示,并附缺失说明;零金额仍需保留对应的未知费用等状态。缺失说明在本页只读展示。总计的缺失状态使用 `summary.quality`,每日使用本行 `quality`,不累计每日计数。
-
-以下字段位于外层业务 `data` 中,资金页只兼容接收,不生成设置控件或每日统计列:
-
-| 字段 | 类型/含义 |
-|---|---|
-| `activity_started_at` | string,活动统计开始时间 |
-| `valid_deposit_min` | string 或 null,有效会员累计存款门槛 |
-| `valid_effective_bet_min` | string 或 null,有效会员累计有效投注门槛 |
-| `valid_member_policy` | object,资格规则:`id`、`effective_date`、`min_deposit`、`min_effective_bet`、`date`、`available`、`unavailable_reasons`;`date` 对应本次结束日 |
-| `commission_balances` | object,查询时佣金余额快照,结构见下表 |
-
-| `commission_balances` 字段 | 含义 |
-|---|---|
-| `date_basis` | 固定 `current_account_balance` |
-| `as_of` | 余额快照时间 |
-| `agent_unpaid` | 未付代理佣金,金额字符串或 null |
-| `share_unclaimed` | 未领取分享佣金,包含预留部分,金额字符串或 null |
-| `share_reserved` | 已预留分享佣金,金额字符串或 null |
-| `share_debt` | 分享佣金欠款,金额字符串或 null |
-
-佣金余额快照不分摊到每日,也不计入期间净入金或公司净收益。每日列表和区间总计均不提供在线人数、有效会员数、活跃人数、活跃率或活动时长列。
-
-## 6. 页面交互与错误处理
+## 5. 页面交互与错误处理
 
 | 操作/状态 | 页面行为 |
 |---|---|
-| 首次进入 | 北京时间当天,`page=1`、`limit=20`,加载总计和每日列表 |
-| 查询 | 校验日期后以新日期和 `page=1` 请求;结果对应已提交的日期 |
-| 重置 | 日期恢复北京时间当天,`page=1`、`limit=20`,重新查询 |
-| 分页 | 保持已提交日期,改变页码;调整每页条数后回到第 1 页 |
-| 刷新 | 重新请求当前已提交日期和页码,同时更新总计、列表及更新时间 |
+| 首次进入 | 日期为空,查询全部时间,`page=1`、`limit=20`,加载总计和每日列表 |
+| 查询 | 以已选择的时间模式和 `page=1` 请求;全部时间不传日期 |
+| 重置 | 清空日期,恢复全部时间、`page=1`、`limit=20`,重新查询 |
+| 分页 | 保持已提交的时间模式,改变页码;调整每页条数后回到第1页 |
+| 刷新 | 重新请求当前已提交条件和页码,同时更新总计、列表 |
 | 加载 | 显示加载状态并防止重复提交;快速连续查询以最后一次请求结果为准 |
-| 无业务日 | 保留每日行;完整且金额为零时展示 `0.00`,有缺失时展示相应状态 |
+| 无业务日 | 保留每日行;金额为零时展示 `0.00`,`null` 显示“—” |
 | 空页 | `data.data=[]` 时展示“当前页暂无数据”,保留区间总计,允许返回有效页 |
-| 首次请求失败 | 展示错误信息和重试操作,结束加载状态 |
-| 刷新失败 | 可保留上次结果,标明“刷新失败,当前为上次结果”,保留其日期及更新时间 |
+| 请求失败 | 展示失败文案和重试操作,结束加载状态;不将失败结果替换成零金额 |
 
 参数或服务异常的返回示例:
 
@@ -301,4 +212,4 @@ Accept: application/json
 | `code=-1` 且 `msg="无权限访问"` | 展示无权限状态 |
 | 网络失败、非 JSON 响应或 HTTP 错误 | 使用请求层错误状态,结束加载,提供重试 |
 
-服务异常的提示为“报表暂不可用,请稍后重试”。业务是否成功按外层 `code` 判断;失败结果不转换为零金额或空业务数据。
+服务异常的提示为“报表暂不可用,请稍后重试”。业务是否成功按外层 `code` 判断。

+ 27 - 87
docs/总后台/龙虎榜-前端接入.md

@@ -1,6 +1,8 @@
 # 龙虎榜:前端接入
 
-项目:`bot-28` 总后台。页面展示会员排名,固定营业范围 `business`,筛选包含日期和排名项目,下方为列表与分页。
+项目:`bot-28` 总后台。页面展示会员排名,固定营业范围 `business`,筛选包含时间范围和排名项目,下方为列表与分页。
+
+筛选区下方不显示统计说明或元数据整段、黄色数据完整性提示;列表不显示“数据状态/数据不完整”列。币种、数据更新时间、查询时间、资格政策、活动采集起点和佣金余额快照不另作说明行、角标或弹窗。`quality`、`complete`、`known_*` 等字段仍由接口返回,本页不渲染,也不用已知小计代替最终结果;最终值为 `null` 时只显示“—”。
 
 ## 接口与认证
 
@@ -20,7 +22,7 @@ Accept: application/json
 | `GET /admin/reporting/orders` | 已有游戏交易追溯 | `admin/reporting/summary` |
 | `GET /admin/reporting/events` | 已有资金记录追溯 | `admin/reporting/summary` |
 
-本页主请求为 `leaderboard`;公共选项和已有追溯入口使用各自权限控制。
+本页主请求为 `leaderboard`;公共选项和已有追溯入口使用各自权限控制。若打开会员统计或订单、资金追溯,保留当前日期条件及会员分组;`details`、`orders`、`events` 均回传 `filters`,全部时间追溯不传日期。
 
 ## 请求参数
 
@@ -30,13 +32,19 @@ GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metri
 
 | 参数 | 类型 | 默认值 | 说明 |
 |---|---|---|---|
-| `start_date` | string | 北京时间当天 | `YYYY-MM-DD`,包含开始日 |
-| `end_date` | string | 北京时间当天 | `YYYY-MM-DD`,包含结束日 |
+| `start_date` | string | 不限定 | 与结束日期一起传;`YYYY-MM-DD` |
+| `end_date` | string | 不限定 | 与开始日期一起传,包含结束日 |
 | `metric` | string | `deposit_amount` | 排名项目,见下表 |
 | `page` | integer | `1` | 页码,范围 `1`~`1000000` |
 | `limit` | integer | `20` | 每页条数,范围 `1`~`100` |
 
-日期使用 `Asia/Shanghai`(UTC+8),起止日期一起提交;开始日不晚于结束日,结束日不晚于当天,包含首尾日最多366天。本页不传 `report_type`,响应固定为 `business`。
+默认查询全部时间:不传 `start_date`、`end_date`,日期为空时也按全部处理,覆盖最早历史日期至北京时间当天;无历史时覆盖当天。选择日期区间时同时传 `start_date`、`end_date`,按北京时间包含首尾日查询,最多366天,开始日不得晚于结束日,结束日不得晚于当天。
+
+```http
+GET /admin/reporting/leaderboard?metric=deposit_amount&page=1&limit=20
+```
+
+`filters.start_date`、`filters.end_date` 回显实际覆盖日期;全部时间翻页仍不传日期。本页不传 `report_type`,响应固定为 `business`。
 
 | `metric` | 项目/列标题 | 含义 |
 |---|---|---|
@@ -61,18 +69,12 @@ GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metri
 
 业务错误也可能返回 HTTP 200,以 `code` 判断结果。参数错误的 `msg` 是第一条校验文案;报表暂不可用的文案为“报表暂不可用,请稍后重试”。
 
-业务对象字段:
+本页使用的业务对象字段:
 
 | 字段 | 类型 | 说明 |
 |---|---|---|
-| `filters` | object | 本次生效的筛选;含日期、项目、分页和默认范围 `report_type=business`、`platform=all`、`category=all`、`settlement_status=all` |
-| `timezone` / `currency` | string | 时间区与钱包本位币,如 `Asia/Shanghai`、`CNY` |
-| `data_updated_at` | string/null | 数据更新时间;空值显示“尚未完成同步” |
-| `server_time` | string | 查询时间 |
-| `activity_started_at` | string | 活动时长采集开始时间 |
-| `valid_deposit_min` / `valid_effective_bet_min` | 金额字符串/null | 有效会员的累计存款/累计有效投注门槛,只读 |
-| `valid_member_policy` | object | 查询结束日生效的分享赚钱资格政策,只读;`available` 表示是否可用,`date` 为资格截止日,`effective_date` 为生效日,`min_deposit`/`min_effective_bet` 为两项门槛 |
-| `commission_balances` | object | 查询时佣金账户余额;`date_basis`、`as_of` 表示余额口径与时间,金额字段为 `agent_unpaid`、`share_unclaimed`、`share_reserved`、`share_debt`,可为 `null` |
+| `filters` | object | 本次生效的筛选;含实际覆盖日期、项目、分页和默认范围 `report_type=business`、`platform=all`、`category=all`、`settlement_status=all` |
+| `currency` | string | 金额格式使用的本位币,如 `CNY` |
 | `total` | integer | 当前项目的会员总数 |
 | `data` | array | 当前页列表,完整路径为外层 `data.data` |
 | `unallocated` | object/null | 未分配公共资金与成本;不进入会员列表,不计入 `total` |
@@ -91,7 +93,7 @@ GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metri
 | `withdraw_amount` | 金额字符串/null | 提款总额 |
 | `activity_minutes` | integer/null | 活动时长,单位分钟;未知为 `null` |
 
-其他返回字段:
+可保留的其他业务列:
 
 | 字段 | 含义 |
 |---|---|
@@ -102,15 +104,15 @@ GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metri
 | `agent_accrual`、`share_accrual` | 应计代理/分享佣金 |
 | `manual_credit`、`manual_debit` | 人工上分/扣分 |
 | `settled_bet_amount`、`refund_amount`、`game_gross_profit` | 已结算投注净额、退款、游戏毛利 |
-| `company_profit`、`net_cash_flow` | 公司净收益、实际净入金;数据不完整时为 `null` |
-| `known_company_profit`、`known_net_cash_flow` | 已知小计,展示名称带“未完成核实” |
+| `company_profit`、`net_cash_flow` | 公司净收益、实际净入金;`null` 显示“—” |
 | `member_count`、`valid_member_count`、`active_member_count` | 会员参考人数、有效会员人数、期间活跃会员数;有效会员无法计算时为 `null` |
-| `known_activity_minutes`、`active_rate`、`activity_population_complete` | 已采集分钟、期间活跃占比、活动人口范围完整性;占比 `"0.250000"` 表示25% |
+| `active_rate` | 期间活跃占比,如 `"0.250000"` 显示25%;`null` 显示“—” |
 | `order_count` | 订单数 |
-| `complete`、`quality` | 完整性状态;`complete=false` 时显示简短只读说明,`quality` 为具体状态标志 |
 
 ## 返回示例
 
+以下 JSON 仅展示本页使用字段,是有效响应子集;实际响应可以包含其他字段:
+
 ```json
 {
   "code": 0,
@@ -122,36 +124,13 @@ GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metri
       "platform": "all",
       "category": "all",
       "settlement_status": "all",
-      "start_date": "2026-10-26",
+      "start_date": "2024-01-01",
       "end_date": "2026-10-26",
       "page": 1,
       "limit": 20,
       "metric": "deposit_amount"
     },
-    "timezone": "Asia/Shanghai",
     "currency": "CNY",
-    "data_updated_at": "2026-10-26 08:00:00",
-    "activity_started_at": "2026-10-25 00:00:00",
-    "valid_deposit_min": "20.0000000000",
-    "valid_effective_bet_min": "100.0000000000",
-    "valid_member_policy": {
-      "id": 1,
-      "effective_date": "2026-10-01",
-      "min_deposit": "20.0000000000",
-      "min_effective_bet": "100.0000000000",
-      "date": "2026-10-26",
-      "available": true,
-      "unavailable_reasons": []
-    },
-    "commission_balances": {
-      "date_basis": "current_account_balance",
-      "as_of": "2026-10-26 16:00:00",
-      "agent_unpaid": "0.0000000000",
-      "share_unclaimed": "0.0000000000",
-      "share_reserved": "0.0000000000",
-      "share_debt": "0.0000000000"
-    },
-    "server_time": "2026-10-26 16:00:00",
     "total": 1,
     "data": [
       {
@@ -159,68 +138,29 @@ GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metri
         "label": "member2",
         "deposit_amount": "1000.0000000000",
         "withdraw_amount": "0.0000000000",
-        "member_withdraw_amount": "0.0000000000",
-        "agent_withdraw_amount": "0.0000000000",
-        "deposit_fee": "2.0000000000",
-        "withdraw_fee": "0.0000000000",
-        "customer_fee_income": "0.0000000000",
-        "configured_fee": "0.0000000000",
-        "bonus_amount": "0.0000000000",
-        "agent_commission": "0.0000000000",
-        "share_commission": "0.0000000000",
-        "agent_accrual": "0.0000000000",
-        "share_accrual": "0.0000000000",
-        "manual_credit": "0.0000000000",
-        "manual_debit": "0.0000000000",
         "bet_amount": "100.0000000000",
-        "settled_bet_amount": "100.0000000000",
         "payout_amount": "60.0000000000",
-        "refund_amount": "0.0000000000",
-        "game_gross_profit": "40.0000000000",
-        "quality": {
-          "missing_time": 0,
-          "missing_amount": 0,
-          "unverified_fees": 0,
-          "foreign_currency": 0,
-          "unverified_attribution": 0,
-          "sync_errors": 0,
-          "not_synchronized": false
-        },
         "member_account": "member2",
         "nickname": "昵称2",
-        "agent_account": "agent10",
-        "inviter_account": "member1",
-        "member_count": 1,
         "valid_member_count": 1,
         "active_member_count": 0,
-        "activity_population_complete": true,
-        "known_activity_minutes": 0,
         "activity_minutes": 0,
         "active_rate": "0.000000",
-        "order_count": 1,
-        "complete": true,
-        "known_net_cash_flow": "998.0000000000",
-        "known_company_profit": "38.0000000000",
-        "net_cash_flow": "998.0000000000",
-        "company_profit": "38.0000000000",
         "rank": 1
       }
-    ],
-    "unallocated": null
+    ]
   }
 }
 ```
 
 ## 页面交互与展示
 
-初次进入使用北京时间当天、存款总额、第一页、每页20条。查询或切换日期、排名项目、每页条数时回到第一页;翻页保持当前筛选。重置恢复初始值。项目切换显示加载状态,快速切换时以最后一次请求结果更新列表。
+初次进入日期为空,查询全部时间,使用存款总额、第一页、每页20条。选择日期后按区间查询;查询、切换排名项目或每页条数时回到第一页,翻页保留原始日期条件。重置清空日期并恢复初始值。快速切换时以最后一次请求结果更新列表。
 
 成功后使用 `data.data` 渲染列表、`data.total` 渲染分页,空数组显示“暂无数据”。请求失败显示 `msg` 和重试入口,登录失效与无权限使用后台对应状态。
 
-金额按 `currency` 展示为两位小数,真实0显示 `0.00`,`null` 显示“—”。表头使用“投注总额/派奖总额”,分别取 `bet_amount`/`payout_amount`。名次直接取 `rank`,页面保持接口顺序。
-
-活动时长使用“分钟”,真实0显示“0分钟”,`null` 显示“—”。查询范围早于 `activity_started_at` 时,`activity_minutes` 可能为 `null`;`known_activity_minutes` 仅表示已有采集部分。活动时长是区间估算值,页面名称使用“活动时长”。
+`currency` 仅用于金额格式,不另作说明行。金额展示为两位小数,数值为0时显示 `0.00`,`null` 显示“—”。表头使用“投注总额/派奖总额”,分别取 `bet_amount`/`payout_amount`。名次直接取 `rank`,页面保持接口顺序。
 
-列表上方显示 `data_updated_at` 为“数据更新时间”;如展示 `server_time`,标为“查询时间”。有效会员条件仅作只读回显,使用 `valid_member_policy` 中的实际值。
+活动时长使用“分钟”,数值为0时显示“0分钟”,`null` 显示“—”。活动时长是区间估算值,页面名称使用“活动时长”。
 
-页面范围为日期、项目、查询/重置、榜单和分页;不包含“有效会员最低存款设置”“收付款核对”“数据质量”入口。
+页面范围为时间范围、项目、查询/重置、榜单和分页;不包含“有效会员最低存款设置”“收付款核对”“数据质量”入口。

+ 3 - 1
docs/总后台/龙虎榜资金流动与报表-接口.md

@@ -10,6 +10,8 @@
 
 服务项目为 `bot-28`,使用后台 API 域名和管理员 Token。
 
-三个页面均去掉“有效会员最低存款设置”“收付款核对”“数据质量”按钮及关联弹窗。有效会员规则由现有分享赚钱设置提供,页面按接口返回值展示。
+三个页面默认查询全部时间,不传起止日期;选择日期后按区间查询。页面保留筛选、查询/重置、卡片与业务指标、明细及分页;去掉筛选下方统计说明与元数据、黄色完整性提示、列表数据状态列,不新增替代提示。
+
+三个页面均去掉“有效会员最低存款设置”“收付款核对”“数据质量”按钮及关联弹窗。有效会员人数直接使用接口结果;条件管理仍在现有分享赚钱设置。
 
 页面菜单和权限按钮由前端按各文档中的权限 URI 配置。

+ 182 - 56
tests/Integration/ReportingTest.php

@@ -78,6 +78,7 @@ class ReportingTest extends TestCase
         $this->ready();
     }
     protected function tearDown(): void { Carbon::setTestNow(); DB::disconnect(); Facade::clearResolvedInstances(); parent::tearDown(); }
+    private function today(array $filters = []): array { return $filters + ['start_date' => now('Asia/Shanghai')->toDateString(), 'end_date' => now('Asia/Shanghai')->toDateString()]; }
     private function ready(): void { DB::table('reporting_sync_state')->updateOrInsert(['source' => '__run__'], ['state' => '{"complete":true}', 'updated_at' => now()]); }
     private function context(string $source, int $id, int $user = 2, string $platform = 'H5', ?string $success = null): void
     {
@@ -108,7 +109,7 @@ class ReportingTest extends TestCase
     {
         $this->bet(1, ['created_at' => '2026-10-25 23:00:00']); $this->sync->publish('bets', 1);
         $this->assertSame(1, DB::table('reporting_orders')->count());
-        $row = $this->reader->summary([])['summary'];
+        $row = $this->reader->summary($this->today())['summary'];
         $this->assertSame('0.0000000000', $row['bet_amount']); $this->assertSame('100.0000000000', $row['settled_bet_amount']);
         $this->assertSame('60.0000000000', $row['payout_amount']); $this->assertSame('40.0000000000', $row['game_gross_profit']);
         $this->assertSame('40.0000000000', $row['company_profit']);
@@ -119,28 +120,28 @@ class ReportingTest extends TestCase
     public function test_missing_fees_and_success_time_never_produce_fabricated_net_values(): void
     {
         $event = $this->deposit(); $this->bet();
-        $row = $this->reader->summary([])['summary']; $this->assertNull($row['net_cash_flow']); $this->assertNull($row['company_profit']);
+        $row = $this->reader->summary($this->today())['summary']; $this->assertNull($row['net_cash_flow']); $this->assertNull($row['company_profit']);
         $this->assertSame(1, $row['quality']['unverified_fees']); $this->assertSame('1000.0000000000', $row['deposit_amount']);
-        $this->verify($event); $row = $this->reader->summary([])['summary'];
+        $this->verify($event); $row = $this->reader->summary($this->today())['summary'];
         $this->assertSame('998.0000000000', $row['net_cash_flow']); $this->assertSame('38.0000000000', $row['company_profit']);
         $before = DB::table('balance_logs')->get()->toArray(); $this->verify($event, '1000', '3'); $this->sync->publish('balance_logs', 1);
         $this->assertSame(1, DB::table('reporting_reconciliations')->count()); $this->assertEquals($before, DB::table('balance_logs')->get()->toArray());
-        $this->assertSame('997.0000000000', $this->reader->summary([])['summary']['net_cash_flow']);
+        $this->assertSame('997.0000000000', $this->reader->summary($this->today())['summary']['net_cash_flow']);
     }
 
     public function test_chain_withdraw_uses_recipient_amount_and_customer_fee_is_not_an_expense(): void
     {
         DB::table('withdraws')->insert(['id' => 1, 'member_id' => '200', 'amount' => '100', 'to_account' => '98', 'service_charge' => '2', 'exchange_rate' => '7', 'status' => 1, 'created_at' => '2026-10-25 08:00:00', 'updated_at' => '2026-10-26 10:00:00']);
-        $this->sync->publish('withdraws', 1); $row = $this->reader->summary([])['summary'];
+        $this->sync->publish('withdraws', 1); $row = $this->reader->summary($this->today())['summary'];
         $this->assertSame(1, $row['quality']['missing_time']); $this->assertNull($row['net_cash_flow']);
         $event = (int) DB::table('reporting_events')->value('id'); $this->verify($event, '686', '1');
-        $row = $this->reader->summary([])['summary'];
+        $row = $this->reader->summary($this->today())['summary'];
         $this->assertSame('686.0000000000', $row['withdraw_amount']); $this->assertSame('14.0000000000', $row['customer_fee_income']);
         $this->assertSame('1.0000000000', $row['withdraw_fee']); $this->assertSame('-687.0000000000', $row['net_cash_flow']);
         DB::table('withdraws')->where('id', 1)->update(['status' => 2]); $result = $this->sync->sync(true);
-        $this->assertSame(1, $result['errors']); $this->assertNull($this->reader->summary([])['summary']['net_cash_flow']);
+        $this->assertSame(1, $result['errors']); $this->assertNull($this->reader->summary($this->today())['summary']['net_cash_flow']);
         $this->verify($event, '0', '1'); $this->sync->sync(true);
-        $this->assertSame('0.0000000000', $this->reader->summary([])['summary']['withdraw_amount']);
+        $this->assertSame('0.0000000000', $this->reader->summary($this->today())['summary']['withdraw_amount']);
     }
 
     public function test_real_member_agent_sharing_overlap_is_not_added_twice_and_details_are_direct(): void
@@ -150,22 +151,22 @@ class ReportingTest extends TestCase
             $row = $this->reader->summary(['report_type' => $type])['summary']; $this->assertSame('100.0000000000', $row['bet_amount']);
             $this->assertSame('1000.0000000000', $row['deposit_amount']);
         }
-        $this->assertSame('2', $this->reader->details([])['data'][0]['group_id']);
+        $this->assertSame('2', $this->reader->details($this->today())['data'][0]['group_id']);
         $this->assertSame('10', $this->reader->details(['report_type' => 'promotion'])['data'][0]['group_id']);
         $this->assertSame('1', $this->reader->details(['report_type' => 'sharing'])['data'][0]['group_id']);
-        $category = $this->reader->summary([])['categories'][8]; $this->assertNull($category['deposit_amount']); $this->assertNull($category['company_profit']);
+        $category = $this->reader->summary($this->today())['categories'][8]; $this->assertNull($category['deposit_amount']); $this->assertNull($category['company_profit']);
         $this->assertSame('100.0000000000', $category['bet_amount']);
     }
 
     public function test_valid_members_use_both_sharing_thresholds_inclusively(): void
     {
         $this->bet(); $this->deposit(1, '19.99'); $fact = $this->qualificationFact('100');
-        $this->assertSame(0, $this->reader->summary([])['summary']['valid_member_count']);
+        $this->assertSame(0, $this->reader->summary($this->today())['summary']['valid_member_count']);
         DB::table('balance_logs')->where('id', 1)->update(['amount' => '20']);
         DB::table('share_bet_facts')->where('id', $fact)->update(['effective_amount' => '99.99']);
-        $this->assertSame(0, $this->reader->summary([])['summary']['valid_member_count']);
+        $this->assertSame(0, $this->reader->summary($this->today())['summary']['valid_member_count']);
         DB::table('share_bet_facts')->where('id', $fact)->update(['effective_amount' => '100']);
-        $report = $this->reader->summary([]);
+        $report = $this->reader->summary($this->today());
         $this->assertSame(1, $report['summary']['valid_member_count']);
         $this->assertSame('20.0000000000', $report['valid_deposit_min']);
         $this->assertSame('100.0000000000', $report['valid_effective_bet_min']);
@@ -174,7 +175,7 @@ class ReportingTest extends TestCase
         // Presentation switches and the retired report-only value do not change qualification.
         DB::table('reporting_settings')->where('id', 1)->update(['valid_deposit_min' => '9999']);
         DB::table('share_policies')->update(['enabled' => 0, 'benefit_enabled' => 0]);
-        $this->assertSame(1, $this->reader->summary([])['summary']['valid_member_count']);
+        $this->assertSame(1, $this->reader->summary($this->today())['summary']['valid_member_count']);
     }
 
     public function test_qualification_is_cumulative_before_start_and_independent_of_report_filters_and_payment_verification(): void
@@ -210,18 +211,18 @@ class ReportingTest extends TestCase
         $this->qualificationFact('1000', '2026-10-25', 2, ['settled' => 0]);
         $this->qualificationFact('1000', '2026-10-25', 2, ['voided' => 1]);
         $this->qualificationFact('1000', '2026-10-25', 2, ['currency' => 'USDT']);
-        $this->assertSame(0, $this->reader->summary([])['summary']['valid_member_count']);
+        $this->assertSame(0, $this->reader->summary($this->today())['summary']['valid_member_count']);
         DB::table('balance_logs')->insert(['member_id' => '200', 'type' => 1, 'change_type' => '三方充值', 'amount' => '1', 'created_at' => '2026-10-26 23:59:59']);
-        $this->assertSame(0, $this->reader->summary([])['summary']['valid_member_count']);
+        $this->assertSame(0, $this->reader->summary($this->today())['summary']['valid_member_count']);
         DB::table('share_bet_facts')->where('id', $fact)->update(['effective_amount' => '100']);
-        $this->assertSame(1, $this->reader->summary([])['summary']['valid_member_count']);
+        $this->assertSame(1, $this->reader->summary($this->today())['summary']['valid_member_count']);
     }
 
     public function test_pending_sharing_policy_never_applies_before_its_effective_date(): void
     {
         $this->bet(); $this->deposit(1, '20'); $this->qualificationFact();
         DB::table('share_policies')->insert(['effective_date' => '2026-10-27', 'valid_member_min_deposit' => '200', 'valid_member_min_effective_bet' => '1000', 'rules' => '{}']);
-        $report = $this->reader->summary([]);
+        $report = $this->reader->summary($this->today());
         $this->assertSame(1, $report['summary']['valid_member_count']); $this->assertSame(1, $report['valid_member_policy']['id']);
         $this->assertSame('20.0000000000', $this->admin->settings()['valid_deposit_min']);
         Carbon::setTestNow(Carbon::parse('2026-10-27 16:00:00', 'Asia/Shanghai'));
@@ -235,7 +236,7 @@ class ReportingTest extends TestCase
     {
         $this->bet(1, ['status' => 1, 'profit' => '0']);
         DB::table('share_policies')->update(['valid_member_min_deposit' => '0', 'valid_member_min_effective_bet' => '0']);
-        $report = $this->reader->summary([]);
+        $report = $this->reader->summary($this->today());
         $this->assertSame(1, $report['summary']['valid_member_count']); $this->assertSame(4, $report['summary']['member_count']);
         $this->assertSame('0.0000000000', $report['valid_deposit_min']); $this->assertSame('0.0000000000', $report['valid_effective_bet_min']);
         $this->assertSame(0, $this->reader->summary(['settlement_status' => 'settled'])['summary']['valid_member_count']);
@@ -245,22 +246,22 @@ class ReportingTest extends TestCase
     {
         $this->bet(); $this->deposit(); $this->qualificationFact();
         DB::table('share_policies')->update(['valid_member_min_effective_bet' => null]);
-        $report = $this->reader->summary([]);
+        $report = $this->reader->summary($this->today());
         $this->assertNull($report['summary']['valid_member_count']); $this->assertNull($report['valid_effective_bet_min']);
         $this->assertFalse($report['valid_member_policy']['available']);
         $this->assertContains('incomplete_qualification_policy', $report['valid_member_policy']['unavailable_reasons']);
         DB::table('share_policies')->delete();
-        $report = $this->reader->summary([]);
+        $report = $this->reader->summary($this->today());
         $this->assertNull($report['summary']['valid_member_count']); $this->assertNull($report['valid_deposit_min']);
         $this->assertContains('no_effective_policy', $report['valid_member_policy']['unavailable_reasons']);
         DB::table('share_policies')->insert(['effective_date' => '2026-10-01', 'valid_member_min_deposit' => '20', 'valid_member_min_effective_bet' => '100', 'rules' => '{}']);
         DB::table('share_system_state')->delete();
-        $report = $this->reader->summary([]);
+        $report = $this->reader->summary($this->today());
         $this->assertNull($report['summary']['valid_member_count']);
         $this->assertContains('relations_not_initialized', $report['valid_member_policy']['unavailable_reasons']);
         DB::table('share_system_state')->insert(['key' => 'initialized_at', 'value' => '2026-10-01']);
         Schema::drop('share_bet_facts');
-        $report = $this->reader->summary([]);
+        $report = $this->reader->summary($this->today());
         $this->assertNull($report['summary']['valid_member_count']);
         $this->assertContains('missing_table:share_bet_facts', $report['valid_member_policy']['unavailable_reasons']);
     }
@@ -272,8 +273,8 @@ class ReportingTest extends TestCase
             'balance_logs', 'share_accounts', 'share_settlements', 'share_ledger', 'share_claims', 'operation_audits'];
         $before = []; foreach ($tables as $table) $before[$table] = DB::table($table)->get()->toJson();
         for ($i = 0; $i < 2; ++$i) {
-            $this->assertSame(1, $this->reader->summary([])['summary']['valid_member_count']);
-            $this->assertSame(1, $this->reader->details([])['data'][0]['valid_member_count']);
+            $this->assertSame(1, $this->reader->summary($this->today())['summary']['valid_member_count']);
+            $this->assertSame(1, $this->reader->details($this->today())['data'][0]['valid_member_count']);
             $settings = $this->admin->settings();
             $this->assertSame('20.0000000000', $settings['valid_deposit_min']);
             $this->assertSame('100.0000000000', $settings['valid_effective_bet_min']);
@@ -302,7 +303,7 @@ class ReportingTest extends TestCase
         foreach ([['a', '10:00:00'], ['b', '10:00:00'], ['a', '10:01:00'], ['b', '10:01:00'], ['a', '10:20:00'], ['a', '10:21:00']] as [$session, $at]) {
             DB::table('reporting_activity')->insert(['user_id' => 2, 'agent_id' => 10, 'inviter_id' => 1, 'client_platform' => 'H5', 'session_hash' => $session, 'observed_at' => '2026-10-26 ' . $at]);
         }
-        $row = $this->reader->summary([])['summary'];
+        $row = $this->reader->summary($this->today())['summary'];
         $this->assertSame(2, $row['activity_minutes']); $this->assertSame(1, $row['active_member_count']); $this->assertSame(4, $row['member_count']);
         $this->assertSame('0.250000', $row['active_rate']);
         $old = $this->reader->summary(['start_date' => '2026-10-24', 'end_date' => '2026-10-26'])['summary'];
@@ -327,24 +328,24 @@ class ReportingTest extends TestCase
     public function test_activity_snapshot_survives_agent_reassignment_in_summary_details_and_filters(): void
     {
         $this->activityObservation('2026-10-26 10:00:00'); $this->activityObservation('2026-10-26 10:02:00');
-        $before = $this->reader->summary(['report_type' => 'promotion', 'agent_account' => 'agent10'])['summary'];
+        $before = $this->reader->summary($this->today(['report_type' => 'promotion', 'agent_account' => 'agent10']))['summary'];
         DB::table('users')->where('id', 2)->update(['agent_id' => 20]);
-        $after = $this->reader->summary(['report_type' => 'promotion', 'agent_account' => 'agent10'])['summary'];
+        $after = $this->reader->summary($this->today(['report_type' => 'promotion', 'agent_account' => 'agent10']))['summary'];
         $this->assertSame(2, $before['activity_minutes']); $this->assertSame(2, $after['activity_minutes']);
         $this->assertSame(1, $after['active_member_count']); $this->assertFalse($after['activity_population_complete']); $this->assertNull($after['active_rate']);
-        $other = $this->reader->summary(['report_type' => 'promotion', 'agent_account' => 'agent20'])['summary'];
+        $other = $this->reader->summary($this->today(['report_type' => 'promotion', 'agent_account' => 'agent20']))['summary'];
         $this->assertSame(0, $other['activity_minutes']); $this->assertSame(0, $other['active_member_count']);
-        $details = $this->reader->details(['report_type' => 'promotion']);
+        $details = $this->reader->details($this->today(['report_type' => 'promotion']));
         $this->assertSame(1, $details['total']); $this->assertSame('10', $details['data'][0]['group_id']); $this->assertSame(2, $details['data'][0]['activity_minutes']);
-        $byGroup = $this->reader->summary(['report_type' => 'promotion', 'group_id' => '10'])['summary'];
+        $byGroup = $this->reader->summary($this->today(['report_type' => 'promotion', 'group_id' => '10']))['summary'];
         $this->assertSame(2, $byGroup['activity_minutes']);
-        $business = $this->reader->summary(['agent_account' => 'agent10'])['summary']; $this->assertSame(2, $business['activity_minutes']);
+        $business = $this->reader->summary($this->today(['agent_account' => 'agent10']))['summary']; $this->assertSame(2, $business['activity_minutes']);
 
         $this->activityObservation('2026-10-26 11:00:00', 20); $this->activityObservation('2026-10-26 11:03:00', 20);
-        $groups = collect($this->reader->details(['report_type' => 'promotion'])['data'])->keyBy('group_id');
+        $groups = collect($this->reader->details($this->today(['report_type' => 'promotion']))['data'])->keyBy('group_id');
         $this->assertSame(2, $groups[10]['activity_minutes']); $this->assertSame(3, $groups[20]['activity_minutes']);
         $this->assertSame('0.500000', $groups[20]['active_rate']);
-        $all = $this->reader->summary([])['summary']; $this->assertSame(5, $all['activity_minutes']); $this->assertSame(1, $all['active_member_count']);
+        $all = $this->reader->summary($this->today())['summary']; $this->assertSame(5, $all['activity_minutes']); $this->assertSame(1, $all['active_member_count']);
         $this->assertSame('0.250000', $all['active_rate']);
     }
 
@@ -355,45 +356,45 @@ class ReportingTest extends TestCase
         $this->activityObservation('2026-10-26 10:02:00', 20, 1, 'a');
         $this->activityObservation('2026-10-26 10:03:00', 20, 1, 'b');
         DB::table('users')->where('id', 2)->update(['agent_id' => 20]);
-        $groups = collect($this->reader->details(['report_type' => 'promotion'])['data'])->keyBy('group_id');
+        $groups = collect($this->reader->details($this->today(['report_type' => 'promotion']))['data'])->keyBy('group_id');
         $this->assertSame(1, $groups[10]['activity_minutes']); $this->assertSame(2, $groups[20]['activity_minutes']);
-        $all = $this->reader->summary([])['summary']; $this->assertSame(3, $all['activity_minutes']); $this->assertSame(1, $all['active_member_count']);
-        $this->assertSame(1, $this->reader->summary(['report_type' => 'promotion', 'agent_account' => 'agent10'])['summary']['activity_minutes']);
-        $this->assertSame(2, $this->reader->summary(['report_type' => 'promotion', 'agent_account' => 'agent20'])['summary']['activity_minutes']);
+        $all = $this->reader->summary($this->today())['summary']; $this->assertSame(3, $all['activity_minutes']); $this->assertSame(1, $all['active_member_count']);
+        $this->assertSame(1, $this->reader->summary($this->today(['report_type' => 'promotion', 'agent_account' => 'agent10']))['summary']['activity_minutes']);
+        $this->assertSame(2, $this->reader->summary($this->today(['report_type' => 'promotion', 'agent_account' => 'agent20']))['summary']['activity_minutes']);
     }
 
     public function test_activity_uses_inviter_snapshot_and_does_not_inherit_current_null_attribution(): void
     {
         $this->activityObservation('2026-10-26 10:00:00'); $this->activityObservation('2026-10-26 10:04:00');
         DB::table('share_members')->where('user_id', 2)->update(['parent_user_id' => 3]);
-        $groups = collect($this->reader->details(['report_type' => 'sharing'])['data'])->keyBy('group_id');
+        $groups = collect($this->reader->details($this->today(['report_type' => 'sharing']))['data'])->keyBy('group_id');
         $this->assertSame(4, $groups[1]['activity_minutes']); $this->assertArrayNotHasKey(3, $groups->all());
-        $this->assertSame(4, $this->reader->summary(['report_type' => 'sharing', 'inviter_account' => 'member1'])['summary']['activity_minutes']);
-        $this->assertSame(0, $this->reader->summary(['report_type' => 'sharing', 'inviter_account' => 'member3'])['summary']['activity_minutes']);
+        $this->assertSame(4, $this->reader->summary($this->today(['report_type' => 'sharing', 'inviter_account' => 'member1']))['summary']['activity_minutes']);
+        $this->assertSame(0, $this->reader->summary($this->today(['report_type' => 'sharing', 'inviter_account' => 'member3']))['summary']['activity_minutes']);
 
         DB::table('reporting_activity')->delete();
         $this->activityObservation('2026-10-26 11:00:00', null, null); $this->activityObservation('2026-10-26 11:02:00', null, null);
-        $this->assertSame(2, $this->reader->summary([])['summary']['activity_minutes']);
-        $this->assertSame(0, $this->reader->summary(['report_type' => 'promotion'])['summary']['activity_minutes']);
-        $this->assertSame(0, $this->reader->summary(['report_type' => 'sharing'])['summary']['activity_minutes']);
+        $this->assertSame(2, $this->reader->summary($this->today())['summary']['activity_minutes']);
+        $this->assertSame(0, $this->reader->summary($this->today(['report_type' => 'promotion']))['summary']['activity_minutes']);
+        $this->assertSame(0, $this->reader->summary($this->today(['report_type' => 'sharing']))['summary']['activity_minutes']);
     }
 
     public function test_activity_interval_crossing_query_start_keeps_the_starting_snapshot(): void
     {
         $this->activityObservation('2026-10-25 23:59:00', 10); $this->activityObservation('2026-10-26 00:01:00', 20);
         DB::table('users')->where('id', 2)->update(['agent_id' => 20]);
-        $groups = collect($this->reader->details(['report_type' => 'promotion'])['data'])->keyBy('group_id');
+        $groups = collect($this->reader->details($this->today(['report_type' => 'promotion']))['data'])->keyBy('group_id');
         $this->assertSame(1, $groups[10]['activity_minutes']); $this->assertSame(0, $groups[20]['activity_minutes']);
-        $this->assertSame(1, $this->reader->summary([])['summary']['activity_minutes']);
+        $this->assertSame(1, $this->reader->summary($this->today())['summary']['activity_minutes']);
     }
 
     public function test_order_time_reconciliation_survives_reimport_and_does_not_settle_source(): void
     {
         DB::table('bets')->insert(['id' => 1, 'member_id' => '200', 'amount' => '100', 'profit' => '60', 'status' => 2, 'created_at' => '2026-10-25 09:00:00', 'updated_at' => '2026-10-26 10:00:00']);
-        $this->sync->publish('bets', 1); $this->assertNull($this->reader->summary([])['summary']['company_profit']);
+        $this->sync->publish('bets', 1); $this->assertNull($this->reader->summary($this->today())['summary']['company_profit']);
         $id = (int) DB::table('reporting_orders')->value('id'); $before = (array) DB::table('bets')->first();
         $this->admin->reconcileOrder(['id' => $id, 'settled_at' => '2026-10-26 10:00:00', 'evidence' => '开奖结算日志']); $this->sync->publish('bets', 1);
-        $this->assertSame('40.0000000000', $this->reader->summary([])['summary']['company_profit']);
+        $this->assertSame('40.0000000000', $this->reader->summary($this->today())['summary']['company_profit']);
         $this->assertSame($before, (array) DB::table('bets')->first());
     }
 
@@ -418,11 +419,11 @@ class ReportingTest extends TestCase
         DB::table('agent_rebate_records')->insert(['id' => 1, 'agent_id' => 10, 'platform' => 'pc28', 'game_type' => 3, 'settlement_date' => '2026-10-26', 'flow_commission' => '7', 'profit_commission' => '2', 'flow_status' => 'credited', 'profit_status' => 'pending', 'created_at' => '2026-10-26 10:00:00']);
         DB::table('agent_balance_logs')->insert(['id' => 1, 'agent_id' => 10, 'type' => 'commission', 'amount' => '7', 'reference_type' => 'agent_rebate_record', 'reference_id' => 1, 'created_at' => '2026-10-26 11:00:00']);
         foreach ([['share_ledger', 1], ['share_ledger', 2], ['balance_logs', 10], ['agent_rebate_records', 1], ['agent_balance_logs', 1]] as [$source, $id]) $this->sync->publish($source, $id);
-        $row = $this->reader->summary([])['summary'];
+        $row = $this->reader->summary($this->today())['summary'];
         $this->assertSame('17.0000000000', $row['share_accrual']); $this->assertSame('5.0000000000', $row['share_commission']);
         $this->assertSame('9.0000000000', $row['agent_accrual']); $this->assertSame('7.0000000000', $row['agent_commission']);
         $this->assertSame('28.0000000000', $row['company_profit']);
-        $details = $this->reader->details([]); $this->assertSame('7.0000000000', $details['unallocated']['agent_commission']);
+        $details = $this->reader->details($this->today()); $this->assertSame('7.0000000000', $details['unallocated']['agent_commission']);
         $this->assertSame('5.0000000000', $this->reader->summary(['report_type' => 'sharing'])['summary']['share_commission']);
     }
 
@@ -434,9 +435,9 @@ class ReportingTest extends TestCase
         $this->assertSame('100.0000000000', $r['summary']['settled_bet_amount']);
         DB::table('bets')->insert(['id' => 2, 'member_id' => 'missing', 'amount' => '10', 'status' => 1, 'created_at' => '2026-10-26 10:00:00']);
         $r = $this->sync->sync(true); $this->assertSame(1, $r['errors']);
-        $this->assertNull($this->reader->summary([])['summary']['company_profit']); $this->assertSame(1, DB::table('reporting_errors')->count());
+        $this->assertNull($this->reader->summary($this->today())['summary']['company_profit']); $this->assertSame(1, DB::table('reporting_errors')->count());
         DB::table('bets')->where('id', 2)->update(['member_id' => '200']); $this->sync->sync(true);
-        $this->assertSame(0, DB::table('reporting_errors')->count()); $this->assertNotNull($this->reader->summary([])['summary']['company_profit']);
+        $this->assertSame(0, DB::table('reporting_errors')->count()); $this->assertNotNull($this->reader->summary($this->today())['summary']['company_profit']);
     }
 
     public function test_eloquent_context_observer_records_status_transition_once_and_tg_activity_is_deduplicated(): void
@@ -465,7 +466,7 @@ class ReportingTest extends TestCase
         $this->context('sport_order', 1, 2, 'APP'); $this->sync->publish('sport_order', 1);
         DB::table('third_game_orders')->insert(['id' => 1, 'order_key' => str_repeat('a', 64), 'member_id' => '200', 'user_id' => 2, 'game_type' => 4, 'bet_amount' => '30', 'settled_amount' => '-30', 'status' => 1, 'currency' => 'CNY', 'bet_time' => '2026-10-26 09:00:00', 'last_update_time' => '2026-10-26 10:00:00', 'created_at' => '2026-10-26 09:00:00', 'updated_at' => '2026-10-26 10:00:00']);
         $this->sync->publish('third_game_orders', 1);
-        $r = $this->reader->summary([])['summary'];
+        $r = $this->reader->summary($this->today())['summary'];
         $this->assertSame(3, $r['order_count']); $this->assertSame('150.0000000000', $r['bet_amount']);
         $this->assertSame('100.0000000000', $r['settled_bet_amount']); $this->assertSame('18.0000000000', $r['payout_amount']);
         $this->assertSame('50.0000000000', $r['refund_amount']); $this->assertSame('82.0000000000', $r['game_gross_profit']);
@@ -475,7 +476,7 @@ class ReportingTest extends TestCase
         Carbon::setTestNow(Carbon::parse('2026-10-26 16:10:00', 'Asia/Shanghai'));
         DB::table('lhc_order_item')->where('id', 2)->update(['win_amount' => '68', 'updated_at' => now()->timestamp]);
         $this->assertSame(0, $this->sync->sync()['errors']);
-        $this->assertSame('32.0000000000', $this->reader->summary([])['summary']['game_gross_profit']);
+        $this->assertSame('32.0000000000', $this->reader->summary($this->today())['summary']['game_gross_profit']);
         $this->assertSame(4, DB::table('reporting_orders')->count());
     }
 
@@ -486,7 +487,7 @@ class ReportingTest extends TestCase
         try { $this->verify($event); $this->fail('Expected audit failure'); } catch (\Illuminate\Database\QueryException $e) { $this->assertStringContainsString('audit failed', $e->getMessage()); }
         $this->assertSame(0, DB::table('reporting_reconciliations')->count());
         $this->bet(); DB::table('reporting_orders')->update(['currency' => 'USDT']);
-        $r = $this->reader->summary([])['summary']; $this->assertSame('0.0000000000', $r['bet_amount']); $this->assertSame(1, $r['quality']['foreign_currency']); $this->assertNull($r['company_profit']);
+        $r = $this->reader->summary($this->today())['summary']; $this->assertSame('0.0000000000', $r['bet_amount']); $this->assertSame(1, $r['quality']['foreign_currency']); $this->assertNull($r['company_profit']);
     }
 
     public function test_verified_attribution_is_a_reporting_snapshot_and_survives_later_imports(): void
@@ -506,7 +507,132 @@ class ReportingTest extends TestCase
         $this->assertNotEmpty($this->admin->quality([])['sources']);
     }
 
+    private function historicalBet(int $id = 1, string $betAt = '2024-01-01 09:00:00', string $settledAt = '2024-01-02 10:00:00'): void
+    {
+        $this->bet($id, ['created_at' => $betAt, 'amount' => '40', 'profit' => '20']);
+        DB::table('reporting_contexts')->where('source', 'bets')->where('source_id', (string) $id)->update(['succeeded_at' => $settledAt]);
+        $this->sync->publish('bets', $id);
+    }
+
+    public function test_no_dates_default_to_all_history_and_dates_alone_select_a_period(): void
+    {
+        $this->historicalBet(); $this->bet(2);
+        $default = $this->reader->summary([]);
+        $this->assertSame(1, $default['filters']['all_time']); $this->assertSame('2024-01-01', $default['filters']['start_date']);
+        $this->assertSame('2026-10-26', $default['filters']['end_date']);
+        $this->assertSame('140.0000000000', $default['summary']['bet_amount']);
+        $this->assertSame('80.0000000000', $default['summary']['payout_amount']);
+        $blank = $this->reader->summary(['start_date' => '', 'end_date' => null]);
+        $this->assertSame($default['filters'], $blank['filters']); $this->assertSame($default['summary'], $blank['summary']);
+        $range = $this->reader->summary($this->today());
+        $this->assertSame(0, $range['filters']['all_time']); $this->assertSame('2026-10-26', $range['filters']['start_date']);
+        $this->assertSame('100.0000000000', $range['summary']['bet_amount']);
+        $this->assertSame('2026-10-26', $default['valid_member_policy']['date']);
+        $this->assertSame($default['summary'], $this->reader->summary(['all_time' => 0])['summary']);
+        $this->assertSame($range['summary'], $this->reader->summary($this->today(['all_time' => 1]))['summary']);
+        $bounded = $this->reader->params(['start_date' => now('Asia/Shanghai')->subDays(365)->toDateString()]);
+        $this->assertSame(now('Asia/Shanghai')->subDays(365)->toDateString(), $bounded['start_date']);
+        $this->assertSame(366, $this->admin->options()['max_days']);
+    }
+
+    public function test_all_time_funds_paginate_actual_days_and_keep_the_complete_summary(): void
+    {
+        $this->historicalBet(); $this->bet(2); $event = $this->deposit(); $this->verify($event);
+        $first = $this->reader->funds(['limit' => 1]);
+        $days = (int) Carbon::parse('2024-01-01')->diffInDays(Carbon::parse('2026-10-27'));
+        $this->assertSame($days, $first['total']); $this->assertCount(1, $first['data']); $this->assertSame('2026-10-26', $first['data'][0]['date']);
+        $this->assertSame('140.0000000000', $first['summary']['bet_amount']); $this->assertSame('80.0000000000', $first['summary']['payout_amount']);
+        $this->assertSame('100.0000000000', $first['data'][0]['bet_amount']); $this->assertSame('60.0000000000', $first['data'][0]['payout_amount']);
+        $this->assertSame('998.0000000000', $first['summary']['net_cash_flow']);
+        $settled = $this->reader->funds(['limit' => 1, 'page' => $days - 1]);
+        $this->assertSame($first['summary'], $settled['summary']); $this->assertSame('2024-01-02', $settled['data'][0]['date']);
+        $this->assertSame('0.0000000000', $settled['data'][0]['bet_amount']); $this->assertSame('20.0000000000', $settled['data'][0]['payout_amount']);
+        $bet = $this->reader->funds(['limit' => 1, 'page' => $days]);
+        $this->assertSame('2024-01-01', $bet['data'][0]['date']); $this->assertSame('40.0000000000', $bet['data'][0]['bet_amount']);
+        $this->assertSame('0.0000000000', $bet['data'][0]['payout_amount']);
+        $outside = $this->reader->funds(['limit' => 1, 'page' => $days + 1]);
+        $this->assertSame([], $outside['data']); $this->assertSame($first['summary'], $outside['summary']); $this->assertSame($days, $outside['total']);
+        $bounded = $this->reader->funds(['start_date' => '2026-10-25', 'limit' => 1, 'page' => 2]);
+        $this->assertSame(2, $bounded['total']); $this->assertSame('2026-10-25', $bounded['data'][0]['date']);
+        $this->assertSame('100.0000000000', $bounded['summary']['bet_amount']);
+    }
+
+    public function test_all_time_uses_the_effective_reconciliation_date_and_amount(): void
+    {
+        $event = $this->deposit();
+        DB::table('balance_logs')->where('id', 1)->update(['created_at' => '2019-01-01 09:00:00']); $this->sync->publish('balance_logs', 1);
+        $this->admin->reconcile(['event_id' => $event, 'actual_amount' => '500', 'external_fee' => '3', 'occurred_at' => '2022-02-03 10:00:00',
+            'currency' => 'CNY', 'evidence' => '历史到账凭据']);
+        $all = $this->reader->summary([]);
+        $this->assertSame('2022-02-03', $all['filters']['start_date']); $this->assertSame('500.0000000000', $all['summary']['deposit_amount']);
+        $this->assertSame('497.0000000000', $all['summary']['net_cash_flow']);
+        $this->assertSame('0.0000000000', $this->reader->summary($this->today())['summary']['deposit_amount']);
+        $funds = $this->reader->funds(['limit' => 1,
+            'page' => (int) Carbon::parse('2022-02-03')->diffInDays(Carbon::parse('2026-10-26')) + 1]);
+        $this->assertSame('2022-02-03', $funds['data'][0]['date']); $this->assertSame('500.0000000000', $funds['data'][0]['deposit_amount']);
+        $events = $this->reader->events([]);
+        $this->assertSame(1, $events['total']); $this->assertSame('2022-02-03 10:00:00', $events['data'][0]['verified_at']);
+        $this->assertSame($all['filters']['start_date'], $events['filters']['start_date']); $this->assertSame(1, $events['filters']['all_time']);
+    }
+
+    public function test_all_time_does_not_substitute_the_application_date_for_unknown_payment_time(): void
+    {
+        DB::table('withdraws')->insert(['id' => 1, 'member_id' => '200', 'amount' => '100', 'to_account' => '98', 'service_charge' => '2',
+            'exchange_rate' => '7', 'status' => 1, 'created_at' => '2001-01-01 08:00:00']);
+        $this->sync->publish('withdraws', 1);
+        $all = $this->reader->summary([]);
+        $this->assertSame('2026-10-01', $all['filters']['start_date']); $this->assertSame(1, $all['summary']['quality']['missing_time']);
+        $this->assertSame('0.0000000000', $all['summary']['withdraw_amount']); $this->assertNull($all['summary']['net_cash_flow']);
+        $funds = $this->reader->funds(['limit' => 100]);
+        $this->assertSame(26, $funds['total']); $this->assertCount(26, $funds['data']);
+        foreach ($funds['data'] as $day) {
+            $this->assertSame('0.0000000000', $day['withdraw_amount']); $this->assertSame(1, $day['quality']['missing_time']); $this->assertNull($day['net_cash_flow']);
+        }
+        $events = $this->reader->events([]);
+        $this->assertSame(1, $events['total']); $this->assertNull($events['data'][0]['occurred_at']);
+        $this->assertSame('2001-01-01 08:00:00', $events['data'][0]['candidate_at']);
+    }
+
+    public function test_all_time_first_day_uses_real_members_and_activity_and_empty_data_falls_back_to_today(): void
+    {
+        DB::table('users')->update(['created_at' => '2026-10-26 00:00:00']);
+        DB::table('users')->where('from', 2)->update(['created_at' => '1999-01-01 00:00:00']);
+        $this->assertSame('2026-10-26', $this->reader->params([])['start_date']);
+        DB::table('users')->where('id', 2)->update(['created_at' => '2023-01-01 00:00:00']);
+        $this->assertSame('2023-01-01', $this->reader->params([])['start_date']);
+        $this->activityObservation('2022-01-01 10:00:00'); $this->activityObservation('2022-01-01 10:02:00');
+        $all = $this->reader->summary([]);
+        $this->assertSame('2022-01-01', $all['filters']['start_date']); $this->assertSame(2, $all['summary']['known_activity_minutes']);
+        $this->assertNull($all['summary']['activity_minutes']);
+        DB::table('reporting_activity')->delete(); DB::table('users')->delete();
+        $empty = $this->reader->funds([]);
+        $this->assertSame('2026-10-26', $empty['filters']['start_date']); $this->assertSame(1, $empty['total']); $this->assertCount(1, $empty['data']);
+        $this->assertSame('0.0000000000', $empty['summary']['bet_amount']);
+    }
+
+    public function test_all_time_preserves_account_platform_settlement_and_ranking_filters(): void
+    {
+        $this->historicalBet(); $this->bet(2, ['member_id' => '500', 'amount' => '200']);
+        $this->bet(3, ['status' => 1, 'amount' => '30', 'profit' => '0']); $this->deposit();
+        foreach (['member_account' => 'member2', 'agent_account' => 'agent10', 'inviter_account' => 'member1'] as $filter => $value) {
+            $this->assertSame('70.0000000000', $this->reader->summary([$filter => $value])['summary']['bet_amount']);
+        }
+        $this->assertSame('40.0000000000', $this->reader->summary(['member_account' => 'member2', 'settlement_status' => 'settled'])['summary']['bet_amount']);
+        $this->assertSame('0.0000000000', $this->reader->summary(['platform' => 'APP'])['summary']['bet_amount']);
+        $this->assertSame('0.0000000000', $this->reader->summary(['category' => 'self:sport'])['summary']['bet_amount']);
+        $ranks = $this->reader->details(['metric' => 'bet_amount', 'page' => 2, 'limit' => 1], true);
+        $this->assertSame(2, $ranks['total']); $this->assertSame('2', $ranks['data'][0]['group_id']); $this->assertSame(2, $ranks['data'][0]['rank']);
+        $orders = $this->reader->orders(['member_account' => 'member2', 'page' => 2, 'limit' => 1]);
+        $this->assertSame(2, $orders['total']); $this->assertSame('2024-01-01 09:00:00', $orders['data'][0]['bet_at']);
+        $this->assertSame(1, $orders['filters']['all_time']); $this->assertSame('2024-01-01', $orders['filters']['start_date']);
+        $this->assertSame(0, $this->reader->events(['member_account' => 'member5'])['total']);
+        $this->assertSame(1, $this->reader->events(['member_account' => 'member2', 'kind' => 'deposit'])['total']);
+    }
+
     /** @dataProvider invalidQueries */
     public function test_invalid_filters_do_not_query_unbounded_dates(array $input): void { $this->expectException(ValidationException::class); $this->reader->summary($input); }
-    public static function invalidQueries(): array { return [[['start_date' => '2026-02-30']], [['end_date' => '2026-10-27']], [['start_date' => '2020-01-01']], [['platform' => 'ios']], [['settlement_status' => 'paid']], [['limit' => 999]]]; }
+    public static function invalidQueries(): array { return [[['start_date' => '2026-02-30']], [['end_date' => '2026-10-27']], [['start_date' => '2020-01-01']],
+        [['platform' => 'ios']], [['settlement_status' => 'paid']], [['limit' => 999]], [['start_date' => []]], [['end_date' => []]],
+        [['start_date' => '2026-10-26', 'end_date' => '2026-10-25']], [['end_date' => '2026-02-30']],
+        [['start_date' => '2025-01-01', 'end_date' => '2026-10-26']], [['start_date' => 'not-a-date', 'end_date' => '2026-10-26']]]; }
 }