549 lines
20 KiB
Clojure
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}))))
|