From 85597fd9ebbab41fb3dc01e240aca320378d1827 Mon Sep 17 00:00:00 2001 From: Bryce Date: Thu, 14 Sep 2023 22:45:05 -0700 Subject: [PATCH 01/13] ssr sales --- iol_ion/src/iol_ion/query.clj | 11 ++ src/clj/auto_ap/datomic/sales_orders.clj | 17 ++- src/clj/auto_ap/ssr/components/paginator.clj | 8 +- src/clj/auto_ap/ssr/core.clj | 77 ++++++------- src/clj/auto_ap/ssr/grid_page_helper.clj | 64 ++++++----- src/clj/auto_ap/ssr/pos/sales_orders.clj | 107 +++++++++++++++++++ src/cljc/auto_ap/ssr_routes.cljc | 2 + 7 files changed, 212 insertions(+), 74 deletions(-) create mode 100644 src/clj/auto_ap/ssr/pos/sales_orders.clj diff --git a/iol_ion/src/iol_ion/query.clj b/iol_ion/src/iol_ion/query.clj index ba4159fc..9449d845 100644 --- a/iol_ion/src/iol_ion/query.clj +++ b/iol_ion/src/iol_ion/query.clj @@ -48,6 +48,8 @@ [client end])))) + + (defn can-see-client? [identity client] (when (not client) (println "WARNING - permission checking for null client")) @@ -72,6 +74,15 @@ (time/plus (time/days 1)) coerce/to-date)) +(defn scan-sales-orders [db clients start end] + (for [c clients + :let [c (entid db c)] + r (seq (dc/index-range db + :sales-order/client+date + [c (or start #inst "2001-01-01T08:00:00.000-00:00") ] + [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))] + [(:e r) (first (:v r)) (second (:v r))])) + (defn scan-invoices [db clients start end] (for [c clients :let [c (entid db c)] diff --git a/src/clj/auto_ap/datomic/sales_orders.clj b/src/clj/auto_ap/datomic/sales_orders.clj index f5e383c2..36c5fe00 100644 --- a/src/clj/auto_ap/datomic/sales_orders.clj +++ b/src/clj/auto_ap/datomic/sales_orders.clj @@ -70,22 +70,17 @@ :else visible-clients) - (take 3) + (take 10) set) _ (mu/log ::selected-clients :selected-clients selected-clients) query (cond-> {:query {:find [] - :in ['$ '[?c ...]] - :where []} - :args [db selected-clients]} + :in ['$ '[?clients ?start-date ?end-date]] + :where '[[(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} + :args [db [selected-clients + (some-> (:start (:date-range args)) c/to-date) + (some-> (:end (:date-range args)) c/to-date )]]} - - true - (merge-query {:query {:in ['?start-date '?end-date] - :where '[[(iol-ion.query/sales-orders-in-range $ ?c ?start-date ?end-date) [?e ...]]]} - :args [(or (some-> (:start (:date-range args)) c/to-date) (iol-ion.query/recent-date 5)) - (or (some-> (:end (:date-range args)) c/to-date ) - (c/to-date (time/now)))]}) (:sort args) (add-sorter-fields-2 {"client" ['[?e :sales-order/client ?c] '[?c :client/name ?sort-client]] "location" ['[?e :sales-order/location ?sort-location]] diff --git a/src/clj/auto_ap/ssr/components/paginator.clj b/src/clj/auto_ap/ssr/components/paginator.clj index c89caffb..6a27ff20 100644 --- a/src/clj/auto_ap/ssr/components/paginator.clj +++ b/src/clj/auto_ap/ssr/components/paginator.clj @@ -1,4 +1,5 @@ -(ns auto-ap.ssr.components.paginator) +(ns auto-ap.ssr.components.paginator + (:require [auto-ap.ssr.svg :as svg])) (defn bound [x y z] (cond @@ -34,7 +35,10 @@ (not= current-page x) (str " text-gray-500 bg-white border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"))) (assoc :href "#")) - (inc x)]])) + [:div.htmx-indicator.flex.items-center + (svg/spinner {:class "inline w-4 h-4 text-black"})] + [:div.htmx-indicator-hidden + (inc x)]]])) last-page-button (Math/min (long total-pages) (long (+ max-buttons first-page-button))) diff --git a/src/clj/auto_ap/ssr/core.clj b/src/clj/auto_ap/ssr/core.clj index 8dc4bd5d..a32a3bf2 100644 --- a/src/clj/auto_ap/ssr/core.clj +++ b/src/clj/auto_ap/ssr/core.clj @@ -12,47 +12,50 @@ [auto-ap.ssr.company-dropdown :as company-dropdown] [auto-ap.ssr.company.reports :as company-reports] [auto-ap.ssr.invoice.glimpse :as invoice-glimpse] + [auto-ap.ssr.pos.sales-orders :as pos-sales] [auto-ap.routes.ezcater-xls :as ezcater-xls] [auto-ap.ssr.company :as company])) ;; from auto-ap.ssr-routes, because they're shared -(def key->handler {:logout auth/logout - :admin-history (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page))) - :admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page))) - :admin-history-inspect (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/inspect))) - :active-client (wrap-client-redirect-unauthenticated (wrap-secure (wrap-secure company-dropdown/active-client))) - :company-dropdown-search-results - (wrap-client-redirect-unauthenticated (wrap-secure company-dropdown/dropdown-search-results)) - :company (wrap-client-redirect-unauthenticated (wrap-secure company/page)) - :company-1099 (wrap-client-redirect-unauthenticated (wrap-secure company-1099/page)) - :company-1099-vendor-table (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-table)) - :company-1099-vendor-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-dialog)) - :company-1099-vendor-save (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-save)) - :company-plaid (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/page)) - :company-plaid-table (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/table)) - :company-plaid-link (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/link)) - :company-plaid-relink (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/relink)) - :company-yodlee (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/page)) - :company-yodlee-table (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/table)) - :company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog)) - :company-yodlee-provider-account-refresh (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/refresh-provider-account)) - :company-yodlee-provider-account-reauthenticate (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/reauthenticate)) - :company-reports (wrap-client-redirect-unauthenticated (wrap-secure company-reports/page)) - :company-reports-table (wrap-client-redirect-unauthenticated (wrap-secure company-reports/table)) - :company-reports-delete (wrap-client-redirect-unauthenticated (wrap-admin company-reports/delete-report)) - :invoice-glimpse (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/page)) - :invoice-glimpse-upload (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/upload)) - :invoice-glimpse-textract-invoice (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/textract-invoice)) - :invoice-glimpse-create-invoice (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/create-invoice)) - :invoice-glimpse-update-textract-invoice (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/update-textract-invoice)) - :transaction-insights (wrap-client-redirect-unauthenticated (wrap-admin insights/page)) - :transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-admin insights/insight-table)) - :transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-admin insights/transaction-rows)) - :transaction-insight-code (wrap-client-redirect-unauthenticated (wrap-admin insights/code)) - :transaction-insight-disapprove (wrap-client-redirect-unauthenticated (wrap-admin insights/disapprove)) - :transaction-insight-explain (wrap-client-redirect-unauthenticated (wrap-admin insights/explain)) - :admin-ezcater-xls (wrap-client-redirect-unauthenticated (wrap-admin ezcater-xls/page)) - :search (wrap-client-redirect-unauthenticated (wrap-secure search/dialog-contents))}) +(def key->handler (into {:logout auth/logout + :admin-history (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page))) + :admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page))) + :admin-history-inspect (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/inspect))) + :active-client (wrap-client-redirect-unauthenticated (wrap-secure (wrap-secure company-dropdown/active-client))) + :company-dropdown-search-results + (wrap-client-redirect-unauthenticated (wrap-secure company-dropdown/dropdown-search-results)) + :company (wrap-client-redirect-unauthenticated (wrap-secure company/page)) + :company-1099 (wrap-client-redirect-unauthenticated (wrap-secure company-1099/page)) + :company-1099-vendor-table (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-table)) + :company-1099-vendor-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-dialog)) + :company-1099-vendor-save (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-save)) + :company-plaid (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/page)) + :company-plaid-table (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/table)) + :company-plaid-link (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/link)) + :company-plaid-relink (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/relink)) + :company-yodlee (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/page)) + :company-yodlee-table (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/table)) + :company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog)) + :company-yodlee-provider-account-refresh (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/refresh-provider-account)) + :company-yodlee-provider-account-reauthenticate (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/reauthenticate)) + :company-reports (wrap-client-redirect-unauthenticated (wrap-secure company-reports/page)) + :company-reports-table (wrap-client-redirect-unauthenticated (wrap-secure company-reports/table)) + :company-reports-delete (wrap-client-redirect-unauthenticated (wrap-admin company-reports/delete-report)) + :invoice-glimpse (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/page)) + :invoice-glimpse-upload (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/upload)) + :invoice-glimpse-textract-invoice (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/textract-invoice)) + :invoice-glimpse-create-invoice (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/create-invoice)) + :invoice-glimpse-update-textract-invoice (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/update-textract-invoice)) + :transaction-insights (wrap-client-redirect-unauthenticated (wrap-admin insights/page)) + :transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-admin insights/insight-table)) + :transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-admin insights/transaction-rows)) + :transaction-insight-code (wrap-client-redirect-unauthenticated (wrap-admin insights/code)) + :transaction-insight-disapprove (wrap-client-redirect-unauthenticated (wrap-admin insights/disapprove)) + :transaction-insight-explain (wrap-client-redirect-unauthenticated (wrap-admin insights/explain)) + :admin-ezcater-xls (wrap-client-redirect-unauthenticated (wrap-admin ezcater-xls/page)) + :search (wrap-client-redirect-unauthenticated (wrap-secure search/dialog-contents))} + + pos-sales/key->handler)) diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index 4f3068d8..e104e602 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -12,13 +12,19 @@ [unilog.context :as lc] [com.brunobonacci.mulog :as mu])) -(defn row* [gridspec user entity {:keys [flash? delete-after-settle?] :as options}] - (let [cells (mapv (fn [header] - (com/data-grid-cell {:class (if-let [show-starting (:show-starting header)] - (format "hidden %s:table-cell" show-starting) - (:class header))} - ((:render header) entity))) - (:headers gridspec)) +(defn row* [gridspec user entity {:keys [flash? delete-after-settle? params] :as options}] + (let [cells (->> gridspec + :headers + (filter (fn [h] + (if (and (:hide? h) + ((:hide? h) params)) + nil + h))) + (mapv (fn [header] + (com/data-grid-cell {:class (if-let [show-starting (:show-starting header)] + (format "hidden %s:table-cell" show-starting) + (:class header))} + ((:render header) entity))))) cells (conj cells (com/data-grid-right-stack-cell {} (into [:form [:input {:type :hidden :name "id" :value ((:id-fn gridspec) entity)}]] @@ -73,7 +79,7 @@ (sort-by-list sort)] :action-buttons ((:action-buttons grid-spec) user params) :rows (for [entity entities] - (row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity))})) + (row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity)) :params params})) :thead-params {:hx-get (bidi/path-for ssr-routes/only-routes (:route grid-spec)) :hx-target (str "#" (:id grid-spec)) @@ -81,25 +87,35 @@ :hx-vals "js:{\"toggle-sort\": event.detail.key || \"\"}"} :headers (conj - (mapv - (fn [h] - (if (:sort-key h) - (com/data-grid-sort-header {:class (if-let [show-starting (:show-starting h)] + (->> grid-spec + :headers + (map + (fn [h] + (cond + (and (:hide? h) + ((:hide? h) params)) + nil + + (:sort-key h) + (com/data-grid-sort-header {:class (if-let [show-starting (:show-starting h)] + (format "hidden %s:table-cell" show-starting) + (:class h)) + :sort-key (:sort-key h)} + + [:div.flex.gap-4.items-center + (:name h) + [:div.h-6.w-6.text-gray-400.dark:text-gray-500 (sort-icon sort (:sort-key h))]]) + + :else + (com/data-grid-header {:class (if-let [show-starting (:show-starting h)] (format "hidden %s:table-cell" show-starting) (:class h)) :sort-key (:sort-key h)} + (:name h)) - [:div.flex.gap-4.items-center - (:name h) - [:div.h-6.w-6.text-gray-400.dark:text-gray-500 (sort-icon sort (:sort-key h))]]) - (com/data-grid-header {:class (if-let [show-starting (:show-starting h)] - (format "hidden %s:table-cell" show-starting) - (:class h)) - :sort-key (:sort-key h)} - (:name h)) - - )) - (:headers grid-spec)) + ))) + (filter identity) + (into [])) (com/data-grid-header {}))}))) @@ -146,7 +162,7 @@ (defn params->query-string [q] (-> q - (dissoc :client :session) + (dissoc :client :session :client-selection :clients) (update :sort sort->query) (url/map->query))) diff --git a/src/clj/auto_ap/ssr/pos/sales_orders.clj b/src/clj/auto_ap/ssr/pos/sales_orders.clj new file mode 100644 index 00000000..086b04c7 --- /dev/null +++ b/src/clj/auto_ap/ssr/pos/sales_orders.clj @@ -0,0 +1,107 @@ +(ns auto-ap.ssr.pos.sales-orders + (:require + [auto-ap.datomic.sales-orders :as d-sales] + [auto-ap.routes.utils + :refer [wrap-client-redirect-unauthenticated wrap-secure]] + [auto-ap.ssr-routes :as ssr-routes] + [auto-ap.ssr.components :as com] + [auto-ap.ssr.grid-page-helper :as helper] + [auto-ap.ssr.svg :as svg] + [auto-ap.time :as atime] + [bidi.bidi :as bidi] + [clj-time.coerce :as coerce])) + +(def grid-page {:id "sales-table" + :nav (com/main-aside-nav) + :id-fn :db/id + :fetch-page (fn [user args] + (d-sales/get-graphql (-> args + (assoc :date-range {:start (coerce/to-date-time #inst "2023-05-01")})))) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "POS"] + + [:a {:href (bidi/path-for ssr-routes/only-routes + :pos-sales)} + "Sales"]] + :title "Sales orders" + :entity-name "Sales orders" + :route :pos-sales-table + :action-buttons (fn [user _] + nil) + :row-buttons (fn [user e] + + (when (:sales-order/reference-link e) + [(com/a-icon-button {:href (:sales-order/reference-link e)} + svg/external-link) + #_(when (is-admin? user) + (com/icon-button {:hx-delete (str (bidi/path-for ssr-routes/only-routes + :company-reports-delete + :request-method :delete)) + :hx-target "closest tr"} + svg/trash))])) + :headers [ + {:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [args] + (= (count (:clients args)) 1)) + :render #(-> % :sales-order/client :client/code)} + {:key "date" + :name "Date" + :sort-key "date" + :render #(atime/unparse-local (:sales-order/date %) atime/standard-time)} + {:key "source" + :name "Source" + :sort-key "source" + :render (fn [sales-order] + (when (:sales-order/source sales-order) + (com/pill {:color :primary } + (:sales-order/source sales-order))))} + {:key "total" + :name "Total" + :sort-key "total" + :render #(some->> % :sales-order/total (format "$%.2f"))} + {:key "tax" + :name "Tax" + :sort-key "tax" + :render #(some->> % :sales-order/tax (format "$%.2f"))} + {:key "tip" + :name "Tip" + :sort-key "tip" + :render #(some->> % :sales-order/tip (format "$%.2f"))} + {:key "Payment methods" + :name "Payment Methods" + :render (fn [sales-order] + (println ) + (for [method (->> sales-order :sales-order/charges (map :charge/type-name) set)] + (com/pill {:color :primary } + method)))}]}) + +(def row* (partial helper/row* grid-page)) +(def table* (partial helper/table* grid-page)) +(def table (partial helper/table grid-page)) +(def page (partial helper/page grid-page)) + +#_(defn delete-report [{:keys [form-params identity]}] + + (let [[id-to-delete key] (first (dc/q '[:find ?i ?k + :in $ ?i + :where [?i :report/key ?k]] + (dc/db conn) + (some-> (get form-params "id") not-empty Long/parseLong))) + report (dc/pull (dc/db conn) r/default-read id-to-delete)] + (assert-can-see-client identity (:report/client report)) + (when id-to-delete + (s3/delete-object :bucket-name (:data-bucket env) + :key key) + @(dc/transact conn [[:db/retractEntity id-to-delete]])) + (html-response + (row* identity + report + {:flash? true + :delete-after-settle? true})))) + +(def key->handler + {:pos-sales (wrap-client-redirect-unauthenticated (wrap-secure page)) + :pos-sales-table (wrap-client-redirect-unauthenticated (wrap-secure table))}) diff --git a/src/cljc/auto_ap/ssr_routes.cljc b/src/cljc/auto_ap/ssr_routes.cljc index cd0d1949..c6698efc 100644 --- a/src/cljc/auto_ap/ssr_routes.cljc +++ b/src/cljc/auto_ap/ssr_routes.cljc @@ -19,6 +19,8 @@ ["/disapprove/" [#"\d+" :transaction-id]] {:delete :transaction-insight-disapprove} ["/rows/" [#"\d+" :after]] {:get :transaction-insight-rows} ["/explain/" [#"\d+" :transaction-id]] {:get :transaction-insight-explain}}} + "pos" {"/sales" {"" {:get :pos-sales} + "/table" {:get :pos-sales-table}}} "company" {"" :company "/dropdown" :company-dropdown-search-results "/active" {:put :active-client} From e23cc6e8fdf269c19d9b494f12e30ed60d9f4449 Mon Sep 17 00:00:00 2001 From: Bryce Date: Sat, 23 Sep 2023 07:13:32 -0700 Subject: [PATCH 02/13] progress on ssr sales. --- src/clj/auto_ap/ssr/grid_page_helper.clj | 36 +++++++++--------- src/clj/auto_ap/ssr/pos/sales_orders.clj | 47 ++++++++++++++++++++---- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index e104e602..5f62001a 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -64,10 +64,7 @@ per-page (or per-page 30) [entities total] ((:fetch-page grid-spec) user - {:start start - :per-page per-page - :clients clients - :sort sort})] + params)] (com/data-grid-card {:id (:id grid-spec) :title (:title grid-spec) :route (:route grid-spec) @@ -162,7 +159,7 @@ (defn params->query-string [q] (-> q - (dissoc :client :session :client-selection :clients) + (dissoc :client :session :client-selection :clients :query-params) (update :sort sort->query) (url/map->query))) @@ -177,6 +174,7 @@ hx-sort (assoc :sort (parse-sort grid-spec hx-sort)) q-sort (assoc :sort (parse-sort grid-spec q-sort)) (not-empty q-toggle-sort) (update :sort #(toggle-sort grid-spec % q-toggle-sort) ) + (seq query-params) (assoc :query-params query-params) (:session request) (assoc :session (:session request)) (:client-selection (:session request)) (assoc :client-selection (:client-selection (:session request))) (:clients request) (assoc :clients (:clients request)) @@ -193,15 +191,19 @@ :headers {"hx-push-url" (str "?" query-string)}))) (defn page [grid-spec {:keys [identity] :as request}] - (base-page - request - (com/page {:nav (:nav grid-spec) - :client-selection (:client-selection (:session request)) - :clients (:clients request) - :client (:client request) - :identity (:identity request)} - (apply com/breadcrumbs {} (:breadcrumbs grid-spec)) - (table* grid-spec - identity - (extract-params grid-spec request))) - (:title grid-spec))) + (let [params (extract-params grid-spec request)] + (base-page + request + (com/page {:nav (:nav grid-spec) + :page-specific (when-let [page-specific-nav (:page-specific-nav grid-spec)] + (page-specific-nav params)) + :client-selection (:client-selection (:session request)) + :clients (:clients request) + :client (:client request) + :identity (:identity request)} + (apply com/breadcrumbs {} (:breadcrumbs grid-spec)) + (table* grid-spec + identity + params + )) + (:title grid-spec)))) diff --git a/src/clj/auto_ap/ssr/pos/sales_orders.clj b/src/clj/auto_ap/ssr/pos/sales_orders.clj index 086b04c7..2276cd48 100644 --- a/src/clj/auto_ap/ssr/pos/sales_orders.clj +++ b/src/clj/auto_ap/ssr/pos/sales_orders.clj @@ -11,13 +11,44 @@ [bidi.bidi :as bidi] [clj-time.coerce :as coerce])) -(def grid-page {:id "sales-table" - :nav (com/main-aside-nav) - :id-fn :db/id - :fetch-page (fn [user args] - (d-sales/get-graphql (-> args - (assoc :date-range {:start (coerce/to-date-time #inst "2023-05-01")})))) - :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes +(defn filters [params] + [:form {"hx-trigger" "change delay:1000ms" + "hx-get" (bidi/path-for ssr-routes/only-routes + :pos-sales-table) + "hx-target" "#sales-table"} + [:div.space-y-6 + [:div + (com/field {:label "Starting"} + (com/date-input {:name "start-date" + :value (:start-date params) + :placeholder "Date"}))] + [:div + (com/field {:label "Ending"} + (com/date-input {:name "end-date" + :value nil + :placeholder "Date"}))] + [:div + (com/field {:label "Total"} + (com/money-input {:name "total-gte" + :value nil + :placeholder "Total >="}))]]]) + +(def grid-page {:id "sales-table" + :nav (com/main-aside-nav) + :page-specific-nav filters + :id-fn :db/id + :fetch-page (fn [user args] + (d-sales/get-graphql (-> args + (assoc :date-range {:start (some-> args + :query-params + (get "start-date") + (atime/parse atime/iso-date)) + :end (some-> args + :query-params + (get "end-date") + (atime/parse atime/iso-date))} + :total-gte (some-> args :query-params (get "total-gte") (#(if (string? %) (Double/parseDouble %) (double %)))))))) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "POS"] @@ -40,7 +71,7 @@ :request-method :delete)) :hx-target "closest tr"} svg/trash))])) - :headers [ + :headers [ {:key "client" :name "Client" :sort-key "client" From d5565f7cf8be7aaab3a17b480545106e3e1d84b9 Mon Sep 17 00:00:00 2001 From: Bryce Date: Mon, 25 Sep 2023 16:26:42 -0700 Subject: [PATCH 03/13] Revisions on grid page to make parameters easier to grok --- src/clj/auto_ap/datomic.clj | 10 +-- src/clj/auto_ap/datomic/reports.clj | 27 ++++---- src/clj/auto_ap/datomic/yodlee2.clj | 3 +- src/clj/auto_ap/ssr/company/company_1099.clj | 3 +- src/clj/auto_ap/ssr/company/plaid.clj | 11 ++- src/clj/auto_ap/ssr/company/reports.clj | 8 ++- src/clj/auto_ap/ssr/company/yodlee.clj | 7 +- src/clj/auto_ap/ssr/grid_page_helper.clj | 70 +++++++++++--------- src/clj/auto_ap/ssr/pos/sales_orders.clj | 57 +++++++--------- 9 files changed, 107 insertions(+), 89 deletions(-) diff --git a/src/clj/auto_ap/datomic.clj b/src/clj/auto_ap/datomic.clj index bfc086f2..6ad41259 100644 --- a/src/clj/auto_ap/datomic.clj +++ b/src/clj/auto_ap/datomic.clj @@ -623,14 +623,16 @@ (defn apply-pagination-raw [args results] {:entries (->> results - (drop (:start args 0)) - (take (:count args (or (:per-page args) default-pagination-size)))) + (drop (or (:start args) 0)) + (take (or (:count args) (:per-page args) default-pagination-size))) :count (count results)}) (defn apply-pagination [args results] {:ids (->> results - (drop (:start args 0)) - (take (:count args (or (:per-page args) default-pagination-size))) + (drop (or (:start args) 0)) + (take (or (:count args ) + (:per-page args) + default-pagination-size)) (map last)) :count (count results)}) diff --git a/src/clj/auto_ap/datomic/reports.clj b/src/clj/auto_ap/datomic/reports.clj index 2f030134..4cf84daa 100644 --- a/src/clj/auto_ap/datomic/reports.clj +++ b/src/clj/auto_ap/datomic/reports.clj @@ -10,7 +10,8 @@ query2]] [auto-ap.graphql.utils :refer [can-see-client? extract-client-ids]] [clj-time.coerce :as c] - [datomic.api :as dc])) + [datomic.api :as dc] + [clojure.set :as set])) (def default-read '[:db/id :report/client :report/created :report/url :report/name :report/creator]) @@ -21,16 +22,11 @@ (when (:client-code args) [:client/code (:client-code args)])) query (cond-> {:query {:find [] - :in ['$ ] - :where []} - :args [db]} + :in '[$ [?c ...]] + :where '[[?e :report/client ?c]]} + :args [db valid-clients]} - (seq (:clients args)) - (merge-query {:query {:in ['[?xx ...]] - :where ['[?e :report/client ?xx]]} - :args [valid-clients]}) - (:sort args) (add-sorter-fields {"client" ['[?e :report/client ?c] '[?c :client/name ?sort-client]] "created" ['[?e :report/created ?sort-created]] @@ -48,16 +44,21 @@ (defn graphql-results [ids db args] (let [results (->> (pull-many db default-read ids) (map #(update % :report/created c/from-date)) - (group-by :db/id))] + (group-by :db/id)) + valid-clients (extract-client-ids (:clients args) + (:client-id args) + (when (:client-code args) + [:client/code (:client-code args)]))] (->> ids (map results) (filter identity) (map first) (filter (fn [r] - (every? - #(can-see-client? (:id args) %) - (map :db/id (:report/client r)))))))) + (let [used-clients (set (map :db/id (:report/client r)))] + (= used-clients + (set/intersection valid-clients + used-clients)))))))) (defn get-graphql [args] (let [db (dc/db conn) diff --git a/src/clj/auto_ap/datomic/yodlee2.clj b/src/clj/auto_ap/datomic/yodlee2.clj index ea65ae13..28a2a160 100644 --- a/src/clj/auto_ap/datomic/yodlee2.clj +++ b/src/clj/auto_ap/datomic/yodlee2.clj @@ -40,7 +40,8 @@ '[?client-id :client/code ?client-code]]} :args [ (:client-code args)]}) - (:sort args) (add-sorter-fields {"status" ['[?e :yodlee-provider-account/status ?sort-status]]} + (:sort args) (add-sorter-fields {"status" ['[?e :yodlee-provider-account/status ?sort-status]] + "last-updated" ['[?e :yodlee-provider-account/last-updated ?sort-last-updated]]} args) true (merge-query {:query {:find ['?e ] diff --git a/src/clj/auto_ap/ssr/company/company_1099.clj b/src/clj/auto_ap/ssr/company/company_1099.clj index a2a6ef91..6669999d 100644 --- a/src/clj/auto_ap/ssr/company/company_1099.clj +++ b/src/clj/auto_ap/ssr/company/company_1099.clj @@ -111,7 +111,8 @@ (sort-by (fn [[client _ amount]] [(:client/code client ) amount])) (into [])) - paginated (apply-pagination-raw args all)] + paginated (apply-pagination-raw {:start (:start (:parsed-query-params args)) + :per-page (:per-page (:parsed-query-params args))} all)] [(:entries paginated) (:count paginated)])) (def grid-page {:id "vendor-table" diff --git a/src/clj/auto_ap/ssr/company/plaid.clj b/src/clj/auto_ap/ssr/company/plaid.clj index 16ab4255..c65fe813 100644 --- a/src/clj/auto_ap/ssr/company/plaid.clj +++ b/src/clj/auto_ap/ssr/company/plaid.clj @@ -142,7 +142,12 @@ :nav (com/company-aside-nav) :id-fn :db/id :fetch-page (fn [user args] - (get-page (assoc args :id user))) + (get-page {:client (:client args) + :clients (:clients args) + :start (:start (:parsed-query-params args)) + :sort (:sort (:parsed-query-params args)) + :per-page (:per-page (:parsed-query-params args)) + })) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "My Company"] @@ -175,8 +180,8 @@ "Reauthenticate")]]) :headers [{:key "plaid-item" :name "Plaid Item" - :sort-key "id" - :render :db/id} + :sort-key "external-id" + :render :plaid-item/external-id} {:key "status" :name "Status" :sort-key "status" diff --git a/src/clj/auto_ap/ssr/company/reports.clj b/src/clj/auto_ap/ssr/company/reports.clj index 62c93e72..ebb00243 100644 --- a/src/clj/auto_ap/ssr/company/reports.clj +++ b/src/clj/auto_ap/ssr/company/reports.clj @@ -19,7 +19,13 @@ :nav (com/company-aside-nav) :id-fn :db/id :fetch-page (fn [user args] - (r/get-graphql (into args {:id user}))) + (prn args) + (r/get-graphql {:client (:client args) + :clients (:clients args) + :start (:start (:parsed-query-params args)) + :per-page (:per-page (:parsed-query-params args)) + :sort (:sort (:parsed-query-params args)) + })) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "My Company"] diff --git a/src/clj/auto_ap/ssr/company/yodlee.clj b/src/clj/auto_ap/ssr/company/yodlee.clj index 3078c6f5..e137f176 100644 --- a/src/clj/auto_ap/ssr/company/yodlee.clj +++ b/src/clj/auto_ap/ssr/company/yodlee.clj @@ -81,7 +81,12 @@ fastlink.open({fastLinkURL: '%s', :nav (com/company-aside-nav) :id-fn :db/id :fetch-page (fn [user args] - (yodlee2/get-graphql (assoc args :id user))) + (yodlee2/get-graphql + {:client (:client args) + :clients (:clients args) + :sort (:sort (:parsed-query-params args)) + :start (:start (:parsed-query-params args)) + :per-page (:per-page (:parsed-query-params args))})) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "My Company"] diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index 5f62001a..14d24d3e 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -59,7 +59,7 @@ )) "default sort")) -(defn table* [grid-spec user {:keys [start per-page clients flash-id sort] :as params}] +(defn table* [grid-spec user {{:keys [start per-page clients flash-id sort]} :parsed-query-params :as params}] (let [start (or start 0) per-page (or per-page 30) [entities total] ((:fetch-page grid-spec) @@ -119,16 +119,20 @@ (defn parse-sort [grid-spec q] (if (not-empty q) - (into [] - (map (fn [k] - (let [[k v] (str/split k #":")] - {:sort-key (str k) - :asc (boolean (= "asc" v)) - :name (:name (first (filter #(= (str k) (:sort-key %)) (:headers grid-spec)))) - :sort-icon (if (= (boolean (= "asc" v)) true) - svg/sort-down - svg/sort-up)})) - (str/split q #","))) + (->> + (str/split q #",") + (map (fn [k] + (let [[key asc?] (str/split k #":") + matching-header (first (filter #(= (str key) (:sort-key %)) (:headers grid-spec)))] + {:sort-key (str key) + :asc (boolean (= "asc" asc?)) + :matching-header matching-header + :name (:name matching-header) + :sort-icon (if (= (boolean (= "asc" asc?)) true) + svg/sort-down + svg/sort-up)}))) + (filter :matching-header) + (into [])) [])) (defn toggle-sort [grid-spec q k] @@ -159,36 +163,38 @@ (defn params->query-string [q] (-> q - (dissoc :client :session :client-selection :clients :query-params) + :parsed-query-params (update :sort sort->query) (url/map->query))) (defn extract-params [grid-spec {:keys [query-params hx-query-params identity session] :as request}] (let [{hx-start "start" hx-per-page "per-page" hx-sort "sort" } hx-query-params - {q-start "start" q-per-page "per-page" q-sort "sort" q-toggle-sort "toggle-sort"} query-params] - (cond-> {} - hx-start (assoc :start (some-> hx-start not-empty (Long/parseLong ))) - q-start (assoc :start (some-> q-start not-empty (Long/parseLong ))) - hx-per-page (assoc :per-page (some-> hx-per-page not-empty (Long/parseLong ))) - q-per-page (assoc :per-page (some-> q-per-page not-empty (Long/parseLong ))) - hx-sort (assoc :sort (parse-sort grid-spec hx-sort)) - q-sort (assoc :sort (parse-sort grid-spec q-sort)) - (not-empty q-toggle-sort) (update :sort #(toggle-sort grid-spec % q-toggle-sort) ) - (seq query-params) (assoc :query-params query-params) - (:session request) (assoc :session (:session request)) - (:client-selection (:session request)) (assoc :client-selection (:client-selection (:session request))) - (:clients request) (assoc :clients (:clients request)) - (:client request) (assoc :client (:client request))))) + {q-start "start" q-per-page "per-page" q-sort "sort" q-toggle-sort "toggle-sort"} query-params + raw-query-params (merge (or hx-query-params {}) query-params) + + parsed-query-params (cond-> (into {} (map (fn [[k v]] [(keyword k) v]) raw-query-params)) + hx-start (assoc :start (some-> hx-start not-empty (Long/parseLong ))) + q-start (assoc :start (some-> q-start not-empty (Long/parseLong ))) + hx-per-page (assoc :per-page (some-> hx-per-page not-empty (Long/parseLong ))) + q-per-page (assoc :per-page (some-> q-per-page not-empty (Long/parseLong ))) + hx-sort (assoc :sort (doto (parse-sort grid-spec hx-sort) println)) + q-sort (assoc :sort (doto (parse-sort grid-spec q-sort) println )) + (not-empty q-toggle-sort) (update :sort #(toggle-sort grid-spec % q-toggle-sort) ) + true (dissoc :toggle-sort))] + {:raw-query-params raw-query-params + :parsed-query-params parsed-query-params + :client-selection (:client-selection (:session request)) + :clients (:clients request) + :client (:client request) + :request request})) -(defn table [grid-spec {:keys [query-params hx-query-params identity session] :as request}] - (let [params (extract-params grid-spec request) - query-string (params->query-string params)] +(defn table [grid-spec {:keys [identity] :as request}] + (let [params (extract-params grid-spec request)] (html-response (table* grid-spec identity - params - ) - :headers {"hx-push-url" (str "?" query-string)}))) + params) + :headers {"hx-push-url" (str "?" (params->query-string params))}))) (defn page [grid-spec {:keys [identity] :as request}] (let [params (extract-params grid-spec request)] diff --git a/src/clj/auto_ap/ssr/pos/sales_orders.clj b/src/clj/auto_ap/ssr/pos/sales_orders.clj index 2276cd48..03cc6b3b 100644 --- a/src/clj/auto_ap/ssr/pos/sales_orders.clj +++ b/src/clj/auto_ap/ssr/pos/sales_orders.clj @@ -11,6 +11,12 @@ [bidi.bidi :as bidi] [clj-time.coerce :as coerce])) +;; TODO more filters +;; TODO refunds +;; TODO expected deposits +;; TODO navigate between pages shouldnt copy sort if not applicable +;; TODO remove sort button should work + (defn filters [params] [:form {"hx-trigger" "change delay:1000ms" "hx-get" (bidi/path-for ssr-routes/only-routes @@ -20,17 +26,17 @@ [:div (com/field {:label "Starting"} (com/date-input {:name "start-date" - :value (:start-date params) + :value (:start-date (:parsed-query-params params)) :placeholder "Date"}))] [:div (com/field {:label "Ending"} (com/date-input {:name "end-date" - :value nil + :value (:end-date (:parsed-query-params params)) :placeholder "Date"}))] [:div (com/field {:label "Total"} (com/money-input {:name "total-gte" - :value nil + :value (:total-gte (:parsed-query-params params)) :placeholder "Total >="}))]]]) (def grid-page {:id "sales-table" @@ -38,18 +44,22 @@ :page-specific-nav filters :id-fn :db/id :fetch-page (fn [user args] - (d-sales/get-graphql (-> args - (assoc :date-range {:start (some-> args - :query-params - (get "start-date") - (atime/parse atime/iso-date)) - :end (some-> args - :query-params - (get "end-date") - (atime/parse atime/iso-date))} - :total-gte (some-> args :query-params (get "total-gte") (#(if (string? %) (Double/parseDouble %) (double %)))))))) + (d-sales/get-graphql + {:clients (:clients args) + :start (:start (:parsed-query-params args)) + :sort (:sort (:parsed-query-params args)) + :per-page (:per-page (:parsed-query-params args)) + :date-range {:start (some-> args + :raw-query-params + (get "start-date") + (atime/parse atime/iso-date)) + :end (some-> args + :raw-query-params + (get "end-date") + (atime/parse atime/iso-date))} + :total-gte (some-> args :raw-query-params (get "total-gte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) })) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes - :company)} + :company)} "POS"] [:a {:href (bidi/path-for ssr-routes/only-routes @@ -114,25 +124,6 @@ (def table (partial helper/table grid-page)) (def page (partial helper/page grid-page)) -#_(defn delete-report [{:keys [form-params identity]}] - - (let [[id-to-delete key] (first (dc/q '[:find ?i ?k - :in $ ?i - :where [?i :report/key ?k]] - (dc/db conn) - (some-> (get form-params "id") not-empty Long/parseLong))) - report (dc/pull (dc/db conn) r/default-read id-to-delete)] - (assert-can-see-client identity (:report/client report)) - (when id-to-delete - (s3/delete-object :bucket-name (:data-bucket env) - :key key) - @(dc/transact conn [[:db/retractEntity id-to-delete]])) - (html-response - (row* identity - report - {:flash? true - :delete-after-settle? true})))) - (def key->handler {:pos-sales (wrap-client-redirect-unauthenticated (wrap-secure page)) :pos-sales-table (wrap-client-redirect-unauthenticated (wrap-secure table))}) From 94ef7ebf03777018830499eec8aab42bb9f6d28b Mon Sep 17 00:00:00 2001 From: Bryce Date: Mon, 25 Sep 2023 23:44:35 -0700 Subject: [PATCH 04/13] progress for sales --- resources/public/output.css | 55 +++++-- src/clj/auto_ap/datomic/sales_orders.clj | 5 + src/clj/auto_ap/ssr/components.clj | 4 +- src/clj/auto_ap/ssr/components/radio.clj | 13 ++ src/clj/auto_ap/ssr/grid_page_helper.clj | 20 ++- src/clj/auto_ap/ssr/pos/sales_orders.clj | 193 +++++++++++++++-------- 6 files changed, 211 insertions(+), 79 deletions(-) create mode 100644 src/clj/auto_ap/ssr/components/radio.clj diff --git a/resources/public/output.css b/resources/public/output.css index bf55e1ee..24a68b5c 100644 --- a/resources/public/output.css +++ b/resources/public/output.css @@ -1368,6 +1368,10 @@ input:checked + .toggle-bg { width: 100%; } +.w-48 { + width: 12rem; +} + .max-w-2xl { max-width: 42rem; } @@ -1520,6 +1524,10 @@ input:checked + .toggle-bg { align-items: center; } +.items-baseline { + align-items: baseline; +} + .items-stretch { align-items: stretch; } @@ -1677,6 +1685,11 @@ input:checked + .toggle-bg { border-top-right-radius: 0.25rem; } +.rounded-t-lg { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + .border { border-width: 1px; } @@ -1835,16 +1848,16 @@ input:checked + .toggle-bg { background-color: rgb(255 154 154 / var(--tw-bg-opacity)); } +.bg-red-300 { + --tw-bg-opacity: 1; + background-color: rgb(255 104 104 / var(--tw-bg-opacity)); +} + .bg-red-50 { --tw-bg-opacity: 1; background-color: rgb(255 230 230 / var(--tw-bg-opacity)); } -.bg-red-500 { - --tw-bg-opacity: 1; - background-color: rgb(255 3 3 / var(--tw-bg-opacity)); -} - .bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); @@ -1859,15 +1872,15 @@ input:checked + .toggle-bg { background-color: rgb(253 246 178 / var(--tw-bg-opacity)); } -.bg-red-300 { - --tw-bg-opacity: 1; - background-color: rgb(255 104 104 / var(--tw-bg-opacity)); -} - .bg-opacity-50 { --tw-bg-opacity: 0.5; } +.object-contain { + -o-object-fit: contain; + object-fit: contain; +} + .p-1 { padding: 0.25rem; } @@ -2012,6 +2025,10 @@ input:checked + .toggle-bg { text-align: right; } +.align-baseline { + vertical-align: baseline; +} + .text-2xl { font-size: 1.5rem; line-height: 2rem; @@ -2078,6 +2095,11 @@ input:checked + .toggle-bg { line-height: 1.25; } +.text-black { + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); +} + .text-blue-400 { --tw-text-opacity: 1; color: rgb(51 176 238 / var(--tw-text-opacity)); @@ -2758,6 +2780,10 @@ input:checked + .toggle-bg { --tw-ring-offset-color: #1F2937; } +:is(.dark .dark\:ring-offset-gray-700) { + --tw-ring-offset-color: #374151; +} + :is(.dark .dark\:hover\:bg-blue-600:hover) { --tw-bg-opacity: 1; background-color: rgb(0 125 187 / var(--tw-bg-opacity)); @@ -2858,6 +2884,15 @@ input:checked + .toggle-bg { --tw-ring-color: rgb(97 145 37 / var(--tw-ring-opacity)); } +:is(.dark .dark\:focus\:ring-blue-600:focus) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(0 125 187 / var(--tw-ring-opacity)); +} + +:is(.dark .dark\:focus\:ring-offset-gray-700:focus) { + --tw-ring-offset-color: #374151; +} + :is(.dark .group:hover .dark\:group-hover\:text-white) { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); diff --git a/src/clj/auto_ap/datomic/sales_orders.clj b/src/clj/auto_ap/datomic/sales_orders.clj index 36c5fe00..09f9c409 100644 --- a/src/clj/auto_ap/datomic/sales_orders.clj +++ b/src/clj/auto_ap/datomic/sales_orders.clj @@ -164,3 +164,8 @@ matching-count (summarize-orders ids-to-retrieve)])) +(defn summarize-graphql [args] + (let [db (dc/db conn) + {ids-to-retrieve :ids matching-count :count} (mu/trace ::get-sales-order-ids [] (raw-graphql-ids db args))] + (summarize-orders ids-to-retrieve))) + diff --git a/src/clj/auto_ap/ssr/components.clj b/src/clj/auto_ap/ssr/components.clj index 5df4f447..9f9e1484 100644 --- a/src/clj/auto_ap/ssr/components.clj +++ b/src/clj/auto_ap/ssr/components.clj @@ -9,7 +9,8 @@ [auto-ap.ssr.components.page :as page] [auto-ap.ssr.components.data-grid :as data-grid] [auto-ap.ssr.components.tags :as tags] - [auto-ap.ssr.components.paginator :as paginator])) + [auto-ap.ssr.components.paginator :as paginator] + [auto-ap.ssr.components.radio :as radio])) (def breadcrumbs breadcrumbs/breadcrumbs-) @@ -36,6 +37,7 @@ (def navbar navbar/navbar-) (def page page/page-) +(def radio radio/radio-) (def pill tags/pill-) (def badge tags/badge-) diff --git a/src/clj/auto_ap/ssr/components/radio.clj b/src/clj/auto_ap/ssr/components/radio.clj new file mode 100644 index 00000000..150ef0fb --- /dev/null +++ b/src/clj/auto_ap/ssr/components/radio.clj @@ -0,0 +1,13 @@ +(ns auto-ap.ssr.components.radio) + +(defn radio- [{:keys [options name title]}] + [:h3 {:class "mb-4 font-semibold text-gray-900 dark:text-white"} title] + [:ul {:class "w-48 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white"} + (for [{:keys [value content]} options] + [:li {:class "w-full border-b border-gray-200 rounded-t-lg dark:border-gray-600"} + [:div {:class "flex items-center pl-3"} + [:input {:id (str "list-" name "-" value) + :type "radio", + :value value + :name name :class "w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"}] + [:label {:for (str "list-" name "-" value) :class "w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"} content]]])]) diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index 14d24d3e..357c7986 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -44,17 +44,24 @@ first :sort-icon)) -(defn sort-by-list [sort] +(defn sort-by-list [grid-spec sort] (if (seq sort) (into [:div.flex.gap-2.items-center + "sorted by" ] - (for [{:keys [name sort-icon ]} sort] + (for [{:keys [name sort-icon sort-key ]} sort] [:div.py-1.px-3.text-sm.rounded.bg-gray-100.dark:bg-gray-600.flex.items-center.gap-2.relative name [:div.h-4.w-4.mr-3 sort-icon] [:div {:class "absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white hover:scale-110 transition-all duration-300 bg-gray-400 border-2 border-white rounded-full -top-2 -right-2 dark:border-gray-900"} - [:div.h-4.w-4 svg/x] + [:a {:href (str (bidi/path-for ssr-routes/only-routes + (:route grid-spec)) "?remove-sort=" sort-key) + :hx-boost "true" + :hx-target (str "#" (:id grid-spec)) + + } + [:div.h-4.w-4 svg/x]] ]] )) "default sort")) @@ -73,7 +80,7 @@ :total total :subtitle [:div.flex.items-center.gap-2 [:span (format "Total %s: %d, " (:entity-name grid-spec) total)] - (sort-by-list sort)] + (sort-by-list grid-spec sort)] :action-buttons ((:action-buttons grid-spec) user params) :rows (for [entity entities] (row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity)) :params params})) @@ -169,7 +176,7 @@ (defn extract-params [grid-spec {:keys [query-params hx-query-params identity session] :as request}] (let [{hx-start "start" hx-per-page "per-page" hx-sort "sort" } hx-query-params - {q-start "start" q-per-page "per-page" q-sort "sort" q-toggle-sort "toggle-sort"} query-params + {q-start "start" q-per-page "per-page" q-sort "sort" q-toggle-sort "toggle-sort" q-remove-sort "remove-sort"} query-params raw-query-params (merge (or hx-query-params {}) query-params) parsed-query-params (cond-> (into {} (map (fn [[k v]] [(keyword k) v]) raw-query-params)) @@ -180,7 +187,8 @@ hx-sort (assoc :sort (doto (parse-sort grid-spec hx-sort) println)) q-sort (assoc :sort (doto (parse-sort grid-spec q-sort) println )) (not-empty q-toggle-sort) (update :sort #(toggle-sort grid-spec % q-toggle-sort) ) - true (dissoc :toggle-sort))] + (not-empty q-remove-sort) (update :sort (fn [s] (filter (comp (complement #{q-remove-sort}) :sort-key) s)) ) + true (dissoc :toggle-sort :remove-sort))] {:raw-query-params raw-query-params :parsed-query-params parsed-query-params :client-selection (:client-selection (:session request)) diff --git a/src/clj/auto_ap/ssr/pos/sales_orders.clj b/src/clj/auto_ap/ssr/pos/sales_orders.clj index 03cc6b3b..48dbb610 100644 --- a/src/clj/auto_ap/ssr/pos/sales_orders.clj +++ b/src/clj/auto_ap/ssr/pos/sales_orders.clj @@ -11,11 +11,11 @@ [bidi.bidi :as bidi] [clj-time.coerce :as coerce])) -;; TODO more filters ;; TODO refunds ;; TODO expected deposits -;; TODO navigate between pages shouldnt copy sort if not applicable -;; TODO remove sort button should work +;; TODO loading screen +;; TODO default date range +;; always should be fast (defn filters [params] [:form {"hx-trigger" "change delay:1000ms" @@ -33,11 +33,84 @@ (com/date-input {:name "end-date" :value (:end-date (:parsed-query-params params)) :placeholder "Date"}))] - [:div + [:div (com/field {:label "Total"} - (com/money-input {:name "total-gte" - :value (:total-gte (:parsed-query-params params)) - :placeholder "Total >="}))]]]) + [:div.flex.space-x-4.items-baseline + (com/money-input {:name "total-gte" + :value (:total-gte (:parsed-query-params params)) + :placeholder "0.01"}) + [:div.align-baseline + "to"] + (com/money-input {:name "total-lte" + :value (:total-lte (:parsed-query-params params)) + :placeholder "9999.34"})])] + [:div + (com/field {:label "Payment Method"} + (com/radio {:name "payment-method" + :options [{:value "all" + :content "All"} + {:value "cash" + :content "Cash"} + {:value "card" + :content "Card"} + {:value "gift-card" + :content "Gift Card"} + {:value "other" + :content "Other"} + ]}))] + [:div + + (com/field {:label "Processor"} + (com/radio {:name "processor" + :options [{:value "" + :content "All"} + {:value "square" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/square.png" :style {:width "16px" :height "16px"}}] [:div "Square"]]} + {:value "doordash" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/doordash.png" :style {:width "16px" :height "16px"}}] [:div "Doordash"]]} + {:value "uber-eats" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/ubereats.png" :style {:width "16px" :height "16px"}}] [:div "Uber eats"]]} + {:value "grubhub" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/grubhub.png" :style {:width "16px" :height "16px"}}] [:div "Grubhub"]]} + {:value "koala" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/koala.png" :style {:width "16px" :height "16px"}}] [:div "Koala"]]} + {:value "ezcater" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/ezcater.png" :style {:width "16px" :height "16px"}}] [:div "EZCater"]]} + {:value "na" + :content "No Processor"} + ]}))] + + [:div + (com/field {:label "Total"} + (com/text-input {:name "category" + :value (:category (:parsed-query-params params)) + :placeholder "Fries"}))]]]) + +(defn args->graphql-params [args] + {:clients (:clients args) + :start (:start (:parsed-query-params args)) + :sort (:sort (:parsed-query-params args)) + :per-page (:per-page (:parsed-query-params args)) + :category (not-empty (:category (:parsed-query-params args))) + :date-range {:start (some-> args + :raw-query-params + (get "start-date") + (atime/parse atime/iso-date)) + :end (some-> args + :raw-query-params + (get "end-date") + (atime/parse atime/iso-date))} + :total-gte (some-> args :raw-query-params (get "total-gte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) + :total-lte (some-> args :raw-query-params (get "total-lte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) + :type-name (condp = (:payment-method (:parsed-query-params args)) + "cash" "CASH" + "" nil + "all" nil + "card" "CARD" + "gift-card" "SQUARE_GIFT_CARD" + "other" "OTHER" + nil) + :processor (some-> args :parsed-query-params :processor not-empty keyword)}) (def grid-page {:id "sales-table" :nav (com/main-aside-nav) @@ -45,31 +118,27 @@ :id-fn :db/id :fetch-page (fn [user args] (d-sales/get-graphql - {:clients (:clients args) - :start (:start (:parsed-query-params args)) - :sort (:sort (:parsed-query-params args)) - :per-page (:per-page (:parsed-query-params args)) - :date-range {:start (some-> args - :raw-query-params - (get "start-date") - (atime/parse atime/iso-date)) - :end (some-> args - :raw-query-params - (get "end-date") - (atime/parse atime/iso-date))} - :total-gte (some-> args :raw-query-params (get "total-gte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) })) + (args->graphql-params args) + )) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} - "POS"] + "POS"] - [:a {:href (bidi/path-for ssr-routes/only-routes - :pos-sales)} - "Sales"]] + [:a {:href (bidi/path-for ssr-routes/only-routes + :pos-sales)} + "Sales"]] :title "Sales orders" :entity-name "Sales orders" :route :pos-sales-table - :action-buttons (fn [user _] - nil) + :action-buttons (fn [user args] + (let [{:keys [total tax]} (d-sales/summarize-graphql (args->graphql-params args))] + [ + (com/pill {:color :primary} + (format "Total $%.2f" total) + ) + (com/pill {:color :secondary} + (format "Tax $%.2f" tax ) + )])) :row-buttons (fn [user e] (when (:sales-order/reference-link e) @@ -82,42 +151,42 @@ :hx-target "closest tr"} svg/trash))])) :headers [ - {:key "client" - :name "Client" - :sort-key "client" - :hide? (fn [args] - (= (count (:clients args)) 1)) - :render #(-> % :sales-order/client :client/code)} - {:key "date" - :name "Date" - :sort-key "date" - :render #(atime/unparse-local (:sales-order/date %) atime/standard-time)} - {:key "source" - :name "Source" - :sort-key "source" - :render (fn [sales-order] - (when (:sales-order/source sales-order) - (com/pill {:color :primary } - (:sales-order/source sales-order))))} - {:key "total" - :name "Total" - :sort-key "total" - :render #(some->> % :sales-order/total (format "$%.2f"))} - {:key "tax" - :name "Tax" - :sort-key "tax" - :render #(some->> % :sales-order/tax (format "$%.2f"))} - {:key "tip" - :name "Tip" - :sort-key "tip" - :render #(some->> % :sales-order/tip (format "$%.2f"))} - {:key "Payment methods" - :name "Payment Methods" - :render (fn [sales-order] - (println ) - (for [method (->> sales-order :sales-order/charges (map :charge/type-name) set)] - (com/pill {:color :primary } - method)))}]}) + {:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [args] + (= (count (:clients args)) 1)) + :render #(-> % :sales-order/client :client/code)} + {:key "date" + :name "Date" + :sort-key "date" + :render #(atime/unparse-local (:sales-order/date %) atime/standard-time)} + {:key "source" + :name "Source" + :sort-key "source" + :render (fn [sales-order] + (when (:sales-order/source sales-order) + (com/pill {:color :primary } + (:sales-order/source sales-order))))} + {:key "total" + :name "Total" + :sort-key "total" + :render #(some->> % :sales-order/total (format "$%.2f"))} + {:key "tax" + :name "Tax" + :sort-key "tax" + :render #(some->> % :sales-order/tax (format "$%.2f"))} + {:key "tip" + :name "Tip" + :sort-key "tip" + :render #(some->> % :sales-order/tip (format "$%.2f"))} + {:key "Payment methods" + :name "Payment Methods" + :render (fn [sales-order] + (println ) + (for [method (->> sales-order :sales-order/charges (map :charge/type-name) set)] + (com/pill {:color :primary } + method)))}]}) (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page)) From 160c289f0389b080d55ac59cc2579544107b594a Mon Sep 17 00:00:00 2001 From: Bryce Date: Tue, 26 Sep 2023 14:30:21 -0700 Subject: [PATCH 05/13] progress towards sales. --- resources/public/output.css | 114 +++++++++++++++--- src/clj/auto_ap/ssr/components.clj | 2 + src/clj/auto_ap/ssr/components/aside.clj | 5 +- src/clj/auto_ap/ssr/components/buttons.clj | 28 +++++ src/clj/auto_ap/ssr/components/data_grid.clj | 7 +- src/clj/auto_ap/ssr/components/inputs.clj | 29 +++-- src/clj/auto_ap/ssr/components/radio.clj | 20 ++- src/clj/auto_ap/ssr/grid_page_helper.clj | 50 ++++++-- src/clj/auto_ap/ssr/pos/sales_orders.clj | 85 ++++++++----- src/clj/auto_ap/ssr/ui.clj | 17 ++- src/clj/auto_ap/ssr/utils.clj | 11 +- .../auto_ap/views/components/layouts.cljs | 4 +- 12 files changed, 299 insertions(+), 73 deletions(-) diff --git a/resources/public/output.css b/resources/public/output.css index 24a68b5c..360a5025 100644 --- a/resources/public/output.css +++ b/resources/public/output.css @@ -1095,6 +1095,14 @@ input:checked + .toggle-bg { top: 1.25rem; } +.left-1\/2 { + left: 50%; +} + +.top-2\/4 { + top: 50%; +} + .z-10 { z-index: 10; } @@ -1324,6 +1332,10 @@ input:checked + .toggle-bg { height: 100vh; } +.h-56 { + height: 14rem; +} + .max-h-96 { max-height: 24rem; } @@ -1348,6 +1360,10 @@ input:checked + .toggle-bg { width: 1rem; } +.w-48 { + width: 12rem; +} + .w-5 { width: 1.25rem; } @@ -1368,8 +1384,8 @@ input:checked + .toggle-bg { width: 100%; } -.w-48 { - width: 12rem; +.w-56 { + width: 14rem; } .max-w-2xl { @@ -1437,6 +1453,16 @@ input:checked + .toggle-bg { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } +.-translate-x-1\/2 { + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.-translate-y-1\/2 { + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + .rotate-180 { --tw-rotate: 180deg; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -1460,6 +1486,16 @@ input:checked + .toggle-bg { animation: spin 1s linear infinite; } +@keyframes pulse { + 50% { + opacity: .5; + } +} + +.animate-pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + .cursor-default { cursor: default; } @@ -1480,6 +1516,12 @@ input:checked + .toggle-bg { list-style-type: none; } +.appearance-none { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + .grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } @@ -1670,6 +1712,10 @@ input:checked + .toggle-bg { border-radius: 0.5rem; } +.rounded-md { + border-radius: 0.375rem; +} + .rounded-l-lg { border-top-left-radius: 0.5rem; border-bottom-left-radius: 0.5rem; @@ -1876,11 +1922,6 @@ input:checked + .toggle-bg { --tw-bg-opacity: 0.5; } -.object-contain { - -o-object-fit: contain; - object-fit: contain; -} - .p-1 { padding: 0.25rem; } @@ -2095,6 +2136,10 @@ input:checked + .toggle-bg { line-height: 1.25; } +.leading-none { + line-height: 1; +} + .text-black { --tw-text-opacity: 1; color: rgb(0 0 0 / var(--tw-text-opacity)); @@ -2187,6 +2232,14 @@ input:checked + .toggle-bg { opacity: 1; } +.opacity-50 { + opacity: 0.5; +} + +.opacity-5 { + opacity: 0.05; +} + .shadow { --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); @@ -2493,6 +2546,10 @@ input:checked + .toggle-bg { text-decoration-line: underline; } +.focus\:z-10:focus { + z-index: 10; +} + .focus\:border-blue-500:focus { --tw-border-opacity: 1; border-color: rgb(0 156 234 / var(--tw-border-opacity)); @@ -2503,6 +2560,11 @@ input:checked + .toggle-bg { border-color: rgb(121 181 46 / var(--tw-border-opacity)); } +.focus\:text-green-700:focus { + --tw-text-opacity: 1; + color: rgb(73 109 28 / var(--tw-text-opacity)); +} + .focus\:outline-none:focus { outline: 2px solid transparent; outline-offset: 2px; @@ -2555,6 +2617,11 @@ input:checked + .toggle-bg { --tw-ring-color: rgb(175 211 130 / var(--tw-ring-opacity)); } +.focus\:ring-green-700:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(73 109 28 / var(--tw-ring-opacity)); +} + .focus\:ring-primary-500:focus { --tw-ring-opacity: 1; --tw-ring-color: rgb(121 181 46 / var(--tw-ring-opacity)); @@ -2766,6 +2833,11 @@ input:checked + .toggle-bg { color: rgb(250 202 21 / var(--tw-text-opacity)); } +:is(.dark .dark\:text-blue-200) { + --tw-text-opacity: 1; + color: rgb(153 215 247 / var(--tw-text-opacity)); +} + :is(.dark .dark\:placeholder-gray-400)::-moz-placeholder { --tw-placeholder-opacity: 1; color: rgb(156 163 175 / var(--tw-placeholder-opacity)); @@ -2776,14 +2848,14 @@ input:checked + .toggle-bg { color: rgb(156 163 175 / var(--tw-placeholder-opacity)); } -:is(.dark .dark\:ring-offset-gray-800) { - --tw-ring-offset-color: #1F2937; -} - :is(.dark .dark\:ring-offset-gray-700) { --tw-ring-offset-color: #374151; } +:is(.dark .dark\:ring-offset-gray-800) { + --tw-ring-offset-color: #1F2937; +} + :is(.dark .dark\:hover\:bg-blue-600:hover) { --tw-bg-opacity: 1; background-color: rgb(0 125 187 / var(--tw-bg-opacity)); @@ -2854,11 +2926,21 @@ input:checked + .toggle-bg { border-color: rgb(121 181 46 / var(--tw-border-opacity)); } +:is(.dark .dark\:focus\:text-white:focus) { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + :is(.dark .dark\:focus\:ring-blue-500:focus) { --tw-ring-opacity: 1; --tw-ring-color: rgb(0 156 234 / var(--tw-ring-opacity)); } +:is(.dark .dark\:focus\:ring-blue-600:focus) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(0 125 187 / var(--tw-ring-opacity)); +} + :is(.dark .dark\:focus\:ring-blue-800:focus) { --tw-ring-opacity: 1; --tw-ring-color: rgb(0 62 94 / var(--tw-ring-opacity)); @@ -2869,6 +2951,11 @@ input:checked + .toggle-bg { --tw-ring-color: rgb(75 85 99 / var(--tw-ring-opacity)); } +:is(.dark .dark\:focus\:ring-green-500:focus) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(121 181 46 / var(--tw-ring-opacity)); +} + :is(.dark .dark\:focus\:ring-green-800:focus) { --tw-ring-opacity: 1; --tw-ring-color: rgb(48 72 18 / var(--tw-ring-opacity)); @@ -2884,11 +2971,6 @@ input:checked + .toggle-bg { --tw-ring-color: rgb(97 145 37 / var(--tw-ring-opacity)); } -:is(.dark .dark\:focus\:ring-blue-600:focus) { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(0 125 187 / var(--tw-ring-opacity)); -} - :is(.dark .dark\:focus\:ring-offset-gray-700:focus) { --tw-ring-offset-color: #374151; } diff --git a/src/clj/auto_ap/ssr/components.clj b/src/clj/auto_ap/ssr/components.clj index 9f9e1484..6904fafd 100644 --- a/src/clj/auto_ap/ssr/components.clj +++ b/src/clj/auto_ap/ssr/components.clj @@ -18,6 +18,8 @@ (def button-icon buttons/button-icon-) (def icon-button buttons/icon-button-) (def a-icon-button buttons/a-icon-button-) +(def button-group buttons/group-) +(def button-group-button buttons/group-button-) (def modal dialog/modal-) (def modal-card dialog/modal-card-) diff --git a/src/clj/auto_ap/ssr/components/aside.clj b/src/clj/auto_ap/ssr/components/aside.clj index 03b66b65..1d843d2c 100644 --- a/src/clj/auto_ap/ssr/components/aside.clj +++ b/src/clj/auto_ap/ssr/components/aside.clj @@ -178,8 +178,9 @@ :icon svg/receipt-register-1} "Sales") (sub-menu- {:id "dropdown-sales"} - (menu-button- {:href (bidi/path-for client-routes/routes - :sales-orders)} "Sales") + (menu-button- {:href (str (bidi/path-for client-routes/routes + :pos-sales) + "?date-range=week")} "Sales") (menu-button- {:href (bidi/path-for client-routes/routes :expected-deposits)} "Expected Deposits") #_(menu-button- {:href "Sales"} "Cash Shifts") diff --git a/src/clj/auto_ap/ssr/components/buttons.clj b/src/clj/auto_ap/ssr/components/buttons.clj index e323a833..1beef696 100644 --- a/src/clj/auto_ap/ssr/components/buttons.clj +++ b/src/clj/auto_ap/ssr/components/buttons.clj @@ -46,3 +46,31 @@ (svg/spinner {:class "inline w-4 h-4 text-white"}) [:div.ml-3 "Loading..."]] (into [:div.htmx-indicator-hidden ] children)]) + + + +(defn group-button- [{:keys [size] :or {size :normal} :as params} & children] + (into [:button (cond-> params + true (assoc :type (or (:type params) "button")) + true (update :class (fn [c] + (cond-> c + true (str " font-medium text-gray-900 bg-white border border-gray-200 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:ring-2 focus:ring-green-700 focus:text-green-700 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-green-500 dark:focus:text-white") + + (= :small size) + (str " text-xs px-3 py-2") + + (= :normal size) + (str " text-sm px-4 py-2") + ) + )) + true (dissoc :size))] children )) + +(defn group- [{:keys [name]} & children] + (let [children (-> children + vec + (update-in [0 1 :class] str " rounded-l-lg") + (update-in [(dec (count children)) 1 :class] str " rounded-r-lg"))] + (into [:div {:class "inline-flex rounded-md shadow-sm", :role "group" + :hx-on:click "this.querySelector(\"input\").value = event.target.value; this.querySelector(\"input\").dispatchEvent(new Event('change', {bubbles: true}));"} + [:input {:type "hidden" :name name}]] + children))) diff --git a/src/clj/auto_ap/ssr/components/data_grid.clj b/src/clj/auto_ap/ssr/components/data_grid.clj index 5625818e..8e4000d8 100644 --- a/src/clj/auto_ap/ssr/components/data_grid.clj +++ b/src/clj/auto_ap/ssr/components/data_grid.clj @@ -69,6 +69,7 @@ :hx-trigger "clientSelected from:body" :hx-swap "outerHTML swap:300ms" :id id} + (content-card- {} [:div {:class "flex flex-col px-4 py-3 space-y-3 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:space-x-4 text-gray-800 dark:text-gray-100"} @@ -97,4 +98,8 @@ :request-method :get) "?start=" (* page per-page)) :hx-target (str "#" id) - :hx-swap "outerHTML show:#app:top"})})))]) + :hx-swap "outerHTML show:#app:top" + :hx-indicator (str "#" id)})})) + [:div {:class "htmx-indicator absolute -translate-x-1/2 -translate-y-1/2 top-2/4 left-1/2 overflow-hidden w-full h-full"} + [:div {:class "flex items-center justify-center w-full h-full border border-gray-200 rounded-lg bg-gray-50 dark:bg-gray-800 dark:border-gray-700 bg-opacity-50" } + [:div {:class "px-3 py-1 text-xs font-medium leading-none text-center text-blue-800 bg-blue-200 rounded-full animate-pulse dark:bg-blue-900 dark:text-blue-200"} "loading..."]]])]) diff --git a/src/clj/auto_ap/ssr/components/inputs.clj b/src/clj/auto_ap/ssr/components/inputs.clj index e781045a..3b069941 100644 --- a/src/clj/auto_ap/ssr/components/inputs.clj +++ b/src/clj/auto_ap/ssr/components/inputs.clj @@ -11,28 +11,39 @@ (:allow-blank? params) (conj [:option {:value "" :selected (not (:value params))} ""]))] children)) -(defn text-input- [params] +(defn use-size [size] + (if (= :small size) + (str " " "text-xs p-2") + (str " " "text-sm p-2.25"))) +(defn text-input- [{:keys [size] :as params}] [:input - (update params - :class str " bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500") + (-> params + (update + :class str " bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500") + (update :class #(str % (use-size size))) + ) ]) -(defn money-input- [params] +(defn money-input- [{:keys [size] :as params}] [:input (-> params (update - :class str " bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 text-right" + :class str " bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 text-right appearance-none" ) + (update :class #(str % (use-size size))) (assoc :type "number" - :step "0.01")) + :step "0.01") + (dissoc :size)) ]) -(defn date-input- [params] +(defn date-input- [{:keys [size] :as params}] [:input (-> params (update - :class str " bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500") - (assoc :type "date"))]) + :class str " bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500") + (assoc :type "date") + (update :class #(str % (use-size size))) + (dissoc :size))]) (defn field- [params & rest] (into diff --git a/src/clj/auto_ap/ssr/components/radio.clj b/src/clj/auto_ap/ssr/components/radio.clj index 150ef0fb..f9a701a3 100644 --- a/src/clj/auto_ap/ssr/components/radio.clj +++ b/src/clj/auto_ap/ssr/components/radio.clj @@ -1,6 +1,6 @@ (ns auto-ap.ssr.components.radio) -(defn radio- [{:keys [options name title]}] +(defn radio- [{:keys [options name title size] :or {size :medium}}] [:h3 {:class "mb-4 font-semibold text-gray-900 dark:text-white"} title] [:ul {:class "w-48 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white"} (for [{:keys [value content]} options] @@ -9,5 +9,19 @@ [:input {:id (str "list-" name "-" value) :type "radio", :value value - :name name :class "w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"}] - [:label {:for (str "list-" name "-" value) :class "w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"} content]]])]) + :name name + :class + (cond-> "w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500" + (= size :small) + (str " " "text-xs") + + (= size :medium) + (str " " "text-sm"))}] + [:label {:for (str "list-" name "-" value) + :class + (cond-> "w-full ml-2 font-medium text-gray-900 dark:text-gray-300" + (= size :small) + (str " " "text-xs py-2") + + (= size :medium) + (str " " "text-sm py-3"))} content]]])]) diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index 357c7986..e444144f 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -10,7 +10,9 @@ [clojure.string :as str] [auto-ap.ssr.svg :as svg] [unilog.context :as lc] - [com.brunobonacci.mulog :as mu])) + [com.brunobonacci.mulog :as mu] + [auto-ap.time :as atime] + [clj-time.core :as time])) (defn row* [gridspec user entity {:keys [flash? delete-after-settle? params] :as options}] (let [cells (->> gridspec @@ -72,15 +74,16 @@ [entities total] ((:fetch-page grid-spec) user params)] + (com/data-grid-card {:id (:id grid-spec) :title (:title grid-spec) :route (:route grid-spec) :start start :per-page per-page :total total - :subtitle [:div.flex.items-center.gap-2 - [:span (format "Total %s: %d, " (:entity-name grid-spec) total)] - (sort-by-list grid-spec sort)] + :subtitle [:div.flex.items-center.gap-2 + [:span (format "Total %s: %d, " (:entity-name grid-spec) total)] + (sort-by-list grid-spec sort)] :action-buttons ((:action-buttons grid-spec) user params) :rows (for [entity entities] (row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity)) :params params})) @@ -174,9 +177,33 @@ (update :sort sort->query) (url/map->query))) +(defn use-date-range [query-params date-range] + (condp = date-range + "week" + (assoc query-params + :start-date (atime/unparse-local (time/plus (time/now) (time/days -7)) atime/iso-date) + :end-date (atime/unparse-local (time/now) atime/iso-date)) + + "month" + (assoc query-params + :start-date (atime/unparse-local (time/plus (time/now) (time/months -1)) atime/iso-date) + :end-date (atime/unparse-local (time/now) atime/iso-date)) + + "year" + (assoc query-params + :start-date (atime/unparse-local (time/plus (time/now) (time/years -1)) atime/iso-date) + :end-date (atime/unparse-local (time/now) atime/iso-date)) + + "all" + (assoc query-params + :start-date (atime/unparse-local (time/plus (time/now) (time/years -3)) atime/iso-date) + :end-date (atime/unparse-local (time/now) atime/iso-date)) + + query-params)) + (defn extract-params [grid-spec {:keys [query-params hx-query-params identity session] :as request}] (let [{hx-start "start" hx-per-page "per-page" hx-sort "sort" } hx-query-params - {q-start "start" q-per-page "per-page" q-sort "sort" q-toggle-sort "toggle-sort" q-remove-sort "remove-sort"} query-params + {q-start "start" q-per-page "per-page" q-sort "sort" q-toggle-sort "toggle-sort" q-remove-sort "remove-sort" date-range "date-range"} query-params raw-query-params (merge (or hx-query-params {}) query-params) parsed-query-params (cond-> (into {} (map (fn [[k v]] [(keyword k) v]) raw-query-params)) @@ -184,11 +211,12 @@ q-start (assoc :start (some-> q-start not-empty (Long/parseLong ))) hx-per-page (assoc :per-page (some-> hx-per-page not-empty (Long/parseLong ))) q-per-page (assoc :per-page (some-> q-per-page not-empty (Long/parseLong ))) - hx-sort (assoc :sort (doto (parse-sort grid-spec hx-sort) println)) - q-sort (assoc :sort (doto (parse-sort grid-spec q-sort) println )) + hx-sort (assoc :sort (parse-sort grid-spec hx-sort)) + q-sort (assoc :sort (parse-sort grid-spec q-sort)) (not-empty q-toggle-sort) (update :sort #(toggle-sort grid-spec % q-toggle-sort) ) (not-empty q-remove-sort) (update :sort (fn [s] (filter (comp (complement #{q-remove-sort}) :sort-key) s)) ) - true (dissoc :toggle-sort :remove-sort))] + date-range (use-date-range date-range) + true (dissoc :toggle-sort :remove-sort :date-range))] {:raw-query-params raw-query-params :parsed-query-params parsed-query-params :client-selection (:client-selection (:session request)) @@ -202,7 +230,9 @@ grid-spec identity params) - :headers {"hx-push-url" (str "?" (params->query-string params))}))) + :headers {"hx-push-url" (str "?" (params->query-string params))} + :oob (when-let [oob-render (:oob-render grid-spec)] + (oob-render identity params))))) (defn page [grid-spec {:keys [identity] :as request}] (let [params (extract-params grid-spec request)] @@ -210,7 +240,7 @@ request (com/page {:nav (:nav grid-spec) :page-specific (when-let [page-specific-nav (:page-specific-nav grid-spec)] - (page-specific-nav params)) + [:div#page-specific-nav (page-specific-nav params)]) :client-selection (:client-selection (:session request)) :clients (:clients request) :client (:client request) diff --git a/src/clj/auto_ap/ssr/pos/sales_orders.clj b/src/clj/auto_ap/ssr/pos/sales_orders.clj index 48dbb610..24b95c91 100644 --- a/src/clj/auto_ap/ssr/pos/sales_orders.clj +++ b/src/clj/auto_ap/ssr/pos/sales_orders.clj @@ -14,40 +14,65 @@ ;; TODO refunds ;; TODO expected deposits ;; TODO loading screen -;; TODO default date range ;; always should be fast +;; make params parsing composable -(defn filters [params] - [:form {"hx-trigger" "change delay:1000ms" - "hx-get" (bidi/path-for ssr-routes/only-routes - :pos-sales-table) - "hx-target" "#sales-table"} - [:div.space-y-6 - [:div - (com/field {:label "Starting"} +(defn date-range-field* [params] + [:div#date-range {} + (com/field {:label "Date Range"} + [:div.space-y-4 + [:div + (com/button-group {:name "date-range"} + (com/button-group-button {:size :small :value "all" :hx-trigger "click"} "All") + (com/button-group-button {:size :small :value "week" :hx-trigger "click"} "Week") + (com/button-group-button {:size :small :value "month" :hx-trigger "click"} "Month") + (com/button-group-button {:size :small :value "year" :hx-trigger "click"} "Year")) + ] + [:div.flex.space-x-1.items-baseline (com/date-input {:name "start-date" :value (:start-date (:parsed-query-params params)) - :placeholder "Date"}))] - [:div - (com/field {:label "Ending"} + :placeholder "Date" + :size :small}) + (com/date-input {:name "end-date" :value (:end-date (:parsed-query-params params)) - :placeholder "Date"}))] + :placeholder "Date" + :size :small})]] + )]) +(defn filters [params] + [:form {"hx-trigger" "change delay:500ms, keyup from:.hot-filter delay:1000ms" + "hx-get" (bidi/path-for ssr-routes/only-routes + :pos-sales-table) + "hx-target" "#sales-table" + "hx-indicator" "#sales-table" + #_#_:hx-disabled-elt "find fieldset"} + + [:fieldset.space-y-6 + (date-range-field* params) [:div (com/field {:label "Total"} [:div.flex.space-x-4.items-baseline (com/money-input {:name "total-gte" + :id "total-gte" + :hx-preserve "true" + :class "hot-filter" :value (:total-gte (:parsed-query-params params)) - :placeholder "0.01"}) + :placeholder "0.01" + :size :small}) [:div.align-baseline "to"] (com/money-input {:name "total-lte" + :hx-preserve "true" + :id "total-lte" + :class "hot-filter" :value (:total-lte (:parsed-query-params params)) - :placeholder "9999.34"})])] + :placeholder "9999.34" + :size :small})])] [:div (com/field {:label "Payment Method"} - (com/radio {:name "payment-method" - :options [{:value "all" + (com/radio {:size :small + :name "payment-method" + :options [{:value "all" :content "All"} {:value "cash" :content "Cash"} @@ -61,7 +86,8 @@ [:div (com/field {:label "Processor"} - (com/radio {:name "processor" + (com/radio {:size :small + :name "processor" :options [{:value "" :content "All"} {:value "square" @@ -81,10 +107,14 @@ ]}))] [:div - (com/field {:label "Total"} + (com/field {:label "Category"} (com/text-input {:name "category" + :class "hot-filter" + :id "category" + :hx-preserve "true" :value (:category (:parsed-query-params params)) - :placeholder "Fries"}))]]]) + :placeholder "Fries" + :size :small}))]]]) (defn args->graphql-params [args] {:clients (:clients args) @@ -93,12 +123,12 @@ :per-page (:per-page (:parsed-query-params args)) :category (not-empty (:category (:parsed-query-params args))) :date-range {:start (some-> args - :raw-query-params - (get "start-date") + :parsed-query-params + :start-date (atime/parse atime/iso-date)) :end (some-> args - :raw-query-params - (get "end-date") + :parsed-query-params + :end-date (atime/parse atime/iso-date))} :total-gte (some-> args :raw-query-params (get "total-gte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) :total-lte (some-> args :raw-query-params (get "total-lte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) @@ -118,8 +148,10 @@ :id-fn :db/id :fetch-page (fn [user args] (d-sales/get-graphql - (args->graphql-params args) - )) + (args->graphql-params args))) + :oob-render + (fn [user params] + [(assoc-in (date-range-field* params) [1 :hx-swap-oob] true)]) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "POS"] @@ -183,7 +215,6 @@ {:key "Payment methods" :name "Payment Methods" :render (fn [sales-order] - (println ) (for [method (->> sales-order :sales-order/charges (map :charge/type-name) set)] (com/pill {:color :primary } method)))}]}) diff --git a/src/clj/auto_ap/ssr/ui.clj b/src/clj/auto_ap/ssr/ui.clj index c2f72c99..ff836c55 100644 --- a/src/clj/auto_ap/ssr/ui.clj +++ b/src/clj/auto_ap/ssr/ui.clj @@ -30,7 +30,7 @@ #_[:script {:src "https://unpkg.com/htmx.org@1.8.4" :integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV" :crossorigin= "anonymous"}] - [:script {:src "https://unpkg.com/htmx.org@1.9.0/dist/htmx.js" + [:script {:src "https://unpkg.com/htmx.org@1.9.6/dist/htmx.min.js" :crossorigin= "anonymous"}] [:script {:src "https://unpkg.com/htmx.org/dist/ext/debug.js"}] [:script {:src "/js/htmx-disable.js"}] @@ -38,6 +38,21 @@ [:script {:type "text/javascript", :src "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/autoComplete.min.js"}] [:script {:src "https://unpkg.com/dropzone@5.9.3/dist/min/dropzone.min.js"}] [:link {:rel "stylesheet" :href "https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" :type "text/css"}] + [:style + " +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + /* display: none; <- Crashes Chrome on hover */ + -webkit-appearance: none; + margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ +} +input[type=number] { + -moz-appearance:textfield; /* Firefox */ +} + + " + + ] [:body {:hx-ext "disable-submit"} contents [:script {:src "/js/flowbite.min.js"}]]])) diff --git a/src/clj/auto_ap/ssr/utils.clj b/src/clj/auto_ap/ssr/utils.clj index 929d5d64..df5d8968 100644 --- a/src/clj/auto_ap/ssr/utils.clj +++ b/src/clj/auto_ap/ssr/utils.clj @@ -5,14 +5,21 @@ [hiccup2.core :as hiccup] [clojure.string :as str])) -(defn html-response [hiccup & {:keys [status headers] :or {status 200 headers {}}}] +(defn html-response [hiccup & {:keys [status headers oob] :or {status 200 headers {} oob []}}] {:status status :headers (into {"Content-Type" "text/html"} headers) :body (str (hiccup/html {} - hiccup))}) + hiccup) + "\n" + (str/join "\n" + (map (fn [o] + (hiccup/html + {} + o)) + oob)))}) (defn wrap-error-response [handler] (fn [request] diff --git a/src/cljs/auto_ap/views/components/layouts.cljs b/src/cljs/auto_ap/views/components/layouts.cljs index e74cf2e3..1b8c0eb6 100644 --- a/src/cljs/auto_ap/views/components/layouts.cljs +++ b/src/cljs/auto_ap/views/components/layouts.cljs @@ -173,8 +173,8 @@ :href (bidi/path-for routes/routes :payments)} "Payments" ] (when (= "admin" (:user/role @user)) - [:a.navbar-item {:class [(active-when ap = :sales-orders)] - :href (bidi/path-for routes/routes :sales-orders)} + [:a.navbar-item {:class [(active-when ap = :pos-sales)] + :href (str (bidi/path-for ssr-routes/only-routes :pos-sales) "?date-range=week")} "POS" ]) [:a.navbar-item {:class [(active-when ap = :transactions)] :href (bidi/path-for routes/routes :transactions)} From c5c752417b867fcade626d213b0bc3650ef8dc02 Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 27 Sep 2023 09:03:16 -0700 Subject: [PATCH 06/13] Maybe using new datepicker --- resources/public/js/htmx-disable.js | 4 +++ src/clj/auto_ap/ssr/components/inputs.clj | 21 +++++++----- src/clj/auto_ap/ssr/grid_page_helper.clj | 18 +++++----- src/clj/auto_ap/ssr/pos/sales_orders.clj | 41 ++++++++++++----------- src/clj/auto_ap/ssr/ui.clj | 5 ++- 5 files changed, 50 insertions(+), 39 deletions(-) diff --git a/resources/public/js/htmx-disable.js b/resources/public/js/htmx-disable.js index 2920025f..97af300b 100644 --- a/resources/public/js/htmx-disable.js +++ b/resources/public/js/htmx-disable.js @@ -16,3 +16,7 @@ htmx.defineExtension('disable-submit', { */ } }) + +initDatepicker = function(elem) { +elem.dp = new Datepicker(elem, {format: "mm/dd/yyyy", autohide: true}); +} \ No newline at end of file diff --git a/src/clj/auto_ap/ssr/components/inputs.clj b/src/clj/auto_ap/ssr/components/inputs.clj index 3b069941..e9c9fc38 100644 --- a/src/clj/auto_ap/ssr/components/inputs.clj +++ b/src/clj/auto_ap/ssr/components/inputs.clj @@ -1,4 +1,5 @@ -(ns auto-ap.ssr.components.inputs) +(ns auto-ap.ssr.components.inputs + (:require [hiccup2.core :as hiccup])) (defn select- [params & children] (into @@ -37,13 +38,17 @@ ]) (defn date-input- [{:keys [size] :as params}] - [:input - (-> params - (update - :class str " bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500") - (assoc :type "date") - (update :class #(str % (use-size size))) - (dissoc :size))]) + [:div + [:input + (-> params + (update + :class str " bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500") + (assoc :type "text") + (assoc "_" (hiccup/raw "init initDatepicker(me)")) + (assoc "hx-on" (hiccup/raw "changeDate: htmx.trigger(this, \"change\") + htmx:beforeCleanupElement: this.dp.destroy()")) + (update :class #(str % (use-size size))) + (dissoc :size))]]) (defn field- [params & rest] (into diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index e444144f..480dbc46 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -9,8 +9,6 @@ [cemerick.url :as url] [clojure.string :as str] [auto-ap.ssr.svg :as svg] - [unilog.context :as lc] - [com.brunobonacci.mulog :as mu] [auto-ap.time :as atime] [clj-time.core :as time])) @@ -181,23 +179,23 @@ (condp = date-range "week" (assoc query-params - :start-date (atime/unparse-local (time/plus (time/now) (time/days -7)) atime/iso-date) - :end-date (atime/unparse-local (time/now) atime/iso-date)) + :start-date (atime/unparse-local (time/plus (time/now) (time/days -7)) atime/normal-date) + :end-date (atime/unparse-local (time/now) atime/normal-date)) "month" (assoc query-params - :start-date (atime/unparse-local (time/plus (time/now) (time/months -1)) atime/iso-date) - :end-date (atime/unparse-local (time/now) atime/iso-date)) + :start-date (atime/unparse-local (time/plus (time/now) (time/months -1)) atime/normal-date) + :end-date (atime/unparse-local (time/now) atime/normal-date)) "year" (assoc query-params - :start-date (atime/unparse-local (time/plus (time/now) (time/years -1)) atime/iso-date) - :end-date (atime/unparse-local (time/now) atime/iso-date)) + :start-date (atime/unparse-local (time/plus (time/now) (time/years -1)) atime/normal-date) + :end-date (atime/unparse-local (time/now) atime/normal-date)) "all" (assoc query-params - :start-date (atime/unparse-local (time/plus (time/now) (time/years -3)) atime/iso-date) - :end-date (atime/unparse-local (time/now) atime/iso-date)) + :start-date (atime/unparse-local (time/plus (time/now) (time/years -3)) atime/normal-date) + :end-date (atime/unparse-local (time/now) atime/normal-date)) query-params)) diff --git a/src/clj/auto_ap/ssr/pos/sales_orders.clj b/src/clj/auto_ap/ssr/pos/sales_orders.clj index 24b95c91..77391e53 100644 --- a/src/clj/auto_ap/ssr/pos/sales_orders.clj +++ b/src/clj/auto_ap/ssr/pos/sales_orders.clj @@ -8,12 +8,10 @@ [auto-ap.ssr.grid-page-helper :as helper] [auto-ap.ssr.svg :as svg] [auto-ap.time :as atime] - [bidi.bidi :as bidi] - [clj-time.coerce :as coerce])) + [bidi.bidi :as bidi])) ;; TODO refunds ;; TODO expected deposits -;; TODO loading screen ;; always should be fast ;; make params parsing composable @@ -30,17 +28,27 @@ ] [:div.flex.space-x-1.items-baseline (com/date-input {:name "start-date" - :value (:start-date (:parsed-query-params params)) + :value (some-> params + :parsed-query-params + :start-date + (atime/parse atime/normal-date) + (atime/unparse-local + atime/normal-date)) :placeholder "Date" :size :small}) (com/date-input {:name "end-date" - :value (:end-date (:parsed-query-params params)) + :value (some-> params + :parsed-query-params + :end-date + (atime/parse atime/normal-date) + (atime/unparse-local + atime/normal-date)) :placeholder "Date" :size :small})]] )]) (defn filters [params] - [:form {"hx-trigger" "change delay:500ms, keyup from:.hot-filter delay:1000ms" + [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" "hx-get" (bidi/path-for ssr-routes/only-routes :pos-sales-table) "hx-target" "#sales-table" @@ -125,11 +133,11 @@ :date-range {:start (some-> args :parsed-query-params :start-date - (atime/parse atime/iso-date)) + (atime/parse atime/normal-date)) :end (some-> args :parsed-query-params :end-date - (atime/parse atime/iso-date))} + (atime/parse atime/normal-date))} :total-gte (some-> args :raw-query-params (get "total-gte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) :total-lte (some-> args :raw-query-params (get "total-lte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) :type-name (condp = (:payment-method (:parsed-query-params args)) @@ -146,11 +154,11 @@ :nav (com/main-aside-nav) :page-specific-nav filters :id-fn :db/id - :fetch-page (fn [user args] + :fetch-page (fn [_ args] (d-sales/get-graphql (args->graphql-params args))) :oob-render - (fn [user params] + (fn [_ params] [(assoc-in (date-range-field* params) [1 :hx-swap-oob] true)]) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} @@ -162,7 +170,7 @@ :title "Sales orders" :entity-name "Sales orders" :route :pos-sales-table - :action-buttons (fn [user args] + :action-buttons (fn [_ args] (let [{:keys [total tax]} (d-sales/summarize-graphql (args->graphql-params args))] [ (com/pill {:color :primary} @@ -171,17 +179,10 @@ (com/pill {:color :secondary} (format "Tax $%.2f" tax ) )])) - :row-buttons (fn [user e] - + :row-buttons (fn [_ e] (when (:sales-order/reference-link e) [(com/a-icon-button {:href (:sales-order/reference-link e)} - svg/external-link) - #_(when (is-admin? user) - (com/icon-button {:hx-delete (str (bidi/path-for ssr-routes/only-routes - :company-reports-delete - :request-method :delete)) - :hx-target "closest tr"} - svg/trash))])) + svg/external-link)])) :headers [ {:key "client" :name "Client" diff --git a/src/clj/auto_ap/ssr/ui.clj b/src/clj/auto_ap/ssr/ui.clj index ff836c55..b10c0e4f 100644 --- a/src/clj/auto_ap/ssr/ui.clj +++ b/src/clj/auto_ap/ssr/ui.clj @@ -21,7 +21,7 @@ [:title (str "Integreat | " page-name)] [:link {:href "/css/font.min.css", :rel "stylesheet"}] [:link {:rel "icon" :type "image/png" :href "/favicon.png"}] - [:link {:rel "stylesheet", :href "/css/react-datepicker.min.inc.css"}] + #_[:link {:rel "stylesheet", :href "/css/react-datepicker.min.inc.css"}] [:link {:rel "stylesheet", :href "/output.css"}] [:script {:src "https://unpkg.com/hyperscript.org@0.9.7/dist/_hyperscript.min.js"}] @@ -35,6 +35,9 @@ [:script {:src "https://unpkg.com/htmx.org/dist/ext/debug.js"}] [:script {:src "/js/htmx-disable.js"}] [:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async"}]] + [:link {:rel "stylesheet" :href "https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.1.4/dist/css/datepicker.min.css"}] + + [:script {:type "text/javascript" :src "https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.1.4/dist/js/datepicker-full.min.js"}] [:script {:type "text/javascript", :src "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/autoComplete.min.js"}] [:script {:src "https://unpkg.com/dropzone@5.9.3/dist/min/dropzone.min.js"}] [:link {:rel "stylesheet" :href "https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" :type "text/css"}] From da6e492b4de1fa9feee24b534c57205f07b2d727 Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 27 Sep 2023 15:59:22 -0700 Subject: [PATCH 07/13] merged. --- resources/public/output.css | 90 +++++++++++++++---------------------- 1 file changed, 37 insertions(+), 53 deletions(-) diff --git a/resources/public/output.css b/resources/public/output.css index 360a5025..8242215f 100644 --- a/resources/public/output.css +++ b/resources/public/output.css @@ -1075,6 +1075,10 @@ input:checked + .toggle-bg { left: 0px; } +.left-1\/2 { + left: 50%; +} + .right-0 { right: 0px; } @@ -1091,18 +1095,14 @@ input:checked + .toggle-bg { top: 0.5rem; } -.top-5 { - top: 1.25rem; -} - -.left-1\/2 { - left: 50%; -} - .top-2\/4 { top: 50%; } +.top-5 { + top: 1.25rem; +} + .z-10 { z-index: 10; } @@ -1332,10 +1332,6 @@ input:checked + .toggle-bg { height: 100vh; } -.h-56 { - height: 14rem; -} - .max-h-96 { max-height: 24rem; } @@ -1384,10 +1380,6 @@ input:checked + .toggle-bg { width: 100%; } -.w-56 { - width: 14rem; -} - .max-w-2xl { max-width: 42rem; } @@ -1428,11 +1420,21 @@ input:checked + .toggle-bg { flex-basis: 25%; } +.-translate-x-1\/2 { + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + .-translate-x-full { --tw-translate-x: -100%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } +.-translate-y-1\/2 { + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + .-translate-y-full { --tw-translate-y: -100%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -1453,16 +1455,6 @@ input:checked + .toggle-bg { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.-translate-x-1\/2 { - --tw-translate-x: -50%; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} - -.-translate-y-1\/2 { - --tw-translate-y: -50%; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} - .rotate-180 { --tw-rotate: 180deg; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -1476,16 +1468,6 @@ input:checked + .toggle-bg { transform: none; } -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -.animate-spin { - animation: spin 1s linear infinite; -} - @keyframes pulse { 50% { opacity: .5; @@ -1496,6 +1478,16 @@ input:checked + .toggle-bg { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.animate-spin { + animation: spin 1s linear infinite; +} + .cursor-default { cursor: default; } @@ -2132,14 +2124,14 @@ input:checked + .toggle-bg { line-height: 2.25rem; } -.leading-tight { - line-height: 1.25; -} - .leading-none { line-height: 1; } +.leading-tight { + line-height: 1.25; +} + .text-black { --tw-text-opacity: 1; color: rgb(0 0 0 / var(--tw-text-opacity)); @@ -2232,14 +2224,6 @@ input:checked + .toggle-bg { opacity: 1; } -.opacity-50 { - opacity: 0.5; -} - -.opacity-5 { - opacity: 0.05; -} - .shadow { --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); @@ -2763,6 +2747,11 @@ input:checked + .toggle-bg { --tw-bg-opacity: 0.8; } +:is(.dark .dark\:text-blue-200) { + --tw-text-opacity: 1; + color: rgb(153 215 247 / var(--tw-text-opacity)); +} + :is(.dark .dark\:text-blue-300) { --tw-text-opacity: 1; color: rgb(102 196 242 / var(--tw-text-opacity)); @@ -2833,11 +2822,6 @@ input:checked + .toggle-bg { color: rgb(250 202 21 / var(--tw-text-opacity)); } -:is(.dark .dark\:text-blue-200) { - --tw-text-opacity: 1; - color: rgb(153 215 247 / var(--tw-text-opacity)); -} - :is(.dark .dark\:placeholder-gray-400)::-moz-placeholder { --tw-placeholder-opacity: 1; color: rgb(156 163 175 / var(--tw-placeholder-opacity)); From fddd3d90078469c806f43a4ae9d37dd06ccb1112 Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 27 Sep 2023 23:52:57 -0700 Subject: [PATCH 08/13] makes all sales orders use new grid helper --- iol_ion/src/iol_ion/query.clj | 27 +++ resources/public/output.css | 12 ++ resources/schema.edn | 17 ++ scratch-sessions/fix-page-performance.repl | 33 +++ src/clj/auto_ap/ssr/components/aside.clj | 23 +- src/clj/auto_ap/ssr/core.clj | 86 ++++---- src/clj/auto_ap/ssr/grid_page_helper.clj | 1 + .../auto_ap/ssr/pos/cash_drawer_shifts.clj | 177 +++++++++++++++ src/clj/auto_ap/ssr/pos/common.clj | 77 +++++++ src/clj/auto_ap/ssr/pos/expected_deposits.clj | 110 ++++++++++ src/clj/auto_ap/ssr/pos/refunds.clj | 185 ++++++++++++++++ src/clj/auto_ap/ssr/pos/sales_orders.clj | 85 +------- src/clj/auto_ap/ssr/pos/tenders.clj | 204 ++++++++++++++++++ src/cljc/auto_ap/ssr_routes.cljc | 11 +- 14 files changed, 926 insertions(+), 122 deletions(-) create mode 100644 src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj create mode 100644 src/clj/auto_ap/ssr/pos/common.clj create mode 100644 src/clj/auto_ap/ssr/pos/expected_deposits.clj create mode 100644 src/clj/auto_ap/ssr/pos/refunds.clj create mode 100644 src/clj/auto_ap/ssr/pos/tenders.clj diff --git a/iol_ion/src/iol_ion/query.clj b/iol_ion/src/iol_ion/query.clj index 9449d845..85ad604e 100644 --- a/iol_ion/src/iol_ion/query.clj +++ b/iol_ion/src/iol_ion/query.clj @@ -83,6 +83,33 @@ [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))] [(:e r) (first (:v r)) (second (:v r))])) +(defn scan-charges [db clients start end] + (for [c clients + :let [c (entid db c)] + r (seq (dc/index-range db + :charge/client+date + [c (or start #inst "2001-01-01T08:00:00.000-00:00") ] + [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))] + [(:e r) (first (:v r)) (second (:v r))])) + +(defn scan-sales-refunds [db clients start end] + (for [c clients + :let [c (entid db c)] + r (seq (dc/index-range db + :sales-refund/client+date + [c (or start #inst "2001-01-01T08:00:00.000-00:00") ] + [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))] + [(:e r) (first (:v r)) (second (:v r))])) + +(defn scan-cash-drawer-shifts [db clients start end] + (for [c clients + :let [c (entid db c)] + r (seq (dc/index-range db + :cash-drawer-shift/client+date + [c (or start #inst "2001-01-01T08:00:00.000-00:00") ] + [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))] + [(:e r) (first (:v r)) (second (:v r))])) + (defn scan-invoices [db clients start end] (for [c clients :let [c (entid db c)] diff --git a/resources/public/output.css b/resources/public/output.css index 8242215f..4b5aa9b4 100644 --- a/resources/public/output.css +++ b/resources/public/output.css @@ -1651,6 +1651,18 @@ input:checked + .toggle-bg { margin-bottom: calc(1.5rem * var(--tw-space-y-reverse)); } +.space-y-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); +} + +.space-y-1\.5 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.375rem * var(--tw-space-y-reverse)); +} + .divide-y > :not([hidden]) ~ :not([hidden]) { --tw-divide-y-reverse: 0; border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); diff --git a/resources/schema.edn b/resources/schema.edn index 287f359d..4d177fba 100644 --- a/resources/schema.edn +++ b/resources/schema.edn @@ -1806,6 +1806,23 @@ :db/valueType :db.type/tuple :db/tupleAttrs [ :payment/client :payment/date] :db/cardinality :db.cardinality/one + :db/index true} + + {:db/ident :charge/client+date + :db/valueType :db.type/tuple + :db/tupleAttrs [ :charge/client :charge/date] + :db/cardinality :db.cardinality/one + :db/index true} + + {:db/ident :sales-refund/client+date + :db/valueType :db.type/tuple + :db/tupleAttrs [ :sales-refund/client :sales-refund/date] + :db/cardinality :db.cardinality/one + :db/index true} + {:db/ident :cash-drawer-shift/client+date + :db/valueType :db.type/tuple + :db/tupleAttrs [ :cash-drawer-shift/client :cash-drawer-shift/date] + :db/cardinality :db.cardinality/one :db/index true}] diff --git a/scratch-sessions/fix-page-performance.repl b/scratch-sessions/fix-page-performance.repl index 4bfe274c..d6f5dc04 100644 --- a/scratch-sessions/fix-page-performance.repl +++ b/scratch-sessions/fix-page-performance.repl @@ -180,4 +180,37 @@ {:user/name "hydrate-tuples"}) +(auto-ap.datomic/audit-transact-batch (->> (dc/q '[:find ?e ?c + :in $ + :where [?e :charge/client ?c]] + (dc/db conn) + ) + (map (fn [[i c]] + {:db/id i + :charge/client c}))) + + {:user/name "hydrate-tuples"}) + +(auto-ap.datomic/audit-transact-batch (->> (dc/q '[:find ?e ?c + :in $ + :where [?e :cash-drawer-shift/client ?c]] + (dc/db conn) + ) + (map (fn [[i c]] + {:db/id i + :cash-drawer-shift/client c}))) + + {:user/name "hydrate-tuples"}) + +(auto-ap.datomic/audit-transact-batch (->> (dc/q '[:find ?e ?c + :in $ + :where [?e :sales-refund/client ?c]] + (dc/db conn) + ) + (map (fn [[i c]] + {:db/id i + :sales-refund/client c}))) + + {:user/name "hydrate-tuples"}) + ) diff --git a/src/clj/auto_ap/ssr/components/aside.clj b/src/clj/auto_ap/ssr/components/aside.clj index 1d843d2c..bbbbc761 100644 --- a/src/clj/auto_ap/ssr/components/aside.clj +++ b/src/clj/auto_ap/ssr/components/aside.clj @@ -10,7 +10,7 @@ [:a (-> params (dissoc :icon) (assoc :type "button") - (update :class str " cursor-pointer flex items-center p-2 w-full text-sm text-gray-600 rounded-lg transition duration-75 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700") + (update :class str " cursor-pointer flex items-center p-2 w-full text-xs text-gray-600 rounded-lg transition duration-75 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700") (assoc :hx-indicator "find .htmx-indicator") (assoc :hx-select "#app-contents") (assoc :hx-target "#app-contents") @@ -28,7 +28,7 @@ (svg/spinner-primary {:class "inline w-4 h-4 text-white"})]]]) (defn sub-menu- [params & children] - [:ul {:id (:id params) :class "hidden py-2 space-y-2"} + [:ul {:id (:id params) :class "hidden py-2 space-y-1.5"} (for [c children] [:li (update-in c [1 1 :class ] str " flex items-center p-2 pl-11 w-full text-base font-normal text-gray-900 rounded-lg transition duration-75 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700")])]) @@ -148,7 +148,7 @@ ")]]) (defn main-aside-nav- [] - [:ul {:class "space-y-2"} + [:ul {:class "space-y-1"} [:li (menu-button- {:icon svg/pie @@ -178,11 +178,22 @@ :icon svg/receipt-register-1} "Sales") (sub-menu- {:id "dropdown-sales"} - (menu-button- {:href (str (bidi/path-for client-routes/routes + (menu-button- {:href (str (bidi/path-for ssr-routes/only-routes :pos-sales) "?date-range=week")} "Sales") - (menu-button- {:href (bidi/path-for client-routes/routes - :expected-deposits)} "Expected Deposits") + (menu-button- {:href (str (bidi/path-for ssr-routes/only-routes + :pos-expected-deposits) + "?date-range=week")} "Expected Deposits") + (menu-button- {:href (str (bidi/path-for ssr-routes/only-routes + :pos-tenders) + "?date-range=week")} "Tenders") + + (menu-button- {:href (str (bidi/path-for ssr-routes/only-routes + :pos-refunds) + "?date-range=week")} "Refunds") + (menu-button- {:href (str (bidi/path-for ssr-routes/only-routes + :pos-cash-drawer-shifts) + "?date-range=week")} "Cash drawer shifts") #_(menu-button- {:href "Sales"} "Cash Shifts") #_(menu-button- {:href "Sales"} "Tenders"))] [:li diff --git a/src/clj/auto_ap/ssr/core.clj b/src/clj/auto_ap/ssr/core.clj index a32a3bf2..e0c4f160 100644 --- a/src/clj/auto_ap/ssr/core.clj +++ b/src/clj/auto_ap/ssr/core.clj @@ -13,49 +13,57 @@ [auto-ap.ssr.company.reports :as company-reports] [auto-ap.ssr.invoice.glimpse :as invoice-glimpse] [auto-ap.ssr.pos.sales-orders :as pos-sales] + [auto-ap.ssr.pos.refunds :as pos-refunds] + [auto-ap.ssr.pos.expected-deposits :as pos-expected-deposits] + [auto-ap.ssr.pos.cash-drawer-shifts :as pos-cash-drawer-shifts] + [auto-ap.ssr.pos.tenders :as pos-tenders] [auto-ap.routes.ezcater-xls :as ezcater-xls] [auto-ap.ssr.company :as company])) ;; from auto-ap.ssr-routes, because they're shared -(def key->handler (into {:logout auth/logout - :admin-history (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page))) - :admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page))) - :admin-history-inspect (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/inspect))) - :active-client (wrap-client-redirect-unauthenticated (wrap-secure (wrap-secure company-dropdown/active-client))) - :company-dropdown-search-results - (wrap-client-redirect-unauthenticated (wrap-secure company-dropdown/dropdown-search-results)) - :company (wrap-client-redirect-unauthenticated (wrap-secure company/page)) - :company-1099 (wrap-client-redirect-unauthenticated (wrap-secure company-1099/page)) - :company-1099-vendor-table (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-table)) - :company-1099-vendor-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-dialog)) - :company-1099-vendor-save (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-save)) - :company-plaid (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/page)) - :company-plaid-table (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/table)) - :company-plaid-link (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/link)) - :company-plaid-relink (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/relink)) - :company-yodlee (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/page)) - :company-yodlee-table (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/table)) - :company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog)) - :company-yodlee-provider-account-refresh (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/refresh-provider-account)) - :company-yodlee-provider-account-reauthenticate (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/reauthenticate)) - :company-reports (wrap-client-redirect-unauthenticated (wrap-secure company-reports/page)) - :company-reports-table (wrap-client-redirect-unauthenticated (wrap-secure company-reports/table)) - :company-reports-delete (wrap-client-redirect-unauthenticated (wrap-admin company-reports/delete-report)) - :invoice-glimpse (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/page)) - :invoice-glimpse-upload (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/upload)) - :invoice-glimpse-textract-invoice (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/textract-invoice)) - :invoice-glimpse-create-invoice (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/create-invoice)) - :invoice-glimpse-update-textract-invoice (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/update-textract-invoice)) - :transaction-insights (wrap-client-redirect-unauthenticated (wrap-admin insights/page)) - :transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-admin insights/insight-table)) - :transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-admin insights/transaction-rows)) - :transaction-insight-code (wrap-client-redirect-unauthenticated (wrap-admin insights/code)) - :transaction-insight-disapprove (wrap-client-redirect-unauthenticated (wrap-admin insights/disapprove)) - :transaction-insight-explain (wrap-client-redirect-unauthenticated (wrap-admin insights/explain)) - :admin-ezcater-xls (wrap-client-redirect-unauthenticated (wrap-admin ezcater-xls/page)) - :search (wrap-client-redirect-unauthenticated (wrap-secure search/dialog-contents))} - - pos-sales/key->handler)) +(def key->handler + (-> {:logout auth/logout + :admin-history (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page))) + :admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page))) + :admin-history-inspect (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/inspect))) + :active-client (wrap-client-redirect-unauthenticated (wrap-secure (wrap-secure company-dropdown/active-client))) + :company-dropdown-search-results + (wrap-client-redirect-unauthenticated (wrap-secure company-dropdown/dropdown-search-results)) + :company (wrap-client-redirect-unauthenticated (wrap-secure company/page)) + :company-1099 (wrap-client-redirect-unauthenticated (wrap-secure company-1099/page)) + :company-1099-vendor-table (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-table)) + :company-1099-vendor-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-dialog)) + :company-1099-vendor-save (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-save)) + :company-plaid (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/page)) + :company-plaid-table (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/table)) + :company-plaid-link (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/link)) + :company-plaid-relink (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/relink)) + :company-yodlee (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/page)) + :company-yodlee-table (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/table)) + :company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog)) + :company-yodlee-provider-account-refresh (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/refresh-provider-account)) + :company-yodlee-provider-account-reauthenticate (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/reauthenticate)) + :company-reports (wrap-client-redirect-unauthenticated (wrap-secure company-reports/page)) + :company-reports-table (wrap-client-redirect-unauthenticated (wrap-secure company-reports/table)) + :company-reports-delete (wrap-client-redirect-unauthenticated (wrap-admin company-reports/delete-report)) + :invoice-glimpse (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/page)) + :invoice-glimpse-upload (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/upload)) + :invoice-glimpse-textract-invoice (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/textract-invoice)) + :invoice-glimpse-create-invoice (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/create-invoice)) + :invoice-glimpse-update-textract-invoice (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/update-textract-invoice)) + :transaction-insights (wrap-client-redirect-unauthenticated (wrap-admin insights/page)) + :transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-admin insights/insight-table)) + :transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-admin insights/transaction-rows)) + :transaction-insight-code (wrap-client-redirect-unauthenticated (wrap-admin insights/code)) + :transaction-insight-disapprove (wrap-client-redirect-unauthenticated (wrap-admin insights/disapprove)) + :transaction-insight-explain (wrap-client-redirect-unauthenticated (wrap-admin insights/explain)) + :admin-ezcater-xls (wrap-client-redirect-unauthenticated (wrap-admin ezcater-xls/page)) + :search (wrap-client-redirect-unauthenticated (wrap-secure search/dialog-contents))} + (into pos-sales/key->handler) + (into pos-expected-deposits/key->handler) + (into pos-tenders/key->handler) + (into pos-cash-drawer-shifts/key->handler) + (into pos-refunds/key->handler))) diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index 480dbc46..fbd1fb7f 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -88,6 +88,7 @@ :thead-params {:hx-get (bidi/path-for ssr-routes/only-routes (:route grid-spec)) :hx-target (str "#" (:id grid-spec)) + :hx-indicator (str "#" (:id grid-spec)) :hx-trigger "sorted once" :hx-vals "js:{\"toggle-sort\": event.detail.key || \"\"}"} :headers diff --git a/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj b/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj new file mode 100644 index 00000000..13e56c06 --- /dev/null +++ b/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj @@ -0,0 +1,177 @@ +(ns auto-ap.ssr.pos.cash-drawer-shifts + (:require + [auto-ap.datomic + :refer [add-sorter-fields + apply-pagination + apply-sort-3 + conn + merge-query + pull-many + query2]] + [auto-ap.graphql.utils :refer [extract-client-ids]] + [auto-ap.routes.utils + :refer [wrap-client-redirect-unauthenticated wrap-secure]] + [auto-ap.ssr.pos.common :refer [date-range-field* processor-field* total-field*]] + [auto-ap.ssr-routes :as ssr-routes] + [auto-ap.ssr.components :as com] + [auto-ap.ssr.grid-page-helper :as helper] + [auto-ap.ssr.svg :as svg] + [auto-ap.time :as atime] + [bidi.bidi :as bidi] + [clj-time.coerce :as c] + [datomic.api :as dc] + [clojure.set :as set])) + +;; always should be fast +;; make params parsing composable + +(defn filters [params] + [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" + "hx-get" (bidi/path-for ssr-routes/only-routes + :pos-cash-drawer-shift-table) + "hx-target" "#cash-drawer-shift-table" + "hx-indicator" "#cash-drawer-shift-table" + #_#_:hx-disabled-elt "find fieldset"} + + [:fieldset.space-y-6 + (date-range-field* params) + (total-field* params)]]) + +(defn <-datomic [result] + (cond-> result + true (update :cash-drawer-shift/date c/from-date))) + +(def default-read '[* + {:cash-drawer-shift/client [:client/name :db/id :client/code]}]) + +(defn fetch-ids [db args] + (let [query-params (:parsed-query-params args) + valid-clients (extract-client-ids (:clients args) + (:client-id query-params) + (when (:client-code query-params) + [:client/code (:client-code query-params)])) + valid-clients (->> valid-clients + (take 10) + set) + start-date (some-> args + :parsed-query-params + :start-date + (atime/parse atime/normal-date)) + end-date (some-> args + :parsed-query-params + :end-date + (atime/parse atime/normal-date)) + query (cond-> {:query {:find [] + :in ['$ '[?clients ?start-date ?end-date]] + :where '[[(iol-ion.query/scan-cash-drawer-shifts $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} + :args [db [valid-clients + (some-> start-date c/to-date) + (some-> end-date c/to-date )]]} + (:sort query-params) (add-sorter-fields {"client" ['[?e :cash-drawer-shift/client ?c] + '[?c :client/name ?sort-client]] + "date" ['[?e :cash-drawer-shift/date ?sort-date]] + "paid-in" ['[?e :cash-drawer-shift/paid-in ?sort-paid-in]] + "paid-out" ['[?e :cash-drawer-shift/paid-out ?sort-paid-out]] + "expected-cash" ['[?e :cash-drawer-shift/expected-cash ?sort-expected-cash]] + "opened-cash" ['[?e :cash-drawer-shift/opened-cash ?sort-opened-cash]] + } + query-params) + + (:exact-match-id query-params) + (merge-query {:query {:in ['?e] + :where []} + :args [(:exact-match-id query-params)]}) + + start-date + (merge-query {:query {:in '[?start-date] + :where ['[?e :cash-drawer-shift/date ?date] + '[(>= ?date ?start-date)]]} + :args [(c/to-date start-date)]}) + + end-date + (merge-query {:query {:in '[?end-date] + :where ['[?e :cash-drawer-shift/date ?date] + '[(<= ?date ?end-date)]]} + :args [(c/to-date end-date)]}) + + true + (merge-query {:query {:find ['?sort-default '?e] + :where ['[?e :cash-drawer-shift/date ?sort-default]]}}))] + + (cond->> (query2 query) + true (apply-sort-3 query-params) + true (apply-pagination query-params)))) + +(defn hydrate-results [ids db _] + (let [results (->> (pull-many db default-read ids) + (group-by :db/id)) + cash-drawer-shifts (->> ids + (map results) + (map first) + (mapv <-datomic) + )] + cash-drawer-shifts)) + +(defn fetch-page [_ args] + (let [db (dc/db conn) + {ids-to-retrieve :ids matching-count :count} (fetch-ids db args)] + + [(->> (hydrate-results ids-to-retrieve db args)) + matching-count])) + +(def grid-page {:id "cash-drawer-shift-table" + :nav (com/main-aside-nav) + :page-specific-nav filters + :id-fn :db/id + :fetch-page fetch-page + :oob-render + (fn [_ params] + [(assoc-in (date-range-field* params) [1 :hx-swap-oob] true)]) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "POS"] + + [:a {:href (bidi/path-for ssr-routes/only-routes + :pos-cash-drawer-shifts)} + "Cash Drawer Shifts"]] + :title "Cash drawer shifts" + :entity-name "Cash drawer shift" + :route :pos-cash-drawer-shift-table + :action-buttons (fn [_ args]) + :row-buttons (fn [_ e]) + :headers [{:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [args] + (= (count (:clients args)) 1)) + :render #(-> % :cash-drawer-shift/client :client/code)} + {:key "date" + :name "Date" + :sort-key "date" + :render #(atime/unparse-local (:cash-drawer-shift/date %) atime/standard-time)} + {:key "paid-in" + :name "Paid in" + :sort-key "paid-in" + :render #(some->> % :cash-drawer-shift/paid-in (format "$%.2f"))} + {:key "paid-out" + :name "Paid out" + :sort-key "paid-out" + :render #(some->> % :cash-drawer-shift/paid-out (format "$%.2f"))} + {:key "expected-cash" + :name "Expected cash" + :sort-key "expected-cash" + :render #(some->> % :cash-drawer-shift/expected-cash (format "$%.2f"))} + {:key "opened-cash" + :name "Opened cash" + :sort-key "opened-cash" + :render #(some->> % :cash-drawer-shift/opened-cash (format "$%.2f"))} + ]}) + +(def row* (partial helper/row* grid-page)) +(def table* (partial helper/table* grid-page)) +(def table (partial helper/table grid-page)) +(def page (partial helper/page grid-page)) + +(def key->handler + {:pos-cash-drawer-shifts (wrap-client-redirect-unauthenticated (wrap-secure page)) + :pos-cash-drawer-shift-table (wrap-client-redirect-unauthenticated (wrap-secure table))}) diff --git a/src/clj/auto_ap/ssr/pos/common.clj b/src/clj/auto_ap/ssr/pos/common.clj new file mode 100644 index 00000000..b87b78c7 --- /dev/null +++ b/src/clj/auto_ap/ssr/pos/common.clj @@ -0,0 +1,77 @@ +(ns auto-ap.ssr.pos.common + (:require [auto-ap.ssr.components :as com] + [auto-ap.time :as atime])) + + +(defn date-range-field* [params] + [:div#date-range {} + (com/field {:label "Date Range"} + [:div.space-y-4 + [:div + (com/button-group {:name "date-range"} + (com/button-group-button {:size :small :value "all" :hx-trigger "click"} "All") + (com/button-group-button {:size :small :value "week" :hx-trigger "click"} "Week") + (com/button-group-button {:size :small :value "month" :hx-trigger "click"} "Month") + (com/button-group-button {:size :small :value "year" :hx-trigger "click"} "Year")) + ] + [:div.flex.space-x-1.items-baseline + (com/date-input {:name "start-date" + :value (some-> params + :parsed-query-params + :start-date + (atime/parse atime/normal-date) + (atime/unparse-local + atime/normal-date)) + :placeholder "Date" + :size :small}) + + (com/date-input {:name "end-date" + :value (some-> params + :parsed-query-params + :end-date + (atime/parse atime/normal-date) + (atime/unparse-local + atime/normal-date)) + :placeholder "Date" + :size :small})]] + )]) +(defn processor-field* [params] + (com/field {:label "Processor"} + (com/radio {:size :small + :name "processor" + :options [{:value "" + :content "All"} + {:value "square" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/square.png" :style {:width "16px" :height "16px"}}] [:div "Square"]]} + {:value "doordash" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/doordash.png" :style {:width "16px" :height "16px"}}] [:div "Doordash"]]} + {:value "uber-eats" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/ubereats.png" :style {:width "16px" :height "16px"}}] [:div "Uber eats"]]} + {:value "grubhub" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/grubhub.png" :style {:width "16px" :height "16px"}}] [:div "Grubhub"]]} + {:value "koala" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/koala.png" :style {:width "16px" :height "16px"}}] [:div "Koala"]]} + {:value "ezcater" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/ezcater.png" :style {:width "16px" :height "16px"}}] [:div "EZCater"]]} + {:value "na" + :content "No Processor"}]}))) + +(defn total-field* [params] + (com/field {:label "Total"} + [:div.flex.space-x-4.items-baseline + (com/money-input {:name "total-gte" + :id "total-gte" + :hx-preserve "true" + :class "hot-filter" + :value (:total-gte (:parsed-query-params params)) + :placeholder "0.01" + :size :small}) + [:div.align-baseline + "to"] + (com/money-input {:name "total-lte" + :hx-preserve "true" + :id "total-lte" + :class "hot-filter" + :value (:total-lte (:parsed-query-params params)) + :placeholder "9999.34" + :size :small})])) diff --git a/src/clj/auto_ap/ssr/pos/expected_deposits.clj b/src/clj/auto_ap/ssr/pos/expected_deposits.clj new file mode 100644 index 00000000..142c0b3d --- /dev/null +++ b/src/clj/auto_ap/ssr/pos/expected_deposits.clj @@ -0,0 +1,110 @@ +(ns auto-ap.ssr.pos.expected-deposits + (:require + [auto-ap.datomic.expected-deposit :as d-expected-deposit] + [auto-ap.routes.utils + :refer [wrap-client-redirect-unauthenticated wrap-secure]] + [auto-ap.ssr-routes :as ssr-routes] + [auto-ap.ssr.components :as com] + [auto-ap.ssr.grid-page-helper :as helper] + [auto-ap.ssr.pos.common :refer [date-range-field* total-field*]] + [auto-ap.ssr.svg :as svg] + [auto-ap.time :as atime] + [bidi.bidi :as bidi])) + +;; TODO refunds +;; always should be fast +;; make params parsing composable + +(defn filters [params] + [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" + "hx-get" (bidi/path-for ssr-routes/only-routes + :pos-expected-deposit-table) + "hx-target" "#expected-deposit-table" + "hx-indicator" "#expected-deposit-table" + #_#_:hx-disabled-elt "find fieldset"} + + [:fieldset.space-y-6 + (date-range-field* params) + (total-field* params)]]) + +(defn args->graphql-params [args] + {:clients (:clients args) + :start (:start (:parsed-query-params args)) + :sort (:sort (:parsed-query-params args)) + :per-page (:per-page (:parsed-query-params args)) + :exact-match-id (some-> (:parsed-query-params args) :exact-match-id Long/parseLong) + :date-range {:start (some-> args + :parsed-query-params + :start-date + (atime/parse atime/normal-date)) + :end (some-> args + :parsed-query-params + :end-date + (atime/parse atime/normal-date))} + :total-gte (some-> args :raw-query-params (get "total-gte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) + :total-lte (some-> args :raw-query-params (get "total-lte") not-empty (#(if (string? %) (Double/parseDouble %) (double %))))}) + +(def grid-page {:id "expected-deposit-table" + :nav (com/main-aside-nav) + :page-specific-nav filters + :id-fn :db/id + :fetch-page (fn [_ args] + (d-expected-deposit/get-graphql + (args->graphql-params args))) + :oob-render + (fn [_ params] + [(assoc-in (date-range-field* params) [1 :hx-swap-oob] true)]) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "POS"] + + [:a {:href (bidi/path-for ssr-routes/only-routes + :pos-expected-deposits)} + "Expected deposits"]] + :title "Expected deposits" + :entity-name "Expected deposit" + :route :pos-expected-deposit-table + :action-buttons (fn [_ args] + #_(let [{:keys [total tax]} (d-sales/summarize-graphql (args->graphql-params args))] + [ + (com/pill {:color :primary} + (format "Total $%.2f" total) + ) + (com/pill {:color :secondary} + (format "Tax $%.2f" tax ) + )])) + :row-buttons (fn [_ e] + (when (:expected-deposit/reference-link e) + [(com/a-icon-button {:href (:expected-deposit/reference-link e)} + svg/external-link)])) + :headers [{:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [args] + (= (count (:clients args)) 1)) + :render #(-> % :expected-deposit/client :client/code)} + {:key "date" + :name "Date" + :sort-key "date" + :render #(atime/unparse-local (:expected-deposit/date %) atime/standard-time)} + {:key "sales-date" + :name "Sales Date" + :sort-key "sales-date" + :render #(atime/unparse-local (:expected-deposit/sales-date %) atime/standard-time)} + {:key "total" + :name "Total" + :sort-key "total" + :render #(some->> % :expected-deposit/total (format "$%.2f"))} + {:key "fee" + :name "Fee" + :sort-key "fee" + :render #(some->> % :expected-deposit/fee (format "$%.2f"))}]}) + +(def row* (partial helper/row* grid-page)) +(def table* (partial helper/table* grid-page)) +(def table (partial helper/table grid-page)) +(def page (partial helper/page grid-page)) + +(def key->handler + {:pos-expected-deposits (wrap-client-redirect-unauthenticated (wrap-secure page)) + :pos-expected-deposit-table (wrap-client-redirect-unauthenticated (wrap-secure table))}) diff --git a/src/clj/auto_ap/ssr/pos/refunds.clj b/src/clj/auto_ap/ssr/pos/refunds.clj new file mode 100644 index 00000000..e767901b --- /dev/null +++ b/src/clj/auto_ap/ssr/pos/refunds.clj @@ -0,0 +1,185 @@ +(ns auto-ap.ssr.pos.refunds + (:require + [auto-ap.datomic + :refer [add-sorter-fields + apply-pagination + apply-sort-3 + conn + merge-query + pull-many + query2]] + [auto-ap.graphql.utils :refer [extract-client-ids]] + [auto-ap.routes.utils + :refer [wrap-client-redirect-unauthenticated wrap-secure]] + [auto-ap.ssr.pos.common :refer [date-range-field* processor-field* total-field*]] + [auto-ap.ssr-routes :as ssr-routes] + [auto-ap.ssr.components :as com] + [auto-ap.ssr.grid-page-helper :as helper] + [auto-ap.ssr.svg :as svg] + [auto-ap.time :as atime] + [bidi.bidi :as bidi] + [clj-time.coerce :as c] + [datomic.api :as dc] + [clojure.set :as set])) + +;; TODO refunds +;; always should be fast +;; make params parsing composable + +(defn filters [params] + [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" + "hx-get" (bidi/path-for ssr-routes/only-routes + :pos-refund-table) + "hx-target" "#refund-table" + "hx-indicator" "#refund-table" + #_#_:hx-disabled-elt "find fieldset"} + + [:fieldset.space-y-6 + (date-range-field* params) + (total-field* params)]]) + +(defn <-datomic [result] + (cond-> result + true (update :sales-refund/date c/from-date))) + +(def default-read '[* + {:sales-refund/client [:client/name :db/id :client/code]}]) + +(defn fetch-ids [db args] + (let [query-params (:parsed-query-params args) + valid-clients (extract-client-ids (:clients args) + (:client-id query-params) + (when (:client-code query-params) + [:client/code (:client-code query-params)])) + valid-clients (->> valid-clients + (take 10) + set) + start-date (some-> args + :parsed-query-params + :start-date + (atime/parse atime/normal-date)) + end-date (some-> args + :parsed-query-params + :end-date + (atime/parse atime/normal-date)) + total-gte (some-> args :parsed-query-params :total-gte not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) + total-lte (some-> args :parsed-query-params :total-lte not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) + query (cond-> {:query {:find [] + :in ['$ '[?clients ?start-date ?end-date]] + :where '[[(iol-ion.query/scan-sales-refunds $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} + :args [db [valid-clients + (some-> start-date c/to-date) + (some-> end-date c/to-date )]]} + (:sort query-params) (add-sorter-fields {"client" ['[?e :sales-refund/client ?c] + '[?c :client/name ?sort-client]] + "date" ['[?e :sales-refund/date ?sort-date]] + "total" ['[?e :sales-refund/total ?sort-total]] + "fee" ['[?e :sales-refund/fee ?sort-tip]] + "type" ['[?e :sales-refund/type ?type]]} + query-params) + + (:exact-match-id query-params) + (merge-query {:query {:in ['?e] + :where []} + :args [(:exact-match-id query-params)]}) + + total-gte + (merge-query {:query {:in ['?total-gte] + :where ['[?e :sales-refund/total ?a] + '[(>= ?a ?total-gte)]]} + :args [total-gte]}) + + total-lte + (merge-query {:query {:in ['?total-lte] + :where ['[?e :sales-refund/total ?a] + '[(<= ?a ?total-lte)]]} + :args [total-lte]}) + + start-date + (merge-query {:query {:in '[?start-date] + :where ['[?e :sales-refund/date ?date] + '[(>= ?date ?start-date)]]} + :args [(c/to-date start-date)]}) + + end-date + (merge-query {:query {:in '[?end-date] + :where ['[?e :sales-refund/date ?date] + '[(<= ?date ?end-date)]]} + :args [(c/to-date end-date)]}) + + true + (merge-query {:query {:find ['?sort-default '?e] + :where ['[?e :sales-refund/date ?sort-default]]}}))] + + (cond->> (query2 query) + true (apply-sort-3 query-params) + true (apply-pagination query-params)))) + +(defn hydrate-results [ids db _] + (let [results (->> (pull-many db default-read ids) + (group-by :db/id)) + refunds (->> ids + (map results) + (map first) + (mapv <-datomic) + )] + refunds)) + +(defn fetch-page [_ args] + (let [db (dc/db conn) + {ids-to-retrieve :ids matching-count :count} (fetch-ids db args)] + + [(->> (hydrate-results ids-to-retrieve db args)) + matching-count])) + +(def grid-page {:id "refund-table" + :nav (com/main-aside-nav) + :page-specific-nav filters + :id-fn :db/id + :fetch-page fetch-page + :oob-render + (fn [_ params] + [(assoc-in (date-range-field* params) [1 :hx-swap-oob] true)]) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "POS"] + + [:a {:href (bidi/path-for ssr-routes/only-routes + :pos-refunds)} + "Refunds"]] + :title "Refunds" + :entity-name "Refund" + :route :pos-refund-table + :action-buttons (fn [_ args]) + :row-buttons (fn [_ e]) + :headers [{:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [args] + (= (count (:clients args)) 1)) + :render #(-> % :sales-refund/client :client/code)} + {:key "date" + :name "Date" + :sort-key "date" + :render #(atime/unparse-local (:sales-refund/date %) atime/standard-time)} + {:key "total" + :name "Total" + :sort-key "total" + :render #(some->> % :sales-refund/total (format "$%.2f"))} + {:key "type" + :name "Type" + :sort-key "type" + :render :sales-refund/type} + {:key "fee" + :name "Fee" + :sort-key "fee" + :render #(some->> % :sales-refund/fee (format "$%.2f"))}]}) + +(def row* (partial helper/row* grid-page)) +(def table* (partial helper/table* grid-page)) +(def table (partial helper/table grid-page)) +(def page (partial helper/page grid-page)) + +(def key->handler + {:pos-refunds (wrap-client-redirect-unauthenticated (wrap-secure page)) + :pos-refund-table (wrap-client-redirect-unauthenticated (wrap-secure table))}) diff --git a/src/clj/auto_ap/ssr/pos/sales_orders.clj b/src/clj/auto_ap/ssr/pos/sales_orders.clj index 77391e53..3aaaf682 100644 --- a/src/clj/auto_ap/ssr/pos/sales_orders.clj +++ b/src/clj/auto_ap/ssr/pos/sales_orders.clj @@ -6,6 +6,8 @@ [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components :as com] [auto-ap.ssr.grid-page-helper :as helper] + [auto-ap.ssr.pos.common + :refer [date-range-field* processor-field* total-field*]] [auto-ap.ssr.svg :as svg] [auto-ap.time :as atime] [bidi.bidi :as bidi])) @@ -15,67 +17,18 @@ ;; always should be fast ;; make params parsing composable -(defn date-range-field* [params] - [:div#date-range {} - (com/field {:label "Date Range"} - [:div.space-y-4 - [:div - (com/button-group {:name "date-range"} - (com/button-group-button {:size :small :value "all" :hx-trigger "click"} "All") - (com/button-group-button {:size :small :value "week" :hx-trigger "click"} "Week") - (com/button-group-button {:size :small :value "month" :hx-trigger "click"} "Month") - (com/button-group-button {:size :small :value "year" :hx-trigger "click"} "Year")) - ] - [:div.flex.space-x-1.items-baseline - (com/date-input {:name "start-date" - :value (some-> params - :parsed-query-params - :start-date - (atime/parse atime/normal-date) - (atime/unparse-local - atime/normal-date)) - :placeholder "Date" - :size :small}) - - (com/date-input {:name "end-date" - :value (some-> params - :parsed-query-params - :end-date - (atime/parse atime/normal-date) - (atime/unparse-local - atime/normal-date)) - :placeholder "Date" - :size :small})]] - )]) + (defn filters [params] - [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" - "hx-get" (bidi/path-for ssr-routes/only-routes + [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" + "hx-get" (bidi/path-for ssr-routes/only-routes :pos-sales-table) - "hx-target" "#sales-table" - "hx-indicator" "#sales-table" + "hx-target" "#sales-table" + "hx-indicator" "#sales-table" #_#_:hx-disabled-elt "find fieldset"} [:fieldset.space-y-6 (date-range-field* params) - [:div - (com/field {:label "Total"} - [:div.flex.space-x-4.items-baseline - (com/money-input {:name "total-gte" - :id "total-gte" - :hx-preserve "true" - :class "hot-filter" - :value (:total-gte (:parsed-query-params params)) - :placeholder "0.01" - :size :small}) - [:div.align-baseline - "to"] - (com/money-input {:name "total-lte" - :hx-preserve "true" - :id "total-lte" - :class "hot-filter" - :value (:total-lte (:parsed-query-params params)) - :placeholder "9999.34" - :size :small})])] + [:div (total-field* params)] [:div (com/field {:label "Payment Method"} (com/radio {:size :small @@ -92,27 +45,7 @@ :content "Other"} ]}))] [:div - - (com/field {:label "Processor"} - (com/radio {:size :small - :name "processor" - :options [{:value "" - :content "All"} - {:value "square" - :content [:div.flex.space-x-2 [:img.align-center {:src "/img/square.png" :style {:width "16px" :height "16px"}}] [:div "Square"]]} - {:value "doordash" - :content [:div.flex.space-x-2 [:img.align-center {:src "/img/doordash.png" :style {:width "16px" :height "16px"}}] [:div "Doordash"]]} - {:value "uber-eats" - :content [:div.flex.space-x-2 [:img.align-center {:src "/img/ubereats.png" :style {:width "16px" :height "16px"}}] [:div "Uber eats"]]} - {:value "grubhub" - :content [:div.flex.space-x-2 [:img.align-center {:src "/img/grubhub.png" :style {:width "16px" :height "16px"}}] [:div "Grubhub"]]} - {:value "koala" - :content [:div.flex.space-x-2 [:img.align-center {:src "/img/koala.png" :style {:width "16px" :height "16px"}}] [:div "Koala"]]} - {:value "ezcater" - :content [:div.flex.space-x-2 [:img.align-center {:src "/img/ezcater.png" :style {:width "16px" :height "16px"}}] [:div "EZCater"]]} - {:value "na" - :content "No Processor"} - ]}))] + (processor-field* params)] [:div (com/field {:label "Category"} diff --git a/src/clj/auto_ap/ssr/pos/tenders.clj b/src/clj/auto_ap/ssr/pos/tenders.clj new file mode 100644 index 00000000..1d6de99e --- /dev/null +++ b/src/clj/auto_ap/ssr/pos/tenders.clj @@ -0,0 +1,204 @@ +(ns auto-ap.ssr.pos.tenders + (:require + [auto-ap.datomic + :refer [add-sorter-fields + apply-pagination + apply-sort-3 + conn + merge-query + pull-many + query2]] + [auto-ap.graphql.utils :refer [extract-client-ids]] + [auto-ap.routes.utils + :refer [wrap-client-redirect-unauthenticated wrap-secure]] + [auto-ap.ssr.pos.common :refer [date-range-field* processor-field* total-field*]] + [auto-ap.ssr-routes :as ssr-routes] + [auto-ap.ssr.components :as com] + [auto-ap.ssr.grid-page-helper :as helper] + [auto-ap.ssr.svg :as svg] + [auto-ap.time :as atime] + [bidi.bidi :as bidi] + [clj-time.coerce :as c] + [datomic.api :as dc] + [clojure.set :as set])) + +;; TODO refunds +;; always should be fast +;; make params parsing composable + +(defn filters [params] + [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" + "hx-get" (bidi/path-for ssr-routes/only-routes + :pos-tender-table) + "hx-target" "#tender-table" + "hx-indicator" "#tender-table" + #_#_:hx-disabled-elt "find fieldset"} + + [:fieldset.space-y-6 + (date-range-field* params) + (processor-field* params) + (total-field* params)]]) + +(defn <-datomic [result] + (let [expected-deposit (some-> (:expected-deposit/_charges result) + first + (update :expected-deposit/date c/from-date))] + (cond-> result + true (update :charge/date c/from-date) + true (update :charge/processor :db/ident) + expected-deposit (assoc :expected-deposit expected-deposit)))) + +(def default-read '[* + {:charge/client [:client/name :db/id :client/code] + :charge/processor [:db/ident] + :expected-deposit/_charges [:expected-deposit/date :db/id]}]) + +(defn fetch-ids [db args] + (let [query-params (:parsed-query-params args) + valid-clients (extract-client-ids (:clients args) + (:client args) + (:client-id query-params) + (when (:client-code query-params) + [:client/code (:client-code query-params)])) + valid-clients (->> valid-clients + (take 10) + set) + _ (println "valid clients" valid-clients) + start-date (some-> args + :parsed-query-params + :start-date + (atime/parse atime/normal-date)) + end-date (some-> args + :parsed-query-params + :end-date + (atime/parse atime/normal-date)) + total-gte (some-> args :parsed-query-params :total-gte not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) + total-lte (some-> args :parsed-query-params :total-lte not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) + processor (some-> query-params :processor (#(keyword "ccp-processor" (name %)))) + query (cond-> {:query {:find [] + :in ['$ '[?clients ?start-date ?end-date]] + :where '[[(iol-ion.query/scan-charges $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} + :args [db [valid-clients + (some-> start-date c/to-date) + (some-> end-date c/to-date )]]} + (:sort query-params) (add-sorter-fields {"client" ['[?e :charge/client ?c] + '[?c :client/name ?sort-client]] + "date" ['[?e :charge/date ?sort-date]] + "total" ['[?e :charge/total ?sort-total]] + "tip" ['[?e :charge/tip ?sort-tip]] + "processor" ['[?e :charge/processor ?p] + '[?p :db/ident ?p2] + '[(name ?p2) ?sort-processor]]} + query-params) + + (:exact-match-id query-params) + (merge-query {:query {:in ['?e] + :where []} + :args [(:exact-match-id query-params)]}) + + total-gte + (merge-query {:query {:in ['?total-gte] + :where ['[?e :charge/total ?a] + '[(>= ?a ?total-gte)]]} + :args [total-gte]}) + + total-lte + (merge-query {:query {:in ['?total-lte] + :where ['[?e :charge/total ?a] + '[(<= ?a ?total-lte)]]} + :args [total-lte]}) + + processor + (merge-query {:query {:in '[?processor] + :where ['[?e :charge/processor ?processor]]} + :args [processor]}) + + + true + (merge-query {:query {:find ['?sort-default '?e]}}))] + (cond->> (query2 query) + true (apply-sort-3 query-params) + true (apply-pagination query-params)))) + +(defn hydrate-results [ids db _] + (let [results (->> (pull-many db default-read ids) + (group-by :db/id)) + charges (->> ids + (map results) + (map first) + (mapv <-datomic) + )] + charges)) + +(defn fetch-page [_ args] + (let [db (dc/db conn) + {ids-to-retrieve :ids matching-count :count} (fetch-ids db args)] + + [(->> (hydrate-results ids-to-retrieve db args)) + matching-count])) + +(def grid-page {:id "tender-table" + :nav (com/main-aside-nav) + :page-specific-nav filters + :id-fn :db/id + :fetch-page fetch-page + :oob-render + (fn [_ params] + [(assoc-in (date-range-field* params) [1 :hx-swap-oob] true)]) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "POS"] + + [:a {:href (bidi/path-for ssr-routes/only-routes + :pos-tenders)} + "Tenders"]] + :title "Tenders" + :entity-name "Tender" + :route :pos-tender-table + :action-buttons (fn [_ args]) + :row-buttons (fn [_ e] + (when (:charge/reference-link e) + [(com/a-icon-button {:href (:charge/reference-link e)} + svg/external-link)])) + :headers [{:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [args] + (= (count (:clients args)) 1)) + :render #(-> % :charge/client :client/code)} + {:key "date" + :name "Date" + :sort-key "date" + :render #(atime/unparse-local (:charge/date %) atime/standard-time)} + {:key "total" + :name "Total" + :sort-key "total" + :render #(some->> % :charge/total (format "$%.2f"))} + {:key "processor" + :name "Processor" + :sort-key "processor" + :render (fn [sales-order] + (when (:charge/processor sales-order) + (com/pill {:color :primary } + (name (:charge/processor sales-order)))))} + {:key "tip" + :name "Tip" + :sort-key "tip" + :render #(some->> % :charge/tip (format "$%.2f"))} + {:key "links" + :name "Links" + :render (fn [entity] + (when-let [expected-deposit-id (some->> entity :expected-deposit :db/id)] + [:a {:href (str (bidi/path-for ssr-routes/only-routes + :pos-expected-deposits) + "?exact-match-id=" expected-deposit-id)} + (com/pill {:color :secondary} "expected deposit")]))}]}) + +(def row* (partial helper/row* grid-page)) +(def table* (partial helper/table* grid-page)) +(def table (partial helper/table grid-page)) +(def page (partial helper/page grid-page)) + +(def key->handler + {:pos-tenders (wrap-client-redirect-unauthenticated (wrap-secure page)) + :pos-tender-table (wrap-client-redirect-unauthenticated (wrap-secure table))}) diff --git a/src/cljc/auto_ap/ssr_routes.cljc b/src/cljc/auto_ap/ssr_routes.cljc index c6698efc..79f6edce 100644 --- a/src/cljc/auto_ap/ssr_routes.cljc +++ b/src/cljc/auto_ap/ssr_routes.cljc @@ -20,7 +20,16 @@ ["/rows/" [#"\d+" :after]] {:get :transaction-insight-rows} ["/explain/" [#"\d+" :transaction-id]] {:get :transaction-insight-explain}}} "pos" {"/sales" {"" {:get :pos-sales} - "/table" {:get :pos-sales-table}}} + "/table" {:get :pos-sales-table}} + "/expected-deposit" {"" {:get :pos-expected-deposits} + "/table" {:get :pos-expected-deposit-table}} + "/tenders" {"" {:get :pos-tenders} + "/table" {:get :pos-tender-table}} + "/refunds" {"" {:get :pos-refunds} + "/table" {:get :pos-refund-table}} + "/cash-drawer-shifts" {"" {:get :pos-cash-drawer-shifts} + "/table" {:get :pos-cash-drawer-shift-table}}} + "company" {"" :company "/dropdown" :company-dropdown-search-results "/active" {:put :active-client} From c73c7619d8ff9e530197e3ce1f18912750f1f5ce Mon Sep 17 00:00:00 2001 From: Bryce Date: Thu, 28 Sep 2023 08:05:14 -0700 Subject: [PATCH 09/13] cleanup --- src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj b/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj index 13e56c06..93574c6b 100644 --- a/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj +++ b/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj @@ -11,16 +11,14 @@ [auto-ap.graphql.utils :refer [extract-client-ids]] [auto-ap.routes.utils :refer [wrap-client-redirect-unauthenticated wrap-secure]] - [auto-ap.ssr.pos.common :refer [date-range-field* processor-field* total-field*]] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components :as com] [auto-ap.ssr.grid-page-helper :as helper] - [auto-ap.ssr.svg :as svg] + [auto-ap.ssr.pos.common :refer [date-range-field* total-field*]] [auto-ap.time :as atime] [bidi.bidi :as bidi] [clj-time.coerce :as c] - [datomic.api :as dc] - [clojure.set :as set])) + [datomic.api :as dc])) ;; always should be fast ;; make params parsing composable From d1798f6b5c64cc3d68d04dfee35316f05efaed3c Mon Sep 17 00:00:00 2001 From: Bryce Date: Thu, 28 Sep 2023 20:41:00 -0700 Subject: [PATCH 10/13] further cleans up POS --- iol_ion/src/iol_ion/query.clj | 12 + resources/datomic/extensions.edn | 1 + resources/schema.edn | 5 + src/clj/auto_ap/query_params.clj | 119 +++++++++ src/clj/auto_ap/ssr/components.clj | 1 + src/clj/auto_ap/ssr/components/buttons.clj | 13 + src/clj/auto_ap/ssr/grid_page_helper.clj | 179 +++++-------- .../auto_ap/ssr/pos/cash_drawer_shifts.clj | 65 ++--- src/clj/auto_ap/ssr/pos/common.clj | 44 ++-- src/clj/auto_ap/ssr/pos/expected_deposits.clj | 182 +++++++++---- src/clj/auto_ap/ssr/pos/refunds.clj | 93 +++---- src/clj/auto_ap/ssr/pos/sales_orders.clj | 239 +++++++++++++----- src/clj/auto_ap/ssr/pos/tenders.clj | 112 ++++---- 13 files changed, 684 insertions(+), 381 deletions(-) create mode 100644 resources/datomic/extensions.edn create mode 100644 src/clj/auto_ap/query_params.clj diff --git a/iol_ion/src/iol_ion/query.clj b/iol_ion/src/iol_ion/query.clj index 85ad604e..76ed64d7 100644 --- a/iol_ion/src/iol_ion/query.clj +++ b/iol_ion/src/iol_ion/query.clj @@ -101,6 +101,15 @@ [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))] [(:e r) (first (:v r)) (second (:v r))])) +(defn scan-expected-deposits [db clients start end] + (for [c clients + :let [c (entid db c)] + r (seq (dc/index-range db + :expected-deposit/client+date + [c (or start #inst "2001-01-01T08:00:00.000-00:00") ] + [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))] + [(:e r) (first (:v r)) (second (:v r))])) + (defn scan-cash-drawer-shifts [db clients start end] (for [c clients :let [c (entid db c)] @@ -145,3 +154,6 @@ [c (or start #inst "2001-01-01T08:00:00.000-00:00") ] [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))] [(:e r) (first (:v r)) (second (:v r))])) + +(defn ident [x] + (:db/ident x)) diff --git a/resources/datomic/extensions.edn b/resources/datomic/extensions.edn new file mode 100644 index 00000000..5b7271be --- /dev/null +++ b/resources/datomic/extensions.edn @@ -0,0 +1 @@ +{:xforms [clj-time.coerce/to-date clj-time.coerce/from-date iol-ion.query/ident ]} diff --git a/resources/schema.edn b/resources/schema.edn index 4d177fba..9c2a6558 100644 --- a/resources/schema.edn +++ b/resources/schema.edn @@ -1823,6 +1823,11 @@ :db/valueType :db.type/tuple :db/tupleAttrs [ :cash-drawer-shift/client :cash-drawer-shift/date] :db/cardinality :db.cardinality/one + :db/index true} + {:db/ident :expected-deposit/client+date + :db/valueType :db.type/tuple + :db/tupleAttrs [ :expected-deposit/client :expected-deposit/date] + :db/cardinality :db.cardinality/one :db/index true}] diff --git a/src/clj/auto_ap/query_params.clj b/src/clj/auto_ap/query_params.clj new file mode 100644 index 00000000..3f4f8d4b --- /dev/null +++ b/src/clj/auto_ap/query_params.clj @@ -0,0 +1,119 @@ +(ns auto-ap.query-params + (:require [auto-ap.time :as atime] + [auto-ap.ssr.svg :as svg] + [clj-time.core :as time] + [clojure.string :as str])) + + +(defn wrap-parse-query-params [handler parser] + (fn parsed-handler [request] + (handler (assoc request :parsed-query-params (parser (->> (concat (:hx-query-params request) (:query-params request)) + (map (fn [[k v]] [(keyword k) v])) + (into {}))))))) + +(defn parse-key [k parser] + (fn [query-params] + (if (contains? query-params k) + (update query-params k #(some-> % not-empty parser)) + query-params))) + +(defn parse-date [d] + (atime/parse d atime/normal-date)) + +(defn parse-keyword [ns name] + (some->> name (keyword ns))) + +(defn parse-sort [grid-spec q] + (if (not-empty q) + (->> + (str/split q #",") + (map (fn [k] + (let [[key asc?] (str/split k #":") + matching-header (first (filter #(= (str key) (:sort-key %)) (:headers grid-spec)))] + {:sort-key (str key) + :asc (boolean (= "asc" asc?)) + :matching-header matching-header + :name (:name matching-header) + :sort-icon (if (= (boolean (= "asc" asc?)) true) + svg/sort-down + svg/sort-up)}))) + (filter :matching-header) + (into [])) + [])) + +(defn parse-long [l] + (try + (Long/parseLong l) + (catch Exception e + nil))) + +(defn parse-double [l] + (try + (Double/parseDouble l) + (catch Exception e + nil))) + +(defn apply-date-range [source-key start-date-key end-date-key] + (fn [query-params] + (dissoc + (condp = (source-key query-params) + "week" + (assoc query-params + start-date-key (time/plus (time/now) (time/days -7)) + end-date-key (time/now)) + + "month" + (assoc query-params + start-date-key (time/plus (time/now) (time/months -1)) + end-date-key (time/now)) + + "year" + (assoc query-params + start-date-key (time/plus (time/now) (time/years -1)) + end-date-key (time/now)) + + "all" + (assoc query-params + start-date-key (time/plus (time/now) (time/years -3)) + end-date-key (time/now)) + + query-params) + :date-range))) + +(defn apply-toggle-sort [grid-spec] + (fn toggle-sort [query-params] + (if (:toggle-sort query-params) + (let [key-to-toggle (:toggle-sort query-params) + current-sort (:sort query-params) + presently-sorted? ((set (map :sort-key current-sort)) key-to-toggle) + new-sort (if presently-sorted? + (mapv + (fn [s] + (if (= (:sort-key s) + key-to-toggle) + (-> s + (update :asc + #(boolean (not %))) + (update :sort-icon (fn [x] + (if (= x svg/sort-down) + svg/sort-up + svg/sort-down)))) + s)) + current-sort) + (conj current-sort {:sort-key key-to-toggle + :asc true + :name (:name (first (filter #(= (str key-to-toggle) (:sort-key %)) (:headers grid-spec)))) + :sort-icon svg/sort-down}))] + (-> query-params + (assoc :sort new-sort) + (dissoc :toggle-sort))) + query-params))) + +(defn apply-remove-sort [] + (fn remove-sort [query-params] + (if-let [remove-sort-key (:remove-sort query-params)] + (-> query-params + (update :sort (fn [current-sort] + (filterv (comp (complement #{remove-sort-key}) :sort-key) current-sort))) + (dissoc :remove-sort)) + query-params))) diff --git a/src/clj/auto_ap/ssr/components.clj b/src/clj/auto_ap/ssr/components.clj index 6904fafd..86dae15f 100644 --- a/src/clj/auto_ap/ssr/components.clj +++ b/src/clj/auto_ap/ssr/components.clj @@ -15,6 +15,7 @@ (def breadcrumbs breadcrumbs/breadcrumbs-) (def button buttons/button-) +(def a-button buttons/a-button-) (def button-icon buttons/button-icon-) (def icon-button buttons/icon-button-) (def a-icon-button buttons/a-icon-button-) diff --git a/src/clj/auto_ap/ssr/components/buttons.clj b/src/clj/auto_ap/ssr/components/buttons.clj index 1beef696..14a7aa1e 100644 --- a/src/clj/auto_ap/ssr/components/buttons.clj +++ b/src/clj/auto_ap/ssr/components/buttons.clj @@ -17,6 +17,19 @@ [:div.ml-3 "Loading..."]] (into [:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center] children)]) +(defn a-button- [params & children] + [:a (update params + :class #(cond-> % + true (str " focus:ring-4 font-bold rounded-lg text-xs p-3 text-center mr-2 inline-flex items-center hover:scale-105 transition duration-100 justify-center") + (= :secondary (:color params)) (str " text-white bg-blue-500 hover:bg-blue-600 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700") + (= :primary (:color params)) (str " text-white bg-green-500 hover:bg-green-600 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 ") + (nil? (:color params)) + (str " bg-white dark:bg-gray-600 border-gray-300 dark:border-gray-700 text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-100 font-medium border border-gray-300 dark:border-gray-700"))) + [:div.htmx-indicator.flex.items-center + (svg/spinner {:class "inline w-4 h-4 text-white"}) + [:div.ml-3 "Loading..."]] + (into [:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center] children)]) + (defn icon-button- [params & children] (into [:button diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index fbd1fb7f..230362ec 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -1,23 +1,24 @@ (ns auto-ap.ssr.grid-page-helper (:require + [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components :as com] + [auto-ap.ssr.svg :as svg] [auto-ap.ssr.ui :refer [base-page]] [auto-ap.ssr.utils :refer [html-response]] - [hiccup2.core :as hiccup] [bidi.bidi :as bidi] - [auto-ap.ssr-routes :as ssr-routes] [cemerick.url :as url] [clojure.string :as str] - [auto-ap.ssr.svg :as svg] + [hiccup2.core :as hiccup] + [clj-time.core :as time] [auto-ap.time :as atime] - [clj-time.core :as time])) + [auto-ap.query-params :as query-params])) -(defn row* [gridspec user entity {:keys [flash? delete-after-settle? params] :as options}] +(defn row* [gridspec user entity {:keys [flash? delete-after-settle? request] :as options}] (let [cells (->> gridspec :headers (filter (fn [h] (if (and (:hide? h) - ((:hide? h) params)) + ((:hide? h) request)) nil h))) (mapv (fn [header] @@ -28,7 +29,7 @@ cells (conj cells (com/data-grid-right-stack-cell {} (into [:form [:input {:type :hidden :name "id" :value ((:id-fn gridspec) entity)}]] - ((:row-buttons gridspec) user entity))))] + ((:row-buttons gridspec) request entity))))] ;; TODO double check usage of row buttons user and identity in callers (apply com/data-grid-row {:class (when flash? "live-added") @@ -66,12 +67,11 @@ )) "default sort")) -(defn table* [grid-spec user {{:keys [start per-page clients flash-id sort]} :parsed-query-params :as params}] +(defn table* [grid-spec user {{:keys [start per-page flash-id sort]} :parsed-query-params :as request}] (let [start (or start 0) per-page (or per-page 30) [entities total] ((:fetch-page grid-spec) - user - params)] + request)] (com/data-grid-card {:id (:id grid-spec) :title (:title grid-spec) @@ -82,9 +82,9 @@ :subtitle [:div.flex.items-center.gap-2 [:span (format "Total %s: %d, " (:entity-name grid-spec) total)] (sort-by-list grid-spec sort)] - :action-buttons ((:action-buttons grid-spec) user params) + :action-buttons ((:action-buttons grid-spec) request) :rows (for [entity entities] - (row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity)) :params params})) + (row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity)) :request request})) :thead-params {:hx-get (bidi/path-for ssr-routes/only-routes (:route grid-spec)) :hx-target (str "#" (:id grid-spec)) @@ -99,7 +99,7 @@ (fn [h] (cond (and (:hide? h) - ((:hide? h) params)) + ((:hide? h) request)) nil (:sort-key h) @@ -126,43 +126,9 @@ -(defn parse-sort [grid-spec q] - (if (not-empty q) - (->> - (str/split q #",") - (map (fn [k] - (let [[key asc?] (str/split k #":") - matching-header (first (filter #(= (str key) (:sort-key %)) (:headers grid-spec)))] - {:sort-key (str key) - :asc (boolean (= "asc" asc?)) - :matching-header matching-header - :name (:name matching-header) - :sort-icon (if (= (boolean (= "asc" asc?)) true) - svg/sort-down - svg/sort-up)}))) - (filter :matching-header) - (into [])) - [])) -(defn toggle-sort [grid-spec q k] - (if ((set (map :sort-key q)) k) - (mapv - (fn [s] - (if (= (:sort-key s) - k) - (-> s - (update :asc - #(boolean (not %))) - (update :sort-icon (fn [x] - (if (= x svg/sort-down) - svg/sort-up - svg/sort-down)))) - s)) - q) - (conj q {:sort-key k - :asc true - :name (:name (first (filter #(= (str k) (:sort-key %)) (:headers grid-spec)))) - :sort-icon svg/sort-down}))) + + (defn sort->query [s] (str/join "," (map (fn [k] (format "%s:%s" (:sort-key k) (if (= true (:asc k)) @@ -176,77 +142,72 @@ (update :sort sort->query) (url/map->query))) -(defn use-date-range [query-params date-range] - (condp = date-range - "week" - (assoc query-params - :start-date (atime/unparse-local (time/plus (time/now) (time/days -7)) atime/normal-date) - :end-date (atime/unparse-local (time/now) atime/normal-date)) +(defn default-unparse-query-params [query-params] + (reduce + (fn [query-params [k value]] + (assoc query-params k + (cond (= k :sort) + (sort->query value) - "month" - (assoc query-params - :start-date (atime/unparse-local (time/plus (time/now) (time/months -1)) atime/normal-date) - :end-date (atime/unparse-local (time/now) atime/normal-date)) + (instance? org.joda.time.base.AbstractInstant value) + (atime/unparse-local value atime/normal-date) - "year" - (assoc query-params - :start-date (atime/unparse-local (time/plus (time/now) (time/years -1)) atime/normal-date) - :end-date (atime/unparse-local (time/now) atime/normal-date)) + (instance? Long value) + (str value) - "all" - (assoc query-params - :start-date (atime/unparse-local (time/plus (time/now) (time/years -3)) atime/normal-date) - :end-date (atime/unparse-local (time/now) atime/normal-date)) + (instance? Double value) + (format "%.2f" value) + (instance? Float value) + (format "%.2f" value) + + (keyword? value) + (name value) + + :else + value))) + query-params query-params)) -(defn extract-params [grid-spec {:keys [query-params hx-query-params identity session] :as request}] - (let [{hx-start "start" hx-per-page "per-page" hx-sort "sort" } hx-query-params - {q-start "start" q-per-page "per-page" q-sort "sort" q-toggle-sort "toggle-sort" q-remove-sort "remove-sort" date-range "date-range"} query-params - raw-query-params (merge (or hx-query-params {}) query-params) - - parsed-query-params (cond-> (into {} (map (fn [[k v]] [(keyword k) v]) raw-query-params)) - hx-start (assoc :start (some-> hx-start not-empty (Long/parseLong ))) - q-start (assoc :start (some-> q-start not-empty (Long/parseLong ))) - hx-per-page (assoc :per-page (some-> hx-per-page not-empty (Long/parseLong ))) - q-per-page (assoc :per-page (some-> q-per-page not-empty (Long/parseLong ))) - hx-sort (assoc :sort (parse-sort grid-spec hx-sort)) - q-sort (assoc :sort (parse-sort grid-spec q-sort)) - (not-empty q-toggle-sort) (update :sort #(toggle-sort grid-spec % q-toggle-sort) ) - (not-empty q-remove-sort) (update :sort (fn [s] (filter (comp (complement #{q-remove-sort}) :sort-key) s)) ) - date-range (use-date-range date-range) - true (dissoc :toggle-sort :remove-sort :date-range))] - {:raw-query-params raw-query-params - :parsed-query-params parsed-query-params - :client-selection (:client-selection (:session request)) - :clients (:clients request) - :client (:client request) - :request request})) + + (defn table [grid-spec {:keys [identity] :as request}] - (let [params (extract-params grid-spec request)] + (let [unparse-query-params (or (:unparse-query grid-spec) + default-unparse-query-params)] (html-response (table* grid-spec identity - params) - :headers {"hx-push-url" (str "?" (params->query-string params))} + request) + :headers {"hx-push-url" (str "?" (url/map->query (unparse-query-params (:parsed-query-params request))))} :oob (when-let [oob-render (:oob-render grid-spec)] - (oob-render identity params))))) + (oob-render request))))) + (defn page [grid-spec {:keys [identity] :as request}] - (let [params (extract-params grid-spec request)] - (base-page - request - (com/page {:nav (:nav grid-spec) - :page-specific (when-let [page-specific-nav (:page-specific-nav grid-spec)] - [:div#page-specific-nav (page-specific-nav params)]) - :client-selection (:client-selection (:session request)) - :clients (:clients request) - :client (:client request) - :identity (:identity request)} - (apply com/breadcrumbs {} (:breadcrumbs grid-spec)) - (table* grid-spec - identity - params - )) - (:title grid-spec)))) + (base-page + request + (com/page {:nav (:nav grid-spec) + :page-specific (when-let [page-specific-nav (:page-specific-nav grid-spec)] + [:div#page-specific-nav (page-specific-nav request)]) + :client-selection (:client-selection (:session request)) + :clients (:clients request) + :client (:client request) + :identity (:identity request)} + (apply com/breadcrumbs {} (:breadcrumbs grid-spec)) + (table* grid-spec + identity + request)) + (:title grid-spec))) + +(defn default-parse-query-params [grid-spec] + (comp + (query-params/apply-remove-sort) + (query-params/apply-toggle-sort grid-spec) + (query-params/apply-date-range :date-range :start-date :end-date) + (query-params/parse-key :exact-match-id query-params/parse-long) + (query-params/parse-key :sort #(query-params/parse-sort grid-spec %)) + (query-params/parse-key :per-page query-params/parse-long) + (query-params/parse-key :start query-params/parse-long) + (query-params/parse-key :start-date query-params/parse-date) + (query-params/parse-key :end-date query-params/parse-date))) diff --git a/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj b/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj index 93574c6b..df5cc696 100644 --- a/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj +++ b/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj @@ -9,6 +9,7 @@ pull-many query2]] [auto-ap.graphql.utils :refer [extract-client-ids]] + [auto-ap.query-params :as query-params] [auto-ap.routes.utils :refer [wrap-client-redirect-unauthenticated wrap-secure]] [auto-ap.ssr-routes :as ssr-routes] @@ -21,7 +22,6 @@ [datomic.api :as dc])) ;; always should be fast -;; make params parsing composable (defn filters [params] [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" @@ -35,36 +35,26 @@ (date-range-field* params) (total-field* params)]]) -(defn <-datomic [result] - (cond-> result - true (update :cash-drawer-shift/date c/from-date))) - -(def default-read '[* +(def default-read '[:db/id + :cash-drawer-shift/paid-in :cash-drawer-shift/paid-out :cash-drawer-shift/expected-cash :cash-drawer-shift/opened-cash + [:cash-drawer-shift/date :xform clj-time.coerce/from-date] {:cash-drawer-shift/client [:client/name :db/id :client/code]}]) -(defn fetch-ids [db args] - (let [query-params (:parsed-query-params args) - valid-clients (extract-client-ids (:clients args) +(defn fetch-ids [db request] + (let [query-params (:parsed-query-params request) + valid-clients (extract-client-ids (:clients request) (:client-id query-params) (when (:client-code query-params) [:client/code (:client-code query-params)])) valid-clients (->> valid-clients (take 10) set) - start-date (some-> args - :parsed-query-params - :start-date - (atime/parse atime/normal-date)) - end-date (some-> args - :parsed-query-params - :end-date - (atime/parse atime/normal-date)) query (cond-> {:query {:find [] :in ['$ '[?clients ?start-date ?end-date]] :where '[[(iol-ion.query/scan-cash-drawer-shifts $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} :args [db [valid-clients - (some-> start-date c/to-date) - (some-> end-date c/to-date )]]} + (some-> (:start-date query-params) c/to-date) + (some-> (:end-date query-params) c/to-date )]]} (:sort query-params) (add-sorter-fields {"client" ['[?e :cash-drawer-shift/client ?c] '[?c :client/name ?sort-client]] "date" ['[?e :cash-drawer-shift/date ?sort-date]] @@ -80,18 +70,6 @@ :where []} :args [(:exact-match-id query-params)]}) - start-date - (merge-query {:query {:in '[?start-date] - :where ['[?e :cash-drawer-shift/date ?date] - '[(>= ?date ?start-date)]]} - :args [(c/to-date start-date)]}) - - end-date - (merge-query {:query {:in '[?end-date] - :where ['[?e :cash-drawer-shift/date ?date] - '[(<= ?date ?end-date)]]} - :args [(c/to-date end-date)]}) - true (merge-query {:query {:find ['?sort-default '?e] :where ['[?e :cash-drawer-shift/date ?sort-default]]}}))] @@ -105,16 +83,14 @@ (group-by :db/id)) cash-drawer-shifts (->> ids (map results) - (map first) - (mapv <-datomic) - )] + (map first))] cash-drawer-shifts)) -(defn fetch-page [_ args] +(defn fetch-page [request] (let [db (dc/db conn) - {ids-to-retrieve :ids matching-count :count} (fetch-ids db args)] + {ids-to-retrieve :ids matching-count :count} (fetch-ids db request)] - [(->> (hydrate-results ids-to-retrieve db args)) + [(->> (hydrate-results ids-to-retrieve db request)) matching-count])) (def grid-page {:id "cash-drawer-shift-table" @@ -123,8 +99,8 @@ :id-fn :db/id :fetch-page fetch-page :oob-render - (fn [_ params] - [(assoc-in (date-range-field* params) [1 :hx-swap-oob] true)]) + (fn [request] + [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "POS"] @@ -135,7 +111,7 @@ :title "Cash drawer shifts" :entity-name "Cash drawer shift" :route :pos-cash-drawer-shift-table - :action-buttons (fn [_ args]) + :action-buttons (fn [request]) :row-buttons (fn [_ e]) :headers [{:key "client" :name "Client" @@ -167,8 +143,13 @@ (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page)) -(def table (partial helper/table grid-page)) -(def page (partial helper/page grid-page)) +(def table + (query-params/wrap-parse-query-params (partial helper/table grid-page) + (helper/default-parse-query-params grid-page)) + + ) +(def page (query-params/wrap-parse-query-params (partial helper/page grid-page) + (helper/default-parse-query-params grid-page))) (def key->handler {:pos-cash-drawer-shifts (wrap-client-redirect-unauthenticated (wrap-secure page)) diff --git a/src/clj/auto_ap/ssr/pos/common.clj b/src/clj/auto_ap/ssr/pos/common.clj index b87b78c7..3eeaa63d 100644 --- a/src/clj/auto_ap/ssr/pos/common.clj +++ b/src/clj/auto_ap/ssr/pos/common.clj @@ -1,9 +1,15 @@ (ns auto-ap.ssr.pos.common (:require [auto-ap.ssr.components :as com] - [auto-ap.time :as atime])) + [auto-ap.time :as atime] + [auto-ap.ssr.svg :as svg])) +;; TODO make date-input take clj date +;; TODO make total fields take decimals -(defn date-range-field* [params] +(defn date-range-field* [request] + (println "DR" (some-> request + :parsed-query-params + )) [:div#date-range {} (com/field {:label "Date Range"} [:div.space-y-4 @@ -16,26 +22,22 @@ ] [:div.flex.space-x-1.items-baseline (com/date-input {:name "start-date" - :value (some-> params + :value (some-> request :parsed-query-params :start-date - (atime/parse atime/normal-date) - (atime/unparse-local - atime/normal-date)) + (atime/unparse-local atime/normal-date)) :placeholder "Date" :size :small}) (com/date-input {:name "end-date" - :value (some-> params + :value (some-> request :parsed-query-params :end-date - (atime/parse atime/normal-date) - (atime/unparse-local - atime/normal-date)) + (atime/unparse-local atime/normal-date)) :placeholder "Date" - :size :small})]] - )]) -(defn processor-field* [params] + :size :small})]])]) + +(defn processor-field* [request] (com/field {:label "Processor"} (com/radio {:size :small :name "processor" @@ -56,14 +58,14 @@ {:value "na" :content "No Processor"}]}))) -(defn total-field* [params] +(defn total-field* [request] (com/field {:label "Total"} [:div.flex.space-x-4.items-baseline (com/money-input {:name "total-gte" :id "total-gte" :hx-preserve "true" :class "hot-filter" - :value (:total-gte (:parsed-query-params params)) + :value (:total-gte (:parsed-query-params request)) :placeholder "0.01" :size :small}) [:div.align-baseline @@ -72,6 +74,16 @@ :hx-preserve "true" :id "total-lte" :class "hot-filter" - :value (:total-lte (:parsed-query-params params)) + :value (:total-lte (:parsed-query-params request)) :placeholder "9999.34" :size :small})])) + +(defn exact-match-id-field* [request] + (when-let [exact-match-id (:exact-match-id (:parsed-query-params request))] + [:div + (com/field {:label "Exact match"} + (com/pill {:color :primary} + [:span.inline-flex.gap-2 + exact-match-id + [:a {:href "?exact-match-id="} + [:div.h-4.w-4 svg/x]]]))])) diff --git a/src/clj/auto_ap/ssr/pos/expected_deposits.clj b/src/clj/auto_ap/ssr/pos/expected_deposits.clj index 142c0b3d..1aebefd0 100644 --- a/src/clj/auto_ap/ssr/pos/expected_deposits.clj +++ b/src/clj/auto_ap/ssr/pos/expected_deposits.clj @@ -1,21 +1,31 @@ (ns auto-ap.ssr.pos.expected-deposits (:require - [auto-ap.datomic.expected-deposit :as d-expected-deposit] + [auto-ap.datomic + :refer [add-sorter-fields + apply-pagination + apply-sort-3 + conn + merge-query + pull-many + query2]] + [auto-ap.graphql.utils :refer [extract-client-ids]] + [auto-ap.query-params :as query-params] [auto-ap.routes.utils :refer [wrap-client-redirect-unauthenticated wrap-secure]] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components :as com] [auto-ap.ssr.grid-page-helper :as helper] - [auto-ap.ssr.pos.common :refer [date-range-field* total-field*]] + [auto-ap.ssr.pos.common :refer [date-range-field* total-field* exact-match-id-field*]] [auto-ap.ssr.svg :as svg] [auto-ap.time :as atime] - [bidi.bidi :as bidi])) + [bidi.bidi :as bidi] + [clj-time.coerce :as c] + [datomic.api :as dc] + [auto-ap.client-routes :as client-routes])) -;; TODO refunds -;; always should be fast ;; make params parsing composable -(defn filters [params] +(defn filters [request] [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" "hx-get" (bidi/path-for ssr-routes/only-routes :pos-expected-deposit-table) @@ -24,36 +34,112 @@ #_#_:hx-disabled-elt "find fieldset"} [:fieldset.space-y-6 - (date-range-field* params) - (total-field* params)]]) + (date-range-field* request) + (exact-match-id-field* request)]]) -(defn args->graphql-params [args] - {:clients (:clients args) - :start (:start (:parsed-query-params args)) - :sort (:sort (:parsed-query-params args)) - :per-page (:per-page (:parsed-query-params args)) - :exact-match-id (some-> (:parsed-query-params args) :exact-match-id Long/parseLong) - :date-range {:start (some-> args - :parsed-query-params - :start-date - (atime/parse atime/normal-date)) - :end (some-> args - :parsed-query-params - :end-date - (atime/parse atime/normal-date))} - :total-gte (some-> args :raw-query-params (get "total-gte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) - :total-lte (some-> args :raw-query-params (get "total-lte") not-empty (#(if (string? %) (Double/parseDouble %) (double %))))}) +(def default-read '[:db/id + :expected-deposit/location + :expected-deposit/total + :expected-deposit/fee + [:expected-deposit/date :xform clj-time.coerce/from-date] + {:expected-deposit/client [:client/name :db/id :client/code] + [:expected-deposit/status :xform iol-ion.query/ident] [:db/ident] + :transaction/_expected-deposit [:transaction/date :db/id]}]) + +(defn fetch-ids [db request] + + (let [query-params (:parsed-query-params request) + valid-clients (extract-client-ids (:clients request) + (:client request) + (:client-id query-params) + (when (:client-code query-params) + [:client/code (:client-code query-params)])) + query (cond-> {:query {:find [] + :in ['$ '[?clients ?start-date ?end-date]] + :where '[[(iol-ion.query/scan-expected-deposits $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} + :args [db [valid-clients + (some-> (:start-date query-params) c/to-date) + (some-> (:end-date query-params) c/to-date)]]} + (:sort query-params) (add-sorter-fields {"client" ['[?e :expected-deposit/client ?c] + '[?c :client/name ?sort-client]] + "location" ['[?e :expected-deposit/location ?sort-location]] + "date" ['[?e :expected-deposit/date ?sort-date]] + "total" ['[?e :expected-deposit/total ?sort-total]] + "fee" ['[?e :expected-deposit/fee ?sort-fee]]} + query-params) + + (:exact-match-id query-params) + (merge-query {:query {:in ['?e] + :where []} + :args [(:exact-match-id query-params)]}) + + + (:total-gte query-params) + (merge-query {:query {:in ['?total-gte] + :where ['[?e :expected-deposit/total ?a] + '[(>= ?a ?total-gte)]]} + :args [(:total-gte query-params)]}) + + (:total-lte query-params) + (merge-query {:query {:in ['?total-lte] + :where ['[?e :expected-deposit/total ?a] + '[(<= ?a ?total-lte)]]} + :args [(:total-lte query-params)]}) + + (:total query-params) + (merge-query {:query {:in ['?total] + :where ['[?e :expected-deposit/total ?expected-deposit-total] + '[(iol-ion.query/dollars= ?expected-deposit-total ?total)]]} + :args [(:total query-params)]}) + + true + (merge-query {:query {:find ['?sort-default '?e] + :where ['[?e :expected-deposit/date ?sort-default]]}}))] + + (cond->> (query2 query) + true (apply-sort-3 query-params) + true (apply-pagination query-params)))) + +(defn hydrate-results [ids db _] + (let [results (->> (pull-many db default-read ids) + (group-by :db/id)) + payments (->> ids + (map results) + (map first) + (map (fn get-totals [ed] + (assoc ed :totals + (->> (dc/q '[:find ?d4 (count ?c) (sum ?a) + :in $ ?ed + :where [?ed :expected-deposit/charges ?c] + [?c :charge/total ?a] + [?o :sales-order/charges ?c] + [?o :sales-order/date ?d] + [(clj-time.coerce/from-date ?d) ?d2] + [(auto-ap.time/localize ?d2) ?d3] + [(clj-time.coerce/to-local-date ?d3) ?d4]] + (dc/db conn) + (:db/id ed)) + (map (fn [[date count amount]] + {:date (c/to-date-time date) + :count count + :amount amount})))))))] + payments)) + +(defn fetch-page [args] + (let [db (dc/db conn) + {ids-to-retrieve :ids matching-count :count} (fetch-ids db args)] + + [(->> (hydrate-results ids-to-retrieve db args)) + matching-count])) (def grid-page {:id "expected-deposit-table" :nav (com/main-aside-nav) :page-specific-nav filters :id-fn :db/id - :fetch-page (fn [_ args] - (d-expected-deposit/get-graphql - (args->graphql-params args))) + :fetch-page fetch-page :oob-render - (fn [_ params] - [(assoc-in (date-range-field* params) [1 :hx-swap-oob] true)]) + (fn [request] + [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "POS"] @@ -64,19 +150,18 @@ :title "Expected deposits" :entity-name "Expected deposit" :route :pos-expected-deposit-table - :action-buttons (fn [_ args] - #_(let [{:keys [total tax]} (d-sales/summarize-graphql (args->graphql-params args))] - [ - (com/pill {:color :primary} - (format "Total $%.2f" total) - ) - (com/pill {:color :secondary} - (format "Tax $%.2f" tax ) - )])) + :action-buttons (fn [request] + ) :row-buttons (fn [_ e] - (when (:expected-deposit/reference-link e) - [(com/a-icon-button {:href (:expected-deposit/reference-link e)} - svg/external-link)])) + [ + (when (:expected-deposit/reference-link e) + (com/a-icon-button {:href (:expected-deposit/reference-link e)} + svg/external-link)) + (when-let [transaction-id (-> e (:transaction/_expected-deposit) first :db/id)] + (com/a-button {:href (str (bidi/path-for client-routes/routes + :transactions) + "?exact-match-id=" + transaction-id)} "Transaction"))]) :headers [{:key "client" :name "Client" :sort-key "client" @@ -102,8 +187,19 @@ (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page)) -(def table (partial helper/table grid-page)) -(def page (partial helper/page grid-page)) +(def table (-> (partial helper/table grid-page) + (query-params/wrap-parse-query-params + (comp + (query-params/parse-key :total-gte query-params/parse-double) + (query-params/parse-key :total-lte query-params/parse-double) + (helper/default-parse-query-params grid-page))))) + +(def page (-> (partial helper/page grid-page) + (query-params/wrap-parse-query-params + (comp + (query-params/parse-key :total-gte query-params/parse-double) + (query-params/parse-key :total-lte query-params/parse-double) + (helper/default-parse-query-params grid-page))))) (def key->handler {:pos-expected-deposits (wrap-client-redirect-unauthenticated (wrap-secure page)) diff --git a/src/clj/auto_ap/ssr/pos/refunds.clj b/src/clj/auto_ap/ssr/pos/refunds.clj index e767901b..b774b226 100644 --- a/src/clj/auto_ap/ssr/pos/refunds.clj +++ b/src/clj/auto_ap/ssr/pos/refunds.clj @@ -20,13 +20,14 @@ [bidi.bidi :as bidi] [clj-time.coerce :as c] [datomic.api :as dc] - [clojure.set :as set])) + [clojure.set :as set] + [auto-ap.query-params :as query-params])) ;; TODO refunds ;; always should be fast ;; make params parsing composable -(defn filters [params] +(defn filters [request] [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" "hx-get" (bidi/path-for ssr-routes/only-routes :pos-refund-table) @@ -35,47 +36,37 @@ #_#_:hx-disabled-elt "find fieldset"} [:fieldset.space-y-6 - (date-range-field* params) - (total-field* params)]]) + (date-range-field* request) + (total-field* request)]]) -(defn <-datomic [result] - (cond-> result - true (update :sales-refund/date c/from-date))) - -(def default-read '[* +(def default-read '[:db/id + :sales-refund/total + :sales-refund/fee + :sales-refund/type + [:sales-refund/date :xform clj-time.coerce/from-date] {:sales-refund/client [:client/name :db/id :client/code]}]) -(defn fetch-ids [db args] - (let [query-params (:parsed-query-params args) - valid-clients (extract-client-ids (:clients args) +(defn fetch-ids [db request] + (let [query-params (:parsed-query-params request) + valid-clients (extract-client-ids (:clients request) (:client-id query-params) (when (:client-code query-params) [:client/code (:client-code query-params)])) valid-clients (->> valid-clients (take 10) set) - start-date (some-> args - :parsed-query-params - :start-date - (atime/parse atime/normal-date)) - end-date (some-> args - :parsed-query-params - :end-date - (atime/parse atime/normal-date)) - total-gte (some-> args :parsed-query-params :total-gte not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) - total-lte (some-> args :parsed-query-params :total-lte not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) query (cond-> {:query {:find [] :in ['$ '[?clients ?start-date ?end-date]] :where '[[(iol-ion.query/scan-sales-refunds $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} :args [db [valid-clients - (some-> start-date c/to-date) - (some-> end-date c/to-date )]]} + (some-> query-params :start-date c/to-date) + (some-> query-params :end-date c/to-date )]]} (:sort query-params) (add-sorter-fields {"client" ['[?e :sales-refund/client ?c] '[?c :client/name ?sort-client]] "date" ['[?e :sales-refund/date ?sort-date]] "total" ['[?e :sales-refund/total ?sort-total]] "fee" ['[?e :sales-refund/fee ?sort-tip]] - "type" ['[?e :sales-refund/type ?type]]} + "type" ['[?e :sales-refund/type ?sort-type]]} query-params) (:exact-match-id query-params) @@ -83,29 +74,17 @@ :where []} :args [(:exact-match-id query-params)]}) - total-gte + (:total-gte query-params) (merge-query {:query {:in ['?total-gte] :where ['[?e :sales-refund/total ?a] '[(>= ?a ?total-gte)]]} - :args [total-gte]}) + :args [(:total-gte query-params)]}) - total-lte + (:total-lte query-params) (merge-query {:query {:in ['?total-lte] :where ['[?e :sales-refund/total ?a] '[(<= ?a ?total-lte)]]} - :args [total-lte]}) - - start-date - (merge-query {:query {:in '[?start-date] - :where ['[?e :sales-refund/date ?date] - '[(>= ?date ?start-date)]]} - :args [(c/to-date start-date)]}) - - end-date - (merge-query {:query {:in '[?end-date] - :where ['[?e :sales-refund/date ?date] - '[(<= ?date ?end-date)]]} - :args [(c/to-date end-date)]}) + :args [(:total-lte query-params)]}) true (merge-query {:query {:find ['?sort-default '?e] @@ -120,16 +99,14 @@ (group-by :db/id)) refunds (->> ids (map results) - (map first) - (mapv <-datomic) - )] + (map first))] refunds)) -(defn fetch-page [_ args] +(defn fetch-page [request] (let [db (dc/db conn) - {ids-to-retrieve :ids matching-count :count} (fetch-ids db args)] + {ids-to-retrieve :ids matching-count :count} (fetch-ids db request)] - [(->> (hydrate-results ids-to-retrieve db args)) + [(->> (hydrate-results ids-to-retrieve db request)) matching-count])) (def grid-page {:id "refund-table" @@ -138,8 +115,8 @@ :id-fn :db/id :fetch-page fetch-page :oob-render - (fn [_ params] - [(assoc-in (date-range-field* params) [1 :hx-swap-oob] true)]) + (fn [request] + [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "POS"] @@ -150,8 +127,8 @@ :title "Refunds" :entity-name "Refund" :route :pos-refund-table - :action-buttons (fn [_ args]) - :row-buttons (fn [_ e]) + :action-buttons (fn [request]) + :row-buttons (fn [request _]) :headers [{:key "client" :name "Client" :sort-key "client" @@ -177,8 +154,18 @@ (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page)) -(def table (partial helper/table grid-page)) -(def page (partial helper/page grid-page)) +(def table (-> (partial helper/table grid-page) + (query-params/wrap-parse-query-params + (comp + (query-params/parse-key :total-gte query-params/parse-double) + (query-params/parse-key :total-lte query-params/parse-double) + (helper/default-parse-query-params grid-page))))) +(def page (-> (partial helper/page grid-page) + (query-params/wrap-parse-query-params + (comp + (query-params/parse-key :total-gte query-params/parse-double) + (query-params/parse-key :total-lte query-params/parse-double) + (helper/default-parse-query-params grid-page))))) (def key->handler {:pos-refunds (wrap-client-redirect-unauthenticated (wrap-secure page)) diff --git a/src/clj/auto_ap/ssr/pos/sales_orders.clj b/src/clj/auto_ap/ssr/pos/sales_orders.clj index 3aaaf682..54133acc 100644 --- a/src/clj/auto_ap/ssr/pos/sales_orders.clj +++ b/src/clj/auto_ap/ssr/pos/sales_orders.clj @@ -1,6 +1,16 @@ (ns auto-ap.ssr.pos.sales-orders (:require + [auto-ap.datomic + :refer [add-sorter-fields + apply-pagination + apply-sort-3 + conn + merge-query + pull-many + query2]] [auto-ap.datomic.sales-orders :as d-sales] + [auto-ap.graphql.utils :refer [extract-client-ids]] + [auto-ap.query-params :as query-params] [auto-ap.routes.utils :refer [wrap-client-redirect-unauthenticated wrap-secure]] [auto-ap.ssr-routes :as ssr-routes] @@ -10,15 +20,16 @@ :refer [date-range-field* processor-field* total-field*]] [auto-ap.ssr.svg :as svg] [auto-ap.time :as atime] - [bidi.bidi :as bidi])) + [bidi.bidi :as bidi] + [clj-time.coerce :as c] + [datomic.api :as dc])) -;; TODO refunds -;; TODO expected deposits -;; always should be fast -;; make params parsing composable +;; TODO incorporate parsing into spec +;; TODO incorporate permissions into spec +;; TODO incorporate limiting clients into spec -(defn filters [params] +(defn filters [request] [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" "hx-get" (bidi/path-for ssr-routes/only-routes :pos-sales-table) @@ -27,25 +38,25 @@ #_#_:hx-disabled-elt "find fieldset"} [:fieldset.space-y-6 - (date-range-field* params) - [:div (total-field* params)] + (date-range-field* request) + (total-field* request) [:div (com/field {:label "Payment Method"} (com/radio {:size :small :name "payment-method" - :options [{:value "all" + :options [{:value "" :content "All"} - {:value "cash" + {:value "CASH" :content "Cash"} - {:value "card" + {:value "CARD" :content "Card"} - {:value "gift-card" + {:value "SQUARE_GIFT_CARD" :content "Gift Card"} - {:value "other" + {:value "OTHER" :content "Other"} ]}))] [:div - (processor-field* params)] + (processor-field* request)] [:div (com/field {:label "Category"} @@ -53,46 +64,132 @@ :class "hot-filter" :id "category" :hx-preserve "true" - :value (:category (:parsed-query-params params)) + :value (:category (:parsed-query-params request)) :placeholder "Fries" :size :small}))]]]) -(defn args->graphql-params [args] - {:clients (:clients args) - :start (:start (:parsed-query-params args)) - :sort (:sort (:parsed-query-params args)) - :per-page (:per-page (:parsed-query-params args)) - :category (not-empty (:category (:parsed-query-params args))) - :date-range {:start (some-> args - :parsed-query-params - :start-date - (atime/parse atime/normal-date)) - :end (some-> args - :parsed-query-params - :end-date - (atime/parse atime/normal-date))} - :total-gte (some-> args :raw-query-params (get "total-gte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) - :total-lte (some-> args :raw-query-params (get "total-lte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) - :type-name (condp = (:payment-method (:parsed-query-params args)) - "cash" "CASH" - "" nil - "all" nil - "card" "CARD" - "gift-card" "SQUARE_GIFT_CARD" - "other" "OTHER" - nil) - :processor (some-> args :parsed-query-params :processor not-empty keyword)}) +(def default-read '[:db/id + :sales-order/external-id, + :sales-order/location, + [:sales-order/date, :xform clj-time.coerce/from-date] + :sales-order/total, + :sales-order/tax, + :sales-order/tip, + :sales-order/line-items, + :sales-order/discount, + :sales-order/returns, + :sales-order/service-charge, + :sales-order/vendor, + :sales-order/source, + :sales-order/reference-link, + {:sales-order/client [:client/name :db/id :client/code] + :sales-order/charges [ + :charge/type-name, + :charge/total, + :charge/tax, + :charge/tip, + :charge/external-id, + :charge/note, + :charge/date, + :charge/client, + :charge/location, + :charge/reference-link, + {:charge/processor [:db/ident]} {:expected-deposit/_charges [:db/id]}]}]) + +(defn fetch-ids [db request] + (let [query-params (:parsed-query-params request) + valid-clients (extract-client-ids (:clients request) + (:client request) + (:client-id query-params) + (when (:client-code query-params) + [:client/code (:client-code query-params)])) + valid-clients (->> valid-clients + (take 10) + set) + query (cond-> {:query {:find [] + :in ['$ '[?clients ?start-date ?end-date]] + :where '[[(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} + :args [db [valid-clients + (some-> (:start-date query-params) c/to-date) + (some-> (:end-date query-params) c/to-date)]]} + (:sort query-params) (add-sorter-fields {"client" ['[?e :sales-order/client ?c] + '[?c :client/name ?sort-client]] + "date" ['[?e :sales-order/date ?sort-date]] + "total" ['[?e :sales-order/total ?sort-total]] + "tax" ['[?e :sales-order/tax ?sort-tax]] + "tip" ['[?e :sales-order/tip ?sort-tip]] + "source" ['[?e :sales-order/source ?sort-source]] + "processor" ['[?e :sales-order/processor ?p] + '[?p :db/ident ?p2] + '[(name ?p2) ?sort-processor]]} + query-params) + + (:exact-match-id query-params) + (merge-query {:query {:in ['?e] + :where []} + :args [(:exact-match-id query-params)]}) + + (:total-gte query-params) + (merge-query {:query {:in ['?total-gte] + :where ['[?e :sales-order/total ?a] + '[(>= ?a ?total-gte)]]} + :args [(:total-gte query-params)]}) + + (:total-lte query-params) + (merge-query {:query {:in ['?total-lte] + :where ['[?e :sales-order/total ?a] + '[(<= ?a ?total-lte)]]} + :args [(:total-lte query-params)]}) + + (not-empty (:payment-method query-params)) + (merge-query {:query {:in ['?type-name] + :where ['[?e :sales-order/charges ?chg] + '[?chg :charge/type-name ?type-name]]} + :args [(:payment-method query-params)]}) + + (not-empty (:category query-params)) + (merge-query {:query {:in ['?category] + :where ['[?e :sales-order/line-items ?li] + '[?li :order-line-item/category ?category]]} + :args [(:category query-params)]}) + + (:processor query-params) + (merge-query {:query {:in ['?processor] + :where ['[?e :sales-order/charges ?chg] + '[?chg :charge/processor ?processor]]} + :args [(:processor query-params)]}) + + + true + (merge-query {:query {:find ['?sort-default '?e]}}))] + (cond->> (query2 query) + true (apply-sort-3 query-params) + true (apply-pagination query-params)))) + +(defn hydrate-results [ids db _] + (let [results (->> (pull-many db default-read ids) + (group-by :db/id)) + charges (->> ids + (map results) + (map first))] + charges)) + +(defn fetch-page [request] + (let [db (dc/db conn) + {ids-to-retrieve :ids matching-count :count} (fetch-ids db request)] + + [(->> (hydrate-results ids-to-retrieve db request)) + matching-count])) + (def grid-page {:id "sales-table" :nav (com/main-aside-nav) :page-specific-nav filters :id-fn :db/id - :fetch-page (fn [_ args] - (d-sales/get-graphql - (args->graphql-params args))) + :fetch-page fetch-page :oob-render - (fn [_ params] - [(assoc-in (date-range-field* params) [1 :hx-swap-oob] true)]) + (fn [request] + [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "POS"] @@ -103,15 +200,16 @@ :title "Sales orders" :entity-name "Sales orders" :route :pos-sales-table - :action-buttons (fn [_ args] - (let [{:keys [total tax]} (d-sales/summarize-graphql (args->graphql-params args))] - [ - (com/pill {:color :primary} - (format "Total $%.2f" total) - ) - (com/pill {:color :secondary} - (format "Tax $%.2f" tax ) - )])) + :action-buttons (fn [request] + (let [{:keys [total tax]} (d-sales/summarize-orders (:ids (fetch-ids (dc/db conn) request)))] + (when (and total tax) + [ + (com/pill {:color :primary} + (format "Total $%.2f" total) + ) + (com/pill {:color :secondary} + (format "Tax $%.2f" tax ) + )]))) :row-buttons (fn [_ e] (when (:sales-order/reference-link e) [(com/a-icon-button {:href (:sales-order/reference-link e)} @@ -149,14 +247,37 @@ {:key "Payment methods" :name "Payment Methods" :render (fn [sales-order] - (for [method (->> sales-order :sales-order/charges (map :charge/type-name) set)] - (com/pill {:color :primary } - method)))}]}) + [:div.flex.space-x-2 + (for [payment-method (->> sales-order :sales-order/charges (map :charge/type-name) set)] + (com/pill {:color :primary } + (condp = payment-method + "CASH" "cash" + "" nil + "ALL" nil + "CARD" "card" + "SQUARE_GIFT_CARD" "gift card" + "OTHER" "other" + nil)))])}]}) (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page)) -(def table (partial helper/table grid-page)) -(def page (partial helper/page grid-page)) + + + +(def table (-> (partial helper/table grid-page) + (query-params/wrap-parse-query-params + (comp + (query-params/parse-key :processor #(query-params/parse-keyword "ccp-processor" %)) + (query-params/parse-key :total-gte query-params/parse-double) + (query-params/parse-key :total-lte query-params/parse-double) + (helper/default-parse-query-params grid-page))))) +(def page (-> (partial helper/page grid-page) + (query-params/wrap-parse-query-params + (comp + (query-params/parse-key :processor #(query-params/parse-keyword "ccp-processor" %)) + (query-params/parse-key :total-gte query-params/parse-double) + (query-params/parse-key :total-lte query-params/parse-double) + (helper/default-parse-query-params grid-page))))) (def key->handler {:pos-sales (wrap-client-redirect-unauthenticated (wrap-secure page)) diff --git a/src/clj/auto_ap/ssr/pos/tenders.clj b/src/clj/auto_ap/ssr/pos/tenders.clj index 1d6de99e..b615d195 100644 --- a/src/clj/auto_ap/ssr/pos/tenders.clj +++ b/src/clj/auto_ap/ssr/pos/tenders.clj @@ -20,13 +20,11 @@ [bidi.bidi :as bidi] [clj-time.coerce :as c] [datomic.api :as dc] - [clojure.set :as set])) + [auto-ap.query-params :as query-params])) -;; TODO refunds ;; always should be fast -;; make params parsing composable -(defn filters [params] +(defn filters [request] [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" "hx-get" (bidi/path-for ssr-routes/only-routes :pos-tender-table) @@ -35,52 +33,36 @@ #_#_:hx-disabled-elt "find fieldset"} [:fieldset.space-y-6 - (date-range-field* params) - (processor-field* params) - (total-field* params)]]) + (date-range-field* request) + (processor-field* request) + (total-field* request)]]) -(defn <-datomic [result] - (let [expected-deposit (some-> (:expected-deposit/_charges result) - first - (update :expected-deposit/date c/from-date))] - (cond-> result - true (update :charge/date c/from-date) - true (update :charge/processor :db/ident) - expected-deposit (assoc :expected-deposit expected-deposit)))) +(def default-read '[:db/id + :charge/reference-link + :charge/total + :charge/tip + [:charge/date :xform clj-time.coerce/from-date] + {:charge/client [:client/name :db/id :client/code] + [:charge/processor :xform iol-ion.query/ident] [:db/ident] + :expected-deposit/_charges [:expected-deposit/date :db/id]}]) -(def default-read '[* - {:charge/client [:client/name :db/id :client/code] - :charge/processor [:db/ident] - :expected-deposit/_charges [:expected-deposit/date :db/id]}]) - -(defn fetch-ids [db args] - (let [query-params (:parsed-query-params args) - valid-clients (extract-client-ids (:clients args) - (:client args) +(defn fetch-ids [db request] + (let [query-params (:parsed-query-params request) + valid-clients (extract-client-ids (:clients request) + (:client request) (:client-id query-params) (when (:client-code query-params) [:client/code (:client-code query-params)])) valid-clients (->> valid-clients (take 10) set) - _ (println "valid clients" valid-clients) - start-date (some-> args - :parsed-query-params - :start-date - (atime/parse atime/normal-date)) - end-date (some-> args - :parsed-query-params - :end-date - (atime/parse atime/normal-date)) - total-gte (some-> args :parsed-query-params :total-gte not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) - total-lte (some-> args :parsed-query-params :total-lte not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) - processor (some-> query-params :processor (#(keyword "ccp-processor" (name %)))) + processor (some-> query-params :processor (#(keyword "ccp-processor" (name %)))) ; TODO parse query (cond-> {:query {:find [] :in ['$ '[?clients ?start-date ?end-date]] :where '[[(iol-ion.query/scan-charges $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} :args [db [valid-clients - (some-> start-date c/to-date) - (some-> end-date c/to-date )]]} + (some-> (:start-date query-params) c/to-date) + (some-> (:end-date query-params) c/to-date)]]} (:sort query-params) (add-sorter-fields {"client" ['[?e :charge/client ?c] '[?c :client/name ?sort-client]] "date" ['[?e :charge/date ?sort-date]] @@ -96,17 +78,17 @@ :where []} :args [(:exact-match-id query-params)]}) - total-gte + (:total-gte query-params) (merge-query {:query {:in ['?total-gte] :where ['[?e :charge/total ?a] '[(>= ?a ?total-gte)]]} - :args [total-gte]}) + :args [(:total-gte query-params)]}) - total-lte + (:total-lte query-params) (merge-query {:query {:in ['?total-lte] :where ['[?e :charge/total ?a] '[(<= ?a ?total-lte)]]} - :args [total-lte]}) + :args [(:total-lte query-params)]}) processor (merge-query {:query {:in '[?processor] @@ -116,6 +98,7 @@ true (merge-query {:query {:find ['?sort-default '?e]}}))] + (cond->> (query2 query) true (apply-sort-3 query-params) true (apply-pagination query-params)))) @@ -125,16 +108,14 @@ (group-by :db/id)) charges (->> ids (map results) - (map first) - (mapv <-datomic) - )] + (map first))] charges)) -(defn fetch-page [_ args] +(defn fetch-page [request] (let [db (dc/db conn) - {ids-to-retrieve :ids matching-count :count} (fetch-ids db args)] + {ids-to-retrieve :ids matching-count :count} (fetch-ids db request)] - [(->> (hydrate-results ids-to-retrieve db args)) + [(->> (hydrate-results ids-to-retrieve db request)) matching-count])) (def grid-page {:id "tender-table" @@ -143,8 +124,8 @@ :id-fn :db/id :fetch-page fetch-page :oob-render - (fn [_ params] - [(assoc-in (date-range-field* params) [1 :hx-swap-oob] true)]) + (fn [request] + [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "POS"] @@ -155,16 +136,16 @@ :title "Tenders" :entity-name "Tender" :route :pos-tender-table - :action-buttons (fn [_ args]) - :row-buttons (fn [_ e] + :action-buttons (fn [request]) + :row-buttons (fn [request e] (when (:charge/reference-link e) [(com/a-icon-button {:href (:charge/reference-link e)} svg/external-link)])) :headers [{:key "client" :name "Client" :sort-key "client" - :hide? (fn [args] - (= (count (:clients args)) 1)) + :hide? (fn [request] + (= (count (:clients request)) 1)) :render #(-> % :charge/client :client/code)} {:key "date" :name "Date" @@ -188,17 +169,30 @@ {:key "links" :name "Links" :render (fn [entity] - (when-let [expected-deposit-id (some->> entity :expected-deposit :db/id)] + (when-let [expected-deposit-id (some->> entity :expected-deposit/_charges first :db/id)] [:a {:href (str (bidi/path-for ssr-routes/only-routes :pos-expected-deposits) - "?exact-match-id=" expected-deposit-id)} + "?exact-match-id=" expected-deposit-id) + :hx-boost "true"} (com/pill {:color :secondary} "expected deposit")]))}]}) (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page)) -(def table (partial helper/table grid-page)) -(def page (partial helper/page grid-page)) +(def table (-> (partial helper/table grid-page) + (query-params/wrap-parse-query-params + (comp + (query-params/parse-key :processor #(query-params/parse-keyword "ccp-processor" %)) + (query-params/parse-key :total-gte query-params/parse-double) + (query-params/parse-key :total-lte query-params/parse-double) + (helper/default-parse-query-params grid-page))))) +(def page (-> (partial helper/page grid-page) + (query-params/wrap-parse-query-params + (comp + (query-params/parse-key :processor #(query-params/parse-keyword "ccp-processor" %)) + (query-params/parse-key :total-gte query-params/parse-double) + (query-params/parse-key :total-lte query-params/parse-double) + (helper/default-parse-query-params grid-page))))) (def key->handler - {:pos-tenders (wrap-client-redirect-unauthenticated (wrap-secure page)) + {:pos-tenders (wrap-client-redirect-unauthenticated (wrap-secure page)) :pos-tender-table (wrap-client-redirect-unauthenticated (wrap-secure table))}) From 99accc067c9d503d29e39a2c5b6bcf549765e4c3 Mon Sep 17 00:00:00 2001 From: Bryce Date: Thu, 28 Sep 2023 22:22:25 -0700 Subject: [PATCH 11/13] progress on migrating all grids. --- scratch-sessions/fix-page-performance.repl | 11 + src/clj/auto_ap/datomic/yodlee2.clj | 2 +- src/clj/auto_ap/ssr/company/company_1099.clj | 174 ++++++++-------- src/clj/auto_ap/ssr/company/plaid.clj | 148 ++++++------- src/clj/auto_ap/ssr/company/reports.clj | 101 ++++----- src/clj/auto_ap/ssr/company/yodlee.clj | 157 +++++++------- src/clj/auto_ap/ssr/components/aside.clj | 1 + src/clj/auto_ap/ssr/components/page.clj | 10 +- src/clj/auto_ap/ssr/grid_page_helper.clj | 165 +++++++++++---- .../auto_ap/ssr/pos/cash_drawer_shifts.clj | 119 +++++------ src/clj/auto_ap/ssr/pos/common.clj | 3 - src/clj/auto_ap/ssr/pos/expected_deposits.clj | 125 ++++++----- src/clj/auto_ap/ssr/pos/refunds.clj | 112 +++++----- src/clj/auto_ap/ssr/pos/sales_orders.clj | 196 ++++++++---------- src/clj/auto_ap/ssr/pos/tenders.clj | 161 +++++++------- 15 files changed, 743 insertions(+), 742 deletions(-) diff --git a/scratch-sessions/fix-page-performance.repl b/scratch-sessions/fix-page-performance.repl index d6f5dc04..d641f408 100644 --- a/scratch-sessions/fix-page-performance.repl +++ b/scratch-sessions/fix-page-performance.repl @@ -213,4 +213,15 @@ {:user/name "hydrate-tuples"}) +(auto-ap.datomic/audit-transact-batch (->> (dc/q '[:find ?e ?c + :in $ + :where [?e :expected-deposit/client ?c]] + (dc/db conn) + ) + (map (fn [[i c]] + {:db/id i + :expected-deposit/client c}))) + + {:user/name "hydrate-tuples"}) + ) diff --git a/src/clj/auto_ap/datomic/yodlee2.clj b/src/clj/auto_ap/datomic/yodlee2.clj index 28a2a160..4692d37e 100644 --- a/src/clj/auto_ap/datomic/yodlee2.clj +++ b/src/clj/auto_ap/datomic/yodlee2.clj @@ -12,7 +12,7 @@ [clj-time.coerce :as c] [datomic.api :as dc])) -(def default-read '[*]) +(def default-read '[* {:yodlee-provider-account/client [:client/code]}]) (defn <-datomic [x] (-> x (update :yodlee-provider-account/last-updated c/from-date))) diff --git a/src/clj/auto_ap/ssr/company/company_1099.clj b/src/clj/auto_ap/ssr/company/company_1099.clj index 6669999d..b2095d9c 100644 --- a/src/clj/auto_ap/ssr/company/company_1099.clj +++ b/src/clj/auto_ap/ssr/company/company_1099.clj @@ -115,91 +115,93 @@ :per-page (:per-page (:parsed-query-params args))} all)] [(:entries paginated) (:count paginated)])) -(def grid-page {:id "vendor-table" - :nav (com/company-aside-nav) - :id-fn (comp :db/id second) - :fetch-page (fn [user args] - (get-1099-companies user args) - #_(r/get-graphql (into args {:id user}))) - :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes - :company)} - "My Company"] +(def grid-page + (helper/build + {:id "vendor-table" + :nav (com/company-aside-nav) + :id-fn (comp :db/id second) + :fetch-page (fn [user args] + (get-1099-companies user args) + #_(r/get-graphql (into args {:id user}))) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "My Company"] - [:a {:href (bidi/path-for ssr-routes/only-routes - :company-1099)} - "1099 Vendor Info"]] - :title "1099 Vendors" - :entity-name "Vendors" - :route :company-1099-vendor-table - :action-buttons (fn [user _] - nil) - :row-buttons (fn [user e] - [(com/icon-button {:hx-get (str (bidi/path-for ssr-routes/only-routes - :company-1099-vendor-dialog - :vendor-id (:db/id (second e))) - "?" - (url/map->query {:client-id (:db/id (first e))})) - :hx-ext "debug" - :hx-target "#modal-holder" - :hx-swap "outerHTML"} - svg/pencil)]) - :headers [{:key "Client" - :name "Client" - :sort-key "client" - :render (comp :client/code first)} - {:key "vendor-name" - :name "Vendor Name" - :sort-key "vendor" - :render (fn [[_ vendor]] - [:div.flex.whitespace-nowrap.items-center.gap-4 - [:div [:div (:vendor/name vendor)] - [:div.text-sm.text-gray-400 - (or (-> vendor :vendor/legal-entity-name not-empty) - (str (-> vendor :vendor/legal-entity-first-name) " " - (-> vendor :vendor/legal-entity-middle-name) " " - (-> vendor :vendor/legal-entity-last-name)))]] - (when-let [t99-type (some-> vendor :vendor/legal-entity-1099-type :db/ident name)] - (com/pill - {:class "text-xs font-medium" - :color :primary} - (str/capitalize t99-type)) - )])} - {:key "tin" - :name "TIN" - :sort-key "tin" - :show-starting "md" - :render (fn [[_ vendor]] - [:div.flex.gap-4 - (when-let [tin (-> vendor :vendor/legal-entity-tin)] - [:span {:class "text-xs font-medium py-0.5 "} - tin]) - (when-let [tin-type (some-> vendor :vendor/legal-entity-tin-type :db/ident name)] - (com/pill {:class "text-xs font-medium" - :color :yellow} - (name tin-type)))] - )} - {:key "address" - :name "Address" - :sort-key "address" - :show-starting "lg" - :render (fn [[_ vendor]] - (if (-> vendor :vendor/address :address/street1) - [:div - [:div (-> vendor :vendor/address :address/street1)] " " - [:div - (-> vendor :vendor/address :address/street2)] " " - [:div - (-> vendor :vendor/address :address/city) " " - (-> vendor :vendor/address :address/state) "," - (-> vendor :vendor/address :address/zip)]] - [:p.text-sm.italic.text-gray-400 "No address"]))} - {:key "paid" - :name "Paid" - :sort-key "paid" - :render (fn [[_ _ paid]] - (com/pill {:class "text-xs font-medium" - :color :primary} - "Paid $" (Math/round paid)))}]}) + [:a {:href (bidi/path-for ssr-routes/only-routes + :company-1099)} + "1099 Vendor Info"]] + :title "1099 Vendors" + :entity-name "Vendors" + :route :company-1099-vendor-table + :action-buttons (fn [user _] + nil) + :row-buttons (fn [user e] + [(com/icon-button {:hx-get (str (bidi/path-for ssr-routes/only-routes + :company-1099-vendor-dialog + :vendor-id (:db/id (second e))) + "?" + (url/map->query {:client-id (:db/id (first e))})) + :hx-ext "debug" + :hx-target "#modal-holder" + :hx-swap "outerHTML"} + svg/pencil)]) + :headers [{:key "Client" + :name "Client" + :sort-key "client" + :render (comp :client/code first)} + {:key "vendor-name" + :name "Vendor Name" + :sort-key "vendor" + :render (fn [[_ vendor]] + [:div.flex.whitespace-nowrap.items-center.gap-4 + [:div [:div (:vendor/name vendor)] + [:div.text-sm.text-gray-400 + (or (-> vendor :vendor/legal-entity-name not-empty) + (str (-> vendor :vendor/legal-entity-first-name) " " + (-> vendor :vendor/legal-entity-middle-name) " " + (-> vendor :vendor/legal-entity-last-name)))]] + (when-let [t99-type (some-> vendor :vendor/legal-entity-1099-type :db/ident name)] + (com/pill + {:class "text-xs font-medium" + :color :primary} + (str/capitalize t99-type)) + )])} + {:key "tin" + :name "TIN" + :sort-key "tin" + :show-starting "md" + :render (fn [[_ vendor]] + [:div.flex.gap-4 + (when-let [tin (-> vendor :vendor/legal-entity-tin)] + [:span {:class "text-xs font-medium py-0.5 "} + tin]) + (when-let [tin-type (some-> vendor :vendor/legal-entity-tin-type :db/ident name)] + (com/pill {:class "text-xs font-medium" + :color :yellow} + (name tin-type)))] + )} + {:key "address" + :name "Address" + :sort-key "address" + :show-starting "lg" + :render (fn [[_ vendor]] + (if (-> vendor :vendor/address :address/street1) + [:div + [:div (-> vendor :vendor/address :address/street1)] " " + [:div + (-> vendor :vendor/address :address/street2)] " " + [:div + (-> vendor :vendor/address :address/city) " " + (-> vendor :vendor/address :address/state) "," + (-> vendor :vendor/address :address/zip)]] + [:p.text-sm.italic.text-gray-400 "No address"]))} + {:key "paid" + :name "Paid" + :sort-key "paid" + :render (fn [[_ _ paid]] + (com/pill {:class "text-xs font-medium" + :color :primary} + "Paid $" (Math/round paid)))}]})) @@ -318,5 +320,5 @@ "Save")]]] [:div])]])))) -(def vendor-table (partial helper/table grid-page)) -(def page (partial helper/page grid-page)) +(def vendor-table (helper/table-route grid-page)) +(def page (helper/page-route grid-page)) diff --git a/src/clj/auto_ap/ssr/company/plaid.clj b/src/clj/auto_ap/ssr/company/plaid.clj index c65fe813..8ab4e736 100644 --- a/src/clj/auto_ap/ssr/company/plaid.clj +++ b/src/clj/auto_ap/ssr/company/plaid.clj @@ -9,8 +9,7 @@ pull-attr pull-many-by-id query2]] - [auto-ap.graphql.utils - :refer [assert-can-see-client extract-client-ids]] + [auto-ap.graphql.utils :refer [assert-can-see-client]] [auto-ap.logging :as alog] [auto-ap.plaid.core :as p] [auto-ap.ssr-routes :as ssr-routes] @@ -36,43 +35,36 @@ :plaid-account/balance :plaid-account/name]}]) -(defn raw-graphql-ids [db args] - (let [valid-clients (extract-client-ids (:clients args) - (:client-id args) - (when (:client-code args) - [:client/code (:client-code args)])) +(defn fetch-ids [db request] + (let [query-params (:parsed-query-params request) query (cond-> {:query {:find [] :in ['$ '[?xx ...]] :where ['[?e :plaid-item/client ?xx]]} - :args [db valid-clients]} + :args [db (:trimmed-clients request)]} - (:sort args) (add-sorter-fields {"external-id" ['[?e :plaid-item/external-id ?sort-external-id]] + (:sort query-params) (add-sorter-fields {"external-id" ['[?e :plaid-item/external-id ?sort-external-id]] "status" ['[?e :plaid-item/status ?sort-status]]} - args) - - (:client-id args) - (merge-query {:query {:in '[?client-id] - :where ['[?e :plaid-item/client ?client-id]]} - :args [(:client-id args)]}) + query-params) true (merge-query {:query {:find ['?e] :where ['[?e :plaid-item/external-id]]}}))] + (clojure.pprint/pprint query-params) (cond->> (query2 query) - true (apply-sort-3 args) - true (apply-pagination args)))) + true (apply-sort-3 query-params) + true (apply-pagination query-params)))) -(defn graphql-results [ids db _] +(defn hydrate-results [ids db _] (let [results (pull-many-by-id db default-read ids)] (->> ids (map results)))) -(defn get-page [args] +(defn fetch-page [request] (let [db (dc/db conn) - {ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)] - [(graphql-results ids-to-retrieve db args) + {ids-to-retrieve :ids matching-count :count} (fetch-ids db request)] + [(hydrate-results ids-to-retrieve db request) matching-count])) @@ -138,65 +130,61 @@ "Start relink")]))) -(def grid-page {:id "plaid-table" - :nav (com/company-aside-nav) - :id-fn :db/id - :fetch-page (fn [user args] - (get-page {:client (:client args) - :clients (:clients args) - :start (:start (:parsed-query-params args)) - :sort (:sort (:parsed-query-params args)) - :per-page (:per-page (:parsed-query-params args)) - })) - :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes - :company)} - "My Company"] +(def grid-page + (helper/build + {:id "plaid-table" + :nav (com/company-aside-nav) + :fetch-page fetch-page + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "My Company"] - [:a {:href (bidi/path-for ssr-routes/only-routes - :company-plaid)} - "Plaid"]] - :title "Plaid Accounts" - :entity-name "Plaid accounts" - :route :company-plaid-table - :action-buttons (fn [user params] - (when-let [client-code (:client/code (:client params))] - [[:div {:hx-post (str (bidi/path-for ssr-routes/only-routes - :company-plaid-link - :request-method :post)) - :hx-vals (hiccup/raw (format "js:{client_code: \"%s\", public_token: event.detail.public_token}", client-code)) - :hx-trigger "linked"} - [:script (hiccup/raw (plaid-link-script (p/get-link-token client-code)))] - (com/button {:color :primary - :id "link-account" - :onClick "window.plaid.open()"} - (com/button-icon {} svg/refresh) - "Link new account")]])) - :row-buttons (fn [user e] - [[:div (com/button {:hx-put (str (bidi/path-for ssr-routes/only-routes - :company-plaid-relink) - "?plaid-item-id=" (:db/id e)) - :color :primary - :hx-target "closest div"} - "Reauthenticate")]]) - :headers [{:key "plaid-item" - :name "Plaid Item" - :sort-key "external-id" - :render :plaid-item/external-id} - {:key "status" - :name "Status" - :sort-key "status" - :render #(when-let [status (:plaid-item/status %)] - [:div [:div (com/pill {:color :primary} - status)] - [:div (atime/unparse-local (coerce/to-date-time (:plaid-item/last-updated %)) atime/normal-date)]])} + [:a {:href (bidi/path-for ssr-routes/only-routes + :company-plaid)} + "Plaid"]] + :title "Plaid Accounts" + :entity-name "Plaid accounts" + :route :company-plaid-table + :action-buttons (fn [request] + (when-let [client-code (:client/code (:client request))] + [[:div {:hx-post (str (bidi/path-for ssr-routes/only-routes + :company-plaid-link + :request-method :post)) + :hx-vals (hiccup/raw (format "js:{client_code: \"%s\", public_token: event.detail.public_token}", client-code)) + :hx-trigger "linked"} + [:script (hiccup/raw (plaid-link-script (p/get-link-token client-code)))] + (com/button {:color :primary + :id "link-account" + :onClick "window.plaid.open()"} + (com/button-icon {} svg/refresh) + (format "Link %s account" client-code))]])) + :row-buttons (fn [request e] + [[:div (com/button {:hx-put (str (bidi/path-for ssr-routes/only-routes + :company-plaid-relink) + "?plaid-item-id=" (:db/id e)) + :color :primary + :hx-target "closest div"} + "Reauthenticate")]]) + :headers [{:key "plaid-item" + :name "Plaid Item" + :sort-key "external-id" + :render :plaid-item/external-id} + {:key "status" + :name "Status" + :sort-key "status" + :render #(when-let [status (:plaid-item/status %)] + [:div [:div (com/pill {:color :primary} + status)] + [:div (atime/unparse-local (coerce/to-date-time (:plaid-item/last-updated %)) atime/normal-date)]])} - {:key "accounts" - :name "Accounts" - :show-starting "md" - :render (fn [e] - [:ul - (for [a (:plaid-item/accounts e)] - [:li (:plaid-account/name a) " - " (:plaid-account/number a)])])}]}) + {:key "accounts" + :name "Accounts" + :show-starting "md" + :render (fn [e] + [:ul + (for [a (:plaid-item/accounts e)] + [:li (:plaid-account/name a) " - " (:plaid-account/number a)])])}]})) -(def page (partial helper/page grid-page)) -(def table (partial helper/table grid-page)) + +(def page (helper/page-route grid-page)) +(def table (helper/table-route grid-page)) diff --git a/src/clj/auto_ap/ssr/company/reports.clj b/src/clj/auto_ap/ssr/company/reports.clj index ebb00243..af41992a 100644 --- a/src/clj/auto_ap/ssr/company/reports.clj +++ b/src/clj/auto_ap/ssr/company/reports.clj @@ -15,59 +15,60 @@ [datomic.api :as dc] [com.brunobonacci.mulog :as mu])) -(def grid-page {:id "report-table" - :nav (com/company-aside-nav) - :id-fn :db/id - :fetch-page (fn [user args] - (prn args) - (r/get-graphql {:client (:client args) - :clients (:clients args) - :start (:start (:parsed-query-params args)) - :per-page (:per-page (:parsed-query-params args)) - :sort (:sort (:parsed-query-params args)) - })) - :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes - :company)} - "My Company"] +(def grid-page + (helper/build {:id "report-table" + :nav (com/company-aside-nav) + :id-fn :db/id + :fetch-page (fn [user args] + (prn args) + (r/get-graphql {:client (:client args) + :clients (:clients args) + :start (:start (:parsed-query-params args)) + :per-page (:per-page (:parsed-query-params args)) + :sort (:sort (:parsed-query-params args)) + })) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "My Company"] - [:a {:href (bidi/path-for ssr-routes/only-routes - :company-reports)} - "Reports"]] - :title "Reports" - :entity-name "Reports" - :route :company-reports-table - :action-buttons (fn [user _] - nil) - :row-buttons (fn [user e] - (com/a-icon-button {:href (:report/url e)} - svg/download)[ - (when (is-admin? user) - (com/icon-button {:hx-delete (str (bidi/path-for ssr-routes/only-routes - :company-reports-delete - :request-method :delete)) - :hx-target "closest tr"} - svg/trash))]) - :headers [{:key "name" - :name "Name" - :sort-key "name" - :render :report/name} - {:key "created-by" - :name "Created by" - :sort-key "creator" - :render (fn [report] - (when (:report/creator report) - (com/pill {:color :primary } - (:report/creator report))))} - {:key "created" - :name "Created" - :sort-key "created" - :render #(atime/unparse-local (:report/created %) - atime/normal-date)}]}) + [:a {:href (bidi/path-for ssr-routes/only-routes + :company-reports)} + "Reports"]] + :title "Reports" + :entity-name "Reports" + :route :company-reports-table + :action-buttons (fn [user _] + nil) + :row-buttons (fn [user e] + (com/a-icon-button {:href (:report/url e)} + svg/download)[ + (when (is-admin? user) + (com/icon-button {:hx-delete (str (bidi/path-for ssr-routes/only-routes + :company-reports-delete + :request-method :delete)) + :hx-target "closest tr"} + svg/trash))]) + :headers [{:key "name" + :name "Name" + :sort-key "name" + :render :report/name} + {:key "created-by" + :name "Created by" + :sort-key "creator" + :render (fn [report] + (when (:report/creator report) + (com/pill {:color :primary } + (:report/creator report))))} + {:key "created" + :name "Created" + :sort-key "created" + :render #(atime/unparse-local (:report/created %) + atime/normal-date)}]})) (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page)) -(def table (partial helper/table grid-page)) -(def page (partial helper/page grid-page)) +(def table (helper/table-route grid-page)) +(def page (helper/page-route grid-page)) (defn delete-report [{:keys [form-params identity]}] @@ -76,7 +77,7 @@ :where [?i :report/key ?k]] (dc/db conn) (some-> (get form-params "id") not-empty Long/parseLong))) - report (dc/pull (dc/db conn) r/default-read id-to-delete)] + report (dc/pull (dc/db conn) r/default-read id-to-delete)] (assert-can-see-client identity (:report/client report)) (when id-to-delete (s3/delete-object :bucket-name (:data-bucket env) diff --git a/src/clj/auto_ap/ssr/company/yodlee.clj b/src/clj/auto_ap/ssr/company/yodlee.clj index e137f176..fb2401e5 100644 --- a/src/clj/auto_ap/ssr/company/yodlee.clj +++ b/src/clj/auto_ap/ssr/company/yodlee.clj @@ -1,21 +1,19 @@ (ns auto-ap.ssr.company.yodlee (:require - [auto-ap.datomic :refer [conn]] + [auto-ap.datomic :refer [conn pull-attr]] [auto-ap.datomic.yodlee2 :as yodlee2] [auto-ap.graphql.utils :refer [is-admin?]] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components :as com] - [auto-ap.ssr.svg :as svg] - [auto-ap.ssr.ui :refer [base-page]] [auto-ap.ssr.grid-page-helper :as helper] + [auto-ap.ssr.svg :as svg] [auto-ap.ssr.utils :refer [html-response]] [auto-ap.time :as atime] [auto-ap.yodlee.core2 :as yodlee] [bidi.bidi :as bidi] [config.core :refer [env]] [datomic.api :as dc] - [hiccup2.core :as hiccup] - [auto-ap.datomic :refer [pull-attr]])) + [hiccup2.core :as hiccup])) (def default-read '[:db/id :yodlee-provider-account/last-updated @@ -77,79 +75,88 @@ fastlink.open({fastLinkURL: '%s', (pull-attr (dc/db conn) :yodlee-provider-account/id (Long/parseLong (get form-params "id")))))]] [:div])))) -(def grid-page {:id "yodlee-table" - :nav (com/company-aside-nav) - :id-fn :db/id - :fetch-page (fn [user args] - (yodlee2/get-graphql - {:client (:client args) - :clients (:clients args) - :sort (:sort (:parsed-query-params args)) - :start (:start (:parsed-query-params args)) - :per-page (:per-page (:parsed-query-params args))})) - :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes - :company)} - "My Company"] +(def grid-page + (helper/build + {:id "yodlee-table" + :nav (com/company-aside-nav) + :id-fn :db/id + :fetch-page (fn [request] + (println (count (:clients request))) + (yodlee2/get-graphql + {:client (:client request) + :clients (:clients request) + :sort (:sort (:parsed-query-params request)) + :start (:start (:parsed-query-params request)) + :per-page (:per-page (:parsed-query-params request))})) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "My Company"] - [:a {:href (bidi/path-for ssr-routes/only-routes - :company-yodlee)} - "Yodlee"]] - :title "Yodlee Accounts" - :entity-name "Yodlee accounts" - :route :company-yodlee-table - :action-buttons (fn [user _] - [(com/button {:color :primary - :on-click "openFastlink()" - :hx-get (bidi/path-for ssr-routes/only-routes - :company-yodlee-fastlink-dialog) - :hx-target "#modal-holder"} - (com/button-icon {} svg/refresh) - "Link new account")]) - :row-buttons (fn [user e] - [ - (com/button {:hx-put (bidi/path-for ssr-routes/only-routes - :company-yodlee-provider-account-reauthenticate) - :color :primary - :hx-target "#modal-holder"} - "Reauthenticate") - (when (is-admin? user) - (com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes - :company-yodlee-provider-account-refresh) - :hx-target "closest tr"} - svg/refresh))]) - :headers [{:key "provider-account" - :name "Provider Account" - :sort-key "provider-account" - :render :yodlee-provider-account/id} - {:key "status" - :name "Status" - :sort-key "status" - :render #(when-let [status (:yodlee-provider-account/status %)] - (com/pill {:color (if (not= status "SUCCESS") - :yellow - :primary) } - status))} - {:key "detailed-status" - :name "Detailed Status" - :sort-key "detailed-status" - :render #(when-let [status (:yodlee-provider-account/detailed-status %)] - status)} + [:a {:href (bidi/path-for ssr-routes/only-routes + :company-yodlee)} + "Yodlee"]] + :title "Yodlee Accounts" + :entity-name "Yodlee accounts" + :route :company-yodlee-table + :action-buttons (fn [request] + [(com/button {:color :primary + :on-click "openFastlink()" + :hx-get (bidi/path-for ssr-routes/only-routes + :company-yodlee-fastlink-dialog) + :hx-target "#modal-holder"} + (com/button-icon {} svg/refresh) + "Link new account")]) + :row-buttons (fn [request _] + [ + (com/button {:hx-put (bidi/path-for ssr-routes/only-routes + :company-yodlee-provider-account-reauthenticate) + :color :primary + :hx-target "#modal-holder"} + "Reauthenticate") + (when (is-admin? (:identity request)) + (com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes + :company-yodlee-provider-account-refresh) + :hx-target "closest tr"} + svg/refresh))]) + :headers [{:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [args] + (= (count (:clients args)) 1)) + :render #(-> % :yodlee-provider-account/client :client/code)} + {:key "provider-account" + :name "Provider Account" + :sort-key "provider-account" + :render :yodlee-provider-account/id} + {:key "status" + :name "Status" + :sort-key "status" + :render #(when-let [status (:yodlee-provider-account/status %)] + (com/pill {:color (if (not= status "SUCCESS") + :yellow + :primary) } + status))} + {:key "detailed-status" + :name "Detailed Status" + :sort-key "detailed-status" + :render #(when-let [status (:yodlee-provider-account/detailed-status %)] + status)} - {:key "last-updated" - :name "Last Updated" - :sort-key "last-updated" - :render #(atime/unparse-local (:yodlee-provider-account/last-updated %) - atime/normal-date)} - {:key "accounts" - :name "Accounts" - :show-starting "md" - :render (fn [e] - [:ul - (for [a (:yodlee-provider-account/accounts e)] - [:li (:yodlee-account/name a) " - " (:yodlee-account/number a)])])}]}) + {:key "last-updated" + :name "Last Updated" + :sort-key "last-updated" + :render #(atime/unparse-local (:yodlee-provider-account/last-updated %) + atime/normal-date)} + {:key "accounts" + :name "Accounts" + :show-starting "md" + :render (fn [e] + [:ul + (for [a (:yodlee-provider-account/accounts e)] + [:li (:yodlee-account/name a) " - " (:yodlee-account/number a)])])}]})) -(def page (partial helper/page grid-page)) -(def table (partial helper/table grid-page)) +(def page (helper/page-route grid-page)) +(def table (helper/table-route grid-page)) ;; TODO delete-after-settle (defn refresh-provider-account [{:keys [form-params identity]}] diff --git a/src/clj/auto_ap/ssr/components/aside.clj b/src/clj/auto_ap/ssr/components/aside.clj index bbbbc761..6b7804ed 100644 --- a/src/clj/auto_ap/ssr/components/aside.clj +++ b/src/clj/auto_ap/ssr/components/aside.clj @@ -12,6 +12,7 @@ (assoc :type "button") (update :class str " cursor-pointer flex items-center p-2 w-full text-xs text-gray-600 rounded-lg transition duration-75 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700") (assoc :hx-indicator "find .htmx-indicator") + (assoc :hx-boost "true") (assoc :hx-select "#app-contents") (assoc :hx-target "#app-contents") (assoc :hx-swap "outerHTML")) diff --git a/src/clj/auto_ap/ssr/components/page.clj b/src/clj/auto_ap/ssr/components/page.clj index e6df4eac..ebda28be 100644 --- a/src/clj/auto_ap/ssr/components/page.clj +++ b/src/clj/auto_ap/ssr/components/page.clj @@ -6,7 +6,10 @@ [auto-ap.ssr.svg :as svg])) (defn page- [{:keys [nav page-specific client client-selection identity app-params] :or {app-params {}}} & children] - [:div#app + [:div#app {"_" (hiccup/raw " + on notification put event.detail.value into #notification-details then add .htmx-added to #notification-holder then remove .hidden from #notification-holder then wait 30ms then remove .htmx-added from #notification-holder + on htmx:responseError put event.detail.xhr.response into #error-details then add .htmx-added to #error-holder then remove .hidden from #error-holder then wait 30ms then remove .htmx-added from #error-holder" + )} (navbar- {:client-selection client-selection :client client :identity identity}) @@ -14,10 +17,7 @@ (left-aside- {:nav nav :page-specific page-specific}) [:div#main-content {:class "relative w-full h-full lg:pl-64 overflow-y-auto px-4 bg-gray-100 dark:bg-gray-900 min-h-content " - "_" (hiccup/raw " - on notification put event.detail.value into #notification-details then add .htmx-added to #notification-holder then remove .hidden from #notification-holder then wait 30ms then remove .htmx-added from #notification-holder - on htmx:responseError put event.detail.xhr.response into #error-details then add .htmx-added to #error-holder then remove .hidden from #error-holder then wait 30ms then remove .htmx-added from #error-holder" - )} + } [:div#notification-holder.hidden [:div.fixed.top-0.right-0.left-0.z-30.mx-auto.max-w-screen-lg.w-screen-lg.my-0.pt-8.rounded-lg [:div.relative diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index 230362ec..36b1782c 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -1,17 +1,21 @@ (ns auto-ap.ssr.grid-page-helper (:require + [auto-ap.graphql.utils :refer [extract-client-ids]] + [auto-ap.query-params :as query-params] + [auto-ap.routes.utils + :refer [wrap-client-redirect-unauthenticated wrap-secure]] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components :as com] [auto-ap.ssr.svg :as svg] [auto-ap.ssr.ui :refer [base-page]] [auto-ap.ssr.utils :refer [html-response]] + [auto-ap.time :as atime] + [malli.core :as m] [bidi.bidi :as bidi] [cemerick.url :as url] [clojure.string :as str] [hiccup2.core :as hiccup] - [clj-time.core :as time] - [auto-ap.time :as atime] - [auto-ap.query-params :as query-params])) + [malli.transform :as mt2])) (defn row* [gridspec user entity {:keys [flash? delete-after-settle? request] :as options}] (let [cells (->> gridspec @@ -125,23 +129,12 @@ (com/data-grid-header {}))}))) - - - - - (defn sort->query [s] (str/join "," (map (fn [k] (format "%s:%s" (:sort-key k) (if (= true (:asc k)) "asc" "desc"))) s))) -(defn params->query-string [q] - (-> q - :parsed-query-params - (update :sort sort->query) - (url/map->query))) - (defn default-unparse-query-params [query-params] (reduce (fn [query-params [k value]] @@ -169,37 +162,6 @@ query-params query-params)) - - - -(defn table [grid-spec {:keys [identity] :as request}] - (let [unparse-query-params (or (:unparse-query grid-spec) - default-unparse-query-params)] - (html-response (table* - grid-spec - identity - request) - :headers {"hx-push-url" (str "?" (url/map->query (unparse-query-params (:parsed-query-params request))))} - :oob (when-let [oob-render (:oob-render grid-spec)] - (oob-render request))))) - - -(defn page [grid-spec {:keys [identity] :as request}] - (base-page - request - (com/page {:nav (:nav grid-spec) - :page-specific (when-let [page-specific-nav (:page-specific-nav grid-spec)] - [:div#page-specific-nav (page-specific-nav request)]) - :client-selection (:client-selection (:session request)) - :clients (:clients request) - :client (:client request) - :identity (:identity request)} - (apply com/breadcrumbs {} (:breadcrumbs grid-spec)) - (table* grid-spec - identity - request)) - (:title grid-spec))) - (defn default-parse-query-params [grid-spec] (comp (query-params/apply-remove-sort) @@ -211,3 +173,116 @@ (query-params/parse-key :start query-params/parse-long) (query-params/parse-key :start-date query-params/parse-date) (query-params/parse-key :end-date query-params/parse-date))) + +(defn wrap-trim-client-ids [handler] + (fn trim-client-ids [request] + (let [valid-clients (extract-client-ids (:clients request) + (:client request) + (:client-id (:parsed-query-params request)) + (when (:client-code (:parsed-query-params request)) + [:client/code (:client-code (:parsed-query-params request))])) + valid-clients (->> valid-clients + (take 20) + set)] + (handler (assoc request :trimmed-clients valid-clients))))) + +(defn table-route [grid-spec] + (-> (fn table [{:keys [identity] :as request}] + (let [unparse-query-params (or (:unparse-query grid-spec) + default-unparse-query-params)] + (html-response (table* + grid-spec + identity + request) + :headers {"hx-push-url" (str "?" (url/map->query (unparse-query-params (:parsed-query-params request))))} + :oob (when-let [oob-render (:oob-render grid-spec)] + (oob-render request))))) + (wrap-trim-client-ids) + (query-params/wrap-parse-query-params (or (:parse-query-params grid-spec) + (default-parse-query-params grid-spec))) + (wrap-secure) + (wrap-client-redirect-unauthenticated))) + +(defn page-route [grid-spec ] + (-> (fn page [{:keys [identity] :as request}] + (base-page + request + (com/page {:nav (:nav grid-spec) + :page-specific (when-let [page-specific-nav (:page-specific-nav grid-spec)] + [:div#page-specific-nav (page-specific-nav request)]) + :client-selection (:client-selection (:session request)) + :clients (:clients request) + :client (:client request) + :identity (:identity request)} + (apply com/breadcrumbs {} (:breadcrumbs grid-spec)) + (table* grid-spec + identity + request)) + (:title grid-spec))) + (wrap-trim-client-ids) + (query-params/wrap-parse-query-params (or (:parse-query-params grid-spec) + (default-parse-query-params grid-spec))) + (wrap-secure) + (wrap-client-redirect-unauthenticated))) + +(def request-spec (m/schema [:map])) +(def entity-spec (m/schema [:map])) +(def header-spec (m/schema [:map + [:key :string] + [:name :string] + [:sort-key {:optional true} :string] + [:render [:=> [:cat entity-spec] :any]] + [:hide? {:optional true} [:=> [:cat entity-spec] :boolean]]])) +(def grid-spec (m/schema [:map + [:id :string] + [:nav vector?] + [:page-specific-nav + {:optional true + :default (fn [request])} + [:maybe [:=> + [:cat request-spec] + vector?]]] + [:id-fn {:default :db/id + :optional true} + [:=> + [:cat map?] + nat-int?]] + [:fetch-page [:=> + [:cat request-spec] + [:cat [:vector entity-spec] :int]]] + [:parse-query-params + {:optional true} + [:=> + [:cat [:map-of :keyword :any]] + [:map-of :keyword :any]]] + [:oob-render + {:optional true + :default (fn [request])} + [:=> + [:cat request-spec] + vector?]] + [:breadcrumbs [:vector vector?]] + [:title :string] + [:entity-name :string] + [:route :keyword] + [:action-buttons + {:default (fn [request]) + :optional true} + [:=> + [:cat request-spec] + [:maybe [:vector vector?]]]] + [:row-buttons + {:default (fn [request entity]) + :optional true} + [:=> + [:cat request-spec entity-spec] + [:maybe [:vector vector?]]]] + [:headers [:vector header-spec]]])) + +(defn build [grid-page] + (when-not (m/validate grid-spec grid-page) + (throw (ex-info "Could not validate grid" + (m/explain grid-spec grid-page)))) + (m/decode grid-spec grid-page (mt2/default-value-transformer {::mt2/add-optional-keys true}))) + + diff --git a/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj b/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj index df5cc696..12cfe3fd 100644 --- a/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj +++ b/src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj @@ -8,10 +8,6 @@ merge-query pull-many query2]] - [auto-ap.graphql.utils :refer [extract-client-ids]] - [auto-ap.query-params :as query-params] - [auto-ap.routes.utils - :refer [wrap-client-redirect-unauthenticated wrap-secure]] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components :as com] [auto-ap.ssr.grid-page-helper :as helper] @@ -19,7 +15,8 @@ [auto-ap.time :as atime] [bidi.bidi :as bidi] [clj-time.coerce :as c] - [datomic.api :as dc])) + [datomic.api :as dc] + [malli.core :as m])) ;; always should be fast @@ -42,17 +39,10 @@ (defn fetch-ids [db request] (let [query-params (:parsed-query-params request) - valid-clients (extract-client-ids (:clients request) - (:client-id query-params) - (when (:client-code query-params) - [:client/code (:client-code query-params)])) - valid-clients (->> valid-clients - (take 10) - set) query (cond-> {:query {:find [] :in ['$ '[?clients ?start-date ?end-date]] :where '[[(iol-ion.query/scan-cash-drawer-shifts $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} - :args [db [valid-clients + :args [db [(:trimmed-clients request) (some-> (:start-date query-params) c/to-date) (some-> (:end-date query-params) c/to-date )]]} (:sort query-params) (add-sorter-fields {"client" ['[?e :cash-drawer-shift/client ?c] @@ -93,64 +83,57 @@ [(->> (hydrate-results ids-to-retrieve db request)) matching-count])) -(def grid-page {:id "cash-drawer-shift-table" - :nav (com/main-aside-nav) - :page-specific-nav filters - :id-fn :db/id - :fetch-page fetch-page - :oob-render - (fn [request] - [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) - :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes - :company)} - "POS"] +(def grid-page + (helper/build + {:id "cash-drawer-shift-table" + :nav (com/main-aside-nav) + :page-specific-nav filters + :fetch-page fetch-page + :oob-render + (fn [request] + [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "POS"] + + [:a {:href (bidi/path-for ssr-routes/only-routes + :pos-cash-drawer-shifts)} + "Cash Drawer Shifts"]] + :title "Cash drawer shifts" + :entity-name "Cash drawer shift" + :route :pos-cash-drawer-shift-table + :headers [{:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [args] + (= (count (:clients args)) 1)) + :render #(-> % :cash-drawer-shift/client :client/code)} + {:key "date" + :name "Date" + :sort-key "date" + :render #(atime/unparse-local (:cash-drawer-shift/date %) atime/standard-time)} + {:key "paid-in" + :name "Paid in" + :sort-key "paid-in" + :render #(some->> % :cash-drawer-shift/paid-in (format "$%.2f"))} + {:key "paid-out" + :name "Paid out" + :sort-key "paid-out" + :render #(some->> % :cash-drawer-shift/paid-out (format "$%.2f"))} + {:key "expected-cash" + :name "Expected cash" + :sort-key "expected-cash" + :render #(some->> % :cash-drawer-shift/expected-cash (format "$%.2f"))} + {:key "opened-cash" + :name "Opened cash" + :sort-key "opened-cash" + :render #(some->> % :cash-drawer-shift/opened-cash (format "$%.2f"))} + ]})) - [:a {:href (bidi/path-for ssr-routes/only-routes - :pos-cash-drawer-shifts)} - "Cash Drawer Shifts"]] - :title "Cash drawer shifts" - :entity-name "Cash drawer shift" - :route :pos-cash-drawer-shift-table - :action-buttons (fn [request]) - :row-buttons (fn [_ e]) - :headers [{:key "client" - :name "Client" - :sort-key "client" - :hide? (fn [args] - (= (count (:clients args)) 1)) - :render #(-> % :cash-drawer-shift/client :client/code)} - {:key "date" - :name "Date" - :sort-key "date" - :render #(atime/unparse-local (:cash-drawer-shift/date %) atime/standard-time)} - {:key "paid-in" - :name "Paid in" - :sort-key "paid-in" - :render #(some->> % :cash-drawer-shift/paid-in (format "$%.2f"))} - {:key "paid-out" - :name "Paid out" - :sort-key "paid-out" - :render #(some->> % :cash-drawer-shift/paid-out (format "$%.2f"))} - {:key "expected-cash" - :name "Expected cash" - :sort-key "expected-cash" - :render #(some->> % :cash-drawer-shift/expected-cash (format "$%.2f"))} - {:key "opened-cash" - :name "Opened cash" - :sort-key "opened-cash" - :render #(some->> % :cash-drawer-shift/opened-cash (format "$%.2f"))} - ]}) (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page)) -(def table - (query-params/wrap-parse-query-params (partial helper/table grid-page) - (helper/default-parse-query-params grid-page)) - - ) -(def page (query-params/wrap-parse-query-params (partial helper/page grid-page) - (helper/default-parse-query-params grid-page))) (def key->handler - {:pos-cash-drawer-shifts (wrap-client-redirect-unauthenticated (wrap-secure page)) - :pos-cash-drawer-shift-table (wrap-client-redirect-unauthenticated (wrap-secure table))}) + {:pos-cash-drawer-shifts (helper/page-route grid-page) + :pos-cash-drawer-shift-table (helper/table-route grid-page)}) diff --git a/src/clj/auto_ap/ssr/pos/common.clj b/src/clj/auto_ap/ssr/pos/common.clj index 3eeaa63d..ef37e0af 100644 --- a/src/clj/auto_ap/ssr/pos/common.clj +++ b/src/clj/auto_ap/ssr/pos/common.clj @@ -7,9 +7,6 @@ ;; TODO make total fields take decimals (defn date-range-field* [request] - (println "DR" (some-> request - :parsed-query-params - )) [:div#date-range {} (com/field {:label "Date Range"} [:div.space-y-4 diff --git a/src/clj/auto_ap/ssr/pos/expected_deposits.clj b/src/clj/auto_ap/ssr/pos/expected_deposits.clj index 1aebefd0..a11b17bc 100644 --- a/src/clj/auto_ap/ssr/pos/expected_deposits.clj +++ b/src/clj/auto_ap/ssr/pos/expected_deposits.clj @@ -21,7 +21,8 @@ [bidi.bidi :as bidi] [clj-time.coerce :as c] [datomic.api :as dc] - [auto-ap.client-routes :as client-routes])) + [auto-ap.client-routes :as client-routes] + [malli.core :as m])) ;; make params parsing composable @@ -132,75 +133,65 @@ [(->> (hydrate-results ids-to-retrieve db args)) matching-count])) -(def grid-page {:id "expected-deposit-table" - :nav (com/main-aside-nav) - :page-specific-nav filters - :id-fn :db/id - :fetch-page fetch-page - :oob-render - (fn [request] - [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) - :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes - :company)} - "POS"] +(def grid-page + (helper/build + {:id "expected-deposit-table" + :nav (com/main-aside-nav) + :page-specific-nav filters + :fetch-page fetch-page + :parse-query-params (comp + (query-params/parse-key :total-gte query-params/parse-double) + (query-params/parse-key :total-lte query-params/parse-double) + (helper/default-parse-query-params grid-page)) + :oob-render + (fn [request] + [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "POS"] - [:a {:href (bidi/path-for ssr-routes/only-routes - :pos-expected-deposits)} - "Expected deposits"]] - :title "Expected deposits" - :entity-name "Expected deposit" - :route :pos-expected-deposit-table - :action-buttons (fn [request] - ) - :row-buttons (fn [_ e] - [ - (when (:expected-deposit/reference-link e) - (com/a-icon-button {:href (:expected-deposit/reference-link e)} - svg/external-link)) - (when-let [transaction-id (-> e (:transaction/_expected-deposit) first :db/id)] - (com/a-button {:href (str (bidi/path-for client-routes/routes - :transactions) - "?exact-match-id=" - transaction-id)} "Transaction"))]) - :headers [{:key "client" - :name "Client" - :sort-key "client" - :hide? (fn [args] - (= (count (:clients args)) 1)) - :render #(-> % :expected-deposit/client :client/code)} - {:key "date" - :name "Date" - :sort-key "date" - :render #(atime/unparse-local (:expected-deposit/date %) atime/standard-time)} - {:key "sales-date" - :name "Sales Date" - :sort-key "sales-date" - :render #(atime/unparse-local (:expected-deposit/sales-date %) atime/standard-time)} - {:key "total" - :name "Total" - :sort-key "total" - :render #(some->> % :expected-deposit/total (format "$%.2f"))} - {:key "fee" - :name "Fee" - :sort-key "fee" - :render #(some->> % :expected-deposit/fee (format "$%.2f"))}]}) + [:a {:href (bidi/path-for ssr-routes/only-routes + :pos-expected-deposits)} + "Expected deposits"]] + :title "Expected deposits" + :entity-name "Expected deposit" + :route :pos-expected-deposit-table + :row-buttons (fn [_ e] + [ + (when (:expected-deposit/reference-link e) + (com/a-icon-button {:href (:expected-deposit/reference-link e)} + svg/external-link)) + (when-let [transaction-id (-> e (:transaction/_expected-deposit) first :db/id)] + (com/a-button {:href (str (bidi/path-for client-routes/routes + :transactions) + "?exact-match-id=" + transaction-id)} "Transaction"))]) + :headers [{:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [args] + (= (count (:clients args)) 1)) + :render #(-> % :expected-deposit/client :client/code)} + {:key "date" + :name "Date" + :sort-key "date" + :render #(atime/unparse-local (:expected-deposit/date %) atime/standard-time)} + {:key "sales-date" + :name "Sales Date" + :sort-key "sales-date" + :render #(atime/unparse-local (:expected-deposit/sales-date %) atime/standard-time)} + {:key "total" + :name "Total" + :sort-key "total" + :render #(some->> % :expected-deposit/total (format "$%.2f"))} + {:key "fee" + :name "Fee" + :sort-key "fee" + :render #(some->> % :expected-deposit/fee (format "$%.2f"))}]})) (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page)) -(def table (-> (partial helper/table grid-page) - (query-params/wrap-parse-query-params - (comp - (query-params/parse-key :total-gte query-params/parse-double) - (query-params/parse-key :total-lte query-params/parse-double) - (helper/default-parse-query-params grid-page))))) - -(def page (-> (partial helper/page grid-page) - (query-params/wrap-parse-query-params - (comp - (query-params/parse-key :total-gte query-params/parse-double) - (query-params/parse-key :total-lte query-params/parse-double) - (helper/default-parse-query-params grid-page))))) (def key->handler - {:pos-expected-deposits (wrap-client-redirect-unauthenticated (wrap-secure page)) - :pos-expected-deposit-table (wrap-client-redirect-unauthenticated (wrap-secure table))}) + {:pos-expected-deposits (helper/page-route grid-page) + :pos-expected-deposit-table (helper/table-route grid-page)}) diff --git a/src/clj/auto_ap/ssr/pos/refunds.clj b/src/clj/auto_ap/ssr/pos/refunds.clj index b774b226..49f741b1 100644 --- a/src/clj/auto_ap/ssr/pos/refunds.clj +++ b/src/clj/auto_ap/ssr/pos/refunds.clj @@ -21,7 +21,8 @@ [clj-time.coerce :as c] [datomic.api :as dc] [clojure.set :as set] - [auto-ap.query-params :as query-params])) + [auto-ap.query-params :as query-params] + [malli.core :as m])) ;; TODO refunds ;; always should be fast @@ -48,17 +49,10 @@ (defn fetch-ids [db request] (let [query-params (:parsed-query-params request) - valid-clients (extract-client-ids (:clients request) - (:client-id query-params) - (when (:client-code query-params) - [:client/code (:client-code query-params)])) - valid-clients (->> valid-clients - (take 10) - set) query (cond-> {:query {:find [] :in ['$ '[?clients ?start-date ?end-date]] :where '[[(iol-ion.query/scan-sales-refunds $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} - :args [db [valid-clients + :args [db [(:trimmed-clients request) (some-> query-params :start-date c/to-date) (some-> query-params :end-date c/to-date )]]} (:sort query-params) (add-sorter-fields {"client" ['[?e :sales-refund/client ?c] @@ -109,64 +103,54 @@ [(->> (hydrate-results ids-to-retrieve db request)) matching-count])) -(def grid-page {:id "refund-table" - :nav (com/main-aside-nav) - :page-specific-nav filters - :id-fn :db/id - :fetch-page fetch-page - :oob-render - (fn [request] - [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) - :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes - :company)} - "POS"] +(def grid-page + (helper/build {:id "refund-table" + :nav (com/main-aside-nav) + :page-specific-nav filters + :fetch-page fetch-page + :parse-query-params (comp + (query-params/parse-key :total-gte query-params/parse-double) + (query-params/parse-key :total-lte query-params/parse-double) + (helper/default-parse-query-params grid-page)) + :oob-render + (fn [request] + [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "POS"] - [:a {:href (bidi/path-for ssr-routes/only-routes - :pos-refunds)} - "Refunds"]] - :title "Refunds" - :entity-name "Refund" - :route :pos-refund-table - :action-buttons (fn [request]) - :row-buttons (fn [request _]) - :headers [{:key "client" - :name "Client" - :sort-key "client" - :hide? (fn [args] - (= (count (:clients args)) 1)) - :render #(-> % :sales-refund/client :client/code)} - {:key "date" - :name "Date" - :sort-key "date" - :render #(atime/unparse-local (:sales-refund/date %) atime/standard-time)} - {:key "total" - :name "Total" - :sort-key "total" - :render #(some->> % :sales-refund/total (format "$%.2f"))} - {:key "type" - :name "Type" - :sort-key "type" - :render :sales-refund/type} - {:key "fee" - :name "Fee" - :sort-key "fee" - :render #(some->> % :sales-refund/fee (format "$%.2f"))}]}) + [:a {:href (bidi/path-for ssr-routes/only-routes + :pos-refunds)} + "Refunds"]] + :title "Refunds" + :entity-name "Refund" + :route :pos-refund-table + :headers [{:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [args] + (= (count (:clients args)) 1)) + :render #(-> % :sales-refund/client :client/code)} + {:key "date" + :name "Date" + :sort-key "date" + :render #(atime/unparse-local (:sales-refund/date %) atime/standard-time)} + {:key "total" + :name "Total" + :sort-key "total" + :render #(some->> % :sales-refund/total (format "$%.2f"))} + {:key "type" + :name "Type" + :sort-key "type" + :render :sales-refund/type} + {:key "fee" + :name "Fee" + :sort-key "fee" + :render #(some->> % :sales-refund/fee (format "$%.2f"))}]})) (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page)) -(def table (-> (partial helper/table grid-page) - (query-params/wrap-parse-query-params - (comp - (query-params/parse-key :total-gte query-params/parse-double) - (query-params/parse-key :total-lte query-params/parse-double) - (helper/default-parse-query-params grid-page))))) -(def page (-> (partial helper/page grid-page) - (query-params/wrap-parse-query-params - (comp - (query-params/parse-key :total-gte query-params/parse-double) - (query-params/parse-key :total-lte query-params/parse-double) - (helper/default-parse-query-params grid-page))))) (def key->handler - {:pos-refunds (wrap-client-redirect-unauthenticated (wrap-secure page)) - :pos-refund-table (wrap-client-redirect-unauthenticated (wrap-secure table))}) + {:pos-refunds (helper/page-route grid-page) + :pos-refund-table (helper/table-route grid-page)}) diff --git a/src/clj/auto_ap/ssr/pos/sales_orders.clj b/src/clj/auto_ap/ssr/pos/sales_orders.clj index 54133acc..3ae3dbcd 100644 --- a/src/clj/auto_ap/ssr/pos/sales_orders.clj +++ b/src/clj/auto_ap/ssr/pos/sales_orders.clj @@ -22,12 +22,8 @@ [auto-ap.time :as atime] [bidi.bidi :as bidi] [clj-time.coerce :as c] - [datomic.api :as dc])) - -;; TODO incorporate parsing into spec -;; TODO incorporate permissions into spec -;; TODO incorporate limiting clients into spec - + [datomic.api :as dc] + [malli.core :as m])) (defn filters [request] [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" @@ -98,18 +94,10 @@ (defn fetch-ids [db request] (let [query-params (:parsed-query-params request) - valid-clients (extract-client-ids (:clients request) - (:client request) - (:client-id query-params) - (when (:client-code query-params) - [:client/code (:client-code query-params)])) - valid-clients (->> valid-clients - (take 10) - set) query (cond-> {:query {:find [] :in ['$ '[?clients ?start-date ?end-date]] :where '[[(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} - :args [db [valid-clients + :args [db [(:trimmed-clients request) (some-> (:start-date query-params) c/to-date) (some-> (:end-date query-params) c/to-date)]]} (:sort query-params) (add-sorter-fields {"client" ['[?e :sales-order/client ?c] @@ -182,103 +170,93 @@ matching-count])) -(def grid-page {:id "sales-table" - :nav (com/main-aside-nav) - :page-specific-nav filters - :id-fn :db/id - :fetch-page fetch-page - :oob-render - (fn [request] - [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) - :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes - :company)} - "POS"] +(def grid-page + (helper/build + {:id "sales-table" + :nav (com/main-aside-nav) + :page-specific-nav filters + :fetch-page fetch-page + :parse-query-params (comp + (query-params/parse-key :processor #(query-params/parse-keyword "ccp-processor" %)) + (query-params/parse-key :total-gte query-params/parse-double) + (query-params/parse-key :total-lte query-params/parse-double) + (helper/default-parse-query-params grid-page)) + :oob-render + (fn [request] + [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "POS"] + + [:a {:href (bidi/path-for ssr-routes/only-routes + :pos-sales)} + "Sales"]] + :title "Sales orders" + :entity-name "Sales orders" + :route :pos-sales-table + :action-buttons (fn [request] + (let [{:keys [total tax]} (d-sales/summarize-orders (:ids (fetch-ids (dc/db conn) request)))] + (when (and total tax) + [ + (com/pill {:color :primary} + (format "Total $%.2f" total) + ) + (com/pill {:color :secondary} + (format "Tax $%.2f" tax ) + )]))) + :row-buttons (fn [_ e] + (when (:sales-order/reference-link e) + [(com/a-icon-button {:href (:sales-order/reference-link e)} + svg/external-link)])) + :headers [{:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [args] + (= (count (:clients args)) 1)) + :render #(-> % :sales-order/client :client/code)} + {:key "date" + :name "Date" + :sort-key "date" + :render #(atime/unparse-local (:sales-order/date %) atime/standard-time)} + {:key "source" + :name "Source" + :sort-key "source" + :render (fn [sales-order] + (when (:sales-order/source sales-order) + (com/pill {:color :primary } + (:sales-order/source sales-order))))} + {:key "total" + :name "Total" + :sort-key "total" + :render #(some->> % :sales-order/total (format "$%.2f"))} + {:key "tax" + :name "Tax" + :sort-key "tax" + :render #(some->> % :sales-order/tax (format "$%.2f"))} + {:key "tip" + :name "Tip" + :sort-key "tip" + :render #(some->> % :sales-order/tip (format "$%.2f"))} + {:key "Payment methods" + :name "Payment Methods" + :render (fn [sales-order] + [:div.flex.space-x-2 + (for [payment-method (->> sales-order :sales-order/charges (map :charge/type-name) set)] + (com/pill {:color :primary } + (condp = payment-method + "CASH" "cash" + "" nil + "ALL" nil + "CARD" "card" + "SQUARE_GIFT_CARD" "gift card" + "OTHER" "other" + nil)))])}]} + )) - [:a {:href (bidi/path-for ssr-routes/only-routes - :pos-sales)} - "Sales"]] - :title "Sales orders" - :entity-name "Sales orders" - :route :pos-sales-table - :action-buttons (fn [request] - (let [{:keys [total tax]} (d-sales/summarize-orders (:ids (fetch-ids (dc/db conn) request)))] - (when (and total tax) - [ - (com/pill {:color :primary} - (format "Total $%.2f" total) - ) - (com/pill {:color :secondary} - (format "Tax $%.2f" tax ) - )]))) - :row-buttons (fn [_ e] - (when (:sales-order/reference-link e) - [(com/a-icon-button {:href (:sales-order/reference-link e)} - svg/external-link)])) - :headers [ - {:key "client" - :name "Client" - :sort-key "client" - :hide? (fn [args] - (= (count (:clients args)) 1)) - :render #(-> % :sales-order/client :client/code)} - {:key "date" - :name "Date" - :sort-key "date" - :render #(atime/unparse-local (:sales-order/date %) atime/standard-time)} - {:key "source" - :name "Source" - :sort-key "source" - :render (fn [sales-order] - (when (:sales-order/source sales-order) - (com/pill {:color :primary } - (:sales-order/source sales-order))))} - {:key "total" - :name "Total" - :sort-key "total" - :render #(some->> % :sales-order/total (format "$%.2f"))} - {:key "tax" - :name "Tax" - :sort-key "tax" - :render #(some->> % :sales-order/tax (format "$%.2f"))} - {:key "tip" - :name "Tip" - :sort-key "tip" - :render #(some->> % :sales-order/tip (format "$%.2f"))} - {:key "Payment methods" - :name "Payment Methods" - :render (fn [sales-order] - [:div.flex.space-x-2 - (for [payment-method (->> sales-order :sales-order/charges (map :charge/type-name) set)] - (com/pill {:color :primary } - (condp = payment-method - "CASH" "cash" - "" nil - "ALL" nil - "CARD" "card" - "SQUARE_GIFT_CARD" "gift card" - "OTHER" "other" - nil)))])}]}) (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page)) - - -(def table (-> (partial helper/table grid-page) - (query-params/wrap-parse-query-params - (comp - (query-params/parse-key :processor #(query-params/parse-keyword "ccp-processor" %)) - (query-params/parse-key :total-gte query-params/parse-double) - (query-params/parse-key :total-lte query-params/parse-double) - (helper/default-parse-query-params grid-page))))) -(def page (-> (partial helper/page grid-page) - (query-params/wrap-parse-query-params - (comp - (query-params/parse-key :processor #(query-params/parse-keyword "ccp-processor" %)) - (query-params/parse-key :total-gte query-params/parse-double) - (query-params/parse-key :total-lte query-params/parse-double) - (helper/default-parse-query-params grid-page))))) - (def key->handler - {:pos-sales (wrap-client-redirect-unauthenticated (wrap-secure page)) - :pos-sales-table (wrap-client-redirect-unauthenticated (wrap-secure table))}) + {:pos-sales (helper/page-route grid-page) + :pos-sales-table (helper/table-route grid-page)}) diff --git a/src/clj/auto_ap/ssr/pos/tenders.clj b/src/clj/auto_ap/ssr/pos/tenders.clj index b615d195..27c4de7e 100644 --- a/src/clj/auto_ap/ssr/pos/tenders.clj +++ b/src/clj/auto_ap/ssr/pos/tenders.clj @@ -8,19 +8,19 @@ merge-query pull-many query2]] - [auto-ap.graphql.utils :refer [extract-client-ids]] - [auto-ap.routes.utils - :refer [wrap-client-redirect-unauthenticated wrap-secure]] - [auto-ap.ssr.pos.common :refer [date-range-field* processor-field* total-field*]] + [auto-ap.query-params :as query-params] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components :as com] [auto-ap.ssr.grid-page-helper :as helper] + [auto-ap.ssr.pos.common + :refer [date-range-field* processor-field* total-field*]] [auto-ap.ssr.svg :as svg] [auto-ap.time :as atime] [bidi.bidi :as bidi] [clj-time.coerce :as c] [datomic.api :as dc] - [auto-ap.query-params :as query-params])) + [malli.core :as m] + [malli.transform :as mt2])) ;; always should be fast @@ -48,19 +48,10 @@ (defn fetch-ids [db request] (let [query-params (:parsed-query-params request) - valid-clients (extract-client-ids (:clients request) - (:client request) - (:client-id query-params) - (when (:client-code query-params) - [:client/code (:client-code query-params)])) - valid-clients (->> valid-clients - (take 10) - set) - processor (some-> query-params :processor (#(keyword "ccp-processor" (name %)))) ; TODO parse query (cond-> {:query {:find [] :in ['$ '[?clients ?start-date ?end-date]] :where '[[(iol-ion.query/scan-charges $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]} - :args [db [valid-clients + :args [db [(:trimmed-clients request) (some-> (:start-date query-params) c/to-date) (some-> (:end-date query-params) c/to-date)]]} (:sort query-params) (add-sorter-fields {"client" ['[?e :charge/client ?c] @@ -90,10 +81,10 @@ '[(<= ?a ?total-lte)]]} :args [(:total-lte query-params)]}) - processor + (:processor query-params) (merge-query {:query {:in '[?processor] :where ['[?e :charge/processor ?processor]]} - :args [processor]}) + :args [(:processor query-params)]}) true @@ -118,81 +109,73 @@ [(->> (hydrate-results ids-to-retrieve db request)) matching-count])) -(def grid-page {:id "tender-table" - :nav (com/main-aside-nav) - :page-specific-nav filters - :id-fn :db/id - :fetch-page fetch-page - :oob-render - (fn [request] - [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) - :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes - :company)} - "POS"] +(def grid-page + (helper/build + {:id "tender-table" + :nav (com/main-aside-nav) + :page-specific-nav filters + :fetch-page fetch-page + :parse-query-params (comp + (query-params/parse-key :processor #(query-params/parse-keyword "ccp-processor" %)) + (query-params/parse-key :total-gte query-params/parse-double) + (query-params/parse-key :total-lte query-params/parse-double) + (helper/default-parse-query-params grid-page)) + :oob-render + (fn [request] + [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "POS"] + + [:a {:href (bidi/path-for ssr-routes/only-routes + :pos-tenders)} + "Tenders"]] + :title "Tenders" + :entity-name "Tender" + :route :pos-tender-table + :row-buttons (fn [request e] + (when (:charge/reference-link e) + [(com/a-icon-button {:href (:charge/reference-link e)} + svg/external-link)])) + :headers [{:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [request] + (= (count (:clients request)) 1)) + :render #(-> % :charge/client :client/code)} + {:key "date" + :name "Date" + :sort-key "date" + :render #(atime/unparse-local (:charge/date %) atime/standard-time)} + {:key "total" + :name "Total" + :sort-key "total" + :render #(some->> % :charge/total (format "$%.2f"))} + {:key "processor" + :name "Processor" + :sort-key "processor" + :render (fn [sales-order] + (when (:charge/processor sales-order) + (com/pill {:color :primary } + (name (:charge/processor sales-order)))))} + {:key "tip" + :name "Tip" + :sort-key "tip" + :render #(some->> % :charge/tip (format "$%.2f"))} + {:key "links" + :name "Links" + :render (fn [entity] + (when-let [expected-deposit-id (some->> entity :expected-deposit/_charges first :db/id)] + [:a {:href (str (bidi/path-for ssr-routes/only-routes + :pos-expected-deposits) + "?exact-match-id=" expected-deposit-id) + :hx-boost "true"} + (com/pill {:color :secondary} "expected deposit")]))}]})) - [:a {:href (bidi/path-for ssr-routes/only-routes - :pos-tenders)} - "Tenders"]] - :title "Tenders" - :entity-name "Tender" - :route :pos-tender-table - :action-buttons (fn [request]) - :row-buttons (fn [request e] - (when (:charge/reference-link e) - [(com/a-icon-button {:href (:charge/reference-link e)} - svg/external-link)])) - :headers [{:key "client" - :name "Client" - :sort-key "client" - :hide? (fn [request] - (= (count (:clients request)) 1)) - :render #(-> % :charge/client :client/code)} - {:key "date" - :name "Date" - :sort-key "date" - :render #(atime/unparse-local (:charge/date %) atime/standard-time)} - {:key "total" - :name "Total" - :sort-key "total" - :render #(some->> % :charge/total (format "$%.2f"))} - {:key "processor" - :name "Processor" - :sort-key "processor" - :render (fn [sales-order] - (when (:charge/processor sales-order) - (com/pill {:color :primary } - (name (:charge/processor sales-order)))))} - {:key "tip" - :name "Tip" - :sort-key "tip" - :render #(some->> % :charge/tip (format "$%.2f"))} - {:key "links" - :name "Links" - :render (fn [entity] - (when-let [expected-deposit-id (some->> entity :expected-deposit/_charges first :db/id)] - [:a {:href (str (bidi/path-for ssr-routes/only-routes - :pos-expected-deposits) - "?exact-match-id=" expected-deposit-id) - :hx-boost "true"} - (com/pill {:color :secondary} "expected deposit")]))}]}) (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page)) -(def table (-> (partial helper/table grid-page) - (query-params/wrap-parse-query-params - (comp - (query-params/parse-key :processor #(query-params/parse-keyword "ccp-processor" %)) - (query-params/parse-key :total-gte query-params/parse-double) - (query-params/parse-key :total-lte query-params/parse-double) - (helper/default-parse-query-params grid-page))))) -(def page (-> (partial helper/page grid-page) - (query-params/wrap-parse-query-params - (comp - (query-params/parse-key :processor #(query-params/parse-keyword "ccp-processor" %)) - (query-params/parse-key :total-gte query-params/parse-double) - (query-params/parse-key :total-lte query-params/parse-double) - (helper/default-parse-query-params grid-page))))) (def key->handler - {:pos-tenders (wrap-client-redirect-unauthenticated (wrap-secure page)) - :pos-tender-table (wrap-client-redirect-unauthenticated (wrap-secure table))}) + {:pos-tenders (helper/page-route grid-page) + :pos-tender-table (helper/table-route grid-page)}) From 1320667e6928872b6e2a3e5ab645d933f72dac1e Mon Sep 17 00:00:00 2001 From: Bryce Date: Thu, 28 Sep 2023 22:42:21 -0700 Subject: [PATCH 12/13] Everything should be migrated over great --- src/clj/auto_ap/datomic/yodlee2.clj | 69 --------------- src/clj/auto_ap/ssr/company/company_1099.clj | 91 +++++--------------- src/clj/auto_ap/ssr/company/reports.clj | 90 ++++++++++++++----- src/clj/auto_ap/ssr/company/yodlee.clj | 60 ++++++++++--- 4 files changed, 137 insertions(+), 173 deletions(-) delete mode 100644 src/clj/auto_ap/datomic/yodlee2.clj diff --git a/src/clj/auto_ap/datomic/yodlee2.clj b/src/clj/auto_ap/datomic/yodlee2.clj deleted file mode 100644 index 4692d37e..00000000 --- a/src/clj/auto_ap/datomic/yodlee2.clj +++ /dev/null @@ -1,69 +0,0 @@ -(ns auto-ap.datomic.yodlee2 - (:require - [auto-ap.datomic - :refer [add-sorter-fields - apply-pagination - apply-sort-3 - conn - merge-query - pull-many - query2]] - [auto-ap.graphql.utils :refer [extract-client-ids]] - [clj-time.coerce :as c] - [datomic.api :as dc])) - -(def default-read '[* {:yodlee-provider-account/client [:client/code]}]) - -(defn <-datomic [x] - (-> x (update :yodlee-provider-account/last-updated c/from-date))) - -(defn raw-graphql-ids [db args] - (let [valid-clients (extract-client-ids (:clients args) - (:client-id args) - (when (:client-code args) - [:client/code (:client-code args)]))] - (->> (cond-> {:query {:find [] - :in ['$ '[?xx ...]] - :where ['[?e :yodlee-provider-account/id] - '[?e :yodlee-provider-account/client ?xx]]} - :args [db valid-clients]} - - - (:client-id args) - (merge-query {:query {:in ['?client-id] - :where ['[?e :yodlee-provider-account/client ?client-id]]} - :args [ (:client-id args)]}) - - (:client-code args) - (merge-query {:query {:in ['?client-code] - :where ['[?e :yodlee-provider-account/client ?client-id] - '[?client-id :client/code ?client-code]]} - :args [ (:client-code args)]}) - - (:sort args) (add-sorter-fields {"status" ['[?e :yodlee-provider-account/status ?sort-status]] - "last-updated" ['[?e :yodlee-provider-account/last-updated ?sort-last-updated]]} - args) - true - (merge-query {:query {:find ['?e ] - :where ['[?e :yodlee-provider-account/id]]}}) ) - - (query2) - (apply-sort-3 args) - (apply-pagination args)))) - - -(defn graphql-results [ids db _] - (let [results (->> (pull-many db default-read ids) - (group-by :db/id))] - (->> ids - (map results) - (map first) - (mapv <-datomic)))) - - -(defn get-graphql [args] - (let [db (dc/db conn) - {ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)] - [(->> (graphql-results ids-to-retrieve db args)) - matching-count])) - diff --git a/src/clj/auto_ap/ssr/company/company_1099.clj b/src/clj/auto_ap/ssr/company/company_1099.clj index b2095d9c..f994d169 100644 --- a/src/clj/auto_ap/ssr/company/company_1099.clj +++ b/src/clj/auto_ap/ssr/company/company_1099.clj @@ -44,75 +44,32 @@ client-id vendor-id))) -(defn get-1099-companies [user {:keys [clients] :as args}] - (let [valid-clients (extract-client-ids (:clients args) - (:client-id args) - (when (:client-code args) - [:client/code (:client-code args)])) - results (cond - clients - (dc/q '[:find - (pull ?c [:client/code :db/id]) - (pull ?v vendor-read) - (sum ?a) - :with ?d - :in $ [?c ...] vendor-read - :where - [?p :payment/client ?c] - [?p :payment/date ?d ] - [(>= ?d #inst "2022-01-01T08:00")] - [(< ?d #inst "2023-01-01T08:00")] - [?p :payment/type :payment-type/check] - [?p :payment/amount ?a] - [?p :payment/vendor ?v]] - (dc/db conn) - valid-clients - vendor-read) - - (is-admin? user) - (dc/q '[:find - (pull ?c [:client/code :db/id]) - (pull ?v vendor-read) - (sum ?a) - :with ?d - :in $ vendor-read - :where - [?p :payment/date ?d ] - [(>= ?d #inst "2022-01-01T08:00")] - [(< ?d #inst "2023-01-01T08:00")] - [?p :payment/type :payment-type/check] - [?p :payment/client ?c] - [?p :payment/amount ?a] - [?p :payment/vendor ?v]] - (dc/db conn) - vendor-read) - - :else - (dc/q '[:find - (pull ?c [:client/code :db/id]) - (pull ?v vendor-read) - (sum ?a) - :with ?d - :in $ [?c ...] vendor-read - :where - [?p :payment/client ?c] - [?p :payment/date ?d ] - [(>= ?d #inst "2022-01-01T08:00")] - [(< ?d #inst "2023-01-01T08:00")] - [?p :payment/type :payment-type/check] - [?p :payment/amount ?a] - [?p :payment/vendor ?v]] - (dc/db conn) - valid-clients - vendor-read)) +(defn fetch-page [{:keys [trimmed-clients parsed-query-params] :as request user :identity}] + (let [results (dc/q '[:find + (pull ?c [:client/code :db/id]) + (pull ?v vendor-read) + (sum ?a) + :with ?d + :in $ [?c ...] vendor-read + :where + [?p :payment/client ?c] + [?p :payment/date ?d ] + [(>= ?d #inst "2022-01-01T08:00")] + [(< ?d #inst "2023-01-01T08:00")] + [?p :payment/type :payment-type/check] + [?p :payment/amount ?a] + [?p :payment/vendor ?v]] + (dc/db conn) + trimmed-clients + vendor-read) all (->> results (filter (fn [[_ _ a]] (>= (or a 0.0) 600.0))) (sort-by (fn [[client _ amount]] [(:client/code client ) amount])) (into [])) - paginated (apply-pagination-raw {:start (:start (:parsed-query-params args)) - :per-page (:per-page (:parsed-query-params args))} all)] + paginated (apply-pagination-raw {:start (:start parsed-query-params) + :per-page (:per-page parsed-query-params)} all)] [(:entries paginated) (:count paginated)])) (def grid-page @@ -120,9 +77,7 @@ {:id "vendor-table" :nav (com/company-aside-nav) :id-fn (comp :db/id second) - :fetch-page (fn [user args] - (get-1099-companies user args) - #_(r/get-graphql (into args {:id user}))) + :fetch-page fetch-page :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "My Company"] @@ -133,9 +88,7 @@ :title "1099 Vendors" :entity-name "Vendors" :route :company-1099-vendor-table - :action-buttons (fn [user _] - nil) - :row-buttons (fn [user e] + :row-buttons (fn [request e] [(com/icon-button {:hx-get (str (bidi/path-for ssr-routes/only-routes :company-1099-vendor-dialog :vendor-id (:db/id (second e))) diff --git a/src/clj/auto_ap/ssr/company/reports.clj b/src/clj/auto_ap/ssr/company/reports.clj index af41992a..2b764d0e 100644 --- a/src/clj/auto_ap/ssr/company/reports.clj +++ b/src/clj/auto_ap/ssr/company/reports.clj @@ -1,7 +1,14 @@ (ns auto-ap.ssr.company.reports (:require [amazonica.aws.s3 :as s3] - [auto-ap.datomic :refer [conn]] + [auto-ap.datomic + :refer [add-sorter-fields + apply-pagination + apply-sort-3 + conn + merge-query + pull-many + query2]] [auto-ap.datomic.reports :as r] [auto-ap.graphql.utils :refer [assert-can-see-client is-admin?]] [auto-ap.ssr-routes :as ssr-routes] @@ -11,22 +18,61 @@ [auto-ap.ssr.utils :refer [html-response]] [auto-ap.time :as atime] [bidi.bidi :as bidi] + [clojure.set :as set] [config.core :refer [env]] - [datomic.api :as dc] - [com.brunobonacci.mulog :as mu])) + [datomic.api :as dc])) + +(def default-read '[:db/id :report/client [:report/created :xform clj-time.coerce/from-date] :report/url :report/name :report/creator]) + +(defn fetch-ids [db request] + + (let [query-params (:parsed-query-params request) + query (cond-> {:query {:find [] + :in '[$ [?c ...]] + :where '[[?e :report/client ?c]]} + :args [db (:trimmed-clients request)]} + + + (:sort query-params) (add-sorter-fields {"client" ['[?e :report/client ?c] + '[?c :client/name ?sort-client]] + "created" ['[?e :report/created ?sort-created]] + "creator" ['[?e :report/creator ?sort-creator]] + "name" ['[?e :report/name ?sort-name] + ]} + query-params) + + true + (merge-query {:query {:find ['?sort-default '?e] :where ['[?e :report/created ?sort-default]]}}))] + (->> (query2 query) + (apply-sort-3 (update query-params :sort conj {:sort-key "default-2" :asc true})) + (apply-pagination query-params)))) + +(defn hydrate-results [ids db request] + (let [results (->> (pull-many db default-read ids) + (group-by :db/id)) + valid-clients (:trimmed-clients request)] + (->> ids + (map results) + (filter identity) + + (map first) + (filter (fn [r] + (let [used-clients (set (map :db/id (:report/client r)))] + (= used-clients + (set/intersection valid-clients + used-clients)))))))) + +(defn fetch-page [args] + (let [db (dc/db conn) + {ids-to-retrieve :ids matching-count :count} (fetch-ids db args)] + + [(->> (hydrate-results ids-to-retrieve db args)) + matching-count])) (def grid-page (helper/build {:id "report-table" :nav (com/company-aside-nav) - :id-fn :db/id - :fetch-page (fn [user args] - (prn args) - (r/get-graphql {:client (:client args) - :clients (:clients args) - :start (:start (:parsed-query-params args)) - :per-page (:per-page (:parsed-query-params args)) - :sort (:sort (:parsed-query-params args)) - })) + :fetch-page fetch-page :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "My Company"] @@ -37,17 +83,15 @@ :title "Reports" :entity-name "Reports" :route :company-reports-table - :action-buttons (fn [user _] - nil) - :row-buttons (fn [user e] - (com/a-icon-button {:href (:report/url e)} - svg/download)[ - (when (is-admin? user) - (com/icon-button {:hx-delete (str (bidi/path-for ssr-routes/only-routes - :company-reports-delete - :request-method :delete)) - :hx-target "closest tr"} - svg/trash))]) + :row-buttons (fn [request e] + [(com/a-icon-button {:href (:report/url e)} + svg/download) + (when (is-admin? (:identity request)) + (com/icon-button {:hx-delete (str (bidi/path-for ssr-routes/only-routes + :company-reports-delete + :request-method :delete)) + :hx-target "closest tr"} + svg/trash))]) :headers [{:key "name" :name "Name" :sort-key "name" diff --git a/src/clj/auto_ap/ssr/company/yodlee.clj b/src/clj/auto_ap/ssr/company/yodlee.clj index 71d58936..eb619894 100644 --- a/src/clj/auto_ap/ssr/company/yodlee.clj +++ b/src/clj/auto_ap/ssr/company/yodlee.clj @@ -1,7 +1,14 @@ (ns auto-ap.ssr.company.yodlee (:require - [auto-ap.datomic :refer [conn pull-attr]] - [auto-ap.datomic.yodlee2 :as yodlee2] + [auto-ap.datomic + :refer [add-sorter-fields + apply-pagination + apply-sort-3 + conn + merge-query + pull-attr + pull-many + query2]] [auto-ap.graphql.utils :refer [is-admin?]] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components :as com] @@ -16,15 +23,51 @@ [hiccup2.core :as hiccup])) (def default-read '[:db/id - :yodlee-provider-account/last-updated + [:yodlee-provider-account/last-updated :xform clj-time.coerce/from-date] :yodlee-provider-account/status :yodlee-provider-account/id :yodlee-provider-account/detailed-status {:yodlee-provider-account/accounts [:yodlee-account/name :yodlee-account/number] - :yodlee-provider-account/client [:client/code]}]) + :yodlee-provider-account/client [:client/code]}]) +(defn fetch-ids [db request] + (let [query-params (:parsed-query-params request)] + (->> (cond-> {:query {:find [] + :in ['$ '[?xx ...]] + :where ['[?e :yodlee-provider-account/id] + '[?e :yodlee-provider-account/client ?xx]]} + :args [db (:trimmed-clients request)]} + + + (:sort query-params) (add-sorter-fields {"status" ['[?e :yodlee-provider-account/status ?sort-status]] + "last-updated" ['[?e :yodlee-provider-account/last-updated ?sort-last-updated]]} + query-params) + true + (merge-query {:query {:find ['?e ] + :where ['[?e :yodlee-provider-account/id]]}})) + + (query2) + (apply-sort-3 query-params) + (apply-pagination query-params)))) + + +(defn hydrate-results [ids db _] + (let [results (->> (pull-many db default-read ids) + (group-by :db/id))] + (->> ids + (map results) + (map first)))) + + +(defn fetch-page [request] + (let [db (dc/db conn) + {ids-to-retrieve :ids matching-count :count} (fetch-ids db request)] + [(->> (hydrate-results ids-to-retrieve db request)) + matching-count])) + + (defn fastlink-dialog [{:keys [client]}] (html-response (com/modal @@ -85,14 +128,7 @@ fastlink.open({fastLinkURL: '%s', {:id "yodlee-table" :nav (com/company-aside-nav) :id-fn :db/id - :fetch-page (fn [request] - (println (count (:clients request))) - (yodlee2/get-graphql - {:client (:client request) - :clients (:clients request) - :sort (:sort (:parsed-query-params request)) - :start (:start (:parsed-query-params request)) - :per-page (:per-page (:parsed-query-params request))})) + :fetch-page fetch-page :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} "My Company"] From 8c3756f1c12a55dbd0ec49bdf23252deeb46b3c2 Mon Sep 17 00:00:00 2001 From: Bryce Date: Thu, 28 Sep 2023 23:22:25 -0700 Subject: [PATCH 13/13] Sets up impersonate for ssr pages too --- src/clj/auto_ap/datomic/reports.clj | 68 ------------------- src/clj/auto_ap/ssr/auth.clj | 12 +++- src/clj/auto_ap/ssr/company/yodlee.clj | 7 +- src/clj/auto_ap/ssr/core.clj | 3 +- src/clj/auto_ap/ssr/grid_page_helper.clj | 1 + src/clj/auto_ap/ssr/pos/sales_orders.clj | 1 + src/cljc/auto_ap/ssr_routes.cljc | 3 +- .../views/pages/admin/users/table.cljs | 39 ++++++++--- 8 files changed, 50 insertions(+), 84 deletions(-) delete mode 100644 src/clj/auto_ap/datomic/reports.clj diff --git a/src/clj/auto_ap/datomic/reports.clj b/src/clj/auto_ap/datomic/reports.clj deleted file mode 100644 index 4cf84daa..00000000 --- a/src/clj/auto_ap/datomic/reports.clj +++ /dev/null @@ -1,68 +0,0 @@ -(ns auto-ap.datomic.reports - (:require - [auto-ap.datomic - :refer [add-sorter-fields - apply-pagination - apply-sort-3 - conn - merge-query - pull-many - query2]] - [auto-ap.graphql.utils :refer [can-see-client? extract-client-ids]] - [clj-time.coerce :as c] - [datomic.api :as dc] - [clojure.set :as set])) - -(def default-read '[:db/id :report/client :report/created :report/url :report/name :report/creator]) - -(defn raw-graphql-ids [db args] - - (let [valid-clients (extract-client-ids (:clients args) - (:client-id args) - (when (:client-code args) - [:client/code (:client-code args)])) - query (cond-> {:query {:find [] - :in '[$ [?c ...]] - :where '[[?e :report/client ?c]]} - :args [db valid-clients]} - - - (:sort args) (add-sorter-fields {"client" ['[?e :report/client ?c] - '[?c :client/name ?sort-client]] - "created" ['[?e :report/created ?sort-created]] - "creator" ['[?e :report/creator ?sort-creator]] - "name" ['[?e :report/name ?sort-name] - ]} - args) - - true - (merge-query {:query {:find ['?sort-default '?e] :where ['[?e :report/created ?sort-default]]}}))] - (->> (query2 query) - (apply-sort-3 (update args :sort conj {:sort-key "default-2" :asc true})) - (apply-pagination args)))) - -(defn graphql-results [ids db args] - (let [results (->> (pull-many db default-read ids) - (map #(update % :report/created c/from-date)) - (group-by :db/id)) - valid-clients (extract-client-ids (:clients args) - (:client-id args) - (when (:client-code args) - [:client/code (:client-code args)]))] - (->> ids - (map results) - (filter identity) - - (map first) - (filter (fn [r] - (let [used-clients (set (map :db/id (:report/client r)))] - (= used-clients - (set/intersection valid-clients - used-clients)))))))) - -(defn get-graphql [args] - (let [db (dc/db conn) - {ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)] - - [(->> (graphql-results ids-to-retrieve db args)) - matching-count])) diff --git a/src/clj/auto_ap/ssr/auth.clj b/src/clj/auto_ap/ssr/auth.clj index b5afa02e..6f863dc4 100644 --- a/src/clj/auto_ap/ssr/auth.clj +++ b/src/clj/auto_ap/ssr/auth.clj @@ -1,6 +1,16 @@ -(ns auto-ap.ssr.auth) +(ns auto-ap.ssr.auth + (:require [buddy.sign.jwt :as jwt] + [config.core :refer [env]])) (defn logout [request] {:status 301 :headers {"Location" "/login"} :session {}}) + + +(defn impersonate [request] + {:status 200 + :session {:identity (dissoc (jwt/unsign (get-in request [:query-params "jwt"]) + (:jwt-secret env) + {:alg :hs512}) + :exp)}}) diff --git a/src/clj/auto_ap/ssr/company/yodlee.clj b/src/clj/auto_ap/ssr/company/yodlee.clj index eb619894..699c40fa 100644 --- a/src/clj/auto_ap/ssr/company/yodlee.clj +++ b/src/clj/auto_ap/ssr/company/yodlee.clj @@ -9,7 +9,7 @@ pull-attr pull-many query2]] - [auto-ap.graphql.utils :refer [is-admin?]] + [auto-ap.graphql.utils :refer [assert-can-see-client is-admin?]] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components :as com] [auto-ap.ssr.grid-page-helper :as helper] @@ -94,7 +94,10 @@ fastlink.open({fastLinkURL: '%s', ] [:div])))) -(defn reauthenticate [{:keys [form-params]}] +(defn reauthenticate [{:keys [form-params identity]}] + (assert-can-see-client identity (-> (dc/pull (dc/db conn) '[{:yodlee-provider-account/client [:db/id]}] (Long/parseLong (get form-params "id"))) + :yodlee-provider-account/client + :db/id)) (html-response (com/modal {} diff --git a/src/clj/auto_ap/ssr/core.clj b/src/clj/auto_ap/ssr/core.clj index e0c4f160..a26400b0 100644 --- a/src/clj/auto_ap/ssr/core.clj +++ b/src/clj/auto_ap/ssr/core.clj @@ -25,6 +25,7 @@ (def key->handler (-> {:logout auth/logout + :impersonate (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin auth/impersonate))) :admin-history (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page))) :admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page))) :admin-history-inspect (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/inspect))) @@ -44,7 +45,7 @@ :company-yodlee-table (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/table)) :company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog)) :company-yodlee-provider-account-refresh (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/refresh-provider-account)) - :company-yodlee-provider-account-reauthenticate (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/reauthenticate)) + :company-yodlee-provider-account-reauthenticate (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/reauthenticate)) :company-reports (wrap-client-redirect-unauthenticated (wrap-secure company-reports/page)) :company-reports-table (wrap-client-redirect-unauthenticated (wrap-secure company-reports/table)) :company-reports-delete (wrap-client-redirect-unauthenticated (wrap-admin company-reports/delete-report)) diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index 36b1782c..50d666e4 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -184,6 +184,7 @@ valid-clients (->> valid-clients (take 20) set)] + (println "VALID CLIENTS ARE" valid-clients) (handler (assoc request :trimmed-clients valid-clients))))) (defn table-route [grid-spec] diff --git a/src/clj/auto_ap/ssr/pos/sales_orders.clj b/src/clj/auto_ap/ssr/pos/sales_orders.clj index 3ae3dbcd..7398c9f7 100644 --- a/src/clj/auto_ap/ssr/pos/sales_orders.clj +++ b/src/clj/auto_ap/ssr/pos/sales_orders.clj @@ -150,6 +150,7 @@ true (merge-query {:query {:find ['?sort-default '?e]}}))] + (clojure.pprint/pprint query) (cond->> (query2 query) true (apply-sort-3 query-params) true (apply-pagination query-params)))) diff --git a/src/cljc/auto_ap/ssr_routes.cljc b/src/cljc/auto_ap/ssr_routes.cljc index 79f6edce..282fc13d 100644 --- a/src/cljc/auto_ap/ssr_routes.cljc +++ b/src/cljc/auto_ap/ssr_routes.cljc @@ -1,6 +1,7 @@ (ns auto-ap.ssr-routes) -(def routes {"logout" :logout +(def routes {"impersonate" :impersonate + "logout" :logout "search" :search "invoice" {"/glimpse" {"" {:get :invoice-glimpse :post :invoice-glimpse-upload diff --git a/src/cljs/auto_ap/views/pages/admin/users/table.cljs b/src/cljs/auto_ap/views/pages/admin/users/table.cljs index 3decaa90..7baa9576 100644 --- a/src/cljs/auto_ap/views/pages/admin/users/table.cljs +++ b/src/cljs/auto_ap/views/pages/admin/users/table.cljs @@ -1,11 +1,32 @@ (ns auto-ap.views.pages.admin.users.table - (:require - [clojure.string :as str] - [re-frame.core :as re-frame] - [auto-ap.views.utils :refer [action-cell-width]] - [auto-ap.views.pages.admin.users.form :as form] + (:require [auto-ap.views.components.buttons :as buttons] - [auto-ap.views.components.grid :as grid])) + [auto-ap.views.components.grid :as grid] + [auto-ap.views.pages.admin.users.form :as form] + [auto-ap.views.utils + :refer [action-cell-width dispatch-event with-user]] + [clojure.string :as str] + [re-frame.core :as re-frame])) + +(re-frame/reg-event-fx + ::impersonated + (fn [_ [_ impersonate-jwt]] + (println "SUCCESED") + (.setItem js/localStorage "jwt" impersonate-jwt) + (.removeItem js/localStorage "last-client-id" nil) + (.removeItem js/localStorage "last-selected-clients" nil) + (.reload (.-location js/document ) true) + {})) + +(re-frame/reg-event-fx + ::impersonate + [with-user] + (fn [{:keys [db user]} [_ impersonate-jwt]] + (js/alert "HI") + + {:http {:method "GET" + :uri (str "/impersonate?jwt=" impersonate-jwt) + :on-success [::impersonated impersonate-jwt]}})) (re-frame/reg-event-fx ::params-changed @@ -50,11 +71,7 @@ [grid/cell {} role] [grid/cell {} (str/join ", " (map :name clients))] [grid/cell {} - [:a.button {:on-click (fn [] - (.setItem js/localStorage "jwt" (:impersonate-jwt c)) - (.removeItem js/localStorage "last-client-id" nil) - (.removeItem js/localStorage "last-selected-clients" nil) - (.reload (.-location js/document ) true))} + [:a.button {:on-click (dispatch-event [::impersonate (:impersonate-jwt c)])} "Impersonate"]