docs(plan): IA 计划加 Task 0 订单过期态后端 + 前端 expired 处理 + 部署步骤

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
This commit is contained in:
wangjia
2026-07-12 17:14:09 +08:00
parent 6f74c80025
commit a0367b37e5
@@ -4,7 +4,7 @@
**Goal:** 把已评审的 IA(`docs/account-ia-reorg-design.html`)落成 Flutter:账户/设置/联系去重归位,Account 提为桌面一级项,顶栏加通知铃铛,账户页加邀请入口,并为 Spec ②推广激励 / ③系统通知 留占位屏与接口。
**Architecture:** 复用现有 Riverpod + shell(desktop/mobile/tablet) + `open(NavView, mobilePage)` 下钻模式。多数是改造现有页面(account_page/settings_page/account_screens)+ 导航(navigation_provider/desktop_shell);新建仅 2 个占位屏(InviteScreen/NotificationsScreen)+ 铃铛 widget。邀请奖励引擎、通知下发机制**不在本计划**(各自 Spec)。
**Architecture:** 复用现有 Riverpod + shell(desktop/mobile/tablet) + `open(NavView, mobilePage)` 下钻模式。多数是改造现有页面(account_page/settings_page/account_screens)+ 导航(navigation_provider/desktop_shell);新建仅 2 个占位屏(InviteScreen/NotificationsScreen)+ 铃铛 widget。**唯一后端改动 = 订单过期态(Task 0display-only 零迁移)**——付款/订单核心后端已完成并部署生产。邀请奖励引擎、通知下发机制**不在本计划**(各自 Spec ②③,前后端一起)。
**Tech Stack:** Flutter / Dart · flutter_riverpod 2.x · 现有 `PangolinIcons`(生成,勿手改) · `AppText` 抽象基类 + 6 语言 strings。
@@ -18,6 +18,77 @@
---
### Task 0: 后端 — 订单过期态(display-only,零迁移)
**Files:**
- Modify: `server/internal/pay/handler.go`(`orderViewFromRow` + `orderPendingTTL` 常量)
- Test: `server/internal/pay/handler_test.go`
**Interfaces:**
- Produces: 订单视图 `status` 新增可能值 `"expired"`(供前端映射)。判定:本地台账 `status=="created"``created_at + orderPendingTTL < now` → 视图显 `expired`(DB 不改,与金额兜底同为展示层)。
- [ ] **Step 1: 写过期判定测试(失败)**
`handler_test.go` 加:
```go
// 老 created 单超 TTL → 列表视图显 expired;近期 created → 仍 created。
func TestList_ExpiredWhenStale(t *testing.T) {
router, st := newHandlerRig(t, nil)
ctx := context.Background()
// 直接构造两条 created 单,改 created_at 到很久以前 / 刚刚
if err := st.Insert(ctx, 1, "uuid-1", "pro_month", "old", "alipay", 2999, "CNY"); err != nil { t.Fatal(err) }
if err := st.Insert(ctx, 1, "uuid-1", "pro_month", "fresh", "alipay", 2999, "CNY"); err != nil { t.Fatal(err) }
// 把 old 的 created_at 回拨到 TTL 之前
if _, err := st.db.ExecContext(ctx,
`UPDATE pay_purchases SET created_at = ? WHERE out_trade_no = 'old'`,
time.Now().Add(-2*orderPendingTTL).UTC()); err != nil { t.Fatal(err) }
w := httptest.NewRecorder()
router.ServeHTTP(w, authed(httptest.NewRequest(http.MethodGet, "/v1/pay/orders", nil), 1))
var resp struct{ Orders []orderView `json:"orders"` }
_ = json.Unmarshal(w.Body.Bytes(), &resp)
got := map[string]string{}
for _, o := range resp.Orders { got[o.OrderNo] = o.Status }
if got["old"] != "expired" { t.Fatalf("old 应过期, got %q", got["old"]) }
if got["fresh"] != "created" { t.Fatalf("fresh 应 created, got %q", got["fresh"]) }
}
```
> 注:`st.db` 是 `Store` 的私有字段,测试同包(`package pay`)可访问。
- [ ] **Step 2: 跑测试确认失败**
Run: `cd server && go test ./internal/pay/ -run TestList_ExpiredWhenStale`
Expected: FAIL(old 仍为 created)。
- [ ] **Step 3: 实现过期判定**
`handler.go` 加常量 + 在 `orderViewFromRow` 里判定:
```go
// 未付订单展示层过期窗口(近似 pay 会话时效;仅影响列表/详情显示,不改台账)。
const orderPendingTTL = 30 * time.Minute
```
`orderViewFromRow` 组装 `v` 后、`return v` 前加:
```go
if row.Status == "created" && row.CreatedAt.Add(orderPendingTTL).Before(time.Now()) {
v.Status = "expired" // 展示层:台账仍 created,可继续支付/取消
}
```
- [ ] **Step 4: 跑测试确认通过**
Run: `cd server && go test ./internal/pay/`
Expected: ok(含新测试 + 原有全绿)。
- [ ] **Step 5: Commit**
```bash
git add server/internal/pay/handler.go server/internal/pay/handler_test.go
git commit -m "feat(server/pay): 订单展示层过期态(created 超 TTL 显 expired,零迁移)"
```
> **部署**:本任务改 server,需重编 linux 二进制 + 部署 pangolin1(Task 8 统一部署,或单独发)。
---
### Task 1: 导航枚举加 invite / notifications
**Files:**
@@ -87,6 +158,8 @@ git commit -m "feat(nav): NavView 加 invite/notifications + kAccountSubViews
String get settingsGroupConn; // 连接 / Connection
String get settingsGroupAppearance;// 外观 / Appearance
String get settingsGroupAbout; // 关于 / About
// ── 订单过期态(Task 0 后端产出) ──
String get orderStatusExpired; // 已过期 / Expired
```
- [ ] **Step 2: 6 份 strings 各实现(带 @override)**
@@ -365,6 +438,36 @@ git commit -m "feat(settings): 删购买/订单卡 + 归连接/外观/关于三
---
### Task 7.5: 订单页处理 expired 态
**Files:**
- Modify: `client/lib/screens/orders_page.dart`(状态→pill 映射)
**Interfaces:**
- Consumes: 后端 `status=="expired"`(Task 0)、`t.orderStatusExpired`(Task 2)。
- [ ] **Step 1: orders_page 状态映射加 expired**
`orders_page.dart``_statusPill`(created→connecting / paid→connected / canceled→neutral)加一支:
```dart
'expired' => (status: PangolinStatus.neutral, label: t.orderStatusExpired),
```
过期单动作沿用 created 的「继续支付」(台账仍可 retry;retry 若遇 pay 会话过期会 409→客户端已有重新下单兜底)。
- [ ] **Step 2: analyze**
Run: `cd client && flutter analyze lib/screens/orders_page.dart`
Expected: No issues found。
- [ ] **Step 3: Commit**
```bash
git add client/lib/screens/orders_page.dart
git commit -m "feat(client/orders): 订单列表/详情显示 expired 过期态"
```
---
### Task 8: 全量验收 + 收尾
**Files:** 无(验收)
@@ -384,15 +487,24 @@ Expected: All tests passed。
Run: `cd /Users/wangjia/code/pangolin/.claude/worktrees/pay-v2-integration && node tools/check-l1-sync.mjs && node design/prototype/tools/check-ds.mjs`
Expected: 图标同集 OK + l10n 完整 OK + check-ds 5 项过。
- [ ] **Step 4: 更新决策日志**
- [ ] **Step 4: 服务端重编 + 部署(Task 0 改了 server)**
`docs/pay-v2-dev-log.md` 追加「IA 重构落地」step。
```bash
cd server && go test ./... && GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -o /tmp/pgl-new ./cmd/server
scp /tmp/pgl-new pangolin1:/tmp/pgl-new
ssh pangolin1 'cp -a /usr/local/bin/pangolin-server /usr/local/bin/pangolin-server.bak-ia && mv /tmp/pgl-new /usr/local/bin/pangolin-server && chmod +x /usr/local/bin/pangolin-server && systemctl restart pangolin-server && sleep 2 && systemctl is-active pangolin-server'
```
Expected: `active`。验证 `curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/v1/pay/orders` → 401(路由在)。
- [ ] **Step 5: Commit**
- [ ] **Step 5: 更新决策日志**
`docs/pay-v2-dev-log.md` 追加「IA 重构落地 + 订单过期态」step。
- [ ] **Step 6: Commit**
```bash
git add docs/pay-v2-dev-log.md
git commit -m "docs(pay-v2): IA 重构落地验收记录"
git commit -m "docs(pay-v2): IA 重构 + 订单过期态 落地验收记录"
```
---