Originally created by: pyy3
Adds installable Office.js Custom Functions under the K namespace — a cross-platform (Windows / Mac / Excel-on-the-web), macro-free path to live =EPM() data. They complement, not replace the VBA in excel/OpenEPM.bas, which is kept intact for demos.
=EPM() (VBA, bare) and =K.EPM() (add-in, namespaced) never collide, so a single workbook can use both.
Custom Functions are always namespaced (=NS.NAME); a dotless =EPM() from an add-in is impossible without an XLL/Excel-DNA build — the VBA already provides that.
OpenEPM.bas)| Function | Behaviour |
|---|---|
=K.EPM(entity, year, period, account, [measure], [scenario], [costCenter], [department], [scenarioId]) |
Value lookup; defaults period_net_amount / actuals. period accepts 1-12, Q1-Q4, H1-H2, FY |
=K.EPM_BUDGET / EPM_VARIANCE / EPM_DEBIT / EPM_CREDIT |
Convenience wrappers |
=K.EPMSAVE(amount, entity, year, period, account, scenarioId, layer, [costCenter], [department]) |
Budget write-back on recalc |
Unlike the VBA (manual Ctrl+Shift+R refresh), Custom Functions fetch on their own — every =K.EPM* cell in a recalc pass is debounced into one konsol.api.epm_batch POST (chunked at the backend's MAX_BATCH=2000). EPMSAVE posts to konsol.api.budget_cell_save. Same-origin, cookie auth, no CORS.
excel-addin/src/functions.json — Custom Functions metadataexcel-addin/src/functions.js — implementation + debounced batcher + per-cell error mappingexcel-addin/src/functions.html — runtime pageexcel-addin/manifest.xml — CustomFunctions extension point, namespace K, runtime URLsexcel-addin/package.json — deploy copies the new filesdocs/prd/PRD-EXCEL-CUSTOM-FUNCTIONS.md — specexcel-addin/src/taskpane.js — batch refresh sent measure:"amount"/scenario:"actual"; corrected to backend defaults period_net_amount/actualsmanifest.xml well-formed; functions.json valid JSON.konsol/api.py (epm_batch, budget_cell_save).manifest.xml and smoke-test recalc + write-back. No Office.js runtime in the build env (same gap as [#35]).🤖 Generated with Claude Code
Originally posted by: pyy3
🤖 Code review (high-effort, multi-angle + verified)
Reviewed the diff across 7 finder angles with targeted verification (incl. Microsoft Office docs). Ranked by severity.
🔴 1. Cookie auth will fail for
=K.EPM()— no shared runtime declaredWith no
<Runtimes>/shared runtime, custom functions run in the JavaScript-only runtime, which does not support cookies. Sofetch(..., {credentials:"include"})cannot carry the Frappe session cookie — every=K.EPMcell returns 401 ("Not logged in") even after the user signs in via the task pane.Fix: declare a shared runtime and/or follow custom-functions auth guidance (token via
OfficeRuntime.storage) instead of relying on the cookie. This blocks the feature on Windows desktop.🔴 2.
=K.EPMSAVE— write-storm, silent value loss, unhandled reject (functions.jsepmSave)Diverges from the VBA
EPMSAVEin three ways:pSaveCache) → re-POSTsbudget_cell_saveon every recalc → duplicate writes + backend hammering.#VALUE!(VBA always returnsamount)..catch→ an offline/network error surfaces a rawTypeError: Failed to fetchrather than a friendly error.🟠 3. One bad
yearpoisons the whole batch (functions.jsmakeReq+ backendepm_batch)Non-numeric
year→Number()→NaN→JSON.stringify→"year":null. Backend doesint(req.get("year",0))→int(None)outside the per-cell try/except → HTTP 500 →sendChunk's.catchrejects every cell in the chunk. One typo'd cell breaks all=K.EPMin the batch. (periodis guarded;yearisn't.) Fix on either side: guardNaNinmakeReq, and/or wrap theint(year)in the backend per-cell try.🟡 4. Microtask debounce may not coalesce a large recalc into one POST
Promise.resolve().then(flush)only batches cells enqueued in a single synchronous turn. If Office dispatches invocations across event-loop turns, you get multiple POSTs — undercutting the "single round-trip per recalc" claim. AsetTimeout(flush, 0)spans turns more reliably.🟡 5. Falsy-zero drops dimensions (
makeReq)A cost center / scenarioId literally
"0"(or numeric 0) is omitted byif (costCenter)→ query silently aggregates across all cost centers, returning a wrong larger value with no error.🟢 6. Cleanup
401/403+!res.okerror block is duplicated betweensendChunkandepmSave— extract apostJson()helper.functions.jsuses bare relative paths whiletaskpane.jsroutes throughconst FRAPPE_URL. Pick one convention.Checked and dropped: "won't register on web/Mac" (the non-shared form does register on all three platforms — the cookie limitation above is the real issue); "backend returns misaligned
values[]" (epm_batchpads to full length, so positional alignment holds).Not runtime-verified — none of this was exercised on a live Office.js host; [#1] in particular should be confirmed by sideloading on Excel desktop.
🤖 Generated with Claude Code
Related
Tickets:
#1Originally posted by: pyy3
✅ Review fixes pushed (
14e2a74)<Runtimes lifetime="long">+SharedRuntimerequirement,CustomFunctionsunder<AllFormFactors>,Page→ shared task-pane page).taskpane.htmlloadsfunctions.jsso the functions register in that runtime and share the session cookie. Removed the standalonefunctions.html.#VALUE!(VBA parity); validatesamount/year/periodare finite first.enqueue()rejects a non-numericyearclient-side so one bad cell no longer 500s the whole batch. Backendint(year)-outside-try hardening noted as a follow-upkonsolPR in the PRD.postJson()helper (dedupes the 401/403 +!res.okblocks) and added aFRAPPE_URLconstant to matchtaskpane.js.Manifest re-validated well-formed;
functions.jspassesnode --check. Still not runtime-verified — the shared-runtime cookie flow in particular should be smoke-tested on a live Office.js host.🤖 Generated with Claude Code
Ticket changed by: grynn-in