Adds the ability to do a column per location
This commit is contained in:
@@ -192,6 +192,9 @@
|
|||||||
(throw (ex-info "Please select a client." {:validation-error "Please select a client."})))
|
(throw (ex-info "Please select a client." {:validation-error "Please select a client."})))
|
||||||
_ (doseq [client-id client-ids]
|
_ (doseq [client-id client-ids]
|
||||||
(assert-can-see-client (:id context) client-id))
|
(assert-can-see-client (:id context) client-id))
|
||||||
|
_ (when (and (:include_deltas args)
|
||||||
|
(:column_per_location args))
|
||||||
|
(throw (ex-info "Please select one of 'Include deltas' or 'Column per location'" {:validation-error "Please select one of 'Include deltas' or 'Column per location'"})))
|
||||||
all-ledger-entries (->> client-ids
|
all-ledger-entries (->> client-ids
|
||||||
(map (fn [client-id]
|
(map (fn [client-id]
|
||||||
[client-id (full-ledger-for-client client-id)]))
|
[client-id (full-ledger-for-client client-id)]))
|
||||||
@@ -584,14 +587,16 @@
|
|||||||
:args {:client_id {:type :id}
|
:args {:client_id {:type :id}
|
||||||
:client_ids {:type '(list :id)}
|
:client_ids {:type '(list :id)}
|
||||||
:periods {:type '(list :date_range)}
|
:periods {:type '(list :date_range)}
|
||||||
:include_deltas {:type 'Boolean}}
|
:include_deltas {:type 'Boolean}
|
||||||
|
:column_per_location {:type 'Boolean}}
|
||||||
:resolve :get-profit-and-loss}
|
:resolve :get-profit-and-loss}
|
||||||
|
|
||||||
:profit_and_loss_pdf {:type :report_pdf
|
:profit_and_loss_pdf {:type :report_pdf
|
||||||
:args {:client_id {:type :id}
|
:args {:client_id {:type :id}
|
||||||
:client_ids {:type '(list :id)}
|
:client_ids {:type '(list :id)}
|
||||||
:periods {:type '(list :date_range)}
|
:periods {:type '(list :date_range)}
|
||||||
:include_deltas {:type 'Boolean}}
|
:include_deltas {:type 'Boolean}
|
||||||
|
:column_per_location {:type 'Boolean}}
|
||||||
:resolve :profit-and-loss-pdf}
|
:resolve :profit-and-loss-pdf}
|
||||||
|
|
||||||
:balance_sheet_pdf {:type :report_pdf
|
:balance_sheet_pdf {:type :report_pdf
|
||||||
|
|||||||
@@ -90,9 +90,11 @@
|
|||||||
(vals ranges))
|
(vals ranges))
|
||||||
false))
|
false))
|
||||||
|
|
||||||
(defn locations [data]
|
(defn client-locations [pnl-data]
|
||||||
(->> data
|
(->> pnl-data
|
||||||
|
:data
|
||||||
(filter (comp in-range? :numeric-code))
|
(filter (comp in-range? :numeric-code))
|
||||||
|
(filter #(not= "A" (:location %)))
|
||||||
(group-by (juxt :client-id :location))
|
(group-by (juxt :client-id :location))
|
||||||
(filter (fn [[_ as]]
|
(filter (fn [[_ as]]
|
||||||
(not (dollars-0? (reduce + 0 (map :amount as))))))
|
(not (dollars-0? (reduce + 0 (map :amount as))))))
|
||||||
@@ -112,6 +114,15 @@
|
|||||||
"ZZZZZZ"
|
"ZZZZZZ"
|
||||||
(:location x))]))))
|
(:location x))]))))
|
||||||
|
|
||||||
|
(defn locations [pnl-data]
|
||||||
|
(->> (client-locations pnl-data)
|
||||||
|
(map second)
|
||||||
|
set
|
||||||
|
(sort-by (fn [x]
|
||||||
|
(if (= x "HQ" )
|
||||||
|
"ZZZZZZ"
|
||||||
|
x)))))
|
||||||
|
|
||||||
(defn aggregate-accounts [pnl-data]
|
(defn aggregate-accounts [pnl-data]
|
||||||
(reduce (fnil + 0.0) 0.0 (map :amount (:data pnl-data))))
|
(reduce (fnil + 0.0) 0.0 (map :amount (:data pnl-data))))
|
||||||
|
|
||||||
@@ -179,34 +190,32 @@
|
|||||||
accounts))))
|
accounts))))
|
||||||
|
|
||||||
|
|
||||||
(defn used-accounts [pnl-data]
|
(defn used-accounts [pnl-datas]
|
||||||
(->> (:data pnl-data)
|
(->>
|
||||||
|
pnl-datas
|
||||||
|
(mapcat :data)
|
||||||
(map #(select-keys % [:numeric-code :name]))
|
(map #(select-keys % [:numeric-code :name]))
|
||||||
(set)
|
(set)
|
||||||
(sort-by :numeric-code)))
|
(sort-by :numeric-code)))
|
||||||
|
|
||||||
(defn subtotal-row [pnl-data title & [cell-args]]
|
(defn subtotal-by-column-row [pnl-datas title & [cell-args]]
|
||||||
(into [{:value title
|
(into [{:value title
|
||||||
:bold true
|
:bold true}]
|
||||||
:filters (when (:from-numeric-code (:filters pnl-data)) ;; don't allow filtering when you don't at least filter numeric codes
|
|
||||||
(:filters pnl-data))}]
|
|
||||||
(map
|
(map
|
||||||
(fn [p]
|
(fn [p]
|
||||||
(let [data (filter-period pnl-data p)]
|
|
||||||
(merge
|
(merge
|
||||||
{:format :dollar
|
{:format :dollar
|
||||||
:value (aggregate-accounts data)
|
:value (aggregate-accounts p)
|
||||||
:filters (when (:from-numeric-code (:filters data)) ;; don't allow filtering when you don't at least filter numeric codes
|
:filters (when (:from-numeric-code (:filters p)) ;; don't allow filtering when you don't at least filter numeric codes
|
||||||
(:filters data))}
|
(:filters p))}
|
||||||
cell-args)))
|
cell-args))
|
||||||
(-> pnl-data :args :periods))))
|
pnl-datas)))
|
||||||
|
|
||||||
(defn calc-percent-of-sales [table pnl-data]
|
(defn calc-percent-of-sales [table pnl-datas]
|
||||||
(let [sales-pnl-data (filter-categories pnl-data [:sales])
|
(let [sales (map
|
||||||
sales (map
|
|
||||||
(fn [p]
|
(fn [p]
|
||||||
(aggregate-accounts (filter-period sales-pnl-data p)))
|
(aggregate-accounts (filter-categories p [:sales])))
|
||||||
(-> pnl-data :args :periods))]
|
pnl-datas)]
|
||||||
(->> table
|
(->> table
|
||||||
(map (fn [[_ & values]]
|
(map (fn [[_ & values]]
|
||||||
(map
|
(map
|
||||||
@@ -231,7 +240,7 @@
|
|||||||
(:value a))})))))))
|
(:value a))})))))))
|
||||||
|
|
||||||
(defn combine-tables
|
(defn combine-tables
|
||||||
([pnl-data table percent-of-sales deltas]
|
([[pnl-data] table percent-of-sales deltas]
|
||||||
(map (fn [[title & row] percent-of-sales deltas ]
|
(map (fn [[title & row] percent-of-sales deltas ]
|
||||||
(let [deltas (cons {:value 0.0
|
(let [deltas (cons {:value 0.0
|
||||||
:format :dollar
|
:format :dollar
|
||||||
@@ -249,21 +258,39 @@
|
|||||||
percent-of-sales
|
percent-of-sales
|
||||||
deltas)))
|
deltas)))
|
||||||
|
|
||||||
(defn headers [pnl-data header-title]
|
(defn headers [[pnl-data :as pnl-datas] header-title]
|
||||||
(let [big-header (into [{:value header-title
|
(let [
|
||||||
|
big-header (into [{:value header-title
|
||||||
:bold true}]
|
:bold true}]
|
||||||
(map (fn [p]
|
(map (fn [p]
|
||||||
{:value
|
{:value
|
||||||
(str (date->str (:start p))
|
(str (date->str (:start p))
|
||||||
" - "
|
" - "
|
||||||
(date->str (:end p)))
|
(date->str (:end p)))
|
||||||
:colspan (if (-> pnl-data :args :include-deltas)
|
:colspan (cond
|
||||||
|
(-> pnl-data :args :include-deltas)
|
||||||
3
|
3
|
||||||
|
|
||||||
|
(-> pnl-data :args :column-per-location)
|
||||||
|
(* 2 (/ (count pnl-datas)
|
||||||
|
(count (-> pnl-data :args :periods))))
|
||||||
|
|
||||||
|
:else
|
||||||
2)
|
2)
|
||||||
:align :center
|
:align :center
|
||||||
:bold true})
|
:bold true})
|
||||||
(:periods (:args pnl-data))))
|
(:periods (:args pnl-data))))
|
||||||
sub-header (into [{:value ""}]
|
sub-header (into [{:value ""}]
|
||||||
|
(if (-> pnl-data :args :column-per-location)
|
||||||
|
(mapcat
|
||||||
|
(fn [p]
|
||||||
|
(cond-> [{:value (-> p :filters :location)
|
||||||
|
:align :right}
|
||||||
|
{:value "%"
|
||||||
|
:align :right}]
|
||||||
|
(-> pnl-data :args :include-deltas) (conj {:value "+/-"
|
||||||
|
:align :right})))
|
||||||
|
pnl-datas)
|
||||||
(mapcat
|
(mapcat
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(cond-> [{:value "Amt"
|
(cond-> [{:value "Amt"
|
||||||
@@ -272,107 +299,115 @@
|
|||||||
:align :right}]
|
:align :right}]
|
||||||
(-> pnl-data :args :include-deltas) (conj {:value "+/-"
|
(-> pnl-data :args :include-deltas) (conj {:value "+/-"
|
||||||
:align :right})))
|
:align :right})))
|
||||||
(:periods (:args pnl-data))))]
|
pnl-datas)))]
|
||||||
[big-header
|
[big-header
|
||||||
sub-header]))
|
sub-header]))
|
||||||
|
|
||||||
(defn location-summary-table [pnl-data title]
|
(defn location-summary-table [pnl-datas title]
|
||||||
(let [table [(subtotal-row (filter-categories pnl-data [:sales]) "Sales")
|
(let [table [(subtotal-by-column-row (map #(filter-categories % [:sales])
|
||||||
(subtotal-row (filter-categories pnl-data [:cogs ]) "Cogs")
|
pnl-datas)
|
||||||
|
"Sales")
|
||||||
|
(subtotal-by-column-row (map #(filter-categories % [:cogs ]) pnl-datas)
|
||||||
|
"Cogs")
|
||||||
|
|
||||||
(subtotal-row (filter-categories pnl-data [:payroll ]) "Payroll")
|
(subtotal-by-column-row (map #(filter-categories % [:payroll ]) pnl-datas)
|
||||||
|
"Payroll")
|
||||||
|
|
||||||
(subtotal-row (-> pnl-data
|
(subtotal-by-column-row (map #(-> %
|
||||||
(filter-categories [:sales :payroll :cogs])
|
(filter-categories [:sales :payroll :cogs])
|
||||||
(negate #{:payroll :cogs}))
|
(negate #{:payroll :cogs}))
|
||||||
|
pnl-datas)
|
||||||
"Gross Profits")
|
"Gross Profits")
|
||||||
|
|
||||||
(subtotal-row (filter-categories pnl-data [:controllable :fixed-overhead :ownership-controllable])
|
(subtotal-by-column-row (map #(filter-categories % [:controllable :fixed-overhead :ownership-controllable])
|
||||||
|
pnl-datas)
|
||||||
"Overhead")
|
"Overhead")
|
||||||
|
|
||||||
(subtotal-row (-> pnl-data
|
(subtotal-by-column-row (map #(-> %
|
||||||
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
||||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||||
|
pnl-datas)
|
||||||
"Net Income")]
|
"Net Income")]
|
||||||
percent-of-sales (calc-percent-of-sales table pnl-data)
|
percent-of-sales (calc-percent-of-sales table pnl-datas)
|
||||||
deltas (calc-deltas table)]
|
deltas (calc-deltas table)]
|
||||||
{:header (headers pnl-data title)
|
{:header (headers pnl-datas title)
|
||||||
:rows (combine-tables pnl-data table percent-of-sales deltas)}))
|
:rows (combine-tables pnl-datas table percent-of-sales deltas)}))
|
||||||
|
|
||||||
|
|
||||||
(defn detail-rows [pnl-data grouping title]
|
(defn detail-rows [pnl-datas grouping title]
|
||||||
(let [pnl-data (filter-categories pnl-data [grouping])
|
(let [pnl-datas (map #(filter-categories % [grouping])
|
||||||
|
pnl-datas)
|
||||||
individual-accounts
|
individual-accounts
|
||||||
(for [[grouping-name from to] (groupings grouping)
|
(for [[grouping-name from to] (groupings grouping)
|
||||||
:let [pnl-data (filter-numeric-code pnl-data from to)
|
:let [pnl-datas (map #(filter-numeric-code % from to)
|
||||||
account-codes (used-accounts pnl-data)]
|
pnl-datas)
|
||||||
|
account-codes (used-accounts pnl-datas)]
|
||||||
:when (seq account-codes)
|
:when (seq account-codes)
|
||||||
row (-> [[{:value (str "---" grouping-name "---")}]]
|
row (-> [[{:value (str "---" grouping-name "---")}]]
|
||||||
(into (for [{:keys [numeric-code name]} account-codes]
|
(into (for [{:keys [numeric-code name]} account-codes]
|
||||||
(into [{:value name}]
|
(into [{:value name}]
|
||||||
(map
|
(map
|
||||||
(fn [p]
|
(fn [p]
|
||||||
(let [pnl-data (-> pnl-data
|
(let [pnl-data (-> p (filter-numeric-code numeric-code numeric-code))]
|
||||||
(filter-numeric-code numeric-code numeric-code)
|
|
||||||
(filter-period p)
|
|
||||||
)]
|
|
||||||
{:format :dollar
|
{:format :dollar
|
||||||
:filters (:filters pnl-data)
|
:filters (:filters pnl-data)
|
||||||
:value (aggregate-accounts pnl-data)}))
|
:value (aggregate-accounts pnl-data)}))
|
||||||
|
|
||||||
(-> pnl-data :args :periods)))))
|
pnl-datas))))
|
||||||
(conj (subtotal-row pnl-data "" {:border [:top]})))]
|
(conj (subtotal-by-column-row pnl-datas "" {:border [:top]})))]
|
||||||
row)]
|
row)]
|
||||||
(-> [[{:value title
|
(-> [[{:value title
|
||||||
:bold true}]]
|
:bold true}]]
|
||||||
(into individual-accounts)
|
(into individual-accounts)
|
||||||
(conj (subtotal-row pnl-data title)))))
|
(conj (subtotal-by-column-row pnl-datas title)))))
|
||||||
|
|
||||||
(defn location-detail-table [pnl-data client-data title]
|
(defn location-detail-table [pnl-datas client-data title prefix]
|
||||||
(let [table (-> []
|
(let [table (-> []
|
||||||
(into (detail-rows pnl-data
|
(into (detail-rows pnl-datas
|
||||||
:sales
|
:sales
|
||||||
(str (:prefix pnl-data) " Sales")))
|
(str prefix " Sales")))
|
||||||
(into (detail-rows pnl-data
|
(into (detail-rows pnl-datas
|
||||||
:cogs
|
:cogs
|
||||||
(str (:prefix pnl-data) " COGS")))
|
(str prefix " COGS")))
|
||||||
(into (detail-rows
|
(into (detail-rows
|
||||||
pnl-data
|
pnl-datas
|
||||||
:payroll
|
:payroll
|
||||||
(str (:prefix pnl-data) " Payroll")))
|
(str prefix " Payroll")))
|
||||||
(conj (subtotal-row (filter-categories pnl-data [:payroll :cogs])
|
(conj (subtotal-by-column-row (map #(filter-categories % [:payroll :cogs])
|
||||||
(str (:prefix pnl-data) " Prime Costs")))
|
pnl-datas)
|
||||||
(conj (subtotal-row (-> pnl-data
|
(str prefix " Prime Costs")))
|
||||||
|
(conj (subtotal-by-column-row (map #(-> %
|
||||||
(filter-categories [:sales :payroll :cogs])
|
(filter-categories [:sales :payroll :cogs])
|
||||||
(negate #{:payroll :cogs}))
|
(negate #{:payroll :cogs}))
|
||||||
(str (:prefix pnl-data) " Gross Profits")))
|
pnl-datas)
|
||||||
|
(str prefix " Gross Profits")))
|
||||||
(into (detail-rows
|
(into (detail-rows
|
||||||
pnl-data
|
pnl-datas
|
||||||
:controllable
|
:controllable
|
||||||
(str (:prefix pnl-data) " Controllable Expenses")))
|
(str prefix " Controllable Expenses")))
|
||||||
(into (detail-rows
|
(into (detail-rows
|
||||||
pnl-data
|
pnl-datas
|
||||||
:fixed-overhead
|
:fixed-overhead
|
||||||
(str (:prefix pnl-data) " Fixed Overhead")))
|
(str prefix " Fixed Overhead")))
|
||||||
(into (detail-rows
|
(into (detail-rows
|
||||||
pnl-data
|
pnl-datas
|
||||||
:ownership-controllable
|
:ownership-controllable
|
||||||
(str (:prefix pnl-data) " Ownership Controllable")))
|
(str prefix " Ownership Controllable")))
|
||||||
(conj (subtotal-row (-> pnl-data
|
(conj (subtotal-by-column-row (map #(filter-categories % [:controllable :fixed-overhead :ownership-controllable]) pnl-datas)
|
||||||
(filter-categories [:controllable :fixed-overhead :ownership-controllable]))
|
(str prefix " Overhead")))
|
||||||
(str (:prefix pnl-data) " Overhead")))
|
(conj (subtotal-by-column-row (map #(-> %
|
||||||
(conj (subtotal-row (-> pnl-data
|
|
||||||
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
||||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||||
(str (:prefix pnl-data) " Net Income")))
|
pnl-datas)
|
||||||
(conj (subtotal-row (-> client-data
|
(str prefix " Net Income")))
|
||||||
|
(conj (subtotal-by-column-row (-> client-data
|
||||||
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
||||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||||
"All Location Net Income")))
|
"All Location Net Income")))
|
||||||
percent-of-sales (calc-percent-of-sales table pnl-data)
|
percent-of-sales (calc-percent-of-sales table pnl-datas)
|
||||||
deltas (into [] (calc-deltas table))]
|
deltas (into [] (calc-deltas table))]
|
||||||
{:header (headers pnl-data title)
|
{:header (headers pnl-datas title)
|
||||||
:rows (combine-tables pnl-data table percent-of-sales deltas)}))
|
:rows (combine-tables pnl-datas table percent-of-sales deltas)}))
|
||||||
|
|
||||||
(defn warning-message [pnl-data]
|
(defn warning-message [pnl-data]
|
||||||
(let [errors (->> pnl-data
|
(let [errors (->> pnl-data
|
||||||
@@ -390,19 +425,47 @@
|
|||||||
|
|
||||||
(defn summarize-pnl [pnl-data]
|
(defn summarize-pnl [pnl-data]
|
||||||
{:warning (warning-message pnl-data)
|
{:warning (warning-message pnl-data)
|
||||||
:summaries (for [[client-id location] (locations (:data pnl-data))]
|
:summaries
|
||||||
(location-summary-table (-> pnl-data
|
(if (-> pnl-data :args :column-per-location)
|
||||||
(filter-client client-id)
|
(for [client-id (set (map first (client-locations pnl-data)))]
|
||||||
(filter-location location))
|
(location-summary-table (for [period (-> pnl-data :args :periods )
|
||||||
(str (-> pnl-data :clients-by-id (get client-id)) " (" location ") Summary")))
|
location (locations pnl-data)]
|
||||||
:details (for [[client-id location] (locations (:data pnl-data))]
|
(-> pnl-data
|
||||||
(location-detail-table (-> pnl-data
|
|
||||||
(filter-client client-id)
|
(filter-client client-id)
|
||||||
(filter-location location)
|
(filter-location location)
|
||||||
(assoc :prefix location))
|
(filter-period period)))
|
||||||
|
(str (-> pnl-data :clients-by-id (get client-id)) " Summary")))
|
||||||
|
(for [[client-id location] (client-locations pnl-data)]
|
||||||
|
(location-summary-table (for [period (-> pnl-data :args :periods )]
|
||||||
|
(-> pnl-data
|
||||||
|
(filter-client client-id)
|
||||||
|
(filter-location location)
|
||||||
|
(filter-period period)))
|
||||||
|
(str (-> pnl-data :clients-by-id (get client-id)) " (" location ") Summary"))))
|
||||||
|
:details
|
||||||
|
(doall (if (-> pnl-data :args :column-per-location)
|
||||||
|
(for [client-id (set (map first (client-locations pnl-data)))]
|
||||||
|
(location-detail-table (for [period (-> pnl-data :args :periods )
|
||||||
|
location (locations pnl-data)]
|
||||||
|
(-> pnl-data
|
||||||
|
(filter-client client-id)
|
||||||
|
(filter-location location)
|
||||||
|
(filter-period period)))
|
||||||
(-> pnl-data
|
(-> pnl-data
|
||||||
(filter-client client-id))
|
(filter-client client-id))
|
||||||
(str (-> pnl-data :clients-by-id (get client-id)) " (" location ") Detail")))})
|
(str (-> pnl-data :clients-by-id (get client-id)) " Detail")
|
||||||
|
""))
|
||||||
|
(for [[client-id location] (client-locations pnl-data)]
|
||||||
|
(location-detail-table (for [period (-> pnl-data :args :periods )]
|
||||||
|
(-> pnl-data
|
||||||
|
(filter-client client-id)
|
||||||
|
(filter-location location)
|
||||||
|
(filter-period period)))
|
||||||
|
(-> pnl-data
|
||||||
|
(filter-client client-id))
|
||||||
|
(str (-> pnl-data :clients-by-id (get client-id)) " (" location ") Detail")
|
||||||
|
location))))
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
(defn balance-sheet-headers [pnl-data]
|
(defn balance-sheet-headers [pnl-data]
|
||||||
@@ -420,19 +483,26 @@
|
|||||||
(:value b))})]))))
|
(:value b))})]))))
|
||||||
|
|
||||||
(defn summarize-balance-sheet [pnl-data]
|
(defn summarize-balance-sheet [pnl-data]
|
||||||
(let [table (-> []
|
(let [
|
||||||
(into (detail-rows pnl-data
|
pnl-datas (map (fn [p]
|
||||||
|
(filter-period pnl-data p))
|
||||||
|
(:periods (:args pnl-data)))
|
||||||
|
table (-> []
|
||||||
|
(into (detail-rows pnl-datas
|
||||||
:assets
|
:assets
|
||||||
"Assets"))
|
"Assets"))
|
||||||
(into (detail-rows pnl-data
|
(into (detail-rows pnl-datas
|
||||||
:liabilities
|
:liabilities
|
||||||
"Liabilities"))
|
"Liabilities"))
|
||||||
(into (detail-rows pnl-data
|
(into (detail-rows pnl-datas
|
||||||
:equities
|
:equities
|
||||||
"Owner's Equity"))
|
"Owner's Equity"))
|
||||||
(conj (subtotal-row (-> pnl-data
|
(conj (subtotal-by-column-row
|
||||||
|
(map #(-> %
|
||||||
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
||||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||||
|
pnl-datas)
|
||||||
|
|
||||||
"Retained Earnings")))
|
"Retained Earnings")))
|
||||||
table (if (:include-comparison (:args pnl-data))
|
table (if (:include-comparison (:args pnl-data))
|
||||||
(append-deltas table)
|
(append-deltas table)
|
||||||
|
|||||||
@@ -73,7 +73,9 @@
|
|||||||
:owns-state {:single ::page}
|
:owns-state {:single ::page}
|
||||||
:query-obj {:venia/queries [[:profit-and-loss
|
:query-obj {:venia/queries [[:profit-and-loss
|
||||||
{:client-ids (map :id (:clients (:data db)))
|
{:client-ids (map :id (:clients (:data db)))
|
||||||
:periods (mapv encode-period (:periods (:data db)))}
|
:periods (mapv encode-period (:periods (:data db)))
|
||||||
|
:include-deltas (:include-deltas (:data db))
|
||||||
|
:column-per-location (:column-per-location (:data db))}
|
||||||
[[:periods [[:accounts [:name :amount :client_id :account-type :id :count :numeric-code :location]]]]]]]}
|
[[:periods [[:accounts [:name :amount :client_id :account-type :id :count :numeric-code :location]]]]]]]}
|
||||||
:on-success [::received]}
|
:on-success [::received]}
|
||||||
:set-uri-params {:periods (mapv
|
:set-uri-params {:periods (mapv
|
||||||
@@ -124,6 +126,7 @@ NOTE: Please review the transactions we may have question for you here: https://
|
|||||||
:query-obj {:venia/queries [[:profit-and-loss-pdf
|
:query-obj {:venia/queries [[:profit-and-loss-pdf
|
||||||
{:client-ids (map :id (:clients (:data db)))
|
{:client-ids (map :id (:clients (:data db)))
|
||||||
:include-deltas (:include-deltas (:data db))
|
:include-deltas (:include-deltas (:data db))
|
||||||
|
:column-per-location (:column-per-location (:data db))
|
||||||
:periods (mapv encode-period (:periods (:data db)))}
|
:periods (mapv encode-period (:periods (:data db)))}
|
||||||
[:url :name]]]}
|
[:url :name]]]}
|
||||||
:on-success [::received-pdf]}
|
:on-success [::received-pdf]}
|
||||||
@@ -144,7 +147,7 @@ NOTE: Please review the transactions we may have question for you here: https://
|
|||||||
(= 2 (count field)))
|
(= 2 (count field)))
|
||||||
[field value] ;;already serialized
|
[field value] ;;already serialized
|
||||||
|
|
||||||
(= :periods (first field)) [field (str->date value standard) ]
|
(= :periods (first field)) [field value ]
|
||||||
|
|
||||||
:else nil)
|
:else nil)
|
||||||
)))
|
)))
|
||||||
@@ -233,13 +236,25 @@ NOTE: Please review the transactions we may have question for you here: https://
|
|||||||
])
|
])
|
||||||
@box)))
|
@box)))
|
||||||
|
|
||||||
|
(defn period-preset-button [{:keys [title periods]}]
|
||||||
|
(let [{{:keys [selected-preset]} :data} @(re-frame/subscribe [::forms/form ::form])]
|
||||||
|
[:div.control
|
||||||
|
[:a.button
|
||||||
|
{:class (when (= selected-preset title) "is-active")
|
||||||
|
:on-click (dispatch-event
|
||||||
|
[::change
|
||||||
|
[:periods]
|
||||||
|
periods
|
||||||
|
[:selected-preset] title])}
|
||||||
|
title]]))
|
||||||
|
|
||||||
(defn report-controls [_]
|
(defn report-controls [_]
|
||||||
(let [!box (reagent/atom nil)
|
(let [!box (reagent/atom nil)
|
||||||
active (reagent/atom nil)]
|
active (reagent/atom nil)]
|
||||||
(fn [pnl-form]
|
(fn [pnl-form]
|
||||||
(let [{:keys [raw-field]} pnl-form
|
(let [{:keys [raw-field]} pnl-form
|
||||||
{:keys [data]} @(re-frame/subscribe [::forms/form ::form])
|
{:keys [data]} @(re-frame/subscribe [::forms/form ::form])
|
||||||
{:keys [periods selected-period include-deltas]} data]
|
{:keys [periods selected-preset include-deltas column-per-location]} data]
|
||||||
[:div.report-controls
|
[:div.report-controls
|
||||||
[:div.level.mb-2
|
[:div.level.mb-2
|
||||||
[:div.level-left
|
[:div.level-left
|
||||||
@@ -261,8 +276,8 @@ NOTE: Please review the transactions we may have question for you here: https://
|
|||||||
[:div.level-item
|
[:div.level-item
|
||||||
[buttons/dropdown {:on-click (fn [] (reset! active :range))}
|
[buttons/dropdown {:on-click (fn [] (reset! active :range))}
|
||||||
[:span (str "Range"
|
[:span (str "Range"
|
||||||
(when selected-period
|
(when selected-preset
|
||||||
(str " (" selected-period ")")))]]
|
(str " (" selected-preset ")")))]]
|
||||||
[report-control-detail {:active active :box !box :which :range}
|
[report-control-detail {:active active :box !box :which :range}
|
||||||
[:div
|
[:div
|
||||||
[:h4.subtitle "Range"]
|
[:h4.subtitle "Range"]
|
||||||
@@ -273,14 +288,10 @@ NOTE: Please review the transactions we may have question for you here: https://
|
|||||||
(raw-field
|
(raw-field
|
||||||
[date-picker-friendly {:placeholder "End date"
|
[date-picker-friendly {:placeholder "End date"
|
||||||
:type "date"
|
:type "date"
|
||||||
|
:cljs-date? true
|
||||||
:field [:thirteen-periods-end]}])]
|
:field [:thirteen-periods-end]}])]
|
||||||
[:div.control
|
[period-preset-button {:title "13 periods"
|
||||||
[:a.button
|
:periods (let [today (or (some-> (:thirteen-periods-end data))
|
||||||
{:class (when (= selected-period "13 periods") "is-active")
|
|
||||||
:on-click (dispatch-event
|
|
||||||
[::change
|
|
||||||
[:periods]
|
|
||||||
(let [today (or (some-> (:thirteen-periods-end data) (str->date standard))
|
|
||||||
(local-today))]
|
(local-today))]
|
||||||
(into
|
(into
|
||||||
[{:start (t/plus (t/minus today (t/weeks (* 13 4)))
|
[{:start (t/plus (t/minus today (t/weeks (* 13 4)))
|
||||||
@@ -290,24 +301,18 @@ NOTE: Please review the transactions we may have question for you here: https://
|
|||||||
(for [i (range 13)]
|
(for [i (range 13)]
|
||||||
{:start (t/plus (t/minus today (t/weeks (* (inc i) 4)))
|
{:start (t/plus (t/minus today (t/weeks (* (inc i) 4)))
|
||||||
(t/days 1))
|
(t/days 1))
|
||||||
:end (t/minus today (t/weeks (* i 4)))})))
|
:end (t/minus today (t/weeks (* i 4)))})))}]]]
|
||||||
[:selected-period] "13 periods"])}
|
|
||||||
"13 periods"]]]]
|
|
||||||
|
|
||||||
[:div.control
|
[:div.control
|
||||||
[:div.field.has-addons
|
[:div.field.has-addons
|
||||||
[:div.control
|
[:div.control
|
||||||
(raw-field
|
(raw-field
|
||||||
[date-picker-friendly {:placeholder "End date"
|
[date-picker-friendly {:placeholder "End date"
|
||||||
|
:cljs-date? true
|
||||||
:type "date"
|
:type "date"
|
||||||
:field [:twelve-periods-end]}])]
|
:field [:twelve-periods-end]}])]
|
||||||
[:div.control
|
[period-preset-button {:title "12 months"
|
||||||
[:a.button
|
:periods (let [end-date (or (some-> (:twelve-periods-end data))
|
||||||
{:class (when (= selected-period "12 months") "is-active")
|
|
||||||
:on-click (dispatch-event
|
|
||||||
[::change
|
|
||||||
[:periods]
|
|
||||||
(let [end-date (or (some-> (:twelve-periods-end data) (str->date standard))
|
|
||||||
(local-today))
|
(local-today))
|
||||||
this-month (t/local-date (t/year end-date)
|
this-month (t/local-date (t/year end-date)
|
||||||
(t/month end-date)
|
(t/month end-date)
|
||||||
@@ -320,45 +325,24 @@ NOTE: Please review the transactions we may have question for you here: https://
|
|||||||
(for [i (range 12)]
|
(for [i (range 12)]
|
||||||
{:start (t/minus this-month (t/months (- 11 i)))
|
{:start (t/minus this-month (t/months (- 11 i)))
|
||||||
:end (t/minus (t/minus this-month (t/months (- 10 i)))
|
:end (t/minus (t/minus this-month (t/months (- 10 i)))
|
||||||
(t/days 1))})))
|
(t/days 1))})))}]]]
|
||||||
[:selected-period] "12 months"])}
|
|
||||||
"12 months"]]]]
|
|
||||||
|
|
||||||
[:div.control
|
[period-preset-button {:periods (let [last-sunday (loop [current (local-today)]
|
||||||
[:a.button
|
|
||||||
{:class (when (= selected-period "Last week") "is-active")
|
|
||||||
:on-click (dispatch-event
|
|
||||||
[::change
|
|
||||||
[:periods]
|
|
||||||
(let [last-sunday (loop [current (local-today)]
|
|
||||||
(if (= 7 (t/day-of-week current))
|
(if (= 7 (t/day-of-week current))
|
||||||
current
|
current
|
||||||
(recur (t/minus current (t/period :days 1)))))]
|
(recur (t/minus current (t/period :days 1)))))]
|
||||||
(and-last-year {:start (t/minus last-sunday (t/period :days 6))
|
(and-last-year {:start (t/minus last-sunday (t/period :days 6))
|
||||||
:end last-sunday}))
|
:end last-sunday}))
|
||||||
[:selected-period] "Last week"])}
|
:title "Last week"}]
|
||||||
"Last week"]]
|
|
||||||
[:div.control
|
[period-preset-button {:periods (and-last-year {:start (loop [current (local-today)]
|
||||||
[:a.button
|
|
||||||
{:class (when (= selected-period "Week to date") "is-active")
|
|
||||||
:on-click (dispatch-event
|
|
||||||
[::change
|
|
||||||
[:periods]
|
|
||||||
(and-last-year {:start (loop [current (local-today)]
|
|
||||||
(if (= 1 (t/day-of-week current))
|
(if (= 1 (t/day-of-week current))
|
||||||
current
|
current
|
||||||
(recur (t/minus current (t/period :days 1)))))
|
(recur (t/minus current (t/period :days 1)))))
|
||||||
:end (local-today)})
|
:end (local-today)})
|
||||||
[:selected-period] "Week to date"])}
|
:title "Week to date"}]
|
||||||
"Week to date"]]
|
|
||||||
[:div.control
|
|
||||||
[:a.button
|
|
||||||
{:class (when (= selected-period "Last Month") "is-active")
|
|
||||||
:on-click (dispatch-event
|
|
||||||
|
|
||||||
[::change
|
[period-preset-button {:periods (and-last-year {:start (t/minus (t/local-date (t/year (local-today))
|
||||||
[:periods]
|
|
||||||
(and-last-year {:start (t/minus (t/local-date (t/year (local-today))
|
|
||||||
(t/month (local-today))
|
(t/month (local-today))
|
||||||
1)
|
1)
|
||||||
(t/period :months 1))
|
(t/period :months 1))
|
||||||
@@ -366,53 +350,27 @@ NOTE: Please review the transactions we may have question for you here: https://
|
|||||||
(t/month (local-today))
|
(t/month (local-today))
|
||||||
1)
|
1)
|
||||||
(t/period :days 1))})
|
(t/period :days 1))})
|
||||||
[:selected-period] "Last Month"])}
|
:title "Last month"}]
|
||||||
"Last Month"]]
|
|
||||||
[:div.control
|
[period-preset-button {:periods (and-last-year {:start (t/local-date (t/year (local-today))
|
||||||
[:a.button
|
|
||||||
{:class (when (= selected-period "Month to date") "is-active")
|
|
||||||
:on-click (dispatch-event
|
|
||||||
[::change
|
|
||||||
[:periods]
|
|
||||||
(and-last-year {:start (t/local-date (t/year (local-today))
|
|
||||||
(t/month (local-today))
|
(t/month (local-today))
|
||||||
1)
|
1)
|
||||||
:end (local-today)})
|
:end (local-today)})
|
||||||
[:selected-period] "Month to date"]
|
:title "Month to date"}]
|
||||||
)}
|
|
||||||
"Month to date"]]
|
[period-preset-button {:periods (and-last-year {:start (t/local-date (t/year (local-today)) 1 1)
|
||||||
[:div.control
|
|
||||||
[:a.button
|
|
||||||
{:class (when (= selected-period "Year to date") "is-active")
|
|
||||||
:on-click (dispatch-event
|
|
||||||
[::change
|
|
||||||
[:periods]
|
|
||||||
(and-last-year {:start (t/local-date (t/year (local-today)) 1 1)
|
|
||||||
:end
|
:end
|
||||||
(local-today)})
|
(local-today)})
|
||||||
[:selected-period] "Year to date"])}
|
:title "Year to date"}]
|
||||||
"Year to date"]]
|
|
||||||
[:div.control
|
[period-preset-button {:periods [{:start (t/local-date (dec (t/year (local-today))) 1 1)
|
||||||
[:a.button
|
|
||||||
{:class (when (= selected-period "Full year") "is-active")
|
|
||||||
:on-click (dispatch-event
|
|
||||||
[::change
|
|
||||||
[:periods]
|
|
||||||
[{:start (t/local-date (dec (t/year (local-today))) 1 1)
|
|
||||||
:end (t/local-date (dec (t/year (local-today))) 12 31)}]
|
:end (t/local-date (dec (t/year (local-today))) 12 31)}]
|
||||||
[:selected-period] "Last Calendar year"])}
|
:title "Last calendar year"}]
|
||||||
"Last calendar year"]]
|
|
||||||
[:div.control
|
[period-preset-button {:periods (and-last-year {:start (t/plus (t/minus (local-today) (t/period :years 1))
|
||||||
[:a.button
|
|
||||||
{:class (when (= selected-period "Full year") "is-active")
|
|
||||||
:on-click (dispatch-event
|
|
||||||
[::change
|
|
||||||
[:periods]
|
|
||||||
(and-last-year {:start (t/plus (t/minus (local-today) (t/period :years 1))
|
|
||||||
(t/period :days 1))
|
(t/period :days 1))
|
||||||
:end (local-today)})
|
:end (local-today)})
|
||||||
[:selected-period] "Full year"])}
|
:title "Full year"}]]
|
||||||
"Full year"]]]
|
|
||||||
[:div
|
[:div
|
||||||
[:div.field
|
[:div.field
|
||||||
[:label.checkbox
|
[:label.checkbox
|
||||||
@@ -429,12 +387,14 @@ NOTE: Please review the transactions we may have question for you here: https://
|
|||||||
[:p.help "From"]
|
[:p.help "From"]
|
||||||
(raw-field
|
(raw-field
|
||||||
[date-picker-friendly {:type "date"
|
[date-picker-friendly {:type "date"
|
||||||
|
:cljs-date? true
|
||||||
:field [:periods i :start]}])]
|
:field [:periods i :start]}])]
|
||||||
|
|
||||||
[:div.control
|
[:div.control
|
||||||
[:p.help "To"]
|
[:p.help "To"]
|
||||||
(raw-field
|
(raw-field
|
||||||
[date-picker-friendly {:type "date"
|
[date-picker-friendly {:type "date"
|
||||||
|
:cljs-date? true
|
||||||
:field [:periods i :end]}])]])))]]]
|
:field [:periods i :end]}])]])))]]]
|
||||||
|
|
||||||
[:div.level-item
|
[:div.level-item
|
||||||
@@ -445,6 +405,15 @@ NOTE: Please review the transactions we may have question for you here: https://
|
|||||||
(re-frame/dispatch [::change
|
(re-frame/dispatch [::change
|
||||||
[:include-deltas] (.-checked (.-target e))]))
|
[:include-deltas] (.-checked (.-target e))]))
|
||||||
:label "Include deltas"
|
:label "Include deltas"
|
||||||
|
:type "checkbox"}]]]
|
||||||
|
[:div.level-item
|
||||||
|
[:div
|
||||||
|
[switch-field {:id "column-per-location"
|
||||||
|
:checked (boolean column-per-location)
|
||||||
|
:on-change (fn [e]
|
||||||
|
(re-frame/dispatch [::change
|
||||||
|
[:column-per-location] (.-checked (.-target e))]))
|
||||||
|
:label "Column per location"
|
||||||
:type "checkbox"}]]]]
|
:type "checkbox"}]]]]
|
||||||
[:div.level-right
|
[:div.level-right
|
||||||
[:div.buttons
|
[:div.buttons
|
||||||
|
|||||||
Reference in New Issue
Block a user