Files
CarLog/server/migrations/mysql/0001_init.sql
T
wsh5485 fe17886ac4 feat: 洗车管理系统 v2.8 — 个人 detailer 单用户全栈应用
- 车辆 / 洗车 / 加油 / 充电 / 保养 / 保险 完整 CRUD + 软删
- AI 截图识别(5 类型 OCR schema):OpenAI 兼容 + MiniMax M3
- 化学品 / Grocy 实例对接 + 库存镜像同步
- 仪表盘:30 天频次 + 健康度 + 同比环比 + 油价趋势 + 年均养护
- 月度报表:Excel 6 sheet + PDF
- PWA:manifest / SW / 离线缓存 / iOS 引导
- 安全:bcrypt + CSRF + 登录锁定(IP/用户/全局三级)+ 401 自动跳登录 + 表单草稿
- 高 ROI 8 功能:里程/提醒/成本/搜索/标签/通知/同比/成就
- 3 个新 migration(0016/0017/0018)+ 18 个迁移全幂等
- 101/101 测试通过(含 ipRateLimit / CSRF / retry / stats / tags / notifications)
- 部署:宝塔面板文档 + PM2 + Nginx
2026-06-20 21:11:54 +08:00

149 lines
7.4 KiB
SQL

-- =============================================================================
-- 洗车记录系统 - Migration 0001: 基础表 (MySQL 8.x)
-- =============================================================================
-- -----------------------------------------------------------------------------
-- 1. chemicals - 药剂字典(Grocy 缓存层)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS chemicals (
grocy_product_id VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
category VARCHAR(255) DEFAULT NULL,
unit VARCHAR(50) NOT NULL DEFAULT 'ml',
standard_dose DOUBLE DEFAULT NULL,
notes TEXT DEFAULT NULL,
is_active TINYINT(1) NOT NULL DEFAULT 1,
fetched_at DATETIME DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (grocy_product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE INDEX idx_chemicals_category ON chemicals(category);
CREATE INDEX idx_chemicals_active ON chemicals(is_active);
CREATE INDEX idx_chemicals_fetched ON chemicals(fetched_at);
-- -----------------------------------------------------------------------------
-- 2. weather_snapshots - 天气快照
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS weather_snapshots (
id INT AUTO_INCREMENT PRIMARY KEY,
snapshot_date VARCHAR(10) NOT NULL,
city VARCHAR(100) NOT NULL,
provider VARCHAR(50) NOT NULL,
temp_c DOUBLE DEFAULT NULL,
humidity INT DEFAULT NULL,
weather_desc VARCHAR(255) DEFAULT NULL,
weather_code VARCHAR(20) DEFAULT NULL,
wind_kph DOUBLE DEFAULT NULL,
precip_mm DOUBLE DEFAULT NULL,
raw_json TEXT DEFAULT NULL,
fetched_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE UNIQUE INDEX uk_weather_city_date ON weather_snapshots(city, snapshot_date);
CREATE INDEX idx_weather_date ON weather_snapshots(snapshot_date);
-- -----------------------------------------------------------------------------
-- 3. wash_records - 洗车记录
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS wash_records (
id INT AUTO_INCREMENT PRIMARY KEY,
wash_date VARCHAR(10) NOT NULL,
wash_type VARCHAR(20) NOT NULL,
weather_snapshot_id INT DEFAULT NULL,
location VARCHAR(255) DEFAULT NULL,
cost DOUBLE NOT NULL DEFAULT 0,
duration_min INT DEFAULT NULL,
notes TEXT DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT chk_wash_type CHECK (wash_type IN ('quick','full','detail','other')),
CONSTRAINT fk_wash_weather FOREIGN KEY (weather_snapshot_id) REFERENCES weather_snapshots(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE INDEX idx_wash_records_date ON wash_records(wash_date);
CREATE INDEX idx_wash_records_type ON wash_records(wash_type);
CREATE INDEX idx_wash_records_weather ON wash_records(weather_snapshot_id);
-- -----------------------------------------------------------------------------
-- 4. chemical_usage - 药剂消耗
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS chemical_usage (
id INT AUTO_INCREMENT PRIMARY KEY,
usage_date VARCHAR(10) NOT NULL,
chemical_id VARCHAR(255) NOT NULL,
amount DOUBLE NOT NULL,
wash_record_id INT DEFAULT NULL,
notes TEXT DEFAULT NULL,
sync_status VARCHAR(20) NOT NULL DEFAULT 'pending',
sync_at DATETIME DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT chk_sync_status CHECK (sync_status IN ('pending','synced','failed')),
CONSTRAINT fk_usage_chemical FOREIGN KEY (chemical_id) REFERENCES chemicals(grocy_product_id) ON DELETE RESTRICT,
CONSTRAINT fk_usage_wash FOREIGN KEY (wash_record_id) REFERENCES wash_records(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE INDEX idx_usage_date ON chemical_usage(usage_date);
CREATE INDEX idx_usage_chemical ON chemical_usage(chemical_id);
CREATE INDEX idx_usage_wash ON chemical_usage(wash_record_id);
CREATE INDEX idx_usage_sync ON chemical_usage(sync_status);
-- -----------------------------------------------------------------------------
-- 5. settings - 运行时配置 KV
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS settings (
`key` VARCHAR(100) NOT NULL PRIMARY KEY,
value TEXT DEFAULT NULL,
is_secret TINYINT(1) NOT NULL DEFAULT 0,
description TEXT DEFAULT NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT IGNORE INTO settings (`key`, value, is_secret, description) VALUES
('app_city', NULL, 0, '所在城市(用于天气查询)'),
('app_timezone', 'Asia/Shanghai', 0, '时区'),
('grocy_url', NULL, 0, 'Grocy 实例 URL'),
('grocy_api_token', NULL, 1, 'Grocy REST API token'),
('backup_keep_count', '10', 0, '本地备份保留份数'),
('backup_dir', 'storage/backups', 0, '备份输出目录');
-- -----------------------------------------------------------------------------
-- 6. views
-- -----------------------------------------------------------------------------
DROP VIEW IF EXISTS v_wash_monthly_count;
CREATE VIEW v_wash_monthly_count AS
SELECT
SUBSTRING(wash_date, 1, 7) AS month,
COUNT(*) AS wash_count,
SUM(COALESCE(cost, 0)) AS total_cost
FROM wash_records
GROUP BY SUBSTRING(wash_date, 1, 7)
ORDER BY month DESC;
DROP VIEW IF EXISTS v_chemical_monthly_usage;
CREATE VIEW v_chemical_monthly_usage AS
SELECT
SUBSTRING(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 SUBSTRING(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,
DATEDIFF(NOW(), STR_TO_DATE(wash_date, '%Y-%m-%d')) AS days_since
FROM wash_records
ORDER BY wash_date DESC, id DESC
LIMIT 1;