Files
i/server/migrations/0001_init.sql
T
wsh5485 65b0bb04f8 feat: import CarLog v2.8 code + dev plan
把 CarLog v2.8 全套源码 + 配置导入到 i 仓库作为 baseline:
- server/src/ (13 个路由 + middleware + services + config)
- server/migrations/ (0001~0018 共 18 个迁移 + mysql)
- server/test/ (12 文件 101 测试)
- client/src/ (20 个 view + components + stores + api + composables)
- client/public/ + client/scripts/
- 全部配置文件 (.editorconfig, .eslintrc.json, .prettierrc.json, vitest.config.js, lighthouserc.json, .pa11yci.json, package.json, carlog-init.sql)
- .husky/pre-commit (git hooks)
- docs/install/ (宝塔部署文档)

不含:
- node_modules/ (本地 npm install)
- .env (敏感, 走 .env.example)
- *.zip / *.log / *.sqlite / .DS_Store

新增文档 docs/DEV-PLAN.md:
- Phase 1: 平台基座 (019 migration + 3 个 platform 路由 + 3 个 view)
- Phase 2: CarLog 子系统化 (后端 routes/ → subsystems/carlog/ + 前端 views/ → views/subsystems/carlog/ + 元数据驱动菜单)
- Phase 3: 验证 (测试 + E2E + DB 完整性)
- 交付清单 + commit 模板 + 给 Mavis review 的材料

后续 Trae 实施, 提交后我 code review + 跑测试。
2026-06-20 22:30:19 +08:00

156 lines
7.0 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- =============================================================================
-- 洗车记录系统 - Migration 0001: 基础表(Node.js / better-sqlite3 版)
-- =============================================================================
-- 基础 PRAGMA 由 server/src/db.js 统一设置(journal_mode=WAL / foreign_keys=ON / synchronous=NORMAL / busy_timeout=5000
-- -----------------------------------------------------------------------------
-- 1. chemicals - 药剂字典(Grocy 缓存层)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS chemicals (
grocy_product_id TEXT NOT NULL,
name TEXT NOT NULL,
category TEXT,
unit TEXT NOT NULL DEFAULT 'ml',
standard_dose REAL,
notes TEXT,
is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)),
fetched_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (grocy_product_id)
);
CREATE INDEX IF NOT EXISTS idx_chemicals_category ON chemicals(category);
CREATE INDEX IF NOT EXISTS idx_chemicals_active ON chemicals(is_active);
CREATE INDEX IF NOT EXISTS idx_chemicals_fetched ON chemicals(fetched_at);
-- -----------------------------------------------------------------------------
-- 2. weather_snapshots - 天气快照
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS weather_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_date TEXT NOT NULL,
city TEXT NOT NULL,
provider TEXT NOT NULL CHECK (provider IN ('qweather','openweathermap')),
temp_c REAL,
humidity INTEGER,
weather_desc TEXT,
weather_code TEXT,
wind_kph REAL,
precip_mm REAL,
raw_json TEXT,
fetched_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_weather_city_date ON weather_snapshots(city, snapshot_date);
CREATE INDEX IF NOT EXISTS idx_weather_date ON weather_snapshots(snapshot_date);
-- -----------------------------------------------------------------------------
-- 3. wash_records - 洗车记录
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS wash_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
wash_date TEXT NOT NULL,
wash_type TEXT NOT NULL CHECK (wash_type IN ('quick','full','detail','other')),
weather_snapshot_id INTEGER,
location TEXT,
cost REAL NOT NULL DEFAULT 0,
duration_min INTEGER,
notes TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (weather_snapshot_id) REFERENCES weather_snapshots(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_wash_records_date ON wash_records(wash_date);
CREATE INDEX IF NOT EXISTS idx_wash_records_type ON wash_records(wash_type);
CREATE INDEX IF NOT EXISTS idx_wash_records_weather ON wash_records(weather_snapshot_id);
-- -----------------------------------------------------------------------------
-- 4. chemical_usage - 药剂消耗
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS chemical_usage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
usage_date TEXT NOT NULL,
chemical_id TEXT NOT NULL,
amount REAL NOT NULL,
wash_record_id INTEGER,
notes TEXT,
sync_status TEXT NOT NULL DEFAULT 'pending' CHECK (sync_status IN ('pending','synced','failed')),
sync_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (chemical_id) REFERENCES chemicals(grocy_product_id) ON DELETE RESTRICT,
FOREIGN KEY (wash_record_id) REFERENCES wash_records(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_usage_date ON chemical_usage(usage_date);
CREATE INDEX IF NOT EXISTS idx_usage_chemical ON chemical_usage(chemical_id);
CREATE INDEX IF NOT EXISTS idx_usage_wash ON chemical_usage(wash_record_id);
CREATE INDEX IF NOT EXISTS idx_usage_sync ON chemical_usage(sync_status);
-- -----------------------------------------------------------------------------
-- 5. settings - 运行时配置 KV
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS settings (
key TEXT NOT NULL PRIMARY KEY,
value TEXT,
is_secret INTEGER NOT NULL DEFAULT 0,
description TEXT,
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- 预置 11 个 key
INSERT OR IGNORE INTO settings (key, value, is_secret, description) VALUES
('db_path', NULL, 0, '数据库路径(SQLite 模式)'),
('app_city', NULL, 0, '所在城市(用于天气查询)'),
('app_timezone', 'Asia/Shanghai', 0, 'PHP 时区(保留兼容)'),
('grocy_url', NULL, 0, 'Grocy 实例 URL'),
('grocy_api_token', NULL, 1, 'Grocy REST API token'),
('weather_provider', 'qweather', 0, '天气提供方 qweather/openweathermap'),
('qweather_api_key', NULL, 1, '和风天气 API key'),
('qweather_api_host', 'api.qweather.com', 0, '和风 API host'),
('openweathermap_api_key', NULL, 1, 'OpenWeatherMap API key'),
('backup_keep_count', '10', 0, '本地备份保留份数'),
('backup_dir', 'storage/backups', 0, '备份输出目录');
-- -----------------------------------------------------------------------------
-- 6. 视图
-- -----------------------------------------------------------------------------
DROP VIEW IF EXISTS v_wash_monthly_count;
CREATE VIEW v_wash_monthly_count AS
SELECT
substr(wash_date, 1, 7) AS month,
COUNT(*) AS wash_count,
SUM(COALESCE(cost, 0)) AS total_cost
FROM wash_records
GROUP BY substr(wash_date, 1, 7)
ORDER BY month DESC;
DROP VIEW IF EXISTS v_chemical_monthly_usage;
CREATE VIEW v_chemical_monthly_usage AS
SELECT
substr(cu.usage_date, 1, 7) AS month,
c.grocy_product_id AS grocy_product_id,
c.name AS chemical_name,
c.unit AS unit,
SUM(cu.amount) AS total_amount,
COUNT(*) AS usage_count
FROM chemical_usage cu
JOIN chemicals c ON c.grocy_product_id = cu.chemical_id
GROUP BY substr(cu.usage_date, 1, 7), c.grocy_product_id
ORDER BY month DESC, total_amount DESC;
DROP VIEW IF EXISTS v_last_wash;
CREATE VIEW v_last_wash AS
SELECT
id AS wash_id,
wash_date,
wash_type,
CAST(julianday('now') - julianday(wash_date) AS INTEGER) AS days_since
FROM wash_records
ORDER BY wash_date DESC, id DESC
LIMIT 1;