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:
WorkBuddy
2026-09-17 14:02:00 +08:00
parent 7a2693b239
commit 755a570849
22 changed files with 2164 additions and 136 deletions
+48 -1
View File
@@ -217,7 +217,54 @@ CREATE TABLE activities (
male_limit INT, female_limit INT, total_limit INT, min_age SMALLINT, max_age SMALLINT,
registration_start_at TIMESTAMP WITH TIME ZONE, registration_end_at TIMESTAMP WITH TIME ZONE,
need_review BOOLEAN, status VARCHAR(32), created_by VARCHAR(36),
created_at TIMESTAMP WITH TIME ZONE, updated_at TIMESTAMP WITH TIME ZONE, deleted_at TIMESTAMP WITH TIME ZONE
created_at TIMESTAMP WITH TIME ZONE, updated_at TIMESTAMP WITH TIME ZONE, deleted_at TIMESTAMP WITH TIME ZONE,
-- V023(Phase 14):多类型活动扩展列。此前测试库完全缺失,
-- 导致 Phase 14 全部新增逻辑零覆盖,且非法 activity_type 在测试期不可见
-- (上到 PostgreSQL 才撞 chk_activity_type 返回 500)。
activity_type VARCHAR(64) NOT NULL DEFAULT 'matchmaking',
registration_policy CLOB,
target_group VARCHAR(200),
industry_scope VARCHAR(200),
post_event_interest_enabled BOOLEAN NOT NULL DEFAULT FALSE,
CONSTRAINT chk_activity_type CHECK (activity_type IN (
'matchmaking', 'interest_social', 'relationship_education',
'premarital_guidance', 'marriage_custom_culture', 'anti_fraud'
))
);
-- ============================================================================
-- Phase 14(V001 + V023):活动组织与容量分组
-- 此前测试库完全缺失 organizations / activity_organizations / activity_capacity_groups,
-- 且 activities 缺 V023 全部新列 —— Phase 14 新增逻辑零覆盖。
-- 约束与生产 V023 对齐(chk_org_role 白名单)。
-- ============================================================================
-- V001: organizations 组织表(activity_organizations 的外键目标,且组织名用于活动详情展示)
CREATE TABLE organizations (
id VARCHAR(36) PRIMARY KEY, name VARCHAR(200), type VARCHAR(32),
contact_name VARCHAR(100), contact_phone_enc VARCHAR(500), status VARCHAR(32),
created_at TIMESTAMP WITH TIME ZONE, updated_at TIMESTAMP WITH TIME ZONE,
deleted_at TIMESTAMP WITH TIME ZONE
);
-- V023: activity_organizations 活动多组织关联表
CREATE TABLE activity_organizations (
id VARCHAR(36) PRIMARY KEY, activity_id VARCHAR(36) NOT NULL, organization_id VARCHAR(36) NOT NULL,
role VARCHAR(32) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE, updated_at TIMESTAMP WITH TIME ZONE,
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT uk_activity_org UNIQUE (activity_id, organization_id),
CONSTRAINT chk_org_role CHECK (role IN ('host', 'organizer', 'co_organizer', 'partner', 'venue_partner'))
);
-- V023: activity_capacity_groups 活动容量分组表
CREATE TABLE activity_capacity_groups (
id VARCHAR(36) PRIMARY KEY, activity_id VARCHAR(36) NOT NULL,
group_code VARCHAR(32) NOT NULL, rule_definition CLOB,
capacity INT NOT NULL DEFAULT 0, approved_count INT NOT NULL DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE, updated_at TIMESTAMP WITH TIME ZONE,
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT uk_capacity_group UNIQUE (activity_id, group_code)
);
-- ============================================================================