Files
TREK/wiki/Budget-Tracking.md
T
Maurice 3c5e406229 feat(places): link an expense to a place, the way bookings already do (#1917)
* feat(places): link an expense to a place, the way bookings already do

Recording what a museum ticket or a guided tour cost meant leaving the place,
opening the Costs tab and typing it in again — while a booking has had a Costs
block in its own form all along.

The place form now carries that block. Same component, same flow: with nothing
linked it offers Create expense, which saves the place and then opens the Costs
editor for a new expense pointing at it; once linked it shows the expense with
edit and remove. budget_items gains place_id beside the reservation_id it
already had, with the same nullable FK and the same ON DELETE SET NULL. Desktop
and phone both, and the mobile editor carries the link through its own save path
rather than the desktop one.

The expense belongs to the place, not to a day assignment, so a place sitting on
five days is not five tickets. Hanging it off the assignment would have meant
adding a place to three days quietly creating three expenses and removing a day
quietly deleting one.

Deleting a place takes its expense with it in the same transaction and announces
it, on every path that deletes a place: REST, both MCP tools and the plugin RPC.

Closes #1298

* test(places): teach the RPC stubs about linkedExpenseIds

The delete paths read the linked expense before the place is gone, so the two
hand-built PlacesService stubs threw on the new method instead of reaching the
hook, the remove and the broadcast — four failures out of one missing line.
2026-08-12 21:37:12 +02:00

118 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Costs (Budget Tracking)
Track trip expenses by category, split costs between members, and visualize spending.
> **Renamed to Costs (v3.3.0, #1464):** This feature is now called **Costs** everywhere in the UI — the planner tab reads **Costs** and it is listed as **Costs** in Admin → Addons. Its internal addon id stays `budget`, which is why the permission is `budget_edit` and the MCP scopes are `budget:read` / `budget:write`.
![Costs tab of a trip showing the You owe / You're owed / Outstanding / Total trip spend cards above the dated expense list, with the settle-up transfers and per-member balances in the right-hand column](assets/Costs.png)
![Costs panel](assets/Budget.png)
## Where to find it
Open the **Costs** tab inside the trip planner. The tab is only visible when the Costs addon is enabled.
> **Admin:** Costs is an addon. Enable it in [Admin-Addons](Admin-Addons).
![Create Budget](assets/BudgetCreateBudget.gif)
## Currency
Costs is **multi-currency** (#551). Three settings are involved, and they do different jobs:
- The **trip currency** (Trip → Edit trip) is the trip's accounting base. Every balance and settle-up is calculated in it.
- Each **expense** carries **its own currency** — pick it in the expense modal and enter what the receipt says (a $100 dinner on a rouble trip is `100 USD`). It is converted into the trip currency at a rate **frozen when you save it**, so a settled debt doesn't reopen when the market moves.
- Your **display currency** (Settings → General) converts what you *read* — totals, chart, balances — into one currency. It changes nothing that is stored. Left on **Trip currency** (the default), each trip is shown in its own currency.
165 currencies are supported, with rates from [Frankfurter](https://frankfurter.dev) (no API key needed). When an item's currency differs from the display currency, the modal shows the converted amount alongside the rate (`1 {from} in {to}`), and the ledger row shows both (`$100.00 → 7 668,71 ₽`).
> **Read [Currencies](Currencies) for the full picture** — how the three interact, what happens when you change a trip's currency, and which currency a public share link is shown in.
## Categories
Expenses are grouped into categories. Each category is shown with a small colored square indicator that cycles through a 12-color palette as you add more categories.
From the toolbar you can:
- **Add a category** — type a name and click the **+** button (or press Enter).
- **Rename a category** — click the pencil icon next to its name in the category header.
- **Reorder categories** — drag the grip handle on the left of the category header.
- **Delete a category** — click the trash icon in the category header. This deletes all expense items inside it.
## Expense items
Each category contains a table of items with the following columns:
| Column | Notes |
|---|---|
| Name | Editable inline. Read-only when linked to a reservation. |
| Total | The total cost for this item. |
| Persons | Number of persons (or member chips on multi-member trips). |
| Days | Number of days. |
| Per Person | Calculated: Total ÷ Persons. |
| Per Day | Calculated: Total ÷ Days. |
| Per Person/Day | Calculated: Total ÷ (Persons × Days). |
| Date | Optional expense date. |
| Note | Free-text note. |
Click any editable cell to edit it inline. Drag the grip handle to reorder items within a category.
Add a new item using the inline **add row** at the bottom of each category table.
### Expenses linked to a booking or a place
An expense can hang off a **booking** (reservation or transport) or off a **place** — both offer a **Create expense** button in their form, which saves the record first and then opens the expense editor for it. A linked expense is an ordinary expense: it takes a payer, a split, a date and a currency like any other, and it shows up in the settlement.
Deleting the booking or the place deletes its linked expense with it. Removing the expense from the record's Costs block deletes only the expense and leaves the record standing.
## Splitting costs
The **Persons** column behaves differently depending on the trip:
- **Single-user trip** — enter a number of persons directly.
- **Multi-member trip** — a member chip picker appears. Click the edit button to open the expense modal, where you can select:
- **Equally** — Splits the cost equally among selected members. Remainder cents (from rounding errors) are distributed deterministically and rotated using the item ID to ensure everyone is charged equally over the course of the trip.
- **Custom** — Enter specific custom amounts for each traveler. The sum of the custom splits must balance exactly to the total price.
- **Ticket** — Build an itemized list of expenses (e.g. Apples: $10, cake: $50, Milk: $40) and assign specific trip participants to split each individual item. Individual shares are calculated cent-perfectly, the total expense price is automatically summed, and the list of itemized splits is saved/restored across edits.
Click an assigned member chip again to mark them as **paid** (the chip shows a green ring).
![Add Expense](assets/BudgetAddExpensive.gif)
## Settlement calculator
When multiple members are assigned to expenses and there are outstanding debts between members, a collapsible **Settlement** section appears inside the total card. Click the section header to expand it. It shows the minimum number of transfers needed to settle all debts (using a greedy matching algorithm), including:
- Transfer flows: who pays whom and how much.
- Net balances: each member's overall surplus or deficit.
Balances are always netted in the **trip currency** and converted to your display currency once, at the end — so they stay stable even when the trip mixes currencies.
A recorded payment carries **its own currency** too: settling a rouble debt with a euro transfer is normal, so the payment modal has a currency picker, and its rate is frozen when you record it. A payment made in another currency shows both amounts in the ledger (`$30.00 → 27,00 €`).
![Add payment dialog with From and To member pickers, an amount field and a currency selector](assets/CostsSettleUp.png)
![Final Settlement](assets/BudgetFinalSettlement.gif)
## Costs summary
The right-hand column contains two widgets:
- **Total card** — displays the grand total in large type. On multi-member trips it also shows a per-member breakdown with a proportional bar.
- **Donut chart** — spending by category. Each segment uses that category's color. The legend always shows the amount and percentage for each category; hovering a legend row highlights it.
## Exporting
Click **Export CSV** in the toolbar to download all expenses as a spreadsheet (restored in v3.3.0, #1500). The file is semicolon-delimited with a UTF-8 byte-order mark (so Excel opens it cleanly), rows sorted by date, and is named `costs-<trip>.csv`. The columns are: **Date, Name, Category, Amount, Currency, Amount (<display currency>), Note** — each expense shows both its original amount in its own currency and the converted amount in your display currency.
## Permissions
All write operations (adding/editing/deleting items and categories, and an expense's currency) require the `budget_edit` permission. The **trip** currency lives on the trip itself, so changing that requires `trip_edit` instead.
## See also
- [Currencies](Currencies)
- [Admin-Addons](Admin-Addons)
- [Reservations-and-Bookings](Reservations-and-Bookings)
- [Trip-Planner-Overview](Trip-Planner-Overview)