added AGENTS.ms and docs for Android app
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Проект
|
||||
|
||||
**Balance Android** — нативный Android-клиент приложения Balance для учёта личных финансов. Он повторяет доменную модель iOS/macOS/watchOS-клиентов и синхронизируется с тем же self-hosted BalanceServer.
|
||||
|
||||
Стек:
|
||||
- Kotlin 2.3.21
|
||||
- Jetpack Compose
|
||||
- Room
|
||||
- Coroutines / Flow
|
||||
- WorkManager
|
||||
- Android Keystore
|
||||
- minSdk 26 / compileSdk 36
|
||||
- Java 17 bytecode
|
||||
|
||||
## Структура
|
||||
|
||||
```text
|
||||
BalanceAndroid/
|
||||
├── app/src/main/java/com/example/balanceandroid/
|
||||
│ ├── data/ # Room entities, DAO, финансовая математика
|
||||
│ ├── sync/ # HTTP API, session, preferences, sync repository/worker
|
||||
│ ├── ui/ # Compose UI и theme
|
||||
│ ├── MainActivity.kt
|
||||
│ ├── MainViewModel.kt
|
||||
│ └── BalanceApplication.kt
|
||||
├── app/src/test/ # unit tests
|
||||
├── app/src/main/res/
|
||||
└── docs/
|
||||
```
|
||||
|
||||
## Правила
|
||||
|
||||
### Data layer
|
||||
|
||||
- Все persisted финансовые сущности находятся в `data/`.
|
||||
- Доступ к Room выполняется через `FinanceDao`.
|
||||
- Не помещай HTTP или Compose-логику в DAO.
|
||||
- Изменение Room entities требует проверки schema/migration.
|
||||
- `updatedAt` — timestamp версии записи и обязателен для syncable entities.
|
||||
|
||||
### Финансовая логика
|
||||
|
||||
`FinanceMath` является единым источником правил расчёта.
|
||||
|
||||
- balance = income − expense;
|
||||
- balance adjustments влияют на общий баланс;
|
||||
- adjustments исключаются из периодической аналитики;
|
||||
- spending группируется по `categoryName`.
|
||||
|
||||
При изменении правил обновляй `FinanceMathTest`.
|
||||
|
||||
### Удаления
|
||||
|
||||
Для синхронизируемых объектов нельзя использовать только прямой `DELETE`.
|
||||
|
||||
Используй DAO-методы:
|
||||
- `deleteTransaction`
|
||||
- `deleteCategory`
|
||||
- `deleteBudget`
|
||||
|
||||
Они сначала создают запись в `deletion_journal`, затем удаляют объект.
|
||||
|
||||
### Категории
|
||||
|
||||
Категория хранится snapshot-полями в transactions/budgets. Поэтому при переименовании пользовательской категории текущая реализация обновляет связанные операции и бюджеты. Сохраняй это поведение.
|
||||
|
||||
### Sync
|
||||
|
||||
`SyncRepository` — единственная точка orchestration синхронизации.
|
||||
|
||||
- `ServerApi` отвечает только за HTTP/auth.
|
||||
- `ServerPreferences` отвечает за URL, cursor и sync metadata.
|
||||
- `SecureSessionStore` отвечает за зашифрованную сессию.
|
||||
- `SyncWorker` запускает периодическую sync-задачу.
|
||||
|
||||
Не делай network calls непосредственно из Compose UI.
|
||||
|
||||
### Security
|
||||
|
||||
- Access/refresh tokens не должны попадать в обычные SharedPreferences.
|
||||
- Не логируй tokens/passwords/Authorization.
|
||||
- Session хранится через AES/GCM с ключом Android Keystore.
|
||||
- Для удалённого сервера нужен HTTPS.
|
||||
- HTTP разрешён только локальным адресам, предусмотренным `ServerPreferences`.
|
||||
- Не добавляй credentials или production secrets в репозиторий.
|
||||
|
||||
### Compose
|
||||
|
||||
- UI state идёт из `MainViewModel`.
|
||||
- Данные Room наблюдаются через Flow.
|
||||
- Общие компоненты — `ui/Components.kt`; feature screens — `ui/BalanceRoot.kt` и `ui/Editors.kt`.
|
||||
- Не переносить бизнес-логику в composables.
|
||||
|
||||
### Sync invariants
|
||||
|
||||
При изменении syncable entity:
|
||||
1. сохранить новое значение;
|
||||
2. обновить `updatedAt`;
|
||||
3. дождаться следующего sync.
|
||||
|
||||
При удалении:
|
||||
1. создать deletion journal;
|
||||
2. удалить локальную запись;
|
||||
3. отправить tombstone на сервер.
|
||||
|
||||
Remote change применяется только если его `updatedAt` новее локального.
|
||||
|
||||
## Тестирование
|
||||
|
||||
Минимум:
|
||||
|
||||
```bash
|
||||
./gradlew test
|
||||
```
|
||||
|
||||
Перед PR желательно:
|
||||
|
||||
```bash
|
||||
./gradlew clean test assembleDebug
|
||||
```
|
||||
|
||||
Если менялся sync/data layer, добавь unit tests для новых инвариантов.
|
||||
|
||||
## PR checklist
|
||||
|
||||
- [ ] Изменён правильный слой.
|
||||
- [ ] Финансовая логика остаётся в `FinanceMath`.
|
||||
- [ ] Изменения persisted schema учтены.
|
||||
- [ ] `updatedAt` обновляется.
|
||||
- [ ] Deletes создают tombstones.
|
||||
- [ ] Sync не вызывается напрямую из UI.
|
||||
- [ ] Нет секретов в репозитории/logcat.
|
||||
- [ ] Unit tests проходят.
|
||||
- [ ] README/docs обновлены при изменении setup/API.
|
||||
@@ -94,3 +94,13 @@ export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
|
||||
Проект задаёт для Gradle 2 ГБ heap, 1 ГБ metaspace и не более двух параллельных workers. Эти параметры находятся в `gradle.properties` и применяются также при сборке из Android Studio.
|
||||
|
||||
Debug APK появится в `app/build/outputs/apk/debug/`.
|
||||
|
||||
## Документация проекта
|
||||
|
||||
- [`AGENTS.md`](AGENTS.md) — правила для AI-агентов и разработчиков.
|
||||
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — архитектура Android-клиента.
|
||||
- [`docs/DATA_MODEL.md`](docs/DATA_MODEL.md) — Room-модели и финансовые правила.
|
||||
- [`docs/SYNC.md`](docs/SYNC.md) — протокол синхронизации и безопасность сессии.
|
||||
- [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) — Android Studio, Gradle, тесты и release.
|
||||
- [`docs/PRODUCT.md`](docs/PRODUCT.md) — функциональная документация.
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# Архитектура Balance Android
|
||||
|
||||
## Слои
|
||||
|
||||
```text
|
||||
Jetpack Compose
|
||||
│
|
||||
▼
|
||||
MainViewModel
|
||||
│
|
||||
├──────────────► FinanceMath
|
||||
│
|
||||
▼
|
||||
FinanceDao ────────► Room / SQLite
|
||||
│
|
||||
└──────────────► SyncRepository
|
||||
│
|
||||
┌─────────┴─────────┐
|
||||
▼ ▼
|
||||
ServerApi SecureSessionStore
|
||||
│ │
|
||||
▼ ▼
|
||||
BalanceServer Android Keystore
|
||||
```
|
||||
|
||||
## Application
|
||||
|
||||
`BalanceApplication` создаёт singleton-зависимости:
|
||||
|
||||
- `AppDatabase`
|
||||
- `ServerPreferences`
|
||||
- `SecureSessionStore`
|
||||
- `SyncRepository`
|
||||
|
||||
Также регистрирует уникальный WorkManager job `balance-periodic-sync`.
|
||||
|
||||
## UI
|
||||
|
||||
`MainActivity` запускает Compose root.
|
||||
|
||||
`BalanceRoot` содержит навигацию между:
|
||||
- Обзор;
|
||||
- Аналитика;
|
||||
- Операции;
|
||||
- Бюджеты;
|
||||
- Настройки;
|
||||
- Категории;
|
||||
- Сервер и синхронизация.
|
||||
|
||||
`MainViewModel` объединяет Room flows, sync status/session и настройки в `FinanceUiState`.
|
||||
|
||||
## Data
|
||||
|
||||
Room database называется `balance-android.db`.
|
||||
|
||||
Entities:
|
||||
- `TransactionEntity`
|
||||
- `CategoryEntity`
|
||||
- `BudgetEntity`
|
||||
- `DeletionEntity`
|
||||
|
||||
`FinanceDao` предоставляет Flow для UI и suspend-операции для записи/sync.
|
||||
|
||||
## Sync
|
||||
|
||||
Sync двухфазный:
|
||||
|
||||
```text
|
||||
local changes → push
|
||||
server changes → pull
|
||||
```
|
||||
|
||||
Push идёт пакетами до 100 изменений. Большой JSON (> 1.5 MB) рекурсивно разбивается.
|
||||
|
||||
Pull использует server cursor и `hasMore`.
|
||||
|
||||
Remote changes применяются атомарно через Room transaction.
|
||||
|
||||
## Conflict resolution
|
||||
|
||||
Стратегия — timestamp-based last-write-wins:
|
||||
|
||||
```text
|
||||
remote.updatedAt > local.updatedAt
|
||||
```
|
||||
|
||||
Если локальная запись новее или равна, remote payload не заменяет её.
|
||||
|
||||
## Background sync
|
||||
|
||||
WorkManager запускается с:
|
||||
- периодом 15 минут;
|
||||
- `NetworkType.CONNECTED`.
|
||||
|
||||
Worker ничего не делает без server session/server URL.
|
||||
|
||||
Ограничения Android могут отложить фактический запуск; 15 минут — минимальный период WorkManager, а не гарантия точного времени запуска.
|
||||
|
||||
## Платформа
|
||||
|
||||
Android-клиент не использует CloudKit. Локальное хранение всегда Room; синхронизация с другими клиентами выполняется через BalanceServer.
|
||||
@@ -0,0 +1,111 @@
|
||||
# Модель данных
|
||||
|
||||
## Room schema
|
||||
|
||||
### transactions
|
||||
|
||||
`TransactionEntity`:
|
||||
|
||||
| Поле | Тип | Назначение |
|
||||
|---|---|---|
|
||||
| `id` | String | UUID |
|
||||
| `amount` | Double | сумма |
|
||||
| `date` | Long | epoch millis |
|
||||
| `note` | String | заметка |
|
||||
| `categoryName` | String | snapshot категории |
|
||||
| `categoryIcon` | String | иконка |
|
||||
| `categoryEmoji` | String | emoji |
|
||||
| `categoryColorName` | String | цвет |
|
||||
| `isBalanceAdjustment` | Boolean | корректировка |
|
||||
| `kindRawValue` | String | `income`/`expense` |
|
||||
| `updatedAt` | Long | версия для sync |
|
||||
|
||||
Индексы: `updatedAt`, `date`.
|
||||
|
||||
### categories
|
||||
|
||||
Пользовательские категории:
|
||||
|
||||
- `id`
|
||||
- `name`
|
||||
- `icon`
|
||||
- `emoji`
|
||||
- `colorName`
|
||||
- `kindRawValue`
|
||||
- `createdAt`
|
||||
- `updatedAt`
|
||||
|
||||
Индекс `(name, kindRawValue)` помогает находить категории по имени и типу.
|
||||
|
||||
### budgets
|
||||
|
||||
- `id`
|
||||
- `categoryName`
|
||||
- `categoryIcon`
|
||||
- `categoryEmoji`
|
||||
- `limitAmount`
|
||||
- `monthStart`
|
||||
- `updatedAt`
|
||||
|
||||
### deletion_journal
|
||||
|
||||
Локальный журнал tombstones:
|
||||
|
||||
- `id = entity:recordId`
|
||||
- `entity`
|
||||
- `recordId`
|
||||
- `updatedAt`
|
||||
|
||||
## Built-in categories
|
||||
|
||||
Expense:
|
||||
- Продукты
|
||||
- Транспорт
|
||||
- Дом
|
||||
- Развлечения
|
||||
- Здоровье
|
||||
- Покупки
|
||||
- Подписки
|
||||
- Образование
|
||||
- Другое
|
||||
|
||||
Income:
|
||||
- Зарплата
|
||||
- Подработка
|
||||
- Инвестиции
|
||||
- Подарок
|
||||
- Другое
|
||||
|
||||
Они не являются Room entities.
|
||||
|
||||
## Финансовые правила
|
||||
|
||||
### Общий баланс
|
||||
|
||||
```text
|
||||
sum(income.amount) - sum(expense.amount)
|
||||
```
|
||||
|
||||
`isBalanceAdjustment` здесь не исключается.
|
||||
|
||||
### Периодическая аналитика
|
||||
|
||||
`FinanceMath.summary` исключает:
|
||||
- все balance adjustments;
|
||||
- записи вне `[start, endExclusive)`.
|
||||
|
||||
### Spending
|
||||
|
||||
`FinanceMath.spending`:
|
||||
- берёт только expenses;
|
||||
- исключает adjustments;
|
||||
- группирует по category name;
|
||||
- сортирует по убыванию суммы.
|
||||
|
||||
## Category snapshot
|
||||
|
||||
Transaction и budget не имеют Room relationship на CategoryEntity. Они хранят presentation data непосредственно.
|
||||
|
||||
Это позволяет удалять пользовательскую категорию, не разрушая исторические операции.
|
||||
|
||||
Поэтому rename категории распространяется вручную на связанные transactions/budgets.
|
||||
@@ -0,0 +1,154 @@
|
||||
# Разработка
|
||||
|
||||
## Toolchain
|
||||
|
||||
Текущая версия проекта:
|
||||
|
||||
- Android Studio Quail 1 (2026.1.1) или новее
|
||||
- Gradle 8.13
|
||||
- Android Gradle Plugin 8.13.0
|
||||
- Kotlin 2.3.21
|
||||
- Compose BOM 2026.06.00
|
||||
- compileSdk 36
|
||||
- minSdk 26
|
||||
- Java 17 target
|
||||
- Room 2.8.4
|
||||
- Lifecycle 2.10.0
|
||||
|
||||
Проект не использует сторонние runtime-библиотеки вне стандартного Android/Jetpack stack.
|
||||
|
||||
## Запуск
|
||||
|
||||
Откройте `BalanceAndroid/` в Android Studio.
|
||||
|
||||
Затем:
|
||||
|
||||
1. Gradle Sync.
|
||||
2. Установить Android SDK Platform 36.
|
||||
3. Создать/подключить Android 8+ устройство или emulator.
|
||||
4. Запустить configuration `app`.
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
./gradlew test assembleDebug
|
||||
```
|
||||
|
||||
Полная чистая проверка:
|
||||
|
||||
```bash
|
||||
./gradlew clean test assembleDebug
|
||||
```
|
||||
|
||||
Проверить JVM:
|
||||
|
||||
```bash
|
||||
./gradlew --version
|
||||
```
|
||||
|
||||
Используется Java 17–23; bytecode target — Java 17.
|
||||
|
||||
## SDK
|
||||
|
||||
Можно использовать:
|
||||
|
||||
```bash
|
||||
export ANDROID_HOME="/path/to/Android/Sdk"
|
||||
export ANDROID_SDK_ROOT="$ANDROID_HOME"
|
||||
```
|
||||
|
||||
или `local.properties`:
|
||||
|
||||
```properties
|
||||
sdk.dir=/home/USER/Android/Sdk
|
||||
```
|
||||
|
||||
`local.properties` не должен попадать в Git.
|
||||
|
||||
## Unit tests
|
||||
|
||||
Тесты находятся в:
|
||||
|
||||
```text
|
||||
app/src/test/
|
||||
```
|
||||
|
||||
Основной текущий тестовый класс:
|
||||
|
||||
```text
|
||||
FinanceMathTest
|
||||
```
|
||||
|
||||
Тестируй отдельно:
|
||||
- balance;
|
||||
- period summary;
|
||||
- adjustment exclusion;
|
||||
- spending grouping;
|
||||
- edge cases around period boundaries.
|
||||
|
||||
## Local development
|
||||
|
||||
Приложение работает без сервера. Локальные данные хранятся в Room.
|
||||
|
||||
Для Android Emulator host server:
|
||||
|
||||
```text
|
||||
http://10.0.2.2:8080
|
||||
```
|
||||
|
||||
Для физического Android device `localhost` указывает на сам телефон, поэтому нужен доступный по сети HTTPS endpoint.
|
||||
|
||||
## Application ID
|
||||
|
||||
Текущий пример:
|
||||
|
||||
```text
|
||||
com.example.balanceandroid
|
||||
```
|
||||
|
||||
Перед Google Play release замени его на уникальный production application ID.
|
||||
|
||||
## Release checklist
|
||||
|
||||
- [ ] Production applicationId.
|
||||
- [ ] Release signing key.
|
||||
- [ ] HTTPS server.
|
||||
- [ ] Проверен backup/data extraction policy.
|
||||
- [ ] Проверена миграция Room.
|
||||
- [ ] Проверена background sync.
|
||||
- [ ] Проверена авторизация и token refresh.
|
||||
- [ ] Нет секретов в APK source/config.
|
||||
- [ ] `./gradlew clean test assembleDebug` проходит.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Gradle не находит SDK
|
||||
|
||||
Проверь `ANDROID_HOME`, `ANDROID_SDK_ROOT` или `local.properties`.
|
||||
|
||||
### Неверная Java
|
||||
|
||||
Android Studio → Gradle JDK → Embedded JDK.
|
||||
|
||||
Нужна Java 17–23.
|
||||
|
||||
### Старый Gradle daemon
|
||||
|
||||
```bash
|
||||
./gradlew --stop
|
||||
./gradlew clean test assembleDebug
|
||||
```
|
||||
|
||||
### Sync не работает
|
||||
|
||||
Проверь:
|
||||
1. server URL;
|
||||
2. HTTPS/local HTTP rule;
|
||||
3. login/session;
|
||||
4. network connection;
|
||||
5. cursor/lastPush;
|
||||
6. совместимость BalanceServer protocol.
|
||||
|
||||
### Данные не удаляются на другом устройстве
|
||||
|
||||
Убедись, что вызывается `FinanceDao.deleteTransaction/deleteCategory/deleteBudget`, а не `delete*Direct`.
|
||||
@@ -0,0 +1,107 @@
|
||||
# Функциональная документация
|
||||
|
||||
## Обзор
|
||||
|
||||
Balance Android — локально-first приложение личных финансов.
|
||||
|
||||
Основные функции:
|
||||
- доходы;
|
||||
- расходы;
|
||||
- категории;
|
||||
- бюджеты;
|
||||
- аналитика;
|
||||
- корректировка баланса;
|
||||
- server sync;
|
||||
- тёмная/светлая тема;
|
||||
- несколько валют.
|
||||
|
||||
## Обзор
|
||||
|
||||
Показывает:
|
||||
- общий баланс;
|
||||
- доходы текущего месяца;
|
||||
- расходы текущего месяца;
|
||||
- расходы по категориям;
|
||||
- последние операции.
|
||||
|
||||
## Операции
|
||||
|
||||
Поддерживаются:
|
||||
- добавление;
|
||||
- редактирование;
|
||||
- удаление;
|
||||
- поиск;
|
||||
- фильтрация по income/expense;
|
||||
- дата;
|
||||
- сумма;
|
||||
- категория;
|
||||
- заметка.
|
||||
|
||||
## Корректировка
|
||||
|
||||
Пользователь задаёт целевой фактический баланс.
|
||||
|
||||
Приложение вычисляет:
|
||||
|
||||
```text
|
||||
difference = target - current
|
||||
```
|
||||
|
||||
и создаёт специальную transaction с `isBalanceAdjustment=true`.
|
||||
|
||||
Корректировка меняет общий баланс, но не включается в периодическую аналитику.
|
||||
|
||||
## Аналитика
|
||||
|
||||
Периоды:
|
||||
- месяц;
|
||||
- 3 месяца;
|
||||
- 12 месяцев.
|
||||
|
||||
Показываются:
|
||||
- доходы;
|
||||
- расходы;
|
||||
- результат;
|
||||
- savings rate;
|
||||
- структура расходов по категориям.
|
||||
|
||||
## Бюджеты
|
||||
|
||||
Можно задать месячный лимит для категории.
|
||||
|
||||
Бюджет хранится отдельно и синхронизируется с сервером.
|
||||
|
||||
## Категории
|
||||
|
||||
Можно создавать и редактировать пользовательские категории.
|
||||
|
||||
Поддерживаются:
|
||||
- имя;
|
||||
- тип;
|
||||
- иконка;
|
||||
- emoji;
|
||||
- цвет.
|
||||
|
||||
Есть 50 вариантов иконок, 30 готовых emoji и палитра из 24 цветов согласно текущему UI.
|
||||
|
||||
## Настройки
|
||||
|
||||
- RUB;
|
||||
- EUR;
|
||||
- USD;
|
||||
- SEK;
|
||||
- system/light/dark;
|
||||
- server URL;
|
||||
- регистрация/login/logout;
|
||||
- ручная sync;
|
||||
- reset sync.
|
||||
|
||||
## Offline-first
|
||||
|
||||
Все основные операции выполняются локально через Room. Сервер нужен только для авторизации и межустройственной синхронизации.
|
||||
|
||||
## Background sync
|
||||
|
||||
При наличии аккаунта и server URL WorkManager пытается синхронизировать данные примерно каждые 15 минут при наличии сети.
|
||||
|
||||
Точное время зависит от Android WorkManager и системных ограничений батареи/фоновой работы.
|
||||
@@ -0,0 +1,212 @@
|
||||
# Серверная синхронизация
|
||||
|
||||
Android-клиент использует тот же self-hosted BalanceServer, что и остальные клиенты Balance.
|
||||
|
||||
## API
|
||||
|
||||
POST endpoints:
|
||||
|
||||
```text
|
||||
/v1/auth/register
|
||||
/v1/auth/login
|
||||
/v1/auth/logout
|
||||
/v1/auth/refresh
|
||||
/v1/sync
|
||||
```
|
||||
|
||||
Авторизованный запрос:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <access-token>
|
||||
Content-Type: application/json; charset=utf-8
|
||||
```
|
||||
|
||||
## Session
|
||||
|
||||
`ServerSession` содержит:
|
||||
|
||||
- access token;
|
||||
- refresh token;
|
||||
- expiresAt;
|
||||
- userId;
|
||||
- email.
|
||||
|
||||
`SecureSessionStore` сериализует session JSON и шифрует его AES/GCM.
|
||||
|
||||
AES key создаётся и хранится в Android Keystore.
|
||||
|
||||
При ошибке расшифровки store очищается.
|
||||
|
||||
## Server URL
|
||||
|
||||
`ServerPreferences.normalizeServer` принимает полный URL без:
|
||||
- user info;
|
||||
- query;
|
||||
- fragment.
|
||||
|
||||
Разрешены HTTPS и локальный HTTP.
|
||||
|
||||
HTTP допускается для:
|
||||
- `localhost`
|
||||
- `127.0.0.1`
|
||||
- `10.0.2.2`
|
||||
|
||||
`10.0.2.2` — специальный адрес Android Emulator для host machine.
|
||||
|
||||
## Authentication
|
||||
|
||||
Перед авторизованным запросом клиент проверяет expiry.
|
||||
|
||||
Если осталось менее 60 секунд, выполняется refresh.
|
||||
|
||||
При HTTP 401 клиент один раз refresh-ит session и повторяет исходный запрос.
|
||||
|
||||
Если refresh не удался, session очищается.
|
||||
|
||||
## Sync request
|
||||
|
||||
```json
|
||||
{
|
||||
"cursor": 0,
|
||||
"deviceId": "UUID",
|
||||
"changes": [],
|
||||
"pull": true
|
||||
}
|
||||
```
|
||||
|
||||
Push использует тот же endpoint с `pull=false`.
|
||||
|
||||
## Change envelope
|
||||
|
||||
```json
|
||||
{
|
||||
"entity": "transaction",
|
||||
"id": "UUID",
|
||||
"deleted": false,
|
||||
"updatedAt": "2026-09-08T10:00:00Z",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
|
||||
Entities:
|
||||
|
||||
```text
|
||||
transaction
|
||||
category
|
||||
budget
|
||||
```
|
||||
|
||||
## Transaction payload
|
||||
|
||||
```json
|
||||
{
|
||||
"amount": 1000,
|
||||
"date": "2026-09-08T10:00:00Z",
|
||||
"note": "Кофе",
|
||||
"categoryName": "Кофе",
|
||||
"categoryIcon": "cup.and.saucer.fill",
|
||||
"categoryEmoji": "☕️",
|
||||
"categoryColorName": "brown",
|
||||
"isBalanceAdjustment": false,
|
||||
"kindRawValue": "expense"
|
||||
}
|
||||
```
|
||||
|
||||
## Category payload
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Кофе",
|
||||
"icon": "cup.and.saucer.fill",
|
||||
"emoji": "☕️",
|
||||
"colorName": "brown",
|
||||
"kindRawValue": "expense",
|
||||
"createdAt": "2026-09-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Budget payload
|
||||
|
||||
```json
|
||||
{
|
||||
"categoryName": "Кофе",
|
||||
"categoryIcon": "cup.and.saucer.fill",
|
||||
"categoryEmoji": "☕️",
|
||||
"limit": 5000,
|
||||
"monthStart": "2026-09-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Push
|
||||
|
||||
Изменения выбираются по `updatedAt` между `lastPush` и cutoff.
|
||||
|
||||
Batch size: 100.
|
||||
|
||||
Если JSON batch больше 1.5 MB, он рекурсивно делится.
|
||||
|
||||
## Pull
|
||||
|
||||
Клиент отправляет текущий cursor.
|
||||
|
||||
Сервер возвращает:
|
||||
|
||||
```json
|
||||
{
|
||||
"cursor": 42,
|
||||
"changes": [],
|
||||
"hasMore": false
|
||||
}
|
||||
```
|
||||
|
||||
Если `hasMore=true`, клиент повторяет pull с новым cursor.
|
||||
|
||||
Есть защитные проверки:
|
||||
- cursor не может уменьшиться;
|
||||
- `hasMore=true` должен сопровождаться продвижением cursor.
|
||||
|
||||
## Applying remote changes
|
||||
|
||||
Изменения сортируются по `sequence`.
|
||||
|
||||
Для обычного изменения:
|
||||
|
||||
```text
|
||||
apply remote iff remote.updatedAt > local.updatedAt
|
||||
```
|
||||
|
||||
Для удаления:
|
||||
|
||||
```text
|
||||
delete iff local.updatedAt <= remote.updatedAt
|
||||
```
|
||||
|
||||
После применения соответствующий deletion journal очищается.
|
||||
|
||||
## Scope
|
||||
|
||||
Cursor и lastPush привязаны к:
|
||||
|
||||
```text
|
||||
serverUrl + userId
|
||||
```
|
||||
|
||||
Это позволяет иметь независимое состояние sync для разных серверов/аккаунтов.
|
||||
|
||||
## Reset
|
||||
|
||||
`resetAndSync()` очищает cursor и lastPush текущего scope, затем запускает обычную sync.
|
||||
|
||||
Локальные финансовые данные при этом не удаляются.
|
||||
|
||||
## Background
|
||||
|
||||
`SyncWorker`:
|
||||
- запускается WorkManager;
|
||||
- требует network connection;
|
||||
- пропускает работу без session/server URL;
|
||||
- возвращает `retry`, если sync завершилась ошибкой.
|
||||
|
||||
## Совместимость
|
||||
|
||||
README проекта указывает совместимость Android-клиента с BalanceServer protocol 3.1. При изменении контракта сервера необходимо синхронно обновлять `ServerApi`, `SyncRepository` и документацию.
|
||||
Reference in New Issue
Block a user