From ef843ac9a2aee4a602f059e65a6b0ec7180a2eae Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Mon, 15 Jun 2020 08:19:43 -0700 Subject: [PATCH] starting work on cash flow. --- src/clj/auto_ap/datomic/invoices.clj | 23 ++++++- src/clj/auto_ap/graphql.clj | 20 +++++- src/clj/auto_ap/graphql/clients.clj | 10 ++- src/clj/auto_ap/graphql/invoices.clj | 3 +- src/cljs/auto_ap/effects.cljs | 1 - src/cljs/auto_ap/events.cljs | 8 ++- .../views/components/invoice_table.cljs | 7 +- src/cljs/auto_ap/views/components/modal.cljs | 2 +- .../views/components/vendor_dialog.cljs | 2 +- .../auto_ap/views/pages/admin/clients.cljs | 68 ++++++++++++++++++- src/cljs/auto_ap/views/utils.cljs | 12 +++- 11 files changed, 136 insertions(+), 20 deletions(-) diff --git a/src/clj/auto_ap/datomic/invoices.clj b/src/clj/auto_ap/datomic/invoices.clj index 66145da9..3eb0dec3 100644 --- a/src/clj/auto_ap/datomic/invoices.clj +++ b/src/clj/auto_ap/datomic/invoices.clj @@ -111,7 +111,7 @@ "location" ['[?e :invoice/expense-accounts ?iea] '[?iea :invoice-expense-account/location ?sort-location]] "date" ['[?e :invoice/date ?sort-date]] - "due" ['[?e :invoice/due ?sort-due]] + "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]]} @@ -134,13 +134,30 @@ (mapv <-datomic))] invoices)) +(defn sum-outstanding [ids] + + (->> + (d/query {:query {:find ['?o] + :in ['$ '[?id ...]] + :where ['[?id :invoice/outstanding-balance ?o]] + } + :args [(d/db (d/connect uri)) + ids]}) + (map first) + (reduce + + + 0.0))) + (defn get-graphql [args] (let [db (d/db (d/connect uri)) - {ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)] + {ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args) + outstanding (sum-outstanding ids-to-retrieve)] [(->> (graphql-results ids-to-retrieve db args)) - matching-count])) + matching-count + (doto + outstanding println)])) (defn get-by-id [id] (-> (d/db (d/connect uri)) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index f680f5f1..28525682 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -74,7 +74,8 @@ :location_matches {:type '(list :location_match)} :locations {:type '(list String)} :matches {:type '(list String)} - :bank_accounts {:type '(list :bank_account)}}} + :bank_accounts {:type '(list :bank_account)} + :forecasted_transactions {:type '(list :forecasted_transaction)}}} :contact {:fields {:id {:type :id} :name {:type 'String} @@ -96,6 +97,10 @@ :bank_name {:type 'String} :yodlee_account_id {:type 'Int} :locations {:type '(list String)}}} + :forecasted_transaction {:fields {:identifier {:type 'String} + :id {:type :id} + :day_of_month {:type 'Int} + :amount {:type :money}}} :balance_sheet_account {:fields {:id {:type 'String} :amount {:type 'String} @@ -295,6 +300,7 @@ :invoice_page {:fields {:invoices {:type '(list :invoice)} + :outstanding {:type :money} :count {:type 'Int} :total {:type 'Int} :start {:type 'Int} @@ -500,7 +506,13 @@ :invoice_payment_amount {:fields {:invoice_id {:type :id} :amount {:type 'Float}}} :edit_location_match {:fields {:location {:type 'String} - :match {:type 'String}}} + :match {:type 'String}}} + + :edit_forecasted_transaction {:fields {:identifier {:type 'String} + :id {:type :id} + :day_of_month {:type 'Int} + :amount {:type :money}}} + :date_range {:fields {:start {:type :iso_date} :end {:type :iso_date}}} @@ -529,7 +541,9 @@ :locations {:type '(list String)} :matches {:type '(list String)} :location_matches {:type '(list :edit_location_match)} - :bank_accounts {:type '(list :edit_bank_account)}}} + :bank_accounts {:type '(list :edit_bank_account)} + :forecasted_transactions {:type '(list :edit_forecasted_transaction)} + }} :edit_bank_account {:fields {:id {:type :id } :code {:type 'String} diff --git a/src/clj/auto_ap/graphql/clients.clj b/src/clj/auto_ap/graphql/clients.clj index 221a4934..ae29517d 100644 --- a/src/clj/auto_ap/graphql/clients.clj +++ b/src/clj/auto_ap/graphql/clients.clj @@ -47,6 +47,7 @@ :address/state (:state (:address edit_client)) :address/zip (:zip (:address edit_client))}) + :client/bank-accounts (map #(remove-nils {:db/id (:id %) :bank-account/code (:code %) @@ -67,7 +68,14 @@ } ) (:bank_accounts edit_client)) - })] + }) + [:reset id :client/forecasted-transactions (map #(remove-nils + {:db/id (:id %) + :forecasted-transaction/day-of-month (:day_of_month %) + :forecasted-transaction/identifier (:identifier %) + :forecasted-transaction/amount (:amount %)} + ) + (:forecasted_transactions edit_client))]] result @(d/transact (d/connect uri) transactions)] (println result "ID" id) (-> result :tempids (get id) (or id) d-clients/get-by-id diff --git a/src/clj/auto_ap/graphql/invoices.clj b/src/clj/auto_ap/graphql/invoices.clj index 8b413a35..be468765 100644 --- a/src/clj/auto_ap/graphql/invoices.clj +++ b/src/clj/auto_ap/graphql/invoices.clj @@ -17,8 +17,9 @@ (defn get-invoice-page [context args value] (let [args (assoc args :id (:id context)) - [invoices invoice-count] (d-invoices/get-graphql (update (<-graphql (assoc args :id (:id context))) :status enum->keyword "invoice-status"))] + [invoices invoice-count outstanding] (d-invoices/get-graphql (update (<-graphql (assoc args :id (:id context))) :status enum->keyword "invoice-status"))] [{:invoices (map ->graphql invoices) + :outstanding outstanding :total invoice-count :count (count invoices) :start (:start args 0) diff --git a/src/cljs/auto_ap/effects.cljs b/src/cljs/auto_ap/effects.cljs index 7c8d9362..fc9da770 100644 --- a/src/cljs/auto_ap/effects.cljs +++ b/src/cljs/auto_ap/effects.cljs @@ -159,7 +159,6 @@ :graphql (fn [{:keys [query on-success on-error token variables query-obj]}] (go - (println on-error) (let [headers (if token {"Authorization" (str "Token " token)} {}) diff --git a/src/cljs/auto_ap/events.cljs b/src/cljs/auto_ap/events.cljs index 303eab66..c276f33c 100644 --- a/src/cljs/auto_ap/events.cljs +++ b/src/cljs/auto_ap/events.cljs @@ -45,7 +45,8 @@ :query-obj {:venia/queries [[:client [:id :name :code :email :matches :weekly-debits :weekly-credits :locations [:location-matches [:location :match]] [:bank-accounts [:id :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations :include-in-reports] ] - [:address [:street1 :street2 :city :state :zip]]]] + [:address [:street1 :street2 :city :state :zip]] + [:forecasted-transactions [:id :amount :identifier :day-of-month]]]] [:vendor [:id :name :hidden [:default-account [:name :id :location]] [:primary-contact [:name :phone :email :id]] [:secondary-contact [:id :name :phone :email]] :print-as :invoice-reminder-schedule :code [:account-overrides [[:client [:id :name]] :id [:account [:id :numeric-code :name]]]] @@ -71,7 +72,10 @@ (fn [{:keys [db]} [_ token user]] {:graphql {:token token :query-obj {:venia/queries [[:client - [:id :name :code :matches :locations :weekly-debits :weekly-credits [:location-matches [:location :match]] [:address [:street1 :street2 :city :state :zip]] [:bank-accounts [:id :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations :include-in-reports] ]]] + [:id :name :code :matches :locations :weekly-debits :weekly-credits [:location-matches [:location :match]] + [:address [:street1 :street2 :city :state :zip]] + [:bank-accounts [:id :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations :include-in-reports] ] + [:forecasted-transactions [:id :amount :identifier :day-of-month]]]] [:vendor [:id :name :hidden [:default-account [:name :id :location]] [:primary-contact [:name :phone :email :id]] [:secondary-contact [:id :name :phone :email]] :print-as :invoice-reminder-schedule :code]] [:accounts [:numeric-code :name :location :type :account_set :applicability :id [:client-overrides [:name [:client [:name :id]]]]]]]} diff --git a/src/cljs/auto_ap/views/components/invoice_table.cljs b/src/cljs/auto_ap/views/components/invoice_table.cljs index 114af5ee..46880794 100644 --- a/src/cljs/auto_ap/views/components/invoice_table.cljs +++ b/src/cljs/auto_ap/views/components/invoice_table.cljs @@ -57,6 +57,7 @@ [:client [:name :id :locations]] [:payments [:amount :id [:payment [:id :status :amount :s3_url :check_number [:transaction [:post_date]]]]]]]] + :outstanding :total :start :end]]]}) @@ -172,7 +173,7 @@ visible-expense-accounts @(re-frame/subscribe [::visible-expense-accounts]) selected-client @(re-frame/subscribe [::subs/client]) {:keys [sort]} @(re-frame/subscribe [::table-params]) - {:keys [invoices start end count total]} @invoice-page + {:keys [invoices outstanding start end count total]} @invoice-page visible-checks @(re-frame/subscribe [::visible-checks]) visible-expense-accounts @(re-frame/subscribe [::visible-expense-accounts]) selected-client @(re-frame/subscribe [::subs/client]) @@ -200,7 +201,9 @@ :on-change opc}]] [:div.level-item [sort-by-list {:sort sort - :on-change opc}]]]] + :on-change opc}]] + [:div.level-item + "Outstanding" (nf outstanding)]]] (doall (for [invoices invoice-groups] ^{:key (:id (first invoices))} diff --git a/src/cljs/auto_ap/views/components/modal.cljs b/src/cljs/auto_ap/views/components/modal.cljs index 7c76d8d5..8720d418 100644 --- a/src/cljs/auto_ap/views/components/modal.cljs +++ b/src/cljs/auto_ap/views/components/modal.cljs @@ -14,7 +14,7 @@ [:header.modal-card-head [:p.modal-card-title title] - [:button.delete {:on-click (fn [] (re-frame/dispatch hide-event))}]] + [:button.delete {:on-click (fn [e] (.preventDefault e) (re-frame/dispatch hide-event))}]] (into [:section.modal-card-body] (r/children (r/current-component))) diff --git a/src/cljs/auto_ap/views/components/vendor_dialog.cljs b/src/cljs/auto_ap/views/components/vendor_dialog.cljs index 8ffdb948..710d6528 100644 --- a/src/cljs/auto_ap/views/components/vendor_dialog.cljs +++ b/src/cljs/auto_ap/views/components/vendor_dialog.cljs @@ -107,7 +107,7 @@ ::save [with-user with-is-admin? (forms/triggers-loading ::vendor-form) (forms/in-form ::vendor-form)] (fn [{:keys [user is-admin?] {{:keys [name hidden print-as terms invoice-reminder-schedule primary-contact secondary-contact address default-account terms-overrides account-overrides id] :as data} :data} :db} _] - (println user) + (println user is-admin?) (when (s/valid? ::entity/vendor data) { :graphql {:token user diff --git a/src/cljs/auto_ap/views/pages/admin/clients.cljs b/src/cljs/auto_ap/views/pages/admin/clients.cljs index 177ee6eb..99a0ffea 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients.cljs @@ -13,7 +13,7 @@ [auto-ap.views.components.address :refer [address-field]] [auto-ap.views.components.layouts :refer [side-bar-layout appearing-side-bar side-bar] ] [auto-ap.views.components.admin.side-bar :refer [admin-side-bar]] - [auto-ap.views.utils :refer [login-url dispatch-event dispatch-value-change bind-field horizontal-field]] + [auto-ap.views.utils :refer [login-url dispatch-event dispatch-value-change bind-field horizontal-field nf]] [auto-ap.views.components.modal :refer [action-modal]] [cljs.reader :as edn] [auto-ap.routes :as routes] @@ -58,6 +58,12 @@ :city (:city (:address new-client-data)) :state (:state (:address new-client-data)) :zip (:zip (:address new-client-data))} + :forecasted-transactions (map (fn [{:keys [id day-of-month identifier amount]}] + {:id id + :day-of-month day-of-month + :identifier identifier + :amount amount}) + (:forecasted-transactions new-client-data)) :bank-accounts (map (fn [{:keys [number name check-number include-in-reports type id code bank-name routing bank-code new? sort-order visible yodlee-account-id locations]}] {:number number :name name @@ -101,6 +107,7 @@ [:id :name :code :email :locations :matches :weekly-debits :weekly-credits [:location-matches [:location :match]] [:address [:street1 :street2 :city :state :zip]] + [:forecasted-transactions [:id :amount :identifier :day-of-month]] [:bank-accounts [:id :number :check-number :name :code :bank-code :bank-name :routing :type :visible :yodlee-account-id :sort-order :locations]]]]}]} :on-success [::save-complete] :on-error [::forms/save-error ::new-client]}} @@ -157,6 +164,28 @@ (update :location-matches conj (:location-match client)) (dissoc :location-match)))) +(re-frame/reg-event-db + ::add-forecasted-transaction + [(forms/in-form ::form) (re-frame/path [:data])] + (fn [client _] + (-> client + (update :forecasted-transactions conj (assoc (:new-forecasted-transaction client) :temp-id (random-uuid))) + (dissoc :new-forecasted-transaction)))) + +(re-frame/reg-event-db + ::remove-forecasted-transaction + [(forms/in-form ::form) (re-frame/path [:data])] + (fn [client [_ which]] + + (-> client + (update :forecasted-transactions #(transduce + (filter (fn [x] (and (not= (:temp-id x) which) + (not= (:id x) which)))) + + conj + [] + %))))) + (re-frame/reg-event-db ::remove-location-match [(forms/in-form ::form) (re-frame/path [:data])] @@ -506,6 +535,7 @@ [:div.column.is-third [:a.button.is-primary.is-outlined.is-fullwidth {:on-click (dispatch-event [::add-new-bank-account :cash])} "Add Cash Account"]]] + [:h2.subtitle "Cash flow"] [field "Weekly credits" [:input.input {:type "number" :placeholder "250.00" @@ -516,8 +546,42 @@ :placeholder "250.00" :field [:weekly-debits] :step "0.01"}]] + [:div.field + [:label.label "Forecasted transactions"] + [:div.control + [horizontal-field + nil + + [:p.control + [:p.help "Identifier"] + [raw-field + [:input.input {:type "text" + :placeholder "Identifier" + :field [:new-forecasted-transaction :identifier]}]]] + [:p.control + [:p.help "Day of month"] + [raw-field + [:input.input {:type "number" + :placeholder "Day of month" + :step "1" + :field [:new-forecasted-transaction :day-of-month]}]]] + [:p.control + [:p.help "Amount"] + [raw-field + [:input.input {:type "number" + :placeholder "250.00" + :field [:new-forecasted-transaction :amount] + :step "0.01"}]]] + [:a.button {:on-click (dispatch-event [::add-forecasted-transaction])} "Add"]]] + + [:ul + (for [forecasted-transaction (:forecasted-transactions new-client)] + ^{:key (or (:id forecasted-transaction) + (:temp-id forecasted-transaction))} + [:li (:identifier forecasted-transaction) ": " (nf (:amount forecasted-transaction)) " on day " (:day-of-month forecasted-transaction) " of the month" + [:a {:on-click (dispatch-event [::remove-forecasted-transaction (or (:id forecasted-transaction) + (:temp-id forecasted-transaction))])} [:span.icon [:span.fa.fa-times]]]])]] - [error-notification] [submit-button "Save"]]])) diff --git a/src/cljs/auto_ap/views/utils.cljs b/src/cljs/auto_ap/views/utils.cljs index e180fcb1..23fdcef3 100644 --- a/src/cljs/auto_ap/views/utils.cljs +++ b/src/cljs/auto_ap/views/utils.cljs @@ -11,7 +11,8 @@ [cljs-time.format :as format] [goog.i18n.NumberFormat.Format] [cljs-time.core :as t] - [clojure.string :as str]) + [clojure.string :as str] + [goog.crypt.base64 :as base64]) (:import (goog.i18n NumberFormat) (goog.i18n.NumberFormat Format))) @@ -327,8 +328,13 @@ :before (fn [context] (-> context (assoc-in [:coeffects :is-admin?] (= "admin" - (:user/role - (get-in context [:coeffects :db :user])))))))) + (-> (get-in context [:coeffects :db :user]) + (str/split #"\.") + second + (base64/decodeString ) + (#(.parse js/JSON % )) + (js->clj :keywordize-keys true) + :user/role))))))) (defn query-params [] (reduce-kv