HEL-269: 完成月结锁账、撤演示数据与代码收尾
锁账后写保护与闭期补录留痕;重开须审批并按版本链再结;列表/导出改走真实 API。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
f5e0915f63
commit
e5e326514d
@@ -1096,6 +1096,106 @@ MIGRATIONS: tuple[Migration, ...] = (
|
||||
CREATE INDEX idx_reminders_company ON reminders (company_id);
|
||||
""",
|
||||
),
|
||||
|
||||
Migration(
|
||||
version=10,
|
||||
name="00010_period_close_reopen",
|
||||
# HEL-196/269: monthly close snapshots, reopen approval, late arrivals
|
||||
# after lock, and an append-only period audit trail. closed_periods
|
||||
# (from 0008) remains the live lock index; this table is the versioned
|
||||
# report history.
|
||||
up="""
|
||||
CREATE TABLE period_close_runs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
year_month TEXT NOT NULL,
|
||||
version INTEGER NOT NULL,
|
||||
status TEXT NOT NULL CHECK (status IN (
|
||||
'pending', 'closing', 'closed', 'failed', 'reopened'
|
||||
)),
|
||||
snapshot_json TEXT,
|
||||
snapshot_hash TEXT,
|
||||
report_no TEXT,
|
||||
blockers_json TEXT,
|
||||
fail_reason TEXT,
|
||||
closed_at TEXT,
|
||||
closed_by INTEGER REFERENCES users (id),
|
||||
closed_by_username TEXT,
|
||||
reopen_window_end TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
UNIQUE (year_month, version)
|
||||
);
|
||||
|
||||
CREATE TABLE period_reopen_requests (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
period_close_id INTEGER NOT NULL REFERENCES period_close_runs (id),
|
||||
year_month TEXT NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
companies_note TEXT,
|
||||
window_days INTEGER NOT NULL DEFAULT 3,
|
||||
status TEXT NOT NULL CHECK (status IN ('pending', 'approved', 'rejected')),
|
||||
requester_id INTEGER REFERENCES users (id),
|
||||
requester_username TEXT,
|
||||
requested_at TEXT NOT NULL,
|
||||
reviewer_id INTEGER REFERENCES users (id),
|
||||
reviewer_username TEXT,
|
||||
reviewed_at TEXT,
|
||||
review_comment TEXT,
|
||||
before_json TEXT,
|
||||
after_json TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE period_late_arrivals (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
year_month TEXT NOT NULL,
|
||||
source_row_id INTEGER NOT NULL UNIQUE REFERENCES source_rows (id),
|
||||
status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'absorbed')),
|
||||
created_at TEXT NOT NULL,
|
||||
actor_user_id INTEGER REFERENCES users (id),
|
||||
actor_username TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE period_audit_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_at TEXT NOT NULL,
|
||||
actor_user_id INTEGER REFERENCES users (id),
|
||||
actor_username TEXT,
|
||||
actor_role TEXT,
|
||||
action TEXT NOT NULL,
|
||||
year_month TEXT,
|
||||
object_label TEXT,
|
||||
reason TEXT,
|
||||
before_json TEXT,
|
||||
after_json TEXT,
|
||||
report_no TEXT,
|
||||
related_id INTEGER
|
||||
);
|
||||
|
||||
CREATE INDEX idx_period_close_month ON period_close_runs (year_month, version);
|
||||
CREATE INDEX idx_period_reopen_status ON period_reopen_requests (status, year_month);
|
||||
CREATE INDEX idx_period_audit_created ON period_audit_events (created_at);
|
||||
|
||||
CREATE TRIGGER period_audit_no_update BEFORE UPDATE ON period_audit_events
|
||||
BEGIN SELECT RAISE (ABORT, 'period_audit_events rows are append-only'); END;
|
||||
CREATE TRIGGER period_audit_no_delete BEFORE DELETE ON period_audit_events
|
||||
BEGIN SELECT RAISE (ABORT, 'period_audit_events rows are immutable history'); END;
|
||||
CREATE TRIGGER period_close_snapshot_no_update BEFORE UPDATE ON period_close_runs
|
||||
WHEN OLD.snapshot_json IS NOT NULL AND NEW.snapshot_json IS NOT OLD.snapshot_json
|
||||
BEGIN SELECT RAISE (ABORT, 'closed snapshots cannot be rewritten'); END;
|
||||
""",
|
||||
down="""
|
||||
DROP TRIGGER IF EXISTS period_close_snapshot_no_update;
|
||||
DROP TRIGGER IF EXISTS period_audit_no_delete;
|
||||
DROP TRIGGER IF EXISTS period_audit_no_update;
|
||||
DROP INDEX IF EXISTS idx_period_audit_created;
|
||||
DROP INDEX IF EXISTS idx_period_reopen_status;
|
||||
DROP INDEX IF EXISTS idx_period_close_month;
|
||||
DROP TABLE IF EXISTS period_audit_events;
|
||||
DROP TABLE IF EXISTS period_late_arrivals;
|
||||
DROP TABLE IF EXISTS period_reopen_requests;
|
||||
DROP TABLE IF EXISTS period_close_runs;
|
||||
""",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -1108,6 +1208,10 @@ def connect(path: str | Path) -> sqlite3.Connection:
|
||||
connection = sqlite3.connect(str(db_path), timeout=30)
|
||||
connection.row_factory = sqlite3.Row
|
||||
connection.execute("PRAGMA foreign_keys = ON")
|
||||
# WAL lowers write-lock contention on file databases. Skip :memory:
|
||||
# because WAL requires a real file.
|
||||
if str(db_path) != ":memory:":
|
||||
connection.execute("PRAGMA journal_mode=WAL")
|
||||
return connection
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user