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