Resolve entry identity via entry-id metadata; unfuse user references (libra-#42)
Approving a pending entry created with a reference (e.g. invoice
"42-144") 404'd with "Pending entry unknown not found": the list
endpoints recovered the entry id by parsing links for a libra- prefix,
but reference-bearing entries displace that link with the fused
"{reference}-{entry_id}" form, so the id surfaced as the literal
"unknown" and the approve call round-tripped it.
Make the entry-id transaction metadata the single canonical identity:
- _extract_entry_id() resolves metadata-first (libra- link parsing kept
only for pre-dfdcc44 ledger history); used by /entries/user,
/entries/pending, approve, and reject.
- Creation endpoints no longer fuse the reference with the entry id —
the user reference becomes its own sanitized link and round-trips
verbatim in API responses. Typed exp-/rcv-/inc- links stay as the
settlement-tracking handles.
- format_revenue_entry now writes entry-id metadata like its siblings
and sanitizes its reference link (was appended raw); generic
POST /entries sanitizes its reference link too.
- User-journal reference extraction skips all system link prefixes
(typed links used to leak into the reference field).
Contract documented in CLAUDE.md (Data Integrity → Entry Identity &
Links), pinned by tests/test_entry_identity_api.py and formatter
contract tests in test_unit.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
116df46d38
commit
15d9910073
6 changed files with 374 additions and 89 deletions
|
|
@ -121,6 +121,7 @@ async def post_expense(
|
|||
expense_account: str,
|
||||
currency: Optional[str] = "EUR",
|
||||
is_equity: bool = False,
|
||||
reference: Optional[str] = None,
|
||||
) -> dict[str, Any]:
|
||||
"""User submits an expense — creates Liability (libra owes user) or Equity contribution.
|
||||
|
||||
|
|
@ -136,6 +137,7 @@ async def post_expense(
|
|||
"user_wallet": user_wallet_id,
|
||||
"currency": currency,
|
||||
"is_equity": is_equity,
|
||||
"reference": reference,
|
||||
},
|
||||
)
|
||||
assert r.status_code == 201, f"post_expense failed: {r.status_code} {r.text}"
|
||||
|
|
@ -150,6 +152,7 @@ async def post_income(
|
|||
description: str,
|
||||
revenue_account: str,
|
||||
currency: str = "EUR",
|
||||
reference: Optional[str] = None,
|
||||
) -> dict[str, Any]:
|
||||
"""User submits income on libra's behalf — creates Receivable (user owes libra)."""
|
||||
r = await client.post(
|
||||
|
|
@ -160,13 +163,14 @@ async def post_income(
|
|||
"amount": _amount(amount),
|
||||
"revenue_account": revenue_account,
|
||||
"currency": currency,
|
||||
"reference": reference,
|
||||
},
|
||||
)
|
||||
assert r.status_code == 201, f"post_income failed: {r.status_code} {r.text}"
|
||||
return r.json()
|
||||
|
||||
|
||||
async def list_user_entries(client: AsyncClient, *, wallet_inkey: str) -> list[dict]:
|
||||
async def list_user_entries(client: AsyncClient, *, wallet_inkey: str) -> dict[str, Any]:
|
||||
r = await client.get(
|
||||
"/libra/api/v1/entries/user",
|
||||
headers={"X-Api-Key": wallet_inkey},
|
||||
|
|
@ -175,6 +179,18 @@ async def list_user_entries(client: AsyncClient, *, wallet_inkey: str) -> list[d
|
|||
return r.json()
|
||||
|
||||
|
||||
async def list_pending_entries(
|
||||
client: AsyncClient, *, super_user_headers: dict,
|
||||
) -> list[dict]:
|
||||
"""Admin lists pending (`!`) entries awaiting approval."""
|
||||
r = await client.get(
|
||||
"/libra/api/v1/entries/pending",
|
||||
headers=super_user_headers,
|
||||
)
|
||||
assert r.status_code == 200, f"list_pending_entries failed: {r.status_code} {r.text}"
|
||||
return r.json()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entries — admin side
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue