a much better general ledger profit and loss.
This commit is contained in:
@@ -82,7 +82,7 @@
|
||||
'[(<= ?c ?to-numeric-code)]]}
|
||||
:args [(:to-numeric-code args)]})
|
||||
|
||||
(:location args)
|
||||
(not-empty (:location args))
|
||||
(merge-query {:query {:in ['?location]
|
||||
:where ['[?e :journal-entry/line-items ?li]
|
||||
'[?li :journal-entry-line/location ?location]]}
|
||||
|
||||
@@ -115,6 +115,12 @@
|
||||
{:fields {: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
|
||||
{:fields {:street1 {:type 'String}
|
||||
:street2 {:type 'String}
|
||||
@@ -404,9 +410,9 @@
|
||||
:date {:type :iso_date}}
|
||||
:resolve :get-balance-sheet}
|
||||
|
||||
:profit_and_loss {:type :balance_sheet
|
||||
:profit_and_loss {:type :profit_and_loss_report
|
||||
:args {:client_id {:type :id}
|
||||
:date_range {:type :date_range}}
|
||||
:periods {:type '(list :date_range)}}
|
||||
:resolve :get-profit-and-loss}
|
||||
|
||||
|
||||
|
||||
@@ -106,12 +106,15 @@
|
||||
vals
|
||||
(mapcat (fn [a]
|
||||
(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-overrides a))
|
||||
) )
|
||||
(into {} ))]
|
||||
(fn [a]
|
||||
#_(println a client-id (keys overrides-by-client))
|
||||
{:name (or (:bank-account/name (bank-accounts a))
|
||||
(overrides-by-client [a client-id])
|
||||
(:account/name (accounts a)))
|
||||
@@ -155,16 +158,16 @@
|
||||
(defn get-profit-and-loss [context args value]
|
||||
(let [client-id (:client_id args)
|
||||
_ (assert-can-see-client (:id context) client-id)
|
||||
start-date (coerce/to-date (:start (:date_range 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)))
|
||||
_ (println args)
|
||||
all-ledger-entries (full-ledger-for-client client-id)
|
||||
lookup-account (build-account-lookup client-id)]
|
||||
|
||||
(->graphql
|
||||
{:balance-sheet-accounts (roll-up-until lookup-account all-ledger-entries end-date start-date )
|
||||
:comparable-balance-sheet-accounts (roll-up-until lookup-account all-ledger-entries comparable-end-date comparable-start-date )})))
|
||||
{:periods (reduce (fn [acc {:keys [start end]}]
|
||||
(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]
|
||||
|
||||
@@ -77,10 +77,11 @@
|
||||
|
||||
(re-frame/reg-sub
|
||||
::accounts-by-id
|
||||
:<- [::accounts]
|
||||
:<- [::client]
|
||||
(fn [[accounts client] [_ client-override]]
|
||||
(accounts-by-id accounts (or client-override client))))
|
||||
(fn [[_ client-override]]
|
||||
[(re-frame/subscribe [::accounts client-override])
|
||||
(re-frame/subscribe [::client])])
|
||||
(fn [[accounts client] ]
|
||||
(accounts-by-id accounts client)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::bank-accounts
|
||||
|
||||
@@ -199,25 +199,25 @@
|
||||
[:option "%"]]]]]
|
||||
[:p.control
|
||||
(if (= "$" amount-mode)
|
||||
[bind-field
|
||||
[:input.input {:type "number"
|
||||
:field [index :amount]
|
||||
:style {:text-align "right" :width "7em"}
|
||||
:event [::expense-account-changed event expense-accounts max-value]
|
||||
:disabled disabled
|
||||
:subscription expense-accounts
|
||||
:precision 2
|
||||
:value (get-in expense-account [:amount])
|
||||
:max max-value
|
||||
:step "0.01"}]]
|
||||
[bind-field
|
||||
[:input.input {:type "number"
|
||||
:field [index :amount-percentage]
|
||||
:style {:text-align "right" :width "7em"}
|
||||
:disabled disabled
|
||||
:event [::expense-account-changed event expense-accounts max-value]
|
||||
:precision 2
|
||||
:subscription expense-accounts
|
||||
:value (get-in expense-account [:amount-percentage])
|
||||
:max "100"
|
||||
:step "0.01"}]])]]]]])]))
|
||||
[bind-field
|
||||
[:input.input {:type "number"
|
||||
:field [index :amount]
|
||||
:style {:text-align "right" :width "7em"}
|
||||
:event [::expense-account-changed event expense-accounts max-value]
|
||||
:disabled disabled
|
||||
:subscription expense-accounts
|
||||
:precision 2
|
||||
:value (get-in expense-account [:amount])
|
||||
:max max-value
|
||||
:step "0.01"}]]
|
||||
[bind-field
|
||||
[:input.input {:type "number"
|
||||
:field [index :amount-percentage]
|
||||
:style {:text-align "right" :width "7em"}
|
||||
:disabled disabled
|
||||
:event [::expense-account-changed event expense-accounts max-value]
|
||||
:precision 2
|
||||
:subscription expense-accounts
|
||||
:value (get-in expense-account [:amount-percentage])
|
||||
:max "100"
|
||||
:step "0.01"}]])]]]]])]))
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
(re-frame/reg-event-db
|
||||
::editing
|
||||
(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
|
||||
(select-keys [:description
|
||||
:id
|
||||
@@ -155,7 +155,8 @@
|
||||
(update :accounts (fn [xs]
|
||||
(mapv #(-> %
|
||||
(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)))))))))
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
[cljs-time.core :as t]
|
||||
[re-frame.core :as re-frame]))
|
||||
(def ranges
|
||||
{:sales [40000 48999]
|
||||
{:sales [40000 49999]
|
||||
:cogs [50000 59999]
|
||||
:payroll [60000 69999]
|
||||
:controllable [70000 79999]
|
||||
@@ -23,12 +23,10 @@
|
||||
(fn [db]
|
||||
(->> db
|
||||
::report
|
||||
:balance-sheet-accounts
|
||||
:periods
|
||||
(mapcat :accounts)
|
||||
(map :location)
|
||||
(concat (->> db
|
||||
::report
|
||||
:comparable-balance-sheet-accounts
|
||||
(map :location)))
|
||||
(map not-empty)
|
||||
(filter #(not= "A" %))
|
||||
(filter identity)
|
||||
(set)
|
||||
@@ -93,11 +91,10 @@
|
||||
params)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::accounts
|
||||
(fn [db [_ type only-location]]
|
||||
(->> db
|
||||
::report
|
||||
:balance-sheet-accounts
|
||||
::period-accounts
|
||||
(fn [db [_ which type only-location]]
|
||||
|
||||
(->> (get-in db [::report :periods which :accounts])
|
||||
(map #(update % :amount js/parseFloat))
|
||||
(filter (fn [{:keys [account-type location numeric-code]}]
|
||||
(and (or (nil? only-location)
|
||||
@@ -105,68 +102,35 @@
|
||||
(<= (get-in ranges [type 0]) numeric-code (get-in ranges [type 1])))))
|
||||
(sort-by :numeric-code))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::amount
|
||||
(fn [[_ type only-location]]
|
||||
[(re-frame/subscribe [::accounts type only-location])])
|
||||
(fn [[accounts] _]
|
||||
(reduce + 0 (map :amount accounts))))
|
||||
(defn parse-amounts [period]
|
||||
(update period :accounts (fn [a]
|
||||
(map #(update % :amount js/parseFloat) a))))
|
||||
|
||||
|
||||
(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
|
||||
::comparable-amount
|
||||
(fn [[_ type only-location]]
|
||||
[(re-frame/subscribe [::comparable-accounts-by-id type only-location])])
|
||||
(fn [[accounts] _]
|
||||
(reduce + 0 (map :amount (vals accounts)))))
|
||||
::all-accounts
|
||||
(fn [db [_ which type only-location]]
|
||||
(transduce
|
||||
(comp
|
||||
(map parse-amounts)
|
||||
(map #(by (juxt :numeric-code :location) (:accounts %))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::percent-of-sales
|
||||
(fn [[_ type only-location]]
|
||||
[(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))))
|
||||
conj
|
||||
[]
|
||||
(get-in db [::report :periods]))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::received
|
||||
@@ -180,6 +144,11 @@
|
||||
(fn [db]
|
||||
(-> db ::params)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::periods
|
||||
(fn [db]
|
||||
(::periods db)))
|
||||
|
||||
;; EVENTS
|
||||
|
||||
(re-frame/reg-event-db
|
||||
@@ -203,6 +172,7 @@
|
||||
(re-frame/reg-event-fx
|
||||
::params-change
|
||||
(fn [cofx [_ params]]
|
||||
(println params)
|
||||
(let [c @(re-frame/subscribe [::subs/client])]
|
||||
(cond-> {:db (-> (:db cofx)
|
||||
(assoc-in [::error] nil)
|
||||
@@ -212,10 +182,9 @@
|
||||
{:token (-> cofx :db :user)
|
||||
:query-obj {:venia/queries [[:profit-and-loss
|
||||
{:client-id (:id c)
|
||||
:date-range {:start (:from-date params)
|
||||
:end (:to-date params)}}
|
||||
[[:balance-sheet-accounts [:name :amount :account-type :id :numeric-code :location]]
|
||||
[:comparable-balance-sheet-accounts [:name :amount :account-type :id :numeric-code :location]]]]]}
|
||||
:periods (mapv (fn [[start end] ] {:start (date->str start standard) :end (date->str end standard)} )
|
||||
(:periods params))}
|
||||
[[:periods [[:accounts [:name :amount :account-type :id :numeric-code :location]]]]]]]}
|
||||
:on-success [::received]
|
||||
:on-error [::error]}))))))
|
||||
|
||||
@@ -225,18 +194,16 @@
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::date-picked
|
||||
(fn [cofx [_ f date]]
|
||||
{:dispatch [::params-change (assoc-in @(re-frame/subscribe [::params])
|
||||
f
|
||||
date)]}))
|
||||
(fn [cofx [_ [_ period which] date]]
|
||||
{:dispatch [::range-selected (assoc-in @(re-frame/subscribe [::periods]) [period which] (str->date date standard)) nil]}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::range-selected
|
||||
(fn [cofx [_ from to selected]]
|
||||
(fn [{:keys [db]} [_ periods selected]]
|
||||
{:dispatch [::params-change (assoc @(re-frame/subscribe [::params])
|
||||
:from-date from
|
||||
:to-date to
|
||||
:selected selected)]}))
|
||||
:periods periods
|
||||
:selected selected)]
|
||||
:db (assoc db ::periods periods)}))
|
||||
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
@@ -244,47 +211,43 @@
|
||||
[with-user (re-frame/inject-cofx ::inject/sub [::ledger-params])]
|
||||
(fn [{:keys [user ::ledger-params db]} [_ ]]
|
||||
|
||||
{:db (assoc db
|
||||
::ledger-list-loading true
|
||||
::last-ledger-params ledger-params)
|
||||
:graphql {:token user
|
||||
:query-obj {:venia/queries [[:ledger-page
|
||||
ledger-params
|
||||
[[:journal-entries [:id
|
||||
:source
|
||||
:amount
|
||||
[:vendor
|
||||
[:name :id]]
|
||||
[:client
|
||||
[:name :id]]
|
||||
[:line-items
|
||||
[:id :debit :credit :location
|
||||
[:account [:id :name]]]]
|
||||
:date]]
|
||||
:total
|
||||
:start
|
||||
:end]]]}
|
||||
:on-success [::ledger-list-received]}}))
|
||||
(if (seq ledger-params)
|
||||
{:db (assoc db
|
||||
::ledger-list-loading true
|
||||
::last-ledger-params ledger-params)
|
||||
:graphql {:token user
|
||||
:query-obj {:venia/queries [[:ledger-page
|
||||
ledger-params
|
||||
[[:journal-entries [:id
|
||||
:source
|
||||
:amount
|
||||
[:vendor
|
||||
[:name :id]]
|
||||
[:client
|
||||
[:name :id]]
|
||||
[:line-items
|
||||
[:id :debit :credit :location
|
||||
[:account [:id :name]]]]
|
||||
:date]]
|
||||
:total
|
||||
:start
|
||||
:end]]]}
|
||||
:on-success [::ledger-list-received]}})))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::investigate-clicked
|
||||
(fn [db [_ location from-numeric-code to-numeric-code which]]
|
||||
(-> db
|
||||
(assoc
|
||||
::ledger-list-active? true
|
||||
::ledger-list-loading true
|
||||
::investigate-ledger-params {:client-id (:id @(re-frame/subscribe [::subs/client]))
|
||||
:from-numeric-code from-numeric-code
|
||||
:to-numeric-code to-numeric-code
|
||||
:location location
|
||||
:date-range {:start (if (= :current which)
|
||||
(:from-date (::params db))
|
||||
(date->str (t/minus (str->date (:from-date (::params db)) standard) (t/years 1))
|
||||
standard))
|
||||
:end (if (= :current which)
|
||||
(:to-date (::params db))
|
||||
(date->str (t/minus (str->date (:to-date (::params db)) standard) (t/years 1))
|
||||
standard))}}))))
|
||||
(let [[from to] (get @(re-frame/subscribe [::periods]) which)]
|
||||
(-> db
|
||||
(assoc
|
||||
::ledger-list-active? true
|
||||
::ledger-list-loading true
|
||||
::investigate-ledger-params {:client-id (:id @(re-frame/subscribe [::subs/client]))
|
||||
:from-numeric-code from-numeric-code
|
||||
:to-numeric-code to-numeric-code
|
||||
:location location
|
||||
:date-range {:start (date->str from standard)
|
||||
:end (date->str to standard)}})))))
|
||||
|
||||
(def groupings
|
||||
{:sales [["40000-43999 Food Sales " 40000 43999]
|
||||
@@ -323,133 +286,115 @@
|
||||
["97000 Taxes" 97000 97999]
|
||||
["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]}]
|
||||
[:<>
|
||||
(for [[grouping-name from to] groupings
|
||||
:let [matching-accounts (filter
|
||||
#(<= from (:numeric-code %) to)
|
||||
accounts)
|
||||
total (reduce + 0 (map :amount matching-accounts))
|
||||
comparable-total (reduce + 0 (map #(:amount (get comparable-accounts (:id %)) 0) matching-accounts))]
|
||||
:when (seq matching-accounts)
|
||||
]
|
||||
^{:key grouping-name}
|
||||
[:<>
|
||||
[:tr [:td "---" grouping-name "---"]
|
||||
[:td]
|
||||
[:td]
|
||||
[:td]
|
||||
[:td]
|
||||
[:td]
|
||||
]
|
||||
[:<>
|
||||
(for [account matching-accounts]
|
||||
^{:key (:name account)}
|
||||
[:tr [:td (:name account)]
|
||||
[: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 (->% (if (> sales 0)
|
||||
(/ (:amount account) sales)
|
||||
0.0))]
|
||||
[:td.has-text-right [:a {:on-click (dispatch-event [::investigate-clicked location (:numeric-code account) (:numeric-code account) :comparable])}
|
||||
(->$ (:amount (get comparable-accounts (:id account)) 0))]]
|
||||
[:td.has-text-right (->% (if (> comparable-sales 0)
|
||||
(/ (:amount (get comparable-accounts (:id account)) 0) sales)
|
||||
0.0))]
|
||||
[:td.has-text-right (->$ (- (:amount account ) (:amount (get comparable-accounts (:id account)) 0)))]])]
|
||||
(defn grouping [{:keys [header type groupings location]}]
|
||||
(let [periods @(re-frame/subscribe [::periods])
|
||||
all-accounts @(re-frame/subscribe [::all-accounts])]
|
||||
[:<>
|
||||
(doall
|
||||
(for [[grouping-name from to] groupings
|
||||
:let [account-codes (used-accounts all-accounts [from to] location)]
|
||||
:when (seq account-codes)]
|
||||
^{:key grouping-name}
|
||||
[:<>
|
||||
[:tr [:td "---" grouping-name "---"]
|
||||
(for [[_ i] (map vector periods (range))]
|
||||
[:<>
|
||||
[:td]
|
||||
[:td]
|
||||
(when (not= 0 i)
|
||||
[:td])])]
|
||||
[:<>
|
||||
(for [{:keys [numeric-code name]} account-codes]
|
||||
^{:key numeric-code}
|
||||
[:tr [:td name]
|
||||
(for [[p i] (map vector periods (range))
|
||||
: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 numeric-code i :current])}
|
||||
(->$ amount)]]
|
||||
[:td.has-text-right (->% (percent-of-sales amount all-accounts i location))]
|
||||
(when (not= 0 i)
|
||||
[:td.has-text-right (->$ (- amount
|
||||
(get-in all-accounts [(dec i) [numeric-code location] :amount] 0.0)))])])])]
|
||||
|
||||
[:tr [:th ]
|
||||
(for [[p i] (map vector periods (range))
|
||||
: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]
|
||||
(let [accounts @(re-frame/subscribe [::accounts type location])
|
||||
min-numeric-code (or (first (map :numeric-code accounts)) 0)
|
||||
max-numeric-code (or (last (map :numeric-code accounts)) 0)]
|
||||
(let [all-accounts @(re-frame/subscribe [::all-accounts])
|
||||
periods @(re-frame/subscribe [::periods])
|
||||
[min-numeric-code max-numeric-code] (ranges type)]
|
||||
|
||||
[:<>
|
||||
[:tr [:th.is-size-5 title]
|
||||
[:td]
|
||||
[:td]
|
||||
[:td]]
|
||||
[:tr [:th.is-size-5 title]]
|
||||
|
||||
[grouping {:accounts accounts
|
||||
:location location
|
||||
: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])}]
|
||||
[grouping {:location location
|
||||
:groupings (type groupings)}]
|
||||
|
||||
[:tr [:th.is-size-5 title]
|
||||
[:th.has-text-right [:a
|
||||
{:on-click (dispatch-event [::investigate-clicked location min-numeric-code max-numeric-code :current])}
|
||||
(->$ @(re-frame/subscribe [::amount type location]))]]
|
||||
[:th.has-text-right (->% @(re-frame/subscribe [::percent-of-sales type location]))]
|
||||
[:th.has-text-right [:a
|
||||
{:on-click (dispatch-event [::investigate-clicked location min-numeric-code max-numeric-code :comparable])}
|
||||
(->$ @(re-frame/subscribe [::comparable-amount type location]))]]
|
||||
[:th.has-text-right (->% @(re-frame/subscribe [::comparable-percent-of-sales type location]))]
|
||||
[:th.has-text-right (->$ (- @(re-frame/subscribe [::amount type location])
|
||||
@(re-frame/subscribe [::comparable-amount type location])))]]]))
|
||||
(for [[i p] (map vector (range) periods)
|
||||
:let [amount (aggregate-accounts (filter-accounts all-accounts i [min-numeric-code max-numeric-code] location))]]
|
||||
|
||||
[:<>
|
||||
[:th.has-text-right [:a
|
||||
{:on-click (dispatch-event [::investigate-clicked location min-numeric-code max-numeric-code i])}
|
||||
(->$ amount)]]
|
||||
[:th.has-text-right (->% (percent-of-sales amount all-accounts i location))]
|
||||
(when (not= 0 i)
|
||||
[: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]
|
||||
(let [accounts (transduce (comp
|
||||
(map #(map (fn [a]
|
||||
(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])]
|
||||
(let [all-accounts @(re-frame/subscribe [::all-accounts])
|
||||
periods @(re-frame/subscribe [::periods])]
|
||||
[: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])}
|
||||
(->$ (reduce + 0 (map :amount accounts)))]]
|
||||
[:td.has-text-right (->% (if (> sales 0)
|
||||
(/ (reduce + 0 (map :amount accounts))
|
||||
sales)
|
||||
0))]
|
||||
[:td.has-text-right [:a
|
||||
{:on-click (dispatch-event [::investigate-clicked location min-numeric-code max-numeric-code :comparable])}
|
||||
(->$ (reduce + 0 (map :amount comparable)))]]
|
||||
[:td.has-text-right (->% (if (> comparable-sales 0)
|
||||
(/ (reduce + 0 (map :amount comparable))
|
||||
comparable-sales)
|
||||
0))]
|
||||
[:td.has-text-right (->$ (- (reduce + 0 (map :amount accounts))
|
||||
(reduce + 0 (map :amount comparable))))]]))
|
||||
|
||||
(for [[p i] (map vector periods (range))
|
||||
:let [amount (aggregate-accounts (mapcat (fn [t]
|
||||
(cond->> (filter-accounts all-accounts i (ranges t) location)
|
||||
(negs t) (map #(update % :amount -))))
|
||||
types))]]
|
||||
[:<>
|
||||
[:td.has-text-right [:span (->$ amount)]]
|
||||
[:td.has-text-right (->% (percent-of-sales amount all-accounts i location))]
|
||||
(when (not= 0 i)
|
||||
[:td.has-text-right (->$ (- amount
|
||||
(aggregate-accounts (mapcat (fn [t]
|
||||
(cond->> (filter-accounts all-accounts (dec i) (ranges t) location)
|
||||
(negs t) (map #(update % :amount -))))
|
||||
types))))]
|
||||
)
|
||||
])]))
|
||||
|
||||
(defn location-rows [location]
|
||||
|
||||
[:<>
|
||||
[overall-grouping :sales (str location " Sales") 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]])
|
||||
|
||||
(defn location-summary [location params]
|
||||
[:div
|
||||
[:h2.title.is-4 {:style {:margin-bottom "1rem"}} location " Summary"]
|
||||
[:table.table.compact.balance-sheet {:style {:margin-bottom "2.5rem"}}
|
||||
[:tbody
|
||||
[:tr
|
||||
[:td.has-text-right "Period ending"]
|
||||
[:td.has-text-right (date->str (str->date (:to-date params) standard))]
|
||||
[:td]
|
||||
[:td.has-text-right (when (:to-date params)
|
||||
(date->str (t/minus (str->date (:to-date params) standard) (t/years 1))))]
|
||||
[:td]
|
||||
[:td]]
|
||||
[subtotal [:sales ] #{} "Sales" location]
|
||||
[subtotal [:cogs ] #{} "Cogs" location]
|
||||
[subtotal [:payroll ]#{} "Payroll" 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]]]
|
||||
|
||||
])
|
||||
(let [periods @(re-frame/subscribe [::periods])]
|
||||
[:div
|
||||
[:h2.title.is-4 {:style {:margin-bottom "1rem"}} location " Summary"]
|
||||
[:table.table.compact.balance-sheet {:style {:margin-bottom "2.5rem"}}
|
||||
[:tbody
|
||||
[:tr
|
||||
[:td.has-text-right "Period ending"]
|
||||
(for [[[_ date] i] (map vector periods (range))]
|
||||
[:<>
|
||||
[:td.has-text-right (when date
|
||||
(date->str date))]
|
||||
[:td]
|
||||
(when (not= 0 i)
|
||||
[:td])])]
|
||||
[subtotal [:sales ] #{} "Sales" location]
|
||||
[subtotal [:cogs ] #{} "Cogs" location]
|
||||
[subtotal [:payroll ]#{} "Payroll" 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
|
||||
(with-meta
|
||||
@@ -491,7 +442,8 @@
|
||||
(let [current-client @(re-frame/subscribe [::subs/client])
|
||||
user @(re-frame/subscribe [::subs/user])
|
||||
error @(re-frame/subscribe [::error])
|
||||
params @(re-frame/subscribe [::params])]
|
||||
params @(re-frame/subscribe [::params])
|
||||
periods @(re-frame/subscribe [::periods])]
|
||||
|
||||
(if-not current-client
|
||||
[:div
|
||||
@@ -504,6 +456,23 @@
|
||||
[:h2.title.is-4 "Range"]
|
||||
[:div
|
||||
[: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
|
||||
[:a.button
|
||||
{:class (when (= (:selected params) "Last week") "is-active")
|
||||
@@ -513,101 +482,94 @@
|
||||
current
|
||||
(recur (t/minus current (t/period :days 1)))))]
|
||||
[::range-selected
|
||||
(date->str (t/minus last-sunday (t/period :days 6)) standard)
|
||||
(date->str last-sunday standard)
|
||||
(and-last-year [(t/minus last-sunday (t/period :days 6)) last-sunday])
|
||||
"Last week"]))}
|
||||
"Last week"]]
|
||||
[:p.control
|
||||
[:a.button
|
||||
{:class (when (= (:selected params) "Week to date") "is-active")
|
||||
:on-click (dispatch-event [::range-selected
|
||||
(date->str (loop [current (local-now)]
|
||||
(if (= 1 (t/day-of-week current))
|
||||
current
|
||||
(recur (t/minus current (t/period :days 1)))))
|
||||
standard)
|
||||
(date->str (local-now) standard)
|
||||
(and-last-year [(loop [current (local-now)]
|
||||
(if (= 1 (t/day-of-week current))
|
||||
current
|
||||
(recur (t/minus current (t/period :days 1)))))
|
||||
(local-now)])
|
||||
"Week to date"])}
|
||||
"Week to date"]]
|
||||
[:p.control
|
||||
[:a.button
|
||||
{:class (when (= (:selected params) "Last Month") "is-active")
|
||||
:on-click (dispatch-event [::range-selected
|
||||
(date->str (t/minus (t/local-date (t/year (local-now))
|
||||
(t/month (local-now))
|
||||
1)
|
||||
(t/period :months 1))
|
||||
standard)
|
||||
(date->str (t/minus (t/local-date (t/year (local-now))
|
||||
(t/month (local-now))
|
||||
1)
|
||||
(t/period :days 1)) standard)
|
||||
(and-last-year [(t/minus (t/local-date (t/year (local-now))
|
||||
(t/month (local-now))
|
||||
1)
|
||||
(t/period :months 1))
|
||||
(t/minus (t/local-date (t/year (local-now))
|
||||
(t/month (local-now))
|
||||
1)
|
||||
(t/period :days 1))])
|
||||
"Last Month"])}
|
||||
"Last Month"]]
|
||||
[:p.control
|
||||
[:a.button
|
||||
{:class (when (= (:selected params) "Month to date") "is-active")
|
||||
:on-click (dispatch-event [::range-selected
|
||||
(date->str (t/local-date (t/year (local-now))
|
||||
(t/month (local-now))
|
||||
1)
|
||||
standard)
|
||||
(date->str (local-now) standard)
|
||||
(and-last-year [(t/local-date (t/year (local-now))
|
||||
(t/month (local-now))
|
||||
1)
|
||||
(local-now)])
|
||||
"Month to date"])}
|
||||
"Month to date"]]
|
||||
[:p.control
|
||||
[:a.button
|
||||
{:class (when (= (:selected params) "Year to date") "is-active")
|
||||
:on-click (dispatch-event [::range-selected
|
||||
(date->str (t/local-date (t/year (local-now))
|
||||
1
|
||||
1)
|
||||
standard)
|
||||
(date->str (local-now) standard)
|
||||
(and-last-year [(t/local-date (t/year (local-now)) 1 1)
|
||||
(local-now)])
|
||||
"Year to date"])}
|
||||
"Year to date"]]
|
||||
[:p.control
|
||||
[:a.button
|
||||
{:class (when (= (:selected params) "Full year") "is-active")
|
||||
:on-click (dispatch-event [::range-selected
|
||||
(date->str (t/plus (t/minus (local-now) (t/period :years 1))
|
||||
(t/period :days 1))
|
||||
standard)
|
||||
(date->str (local-now) standard)
|
||||
(and-last-year [(t/plus (t/minus (local-now) (t/period :years 1))
|
||||
(t/period :days 1))
|
||||
(local-now)])
|
||||
"Full year"])}
|
||||
"Full year"]]]]
|
||||
[:div.field.is-grouped
|
||||
[:p.control
|
||||
[:p.help "From"]
|
||||
[bind-field
|
||||
[date-picker {:class-name "input"
|
||||
:class "input"
|
||||
:format-week-number (fn [] "")
|
||||
:previous-month-button-label ""
|
||||
:placeholder "mm/dd/yyyy"
|
||||
:next-month-button-label ""
|
||||
:next-month-label ""
|
||||
:type "date"
|
||||
:field [:from-date]
|
||||
:event [::date-picked]
|
||||
:popper-props (clj->js {:placement "right"})
|
||||
:subscription params}]]]
|
||||
(for [[_ i] (map vector periods (range))]
|
||||
[:div.field.is-grouped
|
||||
[:p.control
|
||||
[:p.help "From"]
|
||||
[bind-field
|
||||
[date-picker {:class-name "input"
|
||||
:class "input"
|
||||
:format-week-number (fn [] "")
|
||||
:previous-month-button-label ""
|
||||
:placeholder "mm/dd/yyyy"
|
||||
:next-month-button-label ""
|
||||
:next-month-label ""
|
||||
:type "date"
|
||||
:field [:periods i 0]
|
||||
:event [::date-picked]
|
||||
:popper-props (clj->js {:placement "right"})
|
||||
:subscription params}]]]
|
||||
|
||||
[:p.control
|
||||
[:p.help "To"]
|
||||
[bind-field
|
||||
[date-picker {:class-name "input"
|
||||
:class "input"
|
||||
:format-week-number (fn [] "")
|
||||
:previous-month-button-label ""
|
||||
:placeholder "mm/dd/yyyy"
|
||||
:next-month-button-label ""
|
||||
:next-month-label ""
|
||||
:type "date"
|
||||
:field [:to-date]
|
||||
:event [::date-picked]
|
||||
:popper-props (clj->js {:placement "right"})
|
||||
:subscription params}]]]]]
|
||||
[:p.control
|
||||
[:p.help "To"]
|
||||
[bind-field
|
||||
[date-picker {:class-name "input"
|
||||
:class "input"
|
||||
:format-week-number (fn [] "")
|
||||
:previous-month-button-label ""
|
||||
:placeholder "mm/dd/yyyy"
|
||||
:next-month-button-label ""
|
||||
:next-month-label ""
|
||||
:type "date"
|
||||
:field [:periods i 1]
|
||||
:event [::date-picked]
|
||||
:popper-props (clj->js {:placement "right"})
|
||||
:subscription params}]]]])]
|
||||
(cond
|
||||
error
|
||||
[:div.notification.is-warning error]
|
||||
@@ -627,19 +589,21 @@
|
||||
[:tbody
|
||||
[:tr
|
||||
[: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.has-text-right (when (:to-date params)
|
||||
(date->str (t/minus (str->date (:to-date params) standard) (t/years 1))))]
|
||||
[:td]
|
||||
[:td.has-text-right "𝝙"]]
|
||||
[:<>
|
||||
(for [[_ end] (rest periods)]
|
||||
[:<>
|
||||
[:td.has-text-right (date->str end)]
|
||||
[:td]
|
||||
[:td.has-text-right "𝝙"]]
|
||||
)]]
|
||||
[:<>
|
||||
(for [location @(re-frame/subscribe [::locations])]
|
||||
^{:key 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)
|
||||
:to-date (date->str (local-now) 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])) }))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -178,9 +178,17 @@
|
||||
(let [field (if (keyword? field) [field] field)
|
||||
event (if (keyword? event) [event] event)
|
||||
selected (get-in subscription field)
|
||||
x (str->date selected standard)
|
||||
selected (if (string? selected)
|
||||
(c/to-date (time/to-default-time-zone (time/from-default-time-zone x)))
|
||||
selected (cond (string? selected)
|
||||
(c/to-date (time/to-default-time-zone (time/from-default-time-zone (str->date selected standard))))
|
||||
|
||||
|
||||
(instance? goog.date.DateTime selected)
|
||||
(c/to-date selected)
|
||||
|
||||
(instance? goog.date.Date selected)
|
||||
(c/to-date selected)
|
||||
|
||||
:else
|
||||
selected )
|
||||
keys (assoc keys
|
||||
:on-change (dispatch-date-change (conj event field))
|
||||
|
||||
Reference in New Issue
Block a user