Overview
In this feature we introduce an ability to create different budget (forecast) versions for the year.
Single budget is always attached to the property/building.
From the technical point of view, budget as an object doesn't exist - it is composed of the lines inside it.
States
Each line has its own lineState - ACTIVE or ARCHIVED, and also contains global budgetState - DRAFT or VALIDATED.
As physical budget doesn't exist, it can be uniquely identified by partId + year + version.
Amounts & lines
Doesn't matter how budget lines are presented on UI, we will always have a single object for one line.
Depending on amounts inside, client should be able to identify where and how to display the line.
Every line will have the following arrays of amounts:
forecastAmountsforecastRecoverableAmountsactualsAmounts
Each Amount object inside the array has the following structure:
{
"period": "2025-01",
"amount": {
"amount": 1000,
"currency": "EURO"
}
}
Also, each budget line will have the following properties, which are calculated by summarizing corresponding amount arrays:
forecastTotalforecastRecoverableTotalactualsTotal
Each amounts array will contain up to 12 amounts at a time, split by period (monthly). If user decides to enter each month amount manually, BE will just accept this information from the client, otherwise total will be automatically split into 12 months.
Array forecastRecoverableAmounts will be propagated only in case if line has recoverable flag set to true.
Validations & display
When the user wants to create a new budget, it should be pre-filled with the latest validated version for the currently selected year or the last validated version from the previous year.
When the budget is created, each line inside has lineState: 'ACTIVE' and budgetState: 'DRAFT' by default.
Amount split mode by default is automatic, and can be changed to manual only when monthly amounts are edited by the user.
When the budget becomes validated - budgetState: 'VALIDATED', every line inside it will be frozen and no action on it will be available.
VALIDATED budget can contain ARCHIVED lines. Only line with no amounts on the current year can be archived.
Line can be deleted only in case the line for the previous version/year doesn't exist.
Special cases
Budget line can be created without being attached to the concrete budget.
Such a row can be identified by amounts - it will have only actualsAmounts array filled in, and others will be empty.
Later on, when creating a new budget, user will be proposed to attach such rows to it.
Actions & URLs
adb-views
Endpoints should return a collection of lines, paginated;
1. GET /views/financials/forecast-income?q={q}
2. GET /views/financials/forecast-outcome?q={q}
3. GET /views/financials/actuals-income?q={q}
4. GET /views/financials/actuals-outcome?q={q}
5. GET /views/financials/latest?year={year} or GET /views/financials/latest?q={q}
6. GET /views/financials/charges-totals/{partId} - to calculate total budget lines summary per single part
adb-parts
1. GET /parts/id
2. POST /parts/{partId}/budgets
3. POST /parts/{partId}/budgets/{budgetId}/lines
4. GET /parts/{partId}/budgets/{budgetId}/lines/{budgetLineId}
5. DELETE /parts/{partId}/budgets/{budgetId}/lines/{budgetLineId}
6. PATCH /parts/{partId}/budgets/{budgetId}/lines/{budgetLineId}/line-state/{line-state}
7. PATCH /parts/{partId}/budgets/{budgetId}/state/{budget-state}
8. PATCH /parts/{partId}/budgets/{budgetId}/lines/{budgetLineId} - patch amounts, type, category, description, allocation keys and other stuff at once
9. GET /parts/{partId}/budgetless
Objects
Budget
On POST request we can have different cases:
- No validated budget exists in this and or/previous year. Then, we will have no lines, and we need to create one automatically, filling only year, type and category.
- We already have a validated budget version for this/previous year. In this case, UI has to set
yearfield to the user selection, copylinesarray and for each line set a newbudgetIdand setidtonull.
On GET request (by ID) we return:
{
"type": "Budget",
"year": "2025",
"version": "1",
"rootPartId": "property1",
"lines": [
{
"type": "BudgetLine",
"partId": "building1", // maybe target object?
"lineType": {...catalog...},
"category": {...catalog...},
"description": "some text",
"allocationKeys": [..array of target objects..]
}
]
}
Q:
- delete the row: we need to add either a flag or a new call
- will we have the global budget state on the Budget object level? open question