From bb2df01d89ac70d579edd50683ea85e21cdef424 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Sat, 30 May 2020 11:49:21 -0700 Subject: [PATCH] weekly debits/credits. --- src/clj/auto_ap/graphql.clj | 29 +++++---- src/clj/auto_ap/graphql/clients.clj | 62 ++++++++++--------- src/clj/auto_ap/parse/csv.clj | 4 +- src/clj/auto_ap/time.clj | 8 +++ src/cljc/auto_ap/entities/clients.cljc | 6 +- src/cljs/auto_ap/events.cljs | 4 +- .../views/components/invoice_table.cljs | 9 ++- .../auto_ap/views/pages/admin/clients.cljs | 29 ++++++++- src/cljs/auto_ap/views/pages/home.cljs | 45 ++++++++++++-- 9 files changed, 140 insertions(+), 56 deletions(-) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index ad5b8bff..cd9f323a 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -67,6 +67,8 @@ {:fields {:id {:type :id} :name {:type 'String} :code {:type 'String} + :weekly_debits {:type :money} + :weekly_credits {:type :money} :email {:type 'String} :address {:type :address} :location_matches {:type '(list :location_match)} @@ -520,6 +522,8 @@ :name {:type 'String} :code {:type 'String} :email {:type 'String} + :weekly_credits {:type :money} + :weekly_debits {:type :money} :address {:type :add_address} :locations {:type '(list String)} :matches {:type '(list String)} @@ -900,7 +904,8 @@ (defn get-cash-flow [context {:keys [client_id]} value] (when client_id - (let [total-cash (reduce + (let [{:client/keys [weekly-credits weekly-debits ]} (doto (d/pull (d/db (d/connect uri)) '[*] client_id ) println) + total-cash (reduce (fn [total [credit debit]] (- (+ total credit) debit)) @@ -921,7 +926,7 @@ '[?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)))]}) + :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/days 31)))]}) outstanding-checks (reduce + 0.0 @@ -933,21 +938,23 @@ '(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)))]})))] + :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (auto-ap.time/local-now) (t/days 31)))]})))] {: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)))}]}))) + :upcoming_credits (mapv + (fn [date] + {:amount (or weekly-credits 0) + :date (coerce/to-date-time date)}) + (take 5 (auto-ap.time/day-of-week-seq 1))) + :upcoming_debits (mapv + (fn [date] + {:amount (- (or weekly-debits 0)) + :date (coerce/to-date-time date)}) + (take 5 (auto-ap.time/day-of-week-seq 1)))}))) (def schema (-> integreat-schema diff --git a/src/clj/auto_ap/graphql/clients.clj b/src/clj/auto_ap/graphql/clients.clj index 6e53ec0e..221a4934 100644 --- a/src/clj/auto_ap/graphql/clients.clj +++ b/src/clj/auto_ap/graphql/clients.clj @@ -27,45 +27,47 @@ (mapv (fn [lm] [:db/retractEntity (:db/id lm)]) (:client/location-matches client)) (mapv (fn [m] [:db/retract (:db/id client) :client/matches m]) (:client/matches client))))) transactions [(remove-nils {:db/id id - :client/code (if (str/blank? (:client/code client)) - (:code edit_client) - (:client/code client)) - :client/name (:name edit_client) + :client/code (if (str/blank? (:client/code client)) + (:code edit_client) + (:client/code client)) + :client/name (:name edit_client) :client/matches (:matches edit_client) - :client/email (:email edit_client) - :client/locations (filter identity (:locations edit_client)) + :client/email (:email edit_client) + :client/locations (filter identity (:locations edit_client)) + :client/weekly-debits (:weekly_debits edit_client) + :client/weekly-credits (:weekly_credits edit_client) :client/location-matches (->> (:location_matches edit_client) (filter (fn [lm] (and (:location lm) (:match lm)))) (map (fn [lm] {:location-match/location (:location lm) :location-match/matches [(:match lm)]}))) - :client/address (remove-nils { - :address/street1 (:street1 (:address edit_client)) - :address/street2 (:street2 (:address edit_client)) - :address/city (:city (:address edit_client)) - :address/state (:state (:address edit_client)) - :address/zip (:zip (:address edit_client))}) + :client/address (remove-nils { + :address/street1 (:street1 (:address edit_client)) + :address/street2 (:street2 (:address edit_client)) + :address/city (:city (:address edit_client)) + :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 %) - :bank-account/bank-name (:bank_name %) - :bank-account/bank-code (:bank_code %) - :bank-account/routing (:routing %) - :bank-account/include-in-reports (:include_in_reports %) + :client/bank-accounts (map #(remove-nils + {:db/id (:id %) + :bank-account/code (:code %) + :bank-account/bank-name (:bank_name %) + :bank-account/bank-code (:bank_code %) + :bank-account/routing (:routing %) + :bank-account/include-in-reports (:include_in_reports %) - :bank-account/name (:name %) - :bank-account/visible (:visible %) - :bank-account/number (:number %) - :bank-account/check-number (:check_number %) - :bank-account/sort-order (:sort_order %) - :bank-account/locations (:locations %) + :bank-account/name (:name %) + :bank-account/visible (:visible %) + :bank-account/number (:number %) + :bank-account/check-number (:check_number %) + :bank-account/sort-order (:sort_order %) + :bank-account/locations (:locations %) - :bank-account/yodlee-account-id (:yodlee_account_id %) - :bank-account/type (keyword "bank-account-type" (name (:type %))) - } - ) (:bank_accounts edit_client)) + :bank-account/yodlee-account-id (:yodlee_account_id %) + :bank-account/type (keyword "bank-account-type" (name (:type %))) + } + ) (:bank_accounts 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/parse/csv.clj b/src/clj/auto_ap/parse/csv.clj index 228ca539..9ae43913 100644 --- a/src/clj/auto_ap/parse/csv.clj +++ b/src/clj/auto_ap/parse/csv.clj @@ -68,7 +68,8 @@ (into {} (map vector header row)))) (map (fn [row] {:vendor-code nil - :customer-identifier (get row "Ship-To Name") + :customer-identifier (str (get row "Ship-To Name") " " (or (get row "Ship-To Number") + (get row "\"Ship-To Number\""))) :invoice-number (str/trim (get row "Invoice Number")) :date (parse-date-fallover (get row "Invoice Date") ["yyyy-MM-dd"]) :total (str/replace (get row "Original Amount") #"[,\$]" "") @@ -81,7 +82,6 @@ (defmethod parse-csv :mama-lus [rows] - (println "MAMA LU4") (transduce (comp (drop 1) (map (fn [[_ po-number despatch-number invoice-number invoice-date customer value :as row]] diff --git a/src/clj/auto_ap/time.clj b/src/clj/auto_ap/time.clj index bbf0148a..0a104ab4 100644 --- a/src/clj/auto_ap/time.clj +++ b/src/clj/auto_ap/time.clj @@ -20,3 +20,11 @@ (f/unparse (f/formatter format) v) (catch Exception e nil))) + + +(defn day-of-week-seq [day] + (let [next-day (loop [d (auto-ap.time/local-now)] + (if (= (time/day-of-week d) day) + d + (recur (time/plus d (time/days 1)))))] + (iterate #(time/plus % (time/days 7)) next-day))) diff --git a/src/cljc/auto_ap/entities/clients.cljc b/src/cljc/auto_ap/entities/clients.cljc index ac0a5bea..99386259 100644 --- a/src/cljc/auto_ap/entities/clients.cljc +++ b/src/cljc/auto_ap/entities/clients.cljc @@ -34,6 +34,9 @@ :checking ::checking-bank-account :credit ::credit-account)) (s/def ::bank-accounts (s/coll-of ::bank-account)) +;; disabled because graphql defaults to string representation +#_(s/def ::weekly-debits (s/nilable double?)) +#_(s/def ::weekly-credits (s/nilable double?)) (s/def ::location string?) (s/def ::locations (s/coll-of ::location :min-count 1)) @@ -45,7 +48,8 @@ (s/def ::client (s/keys :req-un [::name ::code ::locations] :opt-un [::email ::address - + #_::weekly-debits + #_::weekely-credits ::bank-accounts ::id])) diff --git a/src/cljs/auto_ap/events.cljs b/src/cljs/auto_ap/events.cljs index b85e0a3b..303eab66 100644 --- a/src/cljs/auto_ap/events.cljs +++ b/src/cljs/auto_ap/events.cljs @@ -44,7 +44,7 @@ :graphql {:token token :query-obj {:venia/queries [[:client - [:id :name :code :email :matches :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] ] + [: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]]]] [: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 @@ -71,7 +71,7 @@ (fn [{:keys [db]} [_ token user]] {:graphql {:token token :query-obj {:venia/queries [[:client - [:id :name :code :matches :locations [: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] ]]] [: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 78603ed0..114af5ee 100644 --- a/src/cljs/auto_ap/views/components/invoice_table.cljs +++ b/src/cljs/auto_ap/views/components/invoice_table.cljs @@ -1,6 +1,9 @@ (ns auto-ap.views.components.invoice-table (:require [re-frame.core :as re-frame] + [bidi.bidi :as bidi] + [cemerick.url :as url] [auto-ap.subs :as subs] + [auto-ap.routes :as routes] [auto-ap.views.utils :refer [date->str dispatch-event delayed-dispatch nf]] [auto-ap.views.components.paginator :refer [paginator]] [auto-ap.views.components.sort-by-list :refer [sort-by-list]] @@ -145,9 +148,11 @@ } "Payments"]} [:div (for [payment payments] - (if (:s3-url (:payment payment)) + (if (:check-number (:payment payment)) ^{:key (:id payment)} - [:a.dropdown-item {:href (:s3-url (:payment payment)) + [:a.dropdown-item {:href (str (bidi/path-for routes/routes :payments ) + "?" + (url/map->query {:check-number-like (:check-number (:payment payment))})) :target "_new"} [:i.fa.fa-money-check] [:span.icon [:i.fa.fa-money]] diff --git a/src/cljs/auto_ap/views/pages/admin/clients.cljs b/src/cljs/auto_ap/views/pages/admin/clients.cljs index fae3ac11..c8559059 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients.cljs @@ -44,7 +44,8 @@ :email (:email new-client-data) :locations (:locations new-client-data) :matches (vec (:matches new-client-data)) - :location-matches (:location-matches new-client-data) + :location-matches (:location-matches new-client-data) :weekly-credits (:weekly-credits new-client-data) + :weekly-debits (:weekly-debits new-client-data) :address {:street1 (:street1 (:address new-client-data)) :street2 (:street2 (:address new-client-data)), :city (:city (:address new-client-data)) @@ -90,7 +91,10 @@ :operation/name "EditClient"} :venia/queries [{:query/data [:edit-client {:edit-client new-client-req} - [:id :name :code :email :locations :matches [:location-matches [:location :match]] [:address [:street1 :street2 :city :state :zip]] [:bank-accounts [:id :number :check-number :name :code :bank-code :bank-name :routing :type :visible :yodlee-account-id :sort-order :locations]]]]}]} + [:id :name :code :email :locations :matches :weekly-debits :weekly-credits + [:location-matches [:location :match]] + [:address [:street1 :street2 :city :state :zip]] + [: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]}} {:db new-client-form})))) @@ -556,6 +560,27 @@ #_(when (:saving? new-client) [:div.is-overlay {:style {"backgroundColor" "rgba(150,150,150, 0.5)"}}]) #_(println (s/explain-data ::entity/client new-client)) + [:div.field + [:p.help "Weekly credits"] + [:div.control + [bind-field + [:input.input {:type "number" + :placeholder "250.00" + :field [:weekly-credits] + :step "0.01" + :event change-event + :subscription new-client}]]]] + [:div.field + [:p.help "Weekly debits"] + [:div.control + [bind-field + [:input.input {:type "number" + :placeholder "250.00" + :field [:weekly-debits] + :step "0.01" + :event change-event + :subscription new-client}]]]] + (when error [:div.notification.is-warning.animated.fadeInUp error]) diff --git a/src/cljs/auto_ap/views/pages/home.cljs b/src/cljs/auto_ap/views/pages/home.cljs index 55604e94..2137cae3 100644 --- a/src/cljs/auto_ap/views/pages/home.cljs +++ b/src/cljs/auto_ap/views/pages/home.cljs @@ -5,9 +5,10 @@ [auto-ap.routes :as routes] [auto-ap.subs :as subs] [cljs-time.core :as t] - [auto-ap.views.utils :refer [local-now date->str standard]] + [auto-ap.views.utils :refer [local-now date->str standard dispatch-event]] [reagent.core :as r] - [pushy.core :as pushy])) + [pushy.core :as pushy] + )) (def pie-chart (r/adapt-react-class js/Recharts.PieChart)) @@ -91,11 +92,22 @@ (seq cash-flow) (assoc ::cash-flow cash-flow))))) +(re-frame/reg-event-db + ::select-cash-flow-range + [(re-frame/path ::chart-options)] + (fn [chart-options [_ which]] + (assoc chart-options :cash-flow-range which))) + (re-frame/reg-sub ::invoice-stats (fn [db] (::invoice-stats db))) +(re-frame/reg-sub + ::chart-options + (fn [db] + (merge {:cash-flow-range :seven-days} (::chart-options db)))) + (re-frame/reg-sub ::top-expense-categories (fn [db] @@ -113,9 +125,16 @@ pairs)) (re-frame/reg-sub - ::cash-flow + ::cash-flow-data (fn [db] - (let [{:keys [outstanding-payments beginning-balance invoices-due-soon upcoming-credits upcoming-debits]} (::cash-flow db) + (::cash-flow db))) + +(re-frame/reg-sub + ::cash-flow + :<- [::chart-options] + :<- [::cash-flow-data] + (fn [[chart-options cash-flow-data]] + (let [{:keys [outstanding-payments beginning-balance invoices-due-soon upcoming-credits upcoming-debits]} cash-flow-data 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)) @@ -148,7 +167,9 @@ :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)))))) + (if (= :seven-days (:cash-flow-range chart-options)) + (range 1 7) + (range 1 31))))))) (re-frame/reg-event-fx ::mounted @@ -171,7 +192,8 @@ :on-success [::received]}})) (defn home-content [] - (let [client-id (-> @(re-frame/subscribe [::subs/client]) :id)] + (let [client-id (-> @(re-frame/subscribe [::subs/client]) :id) + chart-options @(re-frame/subscribe [::chart-options])] ^{:key client-id} [side-bar-layout {:side-bar [:div] :main [:div [:h1.title "Home"] @@ -184,6 +206,17 @@ @(re-frame/subscribe [::invoice-stats]))}) [:h1.title.is-4 "Cash Flow"] + [:div.buttons.has-addons + [:a.button {:class (when (= :seven-days (:cash-flow-range chart-options)) + ["is-info" "is-selected"]) + :on-click (dispatch-event [::select-cash-flow-range :seven-days])} + "7 days"] + [:a.button {:class (when (= :thirty-days (:cash-flow-range chart-options)) + ["is-info" "is-selected"]) + :on-click (dispatch-event [::select-cash-flow-range :thirty-days])} + "30 days"]] + + (make-cash-flow-chart {:width 800 :height 500 :data (clj->js @(re-frame/subscribe [::cash-flow]))})]}]))