(ns auto-ap.graphql.vendors (:require [auto-ap.datomic :refer [audit-transact conn remove-nils]] [auto-ap.datomic.vendors :as d-vendors] [auto-ap.graphql.utils :refer [->graphql <-graphql assert-admin assert-failure cleanse-query enum->keyword is-admin? result->page]] [clojure.set :as set] [clojure.string :as str] [clojure.tools.logging :as log] [datomic.api :as d])) (defn can-user-edit-vendor? [vendor-id id] (if (is-admin? id) true (empty? (set/difference (set (d/q '[:find [?c ...] :in $ ?v :where [?vu :vendor-usage/vendor ?v] [?vu :vendor-usage/client ?c] [?vu :vendor-usage/count ?d] [(>= ?d 0)]] (d/db conn) vendor-id)) (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} _] (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.")) (when (->> schedule_payment_dom (group-by :client_id) vals (filter #(> (count %) 1)) seq) (assert-failure "Only one schedule payment override allowed per client.")) (when (->> terms_overrides (group-by :client_id) vals (filter #(> (count %) 1)) seq) (assert-failure "Only one terms override allowed per client.")) (when (->> account_overrides (group-by :client_id) vals (filter #(> (count %) 1)) seq) (assert-failure "Only one account override allowed per client.")) (let [ hidden (if (is-admin? (:id context)) hidden false) existing (when id (d/pull (d/db conn) '[:vendor/name] id)) terms-overrides (mapv (fn [to] (cond-> #:vendor-terms-override {:client (:client_id to) :terms (:terms to)} (:id to) (assoc :db/id (:id to)))) terms_overrides) account-overrides (mapv (fn [ao] (cond-> #:vendor-account-override {:client (:client_id ao) :account (:account_id ao)} (:id ao) (assoc :db/id (:id ao)))) account_overrides) schedule-payment-dom (mapv (fn [ao] (cond-> #:vendor-schedule-payment-dom {:client (:client_id ao) :dom (:dom ao)} (:id ao) (assoc :db/id (:id ao)))) schedule_payment_dom) transaction (cond-> [(remove-nils (cond-> #:vendor {:db/id (if id id "vendor") :name name :code code :hidden hidden :terms terms :print-as print_as :default-account default_account_id :invoice-reminder-schedule (keyword invoice_reminder_schedule) :address (when address (remove-nils #:address {:db/id (if (:id address) (:id address) "address") :street1 (:street1 address) :street2 (:street2 address) :city (:city address) :state (:state address) :zip (:zip address)})) :primary-contact (when primary_contact (remove-nils #:contact {:db/id (if (:id primary_contact) (:id primary_contact) "primary") :name (:name primary_contact) :phone (:phone primary_contact) :email (:email primary_contact)})) :secondary-contact (when secondary_contact (remove-nils #:contact {:db/id (if (:id secondary_contact) (:id secondary_contact) "secondary") :name (:name secondary_contact) :phone (:phone secondary_contact) :email (:email secondary_contact)}) )} (is-admin? (:id context)) (assoc :vendor/legal-entity-name (:legal_entity_name in) :vendor/legal-entity-first-name (:legal_entity_first_name in) :vendor/legal-entity-middle-name (:legal_entity_middle_name in) :vendor/legal-entity-last-name (:legal_entity_last_name in) :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"))))] (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/account-overrides account-overrides]) (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/terms-overrides terms-overrides]) (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/schedule-payment-dom schedule-payment-dom]) (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/automatically-paid-when-due (mapv (fn [apwd] {:db/id apwd}) (:automatically_paid_when_due in))]) (not= (:vendor/name existing) name) (conj [:reset (if id id "vendor") :vendor/search-terms [name]])) _ (log/info "Upserting vendor" transaction) transaction-result (audit-transact transaction (:id context))] (-> (d-vendors/get-by-id (or (-> transaction-result :tempids (get "vendor")) id)) (->graphql)))) (defn merge-vendors [context {:keys [from to]} _] (let [transaction (->> (d/query {:query {:find '[?x ?a2] :in '[$ ?vendor-from ] :where ['[?x ?a ?vendor-from] '[?a :db/ident ?a2]]} :args [(d/db conn) from]}) (mapcat (fn [[src attr]] [[:db/retract src attr from] [:db/add src attr to]]))) transaction (conj transaction [:db/retractEntity from])] (audit-transact transaction (:id context)) to)) (defn get-graphql [context args _] (assert-admin (:id context)) (let [args (assoc args :id (:id context)) [vendors vendors-count ] (d-vendors/get-graphql (<-graphql args))] (result->page vendors vendors-count :vendors args))) (defn get-by-id [context args _] (->graphql (d-vendors/get-graphql-by-id (assoc args :id (:id context)) (:id args)))) (defn partial-match-first [query matches] (if-let [best-match (->> matches (filter (fn [[n]] (str/starts-with? (str/lower-case n) (str/lower-case query)))) first)] (cons best-match (filter (complement #{best-match}) matches)) matches)) (defn search [context args _] (if-let [search-query (cleanse-query (:query args))] (let [data (if (is-admin? (:id context)) (d/q '[:find ?n ?i ?s :in $ ?q :where [(fulltext $ :vendor/search-terms ?q) [[?i ?n _ ?s]]]] (d/db conn) search-query) (d/q '[:find ?n ?i ?s :in $ ?q :where [(fulltext $ :vendor/search-terms ?q) [[?i ?n _ ?s]]] (not [?i :vendor/hidden true])] (d/db conn) search-query))] (->> data (sort-by (comp - last)) (partial-match-first (:query args)) (map (fn [[n i]] {:name n :id i})))) []))