# 大屏 — 交易销售统计 — 技术方案

> 依据：同目录 `大屏交易销售功能需求.md` v1.0。数据源为交易市场平台**既有表**（订单、明细、供应商、承销商、日度行情、等级重量配置等）。本模块为**大屏只读看板**；**农资商城相关表与指标本期不设计、不实现**（对齐功能需求 **§3.2**）。

---

## 1. 技术架构

| 项 | 说明 |
| --- | --- |
| **后端** | RuoYi **v3.9.2**（**springboot2** 分支）：JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid |
| **数据库** | MySQL **5.7.39**，InnoDB，`utf8mb4` |
| **前端** | 大屏交易销售专题页；**切换 `statYear`** 拉取看板主数据（本期**单接口**） |
| **代码包** | `com.ruoyi.web.modules.screen`（`TradeSalesScreenController` / `ITradeSalesScreenService` / `vo` / `support`） |

**分层**：`Controller` → `ITradeSalesScreenService`（口径编排、`statDate` 计算）→ `TradeSalesScreenMapper`（只读 SQL）+ `TradeSalesScreenSupport`（行情 7 日聚合、12 月补 0、占比）。

| 场景 | 行为 |
| --- | --- |
| 进入 / 切换 `statYear` | 调用 **§3.1** |
| 数据权限 | **全县**聚合；**不按**供应商/用户过滤 |
| 统计日 `statDate` | `Y = 当前年` → 今日（`Asia/Shanghai`）；`Y` 为历史年 → `Y-12-31` |
| 年内订单上界 | `finish_time` ≤ `statDate 23:59:59`（当前年）；历史年 ≤ `Y-12-31 23:59:59` |
| 入驻商铺数 | `biz_supplier` **实时快照**，**不**随 `statYear` 变 |
| 产地行情近 7 日 | 以 **`statDate`** 为终点 `[statDate−6, statDate]`，**与 Y 无关** |
| 按月图表 | 横轴固定 **12** 月；缺月、当前年**晚于 statDate 的月份**均为 **0** |
| 性能 | 默认实时聚合；可选 **§2.3** 缓存表 |

**依赖表（只读，本期）**

| 表 | 用途 |
| --- | --- |
| `biz_trade_order` | 已完成订单汇总、按月趋势、销售去向关联 |
| `biz_trade_order_line` | 品质等级（`grade_code`）按行统计 |
| `biz_supplier` | 入驻商铺数（有效供应商） |
| `biz_distributor` | 销售去向 `sales_destination`（1 本市 / 2 本省 / 3 外省） |
| `biz_market_daily_quote` | 产地行情近 7 日 |
| `biz_trade_market` | 解析默认行情市场（名称「巴青牦牛交易市场」） |
| `biz_grade_weight_config` | 等级编码 → 展示名称 |

> **不包含**：农资商城订单、店铺、品类、评价等任意表与字段。

---

## 2. 数据库设计

### 2.1 本模块业务表

**无新增业务台账表。** 业务 DDL 以 `sql/biz_trade_order.sql`、`sql/biz_supplier.sql`、`sql/biz_distributor.sql`、`sql/biz_market_daily_quote.sql`、`sql/biz_trade_market.sql`、`sql/biz_grade_weight_config.sql` 为准。

### 2.2 公共参数

记 `statYear = Y`，`statDate` 见 **§1**。

| 边界 | 表达式 |
| --- | --- |
| 年区间起 | `Y-01-01 00:00:00` |
| 年区间止（已完成订单） | `statDate 23:59:59`（见 **§1**） |
| 行情 7 日起 | `statDate − 6 日`（含） |
| 行情 7 日止 | `statDate`（含） |
| 按月分组键 | `MONTH(finish_time)`，应用层补全 1～12 |

**默认行情市场（配置）**

| 项 | 说明 |
| --- | --- |
| 优先 | `application.yml`：`bigscreen.trade-sales.default-market-name`（默认 `巴青牦牛交易市场`） |
| 解析 | `biz_trade_market`：`del_flag='0'` 且 `market_name` 等于配置值 → 取 `id` 作为 `trade_market_id` 过滤条件 |
| 未匹配 | `hasQuoteData=false`，行情区块空态；日志告警 |

### 2.3 可选表 `biz_trade_sales_screen_cache`（性能优化）

**非本期必建。**

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | `bigint(20)` | 主键 |
| `stat_year` | `int(11)` | 统计年份 |
| `payload_json` | `mediumtext` | **§3.1** 响应 JSON |
| `expire_time` | `datetime` | 过期时间 |
| `create_time` | `datetime` | 写入时间 |

**索引**：`UNIQUE uk_stat_year (stat_year)`。TTL 建议 **1～5 分钟**（与功能需求 **§7.3** 一致）。

### 2.4 查询口径（按表）

#### 交易订单 `biz_trade_order`（对齐功能需求 **§2.5**）

| 项 | 条件 |
| --- | --- |
| 纳入 | `del_flag='0'` AND `order_status='1'`（已完成） |
| 按年 | `finish_time` ∈ **[年区间起, 年区间止]** |
| 总览 | `COUNT(*)` 订单笔数；`SUM(total_heads)`；`SUM(total_amount)` |
| 按月 | `GROUP BY MONTH(finish_time)` → 当月 `SUM(total_heads)`、`SUM(total_amount)` |

#### 销售去向（对齐 **§2.8**）

| 项 | 条件 |
| --- | --- |
| 关联 | `biz_trade_order o` JOIN `biz_distributor d` ON `o.distributor_id = d.id` AND `d.del_flag='0'` |
| 订单 | 同 **§2.4** 已完成且在 **Y** 年内 |
| 分组 | `d.sales_destination`（1 / 2 / 3） |
| 指标 | `SUM(o.total_heads)`（头）；占比分母 = 当年已完成订单 `SUM(total_heads)` |

应用层固定返回 **3** 档（本市 / 本省 / 外省），无数据为 `0` 头、`ratio=0`。

#### 品质等级 `biz_trade_order_line`（对齐 **§2.9**）

| 项 | 条件 |
| --- | --- |
| 关联 | `line` JOIN `biz_trade_order o`（同上已完成、年内） |
| 分组 | `line.grade_code` |
| 名称 | 聚合时 `max(line.grade_name)`；无快照时回退 `biz_grade_weight_config.grade_name` |
| 指标 | `COUNT(*)` 明细行（头）；占比分母 = 当年已完成订单明细总行数 |
| 空等级 | `grade_code IS NULL` 的行**不计入**品质占比；若分母为 0 → `hasGradeData=false` |

应用层按配置表 `sort_order` 输出各等级；配置表有而当年无数据的等级 `count=0`、`ratio=0`（可选，与产品约定；**默认**仅返回 `count>0` 的等级 + 固定四档补 0，推荐**固定返回配置表全部等级**便于饼图）。

#### 入驻商铺 `biz_supplier`（对齐 **§2.10**）

| 项 | 条件 |
| --- | --- |
| 指标 | `COUNT(*)` |
| 条件 | `del_flag='0'` |
| 与 **Y** | **无**年份条件 |

#### 日度行情 `biz_market_daily_quote`（对齐 **§2.6、§2.7**）

| 项 | 条件 |
| --- | --- |
| 范围 | `del_flag='0'` AND `trade_market_id = {默认市场ID}` AND `quote_date` BETWEEN 7 日起止 |
| 原始行 | 窗口内全部日度记录（含多维度：公母、重量档等） |

**应用层按 `quote_date` 聚合（`TradeSalesScreenSupport`）**

| 聚合项 | 规则 |
| --- | --- |
| 当日最低价 | 该日各条 `price_min` 的 **MIN** |
| 当日最高价 | 该日各条 `price_max` 的 **MAX** |
| 当日均价 | 该日各条 `avg_price` 的 **算术平均** |
| 7 日卡片 | 7 个自然日「当日均价」有效值的平均；7 日内「当日最低价」的最小、「当日最高价」的最大；`lastQuoteDate` = 窗口内最大 `quote_date` |
| 价格单位 | 窗口内 `quote_date` 最大那天的记录中 `create_time` 最新一条的 `price_unit`（1 元/头 / 2 元/斤） |
| 缺日 | 7 日横轴仍输出该日；`minPrice`/`maxPrice`/`avgPrice` 均为 **0**（对齐功能需求 **§5.3.2** 默认） |

### 2.5 索引建议（既有表）

| 表 | 建议 |
| --- | --- |
| `biz_trade_order` | `(order_status, del_flag, finish_time)` 或 `idx_finish_time` |
| `biz_trade_order_line` | `(order_id)`（已有 `idx_order_id`） |
| `biz_market_daily_quote` | `(trade_market_id, quote_date, del_flag)` |
| `biz_distributor` | 沿用 `idx_sales_destination` |

### 2.6 Mapper 扩展（建议）

| Mapper | 方法 | 说明 |
| --- | --- | --- |
| `TradeSalesScreenMapper` | `selectTradeOverviewByYear(y, endTime)` | 总览三项 |
| `TradeSalesScreenMapper` | `selectMonthlyTradeByYear(y, endTime)` | 按月头数、金额 |
| `TradeSalesScreenMapper` | `selectSalesDestinationByYear(y, endTime)` | 三档头数 |
| `TradeSalesScreenMapper` | `selectQualityGradeByYear(y, endTime)` | 按 `grade_code` 计数 |
| `TradeSalesScreenMapper` | `selectQuotesInWindow(marketId, startDate, endDate)` | 行情原始行 |
| `TradeSalesScreenMapper` | `selectDistinctFinishYears()` | 可选年份 |
| `BizSupplierMapper` | `countForScreen()` | 可复用首页 `countForScreen()` |

XML：`mapper/screen/TradeSalesScreenMapper.xml`。

---

## 3. 接口设计

**统一响应**：`AjaxResult`（`code` / `msg` / `data`）。

**权限标识（示例）**：`bigScreen:tradeSales:query`

**Base Path**：`/bigScreen/tradeSales`

| # | 说明 | Method | URI | 权限 |
| --- | --- | --- | --- | --- |
| 3.1 | 看板主数据（五区块合一） | GET | `/bigScreen/tradeSales/dashboard` | `bigScreen:tradeSales:query` |

前端：进入页 / 切换 `statYear` / 手动刷新 → 仅调用 **3.1**。

---

### 3.1 看板主数据

#### Query

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `statYear` | string | Y | 四位年份；非法 → 业务异常（复用 `AchievementReportValidation.parseStatYear`） |
| `forceRefresh` | boolean | N | 默认 `false`；`true` 时跳过 **§2.3** 缓存 |

#### 响应 `data`（顶层）

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `statYear` | int | 回显 |
| `statDate` | string | 统计时点 `yyyy-MM-dd` |
| `availableYears` | int[] | `biz_trade_order` 已完成订单 `YEAR(finish_time)` 并集 + 当前年，降序 |
| `tradeOverview` | object | 交易总览 **§3.1.1** |
| `originQuote` | object | 产地行情 **§3.1.2** |
| `tradeMonthlyTrend` | array | 数额趋势 **§3.1.3** |
| `salesDestination` | array | 销售去向 **§3.1.4** |
| `qualityGrade` | object | 品质等级 **§3.1.5** |

#### 3.1.1 `tradeOverview`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `orderCount` | long | 牦牛交易订单数（笔） |
| `tradeHeads` | long | 牦牛交易量（头） |
| `tradeAmount` | decimal | 牦牛交易总额（元） |
| `supplierCount` | long | 入驻商铺数（家），**实时** |

#### 3.1.2 `originQuote`

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `hasQuoteData` | boolean | 7 日内是否存在行情登记 |
| `avgPrice7d` | decimal | 7 日均价；无数据可为 `null` |
| `minPrice7d` | decimal | 7 日最低价 |
| `maxPrice7d` | decimal | 7 日最高价 |
| `lastQuoteDate` | string | 最近行情日 `yyyy-MM-dd`；无则 `null` |
| `priceUnit` | int | `1` 元/头 / `2` 元/斤 |
| `priceUnitLabel` | string | 如 `元/头` |
| `dailyTrend` | array | 固定 **7** 条，按日期升序 |

`dailyTrend[]`：

| 字段 | 说明 |
| --- | --- |
| `quoteDate` | `yyyy-MM-dd` |
| `minPrice` / `maxPrice` / `avgPrice` | 当日聚合价；缺日为 **0** |

#### 3.1.3 `tradeMonthlyTrend[]`

固定 **12** 条。

| 字段 | 说明 |
| --- | --- |
| `month` | 1～12 |
| `tradeHeads` | 该月已完成订单总头数 |
| `tradeAmount` | 该月已完成订单总金额（元） |

`month > statDate.getMonth()` 且 `Y` 为当前年时两项为 **0**。

#### 3.1.4 `salesDestination[]`

固定 **3** 条。

| 字段 | 说明 |
| --- | --- |
| `salesDestination` | `1` / `2` / `3` |
| `salesDestinationName` | 本市 / 本省 / 外省 |
| `tradeHeads` | 头数 |
| `ratio` | 占比 **0～100** 数值（如 `33.3` 表示 33.3%），保留 **1** 位小数 |

#### 3.1.5 `qualityGrade`

| 字段 | 说明 |
| --- | --- |
| `hasGradeData` | 当年是否存在 `grade_code` 非空的已完成明细 |
| `totalHeads` | 参与占比计算的明细总行数 |
| `items` | 数组 |

`items[]`：

| 字段 | 说明 |
| --- | --- |
| `gradeCode` | A / B / C / D |
| `gradeName` | 一级 / 二级等 |
| `tradeHeads` | 头数 |
| `ratio` | 占比 **0～100**，保留 **1** 位小数 |

#### 响应示例（节选）

```json
{
  "statYear": 2026,
  "statDate": "2026-05-20",
  "availableYears": [2026, 2025],
  "tradeOverview": {
    "orderCount": 12,
    "tradeHeads": 48,
    "tradeAmount": 960000.00,
    "supplierCount": 5
  },
  "originQuote": {
    "hasQuoteData": true,
    "avgPrice7d": 32.5,
    "minPrice7d": 30.0,
    "maxPrice7d": 35.0,
    "lastQuoteDate": "2026-05-20",
    "priceUnit": 2,
    "priceUnitLabel": "元/斤",
    "dailyTrend": [
      { "quoteDate": "2026-05-14", "minPrice": 31.0, "maxPrice": 33.0, "avgPrice": 32.0 }
    ]
  },
  "tradeMonthlyTrend": [
    { "month": 1, "tradeHeads": 10, "tradeAmount": 200000.00 },
    { "month": 12, "tradeHeads": 0, "tradeAmount": 0 }
  ],
  "salesDestination": [
    { "salesDestination": 1, "salesDestinationName": "本市", "tradeHeads": 20, "ratio": 41.7 }
  ],
  "qualityGrade": {
    "hasGradeData": true,
    "totalHeads": 48,
    "items": [
      { "gradeCode": "B", "gradeName": "二级", "tradeHeads": 30, "ratio": 62.5 }
    ]
  }
}
```

#### 服务端逻辑（摘要）

```
1. year = parseStatYear(statYear); statDate = resolveStatDate(year); endTime = statDate 23:59:59
2. tradeOverview = orderMapper 总览(year, endTime) + supplierMapper.countForScreen()
3. tradeMonthlyTrend = orderMapper 按月 + buildMonthlySeries(12, year, statDate)
4. salesDestination = orderMapper JOIN distributor + fill3Buckets + ratio
5. qualityGrade = lineMapper 分组 + join grade_config + ratio; hasGradeData
6. marketId = resolveDefaultMarketId(configName)
7. rows = quoteMapper 7日窗口(marketId); originQuote = Support.aggregateQuote7d(rows, statDate)
8. availableYears = distinct finish years ∪ current year
9. return AjaxResult.success(vo)
```

---

## 4. 菜单与权限（示例）

| 类型 | 名称 | 权限标识 |
| --- | --- | --- |
| 菜单 | 大屏交易销售 | `bigScreen:tradeSales:query` |

SQL 示例：`sql/big_screen_trade_sales_perm.sql`（挂载「大屏」父菜单下）。

---

## 5. 交付清单

- [ ] `TradeSalesScreenController` + `ITradeSalesScreenService` + VO + `TradeSalesScreenSupport`
- [ ] `TradeSalesScreenMapper` + XML（**§2.6**）
- [ ] `application.yml` 默认行情市场名配置项
- [ ] 单元测试：历史年 `statDate`、12 月补 0、供应商数不随 **Y** 变、行情 7 日聚合、去向三档合计、品质占比
- [ ] 接口测试：`GET /bigScreen/tradeSales/dashboard`
- [ ] 菜单权限 SQL；依赖 `biz_trade_order_line.grade_code`、`biz_grade_weight_config` 已执行

---

## 6. 修订记录

| 版本 | 日期 | 说明 |
| --- | --- | --- |
| 1.0 | 2026-05-20 | 初稿：无新表；单接口五区块；对齐 `大屏交易销售功能需求.md` v1.0；农资商城不纳入 |
