feat(W1): full 5-status account lifecycle (pending/active/suspended/expired/deleted)
- Non-active statuses (pending/suspended/expired/deleted) now uniformly: disable AList user + clear role scopes (soft, keeps user/role/CP/history); no-op when the customer has no AList user yet (pure NocoDB label). - Resume transitions (X -> active) recompute effective grants from current CustomerProducts + expiry, never restore stale scopes. - businessChanged() now also treats after-insert (new row, no previous) as a business change so fresh customers are processed on first webhook. - Lifecycle test covers the full matrix: 10 scenarios, all green. - AccessGrants still not materialized (deferred to W4 per decision). - Customers.status stays account-level, outside permission calc.
This commit is contained in:
@@ -43,17 +43,20 @@ Base: **`AlistAccess`**(`p3lzkresswyowqg`),与旧 `accessDB` 完全隔离
|
||||
### 2. W1 客户开通 + Status Lifecycle — **实测跑通** ✅
|
||||
- 文件:`n8n/W1-customer-provision.json`(由 `n8n/w1-body.js` + 共享函数库经 `gen_w1.js` 生成)
|
||||
- 流程:Webhook → Extract customerId(同时透传 webhook 的 old/new rows)→ Load Env → **单 Code 节点 "Provision & Apply Lifecycle"**(含 login + status 分支 + write-back)
|
||||
- **Step 3 新增 status lifecycle**:
|
||||
- `suspended`/`deleted` → disable AList user + 清空 role scopes(soft delete,保留 user + NocoDB 历史)
|
||||
- `active` → provision(建/更新 user+role)→ **重算 desired scopes(复用共享 calculateEffectiveGrants,不恢复旧值)** → re-enable
|
||||
- 其他 → no-op
|
||||
- **幂等**:见 `n8n/test_w1_lifecycle.js`(26 断言全过,含 echo/suspend 复用/suspend→active resume 重算不恢复陈旧/sync_required 强制重算)
|
||||
- **Lifecycle matrix(5 状态,2026-08-23 定稿)**:
|
||||
- `active`(无 user)→ provision(建 role→建 user)+ recompute + enable
|
||||
- `active`(有 user)→ re-enable + recompute(不恢复旧 scopes)+ apply
|
||||
- `pending`/`suspended`/`expired`/`deleted`(有 user/role)→ disable AList user + 清空 role scopes(soft,保留 user/role/CP/history)
|
||||
- `pending`/`suspended`/`expired`/`deleted`(无 user)→ **no-op**(纯 NocoDB 标签,不建 AList user)
|
||||
- 所有 `X → active` resume → recompute(从当前有效 CP + expiry),**永不恢复旧 scopes**,**不自动 mutate CustomerProducts**
|
||||
- **幂等 + loop 防护**:见 `n8n/test_w1_lifecycle.js`(10 场景全过,覆盖完整 matrix + echo 短路 + sync_required 强制重算)
|
||||
- **Loop guard(2026-08-23 重写,核心)**:不再用 `sync_status==='synced'`,改用 **old/new field comparison + `sync_required` 标志**:
|
||||
- `businessChanged()` = 业务字段(status/username/password/display_name/email/notes)在 `previous_rows`→`rows` 间变化,**或** `sync_required` 置位
|
||||
- `businessChanged()` = 业务字段(status/username/password/display_name/email/notes)在 `previous_rows`→`rows` 间变化,**或** `sync_required` 置位,**或 after-insert(有 new 无 old)**
|
||||
- echo(只动了 alist_user_id/alist_role_id/sync_*/last_sync*)→ 立即 `skip_writeback_echo`,零 AList 变更、零 PATCH
|
||||
- **write-back 值比对守卫**:writeBack() 先 GET 当前记录,只 PATCH 值真会变的字段;值没变 → 无 PATCH → 无 webhook → 从物理上掐断 loop
|
||||
- 写回时始终清 `sync_required: false`
|
||||
- base_path 硬编码 `'/'`;空 role 数组先建 role 再建 user
|
||||
- ⚠ **AccessGrants 本次不物化**:不作为 lifecycle source-of-truth,保持现状,等 W4 统一设计(用户明确指示)
|
||||
|
||||
### 3. W2 CustomerProduct 权限同步 — **实测跑通** ✅
|
||||
- 文件:`n8n/W2-customerproduct-sync.json`(`n8n/w2-body.js` + 共享库 → `gen_w2.js`)
|
||||
|
||||
Reference in New Issue
Block a user