[Table Tools] Party loot ledger and XP tracking #112
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context / Motivation
"Who's carrying the gem / did we split that 800 gp" is the most universal unmet bookkeeping need at the table. It fits Quest Board's campaign-scoped model cleanly, and later feeds the planned Foundry member writeback (
member_to_actor, see issue #24's adapter interface).Spec
Models
LootEntry:id,campaign_id(FK, CASCADE, indexed),session_id(nullable FK, SET NULL)name(Text, required),qty(int, default 1)est_value(free-text) +currency(free-text, e.g. "gp") — no exchange-rate engineholder_member_id(nullable FK → campaign_members; NULL = party pool)notes(nullable),created_by_id(FK users),created_at/updated_atdeleted_atnullable) so accidental deletions are recoverableXpAward:id,campaign_id,session_id(nullable),amount(int),scope:party|member,member_id(nullable FK, required when scope=member),note,created_by_id,created_atGoldLedgerEntry(simple credit/debit):id,campaign_id,session_id(nullable),amount(signed numeric),currency(free-text),description,member_id(nullable = party fund),created_by_id,created_atAPI — campaign-scoped REST under
/api/campaigns/{campaign_id}/loot,/xp,/gold(list/create/update/delete), following the inline-router pattern used for planning arcs/threads inwebapp/backend/app/routers/campaigns.py:3172-3390. Totals endpoint returns per-member XP totals, per-member + party-pool loot, and gold balance.Permissions: GM full CRUD; players read-only by default; campaign setting
players_can_add_loot(default off) allows player creates (still not edit/delete of others' entries). Reuse the existing GM/member dependency guards from campaigns.py.UI: new campaign tab "Loot & XP": loot table (filter by holder, transfer-holder action), XP totals per member, gold ledger with running balance. Session detail page shows that session's loot/XP entries.
Export/import: add the three tables to the campaign export zip and importer —
export_campaign(routers/campaigns.py:2580, bumpEXPORT_SCHEMA_VERSIONfrom "7" at:2566) andimport_campaign(services/import_service.py:71, extendSUPPORTED_SCHEMA_VERSIONSat:40).Out of scope (flagged follow-ups, do not build here)
Acceptance criteria
References
webapp/backend/app/routers/campaigns.py:3172-3390(planning arcs/threads inline-router pattern),:2580(export_campaign),:2566(EXPORT_SCHEMA_VERSION = "7")webapp/backend/app/services/import_service.py:40(SUPPORTED_SCHEMA_VERSIONS),:71(import_campaign)webapp/backend/app/models/session_attendance.py(campaign-scoped per-member model precedent)member_to_actor,item_to_item)Filed from the July 2026 full-project review.
Done — merged in PR #192 (backend
a35d611, frontend1293a3d; squashed to main).Shipped:
LootEntry(soft-delete),XpAward(party/member scope),GoldLedgerEntry; migratione2f3a4b5c6d7. Nullable holder/member FKs (SET NULL) →NULL= party pool / party fund, so removing a member preserves ledger history.Decimal, serialized as JSON strings end-to-end (no float drift); the frontend never does math on gold — all sums come fromGET /loot-totals./loot/{id}/transfer+/loot-totals; read-only session-scoped/sessions/{id}/{loot,xp,gold}. Export/import schema 7→8.players_can_add_lootis on, then creator-only (server-enforced 403).LootLedgercampaign panel (loot table + holder filter + transfer, XP totals + award, gold ledger + running balance), theplayers_can_add_lootsetting toggle, and a read-only session-detail block.Tests: backend 456 pass (+21
test_loot.py); frontend 202 pass (+33). CI run #3879 green (5m49s).Deferred to #149 (loot generator, this milestone): the LLM-driven parcel generator writes into this ledger.
Closing.