Adds bank reconciliation report

This commit is contained in:
2024-04-24 21:27:19 -07:00
parent 04ad2c9b68
commit 99db96ad61
11 changed files with 401 additions and 29 deletions

View File

@@ -8,6 +8,7 @@
wrap-secure]]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.company.reports.expense :as company-expense-report]
[auto-ap.ssr.company.reports.reconciliation :as company-reconciliation-report]
[auto-ap.ssr.components :as com]
[auto-ap.ssr.grid-page-helper :as helper]
[auto-ap.ssr.svg :as svg]
@@ -138,7 +139,8 @@
{:company-reports page
:company-reports-table table
:company-reports-delete delete-report}
company-expense-report/key->handler))
company-expense-report/key->handler)
(into company-reconciliation-report/key->handler))
(fn [h]
(-> h
(wrap-secure)

View File

@@ -0,0 +1,204 @@
(ns auto-ap.ssr.company.reports.reconciliation
(:require [auto-ap.datomic :refer [conn pull-attr]]
[auto-ap.graphql.utils :refer [extract-client-ids]]
[auto-ap.import.intuit :refer [get-intuit-bank-accounts
intuits->transactions]]
[auto-ap.intuit.core :refer [get-transactions]]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components :as com]
[auto-ap.ssr.form-cursor :as fc]
[auto-ap.ssr.hx :as hx]
[auto-ap.ssr.ui :refer [base-page]]
[auto-ap.ssr.utils :refer [apply-middleware-to-all-handlers
clj-date-schema html-response
wrap-schema-enforce]]
[auto-ap.time :as atime]
[bidi.bidi :as bidi]
[cemerick.url :as url]
[clj-time.coerce :as coerce]
[datomic.api :as dc]
[auto-ap.ssr.svg :as svg]))
(defn report* [{:keys [request report]}]
[:div #_{:class "overflow-scroll min-w-full max-h-[700px]"}
(com/data-grid
{:headers (into
[(com/data-grid-header {:class "" #_"sticky left-0 z-60 bg-gray-100"} "Bank Account")
(com/data-grid-header {:class "" #_"sticky left-0 z-60 bg-gray-100"} "Source count")
(com/data-grid-header {:class "" #_"sticky left-0 z-60 bg-gray-100"} "Synced count")
(com/data-grid-header {:class "" #_"sticky left-0 z-60 bg-gray-100"} "Approved transactions")
(com/data-grid-header {:class "" #_"sticky left-0 z-60 bg-gray-100"} "Unapproved transactions")
(com/data-grid-header {:class "" #_"sticky left-0 z-60 bg-gray-100"} "Requires feedback transactions")
(com/data-grid-header {:class "" #_"sticky left-0 z-60 bg-gray-100"} "Missing transactions")])
#_#_:thead-params {:class "sticky top-0 z-50"}}
(for [row report]
(let [matches? (= (:external-transaction-count row)
(:integreat-transaction-count row))
class (if matches? "bg-primary-200 text-primary-900"
"bg-red-200 text-red-900")]
(com/data-grid-row
{}
(com/data-grid-cell {:class class}
(:bank-account/name row))
(com/data-grid-cell {:class class}
(:external-transaction-count row))
(com/data-grid-cell {:class class}
(:integreat-transaction-count row))
(com/data-grid-cell {:class class}
(:approved-count row))
(com/data-grid-cell {:class class}
(:unapproved-count row))
(com/data-grid-cell {:class class}
(:requires-feedback-count row))
(com/data-grid-cell {:class class}
[:div { :x-data (hx/json {:popper nil
:hovering false})
"x-init" "popper = Popper.createPopper($refs.hover_target, $refs.tooltip, {placement: 'bottom', strategy:'fixed', modifiers: [{name: 'preventOverflow'}, {name: 'offset', options: {offset: [0, 10]}}]});"}
(com/button {"x-ref" "hover_target"
"@click.prevent" "hovering=!hovering; $nextTick(() => popper.update())"}
[:div.flex.gap-2.items-center
(count (:missing-transactions row))
[:div.w-4.h-4 svg/question]
])
[:div (hx/alpine-appear {:x-ref "tooltip"
:x-show "hovering"
:class "bg-gray-100 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 p-4"})
(com/data-grid {:headers [(com/data-grid-header {} "Date")
(com/data-grid-header {} "Amount")]}
(for [r (:missing-transactions row)]
(com/data-grid-row {}
(com/data-grid-cell {}
(atime/unparse-local (coerce/to-date-time (:transaction/date r)) atime/normal-date))
(com/data-grid-cell {}
(format "$%,.2f" (:transaction/amount r))))))
]
])))))])
(defn reconciliation-card* [{:keys [request report]}]
(com/content-card {:class "w-full" :id "reconciliation-report"}
[:div {:class "flex flex-col px-8 py-8 space-y-3"}
[:div
[:h1.text-2xl.mb-3.font-bold "Bank Reconciliation Report"]
[:form {:hx-get (bidi.bidi/path-for ssr-routes/only-routes :company-reconciliation-report-card)
:hx-target "#reconciliation-report"
:hx-swap "outerHTML"}
(fc/start-form
(:query-params request)
(:form-errors request)
[:div.flex.gap-2
(fc/with-field :start-date
(com/validated-field {:label "Start"
:errors (fc/field-errors)}
[:div {:class "w-64"}
(com/date-input {:name (fc/field-name)
:class "w-64"
:value (some-> (fc/field-value)
(atime/unparse-local atime/normal-date)) })]))
(fc/with-field :end-date
(com/validated-field {:label "End"
:errors (fc/field-errors)}
[:div {:class "w-64"}
(com/date-input {:name (fc/field-name)
:class "w-64"
:value (some-> (fc/field-value)
(atime/unparse-local atime/normal-date)) })]))
(com/button {:color :primary :class "self-center w-24"} "Run")])]
(if report
(report* {:request request :report report})
[:div "Please choose a time range to run the report"])
]]))
(defn page [request]
(base-page
request
(com/page {:nav com/company-aside-nav
:client-selection (:client-selection request)
:client (:client request)
:clients (:clients request)
:identity (:identity request)
:app-params {:hx-get (bidi/path-for ssr-routes/only-routes :company-reconciliation-report)
:hx-trigger "clientSelected from:body"
:hx-select "#app-contents"
:hx-swap "outerHTML swap:300ms"}}
(com/breadcrumbs {}
[:a {:href (bidi/path-for ssr-routes/only-routes :company)}
"My Company"]
[:a {:href (bidi/path-for ssr-routes/only-routes :company-reconciliation-report)}
"Reconciliation Report"])
(reconciliation-card* {:request request :report nil}))
"My Company"))
(defn normalize-query-params [request]
(-> request
:query-params
(update :vendor-id :db/id)
(update :account-id :db/id)
(update :start-date #(atime/unparse-local % atime/normal-date))
(update :end-date #(atime/unparse-local % atime/normal-date))
url/map->query))
(defn get-report-data [start-date end-date client-ids]
(let [client-codes (map first (dc/q '[:find ?cc :in $ [?c ...] :where [?c :client/code ?cc]] (dc/db conn ) client-ids))]
(for [[ib ba c] (seq (apply get-intuit-bank-accounts (dc/db conn) client-codes))
:let [raw-transactions (get-transactions (atime/unparse-local start-date atime/iso-date)
(atime/unparse-local end-date atime/iso-date)
ib)
ideal-transactions (intuits->transactions raw-transactions ba c)
found-transactions (when (seq ideal-transactions)
(into {} (dc/q '[:find ?si (count ?t)
:in $ [?eid ...]
:where
[?t :transaction/id ?eid]
[?t :transaction/approval-status ?s]
[?s :db/ident ?si]]
(dc/db conn)
(map :transaction/id ideal-transactions))))
missing-transaction-ids (when (seq ideal-transactions)
(->>
(dc/q '[:find ?eid
:in $ [?eid ...]
:where (not [_ :transaction/id ?eid])]
(dc/db conn)
(map :transaction/id ideal-transactions))
(map first)
(into #{})))
missing-transactions (filter (comp missing-transaction-ids :transaction/id) ideal-transactions)]]
{:bank-account/name (pull-attr (dc/db conn) :bank-account/name ba)
:external-transaction-count (count raw-transactions)
:integreat-transaction-count (reduce + 0 (vals found-transactions))
:approved-count (:transaction-approval-status/approved found-transactions 0)
:unapproved-count (:transaction-approval-status/unapproved found-transactions 0)
:requires-feedback-count (:transaction-approval-status/requires-feedback found-transactions 0)
:missing-transactions missing-transactions})))
(defn card [{ {:keys [start-date end-date]} :query-params :as request}]
(let [client-ids (extract-client-ids (:clients request)
(:client-id request)
(when (:client-code request)
[:client/code (:client-code request)]))
report (get-report-data start-date end-date client-ids)]
(html-response
(reconciliation-card* {:request request
:report report})
:headers {"hx-push-url" (str "?" (normalize-query-params request))})))
(def key->handler
(apply-middleware-to-all-handlers
{:company-reconciliation-report page
:company-reconciliation-report-card card}
(fn [h]
(-> h
(wrap-schema-enforce :query-schema
[:map {:default {}}
[:start-date {:optional true}
[:maybe clj-date-schema]]
[:end-date {:optional true}
[:maybe clj-date-schema]] ])))))

View File

@@ -294,6 +294,15 @@
:company-expense-report)
:hx-boost true}
"Expense Report")]
(when (can? (:identity request)
{:subject :reconciliation-report}))
[:li
(menu-button- {:icon svg/report
:active? (= :company-reconciliation-report (:matched-route request))
:href (bidi/path-for ssr-routes/only-routes
:company-reconciliation-report)
:hx-boost true}
"Reconciliation Report")]
[:li
(menu-button- {:icon svg/bank
:active? (= :company-plaid (:matched-route request))