supports yodlee manual import.

This commit is contained in:
BC
2018-07-12 22:57:46 -07:00
parent 085cd17100
commit fb61e1c4ba
4 changed files with 172 additions and 65 deletions

View File

@@ -3,6 +3,7 @@
[auto-ap.db.vendors :as vendors] [auto-ap.db.vendors :as vendors]
[auto-ap.db.invoices :as invoices] [auto-ap.db.invoices :as invoices]
[auto-ap.db.utils :refer [query]] [auto-ap.db.utils :refer [query]]
[auto-ap.yodlee.import :refer [manual-import]]
[auto-ap.utils :refer [by]] [auto-ap.utils :refer [by]]
[auto-ap.parse :as parse] [auto-ap.parse :as parse]
[auto-ap.graphql.utils :refer [assert-admin]] [auto-ap.graphql.utils :refer [assert-admin]]
@@ -48,12 +49,19 @@
(defn parse-amount [i] (defn parse-amount [i]
(try (try
(Double/parseDouble (str/replace (or (second (Double/parseDouble (str/replace (or (second
(re-matches #"[^0-9\.,]*([0-9\.,]+)[^0-9\.,]*" (:amount i))) (re-matches #"[^0-9\.,\\-]*([0-9\.,\\-]+)[^0-9\.,]*" (:amount i)))
"0") "0")
#"," "")) #"," ""))
(catch Exception e (catch Exception e
(throw (Exception. (str "Could not parse total from value '" (:amount i) "'") e))))) (throw (Exception. (str "Could not parse total from value '" (:amount i) "'") e)))))
(defn parse-account-id [i]
(try
(Integer/parseInt (second
(re-matches #"[^0-9,\\-]*([0-9,\\-]+)[^0-9,]*" (:account-id i))))
(catch Exception e
(throw (Exception. (str "Could not parse account from value '" (:account-id i) "'") e)))))
(defn parse-date [{:keys [raw-date]}] (defn parse-date [{:keys [raw-date]}]
(try (try
(parse/parse-value :clj-time "MM/dd/yyyy" raw-date) (parse/parse-value :clj-time "MM/dd/yyyy" raw-date)
@@ -70,6 +78,41 @@
(defroutes routes (defroutes routes
(wrap-routes (wrap-routes
(context "/" []
(context "/transactions" []
(POST "/batch-upload"
{{:keys [data company-id]} :edn-params user :identity}
(assert-admin user)
(let [columns [:status :raw-date :description-original :high-level-category nil nil :amount nil nil nil nil nil :account-id]
rows (->> (str/split data #"\n" )
(drop 1)
(map #(str/split % #"\t"))
(map #(into {} (filter identity (map (fn [c k] [k c] ) % columns))))
(map (parse-or-error :amount parse-amount))
(map (parse-or-error :account-id parse-account-id))
(map (parse-or-error :date parse-date)))
raw-transactions (vec (->> rows
(filter #(not (seq (:errors %))) )
(map (fn [{:keys [description-original status high-level-category amount account-id]}]
{:description-original description-original
:status status
:high-level-category high-level-category
:amount amount
:account-id account-id}))))]
(manual-import raw-transactions company-id 1)
{:status 200
:body (pr-str {:status "success"})
:headers {"Content-Type" "application/edn"}}))
)
(context "/invoices" [] (context "/invoices" []
#_(POST "/upload" #_(POST "/upload"
{{ files "file"} :params :as params} {{ files "file"} :params :as params}
@@ -82,8 +125,8 @@
:headers {"Content-Type" "application/edn"}})) :headers {"Content-Type" "application/edn"}}))
(POST "/upload-integreat" (POST "/upload-integreat"
{{:keys [excel-rows]} :edn-params identity :identity} {{:keys [excel-rows]} :edn-params user :identity}
(assert-admin identity) (assert-admin user)
(let [columns [:raw-date :vendor-name :check :location :invoice-number :amount :company :bill-entered :bill-rejected :added-on :exported-on] (let [columns [:raw-date :vendor-name :check :location :invoice-number :amount :company :bill-entered :bill-rejected :added-on :exported-on]
all-vendors (by :name (vendors/get-all)) all-vendors (by :name (vendors/get-all))
@@ -132,5 +175,5 @@
:already-imported already-imported-count :already-imported already-imported-count
:vendors-not-found vendors-not-found :vendors-not-found vendors-not-found
:errors (map #(dissoc % :date) error-rows)}) :errors (map #(dissoc % :date) error-rows)})
:headers {"Content-Type" "application/edn"}}))) :headers {"Content-Type" "application/edn"}}))))
wrap-secure)) wrap-secure))

View File

@@ -23,7 +23,7 @@
(let [matching-checks (checks/get-graphql {:company-id company-id (let [matching-checks (checks/get-graphql {:company-id company-id
:bank-account-id bank-account-id :bank-account-id bank-account-id
:amount amount :amount (- amount)
:status "pending"})] :status "pending"})]
(if (= 1 (count matching-checks)) (if (= 1 (count matching-checks))
(:id (first matching-checks)) (:id (first matching-checks))
@@ -48,14 +48,19 @@
description-simple :simple} :description description-simple :simple} :description
{merchant-id :i {merchant-id :i
merchant-name :name} :merchant merchant-name :name} :merchant
base-type :baseType
type :type type :type
status :status} status :status}
transaction transaction
amount (if (= "DEBIT" base-type)
(- amount)
amount)
check-number (extract-check-number transaction) check-number (extract-check-number transaction)
company-id (yodlee-account-id->company account-id) company-id (yodlee-account-id->company account-id)
bank-account-id (yodlee-account-id->bank-account-id account-id) bank-account-id (yodlee-account-id->bank-account-id account-id)
check-id (transaction->check-id transaction check-number company-id bank-account-id amount) check-id (transaction->check-id transaction check-number company-id bank-account-id amount)
]] ]]
(println transaction)
(try (try
(transactions/upsert! (transactions/upsert!
@@ -88,8 +93,9 @@
(mapcat (fn [transaction-group] (mapcat (fn [transaction-group]
(map (map
(fn [index {:keys [date description-original high-level-category amount] :as transaction}] (fn [index {:keys [date description-original high-level-category amount account-id] :as transaction}]
{:id (str date "-" description-original "-" amount "-" index) {:id (str date "-" description-original "-" amount "-" index)
:date (time/unparse date "YYYY-MM-dd") :date (time/unparse date "YYYY-MM-dd")
:amount {:amount amount} :amount {:amount amount}
:description {:original description-original :description {:original description-original

View File

@@ -37,6 +37,7 @@
[:div {:class "navbar-end"} [:div {:class "navbar-end"}
(if @user (if @user
[:div {:class (str "navbar-item has-dropdown " (when (get-in @menu [:account :active?]) "is-active"))} [:div {:class (str "navbar-item has-dropdown " (when (get-in @menu [:account :active?]) "is-active"))}
[:a {:class "navbar-link login" :on-click (fn [e] (re-frame/dispatch [::events/toggle-menu :account]))} (:name @user)] [:a {:class "navbar-link login" :on-click (fn [e] (re-frame/dispatch [::events/toggle-menu :account]))} (:name @user)]
[:div {:class "navbar-dropdown"} [:div {:class "navbar-dropdown"}
[:a {:class "navbar-item"} "My profile"] [:a {:class "navbar-item"} "My profile"]
@@ -206,6 +207,8 @@
] ]
[:div {:class "compose has-text-centered"} [:div {:class "compose has-text-centered"}
[:a {:class "button is-danger is-block is-bold" :href (bidi/path-for routes/routes :index) [:a {:class "button is-danger is-block is-bold" :href (bidi/path-for routes/routes :index)

View File

@@ -5,9 +5,10 @@
[reagent.core :as reagent] [reagent.core :as reagent]
[goog.string :as gstring] [goog.string :as gstring]
[auto-ap.views.components.sorter :refer [sorted-column]] [auto-ap.views.components.sorter :refer [sorted-column]]
[auto-ap.views.components.modal :refer [action-modal]]
[auto-ap.views.components.paginator :refer [paginator]] [auto-ap.views.components.paginator :refer [paginator]]
[auto-ap.events :as events] [auto-ap.events :as events]
[auto-ap.views.utils :refer [dispatch-event date->str ]] [auto-ap.views.utils :refer [dispatch-event date->str bind-field]]
[auto-ap.utils :refer [by]] [auto-ap.utils :refer [by]]
[auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table] [auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table]
[auto-ap.subs :as subs])) [auto-ap.subs :as subs]))
@@ -135,19 +136,73 @@
[:a.tag {:href (:s3-url check) :target "_new"} [:i.fa.fa-money-check] [:span.icon [:i.fa.fa-money]] (str " " (:check-number check) " (" (gstring/format "$%.2f" amount ) ")")])] [:a.tag {:href (:s3-url check) :target "_new"} [:i.fa.fa-money-check] [:span.icon [:i.fa.fa-money]] (str " " (:check-number check) " (" (gstring/format "$%.2f" amount ) ")")])]
]))]]])))) ]))]]]))))
(re-frame/reg-event-fx
::manual-yodlee-import
(fn [{:keys [db]} _]
{:dispatch [::events/modal-status ::manual-yodlee-import {:visible? true}]
:db (assoc-in db [::manual-yodlee-import] {:company-id (:id @(re-frame/subscribe [::subs/company]))
:data ""})}))
(re-frame/reg-sub
::manual-yodlee-import
(fn [db]
(-> db ::manual-yodlee-import)))
(re-frame/reg-event-fx
::manual-yodlee-import-completed
(fn [{:keys [db]} _]
{:dispatch [::events/modal-completed ::manual-yodlee-import]}))
(re-frame/reg-event-fx
::manual-yodlee-import-started
(fn [{:keys [db]} _]
(let [manual-yodlee-import (::manual-yodlee-import db)]
{:http {:token (:user db)
:method :post
:body (pr-str manual-yodlee-import)
:headers {"Content-Type" "application/edn"}
:uri (str "/api/transactions/batch-upload")
:on-success [::manual-yodlee-import-completed]
:on-error [::manual-yodlee-import-error]}})))
(defn manual-yodlee-import-modal []
(let [data @(re-frame/subscribe [::manual-yodlee-import])
change-event [::events/change-form [::manual-yodlee-import]]]
[action-modal {:id ::manual-yodlee-import
:title "Manual Yodlee Import"
:action-text "Import"
:save-event [::manual-yodlee-import-started]}
[:div.field
[:label.label
"Yodlee manual import table"]
[:div.control
[bind-field
[:textarea.textarea {:field [:data]
:event change-event
:subscription data}]]]]]))
(def transactions-page (def transactions-page
(with-meta (with-meta
(fn [] (fn []
(let [current-company @(re-frame/subscribe [::subs/company])] (let [current-company @(re-frame/subscribe [::subs/company])
user @(re-frame/subscribe [::subs/user])]
[:div [:div
[:h1.title "Transactions"] [:h1.title "Transactions"]
(when (= "admin" (:role user))
[:div.is-pulled-right
[:button.button.is-danger {:disabled (if current-company
""
"disabled")
:on-click (dispatch-event [::manual-yodlee-import])}
"Manual Yodlee Import"]])
[transaction-table {:id :transactions [transaction-table {:id :transactions
:params (re-frame/subscribe [::params]) :params (re-frame/subscribe [::params])
:transaction-page (re-frame/subscribe [::transaction-page]) :transaction-page (re-frame/subscribe [::transaction-page])
:status (re-frame/subscribe [::subs/status]) :status (re-frame/subscribe [::subs/status])
:on-params-change (fn [params] :on-params-change (fn [params]
(re-frame/dispatch [::params-change params]))}]])) (re-frame/dispatch [::params-change params]))}]
[manual-yodlee-import-modal]]))
{:component-will-mount #(re-frame/dispatch-sync [::params-change {}]) })) {:component-will-mount #(re-frame/dispatch-sync [::params-change {}]) }))