Add vendor pre-population for bulk code and individual edit forms

- Add vendor-changed HTMX handlers for both bulk code and individual edit
- Pre-populate default account at 100% when vendor is selected and no accounts exist
- Fix render-accounts-section to render from step-params correctly
- Change bulk code vendor-changed from hx-get to hx-post to include form data
- Add routes for vendor-changed endpoints
- Update e2e tests to cover vendor pre-population
- Run lein cljfmt fix across codebase
This commit is contained in:
2026-05-21 14:45:19 -07:00
parent 8bd0cee1b1
commit ba87805d4c
210 changed files with 8694 additions and 9627 deletions

View File

@@ -18,7 +18,6 @@
[iol-ion.tx :refer [random-tempid]]
[com.brunobonacci.mulog :as mu]))
(defn get-all-graphql [context args _]
(assert-admin (:id context))
(let [args (assoc args :id (:id context))

View File

@@ -97,7 +97,6 @@
[:line {:line-width 0.15 :color [50 50 50]}]]
[:cell {:colspan 3}]]
[[:cell {:size 9 :leading 11.5} "\n\n\n\n\nMEMO"]
[:cell {:colspan 5 :leading 11.5} (split-memo memo)
[:line {:line-width 0.15 :color [50 50 50]}]]
@@ -186,8 +185,6 @@
:payment/pdf-data
(edn/read-string)
make-check-pdf)]
(s3/put-object :bucket-name (:data-bucket env)
:key (:payment/s3-key check)
@@ -277,7 +274,6 @@
(conj payment)
(into (invoice-payments invoices invoice-amounts)))))
(defmethod invoices->entities :payment-type/debit [invoices vendor client bank-account type index invoice-amounts date]
(when (<= (->> invoices
(map (comp invoice-amounts :db/id))
@@ -297,7 +293,6 @@
(conj payment)
(into (invoice-payments invoices invoice-amounts)))))
(defmethod invoices->entities :payment-type/balance-credit [invoices invoice-amounts]
(when (<= (->> invoices
(map (comp invoice-amounts :db/id))
@@ -488,7 +483,6 @@
{:s3-url nil
:invoices (d-invoices/get-multi (map :invoice_id (:invoice_payments args)))})))
(defn void-payment [context {id :payment_id} _]
(let [check (d-checks/get-by-id id)]
(assert (or (= :payment-status/pending (:payment/status check))
@@ -549,7 +543,6 @@
:invoice-status/unpaid)}]]))))))))
id))
(defn void-payments [context args _]
(assert-admin (:id context))
(let [args (assoc args :clients (:clients context))
@@ -607,7 +600,6 @@
0.001))
invoices)
total-to-pay (reduce + 0 (map :invoice/outstanding-balance invoices-to-be-paid))
_ (when (<= total-to-pay 0.001)
(assert-failure "You must select invoices that need to be paid."))
@@ -637,8 +629,6 @@
[total-to-pay []])))
(into {}))
vendor-id (:db/id (:invoice/vendor (first invoices)))
payment {:db/id (str vendor-id)
:payment/amount total-to-pay
@@ -751,7 +741,6 @@
{:enum-value :pending}
{:enum-value :cleared}]}})
(def resolvers
{:get-potential-payments get-potential-payments
:get-payment-page get-payment-page

View File

@@ -19,9 +19,9 @@
(defn get-admin-client [context {:keys [id]} _]
(assert-admin (:id context))
(->graphql
(-> (d-clients/get-by-id id)
(update :client/bank-accounts (fn [bas]
(map #(set/rename-keys % {:bank-account/use-date-instead-of-post-date? :use-date-instead-of-post-date}) bas))))))
(-> (d-clients/get-by-id id)
(update :client/bank-accounts (fn [bas]
(map #(set/rename-keys % {:bank-account/use-date-instead-of-post-date? :use-date-instead-of-post-date}) bas))))))
(defn get-client-page [context args _]
(assert-admin (:id context))
@@ -29,7 +29,7 @@
[clients clients-count] (d-clients/get-graphql-page (assoc (<-graphql (:filters args))
:clients (:clients context)))
clients (->> clients
(map (fn [c]
(update c :client/bank-accounts (fn [bas]
(map #(set/rename-keys % {:bank-account/use-date-instead-of-post-date? :use-date-instead-of-post-date}) bas)))))
@@ -47,13 +47,6 @@
bank-accounts))))))]
(result->page clients clients-count :clients (:filters args))))
(def objects
{:location_match
{:fields {:location {:type 'String}
@@ -102,15 +95,13 @@
:yodlee_provider_accounts {:type '(list :yodlee_provider_account)}
:plaid_items {:type '(list :plaid_item)}}}
:client_page
:client_page
{:fields {:clients {:type '(list :client)}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
:bank_account
{:fields {:id {:type :id}
:integration_status {:type :integration_status}
@@ -139,9 +130,7 @@
:forecasted_transaction {:fields {:identifier {:type 'String}
:id {:type :id}
:day_of_month {:type 'Int}
:amount {:type :money}}}
})
:amount {:type :money}}}})
(def queries
{:client {:type '(list :client)
@@ -158,12 +147,12 @@
{})
(def input-objects
{ :client_filters
{:client_filters
{:fields {:code {:type 'String}
:name_like {:type 'String}
:start {:type 'Int}
:per_page {:type 'Int}
:sort {:type '(list :sort_item)}}} })
:sort {:type '(list :sort_item)}}}})
(def enums
{:bank_account_type {:values [{:enum-value :check}
@@ -173,11 +162,10 @@
(def resolvers
{:get-client get-client
:get-admin-client get-admin-client
:get-client-page get-client-page })
:get-client-page get-client-page})
(defn attach [schema]
(->
(->
(merge-with merge schema
{:objects objects
:queries queries

View File

@@ -11,7 +11,7 @@
(defn get-all-expected-deposits [context args _]
(assert-admin (:id context))
(map
(comp ->graphql status->graphql)
(comp ->graphql status->graphql)
(first (d-expected-deposit/get-graphql (assoc (<-graphql args) :count Integer/MAX_VALUE)))))
(defn get-expected-deposit-page [context args _]

View File

@@ -17,15 +17,14 @@
{:name name
:id id}))
(def objects
{:ezcater_caterer {:fields {:name {:type 'String}
:id {:type :id}}}})
(def queries
{:search_ezcater_caterer {:type '(list :search_result)
:args {:query {:type 'String}}
:resolve :search-ezcater-caterer}})
:args {:query {:type 'String}}
:resolve :search-ezcater-caterer}})
(def enums
{})

View File

@@ -40,7 +40,6 @@
(merge-query {:query {:find ['?e]
:where ['[?e :import-batch/date]]}}))]
(cond->> (query2 query)
true (apply-sort-3 args)
true (apply-pagination args))))
@@ -66,9 +65,8 @@
(map #(update % :import-batch/date coerce/to-date-time)))
matching-count :data args)))
(defn attach [schema]
(->
(->
(merge-with merge schema
{:objects {:import_batch {:fields {:user_name {:type 'String}
:id {:type :id}
@@ -83,12 +81,10 @@
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
}
:end {:type 'Int}}}}
:queries {:import_batch_page {:type :import_batch_page
:args {:filters {:type :import_batch_filters}}
:resolve :get-import-batch-page}}
:mutations {}
:input-objects {:import_batch_filters {:fields {:start {:type 'Int}

View File

@@ -7,6 +7,6 @@
(defn get-intuit-bank-accounts [context _ _]
(assert-admin (:id context))
(->graphql (map first (dc/q '[:find (pull ?e [*])
:in $
:where [?e :intuit-bank-account/external-id]]
(dc/db conn)))))
:in $
:where [?e :intuit-bank-account/external-id]]
(dc/db conn)))))

View File

@@ -174,8 +174,6 @@
(let [error (str "Expense account total (" expense-account-total ") does not equal invoice total (" total ")")]
(throw (ex-info error {:validation-error error}))))))
(defn add-invoice [context {{:keys [expense_accounts client_id vendor_id] :as in} :invoice} _]
(assert-no-conflicting in)
(assert-can-see-client (:id context) client_id)
@@ -193,8 +191,6 @@
(when-not ((set (map :db/id (:client/bank-accounts (d-clients/get-by-id client-id)))) bank-account-id)
(throw (ex-info (str "Bank account does not belong to client") {:validation-error "Bank account does not belong to client."}))))
(defn add-and-print-invoice [context {{:keys [total client_id vendor_id] :as in} :invoice bank-account-id :bank_account_id type :type} _]
(mu/trace ::validating-invoice [:invoice in]
(do
@@ -261,7 +257,6 @@
(-> (d-invoices/get-by-id id) (->graphql (:id context)))))
(defn get-ids-matching-filters [args]
(let [ids (some-> args
:filters
@@ -448,8 +443,6 @@
[])]
accounts)))
(defn bulk-change-invoices [context args _]
(assert-admin (:id context))
(when-not (:client_id args)

View File

@@ -38,9 +38,9 @@
_ (when (:client_id (:filters args))
(assert-can-see-client (:id context) (:client_id (:filters args))))
clients (or (and (:client_id (:filters args))
[{:db/id (:client_id (:filters args))}])
(:clients context))
[{:db/id (:client_id (:filters args))}])
(:clients context))
[journal-entries journal-entries-count] (l/get-graphql (assoc (<-graphql (:filters args))
:clients clients))
@@ -55,12 +55,10 @@
(let [args (assoc args :id (:id context))
[journal-entries journal-entries-count] (l/get-graphql (assoc (<-graphql (:filters args))
:per-page Integer/MAX_VALUE
:clients (:clients context)))
:clients (:clients context)))]
]
{:csv_content_b64 (Base64/encodeBase64String
(.getBytes
(.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"]]
@@ -83,22 +81,19 @@
(-> 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 )
(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))
))))
(- (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))
@@ -107,57 +102,56 @@
(filter (fn [[d]]
(if start-date
(and
(>= (compare d start-date) 0)
(<= (compare d end-date) 0))
(>= (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))
)
{})
(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))))
[]))))
(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)
(->> (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 _]
@@ -180,30 +174,29 @@
[client-id (build-account-lookup client-id)]))
(into {}))]
(alog/info ::balance-sheet :params args)
(cond-> {:balance-sheet-accounts (mapcat
#(roll-up-until (lookup-account %) (all-ledger-entries %) end-date )
client-ids)
}
#(roll-up-until (lookup-account %) (all-ledger-entries %) end-date)
client-ids)}
(:include_comparison args) (assoc :comparable-balance-sheet-accounts (mapcat
#(roll-up-until (lookup-account %) (all-ledger-entries %) comparable-date )
client-ids))
#(roll-up-until (lookup-account %) (all-ledger-entries %) comparable-date)
client-ids))
true ->graphql)))
(defn get-profit-and-loss-raw [client-ids periods]
(let [ all-ledger-entries (->> client-ids
(map (fn [client-id]
[client-id (full-ledger-for-client client-id)]))
(into {}))
(let [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
(->graphql {:periods
(->> periods
(mapv (fn [{:keys [start end]}]
{:accounts (mapcat
#(roll-up-until (lookup-account %) (all-ledger-entries %) (coerce/to-date end) (coerce/to-date start) )
#(roll-up-until (lookup-account %) (all-ledger-entries %) (coerce/to-date end) (coerce/to-date start))
client-ids)})))})))
(defn get-profit-and-loss [context args _]
@@ -216,12 +209,9 @@
(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'"}))) ]
(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'"})))]
(get-profit-and-loss-raw client-ids (:periods args))))
;; profit and loss based off of index
#_(defn get-profit-and-loss [context args _]
(let [client-id (:client_id args)
@@ -239,17 +229,17 @@
: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])
[?a :account/numeric-code]
(not [?a :account/location])
[?c :client/locations ?l])
(and
[?c :client/bank-accounts ?a]
[(ground "A") ?l]))]
[?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
@@ -257,49 +247,48 @@
[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)}))))})))
{: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)
@@ -320,10 +309,9 @@
(->graphql result)))
(defn assoc-error [f]
(fn [entry]
(try
(try
(f entry)
(catch Exception e
(assoc entry :error (.getMessage e)
@@ -333,13 +321,13 @@
(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))
: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 _]
@@ -353,8 +341,8 @@
(#(l/raw-graphql-ids (dc/db conn) %))
:ids)
_ (alog/info ::trying-to-delete
:count (count ids)
:sample (take 3 ids))
:count (count ids)
:sample (take 3 ids))
specific-ids (l/filter-ids (:ids args))
all-ids (all-ids-not-locked (into (set ids) specific-ids))]
(if (> (count all-ids) 1000)
@@ -364,7 +352,7 @@
(audit-transact-batch
(map (fn [i]
[:db/retractEntity i])
all-ids)
all-ids)
(:id context))
{:message (str "Succesfully deleted " (count all-ids) " ledger entries.")}))))
@@ -372,15 +360,15 @@
(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)))
[]
(->> (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 $
@@ -389,18 +377,18 @@
(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))
{})))
[]
(->> (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
@@ -409,160 +397,158 @@
[?c :client/locations ?location]]
(dc/db conn))
(reduce
(fn [acc [code ba-code]]
(update acc code (fnil conj #{"HQ" "A"}) ba-code))
{})))
(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))
(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)))
[]
(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)
: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)
[: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}))))
: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))))
(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 )
(map :external_id)
(dc/q '[:find ?je
:in $ [?ei ...]
:where [?je :journal-entry/external-id ?ei]]
(dc/db conn)
)
:in $ [?ei ...]
:where [?je :journal-entry/external-id ?ei]]
(dc/db conn))
(map first)
(map (fn [je] [:db/retractEntity je])))]
(alog/info ::manual-import
:errors (count errors)
:sample (take 3 errors))
(mu/trace ::retraction-tx
[:count (count retraction)]
(audit-transact-batch retraction (:id context)))
[: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))))
(let [invalidated
[:count (count ignore-retraction)]
(when (seq ignore-retraction)
(audit-transact-batch ignore-retraction (:id context))))
(let [invalidated
(mu/trace ::success-tx
[:count (count success)]
(for [[_ n] (:tempids (audit-transact-batch (map :tx success) (:id context)))]
@@ -573,7 +559,7 @@
[: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)})
@@ -582,7 +568,6 @@
: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 []
@@ -597,20 +582,19 @@
:clients [{:db/id client-id}])
{:filters {: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 )
: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 ]
(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)
@@ -621,18 +605,18 @@
(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)) )
(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)))]]
(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
@@ -641,7 +625,7 @@
: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
result {:categories
(into base-categories
(for [client-id (:client_ids input)
:let [_ (assert-can-see-client (:id context) client-id)
@@ -675,15 +659,12 @@
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}
@@ -847,7 +828,7 @@
(def input-objects
{:numeric_code_range
{:fields {:from {:type 'Int}
:to {:type 'Int}}}
:to {:type 'Int}}}
:ledger_filters
{:fields {:client_id {:type :id}
:vendor_id {:type :id}
@@ -874,25 +855,23 @@
:credit {:type :money}}}
:import_ledger_entry
{:fields {:source {:type 'String}
:external_id {: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)}}}
})
:line_items {:type '(list :import_ledger_line_item)}}}})
(def enums
{:ledger_category {:values [{:enum-value :sales}
{:enum-value :cogs}
{:enum-value :payroll}
{: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

View File

@@ -21,13 +21,11 @@
:name (first name)}))
[]))
(defn attach [schema]
(->
(->
(merge-with merge schema
{:objects {:plaid_link_result
{:fields {:token {:type 'String}} }
{:fields {:token {:type 'String}}}
:plaid_item
{:fields {:external_id {:type 'String}
@@ -50,7 +48,7 @@
:name {:type 'String}
:number {:type 'String}}}}
:queries {:search_plaid_merchants {:type '(list :plaid_merchant)
:args {:query {:type 'String}}
:resolve :search-plaid-merchants}}})
:args {:query {:type 'String}}
:resolve :search-plaid-merchants}}})
(attach-tracing-resolvers {:search-plaid-merchants search-merchants})))

View File

@@ -1,6 +1,6 @@
(ns auto-ap.graphql.sales-orders
(:require [auto-ap.datomic.sales-orders :as d-sales-orders2]
[auto-ap.graphql.utils :refer [->graphql <-graphql result->page assert-admin] ]
[auto-ap.graphql.utils :refer [->graphql <-graphql result->page assert-admin]]
[com.walmartlabs.lacinia.util :refer [attach-resolvers]]
[auto-ap.graphql.utils :refer [attach-tracing-resolvers]]))
@@ -14,19 +14,18 @@
(defn get-all-sales-orders [context args _]
(assert-admin (:id context))
(map
->graphql
(first (d-sales-orders2/get-graphql (assoc (<-graphql args) :count Integer/MAX_VALUE)))))
->graphql
(first (d-sales-orders2/get-graphql (assoc (<-graphql args) :count Integer/MAX_VALUE)))))
(def objects
{: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}}}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}
:sales_order_total {:type :money}
:sales_order_tax {:type :money}}}
:sales_order
{:fields {:id {:type :id}
@@ -93,8 +92,7 @@
(def resolvers
{:get-all-sales-orders get-all-sales-orders
:get-sales-order-page get-sales-orders-page
})
:get-sales-order-page get-sales-orders-page})
(defn attach [schema]
(->

View File

@@ -29,9 +29,8 @@
(defn get-transaction-rule-matches [context args _]
(if (= "admin" (:user/role (:id context)))
(let [transaction (update (d-transactions/get-by-id (:transaction_id args)) :transaction/date c/to-date)
all-rules (tr/get-all-for-client (:db/id (:transaction/client transaction)))
all-rules (tr/get-all-for-client (:db/id (:transaction/client transaction)))]
]
(mu/log ::counted
:count (count all-rules))
(doto (map ->graphql (rm/get-matching-rules transaction all-rules)) (#(println (count %)))))
@@ -43,7 +42,7 @@
:account account_id
:location location})
(defn delete-transaction-rule [context {:keys [transaction_rule_id ]} _]
(defn delete-transaction-rule [context {:keys [transaction_rule_id]} _]
(assert-admin (:id context))
(let [existing-transaction-rule (tr/get-by-id transaction_rule_id)]
(when-not (:transaction-rule/description existing-transaction-rule)
@@ -59,62 +58,59 @@
(. java.util.regex.Pattern (compile description java.util.regex.Pattern/CASE_INSENSITIVE))
(catch Exception e
(throw (ex-info (ex-message e) {:validation-error (ex-message e)}))))
_ (when-not (dollars= 1.0 account-total)
_ (when-not (dollars= 1.0 account-total)
(let [error (str "Account total (" account-total ") does not reach 100%")]
(throw (ex-info error {:validation-error error}))))
_ (when (and (str/blank? description)
(nil? yodlee_merchant_id))
(nil? yodlee_merchant_id))
(let [error (str "You must provide a description or a yodlee merchant")]
(throw (ex-info error {:validation-error error}))))
_ (doseq [a accounts
:let [{:keys [:account/location :account/name]} (dc/pull (dc/db conn) [:account/location :account/name] (:account_id a))
client (dc/pull (dc/db conn) [:client/locations] client_id)
]]
client (dc/pull (dc/db conn) [:client/locations] client_id)]]
(when (and location (not= location (:location a)))
(let [err (str "Account " name " uses location " (:location a) ", but is supposed to be " location)]
(throw (ex-info err {:validation-error err}) )))
(throw (ex-info err {:validation-error err}))))
(when (and (not location)
(not (get (into #{"Shared"} (:client/locations client))
(:location a))))
(let [err (str "Account " name " uses location " (:location a) ", but doesn't belong to the client.")]
(throw (ex-info err {:validation-error err}) ))))
(throw (ex-info err {:validation-error err})))))
rule-id (if id
id
"transaction-rule")
transaction [[:upsert-entity #:transaction-rule {:db/id (or rule-id (random-tempid))
:description description
:note note
:client client_id
:bank-account bank_account_id
:yodlee-merchant yodlee_merchant_id
:dom-lte dom_lte
:dom-gte dom_gte
:amount-lte amount_lte
:amount-gte amount_gte
:vendor vendor_id
:transaction-approval-status
(some->> transaction_approval_status
name
snake->kebab
(keyword "transaction-approval-status"))
:transaction-rule/accounts (map transaction-rule-account->entity accounts)}]]
:description description
:note note
:client client_id
:bank-account bank_account_id
:yodlee-merchant yodlee_merchant_id
:dom-lte dom_lte
:dom-gte dom_gte
:amount-lte amount_lte
:amount-gte amount_gte
:vendor vendor_id
:transaction-approval-status
(some->> transaction_approval_status
name
snake->kebab
(keyword "transaction-approval-status"))
:transaction-rule/accounts (map transaction-rule-account->entity accounts)}]]
transaction-result (audit-transact transaction (:id context))]
(-> (tr/get-by-id (or (-> transaction-result :tempids (get "transaction-rule"))
id))
id))
((ident->enum-f :transaction-rule/transaction-approval-status))
(->graphql))))
(defn -test-transaction-rule [id {:keys [:transaction-rule/description :transaction-rule/client :transaction-rule/bank-account :transaction-rule/amount-lte :transaction-rule/amount-gte :transaction-rule/dom-lte :transaction-rule/dom-gte :transaction-rule/yodlee-merchant]} include-coded? count]
(let [query (cond-> {:query {:find ['(pull ?e [* {:transaction/client [:client/name]
:transaction/bank-account [:bank-account/name]
:transaction/payment [:db/id]}
])]
:in ['$ ]
:transaction/payment [:db/id]}])]
:in ['$]
:where []}
:args [(dc/db conn)]}
:args [(dc/db conn)]}
description
(merge-query {:query {:in ['?descr]
:where ['[(iol-ion.query/->pattern ?descr) ?description-regex]]}
@@ -170,23 +166,22 @@
:where ['[?e :transaction/client ?client-id]]}
:args [(:db/id client)]})
(not include-coded?)
(merge-query {:query {:where ['[or [?e :transaction/approval-status :transaction-approval-status/unapproved]
[(missing? $ ?e :transaction/approval-status)]]]}})
true
(merge-query {:query {:where ['[?e :transaction/id]]}}))]
(->>
(query2 query)
(transduce (comp
(take (or count 15))
(map first)
(map #(dissoc % :transaction/id))
(map (fn [x]
(update x :transaction/date c/from-date)))
(map ->graphql))
conj []))))
(->>
(query2 query)
(transduce (comp
(take (or count 15))
(map first)
(map #(dissoc % :transaction/id))
(map (fn [x]
(update x :transaction/date c/from-date)))
(map ->graphql))
conj []))))
(defn test-transaction-rule [{:keys [id]} {{:keys [description client_id bank_account_id amount_lte amount_gte dom_lte dom_gte yodlee_merchant_id]} :transaction_rule} _]
(assert-admin id)
@@ -200,7 +195,6 @@
:yodlee-merchant (when yodlee_merchant_id {:db/id yodlee_merchant_id})}
true 15))
(defn run-transaction-rule [{:keys [id]} {:keys [transaction_rule_id count]} _]
(assert-admin id)
(-test-transaction-rule id (tr/get-by-id transaction_rule_id) false count))

View File

@@ -66,14 +66,14 @@
(defn get-ids-matching-filters [args]
(alog/info ::getting-ids-matching-filters
:args args)
:args args)
(let [ids (some-> (:filters args)
(assoc :clients (:clients args))
(assoc :id (:id args))
(<-graphql)
(update :approval-status enum->keyword "transaction-approval-status")
(assoc :per-page Integer/MAX_VALUE)
(d-transactions/raw-graphql-ids )
(d-transactions/raw-graphql-ids)
:ids)
specific-ids (d-transactions/filter-ids (seq (:ids args)))]
(if (seq (:ids args))
@@ -83,13 +83,13 @@
(defn all-ids-not-locked [all-ids]
(->> all-ids
(dc/q '[:find ?t
:in $ [?t ...]
:where
[?t :transaction/client ?c]
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
[?t :transaction/date ?d]
[(>= ?d ?lu)]]
(dc/db conn))
:in $ [?t ...]
:where
[?t :transaction/client ?c]
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
[?t :transaction/date ?d]
[(>= ?d ?lu)]]
(dc/db conn))
(map first)))
(defn bulk-change-status [context args _]
(let [_ (assert-admin (:id context))
@@ -98,47 +98,46 @@
all-ids-not-locked)]
(alog/info ::bulk-change-status
:count (count all-ids)
:sample (take 3 all-ids)
:status (:status args)
)
:count (count all-ids)
:sample (take 3 all-ids)
:status (:status args))
(audit-transact-batch
(->> all-ids
(mapv (fn [t]
[:upsert-transaction {:db/id t
:transaction/approval-status (enum->keyword (:status args) "transaction-approval-status")}])))
(:id context))
{:message (str "Succesfully changed " (count all-ids) " transactions to be " (name (:status args) ) ".")}))
{:message (str "Succesfully changed " (count all-ids) " transactions to be " (name (:status args)) ".")}))
;; TODO very similar to rule-matching
(defn maybe-code-accounts [transaction account-rules valid-locations]
(with-precision 2
(let [accounts (vec (mapcat
(fn [ar]
(let [cents-to-distribute (int (Math/round (Math/abs (* (:percentage ar)
(:transaction/amount transaction)
100))))]
(if (= "Shared" (:location ar))
(->> valid-locations
(map
(fn [cents location]
{:db/id (random-tempid)
:transaction-account/account (:account_id ar)
:transaction-account/amount (* 0.01 cents)
:transaction-account/location location})
(rm/spread-cents cents-to-distribute (count valid-locations))))
[(cond-> {:db/id (random-tempid)
:transaction-account/account (:account_id ar)
:transaction-account/amount (* 0.01 cents-to-distribute)}
(:location ar) (assoc :transaction-account/location (:location ar)))])))
account-rules))
(fn [ar]
(let [cents-to-distribute (int (Math/round (Math/abs (* (:percentage ar)
(:transaction/amount transaction)
100))))]
(if (= "Shared" (:location ar))
(->> valid-locations
(map
(fn [cents location]
{:db/id (random-tempid)
:transaction-account/account (:account_id ar)
:transaction-account/amount (* 0.01 cents)
:transaction-account/location location})
(rm/spread-cents cents-to-distribute (count valid-locations))))
[(cond-> {:db/id (random-tempid)
:transaction-account/account (:account_id ar)
:transaction-account/amount (* 0.01 cents-to-distribute)}
(:location ar) (assoc :transaction-account/location (:location ar)))])))
account-rules))
accounts (mapv
(fn [a]
(update a :transaction-account/amount
#(with-precision 2
(double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP)))))
accounts)
(fn [a]
(update a :transaction-account/amount
#(with-precision 2
(double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP)))))
accounts)
leftover (with-precision 2 (.round (bigdec (- (Math/abs (:transaction/amount transaction))
(Math/abs (reduce + 0.0 (map #(:transaction-account/amount %) accounts)))))
*math-context*))
@@ -152,13 +151,13 @@
(when-not (seq (:clients context))
(throw (ex-info "Client is required"
{:validation-error "Client is required"})))
(let [args (assoc args :clients (:clients context) :id (:id context))
(let [args (assoc args :clients (:clients context) :id (:id context))
client->locations (->> (:clients context)
(map :db/id )
(map :db/id)
(dc/q
'[:find (pull ?e [:db/id :client/locations])
:in $ [?e ...]]
(dc/db conn))
'[:find (pull ?e [:db/id :client/locations])
:in $ [?e ...]]
(dc/db conn))
(map (fn [[client]]
[(:db/id client) (:client/locations client)]))
(into {}))
@@ -166,41 +165,40 @@
transactions (pull-many (dc/db conn) [:db/id :transaction/amount {:transaction/client [:db/id]}] (vec all-ids))
account-total (reduce + 0 (map (fn [x] (:percentage x)) (:accounts args)))]
(alog/info ::bulk-coding-transactions
:count (count transactions)
:sample (take 3 transactions))
:count (count transactions)
:sample (take 3 transactions))
(when
(and
(seq (:accounts args))
(not (dollars= 1.0 account-total)))
(let [error (str "Account total (" account-total ") does not reach 100%")]
(throw (ex-info error {:validation-error error}))))
(and
(seq (:accounts args))
(not (dollars= 1.0 account-total)))
(let [error (str "Account total (" account-total ") does not reach 100%")]
(throw (ex-info error {:validation-error error}))))
(doseq [a (:accounts args)
:let [{:keys [:account/location :account/name]} (dc/pull (dc/db conn)
[:account/location :account/name]
(:account_id a))]]
(when (and location (not= location (:location a)))
(let [err (str "Account " name " uses location " (:location a) ", but is supposed to be " location)]
(throw (ex-info err {:validation-error err}) )))
(throw (ex-info err {:validation-error err}))))
(doseq [[_ locations] client->locations]
(when (and (not location)
(not (get (into #{"Shared"} locations)
(:location a))))
(let [err (str "Account " name " uses location " (:location a) ", but doesn't belong to the client.")]
(throw (ex-info err {:validation-error err}) )))))
(throw (ex-info err {:validation-error err}))))))
(audit-transact-batch
(map (fn [t]
(let [locations (client->locations (-> t :transaction/client :db/id))]
(doto
[:upsert-transaction (cond-> t
(:approval_status args) (assoc :transaction/approval-status (enum->keyword (:approval_status args) "transaction-approval-status"))
(:vendor args) (assoc :transaction/vendor (:vendor args))
(seq (:accounts args)) (assoc :transaction/accounts (maybe-code-accounts t (:accounts args) locations)))]
clojure.pprint/pprint)))
transactions)
(:id context))
(map (fn [t]
(let [locations (client->locations (-> t :transaction/client :db/id))]
(doto
[:upsert-transaction (cond-> t
(:approval_status args) (assoc :transaction/approval-status (enum->keyword (:approval_status args) "transaction-approval-status"))
(:vendor args) (assoc :transaction/vendor (:vendor args))
(seq (:accounts args)) (assoc :transaction/accounts (maybe-code-accounts t (:accounts args) locations)))]
clojure.pprint/pprint)))
transactions)
(:id context))
{:message (str "Successfully coded " (count all-ids) " transactions.")}))
(defn delete-transactions [context args _]
(let [_ (assert-admin (:id context))
args (assoc args :clients (:clients context))
@@ -208,24 +206,24 @@
db (dc/db conn)]
(alog/info ::bulk-delete-transactions
:count (count all-ids)
:sample (take 3 all-ids))
:count (count all-ids)
:sample (take 3 all-ids))
(audit-transact-batch
(mapcat (fn [i]
(let [transaction (dc/pull db [:transaction/payment
:transaction/expected-deposit
:db/id] i)
payment-id (-> transaction :transaction/payment :db/id)
expected-deposit-id (-> transaction :transaction/expected-deposit :db/id)]
(cond->> [[:db/retractEntity [:journal-entry/original-entity i]]]
payment-id (into [{:db/id payment-id
:payment/status :payment-status/pending}
[:db/retract (:db/id transaction) :transaction/payment payment-id]])
expected-deposit-id (into [{:db/id expected-deposit-id
:expected-deposit/status :expected-deposit-status/pending}
[:db/retract (:db/id transaction) :transaction/expected-deposit expected-deposit-id]]))))
all-ids)
(:id context))
(mapcat (fn [i]
(let [transaction (dc/pull db [:transaction/payment
:transaction/expected-deposit
:db/id] i)
payment-id (-> transaction :transaction/payment :db/id)
expected-deposit-id (-> transaction :transaction/expected-deposit :db/id)]
(cond->> [[:db/retractEntity [:journal-entry/original-entity i]]]
payment-id (into [{:db/id payment-id
:payment/status :payment-status/pending}
[:db/retract (:db/id transaction) :transaction/payment payment-id]])
expected-deposit-id (into [{:db/id expected-deposit-id
:expected-deposit/status :expected-deposit-status/pending}
[:db/retract (:db/id transaction) :transaction/expected-deposit expected-deposit-id]]))))
all-ids)
(:id context))
(audit-transact-batch
(mapcat (fn [i]
(let [transaction-tx (if (:suppress args)
@@ -242,21 +240,21 @@
(assert-power-user (:id context))
(let [transaction (d-transactions/get-by-id (:transaction_id args))
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
_ (assert-can-see-client (:id context) (:transaction/client transaction))
matches-set (i-transactions/match-transaction-to-unfulfilled-autopayments (:transaction/amount transaction)
(:db/id (:transaction/client transaction)))]
(->graphql (for [matches matches-set]
(for [[_ invoice-id ] matches]
(for [[_ invoice-id] matches]
(d-invoices/get-by-id invoice-id))))))
(defn get-potential-unpaid-invoices-matches [context args _]
(assert-power-user (:id context))
(let [transaction (d-transactions/get-by-id (:transaction_id args))
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
_ (assert-can-see-client (:id context) (:transaction/client transaction))
matches-set (i-transactions/match-transaction-to-unpaid-invoices (:transaction/amount transaction)
(:db/id (:transaction/client transaction)))]
(->graphql (for [matches matches-set]
(for [[_ invoice-id ] matches]
(for [[_ invoice-id] matches]
(d-invoices/get-by-id invoice-id))))))
(defn unlink-transaction [context args _]
@@ -264,20 +262,20 @@
args (assoc args :id (:id context))
transaction-id (:transaction_id args)
transaction (dc/pull (dc/db conn)
[:transaction/approval-status
:transaction/status
:transaction/date
:transaction/location
:transaction/vendor
:transaction/accounts
:transaction/client [:db/id]
{:transaction/payment [:payment/date {:payment/status [:db/ident]} :db/id]} ]
transaction-id)
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
[:transaction/approval-status
:transaction/status
:transaction/date
:transaction/location
:transaction/vendor
:transaction/accounts
:transaction/client [:db/id]
{:transaction/payment [:payment/date {:payment/status [:db/ident]} :db/id]}]
transaction-id)
_ (assert-can-see-client (:id context) (:transaction/client transaction))
_ (assert-not-locked (:db/id (:transaction/client transaction)) (:transaction/date transaction))
_ (when (:transaction/payment transaction)
(assert-not-locked (:db/id (:transaction/client transaction)) (-> transaction :transaction/payment :payment/date)))
payment (-> transaction :transaction/payment )
payment (-> transaction :transaction/payment)
is-autopay-payment? (some->> (dc/q {:find ['?sp]
:in ['$ '?payment]
:where ['[?ip :invoice-payment/payment ?payment]
@@ -286,8 +284,7 @@
(dc/db conn) (:db/id payment))
seq
(map first)
(every? #(instance? java.util.Date %)))
]
(every? #(instance? java.util.Date %)))]
(alog/info ::unlinking :transaction (pr-str transaction) :autopay is-autopay-payment? :payment (pr-str payment))
@@ -295,49 +292,47 @@
(throw (ex-info "Payment can't be undone because it isn't cleared." {:validation-error "Payment can't be undone because it isn't cleared."})))
(if is-autopay-payment?
(audit-transact
(-> [{:db/id (:db/id payment)
:payment/status :payment-status/pending}
[:upsert-transaction
{:db/id transaction-id
:transaction/approval-status :transaction-approval-status/unapproved
:transaction/payment nil
:transaction/vendor nil
:transaction/location nil
:transaction/accounts nil}]
(-> [{:db/id (:db/id payment)
:payment/status :payment-status/pending}
[:upsert-transaction
{:db/id transaction-id
:transaction/approval-status :transaction-approval-status/unapproved
:transaction/payment nil
:transaction/vendor nil
:transaction/location nil
:transaction/accounts nil}]
[:db/retractEntity (:db/id payment) ]]
[:db/retractEntity (:db/id payment)]]
(into (map (fn [[invoice-payment]]
[:db/retractEntity invoice-payment])
(dc/q {:find ['?ip]
:in ['$ '?p]
:where ['[?ip :invoice-payment/payment ?p]]}
(dc/db conn)
(:db/id payment) ))))
(into (map (fn [[invoice-payment]]
[:db/retractEntity invoice-payment])
(dc/q {:find ['?ip]
:in ['$ '?p]
:where ['[?ip :invoice-payment/payment ?p]]}
(dc/db conn)
(:db/id payment)))))
(:id context))
(audit-transact
[{:db/id (:db/id payment)
:payment/status :payment-status/pending}
[:upsert-transaction
{:db/id transaction-id
:transaction/approval-status :transaction-approval-status/unapproved
:transaction/payment nil
:transaction/vendor nil
:transaction/location nil
:transaction/accounts nil}]]
[{:db/id (:db/id payment)
:payment/status :payment-status/pending}
[:upsert-transaction
{:db/id transaction-id
:transaction/approval-status :transaction-approval-status/unapproved
:transaction/payment nil
:transaction/vendor nil
:transaction/location nil
:transaction/accounts nil}]]
(:id context)))
(-> (d-transactions/get-by-id transaction-id)
approval-status->graphql
->graphql)))
(defn transaction-account->entity [{:keys [id account_id amount location]}]
#:transaction-account {:amount amount
:db/id (or id (random-tempid))
:account account_id
:location location})
(defn assert-valid-expense-accounts [accounts]
(doseq [trans-account accounts
:let [account (dc/pull (dc/db conn)
@@ -351,7 +346,7 @@
(:account/location account)))
(let [err (str "Account uses location '" (:location trans-account) "' but expects '" (:account/location account) "'")]
(throw (ex-info err
{:validation-error err}))))
{:validation-error err}))))
(when (and (empty? (:account/location account))
(= "A" (:location trans-account)))
@@ -359,13 +354,12 @@
(throw (ex-info err
{:validation-error err}))))
(when (nil? (:account_id trans-account))
(throw (ex-info "Account is missing account" {:validation-error "Account is missing account"})))))
(defn edit-transaction [context {{:keys [id accounts vendor_id approval_status memo forecast_match]} :transaction} _]
(let [existing-transaction (d-transactions/get-by-id id)
_ (assert-can-see-client (:id context) (:transaction/client existing-transaction) )
_ (assert-can-see-client (:id context) (:transaction/client existing-transaction))
_ (assert-valid-expense-accounts accounts)
_ (assert-not-locked (:db/id (:transaction/client existing-transaction)) (:transaction/date existing-transaction))
account-total (reduce + 0 (map (fn [x] (:amount x)) accounts))
@@ -378,17 +372,17 @@
set
(conj "A")
(conj "HQ"))))]
(when (and (not (dollars= (Math/abs (:transaction/amount existing-transaction)) account-total))
(or
(and (= approval_status :unapproved)
(> (count accounts) 0))
(not= approval_status :unapproved)))
(not= approval_status :unapproved)))
(let [error (str "Expense account total (" account-total ") does not equal transaction total (" (Math/abs (:transaction/amount existing-transaction)) ")")]
(throw (ex-info error {:validation-error error}))))
(throw (ex-info error {:validation-error error}))))
(when missing-locations
(throw (ex-info (str "Location '" (str/join ", " missing-locations) "' not found on client.") {})) )
(throw (ex-info (str "Location '" (str/join ", " missing-locations) "' not found on client.") {})))
(audit-transact (cond-> [[:upsert-transaction {:db/id id
:transaction/vendor vendor_id
:transaction/memo memo
@@ -413,8 +407,8 @@
(defn match-transaction [context {:keys [transaction_id payment_id]} _]
(let [transaction (d-transactions/get-by-id transaction_id)
payment (d-checks/get-by-id payment_id)
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
_ (assert-can-see-client (:id context) (:payment/client payment) )
_ (assert-can-see-client (:id context) (:transaction/client transaction))
_ (assert-can-see-client (:id context) (:payment/client payment))
_ (assert-not-locked (:db/id (:transaction/client transaction)) (:transaction/date transaction))]
(when (not= (:db/id (:transaction/client transaction))
(:db/id (:payment/client payment)))
@@ -423,7 +417,7 @@
(when-not (dollars= (- (:transaction/amount transaction))
(:payment/amount payment))
(throw (ex-info "Amounts don't match" {:validation-error "Amounts don't match"})))
(audit-transact (into
(audit-transact (into
[{:db/id (:db/id payment)
:payment/status :payment-status/cleared
:payment/date (coerce/to-date (first (sort [(:payment/date payment)
@@ -431,14 +425,14 @@
[:upsert-transaction
{:db/id (:db/id transaction)
:transaction/payment (:db/id payment)
:transaction/vendor (:db/id (:payment/vendor payment))
:transaction/location "A"
:transaction/approval-status :transaction-approval-status/approved
:transaction/accounts [{:db/id (random-tempid)
:transaction-account/account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
:transaction-account/location "A"
:transaction-account/amount (Math/abs (:transaction/amount transaction))}]}]])
:transaction/payment (:db/id payment)
:transaction/vendor (:db/id (:payment/vendor payment))
:transaction/location "A"
:transaction/approval-status :transaction-approval-status/approved
:transaction/accounts [{:db/id (random-tempid)
:transaction-account/account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
:transaction-account/location "A"
:transaction-account/amount (Math/abs (:transaction/amount transaction))}]}]])
(:id context)))
(solr/touch-with-ledger transaction_id)
(-> (d-transactions/get-by-id transaction_id)
@@ -448,7 +442,7 @@
(defn match-transaction-autopay-invoices [context {:keys [transaction_id autopay_invoice_ids]} _]
(let [_ (assert-power-user (:id context))
transaction (d-transactions/get-by-id transaction_id)
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
_ (assert-can-see-client (:id context) (:transaction/client transaction))
db (dc/db conn)
invoice-clients (set (map #(pull-ref db :invoice/client %) autopay_invoice_ids))
invoice-amount (reduce + 0.0 (map #(pull-attr db :invoice/total %) autopay_invoice_ids))
@@ -474,9 +468,9 @@
(:db/id (:transaction/bank-account transaction))
(:db/id (:transaction/client transaction)))]
(alog/info ::adding-payment-from-autopay-invoice
:payment (pr-str payment-tx))
:payment (pr-str payment-tx))
(audit-transact payment-tx (:id context)))
(solr/touch-with-ledger transaction_id)
(solr/touch-with-ledger transaction_id)
(-> (d-transactions/get-by-id transaction_id)
approval-status->graphql
->graphql)))
@@ -485,8 +479,8 @@
(defn match-transaction-unpaid-invoices [context {:keys [transaction_id unpaid_invoice_ids]} _]
(let [_ (assert-power-user (:id context))
transaction (d-transactions/get-by-id transaction_id)
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
_ (assert-can-see-client (:id context) (:transaction/client transaction))
_ (assert-not-locked (:db/id (:transaction/client transaction)) (:transaction/date transaction))
db (dc/db conn)
invoice-clients (set (map #(pull-ref db :invoice/client %) unpaid_invoice_ids))
@@ -502,17 +496,17 @@
(throw (ex-info "Amounts don't match" {:validation-error "Amounts don't match"})))
(when (:transaction/payment transaction)
(throw (ex-info "Transaction already linked" {:validation-error "Transaction already linked"})))
(let [payment-tx (i-transactions/add-new-payment (dc/pull db [:transaction/amount :transaction/date :db/id] transaction_id)
(map (fn [id]
(let [entity (dc/pull db [:invoice/vendor :db/id :invoice/total] id)]
[(or (-> entity :invoice/vendor :db/id)
(-> entity :invoice/vendor))
(-> entity :db/id)
(-> entity :invoice/total)]))
unpaid_invoice_ids)
(:db/id (:transaction/bank-account transaction))
(:db/id (:transaction/client transaction)))]
(map (fn [id]
(let [entity (dc/pull db [:invoice/vendor :db/id :invoice/total] id)]
[(or (-> entity :invoice/vendor :db/id)
(-> entity :invoice/vendor))
(-> entity :db/id)
(-> entity :invoice/total)]))
unpaid_invoice_ids)
(:db/id (:transaction/bank-account transaction))
(:db/id (:transaction/client transaction)))]
(audit-transact payment-tx (:id context)))
(solr/touch-with-ledger transaction_id)
@@ -527,9 +521,8 @@
:count Integer/MAX_VALUE} nil)
(filter #(not (:payment %)))
(map :id ))
(map :id))
transaction_ids)
_ (mu/log ::here :txids transaction_ids)
transaction_ids (all-ids-not-locked transaction_ids)
@@ -553,17 +546,16 @@
(audit-transact (mapv (fn [t]
[:upsert-transaction
(remove-nils (rm/apply-rule {:db/id (:db/id t)
:transaction/amount (:transaction/amount t)}
transaction-rule
:transaction/amount (:transaction/amount t)}
transaction-rule
(or (-> t :transaction/bank-account :bank-account/locations)
(-> t :transaction/client :client/locations))))])
(or (-> t :transaction/bank-account :bank-account/locations)
(-> t :transaction/client :client/locations))))])
transactions)
(:id context))
(doseq [n transactions]
(solr/touch-with-ledger (:db/id n)))
)
(solr/touch-with-ledger (:db/id n))))
(transduce
(comp
(map d-transactions/get-by-id)
@@ -571,12 +563,12 @@
(map ->graphql))
conj
[]
transaction_ids ))
transaction_ids))
(def objects
{:transaction {:fields {:id {:type :id}
:amount {:type 'String}
:memo {:type 'String}
:memo {:type 'String}
:is_locked {:type 'Boolean}
:description_original {:type 'String}
:description_simple {:type 'String}
@@ -628,8 +620,8 @@
:resolve :mutation/bulk-code-transactions}
:delete_transactions {:type :message
:args {:filters {:type :transaction_filters}
:ids {:type '(list :id)}
:suppress {:type 'Boolean}}
:ids {:type '(list :id)}
:suppress {:type 'Boolean}}
:resolve :mutation/delete-transactions}
:edit_transaction {:type :transaction
:args {:transaction {:type :edit_transaction}}
@@ -711,9 +703,8 @@
:mutation/match-transaction-unpaid-invoices match-transaction-unpaid-invoices
:mutation/match-transaction-rules match-transaction-rules})
(defn attach [schema]
(->
(->
(merge-with merge schema
{:objects objects
:queries queries

View File

@@ -13,7 +13,6 @@
[iol-ion.query :refer [entid]]
[slingshot.slingshot :refer [throw+]]))
(defn snake->kebab [s]
(str/replace s #"_" "-"))
@@ -107,8 +106,6 @@
(#{"manager" "user" "power-user" "read-only"} (:user/role id))
(:user/clients id [])))
(defn result->page [results result-count key args]
{key (map ->graphql results)
:total result-count
@@ -197,7 +194,6 @@
(= :client/code (first x)))
[(entid (dc/db conn) x)]
(sequential? x)
(mapcat coerce-client-ids x)
@@ -218,14 +214,14 @@
e)))))
(defn exception->4xx [f]
(try
(try
(f)
(catch Throwable e
(throw+ (ex-info (.getMessage e) {:type :form-validation
:form-validation-errors [(.getMessage e)]}))
(throw+ (ex-info (.getMessage e) {:type :form-validation
:form-validation-errors [(.getMessage e)]}))
#_(throw (ex-info (.getMessage e)
{:type :notification}
e)))))
{:type :notification}
e)))))
(defn notify-if-locked [client-id date]
(try

View File

@@ -130,7 +130,6 @@
:vendor/schedule-payment-dom schedule-payment-dom
:vendor/automatically-paid-when-due (:automatically_paid_when_due in)))]
transaction-result (audit-transact [transaction] (:id context))
new-vendor (d-vendors/get-by-id (or (-> transaction-result :tempids (get "vendor"))
id))]
@@ -160,7 +159,6 @@
(audit-transact transaction (:id context))
to))
(defn get-graphql [context args _]
(assert-admin (:id context))
(let [args (assoc args :id (:id context))
@@ -187,7 +185,6 @@
(if-let [query (not-empty (cleanse-query (:query args)))]
(let [search-query (str "name:(" query ")")]
(for [{:keys [id name]} (solr/query solr/impl "vendors" {"query" (cond-> search-query
(not (is-admin? (:id context))) (str " hidden:false"))
"fields" "id, name"})]