check filter refactor.

This commit is contained in:
Bryce Covert
2020-04-28 13:05:45 -07:00
parent 716b97f701
commit e69de57611
8 changed files with 393 additions and 226 deletions

View File

@@ -5,7 +5,7 @@
"login/" :login
"needs-activation/" :needs-activation
"needs-activation" :needs-activation
"checks/" :checks
"payments/" :payments
"admin/" {"" :admin
"clients" :admin-clients
"users" :admin-users

View File

@@ -71,8 +71,8 @@
[: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 = :checks)]
:href (bidi/path-for routes/routes :checks)}
[:a.navbar-item {:class [(active-when ap = :payments)]
:href (bidi/path-for routes/routes :payments)}
"Payments" ]
[:a.navbar-item {:class [(active-when ap = :transactions)]
:href (bidi/path-for routes/routes :transactions)}

View File

@@ -16,7 +16,7 @@
[auto-ap.views.pages.ledger.external-import :refer [external-import-page]]
[auto-ap.views.pages.ledger.profit-and-loss :refer [profit-and-loss-page]]
[auto-ap.views.pages.login :refer [login-page]]
[auto-ap.views.pages.checks :refer [checks-page]]
[auto-ap.views.pages.payments :refer [payments-page]]
[auto-ap.views.pages.admin :refer [admin-page]]
[auto-ap.views.pages.home :refer [home-page]]
[auto-ap.views.pages.admin.clients :refer [admin-clients-page]]
@@ -46,8 +46,8 @@
(defmethod page :invoices [_]
(unpaid-invoices-page {}))
(defmethod page :checks [_]
(checks-page))
(defmethod page :payments [_]
[payments-page])
(defmethod page :transactions [_]
(transactions-page {}))

View File

@@ -7,7 +7,6 @@
[goog.string :as gstring]
[clojure.spec.alpha :as s]
[auto-ap.views.components.sorter :refer [sorted-column]]
[auto-ap.views.components.vendor-filter :refer [vendor-filter]]
[auto-ap.views.components.date-range-filter :refer [date-range-filter]]
[auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.components.typeahead :refer [typeahead]]
@@ -16,6 +15,8 @@
[auto-ap.events :as events]
[auto-ap.views.utils :refer [dispatch-event date->str bind-field nf with-user]]
[auto-ap.utils :refer [by]]
[auto-ap.views.pages.payments.side-bar :as side-bar]
[auto-ap.views.pages.payments.table :as table]
[auto-ap.views.pages.check :as check]
[auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table]
[auto-ap.subs :as subs]))
@@ -30,31 +31,35 @@
(re-frame/reg-sub
::params
(fn [db]
(-> db (::params {}))))
:<- [::subs/client]
:<- [::side-bar/filter-params]
:<- [::table/table-params]
(fn [[client filter-params table-params]]
(cond-> {}
client (assoc :client-id (:id client))
(seq filter-params) (merge filter-params)
(seq table-params) (merge @(re-frame/subscribe [::table/table-params])))))
(re-frame/reg-event-fx
::params-change
[with-user]
(fn [{:keys [user db]}[_ params]]
{:db (-> db
(assoc-in [:status :loading] true)
(assoc-in [::params] params))
:graphql {:token user
:query-obj {:venia/queries [[:payment_page
(-> params
(assoc :client-id (:id @(re-frame/subscribe [::subs/client])))
(dissoc :check-number-like-current))
[[:payments [:id :status :amount :type :check_number :s3_url
[:bank-account [:name]]
:date [:vendor [:name :id]] [:client [:name :id]]]]
:total
:start
:end]]]}
:on-success [::received]
:on-error [::events/page-failed]}}))
(fn [{:keys [user db]}[_ ]]
(let [new-params @(re-frame/subscribe [::params])]
(when (not= (::last-params db) new-params)
{:db (-> db
(assoc-in [:status :loading] true)
(assoc-in [::last-params] new-params))
:graphql {:token user
:query-obj {:venia/queries [[:payment_page
@(re-frame/subscribe [::params])
[[:payments [:id :status :amount :type :check_number :s3_url
[:bank-account [:name]]
:date [:vendor [:name :id]] [:client [:name :id]]]]
:total
:start
:end]]]}
:on-success [::received]
:on-error [::events/page-failed]}}))))
(re-frame/reg-event-fx
::void-check
@@ -63,7 +68,6 @@
{:token (-> db :user)
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "VoidPayment"}
:venia/queries [{:query/data [:void-payment
{:payment-id (:id payment)}
[:id :status [:bank-account [:name]] :amount :check_number :s3_url :date [:vendor [:name :id]] [:client [:name :id]]]]}]}
@@ -88,189 +92,30 @@
(assoc-in [:status :loading] false))))
(re-frame/reg-event-fx
::invalidated
(fn [cofx [_ params]]
{:dispatch [::params-change @(re-frame/subscribe [::params])]}))
::unmounted
(fn [{:keys [db]} _]
(println "HERE")
{:db (dissoc db ::last-params ::table/table-params ::side-bar/filters ::payment-page)}))
(re-frame/reg-event-db
::change-selected-vendor
(fn [db [_ key value]]
(let [[key] key
updated (assoc-in db [::payment-page :vendor-filter key] value)]
(if (and (= key :vendor-id)
(not= value (get-in db [::params :vendor-id])))
(do
(re-frame/dispatch [::params-change (assoc (::params updated) :vendor-id value)])
(assoc-in updated [::params :vendor-id] value))
updated))))
(re-frame/reg-event-fx
::change-selected-date-range
(fn [{:keys [db]} [_ key value]]
(let [[key] key
updated (-> db
(assoc-in [::payment-page :date-range-filter key] value)
(assoc-in [::params :date-range key] value))]
{:dispatch [::params-change (::params updated)]
:db updated})))
(re-frame/reg-event-fx
::check-number-like-current-changed
(fn [{:keys [db]} [_ params check-like]]
{:db (assoc-in db [::params :check-number-like-current] check-like )
:dispatch-debounce {:event [::check-number-like-settled check-like]
:time 500
:key ::check-number-like}}))
(re-frame/reg-event-fx
::check-number-like-settled
(fn [{:keys [db]} [_ check-like]]
{:dispatch [::params-change (assoc (::params db) :check-number-like check-like :start 0) ]}))
(defn check-number-filter []
(let [{:keys [check-number-like-current] :as params} @(re-frame/subscribe [::params])]
[:div.field
[:div.control [:input.input {:placeholder "10001"
:value check-number-like-current
:on-change (fn [x]
(re-frame/dispatch [::check-number-like-current-changed params (.. x -target -value) ]))} ]]]))
(defn check-table [{:keys [id payment-page status on-params-change vendors params check-boxes checked on-check-changed expense-event]}]
(let [#_#_state (reagent/atom (or @params {}))
selected-client @(re-frame/subscribe [::subs/client])
opc (fn [p]
(on-params-change (merge @params p)))]
(fn [{:keys [id payment-page status on-params-change vendors checked]}]
(let [{:keys [sort]} @params
{:keys [payments start end count total]} @payment-page
selected-client @(re-frame/subscribe [::subs/client])
percentage-size (if selected-client "50%" "33%")]
[:div
[:div.level
[:div.level-left
[:div.level-item
[paginator {:start start :end end :count count :total total
:on-change (fn [p ]
(on-params-change (merge @params p)))}]]
[:div.level-item
[sort-by-list {:sort sort
:on-change opc}]]]]
[:table.table.is-fullwidth
[:thead
[:tr
(when-not selected-client
[sorted-column {:on-sort opc
:style {:width percentage-size :cursor "pointer"}
:sort-key "client"
:sort-name "Client"
:sort sort}
"Client"])
[sorted-column {:on-sort opc
:style {:width percentage-size :cursor "pointer"}
:sort-key "vendor"
:sort-name "Vendor"
:sort sort}
"Vendor"]
[sorted-column {:on-sort opc
:style {:width percentage-size :cursor "pointer"}
:sort-key "bank-account"
:sort-name "Bank Account"
:sort sort}
"Bank Account"]
[sorted-column {:on-sort opc
:style {:width percentage-size :cursor "pointer"}
:sort-key "check-number"
:sort-name "Check #"
:sort sort}
"Check #"]
[sorted-column {:on-sort opc
:style {:width "8em" :cursor "pointer"}
:sort-key "date"
:sort-name "Date"
:sort sort}
"Date"]
[sorted-column {:on-sort opc
:style {:width "8em" :cursor "pointer"}
:class "has-text-right"
:sort-key "amount"
:sort-name "Amount"
:sort sort}
"Amount"]
[sorted-column {:on-sort opc
:style {:width "8em" :cursor "pointer"}
:sort-key "status"
:sort-name "Status"
:sort sort}
"Status"]
[:th {:style {:width "8em"}} "" ]]]
[:tbody
(if (:loading @status)
[:tr
[:td {:col-span 5}
[:i.fa.fa-spin.fa-spinner]]]
(for [{:keys [client s3-url bank-account payments type check-number date amount id vendor status] :as i} (:payments @payment-page)]
^{:key id}
[:tr {:class (:class i)}
(when-not selected-client
[:td (:name client)])
[:td (:name vendor)]
[:td (:name bank-account)]
[:td (cond
(= :cash type) "Cash"
(= :debit type) "Debit"
:else (if s3-url
[:a {:href s3-url :target "_new"} [:a.button [:span.icon [:i.fa.fa-share-square-o]]
[:span (str " " check-number )]]]
check-number))]
[:td (date->str date) ]
[:td.has-text-right (nf amount )]
[:td status]
[:td
(when (or (= :pending status)
(and (#{":cash" :cash} type)
(not= :voided status)))
[:button.button.is-warning.is-outlined {:on-click (dispatch-event [::void-check i])} [:span [:span.icon [:i.fa.fa-minus-circle]]]])]]))]]]))))
(def checks-content
(defn content []
(with-meta
(fn []
(let [current-client @(re-frame/subscribe [::subs/client])]
[:div
[:h1.title "Checks"]
[check-table {:id :payments
:params (re-frame/subscribe [::params])
[:h1.title "Payments"]
[table/table {:id :payments
:payment-page (re-frame/subscribe [::payment-page])
:status (re-frame/subscribe [::subs/status])
:on-params-change (fn [params]
(re-frame/dispatch [::params-change params]))}]]))
:void-event [::void-check]}]]))
{:component-will-mount #(re-frame/dispatch-sync [::params-change {}]) }))
(defn checks-page []
[side-bar-layout {:side-bar
[:div
[:p.menu-label "Vendor"]
[:div [vendor-filter {:on-change-event [::change-selected-vendor]
:value (:vendor-filter @(re-frame/subscribe [::payment-page]))
:vendors @(re-frame/subscribe [::subs/vendors])}]]
[:p.menu-label "Date Range"]
[:div
[date-range-filter
{:on-change-event [::change-selected-date-range]
:value (:date-range-filter @(re-frame/subscribe [::payment-page]))}]]
[:p.menu-label "Check #"]
[:div [check-number-filter]]
]
:main [checks-content]}])
(defn payments-page []
(reagent/create-class
{:display-name "payments-page"
:component-did-mount #(re-frame/dispatch [::params-change {}])
:component-will-unmount #(re-frame/dispatch [::unmounted])
:reagent-render
(fn []
[side-bar-layout {:side-bar [side-bar/side-bar]
:main [content]}])}))

View File

@@ -42,7 +42,6 @@
(let [ap @(re-frame/subscribe [::subs/active-page])
user @(re-frame/subscribe [::subs/user])]
[:div
[:ul.menu-list
[:li.menu-item
[:a.item {:href (bidi/path-for routes/routes :ledger)
@@ -54,14 +53,12 @@
:class [(active-when ap = :profit-and-loss)]}
[:span {:class "icon icon-performance-increase-1" :style {:font-size "25px"}}]
[:span {:class "name"} "Profit & Loss"]]]
[:li.menu-item
[:a.item {:href (bidi/path-for routes/routes :balance-sheet)
:class [(active-when ap = :balance-sheet)]}
[:span {:class "icon icon-accounting-abacus" :style {:font-size "25px"}}]
[:span {:class "name"} "Balance Sheet"]]]
(when (= "admin" (:user/role user))
[:li.menu-item
@@ -69,23 +66,26 @@
:class [(active-when ap = :external-import-ledger)]}
[:span.icon [:i {:class "fa fa-download"}]]
[:span {:class "name"} "External Import"]]])
(when (= :ledger ap)
[:div
[:p.menu-label "Bank Account"]
[:div
[bank-account-filter
{:on-change-event [::filter-changed :bank-account]
:value @(re-frame/subscribe [::filter :bank-account])
:bank-accounts @(re-frame/subscribe [::subs/bank-accounts])}]]
[:p.menu-label "Bank Account"]
[:div
[bank-account-filter
{:on-change-event [::filter-changed :bank-account]
:value @(re-frame/subscribe [::filter :bank-account])
:bank-accounts @(re-frame/subscribe [::subs/bank-accounts])}]]
[:p.menu-label "Vendor"]
[:div
[typeahead-entity {:matches @(re-frame/subscribe [::subs/vendors])
:on-change #(re-frame/dispatch [::filter-changed :vendor %])
:match->text :name
:type "typeahead-entity"
:value @(re-frame/subscribe [::filter :vendor])}]]
[:p.menu-label "Date Range"]
[:div
[date-range-filter
{:on-change-event [::filter-changed :date-range]
:value @(re-frame/subscribe [::filter :date-range])}]]])
[:p.menu-label "Vendor"]
[:div
[typeahead-entity {:matches @(re-frame/subscribe [::subs/vendors])
:on-change #(re-frame/dispatch [::filter-changed :vendor %])
:match->text :name
:type "typeahead-entity"
:value @(re-frame/subscribe [::filter :vendor])}]]
[:p.menu-label "Date Range"]
[:div
[date-range-filter
{:on-change-event [::filter-changed :date-range]
:value @(re-frame/subscribe [::filter :date-range])}]]]]))
]]))

View File

@@ -0,0 +1,111 @@
(ns auto-ap.views.pages.payments
(:require [re-frame.core :as re-frame]
[auto-ap.entities.clients :as client]
[auto-ap.entities.vendors :as vendor]
[auto-ap.entities.invoice :as invoice]
[reagent.core :as reagent]
[goog.string :as gstring]
[clojure.spec.alpha :as s]
[auto-ap.views.components.sorter :refer [sorted-column]]
[auto-ap.views.components.date-range-filter :refer [date-range-filter]]
[auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.components.typeahead :refer [typeahead]]
[auto-ap.views.components.paginator :refer [paginator]]
[auto-ap.views.components.sort-by-list :refer [sort-by-list]]
[auto-ap.events :as events]
[auto-ap.views.utils :refer [dispatch-event date->str bind-field nf with-user]]
[auto-ap.utils :refer [by]]
[auto-ap.views.pages.payments.side-bar :as side-bar]
[auto-ap.views.pages.payments.table :as table]
[auto-ap.views.pages.check :as check]
[auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table]
[auto-ap.subs :as subs]))
(re-frame/reg-sub
::payment-page
(fn [db]
(-> db ::payment-page)))
(re-frame/reg-sub
::params
:<- [::subs/client]
:<- [::side-bar/filter-params]
:<- [::table/table-params]
(fn [[client filter-params table-params]]
(cond-> {}
client (assoc :client-id (:id client))
(seq filter-params) (merge filter-params)
(seq table-params) (merge @(re-frame/subscribe [::table/table-params])))))
(re-frame/reg-event-fx
::params-change
[with-user]
(fn [{:keys [user db]}[_ ]]
(let [new-params @(re-frame/subscribe [::params])]
(when (not= (::last-params db) new-params)
{:db (-> db
(assoc-in [:status :loading] true)
(assoc-in [::last-params] new-params))
:graphql {:token user
:query-obj {:venia/queries [[:payment_page
@(re-frame/subscribe [::params])
[[:payments [:id :status :amount :type :check_number :s3_url
[:bank-account [:name]]
:date [:vendor [:name :id]] [:client [:name :id]]]]
:total
:start
:end]]]}
:on-success [::received]
:on-error [::events/page-failed]}}))))
(re-frame/reg-event-fx
::void-check
(fn [{:keys [db]} [_ payment]]
{:graphql
{:token (-> db :user)
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "VoidPayment"}
:venia/queries [{:query/data [:void-payment
{:payment-id (:id payment)}
[:id :status [:bank-account [:name]] :amount :check_number :s3_url :date [:vendor [:name :id]] [:client [:name :id]]]]}]}
:on-success [::payment-voided]}}))
(re-frame/reg-event-db
::payment-voided
(fn [db [_ {:keys [void-payment]}]]
(-> db
(update-in [::payment-page :payments] (fn [payments]
(mapv (fn [c]
(if (= (:id c) (:id void-payment))
(assoc void-payment :class "live-removed")
c))
payments))))))
(re-frame/reg-event-db
::received
(fn [db [_ data]]
(-> db
(update ::payment-page merge (first (:payment-page data)))
(assoc-in [:status :loading] false))))
(def checks-content
(with-meta
(fn []
(let [current-client @(re-frame/subscribe [::subs/client])]
[:div
[:h1.title "Checks"]
[table/table {:id :payments
:payment-page (re-frame/subscribe [::payment-page])
:status (re-frame/subscribe [::subs/status])
:void-event [::void-check]
}]]))
{:component-will-mount #(re-frame/dispatch-sync [::params-change {}]) }))
(defn payments-page []
[side-bar-layout {:side-bar [side-bar/side-bar]
:main [checks-content]}])

View File

@@ -0,0 +1,87 @@
(ns auto-ap.views.pages.payments.side-bar
(:require [auto-ap.routes :as routes]
[auto-ap.subs :as subs]
[auto-ap.views.utils :refer [active-when]]
[auto-ap.views.components.vendor-filter :refer [vendor-filter]]
[auto-ap.views.components.date-range-filter :refer [date-range-filter]]
[auto-ap.views.components.bank-account-filter :refer [bank-account-filter]]
[auto-ap.views.components.typeahead :refer [typeahead-entity]]
[bidi.bidi :as bidi]
[re-frame.core :as re-frame]))
(re-frame/reg-sub
::filters
(fn [db ]
(::filters db {})))
(re-frame/reg-sub
::filter
:<- [::filters]
(fn [filters [_ which]]
(filters which)))
(re-frame/reg-sub
::filter-params
:<- [::filters]
(fn [filters]
{:vendor-id (:id (:vendor filters))
:date-range (:date-range filters)
:check-number-like (:settled (:check-number-like filters))}))
(re-frame/reg-event-fx
::filter-changed
(fn [{:keys [db]} [_ & params]]
(let [[a b c] params
[which val] (if (= 3 (count params))
[(into [a] b) c]
[[a] b])]
{:dispatch [:auto-ap.views.pages.payments/params-change]
:db (assoc-in db (into [::filters] which) val)})))
(re-frame/reg-event-fx
::check-number-like-settled
[(re-frame/path [::filters :check-number-like])]
(fn [{:keys [db]} [_ description]]
{:db (assoc db :settled description)
:dispatch [::filter-changed :check-number-like [:settled] description]}))
(re-frame/reg-event-fx
::check-number-like-changed
[(re-frame/path [::filters :check-number-like])]
(fn [{:keys [db]} [_ description]]
{:dispatch-debounce
{:event [::check-number-like-settled description]
:time 500
:key ::check-number-like}
:db (assoc db :raw description)}))
(defn check-number-filter []
[:div.field
[:div.control [:input.input {:placeholder "10001"
:value (:raw @(re-frame/subscribe [::filter :check-number-like]))
:on-change (fn [x]
(re-frame/dispatch [::check-number-like-changed (.. x -target -value) ]))} ]]])
(defn side-bar []
(let [ap @(re-frame/subscribe [::subs/active-page])
user @(re-frame/subscribe [::subs/user])]
[:div
[:div
[:p.menu-label "Vendor"]
[:div
[typeahead-entity {:matches @(re-frame/subscribe [::subs/vendors])
:on-change #(re-frame/dispatch [::filter-changed :vendor %])
:match->text :name
:type "typeahead-entity"
:value @(re-frame/subscribe [::filter :vendor])}]]
[:p.menu-label "Date Range"]
[:div
[date-range-filter
{:on-change-event [::filter-changed :date-range]
:value @(re-frame/subscribe [::filter :date-range])}]]
[:p.menu-label "Check #"]
[:div [check-number-filter]]]]))

View File

@@ -0,0 +1,124 @@
(ns auto-ap.views.pages.payments.table
(:require [auto-ap.subs :as subs]
[auto-ap.views.components.paginator :refer [paginator]]
[auto-ap.views.components.sorter :refer [sorted-column]]
[auto-ap.views.components.sort-by-list :refer [sort-by-list]]
[auto-ap.views.utils :refer [date->str dispatch-event nf]]
[goog.string :as gstring]
[re-frame.core :as re-frame]))
(re-frame/reg-sub
::table-params
(fn [db]
(::table-params db)))
(re-frame/reg-event-fx
::params-changed
[(re-frame/path [::table-params])]
(fn [{table-params :db} [_ params :as z]]
{:db (merge table-params params)
:dispatch [:auto-ap.views.pages.payments/params-change]}))
(defn row [{check :check
selected-client :selected-client
void-event :void-event
}]
(let [{:keys [client s3-url bank-account payments type check-number date amount id vendor status] :as check} check]
[:tr {:class (:class check)}
(when-not selected-client
[:td (:name client)])
[:td (:name vendor)]
[:td (:name bank-account)]
[:td (cond
(= :cash type) "Cash"
(= :debit type) "Debit"
:else (if s3-url
[:a {:href s3-url :target "_new"} [:a.button [:span.icon [:i.fa.fa-share-square-o]]
[:span (str " " check-number )]]]
check-number))]
[:td (date->str date) ]
[:td.has-text-right (nf amount )]
[:td status]
[:td
(when (or (= :pending status)
(and (#{":cash" :cash} type)
(not= :voided status)))
[:button.button.is-warning.is-outlined {:on-click (dispatch-event (conj void-event check))} [:span [:span.icon [:i.fa.fa-minus-circle]]]])]]))
(defn table [{:keys [id payment-page status void-event]}]
(let [{:keys [sort]} @(re-frame/subscribe [::table-params])
{:keys [payments start end count total]} @payment-page
selected-client @(re-frame/subscribe [::subs/client])
percentage-size (if selected-client "50%" "33%")
opc (fn [e]
(re-frame/dispatch [::params-changed e]))]
[:div
[:div.level
[:div.level-left
[:div.level-item
[paginator {:start start :end end :count count :total total
:on-change opc}]]
[:div.level-item
[sort-by-list {:sort sort
:on-change opc}]]]]
[:table.table.is-fullwidth
[:thead
[:tr
(when-not selected-client
[sorted-column {:on-sort opc
:style {:width percentage-size :cursor "pointer"}
:sort-key "client"
:sort-name "Client"
:sort sort}
"Client"])
[sorted-column {:on-sort opc
:style {:width percentage-size :cursor "pointer"}
:sort-key "vendor"
:sort-name "Vendor"
:sort sort}
"Vendor"]
[sorted-column {:on-sort opc
:style {:width percentage-size :cursor "pointer"}
:sort-key "bank-account"
:sort-name "Bank Account"
:sort sort}
"Bank Account"]
[sorted-column {:on-sort opc
:style {:width percentage-size :cursor "pointer"}
:sort-key "check-number"
:sort-name "Check #"
:sort sort}
"Check #"]
[sorted-column {:on-sort opc
:style {:width "8em" :cursor "pointer"}
:sort-key "date"
:sort-name "Date"
:sort sort}
"Date"]
[sorted-column {:on-sort opc
:style {:width "8em" :cursor "pointer"}
:class "has-text-right"
:sort-key "amount"
:sort-name "Amount"
:sort sort}
"Amount"]
[sorted-column {:on-sort opc
:style {:width "8em" :cursor "pointer"}
:sort-key "status"
:sort-name "Status"
:sort sort}
"Status"]
[:th {:style {:width "8em"}} "" ]]]
[:tbody
(if (:loading @status)
[:tr
[:td {:col-span 5}
[:i.fa.fa-spin.fa-spinner]]]
(for [check (:payments @payment-page)]
^{:key (:id check)}
[row {:check check
:selected-client selected-client
:void-event void-event}]))]]]))