Adds the ability to do a column per location
This commit is contained in:
@@ -90,9 +90,11 @@
|
||||
(vals ranges))
|
||||
false))
|
||||
|
||||
(defn locations [data]
|
||||
(->> data
|
||||
(defn client-locations [pnl-data]
|
||||
(->> pnl-data
|
||||
:data
|
||||
(filter (comp in-range? :numeric-code))
|
||||
(filter #(not= "A" (:location %)))
|
||||
(group-by (juxt :client-id :location))
|
||||
(filter (fn [[_ as]]
|
||||
(not (dollars-0? (reduce + 0 (map :amount as))))))
|
||||
@@ -112,6 +114,15 @@
|
||||
"ZZZZZZ"
|
||||
(: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]
|
||||
(reduce (fnil + 0.0) 0.0 (map :amount (:data pnl-data))))
|
||||
|
||||
@@ -179,34 +190,32 @@
|
||||
accounts))))
|
||||
|
||||
|
||||
(defn used-accounts [pnl-data]
|
||||
(->> (:data pnl-data)
|
||||
(map #(select-keys % [:numeric-code :name]))
|
||||
(set)
|
||||
(sort-by :numeric-code)))
|
||||
(defn used-accounts [pnl-datas]
|
||||
(->>
|
||||
pnl-datas
|
||||
(mapcat :data)
|
||||
(map #(select-keys % [:numeric-code :name]))
|
||||
(set)
|
||||
(sort-by :numeric-code)))
|
||||
|
||||
(defn subtotal-row [pnl-data title & [cell-args]]
|
||||
(into [{:value title
|
||||
: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))}]
|
||||
(defn subtotal-by-column-row [pnl-datas title & [cell-args]]
|
||||
(into [{:value title
|
||||
:bold true}]
|
||||
(map
|
||||
(fn [p]
|
||||
(let [data (filter-period pnl-data p)]
|
||||
(merge
|
||||
{:format :dollar
|
||||
:value (aggregate-accounts 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))))
|
||||
(merge
|
||||
{:format :dollar
|
||||
:value (aggregate-accounts p)
|
||||
:filters (when (:from-numeric-code (:filters p)) ;; don't allow filtering when you don't at least filter numeric codes
|
||||
(:filters p))}
|
||||
cell-args))
|
||||
pnl-datas)))
|
||||
|
||||
(defn calc-percent-of-sales [table pnl-data]
|
||||
(let [sales-pnl-data (filter-categories pnl-data [:sales])
|
||||
sales (map
|
||||
(defn calc-percent-of-sales [table pnl-datas]
|
||||
(let [sales (map
|
||||
(fn [p]
|
||||
(aggregate-accounts (filter-period sales-pnl-data p)))
|
||||
(-> pnl-data :args :periods))]
|
||||
(aggregate-accounts (filter-categories p [:sales])))
|
||||
pnl-datas)]
|
||||
(->> table
|
||||
(map (fn [[_ & values]]
|
||||
(map
|
||||
@@ -231,7 +240,7 @@
|
||||
(:value a))})))))))
|
||||
|
||||
(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 ]
|
||||
(let [deltas (cons {:value 0.0
|
||||
:format :dollar
|
||||
@@ -249,130 +258,156 @@
|
||||
percent-of-sales
|
||||
deltas)))
|
||||
|
||||
(defn headers [pnl-data header-title]
|
||||
(let [big-header (into [{:value header-title
|
||||
(defn headers [[pnl-data :as pnl-datas] header-title]
|
||||
(let [
|
||||
big-header (into [{:value header-title
|
||||
:bold true}]
|
||||
(map (fn [p]
|
||||
{:value
|
||||
(str (date->str (:start p))
|
||||
" - "
|
||||
(date->str (:end p)))
|
||||
:colspan (if (-> pnl-data :args :include-deltas)
|
||||
:colspan (cond
|
||||
(-> pnl-data :args :include-deltas)
|
||||
3
|
||||
|
||||
(-> pnl-data :args :column-per-location)
|
||||
(* 2 (/ (count pnl-datas)
|
||||
(count (-> pnl-data :args :periods))))
|
||||
|
||||
:else
|
||||
2)
|
||||
:align :center
|
||||
:bold true})
|
||||
(:periods (:args pnl-data))))
|
||||
sub-header (into [{:value ""}]
|
||||
(mapcat
|
||||
(fn [_]
|
||||
(cond-> [{:value "Amt"
|
||||
:align :right}
|
||||
{:value "%"
|
||||
:align :right}]
|
||||
(-> pnl-data :args :include-deltas) (conj {:value "+/-"
|
||||
:align :right})))
|
||||
(:periods (:args pnl-data))))]
|
||||
(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
|
||||
(fn [_]
|
||||
(cond-> [{:value "Amt"
|
||||
:align :right}
|
||||
{:value "%"
|
||||
:align :right}]
|
||||
(-> pnl-data :args :include-deltas) (conj {:value "+/-"
|
||||
:align :right})))
|
||||
pnl-datas)))]
|
||||
[big-header
|
||||
sub-header]))
|
||||
|
||||
(defn location-summary-table [pnl-data title]
|
||||
(let [table [(subtotal-row (filter-categories pnl-data [:sales]) "Sales")
|
||||
(subtotal-row (filter-categories pnl-data [:cogs ]) "Cogs")
|
||||
(defn location-summary-table [pnl-datas title]
|
||||
(let [table [(subtotal-by-column-row (map #(filter-categories % [:sales])
|
||||
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
|
||||
(filter-categories [:sales :payroll :cogs])
|
||||
(negate #{:payroll :cogs}))
|
||||
"Gross Profits")
|
||||
(subtotal-by-column-row (map #(-> %
|
||||
(filter-categories [:sales :payroll :cogs])
|
||||
(negate #{:payroll :cogs}))
|
||||
pnl-datas)
|
||||
"Gross Profits")
|
||||
|
||||
(subtotal-row (filter-categories pnl-data [:controllable :fixed-overhead :ownership-controllable])
|
||||
"Overhead")
|
||||
(subtotal-by-column-row (map #(filter-categories % [:controllable :fixed-overhead :ownership-controllable])
|
||||
pnl-datas)
|
||||
"Overhead")
|
||||
|
||||
(subtotal-row (-> pnl-data
|
||||
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||
"Net Income")]
|
||||
percent-of-sales (calc-percent-of-sales table pnl-data)
|
||||
(subtotal-by-column-row (map #(-> %
|
||||
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||
pnl-datas)
|
||||
"Net Income")]
|
||||
percent-of-sales (calc-percent-of-sales table pnl-datas)
|
||||
deltas (calc-deltas table)]
|
||||
{:header (headers pnl-data title)
|
||||
:rows (combine-tables pnl-data table percent-of-sales deltas)}))
|
||||
{:header (headers pnl-datas title)
|
||||
:rows (combine-tables pnl-datas table percent-of-sales deltas)}))
|
||||
|
||||
|
||||
(defn detail-rows [pnl-data grouping title]
|
||||
(let [pnl-data (filter-categories pnl-data [grouping])
|
||||
(defn detail-rows [pnl-datas grouping title]
|
||||
(let [pnl-datas (map #(filter-categories % [grouping])
|
||||
pnl-datas)
|
||||
individual-accounts
|
||||
(for [[grouping-name from to] (groupings grouping)
|
||||
:let [pnl-data (filter-numeric-code pnl-data from to)
|
||||
account-codes (used-accounts pnl-data)]
|
||||
:let [pnl-datas (map #(filter-numeric-code % from to)
|
||||
pnl-datas)
|
||||
account-codes (used-accounts pnl-datas)]
|
||||
:when (seq account-codes)
|
||||
row (-> [[{:value (str "---" grouping-name "---")}]]
|
||||
(into (for [{:keys [numeric-code name]} account-codes]
|
||||
(into [{:value name}]
|
||||
(map
|
||||
(fn [p]
|
||||
(let [pnl-data (-> pnl-data
|
||||
(filter-numeric-code numeric-code numeric-code)
|
||||
(filter-period p)
|
||||
)]
|
||||
(let [pnl-data (-> p (filter-numeric-code numeric-code numeric-code))]
|
||||
{:format :dollar
|
||||
:filters (:filters pnl-data)
|
||||
:value (aggregate-accounts pnl-data)}))
|
||||
|
||||
(-> pnl-data :args :periods)))))
|
||||
(conj (subtotal-row pnl-data "" {:border [:top]})))]
|
||||
pnl-datas))))
|
||||
(conj (subtotal-by-column-row pnl-datas "" {:border [:top]})))]
|
||||
row)]
|
||||
(-> [[{:value title
|
||||
:bold true}]]
|
||||
(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 (-> []
|
||||
(into (detail-rows pnl-data
|
||||
(into (detail-rows pnl-datas
|
||||
:sales
|
||||
(str (:prefix pnl-data) " Sales")))
|
||||
(into (detail-rows pnl-data
|
||||
(str prefix " Sales")))
|
||||
(into (detail-rows pnl-datas
|
||||
:cogs
|
||||
(str (:prefix pnl-data) " COGS")))
|
||||
(str prefix " COGS")))
|
||||
(into (detail-rows
|
||||
pnl-data
|
||||
pnl-datas
|
||||
:payroll
|
||||
(str (:prefix pnl-data) " Payroll")))
|
||||
(conj (subtotal-row (filter-categories pnl-data [:payroll :cogs])
|
||||
(str (:prefix pnl-data) " Prime Costs")))
|
||||
(conj (subtotal-row (-> pnl-data
|
||||
(filter-categories [:sales :payroll :cogs])
|
||||
(negate #{:payroll :cogs}))
|
||||
(str (:prefix pnl-data) " Gross Profits")))
|
||||
(str prefix " Payroll")))
|
||||
(conj (subtotal-by-column-row (map #(filter-categories % [:payroll :cogs])
|
||||
pnl-datas)
|
||||
(str prefix " Prime Costs")))
|
||||
(conj (subtotal-by-column-row (map #(-> %
|
||||
(filter-categories [:sales :payroll :cogs])
|
||||
(negate #{:payroll :cogs}))
|
||||
pnl-datas)
|
||||
(str prefix " Gross Profits")))
|
||||
(into (detail-rows
|
||||
pnl-data
|
||||
pnl-datas
|
||||
:controllable
|
||||
(str (:prefix pnl-data) " Controllable Expenses")))
|
||||
(str prefix " Controllable Expenses")))
|
||||
(into (detail-rows
|
||||
pnl-data
|
||||
pnl-datas
|
||||
:fixed-overhead
|
||||
(str (:prefix pnl-data) " Fixed Overhead")))
|
||||
(str prefix " Fixed Overhead")))
|
||||
(into (detail-rows
|
||||
pnl-data
|
||||
pnl-datas
|
||||
:ownership-controllable
|
||||
(str (:prefix pnl-data) " Ownership Controllable")))
|
||||
(conj (subtotal-row (-> pnl-data
|
||||
(filter-categories [:controllable :fixed-overhead :ownership-controllable]))
|
||||
(str (:prefix pnl-data) " Overhead")))
|
||||
(conj (subtotal-row (-> pnl-data
|
||||
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||
(str (:prefix pnl-data) " Net Income")))
|
||||
(conj (subtotal-row (-> client-data
|
||||
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||
"All Location Net Income")))
|
||||
percent-of-sales (calc-percent-of-sales table pnl-data)
|
||||
(str prefix " Ownership Controllable")))
|
||||
(conj (subtotal-by-column-row (map #(filter-categories % [:controllable :fixed-overhead :ownership-controllable]) pnl-datas)
|
||||
(str prefix " Overhead")))
|
||||
(conj (subtotal-by-column-row (map #(-> %
|
||||
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||
pnl-datas)
|
||||
(str prefix " Net Income")))
|
||||
(conj (subtotal-by-column-row (-> client-data
|
||||
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||
"All Location Net Income")))
|
||||
percent-of-sales (calc-percent-of-sales table pnl-datas)
|
||||
deltas (into [] (calc-deltas table))]
|
||||
{:header (headers pnl-data title)
|
||||
:rows (combine-tables pnl-data table percent-of-sales deltas)}))
|
||||
{:header (headers pnl-datas title)
|
||||
:rows (combine-tables pnl-datas table percent-of-sales deltas)}))
|
||||
|
||||
(defn warning-message [pnl-data]
|
||||
(let [errors (->> pnl-data
|
||||
@@ -389,20 +424,48 @@
|
||||
errors))))))
|
||||
|
||||
(defn summarize-pnl [pnl-data]
|
||||
{:warning (warning-message pnl-data)
|
||||
:summaries (for [[client-id location] (locations (:data pnl-data))]
|
||||
(location-summary-table (-> pnl-data
|
||||
(filter-client client-id)
|
||||
(filter-location location))
|
||||
(str (-> pnl-data :clients-by-id (get client-id)) " (" location ") Summary")))
|
||||
:details (for [[client-id location] (locations (:data pnl-data))]
|
||||
(location-detail-table (-> pnl-data
|
||||
{:warning (warning-message pnl-data)
|
||||
:summaries
|
||||
(if (-> pnl-data :args :column-per-location)
|
||||
(for [client-id (set (map first (client-locations pnl-data)))]
|
||||
(location-summary-table (for [period (-> pnl-data :args :periods )
|
||||
location (locations pnl-data)]
|
||||
(-> pnl-data
|
||||
(filter-client client-id)
|
||||
(filter-location 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)
|
||||
(assoc :prefix location))
|
||||
(filter-period period)))
|
||||
(-> pnl-data
|
||||
(filter-client client-id))
|
||||
(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))
|
||||
(str (-> pnl-data :clients-by-id (get client-id)) " (" location ") Detail")))})
|
||||
(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]
|
||||
@@ -420,20 +483,27 @@
|
||||
(:value b))})]))))
|
||||
|
||||
(defn summarize-balance-sheet [pnl-data]
|
||||
(let [table (-> []
|
||||
(into (detail-rows pnl-data
|
||||
(let [
|
||||
pnl-datas (map (fn [p]
|
||||
(filter-period pnl-data p))
|
||||
(:periods (:args pnl-data)))
|
||||
table (-> []
|
||||
(into (detail-rows pnl-datas
|
||||
:assets
|
||||
"Assets"))
|
||||
(into (detail-rows pnl-data
|
||||
(into (detail-rows pnl-datas
|
||||
:liabilities
|
||||
"Liabilities"))
|
||||
(into (detail-rows pnl-data
|
||||
(into (detail-rows pnl-datas
|
||||
: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")))
|
||||
(conj (subtotal-by-column-row
|
||||
(map #(-> %
|
||||
(filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable])
|
||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||
pnl-datas)
|
||||
|
||||
"Retained Earnings")))
|
||||
table (if (:include-comparison (:args pnl-data))
|
||||
(append-deltas table)
|
||||
table)]
|
||||
|
||||
Reference in New Issue
Block a user