Add vendor pre-population for bulk code and individual edit forms

- Add vendor-changed HTMX handlers for both bulk code and individual edit
- Pre-populate default account at 100% when vendor is selected and no accounts exist
- Fix render-accounts-section to render from step-params correctly
- Change bulk code vendor-changed from hx-get to hx-post to include form data
- Add routes for vendor-changed endpoints
- Update e2e tests to cover vendor pre-population
- Run lein cljfmt fix across codebase
This commit is contained in:
2026-05-21 14:45:19 -07:00
parent 8bd0cee1b1
commit ba87805d4c
210 changed files with 8694 additions and 9627 deletions

View File

@@ -24,32 +24,32 @@
(def query-schema (mc/schema
[:maybe
(into [:map {} ]
(into [:map {}]
default-grid-fields-schema)]))
(def vendor-read '[:db/id
:vendor/name
{:vendor/legal-entity-1099-type [:db/ident]}
{:vendor/legal-entity-tin-type [:db/ident]}
{:vendor/address [:address/street1
:address/city
:address/state
:address/zip]}
:vendor/name
{:vendor/legal-entity-1099-type [:db/ident]}
{:vendor/legal-entity-tin-type [:db/ident]}
{:vendor/address [:address/street1
:address/city
:address/state
:address/zip]}
{:vendor/default-account [:account/name]}
:vendor/legal-entity-tin
:vendor/legal-entity-name
:vendor/legal-entity-first-name
:vendor/legal-entity-middle-name
:vendor/legal-entity-last-name])
:vendor/legal-entity-tin
:vendor/legal-entity-name
:vendor/legal-entity-first-name
:vendor/legal-entity-middle-name
:vendor/legal-entity-last-name])
(defn sum-for-client-vendor [client-id vendor-id]
(ffirst (dc/q '[:find
(sum ?a)
:with ?d
:with ?d
:in $ ?c ?v
:where
[?p :payment/client ?c]
[?p :payment/date ?d ]
[?p :payment/date ?d]
[(>= ?d #inst "2025-01-01T08:00")]
[(< ?d #inst "2026-01-01T08:00")]
[?p :payment/type :payment-type/check]
@@ -64,11 +64,11 @@
(pull ?c [:client/code :db/id])
(pull ?v vendor-read)
(sum ?a)
:with ?d
:with ?d
:in $ [?c ...] vendor-read
:where
[?p :payment/client ?c]
[?p :payment/date ?d ]
[?p :payment/date ?d]
[(>= ?d #inst "2025-01-01T08:00")]
[(< ?d #inst "2026-01-01T08:00")]
[?p :payment/type :payment-type/check]
@@ -78,105 +78,101 @@
trimmed-clients
vendor-read)
all (->> results
(filter (fn [[_ _ a]]
(>= (or a 0.0) 600.0)))
(sort-by (fn [[client _ amount]]
[(:client/code client ) amount]))
(into []))
(filter (fn [[_ _ a]]
(>= (or a 0.0) 600.0)))
(sort-by (fn [[client _ amount]]
[(:client/code client) amount]))
(into []))
paginated (apply-pagination-raw {:start (:start query-params)
:per-page (:per-page query-params)} all)]
[(:entries paginated) (:count paginated)]))
(def grid-page
(helper/build
{:id "entity-table"
:nav com/company-aside-nav
:id-fn (comp :db/id second)
:fetch-page fetch-page
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
:company)}
"My Company"]
(helper/build
{:id "entity-table"
:nav com/company-aside-nav
:id-fn (comp :db/id second)
:fetch-page fetch-page
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
:company)}
"My Company"]
[:a {:href (bidi/path-for ssr-routes/only-routes
:company-1099)}
"1099 Vendor Info"]]
:title "1099 Vendors"
:entity-name "Vendors"
:query-schema query-schema
:route :company-1099-vendor-table
:row-buttons (fn [request e]
[(com/icon-button {:hx-get (url (bidi/path-for ssr-routes/only-routes
:company-1099-vendor-dialog
:vendor-id (:db/id (second e)))
{:client-id (:db/id (first e))})}
svg/pencil)])
:headers [{:key "Client"
:name "Client"
:sort-key "client"
:render (comp :client/code first)}
{:key "vendor-name"
:name "Vendor Name"
:sort-key "vendor"
:render (fn [[_ vendor]]
[:div.flex.whitespace-nowrap.items-center.gap-4
[:div [:div (:vendor/name vendor)]
[:div.text-sm.text-gray-400
(or (-> vendor :vendor/legal-entity-name not-empty)
(str (-> vendor :vendor/legal-entity-first-name) " "
(-> vendor :vendor/legal-entity-middle-name) " "
(-> vendor :vendor/legal-entity-last-name)))]]
(when-let [t99-type (some-> vendor :vendor/legal-entity-1099-type :db/ident name)]
(com/pill
{:class "text-xs font-medium"
:color :primary}
(str/capitalize t99-type))
)])}
{:key "tin"
:name "TIN"
:sort-key "tin"
:show-starting "md"
:render (fn [[_ vendor]]
[:div.flex.gap-4
(when-let [tin (-> vendor :vendor/legal-entity-tin)]
[:span {:class "text-xs font-medium py-0.5 "}
tin])
(when-let [tin-type (some-> vendor :vendor/legal-entity-tin-type :db/ident name)]
(com/pill {:class "text-xs font-medium"
:color :yellow}
(name tin-type)))]
)}
{:key "expense-account"
:name "Expense Account"
:show-starting "md"
:render (fn [[_ vendor]]
[:div.flex.gap-4
(when-let [tin (-> vendor :vendor/default-account :account/name)]
[:span {:class "text-xs font-medium py-0.5 "}
tin]) ])}
{:key "address"
:name "Address"
:sort-key "address"
:show-starting "lg"
:render (fn [[_ vendor]]
(if (-> vendor :vendor/address :address/street1)
[:a {:href (bidi/path-for ssr-routes/only-routes
:company-1099)}
"1099 Vendor Info"]]
:title "1099 Vendors"
:entity-name "Vendors"
:query-schema query-schema
:route :company-1099-vendor-table
:row-buttons (fn [request e]
[(com/icon-button {:hx-get (url (bidi/path-for ssr-routes/only-routes
:company-1099-vendor-dialog
:vendor-id (:db/id (second e)))
{:client-id (:db/id (first e))})}
svg/pencil)])
:headers [{:key "Client"
:name "Client"
:sort-key "client"
:render (comp :client/code first)}
{:key "vendor-name"
:name "Vendor Name"
:sort-key "vendor"
:render (fn [[_ vendor]]
[:div.flex.whitespace-nowrap.items-center.gap-4
[:div [:div (:vendor/name vendor)]
[:div.text-sm.text-gray-400
(or (-> vendor :vendor/legal-entity-name not-empty)
(str (-> vendor :vendor/legal-entity-first-name) " "
(-> vendor :vendor/legal-entity-middle-name) " "
(-> vendor :vendor/legal-entity-last-name)))]]
(when-let [t99-type (some-> vendor :vendor/legal-entity-1099-type :db/ident name)]
(com/pill
{:class "text-xs font-medium"
:color :primary}
(str/capitalize t99-type)))])}
{:key "tin"
:name "TIN"
:sort-key "tin"
:show-starting "md"
:render (fn [[_ vendor]]
[:div.flex.gap-4
(when-let [tin (-> vendor :vendor/legal-entity-tin)]
[:span {:class "text-xs font-medium py-0.5 "}
tin])
(when-let [tin-type (some-> vendor :vendor/legal-entity-tin-type :db/ident name)]
(com/pill {:class "text-xs font-medium"
:color :yellow}
(name tin-type)))])}
{:key "expense-account"
:name "Expense Account"
:show-starting "md"
:render (fn [[_ vendor]]
[:div.flex.gap-4
(when-let [tin (-> vendor :vendor/default-account :account/name)]
[:span {:class "text-xs font-medium py-0.5 "}
tin])])}
{:key "address"
:name "Address"
:sort-key "address"
:show-starting "lg"
:render (fn [[_ vendor]]
(if (-> vendor :vendor/address :address/street1)
[:div
[:div (-> vendor :vendor/address :address/street1)] " "
[:div
[:div (-> vendor :vendor/address :address/street1)] " "
[:div
(-> vendor :vendor/address :address/street2)] " "
[:div
(-> vendor :vendor/address :address/city) " "
(-> vendor :vendor/address :address/state) ","
(-> vendor :vendor/address :address/zip)]]
[:p.text-sm.italic.text-gray-400 "No address"]))}
{:key "paid"
:name "Paid"
:sort-key "paid"
:render (fn [[_ _ paid]]
(com/pill {:class "text-xs font-medium"
:color :primary}
"Paid $" (Math/round paid)))}]}))
(-> vendor :vendor/address :address/street2)] " "
[:div
(-> vendor :vendor/address :address/city) " "
(-> vendor :vendor/address :address/state) ","
(-> vendor :vendor/address :address/zip)]]
[:p.text-sm.italic.text-gray-400 "No address"]))}
{:key "paid"
:name "Paid"
:sort-key "paid"
:render (fn [[_ _ paid]]
(com/pill {:class "text-xs font-medium"
:color :primary}
"Paid $" (Math/round paid)))}]}))
(def table* (partial helper/table* grid-page))
(def row* (partial helper/row* grid-page))
@@ -185,7 +181,6 @@
{:keys [vendor-id]} :route-params
{:keys [client-id]} :query-params}]
(assert-can-see-client identity client-id)
@(dc/transact conn [[:upsert-entity (-> form-params
@@ -198,30 +193,28 @@
(:address/zip a)
(:db/id a))
a
nil)) ))]])
(html-response
nil))))]])
(html-response
(row* identity [(dc/pull (dc/db conn) [:db/id :client/code] client-id)
(dc/pull (dc/db conn) vendor-read vendor-id)
(sum-for-client-vendor client-id vendor-id)
] {:flash? true})
:headers {"hx-trigger" "modalclose"
"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" vendor-id)}))
(row* identity [(dc/pull (dc/db conn) [:db/id :client/code] client-id)
(dc/pull (dc/db conn) vendor-read vendor-id)
(sum-for-client-vendor client-id vendor-id)] {:flash? true})
:headers {"hx-trigger" "modalclose"
"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" vendor-id)}))
(def default-vendor-read '[* {[:vendor/legal-entity-1099-type :xform iol-ion.query/ident] [:db/ident]
[:vendor/legal-entity-tin-type :xform iol-ion.query/ident] [:db/ident]}])
(def form-schema (mc/schema [:map
[:vendor/address {:default {}}
[:maybe
[:map
[:map
[:db/id {:optional true} [:maybe entity-id]]
[:address/street1 {:optional true} [:maybe [:string {:decode/string strip}]]]
[:address/street2 {:optional true} [:maybe [:string {:decode/string strip}]]]
[:address/city {:optional true} [:maybe [:string {:decode/string strip}]]]
[:address/state {:optional true} [:maybe [:string {:decode/string strip}]]]
[:address/zip {:optional true} [:maybe [:re { :error/message "invalid zip"
[:address/zip {:optional true} [:maybe [:re {:error/message "invalid zip"
:decode/string strip} #"^(\d{5}|)$"]]]]]]
[:vendor/legal-entity-name {:optional true} [:maybe [:string {:decode/string strip}]]]
[:vendor/legal-entity-first-name {:optional true} [:maybe [:string {:decode/string strip}]]]
@@ -237,131 +230,131 @@
(when entity
(mc/decode form-schema entity main-transformer))
{})
form-errors
(modal-response
(com/modal
{}
[:form {:hx-post (url (bidi/path-for ssr-routes/only-routes
:company-1099-vendor-save
:request-method :post
:vendor-id vendor-id)
{:client-id client-id})
:class "w-full h-full max-w-2xl"
:hx-swap "outerHTML swap:300ms"}
form-errors
(modal-response
(com/modal
{}
[:form {:hx-post (url (bidi/path-for ssr-routes/only-routes
:company-1099-vendor-save
:request-method :post
:vendor-id vendor-id)
{:client-id client-id})
:class "w-full h-full max-w-2xl"
:hx-swap "outerHTML swap:300ms"}
[:fieldset {:class "hx-disable w-full h-full"}
(com/modal-card
{}
[:div.flex [:div.p-2 "Vendor 1099 Info"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:vendor/name entity)]]
[:div.grid.grid-cols-6.gap-x-4.gap-y-2
(fc/with-field-default :vendor/address {}
(println "ADDRESS" fc/*current*)
(list [:h4.text-xl.border-b.col-span-6 "Address"]
[:div.col-span-6
(fc/with-field :db/id
(com/hidden {:name (fc/field-name)
:value (fc/field-value)}))
[:fieldset {:class "hx-disable w-full h-full"}
(com/modal-card
{}
[:div.flex [:div.p-2 "Vendor 1099 Info"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:vendor/name entity)]]
[:div.grid.grid-cols-6.gap-x-4.gap-y-2
(fc/with-field :address/street1
(com/validated-field {:label "Street 1"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:class "w-full"
:value (fc/field-value)
:placeholder "1700 Pennsylvania Ave"
:autofocus true})))]
[:div.col-span-6
(fc/with-field :address/street2
(com/validated-field {:label "Street 2"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:class "w-full"
:value (fc/field-value)
:placeholder "Suite 200"})))]
[:div.col-span-3
(fc/with-field :address/city
(com/validated-field {:label "City"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:class "w-full"
:value (fc/field-value)
:placeholder "Cupertino"})))]
[:div.col-span-1
(fc/with-field :address/state
(com/validated-field {:label "State"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:class "w-full"
:value (fc/field-value)
:placeholder "CA"})))]
[:div.col-span-2
(fc/with-field :address/zip
(com/validated-field {:label "Zip"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:class "w-full"
:value (fc/field-value)
:placeholder "98102"})))]))
[:h4.text-xl.border-b.col-span-6 "Legal Entity"]
[:div.col-span-6
(fc/with-field :vendor/legal-entity-name
(com/validated-field {:label "Legal Entity Name"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:class "w-full"
:value (fc/field-value)
:placeholder "Good Restaurant LLC"})))]
[:div.col-span-6.text-center " - OR -"]
[:div.col-span-2
(fc/with-field :vendor/legal-entity-first-name
(com/validated-field {:label "First Name"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:value (fc/field-value)
:placeholder "John"})))]
[:div.col-span-2
(fc/with-field :vendor/legal-entity-middle-name
(com/validated-field {:label "Middle Name"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:value (fc/field-value)
:placeholder "C."})))]
[:div.col-span-2
(fc/with-field :vendor/legal-entity-last-name
(com/validated-field {:label "Last Name"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:value (fc/field-value)
:placeholder "Riley"})))]
[:div.col-span-2
(fc/with-field :vendor/legal-entity-tin
(com/validated-field {:label "TIN"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:value (fc/field-value)
:placeholder "John"})))]
[:div.col-span-2
(fc/with-field :vendor/legal-entity-tin-type
(com/validated-field {:label "TIN Type"
:errors (fc/field-errors)}
(com/select {:name (fc/field-name)
:allow-blank? true
:value (some-> (fc/field-value) name)
:options [["ein" "EIN"]
["ssn" "SSN"]]})))]
[:div.col-span-2
(fc/with-field :vendor/legal-entity-1099-type
(com/validated-field {:label "1099 Type"
:errors (fc/field-errors)}
(com/select {:name (fc/field-name)
:allow-blank? true
:value (some-> (fc/field-value) name)
:options (ref->select-options "legal-entity-1099-type")})))]]
[:div
(com/form-errors {:errors (:errors fc/*form-errors*)})
(com/validated-save-button {:errors form-errors} "Save vendor")])]]))))
(fc/with-field-default :vendor/address {}
(println "ADDRESS" fc/*current*)
(list [:h4.text-xl.border-b.col-span-6 "Address"]
[:div.col-span-6
(fc/with-field :db/id
(com/hidden {:name (fc/field-name)
:value (fc/field-value)}))
(fc/with-field :address/street1
(com/validated-field {:label "Street 1"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:class "w-full"
:value (fc/field-value)
:placeholder "1700 Pennsylvania Ave"
:autofocus true})))]
[:div.col-span-6
(fc/with-field :address/street2
(com/validated-field {:label "Street 2"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:class "w-full"
:value (fc/field-value)
:placeholder "Suite 200"})))]
[:div.col-span-3
(fc/with-field :address/city
(com/validated-field {:label "City"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:class "w-full"
:value (fc/field-value)
:placeholder "Cupertino"})))]
[:div.col-span-1
(fc/with-field :address/state
(com/validated-field {:label "State"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:class "w-full"
:value (fc/field-value)
:placeholder "CA"})))]
[:div.col-span-2
(fc/with-field :address/zip
(com/validated-field {:label "Zip"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:class "w-full"
:value (fc/field-value)
:placeholder "98102"})))]))
[:h4.text-xl.border-b.col-span-6 "Legal Entity"]
[:div.col-span-6
(fc/with-field :vendor/legal-entity-name
(com/validated-field {:label "Legal Entity Name"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:class "w-full"
:value (fc/field-value)
:placeholder "Good Restaurant LLC"})))]
[:div.col-span-6.text-center " - OR -"]
[:div.col-span-2
(fc/with-field :vendor/legal-entity-first-name
(com/validated-field {:label "First Name"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:value (fc/field-value)
:placeholder "John"})))]
[:div.col-span-2
(fc/with-field :vendor/legal-entity-middle-name
(com/validated-field {:label "Middle Name"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:value (fc/field-value)
:placeholder "C."})))]
[:div.col-span-2
(fc/with-field :vendor/legal-entity-last-name
(com/validated-field {:label "Last Name"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:value (fc/field-value)
:placeholder "Riley"})))]
[:div.col-span-2
(fc/with-field :vendor/legal-entity-tin
(com/validated-field {:label "TIN"
:errors (fc/field-errors)}
(com/text-input {:name (fc/field-name)
:value (fc/field-value)
:placeholder "John"})))]
[:div.col-span-2
(fc/with-field :vendor/legal-entity-tin-type
(com/validated-field {:label "TIN Type"
:errors (fc/field-errors)}
(com/select {:name (fc/field-name)
:allow-blank? true
:value (some-> (fc/field-value) name)
:options [["ein" "EIN"]
["ssn" "SSN"]]})))]
[:div.col-span-2
(fc/with-field :vendor/legal-entity-1099-type
(com/validated-field {:label "1099 Type"
:errors (fc/field-errors)}
(com/select {:name (fc/field-name)
:allow-blank? true
:value (some-> (fc/field-value) name)
:options (ref->select-options "legal-entity-1099-type")})))]]
[:div
(com/form-errors {:errors (:errors fc/*form-errors*)})
(com/validated-save-button {:errors form-errors} "Save vendor")])]]))))
(def vendor-table (helper/table-route grid-page))
(def page (helper/page-route grid-page))

View File

@@ -25,9 +25,9 @@
[hiccup2.core :as hiccup]
[malli.core :as mc]))
(def query-schema (mc/schema
[:maybe
(into [:map {} ]
default-grid-fields-schema)]))
[:maybe
(into [:map {}]
default-grid-fields-schema)]))
(def default-read '[:db/id
:plaid-item/external-id
@@ -37,7 +37,7 @@
{:plaid-item/accounts [:db/id
{:bank-account/_plaid-account [{:bank-account/integration-status
[{ [ :integration-status/state :xform iol-ion.query/ident] [:db/ident]}
[{[:integration-status/state :xform iol-ion.query/ident] [:db/ident]}
:integration-status/message
:integration-status/last-attempt
:integration-status/last-updated]}]}
@@ -66,7 +66,6 @@
true (apply-sort-3 query-params)
true (apply-pagination query-params))))
(defn hydrate-results [ids db _]
(let [results (pull-many-by-id db default-read ids)]
(->> ids
@@ -78,15 +77,12 @@
[(hydrate-results ids-to-retrieve db request)
matching-count]))
(defn plaid-link-script [token]
(format "window.plaid = Plaid.create(
{ token: \"%s\",
onSuccess: function (x) { htmx.trigger(\"#link-account\", \"linked\", {\"public_token\": x})}
})", token))
(defn link [{{client-code "client_code" public-token "public_token"} :form-params
:keys [identity]
:as request}]
@@ -99,24 +95,24 @@
(alog/info ::linking-plaid :id identity :client-code client-code)
(assert-can-see-client identity (pull-attr (dc/db conn) :db/id [:client/code client-code]))
(let [access-token (:access_token (p/exchange-public-token public-token client-code))
account-result (p/get-accounts access-token )
account-result (p/get-accounts access-token)
item {:plaid-item/client [:client/code client-code]
:plaid-item/external-id (-> account-result :item :item_id )
:plaid-item/external-id (-> account-result :item :item_id)
:plaid-item/access-token access-token
:plaid-item/status (or (some-> account-result :item :error)
"SUCCESS")
"SUCCESS")
:plaid-item/last-updated (coerce/to-date (time/now))
:db/id "plaid-item"}]
@(dc/transact conn (->> (:accounts account-result)
(map (fn [a]
(let [balance (some-> a :balances :current (* 0.01))]
(cond-> {:plaid-account/external-id (:account_id a)
:plaid-account/number (:mask a)
:plaid-account/name (str (:name a) " " (:mask a))
:plaid-item/_accounts "plaid-item"}
balance (assoc :plaid-account/balance balance)))))
(into [item])))
(map (fn [a]
(let [balance (some-> a :balances :current (* 0.01))]
(cond-> {:plaid-account/external-id (:account_id a)
:plaid-account/number (:mask a)
:plaid-account/name (str (:name a) " " (:mask a))
:plaid-item/_accounts "plaid-item"}
balance (assoc :plaid-account/balance balance)))))
(into [item])))
(alog/info ::access-token-was :token access-token)
{:headers {"Hx-redirect" (bidi/path-for ssr-routes/only-routes
:company-plaid)}}))
@@ -141,115 +137,110 @@
(com/button-icon {} svg/refresh)
"Start relink")])))
(def grid-page
(helper/build
{:id "plaid-table"
:nav com/company-aside-nav
:fetch-page fetch-page
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
:company)}
"My Company"]
(def grid-page
(helper/build
{:id "plaid-table"
:nav com/company-aside-nav
:fetch-page fetch-page
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
:company)}
"My Company"]
[:a {:href (bidi/path-for ssr-routes/only-routes
:company-plaid)}
"Plaid"]]
:title "Plaid Accounts"
:entity-name "Plaid accounts"
:query-schema query-schema
:route :company-plaid-table
:action-buttons (fn [request]
(when-let [client-code (:client/code (:client request))]
[[:div {:hx-post (str (bidi/path-for ssr-routes/only-routes
:company-plaid-link
:request-method :post))
:hx-vals (hiccup/raw (format "js:{client_code: \"%s\", public_token: event.detail.public_token}", client-code))
:hx-trigger "linked"}
[:script (hiccup/raw (plaid-link-script (p/get-link-token client-code)))]
(com/button {:color :primary
:id "link-account"
:onClick "window.plaid.open()"}
(com/button-icon {} svg/refresh)
(format "Link %s account" client-code))]]))
:row-buttons (fn [request e]
[[:div (com/button {:hx-put (str (bidi/path-for ssr-routes/only-routes
:company-plaid-relink)
"?plaid-item-id=" (:db/id e))
:color :primary
:hx-target "closest div"}
"Reauthenticate")]])
:headers [{:key "plaid-item"
:name "Plaid Item"
:sort-key "external-id"
:render :plaid-item/external-id}
{:key "integreat-plaid-status"
:name "Integreat ↔ Plaid status"
:render (fn [e]
[:a {:href (bidi/path-for ssr-routes/only-routes
:company-plaid)}
"Plaid"]]
:title "Plaid Accounts"
:entity-name "Plaid accounts"
:query-schema query-schema
:route :company-plaid-table
:action-buttons (fn [request]
(when-let [client-code (:client/code (:client request))]
[[:div {:hx-post (str (bidi/path-for ssr-routes/only-routes
:company-plaid-link
:request-method :post))
:hx-vals (hiccup/raw (format "js:{client_code: \"%s\", public_token: event.detail.public_token}", client-code))
:hx-trigger "linked"}
[:script (hiccup/raw (plaid-link-script (p/get-link-token client-code)))]
(com/button {:color :primary
:id "link-account"
:onClick "window.plaid.open()"}
(com/button-icon {} svg/refresh)
(format "Link %s account" client-code))]]))
:row-buttons (fn [request e]
[[:div (com/button {:hx-put (str (bidi/path-for ssr-routes/only-routes
:company-plaid-relink)
"?plaid-item-id=" (:db/id e))
:color :primary
:hx-target "closest div"}
"Reauthenticate")]])
:headers [{:key "plaid-item"
:name "Plaid Item"
:sort-key "external-id"
:render :plaid-item/external-id}
{:key "integreat-plaid-status"
:name "Integreat ↔ Plaid status"
:render (fn [e]
(let [bad-integration (->> (:plaid-item/accounts e)
(map (comp
first
:bank-account/_plaid-account))
(filter (comp #{:integration-state/failed :integration-state/unauthorized}
:integration-status/state
:bank-account/integration-status))
first
:bank-account/integration-status)]
[:div
[:div.cursor-pointer (com/pill (cond-> {:color :primary}
bad-integration (assoc :color :red
:x-tooltip "{content: ()=>$refs.tooltip.innerHTML, theme: 'light', allowHTML: true}"))
(let [bad-integration (->> (:plaid-item/accounts e)
(map (comp
first
:bank-account/_plaid-account))
(filter (comp #{:integration-state/failed :integration-state/unauthorized}
:integration-status/state
:bank-account/integration-status))
first
:bank-account/integration-status)]
[:div
[:div.inline-flex.gap-2
(or
(some-> bad-integration
:integration-status/state
name
str/capitalize)
"Success")
(when bad-integration
" (detail)")
(when bad-integration
[:template {:x-ref "tooltip"}
[:div.text-red-700
(:integration-status/message bad-integration)]])])]
[:div.grid.grid-cols-2.gap-1.auto-cols-min.grid-flow-row.shrink
[:div "Attempted: "] [:div (atime/unparse-local (coerce/to-date-time (:integration-status/last-attempt e)) atime/normal-date)]
[:div "Last Updated: "] [:div (atime/unparse-local (coerce/to-date-time (:integration-status/last-updated e)) atime/normal-date)]]]))}
{:key "plaid-bank-status"
:name "Plaid ↔ Bank Status"
:sort-key "plaid-bank-status"
:render (fn [e]
(when-let [status (:plaid-item/status e)]
[:div [:div (com/pill {:color :primary}
status)]
[:div (atime/unparse-local (coerce/to-date-time (:plaid-item/last-updated e)) atime/normal-date)]]))}
{:key "accounts"
:name "Accounts"
:show-starting "md"
:render (fn [e]
[:ul
(for [a (:plaid-item/accounts e)]
[:li [:svg.inline {:data-jdenticon-value (:db/id a) :width "24" :height "24"}] (:plaid-account/name a) " - " (:plaid-account/number a) " - updated "
(atime/unparse-local (:plaid-account/last-synced a) atime/normal-date)])])}]}))
[:div.cursor-pointer (com/pill (cond-> {:color :primary}
bad-integration (assoc :color :red
:x-tooltip "{content: ()=>$refs.tooltip.innerHTML, theme: 'light', allowHTML: true}"))
[:div.inline-flex.gap-2
(or
(some-> bad-integration
:integration-status/state
name
str/capitalize)
"Success")
(when bad-integration
" (detail)")
(when bad-integration
[:template {:x-ref "tooltip"}
[:div.text-red-700
(:integration-status/message bad-integration)]])])]
[:div.grid.grid-cols-2.gap-1.auto-cols-min.grid-flow-row.shrink
[:div "Attempted: "] [:div (atime/unparse-local (coerce/to-date-time (:integration-status/last-attempt e)) atime/normal-date)]
[:div "Last Updated: "] [:div (atime/unparse-local (coerce/to-date-time (:integration-status/last-updated e)) atime/normal-date)]]]))}
{:key "plaid-bank-status"
:name "Plaid ↔ Bank Status"
:sort-key "plaid-bank-status"
:render (fn [e]
(when-let [status (:plaid-item/status e)]
[:div [:div (com/pill {:color :primary}
status)]
[:div (atime/unparse-local (coerce/to-date-time (:plaid-item/last-updated e)) atime/normal-date)]]))}
{:key "accounts"
:name "Accounts"
:show-starting "md"
:render (fn [e]
[:ul
(for [a (:plaid-item/accounts e)]
[:li [:svg.inline {:data-jdenticon-value (:db/id a) :width "24" :height "24"}] (:plaid-account/name a) " - " (:plaid-account/number a) " - updated "
(atime/unparse-local (:plaid-account/last-synced a) atime/normal-date)])])}]}))
(def page (helper/page-route grid-page))
(def table (helper/table-route grid-page))
(def key->handler
(apply-middleware-to-all-handlers
{
:company-plaid page
(def key->handler
(apply-middleware-to-all-handlers
{:company-plaid page
:company-plaid-table table
:company-plaid-link link
:company-plaid-relink relink
}
:company-plaid-relink relink}
(fn [h]
(-> h
(wrap-copy-qp-pqp)

View File

@@ -27,13 +27,12 @@
(def query-schema (mc/schema
[:maybe
(into [:map {:date-range [:date-range :start-date :end-date]}
[:start-date {:optional true}
[:maybe clj-date-schema]]
[:end-date {:optional true}
[:maybe clj-date-schema]]
[:client {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :client/name]}]]]
]
[:client {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :client/name]}]]]]
default-grid-fields-schema)]))
(def default-read '[:db/id :report/client [:report/created :xform clj-time.coerce/from-date] :report/url :report/name :report/creator])
@@ -43,22 +42,20 @@
query (cond-> {:query {:find []
:in '[$ [?c ...]]
:where '[[?e :report/client ?c]]}
:args [db (:trimmed-clients request)]}
:args [db (:trimmed-clients request)]}
(:sort query-params) (add-sorter-fields {"client" ['[?e :report/client ?c]
'[?c :client/name ?sort-client]]
"created" ['[?e :report/created ?sort-created]]
"creator" ['[?e :report/creator ?sort-creator]]
"name" ['[?e :report/name ?sort-name]
]}
query-params)
'[?c :client/name ?sort-client]]
"created" ['[?e :report/created ?sort-created]]
"creator" ['[?e :report/creator ?sort-creator]]
"name" ['[?e :report/name ?sort-name]]}
query-params)
true
(merge-query {:query {:find ['?sort-default '?e] :where ['[?e :report/created ?sort-default]]}}))]
(->> (query2 query)
(apply-sort-3 (update query-params :sort conj {:sort-key "default-2" :asc true}))
(apply-pagination query-params))))
(apply-sort-3 (update query-params :sort conj {:sort-key "default-2" :asc true}))
(apply-pagination query-params))))
(defn hydrate-results [ids db request]
(let [results (->> (pull-many db default-read ids)
@@ -67,7 +64,7 @@
(->> ids
(map results)
(filter identity)
(map first)
(filter (fn [r]
(let [used-clients (set (map :db/id (:report/client r)))]
@@ -78,7 +75,7 @@
(defn fetch-page [args]
(let [db (dc/db conn)
{ids-to-retrieve :ids matching-count :count} (fetch-ids db args)]
[(->> (hydrate-results ids-to-retrieve db args))
matching-count]))
@@ -115,7 +112,7 @@
:sort-key "creator"
:render (fn [report]
(when (:report/creator report)
(com/pill {:color :primary }
(com/pill {:color :primary}
(:report/creator report))))}
{:key "created"
:name "Created"
@@ -129,7 +126,7 @@
(def page (helper/page-route grid-page))
(defn delete-report [{:keys [form-params identity]}]
(let [[id-to-delete key] (first (dc/q '[:find ?i ?k
:in $ ?i
:where [?i :report/key ?k]]
@@ -137,29 +134,28 @@
(some-> (get form-params "id") not-empty Long/parseLong)))
report (dc/pull (dc/db conn) default-read id-to-delete)]
(assert-can-see-client identity (:report/client report))
(when id-to-delete
(when id-to-delete
(s3/delete-object :bucket-name (:data-bucket env)
:key key)
@(dc/transact conn [[:db/retractEntity id-to-delete]]))
(html-response
(row* identity
report
{:flash? true
:delete-after-settle? true}))))
(html-response
(row* identity
report
{:flash? true
:delete-after-settle? true}))))
(def key->handler
(apply-middleware-to-all-handlers
(->>
(into
{:company-reports page
:company-reports-table table
:company-reports-delete delete-report}
company-expense-report/key->handler)
(into
{:company-reports page
:company-reports-table table
:company-reports-delete delete-report}
company-expense-report/key->handler)
(into company-reconciliation-report/key->handler))
(fn [h]
(-> h
(wrap-copy-qp-pqp)
(wrap-copy-qp-pqp)
(wrap-apply-sort grid-page)
(wrap-merge-prior-hx)
(wrap-schema-enforce :query-schema query-schema)

View File

@@ -1,4 +1,4 @@
(ns auto-ap.ssr.company.reports.expense
(ns auto-ap.ssr.company.reports.expense
(:require [auto-ap.datomic :refer [conn merge-query]]
[auto-ap.graphql.utils :refer [extract-client-ids]]
[auto-ap.logging :as alog]
@@ -20,73 +20,71 @@
[hiccup2.core :as hiccup]))
(defn lookup-breakdown-data [request]
(let [query (cond-> {:query '{:find [?cn ?user-date (sum ?amt)]
:with [?e]
:in [$ [?clients ?start ?end]]
:where
[[(iol-ion.query/scan-invoices $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]
(not [?e :invoice/status :invoice-status/voided])
[?e :invoice/date ?d]
[?e :invoice/client ?c]
[?e :invoice/expense-accounts ?iea]
[?iea :invoice-expense-account/amount ?amt]
[?c :client/name ?cn]
[(clj-time.coerce/to-date-time ?d) ?user-date]]}
:args
[(dc/db conn)
[(extract-client-ids (:clients request)
(:client-id request)
(when (:client-code request)
[:client/code (:client-code request)]))
(some-> (time/plus (time/now) (time/days -65)) coerce/to-date)
(some-> (time/now) coerce/to-date)]]}
(let [query (cond-> {:query '{:find [?cn ?user-date (sum ?amt)]
:with [?e]
:in [$ [?clients ?start ?end]]
:where
[[(iol-ion.query/scan-invoices $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]
(not [?e :invoice/status :invoice-status/voided])
[?e :invoice/date ?d]
[?e :invoice/client ?c]
[?e :invoice/expense-accounts ?iea]
[?iea :invoice-expense-account/amount ?amt]
[?c :client/name ?cn]
[(clj-time.coerce/to-date-time ?d) ?user-date]]}
:args
[(dc/db conn)
[(extract-client-ids (:clients request)
(:client-id request)
(when (:client-code request)
[:client/code (:client-code request)]))
(some-> (time/plus (time/now) (time/days -65)) coerce/to-date)
(some-> (time/now) coerce/to-date)]]}
(:vendor-id (:query-params request))
(merge-query {:query '{:in [?v]
:where [ [?e :invoice/vendor ?v]]}
:args [ (:db/id (:vendor-id (:query-params request)))]})
(:account-id (:query-params request))
(merge-query {:query '{:in [?a]
:where [ [?iea :invoice-expense-account/account ?a]]}
:args [ (:db/id (:account-id (:query-params request)))]}))]
(dc/query query)))
(:vendor-id (:query-params request))
(merge-query {:query '{:in [?v]
:where [[?e :invoice/vendor ?v]]}
:args [(:db/id (:vendor-id (:query-params request)))]})
(:account-id (:query-params request))
(merge-query {:query '{:in [?a]
:where [[?iea :invoice-expense-account/account ?a]]}
:args [(:db/id (:account-id (:query-params request)))]}))]
(dc/query query)))
(defn lookup-invoice-total-data [request]
(let [start (:start-date (:query-params request) (time/plus (time/now) (time/days -30)))
end (:end-date (:query-params request) (time/now))
query (cond-> {:query '{:find [?cn ?vn (sum ?t)]
:with [ ?e]
:in [$ [?clients ?start ?end]]
:where
[[(iol-ion.query/scan-invoices $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]
(not [?e :invoice/status :invoice-status/voided])
[?e :invoice/client ?c]
[?e :invoice/total ?t]
[?e :invoice/vendor ?v]
[?v :vendor/name ?vn]
[?c :client/name ?cn]
]}
:args
[(dc/db conn)
[(extract-client-ids (:clients request)
(:client-id request)
(when (:client-code request)
[:client/code (:client-code request)]))
(some-> start coerce/to-date)
(some-> end coerce/to-date)]]})]
(dc/query query)))
(let [start (:start-date (:query-params request) (time/plus (time/now) (time/days -30)))
end (:end-date (:query-params request) (time/now))
query (cond-> {:query '{:find [?cn ?vn (sum ?t)]
:with [?e]
:in [$ [?clients ?start ?end]]
:where
[[(iol-ion.query/scan-invoices $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]
(not [?e :invoice/status :invoice-status/voided])
[?e :invoice/client ?c]
[?e :invoice/total ?t]
[?e :invoice/vendor ?v]
[?v :vendor/name ?vn]
[?c :client/name ?cn]]}
:args
[(dc/db conn)
[(extract-client-ids (:clients request)
(:client-id request)
(when (:client-code request)
[:client/code (:client-code request)]))
(some-> start coerce/to-date)
(some-> end coerce/to-date)]]})]
(defn week-seq
(dc/query query)))
(defn week-seq
([c] (week-seq c (atime/last-monday)))
([c starting] (reverse (for [n (range c)
:let [start (time/minus starting (time/weeks n))
end (time/minus starting (time/weeks (dec n)))]]
[(atime/as-local-time (coerce/to-date-time start)) (atime/as-local-time (coerce/to-date-time end))]))))
(defn- best-week [d weeks]
(reduce
(fn [acc [start end]]
@@ -97,11 +95,10 @@
nil
weeks))
(defn expense-breakdown-card* [request]
(com/card {:class "w-full h-full" :id "expense-breakdown-report"}
[:div {:class "flex flex-col px-8 py-8 space-y-3 w-full h-full"}
[:form {:hx-get (bidi.bidi/path-for ssr-routes/only-routes :company-expense-report-breakdown-card)
:hx-trigger "change"
:hx-target "#expense-breakdown-report"
@@ -157,14 +154,14 @@
(for [d weeks]
(get-in lookup [ea d] 0)))]
[:canvas {:x-data (hx/json {:chart nil
:labels x-axis
:datasets (map (fn [s a] {:label a
:data s
:borderWidth 1})
series
distinct-accounts)})
:x-init
"new Chart($el, {
:labels x-axis
:datasets (map (fn [s a] {:label a
:data s
:borderWidth 1})
series
distinct-accounts)})
:x-init
"new Chart($el, {
type: 'bar',
data: {
labels: labels,
@@ -186,7 +183,7 @@
[:div {:class "flex flex-col px-8 py-8 space-y-3"}
[:div
[:h1.text-2xl.mb-3.font-bold "Invoice totals by vendor"]
[:form {:hx-get (bidi.bidi/path-for ssr-routes/only-routes :company-expense-report-invoice-total-card )
[:form {:hx-get (bidi.bidi/path-for ssr-routes/only-routes :company-expense-report-invoice-total-card)
:hx-trigger "change"
:hx-target "#invoice-totals-report"
:hx-swap "outerHTML"}
@@ -201,7 +198,7 @@
(com/date-input {:name (fc/field-name)
:class "w-64"
:value (some-> (fc/field-value)
(atime/unparse-local atime/normal-date)) })]))
(atime/unparse-local atime/normal-date))})]))
(fc/with-field :end-date
(com/validated-field {:label "End"
:errors (fc/field-errors)}
@@ -209,13 +206,12 @@
(com/date-input {:name (fc/field-name)
:class "w-64"
:value (some-> (fc/field-value)
(atime/unparse-local atime/normal-date)) })]))])]
(atime/unparse-local atime/normal-date))})]))])]
[:div {:class "overflow-scroll min-w-full max-h-[700px]"}
(let [data (lookup-invoice-total-data request)
companies (sort (set (map first data)))
vendors (sort (set (map second data)))
result (by (juxt first second) last data)
]
result (by (juxt first second) last data)]
(com/data-grid
{:headers (into
[(com/data-grid-header {:class "sticky left-0 z-60 bg-gray-100"} "Vendor")]
@@ -231,7 +227,7 @@
(com/data-grid-cell
{}
(or (some->> (get result [company vendor])
(format "$%,.2f" ))
(format "$%,.2f"))
[:span.text-gray-200 "-"])))))))]]]))
(defn page [request]

View File

@@ -53,7 +53,7 @@
(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}" }
(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]])
@@ -67,13 +67,12 @@
(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"}
@@ -88,7 +87,7 @@
(com/date-input {:name (fc/field-name)
:class "w-64"
:value (some-> (fc/field-value)
(atime/unparse-local atime/normal-date)) })]))
(atime/unparse-local atime/normal-date))})]))
(fc/with-field :end-date
(com/validated-field {:label "End"
:errors (fc/field-errors)}
@@ -96,12 +95,11 @@
(com/date-input {:name (fc/field-name)
:class "w-64"
:value (some-> (fc/field-value)
(atime/unparse-local atime/normal-date)) })]))
(atime/unparse-local atime/normal-date))})]))
(com/button {:color :primary :class "self-center w-24"} "Run")])]
(if report
(if report
(report* {:request request :report report})
[:div "Please choose a time range to run the report"])
]]))
[:div "Please choose a time range to run the report"])]]))
(defn page [request]
(base-page
@@ -134,7 +132,7 @@
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))]
(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)
@@ -169,11 +167,11 @@
: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}]
(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)]))
(: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
@@ -182,7 +180,7 @@
(def key->handler
(apply-middleware-to-all-handlers
{:company-reconciliation-report page
{:company-reconciliation-report page
:company-reconciliation-report-card card}
(fn [h]
(-> h
@@ -191,4 +189,4 @@
[:start-date {:optional true}
[:maybe clj-date-schema]]
[:end-date {:optional true}
[:maybe clj-date-schema]] ])))))
[:maybe clj-date-schema]]])))))

View File

@@ -33,36 +33,34 @@
:yodlee-provider-account/client [:client/code]}])
(def query-schema (mc/schema
[:maybe
(into [:map {}
[:client-id {:optional true} [:maybe entity-id]] ]
default-grid-fields-schema)]))
[:maybe
(into [:map {}
[:client-id {:optional true} [:maybe entity-id]]]
default-grid-fields-schema)]))
(defn fetch-ids [db request]
(let [query-params (:query-params request)
query (cond-> {:query {:find []
:in ['$ '[?xx ...]]
:where ['[?e :yodlee-provider-account/id]
'[?e :yodlee-provider-account/client ?xx]]}
:args [db (:trimmed-clients request)]}
:in ['$ '[?xx ...]]
:where ['[?e :yodlee-provider-account/id]
'[?e :yodlee-provider-account/client ?xx]]}
:args [db (:trimmed-clients request)]}
(:sort query-params) (add-sorter-fields {"status" ['[?e :yodlee-provider-account/status ?sort-status]]
"client" ['[?e :yodlee-provider-account/client ?c]
'[?c :client/code ?sort-client]]
"provider-account" ['[?e :yodlee-provider-account/id ?sort-provider-account]]
"last-updated" ['[?e :yodlee-provider-account/last-updated ?sort-last-updated]]}
query-params)
true
(merge-query {:query {:find ['?e ]
:where ['[?e :yodlee-provider-account/id]]}}))]
(:sort query-params) (add-sorter-fields {"status" ['[?e :yodlee-provider-account/status ?sort-status]]
"client" ['[?e :yodlee-provider-account/client ?c]
'[?c :client/code ?sort-client]]
"provider-account" ['[?e :yodlee-provider-account/id ?sort-provider-account]]
"last-updated" ['[?e :yodlee-provider-account/last-updated ?sort-last-updated]]}
query-params)
true
(merge-query {:query {:find ['?e]
:where ['[?e :yodlee-provider-account/id]]}}))]
(->> query
(query2)
(apply-sort-3 query-params)
(apply-pagination query-params))))
(defn hydrate-results [ids db _]
(let [results (->> (pull-many db default-read ids)
(group-by :db/id))]
@@ -70,26 +68,24 @@
(map results)
(map first))))
(defn fetch-page [request]
(let [db (dc/db conn)
{ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]
[(->> (hydrate-results ids-to-retrieve db request))
matching-count]))
(defn fastlink-dialog [{:keys [client]}]
(modal-response
(com/modal
{}
(com/modal-card
{}
[:div.flex [:div.p-2 "Yodlee Fastlink"] ]
[:div
[:div#fa-spot]
[:script {:lang "text/javascript"}
(hiccup/raw
(format "
(com/modal
{}
(com/modal-card
{}
[:div.flex [:div.p-2 "Yodlee Fastlink"]]
[:div
[:div#fa-spot]
[:script {:lang "text/javascript"}
(hiccup/raw
(format "
fastlink.open({fastLinkURL: '%s',
accessToken: '%s',
params: {'configName': 'Aggregation'},
@@ -100,25 +96,24 @@ fastlink.open({fastLinkURL: '%s',
}},
'fa-spot');
" (:yodlee2-fastlink env) (yodlee/get-access-token (:client/code client))))]
]
[:div]))))
" (:yodlee2-fastlink env) (yodlee/get-access-token (:client/code client))))]]
[:div]))))
(defn reauthenticate [{:keys [form-params identity]}]
(assert-can-see-client identity (-> (dc/pull (dc/db conn) '[{:yodlee-provider-account/client [:db/id]}] (Long/parseLong (get form-params "id")))
:yodlee-provider-account/client
:db/id))
(html-response
(com/modal
{}
(com/modal-card
{}
[:div.flex [:div.p-2 "Yodlee Fastlink"] ]
[:div
[:div#fa-spot]
[:script {:lang "text/javascript"}
(hiccup/raw
(format "
(com/modal
{}
(com/modal-card
{}
[:div.flex [:div.p-2 "Yodlee Fastlink"]]
[:div
[:div#fa-spot]
[:script {:lang "text/javascript"}
(hiccup/raw
(format "
fastlink.open({fastLinkURL: '%s',
accessToken: '%s',
params: {'configName': 'Aggregation',
@@ -127,94 +122,93 @@ fastlink.open({fastLinkURL: '%s',
'fa-spot');
"
(:yodlee2-fastlink env)
(yodlee/get-access-token (-> (dc/pull (dc/db conn)
[{:yodlee-provider-account/client [:client/code]}]
(Long/parseLong (get form-params "id")))
:yodlee-provider-account/client
:client/code))
(pull-attr (dc/db conn) :yodlee-provider-account/id (Long/parseLong (get form-params "id")))))]]
[:div]))))
(:yodlee2-fastlink env)
(yodlee/get-access-token (-> (dc/pull (dc/db conn)
[{:yodlee-provider-account/client [:client/code]}]
(Long/parseLong (get form-params "id")))
:yodlee-provider-account/client
:client/code))
(pull-attr (dc/db conn) :yodlee-provider-account/id (Long/parseLong (get form-params "id")))))]]
[:div]))))
(def grid-page
(helper/build
{:id "yodlee-table"
:nav com/company-aside-nav
:id-fn :db/id
:fetch-page fetch-page
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
:company)}
"My Company"]
[:a {:href (bidi/path-for ssr-routes/only-routes
:company-yodlee)}
"Yodlee"]]
:title "Yodlee Accounts"
:entity-name "Yodlee accounts"
:query-schema query-schema
:route :company-yodlee-table
:action-buttons (fn [request]
[[:div.flex.flex-col.flex-shrink
[:div.flex-shrink
(com/button {:color :primary
:on-click "openFastlink()"
:disabled (if (:client request)
false
true)
:hx-get (bidi/path-for ssr-routes/only-routes
:company-yodlee-fastlink-dialog)
:hx-target "#modal-holder"}
(com/button-icon {} svg/refresh)
"Link new account")]
(when-not (:client request)
[:div.text-xs "Note: please select a specific customer to link a new account."])]])
:row-buttons (fn [request _]
[
(com/button {:hx-put (bidi/path-for ssr-routes/only-routes
:company-yodlee-provider-account-reauthenticate)
:color :primary
:hx-target "#modal-holder"}
"Reauthenticate")
(when (is-admin? (:identity request))
(com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
:company-yodlee-provider-account-refresh)
:hx-target "closest tr"}
svg/refresh))])
:headers [{:key "client"
:name "Client"
:sort-key "client"
:hide? (fn [args]
(= (count (:clients args)) 1))
:render #(-> % :yodlee-provider-account/client :client/code)}
{:key "provider-account"
:name "Provider Account"
:sort-key "provider-account"
:render :yodlee-provider-account/id}
{:key "status"
:name "Status"
:sort-key "status"
:render #(when-let [status (:yodlee-provider-account/status %)]
(com/pill {:color (if (not= status "SUCCESS")
:yellow
:primary) }
status))}
{:key "detailed-status"
:name "Detailed Status"
:sort-key "detailed-status"
:render #(when-let [status (:yodlee-provider-account/detailed-status %)]
status)}
(helper/build
{:id "yodlee-table"
:nav com/company-aside-nav
:id-fn :db/id
:fetch-page fetch-page
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
:company)}
"My Company"]
[:a {:href (bidi/path-for ssr-routes/only-routes
:company-yodlee)}
"Yodlee"]]
:title "Yodlee Accounts"
:entity-name "Yodlee accounts"
:query-schema query-schema
:route :company-yodlee-table
:action-buttons (fn [request]
[[:div.flex.flex-col.flex-shrink
[:div.flex-shrink
(com/button {:color :primary
:on-click "openFastlink()"
:disabled (if (:client request)
false
true)
:hx-get (bidi/path-for ssr-routes/only-routes
:company-yodlee-fastlink-dialog)
:hx-target "#modal-holder"}
(com/button-icon {} svg/refresh)
"Link new account")]
(when-not (:client request)
[:div.text-xs "Note: please select a specific customer to link a new account."])]])
:row-buttons (fn [request _]
[(com/button {:hx-put (bidi/path-for ssr-routes/only-routes
:company-yodlee-provider-account-reauthenticate)
:color :primary
:hx-target "#modal-holder"}
"Reauthenticate")
(when (is-admin? (:identity request))
(com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
:company-yodlee-provider-account-refresh)
:hx-target "closest tr"}
svg/refresh))])
:headers [{:key "client"
:name "Client"
:sort-key "client"
:hide? (fn [args]
(= (count (:clients args)) 1))
:render #(-> % :yodlee-provider-account/client :client/code)}
{:key "provider-account"
:name "Provider Account"
:sort-key "provider-account"
:render :yodlee-provider-account/id}
{:key "status"
:name "Status"
:sort-key "status"
:render #(when-let [status (:yodlee-provider-account/status %)]
(com/pill {:color (if (not= status "SUCCESS")
:yellow
:primary)}
status))}
{:key "detailed-status"
:name "Detailed Status"
:sort-key "detailed-status"
:render #(when-let [status (:yodlee-provider-account/detailed-status %)]
status)}
{:key "last-updated"
:name "Last Updated"
:sort-key "last-updated"
:render #(atime/unparse-local (:yodlee-provider-account/last-updated %)
atime/normal-date)}
{:key "accounts"
:name "Accounts"
:show-starting "md"
:render (fn [e]
[:ul
(for [a (:yodlee-provider-account/accounts e)]
[:li (:yodlee-account/name a) " - " (:yodlee-account/number a)])])}]}))
{:key "last-updated"
:name "Last Updated"
:sort-key "last-updated"
:render #(atime/unparse-local (:yodlee-provider-account/last-updated %)
atime/normal-date)}
{:key "accounts"
:name "Accounts"
:show-starting "md"
:render (fn [e]
[:ul
(for [a (:yodlee-provider-account/accounts e)]
[:li (:yodlee-account/name a) " - " (:yodlee-account/number a)])])}]}))
(def page (helper/page-route grid-page))
(def table (helper/table-route grid-page))
@@ -224,26 +218,23 @@ fastlink.open({fastLinkURL: '%s',
(yodlee/refresh-provider-account (:client/code (:yodlee-provider-account/client provider-account))
(:yodlee-provider-account/id provider-account))
(html-response
(helper/row*
grid-page
identity
provider-account
{:flash? true}))))
(helper/row*
grid-page
identity
provider-account
{:flash? true}))))
(def key->handler
(apply-middleware-to-all-handlers
{
:company-yodlee page
(def key->handler
(apply-middleware-to-all-handlers
{:company-yodlee page
:company-yodlee-table table
:company-yodlee-fastlink-dialog fastlink-dialog
}
:company-yodlee-fastlink-dialog fastlink-dialog}
(fn [h]
(-> h
(wrap-copy-qp-pqp)
(wrap-apply-sort grid-page)
(wrap-merge-prior-hx)
(wrap-schema-enforce :query-schema query-schema)
(wrap-schema-enforce :hx-schema query-schema)
(wrap-client-redirect-unauthenticated)
(wrap-secure)))))
(-> h
(wrap-copy-qp-pqp)
(wrap-apply-sort grid-page)
(wrap-merge-prior-hx)
(wrap-schema-enforce :query-schema query-schema)
(wrap-schema-enforce :hx-schema query-schema)
(wrap-client-redirect-unauthenticated)
(wrap-secure)))))