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.invoices :as invoices]
[auto-ap.db.utils :refer [query]]
[auto-ap.yodlee.import :refer [manual-import]]
[auto-ap.utils :refer [by]]
[auto-ap.parse :as parse]
[auto-ap.graphql.utils :refer [assert-admin]]
@@ -48,12 +49,19 @@
(defn parse-amount [i]
(try
(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")
#"," ""))
(catch Exception 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]}]
(try
(parse/parse-value :clj-time "MM/dd/yyyy" raw-date)
@@ -70,67 +78,102 @@
(defroutes routes
(wrap-routes
(context "/invoices" []
#_(POST "/upload"
{{ files "file"} :params :as params}
(let [{:keys [filename tempfile]} files
companies (companies/get-all)
vendors (vendors/get-all)]
(invoices/import (parse/parse-file (.getPath tempfile) filename) companies vendors)
{:status 200
:body (pr-str (invoices/get-pending ((:query-params params ) "company")))
:headers {"Content-Type" "application/edn"}}))
(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}))))]
(POST "/upload-integreat"
{{:keys [excel-rows]} :edn-params identity :identity}
(assert-admin identity)
(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))
(manual-import raw-transactions company-id 1)
all-companies (companies/get-all)
all-companies (merge (by :code all-companies) (by :name all-companies))
{:status 200
:body (pr-str {:status "success"})
:headers {"Content-Type" "application/edn"}}))
)
(context "/invoices" []
#_(POST "/upload"
{{ files "file"} :params :as params}
(let [{:keys [filename tempfile]} files
companies (companies/get-all)
vendors (vendors/get-all)]
(invoices/import (parse/parse-file (.getPath tempfile) filename) companies vendors)
{:status 200
:body (pr-str (invoices/get-pending ((:query-params params ) "company")))
:headers {"Content-Type" "application/edn"}}))
rows (->> (str/split excel-rows #"\n" )
(map #(str/split % #"\t"))
(map #(into {} (map (fn [c k] [k c] ) % columns)))
(map reset-id)
(map assoc-company-code)
(map (parse-or-error :company-id #(parse-company % all-companies)))
(map (parse-or-error :vendor-id #(parse-vendor % all-vendors)))
(map (parse-or-error :invoice-number parse-invoice-number))
(map (parse-or-error :total parse-amount))
(map (parse-or-error :date parse-date)))
error-rows (filter :errors rows)
vendors-not-found (->> rows
(filter #(and (nil? (:vendor-id %))
(not= "Cash" (:check %))))
(map :vendor-name)
set)
insert-rows (vec (->> (filter #(not (seq (:errors %))) rows)
(map (fn [{:keys [vendor-id total company-id amount date invoice-number default-location]}]
{:vendor-id vendor-id
:company-id company-id
:default-location default-location
:total total
:outstanding-balance total
:imported true
:status "unpaid"
:invoice-number invoice-number
:date date}))))
(POST "/upload-integreat"
{{:keys [excel-rows]} :edn-params user :identity}
(assert-admin user)
(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))
inserted-row-count (invoices/upsert-multi! insert-rows)
already-imported-count (- (count insert-rows) inserted-row-count)]
(expense-accounts/assign-defaults!)
all-companies (companies/get-all)
all-companies (merge (by :code all-companies) (by :name all-companies))
rows (->> (str/split excel-rows #"\n" )
(map #(str/split % #"\t"))
(map #(into {} (map (fn [c k] [k c] ) % columns)))
(map reset-id)
(map assoc-company-code)
(map (parse-or-error :company-id #(parse-company % all-companies)))
(map (parse-or-error :vendor-id #(parse-vendor % all-vendors)))
(map (parse-or-error :invoice-number parse-invoice-number))
(map (parse-or-error :total parse-amount))
(map (parse-or-error :date parse-date)))
error-rows (filter :errors rows)
vendors-not-found (->> rows
(filter #(and (nil? (:vendor-id %))
(not= "Cash" (:check %))))
(map :vendor-name)
set)
insert-rows (vec (->> (filter #(not (seq (:errors %))) rows)
(map (fn [{:keys [vendor-id total company-id amount date invoice-number default-location]}]
{:vendor-id vendor-id
:company-id company-id
:default-location default-location
:total total
:outstanding-balance total
:imported true
:status "unpaid"
:invoice-number invoice-number
:date date}))))
{:status 200
:body (pr-str {:imported inserted-row-count
:already-imported already-imported-count
:vendors-not-found vendors-not-found
:errors (map #(dissoc % :date) error-rows)})
:headers {"Content-Type" "application/edn"}})))
inserted-row-count (invoices/upsert-multi! insert-rows)
already-imported-count (- (count insert-rows) inserted-row-count)]
(expense-accounts/assign-defaults!)
{:status 200
:body (pr-str {:imported inserted-row-count
:already-imported already-imported-count
:vendors-not-found vendors-not-found
:errors (map #(dissoc % :date) error-rows)})
:headers {"Content-Type" "application/edn"}}))))
wrap-secure))

View File

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

View File

@@ -37,6 +37,7 @@
[:div {:class "navbar-end"}
(if @user
[: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)]
[:div {:class "navbar-dropdown"}
[:a {:class "navbar-item"} "My profile"]
@@ -206,6 +207,8 @@
]
[:div {:class "compose has-text-centered"}
[: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]
[goog.string :as gstring]
[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.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.views.components.invoice-table :refer [invoice-table] :as invoice-table]
[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 ) ")")])]
]))]]]))))
(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
(with-meta
(fn []
(let [current-company @(re-frame/subscribe [::subs/company])]
(let [current-company @(re-frame/subscribe [::subs/company])
user @(re-frame/subscribe [::subs/user])]
[:div
[: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
:params (re-frame/subscribe [::params])
:transaction-page (re-frame/subscribe [::transaction-page])
:status (re-frame/subscribe [::subs/status])
: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 {}]) }))