From f9c4170998074f8ebe9fced4cdda909b9aee7929 Mon Sep 17 00:00:00 2001 From: ginesdt Date: Thu, 14 Aug 2025 15:07:10 +0200 Subject: [PATCH 1/3] forbid invoices bigger than max available funds --- ui/resources/public/less/page/new-invoice.less | 5 +++++ ui/src/ethlance/ui/page/new_invoice.cljs | 12 +++++++++--- .../ethlance/ui/page/new_invoice/subscriptions.cljs | 8 +++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/ui/resources/public/less/page/new-invoice.less b/ui/resources/public/less/page/new-invoice.less index bce523347..db8001432 100644 --- a/ui/resources/public/less/page/new-invoice.less +++ b/ui/resources/public/less/page/new-invoice.less @@ -94,6 +94,11 @@ padding-left: 0.4em; } } + + > .max-available > div { + display: inline; + margin-left: 0.3rem; + } } > .right-form { diff --git a/ui/src/ethlance/ui/page/new_invoice.cljs b/ui/src/ethlance/ui/page/new_invoice.cljs index 03a4e1fcc..2c94758fe 100644 --- a/ui/src/ethlance/ui/page/new_invoice.cljs +++ b/ui/src/ethlance/ui/page/new_invoice.cljs @@ -7,6 +7,7 @@ [ethlance.ui.component.select-input :refer [c-select-input]] [ethlance.ui.component.textarea-input :refer [c-textarea-input]] [ethlance.ui.component.token-amount-input :refer [c-token-amount-input]] + [ethlance.ui.component.token-info :refer [c-token-info]] [re-frame.core :as re])) @@ -40,6 +41,7 @@ :job/token-amount :job/token-type :job/token-id + :balance [:token-details [:token-detail/id :token-detail/type @@ -65,7 +67,10 @@ (sort-by :job-story/date-created ,,,) reverse) token-display-name (name (or (@job-token :symbol) (@job-token :type) "")) - job-token-decimals (get-in @*invoiced-job [:job :token-details :token-detail/decimals]) + job-token-details (get-in @*invoiced-job [:job :token-details]) + job-token-decimals (:token-detail/decimals job-token-details) + balance-left (get-in @*invoiced-job [:job :balance]) + show-balance-left? (not (nil? balance-left)) no-job-selected? (nil? @*invoiced-job) focus-on-element (fn [id _event] (.focus (.getElementById js/document id))) validations (re/subscribe [:page.new-invoice/validations]) @@ -113,8 +118,9 @@ :disabled no-job-selected? :on-change #(re/dispatch [:page.new-invoice/set-invoice-amount %])}] [:div.post-label token-display-name]] - [:div.usd-estimate @estimated-usd]] - + [:div.usd-estimate @estimated-usd] + (when show-balance-left? + [:div.max-available "Max available:" [c-token-info balance-left job-token-details]])] [:div.right-form [:div.label "Message"] [c-textarea-input diff --git a/ui/src/ethlance/ui/page/new_invoice/subscriptions.cljs b/ui/src/ethlance/ui/page/new_invoice/subscriptions.cljs index 7d43eca23..ac9219646 100644 --- a/ui/src/ethlance/ui/page/new_invoice/subscriptions.cljs +++ b/ui/src/ethlance/ui/page/new_invoice/subscriptions.cljs @@ -64,6 +64,8 @@ :page.new-invoice/validations :<- [::form-fields] (fn [{:keys [invoiced-job invoice-amount]}] - {:invoiced-job (not (nil? invoiced-job)) - :invoice-amount (and (not (nil? (get invoice-amount :token-amount))) - (> (:token-amount invoice-amount) 0))})) + (let [balance-left (get-in invoiced-job [:job :balance-left])] + {:invoiced-job (not (nil? invoiced-job)) + :invoice-amount (and (not (nil? (get invoice-amount :token-amount))) + (> (:token-amount invoice-amount) 0) + (<= (:token-amount invoice-amount) balance-left))}))) From 55113b86706e3f54f1d81f6c7fb53b88cac16885 Mon Sep 17 00:00:00 2001 From: ginesdt Date: Mon, 18 Aug 2025 09:14:25 +0200 Subject: [PATCH 2/3] consider unpaid invoices to get available funds --- .../ethlance/server/graphql/resolvers.cljs | 22 +++++++++++++++++++ .../src/ethlance/shared/graphql/schema.cljs | 1 + ui/src/ethlance/ui/page/new_invoice.cljs | 4 ++-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/server/src/ethlance/server/graphql/resolvers.cljs b/server/src/ethlance/server/graphql/resolvers.cljs index 6e0937093..55608590f 100644 --- a/server/src/ethlance/server/graphql/resolvers.cljs +++ b/server/src/ethlance/server/graphql/resolvers.cljs @@ -1059,6 +1059,27 @@ (:sum result)))) +(defn job->balance-left-resolver + [root _args _] + (db/with-async-resolver-conn conn + (let [parsed-root (graphql-utils/gql->clj root) + job-id (:job/id parsed-root) + query-funding {:select [[(sql/call :sum :job-funding/amount) :amount]] + :from [:JobFunding] + :where [:= :JobFunding.job/id job-id]} + query-invoices {:select [[(sql/call :* -1 :JobStoryInvoiceMessage.invoice/amount-requested) :amount]] + :from [:JobStoryInvoiceMessage] + :join [:jobStory [:= :JobStory.job-story/id :JobStoryInvoiceMessage.job-story/id]] + :where [:and + [:= :JobStory.job/id job-id] + [:in :JobStoryInvoiceMessage.invoice/status ["created" "dispute-raised"]]]} + query {:select [[(sql/call :coalesce (sql/call :sum :t.amount) 0) :result]] + :from [[{:union-all [query-funding query-invoices]} :t]]} + result (:result (balance-left-resolver " job-id " | " result)) + result))) + + (defn sign-in-mutation [_ {:keys [:data :data-signature] :as input} {:keys [config]}] (try-catch-throw @@ -1329,6 +1350,7 @@ :invoices job->invoices-resolver :invoice invoice-resolver :balance job->balance-resolver + :balanceLeft job->balance-left-resolver :job_requiredSkills job->required-skills-resolver} :JobStory {:jobStory_employerFeedback job-story->employer-feedback-resolver :jobStory_candidateFeedback job-story->candidate-feedback-resolver diff --git a/shared/src/ethlance/shared/graphql/schema.cljs b/shared/src/ethlance/shared/graphql/schema.cljs index 0104cac4c..d751727a4 100644 --- a/shared/src/ethlance/shared/graphql/schema.cljs +++ b/shared/src/ethlance/shared/graphql/schema.cljs @@ -453,6 +453,7 @@ job_dateUpdated: Float # TODO: change back to Date after switching to district-ui-graphql balance: Float + balanceLeft: Float job_tokenType: String job_tokenAmount: Float job_tokenAddress: String diff --git a/ui/src/ethlance/ui/page/new_invoice.cljs b/ui/src/ethlance/ui/page/new_invoice.cljs index 2c94758fe..09dbb3e24 100644 --- a/ui/src/ethlance/ui/page/new_invoice.cljs +++ b/ui/src/ethlance/ui/page/new_invoice.cljs @@ -41,7 +41,7 @@ :job/token-amount :job/token-type :job/token-id - :balance + :balance-left [:token-details [:token-detail/id :token-detail/type @@ -69,7 +69,7 @@ token-display-name (name (or (@job-token :symbol) (@job-token :type) "")) job-token-details (get-in @*invoiced-job [:job :token-details]) job-token-decimals (:token-detail/decimals job-token-details) - balance-left (get-in @*invoiced-job [:job :balance]) + balance-left (get-in @*invoiced-job [:job :balance-left]) show-balance-left? (not (nil? balance-left)) no-job-selected? (nil? @*invoiced-job) focus-on-element (fn [id _event] (.focus (.getElementById js/document id))) From a4337ae80fe854a58421ee56f1928a83548b7be7 Mon Sep 17 00:00:00 2001 From: ginesdt Date: Mon, 18 Aug 2025 10:47:40 +0200 Subject: [PATCH 3/3] add balance left to job description --- ui/src/ethlance/ui/page/job_detail.cljs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/src/ethlance/ui/page/job_detail.cljs b/ui/src/ethlance/ui/page/job_detail.cljs index 1e47b5787..85b81312a 100644 --- a/ui/src/ethlance/ui/page/job_detail.cljs +++ b/ui/src/ethlance/ui/page/job_detail.cljs @@ -571,6 +571,7 @@ has-accepted-arbiter? (not (nil? (get results :job/arbiter))) token-details (get results :token-details) job-balance (get results :balance) + job-balance-left (get results :balance-left) invoices (get-in results [:invoices :items]) unpaid-invoices (filter #(= "created" (:invoice/status %)) invoices) @@ -594,7 +595,9 @@ [:div.ticket-listing [:div.ticket [:div.label "Available Funds"] - [c-token-info job-balance token-details]]] + [c-token-info job-balance token-details] + [:div.label "Without unpaid invoices"] + [c-token-info job-balance-left token-details]]] (when job-ongoing? [c-add-funds contract-address (:job/token-id results) token-details]) [:div.profiles @@ -654,6 +657,7 @@ :job/token-address :job/token-id :balance + :balance-left [:token-details [:token-detail/id