(ns auto-ap.datomic.invoices (:require [auto-ap.datomic :refer [add-sorter-fields apply-pagination query2 observable-query 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 extract-client-ids]] [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 [valid-clients (extract-client-ids (:clients args) (:client-id args) (when (:client-code args) [:client/code (:client-code args)])) query (if (:exact-match-id args) {:query {:find '[?e] :in '[$ ?e [?c ...]] :where '[[?e :invoice/client ?c]]} :args [db (:exact-match-id args) valid-clients]} (cond-> {:query {:find [] :in '[$ [?clients ?start ?end]] :where '[ [(iol-ion.query/scan-invoices $ ?clients ?start ?end) [[?e _ ?sort-default] ...]] ]} :args [db [valid-clients (some-> (:start (:date-range args)) coerce/to-date) (some-> (:end (:date-range args)) coerce/to-date)]]} (: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 (: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)))]}) (: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 ]}}) ))] (->> (observable-query 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)))))