Merge branch 'staging'

This commit is contained in:
2024-04-15 18:36:08 -07:00
47 changed files with 663 additions and 415 deletions

View File

@@ -45,7 +45,7 @@
(base-page
request
(com/page {:nav com/admin-aside-nav
:client-selection (:client-selection (:session request))
:client-selection (:client-selection request)
:clients (:clients request)
:client (:client request)
:identity (:identity request)}

View File

@@ -5,6 +5,7 @@
pull-many query2]]
[auto-ap.graphql.utils :refer [extract-client-ids]]
[auto-ap.logging :as alog]
[auto-ap.query-params :refer [wrap-copy-qp-pqp]]
[auto-ap.routes.admin.clients :as route]
[auto-ap.routes.indicators :as indicators]
[auto-ap.routes.queries :as q]
@@ -27,7 +28,7 @@
:refer [apply-middleware-to-all-handlers entity-id
form-validation-error html-response main-transformer
many-entity modal-response ref->enum-schema strip temp-id
wrap-entity wrap-schema-enforce]]
wrap-entity wrap-schema-enforce wrap-merge-prior-hx]]
[auto-ap.time :as atime]
[bidi.bidi :as bidi]
[cheshire.core :as cheshire]
@@ -77,7 +78,7 @@
(com/text-input {:name "name"
:id "name"
:class "hot-filter"
:value (:name (:parsed-query-params request))
:value (:name (:query-params request))
:placeholder "Best Restaurant LLC"
:size :small}))
@@ -85,14 +86,14 @@
(com/text-input {:name "code"
:id "code"
:class "hot-filter"
:value (:code (:parsed-query-params request))
:value (:code (:query-params request))
:placeholder "BRLC"
:size :small}))
(com/field {:label "Group"}
(com/text-input {:name "group"
:id "group"
:class "hot-filter"
:value (:group (:parsed-query-params request))
:value (:group (:query-params request))
:placeholder "NTG"
:size :small}))
(com/field {:label "Select"}
@@ -150,7 +151,7 @@
:client/location-matches [:location-match/matches :location-match/location :db/id]}])
(defn fetch-ids [db request]
(let [query-params (:parsed-query-params request)
(let [query-params (:query-params request)
valid-clients (extract-client-ids #_(:clients request)
(map first (dc/q '[:find ?c :where [?c :client/code]] (dc/db conn)))
(:client-id query-params)
@@ -1842,8 +1843,8 @@
(def key->handler
(apply-middleware-to-all-handlers
{::route/page (helper/page-route grid-page)
::route/table (helper/table-route grid-page)
{::route/page (helper/page-route grid-page :parse-query-params? false)
::route/table (helper/table-route grid-page :parse-query-params? false)
::route/new-location (add-new-primitive-handler [:step-params :client/locations]
""
location-row)
@@ -1904,7 +1905,10 @@
(mm/wrap-wizard client-wizard))}
(fn [h]
(-> h
(wrap-copy-qp-pqp)
(wrap-apply-sort grid-page)
(wrap-merge-prior-hx)
(wrap-schema-enforce :query-schema query-schema)
(wrap-schema-enforce :hx-schema query-schema)
(wrap-admin)
(wrap-client-redirect-unauthenticated)))))

View File

@@ -243,7 +243,7 @@
(base-page
request
(com/page {:nav com/admin-aside-nav
:client-selection (:client-selection (:session request))
:client-selection (:client-selection request)
:clients (:clients request)
:client (:client request)
:identity (:identity request)

View File

@@ -166,8 +166,9 @@
(some-> route-params (get :entity-id) Long/parseLong))]
(base-page request
(com/page {:nav com/admin-aside-nav
:client-selection (:client-selection (:session request))
:client-selection (:client-selection request)
:client (:client request)
:clients (:clients request)
:identity (:identity request)
:request request
:app-params {:hx-get (bidi/path-for ssr-routes/only-routes

View File

@@ -1,5 +1,6 @@
(ns auto-ap.ssr.auth
(:require [buddy.sign.jwt :as jwt]
(:require [auto-ap.session-version :as session-version]
[buddy.sign.jwt :as jwt]
[config.core :refer [env]]))
(defn logout [request]
@@ -13,4 +14,5 @@
:session {:identity (dissoc (jwt/unsign (get-in request [:query-params "jwt"])
(:jwt-secret env)
{:alg :hs512})
:exp)}})
:exp)
:version session-version/current-session-version}})

View File

@@ -131,8 +131,9 @@
(base-page
request
(com/page {:nav com/company-aside-nav
:client-selection (:client-selection (:session request))
:client-selection (:client-selection request)
:client (:client request)
:clients (:clients request)
:identity (:identity request)
:app-params {:hx-get (bidi/path-for ssr-routes/only-routes
:company)

View File

@@ -1,6 +1,7 @@
(ns auto-ap.ssr.company-dropdown
(:require [auto-ap.datomic :refer [conn pull-attr pull-many]]
(:require [auto-ap.datomic :refer [conn pull-many]]
[auto-ap.graphql.utils :refer [cleanse-query]]
[auto-ap.logging :as alog]
[auto-ap.solr :as solr]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.hx :as hx]
@@ -10,7 +11,9 @@
[clojure.string :as str]
[datomic.api :as dc]
[hiccup2.core :as hiccup]
[iol-ion.query :refer [can-see-client?]]))
[iol-ion.query :refer [can-see-client?]]
[clojure.data.json :as json]))
(defn dropdown-search-results* [{:keys [options]}]
[:ul
@@ -25,6 +28,7 @@
:request-method :put)
:hx-target "#company-dropdown"
:hx-headers (hx/json {"x-clients" (pr-str [:group group])})
"@click" (format "globalClientSelection={group: %s}" (hx/json group))
:hx-swap "outerHTML"
:hx-trigger "click"}
name]
@@ -34,6 +38,7 @@
:request-method :put)
:hx-target "#company-dropdown"
:hx-headers (format "{\"x-clients\": \"[%d]\"}" id)
"@click" (format "globalClientSelection={selected: [%d]}" id)
:hx-swap "outerHTML"
:hx-trigger "click"}
name])]])])
@@ -64,11 +69,18 @@
(dropdown-search-results* {:options (get-clients identity (get (:query-params request) "search-text"))})))
(defn dropdown [{:keys [client-selection client identity clients]}]
(alog/peek ::clients clients)
[:div#company-dropdown
[:script
(hiccup/raw
"localStorage.setItem(\"last-client-id\", \"" (:db/id client) "\")" "\n"
"localStorage.setItem(\"last-selected-clients\", " (pr-str (pr-str client-selection)) ")")]
"localStorage.setItem(\"last-selected-clients\", " (json/write-str (json/write-str client-selection))
#_(cond (:group client-selection)
(:group client-selection)
(:selected client-selection)
(:selected client-selection)
:else
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"}
@@ -80,7 +92,7 @@
(and client
(= 1 (count clients)))
( :client/name client)
(:client/name client)
:else
(str (count clients) " Companies"))
@@ -116,6 +128,8 @@
:active-client
:request-method :put)
:hx-target "#company-dropdown"
"@click" "globalClientSelection=\"mine\""
:hx-headers "{\"x-clients\": \":mine\"}"
:hx-swap "outerHTML"
:hx-trigger "click"}
@@ -127,6 +141,7 @@
:active-client
:request-method :put)
:hx-target "#company-dropdown"
"@click" "globalClientSelection=\"all\""
:hx-headers "{\"x-clients\": \":all\"}"
:hx-swap "outerHTML"
:hx-trigger "click"}
@@ -161,7 +176,7 @@ function initCompanyDropdown() {
(defn active-client [{:keys [identity params] :as request}]
(assoc
(html-response
(dropdown {:client-selection (:client-selection (:session request))
(dropdown {:client-selection (:client-selection request)
:clients (:clients request)
:client (:client request)
:identity identity}))

View File

@@ -111,26 +111,26 @@
:active? (= "invoices" selected)}
(menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes
::invoice-route/all-page)
{:date-range "month"})
{:date-range "year"})
:active? (= ::invoice-route/all-page (:matched-route request))
:hx-boost "true"}
"All")
(menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes
::invoice-route/paid-page)
{:date-range "month"})
{:date-range "year"})
:active? (= ::invoice-route/paid-page (:matched-route request))
:hx-boost "true"}
"Paid")
(menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes
::invoice-route/unpaid-page)
{:date-range "month"})
{:date-range "year"})
:active? (= ::invoice-route/unpaid-page (:matched-route request))
:hx-boost "true"}
"Unpaid")
(menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes
::invoice-route/voided-page)
{:date-range "month"})
{:date-range "year"})
:active? (= ::invoice-route/voided-page (:matched-route request))
:hx-boost "true"}
"Voided")

View File

@@ -1,26 +1,28 @@
(ns auto-ap.ssr.components.date-range
(:require [auto-ap.ssr.components :as com]
[auto-ap.time :as atime]))
[auto-ap.time :as atime]
[clj-time.coerce :as c]
[clj-time.core :as t]
[clj-time.periodic :as per]))
(defn date-range-field [{:keys [value id] }]
(defn date-range-field [{:keys [value id]}]
[:div {:id id}
(com/field {:label "Date Range"}
[:div.space-y-4
[: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"))
]
(com/button-group-button {:size :small :value "year" :hx-trigger "click"} "Year"))]
[:div.flex.space-x-1.items-baseline.w-full.justify-start
(com/date-input {:name "start-date"
:value (some-> (:start value)
(atime/unparse-local atime/normal-date))
(atime/unparse-local atime/normal-date))
:placeholder "Date"
:size :small
:class "shrink"})
(com/date-input {:name "end-date"
:value (some-> (:end value)
(atime/unparse-local atime/normal-date))

View File

@@ -14,12 +14,12 @@
(com/a-icon-button {:x-ref "link" "@click.prevent" "show=!show; $nextTick(() => popper.update());" :class "relative"}
svg/paperclip
(com/badge {} (count links)))
(com/badge {:color "blue"} (count links)))
[:div.divide-y.divide-gray-200.bg-white.rounded-lg.shadow.z-50 (hx/alpine-appear {:x-ref "tooltip" :x-show "show" :data-key "show"})
[:div {:class "p-3 overflow-y-auto text-sm text-gray-700 dark:text-gray-200"}
[:div.flex.flex-col.gap-y-2
(for [l links]
[:div.flex-initial
[:a {:href (:link l)}
[:a {:href (:link l) :target "_blank"}
(com/pill {:color (or (:color l) :primary) :class "truncate block shrink grow-0"}
(:content l))]])]]]]))

View File

@@ -114,7 +114,7 @@
:class "dark:text-blue-500"}
"Back"])
(defn default-next-button [linear-wizard step validation-route]
(defn default-next-button [linear-wizard step validation-route & {:keys [next-button-content]}]
(let [steps (steps linear-wizard)
last? (= (step-key step) (last steps))
next-step (when-not last? (->> steps
@@ -131,9 +131,10 @@
{:from (encode-step-key (step-key step))
:to (encode-step-key (step-key next-step))})))
(if next-step
(step-name next-step)
"Save")
(or next-button-content
(if next-step
(step-name next-step)
"Save"))
(when-not last?
[:div.w-5.h-5 svg/arrow-right]))))
@@ -143,7 +144,8 @@
(defn default-step-footer [linear-wizard step & {:keys [validation-route
discard-button
next-button]}]
next-button
next-button-content]}]
[:div.flex.justify-end
[:div.flex.items-baseline.gap-x-4
(com/form-errors {:errors (:errors (:step-params fc/*form-errors*))})
@@ -157,7 +159,8 @@
next-button
validation-route
(default-next-button linear-wizard step validation-route)
(default-next-button linear-wizard step validation-route
:next-button-content next-button-content)
:else
[:div "No action possible."])]])

View File

@@ -21,4 +21,9 @@
children))
(defn badge- [params & children]
[:div {:class (hh/add-class "absolute inline-flex items-center justify-center w-6 h-6 text-xs font-black text-white bg-red-300 border-3 border-white rounded-full -top-2 -right-2 dark:border-gray-900" (:class params))} children])
[:div {:class (-> (hh/add-class "absolute inline-flex items-center justify-center w-6 h-6 text-xs font-black text-white
border-3 border-white rounded-full -top-2 -right-2 dark:border-gray-900"
(:class params)
)
(hh/add-class (or (some-> (:color params) (#(str "bg-" % "-300")))
"bg-red-300")))} children])

View File

@@ -27,7 +27,8 @@
[:li
[:a {:href (bidi/path-for ssr-routes/only-routes :company), :class "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white", :role "menuitem"} "My Company"]]
(when (= "admin" (:user/role identity))
[:a {:href (bidi/path-for ssr-routes/only-routes :auto-ap.routes.admin/page), :class "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white", :role "menuitem"} "Admin"])
[:a {:href (bidi/path-for ssr-routes/only-routes :auto-ap.routes.admin/page),
:class "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white", :role "menuitem"} "Admin"])
[:li
[:a {:href "#", :class "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white", :role "menuitem"
"_" (hiccup/raw "on click toggle .dark on <body />")}

View File

@@ -21,6 +21,8 @@
[malli.transform :as mt]
[taoensso.encore :refer [filter-vals]]))
(defn row* [gridspec user entity {:keys [flash? delete-after-settle? request class] :as options}]
(let [cells (if (:check-boxes? gridspec)
[(com/data-grid-cell {} (com/checkbox {:name "id" :value ((:id-fn gridspec) entity)
@@ -75,10 +77,15 @@
"default sort"))
(defn table* [grid-spec user {{:keys [start per-page flash-id sort]} :parsed-query-params :as request}]
(alog/info ::TABLE-QP
:qp (:query-params request)
:pqp (:parsed-query-params request)
:sort sort)
(let [start (or start 0)
per-page (or per-page 25)
[entities total] ((:fetch-page grid-spec)
request)]
[entities total :as page-results] ((:fetch-page grid-spec)
request)
request (assoc request :page-results page-results)]
(com/data-grid-card {:id (:id grid-spec)
:title (if (string? (:title grid-spec))
@@ -206,8 +213,9 @@
set)]
(handler (assoc request :trimmed-clients valid-clients)))))
(defn table-route [grid-spec]
(-> (fn table [{:keys [identity] :as request}]
(defn table-route [grid-spec & {:keys [parse-query-params?] :or {parse-query-params? true}}]
(cond-> (fn table [{:keys [identity] :as request}]
(let [unparse-query-params (or (:unparse-query grid-spec)
default-unparse-query-params)]
(html-response (table*
@@ -224,49 +232,53 @@
main-transformer))
"sort" sort->query)))
(update (filter-vals #(not (nil? %))
(m/encode (:query-schema grid-spec)
(:query-params request)
main-transformer))
"sort" sort->query))
(m/encode (:query-schema grid-spec)
(:query-params request)
main-transformer))
"sort" sort->query))
(unparse-query-params (:parsed-query-params request)))
"selected" "all-selected")))} ;; TODO seems hacky to special case selected and all-selected here
:oob (when-let [oob-render (:oob-render grid-spec)]
(oob-render request)))))
(wrap-trim-client-ids)
(query-params/wrap-parse-query-params (or (:parse-query-params grid-spec)
true (wrap-trim-client-ids)
parse-query-params? (query-params/wrap-parse-query-params (or (:parse-query-params grid-spec)
(default-parse-query-params grid-spec)))
(wrap-secure)
(wrap-client-redirect-unauthenticated)))
true (wrap-secure)
true (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)
:request request}
(apply com/breadcrumbs {} (:breadcrumbs grid-spec))
[:div {:x-data (hx/json {:selected [] :all_selected false})
"x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})"
:x-init "$watch('selected', s=> $dispatch('selectedChanged', {selected: s, all_selected: all_selected}) );
(defn page-route [grid-spec & {:keys [parse-query-params?] :or {parse-query-params? true}}]
(cond-> (fn page [{:keys [identity] :as request}]
(alog/info ::page-route
:pqp (:parsed-query-params request)
:qp (:query-params 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 request)
:clients (:clients request)
:client (:client request)
:identity (:identity request)
:request request}
(apply com/breadcrumbs {} (:breadcrumbs grid-spec))
[:div {:x-data (hx/json {:selected [] :all_selected false})
"x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})"
:x-init "$watch('selected', s=> $dispatch('selectedChanged', {selected: s, all_selected: all_selected}) );
$watch('all_selected', a=>$dispatch('selectedChanged', {selected: selected, all_selected: a}))"}
(table* grid-spec
identity
request)])
(if (string? (:title grid-spec))
(:title grid-spec)
((:title grid-spec) 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)))
(table* grid-spec
identity
request)])
(if (string? (:title grid-spec))
(:title grid-spec)
((:title grid-spec) request))))
true (wrap-trim-client-ids)
parse-query-params? (query-params/wrap-parse-query-params (or (:parse-query-params grid-spec)
(default-parse-query-params grid-spec)))
true (wrap-secure)
true (wrap-client-redirect-unauthenticated)))
(def request-spec (m/schema [:map]))
(def entity-spec (m/schema [:map]))
@@ -280,8 +292,8 @@
(def grid-spec (m/schema [:map
[:id :string]
[:nav [:=>
[:cat request-spec]
vector?]]
[:cat request-spec]
vector?]]
[:page-specific-nav
{:optional true
:default (fn [request])}
@@ -338,8 +350,8 @@
(handler (update request :query-params
(fn [qp]
((comp
(query-params/apply-remove-sort)
(query-params/apply-toggle-sort grid-spec)
(query-params/parse-key :sort #(query-params/parse-sort grid-spec %)))
(query-params/apply-remove-sort)
(query-params/apply-toggle-sort grid-spec)
(query-params/parse-key :sort #(query-params/parse-sort grid-spec %)))
qp))))))

View File

@@ -421,8 +421,9 @@ invoice_dropzone = new Dropzone(\"#invoice\", {
(base-page
request
(com/page {:nav com/admin-aside-nav
:client-selection (:client-selection (:session request))
:client-selection (:client-selection request)
:client (:client request)
:clients (:clients request)
:identity (:identity request)
:app-params {:hx-get (bidi/path-for ssr-routes/only-routes
:invoice-glimpse)

View File

@@ -10,6 +10,7 @@
[auto-ap.routes.utils
:refer [wrap-client-redirect-unauthenticated]]
[auto-ap.rule-matching :as rm]
[auto-ap.client-routes :as client-routes]
[auto-ap.solr :as solr]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.common-handlers :refer [add-new-entity-handler]]
@@ -229,6 +230,14 @@
:value (fc/field-value)
:content-fn (fn [c] (pull-attr (dc/db conn) :vendor/name c))
:x-model "vendorId"})]))
[:div.mb-4
[:span.text-sm.text-gray-500 "Can't find the vendor? "
(com/link {:href (bidi.bidi/path-for
client-routes/routes
:new-vendor)
:target "new"}
"Add new vendor")
" in a new window, then return here."]]
[:div.flex.items-center.gap-2
@@ -421,17 +430,36 @@
(com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x))))
(defn invoice-expense-account-total* [request]
(format "$%,.2f" (->> (-> request
:multi-form-state
:step-params
:invoice/expense-accounts)
(map (fnil :invoice-expense-account/amount 0.0))
(filter number?)
(reduce + 0.0))))
(let [total (->> (-> request
:multi-form-state
:step-params
:invoice/expense-accounts)
(map (fnil :invoice-expense-account/amount 0.0))
(filter number?)
(reduce + 0.0))]
(format "$%,.2f" total)))
(defn invoice-expense-account-balance* [request]
(let [total (->> (-> request
:multi-form-state
:step-params
:invoice/expense-accounts)
(map (fnil :invoice-expense-account/amount 0.0))
(filter number?)
(reduce + 0.0))
balance (-
(-> request :multi-form-state :snapshot :invoice/total)
total)]
[:span {:class (when-not (dollars= 0.0 balance)
"text-red-300")}
(format "$%,.2f" balance)]))
(defn invoice-expense-account-total [request]
(html-response (invoice-expense-account-total* request)))
(defn invoice-expense-account-balance [request]
(html-response (invoice-expense-account-balance* request)))
(defrecord AccountsStep [linear-wizard]
mm/ModalWizardStep
(step-name [_]
@@ -481,6 +509,19 @@
:hx-swap "innerHTML"}
(invoice-expense-account-total* request))
(com/data-grid-cell {}))
(com/data-grid-row {}
(com/data-grid-cell {})
(com/data-grid-cell {:class "text-right"} [:span.font-bold.text-right "BALANCE"])
(com/data-grid-cell {:id "total"
:class "text-right"
:hx-trigger "change from:closest form target:.amount-field"
:hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/expense-account-balance)
:hx-target "this"
:hx-swap "innerHTML"}
(invoice-expense-account-balance* request))
(com/data-grid-cell {}))
(com/data-grid-row {}
(com/data-grid-cell {})
@@ -592,12 +633,12 @@
(when (seq eas)
(let [leftover (- invoice-total (reduce + 0 (map :invoice-expense-account/amount eas)))
leftover-beyond-a-single-cent? (or (< leftover -1)
(> leftover 1))
(> leftover 1))
leftover (if leftover-beyond-a-single-cent?
0
leftover)
[first-eas & rest] eas]
(cons
(cons
(update first-eas :invoice-expense-account/amount #(+ % leftover))
rest))))
@@ -813,6 +854,9 @@
::route/expense-account-total (-> invoice-expense-account-total
(mm/wrap-wizard new-wizard)
(mm/wrap-decode-multi-form-state))
::route/expense-account-balance (-> invoice-expense-account-balance
(mm/wrap-wizard new-wizard)
(mm/wrap-decode-multi-form-state))
::route/location-select (-> location-select
(wrap-schema-enforce :query-schema [:map
[:name :string]

View File

@@ -7,6 +7,7 @@
[auto-ap.datomic.accounts :as d-accounts]
[auto-ap.datomic.bank-accounts :as d-bank-accounts]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.query-params :refer [wrap-copy-qp-pqp]]
[auto-ap.graphql.checks :as gq-checks :refer [base-payment
invoice-payments
print-checks-internal
@@ -120,8 +121,6 @@
(exact-match-id* request)]])
(defn fetch-ids [db {:keys [query-params route-params] :as request}]
(let [valid-clients (extract-client-ids (:clients request)
(:client-id request)
@@ -238,6 +237,7 @@
query-params)
true
(merge-query {:query {:find ['?sort-default '?e]}})))]
(->> (observable-query query)
(apply-sort-3 (assoc query-params :default-asc? false))
(apply-pagination query-params))))
@@ -251,12 +251,42 @@
(map first))]
refunds))
(defn sum-outstanding [ids]
(->>
(dc/q {:find ['?id '?o]
:in ['$ '[?id ...]]
:where ['[?id :invoice/outstanding-balance ?o]]}
(dc/db conn)
ids)
(map last)
(reduce
+
0.0)))
(defn sum-total-amount [ids]
(->>
(dc/q {:find ['?id '?o]
:in ['$ '[?id ...]]
:where ['[?id :invoice/total ?o]]
}
(dc/db conn)
ids)
(map last)
(reduce
+
0.0)))
(defn fetch-page [request]
(let [db (dc/db conn)
{ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]
{ids-to-retrieve :ids matching-count :count
all-ids :all-ids} (fetch-ids db request)]
[(->> (hydrate-results ids-to-retrieve db request))
matching-count]))
matching-count
(sum-outstanding all-ids)
(sum-total-amount all-ids)]))
(def query-schema (mc/schema
[:maybe [:map {:date-range [:date-range :start-date :end-date]}
@@ -378,7 +408,6 @@
(:query-params request))})))
;; TODO test as a real user
(def grid-page
(helper/build {:id "entity-table"
@@ -392,20 +421,27 @@
(assoc-in (exact-match-id* request) [1 :hx-swap-oob] true)])
:query-schema query-schema
:parse-query-params (fn [p]
(mc/decode query-schema p main-transformer))
(alog/peek ::PARSE
(mc/decode query-schema p main-transformer)))
:action-buttons (fn [request]
[(when (can? (:identity request) {:subject :invoice :activity :bulk-delete})
(com/button {:hx-get (str (bidi/path-for ssr-routes/only-routes ::route/bulk-delete))
"x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})"
"hx-include" "#invoice-filters"
:color :red}
"Void selected"))
(when (can? (:identity request) {:subject :invoice :activity :pay})
(pay-button* {:ids (selected->ids request
(:query-params request))}))
(when (can? (:identity request) {:subject :invoice :activity :create})
(com/button {:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-wizard)}
"New invoice"))])
(let [[_ _ outstanding total] (:page-results request)]
[(com/pill {:color :primary} "Outstanding: "
(format "$%,.2f" outstanding))
(com/pill {:color :secondary} "Total: "
(format "$%,.2f" total))
(when (can? (:identity request) {:subject :invoice :activity :bulk-delete})
(com/button {:hx-get (str (bidi/path-for ssr-routes/only-routes ::route/bulk-delete))
"x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})"
"hx-include" "#invoice-filters"
:color :red}
"Void selected"))
(when (can? (:identity request) {:subject :invoice :activity :pay})
(pay-button* {:ids (selected->ids request
(:query-params request))}))
(when (can? (:identity request) {:subject :invoice :activity :create})
(com/button {:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-wizard)}
"New invoice"))]))
:row-buttons (fn [request entity]
[(when (and (= :invoice-status/unpaid (:invoice/status entity))
(can? (:identity request) {:subject :invoice :activity :delete}))
@@ -556,10 +592,10 @@
_ (audit-transact tx identity)]
(alog/info ::unvoiding-invoice :transaction :tx)
(html-response
(row* identity (dc/pull (dc/db conn) default-read id) {:flash? true
:request request})
:headers (cond-> {"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" id)
"hx-reswap" "outerHTML"}))))
(row* identity (dc/pull (dc/db conn) default-read id) {:flash? true
:request request})
:headers (cond-> {"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" id)
"hx-reswap" "outerHTML"}))))
(defn delete [{invoice :entity :as request identity :identity}]
(exception->notification
@@ -716,7 +752,6 @@
(map :invoice-id invoices))
(into {}))]
(every? (fn [%]
(println "TEST" (:amount %) (outstanding-balances (:invoice-id %)))
(not (does-amount-exceed-outstanding? (:amount %) (outstanding-balances (:invoice-id %)))))
invoices)))]]]
[:has-warning? :boolean]
@@ -968,7 +1003,8 @@
:name (fc/field-name)
:error? (fc/error?)}))))))))))]])
:footer
(mm/default-step-footer linear-wizard this :validation-route ::route/pay-wizard-navigate)
(mm/default-step-footer linear-wizard this :validation-route ::route/pay-wizard-navigate
:next-button-content "Pay")
:validation-route ::route/pay-wizard-navigate)))
(defn add-handwritten-check [request wizard snapshot]
@@ -1137,7 +1173,7 @@
(->> (dc/q '[:find ?i
:in $ [?i ...]
:where [?i :invoice/status :invoice-status/unpaid]
[?i :invoice/client ?c] ]
[?i :invoice/client ?c]]
(dc/db conn)
ids)
(map first)))
@@ -1177,13 +1213,13 @@
(def key->handler
(apply-middleware-to-all-handlers
(->
{::route/all-page (-> (helper/page-route grid-page)
{::route/all-page (-> (helper/page-route grid-page :parse-query-params? false)
(wrap-implied-route-param :status nil))
::route/paid-page (-> (helper/page-route grid-page)
::route/paid-page (-> (helper/page-route grid-page :parse-query-params? false)
(wrap-implied-route-param :status :invoice-status/paid))
::route/unpaid-page (-> (helper/page-route grid-page)
::route/unpaid-page (-> (helper/page-route grid-page :parse-query-params? false)
(wrap-implied-route-param :status :invoice-status/unpaid))
::route/voided-page (-> (helper/page-route grid-page)
::route/voided-page (-> (helper/page-route grid-page :parse-query-params? false)
(wrap-implied-route-param :status :invoice-status/voided))
::route/unvoid (-> unvoid-invoice
(wrap-entity [:route-params :db/id] default-read)
@@ -1212,12 +1248,14 @@
(mm/wrap-wizard pay-wizard)
(mm/wrap-decode-multi-form-state))
::route/table (helper/table-route grid-page)}
::route/table (helper/table-route grid-page :parse-query-params? false)}
(merge new-invoice-wizard/key->handler))
(fn [h]
(-> h
(wrap-copy-qp-pqp)
(wrap-status-from-source)
(wrap-apply-sort grid-page)
(wrap-schema-enforce :query-schema query-schema)
(wrap-merge-prior-hx)
(wrap-schema-enforce :query-schema query-schema)
(wrap-schema-enforce :hx-schema query-schema)
(wrap-client-redirect-unauthenticated)))))

View File

@@ -251,7 +251,7 @@
(base-page
request
(com/page {:nav com/main-aside-nav
:client-selection (:client-selection (:session request))
:client-selection (:client-selection request)
:clients (:clients request)
:client (:client request)
:identity (:identity request)

View File

@@ -7,6 +7,7 @@
[auto-ap.graphql.utils :refer [assert-can-see-client
exception->notification
extract-client-ids notify-if-locked]]
[auto-ap.query-params :refer [wrap-copy-qp-pqp]]
[auto-ap.logging :as alog]
[auto-ap.permissions :refer [can?]]
[auto-ap.routes.invoice :as invoice-route]
@@ -38,8 +39,8 @@
[malli.transform :as mt]))
(defn exact-match-id* [request]
(if (nat-int? (:exact-match-id (:parsed-query-params request)))
[:div {:x-data (hx/json {:exact_match (:exact-match-id (:parsed-query-params request))}) :id "exact-match-id-tag"}
(if (nat-int? (:exact-match-id (:query-params request)))
[:div {:x-data (hx/json {:exact_match (:exact-match-id (:query-params request))}) :id "exact-match-id-tag"}
(com/hidden {:name "exact-match-id"
"x-model" "exact_match"})
(com/pill {:color :primary}
@@ -68,7 +69,7 @@
:value (:vendor (:query-params request))
:value-fn :db/id
:content-fn :vendor/name}))
(date-range-field* request)
(date-range-field* (assoc request :parsed-query-params (:query-params request)))
(com/field {:label "Check #"}
(com/text-input {:name "check-number"
:id "check-number"
@@ -130,7 +131,7 @@
{:transaction/_payment [:db/id :transaction/date]}])
(defn fetch-ids [db {:keys [query-params route-params] :as request}]
(let [ valid-clients (extract-client-ids (:clients request)
(let [valid-clients (extract-client-ids (:clients request)
(:client request)
(:client-id query-params)
(when (:client-code query-params)
@@ -419,7 +420,8 @@
(audit-transact (conj removing-payments updated-payment)
identity)
(html-response (row* (:identity request) updated-payment {:delete-after-settle? true :class "live-removed"})
(html-response (row* (:identity request) updated-payment {:delete-after-settle? true :class "live-removed"
:request request})
:headers {"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id check))})))
;; TODO use decoding here
@@ -529,13 +531,13 @@
(def key->handler
(apply-middleware-to-all-handlers
{::route/cleared-page (-> (helper/page-route grid-page)
{::route/cleared-page (-> (helper/page-route grid-page :parse-query-params? false)
(wrap-implied-route-param :status :payment-status/cleared))
::route/pending-page (-> (helper/page-route grid-page)
::route/pending-page (-> (helper/page-route grid-page :parse-query-params? false)
(wrap-implied-route-param :status :payment-status/pending))
::route/voided-page (-> (helper/page-route grid-page)
::route/voided-page (-> (helper/page-route grid-page :parse-query-params? false)
(wrap-implied-route-param :status :payment-status/voided))
::route/all-page (-> (helper/page-route grid-page)
::route/all-page (-> (helper/page-route grid-page :parse-query-params? false)
(wrap-implied-route-param :status nil))
::route/delete (-> delete
@@ -548,9 +550,10 @@
(wrap-admin))
::route/table (helper/table-route grid-page)}
::route/table (helper/table-route grid-page :parse-query-params? false)}
(fn [h]
(-> h
(wrap-copy-qp-pqp)
(wrap-apply-sort grid-page)
(wrap-merge-prior-hx)
(wrap-status-from-source)

View File

@@ -318,8 +318,9 @@
(base-page
request
(com/page {:nav com/main-aside-nav
:client-selection (:client-selection (:session request))
:client-selection (:client-selection request)
:client (:client request)
:clients (:clients request)
:identity (:identity request)
:app-params {:hx-get (bidi/path-for ssr-routes/only-routes
:transaction-insights)

View File

@@ -69,9 +69,10 @@ input::-webkit-inner-spin-button {
input[type=number] {
-moz-appearance:textfield; /* Firefox */
} "]
[:body {:hx-ext "disable-submit, class-tools"}
[:body {:hx-ext "disable-submit, class-tools"
:x-data (hx/json {:globalClientSelection (or (:client-selection request)
:all )}) ;; TODO remove once session is used
:x-hx-header:x-clients "JSON.stringify(globalClientSelection)"}
contents
[:script {:src "/js/flowbite.min.js"}]

View File

@@ -48,7 +48,7 @@
"hx-target" "#user-table"
"hx-indicator" "#user-table"}
[:fieldset.space-y-6
[:fieldset.space-y-6
(com/field {:label "Name"}
(com/text-input {:name "name"
:id "name"
@@ -57,6 +57,16 @@
:placeholder "Johnny Testerson"
:size :small}))
(com/field {:label "Client"}
(com/typeahead {:name "client"
:placeholder "Search..."
:url (bidi/path-for ssr-routes/only-routes
:company-search)
:id (str "client-search")
:value (:client (:parsed-query-params request))
:value-fn :db/id
:content-fn :client/name}))
(com/field {:label "Email"}
(com/text-input {:name "email"
:id "email"
@@ -66,31 +76,22 @@
:size :small}))
(com/field {:label "Role"}
(com/radio-card {:size :small
:name "role"
:options [{:value ""
:content "All"}
{:value "admin"
:content "Admin"}
{:value "power-user"
:content "Power user"}
{:value "manager"
:content "Manager"}
{:value "user"
:content "User"}
{:value "read-only"
:content "Read Only"}
{:value "none"
:content "None"}]}))
(com/field {:label "Client"}
(com/typeahead {:name "client"
:placeholder "Search..."
:url (bidi/path-for ssr-routes/only-routes
:company-search)
:id (str "client-search")
:value (:client (:parsed-query-params request))
:value-fn :db/id
:content-fn :client/name}))]])
(com/radio-card {:size :small
:name "role"
:options [{:value ""
:content "All"}
{:value "admin"
:content "Admin"}
{:value "power-user"
:content "Power user"}
{:value "manager"
:content "Manager"}
{:value "user"
:content "User"}
{:value "read-only"
:content "Read Only"}
{:value "none"
:content "None"}]}))]])
(def default-read '[:db/id
:user/name

View File

@@ -107,7 +107,7 @@
(mt2/transformer
{:decoders
{:map (fn [m]
(if (not (seq (filter identity (vals m))))
(if (and (map? m) (not (seq (filter identity (vals m)))))
nil
m))
:string empty->nil
@@ -240,9 +240,10 @@
(if date-range-value
(-> (condp = date-range-value
"week"
(assoc m
start-date-key (time/plus (time/now) (time/days -7))
end-date-key (time/now))
(let [last-monday (atime/last-monday)]
(assoc m
start-date-key (time/plus last-monday (time/days -7))
end-date-key last-monday))
"month"
(assoc m
@@ -255,7 +256,7 @@
end-date-key (time/now))
"all"
(assoc m start-date-key (time/plus (time/now) (time/years -3))
(assoc m start-date-key (time/plus (time/now) (time/years -6))
end-date-key (time/now))
m)