diff --git a/data/etc/nginx/conf.d/default.conf b/data/etc/nginx/conf.d/default.conf index aed99293..6306dd12 100644 --- a/data/etc/nginx/conf.d/default.conf +++ b/data/etc/nginx/conf.d/default.conf @@ -44,9 +44,6 @@ proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl; proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; # Mitigate httpoxy attack (see README for details) proxy_set_header Proxy ""; -proxy_buffer_size 128k; -proxy_buffers 4 256k; -proxy_busy_buffers_size 256k; server { server_name _; # This is just an invalid value which will never trigger on a real hostname. listen 80; diff --git a/data/etc/nginx/conf.d/nginx.conf b/data/etc/nginx/conf.d/nginx.conf index 81c2f6fe..5f312e04 100644 --- a/data/etc/nginx/conf.d/nginx.conf +++ b/data/etc/nginx/conf.d/nginx.conf @@ -1,3 +1,7 @@ gzip on; gzip_types application/edn; +proxy_connect_timeout 300; +proxy_send_timeout 300; +proxy_read_timeout 300; +send_timeout 300; diff --git a/scratch-sessions/fixing_transactions_manually_imported_incorrectl.clj b/scratch-sessions/fixing_transactions_manually_imported_incorrectl.clj index 48625efb..08b33fc1 100644 --- a/scratch-sessions/fixing_transactions_manually_imported_incorrectl.clj +++ b/scratch-sessions/fixing_transactions_manually_imported_incorrectl.clj @@ -1,4 +1,4 @@ -(ns fixing-transactions-manually-imported-incorrectl) +(ns auto-ap.yodlee.import) ;; This buffer is for Clojure experiments and evaluation. @@ -14,15 +14,31 @@ (first all-ts) + (def g (auto-ap.yodlee.import/get-existing)) -(map - :transaction/yodlee-merchant - (let [all-bank-accounts (get-all-bank-accounts) +(first + (let [all-bank-accounts (auto-ap.yodlee.import/get-all-bank-accounts) transaction->bank-account (comp (by :bank-account/yodlee-account-id all-bank-accounts) :accountId) all-rules (tr/get-all)] (transactions->txs all-ts transaction->bank-account (rm/rule-applying-fn all-rules) (get-existing)))) (count g) +(with-open [writer (java.io.StringWriter.)] + (clojure.data.csv/write-csv writer + (->> + (let [all-bank-accounts (auto-ap.yodlee.import/get-all-bank-accounts) + all-clients (by :db/id (d-clients/get-all)) + transaction->bank-account (comp (by :bank-account/yodlee-account-id all-bank-accounts) :accountId) + all-bank-accounts (by :db/id all-bank-accounts) + all-rules (tr/get-all)] + (->> (transactions->txs all-ts transaction->bank-account (rm/rule-applying-fn all-rules) (get-existing)) + (map (fn [r] + [(:transaction/id r) (:transaction/amount r) + (coerce/to-date-time (:transaction/date r)) + (:transaction/description-original r) + (:bank-account/code (all-bank-accounts (:transaction/bank-account r))) + (:client/code (all-clients (:transaction/client r)))])))))) + (println (.toString writer))) (g (digest/sha-256 (str 2553824503))) @@ -32,20 +48,117 @@ (with-open [writer (java.io.StringWriter.)] (clojure.data.csv/write-csv writer (->> - (d/query {:query {:find ['?e] + (d/query {:query {:find ['?e '?e2] :in ['$] :where ['[?e :transaction/id ?tid] '[?e :transaction/date ?d] - '(not [?e :transaction/type])]} - :args [(d/since (d/db (d/connect uri)) #inst "2020-06-01")]}) - (map first) + '[?e :transaction/type] + '[?e :transaction/bank-account ?ba] + '[?e2 :transaction/date ?d] + '[(not= ?e2 ?e)] + '[?e :transaction/amount ?a1] + '[?e2 :transaction/amount ?a2] + '[?e2 :transaction/bank-account ?ba] + '[(auto-ap.utils/dollars= ?a1 ?a2)] + '(not [?e2 :transaction/type])]} + :args [(d/since (d/db (d/connect uri)) #inst "2020-04-01")]}) vec - (d/pull-many (d/db (d/connect uri)) - [:db/id - :transaction/date :transaction/id :transaction/amount - :transaction/description-original - {:transaction/client [:client/code]} - {:transaction/bank-account [:bank-account/code]}]) - (map (fn [r] - [(:transaction/id r) (:db/id r) (:transaction/amount r) (coerce/to-date-time (:transaction/date r)) (:transaction/description-original r) (:bank-account/code (:transaction/bank-account r)) (:client/code (:transaction/client r))])))) + (map (fn [[e1 e2]] + [ + (d/pull (d/db (d/connect uri)) + [:db/id + :transaction/date :transaction/id :transaction/amount + :transaction/description-original + {:transaction/client [:client/code]} + {:transaction/bank-account [:bank-account/code]}] + e1) + (d/pull (d/db (d/connect uri)) + [:db/id + :transaction/date :transaction/id :transaction/amount + :transaction/description-original + {:transaction/client [:client/code]} + {:transaction/bank-account [:bank-account/code]}] + e2) + ])) + + (map (fn [[r1 r2]] + [(:transaction/id r1) + (:db/id r1 ) + (:transaction/amount r1) + (coerce/to-date-time (:transaction/date r1)) + (:transaction/description-original r1) + (:bank-account/code (:transaction/bank-account r1)) + (:client/code (:transaction/client r1)) + + (:transaction/id r2) + (:db/id r2) + (:transaction/amount r2) + (coerce/to-date-time (:transaction/date r2)) + (:transaction/description-original r2) + (:bank-account/code (:transaction/bank-account r2)) + (:client/code (:transaction/client r2))]))) + + ) (println (.toString writer))) + + + +(def matches-to-repair2 + (->> + (d/query {:query {:find ['?e '?e2] + :in ['$] + :where ['[?e :transaction/id ?tid] + '[?e :transaction/date ?d] + '[?e :transaction/type] + '[?e :transaction/bank-account ?ba] + '[?e2 :transaction/date ?d] + '[(not= ?e2 ?e)] + '[?e :transaction/amount ?a1] + '[?e2 :transaction/amount ?a2] + '[?e2 :transaction/bank-account ?ba] + '[(auto-ap.utils/dollars= ?a1 ?a2)] + '(not [?e2 :transaction/type])]} + :args [(d/since (d/db (d/connect uri)) #inst "2020-04-01")]}) + vec + (map (fn [[e1 e2]] + [ + (d/pull (d/db (d/connect uri)) + [:db/id + :transaction/date :transaction/id :transaction/amount + :transaction/description-original + :transaction/approval-status + {:transaction/client [:client/code]} + {:transaction/bank-account [:bank-account/code]} + {:journal-entry/_original-entity ['*]}] + e1) + (d/pull (d/db (d/connect uri)) + [:db/id + :transaction/date :transaction/id :transaction/amount + :transaction/description-original + :transaction/approval-status + {:transaction/client [:client/code]} + {:transaction/bank-account [:bank-account/code]} + {:journal-entry/_original-entity ['*]}] + e2) + ])) + )) + +(count matches-to-repair2) + +(defn repair-transaction [[t-auto t-manual]] + (into + [{:db/id "datomic.tx" + :db/doc "Deletes a transaction that was added manually and automatically"} + [:db/retractEntity (:db/id t-auto)]] + (mapv + (fn [j] + [:db/retractEntity (:db/id j)]) + (:journal-entry/_original-entity t-auto)))) + +(defn map-new-transaction-id [[t-auto t-manual]] + [[:db/add (:db/id t-manual) :transaction/id (:transaction/id t-auto)]]) + +(map-new-transaction-id (first matches-to-repair)) + +(doseq [t matches-to-repair] + @(d/transact (d/connect uri) (map-new-transaction-id t))) diff --git a/src/clj/auto_ap/ledger.clj b/src/clj/auto_ap/ledger.clj index 69c95e25..d42a447d 100644 --- a/src/clj/auto_ap/ledger.clj +++ b/src/clj/auto_ap/ledger.clj @@ -33,8 +33,13 @@ (defmethod entity-change->ledger :invoice [db [type id]] - (let [entity (d/pull db ['* {:invoice/vendor '[*] :invoice/payment '[*]}] id)] - (when-not (= true (:invoice/exclude-from-ledger entity)) + (let [entity (d/pull db ['* {:invoice/vendor '[*] + :invoice/payment '[*] + :invoice/status '[:db/ident] + :invoice/import-status '[:db/ident]}] id)] + (when-not (or (= true (:invoice/exclude-from-ledger entity)) + (= :import-status/pending (:db/ident (:invoice/import-status entity))) + (= :invoice-status/voided (:db/ident (:invoice/status entity)))) (remove-nils {:journal-entry/source "invoice" :journal-entry/client (:db/id (:invoice/client entity)) @@ -157,3 +162,5 @@ #_(process-one (d/tx-report-queue (d/connect uri) )) #_(process-all) + +#_(reset! break true) diff --git a/src/clj/auto_ap/parse/excel.clj b/src/clj/auto_ap/parse/excel.clj index 6161e87d..635b197c 100644 --- a/src/clj/auto_ap/parse/excel.clj +++ b/src/clj/auto_ap/parse/excel.clj @@ -1,5 +1,6 @@ (ns auto-ap.parse.excel (:require [auto-ap.parse.templates :as t] + [auto-ap.parse.util :as u] [clojure.string :as str] [dk.ative.docjure.spreadsheet :as d]) (:import (org.apache.poi.ss.util CellAddress))) @@ -9,7 +10,7 @@ (defn template-applies? [text {:keys [keywords]}] (every? #(re-find % text) keywords)) -(defn extract [wb {:keys [extract vendor]}] +(defn extract [wb {:keys [extract vendor parser]}] (if (fn? extract) (extract wb vendor) [(reduce-kv @@ -24,11 +25,15 @@ (map (fn [cell] (let [address (.getAddress cell) cell-value (str (d/read-cell (d/select-cell (.toString (CellAddress. (+ offset-row (.getRow address)) (+ offset-column (.getColumn address)) )) - (first (d/sheet-seq wb)))))] - (if extract-regex - (second (re-find extract-regex cell-value)) - - cell-value)))) + (first (d/sheet-seq wb))))) + raw-result (if extract-regex + (second (re-find extract-regex cell-value)) + + cell-value)] + (if (get parser k) + (u/parse-value (first (get parser k) ) (second (get parser k) ) raw-result) + raw-result + )))) first))) {:vendor-code vendor} extract)])) diff --git a/src/clj/auto_ap/parse/templates.clj b/src/clj/auto_ap/parse/templates.clj index bc07a443..cfe74f6a 100644 --- a/src/clj/auto_ap/parse/templates.clj +++ b/src/clj/auto_ap/parse/templates.clj @@ -271,7 +271,7 @@ ;; PACIFIC SEAFOOD {:vendor "Pacific Seafood" - :keywords [#"pacseafood"] + :keywords [#"(pacseafood|PACIFIC FRESH)"] :extract {:date #"DATE(?:.*\n.*(?=([0-9]+/[0-9]+/[0-9]+)))([0-9]+/[0-9]+/[0-9]+)" :customer-identifier #"DELIVER TO:(?:.*\n)(.*?)(?=\s{2})" :invoice-number #"INVOICE NO\.\n(?:.*?(?= [0-9]+\n)) ([0-9]+)" @@ -439,12 +439,15 @@ :total [#"PAY THIS" -1 0] :date [#"INVOICE DATE" 0 1] :invoice-number [#"INVOICE NUMBER" 0 1]}} - {:vendor "SWO" + {:vendor "Southern Glazers" :keywords [#"Please note that the total invoice amount may"] :extract {:customer-identifier [#"Customer #" 1 0] - :total [#"Total Invoice" 0 5] + :total [#"Subtotal" 0 16 ] :date [#"Date" 0 0 #"Date: (.*)"] - :invoice-number [#"Invoice #" 0 0 #"Invoice #: (.*)"]}} + :invoice-number [#"Invoice #" 0 0 #"Invoice #: (.*)"] + :account-number [#"Customer #" 0 0 #"Customer #: (.*)"]} + :parser { :total [:trim-commas-and-remove-dollars nil] + :date [:clj-time "MM/dd/yyyy"]}} {:vendor "Mama Lu's Foods" :keywords [#"Mama Lu's Foods"] :extract (fn [wb vendor] diff --git a/src/clj/auto_ap/parse/util.clj b/src/clj/auto_ap/parse/util.clj index c7e6b713..1708a09e 100644 --- a/src/clj/auto_ap/parse/util.clj +++ b/src/clj/auto_ap/parse/util.clj @@ -13,6 +13,9 @@ [_ _ value] (str/replace value #"," "") ) +(defmethod parse-value :trim-commas-and-remove-dollars + [_ _ value] + (str/replace (str/replace value #"," "") #"\$" "")) (defmethod parse-value :trim-commas-and-negate [_ _ value] diff --git a/src/clj/auto_ap/routes/yodlee.clj b/src/clj/auto_ap/routes/yodlee.clj index 3eda02ad..6cf85c51 100644 --- a/src/clj/auto_ap/routes/yodlee.clj +++ b/src/clj/auto_ap/routes/yodlee.clj @@ -42,7 +42,7 @@ (let [[session token] (yodlee/get-access-token)] {:status 200 :headers {"Content-Type" "application/edn"} - :body (pr-str (yodlee/get-provider-accounts-with-accounts)) })) + :body (pr-str @yodlee/in-memory-cache) })) (POST "/reauthenticate/:id" {:keys [query-params identity] {:keys [id]} :route-params data :edn-params :as request} @@ -53,6 +53,7 @@ :headers {"Content-Type" "application/edn"} :body (pr-str (yodlee/reauthenticate (Long/parseLong id) data)) }) (catch Exception e + (println e) {:status 500 :headers {"Content-Type" "application/edn"} :body (pr-str {:message (.getMessage e) diff --git a/src/clj/auto_ap/server.clj b/src/clj/auto_ap/server.clj index 83fdbbe0..c51aa6a7 100644 --- a/src/clj/auto_ap/server.clj +++ b/src/clj/auto_ap/server.clj @@ -2,6 +2,7 @@ (:require #_[auto-ap.background.mail :refer [always-process-sqs]] [auto-ap.handler :refer [app]] [auto-ap.ledger :refer [process-all]] + [auto-ap.yodlee.core :refer [load-in-memory-cache]] [nrepl.server :refer [start-server stop-server]] [config.core :refer [env]] [ring.adapter.jetty :refer [run-jetty]]) @@ -15,5 +16,6 @@ (start-server :port 9000 :bind "0.0.0.0" #_#_:handler (cider-nrepl-handler)) (let [port (Integer/parseInt (or (env :port) "3000"))] (future (process-all)) + (future (load-in-memory-cache)) #_(future (always-process-sqs)) (run-jetty app {:port port :join? false}))) diff --git a/src/clj/auto_ap/yodlee/core.clj b/src/clj/auto_ap/yodlee/core.clj index bf8b26c5..fd0f25f1 100644 --- a/src/clj/auto_ap/yodlee/core.clj +++ b/src/clj/auto_ap/yodlee/core.clj @@ -157,20 +157,9 @@ :as :json})))) -(defn reauthenticate [pa data] - (let [cob-session (login-cobrand) - user-session (login-user cob-session) - batch-size 100] - (-> (str (:yodlee-base-url env) "/providerAccounts?providerAccountIds=" pa) - (client/put {:headers (merge base-headers {"Authorization" (auth-header cob-session user-session)}) - :body (json/write-str data) - :as :json})))) -(defn update-yodlee [id] - (update-provider-account id) - (get-provider-account id)) (defn get-specific-transactions [account] (let [cob-session (login-cobrand) @@ -255,6 +244,41 @@ (update-in provider-accounts [(:providerAccountId a) :accounts] conj a)) provider-accounts) vals))) +(defonce in-memory-cache (atom [])) + +(defn load-in-memory-cache [] + (future + (loop [] + (try + (reset! in-memory-cache (get-provider-accounts-with-accounts)) + (catch Exception e + (println e))) + (Thread/sleep (* 60 * 1000 * 5)) + (recur)))) + +(defn refresh-provider-account [id] + (swap! in-memory-cache + (fn [i] + (-> (by :id i) + (update id merge (get-provider-account-detail id)) + vals)))) + +(defn update-yodlee [id] + (update-provider-account id) + (refresh-provider-account id) + ) + +(defn reauthenticate [pa data] + (let [cob-session (login-cobrand) + user-session (login-user cob-session) + batch-size 100] + + (-> (str (:yodlee-base-url env) "/providerAccounts?providerAccountIds=" pa) + + (client/put {:headers (merge base-headers {"Authorization" (auth-header cob-session user-session)}) + :body (json/write-str data) + :as :json})) + (refresh-provider-account pa))) #_(defn get-users [] (let [cob-session (login-cobrand)] (-> "https://developer.api.yodlee.com/ysl/user" diff --git a/src/clj/user.clj b/src/clj/user.clj index d417a206..3a19d8bc 100644 --- a/src/clj/user.clj +++ b/src/clj/user.clj @@ -224,8 +224,6 @@ txes (transduce (comp - - (mapcat (fn parse-map [[account account-name override-name _ type]] (let [code (some-> account not-empty @@ -277,3 +275,28 @@ [{:db/id [:client/code client-code] :client/signature-file (str "https://s3.amazonaws.com/integreat-signature-images/" filename)}])) + +(defn fix-transactions-without-locations [client-code location] + (->> + (d/query {:query {:find ['(pull ?e [*])] + :in ['$ '?client-code] + :where ['[?e :transaction/accounts ?ta] + '[?e :transaction/matched-rule] + '[?e :transaction/approval-status :transaction-approval-status/approved] + '(not [?ta :transaction-account/location]) + '[?e :transaction/client ?c] + '[?c :client/code ?client-code] + ]} + :args [(d/db (d/connect uri)) client-code]}) + (mapcat + (fn [[{:transaction/keys [accounts]}]] + (mapv + (fn [a] + {:db/id (:db/id a) + :transaction-account/location location} + ) + accounts) + ) + ) + vec)) + diff --git a/src/cljc/auto_ap/entities/transaction_rule_account.cljc b/src/cljc/auto_ap/entities/transaction_rule_account.cljc index 781e94ae..bcca931f 100644 --- a/src/cljc/auto_ap/entities/transaction_rule_account.cljc +++ b/src/cljc/auto_ap/entities/transaction_rule_account.cljc @@ -5,6 +5,9 @@ (s/def ::account map?) +(s/def ::location (s/and string? + not-empty)) -(s/def ::transaction-rule-account (s/keys :req-un [::account] +(s/def ::transaction-rule-account (s/keys :req-un [::account + ::location] :opt-un [])) diff --git a/src/cljs/auto_ap/forms.cljs b/src/cljs/auto_ap/forms.cljs index facbb0f5..3c1ed988 100644 --- a/src/cljs/auto_ap/forms.cljs +++ b/src/cljs/auto_ap/forms.cljs @@ -143,7 +143,7 @@ (defn vertical-form [{:keys [can-submit id change-event submit-event ]}] - {:form ^{:key "form"} + {:form (fn [{:keys [title] :as params} & children] (let [{:keys [data active? error]} @(re-frame/subscribe [::form id]) can-submit @(re-frame/subscribe can-submit)] @@ -157,6 +157,20 @@ [:h1.title.is-2 title] [:<> children]])) + + :form-inline + (fn [{:keys [title] :as params} children] + (let [{:keys [data active? error]} @(re-frame/subscribe [::form id]) + can-submit @(re-frame/subscribe can-submit)] + + [:form { :on-submit (fn [e] + (when (.-stopPropagation e) + (.stopPropagation e) + (.preventDefault e)) + (when can-submit + (re-frame/dispatch-sync (vec (conj submit-event params)))))} + [:h1.title.is-2 title] + children])) :raw-field (fn [control] (let [{:keys [data]} @(re-frame/subscribe [::form id])] [bind-field (-> control diff --git a/src/cljs/auto_ap/views/components/typeahead.cljs b/src/cljs/auto_ap/views/components/typeahead.cljs index 900d0163..c36fcb74 100644 --- a/src/cljs/auto_ap/views/components/typeahead.cljs +++ b/src/cljs/auto_ap/views/components/typeahead.cljs @@ -4,7 +4,7 @@ [clojure.string :as str])) (defn get-valid-matches [matches not-found-description not-found-value text] - (let [valid-matches (take 5 (for [[[id t :as match] i] (map vector matches (range)) + (let [valid-matches (take 15 (for [[[id t :as match] i] (map vector matches (range)) :when (str/includes? (or (some-> t .toLowerCase) "") (or (some-> text .toLowerCase) ""))] match)) valid-matches (if (and not-found-description text) @@ -111,10 +111,14 @@ nil)]))}))) (defn get-valid-entity-matches [matches not-found-description not-found-value text match->text] - (let [valid-matches (take 5 (for [[match i] (map vector matches (range)) - :let [t (match->text match)] - :when (str/includes? (or (some-> t .toLowerCase) "") (or (some-> text .toLowerCase) ""))] - match)) + (let [valid-matches (->> (for [[match i] (map vector matches (range)) + :let [t (match->text match) + match-index (str/index-of (or (some-> t .toLowerCase) "") (or (some-> text .toLowerCase) ""))] + :when match-index] + [match match-index ]) + (sort-by second) + (map first) + (take 10)) valid-matches (if (and not-found-description text) (concat valid-matches [[:not-found (not-found-description text) (not-found-value text)]]) valid-matches)] diff --git a/src/cljs/auto_ap/views/pages/admin/yodlee.cljs b/src/cljs/auto_ap/views/pages/admin/yodlee.cljs index 62df17cf..7913a918 100644 --- a/src/cljs/auto_ap/views/pages/admin/yodlee.cljs +++ b/src/cljs/auto_ap/views/pages/admin/yodlee.cljs @@ -5,6 +5,7 @@ [reagent.core :as reagent] [clojure.string :as str] [cljs-time.format :as f] + [cljs-time.core :as time] [auto-ap.subs :as subs] [auto-ap.events.admin.clients :as events] [auto-ap.entities.clients :as entity] @@ -70,8 +71,9 @@ (fn [{:keys [db]} _] {:db (-> db (assoc ::yodlee {:provider-accounts-loading? true}) - #_(assoc ::provider-accounts []) - #_(assoc ::provider-accounts-loading? true)) + (assoc ::save-error nil) + (assoc ::provider-accounts []) + (assoc ::provider-accounts-loading? true)) :http {:token (:user db) :method :get :headers {"Content-Type" "application/edn"} @@ -121,7 +123,7 @@ (re-frame/reg-event-fx ::save-error (fn [{:keys [db]} [_ authentication]] - {:dispatch [::mounted]})) + {:db (assoc :db ::load-error "error")})) (defn yodlee-link-button [] [:div @@ -149,6 +151,14 @@ [:button.button.is-primary {:class (if loading? "is-loading" "") :on-click (dispatch-event [::authenticate-with-yodlee])} "Authenticate with Yodlee"]))]) +(defn yodlee-date->date [d] + (try + (some-> d + (str->date (:date-time-no-ms f/formatters)) + ) + (catch js/Error e + nil))) + (defn yodlee-date->str [d] (try (or (some-> d @@ -209,11 +219,16 @@ :uri (str "/api/yodlee/reauthenticate/" provider-account-id ) :body {"loginForm" {"row" - [{"field" - (mapv (fn [[k v]] - {"id" k - "value" v}) - (:data (get-in db [::forms/forms [::login-form provider-account-id]])))}]}} + (->> (get-in db [::forms/forms [::login-form provider-account-id]]) + :data + (sort-by (fn [[k v]] k)) + (map second) + (map (fn [row] + {"field" + (mapv (fn [[k v]] + {"id" k + "value" v}) + row)})))}} :on-success [::authenticated] :on-error [::forms/save-error [::login-form provider-account-id]]}})) @@ -233,57 +248,73 @@ [:div.card-header-title "Provider account " (:id account) ]] [:div.card-content - [:div.notification.is-info.is-light - [:div.level - [:div.level-left - [:div.level-item - [:p - "This account was last updated on " - (yodlee-date->str (-> account :dataset first :lastUpdated)) - ", and last attempted " - (yodlee-date->str (-> account :dataset first :lastUpdateAttempt)) - "."]]] - [:div.level-right [:button.button.is-success {:on-click (dispatch-event [::kick (:id account)] )} "Force refresh" ]]] - ] - [:div.notification.is-info.is-warning - [:div.level - [:div.level-left - [:div.level-item - "This provider account's status is '" - (-> account :dataset first :additionalStatus) - "'. If this is in error, it might help to try reauthenticating by filling out the form below."]]]] + (if (> (some-> (-> account :dataset first :lastUpdated) + (yodlee-date->date ) + (time/interval (time/now)) + (time/in-days )) + 1) + [:div.notification.is-info.is-light + [:div.level + [:div.level-left + [:div.level-item + [:p + "This account was last updated on " + (yodlee-date->str (-> account :dataset first :lastUpdated)) + ", and last attempted " + (yodlee-date->str (-> account :dataset first :lastUpdateAttempt)) + "."]]] + [:div.level-right [:button.button.is-success {:on-click (dispatch-event [::kick (:id account)] )} "Force refresh" ]]] + + ]) + [yodlee-accounts-table (:accounts account)] - [:div - (if (:field account) - (for [f (:field account)] - (let [{error :error account-data :data } @(re-frame/subscribe [::forms/form [::mfa-form (:id account)]]) - change-event [::forms/change [::mfa-form (:id account)]] - {:keys [form field field-holder raw-field error-notification submit-button]} (forms/vertical-form {:can-submit [::can-submit] - :change-event change-event - :submit-event [::reauthenticate-mfa (:id account)] - :id [::mfa-form (:id account)]} )] - (form {:title "Reauthenticate (login)"} + (if (not= (-> account :dataset first :additionalStatus) + "AVAILABLE_DATA_RETRIEVED") + [:div + [:div.notification.is-info.is-warning + [:div.level + [:div.level-left + [:div.level-item + "This provider account's status is '" + (-> account :dataset first :additionalStatus) + "'. If this is in error, it might help to try reauthenticating by filling out the form below."]]]] + (if (:field account) + (for [f (:field account)] + (let [{error :error account-data :data } @(re-frame/subscribe [::forms/form [::mfa-form (:id account)]]) + change-event [::forms/change [::mfa-form (:id account)]] + {:keys [form-inline field field-holder raw-field error-notification submit-button]} (forms/vertical-form {:can-submit [::can-submit] + :change-event change-event + :submit-event [::reauthenticate-mfa (:id account)] + :id [::mfa-form (:id account)]} )] + (form-inline {:title "Reauthenticate (mfa)"} + [:<> + (error-notification) + (doall + (for [f (-> account :field)] + ^{:key (:id f)} + (field (:label f) + [:input.input {:type "text" :field [(:id f)] :value (-> f :field first :value)}]))) + (submit-button "Reauthenticate")]))) + (let [{error :error account-data :data } @(re-frame/subscribe [::forms/form [::login-form (:id account)]]) + change-event [::forms/change [::login-form (:id account)]] + {:keys [form-inline field field-holder raw-field error-notification submit-button]} (forms/vertical-form {:can-submit [::can-submit] + :change-event change-event + :submit-event [::reauthenticate (:id account)] + :id [::login-form (:id account)]} )] + (form-inline {:title "Reauthenticate (login)"} + [:<> + (error-notification) - (for [f (-> account :field)] - ^{:key (:id f)} - (field (:label f) - [:input.input {:type "text" :field [(:id f)] :value (-> f :field first :value)}])) - (submit-button "Reauthenticate")))) - (let [{error :error account-data :data } @(re-frame/subscribe [::forms/form [::login-form (:id account)]]) - change-event [::forms/change [::login-form (:id account)]] - {:keys [form field field-holder raw-field error-notification submit-button]} (forms/vertical-form {:can-submit [::can-submit] - :change-event change-event - :submit-event [::reauthenticate (:id account)] - :id [::login-form (:id account)]} )] - (form {:title "Reauthenticate (MFA)"} - (error-notification) - (for [f (-> account :loginForm first :row)] - ^{:key (:id f)} - (field (:label f) - [:input.input {:type "text" :field [(:id f)] :value (-> f :field first :value)}])) - (submit-button "Reauthenticate"))))]]]))]])) + (doall + (for [[row i] (map vector (-> account :loginForm last :row) (range)) + f (:field row)] + ^{:key (:id f)} + [:div + (field (:label row) + [:input.input {:type "text" :field [i (:id f)]}])])) + (submit-button "Reauthenticate")])))])]]))]])) (defn admin-yodlee-content [] diff --git a/src/cljs/auto_ap/views/pages/invoices/form.cljs b/src/cljs/auto_ap/views/pages/invoices/form.cljs index 1b43e893..791996bb 100644 --- a/src/cljs/auto_ap/views/pages/invoices/form.cljs +++ b/src/cljs/auto_ap/views/pages/invoices/form.cljs @@ -184,7 +184,6 @@ (re-frame/reg-event-fx ::succeeded - [(forms/triggers-stop ::form)] (fn [{:keys [db]} [_ {:keys [invoice-created invoice-printed]} command result]] (let [invoice (condp = command :edit (:edit-invoice result) @@ -192,11 +191,13 @@ :create (:add-invoice result) :add-and-print (first (:invoices (:add-and-print-invoice result))))] + {:db (cond-> db (#{:create :add-and-print} command) (forms/start-form ::form {:client @(re-frame/subscribe [::subs/client]) :status :unpaid - :date (date->str (c/now) standard)})) + :date (date->str (c/now) standard)}) + (= :edit command) (forms/stop-form ::form)) :dispatch-n (cond-> [(conj invoice-created invoice)] (= :add-and-print command) (conj (conj invoice-printed (:pdf-url (:add-and-print-invoice result)))))}))) diff --git a/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs b/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs index 9d03e2ad..9ff010cf 100644 --- a/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs +++ b/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs @@ -1,11 +1,12 @@ (ns auto-ap.views.pages.ledger.profit-and-loss (:require [auto-ap.subs :as subs] [auto-ap.views.components.layouts :refer [side-bar-layout appearing-side-bar]] - [auto-ap.views.pages.ledger.table :refer [table]] + [auto-ap.views.pages.ledger.table :as ledger-table ] + [vimsical.re-frame.cofx.inject :as inject] [goog.string :as gstring] [auto-ap.utils :refer [dollars-0? by ]] [auto-ap.views.pages.ledger.side-bar :refer [ledger-side-bar]] - [auto-ap.views.utils :refer [date->str date-picker bind-field standard dispatch-event local-now ->% ->$ str->date]] + [auto-ap.views.utils :refer [date->str date-picker bind-field standard dispatch-event local-now ->% ->$ str->date with-user]] [cljs-time.core :as t] [re-frame.core :as re-frame])) (def ranges @@ -67,9 +68,29 @@ (-> db ::ledger-list :ledger-page))) (re-frame/reg-sub - ::ledger-params + ::last-ledger-params (fn [db] - (-> db (::ledger-params {}) ))) + (-> db ::last-ledger-params ))) + +(re-frame/reg-sub + ::investigate-ledger-params + (fn [db] + (-> db ::investigate-ledger-params ))) + +(re-frame/reg-sub + ::ledger-params + :<- [::last-ledger-params] + :<- [::subs/client] + :<- [::ledger-table/table-params] + :<- [::investigate-ledger-params] + (fn [[last-params client table-params investigate-ledger-params]] + (let [params (cond-> {} + client (assoc :client-id (:id client)) + (seq table-params) (merge table-params) + (seq investigate-ledger-params) (merge investigate-ledger-params))] + (when (not= params last-params) + (re-frame/dispatch [::ledger-params-changed])) + params))) (re-frame/reg-sub ::accounts @@ -200,6 +221,8 @@ + + (re-frame/reg-event-fx ::date-picked (fn [cofx [_ f date]] @@ -218,14 +241,15 @@ (re-frame/reg-event-fx ::ledger-params-changed - (fn [{:keys [db]} [_ params]] + [with-user (re-frame/inject-cofx ::inject/sub [::ledger-params])] + (fn [{:keys [user ::ledger-params db]} [_ ]] {:db (assoc db ::ledger-list-loading true - ::ledger-params params) - :graphql {:token (-> db :user) + ::last-ledger-params ledger-params) + :graphql {:token user :query-obj {:venia/queries [[:ledger-page - params + ledger-params [[:journal-entries [:id :source :amount @@ -242,26 +266,25 @@ :end]]]} :on-success [::ledger-list-received]}})) -(re-frame/reg-event-fx +(re-frame/reg-event-db ::investigate-clicked - (fn [{:keys [db] } [_ location from-numeric-code to-numeric-code which]] - {:db (assoc db - ::ledger-list-active? true - ::ledger-list-loading true) - - :dispatch [::ledger-params-changed (assoc (::ledger-params db) - :client-id (:id @(re-frame/subscribe [::subs/client])) - :from-numeric-code from-numeric-code - :to-numeric-code to-numeric-code - :location location - :date-range {:start (if (= :current which) - (:from-date (::params db)) - (date->str (t/minus (str->date (:from-date (::params db)) standard) (t/years 1)) - standard)) - :end (if (= :current which) - (:to-date (::params db)) - (date->str (t/minus (str->date (:to-date (::params db)) standard) (t/years 1)) - standard))})]})) + (fn [db [_ location from-numeric-code to-numeric-code which]] + (-> db + (assoc + ::ledger-list-active? true + ::ledger-list-loading true + ::investigate-ledger-params {:client-id (:id @(re-frame/subscribe [::subs/client])) + :from-numeric-code from-numeric-code + :to-numeric-code to-numeric-code + :location location + :date-range {:start (if (= :current which) + (:from-date (::params db)) + (date->str (t/minus (str->date (:from-date (::params db)) standard) (t/years 1)) + standard)) + :end (if (= :current which) + (:to-date (::params db)) + (date->str (t/minus (str->date (:to-date (::params db)) standard) (t/years 1)) + standard))}})))) (def groupings {:sales [["40000-43999 Food Sales " 40000 43999] @@ -625,17 +648,18 @@ [:div [:a.delete.is-pulled-right {:on-click (dispatch-event [::ledger-list-closing])}] [:div [:h1.title "Ledger entries"] - [table {:id :ledger - :ledger-page ledger-page - :status? false - :status (re-frame/subscribe [::ledger-list-loading]) - :params (re-frame/subscribe [::ledger-params]) - :on-params-change (fn [params] - (re-frame/dispatch [::ledger-params-changed params]))}]]])) + [ledger-table/table {:id :ledger + :ledger-page ledger-page + :status? false + :status (re-frame/subscribe [::ledger-list-loading]) + :params (re-frame/subscribe [::ledger-params]) + :on-params-change (fn [params] + (re-frame/dispatch [::ledger-params-changed params]))}]]])) (defn profit-and-loss-page [] (let [ledger-list-active? @(re-frame/subscribe [::ledger-list-active?]) - user (re-frame/subscribe [::subs/user])] + user (re-frame/subscribe [::subs/user]) + ledger-params @(re-frame/subscribe [::ledger-params])] (if (not= "manager" (:user/role @user)) [side-bar-layout {:side-bar [ledger-side-bar] diff --git a/src/cljs/auto_ap/views/pages/transactions/form.cljs b/src/cljs/auto_ap/views/pages/transactions/form.cljs index 03861870..649b6f64 100644 --- a/src/cljs/auto_ap/views/pages/transactions/form.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/form.cljs @@ -216,22 +216,23 @@ :field [:description-original] :disabled "disabled"}]] - (when (and (seq (:potential-transaction-rule-matches data)) - (not (:matched-rule data)) - (not (:payment data)) - is-admin?) + (cond + (and (seq (:potential-transaction-rule-matches data)) + (not (:matched-rule data)) + (not (:payment data)) + is-admin?) [potential-transaction-rule-matches-box {:potential-transaction-rule-matches (:potential-transaction-rule-matches data) - :edit-completed edit-completed}]) + :edit-completed edit-completed}] - (when (and (seq (:potential-payment-matches data)) - (not (:payment data)) - is-admin?) + (and (seq (:potential-payment-matches data)) + (not (:payment data)) + is-admin?) [potential-payment-matches-box {:potential-payment-matches (:potential-payment-matches data) - :edit-completed edit-completed}]) + :edit-completed edit-completed}] - (when (and (not (seq (:potential-payment-matches data))) - (not (seq (:potential-transaction-rule-matches data)))) + (and (not (seq (:potential-payment-matches data))) + (not (seq (:potential-transaction-rule-matches data)))) [:div [field "Vendor" [typeahead-entity {:matches @(re-frame/subscribe [::subs/vendors]) @@ -274,4 +275,30 @@ [error-notification] (when-not should-disable-for-client? - [submit-button "Save"])])])]) + [submit-button "Save"])] + + :else + + [:div + [field "Approval Status" + [button-radio + {:type "button-radio" + :field [:approval-status] + :options [[:unapproved "Unapproved"] + [:requires-feedback "Client Review"] + [:approved "Approved"] + [:excluded "Excluded from Ledger"]] + :disabled should-disable-for-client?}]] + + [field "Forecasted-transaction" + [typeahead-entity {:matches (doto @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))]) println) + :match->text :identifier + :type "typeahead-entity" + :field [:forecast-match]}]] + + [:hr] + + [error-notification] + (when-not should-disable-for-client? + [submit-button "Save"])] + )])])