diff --git a/src/clj/auto_ap/datomic/vendors.clj b/src/clj/auto_ap/datomic/vendors.clj index c402b4d5..9644bc5d 100644 --- a/src/clj/auto_ap/datomic/vendors.clj +++ b/src/clj/auto_ap/datomic/vendors.clj @@ -19,7 +19,8 @@ :vendor-account-override/account [:account/name :account/numeric-code :db/id]}] :vendor/terms-overrides [* {:vendor-terms-override/client [:client/name :client/code :db/id]}] :vendor/schedule-payment-dom [* {:vendor-schedule-payment-dom/client [:client/name :client/code :db/id]}] - :vendor/automatically-paid-when-due [:db/id :client/name]}]) + :vendor/automatically-paid-when-due [:db/id :client/name] + :vendor/default-account [:db/id :account/numeric-code :account/name]}]) (defn get-usages [args] (->> (cond-> {:query {:find ['?v '?c '(count ?e)] diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index 2cfbc8ee..94d1a09b 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -40,12 +40,17 @@ :serialize #(or (:ident %) (:db/ident %) %)} :iso_date {:parse #(time/parse % time/iso-date) :serialize #(time/unparse % time/iso-date)} - :money {:parse #(cond (and (string? %) - (not (str/blank? %))) - (Double/parseDouble %) + :money {:parse #(do + (cond (and (string? %) + (not (str/blank? %))) + (Double/parseDouble %) - (int? %) - (double %) + (and (string? %) + (str/blank? %)) + 0.0 + + (int? %) + (double %) :else %) diff --git a/src/clj/auto_ap/graphql/ledger.clj b/src/clj/auto_ap/graphql/ledger.clj index dd4f0741..e7233443 100644 --- a/src/clj/auto_ap/graphql/ledger.clj +++ b/src/clj/auto_ap/graphql/ledger.clj @@ -204,6 +204,7 @@ (try (f entry) (catch Exception e + (log/warn e) (assoc entry :error (.getMessage e) :status (or (:status (ex-data e)) :error)))))) @@ -267,99 +268,90 @@ all-accounts (transduce (map (comp str :account/numeric-code)) conj #{} (a/get-accounts)) transaction (doall (map (assoc-error (fn [entry] - (let [entry (-> entry - (update :line_items - (fn [lis] - (mapv - (fn [li ] - (-> li - (update :debit (fnil identity 0.0)) - (update :credit (fnil identity 0.0)))) - lis))))] - (let [vendor (all-vendors (:vendor_name entry))] - (when-not (all-clients (:client_code entry)) - (throw (ex-info (str "Client '" (:client_code entry )"' not found.") {:status :error}) )) - (when-not vendor - (throw (ex-info (str "Vendor '" (:vendor_name entry) "' not found.") {:status :error}))) - (when-not (re-find #"\d{1,2}/\d{1,2}/\d{4}" (:date entry)) - (throw (ex-info (str "Date must be MM/dd/yyyy") {:status :error}))) - (when-not (dollars= (reduce + 0.0 (map :debit (:line_items entry))) - (reduce + 0.0 (map :credit (:line_items entry)))) - (throw (ex-info (str "Debits '" - (reduce + 0 (map :debit (:line_items entry))) - "' and credits '" - (reduce + 0 (map :credit (:line_items entry))) - "' do not add up.") - {:status :error}))) - (when (dollars= (reduce + 0.0 (map :debit (:line_items entry))) - 0.0) - (throw (ex-info (str "Cannot have ledger entries that total $0.00") - {:status :ignored}))) - (assoc entry - :status :success - :tx - (remove-nils - {:journal-entry/source (:source entry) - :journal-entry/client [:client/code (:client_code entry)] - :journal-entry/date (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry))) - :journal-entry/external-id (:external_id entry) - :journal-entry/vendor (:db/id (all-vendors (:vendor_name entry))) - :journal-entry/amount (:amount entry) - :journal-entry/note (:note entry) - :journal-entry/cleared-against (:cleared_against entry) + (let [vendor (all-vendors (:vendor_name entry))] + (when-not (all-clients (:client_code entry)) + (throw (ex-info (str "Client '" (:client_code entry )"' not found.") {:status :error}) )) + (when-not vendor + (throw (ex-info (str "Vendor '" (:vendor_name entry) "' not found.") {:status :error}))) + (when-not (re-find #"\d{1,2}/\d{1,2}/\d{4}" (:date entry)) + (throw (ex-info (str "Date must be MM/dd/yyyy") {:status :error}))) + (when-not (dollars= (reduce + 0.0 (map :debit (:line_items entry))) + (reduce + 0.0 (map :credit (:line_items entry)))) + (throw (ex-info (str "Debits '" + (reduce + 0 (map :debit (:line_items entry))) + "' and credits '" + (reduce + 0 (map :credit (:line_items entry))) + "' do not add up.") + {:status :error}))) + (when (dollars= (reduce + 0.0 (map :debit (:line_items entry))) + 0.0) + (throw (ex-info (str "Cannot have ledger entries that total $0.00") + {:status :ignored}))) + (assoc entry + :status :success + :tx + (remove-nils + {:journal-entry/source (:source entry) + :journal-entry/client [:client/code (:client_code entry)] + :journal-entry/date (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry))) + :journal-entry/external-id (:external_id entry) + :journal-entry/vendor (:db/id (all-vendors (:vendor_name entry))) + :journal-entry/amount (:amount entry) + :journal-entry/note (:note entry) + :journal-entry/cleared-against (:cleared_against entry) - :journal-entry/line-items - (mapv (fn [ea] - (when (and (not (get - (get all-client-locations (:client_code entry)) + :journal-entry/line-items + (mapv (fn [ea] + (when (and (not (get + (get all-client-locations (:client_code entry)) + (:location ea))) + (not= "A" (:location ea))) + (throw (ex-info (str "Location '" (:location ea) "' not found.") + {:status :error}))) + (when (and (<= (:debit ea 0.0) 0.0) + (<= (:credit ea 0.0) 0.0)) + (throw (ex-info (str "Line item amount " (or (:debit ea) (:credit ea)) " must be greater than 0.") + {:status :error}))) + (when (and (not (all-accounts (:account_identifier ea))) + (not (get + (get all-client-bank-accounts (:client_code entry)) + (:account_identifier ea)))) + (throw (ex-info (str "Account '" (:account_identifier ea) "' not found.") + {:status :error}))) + (let [matching-account (when (re-matches #"^[0-9]+$" (:account_identifier ea)) + (a/get-account-by-numeric-code-and-sets (Integer/parseInt (:account_identifier ea)) ["default"]))] + (when (and matching-account + (:account/location matching-account) + (not= (:account/location matching-account) (:location ea))) - (not= "A" (:location ea))) - (throw (ex-info (str "Location '" (:location ea) "' not found.") + (throw (ex-info (str "Account '" + (:account/numeric-code matching-account) + "' requires location '" + (:account/location matching-account) + "' but got '" + (:location ea) + "'") {:status :error}))) - (when (and (<= (:debit ea 0.0) 0.0) - (<= (:credit ea 0.0) 0.0)) - (throw (ex-info (str "Line item amount " (or (:debit ea) (:credit ea)) " must be greater than 0.") - {:status :error}))) - (when (and (not (all-accounts (:account_identifier ea))) - (not (get - (get all-client-bank-accounts (:client_code entry)) - (:account_identifier ea)))) - (throw (ex-info (str "Account '" (:account_identifier ea) "' not found.") - {:status :error}))) - (let [matching-account (when (re-matches #"^[0-9]+$" (:account_identifier ea)) - (a/get-account-by-numeric-code-and-sets (Integer/parseInt (:account_identifier ea)) ["default"]))] - (when (and matching-account - (:account/location matching-account) - (not= (:account/location matching-account) - (:location ea))) - (throw (ex-info (str "Account '" - (:account/numeric-code matching-account) - "' requires location '" - (:account/location matching-account) - "' but got '" - (:location ea) - "'") - {:status :error}))) - (when (and matching-account - (not (:account/location matching-account)) - (= "A" (:location ea))) - (throw (ex-info (str "Account '" - (:account/numeric-code matching-account) - "' cannot use location '" - (:location ea) - "'") - {:status :error}))) - (remove-nils (cond-> {:journal-entry-line/location (:location ea) - :journal-entry-line/debit (when (> (:debit ea) 0) - (:debit ea)) - :journal-entry-line/credit (when (> (:credit ea) 0) - (:credit ea))} - matching-account (assoc :journal-entry-line/account (:db/id matching-account)) - (not matching-account) (assoc :journal-entry-line/account [:bank-account/code (:account_identifier ea)]))))) - (:line_items entry)) - - :journal-entry/cleared true})))))) + (when (and matching-account + (not (:account/location matching-account)) + (= "A" (:location ea))) + (throw (ex-info (str "Account '" + (:account/numeric-code matching-account) + "' cannot use location '" + (:location ea) + "'") + {:status :error}))) + (remove-nils (cond-> {:journal-entry-line/location (:location ea) + :journal-entry-line/debit (when (> (:debit ea) 0) + (:debit ea)) + :journal-entry-line/credit (when (> (:credit ea) 0) + (:credit ea))} + matching-account (assoc :journal-entry-line/account (:db/id matching-account)) + (not matching-account) (assoc :journal-entry-line/account [:bank-account/code (:account_identifier ea)]))))) + (:line_items entry)) + + :journal-entry/cleared true}))))) (:entries args))) errors (filter #(= (:status %) :error) transaction) ignored (filter #(= (:status %) :ignored) transaction) diff --git a/src/clj/auto_ap/routes/auth.clj b/src/clj/auto_ap/routes/auth.clj index 3281863c..dfe4b55d 100644 --- a/src/clj/auto_ap/routes/auth.clj +++ b/src/clj/auto_ap/routes/auth.clj @@ -12,7 +12,7 @@ (defn make-api-token [] (jwt/sign {:user "API" - :exp (time/plus (time/now) (time/days 700)) + :exp (time/plus (time/now) (time/days 1000)) :user/role "admin" :user/name "API"} (:jwt-secret env) diff --git a/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs b/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs index 6ee49961..826e7975 100644 --- a/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs @@ -24,7 +24,7 @@ :location (if (clojure.string/blank? location) nil location) - :numeric-code numeric-code + :numeric-code (js/parseInt numeric-code) :name name :account-set account-set :client-overrides (map (fn [client-override] diff --git a/src/cljs/auto_ap/views/pages/ledger/external_import.cljs b/src/cljs/auto_ap/views/pages/ledger/external_import.cljs index aabf4ca5..397cfcfc 100644 --- a/src/cljs/auto_ap/views/pages/ledger/external_import.cljs +++ b/src/cljs/auto_ap/views/pages/ledger/external_import.cljs @@ -11,7 +11,8 @@ [cljs-time.core :as t] [re-frame.core :as re-frame] [reagent.core :as r] - [clojure.string :as str])) + [clojure.string :as str] + [auto-ap.status :as status])) @@ -63,7 +64,8 @@ (let [successful-set (set (map :external-id (:successful (:import-ledger result)))) error-set (into {} (map (juxt :external-id :error) (:errors (:import-ledger result)))) existing-set (set (map :external-id (:existing (:import-ledger result)))) - ignored-set (set (map :external-id (:ignored (:import-ledger result))))] + ignored-set (set (map :external-id (:ignored (:import-ledger result)))) + seen-set (atom #{})] {:db (-> (forms/save-succeeded db ::form ) (assoc-in [::forms/forms ::form :result] {:errors error-set :success successful-set :existing existing-set :ignored ignored-set}) @@ -80,16 +82,19 @@ (existing-set (line->id %)) "" - (error-set (line->id %)) - [drop-down {:id [::ledger-import-line (line->id %) ] - :is-right? true - :header [:a.button {:aria-haspopup true - :on-click (dispatch-event [::events/toggle-menu [::ledger-import-line (line->id %)]]) - :tab-index "0"} - [:span.is-warning.icon {:title (error-set (line->id %))} [:i.fa.fa-exclamation-triangle]]]} - [drop-down-contents - [:div - [:span.dropdown-item (error-set (line->id %)) ]]]] + (and (error-set (line->id %)) + (not (@seen-set (line->id %)))) + (do + (swap! seen-set conj (line->id %)) + [drop-down {:id [::ledger-import-line (line->id %) ] + :is-right? true + :header [:a.button {:aria-haspopup true + :on-click (dispatch-event [::events/toggle-menu [::ledger-import-line (line->id %)]]) + :tab-index "0"} + [:span.is-warning.icon {:title (error-set (line->id %))} [:i.fa.fa-exclamation-triangle]]]} + [drop-down-contents + [:div + [:span.dropdown-item (error-set (line->id %)) ]]]]) ) :status-category (cond (successful-set (line->id %)) @@ -110,9 +115,9 @@ ::importing (fn [{:keys [db]} _] (when @(re-frame/subscribe [::can-submit]) - {:db (forms/loading db ::form ) - :graphql + {:graphql {:token (-> db :user) + :owns-state {:single ::import} :query-obj {:venia/operation {:operation/type :mutation :operation/name "ImportLedger"} :venia/queries [{:query/data [:import-ledger @@ -137,7 +142,7 @@ [:form.form (if value [:div - [:table.table + [:table.table {:style {:width "100%"}} [:thead [:tr (list @@ -190,22 +195,25 @@ (fn [] (let [current-client @(re-frame/subscribe [::subs/client]) user @(re-frame/subscribe [::subs/user]) + status @(re-frame/subscribe [::status/single ::import]) {:keys [data result active? error id]} @(re-frame/subscribe [::forms/form ::form]) ] [:div [:div.level [:div.level-left [:h1.title "Eternal Import"]] + [:div.level-right [:button.button.is-primary.is-pulled-right.is-large {:disabled (not data) :on-click (dispatch-event [::importing])} "Import"]]] + [status/status-notification {:statuses [[::status/single ::import]]} ] (when result [:div.notification "Imported with " (count (:errors result)) " errors, " (count (:ignored result)) " ignored, " (count (:success result)) " successful."]) - (if @(re-frame/subscribe [::forms/is-loading? ::form]) - [:div [:i.icon.fa.fa-spin.fa-spinner]] + (if (= :loading (:state status )) + [status/big-loader status] [:div [:div.is-clearfix [:div.is-pulled-right