From c52444499fd4cbd6c795b3056ea5710b517bafb4 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Fri, 5 May 2023 15:10:39 -0700 Subject: [PATCH] adds alternative ezcater solution. --- dev-resources/sample-ezcater.xlsx | Bin 0 -> 49222 bytes scratch-sessions/cash-shift.repl | 15 +- scratch-sessions/ezcater_xls.clj | 118 +++++++++++ src/clj/auto_ap/parse.clj | 5 +- src/clj/auto_ap/routes/ezcater_xls.clj | 193 ++++++++++++++++++ src/clj/auto_ap/ssr/core.clj | 4 +- src/clj/auto_ap/ssr/ui.clj | 2 + .../auto_ap/shared_views/admin/side_bar.cljc | 15 +- src/cljc/auto_ap/ssr_routes.cljc | 11 +- .../integration/routes/ezcater_xls.clj | 59 ++++++ .../{ => integration}/routes/invoice_test.clj | 2 +- 11 files changed, 410 insertions(+), 14 deletions(-) create mode 100644 dev-resources/sample-ezcater.xlsx create mode 100644 scratch-sessions/ezcater_xls.clj create mode 100644 src/clj/auto_ap/routes/ezcater_xls.clj create mode 100644 test/clj/auto_ap/integration/routes/ezcater_xls.clj rename test/clj/auto_ap/{ => integration}/routes/invoice_test.clj (98%) diff --git a/dev-resources/sample-ezcater.xlsx b/dev-resources/sample-ezcater.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..e1d064031bb516fb5784cf7ef18e037f3804a449 GIT binary patch literal 49222 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 a + bigdec + (.setScale 2 java.math.RoundingMode/HALF_UP) + (double)))) + +(defn extract-sheet-details [f] + (into [] + (for [row (->> (doc/load-workbook f) + (doc/sheet-seq) + first + (doc/row-seq) + )] + (mapv doc/read-cell (doc/cell-seq row)) + ))) + +(defn rows->maps [rows] + (let [[headers & rows] rows] + (for [r rows] + (into {} + (map vector headers r))))) + + +(defn map->sales-order [r clients] + (let [order-number (get r "Order Number") + event-date (get r "Event Date") + store-name (get r "Store Name") + adjustments (get r "Adjustments") + tax (get r "Sales Tax") + food-total (get r "Food Total") + commission (get r "Commission") + fee (get r "Payment Transaction Fee") + tip (get r "Tip") + caterer-name (get r "Caterer Name") + client (some->> caterer-name + (parse/exact-match clients)) + client-id (:db/id client) + location (first (:client/locations client))] + (if (and event-date client-id location) + #:sales-order + {:date (atime/unparse-local (atime/localize (coerce/to-date-time event-date)) atime/iso-date) + :order-number order-number + :external-id (str "ezcater/order/" client-id "-" location "-" order-number) + :client client + :location location + :reference-link (str order-number) + :line-items [#:order-line-item + {:external-id (str "ezcater/order/" client-id "-" location "-" order-number "-" 0) + :item-name "EZCater Catering" + :category "EZCater Catering" + :discount (fmt-amount (or adjustments 0.0)) + :tax (fmt-amount tax) + :total (fmt-amount (+ food-total + tax + tip))}] + + :charges [#:charge + {:type-name "CARD" + :date (atime/unparse-local (atime/localize (coerce/to-date-time event-date)) atime/iso-date) + :client client-id + :location location + :external-id (str "ezcater/charge/" client-id "-" location "-" order-number "-" 0) + :processor :ccp-processor/ezcater + :total (fmt-amount (+ food-total + tax + tip)) + :tip (fmt-amount tip)}] + :total (fmt-amount (+ food-total + tax + tip)) + :discount (fmt-amount (or adjustments 0.0)) + :service-charge (fmt-amount (+ fee commission)) + :tax (fmt-amount tax) + :tip (fmt-amount tip) + :returns 0.0 + :vendor :vendor/ccp-ezcater + } + (alog/warn ::missing-client + :order order-number + :store-name store-name + :caterer-name caterer-name)))) + +(defn stream->sales-orders [s] + (let [clients (map first (dc/q '[:find (pull ?c [:client/code + :db/id + :client/feature-flags + {:client/location-matches [:location-match/matches :location-match/location]} + :client/name + :client/matches + :client/locations]) + :where [?c :client/code]] + (dc/db conn)))] + (into [] + (->> s + extract-sheet-details + rows->maps + (map #(map->sales-order % clients)) + (filter identity))))) + +(defn import-stuff [] + (with-open [s (io/input-stream "/home/brycecovert/Downloads/test_2023-04-01_2023-04-30_2023-05-04.xlsx")] + (stream->sales-orders s))) + + diff --git a/src/clj/auto_ap/parse.clj b/src/clj/auto_ap/parse.clj index 124d4752..6d54d260 100644 --- a/src/clj/auto_ap/parse.clj +++ b/src/clj/auto_ap/parse.clj @@ -127,7 +127,10 @@ (filter (fn [{:keys [:client/matches :client/name] :as client :or {matches []}}] (seq (filter (fn [m] - (= (.toLowerCase invoice-client-name) (.toLowerCase m))) + (and + m + invoice-client-name + (= (.toLowerCase invoice-client-name) (.toLowerCase m)))) (conj matches name))))) first))) diff --git a/src/clj/auto_ap/routes/ezcater_xls.clj b/src/clj/auto_ap/routes/ezcater_xls.clj new file mode 100644 index 00000000..a78b01c0 --- /dev/null +++ b/src/clj/auto_ap/routes/ezcater_xls.clj @@ -0,0 +1,193 @@ +(ns auto-ap.routes.ezcater-xls + (:require + [auto-ap.datomic :refer [audit-transact conn]] + [auto-ap.logging :as alog] + [auto-ap.parse :as parse] + [auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]] + [auto-ap.ssr-routes :as ssr-routes] + [auto-ap.ssr.ui :refer [base-page]] + [auto-ap.ssr.utils :refer [html-response]] + [auto-ap.time :as atime] + [bidi.bidi :as bidi] + [clj-time.coerce :as coerce] + [clojure.java.io :as io] + [com.brunobonacci.mulog :as mu] + [datomic.api :as dc] + [dk.ative.docjure.spreadsheet :as doc] + [hiccup2.core :as hiccup])) + +(defn fmt-amount [a] + (with-precision 2 + (some-> a + bigdec + (.setScale 2 java.math.RoundingMode/HALF_UP) + (double)))) + +(defn extract-sheet-details [f] + (into [] + (for [row (->> (doc/load-workbook f) + (doc/sheet-seq) + first + (doc/row-seq) + )] + (mapv doc/read-cell (doc/cell-seq row)) + ))) + +(defn rows->maps [rows] + (let [[headers & rows] rows] + (for [r rows] + (into {} + (map vector headers r))))) + + +(defn map->sales-order [r clients] + (let [order-number (get r "Order Number") + event-date (get r "Event Date") + store-name (get r "Store Name") + adjustments (get r "Adjustments") + tax (get r "Sales Tax") + food-total (get r "Food Total") + commission (get r "Commission") + fee (get r "Payment Transaction Fee") + tip (get r "Tip") + caterer-name (get r "Caterer Name") + client (some->> caterer-name + not-empty + (parse/exact-match clients)) + client-id (:db/id client) + location (first (:client/locations client))] + (cond (and event-date client-id location ) + [:order #:sales-order + {:date (coerce/to-date (atime/localize (coerce/to-date-time event-date))) + :external-id (str "ezcater/order/" client-id "-" location "-" order-number) + :client client-id + :location location + :reference-link (str order-number) + :line-items [#:order-line-item + {:external-id (str "ezcater/order/" client-id "-" location "-" order-number "-" 0) + :item-name "EZCater Catering" + :category "EZCater Catering" + :discount (fmt-amount (or adjustments 0.0)) + :tax (fmt-amount tax) + :total (fmt-amount (+ food-total + tax + tip))}] + + :charges [#:charge + {:type-name "CARD" + :date (coerce/to-date (atime/localize (coerce/to-date-time event-date))) + :client client-id + :location location + :external-id (str "ezcater/charge/" client-id "-" location "-" order-number "-" 0) + :processor :ccp-processor/ezcater + :total (fmt-amount (+ food-total + tax + tip)) + :tip (fmt-amount tip)}] + :total (fmt-amount (+ food-total + tax + tip)) + :discount (fmt-amount (or adjustments 0.0)) + :service-charge (fmt-amount (+ fee commission)) + :tax (fmt-amount tax) + :tip (fmt-amount tip) + :returns 0.0 + :vendor :vendor/ccp-ezcater}] + + caterer-name + (do + (alog/warn ::missing-client + :order order-number + :store-name store-name + :caterer-name caterer-name) + [:missing caterer-name]) + + :else + nil))) + +(defn stream->sales-orders [s] + (let [clients (map first (dc/q '[:find (pull ?c [:client/code + :db/id + :client/feature-flags + {:client/location-matches [:location-match/matches :location-match/location]} + :client/name + :client/matches + :client/locations]) + :where [?c :client/code]] + (dc/db conn)))] + (into [] + (->> s + extract-sheet-details + rows->maps + (map #(map->sales-order % clients)) + (filter identity))))) + +(defn import-stuff [] + (with-open [s (io/input-stream "/home/brycecovert/Downloads/test_2023-04-01_2023-04-30_2023-05-04.xlsx")] + (stream->sales-orders s))) + +(defn page* [] + [:div + [:h1.title "EZCater XLS Import"] + [:div.card.block {:style {:width "500px"}} + [:div.card-content + "Please go to " + [:a {:href "https://www.ezcater.com/ez_manage/reports/new" :target "_blank"} "EZCater's report page"] + " to generate a new report. Then drop it below."]] + [:div#page-notification.notification.block {:style {:display "none"}}] + [:div.card.block + [:div.card-content + [:form {:action (bidi/path-for ssr-routes/only-routes + :admin-ezcater-xls) + :method "POST" + :class "dropzone" + :id "ezcater"}]]] + [:script + (hiccup/raw + " + Dropzone.options.ezcater = { + success: function (file, response) { + document.getElementById(\"page-notification\").innerHTML = response; + document.getElementById(\"page-notification\").style[\"display\"] = \"block\"; + } + }")]]) + +(defn upload-xls [{:keys [identity] :as request}] + + (let [file (or (get (:params request) :file) + (get (:params request) "file"))] + (mu/log ::uploading-file + :file file) + (with-open [s (io/input-stream (:tempfile file))] + (try + (let [parse-results (stream->sales-orders s) + new-orders (->> parse-results + (filter (comp #{:order} first)) + (map last)) + + missing-location (->> parse-results + (filter (comp #{:missing} first)) + (map last))] + (audit-transact new-orders identity) + (html-response [:div (format "Successfully imported %d orders." (count new-orders)) + (when (seq missing-location) + [:div "Missing the following locations" + [:ul.ul + (for [ml (into #{} missing-location)] + [:li ml])]])])) + (catch Exception e + (alog/error ::import-error + :error e) + (html-response [:div (.getMessage e)])))))) + +(defn page [{:keys [matched-route request-method] :as request}] + (mu/log ::method + :method request-method) + (if (= :post request-method) + (upload-xls request) + (base-page + request + (page*) + + (admin-side-bar matched-route)))) + diff --git a/src/clj/auto_ap/ssr/core.clj b/src/clj/auto_ap/ssr/core.clj index 43e4f2b1..e5d344d4 100644 --- a/src/clj/auto_ap/ssr/core.clj +++ b/src/clj/auto_ap/ssr/core.clj @@ -7,7 +7,8 @@ [auto-ap.ssr.transaction.insights :as insights] [auto-ap.ssr.company.company-1099 :as company-1099] [auto-ap.ssr.search :as search] - [auto-ap.ssr.company-dropdown :as company-dropdown])) + [auto-ap.ssr.company-dropdown :as company-dropdown] + [auto-ap.routes.ezcater-xls :as ezcater-xls])) ;; from auto-ap.ssr-routes, because they're shared @@ -27,5 +28,6 @@ :transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-secure insights/transaction-rows)) :transaction-insight-approve (wrap-client-redirect-unauthenticated (wrap-secure insights/approve)) :transaction-insight-explain (wrap-client-redirect-unauthenticated (wrap-secure insights/explain)) + :admin-ezcater-xls (wrap-client-redirect-unauthenticated (wrap-admin ezcater-xls/page)) :search (wrap-client-redirect-unauthenticated (wrap-secure search/dialog-contents))}) diff --git a/src/clj/auto_ap/ssr/ui.clj b/src/clj/auto_ap/ssr/ui.clj index 79b092fc..98adeefb 100644 --- a/src/clj/auto_ap/ssr/ui.clj +++ b/src/clj/auto_ap/ssr/ui.clj @@ -43,6 +43,8 @@ :crossorigin= "anonymous"}] [:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async" }]] [:script {:type "text/javascript", :src "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/autoComplete.min.js"}] + [:script {:src "https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"}] + [:link {:rel "stylesheet" :href "https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" :type "text/css"}] [:body [:div {:id "app"} [:div 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 721633c2..a85aff5e 100644 --- a/src/cljc/auto_ap/shared_views/admin/side_bar.cljc +++ b/src/cljc/auto_ap/shared_views/admin/side_bar.cljc @@ -73,23 +73,30 @@ :active-route active-route :route :admin-history :icon-style {:font-size "25px"}}) + (menu-item {:label "Background Jobs" + :icon-class "icon icon-cog-play-1" + :test-route #{:admin-jobs} + :active-route active-route + :route :admin-jobs + :icon-style {:font-size "25px"}}) [:p.menu-label "Import"] (menu-item {:label "Excel Invoices" :icon-class "fa fa-download" :test-route #{:admin-excel-import} :active-route active-route :route :admin-excel-import}) - (menu-item {:label "Excel Invoices" + (menu-item {:label "Import Batches" :icon-class "fa fa-download" :test-route #{:admin-import-batches} :active-route active-route :route :admin-import-batches}) - (menu-item {:label "Background Jobs" + (menu-item {:label "EZCater XLS Import" :icon-class "icon icon-cog-play-1" - :test-route #{:admin-jobs} + :test-route #{:admin-ezcater-xls} :active-route active-route - :route :admin-jobs + :route :admin-ezcater-xls :icon-style {:font-size "25px"}}) + (into [:div ] children)]) #?(:clj diff --git a/src/cljc/auto_ap/ssr_routes.cljc b/src/cljc/auto_ap/ssr_routes.cljc index 1a18a21b..9f4cdfba 100644 --- a/src/cljc/auto_ap/ssr_routes.cljc +++ b/src/cljc/auto_ap/ssr_routes.cljc @@ -2,11 +2,12 @@ (def routes {"logout" :logout "search" :search - "admin" {"/history" {"" :admin-history - "/" :admin-history - #"/search/?" :admin-history-search - ["/" [#"\d+" :entity-id] #"/?"] :admin-history-search - ["/inspect/" [#"\d+" :entity-id] #"/?"] :admin-history-inspect}} + "admin" {"/history" {"" :admin-history + "/" :admin-history + #"/search/?" :admin-history-search + ["/" [#"\d+" :entity-id] #"/?"] :admin-history-search + ["/inspect/" [#"\d+" :entity-id] #"/?"] :admin-history-inspect} + "/ezcater-xls" :admin-ezcater-xls} "transaction" {"/insights" {"" :transaction-insights "/table" :transaction-insight-table ["/approve/" [#"\d+" :transaction-id]] {:post :transaction-insight-approve} diff --git a/test/clj/auto_ap/integration/routes/ezcater_xls.clj b/test/clj/auto_ap/integration/routes/ezcater_xls.clj new file mode 100644 index 00000000..0e4eecbf --- /dev/null +++ b/test/clj/auto_ap/integration/routes/ezcater_xls.clj @@ -0,0 +1,59 @@ +(ns auto-ap.integration.routes.ezcater-xls + (:require + [auto-ap.integration.util + :refer [setup-test-data test-client wrap-setup]] + [auto-ap.routes.ezcater-xls :as sut] + [clojure.java.io :as io] + [clojure.test :refer [deftest is testing use-fixtures]])) + +(use-fixtures :each wrap-setup) + +(deftest stream->sales-ordersx + (testing "Should import nothing when there are no clients" + (with-open [s (io/input-stream (io/resource "sample-ezcater.xlsx"))] + (is (= [:missing "Nick The Greek (Santa Cruz)"] (first (sut/stream->sales-orders s)))))) + (testing "should import for a single client" + (let [{:strs [test-client]} (setup-test-data [(test-client + :db/id "test-client" + :client/code "NGOP" + :client/locations ["DT"] + :client/name "The client" + :client/matches ["Nick the Greek (Elk Grove)"])])] + (with-open [s (io/input-stream (io/resource "sample-ezcater.xlsx"))] + (is (seq (sut/stream->sales-orders s))) + ) + (with-open [s (io/input-stream (io/resource "sample-ezcater.xlsx"))] + (is (= #:sales-order + {:vendor :vendor/ccp-ezcater + :service-charge -95.9 + :date #inst "2023-04-03T18:30:00" + :reference-link "ZA2-320" + :charges + [#:charge{:type-name "CARD" + :date #inst "2023-04-03T18:30:00" + :client test-client + :location "DT" + :external-id + "ezcater/charge/17592186045501-DT-ZA2-320-0" + :processor :ccp-processor/ezcater + :total 516.12 + :tip 0.0}] + :client test-client + :tip 0.0 + :tax 37.12 + :external-id "ezcater/order/17592186045501-DT-ZA2-320" + :total 516.12 + :line-items + [#:order-line-item{:external-id + "ezcater/order/17592186045501-DT-ZA2-320-0" + :item-name "EZCater Catering" + :category "EZCater Catering" + :discount 0.0 + :tax 37.12 + :total 516.12}] + :discount 0.0 + :location "DT" + :returns 0.0} + (last (first (filter (comp #{:order} first) + (sut/stream->sales-orders s)))))))))) + diff --git a/test/clj/auto_ap/routes/invoice_test.clj b/test/clj/auto_ap/integration/routes/invoice_test.clj similarity index 98% rename from test/clj/auto_ap/routes/invoice_test.clj rename to test/clj/auto_ap/integration/routes/invoice_test.clj index facbce6e..7b9c29b7 100644 --- a/test/clj/auto_ap/routes/invoice_test.clj +++ b/test/clj/auto_ap/integration/routes/invoice_test.clj @@ -1,4 +1,4 @@ -(ns auto-ap.routes.invoice-test +(ns auto-ap.integration.routes.invoice-test (:require [auto-ap.datomic :refer [conn]] [auto-ap.datomic.clients :refer [rebuild-search-index]]