Makes tasks card

This commit is contained in:
2024-04-28 20:29:21 -07:00
parent f6b413a9f5
commit 0b2ec31160
5 changed files with 125 additions and 66 deletions

1
.gitignore vendored
View File

@@ -44,3 +44,4 @@ data/solr/data/plaid_merchants/data/
data/solr/data/logs data/solr/data/logs
data/solr/logs data/solr/logs
.vscode/** .vscode/**
sysco-poller/**/*.csv

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
(:require [amazonica.core :refer [defcredential]] (:require [amazonica.core :refer [defcredential]]
[auto-ap.client-routes :as client-routes] [auto-ap.client-routes :as client-routes]
[auto-ap.datomic :refer [conn pull-many]] [auto-ap.datomic :refer [conn pull-many]]
[auto-ap.graphql.utils :refer [limited-clients]] [auto-ap.graphql.utils :refer [extract-client-ids limited-clients]]
[auto-ap.logging :as alog] [auto-ap.logging :as alog]
[auto-ap.routes.auth :as auth] [auto-ap.routes.auth :as auth]
[auto-ap.routes.exports :as exports] [auto-ap.routes.exports :as exports]
@@ -304,12 +304,26 @@
{:status 500 {:status 500
:body (pr-str e)}))))) :body (pr-str e)})))))
(defn wrap-trim-clients [handler]
(fn [request]
(let [valid-clients (extract-client-ids (:clients request)
(:client request)
(:client-id (:parsed-query-params request))
(when (:client-code (:parsed-query-params request))
[:client/code (:client-code (:parsed-query-params request))]))
trimmed-clients (->> valid-clients (take 20) set)]
(handler (assoc request :valid-client-ids valid-clients
:valid-trimmed-client-ids trimmed-clients
:first-client-id (first valid-clients)
:clients-trimmed? (not= (count trimmed-clients) (count valid-clients)))))))
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defonce app (defonce app
(-> route-handler (-> route-handler
(wrap-hx-current-url-params) (wrap-hx-current-url-params)
(wrap-guess-route) (wrap-guess-route)
(wrap-logging) (wrap-logging)
(wrap-trim-clients)
(wrap-hydrate-clients) (wrap-hydrate-clients)
(wrap-store-client-in-session) (wrap-store-client-in-session)
(wrap-gunzip-jwt) (wrap-gunzip-jwt)

View File

@@ -1,9 +1,10 @@
(ns auto-ap.ssr.dashboard (ns auto-ap.ssr.dashboard
(:require [auto-ap.datomic :refer [conn]] (:require [auto-ap.datomic :refer [conn]]
[auto-ap.graphql.ledger :refer [get-profit-and-loss-raw]] [auto-ap.graphql.ledger :refer [get-profit-and-loss-raw]]
[auto-ap.graphql.utils :refer [<-graphql extract-client-ids]] [auto-ap.graphql.utils :refer [<-graphql]]
[auto-ap.ledger.reports :as r] [auto-ap.ledger.reports :as r]
[auto-ap.routes.dashboard :as d-routes] [auto-ap.routes.dashboard :as d-routes]
[auto-ap.routes.invoice :as i-routes]
[auto-ap.routes.utils :refer [wrap-admin [auto-ap.routes.utils :refer [wrap-admin
wrap-client-redirect-unauthenticated]] wrap-client-redirect-unauthenticated]]
[auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr-routes :as ssr-routes]
@@ -15,14 +16,17 @@
[bidi.bidi :as bidi] [bidi.bidi :as bidi]
[clj-time.coerce :as coerce] [clj-time.coerce :as coerce]
[clj-time.core :as time] [clj-time.core :as time]
[datomic.api :as dc])) [datomic.api :as dc]
[hiccup.util :as hu]
[auto-ap.client-routes :as client-routes]
[cemerick.url :as url]))
(defn bank-accounts-card [request] (defn bank-accounts-card [request]
(com/card {:class "inline-block " } (com/card {:class "inline-block " }
[:div.p-4 [:div.p-4
[:h1.text-2xl.font-bold "Bank Accounts"] [:h1.text-2xl.font-bold "Bank Accounts"]
[:div {:class "max-h-[900px] overflow-scroll"} [:div {:class "max-h-[900px] overflow-scroll"}
(for [c (:clients request) (for [c (:valid-trimmed-client-ids request)
b (:client/bank-accounts (dc/pull (dc/db conn) '[{:client/bank-accounts b (:client/bank-accounts (dc/pull (dc/db conn) '[{:client/bank-accounts
[:bank-account/current-balance [:bank-account/current-balance
@@ -36,7 +40,7 @@
[:yodlee-account/last-synced :xform clj-time.coerce/from-date]]} [:yodlee-account/last-synced :xform clj-time.coerce/from-date]]}
{:bank-account/plaid-account [:plaid-account/balance {:bank-account/plaid-account [:plaid-account/balance
[:plaid-account/last-synced :xform clj-time.coerce/from-date]]}]}] [:plaid-account/last-synced :xform clj-time.coerce/from-date]]}]}]
(:db/id c))) c))
:when (not= :bank-account-type/cash (:bank-account/type b))] :when (not= :bank-account-type/cash (:bank-account/type b))]
[:div.flex.flex-col.p-4.border-b-2.border-gray-200 [:div.flex.flex-col.p-4.border-b-2.border-gray-200
[:div.font-bold.text-gray-700 (:client/name c)] [:div.font-bold.text-gray-700 (:client/name c)]
@@ -78,12 +82,7 @@
#_[:div.inline-flex.justify-between.items-baseline]]])]])) #_[:div.inline-flex.justify-between.items-baseline]]])]]))
(defn sales-chart-card [request] (defn sales-chart-card [request]
(let [ (let [ totals
valid-clients (extract-client-ids (:clients request)
(:client-id request)
(when (:client-code request)
[:client/code (:client-code request)]))
totals
(->> (dc/q '[:find ?sd (sum ?total) (->> (dc/q '[:find ?sd (sum ?total)
:with ?e :with ?e
:in $ [?clients ?start-date ?end-date] :in $ [?clients ?start-date ?end-date]
@@ -92,7 +91,7 @@ valid-clients (extract-client-ids (:clients request)
[(iol-ion.query/iso-date ?d) ?sd] [(iol-ion.query/iso-date ?d) ?sd]
[?e :sales-order/total ?total]] [?e :sales-order/total ?total]]
(dc/db conn) (dc/db conn)
[valid-clients [(:valid-trimmed-client-ids request)
(coerce/to-date (time/with-time-at-start-of-day (time/plus (time/now) (time/days -14)))) (coerce/to-date (time/with-time-at-start-of-day (time/plus (time/now) (time/days -14))))
(coerce/to-date (time/with-time-at-start-of-day (time/plus (time/now) (time/days 1))))]) (coerce/to-date (time/with-time-at-start-of-day (time/plus (time/now) (time/days 1))))])
(sort-by first))] (sort-by first))]
@@ -125,11 +124,7 @@ valid-clients (extract-client-ids (:clients request)
});"}]]))) });"}]])))
(defn expense-pie-card [request] (defn expense-pie-card [request]
(let [valid-clients (extract-client-ids (:clients request) (let [ totals
(:client-id request)
(when (:client-code request)
[:client/code (:client-code request)]))
totals
(->> (dc/q '[:find ?an (sum ?amt) (->> (dc/q '[:find ?an (sum ?amt)
:with ?iea :with ?iea
:in $ [?clients ?start-date ?end-date] :in $ [?clients ?start-date ?end-date]
@@ -140,7 +135,7 @@ valid-clients (extract-client-ids (:clients request)
[?iea :invoice-expense-account/amount ?amt] [?iea :invoice-expense-account/amount ?amt]
[?ea :account/name ?an]] [?ea :account/name ?an]]
(dc/db conn) (dc/db conn)
[valid-clients [(:valid-trimmed-client-ids request)
(coerce/to-date (time/with-time-at-start-of-day (time/plus (time/now) (time/months -1)))) (coerce/to-date (time/with-time-at-start-of-day (time/plus (time/now) (time/months -1))))
(coerce/to-date (time/plus (time/with-time-at-start-of-day (time/now)) (time/days 1)))]) (coerce/to-date (time/plus (time/with-time-at-start-of-day (time/now)) (time/days 1)))])
(sort-by last) (sort-by last)
@@ -175,20 +170,15 @@ valid-clients (extract-client-ids (:clients request)
[:h1.text-2xl.font-bold.text-gray-700 [:h1.text-2xl.font-bold.text-gray-700
"Profit and Loss" "Profit and Loss"
] ]
(let [all-clients (extract-client-ids (:clients request) (let [ data (<-graphql (get-profit-and-loss-raw (:valid-trimmed-client-ids request)
(:client-id request)
(when (:client-code request)
[:client/code (:client-code request)]))
clients (take 10 all-clients)
data (<-graphql (get-profit-and-loss-raw clients
[{:start (time/plus (time/now) (time/days -90)) [{:start (time/plus (time/now) (time/days -90))
:end (time/now)}])) :end (time/now)}]))
data (r/->PNLData {} (:accounts (first (:periods data))) {}) data (r/->PNLData {} (:accounts (first (:periods data))) {})
sales (r/aggregate-accounts (r/filter-categories data [ :sales])) sales (r/aggregate-accounts (r/filter-categories data [ :sales]))
expenses (r/aggregate-accounts (r/filter-categories data [ :cogs :payroll :controllable :fixed-overhead :ownership-controllable ]))] expenses (r/aggregate-accounts (r/filter-categories data [ :cogs :payroll :controllable :fixed-overhead :ownership-controllable ]))]
(list (list
(when (not= (count all-clients) (count clients)) #_(when (not= (count all-clients) (count clients))
[:div.bg-yellow-100.rounded-lg.p-4.my-2.text-yellow-900 "Warning: Too many clients are selected. This report only shows 10 of the selected clients."]) )
[:canvas.w-full.h-full.p-8 {:x-data (hx/json {:chart nil [:canvas.w-full.h-full.p-8 {:x-data (hx/json {:chart nil
:labels [(format "Income $%,.2f" sales) (format "Expenses $%,.2f" expenses)] :labels [(format "Income $%,.2f" sales) (format "Expenses $%,.2f" expenses)]
:data [sales expenses]}) :data [sales expenses]})
@@ -219,13 +209,59 @@ valid-clients (extract-client-ids (:clients request)
[:div [:div
"Expenses: " (format "$%,.2f" expenses)])))) "Expenses: " (format "$%,.2f" expenses)]))))
(defn tasks-card [request]
(com/card {:class "w-full h-full p-4 space-y-2"}
[:h1.text-2xl.font-bold.text-gray-700
"Tasks"]
(let [[unpaid-invoice-count unpaid-invoice-amount]
(first (dc/q '[:find (count ?e) (sum ?ab)
:in $ [?clients ?start-date ?end-date]
:where [(iol-ion.query/scan-invoices $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
[?e :invoice/status :invoice-status/unpaid]
[?e :invoice/outstanding-balance ?ab]]
(dc/db conn)
[(:valid-trimmed-client-ids request)
(coerce/to-date (time/with-time-at-start-of-day (time/plus (time/now) (time/years -1))))
nil]))
[uncategorized-transaction-count uncategorized-transaction-amount]
(first (dc/q '[:find (count ?e) (sum ?am)
:in $ [?clients ?start-date ?end-date]
:where [(iol-ion.query/scan-transactions $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
[?e :transaction/approval-status :transaction-approval-status/requires-feedback]
[?e :transaction/amount ?am]]
(dc/db conn)
[(:valid-trimmed-client-ids request)
(coerce/to-date (time/with-time-at-start-of-day (time/plus (time/now) (time/years -1))))
nil]))]
(list
(when (not= 0 (or unpaid-invoice-count 0))
[:div.bg-gray-50.rounded.p-4
[:span "You have " (str unpaid-invoice-count) " unpaid invoices with an outstanding balance of " (format "$%,.2f" unpaid-invoice-amount) ". " ]
(com/link {:href (hu/url (bidi.bidi/path-for ssr-routes/only-routes ::i-routes/unpaid-page)
{:date-range "year"})
:hx-boost "true"}
"Pay now")
])
(when (not= 0 (or uncategorized-transaction-count 0))
[:div.bg-gray-50.rounded.p-4
[:span "You have " (str uncategorized-transaction-count) " transactions needing your feedback. " ]
(com/link {:href (str (bidi.bidi/path-for client-routes/routes :requires-feedback-transactions)
"?date-range="
(url/url-encode (pr-str {:start (atime/unparse-local (time/plus (time/now) (time/years -1)) atime/iso-date) :end (atime/unparse-local (time/now) atime/iso-date)}))) }
"Review now")
])))))
(defn- page-contents [request] (defn- page-contents [request]
[:div [:div
[:div {:class "grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-4 auto-rows-fr max-h-[970px]"} [:div {:class "grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-4 auto-rows-fr max-h-[970px]"}
[:div (expense-pie-card request)] [:div (expense-pie-card request)]
[:div (com/card {:class "w-full h-full p-4"} [:div
[:h1.text-2xl.font-bold.text-gray-700 (tasks-card request)]
"Tasks"])]
[:div.row-span-2 [:div.row-span-2
(bank-accounts-card request)] (bank-accounts-card request)]
@@ -255,6 +291,8 @@ valid-clients (extract-client-ids (:clients request)
(com/breadcrumbs {} (com/breadcrumbs {}
[:a {:href (bidi/path-for ssr-routes/only-routes ::d-routes/page)} [:a {:href (bidi/path-for ssr-routes/only-routes ::d-routes/page)}
"Dashboard"]) "Dashboard"])
(when (:clients-trimmed? request)
[:div.bg-yellow-100.rounded-lg.p-4.my-2.text-yellow-900.border-1 "Warning: These reports are only for twenty of the selected customers. Please select a specific customer to see more detail."])
(page-contents request)) (page-contents request))
"Dashboard")) "Dashboard"))

View File

@@ -1,25 +1,28 @@
(ns auto-ap.views.pages.transactions (ns auto-ap.views.pages.transactions
(:require [auto-ap.effects.forward :as forward] (:require [auto-ap.effects.forward :as forward]
[auto-ap.forms :as forms] [auto-ap.forms :as forms]
[auto-ap.status :as status]
[auto-ap.subs :as subs] [auto-ap.subs :as subs]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.components.layouts [auto-ap.views.components.layouts
:refer :refer
[appearing-side-bar side-bar-layout]] [appearing-side-bar side-bar-layout]]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.pages.data-page :as data-page] [auto-ap.views.pages.data-page :as data-page]
[auto-ap.views.pages.transactions.common :refer [transaction-read data-params->query-params]] [auto-ap.views.pages.transactions.bulk-updates :as bulk]
[auto-ap.views.pages.transactions.common :refer [data-params->query-params
transaction-read]]
[auto-ap.views.pages.transactions.form :as edit] [auto-ap.views.pages.transactions.form :as edit]
[auto-ap.views.pages.transactions.manual :as manual] [auto-ap.views.pages.transactions.manual :as manual]
[auto-ap.views.pages.transactions.bulk-updates :as bulk]
[auto-ap.views.pages.transactions.side-bar :as side-bar] [auto-ap.views.pages.transactions.side-bar :as side-bar]
[auto-ap.views.pages.transactions.table :as table] [auto-ap.views.pages.transactions.table :as table]
[auto-ap.views.utils :refer [dispatch-event with-user date->str standard]] [auto-ap.views.utils :refer [date->str dispatch-event standard
with-user]]
[auto-ap.views.utils :as u]
[cljs-time.core :as time] [cljs-time.core :as time]
[clojure.string :as str]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[reagent.core :as reagent] [reagent.core :as reagent]
[vimsical.re-frame.fx.track :as track] [vimsical.re-frame.fx.track :as track]))
[auto-ap.status :as status]
[clojure.string :as str]))
@@ -122,35 +125,38 @@
(re-frame/reg-event-fx (re-frame/reg-event-fx
::mounted ::mounted
(fn [{:keys [db]} _] (fn [{:keys [db]} _]
{:db (assoc-in db [::data-page/settled-filters ::page :date-range] {:start (date->str (time/plus (time/now) (time/months -1)) (let [db (if (:date-range (u/query-params))
standard)}) db
::track/register {:id ::params (assoc-in db [::data-page/settled-filters ::page :date-range] {:start (date->str (time/plus (time/now) (time/months -1))
:subscription [::data-page/params ::page] standard)}))]
:event-fn (fn [params] {:db db
[::params-change params])} ::track/register {:id ::params
::forward/register [{:id ::updated :subscription [::data-page/params ::page]
:events #{::edit/edited} :event-fn (fn [params]
:event-fn (fn [[_ edited-transaction]] [::params-change params])}
[::data-page/updated-entity ::page edited-transaction])} ::forward/register [{:id ::updated
{:id ::manual-import :events #{::edit/edited}
:events #{::manual/import-completed} :event-fn (fn [[_ edited-transaction]]
:event-fn (fn [[_ result]] [::data-page/updated-entity ::page edited-transaction])}
[::status/info ::manual-import {:id ::manual-import
(str "Successfully " :events #{::manual/import-completed}
(str/join ", " :event-fn (fn [[_ result]]
[(when-let [imported (:import-batch/imported result)] [::status/info ::manual-import
(str "imported " imported)) (str "Successfully "
(when-let [extant (:import-batch/extant result)] (str/join ", "
(str "extant " extant)) [(when-let [imported (:import-batch/imported result)]
(when-let [suppressed (:import-batch/suppressed result)] (str "imported " imported))
(str "suppressed " suppressed)) (when-let [extant (:import-batch/extant result)]
(when-let [not-ready (:import-batch/not-ready result)] (str "extant " extant))
(str "too early " not-ready)) (when-let [suppressed (:import-batch/suppressed result)]
(when-let [error (:validation-error result)] (str "suppressed " suppressed))
(str "errored " error))]) (when-let [not-ready (:import-batch/not-ready result)]
" transactions." (str "too early " not-ready))
(when (:sample-error result) (when-let [error (:validation-error result)]
(str " Sample error: " (:info (:sample-error result)))))])}]})) (str "errored " error))])
" transactions."
(when (:sample-error result)
(str " Sample error: " (:info (:sample-error result)))))])}]})))
(defn action-buttons [] (defn action-buttons []
(let [is-admin? @(re-frame/subscribe [::subs/is-admin?]) (let [is-admin? @(re-frame/subscribe [::subs/is-admin?])