Removes old login page, more progress on transactions

This commit is contained in:
2025-03-10 14:09:45 -07:00
parent 8429d8516c
commit 7373ef41d1
16 changed files with 270 additions and 137 deletions

File diff suppressed because one or more lines are too long

View File

@@ -245,25 +245,25 @@
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]
(->
(dc/pull (dc/db conn)
'[* {: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)
(dc/pull (dc/db conn) default-read id)
(update :transaction/date coerce/from-date)
(update :transaction/post-date coerce/from-date)
(dissoc :transaction/id)))

View File

@@ -1,15 +1,18 @@
(ns auto-ap.routes.auth
(:require
[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]
[clj-http.client :as http]
[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]
[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-secret "OC-WemHurPXYpuIw5cT-B90g")
@@ -91,7 +94,9 @@
(if-let [jwt (user->jwt user token)]
{: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-secret env)
{:alg :hs512}))}

View File

@@ -1,7 +1,13 @@
(ns auto-ap.ssr.auth
(:require [auto-ap.session-version :as session-version]
[buddy.sign.jwt :as jwt]
[config.core :refer [env]]))
(:require
[auto-ap.session-version :as session-version]
[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]
{:status 301
@@ -16,3 +22,86 @@
{:alg :hs512})
:exp)
: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"))

View File

@@ -1,23 +1,25 @@
(ns auto-ap.ssr.components.aside
(:require [auto-ap.client-routes :as client-routes]
[auto-ap.graphql.utils :refer [is-admin?]]
[auto-ap.permissions :refer [can?]]
[auto-ap.routes.admin.clients :as ac-routes]
[auto-ap.routes.admin.excel-invoices :as ei-routes]
[auto-ap.routes.admin.import-batch :as ib-routes]
[auto-ap.routes.admin.transaction-rules :as transaction-rules]
[auto-ap.routes.admin.vendors :as v-routes]
[auto-ap.routes.invoice :as invoice-route]
[auto-ap.routes.ledger :as ledger-routes]
[auto-ap.routes.outgoing-invoice :as oi-routes]
[auto-ap.routes.payments :as payment-routes]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components.tags :as tags]
[auto-ap.ssr.hiccup-helper :as hh]
[auto-ap.ssr.hx :as hx]
[auto-ap.ssr.svg :as svg]
[bidi.bidi :as bidi]
[hiccup.util :as hu]))
(:require
[auto-ap.client-routes :as client-routes]
[auto-ap.graphql.utils :refer [is-admin?]]
[auto-ap.permissions :refer [can?]]
[auto-ap.routes.admin.clients :as ac-routes]
[auto-ap.routes.admin.excel-invoices :as ei-routes]
[auto-ap.routes.admin.import-batch :as ib-routes]
[auto-ap.routes.admin.transaction-rules :as transaction-rules]
[auto-ap.routes.admin.vendors :as v-routes]
[auto-ap.routes.dashboard :as dashboard]
[auto-ap.routes.invoice :as invoice-route]
[auto-ap.routes.ledger :as ledger-routes]
[auto-ap.routes.outgoing-invoice :as oi-routes]
[auto-ap.routes.payments :as payment-routes]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components.tags :as tags]
[auto-ap.ssr.hiccup-helper :as hh]
[auto-ap.ssr.hx :as hx]
[auto-ap.ssr.svg :as svg]
[bidi.bidi :as bidi]
[hiccup.util :as hu]))
(defn menu-button- [params & children]
[:div
@@ -103,7 +105,8 @@
[:li
(menu-button- {:icon svg/pie
:href "/"}
:href (bidi/path-for ssr-routes/only-routes
::dashboard/page)}
"Dashboard")]
(when (can? (:identity request)

View File

@@ -167,7 +167,7 @@
true (assoc :type (or (:type params) "button"))
true (update :class (fn [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)
(str " text-xs px-3 py-2")

View File

@@ -1,11 +1,13 @@
(ns auto-ap.ssr.components.navbar
(:require [auto-ap.graphql.utils :refer [is-admin? limited-clients]]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.company-dropdown :as cd]
[auto-ap.ssr.components.buttons :refer [icon-button-]]
[auto-ap.ssr.components.user-dropdown :as user-dropdown]
[auto-ap.ssr.svg :as svg]
[bidi.bidi :as bidi]))
(:require
[auto-ap.graphql.utils :refer [is-admin? limited-clients]]
[auto-ap.routes.dashboard :as dashboard]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.company-dropdown :as cd]
[auto-ap.ssr.components.buttons :refer [icon-button-]]
[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]}]
[: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"]
[: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"}]]]
[: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"}]]
(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])]

View File

@@ -46,6 +46,7 @@
(def key->handler
(-> {:logout auth/logout
:login auth/login
: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-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page)))

View File

@@ -370,7 +370,7 @@
(:transaction/payment i)
(conj
{: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))})
:color :primary
:content (format "Payment '%s'" (-> i :transaction/payment :payment/date (atime/unparse-local atime/normal-date)))})

View File

@@ -27,7 +27,7 @@
[auto-ap.ssr.utils
:refer [->db-id apply-middleware-to-all-handlers check-allowance
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]
[bidi.bidi :as bidi]
[clj-time.coerce :as coerce]
@@ -261,8 +261,7 @@
:body (mm/default-step-body
{}
[:div {:x-data (hx/json {:clientId (or (fc/field-value (:transaction/client fc/*current*))
(:db/id (:client request)))
:vendorId (fc/field-value (:transaction/vendor fc/*current*))})}
(:db/id (:client request))) })}
;; Read-only transaction details
[:div.mb-6.border.rounded-lg.p-4.bg-gray-50
@@ -380,8 +379,7 @@
:placeholder "Search..."
:url (bidi/path-for ssr-routes/only-routes :vendor-search)
:value (fc/field-value)
:content-fn (fn [c] (pull-attr (dc/db conn) :vendor/name c))
:x-model "vendorId"})]))
:content-fn (fn [c] (pull-attr (dc/db conn) :vendor/name c)) })]))
[:div.mb-4
[:span.text-sm.text-gray-500 "Can't find the vendor? "
(com/link {:href (bidi.bidi/path-for
@@ -525,11 +523,11 @@
(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]))
tx (when tx-id (d-transactions/get-by-id tx-id))
client-id (or (get-in request [:multi-form-state :snapshot :transaction/client])
(get-in request [:client :db/id]))
client-id (-> tx :transaction/client :db/id)
payments (when client-id
(dc/q '[:find [(pull ?p [:db/id :payment/invoice-number :payment/amount :payment/date
{:payment/vendor [:db/id :vendor/name]}]) ...]
@@ -541,36 +539,7 @@
client-id))]
(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]
(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"]]]
[: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)
(get-in request [:route-params :db/id]))
tx (when tx-id (d-transactions/get-by-id tx-id))
@@ -738,17 +707,52 @@
[:div.font-medium "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)
:hx-params "transaction-id"
:hx-params "transaction-id, action"
:hx-trigger "unlinkPayment"
:hx-target "#payment-matches"
:hx-include "this"
:hx-target "#modal-holder"
:hx-swap "outerHTML"
: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/a-button {:color :red :size :small
"@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]
(count (get-available-payments request)))
@@ -781,8 +785,12 @@
:body (mm/default-step-body
{}
[: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"}
(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"}
@@ -790,24 +798,29 @@
(when (> count 0)
(com/badge {:color "green"} (str count))))
"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)]
(when (> count 0)
(com/badge {:color "green"} (str count))))
"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)]
(when (> count 0)
(com/badge {:color "green"} (str count))))
"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)]
(when (> count 0)
(com/badge {:color "green"} (str count))))
"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"))]
[: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)]
[: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)]
@@ -960,9 +973,9 @@
[]
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)
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) (-> 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 "To view the new payment, click "
(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}
"here")
" to view it."])
@@ -1201,8 +1214,10 @@
(:identity request))))
(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"}
[: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
(mm/wrap-wizard edit-wizard)
(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]]))
::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-decode-multi-form-state))
::route/edit-submit (-> mm/submit-handler
(mm/wrap-wizard edit-wizard)
(mm/wrap-decode-multi-form-state))
::route/location-select (-> location-select
(wrap-schema-enforce :query-schema [:map
[:name :string]
[:client-id {:optional true}
[:maybe entity-id]]
[:account-id {:optional true}
[:maybe entity-id]]]))
(wrap-schema-enforce :query-schema [:map
[:name :string]
[:client-id {:optional true}
[:maybe entity-id]]
[:account-id {:optional true}
[:maybe entity-id]]]))
::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-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 (->
(add-new-entity-handler [:step-params :transaction/accounts]
(fn render [cursor request]
(transaction-account-row*
{:value cursor
:client-id (:client-id (:query-params request))}))
(fn build-new-row [base _]
(assoc base :transaction-account/location "Shared")))
(fn render [cursor request]
(transaction-account-row*
{:value cursor
:client-id (:client-id (:query-params request))}))
(fn build-new-row [base _]
(assoc base :transaction-account/location "Shared")))
(wrap-schema-enforce :query-schema [:map
[:client-id {:optional true}
[:maybe entity-id]]]))
::route/match-payment (-> match-payment
(wrap-schema-enforce :form-schema
save-schema))
::route/link-payment (-> link-payment
(wrap-schema-enforce :form-schema
save-schema))
::route/match-autopay-invoices (-> match-autopay-invoices
(wrap-schema-enforce :form-schema
[:map [:transaction-id entity-id]
@@ -1288,11 +1305,12 @@
[:map [:transaction-id entity-id]
[:unpaid-invoice-ids [:vector {:coerce? true} entity-id]]]))
::route/apply-rule (-> apply-rule
(wrap-schema-enforce :form-schema
[:map [:transaction-id entity-id]
[:rule-id entity-id]]))
(wrap-schema-enforce :form-schema
[:map [:transaction-id entity-id]
[:rule-id entity-id]]))
::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))}
(fn [h]
(-> h

View File

@@ -1,8 +1,8 @@
(ns auto-ap.client-routes)
(def routes ["/" {"" :index
"login" :login
"login/" :login
#_#_"login" :login
#_#_"login/" :login
"needs-activation/" :needs-activation
"needs-activation" :needs-activation
"payments/" :payments

View File

@@ -24,7 +24,7 @@
"/account-total" ::account-total
"/account-balance" ::account-balance
"/edit-wizard-new-account" ::edit-wizard-new-account
"/match-payment" ::match-payment
"/match-payment" ::link-payment
"/match-autopay-invoices" ::match-autopay-invoices
"/match-unpaid-invoices" ::match-unpaid-invoices
"/apply-rule" ::apply-rule

View File

@@ -17,6 +17,7 @@
(def routes {"impersonate" :impersonate
"logout" :logout
"login" :login
"search" :search
"indicators" indicator-routes/routes

View File

@@ -32,11 +32,17 @@ module.exports = {
"transform": "scale(1.0)",
"animation-timing-function": "cubic-bezier(0.8, 0, 1, 1)"
}
},
slideUp: {
'0%': { transform: 'translateY(20px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
}
},
animation: {
'shake': 'shake 0.5s ease-out 1',
"gg": "gentleGrow 1s infinite"
"gg": "gentleGrow 1s infinite",
"slideUp": 'slideUp 0.5s ease-out forwards'
},
"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"]

8
tasks Normal file
View 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

View File

@@ -7,7 +7,7 @@
check-vendor-default-account
clientize-vendor get-vendor
location-select*
match-autopay-invoices match-payment
match-autopay-invoices link-payment
match-unpaid-invoices
transaction-account-row*
unlink-payment]]
@@ -87,7 +87,7 @@
:transaction/amount 50.0
:transaction/client {:db/id "client-id"}}])
;; 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
(let [transaction (d-transactions/get-by-id "transaction-id")
payment (dc/pull (dc/db conn) '[:db/id :payment/status] "payment-id")]