B-42: 导入功能加固——逐工作表确认、流式上传与诊断证据
This commit is contained in:
+50
-1
@@ -307,6 +307,53 @@ MIGRATIONS: tuple[Migration, ...] = (
|
||||
ALTER TABLE bank_accounts_new RENAME TO bank_accounts;
|
||||
""",
|
||||
),
|
||||
Migration(
|
||||
version=4,
|
||||
name="0004_per_sheet_reviews",
|
||||
# Per-worksheet lifecycle: the parse outcome (parsed/exception/ignored)
|
||||
# is immutable evidence captured at import time; the human decision
|
||||
# (pending/confirmed/ignored) is the audit-gated gate that lets a
|
||||
# worksheet participate in later matching and calculation.
|
||||
up="""
|
||||
CREATE TABLE sheet_reviews (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
import_batch_id INTEGER NOT NULL REFERENCES import_batches (id),
|
||||
sheet_name TEXT NOT NULL,
|
||||
outcome TEXT NOT NULL
|
||||
CHECK (outcome IN ('parsed', 'exception', 'ignored')),
|
||||
message TEXT,
|
||||
scanned_rows INTEGER,
|
||||
candidate_headers TEXT,
|
||||
sheet_batch_id INTEGER REFERENCES sheet_batches (id),
|
||||
review_status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (review_status IN ('pending', 'confirmed', 'ignored')),
|
||||
review_reason TEXT,
|
||||
reviewed_by INTEGER REFERENCES users (id),
|
||||
reviewed_at TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
UNIQUE (import_batch_id, sheet_name)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_sheet_reviews_batch ON sheet_reviews (import_batch_id);
|
||||
|
||||
CREATE TRIGGER sheet_reviews_evidence_immutable BEFORE UPDATE ON sheet_reviews
|
||||
BEGIN
|
||||
SELECT RAISE (ABORT, 'sheet_reviews parse evidence is immutable')
|
||||
WHERE OLD.outcome != NEW.outcome
|
||||
OR OLD.message IS NOT NEW.message
|
||||
OR OLD.scanned_rows IS NOT NEW.scanned_rows
|
||||
OR OLD.candidate_headers IS NOT NEW.candidate_headers
|
||||
OR OLD.sheet_batch_id IS NOT NEW.sheet_batch_id
|
||||
OR OLD.import_batch_id IS NOT NEW.import_batch_id
|
||||
OR OLD.sheet_name IS NOT NEW.sheet_name;
|
||||
END;
|
||||
""",
|
||||
down="""
|
||||
DROP TRIGGER IF EXISTS sheet_reviews_evidence_immutable;
|
||||
DROP INDEX IF EXISTS idx_sheet_reviews_batch;
|
||||
DROP TABLE IF EXISTS sheet_reviews;
|
||||
""",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -314,7 +361,9 @@ def connect(path: str | Path) -> sqlite3.Connection:
|
||||
db_path = Path(path)
|
||||
if str(db_path) != ":memory:":
|
||||
db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
connection = sqlite3.connect(str(db_path))
|
||||
# Long busy timeout so concurrent uploads/confirmations wait for the
|
||||
# single SQLite writer instead of surfacing "database is locked" 500s.
|
||||
connection = sqlite3.connect(str(db_path), timeout=30)
|
||||
connection.row_factory = sqlite3.Row
|
||||
connection.execute("PRAGMA foreign_keys = ON")
|
||||
return connection
|
||||
|
||||
Reference in New Issue
Block a user