a much better general ledger profit and loss.

This commit is contained in:
Bryce Covert
2020-07-16 22:20:35 -07:00
parent e0b636c2fa
commit 976cd1e7c3
9 changed files with 347 additions and 363 deletions

1
hours.txt Normal file
View File

@@ -0,0 +1 @@
7/16/2020 4 hours

View File

@@ -82,7 +82,7 @@
'[(<= ?c ?to-numeric-code)]]} '[(<= ?c ?to-numeric-code)]]}
:args [(:to-numeric-code args)]}) :args [(:to-numeric-code args)]})
(:location args) (not-empty (:location args))
(merge-query {:query {:in ['?location] (merge-query {:query {:in ['?location]
:where ['[?e :journal-entry/line-items ?li] :where ['[?e :journal-entry/line-items ?li]
'[?li :journal-entry-line/location ?location]]} '[?li :journal-entry-line/location ?location]]}

View File

@@ -115,6 +115,12 @@
{:fields {:balance_sheet_accounts {:type '(list :balance_sheet_account)} {:fields {:balance_sheet_accounts {:type '(list :balance_sheet_account)}
:comparable_balance_sheet_accounts {:type '(list :balance_sheet_account)}}} :comparable_balance_sheet_accounts {:type '(list :balance_sheet_account)}}}
:profit_and_loss_report_period
{:fields {:accounts {:type '(list :balance_sheet_account)}}}
:profit_and_loss_report
{:fields {:periods {:type '(list :profit_and_loss_report_period)}}}
:address :address
{:fields {:street1 {:type 'String} {:fields {:street1 {:type 'String}
:street2 {:type 'String} :street2 {:type 'String}
@@ -404,9 +410,9 @@
:date {:type :iso_date}} :date {:type :iso_date}}
:resolve :get-balance-sheet} :resolve :get-balance-sheet}
:profit_and_loss {:type :balance_sheet :profit_and_loss {:type :profit_and_loss_report
:args {:client_id {:type :id} :args {:client_id {:type :id}
:date_range {:type :date_range}} :periods {:type '(list :date_range)}}
:resolve :get-profit-and-loss} :resolve :get-profit-and-loss}

View File

@@ -106,12 +106,15 @@
vals vals
(mapcat (fn [a] (mapcat (fn [a]
(map (fn [o] (map (fn [o]
[[[(:db/id a) (:db/id (:account-client-override/client o))]] #_(println "override" (:db/id a) (:db/id (:account-client-override/client o)))
#_(print [[(:db/id a) (:db/id (:account-client-override/client o))]])
[[(:db/id a) (:db/id (:account-client-override/client o))]
(:account-client-override/name o)]) (:account-client-override/name o)])
(:account/client-overrides a)) (:account/client-overrides a))
) ) ) )
(into {} ))] (into {} ))]
(fn [a] (fn [a]
#_(println a client-id (keys overrides-by-client))
{:name (or (:bank-account/name (bank-accounts a)) {:name (or (:bank-account/name (bank-accounts a))
(overrides-by-client [a client-id]) (overrides-by-client [a client-id])
(:account/name (accounts a))) (:account/name (accounts a)))
@@ -155,16 +158,16 @@
(defn get-profit-and-loss [context args value] (defn get-profit-and-loss [context args value]
(let [client-id (:client_id args) (let [client-id (:client_id args)
_ (assert-can-see-client (:id context) client-id) _ (assert-can-see-client (:id context) client-id)
start-date (coerce/to-date (:start (:date_range args))) _ (println args)
end-date (coerce/to-date (:end (:date_range args)))
comparable-start-date (coerce/to-date (time/minus (:start (:date_range args)) (time/years 1)))
comparable-end-date (coerce/to-date (time/minus (:end (:date_range args)) (time/years 1)))
all-ledger-entries (full-ledger-for-client client-id) all-ledger-entries (full-ledger-for-client client-id)
lookup-account (build-account-lookup client-id)] lookup-account (build-account-lookup client-id)]
(->graphql (->graphql
{:balance-sheet-accounts (roll-up-until lookup-account all-ledger-entries end-date start-date ) {:periods (reduce (fn [acc {:keys [start end]}]
:comparable-balance-sheet-accounts (roll-up-until lookup-account all-ledger-entries comparable-end-date comparable-start-date )}))) (conj acc
{:accounts (roll-up-until lookup-account all-ledger-entries (coerce/to-date end) (coerce/to-date start) )}))
[]
(:periods args))})))
(defn assoc-error [f] (defn assoc-error [f]

View File

@@ -77,10 +77,11 @@
(re-frame/reg-sub (re-frame/reg-sub
::accounts-by-id ::accounts-by-id
:<- [::accounts] (fn [[_ client-override]]
:<- [::client] [(re-frame/subscribe [::accounts client-override])
(fn [[accounts client] [_ client-override]] (re-frame/subscribe [::client])])
(accounts-by-id accounts (or client-override client)))) (fn [[accounts client] ]
(accounts-by-id accounts client)))
(re-frame/reg-sub (re-frame/reg-sub
::bank-accounts ::bank-accounts

View File

@@ -199,25 +199,25 @@
[:option "%"]]]]] [:option "%"]]]]]
[:p.control [:p.control
(if (= "$" amount-mode) (if (= "$" amount-mode)
[bind-field [bind-field
[:input.input {:type "number" [:input.input {:type "number"
:field [index :amount] :field [index :amount]
:style {:text-align "right" :width "7em"} :style {:text-align "right" :width "7em"}
:event [::expense-account-changed event expense-accounts max-value] :event [::expense-account-changed event expense-accounts max-value]
:disabled disabled :disabled disabled
:subscription expense-accounts :subscription expense-accounts
:precision 2 :precision 2
:value (get-in expense-account [:amount]) :value (get-in expense-account [:amount])
:max max-value :max max-value
:step "0.01"}]] :step "0.01"}]]
[bind-field [bind-field
[:input.input {:type "number" [:input.input {:type "number"
:field [index :amount-percentage] :field [index :amount-percentage]
:style {:text-align "right" :width "7em"} :style {:text-align "right" :width "7em"}
:disabled disabled :disabled disabled
:event [::expense-account-changed event expense-accounts max-value] :event [::expense-account-changed event expense-accounts max-value]
:precision 2 :precision 2
:subscription expense-accounts :subscription expense-accounts
:value (get-in expense-account [:amount-percentage]) :value (get-in expense-account [:amount-percentage])
:max "100" :max "100"
:step "0.01"}]])]]]]])])) :step "0.01"}]])]]]]])]))

View File

@@ -137,7 +137,7 @@
(re-frame/reg-event-db (re-frame/reg-event-db
::editing ::editing
(fn [db [_ which]] (fn [db [_ which]]
(let [accounts-by-id @(re-frame/subscribe [::subs/accounts-by-id (:client which)])] (let [accounts-by-id @(re-frame/subscribe [::subs/all-accounts-by-id (:client which)])]
(-> db (forms/start-form ::form (-> which (-> db (forms/start-form ::form (-> which
(select-keys [:description (select-keys [:description
:id :id
@@ -155,7 +155,8 @@
(update :accounts (fn [xs] (update :accounts (fn [xs]
(mapv #(-> % (mapv #(-> %
(assoc :amount-percentage (* (:percentage %) 100.0)) (assoc :amount-percentage (* (:percentage %) 100.0))
(update :account (fn [a] (accounts-by-id (:id a))))) (update :account (fn [a]
(accounts-by-id (:id a)))))
xs))))))))) xs)))))))))

View File

@@ -10,7 +10,7 @@
[cljs-time.core :as t] [cljs-time.core :as t]
[re-frame.core :as re-frame])) [re-frame.core :as re-frame]))
(def ranges (def ranges
{:sales [40000 48999] {:sales [40000 49999]
:cogs [50000 59999] :cogs [50000 59999]
:payroll [60000 69999] :payroll [60000 69999]
:controllable [70000 79999] :controllable [70000 79999]
@@ -23,12 +23,10 @@
(fn [db] (fn [db]
(->> db (->> db
::report ::report
:balance-sheet-accounts :periods
(mapcat :accounts)
(map :location) (map :location)
(concat (->> db (map not-empty)
::report
:comparable-balance-sheet-accounts
(map :location)))
(filter #(not= "A" %)) (filter #(not= "A" %))
(filter identity) (filter identity)
(set) (set)
@@ -93,11 +91,10 @@
params))) params)))
(re-frame/reg-sub (re-frame/reg-sub
::accounts ::period-accounts
(fn [db [_ type only-location]] (fn [db [_ which type only-location]]
(->> db
::report (->> (get-in db [::report :periods which :accounts])
:balance-sheet-accounts
(map #(update % :amount js/parseFloat)) (map #(update % :amount js/parseFloat))
(filter (fn [{:keys [account-type location numeric-code]}] (filter (fn [{:keys [account-type location numeric-code]}]
(and (or (nil? only-location) (and (or (nil? only-location)
@@ -105,68 +102,35 @@
(<= (get-in ranges [type 0]) numeric-code (get-in ranges [type 1]))))) (<= (get-in ranges [type 0]) numeric-code (get-in ranges [type 1])))))
(sort-by :numeric-code)))) (sort-by :numeric-code))))
(re-frame/reg-sub (defn parse-amounts [period]
::amount (update period :accounts (fn [a]
(fn [[_ type only-location]] (map #(update % :amount js/parseFloat) a))))
[(re-frame/subscribe [::accounts type only-location])])
(fn [[accounts] _]
(reduce + 0 (map :amount accounts)))) (defn filter-accounts [accounts period [from to] only-location]
(->> (get accounts period)
vals
(filter (fn [{:keys [location numeric-code]}]
(and (or (nil? only-location)
(= only-location location))
(<= from numeric-code to))))
(sort-by :numeric-code)))
(defn aggregate-accounts [accounts]
(reduce (fnil + 0.0) 0.0 (map :amount accounts)))
(re-frame/reg-sub (re-frame/reg-sub
::comparable-amount ::all-accounts
(fn [[_ type only-location]] (fn [db [_ which type only-location]]
[(re-frame/subscribe [::comparable-accounts-by-id type only-location])]) (transduce
(fn [[accounts] _] (comp
(reduce + 0 (map :amount (vals accounts))))) (map parse-amounts)
(map #(by (juxt :numeric-code :location) (:accounts %))))
(re-frame/reg-sub conj
::percent-of-sales []
(fn [[_ type only-location]] (get-in db [::report :periods]))))
[(re-frame/subscribe [::amount :sales only-location])
(re-frame/subscribe [::amount type only-location])])
(fn [[sales accounts] _]
(if (> (or sales 0) 0 )
(/ accounts sales)
0.0)))
(re-frame/reg-sub
::comparable-percent-of-sales
(fn [[_ type only-location]]
[(re-frame/subscribe [::comparable-amount :sales only-location])
(re-frame/subscribe [::comparable-amount type only-location])])
(fn [[sales accounts] _]
(if (> (or sales 0) 0 )
(/ accounts sales)
0.0)))
(re-frame/reg-sub
::accounts-by-id
(fn [db [_ type only-location]]
(->> db
::report
:balance-sheet-accounts
(map #(update % :amount js/parseFloat))
(filter (fn [{:keys [account-type location numeric-code]}]
(and (or (nil? only-location)
(= only-location location))
(<= (get-in ranges [type 0]) numeric-code (get-in ranges [type 1])))))
(by :id))))
(re-frame/reg-sub
::comparable-accounts-by-id
(fn [db [_ type only-location]]
(->> db
::report
:comparable-balance-sheet-accounts
(map #(update % :amount js/parseFloat))
(filter (fn [{:keys [account-type location numeric-code]}]
(and (or (nil? only-location)
(= only-location location))
(<= (get-in ranges [type 0]) numeric-code (get-in ranges [type 1])))))
(by :id))))
(re-frame/reg-event-db (re-frame/reg-event-db
::received ::received
@@ -180,6 +144,11 @@
(fn [db] (fn [db]
(-> db ::params))) (-> db ::params)))
(re-frame/reg-sub
::periods
(fn [db]
(::periods db)))
;; EVENTS ;; EVENTS
(re-frame/reg-event-db (re-frame/reg-event-db
@@ -203,6 +172,7 @@
(re-frame/reg-event-fx (re-frame/reg-event-fx
::params-change ::params-change
(fn [cofx [_ params]] (fn [cofx [_ params]]
(println params)
(let [c @(re-frame/subscribe [::subs/client])] (let [c @(re-frame/subscribe [::subs/client])]
(cond-> {:db (-> (:db cofx) (cond-> {:db (-> (:db cofx)
(assoc-in [::error] nil) (assoc-in [::error] nil)
@@ -212,10 +182,9 @@
{:token (-> cofx :db :user) {:token (-> cofx :db :user)
:query-obj {:venia/queries [[:profit-and-loss :query-obj {:venia/queries [[:profit-and-loss
{:client-id (:id c) {:client-id (:id c)
:date-range {:start (:from-date params) :periods (mapv (fn [[start end] ] {:start (date->str start standard) :end (date->str end standard)} )
:end (:to-date params)}} (:periods params))}
[[:balance-sheet-accounts [:name :amount :account-type :id :numeric-code :location]] [[:periods [[:accounts [:name :amount :account-type :id :numeric-code :location]]]]]]]}
[:comparable-balance-sheet-accounts [:name :amount :account-type :id :numeric-code :location]]]]]}
:on-success [::received] :on-success [::received]
:on-error [::error]})))))) :on-error [::error]}))))))
@@ -225,18 +194,16 @@
(re-frame/reg-event-fx (re-frame/reg-event-fx
::date-picked ::date-picked
(fn [cofx [_ f date]] (fn [cofx [_ [_ period which] date]]
{:dispatch [::params-change (assoc-in @(re-frame/subscribe [::params]) {:dispatch [::range-selected (assoc-in @(re-frame/subscribe [::periods]) [period which] (str->date date standard)) nil]}))
f
date)]}))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::range-selected ::range-selected
(fn [cofx [_ from to selected]] (fn [{:keys [db]} [_ periods selected]]
{:dispatch [::params-change (assoc @(re-frame/subscribe [::params]) {:dispatch [::params-change (assoc @(re-frame/subscribe [::params])
:from-date from :periods periods
:to-date to :selected selected)]
:selected selected)]})) :db (assoc db ::periods periods)}))
(re-frame/reg-event-fx (re-frame/reg-event-fx
@@ -244,47 +211,43 @@
[with-user (re-frame/inject-cofx ::inject/sub [::ledger-params])] [with-user (re-frame/inject-cofx ::inject/sub [::ledger-params])]
(fn [{:keys [user ::ledger-params db]} [_ ]] (fn [{:keys [user ::ledger-params db]} [_ ]]
{:db (assoc db (if (seq ledger-params)
::ledger-list-loading true {:db (assoc db
::last-ledger-params ledger-params) ::ledger-list-loading true
:graphql {:token user ::last-ledger-params ledger-params)
:query-obj {:venia/queries [[:ledger-page :graphql {:token user
ledger-params :query-obj {:venia/queries [[:ledger-page
[[:journal-entries [:id ledger-params
:source [[:journal-entries [:id
:amount :source
[:vendor :amount
[:name :id]] [:vendor
[:client [:name :id]]
[:name :id]] [:client
[:line-items [:name :id]]
[:id :debit :credit :location [:line-items
[:account [:id :name]]]] [:id :debit :credit :location
:date]] [:account [:id :name]]]]
:total :date]]
:start :total
:end]]]} :start
:on-success [::ledger-list-received]}})) :end]]]}
:on-success [::ledger-list-received]}})))
(re-frame/reg-event-db (re-frame/reg-event-db
::investigate-clicked ::investigate-clicked
(fn [db [_ location from-numeric-code to-numeric-code which]] (fn [db [_ location from-numeric-code to-numeric-code which]]
(-> db (let [[from to] (get @(re-frame/subscribe [::periods]) which)]
(assoc (-> db
::ledger-list-active? true (assoc
::ledger-list-loading true ::ledger-list-active? true
::investigate-ledger-params {:client-id (:id @(re-frame/subscribe [::subs/client])) ::ledger-list-loading true
:from-numeric-code from-numeric-code ::investigate-ledger-params {:client-id (:id @(re-frame/subscribe [::subs/client]))
:to-numeric-code to-numeric-code :from-numeric-code from-numeric-code
:location location :to-numeric-code to-numeric-code
:date-range {:start (if (= :current which) :location location
(:from-date (::params db)) :date-range {:start (date->str from standard)
(date->str (t/minus (str->date (:from-date (::params db)) standard) (t/years 1)) :end (date->str to standard)}})))))
standard))
:end (if (= :current which)
(:to-date (::params db))
(date->str (t/minus (str->date (:to-date (::params db)) standard) (t/years 1))
standard))}}))))
(def groupings (def groupings
{:sales [["40000-43999 Food Sales " 40000 43999] {:sales [["40000-43999 Food Sales " 40000 43999]
@@ -323,133 +286,115 @@
["97000 Taxes" 97000 97999] ["97000 Taxes" 97000 97999]
["98000 Other Expenses" 98000 98999]]}) ["98000 Other Expenses" 98000 98999]]})
(defn percent-of-sales [amount accounts which location]
(let [sales (aggregate-accounts (filter-accounts accounts which (get ranges :sales) location))]
(if (not (dollars-0? sales))
(/ amount
sales)
0.0)))
(defn used-accounts [accounts [from to] location]
(->> accounts
(mapcat vals)
(filter #(<= from (:numeric-code %) to))
(filter #(= location (:location %)))
(map #(select-keys % [:numeric-code :name]))
(set)
(sort-by :numeric-code)))
(defn grouping [{:keys [header accounts comparable-accounts groupings location sales comparable-sales]}] (defn grouping [{:keys [header type groupings location]}]
[:<> (let [periods @(re-frame/subscribe [::periods])
(for [[grouping-name from to] groupings all-accounts @(re-frame/subscribe [::all-accounts])]
:let [matching-accounts (filter [:<>
#(<= from (:numeric-code %) to) (doall
accounts) (for [[grouping-name from to] groupings
total (reduce + 0 (map :amount matching-accounts)) :let [account-codes (used-accounts all-accounts [from to] location)]
comparable-total (reduce + 0 (map #(:amount (get comparable-accounts (:id %)) 0) matching-accounts))] :when (seq account-codes)]
:when (seq matching-accounts) ^{:key grouping-name}
] [:<>
^{:key grouping-name} [:tr [:td "---" grouping-name "---"]
[:<> (for [[_ i] (map vector periods (range))]
[:tr [:td "---" grouping-name "---"] [:<>
[:td] [:td]
[:td] [:td]
[:td] (when (not= 0 i)
[:td] [:td])])]
[:td] [:<>
] (for [{:keys [numeric-code name]} account-codes]
[:<> ^{:key numeric-code}
(for [account matching-accounts] [:tr [:td name]
^{:key (:name account)} (for [[p i] (map vector periods (range))
[:tr [:td (:name account)] :let [amount (get-in all-accounts [i [numeric-code location] :amount] 0.0)]]
[:td.has-text-right [:a {:on-click (dispatch-event [::investigate-clicked location (:numeric-code account) (:numeric-code account) :current])} [:<>
(->$ (:amount account))]] [:td.has-text-right [:a {:on-click (dispatch-event [::investigate-clicked location numeric-code numeric-code i :current])}
[:td.has-text-right (->% (if (> sales 0) (->$ amount)]]
(/ (:amount account) sales) [:td.has-text-right (->% (percent-of-sales amount all-accounts i location))]
0.0))] (when (not= 0 i)
[:td.has-text-right [:a {:on-click (dispatch-event [::investigate-clicked location (:numeric-code account) (:numeric-code account) :comparable])} [:td.has-text-right (->$ (- amount
(->$ (:amount (get comparable-accounts (:id account)) 0))]] (get-in all-accounts [(dec i) [numeric-code location] :amount] 0.0)))])])])]
[:td.has-text-right (->% (if (> comparable-sales 0)
(/ (:amount (get comparable-accounts (:id account)) 0) sales) [:tr [:th ]
0.0))] (for [[p i] (map vector periods (range))
[:td.has-text-right (->$ (- (:amount account ) (:amount (get comparable-accounts (:id account)) 0)))]])] :let [amount (aggregate-accounts (filter-accounts all-accounts i [from to] location))]]
[:<>
[:th.has-text-right.total [:a {:on-click (dispatch-event [::investigate-clicked location from to i])}
(->$ amount)]]
[:th.has-text-right.total (->% (percent-of-sales amount all-accounts i location))]
(when (not= 0 i)
[:th.has-text-right.total (->$ (- amount
(aggregate-accounts (filter-accounts all-accounts (dec i) [from to] location))))])])]]))]))
[:tr [:th ]
[:th.has-text-right.total [:a
{:on-click (dispatch-event [::investigate-clicked location from to :current])}
(->$ total)] ]
[:th.has-text-right.total (->% (if (> sales 0)
(/ total sales)
0.0))]
[:th.has-text-right.total [:a
{:on-click (dispatch-event [::investigate-clicked location from to :comparable])}
(->$ comparable-total)]]
[:th.has-text-right.total (->% (if (> comparable-sales 0)
(/ comparable-total sales)
0.0))]
[:th.has-text-right.total (->$ (- total comparable-total))]
[:td]
]
[:tr [:td]]])])
(defn overall-grouping [type title location] (defn overall-grouping [type title location]
(let [accounts @(re-frame/subscribe [::accounts type location]) (let [all-accounts @(re-frame/subscribe [::all-accounts])
min-numeric-code (or (first (map :numeric-code accounts)) 0) periods @(re-frame/subscribe [::periods])
max-numeric-code (or (last (map :numeric-code accounts)) 0)] [min-numeric-code max-numeric-code] (ranges type)]
[:<> [:<>
[:tr [:th.is-size-5 title] [:tr [:th.is-size-5 title]]
[:td]
[:td]
[:td]]
[grouping {:accounts accounts [grouping {:location location
:location location :groupings (type groupings)}]
:groupings (type groupings)
:comparable-accounts @(re-frame/subscribe [::comparable-accounts-by-id type location])
:sales @(re-frame/subscribe [::amount :sales location])
:comparable-sales @(re-frame/subscribe [::comparable-amount :sales location])}]
[:tr [:th.is-size-5 title] [:tr [:th.is-size-5 title]
[:th.has-text-right [:a (for [[i p] (map vector (range) periods)
{:on-click (dispatch-event [::investigate-clicked location min-numeric-code max-numeric-code :current])} :let [amount (aggregate-accounts (filter-accounts all-accounts i [min-numeric-code max-numeric-code] location))]]
(->$ @(re-frame/subscribe [::amount type location]))]]
[:th.has-text-right (->% @(re-frame/subscribe [::percent-of-sales type location]))] [:<>
[:th.has-text-right [:a [:th.has-text-right [:a
{:on-click (dispatch-event [::investigate-clicked location min-numeric-code max-numeric-code :comparable])} {:on-click (dispatch-event [::investigate-clicked location min-numeric-code max-numeric-code i])}
(->$ @(re-frame/subscribe [::comparable-amount type location]))]] (->$ amount)]]
[:th.has-text-right (->% @(re-frame/subscribe [::comparable-percent-of-sales type location]))] [:th.has-text-right (->% (percent-of-sales amount all-accounts i location))]
[:th.has-text-right (->$ (- @(re-frame/subscribe [::amount type location]) (when (not= 0 i)
@(re-frame/subscribe [::comparable-amount type location])))]]])) [:th.has-text-right (->$ (- amount
(aggregate-accounts (filter-accounts all-accounts (dec i) [min-numeric-code max-numeric-code] location))))])])]]))
(defn subtotal [types negs title location] (defn subtotal [types negs title location]
(let [accounts (transduce (comp (let [all-accounts @(re-frame/subscribe [::all-accounts])
(map #(map (fn [a] periods @(re-frame/subscribe [::periods])]
(if (negs %)
(update a :amount -)
a))
(deref (re-frame/subscribe [::accounts % location])))))
into
[]
types)
comparable (transduce
(comp
(map #(map (fn [a]
(if (negs %)
(update a :amount -)
a))
(vals (deref (re-frame/subscribe [::comparable-accounts-by-id % location]))))))
into
[]
types)
min-numeric-code (or (first (map :numeric-code accounts)) 0)
max-numeric-code (or (last (map :numeric-code accounts)) 0)
sales @(re-frame/subscribe [::amount :sales location])
comparable-sales @(re-frame/subscribe [::comparable-amount :sales location])]
[:tr [:th.is-size-5 title] [:tr [:th.is-size-5 title]
[:td.has-text-right [:a
{:on-click (dispatch-event [::investigate-clicked location min-numeric-code max-numeric-code :current])} (for [[p i] (map vector periods (range))
(->$ (reduce + 0 (map :amount accounts)))]] :let [amount (aggregate-accounts (mapcat (fn [t]
[:td.has-text-right (->% (if (> sales 0) (cond->> (filter-accounts all-accounts i (ranges t) location)
(/ (reduce + 0 (map :amount accounts)) (negs t) (map #(update % :amount -))))
sales) types))]]
0))] [:<>
[:td.has-text-right [:a [:td.has-text-right [:span (->$ amount)]]
{:on-click (dispatch-event [::investigate-clicked location min-numeric-code max-numeric-code :comparable])} [:td.has-text-right (->% (percent-of-sales amount all-accounts i location))]
(->$ (reduce + 0 (map :amount comparable)))]] (when (not= 0 i)
[:td.has-text-right (->% (if (> comparable-sales 0) [:td.has-text-right (->$ (- amount
(/ (reduce + 0 (map :amount comparable)) (aggregate-accounts (mapcat (fn [t]
comparable-sales) (cond->> (filter-accounts all-accounts (dec i) (ranges t) location)
0))] (negs t) (map #(update % :amount -))))
[:td.has-text-right (->$ (- (reduce + 0 (map :amount accounts)) types))))]
(reduce + 0 (map :amount comparable))))]])) )
])]))
(defn location-rows [location] (defn location-rows [location]
[:<> [:<>
[overall-grouping :sales (str location " Sales") location] [overall-grouping :sales (str location " Sales") location]
[overall-grouping :cogs (str location " COGS") location] [overall-grouping :cogs (str location " COGS") location]
@@ -464,26 +409,32 @@
[subtotal [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable] #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable} "Net Income" nil]]) [subtotal [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable] #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable} "Net Income" nil]])
(defn location-summary [location params] (defn location-summary [location params]
[:div (let [periods @(re-frame/subscribe [::periods])]
[:h2.title.is-4 {:style {:margin-bottom "1rem"}} location " Summary"] [:div
[:table.table.compact.balance-sheet {:style {:margin-bottom "2.5rem"}} [:h2.title.is-4 {:style {:margin-bottom "1rem"}} location " Summary"]
[:tbody [:table.table.compact.balance-sheet {:style {:margin-bottom "2.5rem"}}
[:tr [:tbody
[:td.has-text-right "Period ending"] [:tr
[:td.has-text-right (date->str (str->date (:to-date params) standard))] [:td.has-text-right "Period ending"]
[:td] (for [[[_ date] i] (map vector periods (range))]
[:td.has-text-right (when (:to-date params) [:<>
(date->str (t/minus (str->date (:to-date params) standard) (t/years 1))))] [:td.has-text-right (when date
[:td] (date->str date))]
[:td]] [:td]
[subtotal [:sales ] #{} "Sales" location] (when (not= 0 i)
[subtotal [:cogs ] #{} "Cogs" location] [:td])])]
[subtotal [:payroll ]#{} "Payroll" location] [subtotal [:sales ] #{} "Sales" location]
[subtotal [:sales :payroll :cogs] #{:payroll :cogs} "Gross Profits" location] [subtotal [:cogs ] #{} "Cogs" location]
[subtotal [:controllable :fixed-overhead :ownership-controllable] #{} "Overhead" location] [subtotal [:payroll ]#{} "Payroll" location]
[subtotal [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable] #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable} "Net Income" location]]] [subtotal [:sales :payroll :cogs] #{:payroll :cogs} "Gross Profits" location]
[subtotal [:controllable :fixed-overhead :ownership-controllable] #{} "Overhead" location]
]) [subtotal [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable] #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable} "Net Income" location]]]
]))
(defn and-last-year [[from to]]
[[from to]
[(t/minus from (t/years 1)) (t/minus to (t/years 1))]])
(def profit-and-loss-content (def profit-and-loss-content
(with-meta (with-meta
@@ -491,7 +442,8 @@
(let [current-client @(re-frame/subscribe [::subs/client]) (let [current-client @(re-frame/subscribe [::subs/client])
user @(re-frame/subscribe [::subs/user]) user @(re-frame/subscribe [::subs/user])
error @(re-frame/subscribe [::error]) error @(re-frame/subscribe [::error])
params @(re-frame/subscribe [::params])] params @(re-frame/subscribe [::params])
periods @(re-frame/subscribe [::periods])]
(if-not current-client (if-not current-client
[:div [:div
@@ -504,6 +456,23 @@
[:h2.title.is-4 "Range"] [:h2.title.is-4 "Range"]
[:div [:div
[:div.field.is-grouped [:div.field.is-grouped
[:p.control
[:a.button
{:class (when (= (:selected params) "13 periods") "is-active")
:on-click (dispatch-event
[::range-selected
(let [this-month (t/local-date (t/year (local-now))
(t/month (local-now))
1)]
(into
[[this-month (t/minus (t/plus this-month (t/months 1)) (t/days 1))]]
(for [i (range 12)]
[(t/minus this-month (t/months (- 12 i)))
(t/minus (t/minus this-month (t/months (- 11 i)))
(t/days 1))])))
"13 periods"])}
"13 periods"]]
[:p.control [:p.control
[:a.button [:a.button
{:class (when (= (:selected params) "Last week") "is-active") {:class (when (= (:selected params) "Last week") "is-active")
@@ -513,101 +482,94 @@
current current
(recur (t/minus current (t/period :days 1)))))] (recur (t/minus current (t/period :days 1)))))]
[::range-selected [::range-selected
(date->str (t/minus last-sunday (t/period :days 6)) standard) (and-last-year [(t/minus last-sunday (t/period :days 6)) last-sunday])
(date->str last-sunday standard)
"Last week"]))} "Last week"]))}
"Last week"]] "Last week"]]
[:p.control [:p.control
[:a.button [:a.button
{:class (when (= (:selected params) "Week to date") "is-active") {:class (when (= (:selected params) "Week to date") "is-active")
:on-click (dispatch-event [::range-selected :on-click (dispatch-event [::range-selected
(date->str (loop [current (local-now)] (and-last-year [(loop [current (local-now)]
(if (= 1 (t/day-of-week current)) (if (= 1 (t/day-of-week current))
current current
(recur (t/minus current (t/period :days 1))))) (recur (t/minus current (t/period :days 1)))))
standard) (local-now)])
(date->str (local-now) standard)
"Week to date"])} "Week to date"])}
"Week to date"]] "Week to date"]]
[:p.control [:p.control
[:a.button [:a.button
{:class (when (= (:selected params) "Last Month") "is-active") {:class (when (= (:selected params) "Last Month") "is-active")
:on-click (dispatch-event [::range-selected :on-click (dispatch-event [::range-selected
(date->str (t/minus (t/local-date (t/year (local-now)) (and-last-year [(t/minus (t/local-date (t/year (local-now))
(t/month (local-now)) (t/month (local-now))
1) 1)
(t/period :months 1)) (t/period :months 1))
standard) (t/minus (t/local-date (t/year (local-now))
(date->str (t/minus (t/local-date (t/year (local-now)) (t/month (local-now))
(t/month (local-now)) 1)
1) (t/period :days 1))])
(t/period :days 1)) standard)
"Last Month"])} "Last Month"])}
"Last Month"]] "Last Month"]]
[:p.control [:p.control
[:a.button [:a.button
{:class (when (= (:selected params) "Month to date") "is-active") {:class (when (= (:selected params) "Month to date") "is-active")
:on-click (dispatch-event [::range-selected :on-click (dispatch-event [::range-selected
(date->str (t/local-date (t/year (local-now)) (and-last-year [(t/local-date (t/year (local-now))
(t/month (local-now)) (t/month (local-now))
1) 1)
standard) (local-now)])
(date->str (local-now) standard)
"Month to date"])} "Month to date"])}
"Month to date"]] "Month to date"]]
[:p.control [:p.control
[:a.button [:a.button
{:class (when (= (:selected params) "Year to date") "is-active") {:class (when (= (:selected params) "Year to date") "is-active")
:on-click (dispatch-event [::range-selected :on-click (dispatch-event [::range-selected
(date->str (t/local-date (t/year (local-now)) (and-last-year [(t/local-date (t/year (local-now)) 1 1)
1 (local-now)])
1)
standard)
(date->str (local-now) standard)
"Year to date"])} "Year to date"])}
"Year to date"]] "Year to date"]]
[:p.control [:p.control
[:a.button [:a.button
{:class (when (= (:selected params) "Full year") "is-active") {:class (when (= (:selected params) "Full year") "is-active")
:on-click (dispatch-event [::range-selected :on-click (dispatch-event [::range-selected
(date->str (t/plus (t/minus (local-now) (t/period :years 1)) (and-last-year [(t/plus (t/minus (local-now) (t/period :years 1))
(t/period :days 1)) (t/period :days 1))
standard) (local-now)])
(date->str (local-now) standard)
"Full year"])} "Full year"])}
"Full year"]]]] "Full year"]]]]
[:div.field.is-grouped (for [[_ i] (map vector periods (range))]
[:p.control [:div.field.is-grouped
[:p.help "From"] [:p.control
[bind-field [:p.help "From"]
[date-picker {:class-name "input" [bind-field
:class "input" [date-picker {:class-name "input"
:format-week-number (fn [] "") :class "input"
:previous-month-button-label "" :format-week-number (fn [] "")
:placeholder "mm/dd/yyyy" :previous-month-button-label ""
:next-month-button-label "" :placeholder "mm/dd/yyyy"
:next-month-label "" :next-month-button-label ""
:type "date" :next-month-label ""
:field [:from-date] :type "date"
:event [::date-picked] :field [:periods i 0]
:popper-props (clj->js {:placement "right"}) :event [::date-picked]
:subscription params}]]] :popper-props (clj->js {:placement "right"})
:subscription params}]]]
[:p.control [:p.control
[:p.help "To"] [:p.help "To"]
[bind-field [bind-field
[date-picker {:class-name "input" [date-picker {:class-name "input"
:class "input" :class "input"
:format-week-number (fn [] "") :format-week-number (fn [] "")
:previous-month-button-label "" :previous-month-button-label ""
:placeholder "mm/dd/yyyy" :placeholder "mm/dd/yyyy"
:next-month-button-label "" :next-month-button-label ""
:next-month-label "" :next-month-label ""
:type "date" :type "date"
:field [:to-date] :field [:periods i 1]
:event [::date-picked] :event [::date-picked]
:popper-props (clj->js {:placement "right"}) :popper-props (clj->js {:placement "right"})
:subscription params}]]]]] :subscription params}]]]])]
(cond (cond
error error
[:div.notification.is-warning error] [:div.notification.is-warning error]
@@ -627,19 +589,21 @@
[:tbody [:tbody
[:tr [:tr
[:td.has-text-right "Period Ending"] [:td.has-text-right "Period Ending"]
[:td.has-text-right (date->str (str->date (:to-date params) standard))] [:td.has-text-right (date->str (last (first periods)))]
[:td] [:td]
[:td.has-text-right (when (:to-date params) [:<>
(date->str (t/minus (str->date (:to-date params) standard) (t/years 1))))] (for [[_ end] (rest periods)]
[:td] [:<>
[:td.has-text-right "𝝙"]] [:td.has-text-right (date->str end)]
[:td]
[:td.has-text-right "𝝙"]]
)]]
[:<> [:<>
(for [location @(re-frame/subscribe [::locations])] (for [location @(re-frame/subscribe [::locations])]
^{:key location} ^{:key location}
[location-rows location] [location-rows location]
)]]]])]))) )]]]])])))
{:component-will-mount #(re-frame/dispatch-sync [::params-change {:from-date (date->str (t/minus (local-now) (t/period :years 1)) standard) {:component-will-mount #(do (re-frame/dispatch-sync [::range-selected (and-last-year [(t/minus (local-now) (t/period :years 1)) (local-now)]) nil])) }))
:to-date (date->str (local-now) standard)}]) }))

View File

@@ -178,9 +178,17 @@
(let [field (if (keyword? field) [field] field) (let [field (if (keyword? field) [field] field)
event (if (keyword? event) [event] event) event (if (keyword? event) [event] event)
selected (get-in subscription field) selected (get-in subscription field)
x (str->date selected standard) selected (cond (string? selected)
selected (if (string? selected) (c/to-date (time/to-default-time-zone (time/from-default-time-zone (str->date selected standard))))
(c/to-date (time/to-default-time-zone (time/from-default-time-zone x)))
(instance? goog.date.DateTime selected)
(c/to-date selected)
(instance? goog.date.Date selected)
(c/to-date selected)
:else
selected ) selected )
keys (assoc keys keys (assoc keys
:on-change (dispatch-date-change (conj event field)) :on-change (dispatch-date-change (conj event field))