645 lines
32 KiB
Clojure
645 lines
32 KiB
Clojure
(ns auto-ap.graphql.invoices
|
|
(:require
|
|
[auto-ap.datomic
|
|
:refer [conn pull-attr pull-many pull-ref random-tempid audit-transact audit-transact-batch]]
|
|
[auto-ap.datomic.clients :as d-clients]
|
|
[auto-ap.datomic.invoices :as d-invoices]
|
|
[auto-ap.datomic.vendors :as d-vendors]
|
|
[auto-ap.graphql.checks :as gq-checks]
|
|
[auto-ap.graphql.utils
|
|
:as u
|
|
:refer [<-graphql
|
|
assert-admin
|
|
assert-can-see-client
|
|
assert-failure
|
|
assert-not-locked
|
|
assert-power-user
|
|
attach-tracing-resolvers
|
|
enum->keyword]]
|
|
[auto-ap.rule-matching :as rm]
|
|
[auto-ap.utils :refer [dollars=]]
|
|
[clj-time.coerce :as coerce]
|
|
[clj-time.core :as time]
|
|
[clojure.tools.logging :as log]
|
|
[com.brunobonacci.mulog :as mu]
|
|
[datomic.api :as dc]
|
|
[auto-ap.solr :as solr]))
|
|
|
|
(defn ->graphql [invoice user ]
|
|
(if (= "admin" (:user/role user))
|
|
(u/->graphql invoice)
|
|
(u/->graphql (if (:invoice/source-url-admin-only invoice)
|
|
(dissoc invoice :invoice/source-url)
|
|
invoice))))
|
|
|
|
(defn get-invoice-page [context args _]
|
|
|
|
(let [args (assoc args :id (:id context))
|
|
[invoices invoice-count outstanding total-amount] (-> args
|
|
:filters
|
|
(assoc :id (:id context))
|
|
(<-graphql)
|
|
(update :status enum->keyword "invoice-status")
|
|
(d-invoices/get-graphql))]
|
|
[{:invoices (mapv #(->graphql % (:id context)) invoices)
|
|
:outstanding outstanding
|
|
:total_amount total-amount
|
|
:total invoice-count
|
|
:count (count invoices)
|
|
:start (-> args :filters (:start 0))
|
|
:end (+ (-> args :filters (:start 0)) (count invoices))}]))
|
|
|
|
(defn get-all-invoices [context args _]
|
|
(assert-admin (:id context))
|
|
(map
|
|
u/->graphql
|
|
(first (d-invoices/get-graphql (assoc (<-graphql args)
|
|
:count Integer/MAX_VALUE)))))
|
|
|
|
(defn reject-invoices [context {:keys [invoices]} _]
|
|
(assert-power-user (:id context))
|
|
(doseq [i invoices]
|
|
(assert-can-see-client (:id context) (:db/id (:invoice/client (dc/pull (dc/db conn) [{:invoice/client [:db/id]}] i)))))
|
|
(let [transactions (mapcat (fn [i] [[:db/retractEntity i]])
|
|
invoices)]
|
|
(audit-transact transactions (:id context))
|
|
invoices))
|
|
|
|
(defn approve-invoices [context {:keys [invoices]} _]
|
|
(assert-power-user (:id context))
|
|
(doseq [i invoices
|
|
:let [invoice (dc/pull (dc/db conn) [{:invoice/client [:db/id]}
|
|
:invoice/date]
|
|
i)]]
|
|
(assert-can-see-client (:id context) (-> invoice :invoice/client :db/id))
|
|
(assert-not-locked (-> invoice :invoice/client :db/id) (-> invoice :invoice/date)))
|
|
(let [transactions (map (fn [i] [:upsert-invoice {:db/id i :invoice/import-status :import-status/imported}]) invoices)]
|
|
(audit-transact transactions (:id context))
|
|
invoices))
|
|
|
|
(defn assert-no-conflicting [{:keys [invoice_number client_id vendor_id]}]
|
|
(when-not vendor_id
|
|
(assert-failure "Please specify a vendor."))
|
|
(when-not invoice_number
|
|
(assert-failure "Please specify an invoice."))
|
|
(when (seq (d-invoices/find-conflicting {:invoice/invoice-number invoice_number
|
|
:invoice/vendor vendor_id
|
|
:invoice/client client_id}))
|
|
(throw (ex-info (str "Invoice '" invoice_number "' already exists.") {:invoice-number invoice_number :validation-error (str "Invoice '" invoice_number "' already exists.")}))))
|
|
|
|
(defn expense-account->entity [{:keys [id account_id amount location]}]
|
|
#:invoice-expense-account {:amount amount
|
|
:db/id (or id (random-tempid))
|
|
:account account_id
|
|
:location location})
|
|
|
|
(defn add-invoice-transaction [{:keys [total invoice_number scheduled_payment client_id vendor_id date due expense_accounts]}]
|
|
(let [vendor (d-vendors/get-by-id vendor_id)
|
|
account (:vendor/default-account vendor)
|
|
due (or (and (:vendor/terms vendor)
|
|
(coerce/to-date
|
|
(time/plus date (time/days (d-vendors/terms-for-client-id vendor client_id)))))
|
|
due
|
|
nil)
|
|
scheduled_payment (or scheduled_payment
|
|
(and (d-vendors/automatically-paid-for-client-id? vendor client_id)
|
|
due)
|
|
nil)
|
|
_ (when-not (:db/id account)
|
|
(throw (ex-info (str "Vendor '" (:vendor/name vendor) "' does not have a default expense acount.") {:vendor-id vendor_id})))]
|
|
|
|
[:upsert-invoice {:db/id "invoice"
|
|
:invoice/invoice-number invoice_number
|
|
:invoice/client client_id
|
|
:invoice/vendor vendor_id
|
|
:invoice/import-status :import-status/imported
|
|
:invoice/total total
|
|
:invoice/outstanding-balance total
|
|
:invoice/status :invoice-status/unpaid
|
|
:invoice/date (coerce/to-date date)
|
|
:invoice/expense-accounts (map expense-account->entity
|
|
expense_accounts)
|
|
:invoice/due (coerce/to-date due)
|
|
:invoice/scheduled-payment (coerce/to-date scheduled_payment)}]))
|
|
|
|
(defn assert-valid-expense-accounts [expense_accounts vendor_id]
|
|
(doseq [expense-account expense_accounts
|
|
:let [account (dc/pull (dc/db conn)
|
|
[:db/id
|
|
:account/location
|
|
{:account/invoice-allowance [:db/ident]}]
|
|
(:account_id expense-account))]]
|
|
(when (empty? (:location expense-account))
|
|
(throw (ex-info "Expense account is missing location" {:validation-error "Expense account is missing location"})))
|
|
|
|
(when (and (seq (:account/location account))
|
|
(not= (:location expense-account)
|
|
(:account/location account)))
|
|
(let [err (str "Account uses location '" (:location expense-account) "' but expects '" (:account/location account) "'")]
|
|
(throw (ex-info err
|
|
{:validation-error err}))))
|
|
|
|
(when (and (= :allowance/denied
|
|
(:db/ident (:account/invoice-allowance account)))
|
|
(not= (pull-ref (dc/db conn) :vendor/default-account vendor_id )
|
|
(:db/id account)))
|
|
(let [err (str "Account isn't allowed for invoice use.")]
|
|
(throw (ex-info err
|
|
{:validation-error err}))))
|
|
|
|
(when (and (empty? (:account/location account))
|
|
(= "A" (:location expense-account)))
|
|
(let [err (str "Account uses location '" (:location expense-account) "', which is reserved for liabilities, equities, and assets.")]
|
|
(throw (ex-info err
|
|
{:validation-error err}))))
|
|
|
|
(when (nil? (:account_id expense-account))
|
|
(throw (ex-info "Expense account is missing account" {:validation-error "Expense account is missing account"})))
|
|
|
|
(when (nil? (:amount expense-account))
|
|
(throw (ex-info "Expense account does not have an amount specified." {:validation-error "Expense account does not have an amount specified."})))))
|
|
|
|
(defn assert-invoice-amounts-add-up [{:keys [expense_accounts total]}]
|
|
(let [expense-account-total (reduce + 0 (map (fn [x] (:amount x)) expense_accounts))]
|
|
(when-not (dollars= total expense-account-total)
|
|
(let [error (str "Expense account total (" expense-account-total ") does not equal invoice total (" total ")")]
|
|
(throw (ex-info error {:validation-error error}))))))
|
|
|
|
|
|
|
|
(defn add-invoice [context {{:keys [expense_accounts client_id vendor_id] :as in} :invoice} _]
|
|
(assert-no-conflicting in)
|
|
(assert-can-see-client (:id context) client_id)
|
|
|
|
(assert-not-locked client_id (:date in))
|
|
(assert-valid-expense-accounts expense_accounts vendor_id)
|
|
(assert-invoice-amounts-add-up in)
|
|
|
|
(let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))]
|
|
(solr/touch-with-ledger (get-in transaction-result [:tempids "invoice"]))
|
|
(-> (d-invoices/get-by-id (get-in transaction-result [:tempids "invoice"]))
|
|
(->graphql (:id context)))))
|
|
|
|
(defn assert-bank-account-belongs [client-id bank-account-id]
|
|
(when-not ((set (map :db/id (:client/bank-accounts (d-clients/get-by-id client-id)))) bank-account-id)
|
|
(throw (ex-info (str "Bank account does not belong to client") {:validation-error "Bank account does not belong to client."}))))
|
|
|
|
|
|
|
|
(defn add-and-print-invoice [context {{:keys [total client_id vendor_id] :as in} :invoice bank-account-id :bank_account_id type :type} _]
|
|
(mu/trace ::validating-invoice [:invoice in]
|
|
(do
|
|
(assert-no-conflicting in)
|
|
(assert-can-see-client (:id context) client_id)
|
|
(assert-bank-account-belongs client_id bank-account-id)
|
|
(assert-not-locked client_id (:date in))
|
|
(assert-valid-expense-accounts (:expense_accounts in) vendor_id)
|
|
(assert-invoice-amounts-add-up in)))
|
|
(let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))]
|
|
(mu/trace ::printing-checks
|
|
[]
|
|
(-> (gq-checks/print-checks-internal [{:invoice-id (get-in transaction-result [:tempids "invoice"])
|
|
:amount total}]
|
|
client_id
|
|
bank-account-id
|
|
type
|
|
(:id context))
|
|
u/->graphql))))
|
|
|
|
(defn edit-invoice [context {{:keys [id due invoice_number vendor_id total date expense_accounts scheduled_payment] :as in} :invoice} _]
|
|
(let [invoice (d-invoices/get-by-id id)
|
|
_ (when (seq (d-invoices/find-conflicting {:db/id id
|
|
:invoice/invoice-number invoice_number
|
|
:invoice/vendor (:db/id (:invoice/vendor invoice))
|
|
:invoice/client (:db/id (:invoice/client invoice))}))
|
|
(throw (ex-info (str "Invoice '" invoice_number "' already exists.") {:invoice-number invoice_number})))
|
|
|
|
paid-amount (- (:invoice/total invoice) (:invoice/outstanding-balance invoice))
|
|
_ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
|
|
_ (assert-not-locked (:db/id (:invoice/client invoice)) (:date in))
|
|
_ (assert-valid-expense-accounts expense_accounts vendor_id)
|
|
_ (assert-invoice-amounts-add-up in)
|
|
|
|
updated-invoice {:db/id id
|
|
:invoice/invoice-number invoice_number
|
|
:invoice/date (coerce/to-date date)
|
|
|
|
:invoice/total total
|
|
:invoice/outstanding-balance (- total paid-amount)
|
|
:invoice/expense-accounts (map expense-account->entity
|
|
expense_accounts)
|
|
:invoice/due (coerce/to-date due)
|
|
:invoice/scheduled-payment (coerce/to-date scheduled_payment)}]
|
|
(audit-transact [[:upsert-invoice updated-invoice]]
|
|
(:id context))
|
|
(solr/touch-with-ledger id)
|
|
(-> (d-invoices/get-by-id id)
|
|
(->graphql (:id context)))))
|
|
|
|
(defn void-invoice [context {id :invoice_id} _]
|
|
(let [invoice (d-invoices/get-by-id id)]
|
|
(assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
|
|
(assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice))
|
|
(audit-transact [[:upsert-invoice {:db/id id
|
|
:invoice/total 0.0
|
|
:invoice/outstanding-balance 0.0
|
|
:invoice/status :invoice-status/voided
|
|
:invoice/expense-accounts (map (fn [ea] {:db/id (:db/id ea)
|
|
:invoice-expense-account/amount 0.0})
|
|
(:invoice/expense-accounts invoice))}]]
|
|
(:id context))
|
|
|
|
(-> (d-invoices/get-by-id id) (->graphql (:id context)))))
|
|
|
|
|
|
(defn get-ids-matching-filters [args]
|
|
(let [ids (some-> args
|
|
:filters
|
|
(assoc :id (:id args))
|
|
(<-graphql)
|
|
(update :status enum->keyword "invoice-status")
|
|
(assoc :per-page Integer/MAX_VALUE)
|
|
d-invoices/raw-graphql-ids
|
|
:ids)
|
|
specific-ids (d-invoices/filter-ids (:ids args))]
|
|
(into (set ids) specific-ids)))
|
|
|
|
(defn all-ids-not-locked [all-ids]
|
|
(->> all-ids
|
|
(dc/q '[:find ?i
|
|
:in $ [?i ...]
|
|
:where
|
|
[?i :invoice/client ?c]
|
|
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
|
|
[?i :invoice/date ?d]
|
|
[(>= ?d ?lu)]]
|
|
(dc/db conn))
|
|
(map first)))
|
|
|
|
(defn void-invoices [context args _]
|
|
(let [_ (assert-admin (:id context))
|
|
args (assoc args :id (:id context))
|
|
all-ids (all-ids-not-locked (get-ids-matching-filters args))
|
|
voidable-cash-payments (->> (dc/q '[:find ?p
|
|
:in $ [?i ...]
|
|
:where [?ip :invoice-payment/invoice ?i]
|
|
[?ip :invoice-payment/payment ?p]
|
|
[?p :payment/type :payment-type/cash]
|
|
[?i :invoice/client ?c]
|
|
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
|
|
[?i :invoice/date ?d]
|
|
[(>= ?d ?lu)]]
|
|
(dc/db conn)
|
|
all-ids)
|
|
(map first))
|
|
]
|
|
(log/info "Voiding " (count voidable-cash-payments) "cash payments first")
|
|
(gq-checks/void-payments-internal voidable-cash-payments (:id context))
|
|
|
|
(log/info "Voiding " (count all-ids) args)
|
|
(audit-transact
|
|
(->> all-ids
|
|
(dc/q '[:find (pull ?i [:db/id :invoice/date {:invoice/expense-accounts [:db/id]}])
|
|
:in $ [?i ...]
|
|
:where (not [_ :invoice-payment/invoice ?i])
|
|
[?i :invoice/client ?c]
|
|
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
|
|
[?i :invoice/date ?d]
|
|
[(>= ?d ?lu)]]
|
|
(dc/db conn))
|
|
(map
|
|
(fn [[i]]
|
|
[:upsert-invoice {:db/id (:db/id i)
|
|
:invoice/total 0.0
|
|
:invoice/outstanding-balance 0.0
|
|
:invoice/status :invoice-status/voided
|
|
:invoice/expense-accounts (mapv
|
|
(fn [iea]
|
|
{:db/id (:db/id iea)
|
|
:invoice-expense-account/amount 0.0})
|
|
(:invoice/expense-accounts i))}])))
|
|
(:id context))
|
|
{:message (str "Succesfully voided " (count all-ids))}))
|
|
|
|
(defn unvoid-invoice [context {id :invoice_id} _]
|
|
(let [invoice (d-invoices/get-by-id id)
|
|
_ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
|
|
_ (assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice))
|
|
history (dc/history (dc/db conn))
|
|
txs (dc/q {:find ['?tx '?e '?original-status '?original-outstanding '?total '?ea '?ea-amount]
|
|
:where ['[?e :invoice/status :invoice-status/voided ?tx true]
|
|
'[?e :invoice/status ?original-status ?tx false]
|
|
'[?e :invoice/outstanding-balance ?original-outstanding ?tx false]
|
|
'[?e :invoice/total ?total ?tx false]
|
|
'[?ea :invoice-expense-account/amount ?ea-amount ?tx false]]
|
|
:in ['$ '?e]}
|
|
history id)
|
|
[last-transaction] (->> txs (sort-by first) (last))]
|
|
(audit-transact [[:upsert-invoice
|
|
(->> txs
|
|
(filter (fn [[tx]] (= tx last-transaction)))
|
|
(reduce (fn [new-transaction [_ entity original-status original-outstanding total expense-account expense-account-amount]]
|
|
(-> new-transaction
|
|
(assoc :db/id entity
|
|
:invoice/total total
|
|
:invoice/status original-status
|
|
:invoice/outstanding-balance original-outstanding)
|
|
(update :invoice/expense-accounts (fnil conj []) {:db/id expense-account :invoice-expense-account/amount expense-account-amount})))
|
|
{}))]]
|
|
(:id context))
|
|
|
|
(-> (d-invoices/get-by-id id)
|
|
(->graphql (:id context)))))
|
|
|
|
(defn unautopay-invoice [context {id :invoice_id} _]
|
|
(let [invoice (dc/pull (dc/db conn) [{:invoice/client [:db/id]}
|
|
:invoice-payment/_invoice
|
|
:invoice/total
|
|
:invoice/scheduled-payment
|
|
:invoice/date] id)]
|
|
(assert (:invoice/client invoice))
|
|
(assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
|
|
(assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice))
|
|
(assert (not (seq (:invoice-payment/_invoice invoice))))
|
|
(audit-transact [[:upsert-invoice
|
|
{:db/id id
|
|
:invoice/status :invoice-status/unpaid
|
|
:invoice/outstanding-balance (:invoice/total invoice)
|
|
:invoice/scheduled-payment nil}]]
|
|
(:id context))
|
|
|
|
(-> (d-invoices/get-by-id id)
|
|
(->graphql (:id context)))))
|
|
|
|
(defn edit-expense-accounts [context args _]
|
|
(assert-can-see-client (:id context) (:db/id (:invoice/client (d-invoices/get-by-id (:invoice_id args)))))
|
|
(let [invoice-id (:invoice_id args)
|
|
invoice (d-invoices/get-by-id invoice-id)
|
|
_ (assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice))
|
|
_ (assert-valid-expense-accounts (:expense_accounts args) (:db/id (:invoice/vendor invoice )))]
|
|
|
|
(audit-transact [[:upsert-invoice {:db/id invoice-id
|
|
:invoice/expense-accounts (map
|
|
expense-account->entity
|
|
(:expense_accounts args))}]]
|
|
(:id context))
|
|
(->graphql
|
|
(d-invoices/get-by-id (:invoice_id args))
|
|
(:id context))))
|
|
|
|
;; TODO - multiple versions of this now exist. fix in datomic migration?
|
|
;; Approach could be something like this: (thanks chatgpt)
|
|
;; (defn distribute-cents [amount categories]
|
|
;; (let [dollars (quot amount 1)
|
|
;; cents (rem amount 1)
|
|
;; cents-per-category (quot cents (count categories))
|
|
;; extra-cents (rem cents (count categories))]
|
|
;; (zipmap categories (repeat cents-per-category))
|
|
;; (reduce-kv (fn [acc i category]
|
|
;; (update acc category + (if (< i extra-cents) 1 0)))
|
|
;; acc
|
|
;; categories)))
|
|
;; convert to/from decimal first
|
|
;; for categories, pass in the accounts, and return a tuple of [account amount]
|
|
;; after return, just use (assoc account :invoice-expnese-account/amount amount)
|
|
(defn maybe-code-accounts [invoice account-rules valid-locations]
|
|
(with-precision 2
|
|
(let [accounts (vec (mapcat
|
|
(fn [ar]
|
|
(let [cents-to-distribute (int (Math/round (Math/abs (* (:percentage ar)
|
|
(:invoice/total invoice)
|
|
100))))]
|
|
(if (= "Shared" (:location ar))
|
|
(do
|
|
(->> valid-locations
|
|
(map
|
|
(fn [cents location]
|
|
{:db/id (random-tempid)
|
|
:invoice-expense-account/account (:account_id ar)
|
|
:invoice-expense-account/amount (* 0.01 cents)
|
|
:invoice-expense-account/location location})
|
|
(rm/spread-cents cents-to-distribute (count valid-locations)))))
|
|
[(cond-> {:db/id (random-tempid)
|
|
:invoice-expense-account/account (:account_id ar)
|
|
:invoice-expense-account/amount (* 0.01 cents-to-distribute)}
|
|
(:location ar) (assoc :invoice-expense-account/location (:location ar)))])))
|
|
account-rules))
|
|
accounts (mapv
|
|
(fn [a]
|
|
(update a :invoice-expense-account/amount
|
|
#(with-precision 2
|
|
(double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP)))))
|
|
accounts)
|
|
leftover (with-precision 2 (.round (bigdec (- (Math/abs (:invoice/total invoice))
|
|
(Math/abs (reduce + 0.0 (map #(:invoice-expense-account/amount %) accounts)))))
|
|
*math-context*))
|
|
accounts (if (seq accounts)
|
|
(update-in accounts [(dec (count accounts)) :invoice-expense-account/amount] #(+ % (double leftover)))
|
|
[])]
|
|
accounts)))
|
|
|
|
|
|
|
|
(defn bulk-change-invoices [context args _]
|
|
(assert-admin (:id context))
|
|
(when-not (:client_id args)
|
|
(throw (ex-info "Client is required"
|
|
{:validation-error "client is required"})))
|
|
(let [args (assoc args :id (:id context))
|
|
locations (pull-attr (dc/db conn)
|
|
:client/locations
|
|
(:client_id args))
|
|
all-ids (all-ids-not-locked (get-ids-matching-filters args))
|
|
invoices (pull-many (dc/db conn) '[:db/id :invoice/total] (vec all-ids))
|
|
account-total (reduce + 0 (map (fn [x] (:percentage x)) (:accounts args)))]
|
|
(log/info "client is" locations)
|
|
(when
|
|
(not (dollars= 1.0 account-total))
|
|
(let [error (str "Account total (" account-total ") does not reach 100%")]
|
|
(throw (ex-info error {:validation-error error}))))
|
|
(doseq [a (:accounts args)
|
|
:let [{:keys [:account/location :account/name]} (dc/pull (dc/db conn) [:account/location :account/name] (:account_id a))]]
|
|
(when (and location (not= location (:location a)))
|
|
(let [err (str "Account " name " uses location " (:location a) ", but is supposed to be " location)]
|
|
(throw (ex-info err {:validation-error err}) )))
|
|
(when (and (not location)
|
|
(not (get (into #{"Shared"} locations)
|
|
(:location a))))
|
|
(let [err (str "Account " name " uses location " (:location a) ", but doesn't belong to the client.")]
|
|
(throw (ex-info err {:validation-error err}) ))))
|
|
(log/info "Bulk coding " (count all-ids) args)
|
|
(audit-transact-batch
|
|
(map (fn [i]
|
|
[:upsert-invoice {:db/id (:db/id i)
|
|
:invoice/expense-accounts (maybe-code-accounts i (:accounts args) locations)}])
|
|
invoices)
|
|
(:id context))
|
|
{:message (str "Successfully coded " (count all-ids) " invoices.")}))
|
|
|
|
(def objects
|
|
{:invoice
|
|
{:fields {:id {:type :id}
|
|
:original_id {:type 'Int}
|
|
:client_identifier {:type 'String}
|
|
:total {:type 'String}
|
|
:source_url {:type 'String}
|
|
:outstanding_balance {:type 'String}
|
|
:invoice_number {:type 'String}
|
|
:status {:type 'String}
|
|
:expense_accounts {:type '(list :invoices_expense_accounts)}
|
|
:similarity {:type 'Float}
|
|
:date {:type :iso_date}
|
|
:due {:type :iso_date}
|
|
:client_id {:type 'Int}
|
|
:payments {:type '(list :invoice_payment)}
|
|
:vendor {:type :vendor}
|
|
:client {:type :client}
|
|
:scheduled_payment {:type :iso_date}}}
|
|
|
|
:invoices_expense_accounts
|
|
{:fields {:id {:type :id}
|
|
:invoice_id {:type 'String}
|
|
:account {:type :account}
|
|
:location {:type 'String}
|
|
:amount {:type 'String}}}
|
|
|
|
:invoice_page {:fields {:invoices {:type '(list :invoice)}
|
|
:outstanding {:type :money}
|
|
:total_amount {:type :money}
|
|
:count {:type 'Int}
|
|
:total {:type 'Int}
|
|
:start {:type 'Int}
|
|
:end {:type 'Int}}}})
|
|
|
|
(def queries
|
|
{:invoice_page {:type '(list :invoice_page)
|
|
:args {:filters {:type :invoice_filters}}
|
|
|
|
:resolve :get-invoice-page}
|
|
:all_invoices {:type '(list :invoice)
|
|
:args {:client_id {:type :id}
|
|
:client_code {:type 'String}
|
|
:original_id {:type 'Int}
|
|
:statuses {:type '(list String)}}
|
|
:resolve :get-all-invoices}})
|
|
|
|
(def mutations
|
|
{:edit_invoice {:type :invoice
|
|
:args {:invoice {:type :edit_invoice}}
|
|
:resolve :mutation/edit-invoice}
|
|
:add_invoice {:type :invoice
|
|
:args {:invoice {:type :add_invoice}}
|
|
:resolve :mutation/add-invoice}
|
|
:void_invoice {:type :invoice
|
|
:args {:invoice_id {:type :id}}
|
|
:resolve :mutation/void-invoice}
|
|
:void_invoices {:type :message
|
|
:args {:filters {:type :invoice_filters}
|
|
:ids {:type '(list :id)}}
|
|
:resolve :mutation/void-invoices}
|
|
:unvoid_invoice {:type :invoice
|
|
:args {:invoice_id {:type :id}}
|
|
:resolve :mutation/unvoid-invoice}
|
|
:unautopay_invoice {:type :invoice
|
|
:args {:invoice_id {:type :id}}
|
|
:resolve :mutation/unautopay-invoice}
|
|
:reject_invoices {:type '(list :id)
|
|
:args {:invoices {:type '(list :id)}}
|
|
:resolve :mutation/reject-invoices}
|
|
|
|
:approve_invoices {:type '(list :id)
|
|
:args {:invoices {:type '(list :id)}}
|
|
:resolve :mutation/approve-invoices}
|
|
:add_and_print_invoice {:type :check_result
|
|
:args {:invoice {:type :add_invoice}
|
|
:bank_account_id {:type :id}
|
|
:type {:type :payment_type}}
|
|
:resolve :mutation/add-and-print-invoice}
|
|
:edit_expense_accounts {:type :invoice
|
|
:args {:invoice_id {:type :id}
|
|
:expense_accounts {:type '(list :edit_expense_account)}}
|
|
:resolve :mutation/edit-expense-accounts}
|
|
|
|
:bulk_change_invoices {:type :message
|
|
:args {:filters {:type :invoice_filters}
|
|
:client_id {:type :id}
|
|
:accounts {:type '(list :edit_percentage_account)}
|
|
:ids {:type '(list :id)}}
|
|
:resolve :mutation/bulk-change-invoices}})
|
|
|
|
(def input-objects
|
|
{:add_invoice
|
|
{:fields {:id {:type :id}
|
|
:invoice_number {:type 'String}
|
|
:expense_accounts {:type '(list :edit_expense_account)}
|
|
:location {:type 'String}
|
|
:scheduled_payment {:type :iso_date}
|
|
:date {:type :iso_date}
|
|
:due {:type :iso_date}
|
|
:client_id {:type :id}
|
|
:vendor_id {:type :id}
|
|
:vendor_name {:type 'String}
|
|
:total {:type :money}}}
|
|
:edit_invoice
|
|
{:fields {:id {:type :id}
|
|
:invoice_number {:type 'String}
|
|
:expense_accounts {:type '(list :edit_expense_account)}
|
|
:date {:type :iso_date}
|
|
:scheduled_payment {:type :iso_date}
|
|
:due {:type :iso_date}
|
|
:total {:type :money}}}
|
|
|
|
:edit_expense_account
|
|
{:fields {:id {:type :id}
|
|
:account_id {:type :id}
|
|
:location {:type 'String}
|
|
:amount {:type :money}}}
|
|
|
|
:invoice_filters {:fields {:import_status {:type 'String}
|
|
:exact_match_id {:type :id}
|
|
:date_range {:type :date_range}
|
|
:due_range {:type :date_range}
|
|
:status {:type :invoice_status}
|
|
:unresolved {:type 'Boolean}
|
|
:scheduled_payments {:type 'Boolean}
|
|
:client_id {:type :id}
|
|
:vendor_id {:type :id}
|
|
:account_id {:type :id}
|
|
:amount_lte {:type :money}
|
|
:amount_gte {:type :money}
|
|
:invoice_number_like {:type 'String}
|
|
:location {:type 'String}
|
|
:start {:type 'Int}
|
|
:per_page {:type 'Int}
|
|
:sort {:type '(list :sort_item)}}}})
|
|
|
|
(def enums
|
|
{:invoice_status {:values [{:enum-value :paid}
|
|
{:enum-value :unpaid}
|
|
{:enum-value :voided}]}})
|
|
|
|
(def resolvers
|
|
{:get-invoice-page get-invoice-page
|
|
:get-all-invoices get-all-invoices
|
|
:mutation/reject-invoices reject-invoices
|
|
:mutation/approve-invoices approve-invoices
|
|
:mutation/add-invoice add-invoice
|
|
:mutation/add-and-print-invoice add-and-print-invoice
|
|
:mutation/edit-invoice edit-invoice
|
|
:mutation/void-invoice void-invoice
|
|
:mutation/void-invoices void-invoices
|
|
:mutation/bulk-change-invoices bulk-change-invoices
|
|
:mutation/unvoid-invoice unvoid-invoice
|
|
:mutation/unautopay-invoice unautopay-invoice
|
|
:mutation/edit-expense-accounts edit-expense-accounts})
|
|
|
|
(defn attach [schema]
|
|
(->
|
|
(merge-with merge schema
|
|
{:objects objects
|
|
:queries queries
|
|
:mutations mutations
|
|
:input-objects input-objects
|
|
:enums enums})
|
|
(attach-tracing-resolvers resolvers)))
|