users can now edit vendors.

This commit is contained in:
2022-04-12 15:49:45 -07:00
parent 6ad5541124
commit f73b406abd
3 changed files with 162 additions and 84 deletions

View File

@@ -3,19 +3,41 @@
[auto-ap.datomic :refer [audit-transact conn remove-nils]] [auto-ap.datomic :refer [audit-transact conn remove-nils]]
[auto-ap.datomic.vendors :as d-vendors] [auto-ap.datomic.vendors :as d-vendors]
[auto-ap.graphql.utils [auto-ap.graphql.utils
:refer [->graphql <-graphql assert-admin enum->keyword is-admin? result->page]] :refer [->graphql
<-graphql
assert-admin
assert-failure
enum->keyword
is-admin?
result->page]]
[clojure.set :as set]
[clojure.tools.logging :as log] [clojure.tools.logging :as log]
[datomic.api :as d])) [datomic.api :as d]))
(defn can-user-edit-vendor? [vendor-id id]
(if (is-admin? id)
true
(empty?
(set/difference (set (d/q '[:find [?c ...]
:in $ ?v
:where [?vu :vendor-usage/vendor ?v]
[?vu :vendor-usage/client ?c]
[?vu :vendor-usage/count ?d]
[(>= ?d 0)]]
(d/db conn)
vendor-id))
(set (map :db/id (:user/clients id)))))))
(defn upsert-vendor [context {{:keys [id name hidden terms code print_as primary_contact secondary_contact address default_account_id invoice_reminder_schedule schedule_payment_dom terms_overrides account_overrides] :as in} :vendor} value] (defn upsert-vendor [context {{:keys [id name hidden terms code print_as primary_contact secondary_contact address default_account_id invoice_reminder_schedule schedule_payment_dom terms_overrides account_overrides] :as in} :vendor} value]
(when id
(assert-admin (:id context))) (when (and id (not (can-user-edit-vendor? id (:id context))))
#_(Thread/sleep 3000) (assert-failure "This vendor is managed by Integreat. Please reach out to ben@integreatconsult.com for your changes."))
#_(throw (ex-info "" {:validation-error "can't do that."})) (let [
(let [hidden (if (is-admin? (:id context)) hidden (if (is-admin? (:id context))
hidden hidden
false) false)
existing (when id
(d/pull (d/db conn) '[:vendor/name] id))
terms-overrides (mapv terms-overrides (mapv
(fn [to] (fn [to]
(cond-> (cond->
@@ -82,14 +104,14 @@
:vendor/legal-entity-tin-type (enum->keyword (:legal_entity_tin_type in) "legal-entity-tin-type") :vendor/legal-entity-tin-type (enum->keyword (:legal_entity_tin_type in) "legal-entity-tin-type")
:vendor/legal-entity-1099-type (enum->keyword (:legal_entity_1099_type in) "legal-entity-1099-type"))))] :vendor/legal-entity-1099-type (enum->keyword (:legal_entity_1099_type in) "legal-entity-1099-type"))))]
(is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/account-overrides account-overrides]) (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/account-overrides account-overrides])
(is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/search-terms [name]])
(is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/terms-overrides terms-overrides]) (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/terms-overrides terms-overrides])
(is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/schedule-payment-dom schedule-payment-dom]) (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/schedule-payment-dom schedule-payment-dom])
(is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/automatically-paid-when-due (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/automatically-paid-when-due
(mapv (mapv
(fn [apwd] (fn [apwd]
{:db/id apwd}) {:db/id apwd})
(:automatically_paid_when_due in))])) (:automatically_paid_when_due in))])
(not= (:vendor/name existing) name) (conj [:reset (if id id "vendor") :vendor/search-terms [name]]))
_ (log/info "Upserting vendor" transaction) _ (log/info "Upserting vendor" transaction)
transaction-result (audit-transact transaction (:id context))] transaction-result (audit-transact transaction (:id context))]

View File

@@ -1,23 +1,21 @@
(ns auto-ap.views.components.layouts (ns auto-ap.views.components.layouts
(:require (:require
[reagent.core :as reagent] [auto-ap.events :as events]
[re-frame.core :as re-frame]
[bidi.bidi :as bidi]
[auto-ap.routes :as routes] [auto-ap.routes :as routes]
[auto-ap.subs :as subs] [auto-ap.subs :as subs]
[auto-ap.events :as events] [auto-ap.views.components.dropdown :refer [drop-down drop-down-contents]]
[auto-ap.views.utils :refer [active-when active-when= login-url dispatch-event appearing css-transition-group bind-field]]
[auto-ap.views.components.modal :as modal] [auto-ap.views.components.modal :as modal]
[auto-ap.entities.vendors :as vendor]
[clojure.string :as str]
[reagent.core :as r]
[auto-ap.views.components.vendor-dialog :as vendor-dialog] [auto-ap.views.components.vendor-dialog :as vendor-dialog]
[auto-ap.views.components.buttons :as buttons])) [auto-ap.views.utils
:refer [active-when appearing bind-field dispatch-event dispatch-event-with-propagation login-url]]
[bidi.bidi :as bidi]
[clojure.string :as str]
[re-frame.core :as re-frame]
[reagent.core :as r]))
(defn navbar-drop-down-contents [{:keys [id]} children ] (defn navbar-drop-down-contents [{:keys [id]} children ]
(let [toggle-fn (fn [] (re-frame/dispatch [::events/toggle-menu id]))] (let [toggle-fn (fn [] (re-frame/dispatch [::events/toggle-menu id]))]
(reagent/create-class {:component-did-mount (fn [] (.addEventListener js/document "click" toggle-fn)) (r/create-class {:component-did-mount (fn [] (.addEventListener js/document "click" toggle-fn))
:component-will-unmount (fn [] (.removeEventListener js/document "click" toggle-fn)) :component-will-unmount (fn [] (.removeEventListener js/document "click" toggle-fn))
:reagent-render :reagent-render
(fn [children] (fn [children]
@@ -25,7 +23,7 @@
(defn navbar-drop-down [{:keys [ header id class]} child] (defn navbar-drop-down [{:keys [ header id class]} child]
(let [menu-active? (re-frame/subscribe [::subs/menu-active? id])] (let [menu-active? (re-frame/subscribe [::subs/menu-active? id])]
(reagent/create-class (r/create-class
{:reagent-render (fn [{:keys [header id]} child] {:reagent-render (fn [{:keys [header id]} child]
(let [menu-active? @(re-frame/subscribe [::subs/menu-active? id])] (let [menu-active? @(re-frame/subscribe [::subs/menu-active? id])]
[:div { :class (str "navbar-item has-dropdown " (when menu-active? "is-active " ) " " class)} [:div { :class (str "navbar-item has-dropdown " (when menu-active? "is-active " ) " " class)}
@@ -134,9 +132,16 @@
(when-not is-initial-loading (when-not is-initial-loading
[:div.navbar-end [:div.navbar-end
[:div.navbar-item [:div.navbar-item
[buttons/new-button {:event [::vendor-dialog/started {}] [drop-down {:header [:a.button.is-outlined {:aria-haspopup true
:name "Vendor" :type "button"
:class "is-primary"}]] :on-click (dispatch-event-with-propagation [::events/toggle-menu ::vendor ])}
"Vendors "
[:span " "]
[:span.icon [:i.fa.fa-angle-down {:aria-hidden "true"}]]]
:id ::vendor}
[:<>
[:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/started {}])} "New"]
[:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/edit {}])} "Edit"]]]]
(when (> (count @clients) 1) (when (> (count @clients) 1)

View File

@@ -6,6 +6,8 @@
[auto-ap.status :as status] [auto-ap.status :as status]
[auto-ap.subs :as subs] [auto-ap.subs :as subs]
[auto-ap.views.components.address :refer [address-field]] [auto-ap.views.components.address :refer [address-field]]
[auto-ap.views.components.typeahead.vendor
:refer [search-backed-typeahead]]
[auto-ap.views.components.modal :as modal] [auto-ap.views.components.modal :as modal]
[auto-ap.views.components.typeahead :refer [typeahead-v3]] [auto-ap.views.components.typeahead :refer [typeahead-v3]]
[auto-ap.views.pages.admin.vendors.common :as common] [auto-ap.views.pages.admin.vendors.common :as common]
@@ -17,7 +19,6 @@
with-is-admin? with-is-admin?
with-user]] with-user]]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[clojure.string :as str]
[re-frame.core :as re-frame])) [re-frame.core :as re-frame]))
(re-frame/reg-sub (re-frame/reg-sub
@@ -26,9 +27,6 @@
(fn [form] (fn [form]
(s/valid? ::entity/vendor (:data form)))) (s/valid? ::entity/vendor (:data form))))
(defn ngrams [text len]
(mapv #(.toLowerCase (str/join "" %))
(partition len 1 text)))
(re-frame/reg-event-db (re-frame/reg-event-db
::settled ::settled
@@ -61,7 +59,7 @@
:event [::settled]})] :event [::settled]})]
(forms/change-handler (forms/change-handler
::vendor-form ::vendor-form
(fn [data field value] (fn [data field _]
(let [[override-key? i?] field] (let [[override-key? i?] field]
(if (and (#{:account-overrides :terms-overrides :schedule-payment-dom} override-key?) (if (and (#{:account-overrides :terms-overrides :schedule-payment-dom} override-key?)
(nil? (get-in data [override-key? i? :key]))) (nil? (get-in data [override-key? i? :key])))
@@ -84,49 +82,48 @@
:owns-state {:single ::vendor-form} :owns-state {:single ::vendor-form}
:query-obj {:venia/operation {:operation/type :mutation :query-obj {:venia/operation {:operation/type :mutation
:operation/name "UpsertVendor"} :venia/queries [{:query/data [:upsert-vendor :operation/name "UpsertVendor"} :venia/queries [{:query/data [:upsert-vendor
{:vendor (doto (cond-> {:id id {:vendor (cond-> {:id id
:name name :name name
:print-as print-as :print-as print-as
:terms terms :terms terms
:default-account-id (:id default-account) :default-account-id (:id default-account)
:address address :address address
:primary-contact primary-contact :primary-contact primary-contact
:secondary-contact secondary-contact :secondary-contact secondary-contact
:invoice-reminder-schedule invoice-reminder-schedule} :invoice-reminder-schedule invoice-reminder-schedule}
is-admin? (assoc :hidden hidden is-admin? (assoc :hidden hidden
:terms-overrides (mapv :terms-overrides (mapv
(fn [{:keys [client override id]}] (fn [{:keys [client override id]}]
{:id id {:id id
:client-id (:id client) :client-id (:id client)
:terms override}) :terms override})
terms-overrides) terms-overrides)
:account-overrides (mapv :account-overrides (mapv
(fn [{:keys [client override id]}] (fn [{:keys [client override id]}]
{:id id {:id id
:client-id (:id client) :client-id (:id client)
:account-id (:id override)}) :account-id (:id override)})
account-overrides) account-overrides)
:schedule-payment-dom (mapv :schedule-payment-dom (mapv
(fn [{:keys [client override id]}] (fn [{:keys [client override id]}]
{:id id {:id id
:client-id (:id client) :client-id (:id client)
:dom override}) :dom override})
schedule-payment-dom) schedule-payment-dom)
:automatically-paid-when-due (mapv :automatically-paid-when-due (mapv
:id :id
automatically-paid-when-due) automatically-paid-when-due)
:legal-entity-first-name legal-entity-first-name :legal-entity-first-name legal-entity-first-name
:legal-entity-middle-name legal-entity-middle-name :legal-entity-middle-name legal-entity-middle-name
:legal-entity-last-name legal-entity-last-name :legal-entity-last-name legal-entity-last-name
:legal-entity-tin legal-entity-tin :legal-entity-tin legal-entity-tin
:legal-entity-tin-type (some-> legal-entity-tin-type clojure.core/name not-empty keyword) :legal-entity-tin-type (some-> legal-entity-tin-type clojure.core/name not-empty keyword)
:legal-entity-1099-type (some-> legal-entity-1099-type clojure.core/name not-empty keyword) :legal-entity-1099-type (some-> legal-entity-1099-type clojure.core/name not-empty keyword)
)) ))}
println)}
common/default-read]}]} common/default-read]}]}
:on-success [::save-complete]}}))) :on-success [::save-complete]}})))
(defn client-list [{:keys [override-key override-value-key change-event default-key data]} template] (defn client-list [{:keys [override-key change-event data]}]
(let [clients @(re-frame/subscribe [::subs/clients]) (let [clients @(re-frame/subscribe [::subs/clients])
is-admin? @(re-frame/subscribe [::subs/is-admin?])] is-admin? @(re-frame/subscribe [::subs/is-admin?])]
(when is-admin? (when is-admin?
@@ -150,7 +147,7 @@
[:div.column.is-1 [:div.column.is-1
[:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]))]))) [:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]))])))
(defn default-with-overrides [{:keys [override-key override-value-key change-event default-key data mandatory?]} template] (defn default-with-overrides [{:keys [override-key change-event default-key data mandatory?]} template]
(let [clients @(re-frame/subscribe [::subs/clients]) (let [clients @(re-frame/subscribe [::subs/clients])
is-admin? @(re-frame/subscribe [::subs/is-admin?])] is-admin? @(re-frame/subscribe [::subs/is-admin?])]
[:div [:div
@@ -187,7 +184,7 @@
[:div.column.is-1 [:div.column.is-1
[:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]))])])) [:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]))])]))
(defn client-overrides [{:keys [override-key override-value-key change-event data mandatory?]} template] (defn client-overrides [{:keys [override-key change-event data]} template]
(let [clients @(re-frame/subscribe [::subs/clients]) (let [clients @(re-frame/subscribe [::subs/clients])
is-admin? @(re-frame/subscribe [::subs/is-admin?])] is-admin? @(re-frame/subscribe [::subs/is-admin?])]
(when is-admin? (when is-admin?
@@ -217,9 +214,7 @@
[:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]))]))) [:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]))])))
(defn form-content [{:keys [data change-event]}] (defn form-content [{:keys [data change-event]}]
(let [accounts @(re-frame/subscribe [::subs/accounts]) (let [is-admin? @(re-frame/subscribe [::subs/is-admin?])]
clients @(re-frame/subscribe [::subs/clients])
is-admin? @(re-frame/subscribe [::subs/is-admin?])]
[:div [:div
[horizontal-field [horizontal-field
[:label.label [:span "Name " [:label.label [:span "Name "
@@ -257,7 +252,7 @@
[default-with-overrides {:data data :change-event change-event [default-with-overrides {:data data :change-event change-event
:default-key :terms :default-key :terms
:override-key :terms-overrides} :override-key :terms-overrides}
(fn [field client] (fn [field _]
[:input.input {:type "number" [:input.input {:type "number"
:step "1" :step "1"
:style {:width "4em"} :style {:width "4em"}
@@ -267,15 +262,17 @@
:event change-event :event change-event
:subscription data}])] :subscription data}])]
[:h2.subtitle "Schedule payment when due"] (when is-admin?
[:h2.subtitle "Schedule payment when due"])
[client-list {:data data :change-event change-event [client-list {:data data :change-event change-event
:override-key :automatically-paid-when-due}] :override-key :automatically-paid-when-due}]
[:h2.subtitle "Schedule payment on day of month"] (when is-admin?
[:h2.subtitle "Schedule payment on day of month"])
[client-overrides {:data data :change-event change-event [client-overrides {:data data :change-event change-event
:mandatory? true :mandatory? true
:override-key :schedule-payment-dom} :override-key :schedule-payment-dom}
(fn [field client] (fn [field _]
[:input.input {:type "number" [:input.input {:type "number"
:step "1" :step "1"
:style {:width "5em"} :style {:width "5em"}
@@ -291,7 +288,6 @@
:default-key :default-account :default-key :default-account
:override-key :account-overrides} :override-key :account-overrides}
(fn [field client] (fn [field client]
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/accounts client]) [typeahead-v3 {:entities @(re-frame/subscribe [::subs/accounts client])
:entity->text account->match-text :entity->text account->match-text
:field field :field field
@@ -453,16 +449,56 @@
[:option {:value nil} ""] [:option {:value nil} ""]
[:option {:value "none"} "Don't 1099"] [:option {:value "none"} "Don't 1099"]
[:option {:value "misc"} "Misc"] [:option {:value "misc"} "Misc"]
[:option {:value "landlord"} "Landlord"]]]]]] [:option {:value "landlord"} "Landlord"]]]]]]]))
]))
(defn vendor-dialog [{:keys [save-event] }] (defn vendor-dialog [ ]
(let [clients @(re-frame/subscribe [::subs/clients]) (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::vendor-form])
{:keys [data error warning] :as f} @(re-frame/subscribe [::forms/form ::vendor-form])
change-event [::changed]] change-event [::changed]]
[:div [:div
[form-content {:data data :change-event change-event}]])) [form-content {:data data :change-event change-event}]]))
(re-frame/reg-event-fx
::vendor-selected
[with-user (forms/in-form ::select-vendor-form)]
(fn [{{:keys [data]} :db :keys [user]} _]
{:graphql {:token user
:query-obj {:venia/queries [[:vendor-by-id
{:id (:id (:vendor data))}
common/default-read]]}
:owns-state {:single ::select-vendor-form}
:on-success (fn [r]
(println (:vendor-by-id r))
[::started (:vendor-by-id r)])}}))
(re-frame/reg-sub
::can-submit-select-vendor-form
:<- [::forms/field ::select-vendor-form [:vendor]]
(fn [vendor]
(if vendor
true
false)))
(def select-vendor-form (forms/vertical-form {:submit-event [::vendor-selected]
:change-event [::forms/change ::select-vendor-form]
:can-submit [::can-submit-select-vendor-form]
:id ::select-vendor-form}))
(defn select-vendor-form-content []
(let [_ @(re-frame/subscribe [::forms/form ::select-vendor-form])
{:keys [form-inline field]} select-vendor-form]
(form-inline {}
[:<>
(field "Vendor to edit"
[search-backed-typeahead {:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])
:type "typeahead-v3"
:auto-focus true
:field [:vendor]}])])))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::started ::started
(fn [{:keys [db]} [_ vendor]] (fn [{:keys [db]} [_ vendor]]
@@ -503,3 +539,18 @@
:on-click (dispatch-event [::save]) :on-click (dispatch-event [::save])
:can-submit [::can-submit] :can-submit [::can-submit]
:close-event [::status/completed ::vendor-form]}}]})) :close-event [::status/completed ::vendor-form]}}]}))
(re-frame/reg-event-fx
::edit
(fn [{:keys [db]} [_]]
{:db (-> db (forms/start-form ::select-vendor-form {}))
:dispatch [::modal/modal-requested
{:title "Select Vendor"
:class "is-wide"
:body [select-vendor-form-content]
:confirm {:value "Choose a vendor"
:status-from [::status/single ::select-vendor-form]
:class "is-primary"
:on-click (dispatch-event [::vendor-selected])
:can-submit [::can-submit-select-vendor-form]
:close-event [::status/completed ::select-vendor-form]}}]}))