From dcc7b8f304750ad1aae0ab0670d12bc4ec06fba1 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Sun, 20 Mar 2022 08:50:34 -0700 Subject: [PATCH] progress, working. --- src/clj/auto_ap/pdf/ledger.clj | 377 ++++++++++-------- .../views/pages/ledger/profit_and_loss.cljs | 2 - 2 files changed, 212 insertions(+), 167 deletions(-) diff --git a/src/clj/auto_ap/pdf/ledger.clj b/src/clj/auto_ap/pdf/ledger.clj index 51d87079..db3abbe8 100644 --- a/src/clj/auto_ap/pdf/ledger.clj +++ b/src/clj/auto_ap/pdf/ledger.clj @@ -10,7 +10,8 @@ [clojure.string :as str] [clojure.walk :refer [postwalk]] [config.core :refer [env]] - [datomic.api :as d]) + [datomic.api :as d] + [clojure.tools.logging :as log]) (:import (java.io ByteArrayOutputStream) (java.util UUID))) @@ -78,8 +79,6 @@ (defn locations [data] (->> data - :periods - (mapcat :accounts) (filter (comp in-range? :numeric-code)) (group-by (juxt :client-id :location)) (filter (fn [[k as]] @@ -121,101 +120,67 @@ )) data)) -(defn map-periods [for-every between periods include-deltas] - (into [:<>] - (for [[_ i] (map vector periods (range))] - [:<> (for-every i) - (if (and include-deltas (not= 0 i)) - (between i))]))) -(defn period-header [{:keys [include_deltas periods]}] - [ - [[:cell "Period"] - [:<> - (map-periods - (fn [i] - [:cell {:colspan 2} - (str (date->str (get-in periods [i :start])) " - " (date->str (get-in periods [i :end])))]) - (fn [i] - [:cell ""]) - periods - include_deltas)]] - [[:cell ""] - [:<> (map-periods - (fn [i] - [:<> - [:cell - "Amount"] - [:cell - "% Sales"]]) - (fn [i] - [:cell "𝝙"]) - periods - include_deltas)] - ]]) +(defn aggregate-accounts [pnl-data] + (reduce (fnil + 0.0) 0.0 (map :amount (:data pnl-data)))) -(defn all-accounts [data] - (transduce - (comp - (map #(->> (:accounts %) - (group-by (juxt :numeric-code :client-id :location)) - (map (fn [[k v]] - [k - (reduce (fn [a n] - (-> a - (update :count (fn [z] (+ z (:count n)))) - (update :amount (fn [z] (+ z (:amount n)))))) - (first v) - (rest v))])) - - (into {})))) +(defn best-category [a] + (->> ranges + (filter (fn [[category [start end]]] + (<= start (:numeric-code a) end))) + first + first)) - conj - [] - (:periods data))) +(defn filter-client [pnl-data client] + (update pnl-data :data (fn [data] + ((group-by :client-id data) client)))) + +(defn filter-location [pnl-data location] + (update pnl-data :data (fn [data] + ((group-by :location data) location)))) + +(defn filter-categories [pnl-data categories] + (update pnl-data :data (fn [data] + (mapcat identity + ((apply juxt categories) + (group-by best-category data)))))) + +(defn filter-period [pnl-data period] + (update pnl-data :data (fn [data] + ((group-by :period data) period)))) + +(defn filter-numeric-code [pnl-data from to] + (update pnl-data :data (fn [data] + (filter + #(<= from (:numeric-code %) to) + data)))) + +(defn negate [pnl-data types] + (update pnl-data :data + (fn [accounts] + (map + (fn [account] + (if (types (best-category account)) + (update account :amount -) + account)) + accounts)))) -(defn filter-accounts [accounts period [from to] only-client only-location] - (->> (get accounts period) - vals - - (filter (fn [{:keys [location client-id numeric-code]}] - (and (or (nil? only-location) - (= only-location location)) - (or (nil? only-client) - (= only-client client-id)) - (<= from numeric-code to)))) - (sort-by :numeric-code))) - -(defn aggregate-accounts [accounts] - (reduce (fnil + 0.0) 0.0 (map :amount accounts))) - -(defn used-accounts [accounts [from to] client-id location] - (->> accounts - (mapcat vals) - (filter #(<= from (:numeric-code %) to)) - (filter #(= client-id (:client-id %))) - (filter #(= location (:location %))) +(defn used-accounts [pnl-data] + (->> (:data pnl-data) (map #(select-keys % [:numeric-code :name])) (set) (sort-by :numeric-code))) -(defn subtotal-row [args data types negs title client-id location] - (let [all-accounts (all-accounts data) - raw (map-indexed - (fn [i p] - (aggregate-accounts (mapcat (fn [t] - (cond->> (filter-accounts all-accounts i (ranges t) client-id location) - (negs t) (map #(update % :amount -)))) - types)) - ) - - (:periods args)) - sales (map-indexed - (fn [i _] - (aggregate-accounts (filter-accounts all-accounts i (ranges :sales) client-id location))) - - (:periods args)) +(defn subtotal-row [pnl-data sales-pnl-data title] + (let [raw (map + (fn [p] + (aggregate-accounts (filter-period pnl-data p))) + (-> pnl-data :args :periods)) + sales (map + (fn [p] + (aggregate-accounts (filter-period sales-pnl-data p))) + (-> pnl-data :args :periods)) deltas (->> raw (partition-all 2) (map (fn [[a b]] @@ -234,93 +199,175 @@ ) deltas))))) -(defn location-summary-table [args data client-id location] - [(subtotal-row args data [:sales] #{} "Sales" client-id location) - (subtotal-row args data [:cogs ] #{} "Cogs" client-id location) - (subtotal-row args data [:payroll ]#{} "Payroll" client-id location) - (subtotal-row args data [:sales :payroll :cogs] #{:payroll :cogs} "Gross Profits" client-id location) - (subtotal-row args data [:controllable :fixed-overhead :ownership-controllable] #{} "Overhead" client-id location) - (subtotal-row args data [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable] #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable} "Net Income" client-id location)]) +(defn location-summary-table [pnl-data] + + (let [sales-data (filter-categories pnl-data [:sales])] + [(subtotal-row (filter-categories pnl-data [:sales]) + sales-data + "Sales") + (subtotal-row (filter-categories pnl-data [:cogs ]) + sales-data + "Cogs") + + (subtotal-row (filter-categories pnl-data [:payroll ]) + sales-data + "Payroll") + + (subtotal-row (-> pnl-data + (filter-categories [:sales :payroll :cogs]) + (negate #{:payroll :cogs})) + sales-data + "Gross Profits") + + (subtotal-row (filter-categories pnl-data [:controllable :fixed-overhead :ownership-controllable]) + sales-data + "Overhead") + + (subtotal-row (-> pnl-data + (filter-categories [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable]) + (negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable})) + sales-data + "Net Income") + ])) -(defn detail-sub-rows [args data grouping client-id location] - (let [all-accounts (all-accounts data)] - (for [[grouping-name from to] grouping - :let [account-codes (used-accounts all-accounts [from to] client-id location)] - :when (seq account-codes)] - (-> - [[(str "---" grouping-name "---")]] - (into (for [{:keys [numeric-code name]} account-codes] - (let [raw (map-indexed - (fn [i p] - (get-in all-accounts [i [numeric-code client-id location] :amount] 0.0)) +(defn detail-sub-rows [pnl-data sales-data grouping] + (for [[grouping-name from to] grouping + :let [pnl-data (filter-numeric-code pnl-data from to) + account-codes (used-accounts pnl-data)] + :when (seq account-codes)] + (-> + [[(str "---" grouping-name "---")]] + (into (for [{:keys [numeric-code name]} account-codes] + (let [raw (map + (fn [p] + (-> pnl-data + (filter-numeric-code numeric-code numeric-code) + (filter-period p) + (aggregate-accounts))) - (:periods args)) - sales (map-indexed - (fn [i _] - (aggregate-accounts (filter-accounts all-accounts i (ranges :sales) client-id location))) + (-> pnl-data :args :periods)) + sales (map + (fn [p] + (-> sales-data + (filter-period p) + (aggregate-accounts))) - (:periods args)) - deltas (->> raw - (partition-all 2) - (map (fn [[a b]] - (- b - a))))] - (into [name] - (->> raw - (map (fn [s r] - [r (if (dollars-0? s) - 0.0 - (/ r s))]) - sales) - (partition-all 2) - (mapcat (fn [d [[a a-sales] [b b-sales]]] - [a a-sales b b-sales d] - ) - deltas)))) - - #_[:tr - [:td name] - #_(map-periods - (fn [i] - (let [amount (get-in all-accounts [i [numeric-code client-id location] :amount] 0.0)] - [:<> - [:td.has-text-right (if multi-client? - [:span (->$ amount)] - [:a {:on-click (dispatch-event [::investigate-clicked location numeric-code numeric-code i :current]) - :disabled (boolean multi-client?)} - (->$ amount)])] - [:td.has-text-right (->% (percent-of-sales amount all-accounts i client-id location))]])) - (fn [i] - [:td.has-text-right (->$ (- (get-in all-accounts [i [numeric-code client-id location] :amount] 0.0) - (get-in all-accounts [(dec i) [numeric-code client-id location] :amount] 0.0)))]) - periods - include-deltas)]))) - ))) + (-> pnl-data :args :periods)) + deltas (->> raw + (partition-all 2) + (map (fn [[a b]] + (- b + a))))] + (into [name] + (->> raw + (map (fn [s r] + [r (if (dollars-0? s) + 0.0 + (/ r s))]) + sales) + (partition-all 2) + (mapcat (fn [d [[a a-sales] [b b-sales]]] + [a a-sales b b-sales d] + ) + deltas))))))))) -(defn detail-rows [args data type title client-id location] - (-> [[title]] - (into (detail-sub-rows args data (type groupings) client-id location)) - (conj (subtotal-row args data [type] #{} title client-id location)))) +(defn detail-rows [pnl-data grouping sales-data title] + (let [pnl-data (filter-categories pnl-data [grouping])] + (-> [[title]] + (into (detail-sub-rows pnl-data + sales-data + (grouping groupings))) + (conj (subtotal-row pnl-data sales-data title))))) -(defn location-detail-table [args data client-id location] - (-> [] - (into (detail-rows args data :sales (str location " Sales") client-id location)))) +(defn location-detail-table [pnl-data] + (let [sales-data (filter-categories pnl-data [:sales])] + (-> [] + (into (detail-rows pnl-data + :sales + sales-data + (str (:prefix pnl-data) " Sales"))) + (into (detail-rows pnl-data + :cogs + sales-data + (str (:prefix pnl-data) " COGS"))) + (into (detail-rows + pnl-data + :payroll + sales-data + (str (:prefix pnl-data) " Payroll"))) + (conj (subtotal-row (filter-categories pnl-data [:payroll :cogs]) + sales-data + (str (:prefix pnl-data) " Prime Costs"))) + (conj (subtotal-row (-> pnl-data + (filter-categories [:payroll :cogs]) + (negate #{:payroll :cogs})) + sales-data + (str (:prefix pnl-data) " Gross Profits"))) + (into (detail-rows + pnl-data + :fixed-overhead + sales-data + (str (:prefix pnl-data) " Fixed Overhead"))) + (into (detail-rows + pnl-data + :ownership-controllable + sales-data + (str (:prefix pnl-data) " Ownership Controllable"))) + (conj (subtotal-row (-> pnl-data + (filter-categories [:controllable :fixed-overhead :ownership-controllable])) + sales-data + (str (:prefix pnl-data) " Overhead"))) + (conj (subtotal-row (-> pnl-data + (filter-categories [:controllable :fixed-overhead :ownership-controllable])) + sales-data + (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})) + sales-data + (str (:prefix pnl-data) " Net Income")))))) -(defn summarize-pnl [args data] - {:summaries (for [[client-id location] (locations data)] - (location-summary-table args data client-id location)) - :details (for [[client-id location] (locations data)] - (location-detail-table args data client-id location))} +(defn summarize-pnl [pnl-data] + (try + {:summaries (for [[client-id location] (locations (:data pnl-data))] + (location-summary-table (-> pnl-data + (filter-client client-id) + (filter-location location)))) + :details (for [[client-id location] (locations (:data pnl-data))] + (location-detail-table (-> pnl-data + (filter-client client-id) + (filter-location location) + (assoc :prefix location))))} + (catch Throwable e + + (println e)))) + + + + +(defrecord PNLData [args data] ) (defn make-pnl [args data] - (let [data (<-graphql data) - args (<-graphql args) - _ (clojure.pprint/pprint (summarize-pnl (assoc args :deltas true) data))output-stream (ByteArrayOutputStream.) - _ (println (:client_ids args)) - clients (d/pull-many (d/db conn) '[:client/name] (:client-ids args))] + (let [data (<-graphql data) + args (<-graphql args) + data (->> data + :periods + (mapcat (fn [p1 p2] + (map + (fn [a] + (assoc a :period p1) + ) + (:accounts p2)) + ) + (:periods args))) + report (PNLData. (assoc args :deltas true) data) + _ (clojure.pprint/pprint (summarize-pnl report)) + output-stream (ByteArrayOutputStream.) + _ (println (:client_ids args)) + clients (d/pull-many (d/db conn) '[:client/name] (:client-ids args))] (pdf/pdf (expand [{:left-margin 25 :right-margin 0 :top-margin 0 :bottom-margin 0 :size :letter} diff --git a/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs b/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs index 837d98e0..6fbc8fb0 100644 --- a/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs +++ b/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs @@ -95,7 +95,6 @@ (fn [db] (-> db ::ledger-list-active?))) - (re-frame/reg-sub ::period-accounts :<- [::forms/form ::form] @@ -579,7 +578,6 @@ (let [all-accounts @(re-frame/subscribe [::all-accounts]) periods @(re-frame/subscribe [::periods]) include-deltas @(re-frame/subscribe [::include-deltas])] - (println title client-id) [:tr [:th.is-size-5 title] (map-periods