Quellcode durchsuchen

feat(T01): 迁移422/423 指标多期展示数据地基

- 迁移422: indicator_values 新增 date_source / ref_range_snapshot
- 迁移422 前置: health_indicators.collection_date 存在性兜底(放开实体映射前必须先行)
- 迁移423: 新建 body_fat_scale_records 表
- schema.sql 同步以上 DDL
Sisyphus vor 2 Wochen
Ursprung
Commit
85dcdd06b1

+ 62 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -3323,6 +3323,15 @@ insertSysConfigSeed("gift_membership_pay_price", "198000", "送会员商品达
         // 迁移420: 创建 ppoint_consumption_log(滚动一年销售奖档位聚合数据源;F-4)
         // 注:迁移419 已被 membership_levels.max_daily_analyze 占用,本表顺延为 420。
         createPpointConsumptionLogTable();
+
+        // 迁移422: 指标多期展示(DS-2026-10-25-01)
+        // 顺序红线:先确保 health_indicators.collection_date 物理列存在,才能放开实体映射;
+        //          顺序颠倒会导致缺列环境所有 health_indicators 查询报 Unknown column。
+        ensureHealthIndicatorsCollectionDateColumn();
+        migrateIndicatorValuesTimelineColumns();
+
+        // 迁移423: 体脂秤测量记录表(sourceId 由 memberId 改为 recordId 的前置表)
+        createBodyFatScaleRecordsTable();
     }
 
     private void migrateHealthDimensionsTable() {
@@ -14075,6 +14084,59 @@ log.info("迁移298: 已为无 openid 的 child 账号补齐 family_members 记
         }
     }
 
+    // ==================== 迁移422:指标多期展示数据地基(DS-2026-10-25-01) ====================
+
+    /**
+     * 迁移422 前置:确保 health_indicators.collection_date 物理列存在。
+     * 放开 HealthIndicator.collectionDate 的 @TableField(exist=false) 之前必须先跑这一步;
+     * 任一环境缺列都会让所有 health_indicators 查询报 Unknown column(报告详情页整体崩溃)。
+     */
+    void ensureHealthIndicatorsCollectionDateColumn() {
+        if (!columnExists("health_indicators", "collection_date")) {
+            ensureColumn("health_indicators", "collection_date",
+                    "DATE DEFAULT NULL COMMENT '指标采集日期(从报告提取的检测日期,非入库时间)'");
+            log.info("迁移422: 已补建 health_indicators.collection_date 列");
+        } else {
+            log.info("迁移422: health_indicators.collection_date 已存在,放开实体映射的前置条件满足");
+        }
+    }
+
+    /**
+     * 迁移422: indicator_values 增加日期来源标记 + 参考范围快照。
+     * date_source 区分采样日/上传日;ref_range_snapshot 保证历史判定可复现(字典改了不影响历史)。
+     */
+    void migrateIndicatorValuesTimelineColumns() {
+        ensureColumn("indicator_values", "date_source",
+                "VARCHAR(16) DEFAULT 'unknown' COMMENT 'report_date 来源: real=采样日/upload=上传日/confirm=确认日/unknown=未知(存量)'");
+        ensureColumn("indicator_values", "ref_range_snapshot",
+                "VARCHAR(100) DEFAULT NULL COMMENT '该期参考范围快照(为空时回落 indicator_definitions.ref_range)'");
+        log.info("迁移422: indicator_values 已补齐日期来源与参考范围快照 2 列");
+    }
+
+    // ==================== 迁移423:体脂秤测量记录表 ====================
+
+    /**
+     * 迁移423: 体脂秤测量记录表(一次测量一条记录)。
+     * 现状 sourceId=memberId + insert 会在第二次测量时撞 uk_source_definition 导致整批保存失败;
+     * 本表提供 recordId 作为 indicator_values.source_id 的新取值语义(服务层改造见 T05)。
+     */
+    void createBodyFatScaleRecordsTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `body_fat_scale_records` ("
+                    + "`id` BIGINT AUTO_INCREMENT PRIMARY KEY, "
+                    + "`member_id` BIGINT NOT NULL COMMENT '家庭成员ID(family_members.id)', "
+                    + "`family_id` BIGINT NULL COMMENT '所属家庭', "
+                    + "`image_url` VARCHAR(500) NULL COMMENT '体脂秤照片', "
+                    + "`measured_at` DATE NULL COMMENT '测量日期(取不到为NULL,观测 report_date 记为上传日)', "
+                    + "`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, "
+                    + "INDEX `idx_member_date` (`member_id`, `measured_at`) "
+                    + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='体脂秤测量记录'");
+            log.info("迁移423: 已创建 body_fat_scale_records 表");
+        } catch (Exception e) {
+            log.warn("迁移423: 创建 body_fat_scale_records 表失败(可能已存在): {}", e.getMessage());
+        }
+    }
+
     // ==================== 迁移420:P 点消费日志(F-4 滚动一年档位数据源) ====================
 
     // 迁移420: 创建 ppoint_consumption_log(消费支付成功时落一行,按消费时间滚动一年聚合)

+ 13 - 0
cfc-backend/src/main/resources/schema.sql

@@ -2592,6 +2592,8 @@ CREATE TABLE IF NOT EXISTS indicator_values (
     report_date DATE COMMENT '真实报告日期(时序查询键)',
     unit VARCHAR(32) COMMENT '观测单位',
     status VARCHAR(32) COMMENT '观测状态快照(正常/偏高/偏低等)',
+    date_source VARCHAR(16) DEFAULT 'unknown' COMMENT 'report_date 来源: real=报告提取的采样日 / upload=上传日(未识别到采样日) / confirm=确认入库日 / unknown=未知(存量行)',
+    ref_range_snapshot VARCHAR(100) DEFAULT NULL COMMENT '该期参考范围快照(为空时回落到 indicator_definitions.ref_range)',
     recorded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
     UNIQUE KEY uk_source_definition (source_type, source_id, definition_id),
     INDEX idx_source (source_type, source_id),
@@ -2599,6 +2601,17 @@ CREATE TABLE IF NOT EXISTS indicator_values (
     INDEX idx_subject_def_date (subject_id, definition_id, report_date)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='全域指标值';
 
+-- ========== 体脂秤测量记录表(迁移423;一次测量一条记录,支撑 sourceId=recordId 的多期时序) ==========
+CREATE TABLE IF NOT EXISTS body_fat_scale_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NOT NULL COMMENT '家庭成员ID(family_members.id)',
+    family_id BIGINT COMMENT '所属家庭',
+    image_url VARCHAR(500) COMMENT '体脂秤照片',
+    measured_at DATE COMMENT '测量日期(取不到为NULL,观测 report_date 记为上传日)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_member_date (member_id, measured_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='体脂秤测量记录';
+
 -- ========== 舌诊主记录表 ==========
 CREATE TABLE IF NOT EXISTS tongue_records (
     id BIGINT AUTO_INCREMENT PRIMARY KEY,