(ns auto-ap.graphql (:require [auto-ap.datomic :refer [merge-query uri]] [auto-ap.datomic.checks :as d-checks] [auto-ap.datomic.sales-orders :as d-sales-orders] [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.invoices :as gq-invoices] [auto-ap.graphql.ledger :as gq-ledger] [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 assert-can-see-client]] [auto-ap.graphql.vendors :as gq-vendors] [auto-ap.graphql.yodlee-merchants :as ym] [auto-ap.graphql.yodlee2 :as gq-yodlee2] [auto-ap.logging :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.walmartlabs.lacinia :refer [execute]] [com.walmartlabs.lacinia.schema :as schema] [com.walmartlabs.lacinia.util :refer [attach-resolvers]] [datomic.api :as d] [unilog.context :as lc] [yang.time :refer [time-it]]) (:import clojure.lang.IPersistentMap)) (def integreat-schema { :scalars {:id {:parse #(when % (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)} :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}}} :location_match {:fields {:location {:type 'String} :match {:type 'String} :id {:type :id}}} :client {:fields {:id {:type :id} :name {:type 'String} :code {:type 'String} :signature_file {:type 'String} :week_a_debits {:type :money} :week_a_credits {:type :money} :week_b_debits {:type :money} :week_b_credits {:type :money} :email {:type 'String} :address {:type :address} :location_matches {:type '(list :location_match)} :locations {:type '(list String)} :matches {:type '(list String)} :bank_accounts {:type '(list :bank_account)} :forecasted_transactions {:type '(list :forecasted_transaction)} :yodlee_provider_accounts {:type '(list :yodlee_provider_account)}}} :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)}}} :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}}} :bank_account {:fields {:id {:type :id} :type {:type :ident} :start_date {:type :iso_date} :number {:type 'String} :sort_order {:type 'Int} :visible {:type 'Boolean} :include_in_reports {:type 'Boolean} :routing {:type 'String} :code {:type 'String} :check_number {:type 'Int} :name {:type 'String} :bank_code {:type 'String} :bank_name {:type 'String} :current_balance {:type :money} :yodlee_balance_old {:type :money} :yodlee_account_id {:type 'Int} :yodlee_account {:type :yodlee_account} :locations {:type '(list String)}}} :forecasted_transaction {:fields {:identifier {:type 'String} :id {:type :id} :day_of_month {:type 'Int} :amount {:type :money}}} :balance_sheet_account {:fields {:id {:type 'String} :amount {:type 'String} :location {:type 'String} :count {:type 'Int} :numeric_code {:type 'Int} :account_type {:type :account_type} :name {:type 'String}}} :balance_sheet {:fields {:balance_sheet_accounts {:type '(list :balance_sheet_account)} :comparable_balance_sheet_accounts {:type '(list :balance_sheet_account)}}} :profit_and_loss_report_period {:fields {:accounts {:type '(list :balance_sheet_account)}}} :profit_and_loss_report {:fields {:periods {:type '(list :profit_and_loss_report_period)}}} :address {:fields {: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_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}}} :journal_entry_line {:fields {:id {:type :id} :account {:type :account} :location {:type 'String} :debit {:type 'String} :credit {:type 'String} :running_balance {:type :money}}} :journal_entry {:fields {:id {:type :id} :source {:type 'String} :external_id {:type 'String} :original_entity {:type :id} :amount {:type 'String} :note {:type 'String} :cleared_against {:type 'String} :client {:type :client} :vendor {:type :vendor} :alternate_description {:type 'String} :date {:type 'String} :line_items {:type '(list :journal_entry_line)}}} :order_line_item {:fields {:id {:type :id} :item_name {:type 'String} :total {:type :money} :tax {:type :money} :category {:type 'String} :discount {:type :money}}} :charge {:fields {:id {:type :id} :processor {:type :processor} :type_name {:type 'String} :total {:type :money} :tip {:type :money}}} :sales_order {:fields {:id {:type :id} :location {:type 'String} :external_id {:type 'String} :total {:type :money} :tip {:type :money} :tax {:type :money} :discount {:type :money} :service_charge {:type :money} :returns {:type :money} :client {:type :client} :date {:type 'String} :charges {:type '(list :charge)} :line_items {:type '(list :order_line_item)}}} :expected_deposit {:fields {:id {:type :id} :location {:type 'String} :external_id {:type 'String} :total {:type :money} :fee {:type :money} :client {:type :client} :date {:type 'String}}} :check {:fields {:id {:type :id} :type {:type 'String} :amount {:type 'String} :vendor {:type :vendor} :date {:type 'String} :bank_account {:type :bank_account} :memo {:type 'String} :s3_url {:type 'String} :check_number {:type 'Int} :status {:type 'String} :invoices {:type '(list :invoice_payment)}}} :payment {:fields {:id {:type :id} :type {:type :payment_type} :original_id {:type 'Int} :amount {:type 'String} :vendor {:type :vendor} :client {:type :client} :date {:type 'String} :bank_account {:type :bank_account} :memo {:type 'String} :s3_url {:type 'String} :check_number {:type 'Int} :status {:type :ident} :transaction {:type :transaction} :invoices {:type '(list :invoice_payment)}}} :yodlee_merchant {:fields {:id {:type :id} :yodlee_id {:type 'String} :name {:type 'String}}} :forecast_match {:fields {:id {:type :id} :identifier {:type 'String}}} :transaction {:fields {:id {:type :id} :amount {:type 'String} :description_original {:type 'String} :description_simple {:type 'String} :location {:type 'String} :forecast_match {:type :forecast_match} :status {:type 'String} :yodlee_merchant {:type :yodlee_merchant} :client {:type :client} :accounts {:type '(list :invoices_expense_accounts)} :payment {:type :payment} :vendor {:type :vendor} :bank_account {:type :bank_account} :date {:type 'String} :post_date {:type 'String} :approval_status {:type :transaction_approval_status} :matched_rule {:type :transaction_rule}}} :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}}} :invoice_payment {:fields {:id {:type :id} :amount {:type 'String} :invoice_id {:type 'String} :payment_id {:type 'String} :payment {:type :payment} :invoice {:type :invoice}}} :user {:fields {:id {:type :id} :name {:type 'String} :role {:type 'String} :clients {:type '(list :client)}}} :account_client_override {:fields {:id {:type :id} :client {:type :client} :name {:type 'String}}} :account {:fields {:id {:type :id} :numeric_code {:type 'Int} :type {:type :ident} :applicability {:type :applicability} :account_set {:type 'String} :location {:type 'String} :name {:type 'String} :client_overrides {:type '(list :account_client_override)}}} :invoices_expense_accounts {:fields {:id {:type :id} :invoice_id {:type 'String} :account {:type :account} :location {:type 'String} :amount {:type 'String}}} :percentage_account {:fields {:id {:type :id} :account {:type :account} :location {:type 'String} :percentage {:type :percentage}}} :invoice {:fields {:id {:type :id} :original_id {:type 'Int} :client_identifier {:type 'String} :total {:type 'String} :outstanding_balance {:type 'String} :invoice_number {:type 'String} :status {:type 'String} :expense_accounts {:type '(list :invoices_expense_accounts)} :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}}} :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}}} :invoice_page {:fields {:invoices {:type '(list :invoice)} :outstanding {:type :money} :count {:type 'Int} :total {:type 'Int} :start {:type 'Int} :end {:type 'Int}}} :payment_page {:fields {:payments {:type '(list :payment)} :count {:type 'Int} :total {:type 'Int} :start {:type 'Int} :end {:type 'Int}}} :transaction_page {:fields {:data {:type '(list :transaction)} :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}}} :ledger_page {:fields {:journal_entries {:type '(list :journal_entry)} :count {:type 'Int} :total {:type 'Int} :start {:type 'Int} :end {:type 'Int}}} :sales_order_page {:fields {:sales_orders {:type '(list :sales_order)} :count {:type 'Int} :total {:type 'Int} :start {:type 'Int} :end {:type 'Int} :sales_order_total {:type :money} :sales_order_tax {:type :money}}} :expected_deposit_page {:fields {:expected_deposits {:type '(list :expected_deposit)} :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}}} :import_ledger_entry_result {:fields {:external_id {:type 'String} :error {:type 'String} :status {:type 'String}}} :import_ledger_result {:fields {:successful {:type '(list :import_ledger_entry_result)} :existing {:type '(list :import_ledger_entry_result)} :ignored {:type '(list :import_ledger_entry_result)} :errors {:type '(list :import_ledger_entry_result)}}} :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} :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} :potential_payment_matches {:type '(list :payment) :args {:transaction_id {:type :id}} :resolve :get-potential-payments} :potential_autopay_invoices_matches {:type '(list (list :invoice)) :args {:transaction_id {:type :id}} :resolve :get-potential-autopay-invoices-matches} :potential_unpaid_invoices_matches {:type '(list (list :invoice)) :args {:transaction_id {:type :id}} :resolve :get-potential-unpaid-invoices-matches} :potential_transaction_rule_matches {:type '(list :transaction_rule) :args {:transaction_id {:type :id}} :resolve :get-transaction-rule-matches} :balance_sheet {:type :balance_sheet :args {:client_id {:type :id} :include_comparison {:type 'Boolean} :date {:type :iso_date}} :resolve :get-balance-sheet} :profit_and_loss {:type :profit_and_loss_report :args {:client_id {:type :id} :periods {:type '(list :date_range)}} :resolve :get-profit-and-loss} :yodlee_provider_account_page {:type :yodlee_provider_account_page :args {:client_id {:type :id}} :resolve :get-yodlee-provider-account-page} :invoice_page {:type '(list :invoice_page) :args {: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} :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)}} :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} :accounts {:type '(list :account) :args {:account_set {:type 'String}} :resolve :get-accounts} :all_payments {:type '(list :payment) :args {:client_id {:type :id} :client_code {:type 'String} :original_id {:type 'Int} :statuses {:type '(list String)}} :resolve :get-all-payments} :all_expected_deposits {:type '(list :expected_deposit) :args {:client_id {:type :id} :client_code {:type 'String}} :resolve :get-all-expected-deposits} :all_sales_orders {:type '(list :sales_order) :args {:client_id {:type :id} :date_range {:type :date_range} :client_code {:type 'String}} :resolve :get-all-sales-orders} :yodlee_merchants {:type '(list :yodlee_merchant) :args {} :resolve :get-yodlee-merchants} :transaction_page {:type :transaction_page :args {:filters {:type :transaction_filters}} :resolve :get-transaction-page} :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}} :resolve :get-transaction-rule-page} :ledger_page {:type :ledger_page :args {:filters {:type :ledger_filters}} :resolve :get-ledger-page} :sales_order_page {:type :sales_order_page :args {:client_id {:type :id} :date_range {:type :date_range} :total_lte {:type :money} :total_gte {:type :money} :processor {:type :processor} :start {:type 'Int} :per_page {:type 'Int} :sort {:type '(list :sort_item)}} :resolve :get-sales-order-page} :expected_deposit_page {:type :expected_deposit_page :args {:client_id {:type :id} :date_range {:type :date_range} :total_lte {:type :money} :total_gte {:type :money} :start {:type 'Int} :per_page {:type 'Int} :sort {:type '(list :sort_item)}} :resolve :get-expected-deposit-page} :payment_page {:type '(list :payment_page) :args {:client_id {:type :id} :vendor_id {:type :id} :payment_type {:type :payment_type} :exact_match_id {:type :id} :date_range {:type :date_range} :amount_lte {:type :money} :amount_gte {:type :money} :check_number_like {:type 'String} :invoice_number {:type 'String} :start {:type 'Int} :per_page {:type 'Int} :sort {:type '(list :sort_item)}} :resolve :get-payment-page} :client {:type '(list :client) :resolve :get-client} :vendor {:type '(list :vendor) :resolve :get-vendor} :user {:type '(list :user) :resolve :get-user}} :input-objects { :sort_item {:fields {:sort_key {:type 'String} :sort_name {:type 'String} :asc {:type 'Boolean}}} :transaction_filters {:fields {:client_id {:type :id} :exact_match_id {:type :id} :vendor_id {:type :id} :bank_account_id {:type :id} :account_id {:type :id} :date_range {:type :date_range} :location {:type 'String} :amount_lte {:type :money} :amount_gte {:type :money} :description {:type 'String} :start {:type 'Int} :per_page {:type 'Int} :sort {:type '(list :sort_item)} :approval_status {:type :transaction_approval_status} :unresolved {:type 'Boolean}}} :ledger_filters {:fields {:client_id {:type :id} :vendor_id {:type :id} :account_id {:type :id} :amount_lte {:type :money} :amount_gte {:type :money} :bank_account_id {:type :id} :date_range {:type :date_range} :location {:type 'String} :from_numeric_code {:type 'Int} :to_numeric_code {:type 'Int} :start {:type 'Int} :per_page {:type 'Int} :only_external {:type 'Boolean} :external_id_like {:type 'String} :sort {:type '(list :sort_item)}}} :invoice_payment_amount {:fields {:invoice_id {:type :id} :amount {:type :money}}} :edit_location_match {:fields {:location {:type 'String} :match {:type 'String} :id {:type :id}}} :edit_forecasted_transaction {:fields {:identifier {:type 'String} :id {:type :id} :day_of_month {:type 'Int} :amount {:type :money}}} :date_range {:fields {:start {:type :iso_date} :end {:type :iso_date}}} :import_ledger_line_item {:fields {:account_identifier {:type 'String} :location {:type 'String} :debit {:type :money} :credit {:type :money}}} :import_ledger_entry {:fields {:source {:type 'String} :external_id {:type 'String} :client_code {:type 'String} :date {:type 'String} :vendor_name {:type 'String} :amount {:type :money} :note {:type 'String} :cleared_against {:type 'String} :line_items {:type '(list :import_ledger_line_item)}}} :edit_client {:fields {:id {:type :id} :name {:type 'String} :code {:type 'String} :signature_data {:type 'String} :email {:type 'String} :week_a_credits {:type :money} :week_a_debits {:type :money} :week_b_credits {:type :money} :week_b_debits {:type :money} :address {:type :add_address} :locations {:type '(list String)} :matches {:type '(list String)} :location_matches {:type '(list :edit_location_match)} :bank_accounts {:type '(list :edit_bank_account)} :forecasted_transactions {:type '(list :edit_forecasted_transaction)}}} :edit_bank_account {:fields {:id {:type :id} :code {:type 'String} :type {:type :bank_account_type} :start_date {:type :iso_date} :number {:type 'String} :check_number {:type 'Int} :visible {:type 'Boolean} :include_in_reports {:type 'Boolean} :sort_order {:type 'Int} :name {:type 'String} :bank_code {:type 'String} :routing {:type 'String} :bank_name {:type 'String} :locations {:type '(list String)} :yodlee_account_id {:type 'Int} :yodlee_account {:type 'Int}}} :edit_user {:fields {:id {:type :id} :name {:type 'String} :role {:type 'String} :clients {:type '(list String)}}} :add_contact {:fields {:id {:type :id} :name {:type 'String} :email {:type 'String} :phone {:type 'String}}} :add_address {:fields {: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_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_expense_account {:fields {:id {:type :id} :account_id {:type :id} :location {:type 'String} :amount {:type :money}}} :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_transaction {:fields {:id {:type :id} :vendor_id {:type :id} :forecast_match {:type :id} :approval_status {:type :transaction_approval_status} :accounts {:type '(list :edit_expense_account)}}} :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} :numeric_code {:type 'Int} :location {:type 'String} :account_set {:type 'String} :name {:type 'String} :client_overrides {:type '(list :edit_account_client_override)}}}} :enums {:payment_type {:values [{:enum-value :check} {:enum-value :cash} {:enum-value :debit}]} :processor {:values [{:enum-value :na} {:enum-value :doordash} {:enum-value :uber_eats} {:enum-value :grubhub}]} :tin_type {:values [{:enum-value :ein} {:enum-value :ssn}]} :type_1099 {:values [{:enum-value :none} {:enum-value :misc} {:enum-value :landlord}]} :invoice_status {:values [{:enum-value :paid} {:enum-value :unpaid} {:enum-value :voided}]} :bank_account_type {:values [{:enum-value :check} {:enum-value :credit} {:enum-value :cash}]} :applicability {:values [{:enum-value :global} {:enum-value :optional} {:enum-value :customized}]} :account_type {:values [{:enum-value :dividend} {:enum-value :expense} {:enum-value :asset} {:enum-value :liability} {:enum-value :equity} {:enum-value :revenue}]} :transaction_approval_status {:values [{:enum-value :approved} {:enum-value :unapproved} {:enum-value :requires_feedback} {:enum-value :excluded}]}} :mutations {: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} :bulk_change_transaction_status {:type :message :args {:filters {:type :transaction_filters} :status {:type :transaction_approval_status} :ids {:type '(list :id)}} :resolve :mutation/bulk-change-transaction-status} :delete_external_ledger {:type :message :args {:filters {:type :ledger_filters} :ids {:type '(list :id)}} :resolve :mutation/delete-external-ledger} :delete_transactions {:type :message :args {:filters {:type :transaction_filters} :ids {:type '(list :id)}} :resolve :mutation/delete-transactions} :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} :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} :print_checks {:type :check_result :args {:invoice_payments {:type '(list :invoice_payment_amount)} :bank_account_id {:type :id} :type {:type :payment_type} :client_id {:type :id}} :resolve :mutation/print-checks} :add_handwritten_check {:type :check_result :args {:invoice_payments {:type '(list :invoice_payment_amount)} :date {:type 'String} :check_number {:type 'Int} :bank_account_id {:type :id}} :resolve :mutation/add-handwritten-check} :edit_user {:type :user :args {:edit_user {:type :edit_user}} :resolve :mutation/edit-user} :edit_client {:type :client :args {:edit_client {:type :edit_client}} :resolve :mutation/edit-client} :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} :add_invoice {:type :invoice :args {:invoice {:type :add_invoice}} :resolve :mutation/add-invoice} :import_ledger {:type :import_ledger_result :args {:entries {:type '(list :import_ledger_entry)}} :resolve :mutation/import-ledger} :edit_invoice {:type :invoice :args {:invoice {:type :edit_invoice}} :resolve :mutation/edit-invoice} :upsert_account {:type :account :args {:account {:type :edit_account}} :resolve :mutation/upsert-account} :edit_transaction {:type :transaction :args {:transaction {:type :edit_transaction}} :resolve :mutation/edit-transaction} :match_transaction {:type :transaction :args {:transaction_id {:type :id} :payment_id {:type :id}} :resolve :mutation/match-transaction} :match_transaction_autopay_invoices {:type :transaction :args {:transaction_id {:type :id} :autopay_invoice_ids {:type '(list :id)}} :resolve :mutation/match-transaction-autopay-invoices} :match_transaction_unpaid_invoices {:type :transaction :args {:transaction_id {:type :id} :unpaid_invoice_ids {:type '(list :id)}} :resolve :mutation/match-transaction-unpaid-invoices} :match_transaction_rules {:type '(list :transaction) :args {:transaction_ids {:type '(list :id)} :all {:type 'Boolean} :transaction_rule_id {:type :id}} :resolve :mutation/match-transaction-rules} :unlink_transaction {:type :transaction :args {:transaction_id {:type :id}} :resolve :mutation/unlink-transaction} :void_invoice {:type :invoice :args {:invoice_id {:type :id}} :resolve :mutation/void-invoice} :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} :void_payment {:type :payment :args {:payment_id {:type :id}} :resolve :mutation/void-payment} :edit_expense_accounts {:type :invoice :args {:invoice_id {:type :id} :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-all-payments [context args value] (assert-admin (:id context)) (map ->graphql (first (d-checks/get-graphql (assoc (<-graphql args) :count Integer/MAX_VALUE))))) (defn get-all-sales-orders [context args value] (assert-admin (:id context)) (map ->graphql (first (d-sales-orders/get-graphql (assoc (<-graphql args) :count Integer/MAX_VALUE))))) (defn get-user [context args value] (assert-admin (:id context)) (let [users (d-users/get-graphql args)] (->graphql users))) (defn print-checks [context args value] (assert-can-see-client (:id context) (:client_id args)) (->graphql (gq-checks/print-checks (map (fn [i] {:invoice-id (:invoice_id i) :amount (:amount i)}) (:invoice_payments args)) (:client_id args) (:bank_account_id args) (:type args) (:id context)))) (defn get-expense-account-stats [context {:keys [client_id] } value] (let [result (cond-> {:query {:find ['?account '?account-name '(sum ?amount)] :in ['$] :where []} :args [(d/db (d/connect uri)) 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 (d/query))] (for [[account-id account-name total] result] {:account {:id account-id :name account-name} :total total}))) (defn categorize [x] (cond (<= x 0) :due (<= x 30 ) :due-30 (<= x 60 ) :due-60 :else :due-later)) (defn get-invoice-stats [context {:keys [client_id] } value] (let [result (cond-> {:query {:find ['?name '(sum ?outstanding-balance) '(sum ?total)] :in ['$] :where []} :args [(d/db (d/connect uri)) 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 (d/query)) 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 [context {:keys [client_id]} value] (when client_id (let [{:client/keys [week-a-credits week-a-debits week-b-credits week-b-debits forecasted-transactions ]} (d/pull (d/db (d/connect uri)) '[*] client_id) total-cash (reduce (fn [total [credit debit]] (- (+ total credit) debit)) 0.0 (d/query {: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 [(d/db (d/connect uri)) client_id]})) bills-due-soon (d/query {:query {:find '[?due ?outstanding ?invoice-number ?vendor-id] :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]]} :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/days 180)))]}) outstanding-checks (reduce + 0.0 (map first (d/query {: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 [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/days 180)))]}))) recent-fulfillments (d/query {: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 [(d/db (d/connect uri)) 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]] {:due (coerce/to-date-time due) :invoice_number invoice-number :vendor {:id vendor-id} :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-resolvers {:get-invoice-page gq-invoices/get-invoice-page :get-all-invoices gq-invoices/get-all-invoices :get-yodlee-provider-account-page gq-yodlee2/get-yodlee-provider-account-page :get-all-payments get-all-payments :get-all-expected-deposits gq-expected-deposit/get-all-expected-deposits :get-expected-deposit-page gq-expected-deposit/get-expected-deposit-page :get-all-sales-orders get-all-sales-orders :get-payment-page gq-checks/get-payment-page :get-potential-payments gq-checks/get-potential-payments :get-potential-autopay-invoices-matches gq-transactions/get-potential-autopay-invoices-matches :get-potential-unpaid-invoices-matches gq-transactions/get-potential-unpaid-invoices-matches :get-accounts gq-accounts/get-accounts :get-transaction-page gq-transactions/get-transaction-page :get-ledger-page gq-ledger/get-ledger-page :get-sales-order-page gq-sales-orders/get-sales-orders-page :get-balance-sheet gq-ledger/get-balance-sheet :get-profit-and-loss gq-ledger/get-profit-and-loss :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-client gq-clients/get-client :get-user get-user :mutation/add-handwritten-check gq-checks/add-handwritten-check :mutation/delete-transaction-rule gq-transaction-rules/delete-transaction-rule :mutation/print-checks print-checks :mutation/reject-invoices gq-invoices/reject-invoices :mutation/approve-invoices gq-invoices/approve-invoices :mutation/edit-user gq-users/edit-user :mutation/add-invoice gq-invoices/add-invoice :mutation/add-and-print-invoice gq-invoices/add-and-print-invoice :mutation/edit-invoice gq-invoices/edit-invoice :mutation/edit-transaction gq-transactions/edit-transaction :mutation/unlink-transaction gq-transactions/unlink-transaction :mutation/bulk-change-transaction-status gq-transactions/bulk-change-status :mutation/delete-external-ledger gq-ledger/delete-external-ledger :mutation/delete-transactions gq-transactions/delete-transactions :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/match-transaction gq-transactions/match-transaction :mutation/match-transaction-autopay-invoices gq-transactions/match-transaction-autopay-invoices :mutation/match-transaction-unpaid-invoices gq-transactions/match-transaction-unpaid-invoices :mutation/match-transaction-rules gq-transactions/match-transaction-rules :mutation/edit-client gq-clients/edit-client :mutation/upsert-vendor gq-vendors/upsert-vendor :mutation/upsert-account gq-accounts/upsert-account :mutation/merge-vendors gq-vendors/merge-vendors :mutation/void-invoice gq-invoices/void-invoice :mutation/unvoid-invoice gq-invoices/unvoid-invoice :mutation/unautopay-invoice gq-invoices/unautopay-invoice :mutation/void-payment gq-checks/void-check :mutation/edit-expense-accounts gq-invoices/edit-expense-accounts :mutation/import-ledger gq-ledger/import-ledger :get-vendor gq-vendors/get-graphql}) 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] (lc/with-context {:query q} (log/info "Executing query" 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 (warn-event "validation error" {:validation-error v :data (ex-data e)}) (throw e) #_{:errors [{:message v}]}) (do (error-event "query error" {:error e}) (throw e))))))))