diff --git a/resources/schema.edn b/resources/schema.edn index 389c8bc3..9b430fc1 100644 --- a/resources/schema.edn +++ b/resources/schema.edn @@ -1626,6 +1626,9 @@ :db/doc "An unhashed version of the id", :db/ident :transaction/raw-id, } + {:db/valueType :db.type/ref + :db/cardinality :db.cardinality/one + :db/ident :transaction/plaid-merchant} {:db/valueType #:db{:ident :db.type/ref}, :db/cardinality #:db{:ident :db.cardinality/one}, @@ -1922,6 +1925,11 @@ :db/doc "If not a person, the legal entity naame", :db/ident :vendor/legal-entity-name, } + {:db/valueType db.type/ref, + :db/cardinality :db.cardinality/one, + :db/doc "A link to the plaid merchant for this vendor", + :db/ident :vendor/plaid-merchant, + } {:db/valueType #:db{:ident :db.type/ref}, :db/cardinality #:db{:ident :db.cardinality/one}, @@ -2097,4 +2105,11 @@ :db/doc "The amount of money in the cash drawer at the start of the shift." :db/valueType :db.type/double :db/cardinality :db.cardinality/one} + + {:db/ident :plaid-merchant/name + :db/doc "A plaid merchant" + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one + :db/unique :db.unique/identity} + ] diff --git a/src/clj/auto_ap/datomic/transactions.clj b/src/clj/auto_ap/datomic/transactions.clj index 3ee05eb5..cc55b4cb 100644 --- a/src/clj/auto_ap/datomic/transactions.clj +++ b/src/clj/auto_ap/datomic/transactions.clj @@ -200,7 +200,8 @@ :account/location {:account/client-overrides [:account-client-override/name {:account-client-override/client [:db/id]}]}]}] - :transaction/yodlee-merchant [:db/id :yodlee-merchant/yodlee-id :yodlee-merchant/name]}] + :transaction/yodlee-merchant [:db/id :yodlee-merchant/yodlee-id :yodlee-merchant/name] + :transaction/plaid-merchant [:db/id :plaid-merchant/name]}] ids) (map #(update % :transaction/date coerce/from-date)) (map #(update % :transaction/post-date coerce/from-date)) diff --git a/src/clj/auto_ap/datomic/vendors.clj b/src/clj/auto_ap/datomic/vendors.clj index 40eef2b4..c583351c 100644 --- a/src/clj/auto_ap/datomic/vendors.clj +++ b/src/clj/auto_ap/datomic/vendors.clj @@ -47,6 +47,7 @@ :vendor/legal-entity-tin-type [:db/ident :db/id] :vendor/legal-entity-1099-type [:db/ident :db/id] :vendor/default-account [:db/id :account/numeric-code :account/name] + :vendor/plaid-merchant [:db/id :plaid-merchant/name] :vendor-usage/_vendor [:vendor-usage/client :vendor-usage/count]}]) @@ -124,6 +125,7 @@ {:vendor/default-account [:account/name :db/id :account/location] :vendor/legal-entity-tin-type [:db/ident :db/id] :vendor/legal-entity-1099-type [:db/ident :db/id] + :vendor/plaid-merchant [:db/id :plaid-merchant/name] :vendor/account-overrides [* {:vendor-account-override/client [:client/name :db/id] :vendor-account-override/account [:account/name :account/numeric-code :db/id]}] :vendor/terms-overrides [* {:vendor-terms-override/client [:client/name :db/id]}] diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index 1a3f12a4..d4c7dd39 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -176,6 +176,7 @@ :print_as {:type 'String} :primary_contact {:type :contact} + :plaid_merchant {:type :plaid_merchant} :secondary_contact {:type :contact} :address {:type :address} @@ -210,6 +211,9 @@ :yodlee_id {:type 'String} :name {:type 'String}}} + :plaid_merchant {:fields {:id {:type :id} + :name {:type 'String}}} + :intuit_bank_account {:fields {:id {:type :id} :external_id {:type 'String} :name {:type 'String}}} @@ -470,6 +474,7 @@ :secondary_contact {:type :add_contact} :address {:type :add_address} :default_account_id {:type :id} + :plaid_merchant {:type :id} :account_overrides {:type '(list :add_account_override)} :schedule_payment_dom {:type '(list :add_schedule_payment_dom)} :invoice_reminder_schedule {:type 'String} @@ -882,3 +887,4 @@ :exception e) (throw e)))))))))) + diff --git a/src/clj/auto_ap/graphql/plaid.clj b/src/clj/auto_ap/graphql/plaid.clj index 6e17f729..b90400c5 100644 --- a/src/clj/auto_ap/graphql/plaid.clj +++ b/src/clj/auto_ap/graphql/plaid.clj @@ -16,12 +16,16 @@ assert-can-see-client assert-present attach-tracing-resolvers + cleanse-query limited-clients]] [auto-ap.plaid.core :as p] [clj-time.coerce :as coerce] [clj-time.core :as time] [clojure.tools.logging :as log] - [datomic.api :as dc])) + [datomic.api :as dc] + [auto-ap.solr :as solr] + [manifold.deferred :as de] + [manifold.executor :as ex])) (defn plaid-link-token [context value _] (when-not (:client_id value) @@ -128,6 +132,30 @@ @(dc/transact conn [[:db/retractEntity (:id args)]]) {:message "Item deleted."}) +(defn search-merchants [context args _] + (if-let [query (not-empty (cleanse-query (:query args)))] + (let [search-query (str "name:(" query ")")] + (for [{:keys [id name]} (solr/query solr/impl "plaid_merchants" {"query" search-query + "fields" "id, name"})] + {:id (Long/parseLong id) + :name (first name)})) + [])) + +(def single-thread (ex/fixed-thread-executor 1)) + +(defn rebuild-search-index [] + (de/future-with + single-thread + (auto-ap.solr/index-documents-raw + auto-ap.solr/impl + "plaid_merchants" + (for [[result] (dc/qseq {:query '[:find (pull ?v [:plaid-merchant/name :db/id]) + :in $ + :where [?v :plaid-merchant/name]] + :args [(dc/db conn)]})] + {"id" (:db/id result) + "name" (:plaid-merchant/name result)})))) + (defn attach [schema] (-> (merge-with merge schema @@ -162,7 +190,10 @@ :sort {:type '(list :sort_item)} :start {:type 'Int} :per_page {:type 'Int}} - :resolve :get-plaid-item-page}} + :resolve :get-plaid-item-page} + :search_plaid_merchants {:type '(list :plaid_merchant) + :args {:query {:type 'String}} + :resolve :search-plaid-merchants}} :mutations {:link_plaid {:type :message :args {:client_code {:type 'String} :public_token {:type 'String}} @@ -171,6 +202,7 @@ :args {:id {:type :id}} :resolve :mutation/delete-plaid-item}}}) (attach-tracing-resolvers {:plaid-link-token plaid-link-token + :search-plaid-merchants search-merchants :get-plaid-item-page get-plaid-item-page :mutation/link-plaid link-plaid :mutation/delete-plaid-item delete-plaid-item}))) diff --git a/src/clj/auto_ap/graphql/transactions.clj b/src/clj/auto_ap/graphql/transactions.clj index f996ca75..8fe56b5d 100644 --- a/src/clj/auto_ap/graphql/transactions.clj +++ b/src/clj/auto_ap/graphql/transactions.clj @@ -550,6 +550,7 @@ :accounts {:type '(list :invoices_expense_accounts)} :payment {:type :payment} :expected_deposit {:type :expected_deposit} + :plaid_merchant {:type :plaid_merchant} :vendor {:type :vendor} :bank_account {:type :bank_account} :date {:type 'String} diff --git a/src/clj/auto_ap/graphql/vendors.clj b/src/clj/auto_ap/graphql/vendors.clj index d3bd8aa9..3da3fd85 100644 --- a/src/clj/auto_ap/graphql/vendors.clj +++ b/src/clj/auto_ap/graphql/vendors.clj @@ -34,7 +34,7 @@ (map first))) (set (map :db/id (:user/clients id))))))) -(defn upsert-vendor [context {{:keys [id name hidden terms code print_as primary_contact secondary_contact address default_account_id invoice_reminder_schedule schedule_payment_dom terms_overrides account_overrides] :as in} :vendor} _] +(defn upsert-vendor [context {{:keys [id name hidden terms code print_as primary_contact plaid_merchant secondary_contact address default_account_id invoice_reminder_schedule schedule_payment_dom terms_overrides account_overrides] :as in} :vendor} _] (when (and id (not (can-user-edit-vendor? id (:id context)))) (assert-failure "This vendor is managed by Integreat. Please reach out to ben@integreatconsult.com for your changes.")) @@ -125,6 +125,7 @@ :vendor/legal-entity-tin (:legal_entity_tin in) :vendor/legal-entity-tin-type (enum->keyword (:legal_entity_tin_type in) "legal-entity-tin-type") :vendor/legal-entity-1099-type (enum->keyword (:legal_entity_1099_type in) "legal-entity-1099-type") + :vendor/plaid-merchant plaid_merchant :vendor/account-overrides account-overrides :vendor/terms-overrides terms-overrides :vendor/schedule-payment-dom schedule-payment-dom diff --git a/src/clj/auto_ap/import/plaid.clj b/src/clj/auto_ap/import/plaid.clj index 75b87b1a..26af1275 100644 --- a/src/clj/auto_ap/import/plaid.clj +++ b/src/clj/auto_ap/import/plaid.clj @@ -25,16 +25,18 @@ (defn plaid->transaction [t] - #:transaction {:description-original (:name t) - :raw-id (:transaction_id t) - :db/id (random-tempid) - :id #_{:clj-kondo/ignore [:unresolved-var]} - (di/sha-256 (:transaction_id t)) - :amount (if (= "credit" (:type (:account t))) - (- (double (:amount t))) - (double (:amount t))) - :date (coerce/to-date (atime/parse (:authorized_date t) atime/iso-date)) - :status "POSTED"}) + (cond-> #:transaction {:description-original (:name t) + :raw-id (:transaction_id t) + :db/id (random-tempid) + :id #_{:clj-kondo/ignore [:unresolved-var]} + (di/sha-256 (:transaction_id t)) + :amount (if (= "credit" (:type (:account t))) + (- (double (:amount t))) + (double (:amount t))) + :date (coerce/to-date (atime/parse (:authorized_date t) atime/iso-date)) + :status "POSTED"} + (:merchant_name t) (assoc :transaction/plaid-merchant {:plaid-merchant/name (:merchant_name t) + :db/id (random-tempid)}))) (defn import-plaid-int [] @@ -53,6 +55,7 @@ :account (accounts-by-id (:account_id transaction)))) :transaction/bank-account bank-account-id + :transaction/client client-id)))) (t/finish! import-batch) (catch Exception e diff --git a/src/cljs/auto_ap/views/components/vendor_dialog.cljs b/src/cljs/auto_ap/views/components/vendor_dialog.cljs index 85d2195f..bcd4c178 100644 --- a/src/cljs/auto_ap/views/components/vendor_dialog.cljs +++ b/src/cljs/auto_ap/views/components/vendor_dialog.cljs @@ -78,7 +78,7 @@ (re-frame/reg-event-fx ::save [with-user with-is-admin? (forms/triggers-loading ::vendor-form) (forms/in-form ::vendor-form)] - (fn [{:keys [user is-admin?] {{:keys [name hidden print-as terms invoice-reminder-schedule primary-contact automatically-paid-when-due schedule-payment-dom secondary-contact address default-account terms-overrides account-overrides id legal-entity-name legal-entity-tin legal-entity-tin-type legal-entity-first-name legal-entity-last-name legal-entity-middle-name legal-entity-1099-type] :as data} :data} :db} _] + (fn [{:keys [user is-admin?] {{:keys [name hidden print-as terms invoice-reminder-schedule plaid-merchant primary-contact automatically-paid-when-due schedule-payment-dom secondary-contact address default-account terms-overrides account-overrides id legal-entity-name legal-entity-tin legal-entity-tin-type legal-entity-first-name legal-entity-last-name legal-entity-middle-name legal-entity-1099-type] :as data} :data} :db} _] (if (m/validate schema data) (let [query [:upsert-vendor {:vendor (cond-> {:id id @@ -114,6 +114,7 @@ :automatically-paid-when-due (mapv (comp :id :client) automatically-paid-when-due) + :plaid-merchant (:id plaid-merchant) :legal-entity-name legal-entity-name :legal-entity-first-name legal-entity-first-name :legal-entity-middle-name legal-entity-middle-name @@ -179,8 +180,18 @@ "Print Checks As" [:input.input]] (when is-admin? - [form-builder/raw-field-v2 {:field :hidden} - [com/checkbox {:label "Hidden"}]]) + [:<> + [form-builder/raw-field-v2 {:field :hidden} + [com/checkbox {:label "Hidden"}]] + + [form-builder/field-v2 {:field :plaid-merchant + :required? false} + "Plaid merchant" + [search-backed-typeahead {:search-query (fn [i] + [:search_plaid_merchants + {:query i} + [:name :id]]) + :style {:width "19em"}}]]]) [form-builder/section {:title "Terms"} [form-builder/field-v2 {:field :terms} diff --git a/src/cljs/auto_ap/views/pages/admin/clients/form.cljs b/src/cljs/auto_ap/views/pages/admin/clients/form.cljs index 9d728813..180c4afd 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients/form.cljs @@ -460,9 +460,9 @@ "Intuit Bank Account" [typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts]) :entity->text (fn [m] (str (:name m)))}]] - [form-builder/field-v2 {:field :plaid-accounti} + [form-builder/field-v2 {:field :plaid-account} "Plaid Account" - [typeahead-v3 {:entities @(re-frame/subscribe [::plaid-accounts (:id new-client)]) + [typeahead-v3 {:entities (mapcat :accounts (:plaid-items new-client )) :entity->text (fn [m] (str (:name m)))}]]]) (when (#{:credit ":credit"} type ) @@ -498,7 +498,7 @@ [form-builder/field-v2 {:field :plaid-account} "Plaid Account" - [typeahead-v3 {:entities @(re-frame/subscribe [::plaid-accounts (:id new-client)]) + [typeahead-v3 {:entities (mapcat :accounts (:plaid-items new-client )) :entity->text (fn [m] (str (:name m)))}]]]) [:div.field [:label.label "Locations"] diff --git a/src/cljs/auto_ap/views/pages/admin/vendors.cljs b/src/cljs/auto_ap/views/pages/admin/vendors.cljs index d0c29e44..96b15b63 100644 --- a/src/cljs/auto_ap/views/pages/admin/vendors.cljs +++ b/src/cljs/auto_ap/views/pages/admin/vendors.cljs @@ -22,6 +22,7 @@ [:usage [:client-id :count]] [:primary-contact [:name :phone :email :id]] [:secondary-contact [:id :name :phone :email]] + [:plaid-merchant [:id :name]] :print-as :invoice-reminder-schedule :code :legal-entity-name :legal-entity-first-name :legal-entity-middle-name :legal-entity-last-name diff --git a/src/cljs/auto_ap/views/pages/admin/vendors/common.cljs b/src/cljs/auto_ap/views/pages/admin/vendors/common.cljs index b3248587..3773b3b5 100644 --- a/src/cljs/auto_ap/views/pages/admin/vendors/common.cljs +++ b/src/cljs/auto_ap/views/pages/admin/vendors/common.cljs @@ -7,6 +7,7 @@ [:schedule-payment-dom [[:client [:id :name]] :id :dom]] [:usage [:client-id :count]] [:primary-contact [:name :phone :email :id]] + [:plaid-merchant [:name :id]] [:secondary-contact [:id :name :phone :email]] :print-as :invoice-reminder-schedule :code :legal-entity-name diff --git a/src/cljs/auto_ap/views/pages/transactions/common.cljs b/src/cljs/auto_ap/views/pages/transactions/common.cljs index 1f011c2b..c5343d7e 100644 --- a/src/cljs/auto_ap/views/pages/transactions/common.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/common.cljs @@ -13,6 +13,7 @@ [:accounts [:id :amount :location [:account [:name :id :location :numeric-code]]]] :date [:yodlee_merchant [:name :yodlee-id :id]] + [:plaid_merchant [:name :id]] :post_date [:expected-deposit [:id :date]] [:forecast-match [:id :identifier]] diff --git a/src/cljs/auto_ap/views/pages/transactions/form.cljs b/src/cljs/auto_ap/views/pages/transactions/form.cljs index d920429d..40de83f8 100644 --- a/src/cljs/auto_ap/views/pages/transactions/form.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/form.cljs @@ -60,6 +60,7 @@ (-> which (select-keys [:vendor :amount :payment :client :description-original :yodlee-merchant :id :potential-payment-matches + :plaid-merchant :forecast-match :date :location :accounts :approval-status :matched-rule]) @@ -359,6 +360,10 @@ "Description" [:input.input {:type "text" :disabled "disabled"}]] + [form-builder/field-v2 {:field [:plaid-merchant :name]} + "Merchant" + [:input.input {:type "text" + :disabled "disabled"}]] [form-builder/field-v2 {:field [:date]} "Date" diff --git a/start-solr.sh b/start-solr.sh index 82d4b77d..2a0bf70c 100755 --- a/start-solr.sh +++ b/start-solr.sh @@ -1,3 +1,3 @@ -#/bin/bash +#!/bin/bash sudo docker run --rm -ti -v ~/dev/integreat/data/solr:/var/solr --network=bridge -p 8983:8983 solr