supports yodlee manual import.
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {}]) }))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user