Makes testing of transaction rules work

This commit is contained in:
2023-10-26 21:23:52 -07:00
parent 5ed23f26be
commit 7f7458d54a
12 changed files with 196 additions and 40 deletions

View File

@@ -41,6 +41,7 @@
wrap-entity
wrap-form-4xx-2
wrap-schema-decode]]
[auto-ap.time :as atime]
[auto-ap.utils :refer [dollars=]]
[bidi.bidi :as bidi]
[clojure.string :as str]
@@ -258,33 +259,31 @@
(set))
bank-account-id))
(defn validate-transaction-rule [form-params]
(doseq [[{:transaction-rule-account/keys [account location]} i] (map vector (:transaction-rule/accounts form-params) (range))
:let [account-location (pull-attr (dc/db conn) :account/location account)]
:when (and account-location (not= account-location location))]
(field-validation-error (str "must be " account-location)
[:transaction-rule/accounts i :transaction-rule-account/location]
:form form-params))
(let [total (reduce + 0.0 (map :transaction-rule-account/percentage (:transaction-rule/accounts form-params)))]
(when-not (dollars= 1.0 total)
(form-validation-error (format "Expense accounts total (%d%%) must add to 100%%" (int (* 100.0 total)))
:form form-params)))
(when (and (:transaction-rule/bank-account form-params)
(not (bank-account-belongs-to-client? (:transaction-rule/bank-account form-params)
(:transaction-rule/client form-params))))
(field-validation-error "does not belong to client"
[:transaction-rule/bank-account]
:form form-params)))
(defn transaction-rule-save [{:keys [form-params request-method identity] :as request}]
(validate-transaction-rule form-params)
(let [entity (cond-> form-params
(= :post request-method) (assoc :db/id "new")
true (assoc :transaction-rule/note (entity->note form-params)))
_ (doseq [[{:transaction-rule-account/keys [account location]} i] (map vector (:transaction-rule/accounts entity) (range))
:let [account-location (pull-attr (dc/db conn) :account/location account)]
:when (and account-location (not= account-location location))]
(field-validation-error (str "must be " account-location)
[:transaction-rule/accounts i :transaction-rule-account/location]
:form form-params))
total (reduce +
0.0
(map :transaction-rule-account/percentage
(:transaction-rule/accounts entity)))
_ (when-not (dollars= 1.0 total)
(form-validation-error (format "Expense accounts total (%d%%) must add to 100%%" (int (* 100.0 total)))
:form form-params))
_ (when (and (:transaction-rule/bank-account entity)
(not (bank-account-belongs-to-client? (:transaction-rule/bank-account entity)
(:transaction-rule/client entity))))
(field-validation-error "does not belong to client"
[:transaction-rule/bank-account]
:form form-params))
{:keys [tempids]} (audit-transact [[:upsert-entity entity]]
(:identity request))
updated-rule (dc/pull (dc/db conn)
@@ -295,7 +294,112 @@
:headers (cond-> {"hx-trigger" "modalclose"}
(= :post request-method) (assoc "hx-retarget" "#entity-table tbody"
"hx-reswap" "afterbegin")
(= :put request-method) (assoc "hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id updated-rule)))))))
(= :put request-method) (assoc "hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id updated-rule))
"hx-reswap" "outerHTML")))))
(def transaction-read '[{:transaction/client [:client/name]
:transaction/bank-account [:bank-account/name]}
:transaction/description-original
[:transaction/date :xform clj-time.coerce/from-date]])
(defn transaction-rule-test [{:keys [form-params request-method identity] :as request
{:transaction-rule/keys [description client bank-account amount-lte amount-gte dom-lte dom-gte yodlee-merchant]} :form-params}]
(validate-transaction-rule form-params)
(let [valid-clients (extract-client-ids (:clients request)
client)
query (cond-> {:query {:find ['(pull ?e read)]
:in ['$ 'read]
:where []}
:args [(dc/db conn) transaction-read]}
description
(merge-query {:query {:in ['?descr]
:where ['[(iol-ion.query/->pattern ?descr) ?description-regex]]}
:args [description]})
valid-clients
(merge-query {:query {:in ['[?xx ...]]
:where ['[?e :transaction/client ?xx]]}
:args [(set valid-clients)]})
bank-account
(merge-query {:query {:in ['?bank-account-id]
:where ['[?e :transaction/bank-account ?bank-account-id]]}
:args [bank-account]})
description
(merge-query {:query {:where ['[?e :transaction/description-original ?do]
'[(re-find ?description-regex ?do)]]}})
amount-gte
(merge-query {:query {:in ['?amount-gte]
:where ['[?e :transaction/amount ?ta]
'[(>= ?ta ?amount-gte)]]}
:args [amount-gte]})
amount-lte
(merge-query {:query {:in ['?amount-lte]
:where ['[?e :transaction/amount ?ta]
'[(<= ?ta ?amount-lte)]]}
:args [amount-lte]})
dom-lte
(merge-query {:query {:in ['?dom-lte]
:where ['[?e :transaction/date ?transaction-date]
'[(iol-ion.query/dom ?transaction-date) ?dom]
'[(<= ?dom ?dom-lte)]]}
:args [dom-lte]})
dom-gte
(merge-query {:query {:in ['?dom-gte]
:where ['[?e :transaction/date ?transaction-date]
'[(iol-ion.query/dom ?transaction-date) ?dom]
'[(>= ?dom ?dom-gte)]]}
:args [dom-gte]})
client
(merge-query {:query {:in ['?client-id]
:where ['[?e :transaction/client ?client-id]]}
:args [client]})
true
(merge-query {:query {:where ['[?e :transaction/id]]}}))
results (->>
(query2 query)
(map first))]
(html-response
(com/modal-card {:class "fade-in transition duration-300"}
[:div.p-2.flex.space-x-4 [:div "Transaction Rule"] [:div ">"] [:div "Results"] [:div.ml-4.relative (com/badge {} (count results))]]
(com/data-grid
{:headers [(com/data-grid-header {} "Client")
(com/data-grid-header {} "Bank")
(com/data-grid-header {} "Date")
(com/data-grid-header {} "Description")]}
(for [r (take 15 results)]
(com/data-grid-row
{}
(com/data-grid-cell {} (-> r :transaction/client :client/name))
(com/data-grid-cell {} (-> r :transaction/bank-account :bank-account/name))
(com/data-grid-cell {} (some-> r :transaction/date (atime/unparse-local atime/normal-date)))
(com/data-grid-cell {} (some-> r :transaction/description-original )))))
[:div
(com/button (cond-> {:color :primary
:hx-vals (hx/json (:raw-form-params request))
}
(:db/id form-params) (assoc :hx-put (bidi/path-for ssr-routes/only-routes :admin-transaction-rule-save))
(not (:db/id form-params)) (assoc :hx-post (bidi/path-for ssr-routes/only-routes :admin-transaction-rule-save)))
"Save")
(com/button {:hx-vals (hx/json (:raw-form-params request))
:hx-put (bidi/path-for ssr-routes/only-routes
:admin-transaction-rule-filled-account
)} "Back")
])
:headers (-> {}
(assoc "hx-retarget" ".modal-card")
(assoc "hx-reswap" "outerHTML")))))
@@ -366,6 +470,7 @@
:x-data (hx/json {:location (fc/field-value)})}
[:div {:hx-trigger "changed"
:hx-target "next *"
:hx-swap "outerHTML"
:hx-vals (format "js:{name: '%s', 'client-id': event.detail.clientId || '', 'account-id': event.detail.accountId || '', value: event.detail.location}" (fc/field-name) )
:hx-get (bidi/path-for ssr-routes/only-routes :admin-transaction-rule-location-select)
:x-init "$watch('clientId', cid => $dispatch('changed', $data)); $watch('accountId', cid => $dispatch('changed', $data) )"}]
@@ -554,7 +659,11 @@
]]
[:div
(com/form-errors {:errors (:errors fc/*form-errors*)})
(com/validated-save-button {:errors form-errors} "Save rule")])])))
(com/validated-save-button {:errors form-errors} "Save rule")
(com/validated-save-button {:errors form-errors :color :secondary
:hx-post (bidi/path-for ssr-routes/only-routes :admin-transaction-rule-test)}
"Test rule")])])))
(defn new-account [{{:keys [client-id index]} :query-params}]
@@ -607,6 +716,7 @@
[:transaction-rule-account/percentage percentage])]]))
(defn transaction-dialog [{:keys [entity form-params form-errors]}]
(clojure.pprint/pprint form-params)
(modal-response (dialog* {:entity entity
:form-params (or (when (seq form-params)
form-params)
@@ -649,6 +759,19 @@
(wrap-nested-form-params)
(wrap-form-4xx-2 (-> transaction-dialog
(wrap-entity [:form-params :db/id] default-read))))
:admin-transaction-rule-test (-> transaction-rule-test
(wrap-entity [:form-params :db/id] default-read)
(wrap-schema-decode :form-schema form-schema)
(wrap-nested-form-params)
(wrap-form-4xx-2 (-> transaction-dialog
(wrap-entity [:form-params :db/id] default-read))))
:admin-transaction-rule-filled-account (-> transaction-dialog
(wrap-entity [:form-params :db/id] default-read)
(wrap-schema-decode :form-schema form-schema)
(wrap-nested-form-params)
(wrap-form-4xx-2 (-> transaction-dialog
(wrap-entity [:form-params :db/id] default-read))))
:admin-transaction-rule-edit-dialog (-> transaction-dialog
(wrap-entity [:route-params :db/id] default-read)
(wrap-schema-decode :route-schema [:map [:db/id entity-id]]))

View File

@@ -31,7 +31,7 @@
(def date-input inputs/date-input-)
(def hidden inputs/hidden-)
(def select inputs/select-)
(def typeahead inputs/tytypeahead-
(def typeahead inputs/typeahead-)
(def field-errors inputs/field-errors-)
(def field inputs/field-)
(def validated-field inputs/validated-field-)

View File

@@ -214,8 +214,7 @@
[:li
(menu-button- {:icon svg/cog
:href (bidi/path-for client-routes/routes
:admin-rules)}
:href (bidi/path-for ssr-routes/only-routes :admin-transaction-rules)}
"Rules")]
[:li

View File

@@ -176,7 +176,7 @@
(defn validated-save-button- [{:keys [errors class] :as params} & children]
(button- (-> {:color :primary
(button- (-> {:color (or (:color params) :primary)
:type "submit" :class (cond-> (or class "")
true (hh/add-class "w-32")
(seq errors) (hh/add-class "animate-shake"))}
@@ -184,5 +184,4 @@
(dissoc :errors))
(if (seq children)
children
"Save"))
)
"Save")))

View File

@@ -9,10 +9,10 @@
children])
(defn modal-card- [params header content footer]
[:div#modal-card (update params
[:div (update params
:class (fn [c] (-> c
(or "")
(hh/add-class "w-full p-4 h-full")
(hh/add-class "w-full p-4 h-full modal-card")
)))
[:div {:class "bg-white rounded-lg shadow dark:bg-gray-700 dark:text-white modal-content w-full flex flex-col h-full"}
[:div {:class "flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600 shrink-0"} header]

View File

@@ -64,7 +64,9 @@
(nested-params-request request {}))
([request options]
(let [parse (:key-parser options parse-nested-keys)]
(update-in request [:form-params] nest-params parse))))
(-> request
(assoc :raw-form-params (:form-params request))
(update-in [:form-params] nest-params parse)))))
(defn wrap-nested-form-params
"Middleware to converts a flat map of parameters into a nested map.

View File

@@ -3,9 +3,6 @@
[auto-ap.time :as atime]
[auto-ap.ssr.svg :as svg]))
;; TODO make date-input take clj date
;; TODO make total fields take decimals
(defn date-range-field* [request]
[:div#date-range {}
(com/field {:label "Date Range"}

View File

@@ -33,7 +33,9 @@
:crossorigin= "anonymous"}]
[:script {:src "https://unpkg.com/htmx.org@1.9.6/dist/htmx.min.js"
:crossorigin= "anonymous"}])
[:script {:src "https://unpkg.com/htmx.org@1.9.6/dist/ext/class-tools.js" :crossorigin= "anonymous"}]
[:script {:src "https://unpkg.com/htmx.org/dist/ext/debug.js"}]
[:script {:src "/js/htmx-disable.js"}]
[:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async"}]]
@@ -62,7 +64,7 @@ input[type=number] {
} "]
[:body {:hx-ext "disable-submit"}
[:body {:hx-ext "disable-submit, class-tools"}
contents
[:script {:src "/js/flowbite.min.js"}]

View File

@@ -37,6 +37,15 @@
(assoc-in [:headers "hx-retarget"] "#modal-content")
(assoc-in [:headers "hx-reswap"] "innerHTML"))))))
(defn next-step-modal-response [hiccup & {:as opts}]
(apply html-response
(into
[hiccup]
(mapcat identity
(-> opts
(assoc-in [:headers "hx-retarget"] "#modal-content")
(assoc-in [:headers "hx-reswap"] "innerHTML"))))))
(defn wrap-error-response [handler]
(fn [request]
(try

View File

@@ -37,9 +37,11 @@
:put :admin-transaction-rule-save
:post :admin-transaction-rule-save}
"/table" :admin-transaction-rule-table
"/account/filled" :admin-transaction-rule-filled-account
"/account/new" :admin-transaction-rule-new-account
"/account/location-select" :admin-transaction-rule-location-select
"/account/typeahead" :admin-transaction-rule-account-typeahead
"/test" :admin-transaction-rule-test
"/new" {:get :admin-transaction-rule-new-dialog}
[[#"\d+" :db/id] "/edit"] :admin-transaction-rule-edit-dialog
}}