Adds the ability to set up read only users, adds linking filtering

This commit is contained in:
2024-01-03 21:42:30 -08:00
parent 48f70ef93d
commit 810c3aacb2
10 changed files with 192 additions and 63 deletions

View File

@@ -1432,6 +1432,7 @@
{:db/ident :user-role/user} {:db/ident :user-role/user}
{:db/ident :user-role/none} {:db/ident :user-role/none}
{:db/ident :user-role/power-user} {:db/ident :user-role/power-user}
{:db/ident :user-role/read-only}
{:db/ident :vendor/ccp-square} {:db/ident :vendor/ccp-square}
{:db/unique #:db{:ident :db.unique/identity}, {:db/unique #:db{:ident :db.unique/identity},

View File

@@ -40,6 +40,7 @@
(defn raw-graphql-ids (defn raw-graphql-ids
([args] (raw-graphql-ids (dc/db conn) args)) ([args] (raw-graphql-ids (dc/db conn) args))
([db args] ([db args]
(auto-ap.logging/peek args)
(let [valid-clients (extract-client-ids (:clients args) (let [valid-clients (extract-client-ids (:clients args)
(:client-id args) (:client-id args)
(when (:client-code args) (when (:client-code args)
@@ -104,6 +105,20 @@
:where ['[?e :transaction/approval-status ?approval-status]]} :where ['[?e :transaction/approval-status ?approval-status]]}
:args [(:approval-status args)]}) :args [(:approval-status args)]})
(= (:linked-to args) :payment)
(merge-query {:query {:where ['[?e :transaction/payment]]}})
(= (:linked-to args) :expected-deposit)
(merge-query {:query {:where ['[?e :transaction/expected-deposit]]}})
(= (:linked-to args) :invoice)
(merge-query {:query {:where ['[?e :transaction/payment ?p]
'[_ :invoice-payment/payment ?p]]}})
(= (:linked-to args) :none)
(merge-query {:query {:where ['(not [?e :transaction/payment])
'(not [?e :transaction/expected-deposit])]}})
(:original-id args) (:original-id args)
(merge-query {:query {:in ['?original-id] (merge-query {:query {:in ['?original-id]
:where ['[?e :transaction/client ?c] :where ['[?e :transaction/client ?c]

View File

@@ -663,6 +663,7 @@
:per_page {:type 'Int} :per_page {:type 'Int}
:sort {:type '(list :sort_item)} :sort {:type '(list :sort_item)}
:approval_status {:type :transaction_approval_status} :approval_status {:type :transaction_approval_status}
:linked_to {:type :transaction_link_type}
:unresolved {:type 'Boolean}}} :unresolved {:type 'Boolean}}}
:edit_transaction :edit_transaction
{:fields {:id {:type :id} {:fields {:id {:type :id}
@@ -676,21 +677,25 @@
{:enum-value :unapproved} {:enum-value :unapproved}
{:enum-value :suppressed} {:enum-value :suppressed}
{:enum-value :requires_feedback} {:enum-value :requires_feedback}
{:enum-value :excluded}]}}) {:enum-value :excluded}]}
:transaction_link_type {:values [{:enum-value :none}
{:enum-value :expected_deposit}
{:enum-value :payment}
{:enum-value :invoice}]}})
(def resolvers (def resolvers
{:get-transaction-page get-transaction-page {:get-transaction-page get-transaction-page
:get-potential-autopay-invoices-matches get-potential-autopay-invoices-matches :get-potential-autopay-invoices-matches get-potential-autopay-invoices-matches
:get-potential-unpaid-invoices-matches get-potential-unpaid-invoices-matches :get-potential-unpaid-invoices-matches get-potential-unpaid-invoices-matches
:mutation/edit-transaction edit-transaction :mutation/edit-transaction edit-transaction
:mutation/unlink-transaction unlink-transaction :mutation/unlink-transaction unlink-transaction
:mutation/bulk-change-transaction-status bulk-change-status :mutation/bulk-change-transaction-status bulk-change-status
:mutation/delete-transactions delete-transactions :mutation/delete-transactions delete-transactions
:mutation/bulk-code-transactions bulk-code-transactions :mutation/bulk-code-transactions bulk-code-transactions
:mutation/match-transaction match-transaction :mutation/match-transaction match-transaction
:mutation/match-transaction-autopay-invoices match-transaction-autopay-invoices :mutation/match-transaction-autopay-invoices match-transaction-autopay-invoices
:mutation/match-transaction-unpaid-invoices match-transaction-unpaid-invoices :mutation/match-transaction-unpaid-invoices match-transaction-unpaid-invoices
:mutation/match-transaction-rules match-transaction-rules}) :mutation/match-transaction-rules match-transaction-rules})
(defn attach [schema] (defn attach [schema]

View File

@@ -50,8 +50,8 @@
:where :where
[?p :payment/client ?c] [?p :payment/client ?c]
[?p :payment/date ?d ] [?p :payment/date ?d ]
[(>= ?d #inst "2022-01-01T08:00")] [(>= ?d #inst "2023-01-01T08:00")]
[(< ?d #inst "2023-01-01T08:00")] [(< ?d #inst "2024-01-01T08:00")]
[?p :payment/type :payment-type/check] [?p :payment/type :payment-type/check]
[?p :payment/amount ?a] [?p :payment/amount ?a]
[?p :payment/vendor ?v]] [?p :payment/vendor ?v]]
@@ -69,8 +69,8 @@
:where :where
[?p :payment/client ?c] [?p :payment/client ?c]
[?p :payment/date ?d ] [?p :payment/date ?d ]
[(>= ?d #inst "2022-01-01T08:00")] [(>= ?d #inst "2023-01-01T08:00")]
[(< ?d #inst "2023-01-01T08:00")] [(< ?d #inst "2024-01-01T08:00")]
[?p :payment/type :payment-type/check] [?p :payment/type :payment-type/check]
[?p :payment/amount ?a] [?p :payment/amount ?a]
[?p :payment/vendor ?v]] [?p :payment/vendor ?v]]

View File

@@ -78,6 +78,8 @@
:content "Manager"} :content "Manager"}
{:value "user" {:value "user"
:content "User"} :content "User"}
{:value "read-only"
:content "Read Only"}
{:value "none" {:value "none"
:content "None"}]})) :content "None"}]}))
(com/field {:label "Client"} (com/field {:label "Client"}

View File

@@ -0,0 +1,55 @@
(ns auto-ap.permissions)
(defn can? [user {:keys [client subject activity]}]
(let [role (or (:user/role user) (:role user) user)]
(println "ROLE IS" role)
(cond (#{:user-role/admin "admin"} role)
true
(#{:user-role/power-user "power-user"} role)
(cond
(#{:invoice-page :payment-page :my-company-page :transaction-page :ledger-page} subject)
true
(= [:vendor :create] [subject activity])
true
(= [:vendor :edit] [subject activity])
true
:else false)
(#{:user-role/manager "manager"} role)
(cond
(#{:invoice-page :payment-page :my-company-page :transaction-page} subject)
true
(= [:vendor :create] [subject activity])
true
(= [:vendor :edit] [subject activity])
true
:else false)
(#{:user-role/read-only "read-only"} role)
(cond
(= :ledger-page subject) true
:else false)
(#{:user-role/user "user"} role)
(cond
(#{:invoice-page :payment-page :my-company-page :transaction-page :ledger-page} subject)
true
(= [:vendor :create] [subject activity])
true
(= [:vendor :edit] [subject activity])
true
:else false)
:else
false)))

View File

@@ -1,6 +1,7 @@
(ns auto-ap.views.components.layouts (ns auto-ap.views.components.layouts
(:require (:require
[auto-ap.events :as events] [auto-ap.events :as events]
[auto-ap.permissions :as p]
[auto-ap.forms :as forms] [auto-ap.forms :as forms]
[auto-ap.forms.builder :as form-builder] [auto-ap.forms.builder :as form-builder]
[auto-ap.routes :as routes] [auto-ap.routes :as routes]
@@ -49,13 +50,20 @@
[:span (:user/name @user)]] [:span (:user/name @user)]]
:id ::account} :id ::account}
[:div [:div
[:a {:class "navbar-item" (when (p/can? @user {:subject :my-company-page})
:href (bidi/path-for auto-ap.ssr-routes/only-routes :company)} "My company"] [:a {:class "navbar-item"
[:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/started {}])} "New Vendor"] :href (bidi/path-for auto-ap.ssr-routes/only-routes :company)} "My company"])
[:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/edit {}])} "Edit Vendor"] (when (p/can? @user {:subject :vendor
(when (= "admin" (:user/role @user)) :activity :create})
[:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/started {}])} "New Vendor"])
(when (p/can? @user {:subject :vendor
:activity :edit})
[:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/edit {}])} "Edit Vendor"])
(when (p/can? @user {:subject :admin-page})
[:a {:class "navbar-item" :href (bidi/path-for ssr-routes/only-routes :auto-ap.routes.admin/page)} "Administration"]) [:a {:class "navbar-item" :href (bidi/path-for ssr-routes/only-routes :auto-ap.routes.admin/page)} "Administration"])
[:hr {:class "navbar-divider"}] (when (not= "read-only" (:user/role @user))
[:hr {:class "navbar-divider"}])
[:a.navbar-item {:on-click (fn [] [:a.navbar-item {:on-click (fn []
(.removeItem js/localStorage "last-client-id" nil) (.removeItem js/localStorage "last-client-id" nil)
(.setItem js/localStorage "last-selected-clients" ":all") (.setItem js/localStorage "last-selected-clients" ":all")
@@ -149,7 +157,6 @@
clients (re-frame/subscribe [::subs/clients]) clients (re-frame/subscribe [::subs/clients])
is-initial-loading @(re-frame/subscribe [::subs/is-initial-loading?])] is-initial-loading @(re-frame/subscribe [::subs/is-initial-loading?])]
[:nav {:class "navbar has-shadow is-fixed-top is-grey"} [:nav {:class "navbar has-shadow is-fixed-top is-grey"}
[:div {:class "container"} [:div {:class "container"}
[:div {:class "navbar-brand"} [:div {:class "navbar-brand"}
[:a {:class "navbar-item", :href "../"} [:a {:class "navbar-item", :href "../"}
@@ -164,27 +171,28 @@
(when-not is-initial-loading (when-not is-initial-loading
[:div.navbar-start [:div.navbar-start
[:a.navbar-item {:class [(active-when ap = :index)] [:a.navbar-item {:class [(active-when ap = :index)]
:href (bidi/path-for routes/routes :index)} :href (bidi/path-for routes/routes :index)}
"Home" ] "Home" ]
[:a.navbar-item {:class [(active-when ap #{:unpaid-invoices :paid-invoices})] (when (p/can? @user {:subject :invoice-page})
:href (bidi/path-for routes/routes :unpaid-invoices)} [:a.navbar-item {:class [(active-when ap #{:unpaid-invoices :paid-invoices})]
"Invoices" ] :href (bidi/path-for routes/routes :unpaid-invoices)}
[:a.navbar-item {:class [(active-when ap = :payments)] "Invoices" ])
:href (bidi/path-for routes/routes :payments)} (when (p/can? @user {:subject :payment-page})
"Payments" ] [:a.navbar-item {:class [(active-when ap = :payments)]
(when (= "admin" (:user/role @user)) :href (bidi/path-for routes/routes :payments)}
"Payments" ])
(when (p/can? @user {:subject :pos-page})
[:a.navbar-item {:class [(active-when ap = :pos-sales)] [:a.navbar-item {:class [(active-when ap = :pos-sales)]
:href (str (bidi/path-for ssr-routes/only-routes :pos-sales) "?date-range=week")} :href (str (bidi/path-for ssr-routes/only-routes :pos-sales) "?date-range=week")}
"POS" ]) "POS" ])
[:a.navbar-item {:class [(active-when ap = :transactions)] (when (p/can? @user {:subject :transaction-page})
:href (bidi/path-for routes/routes :transactions)} [:a.navbar-item {:class [(active-when ap = :transactions)]
"Transactions" ] :href (bidi/path-for routes/routes :transactions)}
"Transactions" ])
(when (not= "manager" (:user/role @user)) (when (p/can? @user {:subject :ledger-page})
[:a.navbar-item {:class [(active-when ap = :ledger)] [:a.navbar-item {:class [(active-when ap = :ledger)]
:href (bidi/path-for routes/routes :ledger)} :href (bidi/path-for routes/routes :ledger)}
"Ledger" ])]) "Ledger" ])])
(when-not is-initial-loading (when-not is-initial-loading
[:div.navbar-end [:div.navbar-end
(when (> (count @clients) 1) (when (> (count @clients) 1)

View File

@@ -2,6 +2,7 @@
(:require (:require
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[auto-ap.subs :as subs] [auto-ap.subs :as subs]
[auto-ap.permissions :as p]
[auto-ap.views.components.layouts :refer [loading-layout]] [auto-ap.views.components.layouts :refer [loading-layout]]
[auto-ap.views.pages.unpaid-invoices :refer [unpaid-invoices-page paid-invoices-page all-invoices-page voided-invoices-page]] [auto-ap.views.pages.unpaid-invoices :refer [unpaid-invoices-page paid-invoices-page all-invoices-page voided-invoices-page]]
[auto-ap.views.pages.import-invoices :refer [import-invoices-page]] [auto-ap.views.pages.import-invoices :refer [import-invoices-page]]
@@ -23,67 +24,86 @@
(defmulti page (fn [active-page] active-page)) (defmulti page (fn [active-page] active-page))
(defmethod page :unpaid-invoices [_] (defmethod page :unpaid-invoices [_]
^{:key :unpaid} (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page})
[unpaid-invoices-page {:status :unpaid}]) ^{:key :unpaid}
[unpaid-invoices-page {:status :unpaid}]))
(defmethod page :import-invoices [_] (defmethod page :import-invoices [_]
(import-invoices-page )) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page})
(import-invoices-page )))
(defmethod page :paid-invoices [_] (defmethod page :paid-invoices [_]
^{:key :paid} (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page})
[paid-invoices-page {:status :paid}]) ^{:key :paid}
[paid-invoices-page {:status :paid}]))
(defmethod page :voided-invoices [_] (defmethod page :voided-invoices [_]
^{:key :voided} (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page})
[voided-invoices-page {:status :voided}]) ^{:key :voided}
[voided-invoices-page {:status :voided}]))
(defmethod page :invoices [_] (defmethod page :invoices [_]
^{:key :all} (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page})
[all-invoices-page {}]) ^{:key :all}
[all-invoices-page {}]))
(defmethod page :payments [_] (defmethod page :payments [_]
[payments-page]) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :payment-page})
[payments-page]))
(defmethod page :transactions [_] (defmethod page :transactions [_]
(transactions-page {})) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :transaction-page})
(transactions-page {})))
(defmethod page :unapproved-transactions [_] (defmethod page :unapproved-transactions [_]
[transactions-page {:approval-status :unapproved}]) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :transaction-page})
[transactions-page {:approval-status :unapproved}]))
(defmethod page :approved-transactions [_] (defmethod page :approved-transactions [_]
[transactions-page {:approval-status :approved}]) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :transaction-page})
[transactions-page {:approval-status :approved}]))
(defmethod page :requires-feedback-transactions [_] (defmethod page :requires-feedback-transactions [_]
[transactions-page {:approval-status :requires-feedback}]) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :transaction-page})
[transactions-page {:approval-status :requires-feedback}]))
(defmethod page :excluded-transactions [_] (defmethod page :excluded-transactions [_]
[transactions-page {:approval-status :excluded}]) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :transaction-page})
[transactions-page {:approval-status :excluded}]))
(defmethod page :ledger [_] (defmethod page :ledger [_]
(ledger-page)) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page})
(ledger-page)))
(defmethod page :profit-and-loss [_] (defmethod page :profit-and-loss [_]
(profit-and-loss-page)) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page})
(profit-and-loss-page)))
(defmethod page :cash-flows [_] (defmethod page :cash-flows [_]
(cash-flows-page)) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page})
(cash-flows-page)))
(defmethod page :profit-and-loss-detail [_] (defmethod page :profit-and-loss-detail [_]
(profit-and-loss-detail-page)) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page})
(profit-and-loss-detail-page)))
(defmethod page :balance-sheet [_] (defmethod page :balance-sheet [_]
(balance-sheet-page)) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page})
(balance-sheet-page)))
(defmethod page :admin-clients [_] (defmethod page :admin-clients [_]
(admin-clients-page)) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :admin-page})
(admin-clients-page)))
(defmethod page :admin-specific-client [_] (defmethod page :admin-specific-client [_]
(admin-clients-page)) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :admin-page})
(admin-clients-page)))
(defmethod page :admin-vendors [_] (defmethod page :admin-vendors [_]
(admin-vendors-page)) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :admin-page})
(admin-vendors-page)))
(defmethod page :index [_] (defmethod page :index [_]
(home-page)) (home-page))
@@ -95,10 +115,12 @@
[needs-activation-page]) [needs-activation-page])
(defmethod page :external-import-ledger [_] (defmethod page :external-import-ledger [_]
[external-import-page]) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page})
[external-import-page]))
(defmethod page :external-ledger [_] (defmethod page :external-ledger [_]
[external-ledger-page]) (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page})
[external-ledger-page]))
(defmethod page :initial-error [_] (defmethod page :initial-error [_]
[error-page]) [error-page])

View File

@@ -41,6 +41,7 @@
:location (:location params) :location (:location params)
:import-batch-id (some-> (:import-batch-id params) str) :import-batch-id (some-> (:import-batch-id params) str)
:amount-lte (:amount-lte (:amount-range params)) :amount-lte (:amount-lte (:amount-range params))
:linked-to (:linked-to params)
:description (:description params) :description (:description params)
:approval-status (condp = @(re-frame/subscribe [::subs/active-page]) :approval-status (condp = @(re-frame/subscribe [::subs/active-page])
:transactions nil :transactions nil

View File

@@ -105,6 +105,26 @@
{:on-change-event [::data-page/filter-changed data-page :date-range] {:on-change-event [::data-page/filter-changed data-page :date-range]
:value @(re-frame/subscribe [::data-page/filter data-page :date-range])}]] :value @(re-frame/subscribe [::data-page/filter data-page :date-range])}]]
[:p.menu-label "Linking"]
[:div.field.has-addons
[:p.control [:a.button.is-small {:on-click
#(re-frame/dispatch [::data-page/filter-changed data-page :linked-to nil])}
"All" ]]
[:p.control [:a.button.is-small {:on-click
#(re-frame/dispatch [::data-page/filter-changed data-page :linked-to :none])}
"None" ]]
[:p.control [:a.button.is-small {:on-click
#(re-frame/dispatch [::data-page/filter-changed data-page :linked-to :invoice])}
"Invoice" ]]
[:p.control [:a.button.is-small {:on-click
#(re-frame/dispatch [::data-page/filter-changed data-page :linked-to :expected-deposit])}
"Expected Deposit"]]
[:p.control [:a.button.is-small {:on-click
#(re-frame/dispatch [::data-page/filter-changed data-page :linked-to :payment])}
"Payment"]]]
[:p.menu-label "Location"] [:p.menu-label "Location"]
[:div.field [:div.field
[:div.control [:input.input {:placeholder "SC" [:div.control [:input.input {:placeholder "SC"