Removes old login page, more progress on transactions
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -245,25 +245,25 @@
|
|||||||
vec)
|
vec)
|
||||||
[]))
|
[]))
|
||||||
|
|
||||||
|
(def default-read '[* {:transaction/client [:client/name :db/id :client/code :client/locations :client/groups]
|
||||||
|
:transaction/approval-status [:db/ident :db/id]
|
||||||
|
:transaction/bank-account [:bank-account/name :bank-account/code :bank-account/yodlee-account-id :db/id :bank-account/locations :bank-account/current-balance]
|
||||||
|
:transaction/vendor [:db/id :vendor/name]
|
||||||
|
:transaction/matched-rule [:db/id :transaction-rule/note]
|
||||||
|
:transaction/forecast-match [:db/id :forecasted-transaction/identifier]
|
||||||
|
:transaction/accounts [:transaction-account/amount
|
||||||
|
:db/id
|
||||||
|
:transaction-account/location
|
||||||
|
{:transaction-account/account [:account/name :db/id
|
||||||
|
:account/location
|
||||||
|
{:account/client-overrides [:account-client-override/name
|
||||||
|
{:account-client-override/client [:db/id]}]}]}]
|
||||||
|
:transaction/yodlee-merchant [:db/id :yodlee-merchant/yodlee-id :yodlee-merchant/name]
|
||||||
|
:transaction/plaid-merchant [:db/id :plaid-merchant/name]}])
|
||||||
|
|
||||||
(defn get-by-id [id]
|
(defn get-by-id [id]
|
||||||
(->
|
(->
|
||||||
(dc/pull (dc/db conn)
|
(dc/pull (dc/db conn) default-read id)
|
||||||
'[* {:transaction/client [:client/name :db/id :client/code :client/locations :client/groups]
|
|
||||||
:transaction/approval-status [:db/ident :db/id]
|
|
||||||
:transaction/bank-account [:bank-account/name :bank-account/code :bank-account/yodlee-account-id :db/id :bank-account/locations :bank-account/current-balance]
|
|
||||||
:transaction/vendor [:db/id :vendor/name]
|
|
||||||
:transaction/matched-rule [:db/id :transaction-rule/note]
|
|
||||||
:transaction/forecast-match [:db/id :forecasted-transaction/identifier]
|
|
||||||
:transaction/accounts [:transaction-account/amount
|
|
||||||
:db/id
|
|
||||||
:transaction-account/location
|
|
||||||
{:transaction-account/account [:account/name :db/id
|
|
||||||
:account/location
|
|
||||||
{:account/client-overrides [:account-client-override/name
|
|
||||||
{:account-client-override/client [:db/id]}]}]}]
|
|
||||||
:transaction/yodlee-merchant [:db/id :yodlee-merchant/yodlee-id :yodlee-merchant/name]
|
|
||||||
:transaction/plaid-merchant [:db/id :plaid-merchant/name]}]
|
|
||||||
id)
|
|
||||||
(update :transaction/date coerce/from-date)
|
(update :transaction/date coerce/from-date)
|
||||||
(update :transaction/post-date coerce/from-date)
|
(update :transaction/post-date coerce/from-date)
|
||||||
(dissoc :transaction/id)))
|
(dissoc :transaction/id)))
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
(ns auto-ap.routes.auth
|
(ns auto-ap.routes.auth
|
||||||
(:require
|
(:require
|
||||||
[auto-ap.datomic.users :as users]
|
[auto-ap.datomic.users :as users]
|
||||||
|
[auto-ap.logging :as alog]
|
||||||
|
[auto-ap.session-version :as session-version]
|
||||||
|
[auto-ap.routes.dashboard :as dashboard]
|
||||||
|
[auto-ap.ssr-routes :as ssr-routes]
|
||||||
|
[bidi.bidi :as bidi]
|
||||||
[buddy.sign.jwt :as jwt]
|
[buddy.sign.jwt :as jwt]
|
||||||
[clj-http.client :as http]
|
[clj-http.client :as http]
|
||||||
[clj-time.core :as time]
|
[clj-time.core :as time]
|
||||||
[auto-ap.logging :as alog]
|
|
||||||
[config.core :refer [env]]
|
|
||||||
[com.brunobonacci.mulog :as mu]
|
|
||||||
[clojure.java.io :as io]
|
|
||||||
[clojure.edn :as edn]
|
[clojure.edn :as edn]
|
||||||
[auto-ap.session-version :as session-version]))
|
[clojure.java.io :as io]
|
||||||
|
[com.brunobonacci.mulog :as mu]
|
||||||
|
[config.core :refer [env]]))
|
||||||
|
|
||||||
(def google-client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com")
|
(def google-client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com")
|
||||||
(def google-client-secret "OC-WemHurPXYpuIw5cT-B90g")
|
(def google-client-secret "OC-WemHurPXYpuIw5cT-B90g")
|
||||||
@@ -91,7 +94,9 @@
|
|||||||
|
|
||||||
(if-let [jwt (user->jwt user token)]
|
(if-let [jwt (user->jwt user token)]
|
||||||
{:status 301
|
{:status 301
|
||||||
:headers {"Location" (str (or (not-empty state) "/") "?jwt="
|
:headers {"Location" (str (or (not-empty state)
|
||||||
|
(bidi/path-for ssr-routes/only-routes
|
||||||
|
::dashboard/page)) "?jwt="
|
||||||
(jwt/sign jwt
|
(jwt/sign jwt
|
||||||
(:jwt-secret env)
|
(:jwt-secret env)
|
||||||
{:alg :hs512}))}
|
{:alg :hs512}))}
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
(ns auto-ap.ssr.auth
|
(ns auto-ap.ssr.auth
|
||||||
(:require [auto-ap.session-version :as session-version]
|
(:require
|
||||||
[buddy.sign.jwt :as jwt]
|
[auto-ap.session-version :as session-version]
|
||||||
[config.core :refer [env]]))
|
[auto-ap.ssr.components :as com]
|
||||||
|
[auto-ap.ssr.hx :as hx]
|
||||||
|
[auto-ap.ssr.svg :as svg]
|
||||||
|
[auto-ap.ssr.ui :refer [base-page]]
|
||||||
|
[buddy.sign.jwt :as jwt]
|
||||||
|
[config.core :refer [env]]
|
||||||
|
[hiccup.util :as hu]))
|
||||||
|
|
||||||
(defn logout [request]
|
(defn logout [request]
|
||||||
{:status 301
|
{:status 301
|
||||||
@@ -16,3 +22,86 @@
|
|||||||
{:alg :hs512})
|
{:alg :hs512})
|
||||||
:exp)
|
:exp)
|
||||||
:version session-version/current-session-version}})
|
:version session-version/current-session-version}})
|
||||||
|
|
||||||
|
(defn login-url
|
||||||
|
([] (login-url nil))
|
||||||
|
([next]
|
||||||
|
|
||||||
|
(let [client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com"
|
||||||
|
redirect-uri (str (:base-url env) "/api/oauth")]
|
||||||
|
(str (hu/url "https://accounts.google.com/o/oauth2/auth"
|
||||||
|
(cond-> {"access_type" "online"
|
||||||
|
"client_id" client-id
|
||||||
|
"redirect_uri" redirect-uri
|
||||||
|
"response_type" "code"
|
||||||
|
"max_auth_age" "0"
|
||||||
|
"scope" "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"}
|
||||||
|
next (assoc "state" (hu/url-encode next))))))))
|
||||||
|
|
||||||
|
(defn- page-contents [request]
|
||||||
|
[:div#app { "@notification.document" "notificationDetails=event.detail.value; showNotification=true"
|
||||||
|
|
||||||
|
:x-data (hx/json {:showError false
|
||||||
|
:errorDetails ""
|
||||||
|
:showNotification false
|
||||||
|
:notificationDetails ""})
|
||||||
|
"@htmx:response-error.camel" "errorDetails = $event.detail.xhr.response; showError=true;"
|
||||||
|
}
|
||||||
|
[:div#app-contents.flex.overflow-hidden
|
||||||
|
[:div#main-content {:class "relative w-full h-full overflow-y-auto px-4 bg-gray-100 dark:bg-gray-900 min-h-content " }
|
||||||
|
[:div#notification-holder
|
||||||
|
[:div.fixed.top-0.right-0.left-0.z-30.mx-auto.max-w-screen-lg.w-screen-lg.my-0.pt-8.rounded-lg {:x-show "showNotification" }
|
||||||
|
[:div.relative
|
||||||
|
[:button.absolute.right-2.top-2.w-6.h-6.z-50.text-blue-400
|
||||||
|
{ "@click" "showNotification=false"}
|
||||||
|
svg/filled-x]]
|
||||||
|
|
||||||
|
[:div.m-4.overflow-auto.z-30.flex.center-items.justify-center.text-blue-800.bg-blue-50.dark:bg-gray-800.dark:text-blue-400.border-blue-300.rounded-lg.border.max-h-96
|
||||||
|
{:x-show "showNotification"
|
||||||
|
"x-transition:enter" "transition duration-300 transform ease-in-out"
|
||||||
|
"x-transition:enter-start" "opacity-0 translate-y-full"
|
||||||
|
"x-transition:enter-end" "opacity-100 translate-y-0"
|
||||||
|
"x-transition:leave" "transition duration-300 transform ease-in-out"
|
||||||
|
"x-transition:leave-start" "opacity-100 translate-y-0"
|
||||||
|
"x-transition:leave-end" "opacity-0 translate-y-full"}
|
||||||
|
|
||||||
|
[:div {:class "p-4 text-lg w-full" :role "alert"}
|
||||||
|
[:div.text-sm
|
||||||
|
[:pre#notification-details.text-xs {:x-html "notificationDetails"}]]]]]]
|
||||||
|
[:div {:x-show "showError"
|
||||||
|
:x-init ""}
|
||||||
|
[:div.fixed.top-0.right-0.left-0.z-30.mx-auto.max-w-screen-lg.w-screen-lg.my-0.pt-8.rounded-lg
|
||||||
|
[:div.relative
|
||||||
|
[:button.absolute.right-2.top-2.w-6.h-6.z-50.text-red-600
|
||||||
|
{ "@click" "showError=false"}
|
||||||
|
svg/filled-x]]
|
||||||
|
|
||||||
|
[:div.m-4.overflow-auto.z-30.flex.center-items.justify-center.text-red-800.bg-red-50.dark:bg-gray-800.dark:text-red-400.border-red-300.rounded-lg.border.max-h-96
|
||||||
|
{:x-show "showError"
|
||||||
|
"x-transition:enter" "transition duration-300"
|
||||||
|
"x-transition:enter-start" "opacity-0"
|
||||||
|
"x-transition:enter-end" "opacity-100"}
|
||||||
|
|
||||||
|
[:div {:class "p-4 mb-4 text-lg w-full" :role "alert"}
|
||||||
|
[:div.inline-block.w-8.h-8.mr-2 svg/alert]
|
||||||
|
[:span.font-medium "Oh, drat! An unexpected error has occurred."]
|
||||||
|
[:div.text-sm {:x-data (hx/json {"expandError" false})}
|
||||||
|
[:p "Integreat staff have been notified and are looking into it. "]
|
||||||
|
[:p "To see error details, " [:a.underline.cursor-pointer {"@click" "expandError=true"} "click here"] "."]
|
||||||
|
[:pre#error-details.text-xs {:x-show "expandError" :x-text "errorDetails"}]]]]]]
|
||||||
|
[:div.p-4.flex.flex-row.justify-center.items-center.h-screen
|
||||||
|
(com/card {:class "animate-slideUp"}
|
||||||
|
|
||||||
|
[:div.p-4
|
||||||
|
[:img {:src "/img/logo-big.png"}]
|
||||||
|
[:div
|
||||||
|
[:a.button.is-large.is-primary {:href (login-url (get (:query-params request) "redirect-to"))} "Login with Google"]]
|
||||||
|
"HELLO"])
|
||||||
|
]]] ])
|
||||||
|
|
||||||
|
(defn login [request]
|
||||||
|
(base-page
|
||||||
|
request
|
||||||
|
(page-contents request)
|
||||||
|
|
||||||
|
"Dashboard"))
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
(ns auto-ap.ssr.components.aside
|
(ns auto-ap.ssr.components.aside
|
||||||
(:require [auto-ap.client-routes :as client-routes]
|
(:require
|
||||||
[auto-ap.graphql.utils :refer [is-admin?]]
|
[auto-ap.client-routes :as client-routes]
|
||||||
[auto-ap.permissions :refer [can?]]
|
[auto-ap.graphql.utils :refer [is-admin?]]
|
||||||
[auto-ap.routes.admin.clients :as ac-routes]
|
[auto-ap.permissions :refer [can?]]
|
||||||
[auto-ap.routes.admin.excel-invoices :as ei-routes]
|
[auto-ap.routes.admin.clients :as ac-routes]
|
||||||
[auto-ap.routes.admin.import-batch :as ib-routes]
|
[auto-ap.routes.admin.excel-invoices :as ei-routes]
|
||||||
[auto-ap.routes.admin.transaction-rules :as transaction-rules]
|
[auto-ap.routes.admin.import-batch :as ib-routes]
|
||||||
[auto-ap.routes.admin.vendors :as v-routes]
|
[auto-ap.routes.admin.transaction-rules :as transaction-rules]
|
||||||
[auto-ap.routes.invoice :as invoice-route]
|
[auto-ap.routes.admin.vendors :as v-routes]
|
||||||
[auto-ap.routes.ledger :as ledger-routes]
|
[auto-ap.routes.dashboard :as dashboard]
|
||||||
[auto-ap.routes.outgoing-invoice :as oi-routes]
|
[auto-ap.routes.invoice :as invoice-route]
|
||||||
[auto-ap.routes.payments :as payment-routes]
|
[auto-ap.routes.ledger :as ledger-routes]
|
||||||
[auto-ap.ssr-routes :as ssr-routes]
|
[auto-ap.routes.outgoing-invoice :as oi-routes]
|
||||||
[auto-ap.ssr.components.tags :as tags]
|
[auto-ap.routes.payments :as payment-routes]
|
||||||
[auto-ap.ssr.hiccup-helper :as hh]
|
[auto-ap.ssr-routes :as ssr-routes]
|
||||||
[auto-ap.ssr.hx :as hx]
|
[auto-ap.ssr.components.tags :as tags]
|
||||||
[auto-ap.ssr.svg :as svg]
|
[auto-ap.ssr.hiccup-helper :as hh]
|
||||||
[bidi.bidi :as bidi]
|
[auto-ap.ssr.hx :as hx]
|
||||||
[hiccup.util :as hu]))
|
[auto-ap.ssr.svg :as svg]
|
||||||
|
[bidi.bidi :as bidi]
|
||||||
|
[hiccup.util :as hu]))
|
||||||
|
|
||||||
(defn menu-button- [params & children]
|
(defn menu-button- [params & children]
|
||||||
[:div
|
[:div
|
||||||
@@ -103,7 +105,8 @@
|
|||||||
|
|
||||||
[:li
|
[:li
|
||||||
(menu-button- {:icon svg/pie
|
(menu-button- {:icon svg/pie
|
||||||
:href "/"}
|
:href (bidi/path-for ssr-routes/only-routes
|
||||||
|
::dashboard/page)}
|
||||||
"Dashboard")]
|
"Dashboard")]
|
||||||
|
|
||||||
(when (can? (:identity request)
|
(when (can? (:identity request)
|
||||||
|
|||||||
@@ -167,7 +167,7 @@
|
|||||||
true (assoc :type (or (:type params) "button"))
|
true (assoc :type (or (:type params) "button"))
|
||||||
true (update :class (fn [c]
|
true (update :class (fn [c]
|
||||||
(cond-> c
|
(cond-> c
|
||||||
true (str " font-medium text-gray-900 bg-white border border-gray-200 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:ring-2 focus:ring-green-700 focus:text-green-700 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-green-500 dark:focus:text-white")
|
true (str " font-medium text-gray-900 bg-white border border-gray-200 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:ring-2 focus:ring-green-700 focus:text-green-700 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-green-500 dark:focus:text-white disabled:opacity-50")
|
||||||
|
|
||||||
(= :small size)
|
(= :small size)
|
||||||
(str " text-xs px-3 py-2")
|
(str " text-xs px-3 py-2")
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
(ns auto-ap.ssr.components.navbar
|
(ns auto-ap.ssr.components.navbar
|
||||||
(:require [auto-ap.graphql.utils :refer [is-admin? limited-clients]]
|
(:require
|
||||||
[auto-ap.ssr-routes :as ssr-routes]
|
[auto-ap.graphql.utils :refer [is-admin? limited-clients]]
|
||||||
[auto-ap.ssr.company-dropdown :as cd]
|
[auto-ap.routes.dashboard :as dashboard]
|
||||||
[auto-ap.ssr.components.buttons :refer [icon-button-]]
|
[auto-ap.ssr-routes :as ssr-routes]
|
||||||
[auto-ap.ssr.components.user-dropdown :as user-dropdown]
|
[auto-ap.ssr.company-dropdown :as cd]
|
||||||
[auto-ap.ssr.svg :as svg]
|
[auto-ap.ssr.components.buttons :refer [icon-button-]]
|
||||||
[bidi.bidi :as bidi]))
|
[auto-ap.ssr.components.user-dropdown :as user-dropdown]
|
||||||
|
[auto-ap.ssr.svg :as svg]
|
||||||
|
[bidi.bidi :as bidi]))
|
||||||
|
|
||||||
(defn navbar- [{:keys [client-selection client identity clients dd-env]}]
|
(defn navbar- [{:keys [client-selection client identity clients dd-env]}]
|
||||||
[:nav {:class "fixed z-30 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700"}
|
[:nav {:class "fixed z-30 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700"}
|
||||||
@@ -17,7 +19,7 @@
|
|||||||
[:span {:class "sr-only"} "Open sidebar"]
|
[:span {:class "sr-only"} "Open sidebar"]
|
||||||
[:svg {:class "w-6 h-6", :aria-hidden "true", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
[:svg {:class "w-6 h-6", :aria-hidden "true", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||||
[:path {:clip-rule "evenodd", :fill-rule "evenodd", :d "M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"}]]]
|
[:path {:clip-rule "evenodd", :fill-rule "evenodd", :d "M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"}]]]
|
||||||
[:a {:href "/" :class "flex ml-2 hidden md:mr-24 sm:inline"}
|
[:a {:href (bidi/path-for ssr-routes/only-routes ::dashboard/page) :class "flex ml-2 hidden md:mr-24 sm:inline"}
|
||||||
[:img {:src "/img/logo-big2.png", :class "h-10", :alt "Integreat logo"}]]
|
[:img {:src "/img/logo-big2.png", :class "h-10", :alt "Integreat logo"}]]
|
||||||
(when-not (= "prod" dd-env) [:div.rounded-full.bg-yellow-200.text-lg.text-yellow-800.px-4.hidden.md:block.mr-8 "environment: " dd-env])]
|
(when-not (= "prod" dd-env) [:div.rounded-full.bg-yellow-200.text-lg.text-yellow-800.px-4.hidden.md:block.mr-8 "environment: " dd-env])]
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
|
|
||||||
(def key->handler
|
(def key->handler
|
||||||
(-> {:logout auth/logout
|
(-> {:logout auth/logout
|
||||||
|
:login auth/login
|
||||||
:impersonate (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin auth/impersonate)))
|
:impersonate (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin auth/impersonate)))
|
||||||
:admin-history (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page)))
|
:admin-history (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page)))
|
||||||
:admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page)))
|
:admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page)))
|
||||||
|
|||||||
@@ -370,7 +370,7 @@
|
|||||||
(:transaction/payment i)
|
(:transaction/payment i)
|
||||||
(conj
|
(conj
|
||||||
{:link (hu/url (bidi/path-for ssr-routes/only-routes
|
{:link (hu/url (bidi/path-for ssr-routes/only-routes
|
||||||
::route/payment-page)
|
::payment-routes/all-page)
|
||||||
{:exact-match-id (:db/id (:transaction/payment i))})
|
{:exact-match-id (:db/id (:transaction/payment i))})
|
||||||
:color :primary
|
:color :primary
|
||||||
:content (format "Payment '%s'" (-> i :transaction/payment :payment/date (atime/unparse-local atime/normal-date)))})
|
:content (format "Payment '%s'" (-> i :transaction/payment :payment/date (atime/unparse-local atime/normal-date)))})
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
[auto-ap.ssr.utils
|
[auto-ap.ssr.utils
|
||||||
:refer [->db-id apply-middleware-to-all-handlers check-allowance
|
:refer [->db-id apply-middleware-to-all-handlers check-allowance
|
||||||
check-location-belongs entity-id html-response modal-response
|
check-location-belongs entity-id html-response modal-response
|
||||||
ref->enum-schema strip wrap-schema-enforce]]
|
ref->enum-schema strip wrap-entity wrap-schema-enforce]]
|
||||||
[auto-ap.time :as atime]
|
[auto-ap.time :as atime]
|
||||||
[bidi.bidi :as bidi]
|
[bidi.bidi :as bidi]
|
||||||
[clj-time.coerce :as coerce]
|
[clj-time.coerce :as coerce]
|
||||||
@@ -261,8 +261,7 @@
|
|||||||
:body (mm/default-step-body
|
:body (mm/default-step-body
|
||||||
{}
|
{}
|
||||||
[:div {:x-data (hx/json {:clientId (or (fc/field-value (:transaction/client fc/*current*))
|
[:div {:x-data (hx/json {:clientId (or (fc/field-value (:transaction/client fc/*current*))
|
||||||
(:db/id (:client request)))
|
(:db/id (:client request))) })}
|
||||||
:vendorId (fc/field-value (:transaction/vendor fc/*current*))})}
|
|
||||||
|
|
||||||
;; Read-only transaction details
|
;; Read-only transaction details
|
||||||
[:div.mb-6.border.rounded-lg.p-4.bg-gray-50
|
[:div.mb-6.border.rounded-lg.p-4.bg-gray-50
|
||||||
@@ -380,8 +379,7 @@
|
|||||||
:placeholder "Search..."
|
:placeholder "Search..."
|
||||||
:url (bidi/path-for ssr-routes/only-routes :vendor-search)
|
:url (bidi/path-for ssr-routes/only-routes :vendor-search)
|
||||||
:value (fc/field-value)
|
:value (fc/field-value)
|
||||||
:content-fn (fn [c] (pull-attr (dc/db conn) :vendor/name c))
|
:content-fn (fn [c] (pull-attr (dc/db conn) :vendor/name c)) })]))
|
||||||
:x-model "vendorId"})]))
|
|
||||||
[:div.mb-4
|
[:div.mb-4
|
||||||
[:span.text-sm.text-gray-500 "Can't find the vendor? "
|
[:span.text-sm.text-gray-500 "Can't find the vendor? "
|
||||||
(com/link {:href (bidi.bidi/path-for
|
(com/link {:href (bidi.bidi/path-for
|
||||||
@@ -525,11 +523,11 @@
|
|||||||
|
|
||||||
|
|
||||||
(defn get-available-payments [request]
|
(defn get-available-payments [request]
|
||||||
(let [tx-id (or (-> request :multi-form-state :snapshot :db/id)
|
(let [tx-id (or (get-in request [:form-params :transaction-id])
|
||||||
|
(-> request :multi-form-state :snapshot :db/id)
|
||||||
(get-in request [:route-params :db/id]))
|
(get-in request [:route-params :db/id]))
|
||||||
tx (when tx-id (d-transactions/get-by-id tx-id))
|
tx (when tx-id (d-transactions/get-by-id tx-id))
|
||||||
client-id (or (get-in request [:multi-form-state :snapshot :transaction/client])
|
client-id (-> tx :transaction/client :db/id)
|
||||||
(get-in request [:client :db/id]))
|
|
||||||
payments (when client-id
|
payments (when client-id
|
||||||
(dc/q '[:find [(pull ?p [:db/id :payment/invoice-number :payment/amount :payment/date
|
(dc/q '[:find [(pull ?p [:db/id :payment/invoice-number :payment/amount :payment/date
|
||||||
{:payment/vendor [:db/id :vendor/name]}]) ...]
|
{:payment/vendor [:db/id :vendor/name]}]) ...]
|
||||||
@@ -541,36 +539,7 @@
|
|||||||
client-id))]
|
client-id))]
|
||||||
(filter #(dollars= (Math/abs (:transaction/amount tx)) (:payment/amount %)) payments)))
|
(filter #(dollars= (Math/abs (:transaction/amount tx)) (:payment/amount %)) payments)))
|
||||||
|
|
||||||
(defn payment-matches-view [request]
|
|
||||||
(let [payments (get-available-payments request)]
|
|
||||||
[:div
|
|
||||||
(if (seq payments)
|
|
||||||
[:div
|
|
||||||
[:h3.text-lg.font-bold.mb-4 "Available Payments"]
|
|
||||||
[:div {:hx-post (bidi/path-for ssr-routes/only-routes ::route/match-payment)
|
|
||||||
:hx-trigger "matchPayment"
|
|
||||||
:hx-target "#modal-holder"
|
|
||||||
:hx-include "this"
|
|
||||||
:hx-swap "outerHTML"}
|
|
||||||
(com/hidden {:name "action"
|
|
||||||
:value "link-payment"
|
|
||||||
:form ""})
|
|
||||||
(com/hidden {:name "transaction-id"
|
|
||||||
:value (-> request :multi-form-state :snapshot :db/id)
|
|
||||||
:form ""})
|
|
||||||
[:div.space-y-2
|
|
||||||
[:label.block.text-sm.font-medium.mb-1 "Select a payment to match:"]
|
|
||||||
(when payments
|
|
||||||
(com/radio-card {:options (for [payment payments]
|
|
||||||
{:value (:db/id payment)
|
|
||||||
:content (str (:payment/invoice-number payment) " - "
|
|
||||||
(-> payment :payment/vendor :vendor/name)
|
|
||||||
" - Amount: $" (format "%.2f" (:payment/amount payment))
|
|
||||||
" • Date: " (some-> payment :payment/date coerce/to-date-time (atime/unparse-local atime/normal-date)))})
|
|
||||||
:name "payment-id"
|
|
||||||
:width "w-full"}))
|
|
||||||
(com/a-button {"@click" "$dispatch('matchPayment')"} "Match" #_[:button.mt-4.w-full.py-2.bg-blue-500.text-white.rounded.hover:bg-blue-600 "Match"])]] ]
|
|
||||||
[:div.text-center.py-4.text-gray-500 "No matching payments available for this transaction."]) ]))
|
|
||||||
|
|
||||||
(defn get-available-autopay-invoices [request]
|
(defn get-available-autopay-invoices [request]
|
||||||
(let [tx-id (or (-> request :multi-form-state :snapshot :db/id)
|
(let [tx-id (or (-> request :multi-form-state :snapshot :db/id)
|
||||||
@@ -705,7 +674,7 @@
|
|||||||
[:button.mt-4.w-full.py-2.bg-blue-500.text-white.rounded.hover:bg-blue-600 "Apply"]]]
|
[:button.mt-4.w-full.py-2.bg-blue-500.text-white.rounded.hover:bg-blue-600 "Apply"]]]
|
||||||
[:div.text-center.py-4.text-gray-500 "No matching rules found for this transaction."])]))
|
[:div.text-center.py-4.text-gray-500 "No matching rules found for this transaction."])]))
|
||||||
|
|
||||||
(defn payment-info-view [request]
|
(defn linked-payment-view [request]
|
||||||
(let [tx-id (or (-> request :multi-form-state :snapshot :db/id)
|
(let [tx-id (or (-> request :multi-form-state :snapshot :db/id)
|
||||||
(get-in request [:route-params :db/id]))
|
(get-in request [:route-params :db/id]))
|
||||||
tx (when tx-id (d-transactions/get-by-id tx-id))
|
tx (when tx-id (d-transactions/get-by-id tx-id))
|
||||||
@@ -738,17 +707,52 @@
|
|||||||
[:div.font-medium "Date"]
|
[:div.font-medium "Date"]
|
||||||
[:div (some-> payment :payment/date (atime/unparse-local atime/normal-date))]]
|
[:div (some-> payment :payment/date (atime/unparse-local atime/normal-date))]]
|
||||||
[:div.mt-4 {:hx-post (bidi/path-for ssr-routes/only-routes ::route/unlink-payment)
|
[:div.mt-4 {:hx-post (bidi/path-for ssr-routes/only-routes ::route/unlink-payment)
|
||||||
:hx-params "transaction-id"
|
:hx-params "transaction-id, action"
|
||||||
:hx-trigger "unlinkPayment"
|
:hx-trigger "unlinkPayment"
|
||||||
|
:hx-target "#payment-matches"
|
||||||
:hx-include "this"
|
:hx-include "this"
|
||||||
:hx-target "#modal-holder"
|
|
||||||
:hx-swap "outerHTML"
|
:hx-swap "outerHTML"
|
||||||
:hx-confirm "Are you sure you want to unlink this payment?"}
|
:hx-confirm "Are you sure you want to unlink this payment?"}
|
||||||
|
|
||||||
|
(com/hidden {:name "action"
|
||||||
|
:value "unlink-payment"
|
||||||
|
:form ""})
|
||||||
(com/hidden {:name "transaction-id" :value tx-id :form ""})
|
(com/hidden {:name "transaction-id" :value tx-id :form ""})
|
||||||
(com/a-button {:color :red :size :small
|
(com/a-button {:color :red :size :small
|
||||||
"@click" "$dispatch('unlinkPayment')"} "Unlink Payment")]]])))
|
"@click" "$dispatch('unlinkPayment')"} "Unlink Payment")]]])))
|
||||||
|
|
||||||
|
(defn payment-matches-view [request]
|
||||||
|
(let [payments (get-available-payments request)]
|
||||||
|
[:div#payment-matches
|
||||||
|
(linked-payment-view request)
|
||||||
|
(if (seq payments)
|
||||||
|
[:div
|
||||||
|
[:h3.text-lg.font-bold.mb-4 "Available Payments"]
|
||||||
|
[:div {:hx-post (bidi/path-for ssr-routes/only-routes ::route/link-payment)
|
||||||
|
:hx-trigger "matchPayment"
|
||||||
|
:hx-target "#modal-holder"
|
||||||
|
:hx-include "this"
|
||||||
|
:hx-swap "outerHTML"}
|
||||||
|
(com/hidden {:name "action"
|
||||||
|
:value "link-payment"
|
||||||
|
:form ""})
|
||||||
|
(com/hidden {:name "transaction-id"
|
||||||
|
:value (-> request :entity :db/id)
|
||||||
|
:form ""})
|
||||||
|
[:div.space-y-2
|
||||||
|
[:label.block.text-sm.font-medium.mb-1 "Select a payment to match:"]
|
||||||
|
(when payments
|
||||||
|
(com/radio-card {:options (for [payment payments]
|
||||||
|
{:value (:db/id payment)
|
||||||
|
:content (str (:payment/invoice-number payment) " - "
|
||||||
|
(-> payment :payment/vendor :vendor/name)
|
||||||
|
" - Amount: $" (format "%.2f" (:payment/amount payment))
|
||||||
|
" • Date: " (some-> payment :payment/date coerce/to-date-time (atime/unparse-local atime/normal-date)))})
|
||||||
|
:name "payment-id"
|
||||||
|
:width "w-full"}))
|
||||||
|
(com/a-button {"@click" "$dispatch('matchPayment')"} "Match" #_[:button.mt-4.w-full.py-2.bg-blue-500.text-white.rounded.hover:bg-blue-600 "Match"])]]]
|
||||||
|
[:div.text-center.py-4.text-gray-500 "No matching payments available for this transaction."])]))
|
||||||
|
|
||||||
(defn count-payment-matches [request]
|
(defn count-payment-matches [request]
|
||||||
(count (get-available-payments request)))
|
(count (get-available-payments request)))
|
||||||
|
|
||||||
@@ -781,8 +785,12 @@
|
|||||||
:body (mm/default-step-body
|
:body (mm/default-step-body
|
||||||
{}
|
{}
|
||||||
[:div
|
[:div
|
||||||
(payment-info-view request)
|
|
||||||
[:div {:x-data "{ activeForm: null }"}
|
[:div {:x-data (hx/json {:activeForm (if (:transaction/payment (:entity request))
|
||||||
|
"payment"
|
||||||
|
nil)
|
||||||
|
:canChange (boolean (not (:transaction/payment (:entity request))))})
|
||||||
|
"@unlinked" "canChange=true"}
|
||||||
[:div {:class "flex space-x-2 mb-4"}
|
[:div {:class "flex space-x-2 mb-4"}
|
||||||
(com/button-group {:name "method"}
|
(com/button-group {:name "method"}
|
||||||
(com/button-group-button {"@click" "activeForm = 'payment'" :value "payment" ":class" "{ '!bg-primary-200 text-primary-800': activeForm === 'payment'}" :class "relative"}
|
(com/button-group-button {"@click" "activeForm = 'payment'" :value "payment" ":class" "{ '!bg-primary-200 text-primary-800': activeForm === 'payment'}" :class "relative"}
|
||||||
@@ -790,24 +798,29 @@
|
|||||||
(when (> count 0)
|
(when (> count 0)
|
||||||
(com/badge {:color "green"} (str count))))
|
(com/badge {:color "green"} (str count))))
|
||||||
"Link to payment")
|
"Link to payment")
|
||||||
(com/button-group-button {"@click" "activeForm = 'unpaid'" :value "unpaid" ":class" "{ '!bg-primary-200 text-primary-800': activeForm === 'unpaid'}" :class "relative"}
|
(com/button-group-button {"@click" "activeForm = 'unpaid'" :value "unpaid" ":class" "{ '!bg-primary-200 text-primary-800': activeForm === 'unpaid'}" :class "relative"
|
||||||
|
":disabled" "!canChange"}
|
||||||
(let [count (count-unpaid-invoice-matches request)]
|
(let [count (count-unpaid-invoice-matches request)]
|
||||||
(when (> count 0)
|
(when (> count 0)
|
||||||
(com/badge {:color "green"} (str count))))
|
(com/badge {:color "green"} (str count))))
|
||||||
"Link to unpaid invoices")
|
"Link to unpaid invoices")
|
||||||
(com/button-group-button {"@click" "activeForm = 'autopay'" :value "autopay" ":class" "{ '!bg-primary-200 text-primary-800': activeForm === 'autopay'}" :class "relative"}
|
(com/button-group-button {"@click" "activeForm = 'autopay'" :value "autopay" ":class" "{ '!bg-primary-200 text-primary-800': activeForm === 'autopay'}" :class "relative"
|
||||||
|
":disabled" "!canChange"}
|
||||||
(let [count (count-autopay-invoice-matches request)]
|
(let [count (count-autopay-invoice-matches request)]
|
||||||
(when (> count 0)
|
(when (> count 0)
|
||||||
(com/badge {:color "green"} (str count))))
|
(com/badge {:color "green"} (str count))))
|
||||||
"Link to autopay invoices")
|
"Link to autopay invoices")
|
||||||
(com/button-group-button {"@click" "activeForm = 'rule'" :value "rule" ":class" "{ '!bg-primary-200 text-primary-800': activeForm === 'rule'}" :class "relative"}
|
(com/button-group-button {"@click" "activeForm = 'rule'" :value "rule" ":class" "{ '!bg-primary-200 text-primary-800': activeForm === 'rule'}" :class "relative"
|
||||||
|
":disabled" "!canChange"}
|
||||||
(let [count (count-rule-matches request)]
|
(let [count (count-rule-matches request)]
|
||||||
(when (> count 0)
|
(when (> count 0)
|
||||||
(com/badge {:color "green"} (str count))))
|
(com/badge {:color "green"} (str count))))
|
||||||
"Apply rule")
|
"Apply rule")
|
||||||
(com/button-group-button {"@click" "activeForm = 'manual'" :value "manual" ":class" "{ '!bg-primary-200 text-primary-800': activeForm === 'manual'}"}
|
(com/button-group-button {"@click" "activeForm = 'manual'" :value "manual" ":class" "{ '!bg-primary-200 text-primary-800': activeForm === 'manual'}"
|
||||||
|
":disabled" "!canChange"}
|
||||||
"Manual"))]
|
"Manual"))]
|
||||||
[:div {:x-show "activeForm === 'payment'", :x-transition:enter "transition ease-out duration-500", :x-transition:enter-start "opacity-0 transform scale-95", :x-transition:enter-end "opacity-100 transform scale-100"}
|
[:div {:x-show "activeForm === 'payment'", :x-transition:enter "transition ease-out duration-500", :x-transition:enter-start "opacity-0 transform scale-95", :x-transition:enter-end "opacity-100 transform scale-100"}
|
||||||
|
|
||||||
(payment-matches-view request)]
|
(payment-matches-view request)]
|
||||||
[:div {:x-show "activeForm === 'unpaid'", :x-transition:enter "transition ease-out duration-500", :x-transition:enter-start "opacity-0 transform scale-95", :x-transition:enter-end "opacity-100 transform scale-100"}
|
[:div {:x-show "activeForm === 'unpaid'", :x-transition:enter "transition ease-out duration-500", :x-transition:enter-start "opacity-0 transform scale-95", :x-transition:enter-end "opacity-100 transform scale-100"}
|
||||||
(unpaid-invoices-view request)]
|
(unpaid-invoices-view request)]
|
||||||
@@ -960,9 +973,9 @@
|
|||||||
[]
|
[]
|
||||||
entity)))
|
entity)))
|
||||||
|
|
||||||
(defn match-payment [{{:keys [transaction-id match-payment-id]} :form-params :as request}]
|
(defn link-payment [{{:keys [transaction-id payment-id]} :form-params :as request}]
|
||||||
(let [transaction (d-transactions/get-by-id transaction-id)
|
(let [transaction (d-transactions/get-by-id transaction-id)
|
||||||
payment (d-checks/get-by-id match-payment-id)]
|
payment (d-checks/get-by-id payment-id)]
|
||||||
|
|
||||||
(exception->4xx #(assert-can-see-client (:identity request) (-> transaction :transaction/client :db/id)))
|
(exception->4xx #(assert-can-see-client (:identity request) (-> transaction :transaction/client :db/id)))
|
||||||
(exception->4xx #(assert-can-see-client (:identity request) (-> payment :payment/client :db/id)))
|
(exception->4xx #(assert-can-see-client (:identity request) (-> payment :payment/client :db/id)))
|
||||||
@@ -1000,7 +1013,7 @@
|
|||||||
[:p.text-gray-600.mt-2 "The transaction has been linked to the autopay invoices."]
|
[:p.text-gray-600.mt-2 "The transaction has been linked to the autopay invoices."]
|
||||||
[:p.text-gray-600.mt-2 "To view the new payment, click "
|
[:p.text-gray-600.mt-2 "To view the new payment, click "
|
||||||
(com/link {:href (hu/url (bidi/path-for ssr-routes/only-routes ::payment-route/all-page)
|
(com/link {:href (hu/url (bidi/path-for ssr-routes/only-routes ::payment-route/all-page)
|
||||||
{:exact-match-id match-payment-id})
|
{:exact-match-id payment-id})
|
||||||
:hx-boost true}
|
:hx-boost true}
|
||||||
"here")
|
"here")
|
||||||
" to view it."])
|
" to view it."])
|
||||||
@@ -1201,8 +1214,10 @@
|
|||||||
(:identity request))))
|
(:identity request))))
|
||||||
|
|
||||||
(solr/touch-with-ledger transaction-id)
|
(solr/touch-with-ledger transaction-id)
|
||||||
|
(html-response (payment-matches-view request)
|
||||||
|
:headers {"hx-trigger" "unlinked"})
|
||||||
|
|
||||||
(modal-response
|
#_(modal-response
|
||||||
(com/success-modal {:title "Transaction unlinked successfully"}
|
(com/success-modal {:title "Transaction unlinked successfully"}
|
||||||
|
|
||||||
[:p.text-gray-600.mt-2 "The transaction has been unlinked from its payment."]
|
[:p.text-gray-600.mt-2 "The transaction has been unlinked from its payment."]
|
||||||
@@ -1245,40 +1260,42 @@
|
|||||||
{::route/edit-wizard (-> mm/open-wizard-handler
|
{::route/edit-wizard (-> mm/open-wizard-handler
|
||||||
(mm/wrap-wizard edit-wizard)
|
(mm/wrap-wizard edit-wizard)
|
||||||
(mm/wrap-init-multi-form-state initial-edit-wizard-state)
|
(mm/wrap-init-multi-form-state initial-edit-wizard-state)
|
||||||
|
(wrap-entity [:route-params :db/id] d-transactions/default-read)
|
||||||
(wrap-schema-enforce :route-schema [:map [:db/id entity-id]]))
|
(wrap-schema-enforce :route-schema [:map [:db/id entity-id]]))
|
||||||
::route/edit-wizard-navigate (-> mm/next-handler
|
::route/edit-wizard-navigate (-> mm/next-handler
|
||||||
|
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
|
||||||
(mm/wrap-wizard edit-wizard)
|
(mm/wrap-wizard edit-wizard)
|
||||||
(mm/wrap-decode-multi-form-state))
|
(mm/wrap-decode-multi-form-state))
|
||||||
::route/edit-submit (-> mm/submit-handler
|
::route/edit-submit (-> mm/submit-handler
|
||||||
(mm/wrap-wizard edit-wizard)
|
(mm/wrap-wizard edit-wizard)
|
||||||
(mm/wrap-decode-multi-form-state))
|
(mm/wrap-decode-multi-form-state))
|
||||||
::route/location-select (-> location-select
|
::route/location-select (-> location-select
|
||||||
(wrap-schema-enforce :query-schema [:map
|
(wrap-schema-enforce :query-schema [:map
|
||||||
[:name :string]
|
[:name :string]
|
||||||
[:client-id {:optional true}
|
[:client-id {:optional true}
|
||||||
[:maybe entity-id]]
|
[:maybe entity-id]]
|
||||||
[:account-id {:optional true}
|
[:account-id {:optional true}
|
||||||
[:maybe entity-id]]]))
|
[:maybe entity-id]]]))
|
||||||
::route/account-total (-> account-total
|
::route/account-total (-> account-total
|
||||||
(mm/wrap-wizard edit-wizard)
|
|
||||||
(mm/wrap-decode-multi-form-state))
|
|
||||||
::route/account-balance (-> account-balance
|
|
||||||
(mm/wrap-wizard edit-wizard)
|
(mm/wrap-wizard edit-wizard)
|
||||||
(mm/wrap-decode-multi-form-state))
|
(mm/wrap-decode-multi-form-state))
|
||||||
|
::route/account-balance (-> account-balance
|
||||||
|
(mm/wrap-wizard edit-wizard)
|
||||||
|
(mm/wrap-decode-multi-form-state))
|
||||||
::route/edit-wizard-new-account (->
|
::route/edit-wizard-new-account (->
|
||||||
(add-new-entity-handler [:step-params :transaction/accounts]
|
(add-new-entity-handler [:step-params :transaction/accounts]
|
||||||
(fn render [cursor request]
|
(fn render [cursor request]
|
||||||
(transaction-account-row*
|
(transaction-account-row*
|
||||||
{:value cursor
|
{:value cursor
|
||||||
:client-id (:client-id (:query-params request))}))
|
:client-id (:client-id (:query-params request))}))
|
||||||
(fn build-new-row [base _]
|
(fn build-new-row [base _]
|
||||||
(assoc base :transaction-account/location "Shared")))
|
(assoc base :transaction-account/location "Shared")))
|
||||||
(wrap-schema-enforce :query-schema [:map
|
(wrap-schema-enforce :query-schema [:map
|
||||||
[:client-id {:optional true}
|
[:client-id {:optional true}
|
||||||
[:maybe entity-id]]]))
|
[:maybe entity-id]]]))
|
||||||
::route/match-payment (-> match-payment
|
::route/link-payment (-> link-payment
|
||||||
(wrap-schema-enforce :form-schema
|
(wrap-schema-enforce :form-schema
|
||||||
save-schema))
|
save-schema))
|
||||||
::route/match-autopay-invoices (-> match-autopay-invoices
|
::route/match-autopay-invoices (-> match-autopay-invoices
|
||||||
(wrap-schema-enforce :form-schema
|
(wrap-schema-enforce :form-schema
|
||||||
[:map [:transaction-id entity-id]
|
[:map [:transaction-id entity-id]
|
||||||
@@ -1288,11 +1305,12 @@
|
|||||||
[:map [:transaction-id entity-id]
|
[:map [:transaction-id entity-id]
|
||||||
[:unpaid-invoice-ids [:vector {:coerce? true} entity-id]]]))
|
[:unpaid-invoice-ids [:vector {:coerce? true} entity-id]]]))
|
||||||
::route/apply-rule (-> apply-rule
|
::route/apply-rule (-> apply-rule
|
||||||
(wrap-schema-enforce :form-schema
|
(wrap-schema-enforce :form-schema
|
||||||
[:map [:transaction-id entity-id]
|
[:map [:transaction-id entity-id]
|
||||||
[:rule-id entity-id]]))
|
[:rule-id entity-id]]))
|
||||||
::route/unlink-payment (-> unlink-payment
|
::route/unlink-payment (-> unlink-payment
|
||||||
(wrap-schema-enforce :form-schema
|
(wrap-entity [:form-params :transaction-id] d-transactions/default-read)
|
||||||
|
(wrap-schema-enforce :form-schema
|
||||||
save-schema))}
|
save-schema))}
|
||||||
(fn [h]
|
(fn [h]
|
||||||
(-> h
|
(-> h
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
(ns auto-ap.client-routes)
|
(ns auto-ap.client-routes)
|
||||||
|
|
||||||
(def routes ["/" {"" :index
|
(def routes ["/" {"" :index
|
||||||
"login" :login
|
#_#_"login" :login
|
||||||
"login/" :login
|
#_#_"login/" :login
|
||||||
"needs-activation/" :needs-activation
|
"needs-activation/" :needs-activation
|
||||||
"needs-activation" :needs-activation
|
"needs-activation" :needs-activation
|
||||||
"payments/" :payments
|
"payments/" :payments
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"/account-total" ::account-total
|
"/account-total" ::account-total
|
||||||
"/account-balance" ::account-balance
|
"/account-balance" ::account-balance
|
||||||
"/edit-wizard-new-account" ::edit-wizard-new-account
|
"/edit-wizard-new-account" ::edit-wizard-new-account
|
||||||
"/match-payment" ::match-payment
|
"/match-payment" ::link-payment
|
||||||
"/match-autopay-invoices" ::match-autopay-invoices
|
"/match-autopay-invoices" ::match-autopay-invoices
|
||||||
"/match-unpaid-invoices" ::match-unpaid-invoices
|
"/match-unpaid-invoices" ::match-unpaid-invoices
|
||||||
"/apply-rule" ::apply-rule
|
"/apply-rule" ::apply-rule
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
(def routes {"impersonate" :impersonate
|
(def routes {"impersonate" :impersonate
|
||||||
"logout" :logout
|
"logout" :logout
|
||||||
|
"login" :login
|
||||||
"search" :search
|
"search" :search
|
||||||
"indicators" indicator-routes/routes
|
"indicators" indicator-routes/routes
|
||||||
|
|
||||||
|
|||||||
@@ -32,11 +32,17 @@ module.exports = {
|
|||||||
"transform": "scale(1.0)",
|
"transform": "scale(1.0)",
|
||||||
"animation-timing-function": "cubic-bezier(0.8, 0, 1, 1)"
|
"animation-timing-function": "cubic-bezier(0.8, 0, 1, 1)"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
slideUp: {
|
||||||
|
'0%': { transform: 'translateY(20px)', opacity: '0' },
|
||||||
|
'100%': { transform: 'translateY(0)', opacity: '1' },
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
'shake': 'shake 0.5s ease-out 1',
|
'shake': 'shake 0.5s ease-out 1',
|
||||||
"gg": "gentleGrow 1s infinite"
|
"gg": "gentleGrow 1s infinite",
|
||||||
|
"slideUp": 'slideUp 0.5s ease-out forwards'
|
||||||
},
|
},
|
||||||
"fontFamily": {
|
"fontFamily": {
|
||||||
"sans": ["Calibri", "ui-sans-serif", "system-ui", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial", "Noto Sans", "sans-serif", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"]
|
"sans": ["Calibri", "ui-sans-serif", "system-ui", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial", "Noto Sans", "sans-serif", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"]
|
||||||
|
|||||||
8
tasks
Normal file
8
tasks
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
* Convert transaction form to one where you pick options and hit a single endpoint to persist.
|
||||||
|
* Add tests for edit transaction.
|
||||||
|
* Make it so you can create a new vendor again.
|
||||||
|
* Switch login screen
|
||||||
|
* Hide unhelpful report from the dashboard
|
||||||
|
* Check permissions on ledger, transactions, reports
|
||||||
|
* Make sure that you can't change a transaction if its payment is set
|
||||||
|
* also add tests
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
check-vendor-default-account
|
check-vendor-default-account
|
||||||
clientize-vendor get-vendor
|
clientize-vendor get-vendor
|
||||||
location-select*
|
location-select*
|
||||||
match-autopay-invoices match-payment
|
match-autopay-invoices link-payment
|
||||||
match-unpaid-invoices
|
match-unpaid-invoices
|
||||||
transaction-account-row*
|
transaction-account-row*
|
||||||
unlink-payment]]
|
unlink-payment]]
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
:transaction/amount 50.0
|
:transaction/amount 50.0
|
||||||
:transaction/client {:db/id "client-id"}}])
|
:transaction/client {:db/id "client-id"}}])
|
||||||
;; Perform match-payment
|
;; Perform match-payment
|
||||||
(match-payment {:form-params {"transaction-id" "transaction-id" "payment-id" "payment-id"}})
|
(link-payment {:form-params {"transaction-id" "transaction-id" "payment-id" "payment-id"}})
|
||||||
;; Verify payment and transaction are linked
|
;; Verify payment and transaction are linked
|
||||||
(let [transaction (d-transactions/get-by-id "transaction-id")
|
(let [transaction (d-transactions/get-by-id "transaction-id")
|
||||||
payment (dc/pull (dc/db conn) '[:db/id :payment/status] "payment-id")]
|
payment (dc/pull (dc/db conn) '[:db/id :payment/status] "payment-id")]
|
||||||
|
|||||||
Reference in New Issue
Block a user