profit and loss changes for more than one client.

This commit is contained in:
2021-10-22 07:02:10 -07:00
parent 2d52c2b6a7
commit 16c0e679bc
3 changed files with 305 additions and 239 deletions

View File

@@ -13,6 +13,8 @@
[:span.icon [:i.fa {:class icon}]]]
(r/children (r/current-component)))])
(defn sl-icon [{:keys [event icon class on-click] :as params}]
[:a.button (cond-> params
true (dissoc :event :icon)
@@ -25,3 +27,14 @@
:on-click (dispatch-event event)}
[:span.icon [:i.fa.fa-plus]]
[:span name]])
(defn dropdown [{:keys [event icon class on-click] :as params}]
[:a.button (cond-> params
true (dissoc :event :icon)
(and (not on-click)
event)
(assoc :on-click (dispatch-event event)))
(conj (into
[:<>]
(r/children (r/current-component)))
[:span.icon [:i.fa.fa-chevron-down]])])

View File

@@ -1,6 +1,6 @@
(ns auto-ap.views.components.switch-field)
(defn switch-field [{:keys [id label on-change checked]}]
(defn switch-field [{:keys [id label on-change checked class]}]
[:<>
[:input.switch {:type "checkbox" :id id :on-change on-change :checked checked}]
[:input.switch {:type "checkbox" :id id :on-change on-change :checked checked :class class}]
[:label {:for id} label]])

View File

@@ -8,10 +8,13 @@
[auto-ap.forms :as forms]
[auto-ap.views.pages.ledger.side-bar :refer [ledger-side-bar]]
[auto-ap.views.components.typeahead :refer [typeahead-v3]]
[auto-ap.views.components.buttons :as buttons]
[auto-ap.views.components.switch-field :refer [switch-field]]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.utils :refer [date->str date-picker date-picker-friendly bind-field standard pretty dispatch-event local-today ->% ->$ str->date with-user dispatch-value-change query-params]]
[cljs-time.core :as t]
[re-frame.core :as re-frame]
[react-dom :as react-dom]
[auto-ap.status :as status]
[auto-ap.views.pages.data-page :as data-page]
[vimsical.re-frame.fx.track :as track]
@@ -139,10 +142,6 @@
(fn [db [_ data]]
(-> db (assoc :report (:profit-and-loss data)))))
(re-frame/reg-sub
::params
(fn [db]
(-> db ::params)))
(re-frame/reg-sub
::period-inputs
@@ -152,7 +151,12 @@
(re-frame/reg-sub
::periods
(fn [db]
(::periods db)))
(-> db ::forms/forms ::form :data :periods)))
(re-frame/reg-sub
::include-deltas
(fn [db]
(-> db ::forms/forms ::form :data :include-deltas)))
;; EVENTS
@@ -165,24 +169,22 @@
::params-change
[with-user (forms/in-form ::form)]
(fn [{:keys [db user] :as cofx}]
(let [c @(re-frame/subscribe [::subs/client])]
(cond-> {:graphql {:token user
:owns-state {:single ::page}
:query-obj {:venia/queries [[:profit-and-loss
{:client-ids (or (some-> (:id c) vector)
(some-> (:id (:client (:data db)))
vector))
:periods (mapv (fn [[start end] ] {:start (date->str start standard) :end (date->str end standard)} )
(:periods (:data db)))}
[[:periods [[:accounts [:name :amount :client_id :account-type :id :count :numeric-code :location]]]]]]]}
:on-success [::received]}
:set-uri-params {:periods (mapv (fn [[start end title]]
[(date->str start standard)
(date->str end standard)])
(:periods (:data db)))
:client (select-keys (:client (:data db))
[:name :id])}
:db (dissoc db :report)}))))
(cond-> {:graphql {:token user
:owns-state {:single ::page}
:query-obj {:venia/queries [[:profit-and-loss
{:client-ids (some-> (:id (:client (:data db)))
vector)
:periods (mapv (fn [[start end] ] {:start (date->str start standard) :end (date->str end standard)} )
(:periods (:data db)))}
[[:periods [[:accounts [:name :amount :client_id :account-type :id :count :numeric-code :location]]]]]]]}
:on-success [::received]}
:set-uri-params {:periods (mapv (fn [[start end title]]
[(date->str start standard)
(date->str end standard)])
(:periods (:data db)))
:client (select-keys (:client (:data db))
[:name :id])}
:db (dissoc db :report)})))
@@ -276,7 +278,6 @@
:can-submit [::can-submit-period-form]
:id ::period-form}))
;; 877 w fremont ave suite i4
(defn change-period-body [idx which]
(let [{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::period-form])
{:keys [form-inline horizontal-field field raw-field error-notification submit-button]} change-period-form]
@@ -424,11 +425,12 @@
{:key (str "between-" i)}))]))
;; reimplement include-deltas. Are numbers even correct? support actually using more than 1 client.
;; TODO Allow choosing more than one client
;; TODO allow "sandwhiching" clients data
;; TODO do we need squashing locations?
(defn grouping [{:keys [header type groupings location periods all-accounts]}]
(let [params @(re-frame/subscribe [::params])]
(let [include-deltas @(re-frame/subscribe [::include-deltas])]
[:<>
(doall
(for [[grouping-name from to] groupings
@@ -445,7 +447,7 @@
(fn [i]
[:td])
periods
(:include-deltas params))]
include-deltas)]
[:<>
(for [{:keys [numeric-code name]} account-codes]
^{:key numeric-code}
@@ -462,7 +464,7 @@
[:td.has-text-right (->$ (- (get-in all-accounts [i [numeric-code location] :amount] 0.0)
(get-in all-accounts [(dec i) [numeric-code location] :amount] 0.0)))])
periods
(:include-deltas params))])]
include-deltas)])]
[:tr
[:th]
@@ -477,7 +479,7 @@
[:th.has-text-right.total (->$ (- (aggregate-accounts (filter-accounts all-accounts i [from to] location))
(aggregate-accounts (filter-accounts all-accounts (dec i) [from to] location))))])
periods
(:include-deltas params))]]))]))
include-deltas)]]))]))
@@ -485,7 +487,7 @@
(let [all-accounts @(re-frame/subscribe [::all-accounts])
periods @(re-frame/subscribe [::periods])
[min-numeric-code max-numeric-code] (ranges type)
params @(re-frame/subscribe [::params])]
include-deltas @(re-frame/subscribe [::include-deltas])]
[:<>
[:tr [:th.is-size-5 title]]
@@ -509,12 +511,12 @@
[:th.has-text-right (->$ (- (aggregate-accounts (filter-accounts all-accounts i [min-numeric-code max-numeric-code] location))
(aggregate-accounts (filter-accounts all-accounts (dec i) [min-numeric-code max-numeric-code] location))))])
periods
(:include-deltas params))]]))
include-deltas)]]))
(defn subtotal [types negs title location]
(let [all-accounts @(re-frame/subscribe [::all-accounts])
periods @(re-frame/subscribe [::periods])
params @(re-frame/subscribe [::params])]
include-deltas @(re-frame/subscribe [::include-deltas])]
[:tr [:th.is-size-5 title]
(map-periods
@@ -536,7 +538,7 @@
(negs t) (map #(update % :amount -))))
types))))])
periods
(:include-deltas params))]))
include-deltas)]))
(defn period-header [{:keys [include-deltas periods]}]
[:<>
@@ -582,13 +584,13 @@
[subtotal [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable] #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable} (str location " Net Income") location]
[subtotal [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable] #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable} "Net Income" nil]])
(defn location-summary [location params]
(defn location-summary [location include-deltas]
(let [periods @(re-frame/subscribe [::periods])]
[:div
[:h2.title.is-4.mb-4 location " Summary"]
[:table.table.compact.balance-sheet.mb-6
[:tbody
[period-header {:include-deltas (:include-deltas params)
[period-header {:include-deltas include-deltas
:periods periods}]
@@ -597,9 +599,7 @@
[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]]]
]))
[subtotal [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable] #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable} "Net Income" location]]]]))
(re-frame/reg-sub
::can-submit
@@ -611,213 +611,265 @@
:submit-event [::params-change]
:id ::form}))
(defn report-control-detail [{:keys [active box which]} children]
(when (and @box
(= which @active))
(react-dom/createPortal (reagent/as-element
[:div.notification.is-light
[:a.delete {:on-click (fn [] (reset! active nil))}]
children
])
@box)))
(defn report-controls [pnl-form]
(let [!box (reagent/atom nil)
active (reagent/atom nil)]
(fn [pnl-form]
(let [{:keys [form-inline field raw-field error-notification submit-button ]} pnl-form
{:keys [data report active? error id]} @(re-frame/subscribe [::forms/form ::form])
{:keys [periods selected-period include-deltas]} data]
[:div.report-controls
[:div.level.mb-2
[:div.level-left
[:div.level-item
[buttons/dropdown {:on-click (fn [] (reset! active :clients))}
[:span (str "Companies"
(when (:client data)
(str " (" (:name (:client data)) ")")))]]
[report-control-detail {:active active :box !box :which :clients}
[:div {:style {:width "20em"}}
[:h4.subtitle "Companies"]
(raw-field
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients])
:entity->text :name
:type "typeahead-v3"
:field [:client]}])]]]
[:div.level-item
[buttons/dropdown {:on-click (fn [] (reset! active :range))}
[:span (str "Range"
(when selected-period
(str " (" selected-period ")")))]]
[report-control-detail {:active active :box !box :which :range}
[:div
[:h4.subtitle "Range"]
[:div.field.is-grouped
[:div.control
[:div.field.has-addons
[:div.control
(raw-field
[date-picker-friendly {:placeholder "End date"
:type "date"
:field [:thirteen-periods-end]}])]
[:div.control
[:a.button
{:class (when (= selected-period "13 periods") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(let [today (or (some-> (:thirteen-periods-end data) (str->date standard))
(local-today))]
(into
[[(t/plus (t/minus today (t/weeks (* 13 4)))
(t/days 1))
today
"Total"]]
(for [i (range 13)]
[(t/plus (t/minus today (t/weeks (* (inc i) 4)))
(t/days 1))
(t/minus today (t/weeks (* i 4)))])))
[:selected-period] "13 periods"])}
"13 periods"]]]]
[:div.control
[:div.field.has-addons
[:div.control
(raw-field
[date-picker-friendly {:placeholder "End date"
:type "date"
:field [:twelve-periods-end]}])
]
[:div.control
[:a.button
{:class (when (= selected-period "12 months") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(let [end-date (or (some-> (:twelve-periods-end data) (str->date standard))
(local-today))
this-month (t/local-date (t/year end-date)
(t/month end-date)
1)
]
(into
[[(t/minus this-month (t/months 11))
(t/minus (t/plus this-month (t/months 1))
(t/days 1))
"Total"]]
(for [i (range 12)]
[(t/minus this-month (t/months (- 11 i)))
(t/minus (t/minus this-month (t/months (- 10 i)))
(t/days 1))])))
[:selected-period] "12 months"])}
"12 months"]]]]
[:div.control
[:a.button
{:class (when (= selected-period "Last week") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(let [last-sunday (loop [current (local-today)]
(if (= 7 (t/day-of-week current))
current
(recur (t/minus current (t/period :days 1)))))]
(and-last-year [(t/minus last-sunday (t/period :days 6)) last-sunday]))
[:selected-period] "Last week"])}
"Last week"]]
[:div.control
[:a.button
{:class (when (= selected-period "Week to date") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(and-last-year [(loop [current (local-today)]
(if (= 1 (t/day-of-week current))
current
(recur (t/minus current (t/period :days 1)))))
(local-today)])
[:selected-period] "Week to date"])}
"Week to date"]]
[:div.control
[:a.button
{:class (when (= selected-period "Last Month") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(and-last-year [(t/minus (t/local-date (t/year (local-today))
(t/month (local-today))
1)
(t/period :months 1))
(t/minus (t/local-date (t/year (local-today))
(t/month (local-today))
1)
(t/period :days 1))])
[:selected-period] "Last Month"])}
"Last Month"]]
[:div.control
[:a.button
{:class (when (= selected-period "Month to date") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(and-last-year [(t/local-date (t/year (local-today))
(t/month (local-today))
1)
(local-today)])
[:selected-period] "Month to date"]
)}
"Month to date"]]
[:div.control
[:a.button
{:class (when (= selected-period "Year to date") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(and-last-year [(t/local-date (t/year (local-today)) 1 1)
(local-today)])
[:selected-period] "Year to date"])}
"Year to date"]]
[:div.control
[:a.button
{:class (when (= selected-period "Full year") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(and-last-year [(t/plus (t/minus (local-today) (t/period :years 1))
(t/period :days 1))
(local-today)])
[:selected-period] "Full year"])}
"Full year"]]]
[:div
[:div.field
[:label.checkbox
(raw-field
[:input {:type "checkbox"
:field [:show-advanced?]}])
" Show Advanced"]]]
(when (:show-advanced? data)
(doall
(for [[_ i] (map vector periods (range))]
^{:key i}
[:div.field.is-grouped
[:div.control
[:p.help "From"]
(raw-field
[date-picker-friendly {:type "date"
:field [:periods i 0]}])]
[:div.control
[:p.help "To"]
(raw-field
[date-picker-friendly {:type "date"
:field [:periods i 1]}])]])))]]]
[:div.level-item
[:div
[switch-field {:id "include-deltas"
:checked (boolean include-deltas)
:on-change (fn [e]
(re-frame/dispatch [::forms/change ::form
[:include-deltas] (.-checked (.-target e))]))
:label "Include deltas"
:type "checkbox"}]]]]
[:div.level-right
[:button.button.is-primary "Run"]]]
[:div.report-control-detail {:ref (fn [el]
(when-not @!box
(reset! !box el)))}]]))))
(defn profit-and-loss-content []
(let [current-client @(re-frame/subscribe [::subs/client])
clients @(re-frame/subscribe [::subs/clients])
user @(re-frame/subscribe [::subs/user])
(let [user @(re-frame/subscribe [::subs/user])
status @(re-frame/subscribe [::status/single ::page])
params @(re-frame/subscribe [::params])
unresolved-accounts @(re-frame/subscribe [::uncategorized-accounts])
{:keys [data report active? error id]} @(re-frame/subscribe [::forms/form ::form])
{:keys [form-inline field raw-field error-notification submit-button ]} pnl-form
periods (:periods data)]
(when data
(form-inline {}
[:div
[:h1.title "Profit and Loss - " (:name current-client)]
[status/status-notification {:statuses [[::status/single ::page]]}]
[:div.report-controls
(when-not current-client
[:<>
[:h2.title.is-4 "Entities"]
(raw-field
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients])
:entity->text :name
:type "typeahead-v3"
:field [:client]}])])
[:h2.title.is-4 "Range"]
[:div
[:div.field.is-grouped
[:div.control
[:div.field.has-addons
[:div.control
(raw-field
[date-picker-friendly {:placeholder "End date"
:type "date"
:field [:thirteen-periods-end]}])]
[:div.control
[:a.button
{:class (when (= (:selected params) "13 periods") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(let [today (or (some-> (:thirteen-periods-end data) (str->date standard))
(local-today))]
(into
[[(t/plus (t/minus today (t/weeks (* 13 4)))
(t/days 1))
today
"Total"]]
(for [i (range 13)]
[(t/plus (t/minus today (t/weeks (* (inc i) 4)))
(t/days 1))
(t/minus today (t/weeks (* i 4)))])))])}
"13 periods"]]]]
(form-inline {}
[:div
[status/status-notification {:statuses [[::status/single ::page]]}]
[report-controls pnl-form]
[status/big-loader status]
(when (and (not= :loading (:state status))
report)
[:div.control
[:div.field.has-addons
[:div.control
(raw-field
[date-picker-friendly {:placeholder "End date"
:type "date"
:field [:twelve-periods-end]}])
]
[:div.control
[:a.button
{:class (when (= (:selected params) "13 periods") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(let [end-date (or (some-> (:twelve-periods-end data) (str->date standard))
(local-today))
this-month (t/local-date (t/year end-date)
(t/month end-date)
1)
]
(into
[[(t/minus this-month (t/months 11))
(t/minus (t/plus this-month (t/months 1))
(t/days 1))
"Total"]]
(for [i (range 12)]
[(t/minus this-month (t/months (- 11 i)))
(t/minus (t/minus this-month (t/months (- 10 i)))
(t/days 1))])))])}
"12 months"]]]]
[:div
[:div.control
[:a.button
{:class (when (= (:selected params) "Last week") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(let [last-sunday (loop [current (local-today)]
(if (= 7 (t/day-of-week current))
current
(recur (t/minus current (t/period :days 1)))))]
(and-last-year [(t/minus last-sunday (t/period :days 6)) last-sunday]))])}
"Last week"]]
[:div.control
[:a.button
{:class (when (= (:selected params) "Week to date") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(and-last-year [(loop [current (local-today)]
(if (= 1 (t/day-of-week current))
current
(recur (t/minus current (t/period :days 1)))))
(local-today)])])}
"Week to date"]]
[:div.control
[:a.button
{:class (when (= (:selected params) "Last Month") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(and-last-year [(t/minus (t/local-date (t/year (local-today))
(t/month (local-today))
1)
(t/period :months 1))
(t/minus (t/local-date (t/year (local-today))
(t/month (local-today))
1)
(t/period :days 1))])])}
"Last Month"]]
[:div.control
[:a.button
{:class (when (= (:selected params) "Month to date") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(and-last-year [(t/local-date (t/year (local-today))
(t/month (local-today))
1)
(local-today)])]
)}
"Month to date"]]
[:div.control
[:a.button
{:class (when (= (:selected params) "Year to date") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(and-last-year [(t/local-date (t/year (local-today)) 1 1)
(local-today)])])}
"Year to date"]]
[:div.control
[:a.button
{:class (when (= (:selected params) "Full year") "is-active")
:on-click (dispatch-event
[::forms/change ::form
[:periods]
(and-last-year [(t/plus (t/minus (local-today) (t/period :years 1))
(t/period :days 1))
(local-today)])])}
"Full year"]]]]
[:div
[:div.field
[:label.checkbox
(raw-field
[:input {:type "checkbox"
:field [:show-advanced?]}])
" Show Advanced"]]]
(when (:show-advanced? data)
(doall
(for [[_ i] (map vector periods (range))]
^{:key i}
[:div.field.is-grouped
[:div.control
[:p.help "From"]
(raw-field
[date-picker-friendly {:type "date"
:field [:periods i 0]}])]
[:div.control
[:p.help "To"]
(raw-field
[date-picker-friendly {:type "date"
:field [:periods i 1]}])]])))
(submit-button "Run")]
[status/big-loader status]
(when (and (not= :loading (:state status))
report)
[:div
(when (seq unresolved-accounts)
[:div.notification.is-warning.is-light
"This report does not include " (str/join ", "
(map #(str (:count %) " unresolved ledger entries for " (if (str/blank? (:location %))
" all locations"
(:location %)))
unresolved-accounts))])
[:h1.title "Profit and Loss - " (:name (:client data))]
(when (seq unresolved-accounts)
[:div.notification.is-warning.is-light
"This report does not include " (str/join ", "
(map #(str (:count %) " unresolved ledger entries for " (if (str/blank? (:location %))
" all locations"
(:location %)))
unresolved-accounts))])
[:<>
(for [location @(re-frame/subscribe [::locations])]
^{:key (str location "-summary")}
[location-summary location (:include-deltas data)]
)]
[:h2.title.is-4 {:style {:margin-bottom "1rem"}} "Detail"]
[:table.table.compact.balance-sheet
[:tbody
[period-header {:include-deltas (:include-deltas data)
:periods periods}]
[:<>
(for [location @(re-frame/subscribe [::locations])]
^{:key (str location "-summary")}
[location-summary location params]
)]
[:h2.title.is-4 {:style {:margin-bottom "1rem"}} "Detail"]
[:table.table.compact.balance-sheet
[:tbody
[period-header {:include-deltas (:include-deltas params)
:periods periods}]
[:<>
(for [location @(re-frame/subscribe [::locations])]
^{:key location}
[location-rows location])]]]])]))))
^{:key location}
[location-rows location])]]]])])))
(re-frame/reg-event-fx
::unmounted-pnl
@@ -836,7 +888,8 @@
(fn [dt]
(str->date dt standard))
period))))
:client (:client qp)
:client (or (:client qp)
(some-> @(re-frame/subscribe [::subs/client]) (select-keys [:name :id])))
:include-deltas true})
::track/register {:id ::ledger-params
:subscription [::data-page/params ::ledger]