diff --git a/src/clj/auto_ap/datomic/accounts.clj b/src/clj/auto_ap/datomic/accounts.clj index c58fdb49..9997e5b4 100644 --- a/src/clj/auto_ap/datomic/accounts.clj +++ b/src/clj/auto_ap/datomic/accounts.clj @@ -2,12 +2,17 @@ (:require [datomic.api :as d] [auto-ap.graphql.utils :refer [->graphql]] [auto-ap.datomic :refer [uri merge-query]])) +(defn <-datomic [a] + (update a :account/applicability :db/ident)) (defn get-accounts ([] (get-accounts {})) ([args] - (let [query (cond-> {:query {:find ['(pull ?e [* {:account/type [:db/ident :db/id]}])] + (let [query (cond-> {:query {:find ['(pull ?e [* {:account/type [:db/ident :db/id] + :account/applicability [:db/ident :db/id] + :account/client-overrides [:account-client-override/name + {:account-client-override/client [:db/id :client/name]}]}])] :in ['$] :where [['?e :account/name]]} :args [(d/db (d/connect uri))]} @@ -16,7 +21,21 @@ :args [(:account-set args)]}))] (->> (d/query query) - (map first))))) + (map first) + (map <-datomic))))) + +(defn get-by-id [id] + (let [query {:query {:find ['(pull ?e [* {:account/type [:db/ident :db/id] + :account/applicability [:db/ident :db/id] + :account/client-overrides [:account-client-override/name + {:account-client-override/client [:db/id :client/name]}]}])] + :in ['$ '?e]} + :args [(d/db (d/connect uri) ) id]}] + (->> + (d/query query) + (map first) + (map <-datomic) + first))) (defn get-account-by-numeric-code-and-sets [numeric-code sets] (let [query (cond-> {:query {:find ['(pull ?e [* {:account/type [:db/ident :db/id]}])] @@ -26,6 +45,7 @@ (->> (d/query query) (map first) + (map <-datomic) (first)))) #_(get-account-by-numeric-code-and-sets 5110 nil) diff --git a/src/clj/auto_ap/datomic/migrate.clj b/src/clj/auto_ap/datomic/migrate.clj index f6c803c4..5977b118 100644 --- a/src/clj/auto_ap/datomic/migrate.clj +++ b/src/clj/auto_ap/datomic/migrate.clj @@ -192,7 +192,42 @@ :db/doc "Overrides per-client" :db/valueType :db.type/ref :db/cardinality :db.cardinality/many}]]} - :auto-ap/add-reset-rels {:txes-fn `reset-function}}] + :auto-ap/add-reset-rels {:txes-fn `reset-function} + :auto-ap/add-account-overrides {:txes [[{:db/ident :account/applicability + :db/doc ":global, :optional :customized" + :db/valueType :db.type/ref + :db/cardinality :db.cardinality/one} + + {:db/ident :account-applicability/global + :db/doc "The account applies to all cutsomers"} + + {:db/ident :account-applicability/optional + :db/doc "This account is optional"} + + {:db/ident :account-applicability/customized + :db/doc "This account is customized per-customer"} + + {:db/ident :account/client-overrides + :db/doc "Customizations per customer" + :db/valueType :db.type/ref + :db/isComponent true + :db/cardinality :db.cardinality/many} + + + {:db/ident :account-client-override/client + :db/doc "The client for the override" + :db/valueType :db.type/ref + :db/cardinality :db.cardinality/one} + + {:db/ident :account-client-override/enabled + :db/doc "Does this apply?" + :db/valueType :db.type/boolean + :db/cardinality :db.cardinality/one} + + {:db/ident :account-client-override/name + :db/doc "client override" + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one}]]} }] (println "Conforming database...") (c/ensure-conforms conn norms-map) (when (not (seq args)) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index 368fa544..291ef4c3 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -117,7 +117,7 @@ :terms {:type 'Int} }} - :account_override + :vendor_account_override {:fields {:id {:type :id} :client {:type :client} :account {:type :account}}} @@ -129,7 +129,7 @@ :terms {:type 'Int} :hidden {:type 'Boolean} :terms_overrides {:type '(list :terms_override)} - :account_overrides {:type '(list :account_override)} + :account_overrides {:type '(list :vendor_account_override)} :print_as {:type 'String} :primary_contact {:type :contact} @@ -244,14 +244,19 @@ :role {:type 'String} :clients {:type '(list :client)}}} - + :account_client_override + {:fields {:id {:type :id} + :client {:type :client} + :name {:type 'String}}} :account {:fields {:id {:type :id} :numeric_code {:type 'Int} :type {:type :ident} + :applicability {:type :applicability} :account_set {:type 'String} :location {:type 'String} - :name {:type 'String}}} + :name {:type 'String} + :client_overrides {:type '(list :account_client_override)}}} :invoices_expense_accounts {:fields {:id {:type :id} @@ -613,13 +618,20 @@ :accounts {:type '(list :edit_percentage_account)} :transaction_approval_status {:type :transaction_approval_status}}} + :edit_account_client_override + {:fields {:id {:type :id} + :client_id {:type :id} + :name {:type 'String}}} + :edit_account {:fields {:id {:type :id} :type {:type :account_type} + :applicability {:type :applicability} :numeric_code {:type 'Int} :location {:type 'String} :account_set {:type 'String} - :name {:type 'String}}}} + :name {:type 'String} + :client_overrides {:type '(list :edit_account_client_override)}}}} :enums {:payment_type {:values [{:enum-value :check} {:enum-value :cash} @@ -630,6 +642,9 @@ :bank_account_type {:values [{:enum-value :check} {:enum-value :credit} {:enum-value :cash}]} + :applicability {:values [{:enum-value :global} + {:enum-value :optional} + {:enum-value :customized}]} :account_type {:values [{:enum-value :dividend} {:enum-value :expense} {:enum-value :asset} diff --git a/src/clj/auto_ap/graphql/accounts.clj b/src/clj/auto_ap/graphql/accounts.clj index 25d71901..463beaca 100644 --- a/src/clj/auto_ap/graphql/accounts.clj +++ b/src/clj/auto_ap/graphql/accounts.clj @@ -1,14 +1,16 @@ (ns auto-ap.graphql.accounts (:require [datomic.api :as d] [auto-ap.datomic.accounts :as d-accounts] - [auto-ap.graphql.utils :refer [->graphql <-graphql] ] + [auto-ap.graphql.utils :refer [->graphql <-graphql enum->keyword] ] [auto-ap.datomic :refer [uri merge-query remove-nils]])) + + (defn get-accounts [context args value] (->graphql (d-accounts/get-accounts (<-graphql args)))) (defn upsert-account [context args value] - (let [{{:keys [id numeric-code location account-set name type]} :account} (<-graphql args)] + (let [{{:keys [id client-overrides numeric-code location applicability account-set name type]} :account} (<-graphql args)] (when-not id (when (seq (d/query {:query {:find ['?e] :in '[$ ?account-set ?numeric-code] @@ -26,16 +28,21 @@ {:db/id (or id "new-account") :account/name name :account/type (keyword "account-type" (clojure.core/name type)) + :account/applicability (enum->keyword applicability "account-applicability") :account/account-set account-set :account/location location :account/numeric-code (if-not id numeric-code) :account/code (if-not id - (str numeric-code))})] + (str numeric-code))}) + [:reset (or id "new-account") :account/client-overrides + (mapv + (fn [client-override] + (remove-nils + {:account-client-override/client (:client-id client-override) + :account-client-override/name (:name client-override)})) + client-overrides)]] (and (not location) (:account/location original)) (conj [:db/retract (or id "new-account") :account/location (:account/location original)])))] (->graphql - (if id - (:account args) - (assoc (:account args) - :id (get-in result [:tempids "new-account"]))))))) + (d-accounts/get-by-id (or id (get-in result [:tempids "new-account"]))))))) diff --git a/src/cljs/auto_ap/events.cljs b/src/cljs/auto_ap/events.cljs index 35513142..8224a506 100644 --- a/src/cljs/auto_ap/events.cljs +++ b/src/cljs/auto_ap/events.cljs @@ -51,7 +51,7 @@ [:id :name :hidden [:default-account [:name :id :location]] [:primary-contact [:name :phone :email :id]] [:secondary-contact [:id :name :phone :email]] :print-as :invoice-reminder-schedule :code [:account-overrides [[:client [:id :name]] :id [:account [:id :numeric-code :name]]]] [:terms-overrides [[:client [:id :name]] :id :terms]]]] - [:accounts [:numeric-code :location :name :type :account_set :id]]]} + [:accounts [:numeric-code :location :name :type :account_set :applicability :id [:client-overrides [:name [:client [:name :id]]]]]]]} :on-success [::received-initial]}})))) (def vendor-query [:id :name :hidden :terms [:default-account [:name :id :location]] @@ -75,7 +75,7 @@ [:id :name :code :matches :locations [:location-matches [:location :match]] [:address [:street1 :street2 :city :state :zip]] [:bank-accounts [:id :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations] ]]] [:vendor [:id :name :hidden [:default-account [:name :id :location]] [:primary-contact [:name :phone :email :id]] [:secondary-contact [:id :name :phone :email]] :print-as :invoice-reminder-schedule :code]] - [:accounts [:numeric-code :name :location :type :account_set :id]]]} + [:accounts [:numeric-code :name :location :type :account_set :applicability :id [:client-overrides [:name [:client [:name :id]]]]]]]} :on-success [::received-initial]} :db (assoc db :user (assoc user :token token))})) diff --git a/src/cljs/auto_ap/forms.cljs b/src/cljs/auto_ap/forms.cljs index 58c4efc7..f41a2e2a 100644 --- a/src/cljs/auto_ap/forms.cljs +++ b/src/cljs/auto_ap/forms.cljs @@ -24,17 +24,39 @@ "is-loading" ""))) -(defn start-form [db form data] - (assoc-in db [::forms form] {:error nil - :active? true - :id (random-uuid) - :status nil - :data data})) +(defn start-form + ([db form data] + (start-form db form data nil)) + ([db form data complete-listener] + (assoc-in db [::forms form] {:error nil + :active? true + :id (random-uuid) + :status nil + :data data + :complete-listener complete-listener}))) -(defn saved-form [db form data] +(defn ^:depracated saved-form [db form data] (update-in db [::forms form] assoc :error nil :status nil :data data)) +(defn triggers-saved [form data-key] + (i/->interceptor + :id :triggers-saved + :before (fn [context] + context) + :after (fn [context] + (let [db (i/get-coeffect context :db) + result (get-in (i/get-coeffect context :event) [1 data-key])] + (println (get-in db [::forms form :complete-listener])) + (cond-> context + true + (i/assoc-effect :db (update-in db + [::forms form] + assoc :error nil :status nil :data result)) + + (get-in db [::forms form :complete-listener]) + (i/assoc-effect :dispatch (conj (get-in db [::forms form :complete-listener ]) + result))))))) (defn stop-form [db form] (update db ::forms dissoc form)) @@ -48,8 +70,6 @@ [form-name] (re-frame/path [::forms form-name])) - - (re-frame/reg-event-db ::change (fn [db [_ form & path-pairs]] @@ -140,6 +160,10 @@ [bind-field (-> control (assoc-in [1 :subscription] data) (assoc-in [1 :event] change-event))])) + :field-holder (fn [label control] + [:div.field + (when label [:p.help label]) + [:div.control control]]) :field (fn [label control] (let [{:keys [data]} @(re-frame/subscribe [::form id])] [:div.field diff --git a/src/cljs/auto_ap/views/pages/admin/accounts.cljs b/src/cljs/auto_ap/views/pages/admin/accounts.cljs index 17771398..b6699de5 100644 --- a/src/cljs/auto_ap/views/pages/admin/accounts.cljs +++ b/src/cljs/auto_ap/views/pages/admin/accounts.cljs @@ -38,7 +38,9 @@ [:td name] [:td type] [:td location] - [:td [:a.button {:on-click (dispatch-event [::account-form/editing account])} [:span [:span.icon [:i.fa.fa-pencil]]]]]])]]])]) + [:td [:a.button {:on-click (dispatch-event [::account-form/editing + account + [::edit-completed]])} [:span [:span.icon [:i.fa.fa-pencil]]]]]])]]])]) (defn admin-accounts-content [] [:div @@ -46,12 +48,14 @@ [:div [:h1.title "Accounts"] [:div.is-pulled-right - [:a.button.is-success {:on-click (dispatch-event [::account-form/editing {:type :asset - :account-set "default"}])} "New Account"]] + [:a.button.is-success {:on-click (dispatch-event [::account-form/editing + {:type :asset + :account-set "default"} + [::edit-completed]])} "New Account"]] [accounts-table {:accounts accounts}]])]) (defn admin-accounts-page [] (let [{:keys [active?]} @(re-frame/subscribe [::forms/form ::account-form/form])] [side-bar-layout {:side-bar [admin-side-bar {}] :main [admin-accounts-content] - :right-side-bar [appearing-side-bar {:visible? active?} [account-form/form {:edit-completed [::edit-completed]}]]}])) + :right-side-bar [appearing-side-bar {:visible? active?} [account-form/form ]]}])) diff --git a/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs b/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs index ecf80c11..2a903c97 100644 --- a/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/accounts/form.cljs @@ -2,45 +2,66 @@ (:require [auto-ap.forms :as forms] [clojure.string :as str] [clojure.spec.alpha :as s] + [auto-ap.subs :as subs] [auto-ap.entities.account :as entity] [auto-ap.views.utils :refer [bind-field dispatch-event]] + [auto-ap.views.components.typeahead :refer [typeahead-entity]] [re-frame.core :as re-frame])) (def types [:dividend :expense :asset :liability :equity :revenue]) +(def applicabilities [:global :optional :customized]) + -(re-frame/reg-sub - ::can-submit - (fn [db] - true)) (re-frame/reg-sub ::request :<- [::forms/form ::form] - (fn [{{:keys [id location type numeric-code name account-set]} :data}] + (fn [{{:keys [id location type client-overrides applicability numeric-code name account-set]} :data}] {:id id :type (keyword type) + :applicability (keyword applicability) :location (if (clojure.string/blank? location) nil location) :numeric-code numeric-code :name name - :account-set account-set})) + :account-set account-set + :client-overrides (map (fn [client-override] + {:client-id (:id (:client client-override)) + :name (if (str/blank? (:name client-override)) + nil + (:name client-override))}) + client-overrides)})) + +(re-frame/reg-sub + ::can-submit + :<- [::request] + (fn [request] + (s/valid? ::entity/account request))) (re-frame/reg-event-db ::editing - (fn [db [_ which]] + (fn [db [_ which complete-listener]] (-> db - (forms/start-form ::form which)))) + (forms/start-form ::form which complete-listener)))) (re-frame/reg-event-fx ::edited - (fn [{:keys [db]} [_ edit-completed {:keys [upsert-account]}]] - {:db (-> db (forms/saved-form ::form upsert-account)) - :dispatch (conj edit-completed upsert-account)})) + [(forms/triggers-saved ::form :upsert-account)] + (fn [{:keys [db]} [_ {:keys [upsert-account]}]])) + +(re-frame/reg-event-db + ::add-client-override + [(forms/in-form ::form)] + (fn [form] + (update form :data (fn [data] + (-> data + (update :client-overrides conj (:new-client-override data)) + (dissoc :new-client-override)))))) (re-frame/reg-event-fx ::saving - (fn [{:keys [db]} [_ edit-completed]] + (fn [{:keys [db]} _] (when @(re-frame/subscribe [::can-submit]) (let [{{:keys [id type name numeric-code account-set]} :data :as data} @(re-frame/subscribe [::forms/form ::form])] {:db (forms/loading db ::form ) @@ -50,88 +71,88 @@ :operation/name "UpsertAccount"} :venia/queries [{:query/data [:upsert-account {:account @(re-frame/subscribe [::request])} - [:id :type :name :account-set :numeric-code :location]]}]} - :on-success [::edited edit-completed] + [:id :type :name :account-set :numeric-code :location :applicability [:client-overrides [:name [:client [:id :name]]]]]]}]} + :on-success [::edited] :on-error [::forms/save-error ::form]}})))) -(defn form [{:keys [edit-completed]}] +(def account-form (forms/vertical-form {:can-submit [::can-submit] + :change-event [::forms/change ::form] + :submit-event [::saving] + :id ::form})) + +(defn form [_] (let [{error :error account :data } @(re-frame/subscribe [::forms/form ::form]) - change-event [::forms/change ::form]] + {:keys [form field field-holder raw-field error-notification submit-button]} account-form + change-event [::forms/change ::form]] - [forms/side-bar-form {:form ::form} - [:form - [:h1.title.is-2 (if (:id account) - "Edit account" - "Add account")] + ^{:key (:id account)} + [form {:title (if (:id account) + "Edit account" + "Add account")} - [:div.field - [:p.help "Account Set"] - [:div.control - [bind-field + [field "Account Set" + [:input.input {:type "text" + :field :account-set + :disabled (boolean (:id account)) + :spec ::entity/account-set}]] + + + [field "Code" + [:input.input {:type "text" + :field :numeric-code + :disabled (boolean (:id account)) + :spec ::entity/numeric-code}]] + + + [field "Name" + [:input.input {:type "text" + :field :name + :spec ::entity/name}]] + + + [field-holder "Account Type" + [:div.select + [raw-field + [:select {:type "select" + :field :type + :spec (set types)} + (map (fn [l] + [:option {:value (name l)} (str/capitalize (name l))]) types)]]]] + + + [field "Location" + [:input.input.known-field.location {:type "text" + :field :location + :spec ::entity/location}]] + + [:h2.subtitle "Client"] + [field-holder "Applicability" + [:div.select + [raw-field + [:select {:type "select" + :field :applicability + :spec (set applicabilities)} + (map (fn [l] + [:option {:value (name l)} (str/capitalize (name l))]) applicabilities)]]]] + [field-holder "Customizations" + [:div.field.has-addons + [:p.control + ^{:key (count (:client-overrides account))} ;; resets after adding + [raw-field + [typeahead-entity {:matches @(re-frame/subscribe [::subs/clients]) + :match->text :name + :type "typeahead" + :field [:new-client-override :client]}]]] + [:p.control + [raw-field [:input.input {:type "text" - :field :account-set - :disabled (if (:id account) - "disabled" - "") - :spec ::entity/account-set - :event change-event - :subscription account}]]]] + :placeholder "Bubblegum" + :field [:new-client-override :name]}]]] + [:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-client-override])} "Add"]]]] + [:ul + (for [client-override (:client-overrides account)] + ^{:key (:name client-override)} + [:li (:name (:client client-override)) "-" (:name client-override)])] + [error-notification] - [:div.field - [:p.help "Code"] - [:div.control - [bind-field - [:input.input {:type "text" - :field :numeric-code - :disabled (if (:id account) - "disabled" - "") - :spec ::entity/numeric-code - :event change-event - :subscription account}]]]] - - [:div.field - [:p.help "Name"] - [:div.control - [bind-field - [:input.input {:type "text" - :field :name - :spec ::entity/name - :event change-event - :subscription account}]]]] - - - - [:div.field - [:p.help "Account Type"] - [:div.control - [:div.select - [bind-field - [:select {:type "select" - :field :type - :spec (set types) - :event change-event - :subscription account} - (map (fn [l] - [:option {:value (name l)} (str/capitalize (name l))]) types)]]]]] - - - [:div.field - [:p.help "Location"] - [:div.control - [bind-field - [:input.input.known-field.location {:type "text" - :field :location - :spec ::entity/location - :event change-event - :subscription account}]]]] - - (when error - [:div.notification.is-warning.animated.fadeInUp - error]) - - [:button.button.is-large.is-primary {:disabled (if (s/valid? ::entity/account @(re-frame/subscribe [::request])) - "" - "disabled") - :on-click (dispatch-event [::saving edit-completed]) - :class (str @(re-frame/subscribe [::forms/loading-class ::form]) (when error " animated shake"))} "Save"]]])) + [submit-button "Save"]]))