Makes balance sheet work like pnl, updates pnl as necessary
This commit is contained in:
@@ -212,7 +212,7 @@
|
||||
(let [data (get-profit-and-loss context args value)
|
||||
result (print-pnl (:id context) args data)]
|
||||
|
||||
(->graphql {:report_url result})))
|
||||
(->graphql result)))
|
||||
|
||||
|
||||
(defn assoc-error [f]
|
||||
@@ -513,7 +513,8 @@
|
||||
:name {:type 'String}}}
|
||||
|
||||
:profit_and_loss_pdf
|
||||
{:fields {:report_url {:type 'String}}}
|
||||
{:fields {:url {:type 'String}
|
||||
:name {:type 'String}}}
|
||||
|
||||
:balance_sheet
|
||||
{:fields {:balance_sheet_accounts {:type '(list :balance_sheet_account)}
|
||||
|
||||
@@ -216,4 +216,5 @@
|
||||
:report/url url
|
||||
:report/creator (:user user)
|
||||
:report/created (java.util.Date.)}])
|
||||
url))
|
||||
{:report/name name
|
||||
:report/url url }))
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
|
||||
|
||||
(def ranges
|
||||
{:sales [40000 49999]
|
||||
{:assets [11000 19999]
|
||||
:liabilities [20100 28999]
|
||||
:equities [30000 39999]
|
||||
:sales [40000 49999]
|
||||
:cogs [50000 59999]
|
||||
:payroll [60000 69999]
|
||||
:controllable [70000 79999]
|
||||
@@ -26,7 +29,21 @@
|
||||
:ownership-controllable [90000 99999]})
|
||||
|
||||
(def groupings
|
||||
{:sales [["40000-43999 Food Sales " 40000 43999]
|
||||
{:assets [["1100 Cash and Bank Accounts" 11000 11999]
|
||||
["1200 Accounts Receivable" 12000 12999]
|
||||
["1300 Inventory" 13000 13999]
|
||||
["1400 Prepaid Expenses" 14000 14999]
|
||||
["1500 Property and Equipment" 15000 15999]
|
||||
["1600 Intangible Assets" 16000 16999]
|
||||
["1700 Other Assets" 17000 19999]]
|
||||
:liabilities [["2000 Accounts Payable" 20100 23999]
|
||||
["2400 Accrued Expenses" 24000 24999]
|
||||
["2500 Other Liabilities" 25000 25999]
|
||||
["2600 Split Accounts" 26000 26999]
|
||||
["2700 Current Portion of Long-Term Debt" 27000 27999]
|
||||
["2800 Notes Payable" 28000 28999]]
|
||||
:equities [["3000 Owner's Equity" 30000 39999]]
|
||||
:sales [["40000-43999 Food Sales " 40000 43999]
|
||||
["44000-46999 Alcohol Sales" 44000 46999]
|
||||
["47000 Merchandise Sales" 47000 47999]
|
||||
["48000 Other Operating Income" 48000 48999]
|
||||
@@ -120,11 +137,27 @@
|
||||
(update :filters (fn [f]
|
||||
(assoc f :location location)))))
|
||||
|
||||
(defn filter-numeric-code [pnl-data from to]
|
||||
(-> pnl-data
|
||||
(update :data (fn [data]
|
||||
(filter
|
||||
#(<= from (or (:numeric-code %) 0) to)
|
||||
data)))
|
||||
(update :filters (fn [f]
|
||||
(assoc f
|
||||
:from-numeric-code from
|
||||
:to-numeric-code to)))))
|
||||
|
||||
(defn filter-categories [pnl-data categories]
|
||||
(update pnl-data :data (fn [data]
|
||||
(mapcat identity
|
||||
((apply juxt categories)
|
||||
(group-by best-category data))))))
|
||||
(if (= 1 (count categories))
|
||||
(let [[from to] (ranges (first categories))]
|
||||
(-> pnl-data
|
||||
(filter-numeric-code from to)))
|
||||
(-> pnl-data
|
||||
(update :data (fn [data]
|
||||
(mapcat identity
|
||||
((apply juxt categories)
|
||||
(group-by best-category data))))))))
|
||||
|
||||
(defn filter-period [pnl-data period]
|
||||
(-> pnl-data
|
||||
@@ -133,16 +166,7 @@
|
||||
(update :filters (fn [f]
|
||||
(assoc f :date-range period)))))
|
||||
|
||||
(defn filter-numeric-code [pnl-data from to]
|
||||
(-> pnl-data
|
||||
(update :data (fn [data]
|
||||
(filter
|
||||
#(<= from (:numeric-code %) to)
|
||||
data)))
|
||||
(update :filters (fn [f]
|
||||
(assoc f
|
||||
:from-numeric-code from
|
||||
:to-numeric-code to)))))
|
||||
|
||||
|
||||
(defn negate [pnl-data types]
|
||||
(update pnl-data :data
|
||||
@@ -172,8 +196,8 @@
|
||||
(merge
|
||||
{:format :dollar
|
||||
:value (aggregate-accounts data)
|
||||
:filters (when (:from-numeric-code (:filters pnl-data)) ;; don't allow filtering when you don't at least filter numeric codes
|
||||
(:filters pnl-data))}
|
||||
:filters (when (:from-numeric-code (:filters data)) ;; don't allow filtering when you don't at least filter numeric codes
|
||||
(:filters data))}
|
||||
cell-args)))
|
||||
(-> pnl-data :args :periods))))
|
||||
|
||||
@@ -206,23 +230,24 @@
|
||||
:value (- (:value b)
|
||||
(:value a))})))))))
|
||||
|
||||
(defn combine-tables [pnl-data table percent-of-sales deltas]
|
||||
(map (fn [[title & row] percent-of-sales deltas ]
|
||||
(let [deltas (cons {:value 0.0
|
||||
:format :dollar
|
||||
:border (:border (first row))} deltas)]
|
||||
(into [title]
|
||||
(mapcat
|
||||
(fn [v p d]
|
||||
(if (:include-deltas (:args pnl-data))
|
||||
[v p d]
|
||||
[v p]))
|
||||
row
|
||||
percent-of-sales
|
||||
deltas))))
|
||||
table
|
||||
percent-of-sales
|
||||
deltas))
|
||||
(defn combine-tables
|
||||
([pnl-data table percent-of-sales deltas]
|
||||
(map (fn [[title & row] percent-of-sales deltas ]
|
||||
(let [deltas (cons {:value 0.0
|
||||
:format :dollar
|
||||
:border (:border (first row))} deltas)]
|
||||
(into [title]
|
||||
(mapcat
|
||||
(fn [v p d]
|
||||
(if (:include-deltas (:args pnl-data))
|
||||
[v p d]
|
||||
[v p]))
|
||||
row
|
||||
percent-of-sales
|
||||
deltas))))
|
||||
table
|
||||
percent-of-sales
|
||||
deltas)))
|
||||
|
||||
(defn headers [pnl-data header-title]
|
||||
(let [big-header (into [{:value header-title
|
||||
@@ -380,6 +405,41 @@
|
||||
(str (-> pnl-data :clients-by-id (get client-id)) " (" location ") Detail")))})
|
||||
|
||||
|
||||
(defn balance-sheet-headers [pnl-data]
|
||||
[(cond-> [{:value "Period Ending"}
|
||||
{:value (:date (:args pnl-data))}]
|
||||
(:include-comparison (:args pnl-data)) (into [{:value (:comparison-date (:args pnl-data))} {:value "+/-"}]))])
|
||||
|
||||
(defn append-deltas [table]
|
||||
(->> table
|
||||
(map (fn [[title a b]]
|
||||
[title a b (and (:value a) (:value b)
|
||||
{:border (:border b)
|
||||
:format :dollar
|
||||
:value (- (:value a)
|
||||
(:value b))})]))))
|
||||
|
||||
(defn summarize-balance-sheet [pnl-data]
|
||||
(println (:periods (:args pnl-data)))
|
||||
(let [table (-> []
|
||||
(into (detail-rows pnl-data
|
||||
:assets
|
||||
"Assets"))
|
||||
(into (detail-rows pnl-data
|
||||
:liabilities
|
||||
"Liabilities"))
|
||||
(into (detail-rows pnl-data
|
||||
:equities
|
||||
"Owner's Equity"))
|
||||
(conj (subtotal-row (-> pnl-data
|
||||
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||
"Retained Earnings")))
|
||||
table (if (:include-comparison (:args pnl-data))
|
||||
(append-deltas table)
|
||||
table)]
|
||||
{:header (balance-sheet-headers pnl-data)
|
||||
:rows table}))
|
||||
|
||||
|
||||
(defrecord PNLData [args data client-names])
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
(ns auto-ap.views.pages.ledger.balance-sheet
|
||||
(:require [auto-ap.subs :as subs]
|
||||
[auto-ap.views.components.layouts :refer [side-bar-layout appearing-side-bar]]
|
||||
[auto-ap.views.components.switch-field :refer [switch-field]]
|
||||
[auto-ap.views.pages.ledger.table :as ledger-table ]
|
||||
[goog.string :as gstring]
|
||||
[vimsical.re-frame.fx.track :as track]
|
||||
[auto-ap.utils :refer [by]]
|
||||
[auto-ap.views.pages.data-page :as data-page]
|
||||
[auto-ap.views.pages.ledger.side-bar :refer [ledger-side-bar]]
|
||||
[auto-ap.views.utils :refer [date->str date-picker bind-field local-now standard ->$ str->date dispatch-event with-user]]
|
||||
[cljs-time.core :as t]
|
||||
[re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[clojure.set :as set]
|
||||
[auto-ap.status :as status]))
|
||||
(:require
|
||||
[auto-ap.forms :as forms]
|
||||
[auto-ap.ledger.reports :as l-reports]
|
||||
[auto-ap.status :as status]
|
||||
[auto-ap.subs :as subs]
|
||||
[auto-ap.views.components.layouts
|
||||
:refer [appearing-side-bar side-bar-layout]]
|
||||
[auto-ap.views.components.switch-field :refer [switch-field]]
|
||||
[auto-ap.views.pages.data-page :as data-page]
|
||||
[auto-ap.views.pages.ledger.side-bar :refer [ledger-side-bar]]
|
||||
[auto-ap.views.pages.ledger.table :as ledger-table]
|
||||
[auto-ap.views.utils
|
||||
:refer [date->str
|
||||
date-picker-friendly
|
||||
dispatch-event
|
||||
local-now
|
||||
standard
|
||||
with-user]]
|
||||
[cljs-time.core :as t]
|
||||
[clojure.set :as set]
|
||||
[re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[vimsical.re-frame.fx.track :as track]
|
||||
[auto-ap.views.pages.ledger.report-table :as rtable]))
|
||||
|
||||
(defn data-params->query-params [params]
|
||||
(when params
|
||||
@@ -27,91 +36,45 @@
|
||||
:date-range (:date-range params)}))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::report
|
||||
(fn [db]
|
||||
(-> db ::report)))
|
||||
::can-submit
|
||||
(fn [_]
|
||||
true))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::accounts
|
||||
(fn [db [_ type]]
|
||||
(->> db
|
||||
::report
|
||||
:balance-sheet-accounts
|
||||
(map #(update % :amount js/parseFloat))
|
||||
(filter (fn [{:keys [account-type]}]
|
||||
(= type account-type)))
|
||||
(sort-by :numeric-code))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::accounts-by-id
|
||||
(fn [db [_ type]]
|
||||
(->> db
|
||||
::report
|
||||
:balance-sheet-accounts
|
||||
(map #(update % :amount js/parseFloat))
|
||||
(filter (fn [{:keys [account-type]}]
|
||||
(= type account-type)))
|
||||
(by :id))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::comparable-accounts-by-id
|
||||
(fn [db [_ type]]
|
||||
(->> db
|
||||
::report
|
||||
:comparable-balance-sheet-accounts
|
||||
(map #(update % :amount js/parseFloat))
|
||||
(filter (fn [{:keys [account-type]}]
|
||||
(= type account-type)))
|
||||
(by :id))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::ledger-list-active?
|
||||
(fn [db]
|
||||
(-> db ::ledger-list-active?)))
|
||||
(def groupings
|
||||
{:asset [["1100 Cash and Bank Accounts" 11000 11999]
|
||||
["1200 Accounts Receivable" 12000 12999]
|
||||
["1300 Inventory" 13000 13999]
|
||||
["1400 Prepaid Expenses" 14000 14999]
|
||||
["1500 Property and Equipment" 15000 15999]
|
||||
["1600 Intangible Assets" 16000 16999]
|
||||
["1700 Other Assets" 17000 19999]]
|
||||
:liability [["2000 Accounts Payable" 20100 23999]
|
||||
["2400 Accrued Expenses" 24000 24999]
|
||||
["2500 Other Liabilities" 25000 25999]
|
||||
["2600 Split Accounts" 26000 26999]
|
||||
["2700 Current Portion of Long-Term Debt" 27000 27999]
|
||||
["2800 Notes Payable" 28000 28999]
|
||||
]
|
||||
:equity [["3000 Owner's Equity" 30000 39999]]
|
||||
:revenue [["Retained Earnings" 40000 49999]]})
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::received
|
||||
[(forms/in-form ::form)]
|
||||
(fn [db [_ data]]
|
||||
(-> db
|
||||
(assoc ::report (:balance-sheet data)))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::params
|
||||
(fn [db]
|
||||
(-> db ::params)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::include-comparison
|
||||
:<- [::params]
|
||||
(fn [params]
|
||||
(:include-comparison params)))
|
||||
(assoc :report (:balance-sheet data)))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::params-change
|
||||
(fn [cofx [_ params]]
|
||||
{:db (-> (:db cofx)
|
||||
(dissoc ::report)
|
||||
(assoc-in [::params] params))
|
||||
:graphql {:token (-> cofx :db :user)
|
||||
::change
|
||||
[with-user (forms/in-form ::form)]
|
||||
(fn [{:keys [db]} [_ & event]]
|
||||
{:db (dissoc db :report)
|
||||
:dispatch-n [(into [::forms/change ::form] event)
|
||||
[::ledger-list-closing]]}))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::ledger-list-closing
|
||||
(fn [db]
|
||||
(assoc db ::ledger-list-active? false)))
|
||||
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::report-requested
|
||||
[with-user (forms/in-form ::form)]
|
||||
(fn [{:keys [db user]} [_]]
|
||||
{:db (dissoc db :report)
|
||||
:graphql {:token user
|
||||
:query-obj {:venia/queries [[:balance-sheet
|
||||
(assoc params
|
||||
(assoc (:data db)
|
||||
:client-id (:id @(re-frame/subscribe [::subs/client])))
|
||||
[[:balance-sheet-accounts [:name :amount :account-type :id :numeric-code]]
|
||||
[:comparable-balance-sheet-accounts [:name :amount :account-type :id :numeric-code]]]]]}
|
||||
@@ -119,46 +82,24 @@
|
||||
:owns-state {:single ::page}
|
||||
:on-success [::received]}}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::date-picked
|
||||
(fn [cofx [_ _ date]]
|
||||
{:dispatch [::params-change (assoc @(re-frame/subscribe [::params])
|
||||
:date
|
||||
date)]}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::comparison-date-picked
|
||||
(fn [cofx [_ _ date]]
|
||||
{:dispatch [::params-change (assoc @(re-frame/subscribe [::params])
|
||||
:comparison-date
|
||||
date)]}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::include-comparison-changed
|
||||
(fn [cofx [_ include-comparison]]
|
||||
{:dispatch [::params-change (assoc @(re-frame/subscribe [::params])
|
||||
:include-comparison
|
||||
include-comparison)]}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::investigate-clicked
|
||||
(fn [{:keys [db]} [_ from-numeric-code to-numeric-code which]]
|
||||
(let [date (if (= :current which)
|
||||
(get @(re-frame/subscribe [::params]) :date)
|
||||
(get @(re-frame/subscribe [::params]) :comparison-date))]
|
||||
{:db (-> db (assoc ::ledger-list-active? true))
|
||||
:dispatch [::data-page/additional-params-changed ::ledger {:client-id (:id @(re-frame/subscribe [::subs/client]))
|
||||
:from-numeric-code from-numeric-code
|
||||
:to-numeric-code to-numeric-code
|
||||
:date-range {:start "2000-01-01"
|
||||
:end date}}]})))
|
||||
(fn [{:keys [db]} [_ {:keys [from-numeric-code to-numeric-code date-range] :as g}]]
|
||||
(println g)
|
||||
|
||||
{:db (-> db (assoc ::ledger-list-active? true))
|
||||
:dispatch [::data-page/additional-params-changed ::ledger {:client-id (:id @(re-frame/subscribe [::subs/client]))
|
||||
:from-numeric-code from-numeric-code
|
||||
:to-numeric-code to-numeric-code
|
||||
:date-range {:start "2000-01-01"
|
||||
:end date-range}}]}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::ledger-params-change
|
||||
[with-user]
|
||||
(fn [{:keys [user db]} [_ ledger-params]]
|
||||
|
||||
(if (seq ledger-params)
|
||||
(fn [{:keys [user]} [_ ledger-params]]
|
||||
(when (seq ledger-params)
|
||||
{:graphql {:token user
|
||||
:owns-state {:single [::data-page/page ::ledger]}
|
||||
:query-obj {:venia/queries [[:ledger-page
|
||||
@@ -180,190 +121,116 @@
|
||||
:start
|
||||
:end]]]}
|
||||
:on-success (fn [result]
|
||||
|
||||
[::data-page/received ::ledger (set/rename-keys (:ledger-page result)
|
||||
{:journal-entries :data})])}})))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::unmounted-balance-sheet
|
||||
(fn [{:keys [db]} _]
|
||||
(fn [_ _]
|
||||
{:dispatch [::data-page/dispose ::ledger]
|
||||
::track/dispose {:id ::ledger-params}}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::mounted-balance-sheet
|
||||
(fn [{:keys [db]} _]
|
||||
{::track/register {:id ::ledger-params
|
||||
{:db (forms/start-form db ::form {:date (date->str (local-now) standard)
|
||||
:comparison-date (date->str (t/minus (local-now) (t/years 1)) standard)
|
||||
:include-comparison true})
|
||||
::track/register {:id ::ledger-params
|
||||
:subscription [::data-page/params ::ledger]
|
||||
:event-fn (fn [params] [::ledger-params-change params])}}))
|
||||
|
||||
(defn grouping [{:keys [header accounts comparable-accounts groupings]}]
|
||||
(let [include-comparison @(re-frame/subscribe [::include-comparison])]
|
||||
|
||||
(for [[grouping-name from to] groupings
|
||||
:let [matching-accounts (filter
|
||||
#(<= from (:numeric-code %) to)
|
||||
accounts)]
|
||||
:when (seq matching-accounts)
|
||||
]
|
||||
(list
|
||||
[:tr [:th "---" grouping-name "---"]
|
||||
[:td]
|
||||
(when include-comparison
|
||||
[:td])
|
||||
(when include-comparison
|
||||
[:td])
|
||||
]
|
||||
(for [account matching-accounts]
|
||||
[:tr [:td (:name account)]
|
||||
[:td.has-text-right [:a {:on-click (dispatch-event [::investigate-clicked (:numeric-code account) (:numeric-code account) :current])}
|
||||
(->$ (:amount account))] ]
|
||||
(when include-comparison
|
||||
[:td.has-text-right [:a {:on-click (dispatch-event [::investigate-clicked (:numeric-code account) (:numeric-code account) :comparable])}
|
||||
(->$ (:amount (get comparable-accounts (:id account)) 0))]])
|
||||
(when include-comparison
|
||||
[:td.has-text-right (->$ (- (:amount account ) (:amount (get comparable-accounts (:id account)) 0)))])])
|
||||
[:tr [:th "---" grouping-name "---"]
|
||||
[:th.has-text-right.total [:a {:on-click (dispatch-event [::investigate-clicked from to :current])}
|
||||
(->$ (reduce + 0 (map :amount
|
||||
matching-accounts)))] ]
|
||||
(when include-comparison
|
||||
[:th.has-text-right.total [:a {:on-click (dispatch-event [::investigate-clicked from to :comparable])}
|
||||
(->$ (reduce + 0 (map #(:amount (get comparable-accounts (:id %)) 0)
|
||||
matching-accounts)))]])
|
||||
(when include-comparison
|
||||
[:th.has-text-right.total (->$ (reduce + 0
|
||||
(map #(- (:amount % ) (:amount (get comparable-accounts (:id %)) 0))
|
||||
matching-accounts)))])
|
||||
[:td]
|
||||
]))))
|
||||
|
||||
(defn overall-grouping [type title]
|
||||
(let [include-comparison @(re-frame/subscribe [::include-comparison])]
|
||||
(list
|
||||
[:tr [:th.has-text-centered title]
|
||||
[:td]
|
||||
(when include-comparison
|
||||
[:td])
|
||||
(when include-comparison
|
||||
[:td])]
|
||||
(grouping {:accounts @(re-frame/subscribe [::accounts type])
|
||||
:groupings (type groupings)
|
||||
:comparable-accounts @(re-frame/subscribe [::comparable-accounts-by-id type])
|
||||
})
|
||||
[:tr [:th.has-text-centered title]
|
||||
[:th.has-text-right (->$ (reduce + 0 (map :amount @(re-frame/subscribe [::accounts type]))))]
|
||||
(when include-comparison
|
||||
[:th.has-text-right (->$ (reduce + 0 (map :amount (vals @(re-frame/subscribe [::comparable-accounts-by-id type])))))])
|
||||
(when include-comparison
|
||||
[:th.has-text-right (->$ (- (reduce + 0 (map :amount @(re-frame/subscribe [::accounts type])))
|
||||
(reduce + 0 (map :amount (vals @(re-frame/subscribe [::comparable-accounts-by-id type]))))))])])))
|
||||
(def balance-sheet-form (forms/vertical-form {:can-submit [::can-submit]
|
||||
:change-event [::change]
|
||||
:submit-event [::report-requested]
|
||||
:id ::form}))
|
||||
|
||||
(defn report-form []
|
||||
(let [{:keys [form-inline raw-field]} balance-sheet-form
|
||||
{:keys [data]} @(re-frame/subscribe [::forms/form ::form])]
|
||||
(form-inline {}
|
||||
[:div
|
||||
[status/status-notification {:statuses [[::status/single ::page]]}]
|
||||
[:div.report-controls
|
||||
[:div.level
|
||||
[:div.level-left
|
||||
[:div.level-item
|
||||
[:div.control
|
||||
[:p.help "Date"]
|
||||
(raw-field
|
||||
[date-picker-friendly {:type "date"
|
||||
:field [:date]}])]]
|
||||
[:div.level-item
|
||||
[:div.control
|
||||
[:div.mt-3]
|
||||
[switch-field {:id "include-comparison"
|
||||
:checked (:include-comparison data)
|
||||
:on-change (fn [e]
|
||||
(re-frame/dispatch [::change [:include-comparison] (.-checked (.-target e))]))
|
||||
:label "Include comparison"
|
||||
:type "checkbox"}]]]
|
||||
[:div.level-item
|
||||
|
||||
(defn retained-earnings []
|
||||
|
||||
(let [include-comparison @(re-frame/subscribe [::include-comparison])]
|
||||
(list
|
||||
|
||||
#_(grouping {:accounts @(re-frame/subscribe [::accounts type])
|
||||
:comparable-accounts @(re-frame/subscribe [::comparable-accounts-by-id type])
|
||||
})
|
||||
[:tr [:th.has-text-centered "Retained Earnings"]
|
||||
[:th.has-text-right (->$ (- (reduce + 0 (map :amount @(re-frame/subscribe [::accounts :revenue])))
|
||||
(reduce + 0 (map :amount @(re-frame/subscribe [::accounts :expense])))))]
|
||||
(when include-comparison
|
||||
[:th.has-text-right (->$ (- (reduce + 0 (map :amount (vals @(re-frame/subscribe [::comparable-accounts-by-id :revenue]))))
|
||||
(reduce + 0 (map :amount (vals @(re-frame/subscribe [::comparable-accounts-by-id :expense]))))))])
|
||||
(when include-comparison
|
||||
[:th.has-text-right (->$ (- (- (reduce + 0 (map :amount @(re-frame/subscribe [::accounts :revenue])))
|
||||
(reduce + 0 (map :amount @(re-frame/subscribe [::accounts :expense]))))
|
||||
(- (reduce + 0 (map :amount (vals @(re-frame/subscribe [::comparable-accounts-by-id :revenue]))))
|
||||
(reduce + 0 (map :amount (vals @(re-frame/subscribe [::comparable-accounts-by-id :expense])))))))])])))
|
||||
(when (boolean (:include-comparison data))
|
||||
[:div.control
|
||||
[:p.help "Comparison Date"]
|
||||
(raw-field
|
||||
[date-picker-friendly {:type "date"
|
||||
:field [:comparison-date]}])])]]
|
||||
[:div.level-right
|
||||
[:div.buttons
|
||||
|
||||
(def balance-sheet-content
|
||||
(with-meta
|
||||
(fn []
|
||||
(let [current-client @(re-frame/subscribe [::subs/client])
|
||||
status @(re-frame/subscribe [::status/single ::page])
|
||||
user @(re-frame/subscribe [::subs/user])
|
||||
params @(re-frame/subscribe [::params])]
|
||||
(if current-client
|
||||
[:div.is-inline
|
||||
[:h1.title "Balance Sheet - " (:name current-client)]
|
||||
[status/status-notification {:statuses [[::status/single ::page]]}]
|
||||
(when @(re-frame/subscribe [::subs/is-admin?])
|
||||
[:button.button.is-secondary {:on-click (dispatch-event [::export-pdf])} "Export"])
|
||||
[:button.button.is-primary "Run"]]]]]])))
|
||||
|
||||
[:div.report-controls
|
||||
|
||||
[:div.level
|
||||
[:div.level-left
|
||||
[:div.level-item
|
||||
[:div.control
|
||||
[:p.help "Date"]
|
||||
[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 [:date]
|
||||
:event [::date-picked]
|
||||
:popper-props (clj->js {:placement "right"})
|
||||
:subscription params}]]]]
|
||||
[:div.level-item
|
||||
|
||||
(defn balance-sheet-report [{:keys [args report-data]}]
|
||||
(let [pnl-data (concat (->> (:balance-sheet-accounts report-data)
|
||||
(map (fn [b]
|
||||
(assoc b
|
||||
:period (:date args)
|
||||
:amount (js/parseFloat (:amount b))))))
|
||||
(->> (:comparable-balance-sheet-accounts report-data)
|
||||
(map (fn [b]
|
||||
(assoc b
|
||||
:period (:comparison-date args)
|
||||
:amount (js/parseFloat (:amount b)))))))
|
||||
|
||||
client-names (->> @(re-frame/subscribe [::subs/clients-by-id])
|
||||
(map (fn [[k v]]
|
||||
[k (:name v)]))
|
||||
(into {}))
|
||||
pnl-data (l-reports/->PNLData args pnl-data client-names)
|
||||
report (l-reports/summarize-balance-sheet pnl-data)]
|
||||
[rtable/table {:widths (into [30 13
|
||||
(when (:include-comparison args)
|
||||
13)
|
||||
(when (:include-comparison args)
|
||||
13)])
|
||||
:click-event ::investigate-clicked
|
||||
:table report}]))
|
||||
|
||||
[:div.control
|
||||
[:div.mt-3]
|
||||
[switch-field {:id "include-comparison"
|
||||
:checked (boolean (:include-comparison params))
|
||||
:on-change (fn [e]
|
||||
(re-frame/dispatch [::include-comparison-changed (.-checked (.-target e))]))
|
||||
:label "Include comparison"
|
||||
:type "checkbox"}]]]
|
||||
[:div.level-item
|
||||
(defn balance-sheet-content []
|
||||
(let [current-client @(re-frame/subscribe [::subs/client])
|
||||
status @(re-frame/subscribe [::status/single ::page])
|
||||
{params :data report :report} @(re-frame/subscribe [::forms/form ::form])]
|
||||
(if current-client
|
||||
[:div.is-inline
|
||||
[status/status-notification {:statuses [[::status/single ::page]]}]
|
||||
[report-form]
|
||||
|
||||
(when (boolean (:include-comparison params))
|
||||
[:div.control
|
||||
|
||||
[:p.help "Comparison Date"]
|
||||
[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 [:comparison-date]
|
||||
:event [::comparison-date-picked]
|
||||
:popper-props (clj->js {:placement "right"})
|
||||
:subscription params}]]])
|
||||
]]]]
|
||||
[status/big-loader status]
|
||||
(when (not= :loading (:state status))
|
||||
[:table.table.compact.balance-sheet
|
||||
[:tr
|
||||
[:td.has-text-right "Period ending"]
|
||||
[:td.has-text-right (date->str (str->date (:date params) standard))]
|
||||
(when (:include-comparison params)
|
||||
[:td.has-text-right (when (:date params)
|
||||
(date->str (str->date (:comparison-date params) standard)))])
|
||||
[:td]]
|
||||
(list
|
||||
(overall-grouping :asset "Assets")
|
||||
(overall-grouping :liability "Liabilities" )
|
||||
(overall-grouping :equity "Owner's Equity" )
|
||||
(retained-earnings))])]
|
||||
[:div
|
||||
[:h1.title "Balance sheet"]
|
||||
[:h2.subtitle "Please choose a client first"]])))
|
||||
{:component-will-mount #(re-frame/dispatch-sync [::params-change {:date (date->str (local-now) standard)
|
||||
:comparison-date (date->str (t/minus (local-now) (t/years 1)) standard)
|
||||
:include-comparison true}]) }))
|
||||
|
||||
[status/big-loader status]
|
||||
(when (and (not= :loading (:state status))
|
||||
report)
|
||||
[balance-sheet-report {:args (assoc params
|
||||
:periods (filter identity (cond-> [(:date params)]
|
||||
(:include-comparison params) (conj (:comparison-date params)))))
|
||||
:report-data report}])]
|
||||
[:div
|
||||
[:h1.title "Balance sheet"]
|
||||
[:h2.subtitle "Please choose a client first"]])))
|
||||
|
||||
(defn ledger-list [_ ]
|
||||
[:div [:a.delete.is-pulled-right {:on-click (dispatch-event [::ledger-list-closing])}]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[auto-ap.ledger.reports :as l-reports]
|
||||
[auto-ap.status :as status]
|
||||
[auto-ap.subs :as subs]
|
||||
[auto-ap.views.pages.ledger.report-table :as rtable]
|
||||
[auto-ap.utils :refer [dollars-0?]]
|
||||
[auto-ap.views.components.buttons :as buttons]
|
||||
[auto-ap.views.components.layouts
|
||||
@@ -68,7 +69,7 @@
|
||||
(assoc db ::ledger-list-active? false)))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::params-change
|
||||
::report-requested
|
||||
[with-user (forms/in-form ::form)]
|
||||
(fn [{:keys [db user] :as cofx}]
|
||||
(cond-> {:graphql {:token user
|
||||
@@ -87,8 +88,13 @@
|
||||
(defn email-body [report-url]
|
||||
(js/encodeURIComponent
|
||||
(str
|
||||
"Your profit and loss report is now ready.
|
||||
Please download it by clicking this link: " report-url)))
|
||||
"Hello,
|
||||
Click here (" report-url ") to download your financial reports. We have not finished reviewing and reconciling these numbers with you. Please review and let us know if anything seems missing or in need of correction.
|
||||
Click here (http://app.integreatconsult.com/) to login to the Financials app to review the details here.
|
||||
Click here (https://share.vidyard.com/watch/MHTo5PyXPxXUpVH93RWFM9?) for a video on how to run a P&L on your own.
|
||||
To see a history of past financial reports, click here: https://app.integreatconsult.com/reports/
|
||||
|
||||
NOTE: Please review the transactions we may have question for you here: https://app.integreatconsult.com/transactions/requires-feedback. You can either edit the transaction to what expense account it should be or email back what it should be.")))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::received-pdf
|
||||
@@ -104,10 +110,11 @@ Please download it by clicking this link: " report-url)))
|
||||
{:dispatch [::modal/modal-requested {:title "Your report is ready"
|
||||
:body [:div
|
||||
[:div "Click "
|
||||
[:a {:href (-> result :profit-and-loss-pdf :report-url) :target "_new"} "here"] " to view it."]
|
||||
[:a {:href (-> result :profit-and-loss-pdf :url) :target "_new"} "here"] " to view it."]
|
||||
(when (and single-client? (seq client-emails))
|
||||
[:div "Once you've confirmed you're happy with it, click "
|
||||
[:a {:href (str "mailto:" (str/join ";" (map :email client-emails)) "?body=" (email-body (-> result :profit-and-loss-pdf :report-url)))}
|
||||
[:a {:href (str "mailto:" (str/join ";" (map :email client-emails)) "?body=" (email-body (-> result :profit-and-loss-pdf :url))
|
||||
"&subject=" (-> result :profit-and-loss-pdf :name) " is ready")}
|
||||
"here"] " to open your email client and to send it to " (str/join "," (map (fn [e]
|
||||
(str (:email e) " (" (:description e) ")"))
|
||||
client-emails)) "."])]}]})))
|
||||
@@ -121,7 +128,7 @@ Please download it by clicking this link: " report-url)))
|
||||
{:client-ids (map :id (:clients (:data db)))
|
||||
:include-deltas (:include-deltas (:data db))
|
||||
:periods (mapv encode-period (:periods (:data db)))}
|
||||
[:report_url]]]}
|
||||
[:url :name]]]}
|
||||
:on-success [::received-pdf]}
|
||||
:set-uri-params {:periods (mapv encode-period (:periods (:data db)))
|
||||
:clients (mapv #(select-keys % [:name :id]) (:clients (:data db))) }
|
||||
@@ -199,7 +206,8 @@ Please download it by clicking this link: " report-url)))
|
||||
(re-frame/reg-event-fx
|
||||
::investigate-clicked
|
||||
(fn [{:keys [db]} [_ {:keys [location from-numeric-code to-numeric-code client-id]
|
||||
{:keys [start end]} :date-range}]]
|
||||
{:keys [start end]} :date-range
|
||||
:as g}]]
|
||||
{:db (-> db (assoc ::ledger-list-active? true))
|
||||
:dispatch [::data-page/additional-params-changed ::ledger {:client-id client-id
|
||||
:from-numeric-code from-numeric-code
|
||||
@@ -209,82 +217,6 @@ Please download it by clicking this link: " report-url)))
|
||||
:end (date->str end standard)}}]}))
|
||||
|
||||
|
||||
|
||||
(defn cell [{:keys [width]} c]
|
||||
(let [cell-contents (cond
|
||||
(and (= :dollar (:format c))
|
||||
(dollars-0? (:value c)))
|
||||
"-"
|
||||
|
||||
(= :dollar (:format c))
|
||||
(->$ (:value c))
|
||||
|
||||
(= :percent (:format c))
|
||||
(->% (:value c))
|
||||
|
||||
:else
|
||||
(str (:value c)))
|
||||
cell-contents (if (:filters c)
|
||||
[:a {:on-click (dispatch-event [::investigate-clicked (:filters c)])}
|
||||
cell-contents]
|
||||
cell-contents)]
|
||||
[:td
|
||||
(cond-> {:style {:width (str width "em")}}
|
||||
|
||||
(:border c) (update :style
|
||||
(fn [s]
|
||||
(->> (:border c)
|
||||
(map
|
||||
(fn [b]
|
||||
[(keyword (str "border-" (name b))) "1px solid black"])
|
||||
)
|
||||
(into s))))
|
||||
(:colspan c) (assoc :colspan (:colspan c))
|
||||
(:align c) (assoc :align (:align c))
|
||||
(= :dollar (:format c)) (assoc :align :right)
|
||||
(= :percent (:format c)) (assoc :align :right)
|
||||
(:bold c) (assoc-in [:style :font-weight] "bold")
|
||||
(:color c) (assoc-in [:style :color] (str "rgb("
|
||||
(str/join ","
|
||||
(:color c))
|
||||
")")))
|
||||
|
||||
cell-contents]))
|
||||
|
||||
(defn cell-count [table]
|
||||
(let [counts (map count (:rows table))]
|
||||
(if (seq counts)
|
||||
(apply max counts)
|
||||
0)))
|
||||
|
||||
(defn table->pdf [{:keys [table widths]}]
|
||||
(let [cell-count (cell-count table)]
|
||||
(-> [:table.table.compact.balance-sheet {:style nil}
|
||||
(map
|
||||
(fn [i header]
|
||||
(into ^{:key i}
|
||||
[:tr]
|
||||
(map
|
||||
(fn [w header i]
|
||||
^{:key i} [cell {:width w} header])
|
||||
widths
|
||||
header
|
||||
(range))))
|
||||
(range)
|
||||
(:header table))]
|
||||
|
||||
(into
|
||||
(for [[i row] (map vector (range) (:rows table))]
|
||||
^{:key i}
|
||||
[:tr
|
||||
(for [[i c] (map vector (range) (take cell-count (concat row (repeat nil))))]
|
||||
^{:key i}
|
||||
[cell {} c])]))
|
||||
(conj ^{:key "last"}
|
||||
[:tr (for [i (range cell-count)]
|
||||
^{:key i}
|
||||
[cell {} {:value " "}])]))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::can-submit
|
||||
(fn [db]
|
||||
@@ -292,7 +224,7 @@ Please download it by clicking this link: " report-url)))
|
||||
|
||||
(def pnl-form (forms/vertical-form {:can-submit [::can-submit]
|
||||
:change-event [::change]
|
||||
:submit-event [::params-change]
|
||||
:submit-event [::report-requested]
|
||||
:id ::form}))
|
||||
|
||||
(defn report-control-detail [{:keys [active box which]} children]
|
||||
@@ -530,18 +462,7 @@ Please download it by clicking this link: " report-url)))
|
||||
(when-not @!box
|
||||
(reset! !box el)))}]]))))
|
||||
|
||||
(defn concat-tables [tables]
|
||||
(println (count tables))
|
||||
(let [[first & rest] tables]
|
||||
{:header (:header first)
|
||||
:rows (concat (:rows first)
|
||||
[[]]
|
||||
(mapcat
|
||||
(fn [table]
|
||||
(-> (:header table)
|
||||
(into (:rows table))
|
||||
(conj [])))
|
||||
rest))}))
|
||||
|
||||
|
||||
(defn pnl-report [{:keys [args report-data]}]
|
||||
(let [pnl-data (->> report-data
|
||||
@@ -560,24 +481,25 @@ Please download it by clicking this link: " report-url)))
|
||||
(into {}))
|
||||
pnl-data (l-reports/->PNLData args pnl-data client-names)
|
||||
report (l-reports/summarize-pnl pnl-data)
|
||||
table (concat-tables (concat (:summaries report) (:details report)))]
|
||||
table (rtable/concat-tables (concat (:summaries report) (:details report)))]
|
||||
[:div
|
||||
[:h1.title "Profit and Loss - " (str/join ", " (map :name (:clients args)))]
|
||||
(when (:warning report)
|
||||
[:div.notification.is-warning.is-light
|
||||
(:warning report)])
|
||||
[table->pdf {:widths (into [20] (take (dec (cell-count table))
|
||||
(mapcat identity
|
||||
(repeat
|
||||
(if (-> pnl-data :args :include-deltas)
|
||||
[13 6 13]
|
||||
[13 6])))))
|
||||
:table table}]]))
|
||||
[rtable/table {:widths (into [20] (take (dec (rtable/cell-count table))
|
||||
(mapcat identity
|
||||
(repeat
|
||||
(if (-> pnl-data :args :include-deltas)
|
||||
[13 6 13]
|
||||
[13 6])))))
|
||||
:click-event ::investigate-clicked
|
||||
:table table}]]))
|
||||
|
||||
|
||||
(defn profit-and-loss-content []
|
||||
(let [status @(re-frame/subscribe [::status/single ::page])
|
||||
{:keys [data report]} @(re-frame/subscribe [::forms/form ::form])
|
||||
(let [status @(re-frame/subscribe [::status/single ::page])
|
||||
{:keys [data report]} @(re-frame/subscribe [::forms/form ::form])
|
||||
{:keys [form-inline]} pnl-form]
|
||||
[:div
|
||||
(form-inline {}
|
||||
@@ -592,7 +514,7 @@ Please download it by clicking this link: " report-url)))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::unmounted-pnl
|
||||
(fn [{:keys [db]} _]
|
||||
(fn [{:keys [_]} _]
|
||||
{::track/dispose {:id ::ledger-params}}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
|
||||
89
src/cljs/auto_ap/views/pages/ledger/report_table.cljs
Normal file
89
src/cljs/auto_ap/views/pages/ledger/report_table.cljs
Normal file
@@ -0,0 +1,89 @@
|
||||
(ns auto-ap.views.pages.ledger.report-table
|
||||
(:require
|
||||
[auto-ap.utils :refer [dollars-0?]]
|
||||
[clojure.string :as str]
|
||||
[auto-ap.views.utils :refer [->$ ->% dispatch-event]]))
|
||||
|
||||
(defn cell [{:keys [width click-event]} c]
|
||||
(let [cell-contents (cond
|
||||
|
||||
(= :dollar (:format c))
|
||||
(->$ (:value c))
|
||||
|
||||
(= :percent (:format c))
|
||||
(->% (:value c))
|
||||
|
||||
:else
|
||||
(str (:value c)))
|
||||
cell-contents (if (:filters c)
|
||||
[:a {:on-click (dispatch-event [click-event (:filters c)])}
|
||||
cell-contents]
|
||||
cell-contents)]
|
||||
[:td
|
||||
(cond-> {:style {:width (str width "em")}}
|
||||
|
||||
(:border c) (update :style
|
||||
(fn [s]
|
||||
(->> (:border c)
|
||||
(map
|
||||
(fn [b]
|
||||
[(keyword (str "border-" (name b))) "1px solid black"])
|
||||
)
|
||||
(into s))))
|
||||
(:colspan c) (assoc :colspan (:colspan c))
|
||||
(:align c) (assoc :align (:align c))
|
||||
(= :dollar (:format c)) (assoc :align :right)
|
||||
(= :percent (:format c)) (assoc :align :right)
|
||||
(:bold c) (assoc-in [:style :font-weight] "bold")
|
||||
(:color c) (assoc-in [:style :color] (str "rgb("
|
||||
(str/join ","
|
||||
(:color c))
|
||||
")")))
|
||||
|
||||
cell-contents]))
|
||||
|
||||
(defn cell-count [table]
|
||||
(let [counts (map count (:rows table))]
|
||||
(if (seq counts)
|
||||
(apply max counts)
|
||||
0)))
|
||||
|
||||
(defn table [{:keys [table widths click-event]}]
|
||||
(let [cell-count (cell-count table)]
|
||||
(-> [:table.table.compact.balance-sheet {:style nil}
|
||||
(map
|
||||
(fn [i header]
|
||||
(into ^{:key i}
|
||||
[:tr]
|
||||
(map
|
||||
(fn [w header i]
|
||||
^{:key i} [cell {:width w :click-event click-event} header])
|
||||
widths
|
||||
header
|
||||
(range))))
|
||||
(range)
|
||||
(:header table))]
|
||||
|
||||
(into
|
||||
(for [[i row] (map vector (range) (:rows table))]
|
||||
^{:key i}
|
||||
[:tr
|
||||
(for [[i c] (map vector (range) (take cell-count (concat row (repeat nil))))]
|
||||
^{:key i}
|
||||
[cell {:click-event click-event} c])]))
|
||||
(conj ^{:key "last"}
|
||||
[:tr (for [i (range cell-count)]
|
||||
^{:key i}
|
||||
[cell {:click-event click-event} {:value " "}])]))))
|
||||
|
||||
(defn concat-tables [tables]
|
||||
(let [[first & rest] tables]
|
||||
{:header (:header first)
|
||||
:rows (concat (:rows first)
|
||||
[[]]
|
||||
(mapcat
|
||||
(fn [table]
|
||||
(-> (:header table)
|
||||
(into (:rows table))
|
||||
(conj [])))
|
||||
rest))}))
|
||||
Reference in New Issue
Block a user