aboutsummaryrefslogtreecommitdiff
path: root/src/cashflow/database.clj
diff options
context:
space:
mode:
authorTim <contact@bytim.eu>2026-02-22 15:25:50 +0100
committerTim <contact@bytim.eu>2026-02-22 15:25:50 +0100
commitf279e20468fb5323c33cbf43346c35ddef7f96e0 (patch)
treec488ee2791296917367f704524fa8e41a0b518ea /src/cashflow/database.clj
Initial commit
Diffstat (limited to 'src/cashflow/database.clj')
-rw-r--r--src/cashflow/database.clj87
1 files changed, 87 insertions, 0 deletions
diff --git a/src/cashflow/database.clj b/src/cashflow/database.clj
new file mode 100644
index 0000000..6daae1f
--- /dev/null
+++ b/src/cashflow/database.clj
@@ -0,0 +1,87 @@
+(ns cashflow.database
+ (:require [datalevin.core :as dcore]
+ [cashflow.frontend.utils :as cfutils]
+ [cashflow.utils :as cutils]
+ [clojure.string :as cstr])
+ (:import java.time.LocalDate))
+
+(def ^:private schema {:type {:db/valueType :db.type/keyword}
+ :user {:db/valueType :db.type/string} ; TODO: multi user
+ :description {:db/valueType :db.type/string}
+ :amount {:db/valueType :db.type/float}
+ :date {:db/valueType :db.type/string}
+ :from {:db/valueType :db.type/string}
+ :to {:db/valueType :db.type/string}
+ :month-interval {:db/valueType :db.type/bigint}})
+
+(def connection (delay (dcore/get-conn "./cashflow.db" schema)))
+
+(defn create-transaction!
+ "Stores one transaction `x` in the database."
+ [x]
+ (dcore/transact! @connection [x]))
+
+(defn list-transactions [type]
+ (->> (dcore/q '[:find (pull ?e [*])
+ :in $ ?type
+ :where [?e :type ?type]]
+ (dcore/db @connection) type)
+ (map first)))
+
+(defn list-one-time-transactions [year month]
+ (->> (list-transactions :one-time)
+ (filter (fn [transaction]
+ (let [transaction-date (LocalDate/parse (:date transaction))]
+ (and (= year (.getYear transaction-date))
+ (= month (.getMonthValue transaction-date))))))
+ (sort-by :date)))
+
+(defn list-recurring-transactions []
+ (let [today (LocalDate/now)]
+ (->> (list-transactions :recurring)
+ (sort-by (fn [transaction]
+ (let [from (try (LocalDate/parse (:from transaction)) (catch Exception _ nil))
+ to (try (LocalDate/parse (:from transaction)) (catch Exception _ nil))]
+ (cond
+ (or (and (nil? from) (nil? to))
+ (and (nil? from) (some? to) (.isBefore today to))
+ (and (some? to) (.isAfter today from))) 0
+ (try (.isBefore today from)
+ (catch Exception _ false)) 1
+ (try (.isAfter today to)
+ (catch Exception _ false)) 2)))))))
+
+(defn recurring-transactions-applying-total
+ ([date]
+ (->> (list-transactions :recurring)
+ (filter (fn [transaction]
+ (let [from (try (LocalDate/parse (:from transaction)) (catch Exception _ nil))
+ to (try (LocalDate/parse (:to transaction)) (catch Exception _ nil))]
+ (try (or (and (nil? from) (nil? to))
+ (and (nil? from) (or (.isAfter to date) (.isEqual to date)))
+ (and (nil? to) (or (.isBefore from date) (.isEqual from date)))
+ (and (or (.isAfter to date) (.isEqual to date))
+ (or (.isBefore from date) (.isEqual from date))))
+ (catch Exception _ false)))))
+ (map (fn [transaction] (/ (:amount transaction) (:month-interval transaction))))
+ (apply +)))
+ ([year month]
+ (recurring-transactions-applying-total (LocalDate/parse (str (cutils/string-min-length (str year) 4 "0" :before) "-"
+ (cutils/string-min-length (str month) 2 "0" :before) "-01")))))
+
+(defn one-time-transactions-total [year month]
+ (->> (list-one-time-transactions year month)
+ (map #(-> % first :amount))
+ (apply +)
+ (+ (recurring-transactions-applying-total year month))))
+
+(defn id->transaction [id]
+ (-> (dcore/q '[:find (pull ?id [*])
+ :in $ ?id
+ :where [?id]]
+ (dcore/db @connection) id)
+ first first))
+
+(defn delete-transaction! [id]
+ (dcore/transact! @connection [[:db/retractEntity id]]))
+