diff --git a/resources/public/output.css b/resources/public/output.css index 9a28ad90..bf55e1ee 100644 --- a/resources/public/output.css +++ b/resources/public/output.css @@ -1859,6 +1859,11 @@ input:checked + .toggle-bg { background-color: rgb(253 246 178 / var(--tw-bg-opacity)); } +.bg-red-300 { + --tw-bg-opacity: 1; + background-color: rgb(255 104 104 / var(--tw-bg-opacity)); +} + .bg-opacity-50 { --tw-bg-opacity: 0.5; } diff --git a/src/clj/auto_ap/jobs/insight_outcome_recommendation.clj b/src/clj/auto_ap/jobs/insight_outcome_recommendation.clj index 45cbb30c..b1cd9b10 100644 --- a/src/clj/auto_ap/jobs/insight_outcome_recommendation.clj +++ b/src/clj/auto_ap/jobs/insight_outcome_recommendation.clj @@ -64,23 +64,27 @@ (assoc similar-transaction :score score))) (defn similar->recommendation [txs client] - (->> txs - (reduce (fn [acc t] - (-> acc - (update-in [[(:db/id (:transaction/vendor t)) - (:db/id (:transaction-account/account (first (:transaction/accounts t))))] - :count] - (fnil inc 0)) - (update-in [[(:db/id (:transaction/vendor t)) - (:db/id (:transaction-account/account (first (:transaction/accounts t))))] - :seen-by-client?] - (fn [seen-by-client?] (or seen-by-client? (= (:db/id (:transaction/client t)) - client)))))) - {}) - (map (fn [[k v]] - (-> k - (conj (:count v)) - (conj (:seen-by-client? v))))))) + (try + (->> txs + (reduce (fn [acc t] + (-> acc + (update-in [[(:db/id (:transaction/vendor t)) + (:db/id (:transaction-account/account (first (:transaction/accounts t))))] + :count] + (fnil inc 0)) + (update-in [[(:db/id (:transaction/vendor t)) + (:db/id (:transaction-account/account (first (:transaction/accounts t))))] + :seen-by-client?] + (fn [seen-by-client?] (or seen-by-client? (= (:db/id (:transaction/client t)) + client)))))) + {}) + (map (fn [[k v]] + (-> k + (conj (:count v)) + (conj (:seen-by-client? v)))))) + (catch Exception e + (println e) + []))) (defn get-outcome-recommendations [] (for [[transaction client] (get-recent-transactions) diff --git a/src/clj/auto_ap/ssr/components/buttons.clj b/src/clj/auto_ap/ssr/components/buttons.clj index 564d2cf9..e323a833 100644 --- a/src/clj/auto_ap/ssr/components/buttons.clj +++ b/src/clj/auto_ap/ssr/components/buttons.clj @@ -7,13 +7,15 @@ (defn button- [params & children] [:button (update params :class #(cond-> % - true (str " text-white focus:ring-4 font-bold rounded-lg text-xs p-3 text-center mr-2 inline-flex items-center hover:scale-105 transition duration-100 justify-center") - (= :secondary (:color params)) (str " bg-blue-500 hover:bg-blue-600 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700") - (= :primary (:color params)) (str " bg-green-500 hover:bg-green-600 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 "))) + true (str " focus:ring-4 font-bold rounded-lg text-xs p-3 text-center mr-2 inline-flex items-center hover:scale-105 transition duration-100 justify-center") + (= :secondary (:color params)) (str " text-white bg-blue-500 hover:bg-blue-600 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700") + (= :primary (:color params)) (str " text-white bg-green-500 hover:bg-green-600 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 ") + (nil? (:color params)) + (str " bg-white dark:bg-gray-600 border-gray-300 dark:border-gray-700 text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-100 font-medium border border-gray-300 dark:border-gray-700"))) [:div.htmx-indicator.flex.items-center (svg/spinner {:class "inline w-4 h-4 text-white"}) [:div.ml-3 "Loading..."]] - (into [:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center ] children)]) + (into [:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center] children)]) (defn icon-button- [params & children] (into diff --git a/src/clj/auto_ap/ssr/components/tags.clj b/src/clj/auto_ap/ssr/components/tags.clj index edf4b59f..c8b19820 100644 --- a/src/clj/auto_ap/ssr/components/tags.clj +++ b/src/clj/auto_ap/ssr/components/tags.clj @@ -20,4 +20,4 @@ children)) (defn badge- [params & children] - [:div {:class "absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white bg-red-500 border-2 border-white rounded-full -top-2 -right-2 dark:border-gray-900"} children]) + [:div {:class "absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white bg-red-300 border-3 border-white rounded-full -top-2 -right-2 dark:border-gray-900"} children]) diff --git a/src/clj/auto_ap/ssr/invoice/glimpse.clj b/src/clj/auto_ap/ssr/invoice/glimpse.clj index 19b10069..ad632103 100644 --- a/src/clj/auto_ap/ssr/invoice/glimpse.clj +++ b/src/clj/auto_ap/ssr/invoice/glimpse.clj @@ -23,7 +23,9 @@ [datomic.api :as dc] [hiccup2.core :as hiccup] [iol-ion.tx :refer [random-tempid]] - [auto-ap.client-routes :as client-routes]) + [auto-ap.client-routes :as client-routes] + [auto-ap.datomic.vendors :as d-vendors] + [clj-time.core :as time]) (:import (java.util UUID))) @@ -328,10 +330,16 @@ invoice_dropzone = new Dropzone(\"#invoice\", { [_ total] (:textract-invoice/total textract-invoice) [_ date] (:textract-invoice/date textract-invoice) [_ invoice-number] (:textract-invoice/invoice-number textract-invoice) + vendor (dc/pull (dc/db conn) d-vendors/default-read vendor-id) location (when client-id (->> (dc/pull (dc/db conn) '[:client/locations] client-id) :client/locations - first))] + first)) + due (and (:vendor/terms vendor) + (coerce/to-date + (time/plus date (time/days (d-vendors/terms-for-client-id vendor client-id))))) + scheduled-payment (and (d-vendors/automatically-paid-for-client-id? vendor client-id) + due)] (when (and client-id date invoice-number vendor-id total) {:db/id (random-tempid) :invoice/client client-id @@ -340,6 +348,8 @@ invoice_dropzone = new Dropzone(\"#invoice\", { :invoice/invoice-number invoice-number :invoice/total total :invoice/date date + :invoice/due (coerce/to-date due) + :invoice/scheduled-payment (coerce/to-date scheduled-payment) :invoice/location location :invoice/import-status :import-status/imported :invoice/outstanding-balance total diff --git a/src/clj/auto_ap/ssr/transaction/insights.clj b/src/clj/auto_ap/ssr/transaction/insights.clj index 8ad6d3c7..1f9188b0 100644 --- a/src/clj/auto_ap/ssr/transaction/insights.clj +++ b/src/clj/auto_ap/ssr/transaction/insights.clj @@ -26,6 +26,20 @@ :transaction/bank-account [:bank-account/code]} :transaction/account-confidence :transaction/date]) +(defn parse-outcome [tx] + (update tx :transaction/outcome-recommendation + (fn [ors] + (map + (fn [[v a c s]] + {:vendor (dc/pull (dc/db conn) + [:vendor/name :db/id] + v) + :account (dc/pull (dc/db conn) + [:account/name :db/id] + a) + :count c + :seen-by-client? s}) + ors)))) (defn transaction-recommendations [identity selected-client & {:keys [after]}] (let [visible-clients (visible-clients identity)] @@ -54,20 +68,7 @@ (#(if after (drop 1 %) %)) - (map (fn [tx] - (update tx :transaction/outcome-recommendation - (fn [ors] - (map - (fn [[v a c s]] - {:vendor (dc/pull (dc/db conn) - [:vendor/name :db/id] - v) - :account (dc/pull (dc/db conn) - [:account/name :db/id] - a) - :count c - :seen-by-client? s}) - ors))))) + (map parse-outcome) (take 50) (into [])))) @@ -144,39 +145,52 @@ [:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))])) (com/data-grid-right-stack-cell {} - (when-not hide-actions? - [:div.flex.gap-2.flex-col {:style {:width "25em"}} - (for [or (sort-by (comp - :count) - (:transaction/outcome-recommendation r))] - [:form {:hx-post (bidi/path-for ssr-routes/only-routes - :transaction-insight-code - :transaction-id (:db/id r)) - :hx-target "closest tr"} - (when-let [vendor-id (:db/id (:vendor or))] - [:input {:type :hidden :value vendor-id :name "vendor"}]) - (when-let [account-id (:db/id (:account or))] - [:input {:type :hidden :value account-id :name "account"}]) - - (com/button {:color (if (:seen-by-client? or) - :primary - :secondary) - :style {:position "relative"}} - (:vendor/name (:vendor or)) - (when (:vendor/name (:vendor or)) - " | ") - (:account/name (:account or)) - (com/badge {:color :secondary} - (:count or)))]) - (com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes - :transaction-insight-explain - :transaction-id (:db/id r)) - :hx-target "#modal-holder" - :hx-swap "outerHTML"} - svg/question)])))) + [:div.flex.gap-2.flex-col {:style {:width "25em"}} + (for [or (take 3 (sort-by (comp - :count) + (:transaction/outcome-recommendation r)))] + [:form {:hx-post (bidi/path-for ssr-routes/only-routes + :transaction-insight-code + :transaction-id (:db/id r)) + :hx-target "closest tr" + :hx-swap "outerHTML" + :disabled hide-actions?} + (when-let [vendor-id (:db/id (:vendor or))] + [:input {:type :hidden :value vendor-id :name "vendor"}]) + (when-let [account-id (:db/id (:account or))] + [:input {:type :hidden :value account-id :name "account"}]) + + (com/button {:color (if (:seen-by-client? or) + :primary + :secondary) + :style {:position "relative" + :display "block" + :width "100%"}} + (:vendor/name (:vendor or)) + (when (:vendor/name (:vendor or)) + " | ") + (:account/name (:account or)) + (com/badge {:color :secondary} + (:count or)))]) + [:div.flex.flex-row.gap-2 + (com/button {:hx-get (bidi/path-for ssr-routes/only-routes + :transaction-insight-explain + :transaction-id (:db/id r)) + :hx-target "#modal-holder" + :hx-swap "outerHTML"} + [:div.flex + svg/question + "Explain"]) + (com/button {:hx-delete (bidi/path-for ssr-routes/only-routes + :transaction-insight-disapprove + :transaction-id (:db/id r)) + :hx-target "closest tr" + :hx-swap "outerHTML"} + [:div.flex + svg/question + "Reject"])]]))) (defn code [{:keys [identity session] {:keys [transaction-id]} :route-params {:strs [vendor account]} :form-params}] - (let [approval-details (dc/pull (dc/db conn) [{:transaction/recommended-account [:account/location :db/id]} - :transaction/recommended-vendor + (let [approval-details (dc/pull (dc/db conn) [:transaction/recommended-vendor :transaction/amount :db/id {:transaction/client [:client/locations]}] @@ -201,25 +215,25 @@ :transaction-account/account (-> account :db/id) :transaction-account/amount (* 0.01 cents) :transaction-account/location location}) - (spread-cents cents-to-distribute (count valid-locations))))}]] + (spread-cents cents-to-distribute (count valid-locations))))}] + db-before (dc/db conn)] @(dc/transact conn [updated-transaction]) (html-response (transaction-row - (dc/pull (dc/db conn) - pull-expr - (Long/parseLong transaction-id)) - :auto-remove? true + (parse-outcome (dc/pull db-before + pull-expr + (Long/parseLong transaction-id))) :hide-actions? true - :class "live-added")))) + :class "live-added" + "_" (hiccup/raw "init transition opacity to 0 then remove me"))))) (defn disapprove [{:keys [identity session] {:keys [transaction-id]} :route-params}] - (let [transaction-id (cond-> transaction-id string? (Long/parseLong))] - @(dc/transact conn [[:upsert-transaction {:db/id transaction-id :transaction/recommended-account nil :transaction/recommended-vendor nil}]]) + (let [transaction-id (cond-> transaction-id string? (Long/parseLong)) + db-before (dc/db conn)] + @(dc/transact conn [[:upsert-transaction {:db/id transaction-id :transaction/outcome-recommendation nil}]]) (html-response (transaction-row - (dc/pull (dc/db conn) pull-expr transaction-id) - :auto-remove? true - :hide-actions? true - :class "live-removed" - "_" (hiccup/raw "init transition opacity to 0 then remove me"))))) + (parse-outcome (dc/pull db-before pull-expr transaction-id)) + :hide-actions? true + "_" (hiccup/raw "init transition opacity to 0 over 500ms then remove me"))))) (defn explain [{:keys [identity session] {:keys [transaction-id]} :route-params}] (let [r (dc/pull (dc/db conn) pull-expr @@ -288,7 +302,8 @@ (com/data-grid-header {:style {:width "15em"}} "Account") (com/data-grid-header {:style {:width "8em"}} "Date") (com/data-grid-header {} "Description") - (com/data-grid-header {:style {:width "8em"}} "Amount")]}))) + (com/data-grid-header {:style {:width "8em"}} "Amount") + (com/data-grid-header {})]}))) (defn insight-table [{:keys [session identity]}] (html-response (insight-table* {:selected-client diff --git a/src/cljc/auto_ap/shared_views/admin/side_bar.cljc b/src/cljc/auto_ap/shared_views/admin/side_bar.cljc index a85aff5e..c2dacca6 100644 --- a/src/cljc/auto_ap/shared_views/admin/side_bar.cljc +++ b/src/cljc/auto_ap/shared_views/admin/side_bar.cljc @@ -34,7 +34,6 @@ :test-route #{:admin} :active-route active-route :route :admin}) - [:p.menu-label "Setup"] (menu-item {:label "Clients" @@ -78,7 +77,7 @@ :test-route #{:admin-jobs} :active-route active-route :route :admin-jobs - :icon-style {:font-size "25px"}}) + :icon-style {:font-size "25px"}}) [:p.menu-label "Import"] (menu-item {:label "Excel Invoices" :icon-class "fa fa-download" @@ -96,8 +95,14 @@ :active-route active-route :route :admin-ezcater-xls :icon-style {:font-size "25px"}}) - - (into [:div ] children)]) + (menu-item {:label "Invoice glimpse" + :icon-class "icon icon-cog-play-1" + :test-route #{:invoice-glimpse} + :active-route active-route + :route :invoice-glimpse + :icon-style {:font-size "25px"}}) + + (into [:div] children)]) #?(:clj (defn admin-side-bar [active-page]