diff --git a/src/clj/auto_ap/datomic/migrate.clj b/src/clj/auto_ap/datomic/migrate.clj index b4c1a9da..1dba1834 100644 --- a/src/clj/auto_ap/datomic/migrate.clj +++ b/src/clj/auto_ap/datomic/migrate.clj @@ -231,7 +231,32 @@ :auto-ap/add-cleared-against {:txes [[{:db/ident :transaction/cleared-against :db/doc "which entitiy it was cleared against" :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]]}}] + :db/cardinality :db.cardinality/one}]]} + :auto-ap/add-cash-flow-schema {:txes [[{:db/ident :client/weekly-debits + :db/doc "How much money gets debited each week" + :db/valueType :db.type/double + :db/cardinality :db.cardinality/one} + {:db/ident :client/weekly-credits + :db/doc "How much money gets credited each week" + :db/valueType :db.type/double + :db/cardinality :db.cardinality/one} + {:db/ident :client/forecasted-transactions + :db/doc "Regular, planned transactions" + :db/valueType :db.type/ref + :db/isComponent true + :db/cardinality :db.cardinality/many} + {:db/ident :forecasted-transaction/amount + :db/doc "Amount of a forcested transaction" + :db/valueType :db.type/double + :db/cardinality :db.cardinality/one} + {:db/ident :forecasted-transaction/day-of-month + :db/doc "Which day the transaction occurs" + :db/valueType :db.type/long + :db/cardinality :db.cardinality/one} + {:db/ident :forecasted-transaction/identifier + :db/doc "An identifier for this forcasted transaction, e.g., 'RENT'" + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one}]]}}] (println "Conforming database...") (c/ensure-conforms conn norms-map) (when (not (seq args)) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index fcd271f3..2d188213 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -345,9 +345,14 @@ :errors {:type '(list :import_ledger_entry_result)} }} + :upcoming_transaction {:fields {:amount {:type :money} + :date {:type :iso_date}}} + :cash_flow_result {:fields {:beginning_balance {:type :money} :invoices_due_soon {:type '(list :invoice)} - :outstanding_payments {:type :money}}} + :outstanding_payments {:type :money} + :upcoming_credits {:type '(list :upcoming_transaction)} + :upcoming_debits {:type '(list :upcoming_transaction)}}} } @@ -363,7 +368,7 @@ :run_transaction_rule {:type '(list :transaction) :args {:transaction_rule_id {:type :id}} - :resolve :run-transaction-rule} + :resolve :run-transaction-rule} :invoice_stats {:type '(list :invoice_stat) :args {:client_id {:type :id}} @@ -893,46 +898,55 @@ (- total outstanding-balance))}))) (defn get-cash-flow [context {:keys [client_id]} value] - (let [total-cash (reduce - (fn [total [credit debit]] - (- (+ total credit) - debit)) - 0.0 - (d/query {:query {:find '[?debit ?credit] - :in '[$ ?client] - :where ['[?j :journal-entry/client ?client] - '[?j :journal-entry/line-items ?je] - '[?je :journal-entry-line/account ?ba] - '[?ba :bank-account/type :bank-account-type/check] - '[(get-else $ ?je :journal-entry-line/debit 0.0) ?debit] - '[(get-else $ ?je :journal-entry-line/credit 0.0) ?credit]]} - :args [(d/db (d/connect uri)) client_id]})) - bills-due-soon (d/query {:query {:find '[?due ?outstanding] - :in '[$ ?client ?due-before] - :where ['[?i :invoice/client ?client] - '[?i :invoice/status :invoice-status/unpaid] - '[?i :invoice/due ?due] - '[(<= ?due ?due-before)] - '[?i :invoice/outstanding-balance ?outstanding]]} - :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/days 7)))]}) - outstanding-checks (reduce - + - 0.0 - (map first (d/query {:query {:find '[?amount] - :in '[$ ?client ?due-before] - :where ['[?p :payment/client ?client] - '[?p :payment/status :payment-status/pending] - '[?p :payment/amount ?amount] - '(or - [?p :payment/type :payment-type/debit] - [?p :payment/type :payment-type/check])]} - :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (auto-ap.time/local-now) (t/days 7)))]})))] - {:beginning_balance total-cash - :outstanding_payments outstanding-checks - :invoices_due_soon (mapv (fn [[due outstanding]] - {:due (coerce/to-date-time due) - :outstanding_balance outstanding}) - bills-due-soon)})) + (when client_id + (let [total-cash (reduce + (fn [total [credit debit]] + (- (+ total credit) + debit)) + 0.0 + (d/query {:query {:find '[?debit ?credit] + :in '[$ ?client] + :where ['[?j :journal-entry/client ?client] + '[?j :journal-entry/line-items ?je] + '[?je :journal-entry-line/account ?ba] + '[?ba :bank-account/type :bank-account-type/check] + '[(get-else $ ?je :journal-entry-line/debit 0.0) ?debit] + '[(get-else $ ?je :journal-entry-line/credit 0.0) ?credit]]} + :args [(d/db (d/connect uri)) client_id]})) + bills-due-soon (d/query {:query {:find '[?due ?outstanding] + :in '[$ ?client ?due-before] + :where ['[?i :invoice/client ?client] + '[?i :invoice/status :invoice-status/unpaid] + '[?i :invoice/due ?due] + '[(<= ?due ?due-before)] + '[?i :invoice/outstanding-balance ?outstanding]]} + :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/days 7)))]}) + outstanding-checks (reduce + + + 0.0 + (map first (d/query {:query {:find '[?amount] + :in '[$ ?client ?due-before] + :where ['[?p :payment/client ?client] + '[?p :payment/status :payment-status/pending] + '[?p :payment/amount ?amount] + '(or + [?p :payment/type :payment-type/debit] + [?p :payment/type :payment-type/check])]} + :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (auto-ap.time/local-now) (t/days 7)))]})))] + {:beginning_balance total-cash + :outstanding_payments outstanding-checks + :invoices_due_soon (mapv (fn [[due outstanding]] + {:due (coerce/to-date-time due) + :outstanding_balance outstanding}) + bills-due-soon) + :upcoming_credits [{:amount 350000.23 + :date (coerce/to-date-time (t/plus (auto-ap.time/local-now) (t/days 0)))} + {:amount 105000.23 + :date (coerce/to-date-time (t/plus (auto-ap.time/local-now) (t/days 3)))}] + :upcoming_debits [{:amount -120000.23 + :date (coerce/to-date-time (t/plus (auto-ap.time/local-now) (t/days 5)))} + {:amount -35000.23 + :date (coerce/to-date-time (t/plus (auto-ap.time/local-now) (t/days 4)))}]}))) (def schema (-> integreat-schema diff --git a/src/cljs/auto_ap/views/pages/home.cljs b/src/cljs/auto_ap/views/pages/home.cljs index 79792772..55604e94 100644 --- a/src/cljs/auto_ap/views/pages/home.cljs +++ b/src/cljs/auto_ap/views/pages/home.cljs @@ -20,7 +20,7 @@ (def cell (r/adapt-react-class js/Recharts.Cell)) (def tool-tip (r/adapt-react-class js/Recharts.Tooltip)) -(def colors ["hsl(171, 100%, 41%)" "hsl(217, 71%, 53%)" "hsl(141, 71%, 48%)" "hsl(48, 100%, 67%)" "hsl(348, 100%, 61%)" "hsl(217, 71%, 53%)"]) +(def colors ["hsl(171, 100%, 41%)" "hsl(217, 71%, 53%)" "hsl(141, 71%, 48%)" "hsl(48, 100%, 67%)" "hsl(348, 100%, 61%)" "hsl(217, 71%, 53%)" "hsl(141, 53%, 53%)"]) (def light-colors ["hsl(171, 60%, 80%)" "hsl(217, 71%, 53%)" "hsl(141, 71%, 48%)" "hsl(48, 100%, 67%)" "hsl(348, 100%, 61%)" "hsl(217, 71%, 53%)"]) (defn make-pie-chart @@ -61,7 +61,10 @@ :on-click redirect-fn}] [bar {:dataKey "invoices" :fill (get colors 3) :stackId "a" :name "Invoices" :on-click redirect-fn}] - + [bar {:dataKey "credits" :fill (get colors 2) :stackId "a" :name "Upcoming Credits" + :on-click redirect-fn}] + [bar {:dataKey "debits" :fill (get colors 4) :stackId "a" :name "Upcoming Debits" + :on-click redirect-fn}] [x-axis {:dataKey "name"}] [y-axis] [legend]]) @@ -98,37 +101,51 @@ (fn [db] (::top-expense-categories db))) +(defn sum-by-date [pairs] + (reduce + (fn [result [date amount]] + (let [due (if (t/before? date (local-now)) + (local-now) + date)] + (update result (date->str due) + (fn [r] (+ (or r 0.0) (js/parseFloat amount)))))) + {} + pairs)) + (re-frame/reg-sub ::cash-flow (fn [db] - (let [{:keys [outstanding-payments beginning-balance invoices-due-soon]} (::cash-flow db) - invoices-due-soon (reduce - (fn [result invoice] - (let [due (if (t/before? (:due invoice) (local-now)) - (local-now) - (:due invoice))] - (update result (date->str due) - (fn [r] (+ (or r 0.0) (:outstanding-balance invoice)))))) - {} - invoices-due-soon) + (let [{:keys [outstanding-payments beginning-balance invoices-due-soon upcoming-credits upcoming-debits]} (::cash-flow db) + invoices-due-soon (sum-by-date (map (fn [i] [(:due i) (:outstanding-balance i)]) invoices-due-soon)) + upcoming-credits (sum-by-date (map (fn [i] [(:date i) (:amount i)]) upcoming-credits)) + upcoming-debits (sum-by-date (map (fn [i] [(:date i) (:amount i)]) upcoming-debits)) start-date (local-now) - effective-balance (- beginning-balance outstanding-payments (invoices-due-soon (date->str start-date) 0.0)) - ] + effective-balance (- beginning-balance outstanding-payments (invoices-due-soon (date->str start-date) 0.0))] (reverse (reduce - (fn [[{:keys [effective-balance] } :as acc] day] - (let [invoices-due-today (invoices-due-soon (date->str (t/plus start-date (t/days day))) 0.0)] + (fn [[{:keys [effective-balance credits-yesterday] } :as acc] day] + (let [invoices-due-today (invoices-due-soon (date->str (t/plus start-date (t/days day))) 0.0) + credits-due-today (upcoming-credits (date->str (t/plus start-date (t/days day))) 0.0) + debits-due-today (upcoming-debits (date->str (t/plus start-date (t/days day))) 0.0)] (let [today (t/plus start-date (t/days day))] (conj acc {:name (date->str today) - :effective-balance (- effective-balance invoices-due-today) + :effective-balance (+ (- effective-balance invoices-due-today ) + debits-due-today + credits-yesterday) + :credits-yesterday credits-due-today + :credits credits-due-today + :debits debits-due-today :invoices (- invoices-due-today) :query-params (cemerick.url/map->query {:due-range {:start (date->str today standard) :end (date->str today standard)}})})))) (list {:name (date->str start-date) :effective-balance effective-balance :invoices (- (invoices-due-soon (date->str start-date) 0.0)) + :credits (upcoming-credits (date->str start-date) 0.0) + :credits-yesterday (upcoming-credits (date->str start-date) 0.0) + :debits (upcoming-debits (date->str start-date) 0.0) :outstanding-payments (- outstanding-payments) :query-params (cemerick.url/map->query {:due-range {:end (date->str start-date standard)}})}) (range 1 7)))))) @@ -146,7 +163,11 @@ [:name :paid :unpaid]] [:cash-flow {:client-id (:id @(re-frame/subscribe [::subs/client]))} - [:beginning-balance :outstanding-payments [:invoices-due-soon [:due :outstanding-balance]]]]]} + [:beginning-balance + :outstanding-payments + [:invoices-due-soon [:due :outstanding-balance]] + [:upcoming-credits [:date :amount]] + [:upcoming-debits [:date :amount]]]]]} :on-success [::received]}})) (defn home-content []