0.2.25: batch historical eNAV backfill + collapsible investor chart
Add POST /api/import/capital-accounts/batch: upload several of a fund's eNAV workbooks at once; each file's ALLOC SI roster is auto-matched to existing members (by fund-admin investor ID, else name/username) and their capital statement is saved at that file's own as-of date, building trend-lines without replacing the latest figures. Members not already in the portal are skipped and reported per file (never created). Capital statements only -- holdings/NAV are untouched. One bad file (wrong password, no ALLOC SI, unreadable date) is reported per-file and does not abort the rest. Import page gains a "Backfill historical capital" batch section (fund picker, multi-file .xlsx input, shared password, per-file results table). Investor portal "Capital over time" chart is now collapsed by default and expands per fund (first login opens clean); applies to InvestorHome and the admin Investor View via the shared component. Tests: backend/tests/test_capital_batch.py (2). Full suite 17 passed; frontend tsc clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
33776d42f4
commit
099459b2f3
@@ -130,6 +130,22 @@ export interface CapitalImportResult {
|
||||
statements_written: number;
|
||||
}
|
||||
|
||||
export interface BatchCapitalFileResult {
|
||||
filename: string;
|
||||
as_of_date: string | null;
|
||||
matched: number;
|
||||
statements_written: number;
|
||||
updated: number;
|
||||
skipped: string[];
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface BatchCapitalImportResult {
|
||||
entity_id: number;
|
||||
files: BatchCapitalFileResult[];
|
||||
total_statements: number;
|
||||
}
|
||||
|
||||
export interface CapitalAccount {
|
||||
id: number;
|
||||
entity_id: number;
|
||||
@@ -440,6 +456,22 @@ export const api = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
}),
|
||||
|
||||
// Batch backfill: several historical eNAV files → capital statements per quarter, auto-matched.
|
||||
capitalImportBatch: async (
|
||||
entityId: number,
|
||||
files: File[],
|
||||
password?: string,
|
||||
): Promise<BatchCapitalImportResult> => {
|
||||
const form = new FormData();
|
||||
form.set("entity_id", String(entityId));
|
||||
if (password) form.set("password", password);
|
||||
for (const f of files) form.append("files", f);
|
||||
const res = await fetch("/api/import/capital-accounts/batch", { method: "POST", body: form });
|
||||
const data = await res.json().catch(() => ({ detail: res.statusText }));
|
||||
if (!res.ok) throw new ApiError(res.status, data.detail || "Batch import failed");
|
||||
return data;
|
||||
},
|
||||
getUser: (id: number) => request<UserDetail>(`/api/users/${id}`),
|
||||
createUser: (data: {
|
||||
name: string;
|
||||
|
||||
Reference in New Issue
Block a user