diff --git a/resources/schema.edn b/resources/schema.edn index fe3e3c6a..b0aa4e06 100644 --- a/resources/schema.edn +++ b/resources/schema.edn @@ -1432,6 +1432,7 @@ {:db/ident :user-role/user} {:db/ident :user-role/none} {:db/ident :user-role/power-user} + {:db/ident :user-role/read-only} {:db/ident :vendor/ccp-square} {:db/unique #:db{:ident :db.unique/identity}, diff --git a/src/clj/auto_ap/datomic/transactions.clj b/src/clj/auto_ap/datomic/transactions.clj index 26190774..42ede8aa 100644 --- a/src/clj/auto_ap/datomic/transactions.clj +++ b/src/clj/auto_ap/datomic/transactions.clj @@ -40,6 +40,7 @@ (defn raw-graphql-ids ([args] (raw-graphql-ids (dc/db conn) args)) ([db args] + (auto-ap.logging/peek args) (let [valid-clients (extract-client-ids (:clients args) (:client-id args) (when (:client-code args) @@ -104,6 +105,20 @@ :where ['[?e :transaction/approval-status ?approval-status]]} :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) (merge-query {:query {:in ['?original-id] :where ['[?e :transaction/client ?c] diff --git a/src/clj/auto_ap/graphql/transactions.clj b/src/clj/auto_ap/graphql/transactions.clj index 89b3c5b2..1a5c9c0b 100644 --- a/src/clj/auto_ap/graphql/transactions.clj +++ b/src/clj/auto_ap/graphql/transactions.clj @@ -663,6 +663,7 @@ :per_page {:type 'Int} :sort {:type '(list :sort_item)} :approval_status {:type :transaction_approval_status} + :linked_to {:type :transaction_link_type} :unresolved {:type 'Boolean}}} :edit_transaction {:fields {:id {:type :id} @@ -676,21 +677,25 @@ {:enum-value :unapproved} {:enum-value :suppressed} {: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 - {:get-transaction-page get-transaction-page - :get-potential-autopay-invoices-matches get-potential-autopay-invoices-matches - :get-potential-unpaid-invoices-matches get-potential-unpaid-invoices-matches - :mutation/edit-transaction edit-transaction - :mutation/unlink-transaction unlink-transaction - :mutation/bulk-change-transaction-status bulk-change-status - :mutation/delete-transactions delete-transactions - :mutation/bulk-code-transactions bulk-code-transactions - :mutation/match-transaction match-transaction + {:get-transaction-page get-transaction-page + :get-potential-autopay-invoices-matches get-potential-autopay-invoices-matches + :get-potential-unpaid-invoices-matches get-potential-unpaid-invoices-matches + :mutation/edit-transaction edit-transaction + :mutation/unlink-transaction unlink-transaction + :mutation/bulk-change-transaction-status bulk-change-status + :mutation/delete-transactions delete-transactions + :mutation/bulk-code-transactions bulk-code-transactions + :mutation/match-transaction match-transaction :mutation/match-transaction-autopay-invoices match-transaction-autopay-invoices - :mutation/match-transaction-unpaid-invoices match-transaction-unpaid-invoices - :mutation/match-transaction-rules match-transaction-rules}) + :mutation/match-transaction-unpaid-invoices match-transaction-unpaid-invoices + :mutation/match-transaction-rules match-transaction-rules}) (defn attach [schema] diff --git a/src/clj/auto_ap/ssr/company/company_1099.clj b/src/clj/auto_ap/ssr/company/company_1099.clj index 2c2875a3..70a8712d 100644 --- a/src/clj/auto_ap/ssr/company/company_1099.clj +++ b/src/clj/auto_ap/ssr/company/company_1099.clj @@ -50,8 +50,8 @@ :where [?p :payment/client ?c] [?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/amount ?a] [?p :payment/vendor ?v]] @@ -69,8 +69,8 @@ :where [?p :payment/client ?c] [?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/amount ?a] [?p :payment/vendor ?v]] diff --git a/src/clj/auto_ap/ssr/users.clj b/src/clj/auto_ap/ssr/users.clj index 5f5051b8..9e4ca3f0 100644 --- a/src/clj/auto_ap/ssr/users.clj +++ b/src/clj/auto_ap/ssr/users.clj @@ -78,6 +78,8 @@ :content "Manager"} {:value "user" :content "User"} + {:value "read-only" + :content "Read Only"} {:value "none" :content "None"}]})) (com/field {:label "Client"} diff --git a/src/cljc/auto_ap/permissions.cljc b/src/cljc/auto_ap/permissions.cljc new file mode 100644 index 00000000..06efda41 --- /dev/null +++ b/src/cljc/auto_ap/permissions.cljc @@ -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))) diff --git a/src/cljs/auto_ap/views/components/layouts.cljs b/src/cljs/auto_ap/views/components/layouts.cljs index 7923afa6..82def69d 100644 --- a/src/cljs/auto_ap/views/components/layouts.cljs +++ b/src/cljs/auto_ap/views/components/layouts.cljs @@ -1,6 +1,7 @@ (ns auto-ap.views.components.layouts (:require [auto-ap.events :as events] + [auto-ap.permissions :as p] [auto-ap.forms :as forms] [auto-ap.forms.builder :as form-builder] [auto-ap.routes :as routes] @@ -49,13 +50,20 @@ [:span (:user/name @user)]] :id ::account} [:div - [:a {:class "navbar-item" - :href (bidi/path-for auto-ap.ssr-routes/only-routes :company)} "My company"] - [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/started {}])} "New Vendor"] - [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/edit {}])} "Edit Vendor"] - (when (= "admin" (:user/role @user)) + (when (p/can? @user {:subject :my-company-page}) + [:a {:class "navbar-item" + :href (bidi/path-for auto-ap.ssr-routes/only-routes :company)} "My company"]) + (when (p/can? @user {:subject :vendor + :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"]) - [:hr {:class "navbar-divider"}] + (when (not= "read-only" (:user/role @user)) + [:hr {:class "navbar-divider"}]) [:a.navbar-item {:on-click (fn [] (.removeItem js/localStorage "last-client-id" nil) (.setItem js/localStorage "last-selected-clients" ":all") @@ -149,7 +157,6 @@ clients (re-frame/subscribe [::subs/clients]) is-initial-loading @(re-frame/subscribe [::subs/is-initial-loading?])] [:nav {:class "navbar has-shadow is-fixed-top is-grey"} - [:div {:class "container"} [:div {:class "navbar-brand"} [:a {:class "navbar-item", :href "../"} @@ -164,27 +171,28 @@ (when-not is-initial-loading [:div.navbar-start [:a.navbar-item {:class [(active-when ap = :index)] - :href (bidi/path-for routes/routes :index)} + :href (bidi/path-for routes/routes :index)} "Home" ] - [:a.navbar-item {:class [(active-when ap #{:unpaid-invoices :paid-invoices})] - :href (bidi/path-for routes/routes :unpaid-invoices)} - "Invoices" ] - [:a.navbar-item {:class [(active-when ap = :payments)] - :href (bidi/path-for routes/routes :payments)} - "Payments" ] - (when (= "admin" (:user/role @user)) + (when (p/can? @user {:subject :invoice-page}) + [:a.navbar-item {:class [(active-when ap #{:unpaid-invoices :paid-invoices})] + :href (bidi/path-for routes/routes :unpaid-invoices)} + "Invoices" ]) + (when (p/can? @user {:subject :payment-page}) + [:a.navbar-item {:class [(active-when ap = :payments)] + :href (bidi/path-for routes/routes :payments)} + "Payments" ]) + (when (p/can? @user {:subject :pos-page}) [: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" ]) - [:a.navbar-item {:class [(active-when ap = :transactions)] - :href (bidi/path-for routes/routes :transactions)} - "Transactions" ] - - (when (not= "manager" (:user/role @user)) + (when (p/can? @user {:subject :transaction-page}) + [:a.navbar-item {:class [(active-when ap = :transactions)] + :href (bidi/path-for routes/routes :transactions)} + "Transactions" ]) + (when (p/can? @user {:subject :ledger-page}) [:a.navbar-item {:class [(active-when ap = :ledger)] :href (bidi/path-for routes/routes :ledger)} "Ledger" ])]) - (when-not is-initial-loading [:div.navbar-end (when (> (count @clients) 1) diff --git a/src/cljs/auto_ap/views/main.cljs b/src/cljs/auto_ap/views/main.cljs index df4db337..40f6bf26 100644 --- a/src/cljs/auto_ap/views/main.cljs +++ b/src/cljs/auto_ap/views/main.cljs @@ -2,6 +2,7 @@ (:require [re-frame.core :as re-frame] [auto-ap.subs :as subs] + [auto-ap.permissions :as p] [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.import-invoices :refer [import-invoices-page]] @@ -23,67 +24,86 @@ (defmulti page (fn [active-page] active-page)) (defmethod page :unpaid-invoices [_] - ^{:key :unpaid} - [unpaid-invoices-page {:status :unpaid}]) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page}) + ^{:key :unpaid} + [unpaid-invoices-page {:status :unpaid}])) (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 [_] - ^{:key :paid} - [paid-invoices-page {:status :paid}]) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page}) + ^{:key :paid} + [paid-invoices-page {:status :paid}])) (defmethod page :voided-invoices [_] - ^{:key :voided} - [voided-invoices-page {:status :voided}]) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page}) + ^{:key :voided} + [voided-invoices-page {:status :voided}])) (defmethod page :invoices [_] - ^{:key :all} - [all-invoices-page {}]) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page}) + ^{:key :all} + [all-invoices-page {}])) (defmethod page :payments [_] - [payments-page]) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :payment-page}) + [payments-page])) (defmethod page :transactions [_] - (transactions-page {})) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :transaction-page}) + (transactions-page {}))) (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 [_] - [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 [_] - [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 [_] - [transactions-page {:approval-status :excluded}]) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :transaction-page}) + [transactions-page {:approval-status :excluded}])) (defmethod page :ledger [_] - (ledger-page)) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page}) + (ledger-page))) (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 [_] - (cash-flows-page)) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page}) + (cash-flows-page))) (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 [_] - (balance-sheet-page)) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page}) + (balance-sheet-page))) (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 [_] - (admin-clients-page)) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :admin-page}) + (admin-clients-page))) (defmethod page :admin-vendors [_] - (admin-vendors-page)) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :admin-page}) + (admin-vendors-page))) (defmethod page :index [_] (home-page)) @@ -95,10 +115,12 @@ [needs-activation-page]) (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 [_] - [external-ledger-page]) + (when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :ledger-page}) + [external-ledger-page])) (defmethod page :initial-error [_] [error-page]) diff --git a/src/cljs/auto_ap/views/pages/transactions/common.cljs b/src/cljs/auto_ap/views/pages/transactions/common.cljs index c4c94eee..59956a17 100644 --- a/src/cljs/auto_ap/views/pages/transactions/common.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/common.cljs @@ -41,6 +41,7 @@ :location (:location params) :import-batch-id (some-> (:import-batch-id params) str) :amount-lte (:amount-lte (:amount-range params)) + :linked-to (:linked-to params) :description (:description params) :approval-status (condp = @(re-frame/subscribe [::subs/active-page]) :transactions nil diff --git a/src/cljs/auto_ap/views/pages/transactions/side_bar.cljs b/src/cljs/auto_ap/views/pages/transactions/side_bar.cljs index 8e993836..2c154da3 100644 --- a/src/cljs/auto_ap/views/pages/transactions/side_bar.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/side_bar.cljs @@ -105,6 +105,26 @@ {:on-change-event [::data-page/filter-changed 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"] [:div.field [:div.control [:input.input {:placeholder "SC"