Makes balance sheet work like pnl, updates pnl as necessary

This commit is contained in:
2022-03-31 19:27:36 -07:00
parent b9dd34d7c7
commit aaa82714d9
6 changed files with 367 additions and 427 deletions

View File

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