Бонусная система со сгоранием бонусов, как нужно делать ?

ksnk

прохожий
Проектируется схема базы данных, в которой юзерам начисляются баллы, и на эти баллы можно какую-то пользу приобрести. У баллов есть время, в течении которого они действуют, после чего сгорают. Для примера - 10-го и 25-го упало по 10 баллов со сроком жизни 1 месяц. 9-го следующего месяца юзер обнаружил у себя 20 баллов, списал 5 баллов (осталось 15), 10- го сгорели баллы от прошлого месяца(10-5), с учетом списанных их стало 10 ...
От системы будет требоваться в любой момент вычислить имеющиеся у клиента баллы. Желательно, иметь возможность отменять операции "сгорания", начисления и списания. Хотя, возможно, это лишнее. В случае ошибки оператора можно и доначислить баллов. Желательно, после нескольких лет работы, не умереть от огромной таблицы, в которой все частичные суммы примерно нулевые... Желательно, иметь возможность вывести график баланса пользователя по дням, за разумный срок... Перед списанием всегда проверяется, что на счету достаточное количество баллов

Можно ли такую схему уложить в одну таблицу базы данных? пока видится так - таблица проводок:
Код:
{id, user, accоunt, created, expired,  description ... }
Начисление баллов - аккаунт положительный, установлен expired. для примера выше на момент 26.01, получитcя вот так
Код:
[
{id:1, user:user, accоunt:10, created:'10.01', expired:'10.02',  description:'начислено 10 баллов' },
{id:2, user:user, accоunt:10, created:'25.01', expired:'25.02',  description:'начислено 10 баллов' },
]
Cписание - account отрицательный, expired не установлен.
Код:
...,{id:3, user:user, accоunt:-5, created:'9.02',  description:'списано 5 баллов' },
10 числа сгорает первые баллы, сгорают автоматически, по ежедневному крону. Для каждой записи с положительным аккаунтом проверяется сумма списаний за все время от created до expired. Если сумма не превосходит начисление - добавляем запись о сгорании, на весь остаток суммы.
Код:
...,{id:4, user:user, accоunt:-5, created:'10.02',  description:'сгорело 5 баллов' },
Таким образом получается, что количество баллов пользователя в определенный день - сумма всех проводок этого пользователя в таблице до этого дня. Считать их достаточно просто, хотя при росте таблицы становится накладно.
С неограниченным ростом таблицы может помочь перенос всех "давних" записей в отдельную таблицу -архив. вместо них остается специальная запись - остаток на "момент переноса".
 

AmdY

Пью пиво
Команда форума
зачем expired, если это вычисляемое поле, почему даты не в формате даты и без года, почему нет флага использован ли бонус?
 
  • Like
Реакции: ksnk

WMix

герр M:)ller
Партнер клуба
Можно ли такую схему уложить в одну таблицу базы данных?
да, это лог,
пришло ушло, сальдо, дата и "id" начисленных/использованных пунктов (использовать начиная с самых старых)..
в таблицу только дописывать не удаляя и не изменяя записи
 

ksnk

прохожий
зачем expired, если это вычисляемое поле, почему даты не в формате даты и без года, почему нет флага использован ли бонус?
expired не вычисляемое. Некоторые баллы не горят. Даты приведены просто так, чтобы был ориентир.
Использован бонус или нет - это про что ? На некоторую, необязательно всю сумму баллов, можно что-то приобрести (использовать?) . У меня строка id:3. Остаток можно бы использовать еще куда-то, пока он не сгорел.

P.S. догнал! Действительно. Скрипту крона, который жжет баллы, нужно перебирать не все протухшие начисления в таблице, а только от той даты, до какой он доработал в последний раз. Эту дату требуется куда-то сохранять. Вероятно, время последнего запуска скрипта
 
Последнее редактирование:

Тугай

Новичок
Одной таблицей похоже, не обойдешься. Если начисления пересекаются по времени, то когда осталось 2 бала, а приход истек по времени, то стало -8 или 2 балла остается от второго прихода ?

Расходовать надо с каждого начисления баллов отдельно, где есть еще остаток, и тогда если не выбрал баллы то этот остаток и сгорит и с ним все промежуточные расходы, которые больше не надо учитывать, хотя формально они были и во время второго прихода.

Нужна еще табличка с "партиями бонусов" или как-то в расходе помечать какой приход расходуется.
 
Последнее редактирование:

WMix

герр M:)ller
Партнер клуба
@Тугай представь склад продуктов питания.
пусть продукт это пользователь, привоз это начисление балов, отгрузка это использование, ну и не забываем про срок хранения.
так вот когда компутеров не было люди справлялись со всем используя обычный складской журнал, который по сути просто табличка

 

Тугай

Новичок
Нет. У него баллы разного вида, одни со сроком годности другие без срока, т.е. это разное молоко. И если молоко сгущённое, то на банке написан срок годности, кроме этой складской карточки, а на баллах он не написан, а надо как-то написать. :)
 

WMix

герр M:)ller
Партнер клуба
от твоей заметки суть изменилась?
нужно 100 литров молока. 50 есть 50 разбодяжили из сухого. абстрактнее думай, на складе по привозу каждой партии будут выдавать новый SKU чтоб не мешать старые и новые.
 

ksnk

прохожий
С вычислением суммы, которую сребуется "сжечь", как заметил Тугай, у меня не все так однозначно. Мягко говоря :)
Сейчас переделываю ...
 

Valick

Новичок
На самом деле бонусная система это гораздо сложнее чем кажется. Бонусные баллы не беруться из-ниоткуда и никуда просто так не деваются. Помимо самих баллов необходимо хранить основание начисления и списания. Ну и калькуляцию самих баллов лучше производить в момент непосредственного начисления или списания и готовый результат хранить в отдельной таблице. Про сгорание - это отдельная история.
 

Тугай

Новичок
Ну смотри два прихода +10, +10 и два расхода -6, -6, остаток 8.
Истекло время одного из приходов и как теперь остаток считать ?
 

Valick

Новичок
Тугай, ну на самом деле если истекло время одного из приходов, то второго расхода уже не будет :) Но направление мыслей правильное, всё сложнее чем кажется.
Упс, не прав. Зависит от того какой из остатков сгорел. И это только 2 бонуса, а если их 200 и половина из них с разным сроком истечения, то картина просто жуть.
Сгорание - это то же списание, только основание - это не покупка чего либо, а именно сгорание.
+10 начисление (основание: при покупке товара , срок сгорания такой-то)
баланс +10
- 6 списание (основание: оплата бонусами)
баланс +4
+10 начисление (основание: за установку приложения ,не сгорает)
баланс +14
-6 списание (основание: оплата бонусами)
баланс +8
И тут настало время баллам гореть...
Калькулируем суму списания с момента начисления 10 сгораемых баллов, если она больше чем сами баллы, то сгорания не происходит
6 + 6 > 10
- 0 сгорание (основание: начисление 10 баллов при покупке)
баланс +8

P.S. а еще бывает возврат покупки...
 
Последнее редактирование:

ksnk

прохожий
Ну смотри два прихода +10, +10 и два расхода -6, -6, остаток 8.
Истекло время одного из приходов и как теперь остаток считать ?
Тут как раз все несложно. Нужно жечь все сразу, списком. А именно, вычислять сумму "сгорающих" начислений (20), сумму расходов (12) и оформить акт на сжигание 8 баллов. Так будет работать ?
 

WMix

герр M:)ller
Партнер клуба
Ну смотри два прихода +10, +10 и два расхода -6, -6, остаток 8.
Код:
date article sku in out bilance
01.01   0001 123 10   -      10
02.01   0001 456 10   -      10
05.01   0001 123  -   6       4
10.01   0001 123  -   4       0
10.01   0001 456  -   2       8
Истекло время одного из приходов и как теперь остаток считать ?
группировка по article + sku, записи с максимальной датой
Код:
date article sku in out bilance
10.01   0001 123  -   4       0
10.01   0001 456  -   2       8
теперь сумма bilance это колво товара на складе
 
Последнее редактирование:

ksnk

прохожий
Решение с "интервальным" выжиганием сгоревших бонусов (сжигать все бонусы, чье время жизни находится между теперешним временем запуска скрипта и прошлым) мне не нравится. Как-то ненадежно смотрится сохранение даты запуска, а вдруг что-то случится и кое где бонусы не сгорят никогда... Да и хочется остаться в рамках одной таблицы... Так что предлагается в обязательном порядке, к каждому сгораемому начислению приписывать проводку сгорания. Даже и с 0 суммой. К каждой такой проводке требуется добавить поле (link)- к какой записи относится. Еще придется добавить поле reason с признаками проводок - начисление/списание/сгорание

Код:
{id, user, reason, accоunt, created, expired, link,  description... }
Вычисление несгоревших пока еще начислений - проверка всех начислений

найти {user=user, reason=начисление, expired не пустой, expired<now()} x left join проводка y(y.id=x.link) где y.id is null



С вычислением суммы, которую сребуется "сжечь", как заметил Тугай, у меня было не все так однозначно. Сейчас будет так



Например в день 1.03 начислено 4 раза по 10 баллов на месяц

Код:
...

// для запутывания истории, введем ранее насчисленные баллы

{id:1, user:user, reason:начисление, created:'01.03.2022',account:10, description:'подарок новому пользователю, несгораемая сумма'},

{id:50, user:user, reason:начисление, created:'20.02.2022',expired:'20.03.2022',account:20, description:'Периодический бонус'},

// теперь все 4 одновременных начисления

{id:100, user:user, reason:начисление, created:'01.03.2022',expired:'01.04.2022',account:10, description:'.Периодический бонус.'},

{id:101, user:user, reason:начисление, created:'01.03.2022',expired:'01.04.2022',account:10, description:'.Периодический бонус.'},

{id:102, user:user, reason:начисление, created:'01.03.2022',expired:'01.04.2022',account:10, description:'.Периодический бонус.'},

{id:103, user:user, reason:начисление, created:'01.03.2022',expired:'01.04.2022',account:10, description:'.Периодический бонус.'},

// 20.03.2022 у нас сгорает ранее начисленная сумма

{id:50, user:user, reason:сгорание, link: 50, created:'20.03.2022',account:-20, description:'...'},

// 30.03 юзер потратил 38 баллов, всего было 40 стало 2

{id:104, user:user, reason:сгорание, created:'20.03.2022',account:-38, description:'...'},
01.04.2022 вычисляем сумму всех сгорающих баллов (40) и сумму использований (38). Получаем 2 балла для сжигания. ДЛя первой в списке делаем акт, для остальных акт с 0 суммой списания. Для экономии умственных расходов,. Конечно, по уму надо бы распихать сгорания в соответствующие приходы, но пока и так должно работать

Код:
...
{id:105, user:user, reason:сгорание, created:'01.04.2022',account:-2, link: 100, description:'...'},
{id:106, user:user, reason:сгорание, created:'01.04.2022',account:0, link: 101, description:'...'},
{id:107, user:user, reason:сгорание, created:'01.04.2022',account:0, link: 102, description:'...'},
{id:108, user:user, reason:сгорание, created:'01.04.2022',account:0, link: 103, description:'...'},
 
Последнее редактирование:

ksnk

прохожий
WMix, вопрос по примеру - структура таблицы с отдельными приходными-расходными столбцами она "исторически сложилась", или имеет какой-то практически важный смысл ? Это я про то, удастся ли обойтись одним приходным столбцом, с отличием в знаке ?
 

WMix

герр M:)ller
Партнер клуба
WMix, вопрос по примеру - структура таблицы с отдельными приходными-расходными столбцами она "исторически сложилась", или имеет какой-то практически важный смысл ? Это я про то, удастся ли обойтись одним приходным столбцом, с отличием в знаке ?
и "исторически сложилась", и имеет важный смысл
это единственный способ, который позволит ответить на любые вопросы связанные с оборотом и состоянием склада в любой момент времени
дополнительные таблички, для облегчения вычислений можно добавить, но без этого ядра, ты всегда будешь терять информацию

in/out можно обьеденить конечно, но запросы иногда не красивые будут, where qty < 0
 
  • Like
Реакции: ksnk

Тугай

Новичок
@WMix, я о том же не -6, а -4 и -2 с указанием прихода. SKU, это и есть вторая таблица, в которой написано что за чудо бонусы.
 

WMix

герр M:)ller
Партнер клуба
ты про описание, я про подсчет, сейчас не о заказных/отгрузочных листах речь, а о доказательном подсчете балов
 

Тугай

Новичок
@ksnk, во всех сгораниях должен быть link, просто жечь нельзя, можно только с начислений.

И тебе нужен link в приходе. сам на себя тоже норм. У WMix хороший, пример, ты просто суммируешь и группируешь по link (там это SKU) и с не нулевых нужно расходовать.
 
  • Like
Реакции: ksnk
Сверху