Как правильно добавить операцию переноса средств между счетами пользователя (финансовый калькулятор на Yii2)?
Всем привет.
Пишу что-то вроде финансового калькулятора на Yii2 для себя. Уже написал бОльшую часть функционала, но столкнулся с проблемой при добавлении новой фичи.
Итак, по задумке пользователь может добавлять свои финансовые операции и просматривать их. Для хранения операций в моей БД есть таблица 'operations':
id | user_id | category_id | source_id | sum | name | type | date
Расшифровка:
- category_id - id категории расходов/доходов пользователя (продукты, транспорт и т.д), внешний ключ на таблицу 'category', где есть описание всех категорий,
- source_id - id платежного средства пользователя (банковская карта, кошелек с наличными и т.д.), внешний ключ на таблицу 'sources', где есть описание всех средств,
- type - тип операции, где 1 - это расход, 2 - доход.
Когда пользователь переходит по адресу operation/create, он может создать новую операцию, выбрав ее тип (расход или доход), категорию, платежное средство, сумму, добавить описание и т.д.
Когда пользователь переходит по адресу operation/view, он может просматривать все свои операции, сгруппированные по датам. При щелчке на какую-либо операцию происходит переход к ее редактированию (operation/update).
Всё это работало замечательно, пока я не решил добавить новый тип операций - "перенос средств пользователя между счетами" (когда человек, например, решил снять деньги с карты и положить их в свой кошелек с наличными).
Сначала я решил реализовать это через сценарии Yii-модели (Active Record) Operations. То есть когда на странице operation/create пользователь выбирает пункт "Перенос средств между счетами", аяксом загружается форма, в которой есть поля "Источник 1", "Источник 2" и "Сумма".
При отправке этой формы я думал создавать в одной транзакции 2 операции (списание с одного счета + зачисление на другой счет). При создании этих двух операций проблем нет. Проблемы возникают на странице просмотра операций operation/view, где по логике будут отображены обе. И если пользователь решит обновить одну из них, мне нужно будет найти вторую и обновить ее. Мне показалось, что это не лучший способ.
Тогда я подумал, что, может быть, следует создать новую таблицу - 'transfers', где будут храниться только операции перевода с одного счета на другой. Но тогда возникает другая проблема - когда пользователь будет переходить на страницу просмотра операций operation/view, мне нужно будет отображать данные не только из таблицы 'operations', но и из таблицы 'transfers', сгруппированные по датам. Мне кажется, что такой подход тоже противоречит нормальной архитектуре приложения.
Поделитесь, пожалуйста, своим опытом - как лучше добавить функционал переноса средств между счетами?
Пользователь вообще не должен редактировать такие операции. Если это просто учет каких-то финансов - то проблем тут критичных нет. Но если это работа с реальными средствами, то сразу же большая проблема. Представьте себя при взаимодействии с банкоматом. Приходите и редактируете себе баланс с 1 рубля на 1000 рублей)) Ну это будет странно.
Решение:
1. Убрать возможность редактирования вообще. Только + (пополнение) и - (снятие) ну и максимум удалить операцию, но тут надо только админам))
2. То же самое что и первое + добавить новый тип "перевод" и новую колонку transfer_id
При переводе мы вписываем в тип "перевод", а в колонку transfer_id добавляем id платежного средства пользователя (банковская карта, кошелек с наличными и т.д.), внешний ключ на таблицу 'sources', где есть описание всех средств
Всё не совсем так.
По задумке, после регистрации в приложении пользователь заводит себе несколько категорий (табл. category), на которые в будущем он будет записывать расходы и доходы. Как я и писал, это могут быть "Продукты", "Транспорт", "Медицина", "Спорт", "Зарплата", "Долг" и т.д.
Также, пользователь заводит свои счета (табл. sources), с которых он будет оплачивать что-то или получать на них средства. Пользователь сам заводит начальный баланс на этих счетах.
И категорию, и счет можно добавить в любое время.
Дальше, он будет с какой-то периодичностью добавлять операции:
- купил продукты на 100 рублей с карты XXXX вчера,
- оплатил проезд на 200 рублей наличными сегодня,
- получал зарплату 1000 руб. на карту ХХХХ сегодня.
И т.д.
Суть приложения в том, что пользователь может посмотреть все свои расходы и приходы за любой период, строить отчеты, графики и т.д.
И это всё нормально работает.
Теперь я решил ввести фичу перекидывания средств между счетами пользователя - например, пользователь завел 2 банковских карты, и впоследствии решил перекинуть часть денег с одной на другую. Или снять деньги с одной и положить в кошелек, а потом потратить. И эти операции в отчетах пользователь тоже должен видеть.
А возможность редактирования нужна, например, если при записи операции он по ошибке указал неверную сумму, выбрал не тот счет и т.д.
1. Добавить новый тип "перевод между счетами"
2. Добавить новую колонку transfer_id (ID счёта в базе)
Тогда получаем:
1. ID -1 Счет карты Visa 1000 руб.
2. ID -2 Яндекс кошелёк 0 руб.
Переводим с Карты на кошелёк 1000 руб. Указываем тип "перевод между счетами" в колонку "transfer_id" помещаем на счет, который осуществлен перевод В данном случае transfer_id = 1.
Далее при каждом редактировании, отслеживаем эти данные кошелька. Если важна функция жёсткой привязки, то вместо ID типа счёта помещаем туда ID записи. При удалении этой операции лезем в базу и ищем все записи в transfer_id == ID. Если нашли - удаляем. При изменении - изменяем данные.
Дмитрий, можно и добавить два transfer_type, transfer_id
В transfer_type указывать Яндекс кошелёк, в transfer_id указывать запись транзакции. но при вашем подходе это особого смысла не имеет. Так как таблица одна работает
Максим, а до этого записать какое-нибудь стандартное значение в таблицу 'category', чтобы потом писать его в обязательное поле 'category_id' таблицы 'operations' при добавлении операций переноса?
Максим, при создании новой операции на странице operation/create пользователь выбирает из списка категорию, в которую будет записана эта операция. Категории он создает, когда регистрируется в приложении. Поля таблицы 'category' - id, name, user_i, type.
Как я и писал в вопросе, вот структура таблицы 'operation':
id | user_id | category_id | source_id | sum | name | type | date
Соответственно, после сохранения операции в category_id записывается id той категории, которую выбрал пользователь.
В случае же создания операции типа "перевод между счетами" пользователь не будет выбирать категорию, он просто выберет 2 счета и сумму. Но чтобы записать эту операцию в таблицу operations нужно всё равно записать какое-то значение в поле 'category_id', которая является внешний ключом к таблице 'category'.
Возможно, стоит создать какую-то стандартную категорию для перевода средств между счетами, которую сразу присваивать при создании нового пользователя?
Дмитрий, нет. Если я все правильно понял как вы описали, то категория будет пустая. Какую вы можете сделать категорию, если у вас это просто перевод денег с одного счета на другой? Конечно, можно придумать какую-то категорию типа «корректировка», но зачем она нужна? Предствьте что вместо категории какой-то товар, а не категория. И вы говорите, что при переносе средств из Карты на кошелёк вам нужно создать какой-то дефолтный товар))
Нет. Не нужно. У вас просто выводится тип операции без категории.
Вы продумайте немного как вы видите это в жизни и будет проще.
По правильному я бы мыслил так:
1. Есть таблица счетов
2. Есть таблица операций по счёту.
Человек заводит два новых счёта «карта» и «яндекс кошелёк». Изначально там нулевой баланс.
Далее человек выбирает счёт карта и заносит в него операцию «внесение наличных». Никакая категория не указывается. У нас на балансе 1000 рублей.
Затем человек расходует эти деньги на хлеб. Создаёт операцию в карте с категорией «продукты» с типом «расход» на сумму 100 руб. Название «хлеб».
На балансе 900 руб.
Затем он решил перевести 900 рублей на кошелёк. Переходит в счёт карта и нажимает перевести между счётами. Выбирает счёт текущий (или уже выбран программно) и куда перевести. Указывает сумму перевода 900 руб.
Далее вносим две записи:
1. Списание средств с баланса Карты
2. Зачисление средств на баланс счета.
Никакие категории не указываем.
Если мы переносим операцию «хлеб» из одного счета в другой тогда просто переносим данные с той же категорией.
Вроде просто. Не знаю что не понятно)
Надо было лучше дизайн таблиц выложить и описать. Сложно столько текста анализировать и рисовать в голове структуру)
Максим, большое спасибо за такое глубокое погружение в вопрос.
Насчет категорий. По моей задумке "категории" - это обязательный инструмент. В частности для того, чтобы пользователь мог потом построить отчеты движений средств по каждой категории за определенный период.
Поэтому при создании операции поле "категория" - это обязательное поле.
Прикладываю структуру таблиц.
По ней видно, что у каждой операции поле 'category_id' - это внешний ключ к таблице 'category', поэтому он должен быть обязательным.
Дмитрий, теперь понял. Вам не надо учитывать вообще операции перевода со счета на счёт! Это ни к чему. У вас простой Учет средств, а не денежная система пользователя! Соотвественно изменить баланс счета вы можете даже вручную зайти в счёт и поменять остаток (колонка total). Так же это можно сделать программно! Переводим со счета на счёт. На одном счете уменьшаем столбец на эту сумму, на другом увеличиваем. Не надо никаких операций. Операции у вас не для этого.
Если у вас iPhone можете посмотреть как сделано это в приложении деньги плюс плюс в app stor Деньги Плюс Плюс — Vladimir Shutyuk
Максим, большое спасибо, посмотрю.
Просто изначально хотел, чтобы пользователь сам делал перевод средств между счетами через операции и видел потом эти операции в отчетах.