886 lines
37 KiB
Clojure
886 lines
37 KiB
Clojure
(ns auto-ap.graphql
|
|
(:require
|
|
[auto-ap.datomic :refer [merge-query conn]]
|
|
[auto-ap.datomic.users :as d-users]
|
|
[auto-ap.graphql.accounts :as gq-accounts]
|
|
[auto-ap.graphql.checks :as gq-checks]
|
|
[auto-ap.graphql.clients :as gq-clients]
|
|
[auto-ap.graphql.expected-deposit :as gq-expected-deposit]
|
|
[auto-ap.graphql.ezcater :as gq-ezcater]
|
|
[auto-ap.graphql.import-batch :as gq-import-batches]
|
|
[auto-ap.graphql.intuit-bank-accounts :as gq-intuit-bank-accounts]
|
|
[auto-ap.graphql.invoices :as gq-invoices]
|
|
[auto-ap.graphql.jobs :as gq-jobs]
|
|
[auto-ap.graphql.ledger :as gq-ledger]
|
|
[auto-ap.graphql.plaid :as gq-plaid]
|
|
[auto-ap.graphql.reports :as gq-reports]
|
|
[auto-ap.graphql.sales-orders :as gq-sales-orders]
|
|
[auto-ap.graphql.transaction-rules :as gq-transaction-rules]
|
|
[auto-ap.graphql.transactions :as gq-transactions]
|
|
[auto-ap.graphql.users :as gq-users]
|
|
[auto-ap.graphql.utils :refer [assert-admin attach-tracing-resolvers]]
|
|
[auto-ap.graphql.vendors :as gq-vendors]
|
|
[auto-ap.graphql.yodlee-merchants :as ym]
|
|
[auto-ap.graphql.yodlee2 :as gq-yodlee2]
|
|
[auto-ap.logging :as alog :refer [error-event info-event warn-event]]
|
|
[auto-ap.time :as time]
|
|
[clj-time.coerce :as coerce]
|
|
[clj-time.core :as t]
|
|
[clojure.string :as str]
|
|
[clojure.tools.logging :as log]
|
|
[clojure.walk :as walk]
|
|
[com.brunobonacci.mulog :as mu]
|
|
[com.unbounce.dogstatsd.core :as statsd]
|
|
[com.walmartlabs.lacinia :refer [execute]]
|
|
[com.walmartlabs.lacinia.parser :as p]
|
|
[com.walmartlabs.lacinia.schema :as schema]
|
|
[com.walmartlabs.lacinia.util :refer [attach-resolvers]]
|
|
[datomic.client.api :as dc]
|
|
[unilog.context :as lc]
|
|
[yang.time :refer [time-it]])
|
|
(:import
|
|
(clojure.lang IPersistentMap)))
|
|
|
|
(def integreat-schema
|
|
{
|
|
:scalars {:id {:parse #(cond (number? %)
|
|
%
|
|
|
|
%
|
|
(Long/parseLong %))
|
|
|
|
:serialize #(.toString %)}
|
|
:ident {:parse (fn [x] {:db/ident x})
|
|
:serialize #(or (:ident %) (:db/ident %) %)}
|
|
:iso_date {:parse #(time/parse % time/iso-date)
|
|
:serialize #(time/unparse % time/iso-date)}
|
|
:iso_date_time {:parse #(clj-time.coerce/to-date-time %)
|
|
:serialize #(str (clj-time.coerce/to-date-time %))}
|
|
:money {:parse #(cond (and (string? %)
|
|
(not (str/blank? %)))
|
|
(Double/parseDouble %)
|
|
|
|
(and (string? %)
|
|
(str/blank? (str/trim %)))
|
|
0.0
|
|
|
|
(nil? %)
|
|
0.0
|
|
|
|
(int? %)
|
|
(double %)
|
|
|
|
:else
|
|
%)
|
|
:serialize #(cond (double? %)
|
|
(str %)
|
|
|
|
(int? %)
|
|
(str %)
|
|
|
|
:else
|
|
%)}
|
|
|
|
:percentage {:parse #(cond (and (string? %)
|
|
(not (str/blank? %)))
|
|
(Double/parseDouble %)
|
|
|
|
(int? %)
|
|
(double %)
|
|
|
|
:else
|
|
%)
|
|
:serialize #(if (double? %)
|
|
(str %)
|
|
%)}}
|
|
:objects
|
|
{
|
|
:message
|
|
{:fields {:message {:type 'String}}}
|
|
|
|
:search_result
|
|
{:fields {:name {:type 'String}
|
|
:id {:type :id}}}
|
|
|
|
:account_search_result
|
|
{:fields {:name {:type 'String}
|
|
:id {:type :id}
|
|
:location {:type 'String}
|
|
:warning {:type 'String}}}
|
|
|
|
:yodlee_provider_account
|
|
{:fields {:id {:type 'Int}
|
|
:client {:type :client}
|
|
:status {:type 'String}
|
|
:detailed_status {:type 'String}
|
|
:last_updated {:type :iso_date}
|
|
:accounts {:type '(list :yodlee_account)}}}
|
|
|
|
:integration_status
|
|
{:fields {:id {:type :id}
|
|
:last_attempt {:type :iso_date}
|
|
:last_updated {:type :iso_date}
|
|
:message {:type 'String}
|
|
:state {:type :integration_state}}}
|
|
|
|
:yodlee_account
|
|
{:fields {:id {:type 'Int}
|
|
:status {:type 'String}
|
|
:available_balance {:type :money}
|
|
:name {:type 'String}
|
|
:number {:type 'String}
|
|
:last_updated {:type :iso_date}}}
|
|
:contact
|
|
{:fields {:id {:type :id}
|
|
:name {:type 'String}
|
|
:email {:type 'String}
|
|
:phone {:type 'String}}}
|
|
|
|
|
|
:address
|
|
{:fields {:id {:type :id}
|
|
:street1 {:type 'String}
|
|
:street2 {:type 'String}
|
|
:city {:type 'String}
|
|
:state {:type 'String}
|
|
:zip {:type 'String}}}
|
|
|
|
:terms_override
|
|
{:fields {:id {:type :id}
|
|
:client {:type :client}
|
|
:terms {:type 'Int}}}
|
|
|
|
:schedule_payment_dom
|
|
{:fields {:id {:type :id}
|
|
:client {:type :client}
|
|
:dom {:type 'Int}}}
|
|
|
|
:vendor_account_override
|
|
{:fields {:id {:type :id}
|
|
:client {:type :client}
|
|
:account {:type :account}}}
|
|
|
|
:usage {:fields {:client_id {:type :id}
|
|
:count {:type 'Int}}}
|
|
|
|
:vendor
|
|
{:fields {:id {:type :id}
|
|
:name {:type 'String}
|
|
:code {:type 'String}
|
|
:terms {:type 'Int}
|
|
:hidden {:type 'Boolean}
|
|
:automatically_paid_when_due {:type '(list :client)}
|
|
:terms_overrides {:type '(list :terms_override)}
|
|
:schedule_payment_dom {:type '(list :schedule_payment_dom)}
|
|
:account_overrides {:type '(list :vendor_account_override)}
|
|
:usage {:type '(list :usage)}
|
|
|
|
:print_as {:type 'String}
|
|
:primary_contact {:type :contact}
|
|
:secondary_contact {:type :contact}
|
|
:address {:type :address}
|
|
|
|
:default_account {:type :account}
|
|
:invoice_reminder_schedule {:type 'String}
|
|
|
|
:legal_entity_name {:type 'String}
|
|
:legal_entity_first_name {:type 'String}
|
|
:legal_entity_middle_name {:type 'String}
|
|
:legal_entity_last_name {:type 'String}
|
|
:legal_entity_tin {:type 'String}
|
|
:legal_entity_tin_type {:type :tin_type}
|
|
:legal_entity_1099_type {:type :type_1099}}}
|
|
|
|
|
|
:reminder
|
|
{:fields {:id {:type 'Int}
|
|
:email {:type 'String}
|
|
:subject {:type 'String}
|
|
:body {:type 'String}
|
|
:scheduled {:type 'String}
|
|
:sent {:type 'String}
|
|
:vendor {:type :vendor}}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:yodlee_merchant {:fields {:id {:type :id}
|
|
:yodlee_id {:type 'String}
|
|
:name {:type 'String}}}
|
|
|
|
:intuit_bank_account {:fields {:id {:type :id}
|
|
:external_id {:type 'String}
|
|
:name {:type 'String}}}
|
|
|
|
:forecast_match {:fields {:id {:type :id}
|
|
:identifier {:type 'String}}}
|
|
|
|
:transaction_rule {:fields {:id {:type :id}
|
|
:note {:type 'String}
|
|
:client {:type :client}
|
|
:bank_account {:type :bank_account}
|
|
:yodlee_merchant {:type :yodlee_merchant}
|
|
:description {:type 'String}
|
|
:amount_lte {:type 'String}
|
|
:amount_gte {:type 'String}
|
|
:dom_lte {:type 'Int}
|
|
:dom_gte {:type 'Int}
|
|
:vendor {:type :vendor}
|
|
:accounts {:type '(list :percentage_account)}
|
|
:transaction_approval_status {:type :transaction_approval_status}}}
|
|
|
|
|
|
|
|
:user
|
|
{:fields {:id {:type :id}
|
|
:name {:type 'String}
|
|
:role {:type :role}
|
|
:clients {:type '(list :client)}}}
|
|
|
|
:csv
|
|
{:fields {:csv_content_b64 {:type 'String}}}
|
|
|
|
:account_client_override
|
|
{:fields {:id {:type :id}
|
|
:client {:type :client}
|
|
:name {:type 'String}}}
|
|
|
|
:account {:fields {:id {:type :id}
|
|
:numeric_code {:type 'Int}
|
|
:invoice_allowance {:type :allowance}
|
|
:vendor_allowance {:type :allowance}
|
|
:type {:type :ident}
|
|
:applicability {:type :applicability}
|
|
:account_set {:type 'String}
|
|
:location {:type 'String}
|
|
:name {:type 'String}
|
|
:client_overrides {:type '(list :account_client_override)}}}
|
|
|
|
:percentage_account
|
|
{:fields {:id {:type :id}
|
|
:account {:type :account}
|
|
:location {:type 'String}
|
|
:percentage {:type :percentage}}}
|
|
|
|
:yodlee_provider_account_page {:fields {:yodlee_provider_accounts {:type '(list :yodlee_provider_account)}
|
|
:count {:type 'Int}
|
|
:total {:type 'Int}
|
|
:start {:type 'Int}
|
|
:end {:type 'Int}}}
|
|
|
|
|
|
|
|
:transaction_rule_page {:fields {:transaction_rules {:type '(list :transaction_rule)}
|
|
:count {:type 'Int}
|
|
:total {:type 'Int}
|
|
:start {:type 'Int}
|
|
:end {:type 'Int}}}
|
|
|
|
|
|
|
|
|
|
|
|
:vendor_page {:fields {:vendors {:type '(list :vendor)}
|
|
:count {:type 'Int}
|
|
:total {:type 'Int}
|
|
:start {:type 'Int}
|
|
:end {:type 'Int}}}
|
|
|
|
:account_page {:fields {:accounts {:type '(list :account)}
|
|
: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}}}
|
|
|
|
:expense_account_stat {:fields {:account {:type :account}
|
|
:total {:type 'String}}}
|
|
|
|
:invoice_stat {:fields {:name {:type 'String}
|
|
:paid {:type 'String}
|
|
:unpaid {:type 'String}}}
|
|
|
|
:upcoming_transaction {:fields {:amount {:type :money}
|
|
:identifier {:type 'String}
|
|
:date {:type :iso_date}}}
|
|
|
|
:cash_flow_result {:fields {:beginning_balance {:type :money}
|
|
:invoices_due_soon {:type '(list :invoice)}
|
|
:outstanding_payments {:type :money}
|
|
:upcoming_credits {:type '(list :upcoming_transaction)}
|
|
:upcoming_debits {:type '(list :upcoming_transaction)}}}}
|
|
|
|
:queries
|
|
{:expense_account_stats {:type '(list :expense_account_stat)
|
|
:args {:client_id {:type :id}}
|
|
:resolve :get-expense-account-stats}
|
|
:potential_transaction_rule_matches {:type '(list :transaction_rule)
|
|
:args {:transaction_id {:type :id}}
|
|
:resolve :get-transaction-rule-matches}
|
|
|
|
|
|
:test_transaction_rule {:type '(list :transaction)
|
|
:args {:transaction_rule {:type :edit_transaction_rule}}
|
|
:resolve :test-transaction-rule}
|
|
|
|
:run_transaction_rule {:type '(list :transaction)
|
|
:args {:transaction_rule_id {:type :id}}
|
|
:resolve :run-transaction-rule}
|
|
|
|
:invoice_stats {:type '(list :invoice_stat)
|
|
:args {:client_id {:type :id}}
|
|
:resolve :get-invoice-stats}
|
|
|
|
:cash_flow {:type :cash_flow_result
|
|
:args {:client_id {:type :id}}
|
|
:resolve :get-cash-flow}
|
|
:yodlee_provider_account_page {:type :yodlee_provider_account_page
|
|
:args {:client_id {:type :id}}
|
|
:resolve :get-yodlee-provider-account-page}
|
|
|
|
:account_page {:type :account_page
|
|
:args {:name_like {:type 'String}
|
|
:numeric_code {:type 'Int}
|
|
:start {:type 'Int}
|
|
:per_page {:type 'Int}
|
|
:sort {:type '(list :sort_item)}}
|
|
:resolve :get-accounts}
|
|
|
|
:all_accounts {:type '(list :account)
|
|
:args {}
|
|
:resolve :get-all-accounts}
|
|
|
|
:search_vendor {:type '(list :search_result)
|
|
:args {:query {:type 'String}}
|
|
:resolve :search-vendor}
|
|
:search_account {:type '(list :account_search_result)
|
|
:args {:query {:type 'String}
|
|
:allowance {:type :account_allowance}
|
|
:client_id {:type :id}
|
|
:vendor_id {:type :id}}
|
|
:resolve :search-account}
|
|
|
|
|
|
|
|
|
|
|
|
:yodlee_merchants {:type '(list :yodlee_merchant)
|
|
:args {}
|
|
:resolve :get-yodlee-merchants}
|
|
|
|
:intuit_bank_accounts {:type '(list :intuit_bank_account)
|
|
:args {}
|
|
:resolve :get-intuit-bank-accounts}
|
|
|
|
:transaction_rule_page {:type :transaction_rule_page
|
|
:args {:client_id {:type :id}
|
|
:vendor_id {:type :id}
|
|
:start {:type 'Int}
|
|
:per_page {:type 'Int}
|
|
:sort {:type '(list :sort_item)}
|
|
:asc {:type 'Boolean}
|
|
:note {:type 'String}
|
|
:description {:type 'String}}
|
|
:resolve :get-transaction-rule-page}
|
|
|
|
|
|
|
|
|
|
:vendor {:type :vendor_page
|
|
:args {:name_like {:type 'String}
|
|
:start {:type 'Int}
|
|
:per_page {:type 'Int}
|
|
:sort {:type '(list :sort_item)}}
|
|
:resolve :get-vendor}
|
|
:user {:type '(list :user)
|
|
:resolve :get-user}
|
|
:vendor_by_id {:type :vendor
|
|
:args {:id {:type :id}}
|
|
:resolve :vendor-by-id}
|
|
:account_for_vendor {:type :account
|
|
:args {:client_id {:type :id}
|
|
:vendor_id {:type :id}}
|
|
:resolve :account-for-vendor}}
|
|
|
|
:input-objects
|
|
{
|
|
:sort_item
|
|
{:fields {:sort_key {:type 'String}
|
|
:sort_name {:type 'String}
|
|
:asc {:type 'Boolean}}}
|
|
|
|
:date_range {:fields {:start {:type :iso_date}
|
|
:end {:type :iso_date}}}
|
|
|
|
:edit_user
|
|
{:fields {:id {:type :id}
|
|
:name {:type 'String}
|
|
:role {:type :role}
|
|
:clients {:type '(list String)}}}
|
|
|
|
:add_contact
|
|
{:fields {:id {:type :id}
|
|
:name {:type 'String}
|
|
:email {:type 'String}
|
|
:phone {:type 'String}}}
|
|
:add_address
|
|
{:fields {:id {:type :id}
|
|
:street1 {:type 'String}
|
|
:street2 {:type 'String}
|
|
:city {:type 'String}
|
|
:state {:type 'String}
|
|
:zip {:type 'String}}}
|
|
|
|
:add_terms_override
|
|
{:fields {:id {:type :id}
|
|
:client_id {:type :id}
|
|
:terms {:type 'Int}}}
|
|
|
|
:add_account_override
|
|
{:fields {:id {:type :id}
|
|
:client_id {:type :id}
|
|
:account_id {:type :id}}}
|
|
|
|
:add_schedule_payment_dom
|
|
{:fields {:id {:type :id}
|
|
:client_id {:type :id}
|
|
:dom {:type 'Int}}}
|
|
|
|
:add_vendor
|
|
{:fields {:id {:type :id}
|
|
:name {:type 'String}
|
|
:terms {:type 'Int}
|
|
:terms_overrides {:type '(list :add_terms_override)}
|
|
:code {:type 'String}
|
|
:automatically_paid_when_due {:type '(list :id)}
|
|
:hidden {:type 'Boolean}
|
|
:print_as {:type 'String}
|
|
:primary_contact {:type :add_contact}
|
|
:secondary_contact {:type :add_contact}
|
|
:address {:type :add_address}
|
|
:default_account_id {:type :id}
|
|
:account_overrides {:type '(list :add_account_override)}
|
|
:schedule_payment_dom {:type '(list :add_schedule_payment_dom)}
|
|
:invoice_reminder_schedule {:type 'String}
|
|
:legal_entity_name {:type 'String}
|
|
:legal_entity_first_name {:type 'String}
|
|
:legal_entity_middle_name {:type 'String}
|
|
:legal_entity_last_name {:type 'String}
|
|
:legal_entity_tin {:type 'String}
|
|
:legal_entity_tin_type {:type :tin_type}
|
|
:legal_entity_1099_type {:type :type_1099}}}
|
|
|
|
:edit_percentage_account
|
|
{:fields {:id {:type :id}
|
|
:account_id {:type :id}
|
|
:location {:type 'String}
|
|
:percentage {:type :percentage}}}
|
|
|
|
:edit_transaction_rule
|
|
{:fields {:id {:type :id}
|
|
:description {:type 'String}
|
|
:note {:type 'String}
|
|
:bank_account_id {:type :id}
|
|
:client_id {:type :id}
|
|
:yodlee_merchant_id {:type :id}
|
|
:amount_lte {:type :money}
|
|
:amount_gte {:type :money}
|
|
:dom_lte {:type 'Int}
|
|
:dom_gte {:type 'Int}
|
|
:vendor_id {:type :id}
|
|
:accounts {:type '(list :edit_percentage_account)}
|
|
:transaction_approval_status {:type :transaction_approval_status}}}
|
|
|
|
:edit_account_client_override
|
|
{:fields {:id {:type :id}
|
|
:client_id {:type :id}
|
|
:name {:type 'String}}}
|
|
|
|
:edit_account
|
|
{:fields {:id {:type :id}
|
|
:type {:type :account_type}
|
|
:applicability {:type :applicability}
|
|
:invoice_allowance {:type :allowance}
|
|
:vendor_allowance {:type :allowance}
|
|
:numeric_code {:type 'Int}
|
|
:location {:type 'String}
|
|
:account_set {:type 'String}
|
|
:name {:type 'String}
|
|
:client_overrides {:type '(list :edit_account_client_override)}}}}
|
|
|
|
:enums {
|
|
:processor {:values [{:enum-value :na}
|
|
{:enum-value :doordash}
|
|
{:enum-value :koala}
|
|
{:enum-value :ezcater}
|
|
{:enum-value :square}
|
|
{:enum-value :uber_eats}
|
|
{:enum-value :grubhub}]}
|
|
|
|
:account_allowance {:values [{:enum-value :vendor}
|
|
{:enum-value :invoice}]}
|
|
:integration_state {:values [{:enum-value :failed}
|
|
{:enum-value :success}
|
|
{:enum-value :unauthorized}]}
|
|
:allowance {:values [{:enum-value :allowed}
|
|
{:enum-value :denied}
|
|
{:enum-value :warn}
|
|
{:enum-value :admin_only}]}
|
|
:tin_type {:values [{:enum-value :ein}
|
|
{:enum-value :ssn}]}
|
|
:type_1099 {:values [{:enum-value :none}
|
|
{:enum-value :misc}
|
|
{:enum-value :landlord}]}
|
|
:applicability {:values [{:enum-value :global}
|
|
{:enum-value :optional}
|
|
{:enum-value :customized}]}
|
|
:role {:values [{:enum-value :none}
|
|
{:enum-value :user}
|
|
{:enum-value :manager}
|
|
{:enum-value :power_user}
|
|
{:enum-value :admin}]}
|
|
:account_type {:values [{:enum-value :dividend}
|
|
{:enum-value :expense}
|
|
{:enum-value :asset}
|
|
{:enum-value :liability}
|
|
{:enum-value :equity}
|
|
{:enum-value :revenue}]}}
|
|
:mutations
|
|
{
|
|
|
|
:delete_transaction_rule
|
|
{:type :id
|
|
:args {:transaction_rule_id {:type :id}}
|
|
:resolve :mutation/delete-transaction-rule}
|
|
:merge_vendors
|
|
{:type :id
|
|
:args {:from {:type :id}
|
|
:to {:type :id}}
|
|
:resolve :mutation/merge-vendors}
|
|
|
|
:edit_user
|
|
{:type :user
|
|
:args {:edit_user {:type :edit_user}}
|
|
:resolve :mutation/edit-user}
|
|
|
|
:upsert_vendor
|
|
{:type :vendor
|
|
:args {:vendor {:type :add_vendor}}
|
|
:resolve :mutation/upsert-vendor}
|
|
|
|
:upsert_transaction_rule
|
|
{:type :transaction_rule
|
|
:args {:transaction_rule {:type :edit_transaction_rule}}
|
|
:resolve :mutation/upsert-transaction-rule}
|
|
|
|
:upsert_account
|
|
{:type :account
|
|
:args {:account {:type :edit_account}}
|
|
:resolve :mutation/upsert-account}}})
|
|
|
|
|
|
(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 get-user [context args _]
|
|
(assert-admin (:id context))
|
|
|
|
(let [users (d-users/get-graphql args)]
|
|
(->graphql users)))
|
|
|
|
|
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
|
(defn categorize [x]
|
|
(cond (<= x 0) :due
|
|
(<= x 30 ) :due-30
|
|
(<= x 60 ) :due-60
|
|
:else :due-later))
|
|
|
|
|
|
(defn get-expense-account-stats [_ {:keys [client_id] } _]
|
|
(let [result (cond-> {:query {:find ['?account '?account-name '(sum ?amount)]
|
|
:in ['$]
|
|
:where []}
|
|
:args [(dc/db conn) client_id]}
|
|
client_id (merge-query {:query {:in ['?c]}
|
|
|
|
:args [client_id]})
|
|
(not client_id) (merge-query {:query {:where ['[?c :client/name]]}})
|
|
|
|
true (merge-query {:query {:where ['[?i :invoice/client ?c]
|
|
'[?i :invoice/expense-accounts ?expense-account]
|
|
'[?expense-account :invoice-expense-account/account ?account]
|
|
'[?account :account/name ?account-name]
|
|
'[?expense-account :invoice-expense-account/amount ?amount]]}})
|
|
|
|
true (dc/q))]
|
|
(for [[account-id account-name total] result]
|
|
{:account {:id account-id :name account-name} :total total})))
|
|
|
|
(defn get-invoice-stats [_ {:keys [client_id] } _]
|
|
(let [result (cond-> {:query {:find ['?name '(sum ?outstanding-balance) '(sum ?total)]
|
|
:in ['$]
|
|
:where []}
|
|
:args [(dc/db conn) client_id]}
|
|
client_id (merge-query {:query {:in ['?c]}
|
|
:args [client_id]})
|
|
(not client_id) (merge-query {:query {:where ['[?c :client/name]]}})
|
|
|
|
true (merge-query {:query {:where ['[?i :invoice/client ?c]
|
|
'[?i :invoice/outstanding-balance ?outstanding-balance]
|
|
'[?i :invoice/total ?total]
|
|
'[?i :invoice/due ?date]
|
|
'[(.toInstant ^java.util.Date ?date) ?d2]
|
|
'[(.between java.time.temporal.ChronoUnit/DAYS (java.time.Instant/now) ?d2 ) ?d3]
|
|
'[(auto-ap.graphql/categorize ?d3) ?name]]}})
|
|
|
|
true (dc/q))
|
|
result (group-by first result)]
|
|
|
|
(for [[id name] [[:due "Due"] [:due-30 "0-30 days"] [:due-60 "31-60 days"] [:due-later ">60 days"]]
|
|
:let [[[_ outstanding-balance total] ] (id result nil)
|
|
outstanding-balance (or outstanding-balance 0)
|
|
total (or total 0)]]
|
|
{:name name :unpaid outstanding-balance :paid (if (= :due id)
|
|
0
|
|
(- total outstanding-balance))})))
|
|
|
|
(defn has-fulfilled? [id date recent-fulfillments]
|
|
|
|
(seq (transduce
|
|
(filter (fn [[potential-id potential-date]]
|
|
(let [date (coerce/to-date-time date)
|
|
potential-date (coerce/to-date-time potential-date)]
|
|
(and (= id potential-id)
|
|
(<= (t/in-days (apply t/interval (sort [date potential-date]))) 10)))))
|
|
conj
|
|
[]
|
|
recent-fulfillments)))
|
|
|
|
(def first-week-a (coerce/to-date-time #inst "1999-12-27T00:00:00.000-07:00"))
|
|
|
|
(defn get-cash-flow [_ {:keys [client_id]} _]
|
|
(when client_id
|
|
(let [{:client/keys [week-a-credits week-a-debits week-b-credits week-b-debits forecasted-transactions ]} (dc/pull (dc/db conn) '[*] client_id)
|
|
total-cash (reduce
|
|
(fn [total [credit debit]]
|
|
(- (+ total credit)
|
|
debit))
|
|
0.0
|
|
(dc/q {:query {:find '[?debit ?credit]
|
|
:in '[$ ?client]
|
|
:where ['[?j :journal-entry/client ?client]
|
|
'[?j :journal-entry/line-items ?je]
|
|
'[?je :journal-entry-line/account ?ba]
|
|
'[?ba :bank-account/type :bank-account-type/check]
|
|
'[(get-else $ ?je :journal-entry-line/debit 0.0) ?debit]
|
|
'[(get-else $ ?je :journal-entry-line/credit 0.0) ?credit]]}
|
|
:args [(dc/db conn) client_id]}))
|
|
bills-due-soon (dc/q {:query {:find '[?due ?outstanding ?invoice-number ?vendor-id ?vendor-name]
|
|
:in '[$ ?client ?due-before]
|
|
:where ['[?i :invoice/client ?client]
|
|
'[?i :invoice/status :invoice-status/unpaid]
|
|
'[?i :invoice/due ?due]
|
|
'[(<= ?due ?due-before)]
|
|
'[?i :invoice/outstanding-balance ?outstanding]
|
|
'[?i :invoice/invoice-number ?invoice-number]
|
|
'[?i :invoice/vendor ?vendor-id]
|
|
'[?vendor-id :vendor/name ?vendor-name]]}
|
|
:args [(dc/db conn) client_id (coerce/to-date (t/plus (time/local-now) (t/days 180)))]})
|
|
outstanding-checks (reduce
|
|
+
|
|
0.0
|
|
(map first (dc/q {:query {:find '[?amount]
|
|
:in '[$ ?client ?due-before]
|
|
:where ['[?p :payment/client ?client]
|
|
'[?p :payment/status :payment-status/pending]
|
|
'[?p :payment/amount ?amount]
|
|
'(or
|
|
[?p :payment/type :payment-type/debit]
|
|
[?p :payment/type :payment-type/check])]}
|
|
:args [(dc/db conn) client_id (coerce/to-date (t/plus (time/local-now) (t/days 180)))]})))
|
|
recent-fulfillments (dc/q {:query {:find '[?f ?d]
|
|
:in '[$ ?client ?min-date]
|
|
:where ['[?t :transaction/forecast-match ?f]
|
|
'[?t :transaction/date ?d]
|
|
'[?t :transaction/client ?client]
|
|
'[(>= ?d ?min-date)]]}
|
|
:args [(dc/db conn) client_id (coerce/to-date (t/plus (time/local-now) (t/months -2)))]})
|
|
forecasted-transactions (for [{:forecasted-transaction/keys [amount identifier day-of-month]
|
|
:db/keys [id]} forecasted-transactions
|
|
month (range -1 7)
|
|
:let [next (t/plus (t/local-date (t/year (time/local-now))
|
|
(t/month (time/local-now))
|
|
(Math/min (Math/max day-of-month 1)
|
|
30))
|
|
(t/months month))]
|
|
:when (not (has-fulfilled? id next recent-fulfillments))]
|
|
{:identifier identifier
|
|
:amount amount
|
|
:date (coerce/to-date-time next)})
|
|
is-week-a? (fn [d]
|
|
(= 0 (mod (t/in-weeks (t/interval first-week-a d)) 2)))]
|
|
|
|
{:beginning_balance total-cash
|
|
:outstanding_payments outstanding-checks
|
|
:invoices_due_soon (mapv (fn [[due outstanding invoice-number vendor-id vendor-name]]
|
|
{:due (coerce/to-date-time due)
|
|
:invoice_number invoice-number
|
|
:vendor {:id vendor-id :name vendor-name}
|
|
:outstanding_balance outstanding})
|
|
bills-due-soon)
|
|
:upcoming_credits (into (mapv
|
|
(fn [date]
|
|
{:amount (if (is-week-a? (coerce/to-date-time date))
|
|
(or week-a-credits 0)
|
|
(or week-b-credits 0))
|
|
:date (coerce/to-date-time date)})
|
|
(take (* 7 4) (time/day-of-week-seq 1)))
|
|
(filter #(>= (:amount %) 0) forecasted-transactions))
|
|
:upcoming_debits (into (mapv
|
|
(fn [date]
|
|
{:amount (- (if (is-week-a? (coerce/to-date-time date))
|
|
(or week-a-debits 0)
|
|
(or week-b-debits 0)))
|
|
:date (coerce/to-date-time date)})
|
|
(take (* 7 4) (time/day-of-week-seq 1)))
|
|
(filter #(< (:amount %) 0) forecasted-transactions))})))
|
|
|
|
|
|
(def schema
|
|
(-> integreat-schema
|
|
(attach-tracing-resolvers
|
|
{
|
|
:get-yodlee-provider-account-page gq-yodlee2/get-yodlee-provider-account-page
|
|
:get-accounts gq-accounts/get-graphql
|
|
:get-all-accounts gq-accounts/get-all-graphql
|
|
:get-transaction-rule-page gq-transaction-rules/get-transaction-rule-page
|
|
:get-transaction-rule-matches gq-transaction-rules/get-transaction-rule-matches
|
|
:get-expense-account-stats get-expense-account-stats
|
|
:get-invoice-stats get-invoice-stats
|
|
:get-cash-flow get-cash-flow
|
|
:get-yodlee-merchants ym/get-yodlee-merchants
|
|
:get-intuit-bank-accounts gq-intuit-bank-accounts/get-intuit-bank-accounts
|
|
:vendor-by-id gq-vendors/get-by-id
|
|
:account-for-vendor gq-accounts/default-for-vendor
|
|
:get-user get-user
|
|
:mutation/delete-transaction-rule gq-transaction-rules/delete-transaction-rule
|
|
:mutation/edit-user gq-users/edit-user
|
|
:mutation/upsert-transaction-rule gq-transaction-rules/upsert-transaction-rule
|
|
:test-transaction-rule gq-transaction-rules/test-transaction-rule
|
|
:run-transaction-rule gq-transaction-rules/run-transaction-rule
|
|
:mutation/upsert-vendor gq-vendors/upsert-vendor
|
|
:mutation/upsert-account gq-accounts/upsert-account
|
|
:mutation/merge-vendors gq-vendors/merge-vendors
|
|
:get-vendor gq-vendors/get-graphql
|
|
:search-vendor gq-vendors/search
|
|
:search-account gq-accounts/search})
|
|
gq-checks/attach
|
|
gq-ledger/attach
|
|
gq-reports/attach
|
|
gq-plaid/attach
|
|
gq-import-batches/attach
|
|
gq-transactions/attach
|
|
gq-expected-deposit/attach
|
|
gq-ezcater/attach
|
|
gq-invoices/attach
|
|
gq-clients/attach
|
|
gq-sales-orders/attach
|
|
gq-jobs/attach
|
|
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-name [q]
|
|
(try
|
|
(str/join "__" (map name (:operations (p/operations (p/parse-query schema q)))))
|
|
(catch Exception _
|
|
"unknown query")))
|
|
|
|
(defn query
|
|
([id q]
|
|
(query id q nil))
|
|
([id q v]
|
|
(statsd/increment "query.graphql.count" {:tags #{(str "query:" (query-name q))}})
|
|
(statsd/time! [(str "query.graphql.time" ) {:tags #{(str "query:" (query-name q))}}]
|
|
(mu/with-context {:query-string q :user id}
|
|
(lc/with-context {:query q}
|
|
(log/info "executing query name" (query-name q))
|
|
(try
|
|
(let [[result time] (time-it (simplify (execute schema q v {:id id})))]
|
|
(info-event "Query completed"
|
|
{:time (:time time)
|
|
:errors (seq (:errors result))})
|
|
(when (seq (:errors result))
|
|
(throw (ex-info "GraphQL error" {:result result})))
|
|
result)
|
|
|
|
(catch Exception e
|
|
(if-let [v (or (:validation-error (ex-data e))
|
|
(:validation-error (ex-data (.getCause e))))]
|
|
|
|
(do
|
|
(alog/warn ::query-validation
|
|
:exception e)
|
|
(warn-event "validation error" {:validation-error v
|
|
:data (ex-data e)})
|
|
(throw e)
|
|
#_{:errors [{:message v}]})
|
|
(do
|
|
(error-event "query error" {:error e})
|
|
(alog/error ::query-error
|
|
:exception e)
|
|
|
|
(throw e))))))))))
|