# BD Manual: goodsStockWarningInfo（库存预警信息）

## 变更概述

- 日期：2026-02-24
- 触发：一致性检查报告发现 API 独有嵌套字段 `goodsStockWarningInfo` 未映射到 ODS/DWD
- 迁移脚本：`db/etl_feiqiu/migrations/2026-02-24__add_goods_stock_warning_info.sql`

## 字段来源

API 端点 `/TenantGoods/GetGoodsInventoryList` 返回的 `store_goods_master` 记录中包含嵌套对象：

```json
{
  "goodsStockWarningInfo": {
    "tenant_goods_id": 0,      // 冗余，已有同名顶层字段
    "site_goods_id": 0,        // 冗余，对应顶层 id
    "sales_day": 0.0,          // → warning_sales_day
    "warning_day_max": 0,      // → warning_day_max
    "warning_day_min": 0       // → warning_day_min
  }
}
```

仅提取 3 个有效字段，冗余 ID 不重复收录。

## 新增字段

| 层 | 表 | 列名 | 类型 | 说明 |
|---|---|---|---|---|
| ODS | `ods.store_goods_master` | `warning_sales_day` | NUMERIC(18,2) | 库存预警参考的日均销量 |
| ODS | `ods.store_goods_master` | `warning_day_max` | INTEGER | 预警天数上限 |
| ODS | `ods.store_goods_master` | `warning_day_min` | INTEGER | 预警天数下限 |
| DWD | `dwd.dim_store_goods_ex` | `warning_sales_day` | NUMERIC(18,2) | 同 ODS，直接映射 |
| DWD | `dwd.dim_store_goods_ex` | `warning_day_max` | INTEGER | 同 ODS，直接映射 |
| DWD | `dwd.dim_store_goods_ex` | `warning_day_min` | INTEGER | 同 ODS，直接映射 |

## 数据流

```
API goodsStockWarningInfo (嵌套 JSON)
  ↓ _merge_record_layers 扁平化（_STOCK_WARNING_FIELD_MAP）
ODS ods.store_goods_master (warning_sales_day / warning_day_max / warning_day_min)
  ↓ DWD FACT_MAPPINGS 直接映射
DWD dwd.dim_store_goods_ex (同名列)
```

## 代码变更

| 文件 | 变更 |
|---|---|
| `apps/etl/connectors/feiqiu/tasks/ods/ods_tasks.py` | `_merge_record_layers` 增加 `goodsStockWarningInfo` 扁平化逻辑 |
| `apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py` | `FACT_MAPPINGS["dwd.dim_store_goods_ex"]` 增加 3 个映射 |
| `db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/schemas/ods.sql` | baseline 同步 |
| `db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/schemas/dwd.sql` | baseline 同步 |

## 兼容性

- 后端 API：无影响（后端不直接读取 ODS/DWD 层这些字段）
- 小程序：无影响
- ETL：ODS schema-aware 插入自动识别新列；DWD 通过 FACT_MAPPINGS 映射
- 管理后台：如需展示库存预警信息，可从 `dwd.dim_store_goods_ex` 读取

## 回滚策略

```sql
ALTER TABLE ods.store_goods_master
    DROP COLUMN IF EXISTS warning_sales_day,
    DROP COLUMN IF EXISTS warning_day_max,
    DROP COLUMN IF EXISTS warning_day_min;
ALTER TABLE dwd.dim_store_goods_ex
    DROP COLUMN IF EXISTS warning_sales_day,
    DROP COLUMN IF EXISTS warning_day_max,
    DROP COLUMN IF EXISTS warning_day_min;
```

## 验证 SQL

```sql
-- 1. 确认 ODS 新列存在
SELECT column_name, data_type FROM information_schema.columns
 WHERE table_schema = 'ods' AND table_name = 'store_goods_master'
   AND column_name IN ('warning_sales_day', 'warning_day_max', 'warning_day_min')
 ORDER BY column_name;
-- 预期：3 行

-- 2. 确认 DWD 新列存在
SELECT column_name, data_type FROM information_schema.columns
 WHERE table_schema = 'dwd' AND table_name = 'dim_store_goods_ex'
   AND column_name IN ('warning_sales_day', 'warning_day_max', 'warning_day_min')
 ORDER BY column_name;
-- 预期：3 行

-- 3. 确认注释已设置
SELECT c.column_name, pgd.description
FROM information_schema.columns c
JOIN pg_catalog.pg_statio_all_tables st ON st.schemaname = c.table_schema AND st.relname = c.table_name
JOIN pg_catalog.pg_description pgd ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
WHERE c.table_schema = 'ods' AND c.table_name = 'store_goods_master'
  AND c.column_name LIKE 'warning_%';
-- 预期：3 行，description 非空
```
