[verified] Implement account valuation history for issue 12
This commit is contained in:
@@ -1,66 +1,65 @@
|
||||
from __future__ import annotations
|
||||
from .shared import Any, VALID_ACCOUNT_TYPES, db, fail, new_id, normalize_liquidity_bucket, normalize_statement_status, now_iso, ok, row_to_dict
|
||||
|
||||
from .shared import (
|
||||
Any,
|
||||
VALID_ACCOUNT_TYPES,
|
||||
db,
|
||||
fail,
|
||||
new_id,
|
||||
normalize_liquidity_bucket,
|
||||
normalize_statement_status,
|
||||
now_iso,
|
||||
ok,
|
||||
row_to_dict,
|
||||
)
|
||||
|
||||
def _record_balance_candidate(conn: Any, account: dict[str, Any], balance: Any = None, synced: str | None = None) -> None:
|
||||
balance = account.get("last_balance") if balance is None else balance
|
||||
synced = account.get("last_synced_at") if synced is None else synced
|
||||
if balance is None or not str(synced or ""):
|
||||
return
|
||||
try:
|
||||
from .account_valuation_service import record_account_valuation_candidate
|
||||
record_account_valuation_candidate(conn, account["id"], str(synced), int(balance), account.get("currency", "KRW"), "account_write", account.get("updated_at", ""), f"account-balance:{account['id']}:{str(synced)[:10]}:{balance}", "account balance write candidate")
|
||||
except (ValueError, TypeError):
|
||||
return
|
||||
|
||||
|
||||
def create_ledger_account(name: str, type: str = "bank", institution: str = "", currency: str = "KRW", active: bool = True, memo: str = "", account_number_masked: str = "", holder_name: str = "", last_balance: int | None = None, last_synced_at: str = "", statement_status: str = "unknown", liquidity_bucket: str = "other", db_path: str | None = None) -> dict[str, Any]:
|
||||
if not name.strip():
|
||||
return fail("validation_error", "account name is required")
|
||||
if type not in VALID_ACCOUNT_TYPES:
|
||||
return fail("validation_error", f"type must be one of {sorted(VALID_ACCOUNT_TYPES)}")
|
||||
if not name.strip(): return fail("validation_error", "account name is required")
|
||||
if type not in VALID_ACCOUNT_TYPES: return fail("validation_error", f"type must be one of {sorted(VALID_ACCOUNT_TYPES)}")
|
||||
try:
|
||||
statement_status_norm = normalize_statement_status(statement_status)
|
||||
liquidity_bucket_norm = normalize_liquidity_bucket(liquidity_bucket)
|
||||
except Exception as e:
|
||||
return fail("validation_error", str(e))
|
||||
except Exception as e: return fail("validation_error", str(e))
|
||||
ts = now_iso(); account_id = new_id("acct")
|
||||
with db(db_path) as conn:
|
||||
conn.execute("""INSERT INTO accounts(id,name,type,institution,currency,active,memo,account_number_masked,holder_name,last_balance,last_synced_at,statement_status,liquidity_bucket,created_at,updated_at)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", (account_id, name.strip(), type, institution, currency, int(active), memo, account_number_masked, holder_name, last_balance, last_synced_at, statement_status_norm, liquidity_bucket_norm, ts, ts))
|
||||
conn.execute("INSERT INTO accounts(id,name,type,institution,currency,active,memo,account_number_masked,holder_name,last_balance,last_synced_at,statement_status,liquidity_bucket,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (account_id,name.strip(),type,institution,currency.upper(),int(active),memo,account_number_masked,holder_name,last_balance,last_synced_at,statement_status_norm,liquidity_bucket_norm,ts,ts))
|
||||
row = conn.execute("SELECT * FROM accounts WHERE id=?", (account_id,)).fetchone()
|
||||
return ok(account=row_to_dict(row))
|
||||
account = row_to_dict(row)
|
||||
_record_balance_candidate(conn, account)
|
||||
return ok(account=account)
|
||||
|
||||
|
||||
def update_ledger_account(account_id: str, db_path: str | None = None, **fields: Any) -> dict[str, Any]:
|
||||
allowed = {"name", "type", "institution", "currency", "active", "memo", "account_number_masked", "holder_name", "last_balance", "last_synced_at", "statement_status", "liquidity_bucket"}
|
||||
updates: list[str] = []
|
||||
params: list[Any] = []
|
||||
for k, v in fields.items():
|
||||
if k not in allowed or v is None:
|
||||
continue
|
||||
try:
|
||||
if k == "type" and v not in VALID_ACCOUNT_TYPES:
|
||||
return fail("validation_error", f"type must be one of {sorted(VALID_ACCOUNT_TYPES)}")
|
||||
if k == "statement_status":
|
||||
v = normalize_statement_status(str(v))
|
||||
if k == "liquidity_bucket":
|
||||
v = normalize_liquidity_bucket(str(v))
|
||||
if k == "active":
|
||||
v = int(bool(v))
|
||||
if k == "last_balance" and v != "":
|
||||
v = int(v)
|
||||
except Exception as e:
|
||||
return fail("validation_error", str(e))
|
||||
updates.append(f"{k}=?")
|
||||
params.append(v)
|
||||
if not updates:
|
||||
return fail("validation_error", "no fields to update")
|
||||
updates.append("updated_at=?")
|
||||
params.extend([now_iso(), account_id])
|
||||
allowed = {"name","type","institution","currency","active","memo","account_number_masked","holder_name","last_balance","last_synced_at","statement_status","liquidity_bucket"}
|
||||
with db(db_path) as conn:
|
||||
cur = conn.execute(f"UPDATE accounts SET {', '.join(updates)} WHERE id=?", params)
|
||||
if cur.rowcount == 0:
|
||||
return fail("not_found", "account not found")
|
||||
current = conn.execute("SELECT * FROM accounts WHERE id=?", (account_id,)).fetchone()
|
||||
if current is None: return fail("not_found", "account not found")
|
||||
updates=[]; params=[]
|
||||
for k,v in fields.items():
|
||||
if k not in allowed or v is None: continue
|
||||
try:
|
||||
if k == "type" and v not in VALID_ACCOUNT_TYPES: return fail("validation_error", f"type must be one of {sorted(VALID_ACCOUNT_TYPES)}")
|
||||
if k == "statement_status": v = normalize_statement_status(str(v))
|
||||
if k == "liquidity_bucket": v = normalize_liquidity_bucket(str(v))
|
||||
if k == "active": v = int(bool(v))
|
||||
if k == "last_balance" and v != "": v = int(v)
|
||||
if k == "currency": v = str(v).upper()
|
||||
except Exception as e: return fail("validation_error", str(e))
|
||||
updates.append(f"{k}=?"); params.append(v)
|
||||
if not updates: return fail("validation_error", "no fields to update")
|
||||
incoming_date = str(fields.get("last_synced_at") or current["last_synced_at"] or "")[:10]
|
||||
stored_date = str(current["last_synced_at"] or "")[:10]
|
||||
historical = bool(incoming_date and stored_date and incoming_date < stored_date)
|
||||
candidate_balance = fields.get("last_balance", current["last_balance"])
|
||||
candidate_date = fields.get("last_synced_at", current["last_synced_at"])
|
||||
if historical:
|
||||
kept=[(u,p) for u,p in zip(updates,params) if not u.startswith(("last_balance=","last_synced_at="))]
|
||||
updates=[u for u,_ in kept]; params=[p for _,p in kept]
|
||||
updates.append("updated_at=?"); params.extend([now_iso(), account_id])
|
||||
conn.execute(f"UPDATE accounts SET {', '.join(updates)} WHERE id=?", params)
|
||||
row = conn.execute("SELECT * FROM accounts WHERE id=?", (account_id,)).fetchone()
|
||||
return ok(account=row_to_dict(row))
|
||||
account = row_to_dict(row)
|
||||
if any(k in fields for k in ("last_balance", "last_synced_at")):
|
||||
_record_balance_candidate(conn, account, candidate_balance, candidate_date)
|
||||
return ok(account=account)
|
||||
|
||||
Reference in New Issue
Block a user