Files
integreat/src/clj/auto_ap/graphql.clj
2018-07-25 19:35:26 -07:00

549 lines
20 KiB
Clojure

(ns auto-ap.graphql
(:require
[com.walmartlabs.lacinia.util :refer [attach-resolvers]]
[com.walmartlabs.lacinia.schema :as schema]
[com.walmartlabs.lacinia :refer [execute]]
[com.walmartlabs.lacinia.executor :as executor]
[com.walmartlabs.lacinia.resolve :as resolve]
[buddy.auth :refer [throw-unauthorized]]
[auto-ap.db.invoices :as invoices]
[auto-ap.utils :refer [by]]
[auto-ap.graphql.utils :refer [assert-admin can-see-company? assert-can-see-company]]
[auto-ap.db.vendors :as vendors]
[auto-ap.db.companies :as companies]
[auto-ap.db.users :as users]
[auto-ap.db.checks :as checks]
[auto-ap.routes.checks :as rchecks]
[auto-ap.graphql.users :as gq-users]
[auto-ap.graphql.checks :as gq-checks]
[auto-ap.graphql.expense-accounts :as expense-accounts]
[auto-ap.graphql.invoices :as gq-invoices]
[auto-ap.graphql.transactions :as gq-transactions]
[auto-ap.db.reminders :as reminders]
[auto-ap.db.invoices-checks :as invoices-checks]
[auto-ap.db.utils :as utils]
[clojure.walk :as walk]
[clojure.string :as str])
(:import
(clojure.lang IPersistentMap)))
(def integreat-schema
{
:objects
{
:company
{:fields {:id {:type 'Int}
:name {:type 'String}
:email {:type 'String}
:locations {:type '(list String)}
:bank_accounts {:type '(list :bank_account)}}}
:bank_account
{:fields {:id {:type 'Int}
:number {:type 'String}
:check_number {:type 'Int}
:name {:type 'String}
:bank_code {:type 'String}
:bank_name {:type 'String}}}
:address
{:fields {:street1 {:type 'String}
:city {:type 'String}
:state {:type 'String}
:zip {:type 'String}}}
:vendor
{:fields {:id {:type 'Int}
:name {:type 'String}
:code {:type 'String}
:print_as {:type 'String}
:primary_contact {:type 'String}
:address {:type :address}
:primary_email {:type 'String}
:primary_phone {:type 'String}
:secondary_contact {:type 'String}
:secondary_email {:type 'String}
:secondary_phone {:type 'String}
:default_expense_account {:type 'Int}
:invoice_reminder_schedule {:type 'String}}}
:reminder
{:fields {:id {:type 'Int}
:email {:type 'String}
:subject {:type 'String}
:body {:type 'String}
:scheduled {:type 'String}
:sent {:type 'String}
:vendor {:type :vendor
:resolve :get-vendor-for-invoice}
}
}
:check {:fields {:id {:type 'Int}
:amount {:type 'String}
:vendor {:type :vendor
:resolve :get-vendor-for-check}
:company {:type :company
:resolve :get-company-for-check}
:date {:type 'String}
:bank_account {:type :bank_account
:resolve :bank-account-for-check}
:memo {:type 'String}
:s3_url {:type 'String}
:check_number {:type 'Int}
:status {:type 'String}
:invoices {:type '(list :invoice_check)
:resolve :get-checks-invoices}
}}
:transaction {:fields {:id {:type 'String}
:amount {:type 'String}
:description_original {:type 'String}
:description_simple {:type 'String}
:status {:type 'String}
:company {:type :company
:resolve :get-company-for-transaction}
:check {:type :check
:resolve :get-check-for-transaction}
:date {:type 'String}
:post_date {:type 'String}}}
:invoice_check
{:fields {:id {:type 'Int}
:amount {:type 'String}
:invoice_id {:type 'Int}
:check_id {:type 'Int}
:check {:type :check
:resolve :get-check-by-id}}}
:user
{:fields {:id {:type 'Int}
:name {:type 'String}
:role {:type 'String}
:companies {:type '(list :company)
:resolve :get-user-companies}}}
:expense_account {:fields {:id {:type 'Int}
:name {:type 'String}
:parent {:type :expense_account
:resolve :get-expense-account-parent}}}
:invoices_expense_accounts
{:fields {:id {:type 'Int}
:invoice_id {:type 'Int}
:expense_account_id {:type 'Int}
:location {:type 'String}
:expense_account {:type :expense_account
:resolve :get-expense-account}
:amount {:type 'String}}}
:invoice
{:fields {:id {:type 'Int}
:total {:type 'String}
:outstanding_balance {:type 'String}
:invoice_number {:type 'String}
:expense_accounts {:type '(list :invoices_expense_accounts)
:resolve :get-invoices-expense-accounts}
:date {:type 'String}
:company_id {:type 'Int}
:checks {:type '(list :invoice_check)
:resolve :get-invoices-checks}
:vendor {:type :vendor
:resolve :get-vendor-for-invoice}
:company {:type :company
:resolve :get-company-for-invoice}}}
:invoice_page {:fields {:invoices {:type '(list :invoice)}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
:check_page {:fields {:checks {:type '(list :check)}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
:transaction_page {:fields {:transactions {:type '(list :transaction)}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
:reminder_page {:fields {:reminders {:type '(list :reminder)}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
:check_result {:fields {:invoices {:type '(list :invoice)}
:pdf_url {:type 'String}}}}
:queries
{:invoice_page {:type '(list :invoice_page)
:args {:imported {:type 'Boolean}
:status {:type 'String}
:company_id {:type 'Int}
:start {:type 'Int}
:sort_by {:type 'String}
:asc {:type 'Boolean}}
:resolve :get-invoice-page}
:all_invoices {:type '(list :invoice)
:args {:company_id {:type 'Int}}
:resolve :get-all-invoices}
:all_checks {:type '(list :check)
:args {:company_id {:type 'Int}}
:resolve :get-all-checks}
:transaction_page {:type '(list :transaction_page)
:args {:company_id {:type 'Int}
:start {:type 'Int}
:sort_by {:type 'String}
:asc {:type 'Boolean}}
:resolve :get-transaction-page}
:check_page {:type '(list :check_page)
:args {:company_id {:type 'Int}
:start {:type 'Int}
:sort_by {:type 'String}
:asc {:type 'Boolean}}
:resolve :get-check-page}
:reminder_page {:type '(list :reminder_page)
:args {:start {:type 'Int}
:sort_by {:type 'String}
:asc {:type 'Boolean}}
:resolve :get-reminder-page}
:company {:type '(list :company)
:resolve :get-company}
:vendor {:type '(list :vendor)
:resolve :get-vendor}
:user {:type '(list :user)
:resolve :get-user}}
:input-objects
{
:invoice_payment {:fields {:invoice_id {:type 'Int}
:amount {:type 'Float}}}
:edit_user
{:fields {:id {:type 'Int}
:name {:type 'String}
:role {:type 'String}
:companies {:type '(list Int)}}}
:edit_expense_account
{:fields {:id {:type 'Int}
:expense_account_id {:type 'Int}
:location {:type 'String}
:amount {:type 'String}}}
:add_invoice
{:fields {:id {:type 'Int}
:invoice_number {:type 'String}
:location {:type 'String}
:date {:type 'String}
:company_id {:type 'Int}
:vendor_id {:type 'Int}
:vendor_name {:type 'String}
:total {:type 'Float}}}
:edit_invoice
{:fields {:id {:type 'Int}
:invoice_number {:type 'String}
:date {:type 'String}
:total {:type 'Float}}}}
:mutations
{:print_checks {:type :check_result
:args {:invoice_payments {:type '(list :invoice_payment)}
:bank_account_id {:type 'Int}
:company_id {:type 'Int}}
:resolve :mutation/print-checks}
:add_handwritten_check {:type :check_result
:args {:invoice_id {:type 'Int}
:amount {:type 'Float}
:date {:type 'String}
:check_number {:type 'Int}
:bank_account_id {:type 'Int}}
:resolve :mutation/add-handwritten-check}
:edit_user {:type :user
:args {:edit_user {:type :edit_user}}
:resolve :mutation/edit-user}
:add_invoice {:type :invoice
:args {:invoice {:type :add_invoice}}
:resolve :mutation/add-invoice}
:edit_invoice {:type :invoice
:args {:invoice {:type :edit_invoice}}
:resolve :mutation/edit-invoice}
:void_invoice {:type :invoice
:args {:invoice_id {:type 'Int}}
:resolve :mutation/void-invoice}
:void_check {:type :check
:args {:check_id {:type 'Int}}
:resolve :mutation/void-check}
:edit_expense_accounts {:type :invoice
:args {:invoice_id {:type 'Int}
:expense_accounts {:type '(list :edit_expense_account)}}
:resolve :mutation/edit-expense-accounts}}})
(defn snake->kebab [s]
(str/replace s #"_" "-"))
(defn kebab [x]
(keyword (snake->kebab (name x))))
(defn kebab->snake [s]
(str/replace s #"-" "_"))
(defn snake [x]
(keyword (kebab->snake (name x))))
(defn ->graphql [m]
(walk/postwalk
(fn [node]
(cond
(keyword? node)
(snake node)
:else
node))
m))
(defn <-graphql [m]
(walk/postwalk
(fn [node]
(cond
(keyword? node)
(kebab node)
:else
node))
m))
(defn get-invoice-page [context args value]
(let [args (assoc args :id (:id context))
extra-context
(cond-> {}
(executor/selects-field? context :invoice/vendor) (assoc :vendor-cache (by :id (vendors/get-all)))
(executor/selects-field? context :invoice/company) (assoc :company-cache (by :id (companies/get-all))))
invoices (map
->graphql
(invoices/get-graphql (<-graphql (assoc args :id (:id context)))))
invoice-count (invoices/count-graphql (<-graphql args))]
(resolve/with-context
[{:invoices invoices
:total invoice-count
:count (count invoices)
:start (:start args 0)
:end (+ (:start args 0) (count invoices))}] extra-context)))
(defn get-all-invoices [context args value]
(assert-admin (:id context))
(let [extra-context
(cond-> {}
(executor/selects-field? context :invoice/vendor) (assoc :vendor-cache (by :id (vendors/get-all)))
(executor/selects-field? context :invoice/company) (assoc :company-cache (by :id (companies/get-all))))
invoices (map
->graphql
(invoices/get-graphql (assoc (<-graphql args)
:limit Integer/MAX_VALUE)))]
(resolve/with-context
invoices extra-context)))
(defn get-all-checks [context args value]
(assert-admin (:id context))
(let [extra-context
(cond-> {}
(executor/selects-field? context :invoice/vendor) (assoc :vendor-cache (by :id (vendors/get-all)))
(or (executor/selects-field? context :check/company)
(executor/selects-field? context :check/bank_account)) (assoc :company-cache (by :id (companies/get-all))))
checks (map
->graphql
(checks/get-graphql (assoc (<-graphql args)
:limit Integer/MAX_VALUE)))]
(resolve/with-context
checks extra-context)))
(defn get-reminder-page [context args value]
(assert-admin (:id context))
(let [extra-context
(cond-> {}
(executor/selects-field? context :reminder/vendor) (assoc :vendor-cache (by :id (vendors/get-all))))
reminders (map
->graphql
(reminders/get-graphql (<-graphql args)))
reminder-count (reminders/count-graphql (<-graphql args))]
(resolve/with-context
[{:reminders reminders
:total reminder-count
:count (count reminders)
:start (:start args 0)
:end (+ (:start args 0) (count reminders))}] extra-context)))
(defn get-vendor-for-invoice [context args value]
(->graphql
(if-let [vendor-cache (:vendor-cache context)]
(vendor-cache (:vendor_id value))
(vendors/get-by-id (:vendor_id value)))))
(defn get-check-by-id [context args value]
(->graphql
(checks/get-by-id (:check_id value))))
(defn get-invoices-checks [context args value]
(->graphql
(invoices-checks/get-for-invoice-id (:id value))))
(defn get-checks-invoices [context args value]
(->graphql
(invoices-checks/get-for-check-id (:id value))))
(defn get-company-for-invoice [context args value]
(->graphql
(if-let [company-cache (:company-cache context)]
(company-cache (:company_id value))
(companies/get-by-id (:company_id value)))))
(defn bank-account-for-check [context args value]
(->graphql
(let [company (if-let [company-cache (:company-cache context)]
(company-cache (:company_id value))
(companies/get-by-id (:company_id value)))]
(first (filter #(= (:id %) (:bank_account_id value)) (:bank-accounts company))) )))
(defn get-user-companies [context args value]
(->graphql
(if-let [company-cache (:company-cache context)]
(map company-cache (:companies value))
(map companies/get-by-id (:companies value)))))
(defn get-company [context args value]
(->graphql
(filter #(can-see-company? (:id context) %)
(companies/get-all))))
(defn join-companies [users]
(let [companies (by :id (companies/get-all))]
(map
(fn [u]
(update u :companies #(map companies %)))
users)))
(defn get-user [context args value]
(assert-admin (:id context))
(let [
users (users/get-all)
extra-context (cond-> context
(executor/selects-field? context :user/companies) (assoc :company-cache (by :id (companies/get-all))))]
(resolve/with-context
(->graphql users) extra-context)))
(defn get-vendor [context args value]
(->graphql
(vendors/get-all)))
(defn print-checks [context args value]
(assert-can-see-company (:id context) (:company_id args))
(->graphql
(rchecks/print-checks (map (fn [i] {:invoice-id (:invoice_id i)
:amount (:amount i)})
(:invoice_payments args))
(:company_id args)
(:bank_account_id args))))
(def schema
(-> integreat-schema
(attach-resolvers {:get-invoice-page get-invoice-page
:get-all-invoices get-all-invoices
:get-all-checks get-all-checks
:bank-account-for-check bank-account-for-check
:get-check-page gq-checks/get-check-page
:get-transaction-page gq-transactions/get-transaction-page
:get-reminder-page get-reminder-page
:get-vendor-for-invoice get-vendor-for-invoice
:get-vendor-for-check gq-checks/get-vendor-for-check
:get-company-for-check gq-checks/get-company-for-check
:get-company-for-transaction gq-transactions/get-company-for-transaction
:get-check-for-transaction gq-transactions/get-check-for-transaction
:get-company-for-invoice get-company-for-invoice
:get-invoices-checks get-invoices-checks
:get-checks-invoices get-checks-invoices
:get-check-by-id get-check-by-id
:get-invoices-expense-accounts gq-invoices/get-invoices-expense-accounts
:get-company get-company
:get-user get-user
:get-user-companies get-user-companies
:mutation/add-handwritten-check gq-checks/add-handwritten-check
:mutation/print-checks print-checks
:mutation/edit-user gq-users/edit-user
:mutation/add-invoice gq-invoices/add-invoice
:mutation/edit-invoice gq-invoices/edit-invoice
:mutation/void-invoice gq-invoices/void-invoice
:mutation/void-check gq-checks/void-check
:mutation/edit-expense-accounts gq-invoices/edit-expense-accounts
:get-vendor get-vendor
:get-expense-account expense-accounts/get-expense-account
:get-expense-account-parent expense-accounts/get-parent})
schema/compile))
(defn simplify
"Converts all ordered maps nested within the map into standard hash maps, and
sequences into vectors, which makes for easier constants in the tests, and eliminates ordering problems."
[m]
(walk/postwalk
(fn [node]
(cond
(instance? IPersistentMap node)
(into {} node)
(seq? node)
(vec node)
(keyword? node)
(kebab node)
:else
node))
m))
(defn query
([id q]
(query id q nil ))
([id q v]
(println "executing graphql query" id q v)
(simplify (execute schema q v {:id id}))))