文件
hl-api-changelog/changelogs-v2/2026-10/03_8747_团期行程汇总按中途终止日截断-修改接口-管理后台.md
T

20 KiB
原始文件 Blame 文件历史

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 8747 团期行程汇总与下钻按出行中终止日截断:终止户之后的天不再计入覆盖户数,天头列出终止户 admin jw(GIT) 修改接口 deployed verified implemented mmg 17c98e509a296fc356835808f8741606d2aad8be v2.1 2026-10-03 已合并 dev-v3(9d1e87b38)并部署 TEST,自签 token 经网关实测:一团 3 户同一份 3 天行程、1 户 D1 出行中终止(真实终止接口),D1 仍 3/3 全团一致,D2、D3 各项 2/3 部分户、天头列出终止户、本日合计由 385.00 / 730.01 变为 0;下钻终止户 terminated=true、endDayNumber=1;对照团与两个存量团改前改后去掉新字段逐字一致(38 项);团级行程单、签单凭证改前改后逐字一致。前端待做:天头渲染「N 户已于 Dx 终止」并可展开户清单,不在每个节点行上重复标;下钻行按 terminated / endDayNumber 标「已于 Dx 终止」。前端已交付(2026-10-03):batch-itinerary.js 加 4 展示纯函数(天头提示停同日「N 户已于 Dx 终止」/不同天退化、清单行、下钻行按 endDayNumber!=null 且本行 dayNumber>endDayNumber 判标记,terminated 仅户级开关);ItineraryTab 天头渲染+点击展开户清单(节点行不重复标);ItineraryNodeModal 截断行标「已于 Dx 终止」;API spec 新增 4 例+组件 spec 新建 3 例全绿,提交 17c98e50。 2026-10-03 dev-v3

order-v3: 团期行程汇总按出行中终止日截断

服务: hl-order-service-v3 PR: #8766(已合入 dev-v3,合并提交 9d1e87b38) Issue: #8747


⚠️ 关键变化

🟢 两个接口纯加字段:汇总 days[] 新增 terminatedOrderCount、terminatedOrders[];下钻 items[] 新增 terminated、endDayNumber。其余字段、入参、判权、错误码不变。

🔴 有户出行中终止的团,终止日之后的读数会变:该户仍计入分母 totalHouseholds,但不再计入那几天任何项的户数 householdCount。原本全团一致的项会变成部分户(如 2/3),不再计入本日合计,dayTotalAmount 相应变小(可能为 0)。这是需求口径,不是回归。

🟢 团内没有终止户时,响应除新字段(0 / 空列表 / false / null)外逐字不变。


一、背景

出行中终止(POST /v3/admin/order/:id/terminate)只把订单置为 COMPLETED,并在 order_terminate_refund.end_day_number 记下停在第几天;行程表一行不动。团期行程逐日汇总(GB-ADM-018)与逐户下钻(GB-ADM-019)此前只排除已取消户,所以终止日之后那几天,这一户仍按完整行程计入覆盖户数,节点会被判成「全团一致」并计入本日合计——页面显示整团都去了,实际少一户。

实施单 04 §3.11.3 在 2026-09-01 已定「保留、可见、按天截断」,本单按此落地。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 团期行程逐日汇总(GB-ADM-018) GET /v3/admin/order/group-batch/:groupBatchId/itinerary 修改 终止户按天截断;days[] 新增 terminatedOrderCount / terminatedOrders[]
2 团期行程某项逐户下钻(GB-ADM-019) GET /v3/admin/order/group-batch/:groupBatchId/itinerary/nodes/:nodeKey 修改 户数与一致性按截断口径;items[] 新增 terminated / endDayNumber

三、接口详情

终止户的计数口径(两个接口相同):

项 口径
怎么算「已终止」 order_terminate_refund 有该户未软删的行;截断天取 end_day_number。按全团活跃户一次批量查,与户数无关
分母 totalHouseholds 计入终止户(已取消户照旧排除)
dayNumber ≤ endDayNumber 的天 该户照常计入
dayNumber > endDayNumber 的天 该户不计入任何项的户数 householdCount,不参与该天的一致性判定与本日合计
行程记录 不隐藏:下钻照常列出该户那一行(has=true、带价量);汇总项的 nodeIds 照常含该户的节点 ID,核单下钻要用
节点级「已用」 不做(实施单 04 §3.11.3:那是核单的职责)

本日合计为什么会变小:本日合计只算 ALL_SAME(全团一致)的项。终止户退出后,终止日之后的项最多只有 M−1 户有,按「只有部分户有」判为 PARTIAL,不再计入。所以有户中途终止的团,终止日之后各天的 dayTotalAmount 会变小,所有项都变成部分户时为 0。

1. 团期行程逐日汇总(GB-ADM-018) GET /v3/admin/order/group-batch/:groupBatchId/itinerary

VO: Result<GroupBatchItineraryRespVO>(无请求体)

使用场景

团期详情「行程」页签。天头按 terminatedOrderCount 渲染「N 户已于 Dx 终止」,点开看 terminatedOrders;节点卡片的「N/M 户」与本日合计按新口径显示。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ 团期 ID 不变

出参字段表

字段 类型 说明
days[].terminatedOrderCount Integer 🆕 本天已处于出行中终止的户数(截断天 < 本天)。这些户仍计入分母,但不计入本天任何项的户数;无终止户为 0,前端为 0 时不渲染天头提示
days[].terminatedOrders[] Array 🆕 本天已终止户清单,按 orderId 升序;无终止户为空数组
days[].terminatedOrders[].orderId String 🆕 子订单 ID(雪花,按字符串传)
days[].terminatedOrders[].teamNo String 🆕 团号;无团号为 null
days[].terminatedOrders[].orderNo String 🆕 子订单编号
days[].terminatedOrders[].customerName String 🆕 客户姓名
days[].terminatedOrders[].endDayNumber Integer 🆕 截断天:该户行程停在第几天
days[].nodes[].householdCount Integer 口径变:终止户在截断天之后不计入;只有终止户排了的项可为 0
days[].nodes[].consistency / countedInDayTotal String / Boolean 口径变:按截断后的户数判;终止日之后原全团一致的项变为 PARTIAL、不计入合计
days[].dayTotalAmount / excludedNodeCount BigDecimal / Integer 口径变:随上面的一致性结果重算,可能变小
其余字段 — 不变(totalHouseholds 含终止户;nodeIds 含终止户的节点 ID)

请求示例

GET /v3/admin/order/group-batch/2106297832652881921/itinerary HTTP/1.1
Authorization: Bearer <管理员 token>

响应示例

TEST 实测(截取第 2 天与其首项;乙户「娜仁其其格」第 1 天行程结束后出行中终止):

{
  "code": 200,
  "message": "成功",
  "data": {
    "groupBatchId": "2106297832652881921",
    "totalHouseholds": 3,
    "dayCountConsistent": true,
    "dayCount": 3,
    "dayCountOutliers": [],
    "days": [
      {
        "dayNumber": 2,
        "dayDate": "2026-10-21",
        "dayTitle": "是的复古风的水果",
        "dayTotalAmount": 0,
        "excludedNodeCount": 7,
        "terminatedOrderCount": 1,
        "terminatedOrders": [
          {
            "orderId": "2106297833152004097",
            "teamNo": "26-5148",
            "orderNo": "HL20261003161831138",
            "customerName": "娜仁其其格",
            "endDayNumber": 1
          }
        ],
        "nodes": [
          {
            "nodeKey": "c44f160ad5e71a8b",
            "nodeIds": ["2106297832707407873", "2106297833231695873", "2106297834452238339"],
            "nodeName": "巴尔虎蒙古部落",
            "nodeType": "SCENIC",
            "resourceType": "SCENIC_SPOT",
            "resourceId": "3001000000000000016",
            "resourceName": "巴尔虎蒙古部落",
            "startTime": null,
            "timePeriod": "EARLY_MORNING",
            "consistency": "PARTIAL",
            "householdCount": 2,
            "totalHouseholds": 3,
            "unitPrice": null,
            "unitPriceMin": 80.0,
            "unitPriceMax": 80.0,
            "quantity": null,
            "quantityMin": 1,
            "quantityMax": 1,
            "totalAmount": null,
            "countedInDayTotal": false
          }
        ]
      }
    ]
  }
}

同一团改前(旧构建)第 2 天:各项 householdCount=3、ALL_SAME,dayTotalAmount=385.0,没有终止字段。

空数据 / 降级响应

  • 团期无活跃子订单:days=[],不查行程、不查截断天。
  • 团内没有终止户:每天 terminatedOrderCount=0、terminatedOrders=[],其余字段与改前逐字一致。
  • 终止在最后一天(截断天 ≥ 行程天数):没有被截断的天,各天 terminatedOrderCount=0。

错误响应

code 场景
589507 无 group-batch:view,或定制师查看非本人名下的团期(不变)
589500 团期不存在(不变)
{
  "code": 589500,
  "message": "团期不存在",
  "data": null
}

业务边界

  • 截断只影响计数,不改行程数据,不隐藏任何项。
  • 已取消户照旧排除,不进分母,也不进截断天查询。
  • dayDate / dayTitle 仍按全部活跃户的多数值取,不受终止影响。
  • 天数一致性预警(dayCountConsistent / dayCountOutliers)不受终止影响:终止不改行程天数。

2. 团期行程某项逐户下钻(GB-ADM-019) GET /v3/admin/order/group-batch/:groupBatchId/itinerary/nodes/:nodeKey

VO: Result<GroupBatchItineraryNodeDetailVO>(无请求体)

使用场景

汇总卡片点进来看「哪几户有、哪几户不一样」。终止户那一行照常列出,前端按 terminated / endDayNumber 标「已于 Dx 终止」。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ 团期 ID 不变
nodeKey Path String ✅ 取自汇总接口,原样回传 不变
dayNumber Query Integer ❌ 取自汇总卡片所在天;不传 = 跨天全看 不变

出参字段表

字段 类型 说明
items[].terminated Boolean 🆕 该户是否已出行中终止(户级标记,与本行是哪天无关)
items[].endDayNumber Integer 🆕 截断天;未终止为 null。本行 dayNumber 大于它时,前端标「已于 Dx 终止」
householdCount / consistency Integer / String 口径变:只认未被截断的出现,与汇总卡片同口径;跨天下钻时,终止户在截断天及之前有出现就计户
items[].has Boolean 不变:该户行程里有没有这一项;终止户截断后的出现仍为 true(记录不隐藏),但不计入 householdCount
其余字段 — 不变

请求示例

GET /v3/admin/order/group-batch/2106297832652881921/itinerary/nodes/c44f160ad5e71a8b?dayNumber=2 HTTP/1.1
Authorization: Bearer <管理员 token>

响应示例

TEST 实测(第 2 天「巴尔虎蒙古部落」,截取前两户):

{
  "code": 200,
  "message": "成功",
  "data": {
    "groupBatchId": "2106297832652881921",
    "nodeKey": "c44f160ad5e71a8b",
    "dayNumber": 2,
    "nodeName": "巴尔虎蒙古部落",
    "consistency": "PARTIAL",
    "householdCount": 2,
    "totalHouseholds": 3,
    "items": [
      {
        "orderId": "2106297832598355970",
        "teamNo": "26-8025",
        "orderNo": "HL20261003161830984",
        "contactName": "孟庆和",
        "customerName": "孟庆和",
        "has": true,
        "dayNumber": 2,
        "unitPrice": 80.0,
        "quantity": 1,
        "totalAmount": 80.0,
        "startTime": null,
        "timePeriod": "EARLY_MORNING",
        "terminated": false,
        "endDayNumber": null
      },
      {
        "orderId": "2106297833152004097",
        "teamNo": "26-5148",
        "orderNo": "HL20261003161831138",
        "contactName": "娜仁其其格",
        "customerName": "娜仁其其格",
        "has": true,
        "dayNumber": 2,
        "unitPrice": 80.0,
        "quantity": 1,
        "totalAmount": 80.0,
        "startTime": null,
        "timePeriod": "EARLY_MORNING",
        "terminated": true,
        "endDayNumber": 1
      }
    ]
  }
}

空数据 / 降级响应

  • nodeKey 没有任何户命中:householdCount=0,每户一行 has=false,不报错(不变);terminated / endDayNumber 照常按户填。
  • 团内没有终止户:每行 terminated=false、endDayNumber=null,其余字段与改前逐字一致。

错误响应

code 场景
589507 无 group-batch:view,或定制师查看非本人名下的团期(不变)
589500 团期不存在(不变)
{
  "code": 589507,
  "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)",
  "data": null
}

业务边界

  • 同一户跨天多行时,terminated / endDayNumber 每行相同(户级);是否被截断看本行 dayNumber 是否大于 endDayNumber。
  • 跨天下钻(不带 dayNumber)的一致性只看未被截断的出现:终止户在截断后改过的价不会把该项判成各户不一致。

四、契约约束与正确调用方式

  • 天头提示只看 days[].terminatedOrderCount,为 0 时不渲染;不要在每个节点行上重复标终止。
  • 「N/M 户」直接用 householdCount / totalHouseholds,不要自己从下钻行数 has=true 去数——终止户截断后的行 has=true 但不计户。
  • 下钻行的终止标记按 endDayNumber != null && dayNumber > endDayNumber 判,terminated 只是户级开关。
  • nodeIds 仍含终止户的节点 ID,原样传给核单下钻 POST /v3/admin/order/group-batch/:groupBatchId/settlement/node-lines 即可看到全团核单行(含终止户已退未用的门票)。

五、数据库行为

  • 零 DDL、零数据迁移、零写入。
  • 新增一条只读批量查询:按全团活跃户 order_id IN (...) 读 order_terminate_refund.end_day_number(与核单下钻同一读口)。两个接口的查询次数由 2 次批量变为 3 次批量,均与户数无关。

六、边界行为

  • 截断天是 end_day_number(停在第几天,1 起),该天本身照常计入,之后的天才截断。
  • 终止户的 order_status 是 COMPLETED,留在活跃集里;已取消户(CANCELLED)照旧整体排除。
  • 只有终止户在截断后的某天排了的项:汇总里照常列出,householdCount=0、PARTIAL、不计入合计,nodeIds 含该户节点 ID。

六.6、修改前后对比

场景 改前 改后
3 户同一行程,1 户 D1 终止,看 D2 某项 3/3、ALL_SAME、计入合计 2/3、PARTIAL、不计入合计
同上,D2 本日合计 385.00 0
同上,D1 3/3、ALL_SAME 不变
同上,D2 天头 无终止信息 terminatedOrderCount=1,列出该户与 endDayNumber=1
下钻 D2 终止户那一行 与其他户无区别 terminated=true、endDayNumber=1,不计入 householdCount
团内无终止户 — 除新字段外逐字不变

六.7、影响评估

  • 是否破坏向后兼容:字段层面否(纯新增);读数层面,有终止户的团在终止日之后的户数、一致性与本日合计会变——这是需求要修的错误读数。
  • 前端是否必须同步上线:否。不改前端时页面照常显示,只是终止日之后的卡片显示部分户、本日合计变小,没有「已终止」的说明;改了才能在天头说明原因。
  • 团级文档:团级行程单(GET .../print-itinerary)与签单凭证(GET .../sign-voucher)也复用行程汇总,但本单让它们走不截断的入口,输出保持原样(TEST 上含终止户的团改前改后逐字一致)。文档是否也按实际截断另行定口径。
  • 回滚:revert PR #8766 后重新部署 order-v3,无数据需要恢复。

七、不影响范围

  • 两个接口的入参、判权(group-batch:view + 定制师归属)、错误码:不变。
  • 团级行程单、签单凭证:不变。
  • 核单下钻(GB-ADM-056):不变(本来就按户带截断天)。
  • 订单级行程、终止行程接口本身、核单与退款计算:不变。
  • 散客单(非团期):不涉及。小程序端:无影响。

八、测试环境已验证

环境:TEST(https://api.test.1814.love) 验证时间:2026-10-03 16:18~16:57 构建身份:TEST 后端检出 dev-v3 @ d778c9a71(包含本单合并提交 9d1e87b38),order-v3 两个实例 16:51 前后重启。零写入判据:部署后连查 6 次对照团汇总,每次 days[] 都带 terminatedOrderCount(旧字节没有这个键)。 身份:自签 token 直打网关;造数用超管,读口取证用管理员账号(非超管)。

8.1 造数(全部经业务接口,唯一 SQL 见下)

团 团号 内容
主团 T26-6215(2106297832652881921) 新班期出发 2026-10-20,3 天行程每天 8 项;3 户各 2 成人线下全款,第 4 户下单后取消;成团 → 声明整团免车 → 乙户「娜仁其其格」出行中终止 endDayNumber=1
对照团 T26-2604(2106297838424244226) 新班期,2 户全款,不终止
存量团 2106039583672299521(5 户)、2104839654727618562(3 户) 只读,近 6 小时无改动、无终止户

终止接口要求订单处于出行中,TEST 上从成团走到出行中要过七道出团门和夜间任务,故只对乙户一张订单用 SQL 把 order_status 由 CUSTOMIZING 置为 TRAVELLING,随后调真实终止接口;终止后订单 COMPLETED、order_terminate_refund.end_day_number=1。

8.2 改前 / 改后(同一批数据,旧构建取一次、新构建取一次)

检查 结果
主团 D1 8 项均 3/3、ALL_SAME,terminatedOrderCount=0
主团 D2 / D3 改前各项 3/3、ALL_SAME,本日合计 385.00 / 730.01;改后各项 2/3、PARTIAL、不计入合计,本日合计 0 / 0,terminatedOrderCount=1,清单为乙户、endDayNumber=1
主团 D2 / D3 nodeIds 仍含 3 户的节点 ID
主团下钻 D2(两项) 乙户 terminated=true、endDayNumber=1;其余两户 false / null;householdCount=2、PARTIAL
主团 totalHouseholds 3(终止户计入、取消户排除);取消户在汇总与下钻中均不出现
对照团、两个存量团 汇总、逐项下钻(按天与跨天)去掉新字段后改前改后逐字一致;新字段全为 0 / 空 / false / null
团级行程单、签单凭证(四个团) 改前改后逐字一致,含主团
数据指纹 四个团改前改后订单与行程行的最近改动时间一致,比对期间没有他人写入

合计 94 项检查全部通过。

本地证据

项 读数
GroupBatchItineraryServiceTest 41 例全过,其中本单新增 10 例(截断、截断当天、最后一天终止、只有终止户的项、跨天下钻、无终止户、取消户、截断天一次批量查询、summaryAsPlanned 不查截断天)
截断天查询次数 4 户团断言 mapTerminateEndDayByOrderIds 只调用一次且入参为全部活跃户
范围回归(有 Docker) groupbatch + settlement + archunit 共 340 类 / 5066 例,源码可执行类与报告逐类对账 340/340;2 例失败均为基底既有(TeamNoResponseFieldGateTest、GroupBatchAdminReadEndpointOwnershipArchTest,引入于 788b9c149,基底对照跑同样红),本单零新增

十、相关文档

  • Issue #8747;PR #8766
  • 需求:docs/group/实施单/04-团期行程安排.html §3.11.2 / §3.11.3
  • 前置:Issue #7378(GB-ADM-018 / 019 原实现)、#7873(核单下钻同一读口)

关联 / 联系人

链接

联系人

  • 后端负责人: @jw