diff --git a/src/clj/auto_ap/pdf/ledger.clj b/src/clj/auto_ap/pdf/ledger.clj index 8dead893..69f8094d 100644 --- a/src/clj/auto_ap/pdf/ledger.clj +++ b/src/clj/auto_ap/pdf/ledger.clj @@ -188,73 +188,65 @@ (defn combine-tables [pnl-data table percent-of-sales deltas] (map (fn [[title & row] percent-of-sales deltas ] - (let [deltas (cons nil deltas)] + (let [deltas (cons "-" deltas)] (into [title] - (filter identity - (mapcat - (fn [v p d] - [v p d]) - row - percent-of-sales - deltas))) + (mapcat + (fn [v p d] + [v p d]) + row + percent-of-sales + deltas)) )) table percent-of-sales deltas)) (defn headers [pnl-data header-title] - (let [deltas (cons nil (repeat "Change")) - big-header (into [{:value header-title + (let [big-header (into [{:value header-title :bold true}] (map-indexed (fn [i p] {:value (str (date->str (:start p)) " - " (date->str (:end p))) - :colspan (if (= 0 i) - 2 - 3) + :colspan 3 :align :center :bold true}) (:periods (:args pnl-data)))) sub-header (into [{:value ""}] - (filter identity - (mapcat - (fn [v p d] - [{:value "Amount" - :align :right - :bold true} - {:value "% Sales" - :align :right - :bold true} - (when d - {:value d - :align :right - :bold true})]) - (:periods (:args pnl-data)) - (:periods (:args pnl-data)) - deltas)))] + (mapcat + (fn [_] + [{:value "Amount" + :align :right + :bold true} + {:value "% Sales" + :align :right + :bold true} + {:value "Change" + :align :right + :bold true}]) + (:periods (:args pnl-data))))] [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") + (let [table [(subtotal-row (filter-categories pnl-data [:sales]) "Sales") + (subtotal-row (filter-categories pnl-data [:cogs ]) "Cogs") - (subtotal-row (filter-categories pnl-data [:payroll ]) "Payroll") + (subtotal-row (filter-categories pnl-data [:payroll ]) "Payroll") - (subtotal-row (-> pnl-data - (filter-categories [:sales :payroll :cogs]) - (negate #{:payroll :cogs})) - "Gross Profits") + (subtotal-row (-> pnl-data + (filter-categories [:sales :payroll :cogs]) + (negate #{:payroll :cogs})) + "Gross Profits") - (subtotal-row (filter-categories pnl-data [:controllable :fixed-overhead :ownership-controllable]) - "Overhead") + (subtotal-row (filter-categories pnl-data [:controllable :fixed-overhead :ownership-controllable]) + "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")] + (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) deltas (calc-deltas table)] {:header (headers pnl-data title) @@ -317,9 +309,6 @@ (conj (subtotal-row (-> pnl-data (filter-categories [:controllable :fixed-overhead :ownership-controllable])) (str (:prefix pnl-data) " Overhead"))) - (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})) @@ -357,7 +346,7 @@ (:colspan cell) (assoc :colspan (:colspan cell))) (cond (= :dollar (:format cell)) - (.format (DecimalFormat. "$###,###.00") (:value cell)) + (.format (DecimalFormat. "$###,##0.00") (:value cell)) (= :percent (:format cell)) (.format (DecimalFormat. "0%") (:value cell)) @@ -365,14 +354,34 @@ :else (str (:value cell)))]) +(defn cell-count [table] + (let [counts (map count (:rows table))] + (if (seq counts) + (apply max counts) + 0))) + (defn table->pdf [table] - (let [cell-count (apply max (map count (:rows table)))] - (-> [:pdf-table {:header (mapv - (fn [header] - (map cell->pdf header)) - (:header table)) - :cell-border false} - (into [70] (take (dec cell-count) (repeat 20)))] + (let [cell-count (cell-count table)] + (-> [:pdf-table {:header (mapv + (fn [header] + (map cell->pdf header)) + (:header table)) + :cell-border false + :width-percent (cond (= 5 cell-count) + 50 + + (= 6 cell-count) + 60 + + (= 7 cell-count) + 70 + + (= 8 cell-count) + 80 + + :else + 100)} + (into [20] (take (dec cell-count) (repeat 10)))] (into (for [row (:rows table)] @@ -381,6 +390,63 @@ (cell->pdf cell) )))) (conj (take cell-count (repeat (cell->pdf {:value " "}))))))) + +(defn split-table [table n] + (let [cell-count (cell-count table)] + (if (<= cell-count n) + [table] + (let [new-table (-> table + (update :rows (fn [rows] + (map + (fn [[header & rest]] + (into [header] + (take (dec n) rest))) + rows))) + (update :header (fn [headers] + (map + (fn [[title & header]] + (first + (reduce + (fn [[so-far a] next] + (let [new-a (+ a (or (:colspan next) + 1))] + (if (<= new-a n) + [(conj so-far next) new-a] + [so-far new-a]))) + + [[title] 1] + header))) + headers)))) + remaining (-> table + (update :rows (fn [rows] + (map + (fn [[header & rest]] + (into [header] + (drop (dec n) rest))) + rows))) + (update :header (fn [headers] + (map + (fn [[title & header]] + (first + (reduce + (fn [[so-far a] next] + (let [new-a (+ a (or (:colspan next) + 1))] + (if (> new-a n) + [(conj so-far next) new-a] + [so-far new-a]))) + + [[title] 1] + header))) + headers))))] + (into [new-table] + (split-table remaining n)))))) + +(defn break-apart-tables [tables] + (for [table tables + table (split-table table 10)] + table)) + (defn make-pnl [args data] (let [data (<-graphql data) @@ -399,13 +465,14 @@ report (summarize-pnl (PNLData. (assoc args :deltas true) data (by :db/id clients))) output-stream (ByteArrayOutputStream.)] (pdf/pdf - [{:left-margin 15 :right-margin 15 :top-margin 15 :bottom-margin 15 :size :letter - :font {:size 8}} - [:heading (str "Profit and Loss - " (str/join ", " (map :client/name clients)))] - (into [:paragraph] - (map table->pdf (:summaries report))) - (into [:paragraph] - (map table->pdf (:details report)))] + (into + [{:left-margin 10 :right-margin 10 :top-margin 15 :bottom-margin 15 :size :letter + :orientation :landscape + :font {:size 8}} + [:heading (str "Profit and Loss - " (str/join ", " (map :client/name clients)))]] + (for [table (break-apart-tables (concat (:summaries report) + (:details report)))] + (table->pdf table))) output-stream) (.toByteArray output-stream)))