0.2.39: bitcoin-denominated view, first-login flow, unfunded + tax center

- BTC prices: btc_prices table, CSV upload on Import page (auto-detected
  date/close columns, upsert by date), entities.close_date as the BTC entry
  mark; statements carry btc_price_cents (as-of) + btc_close_price_cents.
  LP capital blocks show paid-in vs current value in bitcoin terms.
- First login: accounts on the shared default password are flagged
  (must_change_password) and blocked behind a full-screen password change;
  external accounts then get a one-time welcome tour with a 2FA offer
  (users.onboarded_at).
- LP portal: Unfunded (callable commitment) metric; Tax documents center
  aggregating K-1/tax docs across funds, grouped by year.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jonathan Kirkwood
2026-07-12 13:01:50 +02:00
co-authored by Claude Fable 5
parent 0822eca887
commit eac3262f29
22 changed files with 823 additions and 12 deletions
+25
View File
@@ -27,6 +27,10 @@ class UserResponse(BaseModel):
is_service_admin: bool = False
primary_account_id: int | None = None # set when this account logs in under another
totp_enabled: bool = False
# First-login flow: force a password change while on the shared default, then show the
# welcome step (2FA offer) until onboarded_at is stamped.
must_change_password: bool = False
onboarded_at: datetime | None = None
created_at: datetime
@@ -129,6 +133,7 @@ class EntityUpdate(BaseModel):
fund_size_cents: int | None = None
status: EntityStatus | None = None
linked_user_id: int | None = None
close_date: date | None = None
class EntityResponse(BaseModel):
@@ -139,6 +144,7 @@ class EntityResponse(BaseModel):
fund_size_cents: int | None
status: EntityStatus
linked_user_id: int | None = None
close_date: date | None = None
created_at: datetime
@@ -274,6 +280,25 @@ class CapitalAccountResponse(BaseModel):
# Date the member sold/transferred this stake (from EntityAccess); the portal shows an
# "Exited" badge instead of a phantom -100% and drops the position from totals.
exited_on: date | None = None
# Bitcoin-denominated view: BTC/USD at this statement's as-of date (newest uploaded price
# on or before it) and at the fund's close date (the entry mark). Null when no price or
# no close date is set — the portal simply hides the BTC view then.
btc_price_cents: int | None = None
btc_close_price_cents: int | None = None
# --- BTC prices (bitcoin-denominated view) ---
class BtcPricesStatus(BaseModel):
count: int
first_date: date | None = None
last_date: date | None = None
latest_price_cents: int | None = None
class BtcPricesImportResult(BtcPricesStatus):
imported: int # rows upserted from this file (new + updated)
skipped_rows: int # unparseable lines ignored
# --- Partners (members of an entity) ---