Personal finances application for Emacs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

240 lines
7.9 KiB

  1. ;;; elbank-overview.el --- Elbank overview buffer -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2017-2018 Nicolas Petton
  3. ;; Author: Nicolas Petton <nicolas@petton.fr>
  4. ;; This program is free software; you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation, either version 3 of the License, or
  7. ;; (at your option) any later version.
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;; GNU General Public License for more details.
  12. ;; You should have received a copy of the GNU General Public License
  13. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. ;;; Commentary:
  15. ;;
  16. ;;; Code:
  17. (require 'button)
  18. (require 'seq)
  19. (require 'map)
  20. (require 'subr-x)
  21. (require 'elbank-common)
  22. (require 'elbank-boobank)
  23. (require 'elbank-report)
  24. (require 'elbank-budget)
  25. (defvar elbank-overview-buffer-name "*elbank overview*"
  26. "Name of the elbank overview buffer.")
  27. (defvar elbank-overview-mode-map
  28. (let ((map (make-sparse-keymap)))
  29. (define-key map (kbd "g") #'elbank-overview-update-buffer)
  30. (define-key map (kbd "u") #'elbank-overview-update-data)
  31. (define-key map (kbd "r") #'elbank-report)
  32. (define-key map (kbd "b") #'elbank-budget-report)
  33. (define-key map (kbd "n") #'forward-button)
  34. (define-key map (kbd "p") #'backward-button)
  35. (define-key map [tab] #'forward-button)
  36. (define-key map [backtab] #'backward-button)
  37. (define-key map (kbd "q") #'elbank-quit)
  38. map)
  39. "Keymap for `elbank-overview-mode'.")
  40. (define-derived-mode elbank-overview-mode nil "Elbank Overview"
  41. "Major mode for Elbank overview.
  42. \\{elbank-overview-mode-map}"
  43. (read-only-mode)
  44. (setq imenu-prev-index-position-function #'elbank-overview--imenu-prev-index-position-function)
  45. (setq imenu-extract-index-name-function #'elbank-overview--imenu-extract-index-name-function))
  46. (defun elbank-overview-account-at-point (&optional point)
  47. "Return account at POINT, nil if none.
  48. If POINT is nil, use current point."
  49. (get-text-property (or point (point)) 'elbank-account))
  50. (defun elbank-overview--imenu-prev-index-position-function ()
  51. "Move point to previous button in current buffer.
  52. This function is used as a value for
  53. `imenu-prev-index-position-function'."
  54. (ignore-errors (backward-button 1)))
  55. (defun elbank-overview--imenu-extract-index-name-function ()
  56. "Return imenu name for line at point.
  57. This function is used as a value for
  58. `imenu-extract-index-name-function'. Point should be at the
  59. beginning of an account line.
  60. If nothing important is at point, return nil."
  61. (get-text-property (point) 'imenu-name))
  62. ;;;###autoload
  63. (defun elbank-overview ()
  64. "Show an overview of all accounts."
  65. (interactive)
  66. (elbank-read-data)
  67. (unless elbank-data
  68. (when (yes-or-no-p "No data found, import from weboob?")
  69. (elbank-overview-update-data)))
  70. (if-let ((buf (get-buffer elbank-overview-buffer-name)))
  71. (progn
  72. (switch-to-buffer buf)
  73. (elbank-overview-update-buffer))
  74. (let ((buf (get-buffer-create elbank-overview-buffer-name)))
  75. (pop-to-buffer buf)
  76. (elbank-overview-mode)
  77. (elbank-overview-update-buffer))))
  78. ;;;###autoload
  79. (defun elbank-overview-update-buffer ()
  80. "Update the overview buffer with the latest data."
  81. (interactive)
  82. (let ((inhibit-read-only t))
  83. (erase-buffer)
  84. (remove-overlays (point-min) (point-max))
  85. (insert "Bank accounts overview")
  86. (put-text-property (point-at-bol) (point)
  87. 'face 'elbank-header-face)
  88. (insert "\n\n")
  89. (elbank-overview--insert-accounts)
  90. (elbank-overview--insert-hr)
  91. (insert "\n\n")
  92. (insert "Custom reports")
  93. (put-text-property (point-at-bol) (point)
  94. 'face 'elbank-header-face)
  95. (insert " ")
  96. (let ((beg (point)))
  97. (insert "[Customize]")
  98. (make-text-button beg (point)
  99. 'follow-link t
  100. 'action (lambda (&rest _)
  101. (customize-group 'elbank-report))))
  102. (insert "\n\n")
  103. (elbank-overview--insert-saved-reports)
  104. (insert "\n")
  105. (insert "Budget report")
  106. (put-text-property (point-at-bol) (point)
  107. 'face 'elbank-header-face)
  108. (insert "\n- ")
  109. (let ((beg (point))
  110. (name (format "Budget report of %s"
  111. (elbank-format-period
  112. `(month ,(car (last (elbank-transaction-months))))))))
  113. (insert name)
  114. (make-text-button beg (point)
  115. 'follow-link t
  116. 'action (lambda (&rest _)
  117. (elbank-budget-report)))
  118. (put-text-property (point-at-bol) (point-at-eol) 'imenu-name name))
  119. (insert "\n")
  120. (goto-char (point-min))))
  121. (defun elbank-overview-update-data ()
  122. "Read new data from boobank and update the buffer."
  123. (interactive)
  124. (message "Elbank: updating...")
  125. (elbank-boobank-update
  126. (lambda ()
  127. (message "Elbank: done!")
  128. (with-current-buffer elbank-overview-buffer-name
  129. (elbank-overview-update-buffer)))))
  130. (defun elbank-overview--insert-hr ()
  131. "Insert a horizontal rule."
  132. ;; End line
  133. (let ((p (1+ (point))))
  134. (insert "\n\n")
  135. (put-text-property p (1+ p) 'face '(:underline t :inherit border))
  136. (overlay-put (make-overlay p (1+ p))
  137. 'before-string
  138. (propertize "\n" 'face '(:underline t)
  139. 'display '(space :align-to 999)))))
  140. (defun elbank-overview--insert-accounts ()
  141. "Insert all accounts informations in the current buffer."
  142. (seq-do (lambda (group)
  143. (elbank-overview--insert-bank (car group))
  144. (seq-map #'elbank-overview--insert-account
  145. (cdr group))
  146. (insert "\n"))
  147. (seq-group-by #'elbank-account-group (map-elt elbank-data 'accounts))))
  148. (defun elbank-overview--insert-bank (bankname)
  149. "Insert BANKNAME into the current buffer as a header."
  150. (insert (format "%s" bankname))
  151. (put-text-property (point-at-bol) (point)
  152. 'face
  153. 'elbank-subheader-face)
  154. (insert "\n"))
  155. (defun elbank-overview--insert-account (account)
  156. "Insert ACCOUNT informations in the current buffer."
  157. (insert "- ")
  158. (let ((beg (point)))
  159. (insert (format "%s" (map-elt account 'label)))
  160. (make-text-button beg (point)
  161. 'follow-link t
  162. 'action (lambda (&rest _)
  163. (elbank-overview--list-transactions account))))
  164. (let* ((balance (format "%s"
  165. (map-elt account 'balance)))
  166. (fill-width (+ (- (seq-length (elbank--longest-account-label))
  167. (current-column))
  168. (- 25 (seq-length balance)))))
  169. (dotimes (_ fill-width)
  170. (insert " "))
  171. (elbank--insert-amount balance (map-elt account 'currency))
  172. (put-text-property (point-at-bol) (point-at-eol) 'elbank-account account)
  173. (put-text-property (point-at-bol) (point-at-eol) 'imenu-name (elbank-account-name account))
  174. (insert "\n")))
  175. (defun elbank-overview--insert-saved-reports ()
  176. "Insert links to saved monthly and yearly reports."
  177. (seq-doseq (type '(month year))
  178. (elbank-overview--insert-saved-reports-type type)
  179. (insert "\n")))
  180. (defun elbank-overview--insert-saved-reports-type (type)
  181. "Insert links to saved reports of period TYPE."
  182. (let (label reports)
  183. (pcase type
  184. (`month (setq label "Monthly reports")
  185. (setq reports elbank-saved-monthly-reports))
  186. (`year (setq label "Yearly reports")
  187. (setq reports elbank-saved-yearly-reports)))
  188. (when reports
  189. (insert label)
  190. (put-text-property (point-at-bol) (point)
  191. 'face 'elbank-subheader-face)
  192. (insert " ")
  193. (insert "\n")
  194. (seq-doseq (report (seq-sort (lambda (a b)
  195. (string< (car a) (car b)))
  196. reports))
  197. (insert "- ")
  198. (let ((beg (point)))
  199. (insert (car report))
  200. (make-text-button beg (point)
  201. 'action
  202. (lambda (&rest _)
  203. (elbank-report--open-saved-report report type)))
  204. (put-text-property beg (point) 'imenu-name (car report))
  205. (insert "\n"))))))
  206. (defun elbank-overview--list-transactions (account)
  207. "Display the list of transactions for ACCOUNT."
  208. (elbank-report :account-id (map-elt account 'id)
  209. :reverse-sort t))
  210. (provide 'elbank-overview)
  211. ;;; elbank-overview.el ends here