Implement BAT-011 transactional write service split
This commit is contained in:
@@ -2,30 +2,8 @@ from __future__ import annotations
|
||||
|
||||
from .shared import *
|
||||
|
||||
def create_ledger_transaction(transaction_date: str = "", type: str = "expense", amount: int | float = 0, category: str = "", subcategory: str = "", merchant: str = "", description: str = "", account_id: str = "", payment_method: str = "", person: str = "", project: str = "", tags: Any = None, is_fixed: bool = False, currency: str = "KRW", posted_date: str = "", source: str = "manual", external_id: str = "", memo: str = "", dedupe: bool = True, transaction_datetime: str = "", counterparty: str = "", balance_after: int | None = None, branch: str = "", raw_summary: str = "", raw_payload: str = "", db_path: str | None = None) -> dict[str, Any]:
|
||||
try:
|
||||
tx_date = normalize_date(transaction_date, default_today=True)
|
||||
posted = normalize_date(posted_date, default_today=False) if posted_date else ""
|
||||
amount_int = int(amount)
|
||||
tags_json = normalize_tags(tags)
|
||||
except Exception as e:
|
||||
return fail("validation_error", str(e))
|
||||
if type not in VALID_TRANSACTION_TYPES:
|
||||
return fail("validation_error", f"type must be one of {sorted(VALID_TRANSACTION_TYPES)}")
|
||||
if amount_int <= 0:
|
||||
return fail("validation_error", "amount must be a positive integer in minor units; type determines direction")
|
||||
dedupe_key = make_dedupe_key(tx_date, amount_int, merchant, account_id, external_id) if dedupe else ""
|
||||
ts = now_iso(); tx_id = new_id("tx")
|
||||
try:
|
||||
with db(db_path) as conn:
|
||||
conn.execute("""INSERT INTO transactions(id,transaction_date,posted_date,type,amount,currency,category,subcategory,merchant,description,account_id,payment_method,person,project,tags,is_fixed,source,external_id,dedupe_key,memo,transaction_datetime,counterparty,balance_after,branch,raw_summary,raw_payload,created_at,updated_at)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", (tx_id, tx_date, posted, type, amount_int, currency, category, subcategory, merchant, description, account_id, payment_method, person, project, tags_json, int(is_fixed), source, external_id, dedupe_key, memo, transaction_datetime, counterparty, balance_after, branch, raw_summary, raw_payload, ts, ts))
|
||||
row = conn.execute("SELECT * FROM transactions WHERE id=?", (tx_id,)).fetchone()
|
||||
except Exception as e:
|
||||
if "UNIQUE constraint failed" in str(e):
|
||||
return fail("duplicate_transaction", "transaction appears to be duplicated", dedupe_key=dedupe_key)
|
||||
return fail("db_error", str(e))
|
||||
return ok(transaction=row_to_dict(row))
|
||||
from .budgets_write_services import create_ledger_budget
|
||||
from .transactions_write_services import create_ledger_transaction, delete_ledger_transaction, update_ledger_transaction
|
||||
|
||||
def list_ledger_transactions(start_date: str = "", end_date: str = "", type: str = "", category: str = "", person: str = "", account_id: str = "", include_deleted: bool = False, limit: int = 100, db_path: str | None = None) -> dict[str, Any]:
|
||||
clauses=[]; params=[]
|
||||
@@ -136,53 +114,7 @@ def get_balance_reconciliation_report(account_id: str = "", baseline_balance: in
|
||||
})
|
||||
return ok(items=items, count=len(items), tolerance=tolerance_int, baseline_at=baseline_at_norm, routing="preview_only_manual_confirmation_required")
|
||||
|
||||
def update_ledger_transaction(transaction_id: str, db_path: str | None = None, **fields: Any) -> dict[str, Any]:
|
||||
allowed = {"transaction_date","posted_date","type","amount","currency","category","subcategory","merchant","description","account_id","payment_method","person","project","tags","is_fixed","source","external_id","memo","status","transaction_datetime","counterparty","balance_after","branch","raw_summary","raw_payload"}
|
||||
updates=[]; params=[]
|
||||
for k,v in fields.items():
|
||||
if k not in allowed or v is None:
|
||||
continue
|
||||
if k == "tags": v = normalize_tags(v)
|
||||
if k in {"transaction_date","posted_date"} and v: v = normalize_date(v)
|
||||
if k == "amount": v = int(v)
|
||||
if k == "is_fixed": v = int(bool(v))
|
||||
updates.append(f"{k}=?"); params.append(v)
|
||||
if not updates:
|
||||
return fail("validation_error", "no fields to update")
|
||||
updates.append("updated_at=?"); params.append(now_iso()); params.append(transaction_id)
|
||||
with db(db_path) as conn:
|
||||
cur = conn.execute(f"UPDATE transactions SET {', '.join(updates)} WHERE id=?", params)
|
||||
if cur.rowcount == 0:
|
||||
return fail("not_found", "transaction not found")
|
||||
row = conn.execute("SELECT * FROM transactions WHERE id=?", (transaction_id,)).fetchone()
|
||||
return ok(transaction=row_to_dict(row))
|
||||
|
||||
def delete_ledger_transaction(transaction_id: str, hard_delete: bool = False, db_path: str | None = None) -> dict[str, Any]:
|
||||
with db(db_path) as conn:
|
||||
if hard_delete:
|
||||
cur = conn.execute("DELETE FROM transactions WHERE id=?", (transaction_id,))
|
||||
else:
|
||||
cur = conn.execute("UPDATE transactions SET status='deleted', updated_at=? WHERE id=?", (now_iso(), transaction_id))
|
||||
if cur.rowcount == 0:
|
||||
return fail("not_found", "transaction not found")
|
||||
return ok(transaction_id=transaction_id, deleted=True, hard_delete=hard_delete)
|
||||
|
||||
def create_ledger_budget(month: str, category: str, amount: int | float, subcategory: str = "", person: str = "", currency: str = "KRW", memo: str = "", db_path: str | None = None) -> dict[str, Any]:
|
||||
try:
|
||||
month_norm = normalize_month(month); amount_int = int(amount)
|
||||
except Exception as e:
|
||||
return fail("validation_error", str(e))
|
||||
if not category:
|
||||
return fail("validation_error", "category is required")
|
||||
if amount_int < 0:
|
||||
return fail("validation_error", "amount must be non-negative")
|
||||
ts = now_iso(); budget_id = new_id("bud")
|
||||
with db(db_path) as conn:
|
||||
conn.execute("""INSERT INTO budgets(id,month,category,subcategory,person,amount,currency,memo,created_at,updated_at)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?)
|
||||
ON CONFLICT(month,category,subcategory,person) DO UPDATE SET amount=excluded.amount,currency=excluded.currency,memo=excluded.memo,updated_at=excluded.updated_at""", (budget_id, month_norm, category, subcategory, person, amount_int, currency, memo, ts, ts))
|
||||
row = conn.execute("SELECT * FROM budgets WHERE month=? AND category=? AND subcategory=? AND person=?", (month_norm, category, subcategory, person)).fetchone()
|
||||
return ok(budget=row_to_dict(row))
|
||||
|
||||
def list_ledger_budgets(month: str = "", db_path: str | None = None) -> dict[str, Any]:
|
||||
clauses=[]; params=[]
|
||||
|
||||
Reference in New Issue
Block a user