Contract v2: clarify ids, rates, recurring, ledger and attachment details
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dfb4fe4e1a
commit
7fbe86a943
1 file changed
+41
-12
+41
-12
@@ -24,7 +24,10 @@ tombstone и last-write-wins не меняются. Клиент v1 продол
|
||||
внутри клиентов — целые сотые. Курс валют — **строка** десятичного числа.
|
||||
5. Даты — ISO 8601 UTC. Интервалы — `[начало, конец)`.
|
||||
6. Коды валют — ISO 4217 в верхнем регистре (`RUB`, `EUR`, `USD`, `SEK`, …).
|
||||
7. Идентификаторы записей — UUID в нижнем регистре, кроме `setting` (ключ).
|
||||
7. Новые записи получают UUID в нижнем регистре (кроме `setting`, где `id` —
|
||||
ключ). Сервер сравнивает `id` как есть, с учётом регистра, поэтому клиент
|
||||
хранит `id` полученной записи в точности как пришёл и отправляет его так же
|
||||
(старые клиенты могли создавать UUID в верхнем регистре).
|
||||
|
||||
### Возможности сервера
|
||||
|
||||
@@ -70,7 +73,8 @@ tombstone и last-write-wins не меняются. Клиент v1 продол
|
||||
```
|
||||
|
||||
`kindRawValue`: `cash` | `card` | `deposit` | `other` (незнакомое → `other`).
|
||||
`currency` по умолчанию — базовая.
|
||||
Клиент v2 записывает `currency` явно при создании счёта. Отсутствие поля
|
||||
(старая запись) = текущая базовая валюта.
|
||||
|
||||
### transfer
|
||||
|
||||
@@ -146,9 +150,10 @@ tombstone и last-write-wins не меняются. Клиент v1 продол
|
||||
|
||||
- Валюта транзакции: `currency`, иначе валюта её счёта, иначе базовая.
|
||||
- Перевод суммы `x` из `C` в базовую `B`: при `C == B` без изменений; иначе
|
||||
берётся последняя (по `date`, затем по `updatedAt`) запись `rate` с
|
||||
`from=C,to=B` или обратная `from=B,to=C` (тогда делим). Если подходящих
|
||||
записей несколько с одной датой, прямая пара важнее обратной.
|
||||
берётся запись `rate` с `from=C,to=B` или обратная `from=B,to=C` (тогда
|
||||
делим). Выбор: самая поздняя `date` не позже даты расчёта (курсы с будущей
|
||||
датой не используются); при равной дате прямая пара важнее обратной; затем
|
||||
более поздний `updatedAt`.
|
||||
- Результат округляется до сотых половиной вверх (от нуля). Считать через
|
||||
целые сотые и десятичную строку курса, не через `double`-накопление.
|
||||
- Нет курса → сумма **не входит** в общие итоги, клиент показывает
|
||||
@@ -177,7 +182,8 @@ tombstone и last-write-wins не меняются. Клиент v1 продол
|
||||
и время суток): `daily` — каждые `interval` дней, `weekly` — каждые
|
||||
`7·interval` дней, `monthly` — каждые `interval` месяцев в тот же день
|
||||
(если дня нет — последний день месяца), `yearly` — то же с годами
|
||||
(29 февраля → 28 февраля). Повторение после `endDate` не создаётся.
|
||||
(29 февраля → 28 февраля). `endDate` сравнивается по UTC-дате и включается:
|
||||
повторение в день `endDate` создаётся, после — нет.
|
||||
- `occurrence` = UTC-дата `YYYY-MM-DD`; дата транзакции = эта дата + время
|
||||
суток `startDate`.
|
||||
- ID транзакции **детерминирован**: UUID v5 от пространства имён
|
||||
@@ -190,8 +196,11 @@ tombstone и last-write-wins не меняются. Клиент v1 продол
|
||||
повторение не воскрешается, т.к. `generatedThrough` уже позади. Не более
|
||||
366 повторений за раз.
|
||||
- `remind`: клиент показывает повторения из того же окна как «ожидают»;
|
||||
«Записать» создаёт транзакцию с тем же детерминированным ID, «Пропустить»
|
||||
только двигает `generatedThrough`.
|
||||
«Записать» создаёт транзакцию с тем же детерминированным ID и двигает
|
||||
`generatedThrough` только через подряд обработанные повторения (если
|
||||
записать более позднее раньше, более раннее остаётся ожидать).
|
||||
«Пропустить» двигает `generatedThrough` до этого повторения включительно —
|
||||
более ранние ожидающие тоже считаются пропущенными.
|
||||
- `isPaused` — ничего не создаётся и не напоминается.
|
||||
- Часы регулярные операции не создают (их создают телефон и десктоп).
|
||||
|
||||
@@ -222,12 +231,23 @@ tombstone и last-write-wins не меняются. Клиент v1 продол
|
||||
- Пространство выбирается заголовком `X-Balance-Ledger: <id>` на
|
||||
`/v1/sync` и `/v1/attachments/*`. Без заголовка — личное пространство.
|
||||
Не участник → `403 forbidden`, несуществующее → `404 not_found`.
|
||||
- Владелец не может выйти, пока есть участники (только удалить пространство).
|
||||
- Владелец не может выйти (`409 owner_cannot_leave`), даже из пустого
|
||||
пространства — только удалить его.
|
||||
- Ответы: создание пространства и приглашения — `201`; вход по коду и
|
||||
переименование — `200`; удаление пространства и участника — `204`.
|
||||
Повторный вход участника возвращает пространство и не тратит код.
|
||||
`/v1/ledgers/join` ограничен по частоте вместе со входом (30 запросов в
|
||||
минуту с IP).
|
||||
- Клиент, получивший `403`/`404` для выбранного пространства, может сам
|
||||
вернуться в личное (Flutter) или показать ошибку и предложить переключиться
|
||||
(часы).
|
||||
- Все участники равны в правах на записи (last-write-wins как в v1).
|
||||
- Клиент хранит данные пространства отдельно: scope = сервер + пользователь
|
||||
+ пространство. Переключение пространства не переносит записи. Курсор и
|
||||
очередь изменений — свои у каждого пространства.
|
||||
- Ошибки: `invalid_code` (400), `forbidden` (403), `not_found` (404),
|
||||
- Ошибки: `invalid_code` (400), `invalid_name` (400: пустое после `trim`,
|
||||
длиннее 100 символов или с управляющими символами), `invalid_json` (400),
|
||||
`forbidden` (403), `not_found` (404, в том числе удаление не-участника),
|
||||
`owner_cannot_leave` (409). Коды приглашений хранятся на сервере только
|
||||
как хеш.
|
||||
|
||||
@@ -241,11 +261,20 @@ tombstone и last-write-wins не меняются. Клиент v1 продол
|
||||
- Порядок клиента: сначала PUT байтов, потом запись `attachment` через sync.
|
||||
- Файлы хранятся в пространстве; один и тот же файл в двух пространствах
|
||||
хранится дважды.
|
||||
- Ответ PUT: `{"sha256","size","contentType"}`. GET отдаёт
|
||||
`Content-Disposition: attachment`.
|
||||
- Когда в пространстве не осталось живых записей `attachment` с этим
|
||||
`sha256`, сервер может удалить файл.
|
||||
`sha256`, сервер может удалить файл. Файл, на который ещё не ссылалась ни
|
||||
одна запись, хранится не меньше 24 часов после загрузки (клиент загружает
|
||||
байты раньше записи).
|
||||
- Лимит тела 10 МиБ действует только для загрузки вложения; остальные
|
||||
запросы — 2 МиБ, как в v1.
|
||||
- Ошибки: `payload_too_large` (413), `unsupported_media_type` (415),
|
||||
`hash_mismatch` (400).
|
||||
`hash_mismatch` (400, в том числе неверный формат хеша в пути).
|
||||
- Клиент скачивает файл лениво при просмотре и кеширует локально.
|
||||
- Сообщение ошибки для незнакомой сущности на сервере осталось как в v1
|
||||
(«entity must be transaction, category, or budget») ради совместимости с
|
||||
записанными ответами; код `invalid_change` верный.
|
||||
|
||||
## 9. Совместимость с клиентами v1
|
||||
|
||||
|
||||
Reference in new issue
Block a user