0.2.43: historical NAV backfill without touching current holdings
The batch history import now also records each quarter's NAV in the fund's valuation history: the old file's HLD rows are matched by issuer and security name against the book as it exists today, matched rows write that quarter's valuations, unmatched rows are counted and reported, and nothing outside the round is created or modified. A manually signed quarter is never overwritten. The single-file wizard automatically takes the same history-only path when the file is older than the fund's newest round. Previously that import would regress position cost basis to the old file's values and resurrect since-exited positions, corrupting the fund's Invested total.
This commit is contained in:
@@ -25,7 +25,9 @@ from ten31portal.database import get_session
|
||||
from ten31portal.models import (
|
||||
CapitalAccountStatement, Entity, EntityAccess, User, UserRole,
|
||||
)
|
||||
from ten31portal.routers.import_router import _open_workbook, _enav_as_of
|
||||
from ten31portal.routers.import_router import (
|
||||
_open_workbook, _enav_as_of, _parse_schedule_xlsx, upsert_history_round,
|
||||
)
|
||||
from ten31portal.schemas import (
|
||||
BatchCapitalFileResult, BatchCapitalImportResult,
|
||||
CapitalImportCommit, CapitalImportPreview, ImportInvestorPreview, ImportValueRow,
|
||||
@@ -433,7 +435,8 @@ def batch_import(
|
||||
fname = upload.filename or "(unnamed)"
|
||||
res = BatchCapitalFileResult(filename=fname)
|
||||
try:
|
||||
wb = _open_workbook(storage.read_capped(upload), password)
|
||||
file_bytes = storage.read_capped(upload)
|
||||
wb = _open_workbook(file_bytes, password)
|
||||
if "ALLOC SI" not in wb.sheetnames:
|
||||
raise HTTPException(status_code=422, detail="No ALLOC SI tab found in this workbook.")
|
||||
as_of, roster = _parse_alloc_si(wb)
|
||||
@@ -488,11 +491,31 @@ def batch_import(
|
||||
res.matched += 1
|
||||
res.statements_written += 1
|
||||
|
||||
# NAV history leg: record this quarter's valuation round from the file's HLD
|
||||
# sheet, matched against today's book only (holdings are never modified). An
|
||||
# HLD problem must not lose the member statements above, so it runs in a
|
||||
# savepoint and reports per-file.
|
||||
try:
|
||||
with session.begin_nested():
|
||||
_, _, _, positions_prev, _ = _parse_schedule_xlsx(file_bytes, password)
|
||||
if positions_prev:
|
||||
hist = upsert_history_round(entity_id, as_of, positions_prev, admin, session)
|
||||
res.nav_status = hist["status"]
|
||||
res.nav_matched = hist["matched"]
|
||||
res.nav_unmatched = hist["unmatched"]
|
||||
res.nav_cents = hist["nav_cents"]
|
||||
else:
|
||||
res.nav_status = "no-hld"
|
||||
except Exception: # noqa: BLE001 — the savepoint rolled back; members still land
|
||||
res.nav_status = "error"
|
||||
|
||||
record_audit(session, admin.id, "import_batch", "capital_account", entity_id, {
|
||||
"file": fname,
|
||||
"as_of_date": str(as_of),
|
||||
"matched": res.matched,
|
||||
"skipped": len(res.skipped),
|
||||
"nav_status": res.nav_status,
|
||||
"nav_cents": res.nav_cents,
|
||||
})
|
||||
session.commit()
|
||||
total_statements += res.statements_written
|
||||
|
||||
Reference in New Issue
Block a user