diff --git a/src/clj/auto_ap/datomic/transactions.clj b/src/clj/auto_ap/datomic/transactions.clj index 108699ca..cc57ed15 100644 --- a/src/clj/auto_ap/datomic/transactions.clj +++ b/src/clj/auto_ap/datomic/transactions.clj @@ -170,7 +170,7 @@ transaction))) (map #(dissoc % :transaction/id)) (group-by :db/id))] - (clojure.pprint/pprint results) + (->> ids (map results) (map first)))) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index 205e03e6..72485bdd 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -515,6 +515,9 @@ :potential_autopay_invoices_matches {:type '(list (list :invoice)) :args {:transaction_id {:type :id}} :resolve :get-potential-autopay-invoices-matches} + :potential_unpaid_invoices_matches {:type '(list (list :invoice)) + :args {:transaction_id {:type :id}} + :resolve :get-potential-unpaid-invoices-matches} :potential_transaction_rule_matches {:type '(list :transaction_rule) :args {:transaction_id {:type :id}} @@ -1239,6 +1242,7 @@ :get-payment-page gq-checks/get-payment-page :get-potential-payments gq-checks/get-potential-payments :get-potential-autopay-invoices-matches gq-transactions/get-potential-autopay-invoices-matches + :get-potential-unpaid-invoices-matches gq-transactions/get-potential-unpaid-invoices-matches :get-accounts gq-accounts/get-accounts :get-transaction-page gq-transactions/get-transaction-page :get-ledger-page gq-ledger/get-ledger-page diff --git a/src/clj/auto_ap/graphql/transactions.clj b/src/clj/auto_ap/graphql/transactions.clj index 779ab51b..d30e3988 100644 --- a/src/clj/auto_ap/graphql/transactions.clj +++ b/src/clj/auto_ap/graphql/transactions.clj @@ -93,6 +93,16 @@ (for [[_ invoice-id ] matches] (d-invoices/get-by-id invoice-id))))))) +(defn get-potential-unpaid-invoices-matches [context args value] + (assert-admin (:id context)) + (let [transaction (d-transactions/get-by-id (:transaction_id args))] + + (let [matches-set (import/match-transaction-to-unpaid-invoices (:transaction/amount transaction) + (:db/id (:transaction/client transaction)))] + (->graphql (for [matches matches-set] + (for [[_ invoice-id ] matches] + (d-invoices/get-by-id invoice-id))))))) + (defn unlink-transaction [context args value] (let [_ (assert-admin (:id context)) args (assoc args :id (:id context)) diff --git a/src/clj/auto_ap/yodlee/import.clj b/src/clj/auto_ap/yodlee/import.clj index 3286a150..0d5efdb0 100644 --- a/src/clj/auto_ap/yodlee/import.clj +++ b/src/clj/auto_ap/yodlee/import.clj @@ -74,7 +74,7 @@ (group-by first) ;; group by vendors vals) considerations (for [candidate-invoices candidate-invoices-vendor-groups - invoice-count (range 1 30) + invoice-count (range 1 32) consideration (partition invoice-count 1 candidate-invoices) :when (dollars= (reduce (fn [acc [_ _ amount]] (+ acc amount)) 0.0 consideration) @@ -84,6 +84,30 @@ considerations )) +(defn match-transaction-to-unpaid-invoices [amount client-id] + (log/info "trying to find unpaid invoices for " client-id amount) + (let [candidate-invoices-vendor-groups (->> (d/query {:query {:find ['?vendor-id '?e '?total '?d] + :in ['$ '?client-id] + :where ['[?e :invoice/client ?client-id] + '[?e :invoice/status :invoice-status/unpaid] + '(not [_ :invoice-payment/invoice ?e]) + '[?e :invoice/vendor ?vendor-id] + '[?e :invoice/total ?total] + '[?e :invoice/date ?d]]} + :args [(d/db conn) client-id]}) + (sort-by last) ;; sort by scheduled payment date + (group-by first) ;; group by vendors + vals) + considerations (for [candidate-invoices candidate-invoices-vendor-groups + invoice-count (range 1 32) + consideration (partition invoice-count 1 candidate-invoices) + :when (dollars= (reduce (fn [acc [_ _ amount]] + (+ acc amount)) 0.0 consideration) + (- amount))] + consideration)] + (log/info "Found " (count considerations) "unpaid invoice considerations for transaction of" amount) + considerations)) + (defn match-transaction-to-single-unfulfilled-autopayments [amount client-id] (let [considerations (match-transaction-to-unfulfilled-autopayments amount client-id)] (if (= 1 (count considerations)) @@ -127,6 +151,7 @@ nil)) (defn transactions->txs [transactions transaction->bank-account apply-rules existing] + (log/info transactions) (into [] @@ -163,8 +188,10 @@ (t/after? date (:start-date bank-account))) )] (let [existing-check (transaction->existing-payment transaction check-number client-id bank-account-id amount id) - invoices-matches (when-not existing-check - (match-transaction-to-single-unfulfilled-autopayments amount client-id ))] + autopay-invoices-matches (when-not existing-check + (match-transaction-to-unfulfilled-autopayments amount client-id )) + unpaid-invoices-matches (when-not existing-check + (match-transaction-to-unpaid-invoices amount client-id ))] (cond-> [#:transaction {:post-date (coerce/to-date (time/parse post-date "YYYY-MM-dd")) @@ -190,11 +217,12 @@ :location "A" :amount (Math/abs (double amount))}])) - (and (not existing-check) - (seq invoices-matches)) (add-new-payment invoices-matches bank-account-id client-id) + #_(and (not existing-check) + (seq autopay-invoices-matches)) #_(add-new-payment autopay-invoices-matches bank-account-id client-id) - true (update 0 #(apply-rules % valid-locations)) + (and (not (seq autopay-invoices-matches)) + (not (seq unpaid-invoices-matches))) (update 0 #(apply-rules % valid-locations)) true (update 0 remove-nils)))))) @@ -251,9 +279,10 @@ transaction->bank-account (comp all-bank-accounts :bank-account-id)] (log/info "Importing " (count transformed-transactions) " manual transactions") - (let [result (batch-transact - (transactions->txs transformed-transactions transaction->bank-account (rm/rule-applying-fn all-rules) (get-existing)))] - (log/info "Imported " (count result) " manual transactions"))))) + (doseq [tx (transactions->txs transformed-transactions transaction->bank-account (rm/rule-applying-fn all-rules) (get-existing))] + (audit-transact tx {:user/name "Yodlee import" + :user/role ":admin"})) + (log/info "Imported manual transactions")))) (defn do-import ([] diff --git a/src/cljs/auto_ap/views/pages/transactions/form.cljs b/src/cljs/auto_ap/views/pages/transactions/form.cljs index 4aa7e3a3..832e2743 100644 --- a/src/cljs/auto_ap/views/pages/transactions/form.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/form.cljs @@ -53,7 +53,7 @@ (re-frame/reg-event-db ::editing - (fn [db [_ which potential-payment-matches potential-autopay-invoices-matches potential-transaction-rule-matches]] + (fn [db [_ which potential-payment-matches potential-autopay-invoices-matches potential-unpaid-invoices-matches potential-transaction-rule-matches]] (let [locations @(re-frame/subscribe [::subs/locations-for-client (:id (:client which))]) accounts-by-id @(re-frame/subscribe [::subs/accounts-by-id (:client which)])] (forms/start-form db ::form @@ -68,6 +68,7 @@ (assoc :potential-payment-matches potential-payment-matches) (assoc :potential-transaction-rule-matches potential-transaction-rule-matches) (assoc :potential-autopay-invoices-matches potential-autopay-invoices-matches) + (assoc :potential-unpaid-invoices-matches potential-unpaid-invoices-matches) (update :accounts expense-accounts-field/from-graphql accounts-by-id (:amount which) locations)))))) (re-frame/reg-event-db @@ -247,6 +248,30 @@ "Match"]]]))]])) +(defn potential-unpaid-invoices-matches-box [{:keys [potential-unpaid-invoices-matches] :as params}] + (let [states @(re-frame/subscribe [::status/multi ::matching])] + [:div + [:div.notification.is-light.is-info "This transaction may match the following unpaid invoice(s)."] + [:table.table.grid.is-fullwidth + (list + (for [invoices potential-unpaid-invoices-matches] + ^{:key (str invoices)} + [:tr + [:td {:style {:width "30%"}} (:name (:vendor (first invoices)))] + [:td {:style {:overflow "visible" :width "60%"}} [:span.has-tooltip-arrow.has-tooltip-bottom {:data-tooltip (str/join "\n" + (for [i invoices] + (str (:invoice-number i) " (" (->$ (:total i)) ")")) + )} + (count invoices) " invoices" (if (> (count invoices) 1) + (str " from " (date->str (:date (first invoices))) " - " (date->str (:date (last invoices)))) + (str " on " (date->str (:date (first invoices)))))]] + [:td {:style {:width "6em"}} + [:a.button.is-primary.is-small {:on-click (dispatch-event [::matching-unpaid-invoices (map :id invoices)]) + :class (status/class-for (get states [:unpaid-invoices (map :id invoices)])) + :disabled (status/disabled-if-any states)} + "Match"]]]))]])) + + (defonce ^js/React.Context current-tab-context ( react/createContext "default")) (def ^js/React.Provider CurrentTabProvider (. current-tab-context -Provider)) #_(println "Provider is" Provider) @@ -267,7 +292,7 @@ (into [:div] (->> (r/children (r/current-component)) (filter identity) - (filter #(= (doto (:key (second %)) println) current-tab-v) ) + (filter #(= (:key (second %)) current-tab-v) ) first (drop 2)))])))))) @@ -340,10 +365,17 @@ (when (and (seq (:potential-autopay-invoices-matches data)) + #_(not is-already-matched?) + is-admin?) + [tab {:title "Autopay Invoices" :key :autopay-invoices} + [potential-autopay-invoices-matches-box {:potential-autopay-invoices-matches (:potential-autopay-invoices-matches data)}]]) + + (when + (and (seq (:potential-unpaid-invoices-matches data)) (not is-already-matched?) is-admin?) - [tab {:title "Autopay invoices" :key :autopay-invoices} - [potential-autopay-invoices-matches-box {:potential-autopay-invoices-matches (:potential-autopay-invoices-matches data)}]]) + [tab {:title "Unpaid Invoices" :key :unpaid-invoices} + [potential-unpaid-invoices-matches-box {:potential-unpaid-invoices-matches (:potential-unpaid-invoices-matches data)}]]) (when (and (seq (:potential-payment-matches data)) diff --git a/src/cljs/auto_ap/views/pages/transactions/table.cljs b/src/cljs/auto_ap/views/pages/transactions/table.cljs index 79af4884..36833cdc 100644 --- a/src/cljs/auto_ap/views/pages/transactions/table.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/table.cljs @@ -22,7 +22,7 @@ ::editing-matches-found (fn [{:keys [db]} [_ which matches]] {:dispatch - [::edit/editing which (:potential-payment-matches matches) (:potential-autopay-invoices-matches matches) (:potential-transaction-rule-matches matches)]})) + [::edit/editing which (:potential-payment-matches matches) (:potential-autopay-invoices-matches matches) (:potential-unpaid-invoices-matches matches) (:potential-transaction-rule-matches matches)]})) (re-frame/reg-event-fx ::editing-matches-failed @@ -41,6 +41,9 @@ {:transaction_id (:id which)} [:id :memo :check-number [:vendor [:name]]]]} {:query/data [:potential-autopay-invoices-matches + {:transaction_id (:id which)} + [:id :invoice-number :total :date :scheduled-payment [:vendor [:name]]]]} + {:query/data [:potential-unpaid-invoices-matches {:transaction_id (:id which)} [:id :invoice-number :total :date :scheduled-payment [:vendor [:name]]]]}] (when @(re-frame/subscribe [::subs/is-admin?])