Skip to main content

POST budget_cell_save

Save a single budget cell — upserts one period+layer row in a Budget Input document. Designed for EPMSAVE() immediate writes from Excel.

Endpoint

POST /api/method/konsol.api.budget_cell_save

Authentication: Required (Frappe session cookie)

Request Body

ParameterTypeRequiredDescription
scenario_idstringYesScenario identifier (e.g., BUDGET_2025)
data_area_idstringYesEntity code (e.g., USMF)
fiscal_yearintegerYesFiscal year
main_accountstringYesAccount code
fiscal_periodintegerYesPeriod number (112)
amountnumberYesBudget amount
layerstringYesBudget layer: base, challenge, management, board
dim_cost_centerstringNoCost center dimension
dim_departmentstringNoDepartment dimension
base_modifiedstringNoOptimistic-locking baseline — the document modified timestamp the client last read. See Optimistic Locking.

Upsert Behavior

Finds or creates the Budget Input document by the budget grain, then upserts the specific period+layer row within it:

  • If a row with the same fiscal_period + layer exists, its amount is updated.
  • If no matching row exists, a new period row is appended.

The budget grain is the fixed keys (scenario_id, data_area_id, fiscal_year, main_account) plus every Dimension flagged in_budget (Published) — for example, a cost center on a shared Travel account. Two writers that differ only by a budget dimension resolve to different documents instead of silently clobbering each other.

The Budget Input document name is built from the readable grain components followed by a mandatory 8-character digest suffix (a SHA-1 of the exact key tuple), so two distinct keys never collide even when their readable parts sanitise to the same string — e.g. BUD-BUDGET_2025-USMF-2025-6100-1a2b3c4d.

Optimistic Locking

Pass the optional base_modified field (the document modified timestamp the client last read) to guard against lost updates. The behaviour is backward-compatible: omit it for last-write-wins.

  • If the stored document's modified no longer matches base_modified, the write is refused with HTTP status 409 and a conflict payload (see Conflict below) so the client can refresh and re-prompt.
  • On a successful save, the response echoes the new modified baseline.

Example

curl -X POST http://localhost:8069/api/method/konsol.api.budget_cell_save \
-H "Content-Type: application/json" \
-b "cookies.txt" \
-d '{
"scenario_id": "BUDGET_2025",
"data_area_id": "USMF",
"fiscal_year": 2025,
"main_account": "6100",
"fiscal_period": 3,
"amount": 15000,
"layer": "base"
}'

Response

{
"message": {
"status": "ok",
"name": "BUD-BUDGET_2025-USMF-2025-6100-1a2b3c4d",
"value": 15000,
"modified": "2026-06-19 10:32:14.582931"
}
}

The modified field is the new optimistic-locking baseline; pass it back as base_modified on the next save to the same cell.

Error Responses

Conflict (409)

When base_modified is supplied and the stored document has been modified since, the write is refused with HTTP status 409 and the following payload:

{
"message": {
"status": "conflict",
"name": "BUD-BUDGET_2025-USMF-2025-6100-1a2b3c4d",
"fiscal_period": 3,
"layer": "base",
"your_amount": 15000,
"current_amount": 14000,
"current_modified": "2026-06-19 10:35:02.118764"
}
}

current_amount is null (not 0) when the cell has been cleared, so the client can distinguish a cleared cell from a genuine zero. Refresh from current_modified and re-prompt the user before retrying.

Invalid layer

{
"exc_type": "ValidationError",
"_server_messages": "[\"Invalid layer 'forecast'. Allowed: base, board, challenge, management\"]"
}

Invalid fiscal period

{
"exc_type": "ValidationError",
"_server_messages": "[\"fiscal_period must be 1-12\"]"
}