From 16a1d243e8afd3f0af8bcaaa18e9370a43f7d7b4 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Sat, 16 Jul 2022 10:15:47 -0700 Subject: [PATCH] Simplified forms considerably --- resources/public/css/main.css | 8 +- src/clj/auto_ap/graphql/transactions.clj | 40 +- src/clj/auto_ap/graphql/vendors.clj | 26 +- src/cljs/auto_ap/forms/builder.cljs | 42 +- .../auto_ap/views/components/address.cljs | 2 +- src/cljs/auto_ap/views/components/level.cljs | 9 + src/cljs/auto_ap/views/components/modal.cljs | 3 +- .../auto_ap/views/components/typeahead.cljs | 90 +-- .../views/components/typeahead/vendor.cljs | 184 ++++--- .../views/components/vendor_dialog.cljs | 518 +++++++----------- .../views/pages/admin/accounts/form.cljs | 4 +- .../views/pages/admin/clients/form.cljs | 67 +-- src/cljs/auto_ap/views/utils.cljs | 103 ++-- test/clj/auto_ap/import/yodlee_test.clj | 10 +- test/clj/auto_ap/routes/invoice_test.clj | 2 +- test/clj/auto_ap/routes/invoices_test.clj | 3 - 16 files changed, 519 insertions(+), 592 deletions(-) create mode 100644 src/cljs/auto_ap/views/components/level.cljs delete mode 100644 test/clj/auto_ap/routes/invoices_test.clj diff --git a/resources/public/css/main.css b/resources/public/css/main.css index a0bf81f8..67cff5fa 100644 --- a/resources/public/css/main.css +++ b/resources/public/css/main.css @@ -84,6 +84,10 @@ .modal.wide .modal-card { width: 1024px; } + + .modal.semi-wide .modal-card { + width: 700px; + } @keyframes grow-width { from { width: 0px; @@ -347,10 +351,6 @@ nav.navbar .navbar-item.is-active { .modal { overflow: visible; } - - .modal-card-body { - overflow: visible; - } .buttons .dropdown:not(:last-child):not(.is-fullwidth) .button { margin-right: 0.5em; diff --git a/src/clj/auto_ap/graphql/transactions.clj b/src/clj/auto_ap/graphql/transactions.clj index 91e8c19b..b3ed6259 100644 --- a/src/clj/auto_ap/graphql/transactions.clj +++ b/src/clj/auto_ap/graphql/transactions.clj @@ -266,24 +266,30 @@ (throw (ex-info "Payment can't be undone because it isn't cleared." {:validation-error "Payment can't be undone because it isn't cleared."}))) (if is-autopay-payment? (audit-transact - (->> [{:db/id (:db/id payment) - :payment/status :payment-status/pending} - {:db/id transaction-id - :transaction/approval-status :transaction-approval-status/unapproved} + (cond-> [{:db/id (:db/id payment) + :payment/status :payment-status/pending} + {:db/id transaction-id + :transaction/approval-status :transaction-approval-status/unapproved} - [:db/retractEntity (:db/id payment) ] - [:db/retract transaction-id :transaction/payment (:db/id payment)] - [:db/retract transaction-id :transaction/vendor (:db/id (:transaction/vendor transaction))] - [:db/retract transaction-id :transaction/location (:transaction/location transaction)]] - (into (map (fn [a] - [:db/retract transaction-id :transaction/accounts (:db/id a)]) - (:transaction/accounts transaction))) - (into (map (fn [[invoice-payment]] - [:db/retractEntity invoice-payment]) - (d/query {:query {:find ['?ip] - :in ['$ '?p] - :where ['[?ip :invoice-payment/payment ?p]]} - :args [(d/db conn) (:db/id payment)]} )))) + [:db/retractEntity (:db/id payment) ] + [:db/retract transaction-id :transaction/payment (:db/id payment)] + [:db/retract transaction-id :transaction/vendor (:db/id (:transaction/vendor transaction))]] + + (:transaction/location transaction) + (conj [:db/retract transaction-id :transaction/location (:transaction/location transaction)]) + + (seq (:transaction/accounts transaction)) + (into (map (fn [a] + [:db/retract transaction-id :transaction/accounts (:db/id a)]) + (:transaction/accounts transaction))) + + true + (into (map (fn [[invoice-payment]] + [:db/retractEntity invoice-payment]) + (d/query {:query {:find ['?ip] + :in ['$ '?p] + :where ['[?ip :invoice-payment/payment ?p]]} + :args [(d/db conn) (:db/id payment)]} )))) (:id context)) (audit-transact (into (cond-> [{:db/id (:db/id payment) diff --git a/src/clj/auto_ap/graphql/vendors.clj b/src/clj/auto_ap/graphql/vendors.clj index 3093d262..0cefcda7 100644 --- a/src/clj/auto_ap/graphql/vendors.clj +++ b/src/clj/auto_ap/graphql/vendors.clj @@ -5,12 +5,13 @@ [auto-ap.graphql.utils :refer [->graphql <-graphql - cleanse-query assert-admin assert-failure + cleanse-query enum->keyword is-admin? result->page]] + [auto-ap.utils :refer [by]] [clojure.set :as set] [clojure.string :as str] [clojure.tools.logging :as log] @@ -31,9 +32,30 @@ (set (map :db/id (:user/clients id))))))) (defn upsert-vendor [context {{:keys [id name hidden terms code print_as primary_contact secondary_contact address default_account_id invoice_reminder_schedule schedule_payment_dom terms_overrides account_overrides] :as in} :vendor} value] - (when (and id (not (can-user-edit-vendor? id (:id context)))) (assert-failure "This vendor is managed by Integreat. Please reach out to ben@integreatconsult.com for your changes.")) + + (when (->> schedule_payment_dom + (group-by :client_id) + vals + (filter #(> (count %) 1)) + seq) + (assert-failure "Only one schedule payment override allowed per client.")) + + (when (->> terms_overrides + (group-by :client_id) + vals + (filter #(> (count %) 1)) + seq) + (assert-failure "Only one terms override allowed per client.")) + + (when (->> account_overrides + (group-by :client_id) + vals + (filter #(> (count %) 1)) + seq) + (assert-failure "Only one account override allowed per client.")) + (let [ hidden (if (is-admin? (:id context)) hidden diff --git a/src/cljs/auto_ap/forms/builder.cljs b/src/cljs/auto_ap/forms/builder.cljs index e46df026..86cabaff 100644 --- a/src/cljs/auto_ap/forms/builder.cljs +++ b/src/cljs/auto_ap/forms/builder.cljs @@ -18,23 +18,26 @@ (defn builder [{:keys [can-submit data-sub change-event submit-event id fullwidth?] :as z}] (let [data-sub (or data-sub [::forms/form id]) change-event (or change-event [::forms/change id]) - {:keys [data error]} @(re-frame/subscribe data-sub)] + {:keys [data error]} @(re-frame/subscribe data-sub) + status @(re-frame/subscribe [::status/single id])] (r/create-element Provider #js {:value #js {:can-submit @(re-frame/subscribe can-submit) :change-event change-event :submit-event submit-event :error error - :status @(re-frame/subscribe [::status/single id]) + :status status :id id :data data :fullwidth? fullwidth?}} (r/as-element - (into [:form {:on-submit (fn [e] - (when (.-stopPropagation e) - (.stopPropagation e) - (.preventDefault e)) - (when can-submit - (re-frame/dispatch-sync (vec (conj submit-event {})))))}] - (r/children (r/current-component))))))) + [:form {:on-submit (fn [e] + (when (.-stopPropagation e) + (.stopPropagation e) + (.preventDefault e)) + (when can-submit + (re-frame/dispatch-sync (vec (conj submit-event {})))))} + (into [:fieldset {:disabled (boolean (= :loading (:state status)))}] + (r/children (r/current-component)))] + )))) (defn raw-field [] (let [[child] (r/children (r/current-component))] @@ -55,6 +58,7 @@ :else nil))) + (assoc-in [1 :subscription] (aget consume-form "data")) (assoc-in [1 :event] (aget consume-form "change-event")))]))]))])) @@ -62,6 +66,16 @@ (r/create-element FormScopeProvider #js {:value scope} (r/as-element (into [:<>] (r/children (r/current-component)))))) +(defn vertical-control [{:keys [is-small?]}] + (let [[label & children] (r/children (r/current-component))] + [:> Consumer {} + (fn [consume] + (r/as-element + [:div.field + (when label (if (or (aget consume "fullwidth?") + is-small?) [:p.help label] + [:label.label label])) + (into [:div.control ] children)]))])) (defn field [] (let [[label child] (r/children (r/current-component))] @@ -111,6 +125,16 @@ fullwidth? (conj "is-fullwidth")) } child])))])) +(defn hidden-submit-button [] + [:> Consumer {} + (fn [consume] + (let [status (aget consume "status") + can-submit (aget consume "can-submit")] + (r/as-element + [:div {:style {:display "none"}} + [:button.button.is-medium.is-primary {:disabled (or (status/disabled-for status) + (not can-submit))}]])))]) + (defn error-notification [] (let [[child] (r/children (r/current-component))] [:> Consumer {} diff --git a/src/cljs/auto_ap/views/components/address.cljs b/src/cljs/auto_ap/views/components/address.cljs index 4b33a58c..f0ebc02a 100644 --- a/src/cljs/auto_ap/views/components/address.cljs +++ b/src/cljs/auto_ap/views/components/address.cljs @@ -65,7 +65,7 @@ [horizontal-field nil [:div.control - [:p.help "Address"] + [:p.help "Street Address"] [form-builder/raw-field [:input.input.is-expanded {:type "text" :placeholder "1700 Pennsylvania Ave" diff --git a/src/cljs/auto_ap/views/components/level.cljs b/src/cljs/auto_ap/views/components/level.cljs new file mode 100644 index 00000000..fb6f1fb9 --- /dev/null +++ b/src/cljs/auto_ap/views/components/level.cljs @@ -0,0 +1,9 @@ +(ns auto-ap.views.components.level + (:require [reagent.core :as r])) + +(defn left-stack [] + (let [children (r/children (r/current-component))] + [:div.level (r/props (r/current-component)) + (into [:div.level-left] + (for [c children] + [:div.level-item c]))])) diff --git a/src/cljs/auto_ap/views/components/modal.cljs b/src/cljs/auto_ap/views/components/modal.cljs index c7b85ec3..5a4b3818 100644 --- a/src/cljs/auto_ap/views/components/modal.cljs +++ b/src/cljs/auto_ap/views/components/modal.cljs @@ -33,7 +33,8 @@ class (assoc :class class)) [:div.modal-background {:on-click (dispatch-event [::modal-closed])}] - [:div.modal-card + [:div.modal-card (cond-> {} + class (assoc :class class)) [:header.modal-card-head [:p.modal-card-title title] diff --git a/src/cljs/auto_ap/views/components/typeahead.cljs b/src/cljs/auto_ap/views/components/typeahead.cljs index f44c60b9..a853cfc4 100644 --- a/src/cljs/auto_ap/views/components/typeahead.cljs +++ b/src/cljs/auto_ap/views/components/typeahead.cljs @@ -1,91 +1,7 @@ (ns auto-ap.views.components.typeahead - (:require [reagent.core :as r] - [reagent.ratom :as ra] - [clojure.string :as str] - [clojure.set :as set] - [downshift :as ds :refer [useCombobox]] - [react])) + (:require + [auto-ap.views.components.typeahead.vendor :as internal])) (set! *warn-on-infer* true) -;; TODO: This avoids the use of inferred externs by using aget. You could just use the ^js tag though -(defn state-reducer [state actions-and-changes] - (cond - (= (aget actions-and-changes "type") (aget (aget useCombobox "stateChangeTypes" ) "InputChange")) - (doto (aget actions-and-changes "changes") (aset "selectedItem" nil)) - - (and (= (aget actions-and-changes "type") (aget (aget useCombobox "stateChangeTypes") "InputBlur")) - (not (aget state "selectedItem"))) - (doto (aget actions-and-changes "changes" ) (aset "inputValue" nil)) - - :else - (aget actions-and-changes "changes"))) - -(defn typeahead-v3-internal [{:keys [class style disabled entities ^js entity->text entities-by-id entity-index on-change disabled value name auto-focus] :or {disabled false} :as i}] - (let [[items set-items] (react/useState (map clj->js entities)) - [getLabelProps getMenuProps getComboboxProps getToggleButtonProps getInputProps getItemProps isOpen highlightedIndex selectItem selectedItem setInputValue] - (as-> (useCombobox (clj->js {:items items - :defaultHighlightedIndex 0 - :defaultSelectedItem value - :onInputValueChange (fn [input] - (if entities-by-id - (do - (->> (.search entity-index (or (aget input "inputValue") "") #js {:fuzzy 0.2} ) - clj->js - (take 10) - (set-items))) - - (set-items (map clj->js (take 10 (filter (fn [x] (str/includes? (or (some-> (entity->text x) str/lower-case) "") - (or (some-> (aget input "inputValue") str/lower-case) ""))) - entities)))))) - :stateReducer state-reducer - :onSelectedItemChange (fn [z] - (when on-change - (on-change (js->clj (aget z "selectedItem") :keywordize-keys true))))})) $ - (map #(aget $ %) ["getLabelProps" "getMenuProps" "getComboboxProps" "getToggleButtonProps" "getInputProps" "getItemProps" "isOpen" "highlightedIndex" "selectItem" "selectedItem" "setInputValue"]))] - [:<> - [:div.typeahead (assoc (js->clj (getComboboxProps)) - :style style) - (if selectedItem - ^{:key "typeahead"} [:div.input (assoc (js->clj (getInputProps #js {:disabled (if disabled - "disabled" - "")})) - :on-key-up (fn [e] - (when (= 8 (aget e "keyCode" )) - (selectItem nil) - (setInputValue nil) - (when on-change - (on-change nil)))) - :class class - :tab-index "0") - [:div.control - [:div.tags.has-addons - [:span.tag (entity->text (js->clj selectedItem :keywordize-keys true))] - (when name - [:input {:type "hidden" :name name :value (:id (js->clj selectedItem :keywordize-keys true))}]) - (when-not disabled - [:a.tag.is-delete {:on-click (fn [] - (setInputValue nil) - (selectItem nil) - (when on-change - (on-change nil)))}])]]] - ^{:key "typeahead"} [:input.input (js->clj - (getInputProps #js {:disabled (if disabled - "disabled" - "") - :autoFocus (if auto-focus - "autoFocus" - "")}))]) - [:div {:class (if (and isOpen (seq items)) "typeahead-menu")} - [:ul (js->clj (getMenuProps)) - (if (and isOpen (seq items)) - (for [[index item] (map vector (range) (js->clj items :keywordize-keys true))] - ^{:key item} - [:li.typeahead-suggestion (assoc (js->clj (getItemProps #js {:item item :index index})) - :class (if (= index highlightedIndex) - "typeahead-highlighted")) - (entity->text item)]))]]]])) - -(defn typeahead-v3 [{:keys [class disabled entities entity->text entities-by-id entity-index on-change value ] :as props}] - [:div - [:f> typeahead-v3-internal props]]) +(def typeahead-v3 internal/typeahead-v3) diff --git a/src/cljs/auto_ap/views/components/typeahead/vendor.cljs b/src/cljs/auto_ap/views/components/typeahead/vendor.cljs index 87a07b5f..9991939e 100644 --- a/src/cljs/auto_ap/views/components/typeahead/vendor.cljs +++ b/src/cljs/auto_ap/views/components/typeahead/vendor.cljs @@ -3,26 +3,12 @@ [downshift :as ds :refer [useCombobox]] [re-frame.core :as re-frame] [auto-ap.views.utils :refer [with-user]] - [react])) + + [clojure.string :as str] + [react :as react])) (set! *warn-on-infer* true) -;; TODO: This avoids the use of inferred externs by using aget. You could just use the ^js tag though -(defn state-reducer [^js/FakeStateObject state ^js/FakeActionsAndChanges actions-and-changes] - (let [useCombobox ^js/Downshift useCombobox] - (cond - (= (.-type actions-and-changes) (.-InputChange (.-stateChangeTypes ^js/Downshift useCombobox))) - (set! (.-selectedItem (.-changes actions-and-changes)) nil) - - (and (= (.-type actions-and-changes) (.-InputBlur (.-stateChangeTypes ^js/Downshift useCombobox))) - (not (.-selectedItem state))) - (set! (.-inputValue (.-changes actions-and-changes )) - nil) - - :else - nil)) - (.-changes actions-and-changes)) - (re-frame/reg-event-fx ::search-completed (fn [_ [_ set-items set-loading-status result]] @@ -41,13 +27,30 @@ (when (> (count input-value) 2) (set-loading-status :loading) - - {:graphql {:token user - :query-obj {:venia/queries [{:query/data (search-query input-value ) - :query/alias :search-results}]} + + {:graphql {:token user + :query-obj {:venia/queries [{:query/data (search-query input-value ) + :query/alias :search-results}]} :on-success [::search-completed set-items set-loading-status] :on-error [::search-failed set-loading-status]}}))) +;; TODO: This avoids the use of inferred externs by using aget. You could just use the ^js tag though +(defn state-reducer [^js/FakeStateObject state ^js/FakeActionsAndChanges actions-and-changes] + (let [useCombobox ^js/Downshift useCombobox] + (cond + (= (.-type actions-and-changes) (.-InputChange (.-stateChangeTypes ^js/Downshift useCombobox))) + (set! (.-selectedItem (.-changes actions-and-changes)) nil) + + (and (= (.-type actions-and-changes) (.-InputBlur (.-stateChangeTypes ^js/Downshift useCombobox))) + (not (.-selectedItem state))) + (set! (.-inputValue (.-changes actions-and-changes )) + nil) + + :else + nil)) + (.-changes actions-and-changes)) + + (re-frame/reg-event-fx ::input-value-changed (fn [_ [_ input-value search-query set-items set-loading-status]] @@ -58,73 +61,116 @@ :time 250 :key ::input-value-settled}}))) -(defn typeahead-v3-internal [{:keys [class style ^js on-change disabled value name search-query auto-focus] :or {disabled false} :as i}] - (let [[items set-items] (react/useState []) +(defn typeahead-v3-internal [{:keys [class entity->text entities on-input-change style ^js on-change disabled value name auto-focus] :or {disabled false} :as i}] + (let [[items set-items] (react/useState (or (clj->js entities) + [])) + [focused set-focus] (react/useState (boolean auto-focus)) [loading-status set-loading-status] (react/useState false) - [getLabelProps getMenuProps getComboboxProps getToggleButtonProps getInputProps getItemProps isOpen highlightedIndex selectItem selectedItem setInputValue] + [getMenuProps getComboboxProps getInputProps getItemProps isOpen highlightedIndex selectItem selectedItem setInputValue] (as-> (useCombobox (clj->js {:items items :defaultHighlightedIndex 0 :defaultSelectedItem value + :itemToString (fn [] + ;; once an item is selected, you just use empty text + "") :onInputValueChange (fn [input] - (re-frame/dispatch [::input-value-changed (aget input "inputValue") search-query set-items set-loading-status]) - true) + (on-input-change input set-items set-loading-status)) :stateReducer state-reducer :onSelectedItemChange (fn [z] (when on-change (on-change (js->clj (aget z "selectedItem") :keywordize-keys true))))})) $ - (map #(aget $ %) ["getLabelProps" "getMenuProps" "getComboboxProps" "getToggleButtonProps" "getInputProps" "getItemProps" "isOpen" "highlightedIndex" "selectItem" "selectedItem" "setInputValue"]))] + (map #(aget $ %) ["getMenuProps" "getComboboxProps" "getInputProps" "getItemProps" "isOpen" "highlightedIndex" "selectItem" "selectedItem" "setInputValue"]))] [:<> [:div.typeahead (assoc (js->clj (getComboboxProps)) :style style) - (cond - selectedItem - ^{:key "typeahead"} - [:div.input (assoc (js->clj (getInputProps #js {:disabled (if disabled - "disabled" - "")})) - :on-key-up (fn [e] - (when (= 8 (aget e "keyCode" )) - (selectItem nil) - (setInputValue nil) - (when on-change - (on-change nil)))) - :class (if (= :loading loading-status) - "is-loading" - class) - :tab-index "0") - [:div.control - [:div.tags.has-addons - [:span.tag (:name (js->clj selectedItem :keywordize-keys true))] - (when name - [:input {:type "hidden" :name name :value (:id (js->clj selectedItem :keywordize-keys true))}]) - (when-not disabled - [:a.tag.is-delete {:on-click (fn [] - (setInputValue nil) - (selectItem nil) - (when on-change - (on-change nil)))}])]]] + [:div.input {:on-key-up (when selectedItem (fn [e] + (when (= 8 (aget e "keyCode" )) + (selectItem nil) + (setInputValue nil) + (when on-change + (on-change nil))))) + :disabled (cond disabled + "disabled" - :else - ^{:key "typeahead"} [:div.control {:class (when (= :loading loading-status) - "is-loading")} - [:input.input (js->clj - (getInputProps #js {:disabled (if disabled - "disabled" - "") - :autoFocus (if auto-focus - "autoFocus" - "")}))]]) + :else + "") + :class + (cond-> [] + (sequential? class) + (into class) + + (not (sequential? class)) + (conj class) + + focused + (conj "is-focused") + )} + (when selectedItem + ^{:key "hidden"} + [:div.level-item + [:div.control + [:div.tags.has-addons + [:span.tag (:name (js->clj selectedItem :keywordize-keys true))] + (when name + [:input {:type "hidden" :name name :value (:id (js->clj selectedItem :keywordize-keys true))}]) + (when-not disabled + [:a.tag.is-delete {:on-click (fn [] + (setInputValue nil) + (selectItem nil) + (when on-change + (on-change nil)))}])]]]) + ^{:key "main"} + [:div.control {:class (when (= :loading loading-status) + "is-loading") + :style {:padding "0px" + :width "100%" + :height "2em" + :margin "0px"}} + [:input (js->clj (getInputProps #js {:style #js {:border "0px" + :height "2em" + :width "100%" + "outline" "none"} + :disabled disabled + + :onFocus #(set-focus true) + :onBlur #(set-focus false) + :autoFocus (if auto-focus + "autoFocus" + "")}))]]] [:div {:class (when (and isOpen (seq items)) "typeahead-menu")} [:ul (js->clj (getMenuProps)) - (if (and isOpen (seq items)) + (when (and isOpen (seq items)) (for [[index item] (map vector (range) (js->clj items :keywordize-keys true))] ^{:key item} [:li.typeahead-suggestion (assoc (js->clj (getItemProps #js {:item item :index index})) - :class (if (= index highlightedIndex) + :class (when (= index highlightedIndex) "typeahead-highlighted")) - (:name item)]))]]]])) + (if entity->text + (entity->text item) + (:name item))]))]]]])) -(defn search-backed-typeahead [props] +(defn search-backed-typeahead [{:keys [search-query] :as props}] [:div - [:f> typeahead-v3-internal props]]) + [:f> typeahead-v3-internal (assoc props + :on-input-change + (fn [input set-items set-loading-status] + (re-frame/dispatch [::input-value-changed (aget input "inputValue") search-query set-items set-loading-status]) + true))]]) + + +(defn typeahead-v3 [{:keys [entities ^js entity->text entities-by-id entity-index] :as props}] + [:div + [:f> typeahead-v3-internal (assoc props + :on-input-change + (fn [input set-items] + (if entities-by-id + (do + (->> (.search entity-index (or (aget input "inputValue") "") #js {:fuzzy 0.2} ) + clj->js + (take 10) + (set-items))) + + (set-items (map clj->js (take 10 (filter (fn [x] (str/includes? (or (some-> (entity->text x) str/lower-case) "") + (or (some-> (aget input "inputValue") str/lower-case) ""))) + entities)))))))]]) diff --git a/src/cljs/auto_ap/views/components/vendor_dialog.cljs b/src/cljs/auto_ap/views/components/vendor_dialog.cljs index 156c2f3e..6d2ee36e 100644 --- a/src/cljs/auto_ap/views/components/vendor_dialog.cljs +++ b/src/cljs/auto_ap/views/components/vendor_dialog.cljs @@ -3,23 +3,24 @@ [auto-ap.entities.contact :as contact] [auto-ap.entities.vendors :as entity] [auto-ap.forms :as forms] + [auto-ap.forms.builder :as form-builder] [auto-ap.status :as status] + [auto-ap.views.components.level :refer [left-stack]] [auto-ap.subs :as subs] [auto-ap.views.components.address :refer [address2-field]] - [auto-ap.views.components.typeahead.vendor - :refer [search-backed-typeahead]] [auto-ap.views.components.modal :as modal] [auto-ap.views.components.typeahead :refer [typeahead-v3]] + [auto-ap.views.components.typeahead.vendor + :refer [search-backed-typeahead]] [auto-ap.views.pages.admin.vendors.common :as common] [auto-ap.views.utils - :refer [bind-field - dispatch-event - horizontal-field - with-is-admin? - with-user]] + :refer [dispatch-event multi-field str->int with-is-admin? with-user]] [clojure.spec.alpha :as s] [re-frame.core :as re-frame] - [auto-ap.forms.builder :as form-builder])) + [reagent.core :as r])) + +;; Remaining cleanup todos: +;; test minification (re-frame/reg-sub ::can-submit @@ -27,37 +28,6 @@ (fn [form] (s/valid? ::entity/vendor (:data form)))) -(re-frame/reg-event-db - ::removed-override - [(forms/in-form ::vendor-form)] - (fn [form [_ override-key index]] - - (update-in form [:data override-key] - (fn [overrides] - (reduce - (fn [overrides [i override]] - (if (= i index) - overrides - (conj overrides override))) - [] - (map vector (range) overrides)))))) - - - - -(re-frame/reg-event-db - ::changed - [(forms/settles {:key ::vendor-form - :time 500 - :event [::settled]})] - (forms/change-handler - ::vendor-form - (fn [data field _] - (let [[override-key? i?] field] - (if (and (#{:account-overrides :terms-overrides :schedule-payment-dom} override-key?) - (nil? (get-in data [override-key? i? :key]))) - [[override-key? i? :key] (random-uuid)] - []))))) (re-frame/reg-event-fx ::save-complete @@ -74,7 +44,8 @@ {:vendor (cond-> {:id id :name name :print-as print-as - :terms terms + :terms (or (str->int terms) + 0) :default-account-id (:id default-account) :address address :primary-contact primary-contact @@ -82,28 +53,29 @@ :invoice-reminder-schedule invoice-reminder-schedule} is-admin? (assoc :hidden hidden :terms-overrides (mapv - (fn [{:keys [client override id]}] + (fn [{:keys [client terms id]}] {:id id :client-id (:id client) - :terms override}) + :terms (or (str->int terms) 0)}) terms-overrides) :account-overrides (mapv - (fn [{:keys [client override id]}] + (fn [{:keys [client account id]}] {:id id :client-id (:id client) - :account-id (:id override)}) + :account-id (:id account)}) account-overrides) :schedule-payment-dom (mapv - (fn [{:keys [client override id]}] + (fn [{:keys [client dom id]}] {:id id :client-id (:id client) - :dom override}) + :dom (or (str->int dom) + 0)}) schedule-payment-dom) :automatically-paid-when-due (mapv - :id + (comp :id :client) automatically-paid-when-due) :legal-entity-first-name legal-entity-first-name - :legal-entity-middle-name legal-entity-middle-name + :legal-entity-middle-name legal-entity-middle-name :legal-entity-last-name legal-entity-last-name :legal-entity-tin legal-entity-tin :legal-entity-tin-type (some-> legal-entity-tin-type clojure.core/name not-empty keyword) @@ -118,239 +90,176 @@ :operation/name "UpsertVendor"} :venia/queries [{:query/data query}]} :on-success [::save-complete]}})))) -(defn client-list [{:keys [override-key data]}] - (let [clients @(re-frame/subscribe [::subs/clients])] - [form-builder/horizontal-control - "Client" - (doall - (for [[i override] (map vector (range) (conj (override-key data) {:key (random-uuid)}))] - ^{:key (or (:id override) - (:key override))} - [:div.level - [:div.level-left - [:div.level-item - [form-builder/raw-field - [typeahead-v3 {:entities clients - :entity->text :name - :style {:width "13em"} - :type "typeahead-v3" - :field [override-key i]}]]] - - [:div.level-item - [:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]]))])) +(defn pull-left [] + (into [:div {:style {:position "relative" + :left "-40px"}}] + (r/children (r/current-component)))) -(defn default-with-overrides [{:keys [override-key default-key data mandatory?]} template] - (let [clients @(re-frame/subscribe [::subs/clients])] - [:div - [form-builder/horizontal-control - [:span "Default" - (when mandatory? - [:span.has-text-danger " *"])] - (template default-key nil)] - [form-builder/horizontal-control - "Overrides" - (doall - (for [[i override] (map vector (range) (conj (override-key data) {:key (random-uuid)}))] - ^{:key (or - (:id override) - (:key override) - )} - [:div.level - [:div.level-left - [:div.level-item - [form-builder/raw-field - [typeahead-v3 {:entities clients - :entity->text :name - :style {:width "13em"} - :type "typeahead-v3" - :field [override-key i :client]}]]] - [:div.level-item - (template - [override-key i :override] - (get-in data [override-key i :client]))] - [:div.level-item - [:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]]))]])) +(defn contact-field [{:keys [name field]}] + [form-builder/with-scope {:scope field} + [form-builder/vertical-control + name + [left-stack + [form-builder/vertical-control {:is-small? true} + "Name" + [:div.control.has-icons-left + [form-builder/raw-field + [:input.input.is-expanded {:type "text" + :field :name + :spec ::contact/name}]] + [:span.icon.is-small.is-left + [:i.fa.fa-user]]]] + [form-builder/vertical-control {:is-small? true} + "Email" + + [:div.control.has-icons-left + [:span.icon.is-small.is-left + [:i.fa.fa-envelope]] + [form-builder/raw-field + [:input.input {:type "email" + :field :email + :spec ::contact/email}]]]] + [form-builder/vertical-control {:is-small? true} + "Phone" + [:div.control.has-icons-left + [form-builder/raw-field + [:input.input {:type "phone" + :field :phone + :spec ::contact/phone}]] + [:span.icon.is-small.is-left + [:i.fa.fa-phone]]]]]]]) -(defn client-overrides [{:keys [override-key data]} template] - (let [clients @(re-frame/subscribe [::subs/clients])] - [form-builder/horizontal-control - "Overrides" - (doall - (for [[i override] (map vector (range) (conj (override-key data) {:key (random-uuid)}))] - ^{:key (or - (:id override) - (:key override))} - [:div.level - [:div.level-left - [:div.level-item - [form-builder/raw-field - [typeahead-v3 {:entities clients - :entity->text :name - :style {:width "13em"} - :type "typeahead-v3" - :field [override-key i :client]}]]] - [:div.level-item - (template - [override-key i :override] - (get-in data [override-key i :client]))] - [:div.level-item - [:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]]))])) (defn form-content [{:keys [data]}] - - (let [is-admin? @(re-frame/subscribe [::subs/is-admin?])] + (let [is-admin? @(re-frame/subscribe [::subs/is-admin?]) + clients @(re-frame/subscribe [::subs/clients])] [form-builder/builder {:submit-event [::save] :can-submit [::can-submit] - :change-event [::changed] :id ::vendor-form} - [:div - [form-builder/horizontal-field - [:span "Name " [:span.has-text-danger "*"]] - [:input.input {:type "text" - :auto-focus true - :field :name - :spec ::entity/name}]] + [form-builder/field + [:span "Name " [:span.has-text-danger "*"]] + [:input.input {:type "text" + :auto-focus true + :field :name + :spec ::entity/name}]] - [form-builder/horizontal-field - "Print Checks As" - [:input.input {:type "text" - :field :print-as - :spec ::entity/print-as}]] + [form-builder/field + "Print Checks As" + [:input.input {:type "text" + :field :print-as + :spec ::entity/print-as}]] + (when is-admin? + [:div.field + [:label.checkbox + [form-builder/raw-field + [:input {:type "checkbox" + :field [:hidden] + :spec ::entity/hidden}]] + " Hidden"]]) + + [form-builder/section {:title "Terms"} + [form-builder/field + "Terms" + [:input.input {:type "number" + :step "1" + :style {:width "4em"} + :field [:terms] + :size 3 + :spec ::entity/terms}]] (when is-admin? - [form-builder/horizontal-field - "Hidden" - [:input {:type "checkbox" - :field :hidden - :spec ::entity/hidden}]]) + [form-builder/field + "Overrides" + [multi-field {:type "multi-field" + :field [:terms-overrides] + :template [[typeahead-v3 {:entities clients + :entity->text :name + :style {:width "13em"} + :type "typeahead-v3" + :field [:client]}] + [:input.input {:type "number" + :step "1" + :style {:width "4em"} + :field [:terms] + :size 3 + :spec ::entity/terms}]]}]]) + ] + (when is-admin? + [form-builder/section {:title "Schedule payment when due"} + [form-builder/field + "Client" + [multi-field {:type "multi-field" + :field [:automatically-paid-when-due] + :template [[typeahead-v3 {:entities clients + :entity->text :name + :style {:width "13em"} + :type "typeahead-v3" + :field [:client]}]]}]]]) - (if is-admin? - [:<> - [form-builder/section {:title "Terms"} - [default-with-overrides {:data data - :default-key :terms - :override-key :terms-overrides} - (fn [field _] - [form-builder/raw-field - [:input.input {:type "number" - :step "1" - :style {:width "4em"} - :field field - :size 3 - :spec ::entity/terms}]])]]] - - [form-builder/horizontal-field - [:span "Terms"] - [:input.input {:type "number" - :step "1" - :style {:width "4em"} - :field :terms - :size 3 - :spec ::entity/terms}]]) + (when is-admin? + [form-builder/section {:title "Schedule payment on day of month"} + [form-builder/field + "Overrides" + [multi-field {:type "multi-field" + :field [:schedule-payment-dom] + :template [[typeahead-v3 {:entities clients + :entity->text :name + :style {:width "13em"} + :type "typeahead-v3" + :field [:client]}] + [:input.input {:type "number" + :step "1" + :style {:width "4em"} + :field [:dom] + :size 3 + :spec ::entity/terms}]]}]]]) + [form-builder/section {:title "Expense Accounts"} + [form-builder/field + "Default *" + [search-backed-typeahead {:search-query (fn [i] + [:search_account + {:query i} + [:name :id]]) + :type "typeahead-v3" + :style {:width "19em"} + :field [:default-account]}]] (when is-admin? - [form-builder/section {:title "Schedule payment when due"} - [client-list {:data data - :override-key :automatically-paid-when-due}]]) + [form-builder/field + "Overrides" + [multi-field {:type "multi-field" + :field [:account-overrides] + :template (fn [entity] + [[typeahead-v3 {:entities clients + :entity->text :name + :style {:width "19em"} + :type "typeahead-v3" + :field [:client]}] + [search-backed-typeahead {:search-query (fn [i] + [:search_account + {:query i + :client_id (:id (:client entity))} + [:name :id]]) + :type "typeahead-v3" + :style {:width "15em"} + :field [:account]}]])}]])] - (when is-admin? - [form-builder/section {:title "Schedule payment on day of month"} - [client-overrides {:data data - :mandatory? true - :override-key :schedule-payment-dom} - (fn [field _] - [form-builder/raw-field - [:input.input {:type "number" - :step "1" - :style {:width "5em"} - :field field - :size 3 - :spec ::entity/dom}]])]]) - - (if is-admin? - [form-builder/section {:title "Expense Accounts"} - [default-with-overrides {:data data - :mandatory? true - :default-key :default-account - :override-key :account-overrides} - (fn [field client] - [form-builder/raw-field - [search-backed-typeahead {:search-query (fn [i] - [:search_account - {:query i - :client-id (:id client)} - [:name :id]]) - :type "typeahead-v3" - :style {:width "15em"} - :field field}]])]] + [form-builder/with-scope {:scope [:address ]} + [form-builder/section {:title "Address"} + [:div {:style {:width "30em"}} + [address2-field]]]] + + [form-builder/section {:title "Contact"} + [contact-field {:name "Primary" + :field [:primary-contact]}] + [contact-field {:name "Secondary" + :field [:secondary-contact]}]] + - [form-builder/horizontal-field - "Expense Account" - [search-backed-typeahead {:search-query (fn [i] - [:search_account - {:query i} - [:name :id]]) - :type "typeahead-v3" - :field :default-account}]]) - - [form-builder/with-scope {:scope [:address ]} - [form-builder/section {:title "Address"} - [address2-field]]] - - [form-builder/section {:title "Contact"} - [form-builder/horizontal-control - "Primary" - [:div.control.has-icons-left - [form-builder/raw-field - [:input.input.is-expanded {:type "text" - :field [:primary-contact :name] - :spec ::contact/name}]] - [:span.icon.is-small.is-left - [:i.fa.fa-user]]] - - [:div.control.has-icons-left - [:span.icon.is-small.is-left - [:i.fa.fa-envelope]] - [form-builder/raw-field - [:input.input {:type "email" - :field [:primary-contact :email] - :spec ::contact/email}]]] - - [:div.control.has-icons-left - [form-builder/raw-field - [:input.input {:type "phone" - :field [:primary-contact :phone] - :spec ::contact/phone}]] - [:span.icon.is-small.is-left - [:i.fa.fa-phone]]]] - [form-builder/horizontal-control - "Secondary" - [:div.control.has-icons-left - [form-builder/raw-field - [:input.input.is-expanded {:type "text" - :field [:secondary-contact :name] - :spec ::contact/name}]] - [:span.icon.is-small.is-left - [:i.fa.fa-user]]] - [:div.control.has-icons-left - [:span.icon.is-small.is-left - [:i.fa.fa-envelope]] - [form-builder/raw-field - [:input.input {:type "email" - :field [:secondary-contact :email] - :spec ::contact/email}]]] - [:div.control.has-icons-left - [form-builder/raw-field - [:input.input {:type "phone" - :field [:secondary-contact :phone] - :spec ::contact/phone}]] - [:span.icon.is-small.is-left - [:i.fa.fa-phone]]]]] - - - (when is-admin? - [form-builder/section {:title "Legal Entity"} - [form-builder/horizontal-control - "Name" + (when is-admin? + [form-builder/section {:title "Legal Entity"} + [form-builder/vertical-control + "Name" + [left-stack [:div.control [form-builder/raw-field [:input.input {:type "text" @@ -370,16 +279,16 @@ [:input.input {:type "text" :placeholder "Last Name" :field [:legal-entity-last-name] - :spec ::contact/name}]]]] - [form-builder/horizontal-control - "TIN" - [:div.control - [form-builder/raw-field - [:input.input {:type "text" - :placeholder "SSN or EIN" - :field [:legal-entity-tin] - :size "12" - :spec ::contact/name}]]] + :spec ::contact/name}]]]]] + [form-builder/vertical-control + "TIN" + [left-stack + [form-builder/raw-field + [:input.input {:type "text" + :placeholder "SSN or EIN" + :field [:legal-entity-tin] + :size "12" + :spec ::contact/name}]] [:div.control [:div.select @@ -388,19 +297,20 @@ :field [:legal-entity-tin-type]} [:option {:value nil} ""] [:option {:value "ein"} "EIN"] - [:option {:value "ssn"} "SSN"]]]]]] + [:option {:value "ssn"} "SSN"]]]]]]] - [form-builder/horizontal-control - "1099 Type" - [:div.control - [:div.select - [form-builder/raw-field - [:select {:type "select" - :field [:legal-entity-1099-type]} - [:option {:value nil} ""] - [:option {:value "none"} "Don't 1099"] - [:option {:value "misc"} "Misc"] - [:option {:value "landlord"} "Landlord"]]]]]]])]])) + [form-builder/vertical-control + "1099 Type" + [:div.control + [:div.select + [form-builder/raw-field + [:select {:type "select" + :field [:legal-entity-1099-type]} + [:option {:value nil} ""] + [:option {:value "none"} "Don't 1099"] + [:option {:value "misc"} "Misc"] + [:option {:value "landlord"} "Landlord"]]]]]]]) + [form-builder/hidden-submit-button]])) (defn vendor-dialog [ ] (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::vendor-form])] @@ -417,7 +327,6 @@ common/default-read]]} :owns-state {:single ::select-vendor-form} :on-success (fn [r] - (println (:vendor-by-id r)) [::started (:vendor-by-id r)])}})) (re-frame/reg-sub @@ -442,7 +351,7 @@ :type "typeahead-v3" :auto-focus true :field [:vendor]}]] - #_[form-builder/submit-button "Save"]]) + [form-builder/hidden-submit-button]]) @@ -450,33 +359,17 @@ ::started (fn [{:keys [db]} [_ vendor]] {:db (-> db (forms/start-form ::vendor-form (-> vendor - (update :account-overrides #(mapv - (fn [ao] - {:id (:id ao) - :client (:client ao) - :override (:account ao)}) - %)) - - (update :schedule-payment-dom #(mapv - (fn [spdom] - {:id (:id spdom) - :client (:client spdom) - :override (:dom spdom)}) - %)) - (update :terms-overrides #(mapv - (fn [to] - {:id (:id to) - :client (:client to) - :override (:terms to)}) - %)) - (update :automatically-paid-when-due #(mapv identity %)) + (update :automatically-paid-when-due #(mapv (fn [apwd] + {:id (:id apwd) + :client apwd}) + %)) (update :hidden #(if (nil? %) false %))))) :dispatch [::modal/modal-requested {:title "Vendor" - :class "is-wide" :body [vendor-dialog] + :class "semi-wide" :confirm {:value "Save Vendor" :status-from [::status/single ::vendor-form] :class "is-primary" @@ -490,7 +383,6 @@ {:db (-> db (forms/start-form ::select-vendor-form {})) :dispatch [::modal/modal-requested {:title "Select Vendor" - :class "is-wide" :body [select-vendor-form-content] :confirm {:value "Choose a vendor" :status-from [::status/single ::select-vendor-form] diff --git a/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs b/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs index b229cc4d..e83eb8ad 100644 --- a/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs @@ -139,12 +139,12 @@ [multi-field {:type "multi-field" :field [:client-overrides] :template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients]) - :style {:width "20em"} + :style {:width "13em"} :entity->text :name :type "typeahead-v3" :field [:client]}] [:input.input {:type "text" - :style {:width "20em"} + :style {:width "15em"} :placeholder "Bubblegum" :field [:name]}] ]}]) diff --git a/src/cljs/auto_ap/views/pages/admin/clients/form.cljs b/src/cljs/auto_ap/views/pages/admin/clients/form.cljs index 3a0f6fe7..ee55c28c 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients/form.cljs @@ -9,6 +9,7 @@ [auto-ap.views.components.address :refer [address2-field]] [react-signature-canvas] [auto-ap.views.components.typeahead :refer [typeahead-v3]] + [auto-ap.views.components.level :refer [left-stack]] [auto-ap.views.components.typeahead.vendor :refer [search-backed-typeahead]] [auto-ap.views.utils @@ -350,8 +351,6 @@ (#{:credit ":credit"} type) [:span.icon-credit-card-1] :else [:span.icon-accounting-bill])]] - #_[:div.level-item - ] [:div.level-item code ": " name]] [:div.level-right [:div.level-item @@ -647,7 +646,7 @@ :style { :width "15em"}}] [:input.input {:field [:location] :placeholder "DT" - :maxlength 2 + :max-length 2 :style { :width "4em"}}]]}]]]) (defn bank-accounts-section [] @@ -674,41 +673,35 @@ [form-builder/section {:title "Cash Flow"} [:label.label (str "Week A (" next-week-a ")")] - [:div.level - [:div.level-left - [:div.level-item - [form-builder/field - "Regular Credits" - [:input.input {:type "number" - :style {:width "10em"} - :placeholder "500.00" - :field [:week-a-credits] - :step "0.01"}]]] - [:div.level-item - [form-builder/field - "Regular Debits" - [:input.input {:type "number" - :style {:width "10em"} - :placeholder "150.00" - :field [:week-a-debits] - :step "0.01"}]]]]] + [left-stack + [form-builder/field + "Regular Credits" + [:input.input {:type "number" + :style {:width "10em"} + :placeholder "500.00" + :field [:week-a-credits] + :step "0.01"}]] + [form-builder/field + "Regular Debits" + [:input.input {:type "number" + :style {:width "10em"} + :placeholder "150.00" + :field [:week-a-debits] + :step "0.01"}]]] [:label.label (str "Week B (" next-week-b ")")] - [:div.level - [:div.level-left - [:div.level-item - [form-builder/field "Regular Credits" - [:input.input {:type "number" - :style {:width "10em"} - :placeholder "1000.00" - :field [:week-b-credits] - :step "0.01"}]]] - [:div.level-item - [form-builder/field "Regular Debits" - [:input.input {:type "number" - :style {:width "10em"} - :placeholder "250.00" - :field [:week-b-debits] - :step "0.01"}]]]]] + [left-stack + [form-builder/field "Regular Credits" + [:input.input {:type "number" + :style {:width "10em"} + :placeholder "1000.00" + :field [:week-b-credits] + :step "0.01"}]] + [form-builder/field "Regular Debits" + [:input.input {:type "number" + :style {:width "10em"} + :placeholder "250.00" + :field [:week-b-debits] + :step "0.01"}]]] [:div.field [:label.label "Forecasted transactions"] diff --git a/src/cljs/auto_ap/views/utils.cljs b/src/cljs/auto_ap/views/utils.cljs index ee521386..f5254d9d 100644 --- a/src/cljs/auto_ap/views/utils.cljs +++ b/src/cljs/auto_ap/views/utils.cljs @@ -148,55 +148,59 @@ value (conj value {:key (random-uuid) :new? true}))] - [:div {:style {:margin-bottom "1em"}} + [:div {:style {:margin-bottom "0.25em"}} (for [[i override] (map vector (range) value) :let [is-disabled? (if (= false allow-change?) (not (boolean (:new? override))) nil)] ] ^{:key (:key override)} - [:div.level - [:div.level-left {:style (when (and (= i (dec (count value))) - (:new? override)) - {:background "#EEE" - :padding "0.25em 1em 0.25em 0em"})} - [:div.level-item - (if (:new? override) - - [:div.icon.is-medium {:class (when (not= i (dec (count value))) - "has-text-info")} - [:i.fa.fa-plus]] - [:div.icon.is-medium])] - [:<> (for [[idx template] (map vector (range ) template)] - ^{:key idx} + [:div.level {:style {:margin-bottom "0.25em"}} + [:div.level-left {:style {:padding "0.5em 1em"} + :class (cond + (and (= i (dec (count value))) + (:new? override)) + "has-background-light" - [:div.level-item - (update template 1 assoc - :value (let [value (get-in override (get-in template [1 :field])) ;; TODO this is really ugly to support maps or strings - value (if (map? value) - (dissoc value :key :new?) - value)] - (if (= value {}) - nil - value)) - :disabled (or is-disabled? (get-in template [1 :disabled])) - :on-change (fn [e] - (reset! value-repr - (into [] - (filter (fn [r] - (not= [:key :new?] (keys r))) - (assoc-in value (into [i] (get-in template [1 :field])) - (let [this-value (if (and e (.. e -target)) - (.. e -target -value ) - e)] - (if (map? this-value) - (update this-value :key (fnil identity (random-uuid))) - this-value)) )))) - (on-change (mapv - (fn [v] - (dissoc v :new? :key)) - @value-repr))))]) - ] + (:new? override) + "has-background-info-light" + :else + "")} + (let [template (if (fn? template) + (template override) + template)] + + [:<> (for [[idx template] (map vector (range ) template)] + ^{:key idx} + + [:div.level-item + (update template 1 assoc + :value (let [value (get-in override (get-in template [1 :field])) ;; TODO this is really ugly to support maps or strings + value (if (map? value) + (dissoc value :key :new?) + value)] + (if (= value {}) + nil + value)) + :disabled (or is-disabled? (get-in template [1 :disabled])) + :on-change (fn [e] + (reset! value-repr + (into [] + (filter (fn [r] + (not= [:key :new?] (keys r))) + (assoc-in value + (into [i] (get-in template [1 :field])) + (let [this-value (if (and e (.. e -target)) + (.. e -target -value ) + e)] + (if (map? this-value) + (update this-value :key (fnil identity (random-uuid))) + this-value)) )))) + (on-change (mapv + (fn [v] + (dissoc v :new? :key)) + @value-repr))))]) + ]) (when-not disable-remove? [:div.level-item [:a.button.level-item @@ -537,3 +541,18 @@ (defn account->match-text [x] (str (:numeric-code x) " - " (:name x))) + +(defn str->int [x] + (cond + (nil? x) + nil + + (and (string? x) + (str/blank? x)) + nil + + (string? x) + (js/parseInt x) + + :else + x)) diff --git a/test/clj/auto_ap/import/yodlee_test.clj b/test/clj/auto_ap/import/yodlee_test.clj index 00625ec9..4c1d2b91 100644 --- a/test/clj/auto_ap/import/yodlee_test.clj +++ b/test/clj/auto_ap/import/yodlee_test.clj @@ -17,13 +17,15 @@ (t/deftest yodlee->transaction (t/testing "Should parse dates" - (t/is (= #inst "2021-01-01T00:00:00-08:00" (:transaction/date (sut/yodlee->transaction (assoc base-transaction :date "2021-01-01"))))) - (t/is (= #inst "2021-06-01T00:00:00-07:00" (:transaction/date (sut/yodlee->transaction (assoc base-transaction :date "2021-06-01")))))) + (t/is (= #inst "2021-01-01T00:00:00-08:00" (:transaction/date (sut/yodlee->transaction (assoc base-transaction :date "2021-01-01") false)))) + (t/is (= #inst "2021-06-01T00:00:00-07:00" (:transaction/date (sut/yodlee->transaction (assoc base-transaction :date "2021-06-01") false))))) (t/testing "Should invert amount for debits" (t/is (= -12.0 (:transaction/amount (sut/yodlee->transaction (assoc base-transaction :amount {:amount 12.0} - :baseType "DEBIT"))))) + :baseType "DEBIT") + false)))) (t/is (= 12.0 (:transaction/amount (sut/yodlee->transaction (assoc base-transaction :amount {:amount 12.0} - :baseType "CREDIT"))))))) + :baseType "CREDIT") + false)))))) diff --git a/test/clj/auto_ap/routes/invoice_test.clj b/test/clj/auto_ap/routes/invoice_test.clj index 60e22d60..599a25f6 100644 --- a/test/clj/auto_ap/routes/invoice_test.clj +++ b/test/clj/auto_ap/routes/invoice_test.clj @@ -1,4 +1,4 @@ -(ns auto-ap.routes.invoices-test +(ns auto-ap.routes.invoice-test (:require [auto-ap.datomic :refer [uri conn]] [auto-ap.datomic.migrate :as m] diff --git a/test/clj/auto_ap/routes/invoices_test.clj b/test/clj/auto_ap/routes/invoices_test.clj deleted file mode 100644 index 37e2b1aa..00000000 --- a/test/clj/auto_ap/routes/invoices_test.clj +++ /dev/null @@ -1,3 +0,0 @@ -(ns auto-ap.routes.invoice-test - (:require [clojure.test :as t])) -