fix: preserve migrated legacy snapshot closes
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
from jarca_ledger_mcp import service
|
||||
from jarca_ledger_mcp.db import init_db
|
||||
|
||||
|
||||
BASIS = "2026-06-30"
|
||||
|
||||
|
||||
def _legacy_closed_db(tmp_path):
|
||||
path = str(tmp_path / "legacy.sqlite3")
|
||||
init_db(path)
|
||||
asset = service.create_manual_asset("legacy cash", 100, "2026-06-01", "cash", db_path=path)["manual_asset"]
|
||||
with sqlite3.connect(path) as conn:
|
||||
# These are schema-11 close rows after the additive schema-12 migration:
|
||||
# run identity is absent, but row close/correction fields remain frozen.
|
||||
conn.execute(
|
||||
"""INSERT INTO asset_snapshots
|
||||
(id, basis_date, source_type, source_id, name, amount, liquidity_bucket,
|
||||
valuation_source, snapshot_run_id, correction_of_run_id, correction_reason,
|
||||
is_correction, closed_at, created_at)
|
||||
VALUES (?, ?, 'manual_asset', ?, ?, 100, 'cash', 'manual_assets', 'legacy-run-correction',
|
||||
'legacy-initial', 'legacy correction', 1, '2026-06-30T12:00:00+09:00',
|
||||
'2026-06-30T12:00:00+09:00')""",
|
||||
("legacy-row", BASIS, asset["id"], asset["name"]),
|
||||
)
|
||||
conn.execute("DROP TABLE asset_snapshot_runs")
|
||||
conn.execute("UPDATE schema_meta SET value='11' WHERE key='schema_version'")
|
||||
init_db(path)
|
||||
with sqlite3.connect(path) as conn:
|
||||
assert conn.execute("SELECT COUNT(*) FROM asset_snapshot_runs").fetchone()[0] == 0
|
||||
return path, asset["id"]
|
||||
|
||||
|
||||
def test_schema11_closed_rows_remain_frozen_after_live_value_and_date_change(tmp_path):
|
||||
path, asset_id = _legacy_closed_db(tmp_path)
|
||||
service.update_manual_asset(asset_id, valuation_amount=900, valuation_date="2026-06-30", db_path=path)
|
||||
before = hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
|
||||
report = service.get_asset_snapshot_report(BASIS, db_path=path)
|
||||
|
||||
after = hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
|
||||
assert report["success"] is True
|
||||
assert report["source"] == "legacy_asset_snapshots"
|
||||
assert report["is_closed_snapshot"] is True
|
||||
assert report["close_status"] == "legacy_read_only"
|
||||
assert report["net_worth"] == 100
|
||||
assert report["included_items"][0]["amount"] == 100
|
||||
assert report["correction"] == {
|
||||
"is_correction": True,
|
||||
"correction_of_run_id": "legacy-initial",
|
||||
"correction_reason": "legacy correction",
|
||||
}
|
||||
assert report["snapshot_run_id"] == "legacy-run-correction"
|
||||
assert report["approval"] == {}
|
||||
assert "valuation_id" not in report["included_items"][0]
|
||||
assert after == before
|
||||
|
||||
|
||||
def test_schema11_closed_rows_are_frozen_in_timeseries(tmp_path):
|
||||
path, asset_id = _legacy_closed_db(tmp_path)
|
||||
service.update_manual_asset(asset_id, valuation_amount=900, valuation_date="2026-07-01", db_path=path)
|
||||
|
||||
point = service.get_net_worth_timeseries(BASIS, BASIS, db_path=path)["points"][0]
|
||||
|
||||
assert point["net_worth"] == 100
|
||||
assert point["item_count"] == 1
|
||||
assert point["close_status"] == "legacy_read_only"
|
||||
assert point["snapshot_run_id"] == "legacy-run-correction"
|
||||
assert point["approval"] == {}
|
||||
assert point["correction"] == {
|
||||
"is_correction": True,
|
||||
"correction_of_run_id": "legacy-initial",
|
||||
"correction_reason": "legacy correction",
|
||||
}
|
||||
|
||||
|
||||
def test_schema11_closed_rows_are_frozen_in_monthly_settlement(tmp_path):
|
||||
path, asset_id = _legacy_closed_db(tmp_path)
|
||||
service.update_manual_asset(asset_id, valuation_amount=900, valuation_date="2026-07-01", db_path=path)
|
||||
|
||||
monthly = service.get_monthly_settlement_summary("2026-06", db_path=path)
|
||||
assets = monthly["reports"]["assets"]
|
||||
coverage = monthly["source_coverage"]["assets"]
|
||||
|
||||
assert assets["net_worth"] == 100
|
||||
assert assets["is_closed_snapshot"] is True
|
||||
assert coverage["net_worth"] == 100
|
||||
assert coverage["is_closed_snapshot"] is True
|
||||
assert coverage["monthly_source_snapshot_id"] == "legacy-run-correction"
|
||||
assert assets["approval"] == {}
|
||||
assert coverage["approval"] == {}
|
||||
assert coverage["correction"] == assets["correction"]
|
||||
|
||||
|
||||
def test_schema11_latest_legacy_correction_run_is_selected_as_a_complete_group(tmp_path):
|
||||
path = str(tmp_path / "legacy-corrections.sqlite3")
|
||||
init_db(path)
|
||||
with sqlite3.connect(path) as conn:
|
||||
rows = [
|
||||
("old-a", "legacy-run-1", "", "", 0, "2026-06-30T10:00:00+09:00", 40),
|
||||
("old-b", "legacy-run-1", "", "", 0, "2026-06-30T10:00:00+09:00", 60),
|
||||
("new-a", "legacy-run-2", "legacy-run-1", "correct frozen close", 1, "2026-07-01T10:00:00+09:00", 70),
|
||||
("new-b", "legacy-run-2", "legacy-run-1", "correct frozen close", 1, "2026-07-01T10:00:00+09:00", 80),
|
||||
]
|
||||
for row_id, run_id, prior, reason, correction, closed_at, amount in rows:
|
||||
conn.execute(
|
||||
"""INSERT INTO asset_snapshots
|
||||
(id, basis_date, source_type, source_id, name, amount, currency,
|
||||
liquidity_bucket, valuation_source, snapshot_run_id,
|
||||
correction_of_run_id, correction_reason, is_correction,
|
||||
closed_at, created_at)
|
||||
VALUES (?, ?, 'manual_asset', ?, ?, ?, 'KRW', 'cash',
|
||||
'manual_assets', ?, ?, ?, ?, ?, ?)""",
|
||||
(row_id, BASIS, row_id, row_id, amount, run_id, prior, reason,
|
||||
correction, closed_at, closed_at),
|
||||
)
|
||||
conn.execute("DROP TABLE asset_snapshot_runs")
|
||||
conn.execute("UPDATE schema_meta SET value='11' WHERE key='schema_version'")
|
||||
init_db(path)
|
||||
with sqlite3.connect(path) as conn:
|
||||
assert conn.execute("SELECT COUNT(*) FROM asset_snapshot_runs").fetchone()[0] == 0
|
||||
|
||||
before = hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
report = service.get_asset_snapshot_report(BASIS, db_path=path)
|
||||
after = hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
|
||||
assert report["success"] is True
|
||||
assert report["source"] == "legacy_asset_snapshots"
|
||||
assert report["snapshot_run_id"] == "legacy-run-2"
|
||||
assert report["snapshot_count"] == 2
|
||||
assert [item["id"] for item in report["included_items"]] == ["new-a", "new-b"]
|
||||
assert report["net_worth"] == 150
|
||||
assert report["correction"] == {
|
||||
"is_correction": True,
|
||||
"correction_of_run_id": "legacy-run-1",
|
||||
"correction_reason": "correct frozen close",
|
||||
}
|
||||
assert report["approval"] == {}
|
||||
assert all("valuation_id" not in item for item in report["included_items"])
|
||||
assert after == before
|
||||
Reference in New Issue
Block a user