346 lines
17 KiB
Clojure
346 lines
17 KiB
Clojure
(ns auto-ap.datomic.invoices
|
|
(:require
|
|
[auto-ap.datomic
|
|
:refer [add-sorter-fields
|
|
apply-pagination
|
|
query2
|
|
apply-sort-3
|
|
conn
|
|
merge-query
|
|
pull-many]]
|
|
[auto-ap.datomic.accounts :as d-accounts]
|
|
[auto-ap.datomic.vendors :as d-vendors]
|
|
[auto-ap.graphql.utils :refer [limited-clients]]
|
|
[auto-ap.time-utils :refer [next-dom]]
|
|
[clj-time.coerce :as coerce]
|
|
[clj-time.core :as time]
|
|
[com.brunobonacci.mulog :as mu]
|
|
[clojure.set :refer [rename-keys]]
|
|
[datomic.api :as dc]
|
|
[iol-ion.tx :refer [random-tempid]]))
|
|
|
|
(def default-read '[*
|
|
{:invoice/client [:client/name :db/id :client/locations :client/code]}
|
|
{:invoice/vendor [* {:vendor/address [*]}]}
|
|
{:invoice/status [:db/ident]}
|
|
{:invoice/expense-accounts [* {:invoice-expense-account/account [:account/name :db/id
|
|
:account/location
|
|
{:account/client-overrides [:account-client-override/name
|
|
{:account-client-override/client [:db/id]}]}]}]}
|
|
{:invoice-payment/_invoice [* {:invoice-payment/payment [* {:payment/status [*]}
|
|
{:payment/bank-account [*]}
|
|
{:transaction/_payment [*]}]}]}])
|
|
|
|
(defn <-datomic [x]
|
|
(-> x
|
|
(update :invoice/date coerce/from-date)
|
|
(update :invoice/due coerce/from-date)
|
|
(update :invoice/scheduled-payment coerce/from-date)
|
|
(update :invoice/status :db/ident)
|
|
(update :invoice/expense-accounts (fn [eas]
|
|
(map
|
|
#(update % :invoice-expense-account/account d-accounts/clientize (:db/id (:invoice/client x)))
|
|
eas)))
|
|
(rename-keys {:invoice-payment/_invoice :invoice/payments})))
|
|
|
|
(defn raw-graphql-ids
|
|
([args]
|
|
(raw-graphql-ids (dc/db conn) args))
|
|
([db args]
|
|
(let [query
|
|
(if (:exact-match-id args)
|
|
{:query {:find '[?e]
|
|
:in '[$ ?e [?c ...]]
|
|
:where '[[?e :invoice/client ?c]]}
|
|
:args [db
|
|
(:exact-match-id args)
|
|
(map :db/id (:clients args))]}
|
|
(cond-> {:query {:find []
|
|
:in ['$]
|
|
:where ['[?e :invoice/client]]}
|
|
:args [db]}
|
|
|
|
|
|
(:client-id args)
|
|
(merge-query {:query {:in ['?client-id]
|
|
:where ['[?e :invoice/client ?client-id]]}
|
|
:args [ (:client-id args)]})
|
|
|
|
(:client-code args)
|
|
(merge-query {:query {:in ['?client-code]
|
|
:where ['[?e :invoice/client ?client-id]
|
|
'[?client-id :client/code ?client-code]]}
|
|
:args [ (:client-code args)]})
|
|
|
|
(:original-id args)
|
|
(merge-query {:query {:in ['?original-id]
|
|
:where [
|
|
'[?e :invoice/client ?c]
|
|
'[?c :client/original-id ?original-id]]}
|
|
:args [ (cond-> (:original-id args)
|
|
(string? (:original-id args)) Long/parseLong )]})
|
|
|
|
(:start (:date-range args)) (merge-query {:query {:in '[?start-date]
|
|
:where ['[?e :invoice/date ?date]
|
|
'[(>= ?date ?start-date)]]}
|
|
:args [(coerce/to-date (:start (:date-range args)))]})
|
|
|
|
(:end (:date-range args)) (merge-query {:query {:in '[?end-date]
|
|
:where ['[?e :invoice/date ?date]
|
|
'[(<= ?date ?end-date)]]}
|
|
:args [(coerce/to-date (:end (:date-range args)))]})
|
|
|
|
(:start (:due-range args)) (merge-query {:query {:in '[?start-due]
|
|
:where ['[?e :invoice/due ?due]
|
|
'[(>= ?due ?start-due)]]}
|
|
:args [(coerce/to-date (:start (:due-range args)))]})
|
|
|
|
(:end (:due-range args)) (merge-query {:query {:in '[?end-due]
|
|
:where ['[?e :invoice/due ?due]
|
|
'[(<= ?due ?end-due)]]}
|
|
:args [(coerce/to-date (:end (:due-range args)))]})
|
|
true
|
|
(merge-query {:query {:in ['[?xx ...]]
|
|
:where ['[?e :invoice/client ?xx]]}
|
|
:args [ (map :db/id (:clients args))]})
|
|
|
|
(:import-status args)
|
|
(merge-query {:query {:in ['?import-status]
|
|
:where ['[?e :invoice/import-status ?import-status]]}
|
|
:args [ (keyword "import-status" (:import-status args))]})
|
|
(:status args)
|
|
(merge-query {:query {:in ['?status]
|
|
:where ['[?e :invoice/status ?status]]}
|
|
:args [ (:status args)]})
|
|
(:vendor-id args)
|
|
(merge-query {:query {:in ['?vendor-id]
|
|
:where ['[?e :invoice/vendor ?vendor-id]]}
|
|
:args [ (:vendor-id args)]})
|
|
|
|
(:account-id args)
|
|
(merge-query {:query {:in ['?account-id]
|
|
:where ['[?e :invoice/expense-accounts ?iea ?]
|
|
'[?iea :invoice-expense-account/account ?account-id]]}
|
|
:args [ (:account-id args)]})
|
|
|
|
(:amount-gte args)
|
|
(merge-query {:query {:in ['?amount-gte]
|
|
:where ['[?e :invoice/total ?total-filter]
|
|
'[(>= ?total-filter ?amount-gte)]]}
|
|
:args [(:amount-gte args)]})
|
|
|
|
(:amount-lte args)
|
|
(merge-query {:query {:in ['?amount-lte]
|
|
:where ['[?e :invoice/total ?total-filter]
|
|
'[(<= ?total-filter ?amount-lte)]]}
|
|
:args [(:amount-lte args)]})
|
|
|
|
(seq (:invoice-number-like args))
|
|
(merge-query {:query {:in ['?invoice-number-like]
|
|
:where ['[?e :invoice/invoice-number ?invoice-number]
|
|
'[(.contains ^String ?invoice-number ?invoice-number-like)]]}
|
|
:args [(:invoice-number-like args)]})
|
|
|
|
(:scheduled-payments args)
|
|
(merge-query {:query {:in []
|
|
:where ['[?e :invoice/scheduled-payment]]}
|
|
:args []})
|
|
|
|
(:unresolved args)
|
|
(merge-query {:query {:in []
|
|
:where ['(or-join [?e]
|
|
(not [?e :invoice/expense-accounts ])
|
|
(and [?e :invoice/expense-accounts ?ea]
|
|
(not [?ea :invoice-expense-account/account])))]}
|
|
:args []})
|
|
|
|
(seq (:location args))
|
|
(merge-query {:query {:in ['?location]
|
|
:where ['[?e :invoice/expense-accounts ?eas]
|
|
'[?eas :invoice-expense-account/location ?location]]}
|
|
:args [(:location args)]})
|
|
|
|
(:sort args) (add-sorter-fields {"client" ['[?e :invoice/client ?c]
|
|
'[?c :client/name ?sort-client]]
|
|
"vendor" ['[?e :invoice/vendor ?v]
|
|
'[?v :vendor/name ?sort-vendor]]
|
|
"description-original" ['[?e :transaction/description-original ?sort-description-original]]
|
|
"location" ['[?e :invoice/expense-accounts ?iea]
|
|
'[?iea :invoice-expense-account/location ?sort-location]]
|
|
"date" ['[?e :invoice/date ?sort-date]]
|
|
"due" ['[(get-else $ ?e :invoice/due #inst "2050-01-01") ?sort-due]]
|
|
"invoice-number" ['[?e :invoice/invoice-number ?sort-invoice-number]]
|
|
"total" ['[?e :invoice/total ?sort-total]]
|
|
"outstanding-balance" ['[?e :invoice/outstanding-balance ?sort-outstanding-balance]]}
|
|
args)
|
|
true
|
|
(merge-query {:query {:find ['?sort-default '?e ]
|
|
:where ['[?e :invoice/client]
|
|
'[?e :invoice/date ?sort-default]]}}) ))]
|
|
(->> (query2 query)
|
|
(apply-sort-3 args)
|
|
(apply-pagination args)))))
|
|
|
|
|
|
(defn graphql-results [ids db _]
|
|
(let [results (->> (pull-many db default-read ids)
|
|
(group-by :db/id))
|
|
|
|
invoices (->> ids
|
|
(map results)
|
|
(map first)
|
|
(mapv <-datomic))]
|
|
invoices))
|
|
|
|
(defn sum-outstanding [ids]
|
|
|
|
(->>
|
|
(dc/q {:find ['?id '?o]
|
|
:in ['$ '[?id ...]]
|
|
:where ['[?id :invoice/outstanding-balance ?o]]}
|
|
(dc/db conn)
|
|
ids)
|
|
(map last)
|
|
(reduce
|
|
+
|
|
0.0)))
|
|
|
|
(defn sum-total-amount [ids]
|
|
|
|
(->>
|
|
(dc/q {:find ['?id '?o]
|
|
:in ['$ '[?id ...]]
|
|
:where ['[?id :invoice/total ?o]]
|
|
}
|
|
(dc/db conn)
|
|
ids)
|
|
(map last)
|
|
(reduce
|
|
+
|
|
0.0)))
|
|
|
|
(defn get-graphql [args]
|
|
|
|
(let [db (dc/db conn)
|
|
{ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)
|
|
outstanding (sum-outstanding ids-to-retrieve)
|
|
total-amount (sum-total-amount ids-to-retrieve)]
|
|
|
|
|
|
[(->> (graphql-results ids-to-retrieve db args))
|
|
matching-count
|
|
outstanding
|
|
total-amount]))
|
|
|
|
(defn get-by-id [id]
|
|
(-> (dc/db conn)
|
|
(dc/pull default-read id)
|
|
(<-datomic)))
|
|
|
|
(defn get-multi [ids]
|
|
(map <-datomic
|
|
(pull-many (dc/db conn) default-read ids )))
|
|
|
|
|
|
|
|
(defn find-conflicting [{:keys [:invoice/invoice-number :invoice/vendor :invoice/client :db/id]}]
|
|
|
|
(->> (dc/q
|
|
{:find [(list 'pull '?e default-read)]
|
|
:in ['$ '?invoice-number '?vendor '?client '?invoice-id]
|
|
:where '[[?e :invoice/invoice-number ?invoice-number]
|
|
[?e :invoice/vendor ?vendor]
|
|
[?e :invoice/client ?client]
|
|
(not [?e :invoice/status :invoice-status/voided])
|
|
[(not= ?e ?invoice-id)]]}
|
|
(dc/db conn) invoice-number vendor client (or id 0))
|
|
(map first)
|
|
(map <-datomic)))
|
|
|
|
|
|
(defn get-existing-set []
|
|
(let [vendored-results (set (dc/q {:find ['?vendor '?client '?invoice-number]
|
|
:in ['$]
|
|
:where '[[?e :invoice/invoice-number ?invoice-number]
|
|
[?e :invoice/vendor ?vendor]
|
|
[?e :invoice/client ?client]
|
|
(not [?e :invoice/status :invoice-status/voided])
|
|
]}
|
|
(dc/db conn)))
|
|
vendorless-results (->> (dc/q {:find ['?client '?invoice-number]
|
|
:in ['$]
|
|
:where '[[?e :invoice/invoice-number ?invoice-number]
|
|
(not [?e :invoice/vendor])
|
|
[?e :invoice/client ?client]
|
|
(not [?e :invoice/status :invoice-status/voided])
|
|
]}
|
|
(dc/db conn))
|
|
(mapv (fn [[client invoice-number]]
|
|
[nil client invoice-number]) )
|
|
set)]
|
|
(into vendored-results vendorless-results)))
|
|
|
|
(defn filter-ids [ids]
|
|
(if ids
|
|
(->>
|
|
(dc/q {:find ['?e]
|
|
:in ['$ '[?e ...]]
|
|
:where ['[?e :invoice/date]]}
|
|
(dc/db conn) ids)
|
|
(map first)
|
|
vec)
|
|
[]))
|
|
|
|
(defn code-invoice
|
|
([invoice]
|
|
(code-invoice invoice nil))
|
|
([invoice override-account-id]
|
|
(mu/log ::trying-to-code-invoice
|
|
:invoice invoice)
|
|
(let [db (dc/db auto-ap.datomic/conn)
|
|
client-id (or (:db/id (:invoice/client invoice))
|
|
(:invoice/client invoice))
|
|
vendor-id (or (:db/id (:invoice/vendor invoice))
|
|
(:invoice/vendor invoice))
|
|
date (:invoice/date invoice)
|
|
vendor (dc/pull db '[*] vendor-id)
|
|
due (when (:vendor/terms vendor)
|
|
(-> date
|
|
(coerce/to-date-time)
|
|
(time/plus (time/days (d-vendors/terms-for-client-id vendor client-id)))
|
|
coerce/to-date))
|
|
automatically-paid? (boolean (seq (map first (dc/q '[:find ?c
|
|
:in $ ?v ?c
|
|
:where [?v :vendor/automatically-paid-when-due ?c]]
|
|
db
|
|
vendor-id
|
|
client-id))))
|
|
[schedule-payment-dom] (map first (dc/q '[:find ?dom
|
|
:in $ ?v ?c
|
|
:where [?v :vendor/schedule-payment-dom ?sp ]
|
|
[?sp :vendor-schedule-payment-dom/client ?c]
|
|
[?sp :vendor-schedule-payment-dom/dom ?dom]]
|
|
db
|
|
vendor-id
|
|
client-id))
|
|
|
|
scheduled-payment (cond automatically-paid?
|
|
due
|
|
|
|
schedule-payment-dom
|
|
(-> date
|
|
coerce/to-date-time
|
|
(next-dom schedule-payment-dom)
|
|
coerce/to-date)
|
|
:else nil)
|
|
default-expense-account #:invoice-expense-account {:db/id (random-tempid)
|
|
:account (or override-account-id (d-vendors/account-for-client-id vendor client-id))
|
|
:location (:invoice/location invoice)
|
|
:amount (:invoice/total invoice)}]
|
|
(cond-> invoice
|
|
true (assoc :invoice/expense-accounts [default-expense-account])
|
|
due (assoc :invoice/due due)
|
|
scheduled-payment (assoc :invoice/scheduled-payment scheduled-payment)))))
|
|
|
|
|