allows importing only the invoices that were successful.

This commit is contained in:
2026-01-05 21:34:24 -08:00
parent 8511d30715
commit 68444d6311

View File

@@ -1,5 +1,6 @@
(ns auto-ap.ssr.invoice.import
(:require [amazonica.aws.s3 :as s3]
(:require
[amazonica.aws.s3 :as s3]
[auto-ap.datomic
:refer [add-sorter-fields apply-pagination apply-sort-3
audit-transact conn merge-query observable-query
@@ -7,9 +8,8 @@
[auto-ap.datomic.clients :as d-clients]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.datomic.vendors :as d-vendors]
[auto-ap.graphql.utils :refer [assert-can-see-client
assert-not-locked
exception->notification
[auto-ap.graphql.utils :refer [assert-can-see-client assert-not-locked
can-see-client? exception->notification
extract-client-ids]]
[auto-ap.logging :as alog]
[auto-ap.parse :as parse]
@@ -25,9 +25,8 @@
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils
:refer [apply-middleware-to-all-handlers clj-date-schema
entity-id html-response main-transformer ref->enum-schema
strip wrap-entity wrap-implied-route-param
wrap-schema-enforce]]
entity-id html-response ref->enum-schema strip
wrap-entity wrap-implied-route-param wrap-schema-enforce]]
[auto-ap.time :as atime]
[auto-ap.utils :refer [dollars=]]
[bidi.bidi :as bidi]
@@ -37,10 +36,9 @@
[com.brunobonacci.mulog :as mu]
[config.core :refer [env]]
[datomic.api :as dc]
[hiccup2.core :as hiccup]
[malli.core :as mc]
[auto-ap.client-routes :as client-routes])
(:import [java.util UUID]))
[malli.core :as mc])
(:import
[java.util UUID]))
(defn exact-match-id* [request]
@@ -661,17 +659,28 @@
:invoice/status :invoice-status/unpaid}))
(defn validate-invoice [invoice user]
(when-not (:invoice/client invoice)
(throw (ex-info (str "Searched clients for '" (:invoice/client-identifier invoice) "'. No client found in file. Select a client first.")
{:invoice-number (:invoice/invoice-number invoice)
:customer-identifier (:invoice/client-identifier invoice)})))
(assert-can-see-client user (:invoice/client invoice))
(doseq [k [:invoice/invoice-number :invoice/client :invoice/vendor :invoice/total :invoice/outstanding-balance :invoice/date]]
(when (not (get invoice k))
(throw (ex-info (str (name k) "not found on invoice " invoice)
invoice))))
(let [missing-keys (for [k [:invoice/invoice-number :invoice/client :invoice/vendor :invoice/total :invoice/outstanding-balance :invoice/date]
:when (not (get invoice k))]
k
)]
(cond
(not (:invoice/client invoice))
(do
(alog/warn ::no-client :invoice invoice)
(assoc invoice :error-message (str "Searched clients for '" (:invoice/client-identifier invoice) "'. No client found in file. Select a client first.")))
invoice)
(not (can-see-client? user (:invoice/client invoice)))
(do
(alog/warn ::cant-see-client :invoice invoice )
(assoc invoice :error-message "No access for the client in this file.")
)
(seq missing-keys)
(do
(alog/warn ::mising-keys :keys missing-keys)
(assoc invoice :error-message (str "Missing the key " missing-keys)))
:else
invoice)))
(defn admin-only-if-multiple-clients [is]
(let [client-count (->> is
@@ -688,19 +697,29 @@
(map #(import->invoice % user))
(map #(validate-invoice % user))
admin-only-if-multiple-clients
)
errored-invoices (->> potential-invoices
(filter #(:error-message %)))
successful-invoices (->> potential-invoices
(filter #(not (:error-message %))))
proposed-invoices (->> potential-invoices
(filter #(not (:error-message %)))
(mapv d-invoices/code-invoice)
(mapv (fn [i] [:propose-invoice i])))]
(alog/info ::creating-invoice :invoices potential-invoices)
(let [tx (audit-transact potential-invoices user)]
(when-not (seq (dc/q '[:find ?i
(alog/info ::creating-invoice :invoices proposed-invoices)
(let [tx (audit-transact proposed-invoices user)]
#_(when-not (seq (dc/q '[:find ?i
:in $ [?i ...]
:where [?i :invoice/invoice-number]]
(:db-after tx)
(map :e (:tx-data tx))))
(throw (ex-info "No new invoices found."
{:template (:template (first imports))})))
tx)))
{:tx tx
:errored-invoices errored-invoices
:successful-invoices successful-invoices
:imports imports})))
(defn import-internal [tempfile filename force-client force-location force-vendor force-chatgpt identity]
(mu/with-context {:parsing-file filename}
@@ -727,6 +746,7 @@
(try
(import-uploaded-invoice identity imports)
(catch Exception e
(alog/warn ::couldnt-import-upload
:error e
@@ -734,8 +754,7 @@
(throw (ex-info (ex-message e)
{:template (:template ( first imports))
:sample (first imports)}
e))))
imports)
e)))))
(catch Exception e
(alog/warn ::couldnt-import-upload
:error e)
@@ -750,60 +769,73 @@
results (reduce
(fn [result {:keys [filename tempfile]}]
(try
(let [i (import-internal tempfile filename force-client force-location force-vendor force-chatgpt (:identity request) )]
(update result :results conj {:filename filename
:response "success!"
:template (:template (first i))}))
(let [i (import-internal tempfile filename force-client force-location force-vendor force-chatgpt (:identity request))]
(alog/info ::failure-error-count :count (count (:errored-invoices i)) )
(-> result
(update :error? #(or %
(boolean (seq (:errored-invoices i)))))
(update :files conj {:filename filename
:error? (boolean (seq (:errored-invoices i)))
:successful-invoices (count (:successful-invoices i))
:errors [:div
[:p.text-green-500 [:b (count (:successful-invoices i)) " succeeded in total."]
]
[:p [:b (count (:errored-invoices i)) " failed in total."]
]
[:ul
(for [e (take 5 (:errored-invoices i))]
[:li (:error-message e)]) ]]
:template (:template (first (:imports i)))})))
(catch Exception e
(-> result
(assoc :error? true)
(update :results conj {:filename filename
(update :files conj {:filename filename
:errors "Can't process file"
:response (.getMessage e)
:sample (:sample (ex-data e))
:template (:template (ex-data e))})))))
{:error? false :results []}
{:error? false
:files []
}
file)]
(html-response [:div#page-notification.p-4.rounded-lg
{:class (if (:error? results)
"bg-red-50 text-red-700"
"bg-primary-50 text-primary-700")}
(html-response [:div#page-notification.p-4.rounded-lg
[:table
[:thead
[:tr [:td "File"] [:td "Result"]
[:td "Template"]
(if (:error? results)
[:td "Sample match"])]
[:td "Sample"]]
#_[:tr "Result"]
#_[:tr "Template"]
]
(for [r (:results results)]
#_[:tr "Template"]]
(for [r (:files results)]
[:tr
[:td.p-2.border
{:class (if (:error? results)
[:td.p-2.border.align-top
{:class (if (:error? r)
"bg-red-50 text-red-700 border-red-300"
"bg-primary-50 text-primary-700 border-green-500")}
(:filename r)]
[:td.p-2.border
{:class (if (:error? results)
[:td.p-2.border.align-top
{:class (if (:error? r)
"bg-red-50 text-red-700 border-red-300"
"bg-primary-50 text-primary-700 border-green-500")}
(:response r)]
[:td.p-2.border
{:class (if (:error? results)
(:errors r)]
[:td.p-2.border.align-top
{:class (if (:error? r)
"bg-red-50 text-red-700 border-red-300"
"bg-primary-50 text-primary-700 border-green-500")}
"Template: " (:template r)]
(if (:error? results )
[:td.p-2.border
{:class "bg-red-50 text-red-700 border-red-300"}
[:td.p-2.border.align-top
{:class (if (:error? r)
"bg-red-50 text-red-700 border-red-300"
"bg-primary-50 text-primary-700 border-green-500")}
[:ul
(for [[k v] (dissoc (:sample r) :template :source-url :full-text :text)]
[:li (name k) ": " (str v)])]
#_(:template r)])])]]
#_(:template r)]])]]
:headers
{"hx-trigger" "invalidated"})
))
{"hx-trigger" "invalidated"})))
#_(defn wrap-test [handler]
(fn [request]