20 KiB
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 | 8559 | 团期子订单用车需求记录:countedHouseholdCount / countedInSummary 不再随 kind 筛选归零 | admin | wx(GIT) | 修改接口 | deployed | verified | not_required | PR #8612 已 squash 合并 dev-v3(3ecf387979),hl-order-service-v3 dev-v3 分支已滚测试服。本条只改取值语义,字段名、字段个数、HTTP 形态全部不变。 | 2026-09-30 | dev-v3 |
hl-order-service-v3: 团期子订单用车需求记录的「计入汇总」读数不再随 kind 筛选归零
存放目录:
changelogs-v2/2026-09/服务: hl-order-service-v3 (端口 8086) PR: #8612 Issue: #8559 日期: 2026-09-30 影响范围: 团期「查看需求」Tab 用车板块逐户明细端点GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households的两个取值(顶层countedHouseholdCount、每户households[].countedInSummary)
⚠️ 关键变化
- 🔴 这是取值语义纠错,不是字段变更:
countedHouseholdCount和households[].countedInSummary的字段名、类型、位置全部没动,变的是它们在带kind筛选时返回的值。 - 改前:带
kind=TRANSFER调用时,countedHouseholdCount恒为 0,并且返回的每一户countedInSummary恒为 false。原因是「这一户有没有被汇总计入座位」这个判据建在已经被kind截断过的需求行上——kind=TRANSFER的结果集里不可能出现 TRAVEL 行,判据对每一户都落成 false。而团级汇总里那些户的座位是实实在在加进去的,同一页面两块数据互相矛盾。 - 改后:判据改成「这一户在全部活跃需求行里有没有 TRAVEL 行」,与本次
kind筛选无关。三种调用(不传kind/kind=TRAVEL/kind=TRANSFER)下,同一户的countedInSummary取值相同,countedHouseholdCount读数相同。 - 前端要做的事:如果页面里有「
kind=TRANSFER时这个数恒为 0,所以隐藏/特判」这类兜底分支,请删掉——它现在会把正确的非零读数吞掉。另外不要再用「countedInSummary全 false」去推断当前处于接送机筛选态。 - 刻意没改:卡片出不出现仍然随
kind变(kind=TRANSFER时只报了行程用车的户不出卡),这是 #8151 起的既有行为,本次不动。 - 刻意没改:
householdCount(应报车户数)与vehicleRowCount(需求行数)的口径,本次一个字都没动。
一、背景
团期「查看需求」Tab 的用车板块是上下两块:上面是团级汇总,下面是逐户明细。逐户明细支持按 kind 切换筛选(行程用车 / 接送机 / 全部)。countedHouseholdCount 回答的问题是「这个团有几户被汇总计入了座位」——它是用来跟上面那块汇总对账的,天然与「我现在正在看哪一类需求」无关。
| 维度 | 改前(kind=TRANSFER) |
改后(kind=TRANSFER) |
|---|---|---|
countedHouseholdCount |
恒 0 |
与不传 kind 时相同 |
households[].countedInSummary |
每户恒 false |
与不传 kind 时逐户相同 |
| 卡片出现范围 | 随 kind 变 |
随 kind 变(未改) |
| 查库往返次数 | 1 次(IN 单值) | 1 次(IN 两值) |
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 团期子订单用车需求记录 | GET | /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households |
取值语义修正 | countedHouseholdCount 与 households[].countedInSummary 不再随 kind 筛选归零 |
三、接口详情
1. 团期子订单用车需求记录 GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households
VO: GroupVehicleHouseholdsRespVO
使用场景
团期详情「查看需求」Tab 的用车板块下半部分(逐户明细列表)。进入 Tab 时前端默认不传 kind(两类都返);用户点「行程用车 / 接送机」切页签时带上 kind。本端点只读,无副作用。权限点 group-batch:view。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
groupBatchId |
Path | Long | ✅ | 团期主订单 ID | 团期不存在时抛团期未找到错误 |
kind |
Query | String | ❌ | TRAVEL / TRANSFER,不传或空白 = 两类都返 |
其它取值抛 809000「用车需求类别非法」。注意与提交侧「不传按 TRAVEL」的缺省刻意相反 |
出参 Result<GroupVehicleHouseholdsRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
groupBatchId |
String | 团期主订单 ID(雪花,序列化为字符串) |
departDate |
String(yyyy-MM-dd) |
团期出发日,可为 null |
endDate |
String(yyyy-MM-dd) |
团期结束日,与团期详情同源,可为 null |
householdCount |
Integer | 应报车户数 = households 数组长度(含一份需求都没提交的空卡) |
vehicleRowCount |
Integer | 需求行数 = 各户 requirements 长度之和;可以小于 householdCount,不要当不变式用 |
countedHouseholdCount |
Integer | 🔴 被汇总计入座位的户数 = households 中 countedInSummary=true 的条数。不随 kind 筛选变化,三种筛选读数相同。与 householdCount 的差 = 只报接送机的户 + 未提交的户 |
households |
Array | 逐户卡片,按 orderNo 升序(orderNo 为空的排最后);单次最多 500 户 |
households[].orderId |
String | 子订单 ID(雪花,序列化为字符串) |
households[].orderNo |
String | 子订单号 |
households[].teamNo |
String | 团号,可为 null(不用订单号顶替) |
households[].customerName |
String | 客户姓名 |
households[].participantCount |
Integer | 出行人数 |
households[].consultantId |
String | 定制师 adminId,未指派为 null |
households[].consultantName |
String | 定制师姓名,未指派为 null |
households[].countedInSummary |
Boolean | 🔴 该户是否被团级汇总计入座位。不随 kind 筛选变化,同一户三种筛选下取值相同 |
households[].status |
String | 展示用需求状态;该户一份需求都没提交时为 null |
households[].statusName |
String | 状态中文名,status 为 null 时为 null |
households[].requirements |
Array | 该户活跃需求行,0~2 条;无行时为空数组不是 null |
households[].requirements[].requirementId |
String | 需求行 ID(雪花,序列化为字符串) |
households[].requirements[].kind |
String | TRAVEL / TRANSFER |
households[].requirements[].kindName |
String | 类别中文名 |
households[].requirements[].status |
String | PENDING_REVIEW / PENDING / PROCESSING / DONE |
households[].requirements[].statusName |
String | 状态中文名 |
households[].requirements[].fleet |
Array | 车型项:vehicleType / vehicleTypeName / seats / count |
households[].requirements[].specialTags |
Array | 特殊诉求标签:code / name |
households[].requirements[].remark |
String | 备注,≤500,可为 null |
households[].requirements[].serviceDates |
Array<String> | 服务日期(yyyy-MM-dd) |
households[].requirements[].headcount |
Integer | 用车人数 |
households[].requirements[].totalSeatCount |
Integer | 总座位数 |
households[].requirements[].remainingPassengerSeats |
Integer | 余座(扣司机位后) |
households[].requirements[].pickupRequired |
Boolean | 仅 TRANSFER 行有值,TRAVEL 行为 null |
households[].requirements[].dropoffRequired |
Boolean | 仅 TRANSFER 行有值,TRAVEL 行为 null |
households[].requirements[].returnRemark |
String | 打回备注,可为 null |
households[].requirements[].returnedAt |
String(date-time) | 打回时刻,可为 null |
请求示例
GET /v3/admin/order/group-batch/2101690789438570497/requirement/vehicle-households?kind=TRANSFER HTTP/1.1
Host: <admin-gateway>
Authorization: Bearer <admin-token>
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"groupBatchId": "2101690789438570497",
"departDate": "2026-09-12",
"endDate": "2026-09-16",
"householdCount": 3,
"vehicleRowCount": 1,
"countedHouseholdCount": 2,
"households": [
{
"orderId": "2102000000000000001",
"orderNo": "HL20260912100000001",
"teamNo": "26-0480",
"customerName": "陈昊",
"participantCount": 4,
"consultantId": "10001",
"consultantName": "李四",
"countedInSummary": true,
"status": "PENDING",
"statusName": "待车队配",
"requirements": [
{
"requirementId": "2103000000000000011",
"kind": "TRANSFER",
"kindName": "接送机",
"status": "PENDING",
"statusName": "待车队配",
"fleet": [
{ "vehicleType": "suv", "vehicleTypeName": "SUV", "seats": 7, "count": 1 }
],
"specialTags": [
{ "code": "child_seat", "name": "儿童安全座椅" }
],
"remark": null,
"serviceDates": ["2026-09-12"],
"headcount": 4,
"totalSeatCount": 7,
"remainingPassengerSeats": 2,
"pickupRequired": true,
"dropoffRequired": false,
"returnRemark": null,
"returnedAt": null
}
]
},
{
"orderId": "2102000000000000002",
"orderNo": "HL20260912100000002",
"teamNo": "26-0480",
"customerName": "王琳",
"participantCount": 2,
"consultantId": "10001",
"consultantName": "李四",
"countedInSummary": true,
"status": null,
"statusName": null,
"requirements": []
},
{
"orderId": "2102000000000000003",
"orderNo": "HL20260912100000003",
"teamNo": null,
"customerName": "赵敏",
"participantCount": 3,
"consultantId": null,
"consultantName": null,
"countedInSummary": false,
"status": null,
"statusName": null,
"requirements": []
}
]
}
}
上例即改后行为:
kind=TRANSFER筛选下仍然有countedHouseholdCount=2(前两户各有一条活跃 TRAVEL 行,被团级汇总计入了座位),只有第三户没报行程用车所以是false。改前这三户的countedInSummary会全是false、countedHouseholdCount会是0。
空数据 / 降级响应
团期下没有在团子订单(全部退团 / 取消)时,三个计数全 0、households 为空数组(不是 null):
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"groupBatchId": "2101690789438570497",
"departDate": "2026-09-12",
"endDate": "2026-09-16",
"householdCount": 0,
"vehicleRowCount": 0,
"countedHouseholdCount": 0,
"households": []
}
}
单次返回的户数上限为 500 户,超过时截断返回前 500 条并在服务端留 warn,不报错——页面仍可用。截断后 householdCount / vehicleRowCount / countedHouseholdCount 都按截断后的列表重算,三者与 households 数组始终自洽。
错误响应
kind 传了 TRAVEL / TRANSFER 以外的值:
{
"code": 809000,
"message": "用车需求类别非法:BUS",
"success": false,
"data": null
}
业务边界
- 鉴权:需要权限点
group-batch:view;未登录由网关拦截返 401。 - 只读:本端点零写入,重复调用无副作用,可安全轮询。
- 户范围:在团 = 仅排除 CANCELLED。退团 / 取消的户不出现在列表里。
- 只取活跃行:被打回的需求行已失活,不在本列表内(与用房侧「列出打回户」的行为刻意不同)。因此一户被打回后在这里表现为
requirements: []的空卡,而不是灰条。 - 卡片范围仍随
kind变:卡片集合 = 「应报车的户」∪「本次筛选命中需求行的户」。这一条本次未改。 countedInSummary不随kind变:它读的是该户在全部活跃行里有没有 TRAVEL 行。pickupRequired/dropoffRequired只在 TRANSFER 行有值,TRAVEL 行恒 null,不要用false去区分。- 雪花 ID 一律是字符串:
groupBatchId/orderId/consultantId/requirementId都以字符串下发,JS 直接当数字用会被静默截断。
四、契约约束与正确调用方式
本节只写后端接受 / 拒绝请求的规则,不写 UI 渲染建议。
✅ 正确 / ❌ 错误调用对照
| 场景 | 请求 |
|---|---|
| ✅ 进 Tab 默认拉全部 | GET .../vehicle-households(不带 kind) |
| ✅ 切到行程用车页签 | GET .../vehicle-households?kind=TRAVEL |
| ✅ 切到接送机页签 | GET .../vehicle-households?kind=TRANSFER |
| ✅ 显式传空值 | GET .../vehicle-households?kind=(空白按「两类都返」处理) |
| ❌ 传车型当类别 | GET .../vehicle-households?kind=bus → 809000 |
| ❌ 传小写类别 | GET .../vehicle-households?kind=transfer → 809000 |
切换筛选时的必要动作
切换 kind 时,顶部「计入汇总 N 户」这类读数不需要跟着置灰或隐藏——它在三种筛选下是同一个数。如果前端此前为了绕开恒 0 做过「接送机页签下不展示该数」的兜底,现在应当删掉,否则接送机页签会永远看不到这个已经正确的读数。
五、数据库行为
本端点是 GET 只读,零写入:不建行、不改行、不软删、不产生任何 outbox / MQ 事件。
本次改动只调整了服务端的读法(原先按 kind 下推到查询条件,现在恒查两类再在内存里按 kind 分流),SQL 往返次数未变(同一条 IN 查询,requirement_kind 的 IN 列表从 1 个值放宽到 2 个值)。
六、边界行为
- 未登录 → 401(网关拦截)。
- 权限点缺失 → 权限校验失败,不返回数据。
- 团期 ID 不存在 → 抛团期未找到业务错误,HTTP 200 + 业务错误码。
- 团期下无在团子订单 → 三个计数 0 +
households: [],不报错。 - 户数超 500 → 截断到前 500 条,三个计数按截断后重算,HTTP 200。
- 在团订单 ID 存在但订单行缺失(脏数据)→ 跳过该户并在服务端留 warn,整页仍可打开。
- 需求行
requirement_kind为空的脏行 → 显式跳过,不进任何计数。 - 老数据兼容:历史需求行缺
seats/count等字段时对应位为 null,不异常。
六.5、枚举 / 数据字典
kind(用车需求类别,VehicleRequirementKind)
所属字段: 请求 Query kind、响应 households[].requirements[].kind | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
TRAVEL |
行程用车 | 团期行程期间的用车需求;countedInSummary 的判据只认这一类 |
TRANSFER |
接送机 | 接送机 / 接送站需求;pickupRequired / dropoffRequired 只在这一类上有值 |
请求侧不传或传空白 = 两类都返(不是「默认 TRAVEL」)。
status(需求行状态)
所属字段: households[].status、households[].requirements[].status | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
PENDING_REVIEW |
待审核 | 定制师已提交,等团期管理员下发车务 |
PENDING |
待车队配 | 已下发车务,等车队配车 |
PROCESSING |
配车中 | 车务处理中 |
DONE |
配车完成 | 已完成 |
户级 status 为 null 表示该户一份活跃需求都没有(空卡)。
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
countedHouseholdCount |
字段存在,类型 Integer;kind=TRANSFER 时恒 0 |
字段、类型不变;三种筛选读数相同 |
households[].countedInSummary |
字段存在,类型 Boolean;kind=TRANSFER 时恒 false |
字段、类型不变;同一户三种筛选取值相同 |
| 其余全部字段 | — | 未变(无新增、无删除、无改名、无类型变化) |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
不传 kind 时的两个读数 |
正确 | 正确(未变) |
kind=TRAVEL 时的两个读数 |
正确 | 正确(未变) |
kind=TRANSFER 时的两个读数 |
countedHouseholdCount=0、每户 countedInSummary=false |
与不传 kind 时一致 |
| 「计入汇总」的判据数据源 | 已被 kind 截断的需求行 |
该户的全部活跃需求行 |
卡片是否随 kind 变 |
变 | 变(未改) |
householdCount / vehicleRowCount |
— | 未变 |
六.7、影响评估
- 是否破坏向后兼容: 否(无字段增删改名;只是
kind=TRANSFER时的取值由恒 0 / 恒 false 变为真实值) - 前端是否必须同步上线: 否(不改也能正常渲染,只是接送机页签下该数从「永远 0」变成真实值)
- 前端 workaround 清理点: 若页面里有「
kind=TRANSFER时countedHouseholdCount恒 0,所以隐藏该数 / 走另一套算法 / 用countedInSummary全 false 判断当前筛选态」这类兜底分支,删掉它们——保留会吞掉正确读数或做出错误的筛选态判断。
七、不影响范围
- 仅影响:
GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households的countedHouseholdCount与households[].countedInSummary两个取值。 - 零影响:
- 团级用车汇总端点(本次改的是明细侧读法,汇总侧一个字没动)
- 团期正式用车需求的存 / 读 / 撤回 / 免车四个端点
- 用房侧
requirement/hotel-households - 单户用车需求的提交、下发、打回链路
- 团期配车(fleet 侧)任何端点
- 历史数据:本次只改读法,不做任何数据迁移
八、测试环境已验证
- 代码事实(对
origin/dev-v3逐一查证):- 合并提交
3ecf387979(PR #8612 squash 合并进dev-v3)。 GroupBatchVehicleHouseholdService#households查库改为恒取TRAVEL+TRANSFER两类,另建travelOrderIds集合承载「计入汇总」判据;buildHousehold签名增加boolean countedInSummary形参,替代原先在被截断的rows上做的anyMatch。GroupVehicleHouseholdsRespVO#countedHouseholdCount与HouseholdItem#countedInSummary的@ApiModelProperty已同步写明「不随 kind 筛选变化,三种筛选读数相同」。- 卡片集合仍取自按
kind过滤后的rowsByOrder(既有行为,未改)。
- 合并提交
- 部署:
hl-order-service-v3的dev-v3分支已滚到测试服,本端点走管理端网关/v3/admin/**既有通配路由,无新增路由。
GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households → 200 ✓
GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households?kind=TRAVEL → 200 ✓
GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households?kind=TRANSFER → 200 + countedHouseholdCount 与前两次一致 ✓
九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|---|---|---|---|
| — | #8151 | 首次提供本端点(逐户明细 + kind 筛选) |
✅ 有效 |
| — | #8195 | 缺陷 2:householdCount 改为「应报车户数」含空卡;缺陷 5:endDate 与团期详情同源 |
✅ 有效 |
| 本 PR #8612 | #8559 | 「计入汇总」判据改为不受 kind 截断 |
✅ 最新 |
十、相关文档
- 关联 Issue: wx/HL#8559
- 关联 PR: wx/HL#8612
关联 / 联系人
链接
- Issue: #8559
- PR: #8612
- Merge commit: 3ecf387979
联系人
- 后端负责人: @wx