summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJSDurand <mmemmew@gmail.com>2023-08-31 19:02:51 +0800
committerJSDurand <mmemmew@gmail.com>2023-08-31 19:02:51 +0800
commitdf27fb536549b7fce1091b68e1fc98d89f386491 (patch)
tree9baacca836014084f6c8219981fd61831fe83e7e
parent5fc43e2a75b2af4a38f6579087aadbc188af9da4 (diff)
account: filtering and adding a payment method option
* account.el (account-capture-content): Add an option for the payment methods. (org-capture): This is required so that the capture templates variable is available. (account-collect-into-vec, account-agenda-command) (account-match-item, account-agenda-add-filter) (account-agenda-match, account-agenda-remove-filter) (account-mode-map): Add the functionality of filtering. Now the user can filter items based on some properties, like the payment method, the purpose, or the shop name, et cetera.
-rw-r--r--account.el158
1 files changed, 143 insertions, 15 deletions
diff --git a/account.el b/account.el
index a39f350..b17f304 100644
--- a/account.el
+++ b/account.el
@@ -195,7 +195,9 @@ lines around the point."
(purpose (completing-read
"Purpose? " account-purposes-list nil t))
(record-time (format-time-string "[%Y-%m-%d %a %H:%M]"))
- (from (completing-read "From: " (list "cash" "card"))))
+ (from (completing-read
+ "From: "
+ (list "cash" "card" "Line Pay Money"))))
(format
"** %s\n:PROPERTIES:\n:purpose: %s\n:from: %s\n\
:record_time: %s\n:END:\n"
@@ -231,6 +233,8 @@ lines around the point."
:hook #'account-capture-mode)
"The template for capturing account information.")
+(require 'org-capture)
+
(cond
((assoc "m" org-capture-templates #'equal)
(setcdr (assoc "m" org-capture-templates #'equal)
@@ -349,6 +353,10 @@ for which the collector gives N as its index, and ALIST-OF-SHOPS
is the associative list of all associated shop-names and
respective costs, if SHOP-NAME is non-nil.
+In addition, an item is collected if and only if the function
+`account-match-item' returns non-nil when called on
+`account-agenda-filters' and the item.
+
This function does not move point, nor does it modify match data."
(save-match-data
(save-excursion
@@ -475,15 +483,18 @@ This function does not move point, nor does it modify match data."
(or (car-safe
(alist-get name res-list))
0)))
- (setcar (cdr res-element)
- (+ res-value value))
(cond
- (name
- (setf
- (alist-get name (car (cddr res-element)))
- (cons
- (+ name-old-value value)
- shop-marker)))))))))
+ ((account-match-item
+ account-agenda-match account-item)
+ (setcar (cdr res-element)
+ (+ res-value value))
+ (cond
+ (name
+ (setf
+ (alist-get name (car (cddr res-element)))
+ (cons
+ (+ name-old-value value)
+ shop-marker)))))))))))
((user-error "Invalid level %d" level)))))
(let ((n 0))
(while (< n len)
@@ -492,11 +503,12 @@ This function does not move point, nor does it modify match data."
(date
(get-text-property 0 'date title))
(date-encoded
- (encode-time
- (list 0 0 0
- (cadr date)
- (car date)
- (caddr date))))
+ (ignore-errors
+ (encode-time
+ (list 0 0 0
+ (cadr date)
+ (car date)
+ (caddr date)))))
(date-marker
(gethash date-encoded date-markers)))
(cond
@@ -575,7 +587,7 @@ MATCH is required by Org-agenda, and relentlessly ignored by us."
'face 'org-agenda-calendar-event
'org-marker (cddr shop-info)))
(newline))
- (caddr element))
+ (reverse (caddr element)))
(insert
(propertize
(format " all: %d" cost)
@@ -764,6 +776,122 @@ the displayed information.")
'org-agenda-custom-commands
(list "ms" "account view by shop" '((account-agenda-shop-command))))
+;;; Filtering account information
+
+(defvar account-agenda-match nil
+ "The conditions to filter account information for display.
+See the documentation of the function `account-match-item' for
+the valid value.")
+
+(defun account-match-item (match item)
+ "Return non-nil if and only if MATCH matches ITEM.
+ITEM is a valid item if and only if the function
+`account-is-item-p' returns t.
+
+MATCH is a list of cons cells, whose `car' is one of the
+following symbols:
+
+- date
+- shop
+- item
+- cost
+- purpose
+- method (from)
+- time
+
+The `cdr' of the cell can be of the following types:
+
+- nil: the item must have nil value to match
+- string: a regular expression to match the item value
+- function: return non-nil if and only if item value matches.
+
+The first element of MATCH can be the special symbol 'or', in
+which case the item matches if one of the conditions matches;
+otherwise all conditions must match for the item to be considered
+a match."
+ (let ((temp match)
+ (result t)
+ car-temp item-value cdar-temp
+ temp-result or-mode)
+ (cond
+ ((eq (car-safe temp) 'or)
+ (setq temp (cdr temp))
+ (setq or-mode t)))
+ (while (consp temp)
+ (setq car-temp (car temp))
+ (setq cdar-temp (cdr car-temp))
+ (setq temp (cdr temp))
+ (cond
+ ((eq (car car-temp) 'date)
+ (setq item-value (plist-get (cdr item) :date)))
+ ((eq (car car-temp) 'shop)
+ (setq item-value (plist-get (cdr item) :shop-name)))
+ ((eq (car car-temp) 'item)
+ (setq item-value (plist-get (cdr item) :item-name)))
+ ((eq (car car-temp) 'cost)
+ (setq item-value (plist-get (cdr item) :cost)))
+ ((eq (car car-temp) 'purpose)
+ (setq item-value (plist-get (cdr item) :purpose)))
+ ((or (eq (car car-temp) 'method)
+ (eq (car car-temp) 'from))
+ (setq item-value (plist-get (cdr item) :payment-method)))
+ ((eq (car car-temp) 'time)
+ (setq item-value (plist-get (cdr item) :record-time)))
+ ((error "Wrong symbol %S" (car car-temp))))
+ (cond
+ ((null cdar-temp) (setq temp-result (null item-value)))
+ ((stringp cdar-temp)
+ (setq temp-result
+ (and (stringp item-value)
+ (string-match-p cdar-temp item-value))))
+ ((functionp cdar-temp)
+ (setq temp-result (funcall cdar-temp item-value)))
+ ((error "Wrong condition: %S" cdar-temp)))
+ (cond
+ (or-mode
+ (cond
+ (temp-result
+ (setq result t)
+ (setq temp nil))))
+ (t
+ (cond
+ ((not temp-result)
+ (setq result nil)
+ (setq temp nil))))))
+ result))
+
+(defun account-agenda-add-filter (prop value)
+ "Add a filtering condition for PROP matching VALUE."
+ (interactive
+ (let* ((prop
+ (completing-read
+ "Property: "
+ (list
+ "date" "shop" "item" "purpose" "cost" "from" "time")
+ nil t))
+ (val (read-string (format "Match %s: " prop))))
+ (list prop val)))
+ (let ((prop (intern prop)))
+ (cond
+ ((eq (car-safe account-agenda-match) 'or)
+ (setcdr account-agenda-match
+ (cons
+ (cons prop value)
+ (cdr account-agenda-match))))
+ ((setq account-agenda-match
+ (cons
+ (cons prop value)
+ account-agenda-match))))))
+
+(defun account-agenda-remove-filter ()
+ "Set `account-agenda-match' to nil."
+ (interactive)
+ (setq account-agenda-match nil))
+
+(define-key account-mode-map (vector ?/) #'account-agenda-add-filter)
+(define-key account-mode-map (vector ?|)
+ #'account-agenda-remove-filter)
+
;;; Exporting information as graphs
(defun account-agenda-export-to-svg ()