diff --git a/data/solr/data/clients/conf/schema.xml b/data/solr/data/clients/conf/schema.xml
index 72f2331e..0303c54d 100755
--- a/data/solr/data/clients/conf/schema.xml
+++ b/data/solr/data/clients/conf/schema.xml
@@ -160,6 +160,7 @@
+
diff --git a/project.clj b/project.clj
index fa0c0049..898e3939 100644
--- a/project.clj
+++ b/project.clj
@@ -1,5 +1,5 @@
(defproject auto-ap "0.1.0-SNAPSHOT"
- :description "FIXME: write description"
+ :description "FIXME: write description"
:url "http://example.com/FIXME"
:min-lein-version "2.0.0"
:dependencies [[com.google.guava/guava "31.1-jre"]
@@ -109,7 +109,7 @@
[lein-ancient "0.6.15"]]
:clean-targets ^{:protect false} ["resources/public/js/compiled" "target"]
#_#_:ring {:handler auto-ap.handler/app}
- :source-paths ["src/clj" "src/cljc" "src/cljs" "iol_ion/src" ]
+ :source-paths ["src/clj" "src/cljc" "src/cljs" "iol_ion/src"]
:resource-paths ["resources"]
:aliases {"build" ["do" ["uberjar"]]
"fig:dev" ["run" "-m" "figwheel.main" "-b" "dev" "-r"]
@@ -117,10 +117,8 @@
"fig:min" ["run" "-m" "figwheel.main" "-O" "whitespace" "-bo" "min"]}
- :profiles {
- :dev
- {
- :resource-paths ["resources" "target"]
+ :profiles {:dev
+ {:resource-paths ["resources" "target"]
:dependencies [#_[binaryage/devteols "1.0.2"]
[postgresql/postgresql "9.3-1102.jdbc41"]
[org.clojure/tools.namespace "1.4.5"]
@@ -144,13 +142,13 @@
[com.bhauman/rebel-readline-cljs "0.1.4" :exclusions [org.clojure/clojurescript]]
[javax.servlet/servlet-api "2.5"]]
:plugins [[lein-pdo "0.1.1"]]
- :jvm-opts ["-Dconfig=config/dev.edn" "-Xms4G" "-Xmx20G" "-XX:-OmitStackTraceInFastThrow" ]}
+ :jvm-opts ["-Dconfig=config/dev.edn" "-Xms4G" "-Xmx20G" "-XX:-OmitStackTraceInFastThrow"]}
:uberjar
- {
- :java-cmd "/usr/lib/jvm/java-11-openjdk/bin/java"
- :prep-tasks ["fig:min" ]
- :aot [auto-ap.server auto-ap.time clj-time.core clj-time.coerce clj-time.format clojure.tools.logging.impl ]
+ {:java-cmd "/usr/lib/jvm/java-11-openjdk/bin/java"
+ :main auto-ap.main
+ :prep-tasks ["fig:min"]
+ :aot [auto-ap.main auto-ap.time clj-time.core clj-time.coerce clj-time.format clojure.tools.logging.impl]
:dependencies [[com.bhauman/figwheel-main "0.2.18" :exclusions [org.clojure/clojurescript
ring
ring/ring-core
@@ -169,7 +167,7 @@
:provided {:dependencies [[org.clojure/clojurescript "1.11.4"
:exclusions [com.google.code.findbugs/jsr305
com.fasterxml.jackson.core/jackson-core]]
- [reagent "1.0.0" :exclusions [cljsjs/react cljsjs/react-dom cljsjs/react-dom-server] ]
+ [reagent "1.0.0" :exclusions [cljsjs/react cljsjs/react-dom cljsjs/react-dom-server]]
[re-frame "1.1.2"
:exclusions
[reagent
@@ -177,11 +175,8 @@
[re-frame-utils "0.1.0"]
[com.andrewmcveigh/cljs-time "0.5.2"]
[cljs-http "0.1.46"]
- [kibu/pushy "0.3.8"]]}
+ [kibu/pushy "0.3.8"]]}}
- }
-
- :main auto-ap.server
:uberjar-name "auto-ap.jar"
:test-paths ["test/clj"]
:test-selectors {:integration (fn [m]
@@ -190,10 +185,10 @@
(clojure.string/includes? (str (:name m))
"integration")))
:functional (fn [m]
- (or (clojure.string/includes? (str (:ns m))
- "functional")
- (clojure.string/includes? (str (:name m))
- "functional")))}
+ (or (clojure.string/includes? (str (:ns m))
+ "functional")
+ (clojure.string/includes? (str (:name m))
+ "functional")))}
- :prep-tasks [ "compile"])
+ :prep-tasks ["compile"])
diff --git a/resources/schema.edn b/resources/schema.edn
index b0aa4e06..700ef49e 100644
--- a/resources/schema.edn
+++ b/resources/schema.edn
@@ -337,6 +337,10 @@
:db/cardinality #:db{:ident :db.cardinality/many},
:db/doc "A client's locations",
:db/ident :client/locations}
+ {:db/valueType #:db{:ident :db.type/string},
+ :db/cardinality #:db{:ident :db.cardinality/many},
+ :db/doc "The groups that this client belongs in, for fast lookup",
+ :db/ident :client/groups}
{:db/valueType #:db{:ident :db.type/ref},
:db/isComponent true,
:db/cardinality #:db{:ident :db.cardinality/one},
@@ -1800,47 +1804,47 @@
{:db/ident :invoice/client+date
:db/valueType :db.type/tuple
- :db/tupleAttrs [ :invoice/client :invoice/date]
+ :db/tupleAttrs [:invoice/client :invoice/date]
:db/cardinality :db.cardinality/one
:db/index true}
{:db/ident :transaction/client+date
:db/valueType :db.type/tuple
- :db/tupleAttrs [ :transaction/client :transaction/date]
+ :db/tupleAttrs [:transaction/client :transaction/date]
:db/cardinality :db.cardinality/one
:db/index true}
{:db/ident :journal-entry/client+date
:db/valueType :db.type/tuple
- :db/tupleAttrs [ :journal-entry/client :journal-entry/date]
+ :db/tupleAttrs [:journal-entry/client :journal-entry/date]
:db/cardinality :db.cardinality/one
:db/index true}
{:db/ident :payment/client+date
:db/valueType :db.type/tuple
- :db/tupleAttrs [ :payment/client :payment/date]
+ :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/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/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/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/tupleAttrs [:expected-deposit/client :expected-deposit/date]
:db/cardinality :db.cardinality/one
:db/index true}]
diff --git a/src/clj/auto_ap/datomic/clients.clj b/src/clj/auto_ap/datomic/clients.clj
index 7ab7681d..a408e7ff 100644
--- a/src/clj/auto_ap/datomic/clients.clj
+++ b/src/clj/auto_ap/datomic/clients.clj
@@ -79,6 +79,7 @@
(defn get-minimal []
(->> (dc/q '[:find (pull ?e [:client/name :client/code :client/locations :db/id
+ :client/groups
{:client/bank-accounts [{:bank-account/type [:db/ident]}
:bank-account/name
:bank-account/sort-order
diff --git a/src/clj/auto_ap/graphql/clients.clj b/src/clj/auto_ap/graphql/clients.clj
index 4d004881..bc4591d5 100644
--- a/src/clj/auto_ap/graphql/clients.clj
+++ b/src/clj/auto_ap/graphql/clients.clj
@@ -102,6 +102,7 @@
:locked_until {:type :iso_date}
:code {:type 'String}
:feature_flags {:type '(list String)}
+ :groups {:type '(list String)}
:square_auth_token {:type 'String}
:signature_file {:type 'String}
:square_integration_status {:type :integration_status}
diff --git a/src/clj/auto_ap/handler.clj b/src/clj/auto_ap/handler.clj
index ffc61e4f..7e370ac8 100644
--- a/src/clj/auto_ap/handler.clj
+++ b/src/clj/auto_ap/handler.clj
@@ -72,7 +72,7 @@
(defn render-index [_]
(response/resource-response "index.html" {:root "public"}))
-(def match->handler-lookup
+(def match->handler-lookup
(-> {:not-found not-found}
(merge ssr/key->handler)
(merge graphql/match->handler)
@@ -83,13 +83,13 @@
(merge auth/match->handler)
(merge invoices/match->handler)
(merge exports/match->handler)
- (merge
- (into {}
- (map
+ (merge
+ (into {}
+ (map
- (fn [k]
- [k render-index])
- client-routes/all-matches)))))
+ (fn [k]
+ [k render-index])
+ client-routes/all-matches)))))
(def match->handler
(fn [route]
@@ -102,7 +102,7 @@
match->handler))
(defn wrap-guess-route [handler]
- (fn [{:keys [uri request-method] :as request} ]
+ (fn [{:keys [uri request-method] :as request}]
(let [matched-route (:handler
(bidi.bidi/match-route all-routes
uri
@@ -136,21 +136,21 @@
(not= "/api/graphql" (:uri request))
(assoc :query-params (:query-params request)))
(mu/trace ::http-request-trace
- {:pairs []
- :capture (fn [r] {:status (:status r)})}
- (when-not (str/includes? (:uri request) "health-check")
- (alog/info ::http-request-starting))
- (try
- (let [response (handler request)]
- response)
- (catch Exception e
- (alog/error ::request-error
- :status 500
- :exception e)
- (throw e)))))))
+ {:pairs []
+ :capture (fn [r] {:status (:status r)})}
+ (when-not (str/includes? (:uri request) "health-check")
+ (alog/info ::http-request-starting))
+ (try
+ (let [response (handler request)]
+ response)
+ (catch Exception e
+ (alog/error ::request-error
+ :status 500
+ :exception e)
+ (throw e)))))))
(defn wrap-idle-session-timeout
- [handler ]
+ [handler]
(fn [request]
(let [session (:session request {})
end-time (coerce/to-date-time (::idle-timeout session))]
@@ -170,9 +170,9 @@
(assoc response :session (assoc session ::idle-timeout (coerce/to-date end-time)))))))))))
(defn wrap-hx-current-url-params
- [handler ]
+ [handler]
(fn [request]
- (let [query-params (some-> (get-in request [:headers "hx-current-url"]) (url/url ) :query)
+ (let [query-params (some-> (get-in request [:headers "hx-current-url"]) (url/url) :query)
request (assoc request :hx-query-params query-params)]
(handler request))))
@@ -183,30 +183,40 @@
identity (or (-> request :identity)
(-> request :session :identity))
ideal-ids (set (cond
- (or (= :all x-clients)
- (nil? x-clients))
- (->> (dc/q '[:find ?c
- :where [?c :client/code]]
- (dc/db conn))
- (map first))
+ (or (= :all x-clients)
+ (nil? x-clients))
+ (->> (dc/q '[:find ?c
+ :where [?c :client/code]]
+ (dc/db conn))
+ (map first))
- (= :mine x-clients)
- (map :db/id (:user/clients identity))
+ (= :mine x-clients)
+ (map :db/id (:user/clients identity))
+
+ (= :group (first x-clients))
+ (->>
+ (dc/q '[:find ?c
+ :in $ ?g
+ :where [?c :client/groups ?g]]
+ (dc/db conn)
+ (str/upper-case (or (second x-clients) "INVALID")))
+ (map first)
+ set)
+
+ (seq x-clients)
+ (->> x-clients
+ (map (fn [c]
+ (if (string? c)
+ (try
+ (Long/parseLong c)
+ (catch Exception e
+ nil))
+ c)))
+ (filter #(not (nil? %)))
+ set)))
- (seq x-clients)
- (->> x-clients
- (map (fn [c]
- (if (string? c)
- (try
- (Long/parseLong c)
- (catch Exception e
- nil))
- c)))
- (filter #(not (nil? %)))
- set)))
-
limited-clients (some->> (limited-clients identity)
- (map :db/id )
+ (map :db/id)
set)
client-ids (if (= "admin" (:user/role identity))
ideal-ids
@@ -215,8 +225,8 @@
clients (some->> client-ids
seq
(pull-many (dc/db conn)
- d-clients/full-read))]
-
+ d-clients/full-read))]
+
(mu/with-context {:clients (take 10 (map :client/code clients))}
(handler (assoc request
:clients clients
@@ -229,8 +239,10 @@
(let [x-clients (edn/read-string (get headers "x-clients"))
x-clients (try (if-let [client-id (and x-clients
(sequential? x-clients)
+ (first x-clients)
+ (not= :group (first x-clients))
(first x-clients))]
- (do
+ (do
(assert-can-see-client identity (cond-> client-id
(string? client-id) (Long/parseLong)))
[(if (string? client-id)
@@ -261,16 +273,16 @@
(auth/gunzip gz-clients))
(catch Exception e
(alog/error :cant-gunzip-clients
- :error e)
+ :error e)
request))
request)]
(handler request))))
#_(defn wrap-pprint-session
- [handler]
- (fn [request]
- (clojure.pprint/pprint (:session request))
- (handler request)))
+ [handler]
+ (fn [request]
+ (clojure.pprint/pprint (:session request))
+ (handler request)))
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defonce app
@@ -289,13 +301,11 @@
#_(wrap-pprint-session)
(wrap-idle-session-timeout)
(wrap-session {:store (cookie-store
- {:key
- (byte-array
- [42, 52, -31, 105, -126, -33, -118, -69, -82, -59, -15, -69, -38, 103, -102, -1])} )})
+ {:key
+ (byte-array
+ [42, 52, -31, 105, -126, -33, -118, -69, -82, -59, -15, -69, -38, 103, -102, -1])})})
#_(wrap-reload)
(wrap-params)
(mp/wrap-multipart-params)
- (wrap-edn-params)
- ))
-
+ (wrap-edn-params)))
diff --git a/src/clj/auto_ap/main.clj b/src/clj/auto_ap/main.clj
new file mode 100644
index 00000000..ca9d506b
--- /dev/null
+++ b/src/clj/auto_ap/main.clj
@@ -0,0 +1,107 @@
+(ns auto-ap.main
+ (:gen-class)
+ (:require
+ [auto-ap.handler :refer [app]]
+ [auto-ap.server :as server]
+ [auto-ap.jobs.restore-from-backup :as job-restore-from-backup]
+ [auto-ap.jobs.bulk-journal-import :as job-bulk-journal-import]
+ [auto-ap.jobs.close-auto-invoices :as job-close-auto-invoices]
+ [auto-ap.jobs.current-balance-cache :as job-current-balance-cache]
+ [auto-ap.jobs.ezcater-upsert :as job-ezcater-upsert]
+ [auto-ap.jobs.import-uploaded-invoices :as job-import-uploaded-invoices]
+ [auto-ap.jobs.intuit :as job-intuit]
+ [auto-ap.jobs.ntg :as job-ntg]
+ #_[auto-ap.backup :as backup]
+ [auto-ap.jobs.ledger-reconcile :as job-reconcile-ledger]
+ [auto-ap.jobs.load-historical-sales :as job-load-historical-sales]
+ [auto-ap.jobs.plaid :as job-plaid]
+ [auto-ap.jobs.register-invoice-import :as job-register-invoice-import]
+ [auto-ap.jobs.square :as job-square]
+ [auto-ap.jobs.sysco :as job-sysco]
+ [auto-ap.jobs.vendor-usages :as job-vendor-usages]
+ [auto-ap.jobs.yodlee2 :as job-yodlee2]
+ [auto-ap.logging :as alog]
+ [com.unbounce.dogstatsd.core :as statsd]
+ [config.core :refer [env]]
+ [mount.core :as mount]
+ [nrepl.server :refer [start-server]]
+ [ring.adapter.jetty :refer [run-jetty]]
+ [yang.scheduler :as scheduler]
+ [auto-ap.jobs.insight-outcome-recommendation :as insight-outcome-recommendation])
+ (:import
+ (org.eclipse.jetty.server.handler StatisticsHandler)
+ (org.eclipse.jetty.server.handler.gzip GzipHandler)))
+
+(defn add-shutdown-hook! [^Runnable f]
+ (.addShutdownHook (Runtime/getRuntime)
+ (Thread. f)))
+
+(defn shutdown-mount []
+ (mount/stop))
+
+(defn -main [& _]
+ (let [job (System/getenv "INTEGREAT_JOB")]
+ (println "JOB is" job)
+ (cond (= job "square-import-job")
+ (job-square/-main)
+
+ (= job "reconcile-ledger")
+ (job-reconcile-ledger/-main)
+
+ (= job "current-balance-cache")
+ (job-current-balance-cache/-main)
+
+ (= job "yodlee2")
+ (job-yodlee2/-main)
+
+ (= job "yodlee2-accounts")
+ (job-yodlee2/accounts-only)
+
+ (= job "plaid")
+ (job-plaid/-main)
+
+ (= job "intuit")
+ (job-intuit/-main)
+
+ (= job "vendor-usages")
+ (job-vendor-usages/-main)
+
+ (= job "import-uploaded-invoices")
+ (job-import-uploaded-invoices/-main)
+
+ (= job "sysco")
+ (job-sysco/-main)
+
+ (= job "close-auto-invoices")
+ (job-close-auto-invoices/-main)
+
+ (= job "ezcater-upsert")
+ (job-ezcater-upsert/-main)
+
+ (= job "register-invoice-import")
+ (job-register-invoice-import/-main)
+
+ (= job "load-historical-sales")
+ (job-load-historical-sales/-main)
+
+ (= job "bulk-journal-import")
+ (job-bulk-journal-import/-main)
+
+ (= job "restore-from-backup")
+ (job-restore-from-backup/-main)
+
+ (= job "insight-outcome-recommendation")
+ (insight-outcome-recommendation/-main)
+
+ ;; (= job "export-backup")
+ ;; (backup/-main)
+
+ (= job "ntg")
+ (job-ntg/-main)
+
+ :else
+ (do
+ (add-shutdown-hook! shutdown-mount)
+ (start-server :port 9000 :bind "0.0.0.0" #_#_:handler (cider-nrepl-handler))
+ (mount/start)
+ #_(alter-var-root #'nrepl.middleware.print/*print-fn* (constantly clojure.pprint/pprint))))))
\ No newline at end of file
diff --git a/src/clj/auto_ap/server.clj b/src/clj/auto_ap/server.clj
index 0f53c817..23608219 100644
--- a/src/clj/auto_ap/server.clj
+++ b/src/clj/auto_ap/server.clj
@@ -1,40 +1,13 @@
(ns auto-ap.server
- (:gen-class)
- (:require
- [auto-ap.handler :refer [app]]
- [auto-ap.jobs.restore-from-backup :as job-restore-from-backup]
- [auto-ap.jobs.bulk-journal-import :as job-bulk-journal-import]
- [auto-ap.jobs.close-auto-invoices :as job-close-auto-invoices]
- [auto-ap.jobs.current-balance-cache :as job-current-balance-cache]
- [auto-ap.jobs.ezcater-upsert :as job-ezcater-upsert]
- [auto-ap.jobs.import-uploaded-invoices :as job-import-uploaded-invoices]
- [auto-ap.jobs.intuit :as job-intuit]
- [auto-ap.jobs.ntg :as job-ntg]
- #_[auto-ap.backup :as backup]
- [auto-ap.jobs.ledger-reconcile :as job-reconcile-ledger]
- [auto-ap.jobs.load-historical-sales :as job-load-historical-sales]
- [auto-ap.jobs.plaid :as job-plaid]
- [auto-ap.jobs.register-invoice-import :as job-register-invoice-import]
- [auto-ap.jobs.square :as job-square]
- [auto-ap.jobs.sysco :as job-sysco]
- [auto-ap.jobs.vendor-usages :as job-vendor-usages]
- [auto-ap.jobs.yodlee2 :as job-yodlee2]
- [auto-ap.logging :as alog]
- [com.unbounce.dogstatsd.core :as statsd]
- [config.core :refer [env]]
- [mount.core :as mount]
- [nrepl.server :refer [start-server]]
- [ring.adapter.jetty :refer [run-jetty]]
- [yang.scheduler :as scheduler]
- [auto-ap.jobs.insight-outcome-recommendation :as insight-outcome-recommendation])
- (:import
- (org.eclipse.jetty.server.handler StatisticsHandler)
- (org.eclipse.jetty.server.handler.gzip GzipHandler)))
-
-(defn add-shutdown-hook! [^Runnable f]
- (.addShutdownHook (Runtime/getRuntime)
- (Thread. f)))
-
+ (:require [auto-ap.handler :refer [app]]
+ [auto-ap.logging :as alog]
+ [com.unbounce.dogstatsd.core :as statsd]
+ [config.core :refer [env]]
+ [mount.core :as mount]
+ [ring.adapter.jetty :refer [run-jetty]]
+ [yang.scheduler :as scheduler])
+ (:import (org.eclipse.jetty.server.handler StatisticsHandler)
+ (org.eclipse.jetty.server.handler.gzip GzipHandler)))
(defn gzip-handler []
(doto (GzipHandler.)
@@ -88,73 +61,3 @@
:start (scheduler/every (* 1000 10) collect-jetty-stats)
:stop (scheduler/stop jetty-stats))
-(defn shutdown-mount []
- (mount/stop))
-
-(defn -main [& _]
- (let [job (System/getenv "INTEGREAT_JOB")]
- (println "JOB is" job)
- (cond (= job "square-import-job")
- (job-square/-main)
-
- (= job "reconcile-ledger")
- (job-reconcile-ledger/-main)
-
- (= job "current-balance-cache")
- (job-current-balance-cache/-main)
-
- (= job "yodlee2")
- (job-yodlee2/-main)
-
- (= job "yodlee2-accounts")
- (job-yodlee2/accounts-only)
-
- (= job "plaid")
- (job-plaid/-main)
-
- (= job "intuit")
- (job-intuit/-main)
-
- (= job "vendor-usages")
- (job-vendor-usages/-main)
-
- (= job "import-uploaded-invoices")
- (job-import-uploaded-invoices/-main)
-
- (= job "sysco")
- (job-sysco/-main)
-
- (= job "close-auto-invoices")
- (job-close-auto-invoices/-main)
-
- (= job "ezcater-upsert")
- (job-ezcater-upsert/-main)
-
- (= job "register-invoice-import")
- (job-register-invoice-import/-main)
-
- (= job "load-historical-sales")
- (job-load-historical-sales/-main)
-
- (= job "bulk-journal-import")
- (job-bulk-journal-import/-main)
-
- (= job "restore-from-backup")
- (job-restore-from-backup/-main)
-
- (= job "insight-outcome-recommendation")
- (insight-outcome-recommendation/-main)
-
- ;; (= job "export-backup")
- ;; (backup/-main)
-
- (= job "ntg")
- (job-ntg/-main)
-
- :else
- (do
- (add-shutdown-hook! shutdown-mount)
- (start-server :port 9000 :bind "0.0.0.0" #_#_:handler (cider-nrepl-handler))
- (mount/start)
- #_(alter-var-root #'nrepl.middleware.print/*print-fn* (constantly clojure.pprint/pprint))))))
-
diff --git a/src/clj/auto_ap/ssr/admin/clients.clj b/src/clj/auto_ap/ssr/admin/clients.clj
index c466330a..e9b9d088 100644
--- a/src/clj/auto_ap/ssr/admin/clients.clj
+++ b/src/clj/auto_ap/ssr/admin/clients.clj
@@ -74,6 +74,7 @@
:client/name
:client/code
:client/locations
+ :client/groups
:client/matches
:client/week-a-credits
:client/week-b-credits
@@ -277,6 +278,9 @@
[:client/locations [:vector {:decode/arbitrary (fn [m] (if (map? m)
(vals m)
m))} :string]]
+ [:client/groups [:vector {:decode/arbitrary (fn [m] (if (map? m)
+ (vals m)
+ m))} :string]]
[:client/emails {:optional true} [:maybe (many-entity {}
[:db/id [:or entity-id temp-id]]
[:email-contact/description :string]
@@ -394,6 +398,19 @@
(com/data-grid-cell {:class "align-top"}
(com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x))))
+(defn group-row [_]
+ (com/data-grid-row
+ {:x-ref "p"
+ :x-data (hx/json {})}
+ (com/data-grid-cell
+ {}
+ (com/validated-field {}
+ (com/text-input {:name (fc/field-name)
+ :value (fc/field-value)
+ :class "w-24"})))
+ (com/data-grid-cell {:class "align-top"}
+ (com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x))))
+
(defn feature-flag-row [_]
(com/data-grid-row
{:x-ref "p"
@@ -1318,7 +1335,7 @@
(edit-path [_ _] [])
(step-schema [_]
- (mut/select-keys (mm/form-schema linear-wizard) #{:client/feature-flags}))
+ (mut/select-keys (mm/form-schema linear-wizard) #{:client/feature-flags :client/groups}))
(render-step [this _]
(mm/default-render-step
@@ -1335,7 +1352,19 @@
(com/data-grid-new-row {:colspan 2
:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-feature-flag)
:index (count (fc/field-value))}
- "New flag")))))
+ "New flag"))))
+ (fc/with-field :client/groups
+ (com/validated-field
+ {:errors (fc/field-errors)
+ :label "Groups"}
+ (com/data-grid {:headers [(com/data-grid-header {} "Group")
+ (com/data-grid-header {:class "w-16"})]}
+ (fc/cursor-map #(group-row %))
+ (com/data-grid-new-row {:colspan 2
+ :hx-get (bidi/path-for ssr-routes/only-routes ::route/new-group)
+ :index (count (fc/field-value))}
+
+ "New group")))))
:footer
(mm/default-step-footer linear-wizard this :validation-route ::route/navigate)
:validation-route ::route/navigate)))
@@ -1387,14 +1416,17 @@
(first step-key)))))
(form-schema [_] form-schema-2)
(submit [_ {:keys [multi-form-state request-method identity] :as request}]
- (let [snapshot (mc/decode
+ (let [
+ snapshot (mc/decode
form-schema-2
(:snapshot multi-form-state)
mt/strip-extra-keys-transformer)
entity (cond-> snapshot
(= :post request-method) (assoc :db/id "new")
(= :put request-method) (dissoc :client/code)
+
(:client/locked-until snapshot) (update :client/locked-until clj-time.coerce/to-date)
+ (seq (:client/groups snapshot)) (update :client/groups #(mapv str/upper-case %))
(seq (:client/bank-accounts snapshot)) (update :client/bank-accounts
(fn [bank-accounts]
(mapv
@@ -1418,6 +1450,7 @@
"name" (conj (or (:client/matches updated-client) [])
(:client/name updated-client))
"code" (:client/code updated-client)
+
"exact" (map str/upper-case (conj (or (:client/matches updated-client) [])
(:client/name updated-client)))}]))
(html-response
@@ -1669,6 +1702,9 @@
::route/new-match (add-new-primitive-handler [:step-params :client/matches]
""
match-row)
+ ::route/new-group (add-new-primitive-handler [:step-params :client/groups]
+ ""
+ group-row)
::route/new-location-match (add-new-entity-handler [:step-params :client/location-matches]
(fn [cursor _] (location-match-row cursor)))
::route/new-email-contact (add-new-entity-handler [:step-params :client/emails]
diff --git a/src/clj/auto_ap/ssr/company_dropdown.clj b/src/clj/auto_ap/ssr/company_dropdown.clj
index 976a59d2..1d527d08 100644
--- a/src/clj/auto_ap/ssr/company_dropdown.clj
+++ b/src/clj/auto_ap/ssr/company_dropdown.clj
@@ -1,76 +1,92 @@
(ns auto-ap.ssr.company-dropdown
- (:require
- [auto-ap.datomic :refer [conn pull-attr pull-many]]
- [auto-ap.graphql.utils :refer [assert-can-see-client cleanse-query]]
- [auto-ap.solr :as solr]
- [auto-ap.ssr-routes :as ssr-routes]
- [auto-ap.ssr.svg :as svg]
- [auto-ap.ssr.utils :refer [html-response]]
- [bidi.bidi :as bidi]
- [datomic.api :as dc]
- [hiccup2.core :as hiccup]
- [iol-ion.query :refer [can-see-client?]]))
+ (:require [auto-ap.datomic :refer [conn pull-attr pull-many]]
+ [auto-ap.graphql.utils :refer [cleanse-query]]
+ [auto-ap.solr :as solr]
+ [auto-ap.ssr-routes :as ssr-routes]
+ [auto-ap.ssr.hx :as hx]
+ [auto-ap.ssr.svg :as svg]
+ [auto-ap.ssr.utils :refer [html-response]]
+ [bidi.bidi :as bidi]
+ [clojure.string :as str]
+ [datomic.api :as dc]
+ [hiccup2.core :as hiccup]
+ [iol-ion.query :refer [can-see-client?]]))
(defn dropdown-search-results* [{:keys [options]}]
- [:ul
- (for [{:keys [id name]} options]
- [:li
+ [:ul
+ (for [{:keys [id name group]} options]
+ [:li
[:div {:class "flex items-center pl-2 rounded hover:bg-green-100 dark:hover:bg-green-600"}
- [:a {:href "#" :class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
- :hx-put (bidi/path-for ssr-routes/only-routes
- :active-client
- :request-method :put)
- :hx-target "#company-dropdown"
- :hx-headers (format "{\"x-clients\": \"[%d]\"}" id)
- :hx-swap "outerHTML"
- :hx-trigger "click"}
- name]]])])
+ (if group
+ [:a {:href "#" :class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
+
+ :hx-put (bidi/path-for ssr-routes/only-routes
+ :active-client
+ :request-method :put)
+ :hx-target "#company-dropdown"
+ :hx-headers (hx/json {"x-clients" (pr-str [:group group])})
+ :hx-swap "outerHTML"
+ :hx-trigger "click"}
+ name]
+ [:a {:href "#" :class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
+ :hx-put (bidi/path-for ssr-routes/only-routes
+ :active-client
+ :request-method :put)
+ :hx-target "#company-dropdown"
+ :hx-headers (format "{\"x-clients\": \"[%d]\"}" id)
+ :hx-swap "outerHTML"
+ :hx-trigger "click"}
+ name])]])])
+
(defn get-clients [identity query]
- (if-let [query (not-empty (cleanse-query query))]
- (let [search-query (str "name:(" query ")")]
+ (if (str/starts-with? query "g:")
+ (let [search-query (str "groups:(" (subs query 2) ")")]
+ [{:group (subs query 2)
+ :name (str "All clients matching " (subs query 2))}])
+ (if-let [query (not-empty (cleanse-query query))]
+ (let [search-query (str "name:(" query ")")]
- (for [n (pull-many (dc/db conn) [:client/name :db/id]
- (for [{:keys [id name]} (solr/query solr/impl "clients" {"query" search-query
- "fields" "id, name"})
- :let [client-id (Long/parseLong id)]
- :when (can-see-client? identity client-id)]
- client-id))]
- {:id (:db/id n)
- :name (:client/name n)}))
- []))
+ (for [n (pull-many (dc/db conn) [:client/name :db/id]
+ (for [{:keys [id name]} (solr/query solr/impl "clients" {"query" search-query
+ "fields" "id, name"})
+ :let [client-id (Long/parseLong id)]
+ :when (can-see-client? identity client-id)]
+ client-id))]
+ {:id (:db/id n)
+ :name (:client/name n)}))
+ [])))
(defn dropdown-search-results [{:keys [identity] :as request}]
- (html-response
- (dropdown-search-results* {:options (get-clients identity (get (:query-params request) "search-text"))})))
+ (html-response
+ (dropdown-search-results* {:options (get-clients identity (get (:query-params request) "search-text"))})))
-(defn dropdown [{:keys [client-selection client identity]}]
+(defn dropdown [{:keys [client-selection client identity clients]}]
[:div#company-dropdown
[:script
(hiccup/raw
- "localStorage.setItem(\"last-client-id\", \"" (:db/id client) "\")" "\n"
- "localStorage.setItem(\"last-selected-clients\", \"" client-selection "\")"
- )]
- [:div
- [:button#company-dropdown-button { :class "text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
+ "localStorage.setItem(\"last-client-id\", \"" (:db/id client) "\")" "\n"
+ "localStorage.setItem(\"last-selected-clients\", " (pr-str (pr-str client-selection)) ")")]
+ [:div
+ [:button#company-dropdown-button {:class "text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
:type "button"}
(cond
(= :mine client-selection)
"My Companies"
(= :all client-selection)
"All Companies"
- (and (sequential? client-selection)
- (= 1 (count client-selection)))
- (pull-attr (dc/db conn) :client/name (first client-selection))
+
+ (and client
+ (= 1 (count clients)))
+ ( :client/name client)
:else
- (str (count client-selection) " Companies"))
+ (str (count clients) " Companies"))
[:div.w-4.h-4.ml-2
svg/drop-down]]
- [:div#company-dropdown-list.hidden {"_" (hiccup/raw "init call initCompanyDropdown()")
- }
+ [:div#company-dropdown-list.hidden {"_" (hiccup/raw "init call initCompanyDropdown()")}
[:div {:class "z-10 bg-white rounded-lg shadow w-64 dark:bg-gray-700 slide-up duration-500 transition-all"}
[:div {:class "p-3"}
[:label {:for "input-group-search", :class "sr-only"} "Search"]
@@ -83,27 +99,27 @@
:class "block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
:autoFocus true
:tab-index -1
- :hx-trigger "keyup changed delay:500ms, search"
+ :hx-trigger "keyup changed delay:500ms, search"
:hx-get (bidi/path-for ssr-routes/only-routes
:company-dropdown-search-results)
:hx-target "#company-search-results"
- :hx-swap "innerHTML"} ]]
+ :hx-swap "innerHTML"}]]
[:input#company-search-value {:type "hidden"
:name "x-clients"}]]
[:div.divide-y.divide-gray-100
[:div#company-search-results {:class "h-48 px-3 pb-3 overflow-y-auto text-sm text-gray-700 dark:text-gray-200"}]
(when (= "admin" (:user/role identity))
- [:div {:class "flex items-center pl-2 rounded hover:bg-green-100 dark:hover:bg-green-600"}
+ [:div {:class "flex items-center pl-2 rounded hover:bg-green-100 dark:hover:bg-green-600"}
- [:button {:class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
- :hx-put (bidi/path-for ssr-routes/only-routes
- :active-client
- :request-method :put)
- :hx-target "#company-dropdown"
- :hx-headers "{\"x-clients\": \":mine\"}"
- :hx-swap "outerHTML"
- :hx-trigger "click"}
- "Mine"]])
+ [:button {:class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
+ :hx-put (bidi/path-for ssr-routes/only-routes
+ :active-client
+ :request-method :put)
+ :hx-target "#company-dropdown"
+ :hx-headers "{\"x-clients\": \":mine\"}"
+ :hx-swap "outerHTML"
+ :hx-trigger "click"}
+ "Mine"]])
[:div {:class "flex items-center pl-2 rounded hover:bg-green-100 dark:hover:bg-green-600"}
[:button {:class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
@@ -114,11 +130,10 @@
:hx-headers "{\"x-clients\": \":all\"}"
:hx-swap "outerHTML"
:hx-trigger "click"}
- "All"]]]
- ]]
+ "All"]]]]]
[:script {:lang "text/javascript"}
(hiccup/raw
- "
+ "
function initCompanyDropdown() {
var $dropdownTargetEl = document.getElementById('company-dropdown-list');
@@ -145,9 +160,11 @@ function initCompanyDropdown() {
(defn active-client [{:keys [identity params] :as request}]
(assoc
- (html-response
- (dropdown {:client-selection (:client-selection (:session request))
- :client (:client request)
- :identity identity}))
- :headers
- {"hx-trigger" "clientSelected"}))
+ (html-response
+ (dropdown {:client-selection (:client-selection (:session request))
+ :clients (:clients request)
+ :client (:client request)
+ :identity identity}))
+ :headers
+ {"hx-trigger" "clientSelected"}))
+
diff --git a/src/clj/auto_ap/ssr/components/aside.clj b/src/clj/auto_ap/ssr/components/aside.clj
index e48b7520..e4ec2bde 100644
--- a/src/clj/auto_ap/ssr/components/aside.clj
+++ b/src/clj/auto_ap/ssr/components/aside.clj
@@ -201,8 +201,7 @@
[:li
(menu-button- {:icon svg/restaurant
- :href (bidi/path-for ssr-routes/only-routes ::ac-routes/page)
- :target "_new"}
+ :href (bidi/path-for ssr-routes/only-routes ::ac-routes/page) }
"Clients")]
[:li
(menu-button- {:icon svg/vendors
diff --git a/src/clj/auto_ap/ssr/components/navbar.clj b/src/clj/auto_ap/ssr/components/navbar.clj
index 3759e741..28672eb5 100644
--- a/src/clj/auto_ap/ssr/components/navbar.clj
+++ b/src/clj/auto_ap/ssr/components/navbar.clj
@@ -8,7 +8,7 @@
[auto-ap.ssr.svg :as svg]
[bidi.bidi :as bidi]))
-(defn navbar- [{:keys [client-selection client identity]}]
+(defn navbar- [{:keys [client-selection client identity clients]}]
[:nav {:class "fixed z-30 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700"}
[:div {:class "px-3 py-3 lg:px-5 lg:pl-3"}
[:div {:class "flex items-center justify-between"}
@@ -38,6 +38,7 @@
:hx-target "#modal-holder"
:hx-swap "outerHTML"}
svg/search)
- (cd/dropdown {:client-selection client-selection :client client :identity identity})
+ (cd/dropdown {:client-selection client-selection :client client :identity identity
+ :clients clients})
(user-dropdown/dropdown {:identity identity})]]]])
diff --git a/src/clj/auto_ap/ssr/components/page.clj b/src/clj/auto_ap/ssr/components/page.clj
index a83fed42..de908372 100644
--- a/src/clj/auto_ap/ssr/components/page.clj
+++ b/src/clj/auto_ap/ssr/components/page.clj
@@ -6,13 +6,14 @@
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.hx :as hx]))
-(defn page- [{:keys [nav page-specific client client-selection identity app-params] :or {app-params {}}} & children]
+(defn page- [{:keys [nav page-specific client clients client-selection identity app-params] :or {app-params {}}} & children]
[:div#app {"_" (hiccup/raw "
on notification from body 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"
)
:x-data (hx/json {:leftNavShow true})}
(navbar- {:client-selection client-selection
+ :clients clients
:client client
:identity identity})
[:div#app-contents.flex.pt-16.overflow-hidden (assoc app-params
diff --git a/src/clj/auto_ap/ssr/transaction/insights.fiddle b/src/clj/auto_ap/ssr/transaction/insights.fiddle
new file mode 100644
index 00000000..e69de29b
diff --git a/src/cljc/auto_ap/routes/admin/clients.cljc b/src/cljc/auto_ap/routes/admin/clients.cljc
index 4d1601cb..0d1aba66 100644
--- a/src/cljc/auto_ap/routes/admin/clients.cljc
+++ b/src/cljc/auto_ap/routes/admin/clients.cljc
@@ -13,6 +13,7 @@
"/match/new" ::new-match
"/location-match/new" ::new-location-match
"/email-contact/new" ::new-email-contact
+ "/group/new" ::new-group
"/feature-flag/new" ::new-feature-flag
"/new" {:get ::new-dialog}
["/" [#"\d+" :db/id] "/sales-powerquery"] ::biweekly-sales-powerquery
diff --git a/src/cljc/auto_ap/ssr_routes.cljc b/src/cljc/auto_ap/ssr_routes.cljc
index 098051cb..d7f11681 100644
--- a/src/cljc/auto_ap/ssr_routes.cljc
+++ b/src/cljc/auto_ap/ssr_routes.cljc
@@ -71,6 +71,7 @@
"/bank-account/typeahead" :bank-account-typeahead
["/" [#"\d+" :db/id] "/bank-account"] {"/search" :bank-account-search}
"/active" {:put :active-client}
+ "/active-group" {:put :active-client-group}
"/1099" :company-1099
"/1099/table" {:get :company-1099-vendor-table}
"/1099/vendor-dialog" {["/" [#"\d+" :vendor-id]] {:get :company-1099-vendor-dialog
diff --git a/src/cljs/auto_ap/effects.cljs b/src/cljs/auto_ap/effects.cljs
index 6b477e18..ea49046e 100644
--- a/src/cljs/auto_ap/effects.cljs
+++ b/src/cljs/auto_ap/effects.cljs
@@ -19,6 +19,7 @@
(defn maybe-add-x-clients [headers]
(if (or (and (sequential? (:selected-clients @re-frame.db/app-db)) (every? int? (:selected-clients @re-frame.db/app-db)))
(and (sequential? (:selected-clients @re-frame.db/app-db)) (every? string? (:selected-clients @re-frame.db/app-db)))
+ (and (sequential? (:selected-clients @re-frame.db/app-db)) (= :group (first (:selected-clients @re-frame.db/app-db))))
(keyword? (:selected-clients @re-frame.db/app-db)))
(assoc headers "x-clients" (pr-str (:selected-clients @re-frame.db/app-db)))
headers))
diff --git a/src/cljs/auto_ap/events.cljs b/src/cljs/auto_ap/events.cljs
index 6420f31e..8a3e08ce 100644
--- a/src/cljs/auto_ap/events.cljs
+++ b/src/cljs/auto_ap/events.cljs
@@ -27,7 +27,7 @@
(defn client-query []
- (cond-> [:id :name :code :email :locations :feature-flags
+ (cond-> [:id :name :code :email :locations :feature-flags :groups
[:emails [:id :email :description]]
[:bank-accounts [:id :code :bank-name :name :type :visible
:locations :include-in-reports :current-balance
diff --git a/src/cljs/auto_ap/subs.cljs b/src/cljs/auto_ap/subs.cljs
index 9d4337e8..e16f2c39 100644
--- a/src/cljs/auto_ap/subs.cljs
+++ b/src/cljs/auto_ap/subs.cljs
@@ -46,10 +46,10 @@
(fn [[selected-clients user clients]]
(println "SELECTED" selected-clients
"USER" user
- "CLIENTS" clients)
+ "CLIENTS" (count clients))
(cond (= :mine selected-clients)
- (sort-by :name
+ (sort-by :name
(:user/clients user))
(or (and (sequential? selected-clients)
@@ -58,6 +58,15 @@
(nil? selected-clients))
clients
+ (= :group (and (sequential? selected-clients)
+ (first selected-clients)))
+ (let [group (second selected-clients)]
+ (filterv
+ (fn [c]
+ (println "GROUP" group (:groups c))
+ ((set (:groups c)) group))
+ clients))
+
(sequential? selected-clients)
(filter (comp (set (map coerce-string-version selected-clients)) coerce-string-version :id)
clients)
diff --git a/src/cljs/auto_ap/views/components/layouts.cljs b/src/cljs/auto_ap/views/components/layouts.cljs
index 82def69d..ca216162 100644
--- a/src/cljs/auto_ap/views/components/layouts.cljs
+++ b/src/cljs/auto_ap/views/components/layouts.cljs
@@ -86,13 +86,13 @@
clients
(if-let [exact-match (first (filter
(fn [client]
- (= (str/lower-case (:code client)) (str/lower-case client-search)))
+ (= (str/lower-case (or (:code client) "INVALID")) (str/lower-case client-search)))
clients))]
[exact-match]
(filter
(fn [client]
(or
- (str/includes? (str/lower-case (:code client)) (str/lower-case client-search))
+ (str/includes? (str/lower-case (or (:code client) "INVALID")) (str/lower-case client-search))
(str/includes? (str/lower-case (:name client)) (str/lower-case client-search))))
clients)))))