Files
Balance/docs/DATA_MODEL.md
wt da9e17f45f
Android CI / Build and Test (push) Failing after 13m25s
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s
added AGENTS.md and docs in root dir
2026-09-08 13:36:43 +07:00

7.5 KiB
Raw Permalink Blame History

Сквозная модель данных

Три платформы реализуют одну и ту же доменную модель независимо, на своём стеке хранения. Этот документ сопоставляет поля между ними. Полные описания — Balance/docs/DATA_MODEL.md, BalanceAndroid/docs/DATA_MODEL.md, BalanceServer/docs/DATA_MODEL.md.

Транзакция

Логическое поле Apple (FinanceTransaction) Android (TransactionEntity) Сервер
Идентификатор id: UUID id: String (UUID) stable ID
Сумма amount: Double amount: Double amount
Дата операции date: Date date: Long (epoch millis) date
Заметка note: String note: String note
Имя категории (snapshot) categoryName: String categoryName: String category presentation data
Иконка категории categoryIcon: String (SF Symbol) categoryIcon: String —
Emoji категории categoryEmoji: String categoryEmoji: String —
Цвет категории categoryColorName: String categoryColorName: String —
Корректировка баланса isBalanceAdjustment: Bool isBalanceAdjustment: Boolean balance-adjustment flag
Тип операции kindRawValue: String (income/expense) kindRawValue: String transaction kind
Версия/время изменения syncUpdatedAt: Date updatedAt: Long updatedAt
Владелец сессия аккаунта сессия аккаунта принадлежность пользователю

Категория хранится snapshot'ом, а не ссылкой, на всех платформах: это позволяет не терять отображение старой операции при удалении категории и синхронизировать запись без relationship. Из-за этого переименование пользовательской категории должно явно обновлять уже существующие транзакции и бюджеты — и Apple-, и Android-клиент это делают.

Пользовательская категория

Логическое поле Apple (CustomCategory) Android (categories) Сервер
Идентификатор id: UUID id stable ID
Имя name: String name name
Иконка icon: String icon icon/emoji
Emoji emoji: String emoji —
Цвет colorName: String colorName color
Тип kindRawValue: String kindRawValue operation kind
Создание createdAt: Date createdAt creation metadata
Версия syncUpdatedAt: Date updatedAt update metadata

Имя пользовательской категории уникально без учёта регистра/диакритики относительно системных и других пользовательских категорий (проверяется на клиентах). Встроенные системные категории (Продукты, Транспорт, Зарплата и т.д.) не персистятся как отдельные записи ни на одной платформе — они закодированы в клиентском коде.

Месячный бюджет

Логическое поле Apple (MonthlyBudget) Android (budgets) Сервер
Идентификатор id: UUID id stable ID
Имя категории categoryName: String categoryName category information
Иконка/emoji категории categoryIcon, categoryEmoji categoryIcon, categoryEmoji —
Лимит limit: Double limitAmount: Double monthly limit
Начало месяца monthStart: Date monthStart: Long month start
Версия syncUpdatedAt: Date updatedAt: Long updatedAt

Бюджет идентифицируется собственным UUID, но связывается с категорией по сохранённому имени (categoryName), а не по ссылке.

Tombstone / deletion journal

Логическое поле Apple (SyncTombstone) Android (deletion_journal) Сервер (change record)
Идентификатор записи id: UUID id = entity:recordId record ID
Тип сущности entity: String entity entity
ID удалённой записи recordID: String recordId id
Время updatedAt: Date updatedAt: Long updatedAt
Флаг удаления подразумевается (deleted: true в payload) подразумевается deleted: true

Поддерживаемые значения entity одинаковы на всех платформах: transaction, category, budget.

Финансовые инварианты (одинаковы на Apple и Android)

  1. amount не может быть отрицательным через обычный UI.
  2. Общий баланс = sum(income.amount) − sum(expense.amount); записи с isBalanceAdjustment == true учитываются в этой сумме.
  3. Периодическая аналитика исключает balance adjustments и записи вне выбранного периода.
  4. Расходы группируются по categoryName (не по ID категории).
  5. syncUpdatedAt/updatedAt обязано меняться при каждом изменении синхронизируемой сущности.
  6. Удаление синхронизируемой сущности обязано создать tombstone — прямой DELETE/delete() без tombstone запрещён.

Источники правды по расчётам: Balance/Balance/Utilities/FinanceCalculations.swift (Apple) и BalanceAndroid/.../data/FinanceMath.kt (Android). При изменении правил на одной платформе перенеси изменение на другую.

Инварианты на стороне сервера

  1. Record ID стабилен между синхронизациями.
  2. Запись принадлежит ровно одному пользователю; клиент не может читать/изменять чужие записи.
  3. sequence (серверный порядок изменений) не должен уменьшаться.
  4. Повторная доставка одного и того же изменения не должна повреждать данные (идемпотентность).
  5. Удаление обязано быть видимым клиенту через следующий pull.
  6. Все timestamps хранятся в UTC.
  7. Enum/строковые значения (kindRawValue и т.п.) совместимы между iOS и Android — не переименовывать без периода совместимости.