Aside from urls and nomenclature, glimpse works.
This commit is contained in:
@@ -76,7 +76,7 @@
|
|||||||
|
|
||||||
;; reset relationships if it's refs, and not a lookup (i.e., seq of maps, or empty seq)
|
;; reset relationships if it's refs, and not a lookup (i.e., seq of maps, or empty seq)
|
||||||
|
|
||||||
(and (sequential? v) (= :db.type/tuple (ident->value-type a)))
|
(and (sequential? v) (= :db.type/tuple (ident->value-type a)) (not (= :db.cardinality/many (ident->cardinality a))))
|
||||||
(conj ops [:db/add e a v])
|
(conj ops [:db/add e a v])
|
||||||
|
|
||||||
(and (sequential? v) (= :db.type/ref (ident->value-type a)) (every? map? v))
|
(and (sequential? v) (= :db.type/ref (ident->value-type a)) (every? map? v))
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1236,6 +1236,10 @@ input:checked + .toggle-bg {
|
|||||||
margin-top: 1.25rem;
|
margin-top: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ml-auto {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -1520,6 +1524,10 @@ input:checked + .toggle-bg {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content-center {
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.items-start {
|
.items-start {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
@@ -1573,6 +1581,11 @@ input:checked + .toggle-bg {
|
|||||||
column-gap: 0.5rem;
|
column-gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gap-x-4 {
|
||||||
|
-moz-column-gap: 1rem;
|
||||||
|
column-gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.-space-x-px > :not([hidden]) ~ :not([hidden]) {
|
.-space-x-px > :not([hidden]) ~ :not([hidden]) {
|
||||||
--tw-space-x-reverse: 0;
|
--tw-space-x-reverse: 0;
|
||||||
margin-right: calc(-1px * var(--tw-space-x-reverse));
|
margin-right: calc(-1px * var(--tw-space-x-reverse));
|
||||||
@@ -1632,6 +1645,10 @@ input:checked + .toggle-bg {
|
|||||||
border-color: rgb(243 244 246 / var(--tw-divide-opacity));
|
border-color: rgb(243 244 246 / var(--tw-divide-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.justify-self-end {
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
.overflow-auto {
|
.overflow-auto {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
@@ -1870,6 +1887,11 @@ input:checked + .toggle-bg {
|
|||||||
background-color: rgb(253 246 178 / var(--tw-bg-opacity));
|
background-color: rgb(253 246 178 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-blue-50 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(230 245 253 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.bg-opacity-50 {
|
.bg-opacity-50 {
|
||||||
--tw-bg-opacity: 0.5;
|
--tw-bg-opacity: 0.5;
|
||||||
}
|
}
|
||||||
@@ -2014,6 +2036,10 @@ input:checked + .toggle-bg {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.text-2xl {
|
.text-2xl {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
@@ -2150,6 +2176,11 @@ input:checked + .toggle-bg {
|
|||||||
color: rgb(114 59 19 / var(--tw-text-opacity));
|
color: rgb(114 59 19 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-blue-400 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(51 176 238 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.underline {
|
.underline {
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
}
|
}
|
||||||
@@ -2736,6 +2767,11 @@ input:checked + .toggle-bg {
|
|||||||
color: rgb(250 202 21 / var(--tw-text-opacity));
|
color: rgb(250 202 21 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:is(.dark .dark\:text-blue-400) {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(51 176 238 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
:is(.dark .dark\:placeholder-gray-400)::-moz-placeholder {
|
:is(.dark .dark\:placeholder-gray-400)::-moz-placeholder {
|
||||||
--tw-placeholder-opacity: 1;
|
--tw-placeholder-opacity: 1;
|
||||||
color: rgb(156 163 175 / var(--tw-placeholder-opacity));
|
color: rgb(156 163 175 / var(--tw-placeholder-opacity));
|
||||||
|
|||||||
@@ -2127,4 +2127,67 @@
|
|||||||
:db/doc "A url to the pdf on s3"
|
:db/doc "A url to the pdf on s3"
|
||||||
:db/valueType :db.type/string
|
:db/valueType :db.type/string
|
||||||
:db/cardinality :db.cardinality/one}
|
:db/cardinality :db.cardinality/one}
|
||||||
]
|
|
||||||
|
{:db/ident :textract-invoice/total
|
||||||
|
:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/string :db.type/double]
|
||||||
|
:db/cardinality :db.cardinality/one}
|
||||||
|
|
||||||
|
{:db/ident :textract-invoice/total-options
|
||||||
|
:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/string :db.type/double]
|
||||||
|
:db/cardinality :db.cardinality/many}
|
||||||
|
|
||||||
|
{:db/ident :textract-invoice/account-number
|
||||||
|
:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/string :db.type/ref]
|
||||||
|
:db/cardinality :db.cardinality/one}
|
||||||
|
|
||||||
|
{:db/ident :textract-invoice/account-number-options
|
||||||
|
:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/string :db.type/ref]
|
||||||
|
:db/cardinality :db.cardinality/many}
|
||||||
|
|
||||||
|
{:db/ident :textract-invoice/customer-identifier
|
||||||
|
:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/string :db.type/ref]
|
||||||
|
:db/cardinality :db.cardinality/one}
|
||||||
|
|
||||||
|
{:db/ident :textract-invoice/customer-identifier-options
|
||||||
|
:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/string :db.type/ref]
|
||||||
|
:db/cardinality :db.cardinality/many}
|
||||||
|
|
||||||
|
{:db/ident :textract-invoice/vendor-name
|
||||||
|
:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/string :db.type/ref]
|
||||||
|
:db/cardinality :db.cardinality/one}
|
||||||
|
|
||||||
|
{:db/ident :textract-invoice/vendor-name-options
|
||||||
|
:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/string :db.type/ref]
|
||||||
|
:db/cardinality :db.cardinality/many}
|
||||||
|
|
||||||
|
{:db/ident :textract-invoice/date
|
||||||
|
:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/string :db.type/instant]
|
||||||
|
:db/cardinality :db.cardinality/one}
|
||||||
|
|
||||||
|
{:db/ident :textract-invoice/date-options
|
||||||
|
:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/string :db.type/instant]
|
||||||
|
:db/cardinality :db.cardinality/many}
|
||||||
|
|
||||||
|
{:db/ident :textract-invoice/invoice-number
|
||||||
|
:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/string :db.type/string]
|
||||||
|
:db/cardinality :db.cardinality/one}
|
||||||
|
|
||||||
|
{:db/ident :textract-invoice/invoice-number-options
|
||||||
|
:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/string :db.type/string]
|
||||||
|
:db/cardinality :db.cardinality/many}
|
||||||
|
|
||||||
|
{:db/ident :textract-invoice/invoice
|
||||||
|
:db/valueType :db.type/ref
|
||||||
|
:db/cardinality :db.cardinality/one}]
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
(def modal-card dialog/modal-card-)
|
(def modal-card dialog/modal-card-)
|
||||||
|
|
||||||
(def text-input inputs/text-input-)
|
(def text-input inputs/text-input-)
|
||||||
|
(def money-input inputs/money-input-)
|
||||||
|
(def date-input inputs/date-input-)
|
||||||
(def select inputs/select-)
|
(def select inputs/select-)
|
||||||
(def field inputs/field-)
|
(def field inputs/field-)
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,23 @@
|
|||||||
:class str " bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500")
|
:class str " bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500")
|
||||||
])
|
])
|
||||||
|
|
||||||
|
(defn money-input- [params]
|
||||||
|
[:input
|
||||||
|
(-> params
|
||||||
|
(update
|
||||||
|
:class str " bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 text-right"
|
||||||
|
)
|
||||||
|
(assoc :type "number"
|
||||||
|
:step "0.01"))
|
||||||
|
])
|
||||||
|
|
||||||
|
(defn date-input- [params]
|
||||||
|
[:input
|
||||||
|
(-> params
|
||||||
|
(update
|
||||||
|
:class str " bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500")
|
||||||
|
(assoc :type "date"))])
|
||||||
|
|
||||||
(defn field- [params & rest]
|
(defn field- [params & rest]
|
||||||
(into
|
(into
|
||||||
[:div
|
[:div
|
||||||
|
|||||||
@@ -13,7 +13,22 @@
|
|||||||
(left-aside- {:nav nav
|
(left-aside- {:nav nav
|
||||||
:page-specific page-specific})
|
:page-specific page-specific})
|
||||||
[:div#main-content {:class "relative w-full h-full lg:pl-64 overflow-y-auto px-4 bg-gray-100 dark:bg-gray-900 min-h-content "
|
[:div#main-content {:class "relative w-full h-full lg:pl-64 overflow-y-auto px-4 bg-gray-100 dark:bg-gray-900 min-h-content "
|
||||||
"_" (hiccup/raw "on htmx:responseError put event.detail.xhr.response into #error-details then add .htmx-added to #error-holder then remove .hidden from #error-holder then wait 30ms then remove .htmx-added from #error-holder")}
|
"_" (hiccup/raw "
|
||||||
|
on notification put event.detail.value into #notification-details then add .htmx-added to #notification-holder then remove .hidden from #notification-holder then wait 30ms then remove .htmx-added from #notification-holder
|
||||||
|
on htmx:responseError put event.detail.xhr.response into #error-details then add .htmx-added to #error-holder then remove .hidden from #error-holder then wait 30ms then remove .htmx-added from #error-holder"
|
||||||
|
)}
|
||||||
|
[:div#notification-holder.hidden
|
||||||
|
[: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-blue-400
|
||||||
|
{"_" (hiccup/raw "on click add .hidden to #notification-holder")}
|
||||||
|
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.transition-all.duration-500.fade-in.slide-up.max-h-96
|
||||||
|
|
||||||
|
[:div {:class "p-4 text-lg w-full" :role "alert"}
|
||||||
|
[:div.text-sm
|
||||||
|
[:pre#notification-details.text-xs]]]]]]
|
||||||
[:div#error-holder.hidden
|
[:div#error-holder.hidden
|
||||||
[: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.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
|
[:div.relative
|
||||||
|
|||||||
@@ -43,6 +43,8 @@
|
|||||||
:invoice-glimpse (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/page))
|
:invoice-glimpse (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/page))
|
||||||
:invoice-glimpse-upload (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/upload))
|
:invoice-glimpse-upload (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/upload))
|
||||||
:invoice-glimpse-job (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/job-progress))
|
:invoice-glimpse-job (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/job-progress))
|
||||||
|
:invoice-glimpse-create (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/create))
|
||||||
|
:invoice-glimpse-update-job (wrap-client-redirect-unauthenticated (wrap-admin invoice-glimpse/update-job))
|
||||||
:transaction-insights (wrap-client-redirect-unauthenticated (wrap-admin insights/page))
|
:transaction-insights (wrap-client-redirect-unauthenticated (wrap-admin insights/page))
|
||||||
:transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-admin insights/insight-table))
|
:transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-admin insights/insight-table))
|
||||||
:transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-admin insights/transaction-rows))
|
:transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-admin insights/transaction-rows))
|
||||||
|
|||||||
@@ -14,13 +14,16 @@
|
|||||||
[auto-ap.time :as atime]
|
[auto-ap.time :as atime]
|
||||||
[bidi.bidi :as bidi]
|
[bidi.bidi :as bidi]
|
||||||
[cemerick.url :as url]
|
[cemerick.url :as url]
|
||||||
|
[clj-time.coerce :as coerce]
|
||||||
|
[cheshire.core :as cheshire]
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[com.brunobonacci.mulog :as mu]
|
[com.brunobonacci.mulog :as mu]
|
||||||
[config.core :refer [env]]
|
[config.core :refer [env]]
|
||||||
[datomic.api :as dc]
|
[datomic.api :as dc]
|
||||||
[hiccup2.core :as hiccup]
|
[hiccup2.core :as hiccup]
|
||||||
[iol-ion.tx :refer [random-tempid]])
|
[iol-ion.tx :refer [random-tempid]]
|
||||||
|
[auto-ap.client-routes :as client-routes])
|
||||||
(:import
|
(:import
|
||||||
(java.util UUID)))
|
(java.util UUID)))
|
||||||
|
|
||||||
@@ -33,99 +36,89 @@
|
|||||||
(map (fn [sf]
|
(map (fn [sf]
|
||||||
(-> sf
|
(-> sf
|
||||||
(update :label-detection dissoc :geometry)
|
(update :label-detection dissoc :geometry)
|
||||||
(update :value-detection dissoc :geometry))))
|
(update :value-detection dissoc :geometry))))))
|
||||||
#_(group-by (fn [sf]
|
|
||||||
[(get-in sf ["Type" "Text"])
|
|
||||||
(get-in sf ["LabelDetection" "Text"])]
|
|
||||||
))))
|
|
||||||
(defn stack-rank [valid-values field-descriptors]
|
(defn stack-rank [valid-values field-descriptors]
|
||||||
(->> field-descriptors
|
(->> field-descriptors
|
||||||
(filter (comp valid-values :text :type))
|
(filter (comp valid-values :text :type))
|
||||||
(sort-by #(* (-> % :type :confidence)
|
(sort-by #(* (-> % :type :confidence)
|
||||||
(-> % :value-detection :confidence)))
|
(-> % :value-detection :confidence)))
|
||||||
(reverse)
|
(reverse)
|
||||||
(map (comp :text :value-detection))
|
(map (comp :text :value-detection))))
|
||||||
(reduce
|
|
||||||
(fn [[result seen?] new]
|
|
||||||
(if (seen? new)
|
|
||||||
[result seen?]
|
|
||||||
[(conj result new) (conj seen? new)]))
|
|
||||||
[[] #{}])
|
|
||||||
first))
|
|
||||||
|
|
||||||
(defn textract->textract-invoice [tx]
|
|
||||||
(let [lookup (lookup tx)
|
|
||||||
total-options (stack-rank #{"TOTAL"} lookup)
|
|
||||||
account-number-options (stack-rank #{"CUSTOMER_NUMBER"} lookup)
|
|
||||||
customer-identifier-options (stack-rank #{"RECEIVER_NAME"} lookup)
|
|
||||||
vendor-name-options (stack-rank #{"VENDOR_NAME"} lookup)
|
|
||||||
date-options (stack-rank #{"ORDER_DATE"} lookup)
|
|
||||||
invoice-number-options (stack-rank #{"INVOICE_RECEIPT_ID"} lookup)
|
|
||||||
]
|
|
||||||
#:textract-invoice
|
|
||||||
{:total (first total-options)
|
|
||||||
:total-options total-options
|
|
||||||
:account-number (first account-number-options)
|
|
||||||
:account-number-options account-number-options
|
|
||||||
:customer-identifier (first customer-identifier-options)
|
|
||||||
:customer-identifier-options customer-identifier-options
|
|
||||||
:vendor-name (first vendor-name-options)
|
|
||||||
:vendor-name-options (rest vendor-name-options)
|
|
||||||
:date (first date-options)
|
|
||||||
:date-options date-options
|
|
||||||
:invoice-number (first invoice-number-options)
|
|
||||||
:invoice-number-options invoice-number-options
|
|
||||||
}))
|
|
||||||
|
|
||||||
(defn clean-customer [c]
|
(defn clean-customer [c]
|
||||||
(clojure.string/replace c #"\W+" " "))
|
(clojure.string/replace c #"\W+" " "))
|
||||||
|
|
||||||
(defn coalesced->invoice [i]
|
(defn deduplicate [xs]
|
||||||
(mu/with-context {:inference i}
|
(first
|
||||||
(let [vendor-id (->> (solr/query solr/impl "vendors" {"query" (format "name:(%s) ", (:best (:vendor-name i))) "fields" "score, *"})
|
(reduce
|
||||||
(filter (fn [d] (> (:score d) 4.0)))
|
(fn [[so-far seen-parsed?] [raw parsed]]
|
||||||
(map (comp #(Long/parseLong %) :id))
|
(if (seen-parsed? parsed)
|
||||||
first)
|
[so-far seen-parsed?]
|
||||||
account-number (:best (:account-number i))
|
[(conj so-far [raw parsed])
|
||||||
customer-identifier (:best (:customer-identifier i))
|
(conj seen-parsed? parsed)]))
|
||||||
client-id (or
|
[[] #{}]
|
||||||
(when (not-empty account-number)
|
xs)))
|
||||||
(:db/id (d-clients/exact-match (:best (:account-number i)))))
|
|
||||||
(when (:best (:customer-identifier i))
|
(defn textract->textract-invoice [job-id tx]
|
||||||
(->> (solr/query solr/impl "clients" {"query" (format "name:(%s) ", (clean-customer customer-identifier)) "fields" "score, *"})
|
(let [lookup (lookup tx)
|
||||||
#_(filter (fn [d] (> (:score d) 4.0)))
|
total-options (->> (stack-rank #{"AMOUNT_DUE"} lookup)
|
||||||
(map (comp #(Long/parseLong %) :id))
|
(map (fn [t]
|
||||||
first)))
|
[t (some->> t
|
||||||
location (when client-id
|
(re-find #"([0-9.\-]+)")
|
||||||
(->> (dc/pull (dc/db conn) '[:client/locations] client-id)
|
second
|
||||||
:client/locations
|
Double/parseDouble)]))
|
||||||
first))
|
(concat (->> (stack-rank #{"TOTAL"} lookup)
|
||||||
invoice-number (:best (:invoice-number i))
|
(map (fn [t]
|
||||||
total (Double/parseDouble (some->> i
|
[t (some->> t
|
||||||
:total
|
(re-find #"([0-9.\-]+)")
|
||||||
:best
|
second
|
||||||
(re-find #"([0-9.\-]+)")
|
Double/parseDouble)]))))
|
||||||
second) )
|
(deduplicate))
|
||||||
date (or (atime/parse (:best (:date i)) "MM/dd/yyyy")
|
customer-identifier-options (->> (stack-rank #{"CUSTOMER_NUMBER"} lookup)
|
||||||
(atime/parse (:best (:date i)) "MM/dd/yy"))]
|
(map (fn [t]
|
||||||
(when-not vendor-id
|
[t (:db/id (d-clients/exact-match t))]))
|
||||||
(alog/warn ::cant-find-vendor
|
(filter second)
|
||||||
:search-results (solr/query solr/impl "vendors" {"query" (format "name:(%s) ", (:best (:vendor-name i))) "fields" "score, *"})
|
(concat (->> (stack-rank #{"RECEIVER_NAME"} lookup)
|
||||||
:vendor-name (:vendor-name i)))
|
(map (fn [t]
|
||||||
(when-not client-id
|
[t (->> (solr/query solr/impl "clients" {"query" (format "name:(%s) ", (clean-customer t)) "fields" "score, *"})
|
||||||
(alog/warn ::cant-find-customer))
|
#_(filter (fn [d] (> (:score d) 4.0)))
|
||||||
(when (and client-id date invoice-number vendor-id total)
|
(map (comp #(Long/parseLong %) :id))
|
||||||
{:db/id (random-tempid)
|
first)]))))
|
||||||
:invoice/client client-id
|
deduplicate)
|
||||||
:invoice/client-identifier (or account-number customer-identifier)
|
vendor-name-options (->> (stack-rank #{"VENDOR_NAME"} lookup)
|
||||||
:invoice/vendor vendor-id
|
(map (fn [t]
|
||||||
:invoice/invoice-number invoice-number
|
[t (->> (solr/query solr/impl "vendors" {"query" (format "name:(%s) ", t) "fields" "score, *"})
|
||||||
:invoice/total total
|
(filter (fn [d] (> (:score d) 4.0)))
|
||||||
:invoice/date date
|
(map (comp #(Long/parseLong %) :id))
|
||||||
:invoice/location location
|
first)]))
|
||||||
:invoice/import-status :import-status/pending
|
(deduplicate))
|
||||||
:invoice/outstanding-balance total
|
date-options (->> (stack-rank #{"ORDER_DATE" "DELIVERY_DATE"} lookup)
|
||||||
:invoice/status :invoice-status/unpaid}))))
|
(map (fn [t]
|
||||||
|
[t (or (some-> (and (re-find #"\d{1,2}\/\d{1,2}/\d{4,4}" t) (atime/parse t "MM/dd/yyyy"))
|
||||||
|
(coerce/to-date))
|
||||||
|
(some-> (and (re-find #"\d{1,2}\/\d{1,2}/\d{2,2}" t) (atime/parse t "MM/dd/yy"))
|
||||||
|
(coerce/to-date)))]))
|
||||||
|
(deduplicate))
|
||||||
|
invoice-number-options (->> (stack-rank #{"INVOICE_RECEIPT_ID" "PO_NUMBER"} lookup)
|
||||||
|
(map (fn [t]
|
||||||
|
[t t]))
|
||||||
|
(deduplicate))]
|
||||||
|
#:textract-invoice
|
||||||
|
{:db/id [:textract-invoice/job-id job-id]
|
||||||
|
:textract-status "SUCCEEDED"
|
||||||
|
:total (first total-options)
|
||||||
|
:total-options (seq total-options)
|
||||||
|
:customer-identifier (first customer-identifier-options)
|
||||||
|
:customer-identifier-options (seq customer-identifier-options)
|
||||||
|
:vendor-name (first vendor-name-options)
|
||||||
|
:vendor-name-options (seq vendor-name-options)
|
||||||
|
:date (first date-options)
|
||||||
|
:date-options (seq date-options)
|
||||||
|
:invoice-number (first invoice-number-options)
|
||||||
|
:invoice-number-options (seq invoice-number-options)}))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn upload-form* []
|
(defn upload-form* []
|
||||||
[:div
|
[:div
|
||||||
@@ -145,80 +138,138 @@ invoice_dropzone = new Dropzone(\"#invoice\", {
|
|||||||
disablePreviews: true
|
disablePreviews: true
|
||||||
}); ")]])
|
}); ")]])
|
||||||
|
|
||||||
|
(defn customer-identifier-id->customer-identifier-client [[ci client]]
|
||||||
|
(when client
|
||||||
|
(let [real-client (dc/pull (dc/db conn)
|
||||||
|
[:client/name :db/id]
|
||||||
|
client)]
|
||||||
|
[ci [(:db/id real-client) (:client/name real-client)]])))
|
||||||
|
|
||||||
|
(defn vendor-name-tuple->vendor-tuple [[vn vendor]]
|
||||||
|
(when vendor
|
||||||
|
(let [real-vendor (dc/pull (dc/db conn)
|
||||||
|
[:vendor/name :db/id]
|
||||||
|
vendor)]
|
||||||
|
[vn [(:db/id real-vendor) (:vendor/name real-vendor)]])))
|
||||||
|
|
||||||
|
(defn get-job [job-id]
|
||||||
|
(-> (dc/pull (dc/db conn) '[*] [:textract-invoice/job-id job-id])
|
||||||
|
(update :textract-invoice/customer-identifier customer-identifier-id->customer-identifier-client)
|
||||||
|
(update :textract-invoice/customer-identifier-options #(map customer-identifier-id->customer-identifier-client %) )
|
||||||
|
(update :textract-invoice/vendor-name vendor-name-tuple->vendor-tuple)
|
||||||
|
(update :textract-invoice/vendor-name-options #(map vendor-name-tuple->vendor-tuple %) )))
|
||||||
|
|
||||||
(defn refresh-job [job-id]
|
(defn refresh-job [job-id]
|
||||||
(let [{:keys [:db/id :textract-invoice/textract-status]} (dc/pull (dc/db conn) '[:db/id :textract-invoice/textract-status] [:textract-invoice/job-id job-id])]
|
(let [{:keys [:db/id :textract-invoice/textract-status]} (dc/pull (dc/db conn) '[:db/id :textract-invoice/textract-status] [:textract-invoice/job-id job-id])]
|
||||||
(when (= "IN_PROGRESS" textract-status)
|
(when (= "IN_PROGRESS" textract-status)
|
||||||
(let [result (textract/get-expense-analysis {:job-id job-id})]
|
(let [result (textract/get-expense-analysis {:job-id job-id})
|
||||||
@(dc/transact conn [{:db/id id :textract-invoice/textract-status (:job-status result)}])))
|
new-status (:job-status result)]
|
||||||
(dc/pull (dc/db conn) '[*] [:textract-invoice/job-id job-id])))
|
(cond (= "SUCCEEDED" new-status)
|
||||||
|
@(dc/transact conn [[:upsert-entity (textract->textract-invoice job-id result)]])
|
||||||
|
:else
|
||||||
|
@(dc/transact conn [{:db/id id :textract-invoice/textract-status new-status}]))))
|
||||||
|
(get-job job-id)))
|
||||||
|
|
||||||
(defn pill-list* [{:keys [selected options class]}]
|
|
||||||
|
(defn pill-list* [{:keys [selected options class ->text ->value job-id field]}]
|
||||||
(let [options (->> options
|
(let [options (->> options
|
||||||
(filter (complement #{selected}))
|
(filter (complement #{selected}))
|
||||||
(map (fn [x] [:div.shrink (com/pill {:color :secondary} (com/link {:href "#"} x))]) ))]
|
(map (fn [x]
|
||||||
|
[:div.shrink (com/pill {:color :secondary} (com/link {:hx-patch (str (bidi/path-for ssr-routes/only-routes
|
||||||
|
:invoice-glimpse-update-job
|
||||||
|
:job-id job-id)
|
||||||
|
"?"
|
||||||
|
(url/map->query {field (if ->value
|
||||||
|
(->value x)
|
||||||
|
(->text x))}))
|
||||||
|
:hx-target "closest form"
|
||||||
|
:href "#"} (->text x)))]) ))]
|
||||||
(when (seq options)
|
(when (seq options)
|
||||||
[:div.col-span-6.col-start-1.text-xs
|
[:div.col-span-6.col-start-1.text-xs
|
||||||
"Alternates: "
|
"Alternates: "
|
||||||
[:div.flex.gap-2.flex-wrap {:class class}
|
[:div.flex.gap-2.flex-wrap {:class class}
|
||||||
options]])))
|
options]])))
|
||||||
|
|
||||||
(defn textract->invoice-form* [job-id]
|
(defn textract->invoice-form* [textract-invoice]
|
||||||
(let [coalesced (-> (textract/get-expense-analysis {:job-id job-id})
|
[:form {:hx-post (bidi/path-for ssr-routes/only-routes
|
||||||
(textract->textract-invoice))
|
:invoice-glimpse-create
|
||||||
#_#_candidate-invoice (-> coalesced
|
:job-id (:textract-invoice/job-id textract-invoice))}
|
||||||
(coalesced->invoice))]
|
[:div.grid.grid-cols-6.gap-4.mb-4
|
||||||
[:form
|
[:div.col-span-6
|
||||||
[:div.grid.grid-cols-6.gap-4
|
(com/field {:label "Client"}
|
||||||
[:div.col-span-6
|
(com/text-input {:name (path->name [:invoice/client])
|
||||||
(com/field {:label "Client"}
|
:value (-> textract-invoice :textract-invoice/customer-identifier second second)
|
||||||
(com/text-input {:name (path->name [:invoice/client])
|
:placeholder "Client"
|
||||||
:value (:textract-invoice/customer-identifier coalesced)
|
:disabled true
|
||||||
:placeholder "Client"
|
:autofocus true}))]
|
||||||
:disabled true
|
(pill-list* {:selected (:textract-invoice/customer-identifier textract-invoice)
|
||||||
:autofocus true}))]
|
:options (:textract-invoice/customer-identifier-options textract-invoice)
|
||||||
(pill-list* {:selected (:textract-invoice/customer-identifier coalesced)
|
:job-id (:textract-invoice/job-id textract-invoice)
|
||||||
:options (:textract-invoice/customer-identifier-options coalesced)
|
:class "flex-col"
|
||||||
:class "flex-col"})
|
:field "client"
|
||||||
|
:->text (fn [[customer-identifier [id client-name]]]
|
||||||
[:div.col-span-6
|
(format "%s (%s)" client-name customer-identifier))
|
||||||
(com/field {:label "Vendor"}
|
:->value (fn [[client-identifier [id client-name]]]
|
||||||
(com/text-input {:name (path->name [:invoice/vendor])
|
id)})
|
||||||
:value (:textract-invoice/vendor-name coalesced)
|
[:div.col-span-6
|
||||||
:placeholder "Vendor"
|
(com/field {:label "Vendor"}
|
||||||
:disabled true
|
(com/text-input {:name (path->name [:invoice/vendor])
|
||||||
:autofocus true}))]
|
:value (-> textract-invoice :textract-invoice/vendor-name second second)
|
||||||
(pill-list* {:selected (:textract-invoice/vendor-name coalesced)
|
:disabled true
|
||||||
:options (:textract-invoice/vendor-name-options coalesced)
|
:placeholder "Vendor"}))]
|
||||||
:class "flex-row"})
|
(pill-list* {:selected (:textract-invoice/vendor-name textract-invoice)
|
||||||
|
:options (:textract-invoice/vendor-name-options textract-invoice)
|
||||||
[:div.col-span-3
|
:job-id (:textract-invoice/job-id textract-invoice)
|
||||||
(com/field {:label "Date"}
|
:class "flex-row"
|
||||||
(com/text-input {:name (path->name [:invoice/date])
|
:field "vendor"
|
||||||
:value (:textract-invoice/date coalesced)
|
:->text (fn [[vendor-identifier [id vendor-name]]]
|
||||||
:placeholder "Date"
|
(format "%s (%s)" vendor-name vendor-identifier))
|
||||||
:disabled true
|
:->value (fn [[vendor-identifier [id vendor-name]]]
|
||||||
:autofocus true}))]
|
id)})
|
||||||
(pill-list* {:selected (:textract-invoice/date coalesced)
|
[:div.col-span-3
|
||||||
:options (:textract-invoice/date-options coalesced)})
|
(com/field {:label "Date"}
|
||||||
[:div.col-span-2.col-start-1
|
(com/date-input {:name "date"
|
||||||
(com/field {:label "Total"}
|
:value (-> textract-invoice
|
||||||
(com/text-input {:name (path->name [:invoice/total])
|
:textract-invoice/date
|
||||||
:value (:textract-invoice/total coalesced)
|
second
|
||||||
:placeholder "Total"
|
(coerce/to-date-time)
|
||||||
:disabled true
|
(atime/unparse-local atime/iso-date))
|
||||||
:autofocus true}))]
|
:placeholder "Date"}))]
|
||||||
(pill-list* {:selected (:textract-invoice/total coalesced)
|
(pill-list* {:selected (:textract-invoice/date textract-invoice)
|
||||||
:options (:textract-invoice/total-options coalesced)})
|
:options (:textract-invoice/date-options textract-invoice)
|
||||||
|
:job-id (:textract-invoice/job-id textract-invoice)
|
||||||
[:div.col-span-2.col-start-1
|
:field "date"
|
||||||
(com/field {:label "Invoice Number"}
|
:->text (fn [[_ date]]
|
||||||
(com/text-input {:name (path->name [:invoice/invoice-number])
|
(-> date
|
||||||
:value (:textract-invoice/invoice-number coalesced)
|
(coerce/to-date-time)
|
||||||
:placeholder "Invoice Number"
|
(atime/unparse-local atime/iso-date)))})
|
||||||
:disabled true
|
[:div.col-span-2.col-start-1
|
||||||
:autofocus true}))]
|
(com/field {:label "Total"}
|
||||||
(pill-list* {:selected (:textract-invoice/invoice-number coalesced)
|
(com/money-input {:name "total"
|
||||||
:options (:textract-invoice/invoice-number-options coalesced)})]]))
|
:value (-> textract-invoice
|
||||||
|
:textract-invoice/total
|
||||||
|
second)
|
||||||
|
:placeholder "Total"}))]
|
||||||
|
(pill-list* {:selected (:textract-invoice/total textract-invoice)
|
||||||
|
:options (:textract-invoice/total-options textract-invoice)
|
||||||
|
:job-id (:textract-invoice/job-id textract-invoice)
|
||||||
|
:field "total"
|
||||||
|
:->text (fn [[_ amount]]
|
||||||
|
(str amount))})
|
||||||
|
[:div.col-span-2.col-start-1
|
||||||
|
(com/field {:label "Invoice Number"}
|
||||||
|
(com/text-input {:name "invoice-number"
|
||||||
|
:value (-> textract-invoice
|
||||||
|
:textract-invoice/invoice-number
|
||||||
|
first)
|
||||||
|
:placeholder "Invoice Number"}))]
|
||||||
|
(pill-list* {:selected (:textract-invoice/invoice-number textract-invoice)
|
||||||
|
:field "invoice-number"
|
||||||
|
:job-id (:textract-invoice/job-id textract-invoice)
|
||||||
|
:options (:textract-invoice/invoice-number-options textract-invoice)
|
||||||
|
:->text (fn [[_ invoice-number]]
|
||||||
|
(str invoice-number))})]
|
||||||
|
(com/button {:color :primary} "Save")])
|
||||||
|
|
||||||
(defn job-progress* [job-id]
|
(defn job-progress* [job-id]
|
||||||
(when (pull-id (dc/db conn) [:textract-invoice/job-id job-id])
|
(when (pull-id (dc/db conn) [:textract-invoice/job-id job-id])
|
||||||
@@ -227,17 +278,14 @@ invoice_dropzone = new Dropzone(\"#invoice\", {
|
|||||||
(= "IN_PROGRESS" (:textract-invoice/textract-status textract-invoice))
|
(= "IN_PROGRESS" (:textract-invoice/textract-status textract-invoice))
|
||||||
[:div.bg-blue-100.border-2.border-dashed.rounded-lg.border-blue-300.p-4.max-w-md.w-md.text-center.cursor-pointer
|
[:div.bg-blue-100.border-2.border-dashed.rounded-lg.border-blue-300.p-4.max-w-md.w-md.text-center.cursor-pointer
|
||||||
{:hx-get (str (bidi/path-for ssr-routes/only-routes
|
{:hx-get (str (bidi/path-for ssr-routes/only-routes
|
||||||
:invoice-glimpse-job)
|
:invoice-glimpse-job
|
||||||
"?" (url/map->query {:job-id job-id}))
|
:job-id (:textract-invoice/job-id textract-invoice)))
|
||||||
:hx-trigger "load delay:5s"
|
:hx-trigger "load delay:5s"
|
||||||
:hx-swap "outerHTML"}
|
:hx-swap "outerHTML"}
|
||||||
"Analyzing job " (subs (:textract-invoice/job-id textract-invoice) 0 8) "..."]
|
"Analyzing job " (subs (:textract-invoice/job-id textract-invoice) 0 8) "..."]
|
||||||
|
|
||||||
(= "SUCCEEDED" (:textract-invoice/textract-status textract-invoice))
|
(= "SUCCEEDED" (:textract-invoice/textract-status textract-invoice))
|
||||||
[:div.px-4
|
[:div.px-4
|
||||||
[:a.mb-2 {:href (bidi/path-for ssr-routes/only-routes
|
|
||||||
:invoice-glimpse)}
|
|
||||||
(com/button {:color :secondary} "New import")]
|
|
||||||
[:div.flex.flex-row.space-x-4
|
[:div.flex.flex-row.space-x-4
|
||||||
[:div {:style {:width "805"}}
|
[:div {:style {:width "805"}}
|
||||||
(com/card {}
|
(com/card {}
|
||||||
@@ -245,22 +293,21 @@ invoice_dropzone = new Dropzone(\"#invoice\", {
|
|||||||
[:div {:class "basis-1/4"}
|
[:div {:class "basis-1/4"}
|
||||||
(com/card {}
|
(com/card {}
|
||||||
[:div.p-4
|
[:div.p-4
|
||||||
(textract->invoice-form* job-id)])]]]))))
|
(textract->invoice-form* textract-invoice)])]]]))))
|
||||||
|
|
||||||
(defn job-progress [request]
|
|
||||||
(html-response (job-progress* (get (:query-params request) "job-id"))))
|
|
||||||
|
|
||||||
(defn page* [job-id]
|
(defn page* [job-id]
|
||||||
[:div.mt-4
|
[:div#invoice-glimpse-content.mt-4
|
||||||
(com/card {}
|
(com/card {}
|
||||||
[:div.px-4.py-3.space-y-4.flex.flex-col
|
[:div.px-4.py-3.space-y-4.flex.flex-col
|
||||||
[:h1.text-2xl.mb-3.font-bold "Invoice Glimpse"]
|
[:div.flex.gap-x-4 [:h1.text-2xl.font-bold "Invoice Glimpse"] [:div (com/pill {:color :primary} "Beta")]
|
||||||
[:p.text-sm.italic "Import your invoices with the power of AI."]
|
(when job-id
|
||||||
[:div.flex.flex-row.space-x-4 (com/pill {:color :primary} "Beta")
|
[:div.ml-auto [:a.mb-2 {:href (bidi/path-for ssr-routes/only-routes
|
||||||
[:span "Note: This upload is expirimental. Please only use PDFs with a single invoice in them."]]
|
:invoice-glimpse)}
|
||||||
|
(com/button {:color :secondary} "New glimpse")]])]
|
||||||
|
[:p.text-sm.italic "Import your invoices with the power of AI. Please only use PDFs with a single invoice in them."]
|
||||||
|
|
||||||
(when job-id
|
(when job-id
|
||||||
(job-progress* job-id))
|
(job-progress* job-id))
|
||||||
|
|
||||||
(when-not job-id
|
(when-not job-id
|
||||||
(upload-form*))])])
|
(upload-form*))])])
|
||||||
|
|
||||||
@@ -272,6 +319,39 @@ invoice_dropzone = new Dropzone(\"#invoice\", {
|
|||||||
@(dc/transact conn [textract-invoice])
|
@(dc/transact conn [textract-invoice])
|
||||||
textract-invoice))
|
textract-invoice))
|
||||||
|
|
||||||
|
(defn textract-invoice->invoice [textract-invoice]
|
||||||
|
(mu/with-context {:textract-invoice textract-invoice}
|
||||||
|
(let [[_ [vendor-id]] (:textract-invoice/vendor-name textract-invoice)
|
||||||
|
[_ [client-id]] (:textract-invoice/customer-identifier textract-invoice)
|
||||||
|
[_ total] (:textract-invoice/total textract-invoice)
|
||||||
|
[_ date] (:textract-invoice/date textract-invoice)
|
||||||
|
[_ invoice-number] (:textract-invoice/invoice-number textract-invoice)
|
||||||
|
location (when client-id
|
||||||
|
(->> (dc/pull (dc/db conn) '[:client/locations] client-id)
|
||||||
|
:client/locations
|
||||||
|
first))]
|
||||||
|
(when (and client-id date invoice-number vendor-id total)
|
||||||
|
{:db/id (random-tempid)
|
||||||
|
:invoice/client client-id
|
||||||
|
:invoice/client-identifier (first (:textract-invoice/customer-identifier textract-invoice))
|
||||||
|
:invoice/vendor vendor-id
|
||||||
|
:invoice/invoice-number invoice-number
|
||||||
|
:invoice/total total
|
||||||
|
:invoice/date date
|
||||||
|
:invoice/location location
|
||||||
|
:invoice/import-status :import-status/imported
|
||||||
|
:invoice/outstanding-balance total
|
||||||
|
:invoice/status :invoice-status/unpaid}))))
|
||||||
|
|
||||||
|
(defn update-textract-invoice [job-id {:strs [date total invoice-number client vendor]}]
|
||||||
|
@(dc/transact-async conn [[:upsert-entity (cond-> {:db/id [:textract-invoice/job-id job-id]}
|
||||||
|
date (assoc :textract-invoice/date [date (coerce/to-date (atime/parse date atime/iso-date))])
|
||||||
|
total (assoc :textract-invoice/total [total (Double/parseDouble total)])
|
||||||
|
invoice-number (assoc :textract-invoice/invoice-number [invoice-number invoice-number])
|
||||||
|
client (assoc :textract-invoice/customer-identifier [(pull-attr (dc/db conn) :client/name (Long/parseLong client)) (Long/parseLong client)])
|
||||||
|
vendor (assoc :textract-invoice/vendor-name [(pull-attr (dc/db conn) :vendor/name (Long/parseLong vendor)) (Long/parseLong vendor)]))]])
|
||||||
|
(get-job job-id))
|
||||||
|
|
||||||
(defn upload [{:keys [identity] :as request}]
|
(defn upload [{:keys [identity] :as request}]
|
||||||
(let [file (or (get (:params request) :file)
|
(let [file (or (get (:params request) :file)
|
||||||
(get (:params request) "file"))]
|
(get (:params request) "file"))]
|
||||||
@@ -287,17 +367,41 @@ invoice_dropzone = new Dropzone(\"#invoice\", {
|
|||||||
{:content-type "application/pdf"
|
{:content-type "application/pdf"
|
||||||
:content-length (.length (:tempfile file))}))
|
:content-length (.length (:tempfile file))}))
|
||||||
textract-invoice (begin-textract-file s3-location)]
|
textract-invoice (begin-textract-file s3-location)]
|
||||||
|
|
||||||
{:headers {"Location"
|
{:headers {"Location"
|
||||||
(str (bidi/path-for ssr-routes/only-routes
|
(str (bidi/path-for ssr-routes/only-routes
|
||||||
:invoice-glimpse)
|
:invoice-glimpse-job
|
||||||
"?" (url/map->query {:job-id (:textract-invoice/job-id textract-invoice)}))}
|
:job-id (:textract-invoice/job-id textract-invoice)))}
|
||||||
:status 302})
|
:status 302})
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(alog/error ::cant-begin-textract
|
(alog/error ::cant-begin-textract
|
||||||
:error e)
|
:error e)
|
||||||
(html-response [:div (.getMessage e)]))))))
|
(html-response [:div (.getMessage e)]))))))
|
||||||
|
|
||||||
|
(defn update-job [{:as request}]
|
||||||
|
(let [current-job (update-textract-invoice (:job-id (:route-params request)) (:query-params request))]
|
||||||
|
(html-response (textract->invoice-form* current-job))))
|
||||||
|
|
||||||
|
(defn create [request]
|
||||||
|
(let [current-job (update-textract-invoice (:job-id (:route-params request)) (:form-params request))
|
||||||
|
new-invoice (textract-invoice->invoice current-job)
|
||||||
|
new-invoice-id (get-in @(dc/transact conn [[:propose-invoice new-invoice]])
|
||||||
|
[:tempids (:db/id new-invoice)])
|
||||||
|
_ (when new-invoice-id @(dc/transact conn [{:db/id (:db/id current-job)
|
||||||
|
:textract-invoice/invoice new-invoice-id}]))]
|
||||||
|
(if new-invoice-id
|
||||||
|
(html-response (page* nil)
|
||||||
|
:headers {"hx-push-url" (bidi/path-for ssr-routes/only-routes :invoice-glimpse)
|
||||||
|
"hx-retarget" "#invoice-glimpse-content"
|
||||||
|
"hx-trigger" (cheshire/generate-string {"notification" (str (hiccup/html [:div "Successfully created "
|
||||||
|
(com/link {:href (str (bidi/path-for client-routes/routes
|
||||||
|
:invoices)
|
||||||
|
"?exact-match-id="
|
||||||
|
new-invoice-id)}
|
||||||
|
(format "invoice %s" (:invoice/invoice-number new-invoice)))
|
||||||
|
"."]))})})
|
||||||
|
(html-response [:div "This invoice already exists."]
|
||||||
|
:status 400))))
|
||||||
|
|
||||||
(defn page [{:keys [matched-route request-method] :as request}]
|
(defn page [{:keys [matched-route request-method] :as request}]
|
||||||
(mu/log ::method
|
(mu/log ::method
|
||||||
:method request-method)
|
:method request-method)
|
||||||
@@ -318,7 +422,11 @@ invoice_dropzone = new Dropzone(\"#invoice\", {
|
|||||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||||
:invoice-glimpse)}
|
:invoice-glimpse)}
|
||||||
"Glimpse"])
|
"Glimpse"])
|
||||||
(page* (get (:query-params request) "job-id")))
|
(page* (:job-id (:route-params request))))
|
||||||
|
|
||||||
"Invoice Glimpse"))
|
"Invoice Glimpse"))
|
||||||
|
|
||||||
|
(defn job-progress [request]
|
||||||
|
(if (get-in request [:headers "hx-request"])
|
||||||
|
(html-response (job-progress* (:job-id (:route-params request))))
|
||||||
|
(page request)))
|
||||||
|
|||||||
@@ -360,17 +360,19 @@
|
|||||||
(reauthenticate client-code pa data)
|
(reauthenticate client-code pa data)
|
||||||
(refresh-provider-account client-code pa))
|
(refresh-provider-account client-code pa))
|
||||||
|
|
||||||
(defn force-pull-all []
|
(defn force-pull-all
|
||||||
(doseq [[client-code yodlee-provider-account] (seq (dc/q '[:find ?cd ?pai
|
([] (force-pull-all (seq (dc/q '[:find ?cd ?pai
|
||||||
:where [?c :client/code ?cd]
|
:where [?c :client/code ?cd]
|
||||||
[?yap :yodlee-provider-account/client ?c]
|
[?yap :yodlee-provider-account/client ?c]
|
||||||
[?yap :yodlee-provider-account/id ?pai]]
|
[?yap :yodlee-provider-account/id ?pai]]
|
||||||
(dc/db conn)))]
|
(dc/db conn)))))
|
||||||
|
([options]
|
||||||
|
(doseq [[client-code yodlee-provider-account] options]
|
||||||
|
|
||||||
(println "Trying " client-code "account" yodlee-provider-account)
|
(println "Trying " client-code "account" yodlee-provider-account)
|
||||||
(clojure.pprint/pprint (reauthenticate client-code yodlee-provider-account {}))
|
(clojure.pprint/pprint (reauthenticate client-code yodlee-provider-account {}))
|
||||||
(println "waiting")
|
(println "waiting")
|
||||||
(Thread/sleep 15000)
|
(Thread/sleep 15000)
|
||||||
(println "refreshing")
|
(println "refreshing")
|
||||||
(clojure.pprint/pprint (refresh-provider-account client-code yodlee-provider-account))))
|
(clojure.pprint/pprint (refresh-provider-account client-code yodlee-provider-account)))))
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
"search" :search
|
"search" :search
|
||||||
"invoice" {"/glimpse" {"" {:get :invoice-glimpse
|
"invoice" {"/glimpse" {"" {:get :invoice-glimpse
|
||||||
:post :invoice-glimpse-upload}
|
:post :invoice-glimpse-upload}
|
||||||
"/job" {:get :invoice-glimpse-job}}}
|
"/job" {["/" [#"\w+" :job-id]] {:get :invoice-glimpse-job
|
||||||
|
"/create" {:post :invoice-glimpse-create}
|
||||||
|
"/update" {:patch :invoice-glimpse-update-job}}}}}
|
||||||
"admin" {"/history" {"" :admin-history
|
"admin" {"/history" {"" :admin-history
|
||||||
"/" :admin-history
|
"/" :admin-history
|
||||||
#"/search/?" :admin-history-search
|
#"/search/?" :admin-history-search
|
||||||
|
|||||||
Reference in New Issue
Block a user