Files
gaokao-volunteer-system/data/orders/schema.py
Hermes Agent 7fabc23715
Some checks failed
CI / pytest (Python 3.10) (push) Has been cancelled
CI / pytest (Python 3.11) (push) Has been cancelled
CI / pytest (Python 3.12) (push) Has been cancelled
fix(t12): close payment delivery and governance remediation
2026-06-15 11:39:31 +08:00

125 lines
3.9 KiB
Python
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.
"""SQLite Schema 模块 (T4.1)
定义 orders 主表与 order_status_history 审计表的 DDL并提供幂等的
apply_schema() 应用函数。所有表使用 IF NOT EXISTS启用外键约束。
"""
from __future__ import annotations
import sqlite3
from pathlib import Path
SCHEMA_SQL: str = """
-- 订单主表
CREATE TABLE IF NOT EXISTS orders (
-- 主键 / 业务标识
id TEXT PRIMARY KEY,
source TEXT NOT NULL,
external_id TEXT,
service_version TEXT NOT NULL,
amount_cents INTEGER NOT NULL CHECK(amount_cents >= 0),
status TEXT NOT NULL CHECK(status IN
('pending','paid','serving','delivered','completed','refunded')),
status_updated_at TEXT NOT NULL,
-- 客户信息(加密 + 脱敏)
customer_name TEXT,
customer_phone_enc TEXT,
customer_phone_hash TEXT,
customer_wechat TEXT,
customer_email TEXT,
-- 考生信息
candidate_name TEXT,
candidate_id_card_enc TEXT,
candidate_province TEXT,
candidate_score INTEGER,
candidate_rank INTEGER,
candidate_subjects TEXT,
candidate_interests TEXT,
candidate_strong_subjects TEXT,
candidate_weak_subjects TEXT,
candidate_family TEXT,
-- 服务信息
assigned_consultant TEXT,
plan_file TEXT,
audit_report TEXT,
pdf_path TEXT,
-- 时间戳ISO8601 UTC
created_at TEXT NOT NULL,
paid_at TEXT,
started_at TEXT,
delivered_at TEXT,
completed_at TEXT,
-- 元数据
notes TEXT,
tags TEXT,
upgrade_from TEXT,
FOREIGN KEY (upgrade_from) REFERENCES orders(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status);
CREATE INDEX IF NOT EXISTS idx_orders_source ON orders(source);
CREATE INDEX IF NOT EXISTS idx_orders_created_at ON orders(created_at);
CREATE INDEX IF NOT EXISTS idx_orders_phone_hash ON orders(customer_phone_hash);
CREATE UNIQUE INDEX IF NOT EXISTS uniq_orders_external
ON orders(source, external_id) WHERE external_id IS NOT NULL;
-- 状态历史(审计)
CREATE TABLE IF NOT EXISTS order_status_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id TEXT NOT NULL,
from_status TEXT,
to_status TEXT NOT NULL,
actor TEXT,
reason TEXT,
changed_at TEXT NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_status_history_order ON order_status_history(order_id);
"""
def apply_schema(db_path: str | Path) -> sqlite3.Connection:
"""应用 schema 到指定 SQLite 文件,返回连接。
- 启用外键约束 (PRAGMA foreign_keys = ON)
- 父目录自动创建
- 幂等:可重复执行
"""
db_path = Path(db_path)
if db_path.parent and not db_path.parent.exists():
db_path.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(str(db_path))
try:
conn.execute("PRAGMA foreign_keys = ON")
conn.executescript(SCHEMA_SQL)
columns = {
row[1] for row in conn.execute("PRAGMA table_info(orders)").fetchall()
}
if "customer_email" not in columns:
conn.execute("ALTER TABLE orders ADD COLUMN customer_email TEXT")
conn.commit()
except Exception:
conn.close()
raise
return conn
def get_schema_version(conn: sqlite3.Connection) -> int:
"""读取当前 schema 版本号(首次运行返回 0。后续迁移将引入 schema_version 表。"""
try:
row = conn.execute(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='orders'"
).fetchone()
return 1 if row else 0
except sqlite3.DatabaseError:
return 0