(cloud) adds transaction insights

This commit is contained in:
2023-04-18 11:32:58 -07:00
parent ed37b19d6a
commit 69eaf42eda
11 changed files with 518 additions and 3 deletions

View File

@@ -4,6 +4,7 @@
:refer [wrap-admin wrap-client-redirect-unauthenticated wrap-secure]]
[auto-ap.ssr.admin :as admin]
[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-dropdown :as company-dropdown]))
@@ -19,5 +20,10 @@
: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-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))})

View 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)]))

View File

@@ -35,9 +35,12 @@
[: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"}]
[: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"
: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.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/autoComplete.min.js"}]
[:body

View File

@@ -6,6 +6,11 @@
#"/search/?" :admin-history-search
["/" [#"\d+" :entity-id] #"/?"] :admin-history-search
["/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
"/active" {:put :active-client}
"/1099" :company-1099

View File

@@ -1,6 +1,7 @@
(ns auto-ap.views.pages.transactions.side-bar
(:require
[auto-ap.routes :as routes]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.subs :as subs]
[auto-ap.views.components.bank-account-filter
:refer [bank-account-filter]]
@@ -53,6 +54,13 @@
[:span {:class "icon icon-task-list-disable" :style {:font-size "25px"}}]
[: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"]
[:div