The bulk-code "Requires Feedback" option submitted "requires_feedback" (underscore), which decoded to an enum keyword not present in the schema (idents use a hyphen), so selecting it failed validation. Use the hyphenated value and relabel the option, the reconciliation report header to "Client Review" to unify with the sidebar terminology. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
192 lines
11 KiB
Clojure
192 lines
11 KiB
Clojure
(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 {} "Bank Account")
|
|
(com/data-grid-header {} "Source count")
|
|
(com/data-grid-header {} "Synced count")
|
|
(com/data-grid-header {} "Approved transactions")
|
|
(com/data-grid-header {} "Unapproved transactions")
|
|
(com/data-grid-header {} "Client Review transactions")
|
|
(com/data-grid-header {} "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}
|
|
(when (> (count (:missing-transactions row)) 0)
|
|
[:div
|
|
(com/button {:x-tooltip.on.click "{content: ()=>$refs.tooltip.innerHTML, theme: 'light', allowHTML: true}"}
|
|
[:div.flex.gap-2.items-center
|
|
(count (:missing-transactions row))
|
|
[:div.w-4.h-4 svg/question]])
|
|
[:template {:x-ref "tooltip"}
|
|
(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]]]))))) |