diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..807d5983 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ + +# Use bd merge for beads JSONL files +.beads/issues.jsonl merge=beads diff --git a/dev-resources/INVOICE - 03882095.pdf b/dev-resources/INVOICE - 03882095.pdf new file mode 100755 index 00000000..3810d667 Binary files /dev/null and b/dev-resources/INVOICE - 03882095.pdf differ diff --git a/src/clj/auto_ap/square/core3.clj b/src/clj/auto_ap/square/core3.clj index a0df0672..2f192cd2 100644 --- a/src/clj/auto_ap/square/core3.clj +++ b/src/clj/auto_ap/square/core3.clj @@ -293,7 +293,9 @@ (condp = (:name (:source order)) "GRUBHUB" :ccp-processor/grubhub "UBEREATS" :ccp-processor/uber-eats + "Uber Eats" :ccp-processor/uber-eats "DOORDASH" :ccp-processor/doordash + "DoorDash" :ccp-processor/doordash "Koala" :ccp-processor/koala "koala-production" :ccp-processor/koala :ccp-processor/na)) @@ -349,7 +351,10 @@ (s/reduce conj []))] [(remove-nils #:sales-order - {:date (coerce/to-date (time/to-time-zone (coerce/to-date-time (:created_at order)) (time/time-zone-for-id "America/Los_Angeles"))) + {:date (if (= "Invoices" (:name (:source order))) + (when (:closed_at order) + (coerce/to-date (time/to-time-zone (coerce/to-date-time (:closed_at order)) (time/time-zone-for-id "America/Los_Angeles")))) + (coerce/to-date (time/to-time-zone (coerce/to-date-time (:created_at order)) (time/time-zone-for-id "America/Los_Angeles")))) :client (:db/id client) :location (:square-location/client-location location) :external-id (str "square/order/" (:client/code client) "-" (:square-location/client-location location) "-" (:id order)) @@ -379,6 +384,9 @@ ;; sometimes orders stay open in square. At least one payment ;; is needed to import, in order to avoid importing orders in-progress. (and + (if (= "Invoices" (:name (:source order))) + (boolean (:closed_at order)) + true) (or (> (count (:tenders order)) 0) (seq (:returns order))) (or (= #{} (set (map #(:status (:card_details %)) (:tenders order)))) @@ -862,7 +870,11 @@ #_(comment (require 'auto-ap.time-reader) - + @(let [[c [l]] (get-square-client-and-location "DBFS") ] + (log/peek :x [ c l]) + (search c l #clj-time/date-time "2026-03-28" #clj-time/date-time "2026-03-29") + + ) @(let [[c [l]] (get-square-client-and-location "NGAK") ] (log/peek :x [ c l]) @@ -972,13 +984,14 @@ :headers (client-base-headers client) :as :json}) :body))) -(->> - @(let [[c [l]] (get-square-client-and-location "NGGG")] + (->> + @(let [[c [l]] (get-square-client-and-location "NGGG")] - (search c l (time/plus (time/now)))) - (filter (fn [r] - (str/starts-with? (:created_at r) "2024-03-14")))) + (search c l (time/now) (time/plus (time/now) (time/days -1)))) + + (filter (fn [r] + (str/starts-with? (:created_at r) "2024-03-14")))) (def refs (->> @@ -995,35 +1008,35 @@ (map (fn [r] @(get-payment c (:payment_id r))) refs)) -(get-square-client-and-location "NGGB") + (get-square-client-and-location "NGGB") (def my-results (let [[c [l]] (get-square-client-and-location "NGFA")])) (clojure.data.csv/write-csv *out* - (for [c (get-square-clients) - l (:client/square-locations c) - :when (:square-location/client-location l) - bad-row (try (->> @(search c l (coerce/to-date-time #inst "2024-04-01T00:00:00-07:00") (coerce/to-date-time #inst "2024-04-15T23:59:00-07:00")) - (filter #(not (should-import-order? %))) - (map #(first (deref (order->sales-order c l %)))) - (filter (fn already-exists [o] - (when (:sales-order/external-id o) - (seq (dc/q '[:find ?i - :in $ ?ei - :where [?i :sales-order/external-id ?ei]] - (dc/db conn) - (:sales-order/external-id o))))))) - (catch Exception e - []))] - [(:client/code c) (atime/unparse-local (clj-time.coerce/to-date-time (:sales-order/date bad-row)) atime/normal-date) (:sales-order/total bad-row) (:sales-order/tax bad-row) (:sales-order/tip bad-row) (:db/id bad-row)]) - :separator \tab) + (for [c (get-square-clients) + l (:client/square-locations c) + :when (:square-location/client-location l) + bad-row (try (->> @(search c l (coerce/to-date-time #inst "2024-04-01T00:00:00-07:00") (coerce/to-date-time #inst "2024-04-15T23:59:00-07:00")) + (filter #(not (should-import-order? %))) + (map #(first (deref (order->sales-order c l %)))) + (filter (fn already-exists [o] + (when (:sales-order/external-id o) + (seq (dc/q '[:find ?i + :in $ ?ei + :where [?i :sales-order/external-id ?ei]] + (dc/db conn) + (:sales-order/external-id o))))))) + (catch Exception e + []))] + [(:client/code c) (atime/unparse-local (clj-time.coerce/to-date-time (:sales-order/date bad-row)) atime/normal-date) (:sales-order/total bad-row) (:sales-order/tax bad-row) (:sales-order/tip bad-row) (:db/id bad-row)]) + :separator \tab) ;; => - + (require 'auto-ap.time-reader) @@ -1035,7 +1048,7 @@ (def z @(search c l #clj-time/date-time "2025-02-23T00:00:00-08:00" - #clj-time/date-time "2025-02-28T00:00:00-08:00")) + #clj-time/date-time "2025-02-28T00:00:00-08:00")) (take 10 (map #(first (deref (order->sales-order c l %))) z))) @@ -1051,17 +1064,43 @@ (count) ) - (doseq [c (get-square-clients)] - (println "Upserting" (:client/name c)) - @(upsert c)) + + + + (doseq [[code] (seq (dc/q '[:find ?code + :in $ + :where [?o :sales-order/date ?d] + [(>= ?d #inst "2026-01-01")] + [?o :sales-order/source "Invoices"] + [?o :sales-order/client ?c] + [?c :client/code ?code]] + (dc/db conn))) + :let [[c [l]] (get-square-client-and-location code) + ] + order @(search c l #clj-time/date-time "2026-01-01T00:00:00-08:00" (time/now)) + :when (= "Invoices" (:name (:source order) )) + :let [[sales-order] @(order->sales-order c l order)]] + + (when (should-import-order? order) + (println "DATE IS" (:sales-order/date sales-order)) + (when (some-> (:sales-order/date sales-order) coerce/to-date-time (time/after? #clj-time/date-time "2026-2-16T00:00:00-08:00")) + (println "WOULD UPDATE" sales-order) + @(dc/transact auto-ap.datomic/conn [sales-order]) + ) + #_@(dc/transact ) + (println "DONE")) + + + ) #_(filter (comp #{"OTHER"} :type) (mapcat :tenders z)) - (let [[c [l]] (get-square-client-and-location "LFHH")] - (search c l (clj-time.coerce/from-date #inst "2025-02-28") (clj-time.coerce/from-date #inst "2025-03-01")) + @(let [[c [l]] (get-square-client-and-location "NGRY")] + #_(search c l (clj-time.coerce/from-date #inst "2025-02-28") (clj-time.coerce/from-date #inst "2025-03-01")) - (:order (get-order c l "CLjQqkzVfGa82o5hEFUrGtUGO6QZY" )) - ) + (order->sales-order c l (:order (get-order c l "KdvwntmfMNTKBu8NOocbxatOs18YY" ))) + + ) ) diff --git a/src/clj/auto_ap/ssr/components/date_range.clj b/src/clj/auto_ap/ssr/components/date_range.clj index ff1306f1..fe694c35 100644 --- a/src/clj/auto_ap/ssr/components/date_range.clj +++ b/src/clj/auto_ap/ssr/components/date_range.clj @@ -1,11 +1,13 @@ (ns auto-ap.ssr.components.date-range (:require [auto-ap.ssr.components :as com] + [auto-ap.ssr.components.buttons :as but] + [auto-ap.ssr.svg :as svg] [auto-ap.time :as atime] [clj-time.coerce :as c] [clj-time.core :as t] [clj-time.periodic :as per])) -(defn date-range-field [{:keys [value id]}] +(defn date-range-field [{:keys [value id apply-button?]}] [:div {:id id} (com/field {:label "Date Range"} [:div.space-y-4 @@ -21,11 +23,17 @@ (atime/unparse-local atime/normal-date)) :placeholder "Date" :size :small - :class "shrink"}) + :class "shrink date-filter-input"}) (com/date-input {:name "end-date" :value (some-> (:end value) (atime/unparse-local atime/normal-date)) :placeholder "Date" :size :small - :class "shrink"})]])]) + :class "shrink date-filter-input"}) + (when apply-button? + (but/button- {:color :secondary + :size :small + :type "button" + "x-on:click" "$dispatch('datesApplied')"} + "Apply"))]])]) diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index 4309ae32..ce68ed83 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -38,7 +38,7 @@ [auto-ap.ssr.invoice.common :refer [default-read]] [auto-ap.ssr.invoice.import :as invoice-import] [auto-ap.ssr.invoice.new-invoice-wizard :as new-invoice-wizard :refer [location-select*]] - [auto-ap.ssr.pos.common :refer [date-range-field*]] + [auto-ap.ssr.components.date-range :as dr] [auto-ap.ssr.svg :as svg] [auto-ap.ssr.utils :refer [apply-middleware-to-all-handlers assert-schema @@ -78,7 +78,7 @@ [:div {:id "exact-match-id-tag"}])) (defn filters [request] - [:form#invoice-filters {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" + [:form#invoice-filters {"hx-trigger" "datesApplied, change delay:500ms from:.filter-trigger, keyup changed from:.hot-filter delay:1000ms" "hx-get" (bidi/path-for ssr-routes/only-routes ::route/table) "hx-target" "#entity-table" @@ -93,7 +93,8 @@ :url (bidi/path-for ssr-routes/only-routes :vendor-search) :value (:vendor (:query-params request)) :value-fn :db/id - :content-fn :vendor/name})) + :content-fn :vendor/name + :class "filter-trigger"})) (com/field {:label "Account"} (com/typeahead {:name "account" :id "account" @@ -101,8 +102,12 @@ :value (:account (:query-params request)) :value-fn :db/id :content-fn #(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read (:db/id %)) - (:db/id (:client request))))})) - (date-range-field* request) + (:db/id (:client request)))) + :class "filter-trigger"})) + (dr/date-range-field {:value {:start (:start-date (:query-params request)) + :end (:end-date (:query-params request))} + :id "date-range" + :apply-button? true}) (com/field {:label "Check #"} (com/text-input {:name "check-number" :id "check-number" @@ -487,7 +492,10 @@ :fetch-page fetch-page :oob-render (fn [request] - [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true) + [(assoc-in (dr/date-range-field {:value {:start (:start-date (:query-params request)) + :end (:end-date (:query-params request))} + :id "date-range" + :apply-button? true}) [1 :hx-swap-oob] true) (assoc-in (exact-match-id* request) [1 :hx-swap-oob] true)]) :query-schema query-schema :parse-query-params (fn [p] diff --git a/src/clj/auto_ap/ssr/ledger/balance_sheet.clj b/src/clj/auto_ap/ssr/ledger/balance_sheet.clj index 185faba3..adfc67e5 100644 --- a/src/clj/auto_ap/ssr/ledger/balance_sheet.clj +++ b/src/clj/auto_ap/ssr/ledger/balance_sheet.clj @@ -81,7 +81,7 @@ data (into [] (for [client-id client-ids d date - [client-id account-id location debits credits balance count] (iol-ion.query/detailed-account-snapshot (dc/db conn) client-id (coerce/to-date (time/plus d (time/days 1)))) + [client-id account-id location debits credits balance count] (iol-ion.query/detailed-account-snapshot (dc/db conn) client-id (coerce/to-date d)) :let [account ((or (lookup-account client-id) {}) account-id)]] {:client-id client-id :account-id account-id diff --git a/test/clj/auto_ap/parse/templates_test.clj b/test/clj/auto_ap/parse/templates_test.clj index 3313bcfb..fcdfe54b 100644 --- a/test/clj/auto_ap/parse/templates_test.clj +++ b/test/clj/auto_ap/parse/templates_test.clj @@ -51,3 +51,22 @@ (is (= "720.33" (:total (nth results 1)))) (is (= "853.16" (:total (nth results 2)))) (is (= "1066.60" (:total (nth results 3))))))) + +(deftest parse-bonanza-produce-invoice-03882095 + (testing "Should parse Bonanza Produce invoice 03882095 with customer identifier including address" + (let [pdf-file (io/file "dev-resources/INVOICE - 03882095.pdf") + pdf-text (:out (clojure.java.shell/sh "pdftotext" "-layout" (str pdf-file) "-")) + results (sut/parse pdf-text) + result (first results)] + (is (some? results) "parse should return a result") + (is (some? result) "Template should match and return a result") + (when result + (is (= "Bonanza Produce" (:vendor-code result))) + (is (= "03882095" (:invoice-number result))) + (let [d (:date result)] + (is (= 2026 (time/year d))) + (is (= 1 (time/month d))) + (is (= 23 (time/day d)))) + (is (= "NICK THE GREEK" (:customer-identifier result))) + (is (= "600 VISTA WAY" (str/trim (:account-number result)))) + (is (= "946.24" (:total result)))))))