Issues 19+20: entity-aware schedule import and rollup endpoint

Issue 19:
- entity_id now optional on POST /api/import/schedule
- Resolution: explicit entity_id > name match from row 1 > auto-create
- New params: create_entity_type (default fund), create_vintage_year
- Dry-run reports resolution (will_create/matched/existing)
- Frontend: radio toggle between 'Create from file' and 'Use existing'

Issue 20:
- GET /api/entities/rollup: invested_cents and last_signed_value_cents
  per entity in one pass (SQL aggregates, no waterfall)
- EntitiesList uses rollup instead of N+1 API calls
This commit is contained in:
Johnny 5
2026-06-08 03:02:59 +00:00
parent 7c33693eab
commit 30d2284feb
5 changed files with 302 additions and 80 deletions
+21 -28
View File
@@ -1,10 +1,16 @@
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { api, type Entity, type EntityType } from "../api";
import { api, type EntityType } from "../api";
import { useAuth } from "../context/AuthContext";
import { formatMoney, formatGainLoss } from "../format";
interface EntityRow extends Entity {
interface EntityRow {
id: number;
name: string;
type: string;
vintage_year: number | null;
fund_size_cents: number | null;
status: string;
investedCents: number;
lastValueCents: number;
}
@@ -33,32 +39,19 @@ export default function EntitiesList() {
async function loadData() {
setLoading(true);
try {
const ents = await api.listEntities();
const enriched: EntityRow[] = [];
for (const ent of ents) {
const holdings = await api.listHoldings(ent.id);
let investedCents = 0;
let lastValueCents = 0;
for (const h of holdings) {
const positions = await api.listPositions(h.id);
for (const p of positions) {
investedCents += p.cost_cents;
}
}
const rounds = await api.listRounds(ent.id);
const approved = rounds.filter((r) => r.status === "approved");
if (approved.length > 0) {
const latest = approved[0];
lastValueCents = latest.valuations.reduce((sum, v) => sum + v.value_cents, 0);
}
enriched.push({ ...ent, investedCents, lastValueCents });
}
setEntities(enriched);
const rollup = await api.listEntityRollup();
setEntities(
rollup.map((r) => ({
id: r.id,
name: r.name,
type: r.type,
vintage_year: r.vintage_year,
fund_size_cents: r.fund_size_cents,
status: r.status,
investedCents: r.invested_cents,
lastValueCents: r.last_signed_value_cents,
})),
);
} finally {
setLoading(false);
}