diff --git a/scratch-sessions/bad_accounts_for_invoices.clj b/scratch-sessions/bad_accounts_for_invoices.clj index 6dcca901..2eb46d79 100644 --- a/scratch-sessions/bad_accounts_for_invoices.clj +++ b/scratch-sessions/bad_accounts_for_invoices.clj @@ -4,6 +4,8 @@ ;; You can also press C-u C-j to evaluate the expression and pretty-print its result. +(require '[datomic.api :as d]) +(require '[auto-ap.datomic]) (def bad-accounts (-> #{} (into (d/q '[:find [?a ...] @@ -502,10 +504,6 @@ :separator \tab) -(for [c (get-square-clients "NGPG") - l (:client/square-locations c) - :when (:square-location/client-location l)] - @(daily-results c l)) (def results *1) diff --git a/src/clj/auto_ap/datomic/migrate.clj b/src/clj/auto_ap/datomic/migrate.clj index 7f203be3..ba815e43 100644 --- a/src/clj/auto_ap/datomic/migrate.clj +++ b/src/clj/auto_ap/datomic/migrate.clj @@ -189,8 +189,7 @@ (defn migrate [conn] - (let [ - norms-map (merge {:auto-ap/base-schema {:txes auto-ap.datomic/base-schema} + (let [norms-map (merge {:auto-ap/base-schema {:txes auto-ap.datomic/base-schema} :auto-ap/functions {:txes-fn 'auto-ap.datomic.migrate/functions :requires [:auto-ap/base-schema]} :auto-ap/fx-pay-function-10 {:txes-fn 'auto-ap.datomic.migrate/fix-pay-function @@ -339,7 +338,8 @@ {:db/ident :account-client-override/name :db/doc "client override" :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]]} + :db/cardinality :db.cardinality/one}]] + :requires [:auto-ap/base-schema]} :auto-ap/add-cleared-against {:txes [[{:db/ident :transaction/cleared-against :db/doc "which entitiy it was cleared against" :db/valueType :db.type/string @@ -549,9 +549,10 @@ :db/cardinality :db.cardinality/one :db/doc "a name search for accounts" :db/fulltext true}]] - :requires [:auto-ap/add-account-overrides]} + :requires [:auto-ap/add-account-overrides :auto-ap/base-schema]} :auto-ap/add-search-terms-accounts {:txes-fn `add-account-search-terms - :requires [:auto-ap/fulltext-accounts]} + :first-time-only true + :requires [:auto-ap/fulltext-accounts :auto-ap/base-schema :auto-ap/add-accounts]} :auto-ap/add-account-options {:txes [[{:db/ident :account/invoice-allowance :db/valueType :db.type/ref :db/cardinality :db.cardinality/one @@ -564,10 +565,11 @@ :db/doc "Warn on usage"} {:db/ident :allowance/admin-only :db/doc "Only admins can use it"}]] - :requires [:auto-ap/add-search-terms-accounts]} + :requires [:auto-ap/add-search-terms-accounts :auto-ap/add-accounts]} :auto-ap/backfill-account-options {:txes-fn `backfill-account-options + :first-time-only true - :requires [:auto-ap/add-account-options]} + :requires [:auto-ap/add-account-options :auto-ap/add-accounts]} :auto-ap/add-vendor-account-options2 {:txes [[{:db/ident :account/vendor-allowance :db/valueType :db.type/ref @@ -578,10 +580,10 @@ :db/cardinality :db.cardinality/one :db/doc "Whether this account can be used generally"} ]] - :requires [:auto-ap/backfill-account-options]} + :requires [:auto-ap/backfill-account-options :auto-ap/add-accounts]} :auto-ap/backfill-account-options3 {:txes-fn `backfill-account-options2 - - :requires [:auto-ap/add-vendor-account-options2]}} + :first-time-only true + :requires [:auto-ap/add-vendor-account-options2 :auto-ap/add-accounts]}} diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index e430a9d6..a0bf9269 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -363,7 +363,8 @@ :search_account {:type '(list :account_search_result) :args {:query {:type 'String} :allowance {:type :account_allowance} - :client_id {:type :id}} + :client_id {:type :id} + :vendor_id {:type :id}} :resolve :search-account} diff --git a/src/clj/auto_ap/graphql/accounts.clj b/src/clj/auto_ap/graphql/accounts.clj index 7d2598e5..66de4ad5 100644 --- a/src/clj/auto_ap/graphql/accounts.clj +++ b/src/clj/auto_ap/graphql/accounts.clj @@ -5,10 +5,11 @@ [auto-ap.graphql.utils :refer [->graphql <-graphql - cleanse-query assert-admin assert-can-see-client + cleanse-query enum->keyword + is-admin? result->page]] [datomic.api :as d])) @@ -74,7 +75,13 @@ (->graphql (d-accounts/get-by-id (or id (get-in result [:tempids "new-account"]))))))) -(defn search [context {query :query client :client_id allowance :allowance} _] +(def search-pattern [:db/id + :account/numeric-code + :account/location + {:account/vendor-allowance [:db/ident] + :account/default-allowance [:db/ident] + :account/invoice-allowance [:db/ident]}]) +(defn search [context {query :query client :client_id allowance :allowance vendor-id :vendor_id} _] (when client (assert-can-see-client (:id context) client)) (let [query (cleanse-query query) @@ -82,45 +89,58 @@ second (not-empty ) Integer/parseInt) - - pattern [:db/id :account/numeric-code :account/location {:account/vendor-allowance [:db/ident]} {:account/invoice-allowance [:db/ident]}] + + valid-allowances (cond-> #{:allowance/allowed + :allowance/warn} + (is-admin? (:id context)) (conj :allowance/admin-only)) allowance (cond (= allowance :vendor) :account/vendor-allowance (= allowance :invoice) :account/invoice-allowance :else - :account/default-allowance)] + :account/default-allowance) + + vendor-account (when vendor-id + (-> (d/q '[:find ?da + :in $ ?v + :where [?v :vendor/default-account ?da]] + (d/db conn) + vendor-id) + ffirst)) + xform (comp + (filter (fn [[_ a]] + (or + (valid-allowances (-> a allowance :db/ident)) + (= (:db/id a) vendor-account)))) + (map (fn [[n a]] + {:name (str (:account/numeric-code a) " - " n) + :id (:db/id a) + :location (:account/location a) + :warning (when (= :allowance/warn (-> a allowance :db/ident)) + "This account is not typically used for this purpose.")})))] (if query (if num (->> (d/q '[:find ?n (pull ?i pattern) :in $ ?numeric-code ?allowance pattern :where [?i :account/numeric-code ?numeric-code] [?i :account/name ?n] - (or [?i ?allowance :allowance/allowed] - [?i ?allowance :allowance/warn])] + (or [?i :account/applicability :account-applicability/global] + [?i :account/applicability :account-applicability/optional])] (d/db conn) num allowance - pattern) - (map (fn [[n a]] - {:name (str (:account/numeric-code a) " - " n) - :id (:db/id a) - :location (:account/location a) - :warning (when (= :allowance/warn (-> a allowance :db/ident)) - "This account is not typically used for this purpose.")}))) - + search-pattern) + (sequence xform)) (->> (d/q '[:find ?n (pull ?i pattern) ?s :in $ ?q ?allowance pattern :where [(fulltext $ :account/search-terms ?q) [[?i ?n _ ?s]]] [?i :account/numeric-code ?numeric-code] - (or [?i ?allowance :allowance/allowed] - [?i ?allowance :allowance/warn]) (or [?i :account/applicability :account-applicability/global] [?i :account/applicability :account-applicability/optional])] (d/db conn) query allowance - pattern) + search-pattern) (concat (when client (d/q '[:find ?n (pull ?a pattern) ?s :in $ ?c ?q ?allowance pattern @@ -128,20 +148,13 @@ [?i :account-client-override/client ?c] [(fulltext $ :account-client-override/search-terms ?q) [[?i ?n _ ?s]]] [?a :account/client-overrides ?i] - [?a :account/numeric-code ?numeric-code] - (or [?a ?allowance :allowance/allowed] - [?a ?allowance :allowance/warn])] + [?a :account/numeric-code ?numeric-code]] (d/db conn) client query allowance - pattern))) + search-pattern))) (sort-by (comp - last)) - (map (fn [[n a]] - {:name (str (:account/numeric-code a) " - " n) - :id (:db/id a) - :location (:account/location a) - :warning (when (= :allowance/warn (-> a allowance :db/ident)) - "This account is not typically used for this purpose.")})))) + (sequence xform))) []))) diff --git a/src/cljs/auto_ap/views/components/expense_accounts_dialog.cljs b/src/cljs/auto_ap/views/components/expense_accounts_dialog.cljs index 63496c5b..1497471a 100644 --- a/src/cljs/auto_ap/views/components/expense_accounts_dialog.cljs +++ b/src/cljs/auto_ap/views/components/expense_accounts_dialog.cljs @@ -116,12 +116,12 @@ [:tr {:class (when (:warning (:account a)) "has-background-warning-light")} [:td.expandable [:div.control - (println a) [form-builder/raw-field-v2 {:field [:expense-accounts id :account]} [search-backed-typeahead {:search-query (fn [i] [:search_account {:query i :client-id (:id client) + :vendor-id (:id (:vendor (:invoice data))) :allowance :invoice} [:name :id :location :warning]]) :class "is-warn"}]]]] diff --git a/src/cljs/auto_ap/views/components/expense_accounts_field.cljs b/src/cljs/auto_ap/views/components/expense_accounts_field.cljs index 4592c24e..23a4b81d 100644 --- a/src/cljs/auto_ap/views/components/expense_accounts_field.cljs +++ b/src/cljs/auto_ap/views/components/expense_accounts_field.cljs @@ -74,7 +74,7 @@ -(defn expense-accounts-field-v2 [{value :value on-change :on-change allowance :allowance expense-accounts :value client :client max-value :max locations :locations disabled :disabled percentage-only? :percentage-only? :or {percentage-only? false}}] +(defn expense-accounts-field-v2 [{value :value on-change :on-change allowance :allowance expense-accounts :value client :client max-value :max locations :locations disabled :disabled percentage-only? :percentage-only? :or {percentage-only? false} vendor-id :vendor-id}] [form-builder/virtual-builder {:value value :schema schema :on-change (fn [expense-accounts original-expense-accounts] @@ -130,7 +130,8 @@ [:search_account {:query i :allowance allowance - :client-id (:id client)} + :client-id (:id client) + :vendor-id vendor-id} [:name :id :location :warning]]) :disabled disabled}]]]] [:div.column.is-narrow diff --git a/src/cljs/auto_ap/views/pages/invoices/form.cljs b/src/cljs/auto_ap/views/pages/invoices/form.cljs index ef05a4b6..9db65213 100644 --- a/src/cljs/auto_ap/views/pages/invoices/form.cljs +++ b/src/cljs/auto_ap/views/pages/invoices/form.cljs @@ -415,6 +415,7 @@ [form-builder/field-v2 {:field :expense-accounts} "Expense Accounts" [expense-accounts-field-v2 {:descriptor "expense account" + :vendor-id (:id (:vendor data)) :allowance :invoice :locations (:locations (:client data)) :max (:total data) diff --git a/test/clj/auto_ap/integration/graphql/accounts.clj b/test/clj/auto_ap/integration/graphql/accounts.clj new file mode 100644 index 00000000..f567b654 --- /dev/null +++ b/test/clj/auto_ap/integration/graphql/accounts.clj @@ -0,0 +1,167 @@ +(ns auto-ap.integration.graphql.accounts + (:require + [auto-ap.datomic :refer [conn uri]] + [auto-ap.datomic.migrate :as m] + [auto-ap.graphql.accounts :as sut] + [clj-time.core :as time] + [clojure.test :as t :refer [deftest is testing use-fixtures]] + [datomic.api :as d])) + +(defn wrap-setup + [f] + (with-redefs [auto-ap.datomic/uri "datomic:mem://datomic-transactor:4334/invoice"] + (d/create-database uri) + (with-redefs [auto-ap.datomic/conn (d/connect uri)] + (m/migrate conn) + @(d/transact conn (->> (d/q '[:find ?a + :where [?a :account/name]] + (d/db conn)) + (map (fn [[a]] + {:db/id a + :account/applicability :account-applicability/global})))) + (f) + (d/release conn) + (d/delete-database uri)))) + +#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} +(defn admin-token [] + {:user "TEST ADMIN" + :exp (time/plus (time/now) (time/days 1)) + :user/role "admin" + :user/name "TEST ADMIN"}) + +#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} +(defn user-token [client-id] + {:user "TEST USER" + :exp (time/plus (time/now) (time/days 1)) + :user/role "user" + :user/name "TEST USER" + :user/clients [{:db/id client-id}]}) + + +(use-fixtures :each wrap-setup) + +(deftest test-account-search + (testing "It should find matching account names" + (is (> (count (sut/search {:id (admin-token)} + {:query "Food Research"} + nil + )) + 0))) + (testing "It should find exact matches by numbers" + (is (= (count (sut/search {:id (admin-token)} + {:query "5110"} + nil + )) + 1))) + (testing "It should filter out accounts that are not allowed for clients" + @(d/transact conn [{:account/name "CLIENT SPECIFIC" + :db/ident :client-specific-account + :account/numeric-code 99999 + :account/search-terms "CLIENTSPECIFIC" + :account/applicability :account-applicability/customized + :account/default-allowance :allowance/allowed}]) + (is (= 0 (count (sut/search {:id (admin-token)} + {:query "CLIENTSPECIFIC"} + nil + )))) + + (testing "It should show up for the client specific version" + (let [client-id (-> @(d/transact conn [{:client/name "CLIENT" + :db/id "client"} + {:db/ident :client-specific-account + :account/client-overrides [{:account-client-override/client "client" + :account-client-override/name "HI" + :account-client-override/search-terms "HELLOWORLD"}]}]) + :tempids + (get "client"))] + (is (= 1 (count (sut/search {:id (admin-token)} + {:query "HELLOWORLD" + :client_id client-id} + nil)))))) + + (testing "It should hide accounts that arent applicable" + @(d/transact conn [{:account/name "DENIED" + :db/ident :denied-account + :account/numeric-code 99998 + :account/search-terms "DENIED" + :account/applicability :account-applicability/global + :account/default-allowance :allowance/denied + :account/vendor-allowance :allowance/denied + :account/invoice-allowance :allowance/denied}]) + (is (= 0 (count (sut/search {:id (admin-token)} + {:query "DENIED"} + nil)))) + (is (= 0 (count (sut/search {:id (admin-token)} + {:query "DENIED" + :allowance :invoice} + nil)))) + (is (= 0 (count (sut/search {:id (admin-token)} + {:query "DENIED" + :allowance :vendor} + nil))))) + + (testing "It should warn when using a warn account" + @(d/transact conn [{:account/name "WARNING" + :db/ident :warn-account + :account/numeric-code 99997 + :account/search-terms "WARNING" + :account/applicability :account-applicability/global + :account/default-allowance :allowance/warn + :account/vendor-allowance :allowance/warn + :account/invoice-allowance :allowance/warn}]) + (is (some? (:warning (first (sut/search {:id (admin-token)} + {:query "WARNING" + :allowance :global} + nil))))) + (is (some? (:warning (first (sut/search {:id (admin-token)} + {:query "WARNING" + :allowance :invoice} + nil))))) + (is (some? (:warning (first (sut/search {:id (admin-token)} + {:query "WARNING" + :allowance :vendor} + nil)))))) + (testing "It should only include admin accounts for admins" + @(d/transact conn [{:account/name "ADMINONLY" + :db/ident :warn-account + :account/numeric-code 99997 + :account/search-terms "ADMINONLY" + :account/applicability :account-applicability/global + :account/default-allowance :allowance/admin-only + :account/vendor-allowance :allowance/admin-only + :account/invoice-allowance :allowance/admin-only}]) + (is (= 1 (count (sut/search {:id (admin-token)} + {:query "ADMINONLY"} + nil)))) + (is (= 0 (count (sut/search {:id (user-token 1)} + {:query "ADMINONLY"} + nil))))) + + (testing "It should allow searching for vendor accounts for invoices" + (let [vendor-id (-> @(d/transact conn [{:account/name "VENDORONLY" + :db/id "vendor-only" + :db/ident :vendor-only + :account/numeric-code 99996 + :account/search-terms "VENDORONLY" + :account/applicability :account-applicability/global + :account/default-allowance :allowance/allowed + :account/vendor-allowance :allowance/allowed + :account/invoice-allowance :allowance/denied} + {:vendor/name "Allowed" + :vendor/default-account "vendor-only" + :db/id "vendor"}]) + :tempids + (get "vendor"))] + (is (= 0 (count (sut/search {:id (admin-token)} + {:query "VENDORONLY" + :allowance :invoice} + nil)))) + + (is (= 1 (count (sut/search {:id (admin-token)} + {:query "VENDORONLY" + :allowance :invoice + :vendor_id vendor-id} + nil)))))))) + +