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
+32
View File
@@ -45,6 +45,8 @@ export interface User {
is_service_admin: boolean;
primary_account_id: number | null;
totp_enabled: boolean;
must_change_password: boolean;
onboarded_at: string | null;
created_at: string;
}
@@ -154,6 +156,18 @@ export interface BatchCapitalImportResult {
total_statements: number;
}
export interface BtcPricesStatus {
count: number;
first_date: string | null;
last_date: string | null;
latest_price_cents: number | null;
}
export interface BtcPricesImportResult extends BtcPricesStatus {
imported: number;
skipped_rows: number;
}
export interface CapitalAccount {
id: number;
entity_id: number;
@@ -168,6 +182,9 @@ export interface CapitalAccount {
document_id: number | null;
created_at: string;
exited_on: string | null;
// BTC/USD marks for the bitcoin-denominated view (null = no price data / no close date)
btc_price_cents: number | null;
btc_close_price_cents: number | null;
}
export interface Entity {
@@ -178,6 +195,7 @@ export interface Entity {
fund_size_cents: number | null;
status: EntityStatus;
linked_user_id: number | null;
close_date: string | null;
created_at: string;
}
@@ -514,6 +532,20 @@ export const api = {
if (!res.ok) throw new ApiError(res.status, data.detail || "Batch import failed");
return data;
},
// BTC prices (bitcoin-denominated view)
btcPricesStatus: () => request<BtcPricesStatus>("/api/import/btc-prices"),
importBtcPrices: async (file: File): Promise<BtcPricesImportResult> => {
const form = new FormData();
form.set("file", file);
const res = await fetch("/api/import/btc-prices", { method: "POST", body: form });
const data = await res.json().catch(() => ({ detail: res.statusText }));
if (!res.ok) throw new ApiError(res.status, data.detail || "Price import failed");
return data;
},
markOnboarded: () =>
request<{ status: string }>("/api/auth/onboarded", { method: "POST" }),
getUser: (id: number) => request<UserDetail>(`/api/users/${id}`),
createUser: (data: {
name: string;