ofd.lisp 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. (in-package :cl-user)
  2. (defpackage chatikbot.plugins.ofd
  3. (:use :cl :chatikbot.common :alexandria))
  4. (in-package :chatikbot.plugins.ofd)
  5. (defparameter +api-root+ "https://proverkacheka.nalog.ru:9999/v1/")
  6. (defmethod poller-request ((module (eql :ofd)) method &rest params)
  7. (handler-case
  8. (json-request (concatenate 'string +api-root+ method)
  9. :basic-auth *poller-token*
  10. :headers '((:device-id . "bot") (:device-os . "lisp"))
  11. :parameters (rest-parameters params t))
  12. (dex:http-request-failed (e) e)))
  13. (defmethod poller-validate ((module (eql :ofd)) response)
  14. (not (typep response 'dex:http-request-failed)))
  15. (defmethod poller-get-token ((module (eql :ofd)) secret)
  16. (let* ((*poller-token* secret))
  17. (ignore-errors
  18. (poller-request :ofd "mobile/users/login")
  19. *poller-token*)))
  20. (defun login ()
  21. (poller-request :ofd "mobile/users/login"))
  22. (defun cheque-check (fn fd fp n date sum)
  23. (handler-case
  24. (let ((res (poller-call :ofd
  25. (format nil "ofds/*/inns/*/fss/~A/operations/~A/tickets/~A" fn n fd)
  26. :|fiscalSign| fp
  27. :|date| date
  28. :|sum| sum)))
  29. (typecase res
  30. (dex:http-request-not-acceptable nil)
  31. (t (error res))))
  32. (end-of-file () t)))
  33. (defun cheque-get (fn fd fp)
  34. (handler-case
  35. (let ((res (poller-call :ofd
  36. (format nil "inns/*/kkts/*/fss/~A/tickets/~A" fn fd)
  37. :|fiscalSign| fp
  38. :|sendToEmail| "no")))
  39. (typecase res
  40. (list (agets res "document" "receipt"))
  41. (dex:http-request-not-acceptable nil)
  42. (t res)))
  43. (end-of-file () nil)))
  44. (defun receive (fn fd fp n date sum)
  45. (ignore-errors (login))
  46. (when (handler-case (cheque-check fn fd fp n date sum)
  47. (dex:http-request-internal-server-error ()
  48. (ignore-errors (cheque-check fn fd fp n date sum)))
  49. (error (e) (log:warn e) nil))
  50. (or (cheque-get fn fd fp)
  51. (cheque-get fn fd fp)
  52. (cheque-get fn fd fp))))
  53. (defparameter +qr-re+ (cl-ppcre:create-scanner "t=(\\d{8}T\\d{4,6})&s=(\\d+(?:\\.\\d{2})?)&fn=(\\d{16})&i=(\\d{0,10})&fp=(\\d{0,10})&n=(1|2)"))
  54. (defun parse-qt (str)
  55. (multiple-value-bind (matched groups)
  56. (cl-ppcre:scan-to-strings +qr-re+ str)
  57. (when matched
  58. (destructuring-bind (date sum fn fd fp n) (coerce groups 'list)
  59. (list (parse-integer fn) (parse-integer fd) (parse-integer fp)
  60. (parse-integer n) date (round (* 100 (parse-float sum))))))))
  61. (defvar *default-currency* "RUB")
  62. (defvar *default-expense-account* "expenses")
  63. (defvar *default-asset-account* "assets:Cash:RUB")
  64. (defun expense-account (name &optional user address)
  65. (declare (ignorable name user address))
  66. *default-expense-account*)
  67. (defun expense-comment (name &optional user address price quantity sum)
  68. (declare (ignorable name user address price quantity sum))
  69. (format nil "item: ~A~@[, ~A @ ~A~]"
  70. name
  71. (unless (= quantity 1) quantity)
  72. (unless (= quantity 1) (/ price 100))))
  73. (defun asset-account (&optional user address)
  74. (declare (ignorable user address))
  75. *default-asset-account*)
  76. (defun cheque->entry (c)
  77. (let ((total-sum (agets c "totalSum"))
  78. (date (local-time:timestamp-to-universal
  79. (local-time:parse-timestring (agets c "dateTime") )))
  80. (user (or (agets c "retailPlace")
  81. (agets c "user")
  82. (format nil "ИНН ~A" (agets c "userInn"))))
  83. (address (agets c "retailPlaceAddress"))
  84. (items (agets c "items")))
  85. (pta-ledger:make-entry
  86. :date date
  87. :description user
  88. :comment (when address (format nil "address: ~A" address))
  89. :postings (append (loop for i in items
  90. collect
  91. (let ((name (agets i "name"))
  92. (price (agets i "price"))
  93. (quantity (agets i "quantity"))
  94. (sum (agets i "sum")))
  95. (pta-ledger:make-posting
  96. :account (expense-account name user address)
  97. :comment (expense-comment name user address price quantity sum)
  98. :amount (pta-ledger:make-amount
  99. :quantity (/ sum 100)
  100. :commodity *default-currency*))))
  101. (list (pta-ledger:make-posting
  102. :account (asset-account user address)
  103. :amount (pta-ledger:make-amount
  104. :quantity (* -1 (/ total-sum 100))
  105. :commodity *default-currency*)))))))
  106. (defun handle-auth (login pass)
  107. (handler-case
  108. (progn
  109. (poller-authenticate :ofd (cons login pass)))
  110. (poller-cant-authenticate ()
  111. (bot-send-message "Чот не смог, пропробуй другие."))))
  112. (def-message-cmd-handler handler-ofd (:ofd)
  113. (cond
  114. ((= 2 (length *args*)) (apply 'handle-auth *args*))
  115. (:otherwise (bot-send-message "/ofd <login> <pass>"))))
  116. (def-message-handler ofd-handler (-10)
  117. (let ((parsed (parse-qt *text*)))
  118. (when parsed
  119. (telegram-send-chat-action *chat-id* "typing")
  120. (let ((cheque (apply #'receive parsed)))
  121. (if cheque
  122. (let ((ledger-package (find-package :chatikbot.plugins.ledger))
  123. (entry (cheque->entry cheque)))
  124. (if ledger-package
  125. (let ((new-chat-entry (symbol-function
  126. (intern "LEDGER/NEW-CHAT-ENTRY" ledger-package))))
  127. (funcall new-chat-entry *chat-id* (pta-ledger:clone-entry entry)))
  128. (bot-send-message (pta-ledger:render entry) :parse-mode "markdown")))
  129. (bot-send-message "Не смог в чек :(")))
  130. t)))