diff --git a/src/clj/auto_ap/ssr/components/inputs.clj b/src/clj/auto_ap/ssr/components/inputs.clj index da91c35e..b5927407 100644 --- a/src/clj/auto_ap/ssr/components/inputs.clj +++ b/src/clj/auto_ap/ssr/components/inputs.clj @@ -217,7 +217,7 @@ "@mouseout" "active = -1" "@click.prevent" "value.push({value: element.value, label: element.label});" } - [ :input {:type "checkbox" ":checked" "value.includes(element.value)"} ] + [ :input {:type "checkbox" ":checked" "value.map(i=>i.value).includes(element.value)"} ] [:span {"x-html" "element.label"}]]]] [:template {:x-if "elements.length == 0"} [:li {:class "px-4 py-2 flex gap-2 items-center outline-0 focus:bg-neutral-100 hover:bg-neutral-100 whitespace-nowrap [&.active]:bg-primary-500 text-gray-800 dark:text-gray-100 text-xs "} diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index e09077cb..60813628 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -252,18 +252,11 @@ request) :headers {"hx-push-url" (str "?" (url/map->query (dissoc (if (:query-schema grid-spec) - (do - (alog/peek ::setup4 - (pr-str (update (filter-vals #(not (nil? %)) - (m/encode (:query-schema grid-spec) - (:query-params request) - main-transformer)) - "sort" sort->query))) - (update (filter-vals #(not (nil? %)) - (m/encode (:query-schema grid-spec) - (:query-params request) - main-transformer)) - "sort" sort->query)) + (update (filter-vals #(not (nil? %)) + (m/encode (:query-schema grid-spec) + (:query-params request) + main-transformer)) + "sort" sort->query) (unparse-query-params (:parsed-query-params request))) "selected" "all-selected")))} ;; TODO seems hacky to special case selected and all-selected here :oob (when-let [oob-render (:oob-render grid-spec)] diff --git a/src/clj/auto_ap/ssr/ledger/balance_sheet.clj b/src/clj/auto_ap/ssr/ledger/balance_sheet.clj index c2970481..135242b1 100644 --- a/src/clj/auto_ap/ssr/ledger/balance_sheet.clj +++ b/src/clj/auto_ap/ssr/ledger/balance_sheet.clj @@ -6,7 +6,6 @@ [auto-ap.ledger.reports :as l-reports] [auto-ap.logging :as alog] [auto-ap.permissions :refer [wrap-must]] - [auto-ap.query-params :refer [wrap-copy-qp-pqp]] [auto-ap.routes.ledger :as route] [auto-ap.routes.utils :refer [wrap-client-redirect-unauthenticated]] @@ -17,8 +16,9 @@ [auto-ap.ssr.ui :refer [base-page]] [auto-ap.ssr.utils :refer [apply-middleware-to-all-handlers clj-date-schema - entity-id html-response wrap-form-4xx-2 - wrap-merge-prior-hx wrap-schema-enforce]] + field-validation-error html-response wrap-form-4xx-2 + wrap-merge-prior-hx wrap-schema-decode + wrap-schema-enforce]] [auto-ap.time :as atime] [bidi.bidi :as bidi] [clj-time.coerce :as coerce] @@ -55,18 +55,25 @@ (def query-schema (mc/schema - [:maybe [:map - [:client {} - [:vector {:coerce? true :min 1 } - [:entity-map {:pull [:db/id :client/name]}]]] - [:date {} - clj-date-schema] - [:comparison-date {:optional true} - [:maybe clj-date-schema]] - [:include-comparison {:optional true :default false} - [ :boolean {:decode/string {:enter #(if (= % "on") true + [:maybe [:and [:map + [:client {} + [:vector {:coerce? true :min 1 } + [:entity-map {:pull [:db/id :client/name]}]]] + [:date {} + clj-date-schema] + [:comparison-date {:optional true} + [:maybe clj-date-schema]] + [:include-comparison {:optional true :default false} + [ :boolean {:decode/string {:enter #(if (= % "on") true - (boolean %))}}]]]])) + (boolean %))}}]]] + [:fn {:error/message "required" + :error/path [:comparison-date]} + (fn [x] + (if (and (not (:comparison-date x)) + (:include-comparison x)) + false + true))]]])) @@ -187,126 +194,131 @@ ;; 4. Review ledger dialog -(defn run-balance-sheet [{:keys [query-params] :as request}] - (let [client-ids (map :db/id (:client (:query-params request))) - - _ (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 (:identity request) client-id)) - end-date (coerce/to-date (:date (:query-params request))) - comparable-date (coerce/to-date (:comparison-date (:query-params request))) - include-comparison (:include-comparison (:query-params request)) - lookup-account (->> client-ids - (map (fn build-lookup [client-id] - [client-id (build-account-lookup client-id)])) - (into {})) - data (cond-> {:balance-sheet-accounts (into [] - (mapcat - (fn calculate-accounts [client-id ] - (for [ - [client-id account-id location debits credits balance count] (iol-ion.query/detailed-account-snapshot (dc/db conn) client-id end-date) - :let [account ( (or (lookup-account client-id) {}) account-id)]] - {:client-id client-id - :account-id account-id - :location location - :debits debits - :credits credits - :count count - :amount balance - :account-type (:account_type account) - :numeric-code (:numeric_code account) - :name (:name account) })) - - client-ids))} - include-comparison (assoc :comparable-balance-sheet-accounts - (into [] - (mapcat - (fn calculate-accounts [client-id ] - (for [ - [client-id account-id location debits credits balance count] (iol-ion.query/detailed-account-snapshot (dc/db conn) client-id comparable-date) - :let [account ( (or (lookup-account client-id) {}) account-id)]] - {:client-id client-id - :account-id account-id - :location location - :debits debits - :credits credits - :count count - :amount balance - :account-type (:account_type account) - :numeric-code (:numeric_code account) - :name (:name account) })) - - client-ids))) - ) - args (assoc query-params - :periods (filter identity (cond-> [(:date query-params)] - include-comparison (conj (:comparison-date query-params))))) - clients (pull-many (dc/db conn) [:client/code :client/name :db/id] client-ids) +(defn balance-sheet* [{ {:keys [date comparison-date include-comparison client] } :query-params :as request}] + [:div#report + (when (and date client) + (let [client-ids (map :db/id client) + + _ (doseq [client-id client-ids] + (assert-can-see-client (:identity request) client-id)) + + end-date (coerce/to-date date) + comparable-date (coerce/to-date comparison-date) + lookup-account (->> client-ids + (map (fn build-lookup [client-id] + [client-id (build-account-lookup client-id)])) + (into {})) + data (cond-> {:balance-sheet-accounts (into [] + (mapcat + (fn calculate-accounts [client-id ] + (for [ + [client-id account-id location debits credits balance count] (iol-ion.query/detailed-account-snapshot (dc/db conn) client-id end-date) + :let [account ( (or (lookup-account client-id) {}) account-id)]] + {:client-id client-id + :account-id account-id + :location location + :debits debits + :credits credits + :count count + :amount balance + :account-type (:account_type account) + :numeric-code (:numeric_code account) + :name (:name account) })) + + client-ids))} + (and include-comparison comparison-date) + (assoc :comparable-balance-sheet-accounts + (into [] + (mapcat + (fn calculate-accounts [client-id ] + (for [ + [client-id account-id location debits credits balance count] (iol-ion.query/detailed-account-snapshot (dc/db conn) client-id comparable-date) + :let [account ( (or (lookup-account client-id) {}) account-id)]] + {:client-id client-id + :account-id account-id + :location location + :debits debits + :credits credits + :count count + :amount balance + :account-type (:account_type account) + :numeric-code (:numeric_code account) + :name (:name account) })) + + client-ids))) + ) + args (assoc (:query-params request) + :periods (filter identity (cond-> [date] + include-comparison (conj comparison-date)))) + clients (pull-many (dc/db conn) [:client/code :client/name :db/id] client-ids) - data (concat (->> (:balance-sheet-accounts data) - (map (fn [b] - (assoc b - :period (:date args))))) - (->> (:comparable-balance-sheet-accounts data) - (map (fn [b] - (assoc b - :period (:comparison-date args)))))) - pnl-data (l-reports/->PNLData args data (by :db/id :client/code clients)) - client-count (count (set (map :client-id (:data pnl-data)))) - report (l-reports/summarize-balance-sheet pnl-data) ] - (alog/info ::balance-sheet :params args) - (html-response - (list - [:div.text-2xl.font-bold.text-gray-600 (str "Balance Sheet - " (str/join ", " (map :client/name clients))) ] - (table {:widths (cond-> (into [30 ] (repeat 13 client-count)) - (:include-comparison args) (into (repeat 13 (* 2 client-count)))) - :click-event ::investigate-clicked - :table report} ))))) + data (concat (->> (:balance-sheet-accounts data) + (map (fn [b] + (assoc b + :period (:date args))))) + (->> (:comparable-balance-sheet-accounts data) + (map (fn [b] + (assoc b + :period (:comparison-date args)))))) + pnl-data (l-reports/->PNLData args data (by :db/id :client/code clients)) + client-count (count (set (map :client-id (:data pnl-data)))) + report (l-reports/summarize-balance-sheet pnl-data) ] + (alog/info ::balance-sheet :params args) + (list + [:div.text-2xl.font-bold.text-gray-600 (str "Balance Sheet - " (str/join ", " (map :client/name clients))) ] + (table {:widths (cond-> (into [30 ] (repeat 13 client-count)) + (:include-comparison args) (into (repeat 13 (* 2 client-count)))) + :click-event ::investigate-clicked + :table report} ))))]) (defn form* [request] - (fc/start-form - (:form-params request) - (:form-errors request) - [:div#balance-sheet-form.flex.flex-col.gap-4.mt-4 - [:form {:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::route/run-balance-sheet) - :hx-target "#report"} - [:div.flex.gap-8 {:x-data (hx/json {:comparison (boolean (:comparison-date (:form-params request)))})} - (fc/with-field :client + + (let [params (merge (:query-params request) (:form-params request) {})] + (fc/start-form + + params + (:form-errors request) + [:div#balance-sheet-form.flex.flex-col.gap-4.mt-4 + [:form {:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::route/run-balance-sheet) + :hx-target "#balance-sheet-form" + :hx-swap "outerHTML"} + [:div.flex.gap-8 {:x-data (hx/json {:comparison (boolean (:include-comparison params))})} + (fc/with-field :client (com/validated-inline-field {:label "Customers" :errors (fc/field-errors)} - (com/multi-typeahead {:name (fc/field-name) - :class "w-64" - :id "client" - :url (bidi/path-for ssr-routes/only-routes :company-search) - :value (fc/field-value) - :value-fn :db/id - :content-fn :client/name}))) - (fc/with-field :date - (com/validated-inline-field {:label "Date" - :errors (fc/field-errors)} - (com/date-input {:placeholder "12/21/2020" - :name (fc/field-name) - :value (some-> (fc/field-value) - (atime/unparse-local atime/normal-date))}))) - (fc/with-field :include-comparison - (com/toggle {:x-model "comparison" :name (fc/field-name) :checked (boolean (fc/field-value))} "Compare")) - [:div (hx/alpine-appear {:x-show "comparison"}) - (fc/with-field :comparison-date - (com/validated-inline-field {:label "Previous Date" - :errors (fc/field-errors)} - - (com/date-input {:placeholder "12/21/2020" - :name (fc/field-name) - :value (some-> (fc/field-value) - (atime/unparse-local atime/normal-date))})))] - (com/button {:color :primary :class "w-32"} - "Run")]] - [:div#report]])) + (com/multi-typeahead {:name (fc/field-name) + :class "w-64" + :id "client" + :url (bidi/path-for ssr-routes/only-routes :company-search) + :value (fc/field-value) + :value-fn :db/id + :content-fn :client/name}))) + (fc/with-field :date + (com/validated-inline-field {:label "Date" + :errors (fc/field-errors)} + (com/date-input {:placeholder "12/21/2020" + :name (fc/field-name) + :value (some-> (fc/field-value) + (atime/unparse-local atime/normal-date))}))) + (fc/with-field :include-comparison + (com/toggle {:x-model "comparison" :name (fc/field-name) :checked (boolean (fc/field-value))} "Compare")) + [:div (hx/alpine-appear {:x-show "comparison"}) + (fc/with-field :comparison-date + (com/validated-inline-field {:label "Previous Date" + :errors (fc/field-errors)} + + (com/date-input {:placeholder "12/21/2020" + :name (fc/field-name) + :value (some-> (fc/field-value) + (atime/unparse-local atime/normal-date))})))] + (com/button {:color :primary :class "w-32"} + "Run")]] + (balance-sheet* request)]))) (defn form [request] (html-response (form* request) - :headers {"hx-retarget" "#balance-sheet-form"})) + :headers {"hx-retarget" "#balance-sheet-form" + "hx-push-url" (str "?" (:query-string request))})) (defn balance-sheet [request] (base-page @@ -326,8 +338,10 @@ (def key->handler (apply-middleware-to-all-handlers (-> - {::route/balance-sheet balance-sheet - ::route/run-balance-sheet (-> run-balance-sheet + {::route/balance-sheet (-> balance-sheet + (wrap-schema-enforce :query-schema query-schema) + (wrap-form-4xx-2 balance-sheet)) + ::route/run-balance-sheet (-> form (wrap-schema-enforce :query-schema query-schema) (wrap-form-4xx-2 form))}) (fn [h] diff --git a/src/clj/auto_ap/ssr/utils.clj b/src/clj/auto_ap/ssr/utils.clj index c2fabcbe..8188b84b 100644 --- a/src/clj/auto_ap/ssr/utils.clj +++ b/src/clj/auto_ap/ssr/utils.clj @@ -172,7 +172,12 @@ s)) (defn keyword->str [k] - (subs (str k) 1)) + (cond (keyword? k) + (subs (str k) 1) + (string? k) + k + :else + k)) ;; TODO make this bubble the form data automatically