diff --git a/changelogs-v2/2026-09/09_7378_团期行程逐日汇总与逐户下钻-新增接口-管理后台.md b/changelogs-v2/2026-09/09_7378_团期行程逐日汇总与逐户下钻-新增接口-管理后台.md new file mode 100644 index 00000000..740b1010 --- /dev/null +++ b/changelogs-v2/2026-09/09_7378_团期行程逐日汇总与逐户下钻-新增接口-管理后台.md @@ -0,0 +1,476 @@ +--- +schema: "hl-changelog/v2" +ticket: "7378" +title: "新增团期行程只读端点 GB-ADM-018/019:逐日汇总 + 某项逐户下钻" +consumer: "admin" +author: "jw(GIT)" +change_type: "新增接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-09" +status_note: "团期详情新增「行程」区块的两个只读端点:一次 GET 拿到全团逐日行程,每项标好「全团一致 / 都有但价量不同 / 只有部分户有」,点某项再 GET 一次下钻到逐户。后端已部署 TEST 并经真实网关逐条实测(含 55 户团压 N+1、差异造数后已全部回滚)。前端三条必看:① 本日合计 dayTotalAmount 只含 consistency=ALL_SAME 的项,每项有 countedInDayTotal、天级有 excludedNodeCount,界面必须提示,否则运营会把它当成整团实际金额(那是核团的事);② ALL_SAME 读 unitPrice/quantity/totalAmount,VALUE_DIFF/PARTIAL 读 unitPriceMin~Max / quantityMin~Max,两边字段互斥;③ nodeKey 不含天,同一景区第 1、2 天各排一次很常见,点哪张卡就把那张卡的 dayNumber 带上下钻,不带会跨天返回、一户多行。团期层无任何行程写口,要改行程用下钻行里的 orderId 跳到该户订单行程页。⚠️ 本需求无原型(2026-08-31 jw 口述新增),界面形态需 jw 与 mmg 另定,后端不阻塞。" +updated_at: "2026-09-09" +base: "dev-v3" +--- + +# 团期行程逐日汇总与逐户下钻 + +## 一、给前端的一句话 + +团期详情现在能按天看整团行程了。**一次 GET 拿到全团逐日行程**,每一项都标好了 +「全团一致 / 都有但价量不同 / 只有部分户有」,点某一项再 GET 一次能下钻到**逐户**明细。 +两个都是纯新增只读端点,不动任何既有接口,**团期层没有任何行程写口**—— +要改行程还是去那户的订单行程页(下钻每行都带 `orderId` 供跳转)。 + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|---|---|---|---|---| +| 1 | 团期行程逐日汇总 | GET | `/v3/admin/order/group-batch/:groupBatchId/itinerary` | 新增 | 按天返回全团行程,每项带一致性标记与本日合计 | +| 2 | 团期行程某项逐户下钻 | GET | `/v3/admin/order/group-batch/:groupBatchId/itinerary/nodes/:nodeKey` | 新增 | 某一项的逐户明细,含没有该项的户 | + +## 三、接口详情 + +### 1. 团期行程逐日汇总 `GET /v3/admin/order/group-batch/:groupBatchId/itinerary` + +**VO**: `GroupBatchItineraryRespVO` + +#### 使用场景 + +团期详情页新增的「行程」区块。运营出团前要一眼看出「整团第 3 天去哪」以及 +「哪几户跟大部队不一样」。行程是**订单级**的:同一期客人产品相同、出发日相同, +创单事务内从产品快照展开固化,骨架本来一样;下单之后每户可以单独加项、改时间、调价。 +这个接口把全团各户的行程按天叠在一起,标出哪里已经不一样了。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `groupBatchId` | path | Long | 是 | 雪花 ID,按字符串传 | 团期 ID | + +无 query、无 body。**不分页**——单团期的天数与每天的项数是有限的行程结构,不是无界列表。 + +#### 出参 + +`Result`。 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `groupBatchId` | String | 团期 ID(雪花) | +| `totalHouseholds` | Integer | 参与统计的活跃户数(分母 M);已取消 / 已退单户不计入 | +| `dayCountConsistent` | Boolean | 各户行程天数是否一致 | +| `dayCount` | Integer | **多数户**的行程天数;无行程时 `0` | +| `dayCountOutliers` | Array | 天数与多数户不同的户;一致时为 `[]` | +| `dayCountOutliers[].orderId` | String | 子订单 ID(雪花) | +| `dayCountOutliers[].orderNo` | String | 子订单编号 | +| `dayCountOutliers[].contactName` | String | 联系人 | +| `dayCountOutliers[].dayCount` | Integer | 该户的行程天数(该户完全没有行程时为 `0`) | +| `days` | Array | 逐日汇总,按 `dayNumber` 升序 | +| `days[].dayNumber` | Integer | 第几天,从 1 起 | +| `days[].dayDate` | String | `yyyy-MM-dd`;各户不一致时取多数户的值 | +| `days[].dayTitle` | String | 该天标题;取多数户的值 | +| `days[].dayTotalAmount` | Number | **本日合计——只含 `ALL_SAME` 项** | +| `days[].excludedNodeCount` | Integer | 未计入本日合计的项数 | +| `days[].nodes` | Array | 该天的行程项 | +| `days[].nodes[].nodeKey` | String | 归组键,下钻接口原样回传 | +| `days[].nodes[].nodeName` | String | 行程项名称 | +| `days[].nodes[].nodeType` | String | 行程项类型(`SCENIC` / `RESTAURANT` / `ACTIVITY` / `SERVICE` / `CUSTOM`) | +| `days[].nodes[].resourceType` | String | 资源类型(`SCENIC_SPOT` / `RESTAURANT` / `ACTIVITY`;自定义项可能为 `null`) | +| `days[].nodes[].resourceId` | String | 资源 ID(雪花);自定义项为 `null` | +| `days[].nodes[].resourceName` | String | 资源名称快照 | +| `days[].nodes[].startTime` | String | 精确时间 `HH:mm`;各户不一致时取多数户的值 | +| `days[].nodes[].timePeriod` | String | 时段说明(上午 / 下午 / 全天);取多数户的值 | +| `days[].nodes[].consistency` | String | `ALL_SAME` / `VALUE_DIFF` / `PARTIAL` | +| `days[].nodes[].householdCount` | Integer | 有该项的户数 N | +| `days[].nodes[].totalHouseholds` | Integer | 参与统计的总户数 M | +| `days[].nodes[].unitPrice` | Number | 单价;**仅 `ALL_SAME` 有值**,否则 `null` | +| `days[].nodes[].unitPriceMin` / `unitPriceMax` | Number | 单价区间;**仅非 `ALL_SAME` 有值** | +| `days[].nodes[].quantity` | Integer | 数量;仅 `ALL_SAME` 有值 | +| `days[].nodes[].quantityMin` / `quantityMax` | Integer | 数量区间;仅非 `ALL_SAME` 有值 | +| `days[].nodes[].totalAmount` | Number | 小计;仅 `ALL_SAME` 有值 | +| `days[].nodes[].countedInDayTotal` | Boolean | 是否计入本日合计——**只有 `ALL_SAME` 才是 `true`** | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2097498512387104770/itinerary +Authorization: Bearer +``` + +#### 响应示例 + +TEST 实测(3 户团,节选第 1 天的两项 —— 一项全团一致、一项只有一户有): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2097498512387104770", + "totalHouseholds": 3, + "dayCountConsistent": false, + "dayCount": 3, + "dayCountOutliers": [ + { + "orderId": "2097498673159028737", + "orderNo": "HL20260909093347891", + "contactName": "容量验收客3", + "dayCount": 4 + } + ], + "days": [ + { + "dayNumber": 1, + "dayDate": "2026-11-20", + "dayTitle": "抵达与草原初见", + "dayTotalAmount": 545.00, + "excludedNodeCount": 2, + "nodes": [ + { + "nodeKey": "e735c79b9a59a955", + "nodeName": "中俄边境公路(卡线)", + "nodeType": "SCENIC", + "resourceType": "SCENIC_SPOT", + "resourceId": "3001000000000000019", + "resourceName": "中俄边境公路(卡线)", + "startTime": null, + "timePeriod": "MORNING", + "consistency": "ALL_SAME", + "householdCount": 3, + "totalHouseholds": 3, + "unitPrice": 80.00, + "unitPriceMin": null, + "unitPriceMax": null, + "quantity": 1, + "quantityMin": null, + "quantityMax": null, + "totalAmount": 80.00, + "countedInDayTotal": true + }, + { + "nodeKey": "3f2d83708b6957db", + "nodeName": "驯鹿拉车体验", + "nodeType": "ACTIVITY", + "resourceType": "ACTIVITY", + "resourceId": "2029926122787946497", + "resourceName": "驯鹿拉车体验", + "startTime": null, + "timePeriod": "MORNING", + "consistency": "VALUE_DIFF", + "householdCount": 3, + "totalHouseholds": 3, + "unitPrice": null, + "unitPriceMin": 60.00, + "unitPriceMax": 90.00, + "quantity": null, + "quantityMin": 1, + "quantityMax": 1, + "totalAmount": null, + "countedInDayTotal": false + } + ] + } + ] + } +} +``` + +#### 空数据 / 降级响应 + +团期存在但**没有活跃子订单**(或活跃户都没有行程)时回空天数组,不报错: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2089667627927437313", + "totalHouseholds": 0, + "dayCountConsistent": true, + "dayCount": 0, + "dayCountOutliers": [], + "days": [] + } +} +``` + +单项降级:`total_amount`(订单侧的成交小计,签单 / 退款口径下才冻结)为空时, +小计按 `unitPrice × quantity` 现算;两个都缺(如无价的「早餐」)时 `totalAmount` 回 `null`, +**不当 0 计入本日合计**。`startTime` / `timePeriod` / `resourceId` 为空的项照常返回。 + +#### 错误响应 + +团期不存在(**与团期详情同码,不是回空数组**): + +```json +{ "code": 589500, "message": "团期不存在", "data": null, "success": false } +``` + +当前角色没有 `group-batch:view` 权限: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截,返回 `401`。本端点不产生其它业务错误码。 + +#### 业务边界 + +- 只读端点,不改任何数据;**团期层没有行程写口**。 +- 一张表都不建、不给行程表加团期列——实时汇总在团活跃子订单的行程。 +- 活跃集与团期其余聚合同口径:已取消 / 已退单 / 已软删户不出现,**也不计入分母 M**。 +- 返回全量,不分页。 +- `dayDate` / `dayTitle` / `startTime` / `timePeriod` 各户不一致时取**多数户**的值。 +- `dayCount` 取多数户的天数;与多数不同的户进 `dayCountOutliers`(完全无行程的户以 `dayCount=0` 出现)。 +- 本日合计只含 `ALL_SAME` 项;`80` 与 `80.00` 判为同一个价(按数值比,不比标度)。 +- 权限码 `group-batch:view`,与团期看板、六芯片明细、时间线一致。 + +### 2. 团期行程某项逐户下钻 `GET /v3/admin/order/group-batch/:groupBatchId/itinerary/nodes/:nodeKey` + +**VO**: `GroupBatchItineraryNodeDetailVO` + +#### 使用场景 + +汇总页看到某项标着「2/3 户」或「¥80~150」时点进来,看清楚**是哪几户不一样、各自多少**。 +每一行都带 `orderId`,据此跳到那户的订单行程页去改——团期层没有任何行程编辑入口。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `groupBatchId` | path | Long | 是 | 雪花 ID,按字符串传 | 团期 ID | +| `nodeKey` | path | String | 是 | 原样取自汇总响应 | 归组键 | +| `dayNumber` | query | Integer | 否 | 从 1 起 | 只看第几天;不传 = 跨天全看 | + +无 body。 + +#### 出参 + +`Result`。 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `groupBatchId` | String | 团期 ID(雪花) | +| `nodeKey` | String | 原样回显请求里的归组键 | +| `dayNumber` | Integer | 原样回显请求里的天;未传时 `null` | +| `nodeName` | String | 行程项名称;无任何户有该项时 `null` | +| `consistency` | String | `ALL_SAME` / `VALUE_DIFF` / `PARTIAL`,与汇总同口径 | +| `householdCount` | Integer | 有该项的户数 N | +| `totalHouseholds` | Integer | 参与统计的总户数 M | +| `items` | Array | 逐户明细,**含没有该项的户** | +| `items[].orderId` | String | 子订单 ID(雪花)——前端据此跳转到该户订单行程 | +| `items[].orderNo` | String | 子订单编号 | +| `items[].contactName` | String | 联系人 | +| `items[].has` | Boolean | 该户有没有这一项 | +| `items[].dayNumber` | Integer | 第几天;`has=false` 时 `null` | +| `items[].unitPrice` | Number | 单价;`has=false` 时 `null` | +| `items[].quantity` | Integer | 数量;`has=false` 时 `null` | +| `items[].totalAmount` | Number | 小计;`has=false` 时 `null` | +| `items[].startTime` | String | 精确时间 `HH:mm` | +| `items[].timePeriod` | String | 时段说明 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2097498512387104770/itinerary/nodes/3f2d83708b6957db?dayNumber=1 +Authorization: Bearer +``` + +#### 响应示例 + +TEST 实测(该项三户都有,其中一户把单价从 60 改成了 90): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2097498512387104770", + "nodeKey": "3f2d83708b6957db", + "dayNumber": 1, + "nodeName": "驯鹿拉车体验", + "consistency": "VALUE_DIFF", + "householdCount": 3, + "totalHouseholds": 3, + "items": [ + { + "orderId": "2097498512223526914", + "orderNo": "HL20260909093309393", + "contactName": "容量验收客", + "has": true, + "dayNumber": 1, + "unitPrice": 60.00, + "quantity": 1, + "totalAmount": 60.00, + "startTime": null, + "timePeriod": "MORNING" + }, + { + "orderId": "2097498671368003585", + "orderNo": "HL20260909093347283", + "contactName": "容量验收客2", + "has": true, + "dayNumber": 1, + "unitPrice": 90.00, + "quantity": 1, + "totalAmount": 90.00, + "startTime": null, + "timePeriod": "MORNING" + }, + { + "orderId": "2097498673159028737", + "orderNo": "HL20260909093347891", + "contactName": "容量验收客3", + "has": true, + "dayNumber": 1, + "unitPrice": 60.00, + "quantity": 1, + "totalAmount": 60.00, + "startTime": null, + "timePeriod": "MORNING" + } + ] + } +} +``` + +#### 空数据 / 降级响应 + +`nodeKey` 在本团期没有任何户命中(包括拼错的键、带了 `dayNumber` 但那天没有该项)时, +返回**零命中的完整户清单**而不是错误——前端拿到的仍是「谁有谁没有」这张表: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2097498512387104770", + "nodeKey": "deadbeefdeadbeef", + "dayNumber": null, + "nodeName": null, + "consistency": "PARTIAL", + "householdCount": 0, + "totalHouseholds": 3, + "items": [ + { "orderId": "2097498512223526914", "orderNo": "HL20260909093309393", "contactName": "容量验收客", "has": false, "dayNumber": null, "unitPrice": null, "quantity": null, "totalAmount": null, "startTime": null, "timePeriod": null } + ] + } +} +``` + +团期无活跃子订单时 `items` 为 `[]`、`totalHouseholds=0`,同样不报错。 +`has=false` 的行除身份字段外全部为 `null`,前端按「未参加」渲染即可。 + +#### 错误响应 + +团期不存在: + +```json +{ "code": 589500, "message": "团期不存在", "data": null, "success": false } +``` + +当前角色没有 `group-batch:view` 权限: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截,返回 `401`。**未知 `nodeKey` 不是错误**,见上一节。 + +#### 业务边界 + +- 只读端点;`items` 里每行的 `orderId` 就是「去哪改」的跳转依据。 +- `items` **含没有该项的户**(`has=false`),顺序与汇总接口的户序一致(按 `orderId` 升序)。 +- 不带 `dayNumber` 时跨天返回该项的全部出现,同一户可能多行;带 `dayNumber` 则只看那一天。 +- `consistency` 与汇总同口径,但取值集合随 `dayNumber` 收窄而变——只看第 1 天可能 `ALL_SAME`,跨天看可能 `VALUE_DIFF`。 +- 同一户同一天重复出现同一项时只取第一条,保证 `householdCount <= totalHouseholds`。 +- 已取消 / 已退单 / 已软删户既不出现在 `items` 里,也不计入 `totalHouseholds`。 +- 权限码 `group-batch:view`。 + +## 四、契约约束与正确调用方式 + +1. **`nodeKey` 只能原样回传,不要自己拼。** 它是 `(nodeName, resourceType, resourceId)` 的 + 稳定哈希(SHA-256 前 8 字节十六进制),服务端实时算、**不落库**。同一三元组在任何时候、 + 任何请求里都得到同一个键;但它是服务端实现细节,前端从汇总响应里取到就行。 +2. **点哪张卡就把那张卡的 `dayNumber` 带上。** `nodeKey` **不含天**——同一个景区在第 1 天和 + 第 2 天各排一次是很常见的。不带 `dayNumber` 时下钻会跨天返回该项的全部出现, + 同一户可能出现多行(各行带自己的 `dayNumber`),与你点的那张卡就对不上了。 +3. **本日合计 `dayTotalAmount` 只含 `consistency=ALL_SAME` 的项。** 每项有 + `countedInDayTotal` 标明是否计入,天级有 `excludedNodeCount` 给出被排除的项数—— + 请在界面上明确提示,否则运营会把这个数当成「整团实际金额」。整团实际金额是核团的事。 +4. **`ALL_SAME` 与非 `ALL_SAME` 取值字段不同**:`ALL_SAME` 用 `unitPrice` / `quantity` / + `totalAmount`(此时区间字段为 `null`);`VALUE_DIFF` / `PARTIAL` 用 + `unitPriceMin`~`unitPriceMax` / `quantityMin`~`quantityMax`(此时确定值字段为 `null`)。 + 别只读一边。 +5. **团期层没有任何行程写口。** 要改行程,用下钻每行带的 `orderId` 跳到那户的订单行程页去改。 +6. **雪花 ID 一律按字符串处理**(`groupBatchId` / `orderId` / `resourceId` 都以字符串下发)。 +7. 权限码 `group-batch:view`,与团期看板、六芯片明细、时间线同码。 + +## 六、边界行为 + +| 场景 | 行为 | +|---|---| +| 团期不存在 | `589500`(与团期详情同码,非空数组——这两个端点要先确认团期存在) | +| 团期存在但无活跃子订单 | `code=200`,`days=[]`、`totalHouseholds=0`、`dayCount=0`、`dayCountConsistent=true` | +| 有活跃户但都没有行程 | 同上,`days=[]`,不报错 | +| 某户没有行程 | 该户不参与逐项统计,但**计入分母 M**,且在 `dayCountOutliers` 里以 `dayCount=0` 出现 | +| 已取消 / 已退单 / 已软删户 | 不出现在汇总与下钻中,也**不计入分母 M** | +| 不存在的 `nodeKey` | `code=200`,`householdCount=0`、`nodeName=null`、`consistency=PARTIAL`,`items` 里每户 `has=false`,**不报错** | +| `dayNumber` 传了但该天没有该项 | 同上:零命中的空清单,不报错 | +| 各户天数不同 | `dayCountConsistent=false`,`dayCount` 取**多数户**的天数,`dayCountOutliers` 列出与多数不同的户 | +| 同一户同一天重复出现同一项 | 只取第一条;不替订单侧去重,也不因此把该户算成两户(保证 `householdCount <= totalHouseholds`) | +| `80` 与 `80.00` | 判为**同一个价**(按数值比较,不比标度),不会误报成 `VALUE_DIFF` | +| 单价 / 数量为 `null` | 参与一致性判定(都为 `null` 算一致);区间取值时 `null` 不参与 min/max | +| 无 `group-batch:view` 权限 | `589507` | + +## 七、不影响范围 + +- 不改任何既有接口的路径、入参、出参、错误码。 +- 不改订单侧行程的任何读写口。 +- **无表变更、无 Flyway、无数据迁移、无新增错误码。** +- 网关路由不新增(`/v3/admin/**` 已覆盖)。 +- 只滚 `hl-order-service-v3` 一个服务。 + + +## 八、测试环境已验证 + +TEST(`api.test.1814.love`)真实网关逐条取证。主用团期 `2097498512387104770`(3 户 · 3 天 · 真实产品展开), +另用 `2096412454643802114`(**55 户**)压 N+1、`2089667627927437313`(0 户)压空态。 +差异场景由 SQL 造数,**已全部回滚**(回滚后复测:3 户 / 3 天 / 全一致 / 第 1 天合计 `605.00` / `excludedNodeCount=0`,与造数前逐字段一致)。 + +| 验收项 | 实测 | +|---|---| +| 按天返回全团行程,逐项含名称 / 时间 / 单价 × 数量 = 小计 / 本日合计 | ✅ 3 天,合计 `605.00` / `385.00` / `730.01` | +| 各户完全一致时全为全团项,本日合计 = 单户本日合计 | ✅ 25 项全 `ALL_SAME`;第 1 天 30+80+60+35+60+160+100+80 = `605.00` | +| 有户改价 → 该项返回区间,下钻看到每户各自的价 | ✅ `VALUE_DIFF`,`unitPriceMin/Max = 60/90`;下钻三行 60 / 90 / 60 | +| 有户加项 → 标 N/M,下钻列出是哪几户加的 | ✅ `PARTIAL` `1/3`,下钻 `has=false/false/true` | +| 本日合计只含全团一致项,且标出未计入的项 | ✅ 合计由 `605.00` 降为 `545.00`,`excludedNodeCount=2`,两项 `countedInDayTotal=false` | +| 天数不一致 → 预警 + 列出是哪几户 | ✅ `dayCountConsistent=false`,`dayCount=3`,`dayCountOutliers` 指名到 `HL20260909093347891`(4 天) | +| 下钻每行带 `orderId` 供跳转;团期层无写口 | ✅ 每行均带;本次只新增两个 GET | +| `nodeKey` 稳定、不落库 | ✅ 两次请求 25 个 `nodeKey` 完全一致;库中无该列 | +| 已取消户不出现在汇总与下钻,也不计入分母 | ✅ 置 `CANCELLED` 后分母 3→2,其加项与多出的第 4 天一并消失,天数预警自动解除 | +| 空态与未知 `nodeKey` | ✅ 0 户团回 `days: []`;未知键回零命中清单;团期不存在回 `589500` | +| 权限 | ✅ `CUSTOMIZER` 角色两个端点均 `589507`;无 token 由网关拦 `401` | +| 查询次数与户数无关(禁 N+1) | ✅ `performance_schema` 计数:3 户与 55 户**均恰好 2 条** `order_itinerary_*` 查询;55 户耗时 88ms | + +**跨天项实测**(`dayNumber` 存在的理由):「巴尔虎蒙古部落」在第 1、2 天各排一次—— +不带 `dayNumber` 下钻回 6 行(3 户 × 2 天,各带自己的 `dayNumber`),带 `dayNumber=1` 回 3 行,与第 1 天那张卡一致。 + +**小计兜底实测**:该团 `total_amount` 全为空、`unit_price` 有值。兜底前本日合计恒为 `0`, +兜底后 `605.00`;无价的「早餐」小计仍为 `null`,不当 0 计入。 + +## 十、相关文档 + +- 实施单编号:GB-ADM-018 / GB-ADM-019(新增),实施单 `docs/group/实施单/04-团期行程安排.html` +- 需求:《团期模块详细设计 v3.0》§3.11「只读汇总、不建表」;SRS §0.13;《数据模型》§A.9D「归组键不落库」 +- ⚠️ **前端形态无原型可依**:《需求逐屏对照》的团期详情 12 屏里没有「行程」屏,本需求是 2026-08-31 jw 口述新增,契约与验收要点齐备但没有画面。界面长什么样需 jw 与 mmg 另定。 + +## 关联 / 联系人 + +- 工单:https://git.1814.love:8443/wx/HL/issues/7378 +- PR:https://git.1814.love:8443/wx/HL/pulls/7397 +- 后端:jw;前端:mmg