1
0
Innocenty Enikeev 8 лет назад
Родитель
Сommit
b60558a63f
1 измененных файлов с 151 добавлено и 151 удалено
  1. 151 151
      plugins/saver.lisp

+ 151 - 151
plugins/saver.lisp

@@ -3,9 +3,7 @@
 (defsetting *saver-default-timezone* -3 "Default timezone for *saver-notify-hour* calculation. GMT+3")
 (defsetting *saver-default-timezone* -3 "Default timezone for *saver-notify-hour* calculation. GMT+3")
 (defsetting *saver-notify-hour* 11 "Notify with upcoming payments and saves at this time")
 (defsetting *saver-notify-hour* 11 "Notify with upcoming payments and saves at this time")
 
 
-(defstruct saver/payment name amount schedule started)
-
-(defun saver/parse-schedule (schedule)
+(defun %saver/parse-schedule (schedule)
   (labels ((parse (text &optional def-min def-max)
   (labels ((parse (text &optional def-min def-max)
              (let ((dash-pos (position #\- text))
              (let ((dash-pos (position #\- text))
                    (star-pos (position #\* text))
                    (star-pos (position #\* text))
@@ -24,7 +22,7 @@
             (if month-sched (parse month-sched 1 12) (list nil 1 12))
             (if month-sched (parse month-sched 1 12) (list nil 1 12))
             (if year-sched (parse year-sched 1900) (list nil 1900))))))
             (if year-sched (parse year-sched 1900) (list nil 1900))))))
 
 
-(defun saver/get-next-time (schedule universal-time &optional (dir t))
+(defun %saver/get-next-time (schedule universal-time &optional (dir t))
   (labels ((leapp (year)
   (labels ((leapp (year)
              (or (and (zerop (mod year 4))
              (or (and (zerop (mod year 4))
                       (not (zerop (mod year 100))))
                       (not (zerop (mod year 100))))
@@ -85,48 +83,78 @@
                    next-month
                    next-month
                    next-year))))))))))
                    next-year))))))))))
 
 
-(defun saver/count-events (from to schedule)
-  (loop for ut = (saver/get-next-time schedule from) then (saver/get-next-time
-                                                           schedule (+ ut 86400))
+(defstruct saver/payment name amount schedule started)
+
+(defun saver/payment-income-p (payment)
+  (> (saver/payment-amount payment) 0))
+
+(defun saver/payment-next-time (payment moment &optional (dir t))
+  (%saver/get-next-time (%saver/parse-schedule (saver/payment-schedule payment)) moment dir))
+
+(defun saver/payment-count-events (payment from to)
+  (loop for ut = (saver/payment-next-time payment from) then (saver/payment-next-time payment (+ ut 86400))
      while (< ut to)
      while (< ut to)
      counting ut))
      counting ut))
 
 
-(defun saver/get-payment-info (payment salary &optional (moment (get-universal-time)))
-  (let* ((salary (saver/parse-schedule salary))
-         (schedule (saver/parse-schedule (saver/payment-schedule payment)))
-         (next-payment (saver/get-next-time schedule moment))
-         (prev-payment (max (saver/get-next-time schedule moment nil)
+(defun saver/find-next-payment (payments &optional (moment (get-universal-time)))
+  (loop for payment in payments
+     for next-time = (saver/payment-next-time payment moment)
+     with closest-time and closest-payment
+     when (and next-time
+               (or (null closest-time)
+                   (< next-time closest-time)))
+     do (setf closest-time next-time
+              closest-payment payment)
+     finally (return closest-payment)))
+
+(defun saver/get-expense-info (payment incomes &optional (moment (get-universal-time)))
+  (let* ((next-payment (saver/payment-next-time payment moment))
+         (prev-payment (max (saver/payment-next-time payment moment nil)
                             (or (saver/payment-started payment) 0))))
                             (or (saver/payment-started payment) 0))))
     (when next-payment
     (when next-payment
-      (let* ((total-periods (saver/count-events prev-payment next-payment salary))
-             (saved-periods (saver/count-events prev-payment moment salary))
-             (period-amount (floor (saver/payment-amount payment) (max 1 total-periods)))
-             (saved-amount (* saved-periods period-amount)))
-        (list :next-payment next-payment
-              :prev-payment prev-payment
-              :total-periods total-periods
-              :period-amount period-amount
-              :saved-periods saved-periods
-              :saved-amount saved-amount
-              :left-periods (- total-periods saved-periods)
-              :left-amount (- (saver/payment-amount payment) saved-amount))))))
-
-(defun saver/get-period-info (salary payments &optional (moment (get-universal-time)))
-  (let ((next-salary (saver/get-next-time (saver/parse-schedule salary) moment)))
-    (when next-salary
-      (loop for payment in payments
-         for cur-info = (saver/get-payment-info payment salary moment)
-         for next-info = (saver/get-payment-info payment salary next-salary)
-         summing (getf cur-info :saved-amount) into total-saved
-         summing (getf cur-info :left-amount) into total-left
-         summing (if (> (getf next-info :left-periods) 1)
-                     (getf next-info :period-amount)
-                     (getf next-info :left-amount)) into total-period
-         finally (return
-                   (list :next-salary next-salary
-                         :total-saved total-saved
-                         :total-left total-left
-                         :total-period total-period))))))
+      (multiple-value-bind (total-periods total-income)
+          (loop for income in incomes
+             for periods = (saver/payment-count-events income prev-payment next-payment)
+             summing periods into total-periods
+             summing (* periods (saver/payment-amount income)) into total-income
+             finally (return (values total-periods total-income)))
+        (let ((payment-income-fracture (/ (saver/payment-amount payment) total-income -1)))
+          (loop for income in incomes
+             for periods = (saver/payment-count-events income prev-payment moment)
+             summing periods into saved-periods
+             summing (floor (* periods (saver/payment-amount income)
+                               payment-income-fracture)) into saved-amount
+             finally (return
+                       (list :next-payment next-payment
+                             :prev-payment prev-payment
+                             :total-periods total-periods
+                             :total-income total-income
+                             :payment-income-fracture payment-income-fracture
+                             :saved-periods saved-periods
+                             :saved-amount (if (= total-periods saved-periods)
+                                               (* -1 (saver/payment-amount payment))
+                                               saved-amount)
+                             :left-periods (- total-periods saved-periods)
+                             :left-amount (if (= total-periods saved-periods) 0
+                                              (- (- 0 (saver/payment-amount payment))
+                                                 saved-amount))))))))))
+
+
+(defun saver/get-income-info (income payments &optional (moment (get-universal-time)))
+  (let ((incomes (remove-if-not #'saver/payment-income-p payments))
+        (expenses (remove-if #'saver/payment-income-p payments)))
+    (loop for payment in expenses
+       for cur-info = (saver/get-expense-info payment incomes moment)
+       for nxt-info = (saver/get-expense-info payment incomes (saver/payment-next-time income moment))
+       summing (getf cur-info :saved-amount) into cur-saved
+       summing (getf cur-info :left-amount) into cur-left
+       summing (if (> (getf nxt-info :left-periods) 1)
+                   (floor (* (saver/payment-amount income) (getf cur-info :payment-income-fracture)))
+                   (getf nxt-info :left-amount)) into next-save
+       finally (return
+                 (list :cur-saved cur-saved
+                       :cur-left cur-left
+                       :next-save next-save)))))
 
 
 (defun %saver/format-time (universal-time)
 (defun %saver/format-time (universal-time)
   (when universal-time
   (when universal-time
@@ -138,9 +166,7 @@
 ;; Database
 ;; Database
 (def-db-init
 (def-db-init
     (db-execute "create table if not exists saver_payments (chat_id, name, amount, schedule, started, notified)")
     (db-execute "create table if not exists saver_payments (chat_id, name, amount, schedule, started, notified)")
-    (db-execute "create unique index if not exists saver_payments_chat_id_name_idx on saver_payments (chat_id, name)")
-    (db-execute "create table if not exists saver_salaries (chat_id, schedule, notified)")
-    (db-execute "create unique index if not exists saver_salaries_chat_id_idx on saver_salaries (chat_id)"))
+    (db-execute "create unique index if not exists saver_payments_chat_id_name_idx on saver_payments (chat_id, name)"))
 
 
 (defun %db/saver/make-payment (row)
 (defun %db/saver/make-payment (row)
   (when row
   (when row
@@ -151,15 +177,7 @@
 
 
 (defun db/saver/get-payments (chat-id)
 (defun db/saver/get-payments (chat-id)
   (mapcar #'%db/saver/make-payment
   (mapcar #'%db/saver/make-payment
-          (db-select "select name, amount, schedule, started from saver_payments where chat_id = ? order by started" chat-id)))
-
-(defun db/saver/get-not-notified-payments (notified)
-  (loop for row in (db-select "select name, amount, schedule, started, chat_id from saver_payments where notified is null or notified != ?" (%saver/format-time notified))
-     collect (cons (nth 4 row) (%db/saver/make-payment row))))
-
-(defun db/saver/set-payment-notified (chat-id name moment)
-  (db-execute "update saver_payments set notified = ? where chat_id = ? and name = ?"
-              (%saver/format-time moment) chat-id name))
+          (db-select "select name, amount, schedule, started from saver_payments where chat_id = ? order by amount > 0, started" chat-id)))
 
 
 (defun db/saver/add-payment (chat-id payment)
 (defun db/saver/add-payment (chat-id payment)
   (with-slots (name amount schedule started) payment
   (with-slots (name amount schedule started) payment
@@ -169,128 +187,110 @@
 (defun db/saver/del-payment (chat-id name)
 (defun db/saver/del-payment (chat-id name)
   (db-execute "delete from saver_payments where chat_id = ? and name = ?" chat-id name))
   (db-execute "delete from saver_payments where chat_id = ? and name = ?" chat-id name))
 
 
-(defun db/saver/get-salary (chat-id)
-  (caar (db-select "select schedule from saver_salaries where chat_id = ?" chat-id)))
-
-(defun db/saver/get-not-notified-salaries (notified)
-  (loop for row in (db-select "select chat_id, schedule from saver_salaries where notified is null or notified != ?" (%saver/format-time notified))
-     collect (cons (nth 0 row) (nth 1 row))))
+(defun db/saver/get-not-notified-payments (notified)
+  (loop for row in (db-select "select name, amount, schedule, started, chat_id from saver_payments where notified is null or notified != ?" (%saver/format-time notified))
+     collect (cons (nth 4 row) (%db/saver/make-payment row))))
 
 
-(defun db/saver/set-salary-notified (chat-id moment)
-  (db-execute "update saver_salaries set notified = ? where chat_id = ?"
-              (%saver/format-time moment) chat-id))
+(defun db/saver/set-payment-notified (chat-id name moment)
+  (db-execute "update saver_payments set notified = ? where chat_id = ? and name = ?"
+              (%saver/format-time moment) chat-id name))
 
 
-(defun db/saver/set-salary (chat-id schedule)
-  (db-transaction
-    (db-execute "delete from saver_salaries where chat_id = ?" chat-id)
-    (db-execute "insert into saver_salaries (chat_id, schedule) values (?, ?)" chat-id schedule)))
 
 
 ;; Cron
 ;; Cron
 (defun saver/find-today-payments (&optional (moment (get-universal-time)))
 (defun saver/find-today-payments (&optional (moment (get-universal-time)))
   (remove-if-not (lambda (p) (string= (%saver/format-time moment)
   (remove-if-not (lambda (p) (string= (%saver/format-time moment)
                                       (%saver/format-time
                                       (%saver/format-time
-                                       (saver/get-next-time
-                                        (saver/parse-schedule (saver/payment-schedule p))
-                                        moment))))
+                                       (saver/payment-next-time p moment))))
                  (db/saver/get-not-notified-payments moment)
                  (db/saver/get-not-notified-payments moment)
                  :key #'cdr))
                  :key #'cdr))
 
 
-(defun %saver/format-payment-notification (payment moment)
+(defun %saver/format-expense-notification (expense moment)
   (format nil "'~A' надо оплатить *~A* на сумму _~$_"
   (format nil "'~A' надо оплатить *~A* на сумму _~$_"
-          (saver/payment-name payment)
-          (%saver/format-time moment)
-          (/ (saver/payment-amount payment) 100)))
-
-(defun saver/find-today-salaries (&optional (moment (get-universal-time)))
-  (remove-if-not (lambda (p) (string= (%saver/format-time moment)
-                                      (%saver/format-time (saver/get-next-time
-                                                           (saver/parse-schedule p) moment))))
-                 (db/saver/get-not-notified-salaries moment)
-                 :key #'cdr))
+          (saver/payment-name expense)
+          (%saver/format-time (saver/payment-next-time expense moment))
+          (/ (saver/payment-amount expense) -100)))
 
 
-(defun %saver/format-salary-notification (salary payments moment)
-  (let ((period-info (saver/get-period-info salary payments moment)))
-    (format nil "*~A* зарплата. Отложи _~$_!"
-            (%saver/format-time (getf period-info :next-salary))
-            (/ (getf period-info :total-period) 100))))
+(defun %saver/format-income-notification (income payments moment)
+  (let ((income-info (saver/get-income-info income payments moment)))
+    (format nil "*~A* ~A. Отложи _~$_!"
+            (%saver/format-time (saver/payment-next-time income moment))
+            (saver/payment-name income)
+            (/ (getf income-info :next-save) 100))))
 
 
 (defun %saver/is-ok-to-notify (chat-id &optional (moment (get-universal-time)))
 (defun %saver/is-ok-to-notify (chat-id &optional (moment (get-universal-time)))
-  (let ((tz (or
-             (when (boundp '*chat-locations*)
-               (let ((chat-loc (aget chat-id (symbol-value '*chat-locations*))))
-                 (when chat-loc
-                   (round (- 7.5 (aget "latitude" chat-loc)) 15)))) ;; Nautical time
-             *saver-default-timezone*)))
+  (let ((tz (or (alexandria:when-let
+                    (chat-loc (aget chat-id (and (boundp '*chat-locations*) (symbol-value '*chat-locations*))))
+                  (round (- 7.5 (aget "latitude" chat-loc)) 15)) ;; Nautical time
+                *saver-default-timezone*)))
     (>= (nth 2 (multiple-value-list (decode-universal-time moment tz)))
     (>= (nth 2 (multiple-value-list (decode-universal-time moment tz)))
        *saver-notify-hour*)))
        *saver-notify-hour*)))
 
 
 (defcron process-saver (:hour '*)
 (defcron process-saver (:hour '*)
   (let ((moment (get-universal-time)))
   (let ((moment (get-universal-time)))
-    (dolist (cp (saver/find-today-payments moment))
-      (when (%saver/is-ok-to-notify (car cp) moment)
-        (db-transaction
-          (bot-send-message (car cp) (%saver/format-payment-notification (cdr cp) moment) :parse-mode "markdown")
-          (db/saver/set-payment-notified (car cp) (saver/payment-name (cdr cp)) moment))))
-    (dolist (cs (saver/find-today-salaries moment))
-      (when (%saver/is-ok-to-notify (car cs) moment)
-        (db-transaction
-          (bot-send-message (car cs)
-                            (%saver/format-salary-notification (cdr cs)
-                                                               (db/saver/get-payments (car cs))
-                                                               moment)
-                            :parse-mode "markdown")
-          (db/saver/set-salary-notified (car cs) moment))))))
+    (loop for (chat-id . payment) in (saver/find-today-payments moment)
+       when (%saver/is-ok-to-notify chat-id moment)
+       do (let ((payments (db/saver/get-payments chat-id)))
+            (db-transaction
+              (bot-send-message chat-id
+                                (if (saver/payment-income-p payment)
+                                    (%saver/format-income-notification payment payments moment)
+                                    (%saver/format-expense-notification payment moment))
+                                :parse-mode "markdown")
+              (db/saver/set-payment-notified chat-id (saver/payment-name payment) moment))))))
 
 
 
 
 ;; Bot subcommands
 ;; Bot subcommands
-(defun %saver/format-info (payments salary &optional (moment (get-universal-time)))
-  (let* (closest-time
-         closest-payment
-         (payments-info
-          (loop for payment in payments
+(defun %saver/format-info (payments &optional (moment (get-universal-time)))
+  (let* ((expenses (remove-if #'saver/payment-income-p payments))
+         (incomes (remove-if-not #'saver/payment-income-p payments))
+         (expenses-info
+          (loop for payment in expenses
              for idx from 1
              for idx from 1
-             for info = (saver/get-payment-info payment salary moment)
+             for info = (saver/get-expense-info payment incomes moment)
              when info
              when info
-             when (or (null closest-time)
-                      (< (getf info :next-payment) closest-time))
-             do (setf closest-time (getf info :next-payment)
-                      closest-payment payment)
-             collect (format nil "~D) ~A по [[~A]]: накоплено _~$_ из _~$_, осталось _~$_ за ~A платежа"
+             collect (format nil "~D) ~A по [[~A]]: накоплено _~$_ из _~$_, осталось _~$_ к *~A* за ~A платежа"
                              idx
                              idx
                              (saver/payment-name payment)
                              (saver/payment-name payment)
                              (saver/payment-schedule payment)
                              (saver/payment-schedule payment)
                              (/ (getf info :saved-amount) 100)
                              (/ (getf info :saved-amount) 100)
-                             (/ (saver/payment-amount payment) 100)
+                             (/ (saver/payment-amount payment) -100)
                              (/ (getf info :left-amount) 100)
                              (/ (getf info :left-amount) 100)
+                             (%saver/format-time (saver/payment-next-time payment moment))
                              (getf info :left-periods))))
                              (getf info :left-periods))))
-        (period-info (saver/get-period-info salary payments moment)))
-    (if payments-info
-        (format nil "*Платежи*~%~{~A~^~%~}~%~%Накоплено должно быть _~$_~%Следующее накопление *~A* на _~$_~%Следующий платёж '~A' *~A*, _~$_"
-                payments-info
-                (/ (getf period-info :total-saved) 100)
-                (%saver/format-time (getf period-info :next-salary))
-                (/ (getf period-info :total-period) 100)
-                (saver/payment-name closest-payment)
-                (%saver/format-time closest-time)
-                (/ (saver/payment-amount closest-payment) 100))
+         (closest-expense (saver/find-next-payment expenses moment))
+         (incomes-info
+          (loop for payment in incomes
+             for idx from (1+ (length expenses))
+             for info = (saver/get-income-info payment payments moment)
+             when info
+             collect (format nil "~D) ~A по [[~A]]: отложить _~$_ *~A*"
+                             idx
+                             (saver/payment-name payment)
+                             (saver/payment-schedule payment)
+                             (/ (getf info :next-save) 100)
+                             (%saver/format-time (saver/payment-next-time payment moment)))))
+         (closest-income (saver/find-next-payment incomes moment))
+         (closest-income-info (and closest-income (saver/get-income-info closest-income payments moment))))
+    (if (or expenses-info incomes-info)
+        (format nil "*Платежи*~%~{~A~^~%~}~%*Следующий платёж*~%'~A' *~A*, заплатить _~$_~%~%*Накопления*~%~{~A~^~%~}~%*Следующее накопление*~%'~A' *~A*, отложить _~$_~%~%*Накоплено должно быть* _~$_"
+                expenses-info
+                (and closest-expense (saver/payment-name closest-expense))
+                (and closest-expense (%saver/format-time (saver/payment-next-time closest-expense moment)))
+                (and closest-expense (/ (saver/payment-amount closest-expense) -100))
+                incomes-info
+                (and closest-income (saver/payment-name closest-income))
+                (and closest-income (%saver/format-time (saver/payment-next-time closest-income moment)))
+                (and closest-income-info (/ (getf closest-income-info :next-save) 100))
+                (and closest-income-info (/ (getf closest-income-info :cur-saved) 100)))
         "Нет активных платежей.")))
         "Нет активных платежей.")))
 
 
 (defun saver/send-info (chat-id)
 (defun saver/send-info (chat-id)
-  (let ((payments (db/saver/get-payments chat-id))
-        (salary (db/saver/get-salary chat-id)))
-    (if salary
-        (if payments
-            (bot-send-message chat-id (%saver/format-info payments salary) :parse-mode "markdown")
-            (bot-send-message chat-id (format nil "Платежей нет, зарплата: ~A" salary)))
-        (send-response chat-id "Зарплата не задана, /saver salary <cron>"))))
-
-(defun saver/set-salary (chat-id args)
-  (let ((sched (spaced args)))
-    (saver/parse-schedule sched)
-    (db/saver/set-salary chat-id sched)
-    (send-response chat-id (format nil "Зарплата теперь '~A'" sched))))
-
-(defparameter +saver/add-scanner+ (cl-ppcre:create-scanner "^(.+?) (\\d+(?:\\.\\d*)?) ((?:\\d+(?:,\\d+)*(?:-\\d+)?|\\*)(?:\\/\\d+)?(?: (?:\\d+(?:,\\d+)*(?:-\\d+)?|\\*)(?:\\/\\d+)?){0,2})(?: (\\d{4}-\\d{2}-\\d{2}))?$"))
+  (let ((payments (db/saver/get-payments chat-id)))
+    (if payments
+        (bot-send-message chat-id (%saver/format-info payments) :parse-mode "markdown")
+        (bot-send-message chat-id (format nil "Платежей нет, /saver add ...")))))
+
+(defparameter +saver/add-scanner+ (cl-ppcre:create-scanner "^(.+?) (-?\\d+(?:\\.\\d*)?) ((?:\\d+(?:,\\d+)*(?:-\\d+)?|\\*)(?:\\/\\d+)?(?: (?:\\d+(?:,\\d+)*(?:-\\d+)?|\\*)(?:\\/\\d+)?){0,2})(?: (\\d{4}-\\d{2}-\\d{2}))?$"))
 
 
 (defun saver/add-payment (chat-id args)
 (defun saver/add-payment (chat-id args)
   (multiple-value-bind (matched groups) (cl-ppcre:scan-to-strings +saver/add-scanner+ (spaced args))
   (multiple-value-bind (matched groups) (cl-ppcre:scan-to-strings +saver/add-scanner+ (spaced args))
@@ -305,25 +305,26 @@
                                                                      #\- (elt groups 3)))
                                                                      #\- (elt groups 3)))
                                                           (encode-universal-time 0 0 0 day month year))
                                                           (encode-universal-time 0 0 0 day month year))
                                                         (get-universal-time)))))
                                                         (get-universal-time)))))
-          (saver/get-next-time (saver/parse-schedule (saver/payment-schedule payment))
-                               (get-universal-time))
+          (saver/payment-next-time payment (get-universal-time))
           (handler-case
           (handler-case
               (db/saver/add-payment chat-id payment)
               (db/saver/add-payment chat-id payment)
-            (error (e) (send-response chat-id (format nil "Платёж '~A' уже есть!"
+            (error () (send-response chat-id (format nil "Платёж '~A' уже есть!"
                                                       (saver/payment-name payment)))))
                                                       (saver/payment-name payment)))))
           (saver/send-info chat-id))
           (saver/send-info chat-id))
         (send-response chat-id "Неправильно. /saver add <title> <amount> <cron> [started]"))))
         (send-response chat-id "Неправильно. /saver add <title> <amount> <cron> [started]"))))
 
 
 (defun saver/del-payment (chat-id args)
 (defun saver/del-payment (chat-id args)
   (handler-case
   (handler-case
-      (let ((payment (elt (db/saver/get-payments chat-id) (1- (parse-integer (car args)))))
-            (salary (db/saver/get-salary chat-id)))
+      (let* ((payments (db/saver/get-payments chat-id))
+             (payment (elt payments (1- (parse-integer (car args)))))
+             (incomes (remove-if-not #'saver/payment-income-p payments)))
         (db/saver/del-payment chat-id (saver/payment-name payment))
         (db/saver/del-payment chat-id (saver/payment-name payment))
         (bot-send-message chat-id
         (bot-send-message chat-id
                           (format nil "'~A' удалил.~@[ Забрать _~$_ из накопленого.~]"
                           (format nil "'~A' удалил.~@[ Забрать _~$_ из накопленого.~]"
                                   (saver/payment-name payment)
                                   (saver/payment-name payment)
-                                  (and salary
-                                       (/ (getf (saver/get-payment-info payment salary) :saved-amount)
+                                  (and (not (saver/payment-income-p payment))
+                                       incomes
+                                       (/ (getf (saver/get-expense-info payment incomes) :saved-amount)
                                           100)))
                                           100)))
                           :parse-mode "markdown"))
                           :parse-mode "markdown"))
     (error (e) (send-response chat-id (format nil "/saver del <idx> [~A]" e)))))
     (error (e) (send-response chat-id (format nil "/saver del <idx> [~A]" e)))))
@@ -333,7 +334,6 @@
   (if (null args)
   (if (null args)
       (saver/send-info chat-id)
       (saver/send-info chat-id)
       (case (keyify (car args))
       (case (keyify (car args))
-        (:salary (saver/set-salary chat-id (rest args)))
         (:add (saver/add-payment chat-id (rest args)))
         (:add (saver/add-payment chat-id (rest args)))
         (:del (saver/del-payment chat-id (rest args)))
         (:del (saver/del-payment chat-id (rest args)))
-        (t (send-response chat-id "Чот не понял")))))
+        (t (send-response chat-id "Надо /saver add ... или /saver del <idx>")))))