(cloud) adds transaction insights
This commit is contained in:
@@ -25,4 +25,28 @@
|
|||||||
:db/doc "Used to find sales orders quickly",
|
:db/doc "Used to find sales orders quickly",
|
||||||
:db/noHistory true
|
:db/noHistory true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{:db/valueType :db.type/ref
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/ident :transaction/recommended-account
|
||||||
|
:db/doc "The recommended account from the ML alogorithm"
|
||||||
|
:db/noHistory true}
|
||||||
|
|
||||||
|
{:db/valueType :db.type/ref
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/ident :transaction/recommended-vendor
|
||||||
|
:db/doc "The recommended vendor from the ML alogorithm"
|
||||||
|
:db/noHistory true}
|
||||||
|
|
||||||
|
{:db/valueType :db.type/double
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/ident :transaction/vendor-confidence
|
||||||
|
:db/doc "The confidence from the ML alogorithm"
|
||||||
|
:db/noHistory true}
|
||||||
|
|
||||||
|
{:db/valueType :db.type/double
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/ident :transaction/account-confidence
|
||||||
|
:db/doc "The confidence from the ML alogorithm"
|
||||||
|
:db/noHistory true}
|
||||||
]
|
]
|
||||||
|
|||||||
4
resources/public/css/bulma.min.css
vendored
4
resources/public/css/bulma.min.css
vendored
@@ -11060,4 +11060,8 @@ tbody tr.live-added {
|
|||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.htmx-request .htmx-indicator {
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
/*# sourceMappingURL=bulma.min.css.map */
|
/*# sourceMappingURL=bulma.min.css.map */
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
4
resources/sass/bulma.scss
vendored
4
resources/sass/bulma.scss
vendored
@@ -187,6 +187,10 @@ tbody tr.live-added {
|
|||||||
.htmx-indicator.button {
|
.htmx-indicator.button {
|
||||||
opacity: 1.0 !important
|
opacity: 1.0 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.htmx-request .htmx-indicator {
|
||||||
|
opacity: 1.0 !important
|
||||||
|
}
|
||||||
.htmx-request .button.htmx-indicator {
|
.htmx-request .button.htmx-indicator {
|
||||||
@extend .is-loading
|
@extend .is-loading
|
||||||
}
|
}
|
||||||
|
|||||||
172
scratch-sessions/build_ml.clj
Normal file
172
scratch-sessions/build_ml.clj
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
;; This buffer is for Clojure experiments and evaluation.
|
||||||
|
|
||||||
|
;; Press C-j to evaluate the last expression.
|
||||||
|
|
||||||
|
;; You can also press C-u C-j to evaluate the expression and pretty-print its result.
|
||||||
|
|
||||||
|
(ns build-ml
|
||||||
|
(:require [datomic.client.api :as dc]
|
||||||
|
[clojure.java.io :as io]
|
||||||
|
[clojure.data.csv :as csv]
|
||||||
|
[auto-ap.datomic :refer [conn]]))
|
||||||
|
|
||||||
|
(println "hi")
|
||||||
|
|
||||||
|
(defn boot []
|
||||||
|
(user/init-repl)
|
||||||
|
(user/start-db))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn write-vendor-training-data []
|
||||||
|
(with-open [f (io/writer "/mnt/data/dev2/ml-test/input/vendor-training.csv")]
|
||||||
|
(csv/write-csv f
|
||||||
|
(into [["transaction" "client" "bank" "bank_type" "description" "date" "amount" "vendor"]]
|
||||||
|
(->>
|
||||||
|
(dc/q '[:find ?t ?c ?bn ?bt2 ?do ?d ?amt ?v
|
||||||
|
:in $ ?start
|
||||||
|
:where
|
||||||
|
[?t :transaction/date ?d]
|
||||||
|
[(>= ?d ?start)]
|
||||||
|
[?t :transaction/approval-status :transaction-approval-status/approved]
|
||||||
|
(not [?t :transaction/payment])
|
||||||
|
[?t :transaction/client ?c]
|
||||||
|
[?t :transaction/vendor ?v]
|
||||||
|
[?v :vendor/name ?v-name]
|
||||||
|
[?t :transaction/description-original ?do]
|
||||||
|
[?t :transaction/amount ?amt]
|
||||||
|
[?t :transaction/bank-account ?b]
|
||||||
|
|
||||||
|
[?b :bank-account/name ?bn]
|
||||||
|
[?b :bank-account/type ?bat]
|
||||||
|
[?bat :db/ident ?bt]
|
||||||
|
[(name ?bt) ?bt2]
|
||||||
|
|
||||||
|
]
|
||||||
|
(dc/db auto-ap.datomic/conn)
|
||||||
|
#inst "2021-01-01")
|
||||||
|
(map (fn [[t code bn bat d date amt v]]
|
||||||
|
[t code bn bat d (auto-ap.time/unparse-local (clj-time.coerce/to-date-time date) auto-ap.time/iso-date) amt v]))))
|
||||||
|
:quote? (constantly true))))
|
||||||
|
|
||||||
|
(defn write-account-training-data []
|
||||||
|
(with-open [f (io/writer "/mnt/data/dev2/ml-test/input/account-training.csv")]
|
||||||
|
(csv/write-csv f
|
||||||
|
(into [["transaction" "client" "bank" "bank_type" "description" "date" "amount" "vendor" "account"]]
|
||||||
|
(->>
|
||||||
|
(dc/q '[:find ?t ?c ?bn ?bt2 ?do ?d ?amt ?v ?a
|
||||||
|
:in $ ?start
|
||||||
|
:where
|
||||||
|
[?t :transaction/date ?d]
|
||||||
|
[(>= ?d ?start)]
|
||||||
|
[?t :transaction/approval-status :transaction-approval-status/approved]
|
||||||
|
[?t :transaction/accounts ?ta]
|
||||||
|
[?ta :transaction-account/account ?a]
|
||||||
|
[?t :transaction/vendor ?v]
|
||||||
|
[?v :vendor/name ?v-name]
|
||||||
|
[?t :transaction/client ?c]
|
||||||
|
[?t :transaction/description-original ?do]
|
||||||
|
[?t :transaction/amount ?amt]
|
||||||
|
[?t :transaction/bank-account ?b]
|
||||||
|
[?b :bank-account/name ?bn]
|
||||||
|
[?b :bank-account/type ?bat]
|
||||||
|
[?bat :db/ident ?bt]
|
||||||
|
[(name ?bt) ?bt2]]
|
||||||
|
(dc/db auto-ap.datomic/conn)
|
||||||
|
#inst "2022-01-01")
|
||||||
|
(map (fn [[t code bn bat d date amt v a]]
|
||||||
|
[t code bn bat d (auto-ap.time/unparse-local (clj-time.coerce/to-date-time date) auto-ap.time/iso-date) amt v a]))))
|
||||||
|
:quote? (constantly true))))
|
||||||
|
|
||||||
|
(defn write-inference []
|
||||||
|
(with-open [f (io/writer "/mnt/data/dev2/ml-test/input/inference.csv")]
|
||||||
|
(csv/write-csv f
|
||||||
|
(into [["transaction" "client" "bank" "bank_type" "description" "date" "amount"]]
|
||||||
|
(->>
|
||||||
|
(dc/q '[:find ?t ?c ?bn ?bt2 ?do ?d ?amt
|
||||||
|
:in $ ?start
|
||||||
|
:where
|
||||||
|
[?t :transaction/date ?d]
|
||||||
|
[(>= ?d ?start)]
|
||||||
|
[?t :transaction/approval-status :transaction-approval-status/unapproved]
|
||||||
|
(not [?t :transaction/matched-rule])
|
||||||
|
(not [?t :transaction/payment])
|
||||||
|
[?t :transaction/client ?c]
|
||||||
|
[?c :client/code ?code]
|
||||||
|
(not [?t :transaction/vendor])
|
||||||
|
(not [?t :transaction/accounts])
|
||||||
|
[?t :transaction/description-original ?do]
|
||||||
|
[?t :transaction/amount ?amt]
|
||||||
|
[?t :transaction/bank-account ?b]
|
||||||
|
|
||||||
|
[?b :bank-account/name ?bn]
|
||||||
|
[?b :bank-account/type ?bat]
|
||||||
|
[?bat :db/ident ?bt]
|
||||||
|
[(name ?bt) ?bt2]
|
||||||
|
|
||||||
|
]
|
||||||
|
(dc/db auto-ap.datomic/conn)
|
||||||
|
#inst "2023-01-01")
|
||||||
|
(map (fn [[t code bn bat d date amt]]
|
||||||
|
[t code bn bat d (auto-ap.time/unparse-local (clj-time.coerce/to-date-time date) auto-ap.time/iso-date) amt]))
|
||||||
|
))
|
||||||
|
:quote? (constantly true))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn write-vendor-lookup []
|
||||||
|
(with-open [f (io/writer "/mnt/data/dev2/ml-test/input/vendors.csv")]
|
||||||
|
(csv/write-csv f
|
||||||
|
(into [["vendor" "vendor_name"]]
|
||||||
|
(->>
|
||||||
|
(dc/q '[:find ?v ?v-name
|
||||||
|
:where [?v :vendor/name ?v-name]
|
||||||
|
[_ :transaction/vendor ?v]
|
||||||
|
]
|
||||||
|
(dc/db conn))))
|
||||||
|
:separator \,)))
|
||||||
|
|
||||||
|
(defn write-vendor-client-lookup []
|
||||||
|
(with-open [f (io/writer "/mnt/data/dev2/ml-test/input/vendor-client-usage.csv")]
|
||||||
|
(csv/write-csv f
|
||||||
|
(into [["vendor" "client" "vendor_client_count"]]
|
||||||
|
(->>
|
||||||
|
(dc/q '[:find ?v ?c (count ?t)
|
||||||
|
:where
|
||||||
|
[?t :transaction/client ?c]
|
||||||
|
[?t :transaction/vendor ?v]
|
||||||
|
]
|
||||||
|
(dc/db conn))))
|
||||||
|
:separator \,)))
|
||||||
|
|
||||||
|
(defn write-account-lookup []
|
||||||
|
(with-open [f (io/writer "/mnt/data/dev2/ml-test/input/accounts.csv")]
|
||||||
|
(csv/write-csv f
|
||||||
|
(into [["account" "numeric_code"]]
|
||||||
|
(->>
|
||||||
|
(dc/q '[:find ?a ?nm
|
||||||
|
:where [?a :account/numeric-code ?nm]]
|
||||||
|
(dc/db conn))))
|
||||||
|
:separator \,)))
|
||||||
|
|
||||||
|
(defn write-account-client-lookup []
|
||||||
|
(with-open [f (io/writer "/mnt/data/dev2/ml-test/input/account-client-usage.csv")]
|
||||||
|
(csv/write-csv f
|
||||||
|
(into [["client" "account" "client_account_count"]]
|
||||||
|
(->>
|
||||||
|
(dc/q '[:find ?c ?a (count ?t)
|
||||||
|
:where
|
||||||
|
[?t :transaction/client ?c]
|
||||||
|
[?t :transaction/accounts ?ta]
|
||||||
|
[?ta :transaction-account/account ?a]]
|
||||||
|
(dc/db conn))))
|
||||||
|
:separator \,)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn write-all-training []
|
||||||
|
(write-account-lookup)
|
||||||
|
(write-vendor-lookup)
|
||||||
|
(write-account-client-lookup)
|
||||||
|
(write-vendor-client-lookup)
|
||||||
|
(write-account-training-data)
|
||||||
|
(write-vendor-training-data))
|
||||||
71
scratch-sessions/ingest_ml.clj
Normal file
71
scratch-sessions/ingest_ml.clj
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
(ns ingest-ml
|
||||||
|
(:require [datomic.client.api :as dc]
|
||||||
|
[clojure.java.io :as io]
|
||||||
|
[clojure.data.csv :as csv]
|
||||||
|
[iol-ion.tx :refer [upsert-entity]]
|
||||||
|
[auto-ap.datomic :refer [conn]]))
|
||||||
|
|
||||||
|
(println "hi")
|
||||||
|
|
||||||
|
(defn boot []
|
||||||
|
(user/init-repl)
|
||||||
|
(user/start-db))
|
||||||
|
|
||||||
|
|
||||||
|
(defn reset-inference []
|
||||||
|
(doseq [p (->>
|
||||||
|
(dc/q '[:find ?t
|
||||||
|
:where [?t :transaction/recommended-account]]
|
||||||
|
(dc/db conn))
|
||||||
|
(map (fn [[t]]
|
||||||
|
`(upsert-entity
|
||||||
|
~{:db/id t
|
||||||
|
:transaction/recommended-account nil
|
||||||
|
:tranasction/vendor-confidence nil
|
||||||
|
:transaction/account-confidence nil
|
||||||
|
:transaction/recommended-vendor nil})))
|
||||||
|
|
||||||
|
(partition-all 100))]
|
||||||
|
(dc/transact conn {:tx-data p})))
|
||||||
|
|
||||||
|
(defn read-inference []
|
||||||
|
(with-open [reader (io/reader "/mnt/data/dev2/ml-test/inference-outcome.csv")]
|
||||||
|
(->> (csv/read-csv reader)
|
||||||
|
(into []
|
||||||
|
(comp
|
||||||
|
(drop 1)
|
||||||
|
(map (fn [[_ transaction best-vendor best-account account-confidence]]
|
||||||
|
{:db/id (Long/parseLong transaction)
|
||||||
|
:transaction/recommended-account (Long/parseLong best-account)
|
||||||
|
:transaction/account-confidence (Double/parseDouble account-confidence)
|
||||||
|
:transaction/recommended-vendor (Long/parseLong best-vendor)}
|
||||||
|
)))))))
|
||||||
|
|
||||||
|
(defn apply-inference [inference]
|
||||||
|
(doseq [p (->> inference (partition-all 100))]
|
||||||
|
(dc/transact conn {:tx-data p})))
|
||||||
|
|
||||||
|
|
||||||
|
(defn check-applied-inference []
|
||||||
|
(clojure.pprint/pprint
|
||||||
|
(->>
|
||||||
|
(dc/q '[:find ?code ?bac ?do ?amount ?vc ?rvn ?ac ?ran
|
||||||
|
:in $
|
||||||
|
:where
|
||||||
|
[?t :transaction/recommended-account ?ra]
|
||||||
|
[?ra :account/name ?ran]
|
||||||
|
[?t :transaction/account-confidence ?ac]
|
||||||
|
|
||||||
|
[?t :transaction/recommended-vendor ?rv]
|
||||||
|
[?rv :vendor/name ?rvn]
|
||||||
|
|
||||||
|
[?t :transaction/description-original ?do]
|
||||||
|
[?t :transaction/client ?c]
|
||||||
|
[?c :client/code ?code]
|
||||||
|
[?t :transaction/bank-account ?ba]
|
||||||
|
[?ba :bank-account/code ?bac]
|
||||||
|
[?t :transaction/amount ?amount]
|
||||||
|
]
|
||||||
|
(dc/db conn))
|
||||||
|
(shuffle)
|
||||||
|
(take 10))))
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
:refer [wrap-admin wrap-client-redirect-unauthenticated wrap-secure]]
|
:refer [wrap-admin wrap-client-redirect-unauthenticated wrap-secure]]
|
||||||
[auto-ap.ssr.admin :as admin]
|
[auto-ap.ssr.admin :as admin]
|
||||||
[auto-ap.ssr.auth :as auth]
|
[auto-ap.ssr.auth :as auth]
|
||||||
|
[auto-ap.ssr.transaction.insights :as insights]
|
||||||
[auto-ap.ssr.company.company-1099 :as company-1099]
|
[auto-ap.ssr.company.company-1099 :as company-1099]
|
||||||
[auto-ap.ssr.company-dropdown :as company-dropdown]))
|
[auto-ap.ssr.company-dropdown :as company-dropdown]))
|
||||||
|
|
||||||
@@ -19,5 +20,10 @@
|
|||||||
:company-1099 (wrap-client-redirect-unauthenticated (wrap-secure company-1099/page))
|
:company-1099 (wrap-client-redirect-unauthenticated (wrap-secure company-1099/page))
|
||||||
:company-1099-vendor-table (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-table))
|
:company-1099-vendor-table (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-table))
|
||||||
:company-1099-vendor-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-dialog))
|
:company-1099-vendor-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-dialog))
|
||||||
:company-1099-vendor-save (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-save))})
|
:company-1099-vendor-save (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-save))
|
||||||
|
:transaction-insights (wrap-client-redirect-unauthenticated (wrap-secure insights/page))
|
||||||
|
:transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-secure insights/insight-table))
|
||||||
|
:transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-secure insights/transaction-rows))
|
||||||
|
:transaction-insight-approve (wrap-client-redirect-unauthenticated (wrap-secure insights/approve))
|
||||||
|
:transaction-insight-explain (wrap-client-redirect-unauthenticated (wrap-secure insights/explain))})
|
||||||
|
|
||||||
|
|||||||
218
src/clj/auto_ap/ssr/transaction/insights.clj
Normal file
218
src/clj/auto_ap/ssr/transaction/insights.clj
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
(ns auto-ap.ssr.transaction.insights
|
||||||
|
(:require
|
||||||
|
[auto-ap.datomic :refer [conn visible-clients]]
|
||||||
|
[auto-ap.shared-views.company.sidebar :refer [company-side-bar]]
|
||||||
|
[auto-ap.ssr-routes :as ssr-routes]
|
||||||
|
[auto-ap.ssr.ui :refer [base-page]]
|
||||||
|
[auto-ap.ssr.utils :refer [html-response]]
|
||||||
|
[auto-ap.time :as atime]
|
||||||
|
[bidi.bidi :as bidi]
|
||||||
|
[clj-time.coerce :as coerce]
|
||||||
|
[datomic.client.api :as dc]
|
||||||
|
[hiccup2.core :as hiccup]
|
||||||
|
[clj-time.core :as time]))
|
||||||
|
|
||||||
|
(def pull-expr [:transaction/description-original
|
||||||
|
:db/id
|
||||||
|
:transaction/amount
|
||||||
|
{:transaction/client [:client/code]
|
||||||
|
:transaction/bank-account [:bank-account/code]
|
||||||
|
:transaction/recommended-vendor [:vendor/name :db/id]
|
||||||
|
:transaction/recommended-account [:account/name :account/numeric-code :db/id]}
|
||||||
|
:transaction/account-confidence
|
||||||
|
:transaction/date
|
||||||
|
])
|
||||||
|
|
||||||
|
(defn transaction-recommendations [identity selected-client & {:keys [after]}]
|
||||||
|
(let [visible-clients (visible-clients identity)]
|
||||||
|
(->>
|
||||||
|
(dc/q '[:find (pull ?t pull-expr)
|
||||||
|
:in $ [?c ...] pull-expr
|
||||||
|
:where [?t :transaction/recommended-account]
|
||||||
|
[?t :transaction/client ?c]
|
||||||
|
[?t :transaction/approval-status :transaction-approval-status/unapproved]
|
||||||
|
(not [?t :transaction/vendor])]
|
||||||
|
(dc/db conn)
|
||||||
|
(if selected-client
|
||||||
|
[selected-client]
|
||||||
|
visible-clients)
|
||||||
|
pull-expr)
|
||||||
|
(map first)
|
||||||
|
(sort-by :transaction/date)
|
||||||
|
(reverse)
|
||||||
|
(drop-while (fn [x]
|
||||||
|
(if after
|
||||||
|
(not= (Long/parseLong after) (:db/id x))
|
||||||
|
false)))
|
||||||
|
(#(if after
|
||||||
|
(drop 1 %)
|
||||||
|
%))
|
||||||
|
(take 10)
|
||||||
|
(into []))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn transaction-row [r & {:keys [hide-actions? class last?]}]
|
||||||
|
[:tr (cond-> {:class class}
|
||||||
|
last? (assoc :hx-get (bidi/path-for ssr-routes/only-routes
|
||||||
|
:transaction-insight-rows
|
||||||
|
:after (:db/id r))
|
||||||
|
:hx-trigger "intersect once"
|
||||||
|
:hx-indicator "#insight-table"
|
||||||
|
:hx-swap "afterend"))
|
||||||
|
[:td {:style {:width "8em"}}(:client/code (:transaction/client r))]
|
||||||
|
[:td {:style {:width "10em"}} (:bank-account/code (:transaction/bank-account r))]
|
||||||
|
[:td {:style {:width "12em"}} (some-> (:transaction/date r) coerce/to-date-time (atime/unparse-local atime/normal-date))]
|
||||||
|
[:td {:style {:width "30em" :max-width "30em"}} (str (:transaction/description-original r))]
|
||||||
|
[:td {:style {:width "10em"}}
|
||||||
|
(if (> (:transaction/amount r) 0.0 )
|
||||||
|
[:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
|
||||||
|
[:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))])]
|
||||||
|
[:td {:style {:width "12em"}}
|
||||||
|
[:div [:div.tag (:vendor/name (:transaction/recommended-vendor r))]]
|
||||||
|
[:div [:div.tag (str (:account/numeric-code (:transaction/recommended-account r)) " - " (:account/name (:transaction/recommended-account r)))]]
|
||||||
|
[:div [:div.tag
|
||||||
|
{:class (cond
|
||||||
|
(> (:transaction/account-confidence r) 0.90)
|
||||||
|
"is-success is-light"
|
||||||
|
(> (:transaction/account-confidence r) 0.80)
|
||||||
|
"is-info is-light"
|
||||||
|
|
||||||
|
:else
|
||||||
|
"is-warning is-light")}
|
||||||
|
(str "%" (Math/round (* 100.0 (:transaction/account-confidence r))))]]]
|
||||||
|
[:td
|
||||||
|
(when-not hide-actions?
|
||||||
|
[:div.buttons
|
||||||
|
[:button.button {:hx-post (bidi/path-for ssr-routes/only-routes
|
||||||
|
:transaction-insight-approve
|
||||||
|
:transaction-id (:db/id r))
|
||||||
|
:hx-target "closest tr"}
|
||||||
|
[:i.fa.fa-thumbs-up ]]
|
||||||
|
[:button.button
|
||||||
|
[:i.fa.fa-thumbs-down ]]
|
||||||
|
[:a.button {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||||
|
:transaction-insight-explain
|
||||||
|
:transaction-id (:db/id r))
|
||||||
|
:hx-target "#modal-holder"
|
||||||
|
:hx-swap "beforeend"}
|
||||||
|
[:i.fa.fa-question ]]])]])
|
||||||
|
|
||||||
|
(defn approve [{:keys [identity session] {:keys [transaction-id]} :route-params}]
|
||||||
|
(html-response (transaction-row
|
||||||
|
(dc/pull (dc/db conn)
|
||||||
|
pull-expr
|
||||||
|
(Long/parseLong transaction-id))
|
||||||
|
:auto-remove? true
|
||||||
|
:hide-actions? true
|
||||||
|
:class "live-added")))
|
||||||
|
|
||||||
|
(defn explain [{:keys [identity session] {:keys [transaction-id]} :route-params}]
|
||||||
|
(let [r (dc/pull (dc/db conn)
|
||||||
|
pull-expr
|
||||||
|
(Long/parseLong transaction-id))
|
||||||
|
similar (->> {:query '[:find ?date ?do ?amt
|
||||||
|
:in $ ?tr
|
||||||
|
:where
|
||||||
|
[(iol-ion.query/recent-date 180) ?start-date]
|
||||||
|
[?tr :transaction/client ?c]
|
||||||
|
[?tr :transaction/recommended-account ?a ]
|
||||||
|
[?tr :transaction/recommended-vendor ?v ]
|
||||||
|
[?t2 :transaction/client ?c]
|
||||||
|
[?t2 :transaction/date ?date]
|
||||||
|
[(>= ?date ?start-date)]
|
||||||
|
[?t2 :transaction/vendor ?v]
|
||||||
|
[?t2 :transaction/accounts ?a2]
|
||||||
|
[?a2 :transaction-account/account ?a]
|
||||||
|
[?t2 :transaction/description-original ?do]
|
||||||
|
[?t2 :transaction/amount ?amt]]
|
||||||
|
:args [(dc/db conn)
|
||||||
|
(Long/parseLong transaction-id)]
|
||||||
|
:limit 5}
|
||||||
|
dc/q
|
||||||
|
sort
|
||||||
|
reverse)]
|
||||||
|
(html-response [:div.modal.is-active.wide
|
||||||
|
[:div.modal-background {"_" (hiccup/raw "on click remove <#modal-holder div/>")}]
|
||||||
|
[:div.modal-card
|
||||||
|
[:div.modal-card-head
|
||||||
|
[:h1.title "Similar transactions"]
|
||||||
|
[:div.tags
|
||||||
|
[:div.tag.is-large.is-info.is-light (:vendor/name (:transaction/recommended-vendor r))]
|
||||||
|
[:div.tag.is-large.is-info.is-light (str (:account/numeric-code (:transaction/recommended-account r)) " - " (:account/name (:transaction/recommended-account r)))]]]
|
||||||
|
[:div.modal-card-body
|
||||||
|
[:table.table
|
||||||
|
[:thead
|
||||||
|
[:tr
|
||||||
|
[:td "Date"]
|
||||||
|
[:td "Description"]
|
||||||
|
[:td "Amount"]]]
|
||||||
|
[:tbody
|
||||||
|
[:tr
|
||||||
|
[:th (some-> r :transaction/date coerce/to-date-time (atime/unparse-local atime/normal-date))]
|
||||||
|
[:th (-> r :transaction/description-original)]
|
||||||
|
[:th (if (> (-> r :transaction/amount) 0.0 )
|
||||||
|
[:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
|
||||||
|
[:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))])]]
|
||||||
|
(for [[date description amt] similar]
|
||||||
|
[:tr
|
||||||
|
[:td (some-> date coerce/to-date-time (atime/unparse-local atime/normal-date))]
|
||||||
|
[:td description]
|
||||||
|
[:td (if (> amt 0.0 )
|
||||||
|
[:div.tag.is-success.is-light (str "$" (Math/round amt))]
|
||||||
|
[:div.tag.is-danger.is-light (str "$" (Math/round amt))])]])]]
|
||||||
|
|
||||||
|
]]
|
||||||
|
[:button.modal-close.is-large {"_" (hiccup/raw "on click remove <#modal-holder div/>")}]])))
|
||||||
|
|
||||||
|
(defn transaction-rows* [{:keys [selected-client identity after]}]
|
||||||
|
(let [recommendations (transaction-recommendations identity selected-client :after after)]
|
||||||
|
(if (seq recommendations)
|
||||||
|
(for [r recommendations
|
||||||
|
:let [last? (= r (last recommendations))]]
|
||||||
|
(transaction-row r :last? last?))
|
||||||
|
[:tr [:td.has-text-centered.has-text-gray {:colspan 7 }
|
||||||
|
[:i "That's the last of 'em!"]]])))
|
||||||
|
|
||||||
|
(defn transaction-rows [{:keys [session identity route-params]}]
|
||||||
|
(html-response (transaction-rows* {:selected-client (-> session :client :db/id)
|
||||||
|
:identity identity
|
||||||
|
:after (:after route-params)})))
|
||||||
|
|
||||||
|
(defn insight-table* [{:keys [selected-client identity]}]
|
||||||
|
[:div#insight-table {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||||
|
:transaction-insight-table
|
||||||
|
:request-method :get)
|
||||||
|
:hx-trigger "clientSelected from:body"
|
||||||
|
:hx-swap "outerHTML swap:100ms"}
|
||||||
|
|
||||||
|
[:table.table
|
||||||
|
[:thead
|
||||||
|
[:tr
|
||||||
|
[:td "Client"]
|
||||||
|
[:td "Account"]
|
||||||
|
[:td "Date"]
|
||||||
|
[:td "Description"]
|
||||||
|
[:td "Amount"]
|
||||||
|
[:td "Vendor / Account"]
|
||||||
|
[:td "action"]]]
|
||||||
|
[:tbody
|
||||||
|
(transaction-rows* {:selected-client selected-client
|
||||||
|
:identity identity})]]
|
||||||
|
[:div.container.htmx-indicator
|
||||||
|
[:div.column.is-4.is-offset-4.has-text-centered
|
||||||
|
[:div.loader.is-loading.is-active.big.is-centered]]]])
|
||||||
|
|
||||||
|
(defn insight-table [{:keys [session identity]}]
|
||||||
|
(html-response (insight-table* {:selected-client
|
||||||
|
(-> session :client :db/id)
|
||||||
|
:identity identity})))
|
||||||
|
|
||||||
|
(defn page [{:keys [identity matched-route session] :as request}]
|
||||||
|
(base-page
|
||||||
|
request
|
||||||
|
[:div
|
||||||
|
[:h1.title "Transaction Insights"]
|
||||||
|
(insight-table* {:selected-client
|
||||||
|
(-> session :client :db/id)
|
||||||
|
:identity identity})]
|
||||||
|
[:div (company-side-bar matched-route)]))
|
||||||
@@ -35,9 +35,12 @@
|
|||||||
[:link {:rel "stylesheet", :href "https://unpkg.com/placeholder-loading/dist/css/placeholder-loading.min.css"}]
|
[:link {:rel "stylesheet", :href "https://unpkg.com/placeholder-loading/dist/css/placeholder-loading.min.css"}]
|
||||||
#_[:link {:rel "stylesheet", :href "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/css/autoComplete.min.css"}]
|
#_[:link {:rel "stylesheet", :href "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/css/autoComplete.min.css"}]
|
||||||
[:script {:src "https://unpkg.com/hyperscript.org@0.9.7"}]
|
[:script {:src "https://unpkg.com/hyperscript.org@0.9.7"}]
|
||||||
[:script {:src "https://unpkg.com/htmx.org@1.8.4"
|
[:script {:src "https://unpkg.com/@popperjs/core@2"}]
|
||||||
|
#_[:script {:src "https://unpkg.com/htmx.org@1.8.4"
|
||||||
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
|
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
|
||||||
:crossorigin= "anonymous"}]
|
:crossorigin= "anonymous"}]
|
||||||
|
[:script {:src "https://unpkg.com/htmx.org@1.9.0/dist/htmx.js"
|
||||||
|
:crossorigin= "anonymous"}]
|
||||||
[:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async" }]]
|
[:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async" }]]
|
||||||
[:script {:type "text/javascript", :src "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/autoComplete.min.js"}]
|
[:script {:type "text/javascript", :src "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/autoComplete.min.js"}]
|
||||||
[:body
|
[:body
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
#"/search/?" :admin-history-search
|
#"/search/?" :admin-history-search
|
||||||
["/" [#"\d+" :entity-id] #"/?"] :admin-history-search
|
["/" [#"\d+" :entity-id] #"/?"] :admin-history-search
|
||||||
["/inspect/" [#"\d+" :entity-id] #"/?"] :admin-history-inspect}}
|
["/inspect/" [#"\d+" :entity-id] #"/?"] :admin-history-inspect}}
|
||||||
|
"transaction" {"/insights" {"" :transaction-insights
|
||||||
|
"/table" :transaction-insight-table
|
||||||
|
["/approve/" [#"\d+" :transaction-id]] {:post :transaction-insight-approve}
|
||||||
|
["/rows/" [#"\d+" :after]] {:get :transaction-insight-rows}
|
||||||
|
["/explain/" [#"\d+" :transaction-id]] {:get :transaction-insight-explain}}}
|
||||||
"company" {"/dropdown" :company-dropdown-contents
|
"company" {"/dropdown" :company-dropdown-contents
|
||||||
"/active" {:put :active-client}
|
"/active" {:put :active-client}
|
||||||
"/1099" :company-1099
|
"/1099" :company-1099
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
(ns auto-ap.views.pages.transactions.side-bar
|
(ns auto-ap.views.pages.transactions.side-bar
|
||||||
(:require
|
(:require
|
||||||
[auto-ap.routes :as routes]
|
[auto-ap.routes :as routes]
|
||||||
|
[auto-ap.ssr-routes :as ssr-routes]
|
||||||
[auto-ap.subs :as subs]
|
[auto-ap.subs :as subs]
|
||||||
[auto-ap.views.components.bank-account-filter
|
[auto-ap.views.components.bank-account-filter
|
||||||
:refer [bank-account-filter]]
|
:refer [bank-account-filter]]
|
||||||
@@ -53,6 +54,13 @@
|
|||||||
[:span {:class "icon icon-task-list-disable" :style {:font-size "25px"}}]
|
[:span {:class "icon icon-task-list-disable" :style {:font-size "25px"}}]
|
||||||
|
|
||||||
[:span {:class "name"} "Excluded"]]]
|
[:span {:class "name"} "Excluded"]]]
|
||||||
|
[:li.menu-item
|
||||||
|
[:a.item {:href (bidi/path-for ssr-routes/only-routes :transaction-insights)
|
||||||
|
:class [(active-when ap = :transaction-insights)]}
|
||||||
|
|
||||||
|
[:span {:class "icon icon-task-list-disable" :style {:font-size "25px"}}]
|
||||||
|
|
||||||
|
[:span {:class "name"} "Insights"]]]
|
||||||
]]
|
]]
|
||||||
[:p.menu-label "Bank Account"]
|
[:p.menu-label "Bank Account"]
|
||||||
[:div
|
[:div
|
||||||
|
|||||||
Reference in New Issue
Block a user