From f73b406abd7c5b50200861ed4a916016a5bc5bfd Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Tue, 12 Apr 2022 15:49:45 -0700 Subject: [PATCH] users can now edit vendors. --- src/clj/auto_ap/graphql/vendors.clj | 40 +++- .../auto_ap/views/components/layouts.cljs | 35 ++-- .../views/components/vendor_dialog.cljs | 171 ++++++++++++------ 3 files changed, 162 insertions(+), 84 deletions(-) diff --git a/src/clj/auto_ap/graphql/vendors.clj b/src/clj/auto_ap/graphql/vendors.clj index 717b73e2..82552575 100644 --- a/src/clj/auto_ap/graphql/vendors.clj +++ b/src/clj/auto_ap/graphql/vendors.clj @@ -3,19 +3,41 @@ [auto-ap.datomic :refer [audit-transact conn remove-nils]] [auto-ap.datomic.vendors :as d-vendors] [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] [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] - (when id - (assert-admin (:id context))) - #_(Thread/sleep 3000) - #_(throw (ex-info "" {:validation-error "can't do that."})) - (let [hidden (if (is-admin? (:id context)) + + (when (and id (not (can-user-edit-vendor? id (:id context)))) + (assert-failure "This vendor is managed by Integreat. Please reach out to ben@integreatconsult.com for your changes.")) + (let [ + hidden (if (is-admin? (:id context)) hidden false) - + existing (when id + (d/pull (d/db conn) '[:vendor/name] id)) terms-overrides (mapv (fn [to] (cond-> @@ -82,14 +104,14 @@ :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"))))] (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/schedule-payment-dom schedule-payment-dom]) (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/automatically-paid-when-due (mapv (fn [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) transaction-result (audit-transact transaction (:id context))] diff --git a/src/cljs/auto_ap/views/components/layouts.cljs b/src/cljs/auto_ap/views/components/layouts.cljs index ed9d2000..1f35c153 100644 --- a/src/cljs/auto_ap/views/components/layouts.cljs +++ b/src/cljs/auto_ap/views/components/layouts.cljs @@ -1,23 +1,21 @@ (ns auto-ap.views.components.layouts (:require - [reagent.core :as reagent] - [re-frame.core :as re-frame] - [bidi.bidi :as bidi] + [auto-ap.events :as events] [auto-ap.routes :as routes] [auto-ap.subs :as subs] - [auto-ap.events :as events] - [auto-ap.views.utils :refer [active-when active-when= login-url dispatch-event appearing css-transition-group bind-field]] + [auto-ap.views.components.dropdown :refer [drop-down drop-down-contents]] [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.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 ] (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)) :reagent-render (fn [children] @@ -25,7 +23,7 @@ (defn navbar-drop-down [{:keys [ header id class]} child] (let [menu-active? (re-frame/subscribe [::subs/menu-active? id])] - (reagent/create-class + (r/create-class {:reagent-render (fn [{:keys [header id]} child] (let [menu-active? @(re-frame/subscribe [::subs/menu-active? id])] [:div { :class (str "navbar-item has-dropdown " (when menu-active? "is-active " ) " " class)} @@ -134,9 +132,16 @@ (when-not is-initial-loading [:div.navbar-end [:div.navbar-item - [buttons/new-button {:event [::vendor-dialog/started {}] - :name "Vendor" - :class "is-primary"}]] + [drop-down {:header [:a.button.is-outlined {:aria-haspopup true + :type "button" + :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) diff --git a/src/cljs/auto_ap/views/components/vendor_dialog.cljs b/src/cljs/auto_ap/views/components/vendor_dialog.cljs index acbd7ad3..31fce5a4 100644 --- a/src/cljs/auto_ap/views/components/vendor_dialog.cljs +++ b/src/cljs/auto_ap/views/components/vendor_dialog.cljs @@ -6,6 +6,8 @@ [auto-ap.status :as status] [auto-ap.subs :as subs] [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.typeahead :refer [typeahead-v3]] [auto-ap.views.pages.admin.vendors.common :as common] @@ -17,7 +19,6 @@ with-is-admin? with-user]] [clojure.spec.alpha :as s] - [clojure.string :as str] [re-frame.core :as re-frame])) (re-frame/reg-sub @@ -26,9 +27,6 @@ (fn [form] (s/valid? ::entity/vendor (:data form)))) -(defn ngrams [text len] - (mapv #(.toLowerCase (str/join "" %)) - (partition len 1 text))) (re-frame/reg-event-db ::settled @@ -61,7 +59,7 @@ :event [::settled]})] (forms/change-handler ::vendor-form - (fn [data field value] + (fn [data field _] (let [[override-key? i?] field] (if (and (#{:account-overrides :terms-overrides :schedule-payment-dom} override-key?) (nil? (get-in data [override-key? i? :key]))) @@ -84,49 +82,48 @@ :owns-state {:single ::vendor-form} :query-obj {:venia/operation {:operation/type :mutation :operation/name "UpsertVendor"} :venia/queries [{:query/data [:upsert-vendor - {:vendor (doto (cond-> {:id id - :name name - :print-as print-as - :terms terms - :default-account-id (:id default-account) - :address address - :primary-contact primary-contact - :secondary-contact secondary-contact - :invoice-reminder-schedule invoice-reminder-schedule} - is-admin? (assoc :hidden hidden - :terms-overrides (mapv - (fn [{:keys [client override id]}] - {:id id - :client-id (:id client) - :terms override}) - terms-overrides) - :account-overrides (mapv - (fn [{:keys [client override id]}] - {:id id - :client-id (:id client) - :account-id (:id override)}) - account-overrides) - :schedule-payment-dom (mapv - (fn [{:keys [client override id]}] - {:id id - :client-id (:id client) - :dom override}) - schedule-payment-dom) - :automatically-paid-when-due (mapv - :id - automatically-paid-when-due) - :legal-entity-first-name legal-entity-first-name - :legal-entity-middle-name legal-entity-middle-name - :legal-entity-last-name legal-entity-last-name - :legal-entity-tin legal-entity-tin - :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) - )) - println)} + {:vendor (cond-> {:id id + :name name + :print-as print-as + :terms terms + :default-account-id (:id default-account) + :address address + :primary-contact primary-contact + :secondary-contact secondary-contact + :invoice-reminder-schedule invoice-reminder-schedule} + is-admin? (assoc :hidden hidden + :terms-overrides (mapv + (fn [{:keys [client override id]}] + {:id id + :client-id (:id client) + :terms override}) + terms-overrides) + :account-overrides (mapv + (fn [{:keys [client override id]}] + {:id id + :client-id (:id client) + :account-id (:id override)}) + account-overrides) + :schedule-payment-dom (mapv + (fn [{:keys [client override id]}] + {:id id + :client-id (:id client) + :dom override}) + schedule-payment-dom) + :automatically-paid-when-due (mapv + :id + automatically-paid-when-due) + :legal-entity-first-name legal-entity-first-name + :legal-entity-middle-name legal-entity-middle-name + :legal-entity-last-name legal-entity-last-name + :legal-entity-tin legal-entity-tin + :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) + ))} common/default-read]}]} :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]) is-admin? @(re-frame/subscribe [::subs/is-admin?])] (when is-admin? @@ -150,7 +147,7 @@ [:div.column.is-1 [: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]) is-admin? @(re-frame/subscribe [::subs/is-admin?])] [:div @@ -187,7 +184,7 @@ [:div.column.is-1 [: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]) is-admin? @(re-frame/subscribe [::subs/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]]]]]))]))) (defn form-content [{:keys [data change-event]}] - (let [accounts @(re-frame/subscribe [::subs/accounts]) - clients @(re-frame/subscribe [::subs/clients]) - is-admin? @(re-frame/subscribe [::subs/is-admin?])] + (let [is-admin? @(re-frame/subscribe [::subs/is-admin?])] [:div [horizontal-field [:label.label [:span "Name " @@ -257,7 +252,7 @@ [default-with-overrides {:data data :change-event change-event :default-key :terms :override-key :terms-overrides} - (fn [field client] + (fn [field _] [:input.input {:type "number" :step "1" :style {:width "4em"} @@ -267,15 +262,17 @@ :event change-event :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 :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 :mandatory? true :override-key :schedule-payment-dom} - (fn [field client] + (fn [field _] [:input.input {:type "number" :step "1" :style {:width "5em"} @@ -291,7 +288,6 @@ :default-key :default-account :override-key :account-overrides} (fn [field client] - [typeahead-v3 {:entities @(re-frame/subscribe [::subs/accounts client]) :entity->text account->match-text :field field @@ -453,16 +449,56 @@ [:option {:value nil} ""] [:option {:value "none"} "Don't 1099"] [:option {:value "misc"} "Misc"] - [:option {:value "landlord"} "Landlord"]]]]]] - ])) + [:option {:value "landlord"} "Landlord"]]]]]]])) -(defn vendor-dialog [{:keys [save-event] }] - (let [clients @(re-frame/subscribe [::subs/clients]) - {:keys [data error warning] :as f} @(re-frame/subscribe [::forms/form ::vendor-form]) +(defn vendor-dialog [ ] + (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::vendor-form]) change-event [::changed]] [:div [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 ::started (fn [{:keys [db]} [_ vendor]] @@ -503,3 +539,18 @@ :on-click (dispatch-event [::save]) :can-submit [::can-submit] :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]}}]}))