docs: add fleet daily assignment contract

这个提交包含在:
API Changelog Bot 2026-07-08 13:27:53 +08:00
父节点 31b047daf9
当前提交 fb963d2133

查看文件

@ -0,0 +1,396 @@
# 【前端对接·管理后台】车务每日派车切片与车型大类需求契约
> Issue: [wx/HL#4837](https://git.1814.love:8443/wx/HL/issues/4837)
> PR: [#4840](https://git.1814.love:8443/wx/HL/pulls/4840)、[#4841](https://git.1814.love:8443/wx/HL/pulls/4841)
> 服务: `hl-order-service-v3` + `hl-fleet-service`
> 日期: 2026-07-08
> 影响范围: 订单调整/用车需求、车务派单、矩阵派单、派单看板、车队对账
## 1. 结论
- 用车需求只选“车型大类”,不要选具体车型型号。前端应从 `GET /admin/fleet/vehicle-types/list` 或树接口的大类节点取 `typeKey`,提交到 `fleet[].vehicleType`
- 当前测试库 SUV 大类 `typeKey``suv2`;后端提交后会归一保存为规范 key `suv`。前端不要自己把 `suv2` 改成具体车型 ID,也不要提交 `modelId/modelName`
- `fleet_assignment` 已改为“每日切片”7 天行程同一辆车会落 7 条记录,每条 `startDate=endDate=serviceDate`
- 读接口仍按派车组展示,前端不要把每日切片渲染成 7 张卡。以 `assignmentGroupId` 作为同一连续派车段的稳定分组 key。
- 历史跨天单条派车记录已通过迁移拆分;新派单创建前也会兜底拆分历史占位,再按派车组消费。
## 2. 用车需求车型大类
### 2.1 车型大类列表
```http
GET /admin/fleet/vehicle-types/list
Authorization: Bearer <token>
```
响应示例:
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": [
{
"id": "2057378611889180674",
"typeKey": "suv2",
"typeName": "SUV系列",
"icon": "Car",
"description": null,
"sortOrder": 1,
"modelCount": 5,
"inUseCount": 4
},
{
"id": "2057378611889180675",
"typeKey": "mpv",
"typeName": "商务车",
"modelCount": 5,
"inUseCount": 10
}
]
}
```
前端取值规则:
| 用途 | 使用字段 |
|------|----------|
| 下拉展示 | `typeName` |
| 提交用车需求 | `typeKey` |
| 不可用于需求提交 | `models[].id``models[].modelName``vehicleTypeId` |
### 2.2 车型大类树
```http
GET /admin/fleet/vehicle-types
Authorization: Bearer <token>
```
响应中会带 `models[]`,这是给车型管理/价格日历/车辆档案使用的型号列表。订单用车需求仍只提交大类节点的 `typeKey`
```json
{
"code": 200,
"data": [
{
"id": "2057378611889180674",
"typeKey": "suv2",
"typeName": "SUV系列",
"models": [
{
"id": "2057378611889180701",
"modelName": "丰田普拉多",
"seats": 7
}
]
}
]
}
```
## 3. 提交用车需求
```http
PUT /v3/admin/order/2074724409473458177/vehicle-requirement
Authorization: Bearer <token>
Content-Type: application/json
{
"fleet": [
{
"vehicleType": "suv2",
"seats": 7,
"count": 1
}
],
"specialTags": ["中文司机", "需要接送机"],
"remark": "司机会蒙语,第 3 天需要儿童安全座椅"
}
```
响应示例:
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"id": "2074724419590119426",
"version": 1,
"isActive": true,
"status": "PENDING",
"submittedAt": "2026-07-08T13:17:03",
"claimerId": null,
"claimerName": null,
"claimedAt": null,
"branchTaken": "INIT_SUBMIT",
"previousVersion": null,
"assignmentDeletedCount": null
}
}
```
后端落库口径:
```json
[
{
"vehicleType": "suv",
"seats": 7,
"count": 1
}
]
```
说明:
- `suv2` 是当前测试库 SUV 大类真实 `typeKey`,后端会归一为 `suv` 参与车务过滤、矩阵、对账。
- `seats/count` 仍按组提交;每组默认覆盖整个行程,不支持“前几天/后几天车型不同”的 UI。
- 多车场景仍用多组或 `count>1` 表达,后端会展开为多个 `fleetItemIndex`
## 4. 创建派单与每日切片
```http
POST /admin/fleet/assignments
Authorization: Bearer <token>
Content-Type: application/json
{
"orderId": "2074724409473458177",
"orderNo": "HL20260708131700144",
"requirementId": "2074724419590119426",
"fleetItemIndex": 0,
"vehicleId": "2065329516292513793",
"driverId": "2074724495473479681",
"startDate": "2026-07-13",
"endDate": "2026-07-18",
"pickupAt": "海拉尔东山机场",
"dropoffAt": "海拉尔站",
"headcount": 2,
"protocolPrice": "700.00",
"holdMode": 0,
"fromEntry": "from-board",
"skipCityJunctionException": false,
"strictSeats": false,
"requestId": "fleet-assign-20260708-001"
}
```
响应示例:
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"id": "2074724499256741890",
"assignmentGroupId": "333114315542499328",
"assignmentStatus": "assigned",
"protocolPrice": "700.00",
"holdSentAt": null,
"confirmedAt": "2026-07-08T13:17:22",
"sideEffects": {
"vehicleStatusUpdated": true,
"driverStatusUpdated": true
}
}
}
```
落库不变量:
| 字段 | 口径 |
|------|------|
| `assignment_id` | 每日切片自己的 ID |
| `assignment_group_id` | 同一连续派车段共用,前端展示/操作分组 key |
| `start_date` / `end_date` | 每条切片都等于当天服务日 |
| `service_date` | 该条切片服务日 |
| `required_vehicle_type` | 规范车型大类 key,例如 `suv` |
| `protocol_price` | 派车时冻结的协议价日单价 |
示例2026-07-13 至 2026-07-18 会落 6 条:
```json
[
{"assignmentGroupId": "333114315542499328", "serviceDate": "2026-07-13", "startDate": "2026-07-13", "endDate": "2026-07-13"},
{"assignmentGroupId": "333114315542499328", "serviceDate": "2026-07-14", "startDate": "2026-07-14", "endDate": "2026-07-14"},
{"assignmentGroupId": "333114315542499328", "serviceDate": "2026-07-15", "startDate": "2026-07-15", "endDate": "2026-07-15"},
{"assignmentGroupId": "333114315542499328", "serviceDate": "2026-07-16", "startDate": "2026-07-16", "endDate": "2026-07-16"},
{"assignmentGroupId": "333114315542499328", "serviceDate": "2026-07-17", "startDate": "2026-07-17", "endDate": "2026-07-17"},
{"assignmentGroupId": "333114315542499328", "serviceDate": "2026-07-18", "startDate": "2026-07-18", "endDate": "2026-07-18"}
]
```
## 5. 矩阵/看板读取口径
### 5.1 矩阵月视图
```http
GET /admin/fleet/matrix/grid?year=2026&month=7&status=all
```
响应片段:
```json
{
"code": 200,
"data": {
"year": 2026,
"month": 7,
"daysInMonth": 31,
"vehicles": [
{
"id": "2065329516292513793",
"plate": "蒙C06E06",
"assignments": [
{
"id": "2074724499256741890",
"assignmentGroupId": "333114315542499328",
"orderNumericId": "2074724409473458177",
"orderNo": "HL20260708131700144",
"startDay": 13,
"endDay": 18,
"startDate": "2026-07-13",
"endDate": "2026-07-18",
"vehicleCategory": "suv",
"categoryLabel": "SUV",
"assignmentStatus": "assigned",
"protocolPrice": "700.00"
}
]
}
]
}
}
```
前端处理:
- 月视图应按 `vehicles[].assignments[]` 渲染,一个 `assignmentGroupId` 只是一条甘特段。
- 不要再按 DB 每日切片数量重复生成条。
- 拖拽/操作时优先保留 `assignmentGroupId`;旧接口仍以 `assignmentId` 作为 path var 时,传该组任一 `id` 即可,后端会按组处理已接通的分组操作。
### 5.2 矩阵日视图
```http
GET /admin/fleet/matrix/day-orders?date=2026-07-13
```
响应片段:
```json
{
"code": 200,
"data": [
{
"orderNumericId": "2074724409473458177",
"orderNo": "HL20260708131700144",
"startDate": "2026-07-13",
"endDate": "2026-07-18",
"dayInTrip": 1,
"totalDays": 6,
"orderAssignStatus": "assigned",
"assignments": [
{
"assignmentId": "2074724499256741890",
"assignmentGroupId": "333114315542499328",
"fleetItemIndex": 0,
"vehicleCategory": "suv",
"categoryLabel": "SUV",
"vehiclePlate": "蒙C06E06",
"driverName": "API按单险司机4792",
"assignmentStatus": "assigned",
"protocolPrice": "700.00"
}
]
}
]
}
```
### 5.3 看板详情
```http
GET /admin/fleet/board/orders/2074724409473458177
```
响应关注点:
```json
{
"code": 200,
"data": {
"transport": {
"batches": [
{
"travelerNames": "张测试, 李测试",
"transportNo": "CA4792",
"time": "2026-07-13 10:45:00",
"station": "海拉尔东山机场"
},
{
"travelerNames": "张测试, 李测试",
"transportNo": "K4792",
"time": "2026-07-18 16:30:00",
"station": "海拉尔站"
}
],
"pickupRequired": true
},
"operationLog": {
"records": [
{
"opType": "CONFIRMED",
"detail": {
"assignmentId": "2074724499256741890",
"assignmentGroupId": "333114315542499328",
"requiredVehicleType": "SUV",
"requiredSeats": 7,
"tripRange": "2026-07-13 至 2026-07-18"
}
}
]
},
"currentAssignment": {
"id": "2074724499256741890",
"assignmentGroupId": "333114315542499328",
"vehiclePlate": "蒙C06E06",
"driverName": "API按单险司机4792",
"assignmentStatus": "assigned",
"protocolPrice": "700.00"
}
}
}
```
说明:
- 终态 `completed/canceled``currentAssignment` 可能为 `null`,但操作日志仍保留 `assignmentGroupId`
- 大交通接送数据在 `transport.batches[]`;没有接送机/站时间时,调整订单快照接口使用 `vehicleTransportSummary.emptyText = "暂无接送机时间"`,见 44_4829 文档。
## 6. 车队对账影响
车队对账仍按每日服务日聚合。前端不用直接消费 `fleet_assignment` 每日切片,但要理解金额来源:
- `GET /admin/fleet/reconciliation/cars` 的车天数来自每日切片/对账 prep。
- 一个 6 天派车组会贡献 6 个车天。
- `protocolPrice` 是元/车天快照,估算金额 = 日单价 × 车天。
## 7. 本次验证证据
已在测试环境验证:
- 部署:`hl-fleet-service` deploy task `bcac32b8` 成功。
- 全链路 API`.tmp/4792_fleet_full_api_report.md`,115/115 passed。
- 专项契约验证:`.tmp/4837_daily_slice_contract_verify.md`,9/9 passed。
- 验证订单:`HL20260708131700144` / `2074724409473458177`
- 专项断言:
- `fleet_assignment` 未删除记录中 `start_date < end_date` 数量为 0。
- 主订单 2026-07-13 至 2026-07-18 生成 6 条每日切片。
- 同一组 `assignmentGroupId=333114315542499328` 在矩阵月视图只出现 1 次。
- 矩阵日视图 2026-07-13 能查到该派车组。
- 用车需求提交 `suv2` 后,订单侧保存为规范 `suv`