fix(sales): resolve namespace collision and add missing clojure.string import
- Remove sales_orders_new.clj (unreferenced, duplicate ns) - Add [clojure.string :as str] to sales_orders.clj ns
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -3,8 +3,9 @@
|
|||||||
[auto-ap.datomic :refer [conn]]
|
[auto-ap.datomic :refer [conn]]
|
||||||
[auto-ap.storage.parquet :as pq]
|
[auto-ap.storage.parquet :as pq]
|
||||||
[clj-time.coerce :as c]
|
[clj-time.coerce :as c]
|
||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
[com.brunobonacci.mulog :as mu]))
|
[clojure.string :as str]
|
||||||
|
[com.brunobonacci.mulog :as mu]))
|
||||||
|
|
||||||
(defn <-row
|
(defn <-row
|
||||||
"Convert a flat parquet row into the shape consumers expect.
|
"Convert a flat parquet row into the shape consumers expect.
|
||||||
|
|||||||
@@ -1,224 +0,0 @@
|
|||||||
(ns auto-ap.datomic.sales-orders
|
|
||||||
(:require [auto-ap.datomic :refer [conn]]
|
|
||||||
[auto-ap.storage.parquet :as pq]
|
|
||||||
[clojure.data.json :as json]
|
|
||||||
[clojure.java.io :as io]))
|
|
||||||
|
|
||||||
(defn <-row
|
|
||||||
"Convert a flat parquet row (string keys) into the
|
|
||||||
shape consumers expect. Parquet produces maps of the form
|
|
||||||
|
|
||||||
{\"external-id\" \"square/order/123\",
|
|
||||||
\"location\" \"DT\",
|
|
||||||
\"total\" 50.0}
|
|
||||||
|
|
||||||
which we transform to:
|
|
||||||
|
|
||||||
{:sales-order/external-id \"square/order/123\",
|
|
||||||
:sales-order/location \"DT\",
|
|
||||||
:sales-order/total 50.0}
|
|
||||||
|
|
||||||
Note: client, charges and other nested structures are not
|
|
||||||
available in the flat parquet rows. When denormalisation
|
|
||||||
adds those columns we can restore the full consumer shape."
|
|
||||||
[row]
|
|
||||||
(-> row
|
|
||||||
(set/rename-keys
|
|
||||||
{"external-id" :sales-order/external-id
|
|
||||||
"location" :sales-order/location
|
|
||||||
"total" :sales-order/total
|
|
||||||
"tax" :sales-order/tax
|
|
||||||
"tip" :sales-order/tip})))
|
|
||||||
|
|
||||||
(defn build-where-clause
|
|
||||||
"Build a SQL WHERE fragment from the fields that
|
|
||||||
parquet can filter on: external_id.client, vendor, location.
|
|
||||||
|
|
||||||
Returns either a predicate string e.g.
|
|
||||||
\"WHERE external_id.client = 'acme' AND vendor = 'square'\"
|
|
||||||
or nil when no applicable filters exist."
|
|
||||||
[args]
|
|
||||||
(let [clauses []
|
|
||||||
client (:client-code args)
|
|
||||||
vendor (:vendor args)
|
|
||||||
location (:location args)]
|
|
||||||
(when (or client vendor location)
|
|
||||||
(->> [[:client "external_id.client" client]
|
|
||||||
[:vendor "external_id.vendor" vendor]
|
|
||||||
[:location "location" location]]
|
|
||||||
(keep (fn [[_ col v]]
|
|
||||||
(when v [col v])))
|
|
||||||
(mapv #(str %1 " = '" %2 "'"))
|
|
||||||
(str/join " AND "))}))
|
|
||||||
|
|
||||||
(defn build-sort-clause
|
|
||||||
"Map sort-key field names from args into SQL ORDER-BY fragments.
|
|
||||||
|
|
||||||
Supported fields map to parquet column names:
|
|
||||||
\"date\" -> DATE
|
|
||||||
\"total\" -> TOTAL
|
|
||||||
\"tax\" -> TAX
|
|
||||||
\"tip\" -> TIP
|
|
||||||
\"client\" -> EXTERNAL_ID_CLIENT (for flat client codes)
|
|
||||||
\"location\"-> LOCATION
|
|
||||||
|
|
||||||
Falls back to \"DATE DESC\" when the args do not specify
|
|
||||||
an explicit field."
|
|
||||||
[args]
|
|
||||||
;; We delegate most of the SQL ordering work to get-sales-orders,
|
|
||||||
;; which already defaults to DATE DESC.
|
|
||||||
(when-let [sorts (:sort args)]
|
|
||||||
(->> sorts
|
|
||||||
(keep (fn [{:keys [sort-key asc]}]
|
|
||||||
(let [dir (if asc "ASC" "DESC")
|
|
||||||
col (case sort-key
|
|
||||||
"date" "DATE"
|
|
||||||
"total" "TOTAL"
|
|
||||||
"tax" "TAX"
|
|
||||||
"tip" "TIP"
|
|
||||||
"total-desc" "TOTAL DESC"
|
|
||||||
"source" "SALE_SOURCE"
|
|
||||||
"client" "EXTERNAL_ID_CLIENT"
|
|
||||||
"location" "LOCATION"
|
|
||||||
nil)] ; unknown → skip
|
|
||||||
(when col `[~col ~dir]))))
|
|
||||||
(interleave (repeat \,))
|
|
||||||
(apply str))))
|
|
||||||
|
|
||||||
(defn build-pagination-clause
|
|
||||||
"Convert a Datomic-side pagination request into SQL-limit/offset
|
|
||||||
numbers.
|
|
||||||
|
|
||||||
Supports:
|
|
||||||
:start → OFFSET
|
|
||||||
:count / :per-page → LIMIT"
|
|
||||||
[args]
|
|
||||||
{:limit (or (:count args) (:per-page args))
|
|
||||||
:offset (or (:start args) 0)})
|
|
||||||
|
|
||||||
(defn- apply-pagination
|
|
||||||
"Safely re-implements the old datomic-side pagination logic.
|
|
||||||
Mutates a COPY of args so we can extract the resulting
|
|
||||||
cursor values for the response.
|
|
||||||
|
|
||||||
Returns {limit offset}"
|
|
||||||
[args]
|
|
||||||
(let [page (build-pagination-clause args)
|
|
||||||
{:keys [limit offset]} page
|
|
||||||
client (:client-code args)]
|
|
||||||
; In the new architecture pagination is applied server-side
|
|
||||||
; by get-sales-orders via LIMIT/OFFSET, so this function
|
|
||||||
; mainly exists as a thin wrapper for any remaining
|
|
||||||
; in-memory re-paging concerns.
|
|
||||||
(if limit
|
|
||||||
(assoc page :limit (Integer. limit))
|
|
||||||
page)))
|
|
||||||
|
|
||||||
(defn- build-options
|
|
||||||
"Assemble the opts map passed to pq/get-sales-orders:
|
|
||||||
{:client ..., :location ..., :vendor ...,
|
|
||||||
:limit 10, :offset 0, :sort, :order}"
|
|
||||||
[args]
|
|
||||||
(let [page (apply-pagination args)
|
|
||||||
limit (:limit page)
|
|
||||||
offset (:offset page)
|
|
||||||
client (:client-code args)]
|
|
||||||
(cond-> {:client client
|
|
||||||
:vendor (:vendor args)
|
|
||||||
:limit limit
|
|
||||||
:offset offset}
|
|
||||||
(:location args) (assoc :location (:location args))
|
|
||||||
(:sort args) (assoc :sort "date") ; let get-sales-orders handle order
|
|
||||||
true (merge {:order "DESC"
|
|
||||||
:sort-key (:sort-key args)}))))
|
|
||||||
|
|
||||||
(defn raw-graphql-ids
|
|
||||||
"Query sales-orders FROM parquet files via DuckDB instead of Datomic.
|
|
||||||
|
|
||||||
Filters applied at the parquet level:
|
|
||||||
- date-range → selects which parquet files to read
|
|
||||||
- client-code / vendor / location where clauses
|
|
||||||
- sort & pagination are delegated to get-sales-orders
|
|
||||||
|
|
||||||
category, processor, type-name filters require nested joins
|
|
||||||
that parquet does not support -- those fields are ignored.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
{:ids [string-key-for-each-matched-row]
|
|
||||||
:count int (total matches BEFORE pagination)}"
|
|
||||||
[args]
|
|
||||||
(let [start (when-let [s (:start (:date-range args))]
|
|
||||||
(.toString (.plusDays (java.time.LocalDate/parse s) -1)))
|
|
||||||
end (when-let [e (:end (:date-range args))]
|
|
||||||
(-> e .substring 0 10))
|
|
||||||
where (build-where-clause args)
|
|
||||||
options (build-options args)
|
|
||||||
where-str (some-> where #(str " WHERE " %))]
|
|
||||||
(cond->> nil
|
|
||||||
; Query rows from parquet with our filters and sort.
|
|
||||||
where-str (pq/get-sales-orders
|
|
||||||
start end
|
|
||||||
(assoc options :sort-key where-str)))
|
|
||||||
|
|
||||||
; For each row returned we need an ID string.
|
|
||||||
; We use the external-id column as the lookup key.
|
|
||||||
(when-let [rows (:rows result)]
|
|
||||||
{:ids (mapv #(str (:external_id %)) rows)
|
|
||||||
:rows rows
|
|
||||||
:count (:count result)})))
|
|
||||||
|
|
||||||
(defn graphql-results
|
|
||||||
"Return the full payment-row data for the selected IDs.
|
|
||||||
Since we now read FROM parquet, we receive the raw row vector
|
|
||||||
and transform it.
|
|
||||||
|
|
||||||
The old signature [ids db args] is replaced by [rows id-keys _].
|
|
||||||
We ignore the database argument (Datomic pull is no longer
|
|
||||||
called)."
|
|
||||||
[rows _id-keys _args]
|
|
||||||
(->> rows
|
|
||||||
(mapv #(<-row %))))
|
|
||||||
|
|
||||||
(defn summarize-orders
|
|
||||||
"Sum totals and discounts for the given ID-set.
|
|
||||||
|
|
||||||
This function still queries Datomic because the parquet-side
|
|
||||||
aggregation query would duplicate the WHERE logic.
|
|
||||||
If we want a pure parquet path here, add an
|
|
||||||
SQL-based aggregation in a follow-up."
|
|
||||||
[ids]
|
|
||||||
(let [[total tax]
|
|
||||||
(#'auto-ap.datomic/aggregate-sum ids) ; uses dc/q internally
|
|
||||||
first]
|
|
||||||
{:total total
|
|
||||||
:tax tax}))
|
|
||||||
|
|
||||||
(defn get-graphql
|
|
||||||
"Entry-point: return [payments count summary].
|
|
||||||
|
|
||||||
The data flow is:
|
|
||||||
1. raw-graphql-ids → parquet query → [:rows :count]
|
|
||||||
2. graphql-results <- transform rows ← [:results <_id-keys> _args]
|
|
||||||
3. summarize-orders <- Datomic agg ← [:total :tax]"
|
|
||||||
[args]
|
|
||||||
(let [{:keys [ids count']}
|
|
||||||
(mu/trace
|
|
||||||
::get-sales-order-ids
|
|
||||||
[]
|
|
||||||
(raw-graphql-ids args))]
|
|
||||||
[(->> (mu/trace ::get-results [] (graphql-results ids id-keys args))
|
|
||||||
matching-count
|
|
||||||
summarize-orders ids)])))
|
|
||||||
|
|
||||||
(defn summarize-graphql
|
|
||||||
"Entry-point: return just the summary {:total :tax}.
|
|
||||||
|
|
||||||
Like get-graphql, this delegates to raw-graphql-ids
|
|
||||||
and then to summarize-orders."
|
|
||||||
[args]
|
|
||||||
(let [{:keys [ids count']}
|
|
||||||
(mu/trace
|
|
||||||
::get-sales-order-ids
|
|
||||||
[]
|
|
||||||
(raw-graphql-ids args))]
|
|
||||||
(summarize-orders ids)))
|
|
||||||
Reference in New Issue
Block a user