(ns cashflow.database (:require [datalevin.core :as dcore] [cashflow.utils :as cutils]) (: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]]))