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)]]}
: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]]}

View File

@@ -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}

View File

@@ -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]

View File

@@ -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

View File

@@ -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"}]])]]]]])]))

View File

@@ -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)))))))))

View File

@@ -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])) }))

View File

@@ -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))