# 水票配送订单：后端提示词（可直接发给后端）

> 目标：把“水票下单后 -> 配送员接单/配送 -> 用户确认 -> 自动确认”的闭环放到后端，用明确的字段 + 状态机校验保证不越权、不乱跳状态、并发不重复接单。
>
> 接口前缀：当前后端控制器为 `@RequestMapping("/api/glt/glt-ticket-order")`，下文默认都带 `/api` 前缀（如需兼容旧路径，可做网关转发或保留旧路由）。

## 0) 角色与权限边界（务必在后端兜底）
- 用户端（小程序用户）：只能看/操作自己的订单（`userId` = token userId）。
- 配送员端：只能看/操作分配给自己的订单（`riderId` = token userId / rider userId）。
- 管理端：按后台权限控制（可查询/派单/改状态，但仍需 tenantId 隔离）。

建议：对“配送员端接口”忽略前端传入的 `riderId/userId/tenantId`，统一从登录态注入，避免越权。

## 1) 订单查询（配送员端）
建议提供配送员专用分页接口：`GET /api/glt/glt-ticket-order/rider/page`（避免与后台管理分页混用）。
请支持以下筛选，并保证权限隔离：
- `deliveryStatus`：10待配送、20配送中、30待客户确认、40已完成（配送员端必要；不传默认=10）
- `keywords`：支持按地址/备注等模糊搜索（可选）
- 排序：建议默认 `sendTime asc, createTime desc`（或沿用后端默认排序，但请告知前端）

权限隔离要求（配送员端）：
- 只返回当前登录配送员的订单：后端强制 `param.riderId = loginUserId`（前端传不传都一样）。
- `tenantId/deleted` 等同样后端兜底（只查当前租户、只查未删除）。

返回字段建议（配送员端用得上）：
- 门店/仓库/用户/配送员的展示字段：`storeName/storeAddress/storePhone`、`warehouseName/warehouseAddress`、`nickname/phone/avatar`、`riderName/riderPhone`（现有 `pageRel/listRel` 已在做关联返回）。
- 导航相关（详见第 5 节）：收货地址 `lat/lng`、门店/仓库 `lngAndLat`（可关联返回或做快照字段）。

## 2) 配送流程字段（建议后端落库并回传）
订单表建议确保有以下字段（当前前端已按这些字段做流程判断/展示）：
- `riderId/riderName/riderPhone`：配送员信息
- `deliveryStatus`：10/20/30/40
- `sendStartTime`：配送员点击“开始配送”的时间（建议 datetime）
- `sendEndTime`：配送员点击“确认送达”的时间（建议 datetime）
- `sendEndImg`：送达拍照留档图片 URL（可选/必填由后端策略决定；建议 varchar(512)）
- `receiveConfirmTime`：客户确认收货时间（建议 datetime）
- `receiveConfirmType`：10客户手动确认、20配送照片自动确认、30超时自动确认

数据库变更（示例SQL见：`docs/sql/2026-02-06_glt_ticket_order_delivery_fields.sql`）。

强烈建议把“配送状态”与“业务状态(status=0/1、deleted=0/1)”分开，避免混用：
- `status/deleted`：系统通用字段（现有逻辑）
- `deliveryStatus`：配送流程状态（本需求新增）

## 3) 状态流转与校验（强烈建议在后端做）
请在更新订单时做状态机校验，避免前端绕过流程：
- `10 -> 20`：仅允许订单属于当前配送员，且未开始/未送达
- `20 -> 30`：配送员确认送达（可带 `sendEndImg`）
- `20/30 -> 40`：完成；来源可能是
  - 客户手动确认（写 `receiveConfirmTime` + `receiveConfirmType=10`）
  - 配送照片直接完成（写 `receiveConfirmTime` + `receiveConfirmType=20`，并要求 `sendEndImg`）
  - 超时自动确认（写 `receiveConfirmTime` + `receiveConfirmType=30`，建议由定时任务执行）

并发/幂等建议（避免重复点击/重复请求带来的脏数据）：
- 所有“状态变更接口”用条件更新实现原子校验：`UPDATE ... SET ... WHERE id=? AND rider_id=? AND delivery_status=? AND deleted=0`
- 对重复调用做幂等：
  - `start`：如果已是 20 则直接返回成功；如果已到 30/40 返回“状态不允许”
  - `delivered`：如果已是 30/40 则返回成功（或提示已送达）；避免重复写 `sendEndTime`
  - `confirm-receive`：如果已是 40 则返回成功（或提示已完成）

## 4) 建议新增/明确的接口能力
为了避免并发抢单/越权更新，建议新增更语义化的接口（或在 update 内做等价校验）：
- 接单（抢单/派单）：`POST /api/glt/glt-ticket-order/{id}/accept`
  - 配送员端：后端原子校验：仅当 `rider_id IS NULL`（或为 0）时才能写入当前 rider 信息
  - 管理端派单：允许传 `riderId`，但需校验骑手归属门店/租户（如有该约束）
- 开始配送：`POST /api/glt/glt-ticket-order/{id}/start`
  - 写：`sendStartTime=now`、`deliveryStatus=20`
  - 校验：必须 `riderId=当前登录配送员` 且当前 `deliveryStatus=10`
- 确认送达：`POST /api/glt/glt-ticket-order/{id}/delivered`
  - 入参：`sendEndImg`（可选/必填，按策略）
  - 写：`sendEndTime=now`、`deliveryStatus=30`、`sendEndImg`
  - 可选策略 A（推荐可配置）：若 `sendEndImg` 必填且存在，则可直接 `deliveryStatus=40` 并写 `receiveConfirmTime/Type=20`
- 客户确认收货：`POST /api/glt/glt-ticket-order/{id}/confirm-receive`
  - 校验：只能本人 `userId` 操作，且必须 `deliveryStatus=30`
  - 写：`deliveryStatus=40`、`receiveConfirmTime=now`、`receiveConfirmType=10`

接口返回建议：
- 成功统一返回 `ApiResult.success(...)`
- 失败请返回明确 msg（例如：`无权限`、`订单不存在`、`订单状态不允许`、`订单已被其他配送员接单`）

## 5) 为了“导航到收货地址/取货点”的字段补充（建议）
当前仅有 `address` 字符串，无法在小程序内 `openLocation` 精准导航；建议补充：
- 收货地址（推荐至少返回）：`receiverName`、`receiverPhone`、`province/city/region/address/fullAddress`、`lat/lng`
- 取货点（门店/仓库，推荐至少返回）：`store.lngAndLat`、`warehouse.lngAndLat`

实现方式二选一：
- 方式 A（更快）：查询时关联 `shop_user_address`、`shop_store`、`shop_warehouse`，把经纬度字段透出给前端。
- 方式 B（更稳）：下单时把收货地址的 `name/phone/lat/lng/fullAddress` 以及门店/仓库 `lngAndLat` 做快照写入订单，避免后续数据变更影响历史订单导航。

## 6)（可选但很有用）超时自动确认规则
- 建议后端提供可配置项：`autoConfirmHours`（例如 24h/48h）
- 定时任务扫描：`deliveryStatus=30` 且 `sendEndTime < now - autoConfirmHours` 的订单
- 原子更新：只更新仍处于 30 的订单，写入 `deliveryStatus=40`、`receiveConfirmTime=now`、`receiveConfirmType=30`

## 7)（可选）字段/枚举建议（便于前后端对齐）
- `deliveryStatus`：10待配送、20配送中、30待客户确认、40已完成
- `receiveConfirmType`：10客户手动确认、20配送照片自动确认、30超时自动确认
- 时间字段统一返回格式：`yyyy-MM-dd HH:mm:ss`（与项目现有 `@JsonFormat` 风格一致）
