diff --git a/src/clj/auto_ap/graphql/ledger.clj b/src/clj/auto_ap/graphql/ledger.clj index 140f0f2e..c7bc48a7 100644 --- a/src/clj/auto_ap/graphql/ledger.clj +++ b/src/clj/auto_ap/graphql/ledger.clj @@ -162,15 +162,32 @@ (defn get-balance-sheet [context args _] (let [client-id (:client_id args) + client-ids (or (some-> client-id vector) + (filter identity (:client_ids args))) _ (assert-can-see-client (:id context) client-id) + _ (when (not (seq client-ids)) + (throw (ex-info "Please select a client." {:validation-error "Please select a client."}))) + _ (doseq [client-id client-ids] + (assert-can-see-client (:id context) client-id)) end-date (coerce/to-date (:date args)) comparable-date (coerce/to-date (:comparison_date args)) - all-ledger-entries (full-ledger-for-client client-id) - lookup-account (build-account-lookup client-id)] + all-ledger-entries (->> client-ids + (map (fn [client-id] + [client-id (full-ledger-for-client client-id)])) + (into {})) + lookup-account (->> client-ids + (map (fn [client-id] + [client-id (build-account-lookup client-id)])) + (into {}))] (log/info "Running balance sheet with " args) - (cond-> {:balance-sheet-accounts (roll-up-until lookup-account all-ledger-entries end-date)} - (:include_comparison args) (assoc :comparable-balance-sheet-accounts (roll-up-until lookup-account all-ledger-entries comparable-date)) + (cond-> {:balance-sheet-accounts (mapcat + #(roll-up-until (lookup-account %) (all-ledger-entries %) end-date ) + client-ids) + } + (:include_comparison args) (assoc :comparable-balance-sheet-accounts (mapcat + #(roll-up-until (lookup-account %) (all-ledger-entries %) comparable-date ) + client-ids)) true ->graphql))) (defn get-profit-and-loss [context args _] @@ -747,7 +764,7 @@ (def queries {:balance_sheet {:type :balance_sheet - :args {:client_id {:type :id} + :args {:client_ids {:type '(list :id)} :include_comparison {:type 'Boolean} :date {:type :iso_date} :comparison_date {:type :iso_date}} @@ -792,7 +809,7 @@ :resolve :cash-flows-pdf} :balance_sheet_pdf {:type :report_pdf - :args {:client_id {:type :id} + :args {:client_ids {:type '(list :id)} :include_comparison {:type 'Boolean} :date {:type :iso_date} :comparison_date {:type :iso_date}} diff --git a/src/cljc/auto_ap/ledger/reports.cljc b/src/cljc/auto_ap/ledger/reports.cljc index 4b07bc50..6eb7a192 100644 --- a/src/cljc/auto_ap/ledger/reports.cljc +++ b/src/cljc/auto_ap/ledger/reports.cljc @@ -489,14 +489,20 @@ pnl-datas) )] (into (for [{:keys [numeric-code name]} account-codes] - (into [{:value name}] + (into [{:value (str name ":" numeric-code)}] (map (fn [p] - (let [pnl-data (-> p (filter-numeric-code numeric-code numeric-code))] + (let [pnl-data (-> p (filter-numeric-code numeric-code numeric-code)) + this-name-exists? (->> (:data p) + (filter (comp #{name} :name)) + seq)] (merge - {:format :dollar - :filters (:filters pnl-data) - :value (aggregate-accounts pnl-data)} + (if this-name-exists? + {:format :dollar + :filters (:filters pnl-data) + :value (aggregate-accounts pnl-data)} + {:filters (:filters pnl-data) + :value ""}) (:cell-args p)))) pnl-datas)))) @@ -780,9 +786,17 @@ (defn balance-sheet-headers [pnl-data] - [(cond-> [{:value "Period Ending"} - {:value (date->str (:date (:args pnl-data)))}] - (:include-comparison (:args pnl-data)) (into [{:value (date->str (:comparison-date (:args pnl-data)))} {:value "+/-"}]))]) + (doto + [(into [{:value "Period Ending"}] + + (mapcat identity + (for [client (set (map :client-id (:data pnl-data))) ] + (cond-> [{:value (date->str (:date (:args pnl-data)))}] + + (:include-comparison (:args pnl-data)) + (into [{:value (date->str (:comparison-date (:args pnl-data)))} + {:value "+/-"}])))))] + println)) (defn append-deltas [table] (->> table @@ -793,31 +807,80 @@ :value (- (or (:value a) 0.0) (or (:value b) 0.0))})])))) + +#_(defn summarize-balance-sheet [pnl-data] + (reduce + (fn [result table] + (-> result + (update :header into (:header table)) + (update :rows + (fn [current-rows] + (if (seq current-rows) + (map + concat + current-rows + (:rows table)) + (:rows table)))))) + {:header [] + :rows []} + (for [client-id (set (map :client-id (:data pnl-data)))] + (let [pnl-datas (map (fn [p] + (-> pnl-data + (filter-client client-id) + (filter-period p))) + (:periods (:args pnl-data))) + table (-> [] + (into (detail-rows pnl-datas + :assets + "Assets")) + (into (detail-rows pnl-datas + :liabilities + "Liabilities")) + (into (detail-rows pnl-datas + :equities + "Owner's Equity")) + (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)] + {:header (balance-sheet-headers pnl-data) + :rows table}))) + ) + (defn summarize-balance-sheet [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-datas - :liabilities - "Liabilities")) - (into (detail-rows pnl-datas - :equities - "Owner's Equity")) - (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)] - {:header (balance-sheet-headers pnl-data) - :rows table})) + (let [pnl-datas (for [client-id (set (map :client-id (:data pnl-data))) + p (:periods (:args pnl-data))] + (-> pnl-data + (filter-client client-id) + (filter-period p)) + )] + (let [table (-> [] + (into (detail-rows pnl-datas + :assets + "Assets")) + (into (detail-rows pnl-datas + :liabilities + "Liabilities")) + (into (detail-rows pnl-datas + :equities + "Owner's Equity")) + (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)] + {:header (balance-sheet-headers pnl-data) + :rows table})) + ) (defn journal-detail-report [args data client-codes] diff --git a/src/cljs/auto_ap/views/pages/ledger/balance_sheet.cljs b/src/cljs/auto_ap/views/pages/ledger/balance_sheet.cljs index 4670e7d0..86fc526a 100644 --- a/src/cljs/auto_ap/views/pages/ledger/balance_sheet.cljs +++ b/src/cljs/auto_ap/views/pages/ledger/balance_sheet.cljs @@ -13,12 +13,14 @@ [auto-ap.views.pages.ledger.report-table :as rtable] [auto-ap.views.pages.ledger.side-bar :refer [ledger-side-bar]] [auto-ap.views.pages.ledger.table :as ledger-table] + [auto-ap.views.components.buttons :as buttons] [auto-ap.views.utils :refer [date-picker dispatch-event local-now with-user date->str standard]] [cljs-time.core :as t] [clojure.set :as set] [clojure.string :as str] [re-frame.core :as re-frame] + [react-dom :as react-dom] [reagent.core :as reagent] [vimsical.re-frame.cofx.inject :as inject] [vimsical.re-frame.fx.track :as track])) @@ -68,9 +70,10 @@ :graphql {:token user :query-obj {:venia/queries [[:balance-sheet (-> (:data db) - (assoc :client-id (:id client))) - [[:balance-sheet-accounts [:name :amount :account-type :id :numeric-code]] - [:comparable-balance-sheet-accounts [:name :amount :account-type :id :numeric-code]]]]]} + (dissoc :clients) + (assoc :client-ids (map (comp :id :client) (:clients (:data db))))) + [[:balance-sheet-accounts [:name :amount :account-type :id :numeric-code :client-id]] + [:comparable-balance-sheet-accounts [:name :amount :account-type :id :client-id :numeric-code]]]]]} :owns-state {:single ::page} :on-success [::received]}})) @@ -110,7 +113,8 @@ NOTE: Please review the transactions we may have question for you here: https:// :graphql {:token user :query-obj {:venia/queries [[:balance-sheet-pdf (-> (:data db) - (assoc :client-id (:id client))) + (dissoc :clients) + (assoc :client-ids (map (comp :id :client) (:clients (:data db))))) [:url :name]]]} :owns-state {:single ::page} @@ -119,9 +123,9 @@ NOTE: Please review the transactions we may have question for you here: https:// (re-frame/reg-event-fx ::investigate-clicked - (fn [{:keys [db]} [_ {:keys [numeric-code date-range]}]] + (fn [{:keys [db]} [_ {:keys [numeric-code date-range client-id]}]] {:db (-> db (assoc ::ledger-list-active? true)) - :dispatch [::data-page/additional-params-changed ::ledger {:client-id (:id @(re-frame/subscribe [::subs/client])) + :dispatch [::data-page/additional-params-changed ::ledger {:client-id client-id :numeric-code numeric-code :date-range {:start "2000-01-01" :end (date->str date-range standard)}}]})) @@ -168,43 +172,77 @@ NOTE: Please review the transactions we may have question for you here: https:// (fn [{:keys [db]} _] {:db (forms/start-form db ::form {:date (local-now) :comparison-date (t/minus (local-now) (t/years 1)) + :clients (mapv (fn [c] {:client c :id (random-uuid)}) + [(some-> @(re-frame/subscribe [::subs/client]) (select-keys [:name :id]) )]) :include-comparison false}) ::track/register {:id ::ledger-params :subscription [::data-page/params ::ledger] :event-fn (fn [params] [::ledger-params-change params])}})) +(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-form [] - (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])] - [form-builder/builder {:change-event [::change] - :submit-event [::report-requested] - :id ::form} - [:div - [:div.report-controls - [:div.level - [:div.level-left - [:div.level-item - [:div.control - [form-builder/field-v2 {:field :date} - "Date" - [date-picker {:output :cljs-date}]]]] - [:div.level-item - [form-builder/field-v2 {:field :include-comparison} - [:div.mt-5] - [com/switch-input {:id "include-comparison" - :label "Include compariison"}]]] - [:div.level-item + (let [!box (atom nil) + active (reagent/atom nil)] + (fn [] + (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])] + [form-builder/builder {:change-event [::change] + :submit-event [::report-requested] + :id ::form} + [:div + [:div.report-controls + [:div.level + [:div.level-left + [:div.level-item + [:div.mt-5 + [buttons/dropdown {:on-click (fn [] (reset! active :clients))} + [:span (str "Companies" + (when-let [clients (:clients data)] + (str " (" (str/join ", " (map (comp :name :client) clients)) ")")))]] + [report-control-detail {:active active :box !box :which :clients} + [:div {:style {:width "20em"}} + [:h4.subtitle "Companies"] + [form-builder/raw-field-v2 {:field :clients} + [com/multi-field-v2 {:new-text "Add another company" + :template [[form-builder/raw-field-v2 {:field :client} + [com/entity-typeahead {:entities @(re-frame/subscribe [::subs/clients]) + :style {:width "18em"} + :entity->text :name}]]] + :key-fn :id}]]]]]] + [:div.level-item + [:div.control + [form-builder/field-v2 {:field :date} + "Date" + [date-picker {:output :cljs-date}]]]] + [:div.level-item + [form-builder/field-v2 {:field :include-comparison} + [:div.mt-5] + [com/switch-input {:id "include-comparison" + :label "Include compariison"}]]] + [:div.level-item - (when (boolean (:include-comparison data)) - [form-builder/field-v2 {:field :comparison-date} - "Comparison Date" - [date-picker {:output :cljs-date}]])]] - [:div.level-right - [:div.buttons + (when (boolean (:include-comparison data)) + [form-builder/field-v2 {:field :comparison-date} + "Comparison Date" + [date-picker {:output :cljs-date}]])]] + [:div.level-right + [:div.buttons - (when @(re-frame/subscribe [::subs/is-admin?]) - [:button.button.is-secondary {:on-click (dispatch-event [::export-pdf])} "Export"]) - [:button.button.is-primary "Run"]]]]]]])) + (when @(re-frame/subscribe [::subs/is-admin?]) + [:button.button.is-secondary {:on-click (dispatch-event [::export-pdf])} "Export"]) + [:button.button.is-primary "Run"]]]] + [:div.report-control-detail {:ref (fn [el] + (when (not= @!box el) + (reset! !box el)))}]]] + ])))) (defn balance-sheet-report [{:keys [args report-data]}] (let [pnl-data (concat (->> (:balance-sheet-accounts report-data) @@ -222,9 +260,10 @@ NOTE: Please review the transactions we may have question for you here: https:// [k (:name v)])) (into {})) pnl-data (l-reports/->PNLData args pnl-data client-names) - report (l-reports/summarize-balance-sheet pnl-data)] - [rtable/table {:widths (cond-> [30 13] - (:include-comparison args) (into [13 13])) + report (l-reports/summarize-balance-sheet pnl-data) + client-count (count (set (map :client-id (:data pnl-data))))] + [rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count)) + (:include-comparison args) (into (repeat 13 (* 2 client-count)))) :click-event ::investigate-clicked :table report}])) @@ -232,21 +271,17 @@ NOTE: Please review the transactions we may have question for you here: https:// (let [current-client @(re-frame/subscribe [::subs/client]) status @(re-frame/subscribe [::status/single ::page]) {params :data report :report} @(re-frame/subscribe [::forms/form ::form])] - (if current-client - [:div.is-inline - [status/status-notification {:statuses [[::status/single ::page]]}] - [report-form] + [:div.is-inline + [status/status-notification {:statuses [[::status/single ::page]]}] + [report-form] - [status/big-loader status] - (when (and (not= :loading (:state status)) - report) - [balance-sheet-report {:args (assoc params - :periods (filter identity (cond-> [(:date params)] - (:include-comparison params) (conj (:comparison-date params))))) - :report-data report}])] - [:div - [:h1.title "Balance sheet"] - [:h2.subtitle "Please choose a client first"]]))) + [status/big-loader status] + (when (and (not= :loading (:state status)) + report) + [balance-sheet-report {:args (assoc params + :periods (filter identity (cond-> [(:date params)] + (:include-comparison params) (conj (:comparison-date params))))) + :report-data report}])])) (defn ledger-list [_ ] [:div [:a.delete.is-pulled-right {:on-click (dispatch-event [::ledger-list-closing])}]