vendor querying step 1.

This commit is contained in:
2022-04-10 20:28:02 -07:00
parent cbc3c00b8e
commit e897443f7d
9 changed files with 308 additions and 67 deletions

View File

@@ -15,5 +15,4 @@
"--output-filename" :final-output-filename]
:default ["npx" "webpack" "--mode=production" :output-to
"--output-path" :final-output-dir
"--output-filename" :final-output-filename]}
}
"--output-filename" :final-output-filename]}}

View File

@@ -1,6 +1,14 @@
(ns auto-ap.datomic.migrate.vendors
(:require [datomic.api :as d]
[auto-ap.datomic :refer [uri]]))
(:require [datomic.api :as d]))
(defn add-vendor-search-terms [conn]
[(->> (d/q '[:find ?i ?n
:in $
:where [?i :vendor/name ?n]]
(d/db conn))
(map (fn [[i n]]
{:db/id i
:vendor/search-terms n})))])
(def norms-map {:add-1099-stuff {:txes [[{:db/ident :vendor/legal-entity-first-name
:db/doc "The first name for the legal entity"
@@ -56,7 +64,15 @@
:db/valueType :db.type/long
:db/cardinality :db.cardinality/one
:db/noHistory true}]
]}})
]}
::make-fulltext-search {:txes [[{:db/ident :vendor/search-terms
:db/valueType :db.type/string
:db/cardinality :db.cardinality/many
:db/doc "a name search for vendors"
:db/fulltext true}]]
:requires [:auto-ap/base-schema]}
::add-vendor-search-terms {:txes-fn `add-vendor-search-terms
:requires [::make-fulltext-search]}})

View File

@@ -85,6 +85,18 @@
(map #(trim-usage % (limited-clients (:id args))))
#_(map #(assoc % :usage (get usages (:db/id %))))))
(defn get-graphql-by-id [args id]
(->> (cond-> {:query {:find [(list 'pull '?e default-read)]
:in ['$ '?e]
:where ['[?e :vendor/name]]}
:args [(d/db (d/connect uri)) id]})
(d/query)
(map first)
(map #(cleanse (:id args) %))
(map <-datomic)
(map #(trim-usage % (limited-clients (:id args))))
first))
(defn get-by-id [id]
(->> (d/q '[:find (pull ?e [*

View File

@@ -98,6 +98,10 @@
:message
{:fields {:message {:type 'String}}}
:search_result
{:fields {:name {:type 'String}
:id {:type :id}}}
:yodlee_provider_account
{:fields {:id {:type 'Int}
:client {:type :client}
@@ -347,6 +351,10 @@
:args {:account_set {:type 'String}}
:resolve :get-accounts}
:search_vendor {:type '(list :search_result)
:args {:query {:type 'String}}
:resolve :search-vendor}
:all_sales_orders {:type '(list :sales_order)
@@ -393,7 +401,10 @@
:vendor {:type '(list :vendor)
:resolve :get-vendor}
:user {:type '(list :user)
:resolve :get-user}}
:resolve :get-user}
:vendor_by_id {:type :vendor
:args {:id {:type :id}}
:resolve :vendor-by-id}}
:input-objects
{
@@ -777,6 +788,7 @@
:get-cash-flow get-cash-flow
:get-yodlee-merchants ym/get-yodlee-merchants
:get-intuit-bank-accounts gq-intuit-bank-accounts/get-intuit-bank-accounts
:vendor-by-id gq-vendors/get-by-id
:get-user get-user
:mutation/delete-transaction-rule gq-transaction-rules/delete-transaction-rule
:mutation/edit-user gq-users/edit-user
@@ -787,7 +799,8 @@
:mutation/upsert-account gq-accounts/upsert-account
:mutation/merge-vendors gq-vendors/merge-vendors
:mutation/request-import gq-requests/request-import
:get-vendor gq-vendors/get-graphql})
:get-vendor gq-vendors/get-graphql
:search-vendor gq-vendors/search})
gq-checks/attach
gq-ledger/attach
gq-reports/attach

View File

@@ -117,3 +117,21 @@
(defn get-graphql [context args value]
(->graphql
(d-vendors/get-graphql (assoc args :id (:id context)))))
(defn get-by-id [context args value]
(->graphql
(d-vendors/get-graphql-by-id (assoc args :id (:id context))
(:id args))))
(defn search [context args value]
(->> (d/q '[:find ?n ?i ?s
:in $ ?q
:where [(fulltext $ :vendor/search-terms ?q) [[?i ?n _ ?s]]]
(not [?i :vendor/hidden true])]
(d/db conn)
(:query args))
(sort-by last)
(map (fn [[n i]]
{:name n
:id i}
))))

View File

@@ -10,6 +10,13 @@
(fn [db [_ x]]
(get (-> db ::forms) x)))
(re-frame/reg-sub
::field
(fn [db [_ x f]]
(-> (get (-> db ::forms) x)
:data
(get-in f))))
(re-frame/reg-sub
::is-loading?

View File

@@ -14,8 +14,7 @@
(not (get-in accounts [0 :account :id]))))
(defn default-account [accounts default-account amount locations]
[{:id (get-in accounts [0 :id]
(str "new-" (random-uuid)))
[{:id (str "new-" (random-uuid))
:amount (Math/abs amount)
:amount-percentage 100
:amount-mode "%"

View File

@@ -0,0 +1,134 @@
(ns auto-ap.views.components.typeahead.vendor
(:require
[downshift :as ds :refer [useCombobox]]
[re-frame.core :as re-frame]
[auto-ap.views.utils :refer [with-user]]
[react]))
(set! *warn-on-infer* true)
;; TODO: This avoids the use of inferred externs by using aget. You could just use the ^js tag though
(defn state-reducer [^js/FakeStateObject state ^js/FakeActionsAndChanges actions-and-changes]
(let [useCombobox ^js/Downshift useCombobox]
(cond
(= (.-type actions-and-changes) (.-InputChange (.-stateChangeTypes ^js/Downshift useCombobox)))
(set! (.-selectedItem (.-changes actions-and-changes)) nil)
(and (= (.-type actions-and-changes) (.-InputBlur (.-stateChangeTypes ^js/Downshift useCombobox)))
(not (.-selectedItem state)))
(set! (.-inputValue (.-changes actions-and-changes ))
nil)
:else
nil))
(.-changes actions-and-changes))
(re-frame/reg-event-fx
::search-completed
(fn [_ [_ set-items set-loading-status result]]
(set-loading-status nil)
(set-items (:search-results result))
{}))
(re-frame/reg-event-fx
::search-failed
(fn [_ _]
{}))
(re-frame/reg-event-fx
::input-value-settled
[with-user]
(fn [{:keys [user]} [_ input-value search-query set-items set-loading-status]]
(when (> (count input-value) 2)
(set-loading-status :loading)
{:graphql {:token user
:query-obj {:venia/queries [{:query/data (search-query input-value )
:query/alias :search-results}]}
:on-success [::search-completed set-items set-loading-status]
:on-error [::search-failed set-loading-status]}})))
(re-frame/reg-event-fx
::input-value-changed
(fn [_ [_ input-value search-query set-items set-loading-status]]
(set-items [])
(when (> (count input-value) 2)
(set-loading-status :loading)
{:dispatch-debounce {:event [::input-value-settled input-value search-query set-items set-loading-status]
:time 250
:key ::input-value-settled}})))
(defn typeahead-v3-internal [{:keys [class style ^js entity->text on-change disabled value name search-query auto-focus] :or {disabled false} :as i}]
(let [[items set-items] (react/useState [])
[loading-status set-loading-status] (react/useState false)
[getLabelProps getMenuProps getComboboxProps getToggleButtonProps getInputProps getItemProps isOpen highlightedIndex selectItem selectedItem setInputValue]
(as-> (useCombobox (clj->js {:items items
:defaultHighlightedIndex 0
:defaultSelectedItem value
:onInputValueChange (fn [input]
(re-frame/dispatch [::input-value-changed (aget input "inputValue") search-query set-items set-loading-status])
true)
:stateReducer state-reducer
:onSelectedItemChange (fn [z]
(when on-change
(on-change (js->clj (aget z "selectedItem") :keywordize-keys true))))})) $
(map #(aget $ %) ["getLabelProps" "getMenuProps" "getComboboxProps" "getToggleButtonProps" "getInputProps" "getItemProps" "isOpen" "highlightedIndex" "selectItem" "selectedItem" "setInputValue"]))]
#_(println (getInputProps))
[:<>
[:div.typeahead (assoc (js->clj (getComboboxProps))
:style style)
(cond
selectedItem
^{:key "typeahead"}
[:div.input (assoc (js->clj (getInputProps #js {:disabled (if disabled
"disabled"
"")}))
:on-key-up (fn [e]
(when (= 8 (aget e "keyCode" ))
(selectItem nil)
(setInputValue nil)
(when on-change
(on-change nil))))
:class (if (= :loading loading-status)
"is-loading"
class)
:tab-index "0")
[:div.control
[:div.tags.has-addons
[:span.tag (entity->text (js->clj selectedItem :keywordize-keys true))]
(when name
[:input {:type "hidden" :name name :value (:id (js->clj selectedItem :keywordize-keys true))}])
(when-not disabled
[:a.tag.is-delete {:on-click (fn []
(setInputValue nil)
(selectItem nil)
(when on-change
(on-change nil)))}])]]]
:else
^{:key "typeahead"} [:div.control {:class (when (= :loading loading-status)
"is-loading")}
[:input.input (js->clj
(getInputProps #js {:disabled (if disabled
"disabled"
"")
:autoFocus (if auto-focus
"autoFocus"
"")}))]])
[:div {:class (when (and isOpen (seq items))
"typeahead-menu")}
[:ul (js->clj (getMenuProps))
(if (and isOpen (seq items))
(for [[index item] (map vector (range) (js->clj items :keywordize-keys true))]
^{:key item}
[:li.typeahead-suggestion (assoc (js->clj (getItemProps #js {:item item :index index}))
:class (if (= index highlightedIndex)
"typeahead-highlighted"))
(entity->text item)]))]]]]))
(defn search-backed-typeahead [props]
[:div
[:f> typeahead-v3-internal (assoc props
:entity->text :name
)]])

View File

@@ -17,6 +17,7 @@
[auto-ap.views.components.money-field :refer [money-field]]
[auto-ap.views.components.switch-field :refer [switch-field]]
[auto-ap.views.components.typeahead :refer [typeahead-v3]]
[auto-ap.views.components.typeahead.vendor :refer [search-backed-typeahead]]
[auto-ap.views.pages.invoices.common :refer [invoice-read]]
[auto-ap.views.utils
:refer
@@ -26,13 +27,14 @@
[clojure.string :as str]
[re-frame.core :as re-frame]
[reagent.core :as r]
[vimsical.re-frame.fx.track :as track]))
[vimsical.re-frame.fx.track :as track]
[vimsical.re-frame.cofx.inject :as inject]))
;; SUBS
(re-frame/reg-sub
::can-submit
:<- [::forms/form ::form]
(fn [{:keys [data status]} _]
(fn [{:keys [data]} _]
(let [min-total (if (= (:total (:original data)) (:outstanding-balance (:original data)))
nil
(- (:total (:original data)) (:outstanding-balance (:original data))))
@@ -45,7 +47,7 @@
(re-frame/reg-sub
::create-query
:<- [::forms/form ::form]
(fn [{:keys [data] {:keys [id invoice-number date due scheduled-payment location total expense-accounts vendor client]} :data}]
(fn [{{:keys [invoice-number date due scheduled-payment location total expense-accounts vendor client]} :data}]
{:venia/operation {:operation/type :mutation
:operation/name "AddInvoice"}
:venia/queries [{:query/data [:add-invoice
@@ -69,7 +71,7 @@
(re-frame/reg-sub
::edit-query
:<- [::forms/form ::form]
(fn [{:keys [data] {:keys [id invoice-number date due scheduled-payment location total expense-accounts vendor client]} :data}]
(fn [{{:keys [id invoice-number date due scheduled-payment total expense-accounts]} :data}]
{:venia/operation {:operation/type :mutation
:operation/name "EditInvoice"}
:venia/queries [{:query/data [:edit-invoice
@@ -90,8 +92,8 @@
(re-frame/reg-sub
::add-and-print-query
(fn [db [_ bank-account-id type]]
(let [{:keys [data] {:keys [id invoice-number date location total expense-accounts scheduled-payment vendor client]} :data} @(re-frame/subscribe [::forms/form ::form])]
(fn [_ [_ bank-account-id type]]
(let [{{:keys [invoice-number date location total expense-accounts scheduled-payment vendor client]} :data} @(re-frame/subscribe [::forms/form ::form])]
{:venia/operation {:operation/type :mutation
:operation/name "AddAndPrintInvoice"}
:venia/queries [{:query/data [:add-and-print-invoice
@@ -119,7 +121,7 @@
(re-frame/reg-event-db
::updated
(fn [db [_ invoice command]]
(fn [db [_ _ command]]
(if (= :create command)
(-> db
(forms/stop-form ::form )
@@ -178,34 +180,7 @@
(forms/change-handler ::form
(fn [data field value]
(let [locations @(re-frame/subscribe [::subs/locations-for-client (:id (:client data))])]
(cond (and (= [:vendor] field)
value)
(let [schedule-payment-dom (get (by (comp :id :client ) :dom (:schedule-payment-dom value))
(:id (:client data)))]
(cond-> []
(expense-accounts-field/can-replace-with-default? (:expense-accounts data))
(into [[:expense-accounts] (expense-accounts-field/default-account (:expense-accounts data)
@(re-frame/subscribe [::subs/vendor-default-account (:id value) (:client data)])
(:total data)
locations)])
(boolean ((set (map :id (:automatically-paid-when-due value))) (:id (:client data))))
(into [[:scheduled-payment] (:due data)
[:schedule-when-due] true
[:vendor-autopay? ] true])
schedule-payment-dom
(into [[:scheduled-payment] (date->str (next-dom (str->date (:date data) standard) schedule-payment-dom) standard)
[:vendor-autopay?] true])
true
(into [[:schedule-payment-dom] schedule-payment-dom])))
(= [:total] field)
(cond (= [:total] field)
[[:expense-accounts] (recalculate-amounts (:expense-accounts data) value)]
(and (= [:date] field)
@@ -231,7 +206,7 @@
(re-frame/reg-event-fx
::add-and-print
[with-user (forms/in-form ::form)]
(fn [{:keys [user] {:keys [data]} :db} [_ bank-account-id type]]
(fn [{:keys [user]} [_ bank-account-id type]]
{:graphql
{:token user
:owns-state {:single ::form}
@@ -263,7 +238,7 @@
(re-frame/reg-event-fx
::save-requested
[with-user (forms/in-form ::form)]
(fn [{:keys [user db]} [_ fwd-event]]
(fn [{:keys [db]} [_ fwd-event]]
(if (and (:scheduled-payment (:data db))
(not (:vendor-autopay? (:data db))))
{:dispatch
@@ -282,16 +257,80 @@
(re-frame/reg-event-fx
::added-and-printed
(fn [{:keys [db]} [_ result]]
(fn [_ [_ result]]
(let [invoice (first (:invoices (:add-and-print-invoice result)))]
{:dispatch-n [[::updated (assoc invoice :class "live-added") :create]
[::checks-printed [invoice] (:pdf-url (:add-and-print-invoice result))]]})))
(re-frame/reg-event-db
::checks-printed
(fn [db [_ invoices pdf-url]]
(fn [db _]
db))
(re-frame/reg-sub
::client-accounts
:<- [::forms/field ::form [:client]]
:<- [::subs/all-accounts]
(fn [[client all-accounts]]
(subs/accounts-by-id all-accounts client)))
(re-frame/reg-event-fx
::changed-vendor
[with-user (forms/in-form ::form) (re-frame/inject-cofx ::inject/sub [::client-accounts])]
(fn [{::keys [client-accounts] :keys [user] {{:keys [client date due expense-accounts total]} :data} :db} [_ vendor]]
(when (:id vendor)
{:graphql {:token user
:query-obj {:venia/queries [[:vendor-by-id
{:id (:id vendor)}
[[:automatically-paid-when-due [:id]]
[:schedule-payment-dom [[:client [:id]] :dom]]
[:default-account [:id]]]]]}
:on-success (fn [r]
(let [schedule-payment-dom (->> r
:vendor-by-id
:schedule-payment-dom
(filter (fn [spd]
(= (-> spd :client :id)
(:id client))))
first
:dom)
default-account (let [client-override (->> r
:vendor-by-id
:account-overrides
(filter #(= (:id (:client %)) (:id client)))
first
:account
:id)
default-id (->> r :vendor-by-id :default-account :id)
i (or client-override default-id)]
(client-accounts i))
changes (cond-> []
(expense-accounts-field/can-replace-with-default? expense-accounts)
(into [[:expense-accounts] (expense-accounts-field/default-account expense-accounts
default-account
total
(:locations client)
)])
(boolean ((->> r :vendor-by-id :automatically-paid-when-due (map :id) set) (:id client)))
(into [[:scheduled-payment] due
[:schedule-when-due] true
[:vendor-autopay? ] true])
schedule-payment-dom
(into [[:scheduled-payment] (date->str (next-dom (str->date date standard) schedule-payment-dom) standard)
[:vendor-autopay?] true])
true
(into [[:schedule-payment-dom] schedule-payment-dom]))]
(if (seq changes)
(into [::changed ] changes)
[:ignore]))
)
:on-failure [:bad]
}})))
;; VIEWS
@@ -308,16 +347,21 @@
:subscription [::subs/client]
:event-fn (fn [c]
[::maybe-change-client c])}]}))
[::maybe-change-client c])}
{:id ::vendor-change
:subscription [::forms/field ::form [:vendor]]
:event-fn (fn [v]
[::changed-vendor v])}]}))
(re-frame/reg-event-fx
::unmounted
(fn []
{::track/dispose [{:id ::client}]}))
{::track/dispose [{:id ::client}
{:id ::vendor-change}]}))
(defn form-content [{:keys [can-change-amount?] :as params}]
(defn form-content [params]
[layouts/side-bar {:on-close (dispatch-event [::forms/form-closing ::form ])}
(let [{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::form])
(let [{:keys [data id]} @(re-frame/subscribe [::forms/form ::form])
{:keys [form-inline field raw-field error-notification submit-button ]} invoice-form
can-submit? (boolean @(re-frame/subscribe [::can-submit]))
status @(re-frame/subscribe [::status/single ::form])
@@ -339,7 +383,7 @@
(not (seq (:payments data))))
[:div.tag.is-info.is-light "Automatically paid"]
(and (#{:paid ":paid"} (:status data)))
(#{:paid ":paid"} (:status data))
(if-let [check-number (:check-number (:payment (first (:payments data))))]
[:div.tag.is-info.is-light "Paid by check #" check-number ]
[:div.tag.is-info.is-light "Paid"])
@@ -359,13 +403,14 @@
:spec ::invoice/client}]))
(field [:span "Vendor"
[:span.has-text-danger " *"]]
[typeahead-v3 {:entities-by-id @(re-frame/subscribe [::subs/vendors-by-id])
:entity-index @(re-frame/subscribe [::subs/searchable-vendors-index])
:entity->text :name
:type "typeahead-v3"
:disabled exists?
:auto-focus (if @(re-frame/subscribe [::subs/client]) true false)
:field [:vendor]}])
[search-backed-typeahead {:disabled exists?
:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])
:type "typeahead-v3"
:auto-focus (if @(re-frame/subscribe [::subs/client]) true false)
:field [:vendor]}])
(field [:span "Date"
[:span.has-text-danger " *"]]
@@ -374,6 +419,7 @@
:format-week-number (fn [] "")
:previous-month-button-label ""
:placeholder "mm/dd/yyyy"
:disable-keyboard-navigation true
:next-month-button-label ""
:next-month-label ""
:type "date"
@@ -386,6 +432,7 @@
:format-week-number (fn [] "")
:previous-month-button-label ""
:placeholder "mm/dd/yyyy"
:disable-keyboard-navigation true
:next-month-button-label ""
:next-month-label ""
:type "date"
@@ -440,11 +487,7 @@
:max (:total data)
:client (or (:client data) @(re-frame/subscribe [::subs/client]))
:field [:expense-accounts]}])
{:key (str (:id (:vendor data)))})
{:key (str (:id (:vendor data) "none") "-" (:id (:client data) "none") )})
(error-notification)
@@ -464,7 +507,7 @@
:id ::add-and-print-invoice}
[:div
(list
(for [{:keys [id number name type]} (->> (:bank-accounts (:client data)) (filter :visible) (sort-by :sort-order))]
(for [{:keys [id name type]} (->> (:bank-accounts (:client data)) (filter :visible) (sort-by :sort-order))]
(if (= :cash type)
^{:key id} [:a.dropdown-item {:on-click (dispatch-event [::save-requested [::add-and-print id :cash]])} "With cash"]
(list
@@ -476,7 +519,7 @@
{:key id}))])
(defn form [p]
(defn form [_]
(r/create-class
{:display-name "invoice-form"
:component-did-mount #(re-frame/dispatch [::mounted])