(ns auto-ap.graphql.ledger (:require [auto-ap.datomic :refer [audit-transact-batch conn pull-many remove-nils]] [auto-ap.datomic.accounts :as a] [auto-ap.datomic.clients :as d-clients] [auto-ap.datomic.ledger :as l] [auto-ap.graphql.utils :refer [->graphql <-graphql assert-admin assert-can-see-client attach-tracing-resolvers result->page]] [auto-ap.ledger :refer [build-account-lookup]] [auto-ap.ledger.reports :as l-reports] [auto-ap.parse.util :as parse] [auto-ap.pdf.ledger :refer [print-balance-sheet print-cash-flows print-journal-detail-report print-pnl]] [auto-ap.time :as atime] [auto-ap.utils :refer [by dollars=]] [clj-time.coerce :as coerce] [clj-time.core :as t] [clojure.data.csv :as csv] [clojure.tools.logging :as log] [com.brunobonacci.mulog :as mu] [datomic.api :as dc] [iol-ion.tx :refer [random-tempid]] [auto-ap.solr :as solr]) (:import (org.apache.commons.codec.binary Base64))) (defn get-ledger-page [context args _] (let [args (assoc args :id (:id context)) [journal-entries journal-entries-count] (l/get-graphql (assoc (<-graphql (:filters args)) :id (:id context))) journal-entries (mapv (fn [je] (-> je (update :journal-entry/original-entity :db/id))) journal-entries)] (result->page journal-entries journal-entries-count :journal_entries (:filters args)))) (defn get-ledger-csv [context args _] (let [args (assoc args :id (:id context)) [journal-entries journal-entries-count] (l/get-graphql (assoc (<-graphql (:filters args)) :per-page Integer/MAX_VALUE :id (:id context))) ] {:csv_content_b64 (Base64/encodeBase64String (.getBytes (with-open [w (java.io.StringWriter.)] (csv/write-csv w (into [["Client" "Vendor" "Date" "Journal Entry" "Journal Entry Line" "Account Code" "Account Name" "Account Type" "Debit" "Credit" "Net"]] (->> journal-entries (mapcat (fn [j] (map (fn [li] (let [account-type (or (:db/ident (:account/type (:journal-entry-line/account li))) ({:bank-account-type/check :account-type/asset :bank-account-type/cash :account-type/asset :bank-account-type/credit :account-type/liability} (:db/ident (:bank-account/type (:journal-entry-line/account li)))))] [(-> j :journal-entry/client :client/code) (-> j :journal-entry/vendor :vendor/name) (atime/unparse (coerce/to-date-time (-> j :journal-entry/date)) atime/normal-date) (-> j :db/id) (-> li :db/id) (or (-> li :journal-entry-line/account :account/numeric-code) (-> li :journal-entry-line/account :bank-account/numeric-code)) (or (-> li :journal-entry-line/account :account/name) (-> li :journal-entry-line/account :bank-account/name)) (some-> account-type name ) (-> li :journal-entry-line/debit) (-> li :journal-entry-line/credit) (if (#{:account-type/asset :account-type/dividend :account-type/expense} account-type) (- (or (-> li :journal-entry-line/debit) 0.0) (or (-> li :journal-entry-line/credit) 0.0)) (- (or (-> li :journal-entry-line/credit) 0.0) (or (-> li :journal-entry-line/debit) 0.0))) ])) (:journal-entry/line-items j)) )))) :quote? (constantly true)) (.toString w))))})) (defn roll-up-until ([lookup-account all-ledger-entries end-date] (roll-up-until lookup-account all-ledger-entries end-date nil)) ([lookup-account all-ledger-entries end-date start-date] (->> all-ledger-entries (filter (fn [[d]] (if start-date (and (>= (compare d start-date) 0) (<= (compare d end-date) 0)) (<= (compare d end-date) 0)))) (reduce (fn [acc [_ _ account location debit credit]] (-> acc (update-in [[location account] :debit] (fnil + 0.0) debit) (update-in [[location account] :credit] (fnil + 0.0) credit) (update-in [[location account] :count] (fnil + 0) 1)) ) {}) (reduce-kv (fn [acc [location account-id] {:keys [debit credit count]}] (let [account (lookup-account account-id) account-type (:account_type account)] (conj acc (merge {:id (str account-id "-" location) :location (or location "") :count count :debits debit :credits credit :amount (if account-type (if (#{:account-type/asset :account-type/dividend :account-type/expense} account-type) (- debit credit) (- credit debit)) 0.0)} account)))) [])))) (defn full-ledger-for-client [client-id] (->> (dc/q {:find ['?d '?jel '?account '?location '?debit '?credit] :in ['$ '?client-id] :where '[[?e :journal-entry/client ?client-id] [?e :journal-entry/date ?d] [?e :journal-entry/line-items ?jel] (or-join [?e] (and [?e :journal-entry/original-entity ?i] (or-join [?e ?i] (and [?i :transaction/bank-account ?b] (or [?b :bank-account/include-in-reports true] (not [?b :bank-account/include-in-reports]))) (not [?i :transaction/bank-account]))) (not [?e :journal-entry/original-entity ])) [(get-else $ ?jel :journal-entry-line/account :account/unknown) ?account] [(get-else $ ?jel :journal-entry-line/debit 0.0) ?debit ] [(get-else $ ?jel :journal-entry-line/credit 0.0) ?credit] [(get-else $ ?jel :journal-entry-line/location "") ?location]]} (dc/db conn) client-id) (sort-by first))) (defn get-balance-sheet [context args _] (let [client-id (:client_id args) _ (assert-can-see-client (:id context) client-id) end-date (coerce/to-date (:date args)) comparable-date (coerce/to-date (:comparison_date args)) all-ledger-entries (full-ledger-for-client client-id) lookup-account (build-account-lookup client-id)] (log/info "Running balance sheet with " args) (cond-> {:balance-sheet-accounts (roll-up-until lookup-account all-ledger-entries end-date)} (:include_comparison args) (assoc :comparable-balance-sheet-accounts (roll-up-until lookup-account all-ledger-entries comparable-date)) true ->graphql))) (defn get-profit-and-loss [context args _] (let [client-id (:client_id args) client-ids (or (some-> client-id vector) (filter identity (:client_ids args))) _ (when (not (seq client-ids)) (throw (ex-info "Please select a client." {:validation-error "Please select a client."}))) _ (doseq [client-id client-ids] (assert-can-see-client (:id context) client-id)) _ (when (and (:include_deltas args) (:column_per_location args)) (throw (ex-info "Please select one of 'Include deltas' or 'Column per location'" {:validation-error "Please select one of 'Include deltas' or 'Column per location'"}))) all-ledger-entries (->> client-ids (map (fn [client-id] [client-id (full-ledger-for-client client-id)])) (into {})) lookup-account (->> client-ids (map (fn [client-id] [client-id (build-account-lookup client-id)])) (into {}))] (->graphql {:periods (->> (:periods args) (mapv (fn [{:keys [start end]}] {:accounts (mapcat #(roll-up-until (lookup-account %) (all-ledger-entries %) (coerce/to-date end) (coerce/to-date start) ) client-ids)})))}))) ;; profit and loss based off of index #_(defn get-profit-and-loss [context args _] (let [client-id (:client_id args) client-ids (or (some-> client-id vector) (filter identity (:client_ids args))) _ (when (not (seq client-ids)) (throw (ex-info "Please select a client." {:validation-error "Please select a client."}))) _ (doseq [client-id client-ids] (assert-can-see-client (:id context) client-id)) _ (when (and (:include_deltas args) (:column_per_location args)) (throw (ex-info "Please select one of 'Include deltas' or 'Column per location'" {:validation-error "Please select one of 'Include deltas' or 'Column per location'"}))) db (dc/db conn) all-used-account-locations (dc/q '[:find ?c ?a ?l :in $ [?c ...] :where (or-join [?c ?a ?l] (and [?a :account/numeric-code] (not [?a :account/location]) [?c :client/locations ?l]) (and [?a :account/numeric-code] [?a :account/location ?l] [?c :client/locations ?l]) (and [?c :client/bank-accounts ?a] [(ground "A") ?l]))] (dc/db conn) client-ids) lookup-account (->> client-ids (map (fn [client-id] [client-id (build-account-lookup client-id)])) (into {}))] (->graphql {:periods (->> (:periods args) (mapv (fn [{:keys [start end]}] (let [start (coerce/to-date start) end (coerce/to-date end)] {:accounts (mapcat (fn [[c a l]] (let [start-point (->> (dc/index-pull db {:index :avet :selector [:db/id :journal-entry-line/running-balance :journal-entry-line/client+account+location+date] :start [:journal-entry-line/client+account+location+date [c a l start]] :reverse true :limit 1}) (take-while (fn [result] (= [c a l] (take 3 (:journal-entry-line/client+account+location+date result))))) (drop-while (fn [{[_ _ _ date] :journal-entry-line/client+account+location+date}] (>= (compare date start) 0))) first) end-point (->> (dc/index-pull db {:index :avet :selector [:db/id :journal-entry-line/running-balance :journal-entry-line/client+account+location+date] :start [:journal-entry-line/client+account+location+date [c a l end]] :reverse true :limit 1}) (take-while (fn [result] (= [c a l] (take 3 (:journal-entry-line/client+account+location+date result))))) (take 1) (drop-while (fn [{[_ _ _ date] :journal-entry-line/client+account+location+date}] (>= (compare date end) 0))) first)] (when end-point [(merge {:id (str a "-" l) :location (or l "") :count 0 :debits 0 :credits 0 :amount (- (or (:journal-entry-line/running-balance end-point) 0.0) (or (:journal-entry-line/running-balance start-point) 0.0)) } ((lookup-account c) a))]))) all-used-account-locations)}))))}))) (defn profit-and-loss-pdf [context args value] (let [data (get-profit-and-loss context args value) result (print-pnl (:id context) args data)] (->graphql result))) (defn cash-flows-pdf [context args value] (let [data (get-profit-and-loss context args value) result (print-cash-flows (:id context) args data)] (->graphql result))) (defn balance-sheet-pdf [context args value] (let [data (get-balance-sheet context args value) result (print-balance-sheet (:id context) args data)] (->graphql result))) (defn assoc-error [f] (fn [entry] (try (f entry) (catch Exception e (println e) (assoc entry :error (.getMessage e) :status (or (:status (ex-data e)) :error)))))) (defn all-ids-not-locked [all-ids] (->> all-ids (dc/q '[:find ?t :in $ [?t ...] :where [?t :journal-entry/client ?c] [(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu] [?t :journal-entry/date ?d] [(>= ?d ?lu)]] (dc/db conn)) (map first))) (defn delete-external-ledger [context args _] (let [_ (assert-admin (:id context)) args (assoc args :id (:id context)) ids (some-> (:filters args) (assoc :only-external true) (<-graphql) (assoc :per-page Integer/MAX_VALUE) (#(l/raw-graphql-ids (dc/db conn) %)) :ids) specific-ids (l/filter-ids (:ids args)) all-ids (all-ids-not-locked (into (set ids) specific-ids))] (if (> (count all-ids) 1000) {:message (str "You can only delete 1000 ledger entries at a time.")} (do (log/info "Deleting " (count all-ids) args) (audit-transact-batch (map (fn [i] [:db/retractEntity i]) all-ids) (:id context)) {:message (str "Succesfully deleted " (count all-ids) " ledger entries.")})))) (defn import-ledger [context args _] (assert-admin (:id context)) (let [used-vendor-names (set (map :vendor_name (:entries args))) all-vendors (mu/trace ::get-all-vendors [] (->> (dc/q '[:find ?e :in $ [?name ...] :where [?e :vendor/name ?name]] (dc/db conn) used-vendor-names) (map first) (pull-many (dc/db conn) [:db/id :vendor/name]) (by :vendor/name))) client-locked-lookup (mu/trace ::get-all-clients [] (->> (dc/q '[:find ?code ?locked-until :in $ :where [?c :client/code ?code] [(get-else $ ?c :client/locked-until #inst "2000-01-01") ?locked-until]] (dc/db conn)) (into {}))) all-client-bank-accounts (mu/trace ::get-all-client-bank-accounts [] (->> (dc/q '[:find ?code ?ba-code :in $ :where [?c :client/code ?code] [?c :client/bank-accounts ?ba] [?ba :bank-account/code ?ba-code]] (dc/db conn)) (reduce (fn [acc [code ba-code]] (update acc code (fnil conj #{}) ba-code)) {}))) all-client-locations (mu/trace ::get-all-client-locations [] (->> (dc/q '[:find ?code ?location :in $ :where [?c :client/code ?code] [?c :client/locations ?location]] (dc/db conn)) (reduce (fn [acc [code ba-code]] (update acc code (fnil conj #{"HQ" "A"}) ba-code)) {}))) new-hidden-vendors (reduce (fn [new-vendors {:keys [vendor_name]}] (if (or (all-vendors vendor_name) (new-vendors vendor_name)) new-vendors (assoc new-vendors vendor_name {:vendor/name vendor_name :vendor/hidden true :db/id vendor_name}))) {} (:entries args)) _ (mu/trace ::upsert-new-vendors [] (audit-transact-batch (vec (vals new-hidden-vendors)) (:id context))) all-vendors (->> (dc/q '[:find ?e :in $ [?name ...] :where [?e :vendor/name ?name]] (dc/db conn) used-vendor-names) (map first) (pull-many (dc/db conn) [:db/id :vendor/name]) (by :vendor/name)) all-accounts (mu/trace ::get-all-accounts [] (transduce (map (comp str :account/numeric-code)) conj #{} (a/get-accounts))) transaction (mu/trace ::build-transaction [:count (count (:entries args))] (doall (map (assoc-error (fn [entry] (let [vendor (all-vendors (:vendor_name entry))] (when-not (client-locked-lookup (:client_code entry)) (throw (ex-info (str "Client '" (:client_code entry )"' not found.") {:status :error}) )) (when-not vendor (throw (ex-info (str "Vendor '" (:vendor_name entry) "' not found.") {:status :error}))) (when-not (re-find #"\d{1,2}/\d{1,2}/\d{4}" (:date entry)) (throw (ex-info (str "Date must be MM/dd/yyyy") {:status :error}))) (when-let [locked-until (client-locked-lookup (:client_code entry))] (when (and (not (t/after? (coerce/to-date-time (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry)))) (coerce/to-date-time locked-until))) (not (t/equal? (coerce/to-date-time (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry)))) (coerce/to-date-time locked-until)))) (throw (ex-info (str "Client's data is locked until " locked-until) {:status :error})))) (when-not (dollars= (reduce (fnil + 0.0 0.0) 0.0 (map :debit (:line_items entry))) (reduce (fnil + 0.0 0.0) 0.0 (map :credit (:line_items entry)))) (throw (ex-info (str "Debits '" (reduce (fnil + 0.0 0.0) 0 (map :debit (:line_items entry))) "' and credits '" (reduce (fnil + 0.0 0.0) 0 (map :credit (:line_items entry))) "' do not add up.") {:status :error}))) (when (dollars= (reduce (fnil + 0.0 0.0) 0.0 (map :debit (:line_items entry))) 0.0) (throw (ex-info (str "Cannot have ledger entries that total $0.00") {:status :ignored}))) (assoc entry :status :success :tx [:upsert-ledger (remove-nils {:journal-entry/source (:source entry) :journal-entry/client [:client/code (:client_code entry)] :journal-entry/date (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry))) :journal-entry/external-id (:external_id entry) :journal-entry/vendor (:db/id (all-vendors (:vendor_name entry))) :journal-entry/amount (:amount entry) :journal-entry/note (:note entry) :journal-entry/cleared-against (:cleared_against entry) :journal-entry/line-items (mapv (fn [ea] (let [debit (or (:debit ea) 0.0) credit (or (:credit ea) 0.0)] (when (and (not (get (get all-client-locations (:client_code entry)) (:location ea))) (not= "A" (:location ea))) (throw (ex-info (str "Location '" (:location ea) "' not found.") {:status :error}))) (when (and (<= debit 0.0) (<= credit 0.0)) (throw (ex-info (str "Line item amount " (or debit credit) " must be greater than 0.") {:status :error}))) (when (and (not (all-accounts (:account_identifier ea))) (not (get (get all-client-bank-accounts (:client_code entry)) (:account_identifier ea)))) (throw (ex-info (str "Account '" (:account_identifier ea) "' not found.") {:status :error}))) (let [matching-account (when (re-matches #"^[0-9]+$" (:account_identifier ea)) (a/get-account-by-numeric-code-and-sets (Integer/parseInt (:account_identifier ea)) ["default"]))] (when (and matching-account (:account/location matching-account) (not= (:account/location matching-account) (:location ea))) (throw (ex-info (str "Account '" (:account/numeric-code matching-account) "' requires location '" (:account/location matching-account) "' but got '" (:location ea) "'") {:status :error}))) (when (and matching-account (not (:account/location matching-account)) (= "A" (:location ea))) (throw (ex-info (str "Account '" (:account/numeric-code matching-account) "' cannot use location '" (:location ea) "'") {:status :error}))) (remove-nils (cond-> {:db/id (random-tempid) :journal-entry-line/location (:location ea) :journal-entry-line/debit (when (> debit 0) debit) :journal-entry-line/credit (when (> credit 0) credit)} matching-account (assoc :journal-entry-line/account (:db/id matching-account)) (not matching-account) (assoc :journal-entry-line/account [:bank-account/code (:account_identifier ea)])))))) (:line_items entry)) :journal-entry/cleared true})])))) (:entries args)))) errors (filter #(= (:status %) :error) transaction) ignored (filter #(= (:status %) :ignored) transaction) success (filter #(= (:status %) :success) transaction) retraction (mapv (fn [x] [:db/retractEntity [:journal-entry/external-id (:external_id x)]]) success) ignore-retraction (->> ignored (map :external_id ) (dc/q '[:find ?je :in $ [?ei ...] :where [?je :journal-entry/external-id ?ei]] (dc/db conn) ) (map first) (map (fn [je] [:db/retractEntity je])))] (log/info "manual ledger import has " (count success) " new rows") (log/info errors) (mu/trace ::retraction-tx [:count (count retraction)] (audit-transact-batch retraction (:id context))) (mu/trace ::ignore-retraction-tx [:count (count ignore-retraction)] (when (seq ignore-retraction) (audit-transact-batch ignore-retraction (:id context)))) #_(log/info (map :tx success)) (let [invalidated (mu/trace ::success-tx [:count (count success)] (for [[_ n] (:tempids (audit-transact-batch (map :tx success) (:id context)))] n))] (future ; (mu/log ::indexing-solr :count (count invalidated)) (mu/trace ::indexed-external-solr [:count (count invalidated)] (doseq [n invalidated] (solr/touch n))))) {:successful (map (fn [x] {:external_id (:external_id x)}) success) :ignored (map (fn [x] {:external_id (:external_id x)}) ignored) :existing [] :errors (map (fn [x] {:external_id (:external_id x) :error (:error x)}) errors)})) (defn get-journal-detail-report [context input _] (let [category-totals (atom {}) base-categories (into [] (for [client-id (:client_ids input) :let [_ (assert-can-see-client (:id context) client-id) account-lookup (build-account-lookup client-id) c (dc/pull (dc/db conn) '[:client/locations] client-id)] location (:client/locations c) category (:categories input) :let [category (<-graphql category) all-journal-entries (->> (get-ledger-page context {:filters {:client_id client-id :location location :date_range (:date_range input) :from_numeric_code (l-reports/min-numeric-code category ) :to_numeric_code (l-reports/max-numeric-code category ) :per_page Integer/MAX_VALUE}} nil) :journal_entries (mapcat (fn [je] (->> (je :line_items) (filter (fn [jel] (when-let [account (account-lookup (:id (:account jel)))] (and (l-reports/account-belongs-in-category? (:numeric_code account) category) (= location (:location jel))))) ) (map (fn [jel ] {:date (:date je) :debit (:debit jel) :credit (:credit jel) :description (or (:name (:vendor je)) (:alternate_description je)) :account (:account jel) :location (:location jel)}))))) (into [])) _ (swap! category-totals assoc-in [client-id location category] (- (or (reduce + 0.0 (map #(or (:credit %) 0.0) all-journal-entries)) 0.0) (or (reduce + 0.0 (map #(or (:debit %) 0.0) all-journal-entries)) 0.0)) ) journal-entries-by-account (group-by #(account-lookup (get-in % [:account :id])) all-journal-entries)] [account journal-entries] (conj (vec journal-entries-by-account) [nil all-journal-entries]) :let [journal-entries (first (reduce (fn [[acc last-je] je] (let [next-je (assoc je :running_balance (- (+ (or (:running_balance last-je 0.0) 0.0) (or (:credit je 0.0) 0.0)) (or (:debit je 0.0) 0.0)))] [(conj acc next-je) next-je])) [] (sort-by :date journal-entries)))]] {:category (->graphql category) :client_id client-id :location location :account (or account {:name (str "All " (name category))}) :journal_entries (when account journal-entries) :total (- (or (reduce + 0.0 (map #(or (:credit %) 0.0) journal-entries)) 0.0) (or (reduce + 0.0 (map #(or (:debit %) 0.0) journal-entries)) 0.0))})) result {:categories (into base-categories (for [client-id (:client_ids input) :let [_ (assert-can-see-client (:id context) client-id) account-lookup (build-account-lookup client-id) c (dc/pull (dc/db conn) '[:client/locations] client-id)] location (:client/locations c) line [{:client_id client-id :location location :account {:name "Gross Profit"} :journal_entries nil :total (+ (get-in @category-totals [client-id location :sales] 0.0) (get-in @category-totals [client-id location :cogs] 0.0) (get-in @category-totals [client-id location :payroll] 0.0))} {:client_id client-id :location location :account {:name "Overhead"} :journal_entries nil :total (+ (get-in @category-totals [client-id location :controllable] 0.0) (get-in @category-totals [client-id location :fixed-overhead] 0.0) (get-in @category-totals [client-id location :ownership-controllable] 0.0))} {:client_id client-id :location location :account {:name "Net Profit"} :journal_entries nil :total (+ (get-in @category-totals [client-id location :sales] 0.0) (get-in @category-totals [client-id location :cogs] 0.0) (get-in @category-totals [client-id location :payroll] 0.0) (get-in @category-totals [client-id location :controllable] 0.0) (get-in @category-totals [client-id location :fixed-overhead] 0.0) (get-in @category-totals [client-id location :ownership-controllable] 0.0))}]] line))}] result)) (defn journal-detail-report-pdf [context args value] (let [data (get-journal-detail-report context args value) result (print-journal-detail-report (:id context) args data)] (->graphql result))) (def objects {:balance_sheet_account {:fields {:id {:type 'String} :amount {:type 'String} :debits {:type :money} :credits {:type :money} :location {:type 'String} :client_id {:type :id} :count {:type 'Int} :numeric_code {:type 'Int} :account_type {:type :account_type} :name {:type 'String}}} :report_pdf {:fields {:url {:type 'String} :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)}}} :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)}}} :journal_detail_report_row {:fields {:client {:type :client} :description {:type 'String} :date {:type 'String} :account {:type :account} :location {:type 'String} :debit {:type 'String} :credit {:type 'String} :running_balance {:type :money}}} :ledger_page {:fields {:journal_entries {:type '(list :journal_entry)} :count {:type 'Int} :total {:type 'Int} :start {:type 'Int} :end {:type 'Int}}} :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)}}} :journal_detail_report_category {:fields {:category {:type :ledger_category} :account {:type :account} :total {:type :money} :client_id {:type :id} :location {:type 'String} :journal_entries {:type '(list :journal_detail_report_row)}}} :journal_detail_report {:fields {:categories {:type '(list :journal_detail_report_category)}}}}) (def queries {:balance_sheet {:type :balance_sheet :args {:client_id {:type :id} :include_comparison {:type 'Boolean} :date {:type :iso_date} :comparison_date {:type :iso_date}} :resolve :get-balance-sheet} :profit_and_loss {:type :profit_and_loss_report :args {:client_id {:type :id} :client_ids {:type '(list :id)} :periods {:type '(list :date_range)} :include_deltas {:type 'Boolean} :column_per_location {:type 'Boolean}} :resolve :get-profit-and-loss} :journal_detail_report {:type :journal_detail_report :args {:client_id {:type :id} :client_ids {:type '(list :id)} :date_range {:type :date_range} :categories {:type '(list :ledger_category)}} :resolve :get-journal-detail-report} :journal_detail_report_pdf {:type :report_pdf :args {:client_id {:type :id} :client_ids {:type '(list :id)} :date_range {:type :date_range} :categories {:type '(list :ledger_category)}} :resolve :journal-detail-report-pdf} :profit_and_loss_pdf {:type :report_pdf :args {:client_id {:type :id} :client_ids {:type '(list :id)} :periods {:type '(list :date_range)} :include_deltas {:type 'Boolean} :column_per_location {:type 'Boolean}} :resolve :profit-and-loss-pdf} :cash_flows_pdf {:type :report_pdf :args {:client_id {:type :id} :client_ids {:type '(list :id)} :periods {:type '(list :date_range)} :include_deltas {:type 'Boolean} :column_per_location {:type 'Boolean}} :resolve :cash-flows-pdf} :balance_sheet_pdf {:type :report_pdf :args {:client_id {:type :id} :include_comparison {:type 'Boolean} :date {:type :iso_date} :comparison_date {:type :iso_date}} :resolve :balance-sheet-pdf} :ledger_page {:type :ledger_page :args {:filters {:type :ledger_filters}} :resolve :get-ledger-page} :ledger_csv {:type :csv :args {:filters {:type :ledger_filters}} :resolve :get-ledger-csv}}) (def mutations {:import_ledger {:type :import_ledger_result :args {:entries {:type '(list :import_ledger_entry)}} :resolve :mutation/import-ledger} :delete_external_ledger {:type :message :args {:filters {:type :ledger_filters} :ids {:type '(list :id)}} :resolve :mutation/delete-external-ledger}}) (def input-objects {:numeric_code_range {:fields {:from {:type 'Int} :to {:type 'Int}}} :ledger_filters {:fields {:client_id {:type :id} :vendor_id {:type :id} :account_id {:type :id} :exact_match_id {:type :id} :amount_lte {:type :money} :amount_gte {:type :money} :bank_account_id {:type :id} :date_range {:type :date_range} :location {:type 'String} :locations {:type '(list String)} :numeric_code {:type '(list :numeric_code_range)} :start {:type 'Int} :per_page {:type 'Int} :only_external {:type 'Boolean} :external_id_like {:type 'String} :source {:type 'String} :sort {:type '(list :sort_item)}}} :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)}}} }) (def enums {:ledger_category {:values [{:enum-value :sales} {:enum-value :cogs} {:enum-value :payroll} {:enum-value :controllable} {:enum-value :fixed_overhead} {:enum-value :ownership_controllable}]}}) (def resolvers {:get-ledger-page get-ledger-page :get-balance-sheet get-balance-sheet :get-profit-and-loss get-profit-and-loss :profit-and-loss-pdf profit-and-loss-pdf :cash-flows-pdf cash-flows-pdf :journal-detail-report-pdf journal-detail-report-pdf :balance-sheet-pdf balance-sheet-pdf :get-ledger-csv get-ledger-csv :get-journal-detail-report get-journal-detail-report :mutation/delete-external-ledger delete-external-ledger :mutation/import-ledger import-ledger}) (defn attach [schema] (-> (merge-with merge schema {:objects objects :queries queries :mutations mutations :input-objects input-objects :enums enums}) (attach-tracing-resolvers resolvers)))