From 798bfae78a7d88210f0e8406fc2b4b2a00323991 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Thu, 10 May 2018 17:32:22 -0700 Subject: [PATCH] Adding vendor import stuff. --- ...525464770-DOWN-change-amount-to-number.sql | 3 + .../1525464770-UP-change-amount-to-number.sql | 3 + src/clj/auto_ap/db/invoices.clj | 5 +- src/clj/auto_ap/db/vendors.clj | 1 + src/clj/auto_ap/routes/invoices.clj | 23 +++- src/cljs/auto_ap/effects.cljs | 27 ++++- .../views/components/invoice_table.cljs | 13 +- .../views/pages/admin/excel_import.cljs | 112 ++++++++++++++---- .../auto_ap/views/pages/unpaid_invoices.cljs | 18 ++- 9 files changed, 173 insertions(+), 32 deletions(-) create mode 100644 migrator/migrations/1525464770-DOWN-change-amount-to-number.sql create mode 100644 migrator/migrations/1525464770-UP-change-amount-to-number.sql diff --git a/migrator/migrations/1525464770-DOWN-change-amount-to-number.sql b/migrator/migrations/1525464770-DOWN-change-amount-to-number.sql new file mode 100644 index 00000000..26f01708 --- /dev/null +++ b/migrator/migrations/1525464770-DOWN-change-amount-to-number.sql @@ -0,0 +1,3 @@ +-- 1525464770 DOWN change-amount-to-number +alter table invoices drop column total; +alter table invoices add column total varchar(255); diff --git a/migrator/migrations/1525464770-UP-change-amount-to-number.sql b/migrator/migrations/1525464770-UP-change-amount-to-number.sql new file mode 100644 index 00000000..bae6d51f --- /dev/null +++ b/migrator/migrations/1525464770-UP-change-amount-to-number.sql @@ -0,0 +1,3 @@ +-- 1525464770 UP change-amount-to-number +alter table invoices drop column total; +alter table invoices add column total decimal; diff --git a/src/clj/auto_ap/db/invoices.clj b/src/clj/auto_ap/db/invoices.clj index 03782ff4..8557ffdd 100644 --- a/src/clj/auto_ap/db/invoices.clj +++ b/src/clj/auto_ap/db/invoices.clj @@ -36,7 +36,10 @@ [:and [:= :exist.invoice-number :v.invoice-number] [:= :exist.company-id :v.company-id] - [:= :exist.vendor-id :v.vendor-id]]] + [:or [:= :exist.vendor-id :v.vendor-id] + [:and + [:= :exist.vendor-id nil] + [:= :v.vendor-id nil]]]]] :where [:= :exist.id nil] }] }))))) 0 (partition-all 2000 rows)))) diff --git a/src/clj/auto_ap/db/vendors.clj b/src/clj/auto_ap/db/vendors.clj index 91807b96..8c5fb789 100644 --- a/src/clj/auto_ap/db/vendors.clj +++ b/src/clj/auto_ap/db/vendors.clj @@ -40,6 +40,7 @@ (let [[id] (-> (sql/build :insert-into :vendors :values [(unparse data)]) execute!)] + (println "inserted vendor: " data ", id " id) (get-by-id id))) (defn find-with-reminders [] diff --git a/src/clj/auto_ap/routes/invoices.clj b/src/clj/auto_ap/routes/invoices.clj index f5c6e7a2..50aaaf75 100644 --- a/src/clj/auto_ap/routes/invoices.clj +++ b/src/clj/auto_ap/routes/invoices.clj @@ -27,10 +27,16 @@ (defn parse-invoice-number [{:keys [invoice-number]}] (or invoice-number "")) -(defn parse-vendor [{:keys [vendor-name]} vendors] - (if-let [id (:id (vendors vendor-name))] - id - (throw (Exception. (str "Vendor '" vendor-name "' not found."))))) +(defn parse-vendor [{:keys [vendor-name check]} vendors] + (let [id (:id (vendors vendor-name))] + (cond id + id + + (= "Cash" check) + nil + + :else + (throw (Exception. (str "Vendor '" vendor-name "' not found.")))))) (defn parse-amount [i] (try @@ -127,6 +133,11 @@ (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]}] {:vendor-id vendor-id @@ -138,13 +149,15 @@ :date date})))) inserted-row-count (invoices/upsert-multi! insert-rows) - already-imported-count (- (count insert-rows) inserted-row-count)] + already-imported-count (- (count insert-rows) inserted-row-count) + ] {: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"}})) diff --git a/src/cljs/auto_ap/effects.cljs b/src/cljs/auto_ap/effects.cljs index bfb03d5d..e32432ad 100644 --- a/src/cljs/auto_ap/effects.cljs +++ b/src/cljs/auto_ap/effects.cljs @@ -5,7 +5,7 @@ [cljs-time.coerce :as c] [cljs-time.core :as time] [cljs-time.format :as format] - [cljs.core.async :refer [> + (for [{:keys [method body headers uri token]} requests] + (go + (let [headers (if token + (assoc headers "Authorization" (str "Token " token)) + headers) + response (= (:status response) 400) + :error + :success)))) + (async/merge) + (async/reduce conj []) + (async/snake [s] (str/replace s #"-" "_")) diff --git a/src/cljs/auto_ap/views/components/invoice_table.cljs b/src/cljs/auto_ap/views/components/invoice_table.cljs index 338ef67b..82208b31 100644 --- a/src/cljs/auto_ap/views/components/invoice_table.cljs +++ b/src/cljs/auto_ap/views/components/invoice_table.cljs @@ -6,7 +6,8 @@ [auto-ap.views.components.sorter :refer [sorted-column]] [reagent.core :as reagent] [clojure.string :as str] - [cljs-time.format :as format])) + [cljs-time.format :as format] + [goog.string :as gstring])) @@ -23,7 +24,7 @@ -(defn invoice-table [{:keys [id invoice-page status on-params-change vendors params]}] +(defn invoice-table [{:keys [id invoice-page status on-params-change vendors params check-boxes on-check-changed]}] (let [state (reagent/atom (or @params {})) opc (fn [p] (swap! state merge p) @@ -40,6 +41,8 @@ [:table.table.is-fullwidth [:thead [:tr + (when check-boxes + [:th]) [sorted-column {:on-sort opc :style {:width "25%" :cursor "pointer"} :sort-key "vendor" @@ -78,8 +81,12 @@ (for [{:keys [company invoice-number date total id vendor] :as i} (:invoices @invoice-page)] ^{:key id} [:tr + (when check-boxes + [:td [:input.checkbox {:type "checkbox" :on-change (fn [x e] (when on-check-changed + (on-check-changed id)))} ]]) [:td (:name vendor)] [:td (:name company)] [:td invoice-number] [:td (date->str date) ] - [:td total]]))]]])))) + + [:td (gstring/format "$%.2f" total )]]))]]])))) diff --git a/src/cljs/auto_ap/views/pages/admin/excel_import.cljs b/src/cljs/auto_ap/views/pages/admin/excel_import.cljs index 41be6a42..04dfaba4 100644 --- a/src/cljs/auto_ap/views/pages/admin/excel_import.cljs +++ b/src/cljs/auto_ap/views/pages/admin/excel_import.cljs @@ -59,11 +59,79 @@ (assoc-in [::excel-import :rows] nil) (assoc-in [::excel-import :saving?] false))})) +(re-frame/reg-event-db + ::toggle-vendor + (fn [db [_ data]] + (update-in db [::excel-import :create-vendors] (fn [x] + (let [x (or x #{})] + (if (x data) + (disj x data) + (conj x data))))))) +(re-frame/reg-event-fx + ::create-vendors + (fn [{:keys [db]}] + (let [excel-import (::excel-import db)] + {:https {:requests (map (fn [v] + {:token (:user db) + :method :post + :body (pr-str {:name v}) + :headers {"Content-Type" "application/edn"} + :uri (str "/api/vendors/")}) + (get-in db [::excel-import :create-vendors])) + :on-success [::create-vendor-complete] + :on-error [::create-vendor-error]} + :db (-> db + (assoc-in [::excel-import :saving-vendors?] true))}))) + + +(re-frame/reg-event-db + ::create-vendor-complete + (fn [db [_ data]] + (-> db + (update-in [::excel-import :rows :vendors-not-found] + (fn [v] + (reduce disj v (get-in db [::excel-import :create-vendors])))) + (update-in [::excel-import] dissoc :create-vendors)))) + (defn admin-excel-import-page [] [:div - (let [excel-import-data @(re-frame/subscribe [::excel-import])] + (let [{{:keys [vendors-not-found already-imported imported]} :rows + :keys [create-vendors] + :or {create-vendors #{}} + :as excel-import-data} @(re-frame/subscribe [::excel-import])] [:div [:h1.title "Import Invoices from Integreat Excel"] + (when (seq vendors-not-found) + [:article.message.is-warning.is-paddingless + [:div.message-header + "Some vendors could not be found"] + + [:div.message-body + [:h2 "Check the vendors you want to create"] + [:div.columns + (for [[i vendor-group] (map vector (range) (partition-all (max 1 (/ (count vendors-not-found) 3)) vendors-not-found))] + ^{:key i} + [:div.column + (for [v vendor-group] + ^{:key v} [:div.control + [:label.checkbox + [:input {:value v + :checked (if (create-vendors v) + "checked" + "") + + :type "checkbox" + :on-change (fn [] + (re-frame/dispatch [::toggle-vendor v]))}] + (str " " v)]])])] + [:div + [:button.button.is-pulled-right + {:on-click (dispatch-event [::create-vendors]) + :disabled (when-not (seq create-vendors) + "disabled") + } + (str "Create " (count create-vendors) " vendors")]] + [:div.is-clearfix]]]) [bind-field [:textarea.textarea {:rows "20" :field :excel-rows @@ -76,30 +144,32 @@ "is-loading") :disabled (when (:saving? excel-import-data) "disabled")} "Import"] + [:div.is-clearfix] [:div.is-clearfix [:p - (when-let [imported (:imported (:rows excel-import-data))] + (when imported (str imported " rows imported."))] [:p - (when-let [already-imported (:already-imported (:rows excel-import-data))] + (when already-imported (str already-imported " rows already imported."))]] (when-let [errors (:errors (:rows excel-import-data))] + [:div + [:h3 (str "Import errors (" (min 100 (count errors)) " / " (count errors) " )")] + [:table.table.is-fullwidth + [:thead + [:th "Date"] + [:th "Invoice #"] + [:th "Company"] + [:th "Vendor"] + [:th "Amount"] + [:th "Errors"]] - [:div - [:h3 (str "Import errors (" (min 100 (count errors)) ")")] - [:table.table.is-fullwidth - [:thead - [:th "Date"] - [:th "Invoice #"] - [:th "Company"] - [:th "Vendor"] - [:th "Amount"] - [:th "Errors"]] - (for [{:keys [raw-date invoice-number company vendor-name amount] row-errors :errors} (take 100 errors)] - [:tr - [:td raw-date] - [:td invoice-number] - [:td company] - [:td vendor-name] - [:td amount] - [:td (map #(vector :p (:info %)) row-errors)]])]])])]) + (for [{:keys [raw-date invoice-number company vendor-name amount] row-errors :errors} (take 100 errors)] + ^{:key (str raw-date invoice-number company vendor-name amount)} + [:tr + [:td raw-date] + [:td invoice-number] + [:td company] + [:td vendor-name] + [:td amount] + [:td (map (fn [{:keys [info]}] ^{:key info} [:p info]) row-errors)]])]])])]) diff --git a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs index 98fb4e8b..272a6c9e 100644 --- a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs +++ b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs @@ -34,6 +34,16 @@ (assoc ::invoice-page (first (:invoice-page data))) (assoc-in [:status :loading] false)))) +(re-frame/reg-event-db + ::toggle-check + (fn [db [_ data]] + (update-in db [::invoice-page :checked] (fn [x] + + (let [x (or x #{})] + (if (x data) + (disj x data) + (conj x data))))))) + (re-frame/reg-event-fx ::invalidated (fn [cofx [_ params]] @@ -44,10 +54,16 @@ (fn [] [:div [:h1.title "Unpaid invoices"] + [:div.is-pulled-right + [:button.button.is-primary "Print check(s)"]] + [invoice-table {:id :unpaid :params (re-frame/subscribe [::params]) :invoice-page (re-frame/subscribe [::invoice-page]) :status (re-frame/subscribe [::subs/status]) :on-params-change (fn [params] - (re-frame/dispatch [::params-change params])) }]]) + (re-frame/dispatch [::params-change params])) + :check-boxes true + :on-check-changed (fn [which] + (re-frame/dispatch [::toggle-check which]))}]]) {:component-will-mount #(re-frame/dispatch-sync [::params-change {}]) }))