fix(activity): close Phase 14 KEEP-VERIFY/EXTEND — 11 backend + 3 frontend-contract + 2 test-fidelity defects
Phase 14 (multi-type public-welfare activities) verification closure. Backend (D1-D11): - D1 CRITICAL: ActivityMapper.countRemainingCapacity had no @Select and no XML -> activity detail / register / approve all threw Invalid bound statement. Removed the shell; unified on ActivityRegistrationMapper. - D2 CRITICAL: register & approve were read-then-write -> oversold under concurrency. Replaced with single atomic SQL (insertIfCapacityAvailable / approveIfCapacityAvailable, affectedRows==0 => full). - D3 HIGH: activityType / orgRole never validated against production chk_activity_type / chk_org_role -> 500 only on PostgreSQL. - D4 HIGH: ActivityListItemDTO had no activityType -> miniapp category tabs always empty (Gate failure). - D5: male/female counts were fabricated as approvedCount/2; now real aggregation over user_profiles.gender. - D6: minAge/maxAge was a TODO no-op; added assertAgeInRange. - D7: registration_policy write-only; per-type eligibility now enforced (identity / local eligibility / gender quota). - D8: POST /operator/activities dropped type, multi-org and capacity groups and set organizer_org_id to the operator user id; now delegates to createActivityFromAdmin (single implementation). - D9: three operator write endpoints were mounted on the user-side ActivityController (dual implementation, zero callers) -> moved. - D10: NPE on soft-deleted activity in getMyActivities; NPE/500 on missing admin form fields (Instant.parse(null)); tolerant time parsing added. - D11: DTO lacked registeredCount / status / coverFileId / description -> card always showed 0 registered, "view detail", no cover, no summary. Frontend contract (F1-F3): - F1: miniapp category code "classroom" is not in the production whitelist (should be relationship_education); three types had no tab at all. - F2: ui-activity-card read 7 fields that do not exist on the backend; now reads only real DTO fields, plus per-type badge and eligibility hint. - F3: admin-web had no activity management page at all; added ActivityManagementView.vue (type-driven dynamic form, multi-org, capacity groups, publish/cancel) + /activities route and nav entry. Test fidelity (T1-T2): - T1: deleted two assertTrue(true) placeholder tests (testNoOverbooking, testNoDuplicateRegistration) that covered the exact broken invariants. - T2: test schema lacked V023 columns/constraints and three tables; the activity tables were only in the OPTIONAL set (zero coverage) -> schema completed and moved to REQUIRED_IN_TEST_SCHEMA. Tests: ActivityPhase14Test (23 cases), ActivityConstraintGuardTest (9 cases, parses real db/migration/V*.sql plus miniapp source). Two negative-verification rounds proved both guard families actually turn red. Regression: 450 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS (baseline 420). admin-web vue-tsc 0 error, vite build OK.
This commit is contained in:
@@ -351,12 +351,51 @@ D2——`RelationshipService.createMatch` 不写 episode/lastActivityAt,与 `M
|
||||
|
||||
**Files:** activity 模块、Miniapp activity 页面、Admin activity 页面。
|
||||
|
||||
- [ ] 保留活动基础,完成 ActivityType 分类、按类型报名策略、capacity groups 和多组织关系。
|
||||
- [ ] 完成小程序分类 Tabs、类型化卡片、动态资格提示和后台动态表单。
|
||||
- [ ] 验证主办组织关系不自动授予用户库访问权限。
|
||||
- [x] 保留活动基础,完成 ActivityType 分类、按类型报名策略、capacity groups 和多组织关系。
|
||||
- [x] 完成小程序分类 Tabs、类型化卡片、动态资格提示和后台动态表单。
|
||||
- [x] 验证主办组织关系不自动授予用户库访问权限。
|
||||
|
||||
**Gate:** matchmaking 与课堂/反诈类活动显示和资格策略不同;容量按配置正确扣减。
|
||||
|
||||
> **复验完成(2026-09-17)** — Tag:`phase14-recovery-2026-09-17`
|
||||
>
|
||||
> **11 处后端缺陷(D1~D11)**:
|
||||
> - D1 **Critical 生产故障**:`ActivityMapper.countRemainingCapacity` 无 `@Select` 无 XML
|
||||
> → 活动详情/报名/审核三条路径必抛 `Invalid bound statement`。
|
||||
> - D2 **Critical 并发**:报名与审核均为「先读后写」→ 并发超卖;改
|
||||
> `insertIfCapacityAvailable` / `approveIfCapacityAvailable` 单条原子 SQL。
|
||||
> - D3 High 生产故障:`activityType` / `orgRole` 未校验生产 `chk_activity_type` /
|
||||
> `chk_org_role` 白名单("只在 PG 上炸")。
|
||||
> - D4 High 契约:`ActivityListItemDTO` 无 `activityType` → 小程序分类 Tabs 恒无数据。
|
||||
> - D5 Medium:男女人数为 `approvedCount/2` 编造 → 改按 `user_profiles.gender` 真实统计。
|
||||
> - D6 Medium:年龄门槛为 TODO 空壳,完全不生效。
|
||||
> - D7 Medium:`registration_policy` 只写不读,按类型资格策略为空(Gate 未落地)。
|
||||
> - D8 Medium:`POST /operator/activities` 丢弃类型/多组织/容量分组,且把
|
||||
> `organizer_org_id` 错设为操作者 userId → 后台动态表单无法创建带类型活动。
|
||||
> - D9 Medium:三个运营写入端点错挂在**用户端** `ActivityController`(双实现、零调用方)。
|
||||
> - D10 Medium NPE:软删活动致 `getMyActivities` NPE;后台漏填字段致创建活动 500。
|
||||
> - D11 Medium 契约:DTO 缺 `registeredCount`/`status`/`coverFileId`/`description`
|
||||
> → 卡片报名人数恒 0、按钮恒"查看详情"、封面与摘要恒不显示。
|
||||
>
|
||||
> **3 处前端契约缺陷(F1~F3)**:小程序分类 code `classroom` 不在生产白名单
|
||||
> (应为 `relationship_education`)且缺三类入口;卡片读取 7 个后端根本不存在的字段;
|
||||
> admin-web 完全没有活动管理页(已新增 `ActivityManagementView.vue` 类型驱动动态表单)。
|
||||
>
|
||||
> **2 处测试保真度缺陷(T1~T2)**:`testNoOverbooking` /
|
||||
> `testNoDuplicateRegistration` 为 `assertTrue(true)` 占位假绿(已删除);
|
||||
> 测试库缺 V023 列与三张表,且 activity 五表在 `EntityMigrationParityTest`
|
||||
> 只属"可选白名单"(零覆盖)→ 已补齐并移入必选集合。
|
||||
>
|
||||
> **负向验证两轮**:① 后端 —— 从 V023 删 `anti_fraud` + 报名退化为无容量检查
|
||||
> → 3 条用例精准变红;② 前端契约 —— 分类 code 改回 `classroom` + 卡片加回
|
||||
> `activity.suitableFor` → 3 条守卫精准变红。均已还原。
|
||||
>
|
||||
> **全量回归实测**:`[WARNING] Tests run: 450, Failures: 0, Errors: 0, Skipped: 3` /
|
||||
> `BUILD SUCCESS`(4 分 52 秒)。Phase 13A 基线 420 → 450(净 +30)。
|
||||
> admin-web `vue-tsc --noEmit` 0 error、`vite build` 通过。
|
||||
> **Phase 14 已关闭。Phase 15 未自动进入,等待用户确认。**
|
||||
> 报告:`docs/Phase-14-V11-Recovery-Completion-Report-2026-09-17.md`
|
||||
|
||||
### Phase 15:活动报名、审核与签到 — KEEP-VERIFY/EXTEND
|
||||
|
||||
**Files:** activity registration、checkin 服务和 U29~U32 页面。
|
||||
|
||||
Reference in New Issue
Block a user