docs: sync README and DOCS with health version, category delete rules, split transactions

/api/health now returns a version field, default categories can be deleted
once unused, and split transactions (dividing one purchase across a parent
category's subcategories) were fully undocumented — added a dedicated
section in DOCS.md and a mention in README's feature list. Also fixed the
"update to newer code" command, which pulled a Docker image tag that
doesn't exist for locally-built services instead of the actual git repo.
This commit is contained in:
ZProger
2026-09-02 02:26:27 +03:00
parent 946b78d989
commit 4aaa0a0ef0
2 changed files with 58 additions and 9 deletions

63
DOCS.md
View File

@@ -116,8 +116,9 @@ string.
### `GET /api/health`
No auth required. Returns `{"status": "ok"}`. Use this to check the backend is up before hitting
anything else.
No auth required. Returns `{"status": "ok", "version": "1.1.0"}` — `version` is the running app's
version, the same value shown in the UI under Settings. Use this to check the backend is up before
hitting anything else.
## Accounts
@@ -181,7 +182,7 @@ though they can be renamed, recolored, and reordered.
| `GET` | `/categories` | List categories, sorted by `sort_order`. `?kind=income` or `?kind=expense` to filter. |
| `POST` | `/categories` | Create a category (or subcategory, via `parent_id`). |
| `PATCH` | `/categories/{id}` | Update a category (partial). |
| `DELETE` | `/categories/{id}` | Delete a category. Fails with `400` if it's a default category. |
| `DELETE` | `/categories/{id}` | Delete a category. For a default (`is_default: true`) category, fails with `400` if any transaction or transaction split still points at it — a custom category has no such guard and can always be deleted. |
**Create body:**
@@ -293,7 +294,7 @@ Field rules, enforced server-side:
`kind` if given (an `income` transaction can't use an `expense` category). `transfer_account_id`
must be omitted.
- `transfer`: `transfer_account_id` is **required** and must differ from `account_id`.
`category_id` must be omitted — transfers aren't categorized.
`category_id` must be omitted — transfers aren't categorized. `splits` aren't valid either.
- `amount`: required, `> 0` (transfers/expenses aren't signed negative — direction comes from
`type`), up to 14 digits, 2 decimal places.
- `description`: required, 1–255 chars. Auto-capitalized server-side (first letter uppercased).
@@ -301,13 +302,61 @@ Field rules, enforced server-side:
- `notes`: optional, free text.
- `date`: required.
- `tag_ids`: optional list of existing tag IDs. Unknown IDs return `400`.
- `splits`: optional list of `{"category_id": ..., "amount": ..., "note": ...}` — divides the
transaction across 2+ categories instead of one. See **Splitting a transaction** below.
**Update** (`TransactionUpdate`) accepts the same fields, all optional, plus the same
type/category/transfer consistency rules applied to the *effective* (merged) values. `tag_ids`: omit
to leave tags untouched; send (even `[]`) to replace the full tag set.
type/category/transfer/split consistency rules applied to the *effective* (merged) values. `tag_ids`:
omit to leave tags untouched; send (even `[]`) to replace the full tag set. `splits`: omit to leave
splits untouched; send (even `[]`) to replace the full split set — send `[]` together with a
`category_id` to turn a split transaction back into a normal single-category one.
**Response** (`TransactionRead`): the input fields plus `id`, and the nested `account`
(`AccountRead`), `category` (`CategoryRead` or `null`), `tags` (`TagRead[]`).
(`AccountRead`), `category` (`CategoryRead` or `null`), `tags` (`TagRead[]`), `splits`
(`TransactionSplitRead[]`, empty for a normal non-split transaction).
### Splitting a transaction across categories
A single purchase that covers more than one subcategory of the same parent (a supermarket receipt
that's part "Sweets", part "Alcohol", both under "Groceries") can be recorded as one transaction
divided across those categories instead of several separate transactions:
```json
{
"account_id": 1,
"type": "expense",
"amount": 84.20,
"description": "Grocery run",
"date": "2026-08-29",
"splits": [
{ "category_id": 12, "amount": 60.00, "note": "Sweets" },
{ "category_id": 13, "amount": 24.20, "note": "Alcohol" }
]
}
```
Rules, enforced on both create and update (against the row as it would look *after* the change):
- `category_id` on the transaction itself **must be omitted** when `splits` is given — the split
entries carry the categories instead.
- Not valid on a `transfer` transaction.
- At least 2 entries — a single split is just `category_id` with extra steps and is rejected.
- Each split's `category_id` is required and must be a category of the transaction's own kind
(`income`/`expense`, same check a plain `category_id` gets); `amount` is required (`> 0`, up to 14
digits/2 decimals); `note` is optional, up to 200 chars.
- **All splits must share the same top-level category** — either the parent itself (an
unspecified-subcategory line) or one of its direct subcategories. Splitting across two unrelated
top-level categories (e.g. part "Groceries", part "Transport") is rejected with `400`, since the
feature exists to divide one purchase's total *within* one category tree, not to record what's
really two separate transactions as one.
- The split amounts **must sum exactly** to the transaction's own `amount`, or the request is
rejected with `400`.
Aggregation endpoints are split-aware: `/dashboard/summary`'s `spending_by_category` and
`/reports/category-ranking` count a split transaction's amount under every category it actually
touches (rolled up to the shared parent), not just once under a single category — a receipt split
$60/$24.20 between Sweets and Alcohol above contributes exactly those amounts to each subcategory's
total under Groceries.
### Bulk create (`POST /transactions/bulk`)

View File

@@ -58,7 +58,7 @@ Aurum treats your financial life as a **system**, not a spreadsheet.
## 🧩 Core Features
### 💰 Cash Flow & Transactions
Log income, expenses, and transfers across as many accounts as you want, organized into categories (with one level of subcategories) plus free-form tags, and searchable. Already have history elsewhere? Import a bank's CSV export in a guided 3-step wizard instead of typing every line by hand. A dedicated Cash Flow view charts income vs. expense month by month.
Log income, expenses, and transfers across as many accounts as you want, organized into categories (with one level of subcategories) plus free-form tags, and searchable. One purchase spanning multiple subcategories of the same parent (a grocery receipt part "Sweets", part "Alcohol") can be recorded as a single split transaction instead of several — every report and chart still counts each share under its own category. Already have history elsewhere? Import a bank's CSV export in a guided 3-step wizard instead of typing every line by hand. A dedicated Cash Flow view charts income vs. expense month by month.
### 📈 Net Worth Engine
A live net worth timeline (30 days to all-time) aggregating cash and every manually tracked asset — investments, crypto, real estate, vehicles, precious metals — into one number, with a full breakdown by asset class, by how each asset behaves (income / neutral / drain), and by risk level.
@@ -157,7 +157,7 @@ docker compose logs web
docker compose down # stop everything, keep your data
docker compose up -d # start it again later
docker compose down -v # stop AND permanently delete your data — be sure
docker compose pull && docker compose up -d --build # update to newer code
git pull && docker compose up -d --build # update to newer code
```
Your data lives in a Docker named volume (`aurum_pgdata`), not in the repo folder — it survives `docker compose down`, image rebuilds, and `git pull`. It's only gone if you explicitly run `docker compose down -v` or delete the volume yourself. For anything short of that, use the in-app **Settings → Backup & Restore** to export a JSON snapshot of everything before making risky changes.