# Tasks: 用户端统一订单列表 **Input**: Design documents from `/specs/030-unified-order-list/`(spec.md / plan.md / research.md / data-model.md / contracts/api-contract.md / quickstart.md) **Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/ **Tests**: spec(FR-010/SC-006)明确要求自动化测试,TDD:先红后绿。 **Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. ## Format: `[ID] [P?] [Story] Description` - **[P]**: Can run in parallel (different files, no dependencies) - **[Story]**: Which user story this task belongs to (e.g. US1, US2, US3) - Include exact file paths in descriptions ## 构建与测试命令(所有验证任务通用) ```bash export JAVA_HOME="$HOME/.jdks/graalvm-jdk-21.0.12+7.1" export PATH="$JAVA_HOME/bin:$PATH" mvn -pl ruoyi-admin -am test -Dtest='XxxTest' -Dsurefire.failIfNoSpecifiedTests=false ``` 已知基线红测试:flash payType 白名单(见项目记忆),与本 feature 无关,不修。 --- ## Phase 1: Setup (Shared Infrastructure) - [x] T001 基线验证:`mvn -pl ruoyi-admin -am compile` 通过;`git status` 工作区干净(test 分支同步状态)(2026-09-21 编译通过;工作区仅本 feature 文档未提交,代码零改动) --- ## Phase 2: Foundational (Blocking Prerequisites) **Purpose**: 游标、来源枚举、tab 谓词——三个用户故事共同依赖的纯逻辑件(全部可独立单测) - [x] T002 新增 i18n 错误 key:`no.unifiedorder.cursor.invalid`(游标非法)、`no.unifiedorder.param.invalid`(tab/source 非法)加到 `ruoyi-admin/src/main/resources/i18n/` 全部 6 个语言文件(**用 raw 字节模式 perl 脚本追加,沿用 029 教训,勿用 perl -i -pe 直接改写**);先 `grep -n "no.unifiedorder\|no.user.login.success" messages.properties` 找锚点追加(2026-09-21 完成:锚点 no.user.login.success 行后插入,git diff 每文件 +2 行,CR 计数=行数,UTF-8 单次编码) - [x] T003 TDD 先红:新建 `ruoyi-admin/src/test/java/com/ruoyi/app/unifiedorder/UnifiedCursorTest.java`——① 生成与解析往返一致 ② 非法输入(null/垃圾串/负数/越界)抛参数异常 ③ 排序全序比较器(时间 DESC → flash(1, size+1, false)` 查询(外卖 `PosOrderMapper`:userId+tab 条件+游标推进+`cretim DESC,id DESC`;闪送 `FlashDeliveryOrderMapper`:同构)→ Java 按 UnifiedCursor 全序归并 → 截取 size → DTO 组装(外卖批量回填 storeName:收集 shId 批量查 InfoUser 昵称)→ `unifiedStatus` 用 UnifiedOrderTab.matches 判定(all 查询下也要给)(2026-09-21 完成;UnifiedCursor 补 equals/hashCode 值语义) - [x] T010 新建 `ruoyi-admin/src/main/java/com/ruoyi/app/unifiedorder/UnifiedOrderQueryController.java`:`GET /system/unifiedOrder/list`,`@RequestHeader String token` + 显式 `@RequestParam`(tab/source/cursor/size);userId 解析方式复用 `UserOrderController.orderList` 现有 token 解析实现;参数校验用 MessageUtils 错误 key(2026-09-21 完成;@Auth+@Anonymous 照抄 orderList,坏 token 返回参数错误而非 500 崩溃) - [x] T011 转绿:`UnifiedOrder*Test` 全绿;`mvn -pl ruoyi-admin -am test -Dtest='UnifiedOrder*'` 通过(2026-09-21 35/35 绿) **Checkpoint**: MVP 可演示(全 tab 混排滚动 + 来源筛选,用测试数据) --- ## Phase 4: User Story 2 - tab 归类与 SQL 条件一致性 (Priority: P2) **Goal**: 6 tab 归类语义与现有外卖列表一致,且 SQL 条件与内存判定不漂移 **Independent Test**: 谓词边界样本全量 + wrapper SQL 片段断言 - [x] T012 TDD 先红:在 `UnifiedOrderTabTest` 追加 wrapper 断言用例:对每个 tab 断言 `posOrderWrapper().getCustomSqlSegment()` / `flashWrapper().getCustomSqlSegment()` 含预期条件片段(如 unpaid 含 pay_status 与 pay_type IN、refund 含 after_sale_status、闪送 active 含四个状态枚举值),防止 SQL 与 matches() 漂移(2026-09-21 完成,用 getSqlSegment() 求片段;注意 MP paramNameValuePairs 为懒填充,须先 getSqlSegment() 再读参数) - [x] T013 若 T012 发现 wrapper 片段与预期不符,修正 `UnifiedOrderTab.java` 条件构造(常量同源),转绿(无需修正:全部片段断言一次通过,零漂移) - [x] T014 在 `UnifiedOrderQueryServiceTest` 追加组合用例:tab×source 组合抽查(refund+flash 空路、active+takeaway 只外卖)转绿(2026-09-21 通过) **Checkpoint**: 归类语义有双保险(内存判定 + SQL 片段断言) --- ## Phase 5: User Story 3 - 闪送收件人视角 (Priority: P3) **Goal**: "我收的"闪送单进统一列表并带角色标记 **Independent Test**: mock 断言闪送路 wrapper 含 `(user_id = ? OR receiver_user_id = ?)`,role 判定正确 - [x] T015 TDD 先红:`UnifiedOrderQueryServiceTest` 追加:① 闪送路查询条件含 receiver_user_id OR(捕获 wrapper 断言)② A 寄 B 的单在 B 的查询结果中 role=receiver、A 的结果中 role=sender ③ user_id=receiver_user_id 同一人时结果仅一条且 role=sender ④ 渲染块含 role 字段(2026-09-21 红:临时剥离 OR/role 后 2 用例如期失败,self-send 基线通过) - [x] T016 实现:闪送路查询条件加 `receiver_user_id` OR;`UnifiedOrderQueryService` 组装 flash 项时判定 role(user_id 命中优先 sender)→ 转绿(2026-09-21 复原转绿,48/48) **Checkpoint**: 三条故事全部可独立验收 --- ## Phase 6: Polish & Cross-Cutting Concerns - [x] T017 按 data-model.md §5 在 `updatesql/sql.md` 追加三条索引语句(**标注"核实后执行:已存在则跳过"**,不直接执行)(2026-09-21 追加,CRLF 保持) - [x] T018 契约一致性核对:逐条比对 `contracts/api-contract.md`(接口/返回/字段块/tab 语义/错误)与实现,偏差改实现或改契约注明(2026-09-21 全对齐;3 处文档偏差已回写:data-model §4 去掉 del_flag(实体无此字段且现有列表不过滤,保持零漂移)、plan.md 测试文件名对齐 UnifiedOrderTabTest、plan.md 补 UnifiedOrderPageView) - [x] T019 全量验证:`mvn -pl ruoyi-admin -am test -Dtest='UnifiedOrder*'` 全绿 + `mvn -pl ruoyi-admin -am test` 全模块(旧接口零回归,基线红除外)+ compile 通过 + quickstart 手动场景核对;确认未触碰废弃代码清单文件(2026-09-21:UnifiedOrder* 48/48 绿;全模块 552 测试唯一失败=已知基线红 createRejectsUnsupportedPayType,零新增失败;未修改任何既有 Java 文件;quickstart 手动 curl 场景待开发者起服务后核对) - [x] T020 收尾:`git status` 对照 plan.md Project Structure 一致;交付说明列明:索引核实事项、契约文档移交前端、旧接口保留说明(2026-09-21 结构一致:7 主文件 + 4 测试 + i18n +2 行×6 + sql.md 追加) --- ## Phase 7: 追加需求(2026-09-21)——外卖卡片店铺信息修正 - [x] T021 外卖 `storeName` 修正为店铺名称:`UnifiedOrderQueryService` 批量回填由「shId 查 InfoUser 昵称」改为「mdId 查 PosStore.posName」,店铺已删/缺失时回退订单快照 posName/logo;新增 `storeAvatar`(PosStore.logo,同回退)、`goodsCount`(food 快照数组 number 求和,空/非法为 null);DTO/测试/api-contract/data-model 同步 - [x] T022 ACTIVE 谓词线下收款分支扩入现金:`UnifiedOrderTab` `payType='1'` → `IN ('1','4')`(SQL 与 matches 同源,常量改 `OFFLINE_PAYABLE_PAY_TYPES`),配合堂食/自取开放用户现金下单(027 spec 2026-09-21 变更记录,commit 31bb384);测试补 payType=4 判定与 wrapper 断言 --- ## Phase 8: 追加需求(2026-09-21)——来源筛选扩堂食/自取 - [x] T023 `source` 扩枚举 `dinein`(堂食 type=2)/`pickup`(自取 type=1),`takeaway` 收窄为仅外送单(type=0),`all` 不限 type 照常混排:`OrderSource` 增枚举值+`posOrderType`;`UnifiedOrderQueryService` 外卖路按查询源加 type 过滤、游标推进源随查询源(dinein/pickup 单选翻页同秒 id 决胜)、单选跳过闪送路;游标编码单选时为 dinein/pickup,混排仍只产生 takeaway/flash;测试补 type 谓词/路由/游标推进;api-contract/data-model/quickstart 同步 - [x] T024 闪送条目返回 `orderVersion`(乐观锁版本,取 `flash_delivery_order.version`,语义同 FlashDeliveryUserOrderListView):DTO flash 块加字段、`flashItem` 赋值;测试补 orderVersion 断言;api-contract/data-model 同步 --- ## Dependencies & Execution Order ### Phase Dependencies - **Phase 1**:无依赖 - **Phase 2**:依赖 Phase 1;**阻塞所有故事**(cursor/谓词是地基) - **US1 (Phase 3)**:依赖 Phase 2;US2/US3 都在其 Service 之上追加测试与实现 - **US2 (Phase 4)**:依赖 Phase 2(谓词);与 US1 的 Service 部分耦合(wrapper 断言) - **US3 (Phase 5)**:依赖 Phase 3 的 Service 存在 - **Polish (Phase 6)**:依赖全部故事完成 ### 单人执行顺序(推荐) T001 → T002 → T003/T004 → T005 → T006-T011(US1 一体)→ T012-T014(US2)→ T015-T016(US3)→ T017-T020 ### Parallel Opportunities - Phase 2 内 T004(cursor 文件)与 T005(tab 文件)不同文件可并行 - T006 与 T007(两个测试文件)可并行写 --- ## Implementation Strategy ### MVP First (Phase 1-3) 数据层纯逻辑件 + Service/Controller 主链路完成后,统一列表已可端到端演示(全部 tab + 来源筛选 + 滚动)。 ### Incremental Delivery 1. Phase 2 → 纯逻辑件(可先行 review) 2. Phase 3 → MVP 混排列表 3. Phase 4 → 归类双保险 4. Phase 5 → 收件视角补齐 5. Phase 6 → 索引/契约/回归/交付 --- ## Notes - 所有新文件 Lombok + 现有注释风格;保留 CRLF;Java 注释无 `*/` 序列 - **不修改任何既有订单写路径与旧列表接口**(FR-008 回归靠现有测试守护) - 错误消息一律 MessageUtils;tab/source 枚举校验在 Controller 层 - 闪送路 wrapper 的 OR 条件用 MP `and(w -> w.eq(...).or().eq(...))` 嵌套,注意与 tab 条件的括号层级