← Копійка

Перехід з іншого застосунку (з допомогою ШІ)

Копійка імпортує лише одне: власний формат резервної копії, один файл JSON. Імпорту з експортів інших застосунків навмисно немає — кожен застосунок експортує щось своє, і конвертер, правильний для одного експорту, хибний для наступного. Натомість працює ось що: експортуйте дані зі старого застосунку, передайте цей файл і запит нижче ШІ-асистентові (Claude, ChatGPT, локальній моделі — будь-чому, що вміє прочитати файл і написати JSON) та імпортуйте результат.

Усе займає кілька хвилин. Ваші дані потрапляють туди, куди ви їх надсилаєте, і більше нікуди; якщо для вас це важливо, скористайтеся локальною моделлю або попросіть асистента написати скрипт і запустіть його самі.

Кроки

  1. Експортуйте дані зі старого застосунку. Найкраще CSV; JSON чи XLSX теж підійдуть. Вам потрібні операції з датою, сумою, валютою, рахунком, категорією і (якщо є) нотатками, тегами й переказами.
  2. Відкрийте чат із ШІ-асистентом, прикріпіть експорт і вставте запит із наступного розділу. Прочитайте його запитання — зазвичай йому треба знати вашу базову валюту і те, як у файлі позначено перекази.
  3. Збережіть відповідь як kopiyka-import.json (асистент може запропонувати файл; якщо він виводить JSON текстом, скопіюйте все між зовнішніми фігурними дужками).
  4. Перенесіть файл на телефон — AirDrop, iCloud Drive, «Файли».
  5. У Копійці: Налаштування → Керування даними → Імпорт → Замінити все файлом (на щойно встановленому застосунку) або Обʼєднати (щоб додати до того, що вже є). Перед заміною застосунок записує страхувальну резервну копію і повідомляє, де вона.
  6. Перегляньте кілька місяців в «Операціях» і залишки рахунків у «Налаштування → Рахунки». Якщо залишок не збігається, в експорті не було початкового залишку: задайте opening_balance_minor для цього рахунку в JSON (або відредагуйте рахунок у застосунку), щоб сьогоднішній залишок збігався з банківським.

Запит

Скопіюйте весь цей блок. Прикріпіть експорт у тому самому повідомленні.

Я переношу історію своїх особистих фінансів у застосунок під назвою Kopiyka. Він імпортує один
          формат файлу, описаний нижче. Будь ласка, перетвори прикріплений експорт у цей формат.

          Перш ніж щось писати, запитай мене (одним повідомленням) про все, чого не можеш вивести з файлу:
          мою базову валюту, як у експорті позначено перекази між моїми рахунками, чи є категорії вкладеними
          (папка › категорія), і який часовий пояс використовувати, якщо дати не мають зсуву.

          Потім створи ОДИН документ JSON, який точно відповідає специфікації. Правила:

          - Збережи кожну операцію. Не підсумовуй, не вибирай частину і не відкидай рядків. Якщо рядок не
            вдається перетворити, перелічи його в кінці відповіді, а не пропускай мовчки.
          - Гроші — цілі числа в мінорних одиницях валюти *рахунку* (центи, гроші, копійки):
            12.34 EUR → 1234. Витрати відʼємні, доходи додатні.
          - Кожному рядку потрібен `id`: використовуй новий UUID v4 для кожного рядка й той самий id усюди,
            де на цей рядок є посилання (account_id, category_id, parent_id, tag_ids). Ніколи не виводь id з назви.
          - Категорії: один рядок на категорію. Папка — це просто категорія, у якої `parent_id` дорівнює null
            і на яку посилаються інші категорії. Операції мають посилатися на найглибшу категорію, ніколи не
            на папку. Якщо в експорті плаский список категорій, зроби їх усі верхнього рівня.
          - Теги: один рядок на кожен окремий тег; `tag_ids` операції — це *рядок* із JSON-масивом id тегів,
            наприклад "[\"…\",\"…\"]" (порожній список — "[]").
          - Перекази: в експорті зазвичай або один рядок із рахунком-джерелом і рахунком-отримувачем, або два
            рядки (гроші вийшли, гроші прийшли). Створи дві операції — відʼємну на рахунку-джерелі й додатну на
            рахунку-отримувачі, кожну у валюті свого рахунку, — зі спільним новим `transfer_id` і
            `category_id` null. Якщо дві частини в різних валютах, задай `entered_amount_minor`,
            `entered_currency` і `exchange_rate` для частини отримувача, як описано нижче.
          - Дати — ISO 8601 зі зсувом: "2026-03-17T16:28:39+01:00". Якщо в експорті є лише день, використовуй
            12:00:00 у часовому поясі, який я назвав. `updated_at` — епоха в мілісекундах: використовуй час
            експорту (зараз) для кожного рядка. `deleted` завжди 0.
          - Регулярні правила, бюджети, борги й аналітика необовʼязкові. Додавай регулярні правила, лише якщо
            в експорті вони явно є (підписки з періодом); інакше залиш масиви порожніми, і я налаштую їх у застосунку.
          - `settings.base_currency` — моя базова валюта. Залиш `rates` порожнім масивом, якщо в експорті немає
            курсів валют.
          - Виведи в цьому блоці коду JSON і нічого більше. Форматуй з відступом в 1 пробіл.

          === СПЕЦИФІКАЦІЯ: JSON резервної копії Kopiyka ===

          {
           "format": "kopiyka-backup",          // саме так
           "version": 1,
           "exported_at": "2026-09-12T10:00:00.000Z",
           "settings": { "base_currency": "EUR" },
           "accounts": [ Account ],
           "categories": [ Category ],
           "tags": [ Tag ],
           "transactions": [ Transaction ],
           "recurring_rules": [ RecurringRule ],  // може бути []
           "budgets": [],                          // може бути []
           "insights": [],                         // може бути []
           "debts": [],                            // може бути []
           "rates": []                             // може бути []
          }

          Кожен рядок у кожній таблиці має ці три поля:
            id: string (UUID v4)   updated_at: integer (епоха в мс)   deleted: 0

          Account {
            name: string
            currency: string                 // ISO 4217, "EUR", "USD", "PLN", "UAH" …
            type: "cash" | "bank" | "card" | "investment" | "savings" | "other"
            group_name: string               // заголовок, під яким показано рахунки; "Personal", якщо в експорті його немає
            icon: string | null              // назва SF Symbol, напр. "creditcard.fill", "banknote.fill", "building.columns.fill"; null теж підходить
            color: string | null             // "#RRGGBB" або null
            sort: integer                    // порядок показу, 0, 1, 2 …
            archived: 0 | 1
            include_in_net_worth: 0 | 1      // 1, якщо це не кредитний рахунок чи позика, які ви виключаєте
            opening_balance_minor: integer   // залишок до першої операції у файлі; 0, якщо невідомо
          }

          Category {
            name: string
            parent_id: string | null         // id категорії-папки або null для категорії верхнього рівня
            icon: string | null              // назва SF Symbol або null
            color: string | null             // "#RRGGBB" або null
            sort: integer
            kind: "expense" | "income"
            description: string | null       // необовʼязкові слова, що описують, що сюди належить
          }

          Tag {
            name: string
            color: string | null
            category_ids: string             // рядок із JSON-масивом id категорій, для яких призначено тег; "[]" = будь-яких
          }

          Transaction {
            account_id: string
            date: string                     // ISO 8601 зі зсувом
            amount_minor: integer            // мінорні одиниці валюти рахунку; відʼємне = витрата
            category_id: string | null       // найглибша категорія; null для переказів чи рядків без категорії
            payee: string | null             // магазин чи контрагент, якщо експорт відокремлює його від нотатки
            notes: string | null             // нотатка / опис
            tag_ids: string                  // рядок із JSON-масивом id тегів, "[]", якщо тегів немає
            pending: 0 | 1                   // 0 для історії
            transfer_id: string | null       // той самий новий UUID на обох частинах переказу, інакше null
            entered_amount_minor: integer | null   // лише коли платіж був в іншій валюті: сума, як її списано
            entered_currency: string | null        // … і її валюта
            exchange_rate: number | null           // одиниць введеної валюти за 1 одиницю валюти рахунку (entered_amount / amount)
            recurring_id: string | null      // id регулярного правила, що створило цей рядок, інакше null
            lat: number | null               // де це записано, якщо відомо
            lon: number | null
            place: string | null             // назва цього місця для людей
            photo: null                      // фото чеків так імпортувати не можна
            source: null
          }

          RecurringRule {
            account_id: string
            amount_minor: integer            // відʼємне = витрата
            category_id: string | null
            payee: string | null
            notes: string | null
            tag_ids: string                  // "[]", якщо тегів немає
            frequency: "daily" | "weekly" | "monthly" | "yearly"
            interval: integer                // 1 = кожен період, 2 = кожен другий період …
            start_date: string               // "YYYY-MM-DD"
            end_date: string | null
            next_date: string                // "YYYY-MM-DD", перше ще не записане повторення (після сьогодні)
            notify: 0 | 1
            notify_days_before: integer
            auto_post: 0 | 1                 // 1 = записується сам у свій день; 0 = спершу питає
            active: 0 | 1
            time_of_day: string              // "HH:MM"
          }

          === ПРИКЛАД (два рахунки, папка з однією категорією, тег, одна витрата й один переказ) ===

          {
           "format": "kopiyka-backup",
           "version": 1,
           "exported_at": "2026-09-12T10:00:00.000Z",
           "settings": { "base_currency": "EUR" },
           "accounts": [
            { "id": "5f1a2b3c-0001-4a00-8000-000000000001", "updated_at": 1757671200000, "deleted": 0,
              "name": "Main", "currency": "EUR", "type": "bank", "group_name": "Personal", "icon": "building.columns.fill",
              "color": "#2B2B2E", "sort": 0, "archived": 0, "include_in_net_worth": 1, "opening_balance_minor": 250000 },
            { "id": "5f1a2b3c-0001-4a00-8000-000000000002", "updated_at": 1757671200000, "deleted": 0,
              "name": "Cash", "currency": "EUR", "type": "cash", "group_name": "Personal", "icon": "banknote.fill",
              "color": null, "sort": 1, "archived": 0, "include_in_net_worth": 1, "opening_balance_minor": 0 }
           ],
           "categories": [
            { "id": "5f1a2b3c-0002-4a00-8000-000000000001", "updated_at": 1757671200000, "deleted": 0,
              "name": "Food", "parent_id": null, "icon": "cart.fill", "color": "#FF9F0A", "sort": 0, "kind": "expense", "description": null },
            { "id": "5f1a2b3c-0002-4a00-8000-000000000002", "updated_at": 1757671200000, "deleted": 0,
              "name": "Groceries", "parent_id": "5f1a2b3c-0002-4a00-8000-000000000001", "icon": "cart.fill", "color": "#FF9F0A",
              "sort": 1, "kind": "expense", "description": "supermarket, market, bakery" }
           ],
           "tags": [
            { "id": "5f1a2b3c-0003-4a00-8000-000000000001", "updated_at": 1757671200000, "deleted": 0,
              "name": "Weekend", "color": "#30D158", "category_ids": "[]" }
           ],
           "transactions": [
            { "id": "5f1a2b3c-0004-4a00-8000-000000000001", "updated_at": 1757671200000, "deleted": 0,
              "account_id": "5f1a2b3c-0001-4a00-8000-000000000001", "date": "2026-09-06T18:42:00+01:00", "amount_minor": -2350,
              "category_id": "5f1a2b3c-0002-4a00-8000-000000000002", "payee": "Pingo Doce", "notes": null,
              "tag_ids": "[\"5f1a2b3c-0003-4a00-8000-000000000001\"]", "pending": 0, "transfer_id": null,
              "entered_amount_minor": null, "entered_currency": null, "exchange_rate": null, "recurring_id": null,
              "lat": null, "lon": null, "place": null, "photo": null, "source": null },
            { "id": "5f1a2b3c-0004-4a00-8000-000000000002", "updated_at": 1757671200000, "deleted": 0,
              "account_id": "5f1a2b3c-0001-4a00-8000-000000000001", "date": "2026-09-07T09:00:00+01:00", "amount_minor": -10000,
              "category_id": null, "payee": null, "notes": "ATM", "tag_ids": "[]", "pending": 0,
              "transfer_id": "5f1a2b3c-0005-4a00-8000-000000000001",
              "entered_amount_minor": null, "entered_currency": null, "exchange_rate": null, "recurring_id": null,
              "lat": null, "lon": null, "place": null, "photo": null, "source": null },
            { "id": "5f1a2b3c-0004-4a00-8000-000000000003", "updated_at": 1757671200000, "deleted": 0,
              "account_id": "5f1a2b3c-0001-4a00-8000-000000000002", "date": "2026-09-07T09:00:00+01:00", "amount_minor": 10000,
              "category_id": null, "payee": null, "notes": "ATM", "tag_ids": "[]", "pending": 0,
              "transfer_id": "5f1a2b3c-0005-4a00-8000-000000000001",
              "entered_amount_minor": null, "entered_currency": null, "exchange_rate": null, "recurring_id": null,
              "lat": null, "lon": null, "place": null, "photo": null, "source": null }
           ],
           "recurring_rules": [],
           "budgets": [],
           "insights": [],
           "debts": [],
           "rates": []
          }
          

Перевірка результату перед імпортом

  • Файл має починатися з "format": "kopiyka-backup" і містити масив accounts, інакше застосунок відхилить його з помилкою «Not a Kopiyka backup» («Це не резервна копія Копійки»).
  • Кожен account_id / category_id / parent_id / id тегу має існувати у файлі. Під час імпорту застосунок цього не перевіряє: операція, що посилається на невідомий рахунок, ніде не зʼявиться і не врахується в жодному залишку.
  • Суми мають бути цілими числами. 12.34 — неправильно; 1234 — правильно.
  • Переказ — це два рядки з однаковим transfer_id і протилежними знаками.
  • Якщо ви запускали конвертацію двічі, імпортуйте лише останній файл: id щоразу генеруються заново, тож імпорт обох подвоїть усе. (Якщо ви справді хочете повторно імпортувати виправлену версію, використовуйте Замінити все — обʼєднання ніколи нічого не видаляє, тож старі рядки лишилися б.)

Повний набір правил, яких дотримується імпорт, — id визначає запис, обʼєднання ніколи не видаляє, заміна видаляє, курси валют ніколи не відкидаються, — описано в DATA.md (англійською) у корені репозиторію. Повний приклад резервної копії з бюджетами, правилами, подорожами й боргами — демонстраційний набір даних, на якому зроблено знімки екрана: apps/mobile/screenshots/demo/kopiyka-demo.json.