mirror of
https://github.com/mruwnik/chicken-master.git
synced 2025-06-08 13:24:42 +02:00
move order date to recurring
This commit is contained in:
parent
50ab098410
commit
a4bafe61e3
@ -7,7 +7,9 @@
|
||||
org.postgresql/postgresql {:mvn/version "42.2.6"}
|
||||
ring-basic-authentication/ring-basic-authentication {:mvn/version "1.1.0"}
|
||||
ring-cors/ring-cors {:mvn/version "0.1.13"}
|
||||
ring/ring {:mvn/version "1.8.1"}}
|
||||
ring/ring {:mvn/version "1.8.1"}
|
||||
org.dmfs/lib-recur {:mvn/version "0.12.2"}
|
||||
}
|
||||
|
||||
:aliases
|
||||
{:dev {:jvm-opts ["-Dconfig=config/dev/config.edn"]
|
||||
|
14
backend/resources/migrations/004-recurring-orders.edn
Normal file
14
backend/resources/migrations/004-recurring-orders.edn
Normal file
@ -0,0 +1,14 @@
|
||||
{:up ["ALTER TABLE orders ADD recurrence VARCHAR(512)"
|
||||
"ALTER TABLE orders ADD end_date TIMESTAMPTZ"
|
||||
"CREATE TABLE recurrence_exceptions(
|
||||
order_id INT,
|
||||
order_date TIMESTAMPTZ NOT NULL,
|
||||
status order_state DEFAULT 'waiting',
|
||||
PRIMARY KEY(order_id, order_date),
|
||||
CONSTRAINT fk_customer FOREIGN KEY(order_id) REFERENCES orders(id) ON DELETE CASCADE)"
|
||||
"UPDATE orders SET end_date = o.order_date
|
||||
FROM orders AS o
|
||||
WHERE orders.id = o.id"]
|
||||
:down ["DROP TABLE recurrence_exceptions"
|
||||
"ALTER TABLE orders DROP COLUMN end_date"
|
||||
"ALTER TABLE orders DROP COLUMN recurrence"]}
|
10
backend/resources/migrations/005-move-statuses.edn
Normal file
10
backend/resources/migrations/005-move-statuses.edn
Normal file
@ -0,0 +1,10 @@
|
||||
{:up ["INSERT INTO recurrence_exceptions (order_id, order_date, status)
|
||||
SELECT id AS order_id, order_date, status FROM orders
|
||||
WHERE status != 'waiting'
|
||||
ON conflict do nothing"
|
||||
"ALTER TABLE orders DROP COLUMN status"]
|
||||
:down ["ALTER TABLE orders ADD status order_state DEFAULT 'waiting'"
|
||||
"UPDATE orders SET status = ex.status
|
||||
FROM recurrence_exceptions AS ex
|
||||
WHERE orders.id = ex.order_id AND orders.order_date = ex.order_date"]}
|
||||
;
|
@ -43,8 +43,8 @@
|
||||
order (-> request :body (update :id #(or % id)))]
|
||||
(as-edn (orders/replace! user-id order))))
|
||||
|
||||
(defn delete-order [user-id id] (->> id edn/read-string (orders/delete! user-id) as-edn))
|
||||
(defn set-order-state [user-id id status] (as-edn (orders/change-state! user-id (edn/read-string id) status)))
|
||||
(defn delete-order [user-id id day] (->> id edn/read-string (orders/delete! user-id day) as-edn))
|
||||
(defn set-order-state [user-id id day status] (as-edn (orders/change-state! user-id (edn/read-string id) day status)))
|
||||
|
||||
(defn get-stock [user-id] (get-values user-id [:customers :products]))
|
||||
|
||||
@ -64,5 +64,5 @@
|
||||
(GET "/orders" [:as {user-id :basic-authentication}] (get-orders user-id))
|
||||
(POST "/orders" request (update-order request))
|
||||
(PUT "/orders/:id" request (update-order request))
|
||||
(DELETE "/orders/:id" [id :as {user-id :basic-authentication}] (delete-order user-id id))
|
||||
(POST "/orders/:id/:status" [id status :as {user-id :basic-authentication}] (set-order-state user-id id status)))
|
||||
(DELETE "/orders/:id" [id :as {user-id :basic-authentication body :body}] (delete-order user-id id (:day body)))
|
||||
(POST "/orders/:id/:status" [id status :as {user-id :basic-authentication body :body}] (set-order-state user-id id (:day body) status)))
|
||||
|
@ -7,53 +7,86 @@
|
||||
[chicken-master.customers :as customers]
|
||||
[chicken-master.time :as t]))
|
||||
|
||||
(defn upsert-order! [tx user-id customer-id {:keys [id day state notes]}]
|
||||
(defn upsert-order! [tx user-id customer-id {:keys [id day notes]}]
|
||||
(let [order {:customer_id customer-id
|
||||
:notes notes
|
||||
:status (some-> (or state "waiting") name jdbc.types/as-other)
|
||||
:order_date (some-> day t/parse-date t/inst->timestamp)}]
|
||||
:order_date (some-> day t/to-db-date)
|
||||
:end_date (some-> day t/to-db-date)}]
|
||||
(if (db/get-by-id tx user-id :orders id)
|
||||
(do (sql/update! tx :orders order {:id id}) id)
|
||||
(:orders/id (sql/insert! tx :orders (assoc order :user_id user-id))))))
|
||||
|
||||
|
||||
(defn structure-order [items]
|
||||
{:id (-> items first :orders/id)
|
||||
:notes (-> items first :orders/notes)
|
||||
:state (-> items first :orders/status keyword)
|
||||
:day (-> items first :orders/order_date (.toInstant) str (subs 0 10))
|
||||
:who {:id (-> items first :customers/id)
|
||||
:name (-> items first :customers/name)}
|
||||
:products (->> items
|
||||
(filter :products/name)
|
||||
(reduce (fn [coll {:keys [order_products/amount order_products/price products/name]}]
|
||||
(assoc coll (keyword name) {:amount amount :price price})) {}))})
|
||||
{:id (-> items first :orders/id)
|
||||
:notes (-> items first :orders/notes)
|
||||
:recurrence (-> items first :orders/recurrence)
|
||||
:who {:id (-> items first :customers/id)
|
||||
:name (-> items first :customers/name)}
|
||||
:products (->> items
|
||||
(filter :products/name)
|
||||
(reduce (fn [coll {:keys [order_products/amount order_products/price products/name]}]
|
||||
(assoc coll (keyword name) {:amount amount :price price})) {}))})
|
||||
|
||||
(defn item-days
|
||||
"Get all days between `from` and `to` (inclusively) for which the order applies."
|
||||
[from to items]
|
||||
(let [{:orders/keys [recurrence order_date]} (first items)]
|
||||
(->> (t/recurrence->dates (t/latest from order_date) (or recurrence "FREQ=MONTHLY;COUNT=1"))
|
||||
(take-while #(not (t/after % (t/to-inst to))))
|
||||
(map #(vector (t/format-date %) :waiting))
|
||||
(into {}))))
|
||||
|
||||
(defn order-iterator [items days]
|
||||
(->> items
|
||||
(filter :recurrence_exceptions/status)
|
||||
(reduce (fn [coll {:recurrence_exceptions/keys [status order_date]}]
|
||||
(assoc coll (t/format-date order_date) (keyword status)))
|
||||
days)))
|
||||
|
||||
(defn items->orders [from to items]
|
||||
(let [base-order (structure-order items)]
|
||||
(->> items
|
||||
(item-days from to)
|
||||
(order-iterator items)
|
||||
(map (fn [[date status]] (assoc base-order :day date :state status))))))
|
||||
|
||||
(def orders-query
|
||||
"SELECT o.id, o.notes, o.status, o.order_date, c.id, c.name, p.name, op.amount, op.price
|
||||
"SELECT o.id, o.notes, ex.status, o.order_date, o.recurrence, c.id, c.name, p.name, op.amount, op.price, ex.order_date
|
||||
FROM orders o JOIN customers c ON o.customer_id = c.id
|
||||
LEFT OUTER JOIN recurrence_exceptions ex ON o.id = ex.order_id
|
||||
LEFT OUTER JOIN order_products op ON o.id = op.order_id
|
||||
LEFT OUTER JOIN products p on p.id = op.product_id ")
|
||||
|
||||
(defn- get-orders [tx where params]
|
||||
(->> (into [(if where (str orders-query where) orders-query)] params)
|
||||
(sql/query tx)
|
||||
(group-by :orders/id)
|
||||
vals
|
||||
(map structure-order)))
|
||||
(def date-filter-clause "WHERE o.order_date >= ? AND o.end_date <= ? ")
|
||||
(def orders-date-query (str orders-query date-filter-clause))
|
||||
|
||||
(defn get-order [tx user-id id]
|
||||
(first (get-orders tx "WHERE o.id = ? AND o.user_id = ?" [id user-id])))
|
||||
(defn- get-orders
|
||||
([tx where params] (get-orders tx t/min-date t/max-date where params))
|
||||
([tx from to where params]
|
||||
(->> (into [(str orders-date-query (if where (str " AND " where) ""))
|
||||
(t/to-db-date from)
|
||||
(t/to-db-date to)] params)
|
||||
(sql/query tx)
|
||||
(group-by :orders/id)
|
||||
vals
|
||||
(map (partial items->orders from to))
|
||||
(apply concat)
|
||||
(filter #(t/between from (:day %) to)))))
|
||||
|
||||
(defn get-all [user-id] (group-by :day (get-orders db/db-uri "WHERE o.user_id = ?" [user-id])))
|
||||
(defn get-order [tx user-id id & [day]]
|
||||
(first
|
||||
(if day
|
||||
(->> (get-orders tx day day "o.id = ? AND o.user_id = ?" [id user-id])
|
||||
(filter #(= (t/format-date day) (:day %))))
|
||||
(get-orders tx "o.id = ? AND o.user_id = ?" [id user-id]))))
|
||||
|
||||
(defn get-all [user-id] (group-by :day (get-orders db/db-uri "o.user_id = ?" [user-id])))
|
||||
|
||||
(defn- orders-for-days [tx user-id & days]
|
||||
(let [days (remove nil? days)]
|
||||
(->> days
|
||||
(map t/inst->timestamp)
|
||||
(map jdbc.types/as-date)
|
||||
(into [user-id])
|
||||
(get-orders tx (str "WHERE o.user_id = ? AND o.order_date::date IN " (db/psql-list days)))
|
||||
(let [days (->> days (remove nil?) (map t/to-inst))
|
||||
from (apply t/earliest days)
|
||||
to (apply t/latest days)]
|
||||
(->> (get-orders tx from to "o.user_id = ?" [user-id])
|
||||
(group-by :day)
|
||||
(merge (reduce #(assoc %1 (t/format-date %2) {}) {} days)))))
|
||||
|
||||
@ -61,32 +94,59 @@
|
||||
(jdbc/with-transaction [tx db/db-uri]
|
||||
(let [customer-id (or (:id who)
|
||||
(customers/get-or-create-by-name tx user-id (:name who)))
|
||||
previous-day (some->> order :id (db/get-by-id tx user-id :orders) :orders/order_date (.toInstant))]
|
||||
previous-day (some->> order :id (db/get-by-id tx user-id :orders) :orders/order_date t/to-inst)]
|
||||
(products/update-products-mapping! tx user-id :order
|
||||
(upsert-order! tx user-id customer-id order)
|
||||
products)
|
||||
(orders-for-days tx user-id previous-day (some-> order :day t/parse-date)))))
|
||||
|
||||
(defn delete! [user-id id]
|
||||
(jdbc/with-transaction [tx db/db-uri]
|
||||
(let [day (some->> id (db/get-by-id tx user-id :orders) :orders/order_date (.toInstant))]
|
||||
(sql/delete! tx :orders {:id id :user_id user-id})
|
||||
(when day (orders-for-days tx user-id day)))))
|
||||
|
||||
(defn change-state!
|
||||
"Update the state of the given order and also modify the number of products available:
|
||||
* when `fulfilled` decrement the number of products
|
||||
* when `waiting` increment the number (as this means a previously fulfilled order has been returned)"
|
||||
[user-id id state]
|
||||
(jdbc/with-transaction [tx db/db-uri]
|
||||
(let [order (get-order tx user-id id)
|
||||
operator (condp = state
|
||||
"fulfilled" "-"
|
||||
"waiting" "+")]
|
||||
(when (not= (:state order) (keyword state))
|
||||
(doseq [[prod {:keys [amount]}] (:products order)]
|
||||
(jdbc/execute-one! tx
|
||||
[(str "UPDATE products SET amount = amount " operator " ? WHERE name = ?")
|
||||
([user-id id day state] (jdbc/with-transaction [tx db/db-uri] (change-state! tx user-id id day state)))
|
||||
([tx user-id id day state]
|
||||
(let [order (get-order tx user-id id day)
|
||||
operator (condp = state
|
||||
"fulfilled" "-"
|
||||
"waiting" "+"
|
||||
"canceled" "+")]
|
||||
(when (not= (:state order) (keyword state))
|
||||
;; update product counts
|
||||
(doseq [[prod {:keys [amount]}] (:products order)]
|
||||
(jdbc/execute-one! tx
|
||||
[(str "UPDATE products SET amount = amount " operator " ? WHERE name = ?")
|
||||
amount (name prod)]))
|
||||
(sql/update! tx :orders {:status (jdbc.types/as-other state)} {:id id}))
|
||||
(orders-for-days tx user-id (-> order :day t/parse-date)))))
|
||||
|
||||
;; upsert the state for the given day
|
||||
(if (jdbc/execute-one! tx
|
||||
["SELECT * from recurrence_exceptions WHERE order_id = ? AND order_date = ?" id (t/to-db-date day)])
|
||||
(sql/update! tx :recurrence_exceptions {:status (jdbc.types/as-other state)}
|
||||
{:order_id id :order_date (t/to-db-date day)})
|
||||
(sql/insert! tx :recurrence_exceptions {:order_id id
|
||||
:order_date (t/to-db-date day)
|
||||
:status (jdbc.types/as-other state)})))
|
||||
(orders-for-days tx user-id day))))
|
||||
|
||||
(defn delete! [user-id day id]
|
||||
(jdbc/with-transaction [tx db/db-uri]
|
||||
(if day
|
||||
;; Only delete the one day
|
||||
(change-state! tx user-id id day "canceled")
|
||||
;; Delete the order along with all recurrences
|
||||
(when-let [{:orders/keys [order_date end_date]} (some->> id (db/get-by-id tx user-id :orders))]
|
||||
(sql/delete! tx :orders {:id id :user_id user-id})
|
||||
(orders-for-days tx user-id order_date end_date)))))
|
||||
|
||||
;; (delete! 2 "2022-04-20" 240)
|
||||
;; (delete! 2 nil 241)
|
||||
|
||||
;; (change-state! 2 240 "2022-04-20" "waiting")
|
||||
;; (change-state! 2 250 "2022-04-23" "fulfilled")
|
||||
;; (get-orders db/db-uri (t/to-inst #inst "2022-04-20T00:00:00Z") (t/to-inst #inst "2022-04-20T00:00:00Z") nil nil)
|
||||
;; (get-orders db/db-uri (t/to-inst #inst "2022-04-23T00:00:00Z") (t/to-inst #inst "2022-04-24T00:00:00Z") nil nil)
|
||||
;; (get-order db/db-uri 2 242 (t/to-inst #inst "2022-04-20T00:00:00Z"))
|
||||
;; (orders-for-days db/db-uri 2 #inst "2022-04-23T00:00:00Z" #inst "2022-04-23T00:00:00Z")
|
||||
;; (orders-for-days db/db-uri 2 #inst "2022-04-23T00:00:00Z")
|
||||
;; (orders-for-days db/db-uri 2 "2022-04-19")
|
||||
;; (get-all 2)
|
||||
|
@ -1,17 +1,59 @@
|
||||
(ns chicken-master.time
|
||||
(:import [java.time Instant LocalDate ZoneOffset]
|
||||
[java.time.format DateTimeFormatter]
|
||||
[java.sql Timestamp]))
|
||||
|
||||
[java.sql Timestamp]
|
||||
[org.dmfs.rfc5545.recur RecurrenceRule]
|
||||
[org.dmfs.rfc5545 DateTime]))
|
||||
|
||||
(defn recurrence->dates [start rule]
|
||||
(let [iterator (.iterator (RecurrenceRule. rule)
|
||||
(-> start (.toEpochMilli) (DateTime.)))]
|
||||
(take-while identity
|
||||
(repeatedly #(when (.hasNext iterator)
|
||||
(-> iterator (.nextDateTime) (.getTimestamp) (Instant/ofEpochMilli)))))))
|
||||
(defn parse-date [date]
|
||||
(-> date (LocalDate/parse) (.atStartOfDay) (.toInstant ZoneOffset/UTC)))
|
||||
(if (= (count date) 10)
|
||||
(-> date (LocalDate/parse) (.atStartOfDay) (.toInstant ZoneOffset/UTC))
|
||||
(Instant/parse date)))
|
||||
|
||||
(defn format-date [date]
|
||||
(-> DateTimeFormatter/ISO_LOCAL_DATE
|
||||
(.withZone ZoneOffset/UTC)
|
||||
(.format date)))
|
||||
(defprotocol TimeHelpers
|
||||
(to-inst [d])
|
||||
(to-db-date [d])
|
||||
(format-date [date])
|
||||
(before [d1 d2])
|
||||
(after [d1 d2]))
|
||||
|
||||
(defn inst->timestamp [inst] (Timestamp/from inst))
|
||||
(extend-type Instant
|
||||
TimeHelpers
|
||||
(to-inst [d] d)
|
||||
(to-db-date [d] (Timestamp/from d))
|
||||
(format-date [date]
|
||||
(-> DateTimeFormatter/ISO_LOCAL_DATE
|
||||
(.withZone ZoneOffset/UTC)
|
||||
(.format date)))
|
||||
(before [d1 d2] (.isBefore d1 d2))
|
||||
(after [d1 d2] (.isBefore d2 d1)))
|
||||
|
||||
(extend-type java.util.Date
|
||||
TimeHelpers
|
||||
(to-inst [d] (.toInstant d))
|
||||
(to-db-date [d] (-> d to-inst to-db-date))
|
||||
(format-date [date] (format-date (to-inst date)))
|
||||
(before [d1 d2] (< (.compareTo d1 d2) 0))
|
||||
(after [d1 d2] (> (.compareTo d1 d2) 0)))
|
||||
|
||||
(extend-type java.lang.String
|
||||
TimeHelpers
|
||||
(to-inst [d] (parse-date d))
|
||||
(to-db-date [d] (-> d to-inst to-db-date))
|
||||
(format-date [date] (format-date (to-inst date)))
|
||||
(before [d1 d2] (before (to-inst d1) (to-inst d2)))
|
||||
(after [d1 d2] (after (to-inst d1) (to-inst d2))))
|
||||
|
||||
(defn earliest [& ds] (->> ds (map to-inst) (sort before) first))
|
||||
(defn latest [& ds] (->> ds (map to-inst) (sort after) first))
|
||||
(defn between [d1 d2 d3] (and (not (before d2 d1)) (not (after d2 d3))))
|
||||
|
||||
(defn now [] (Instant/now))
|
||||
(def min-date (parse-date "2020-01-01"))
|
||||
(def max-date (.plusSeconds (now) (* 10 356 24 60 60))) ; 10 years from now - can't be bothered to do this properly...
|
||||
|
@ -2,8 +2,10 @@
|
||||
(:require
|
||||
[next.jdbc :as jdbc]
|
||||
[next.jdbc.sql :as sql]
|
||||
[next.jdbc.types :as jdbc.types]
|
||||
[chicken-master.orders :as sut]
|
||||
[chicken-master.products :as products]
|
||||
[chicken-master.time :as t]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :refer [deftest is testing]]))
|
||||
|
||||
@ -13,53 +15,55 @@
|
||||
products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}}]
|
||||
(if products
|
||||
(for [[product {:keys [amount price]}] products]
|
||||
(merge #:orders{:id id :notes notes :status status :order_date date}
|
||||
(merge #:orders{:id id :notes notes :order_date date :end_date date}
|
||||
#:recurrence_exceptions{:order_id id :order_date date :status status}
|
||||
#:customers{:id user_id :name user_name}
|
||||
{:products/name (name product) :order_products/price price :order_products/amount amount}))
|
||||
[(merge #:orders{:id id :notes notes :status status :order_date date}
|
||||
[(merge #:orders{:id id :notes notes :order_date date :end_date date}
|
||||
#:recurrence_exceptions{:order_id id :order_date date :status status}
|
||||
#:customers{:id user_id :name user_name}
|
||||
{:products/name nil :order_products/price nil :order_products/amount nil})]))
|
||||
|
||||
(deftest structure-order-test
|
||||
(testing "basic structure"
|
||||
(is (= (sut/structure-order (raw-order-row))
|
||||
{:id 1, :notes "note", :state :pending, :day "2020-01-01",
|
||||
{:id 1, :notes "note", :recurrence nil,
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}})))
|
||||
|
||||
(testing "missing products"
|
||||
(is (= (sut/structure-order (raw-order-row :products nil))
|
||||
{:id 1, :notes "note", :state :pending, :day "2020-01-01",
|
||||
{:id 1, :notes "note", :recurrence nil
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:products {}}))))
|
||||
|
||||
(deftest test-get-order
|
||||
(testing "correct values returned"
|
||||
(with-redefs [sql/query (fn [_ [query & params]]
|
||||
(is (str/ends-with? query "WHERE o.id = ? AND o.user_id = ?"))
|
||||
(is (= params [123 "1"]))
|
||||
(is (str/ends-with? query "WHERE o.order_date >= ? AND o.end_date <= ? AND o.id = ? AND o.user_id = ?"))
|
||||
(is (= params [(t/to-db-date t/min-date) (t/to-db-date t/max-date) 123 "1"]))
|
||||
(raw-order-row))]
|
||||
(is (= (sut/get-order :tx "1" 123)
|
||||
{:id 1, :notes "note", :state :pending, :day "2020-01-01",
|
||||
{:id 1, :notes "note", :recurrence nil :state :pending, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}))))
|
||||
|
||||
(testing "Only 1 item returned"
|
||||
(with-redefs [sql/query (fn [_ [query & params]]
|
||||
(is (str/ends-with? query "WHERE o.id = ? AND o.user_id = ?"))
|
||||
(is (= params [123 "1"]))
|
||||
(is (str/ends-with? query "WHERE o.order_date >= ? AND o.end_date <= ? AND o.id = ? AND o.user_id = ?"))
|
||||
(is (= params [(t/to-db-date t/min-date) (t/to-db-date t/max-date) 123 "1"]))
|
||||
(concat (raw-order-row)
|
||||
(raw-order-row :id 21)))]
|
||||
(is (= (sut/get-order :tx "1" 123)
|
||||
{:id 1, :notes "note", :state :pending, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}})))))
|
||||
|
||||
(deftest test-get-all
|
||||
(testing "correct values returned"
|
||||
(with-redefs [sql/query (fn [_ [query & params]]
|
||||
(is (str/ends-with? query "WHERE o.user_id = ?"))
|
||||
(is (= params ["1"]))
|
||||
(is (str/ends-with? query "WHERE o.order_date >= ? AND o.end_date <= ? AND o.user_id = ?"))
|
||||
(is (= params [(t/to-db-date t/min-date) (t/to-db-date t/max-date) "1"]))
|
||||
(concat
|
||||
(raw-order-row :id 1 :status "waiting")
|
||||
(raw-order-row :id 2 :date #inst "2020-01-03")
|
||||
@ -67,16 +71,16 @@
|
||||
(raw-order-row :id 4)))]
|
||||
(is (= (sut/get-all "1")
|
||||
{"2020-01-01" [{:id 1, :notes "note", :state :waiting, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}
|
||||
{:id 3, :notes "note", :state :pending, :day "2020-01-01",
|
||||
:who {:id 43, :name "John"},
|
||||
:who {:id 43, :name "John"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}
|
||||
{:id 4, :notes "note", :state :pending, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]
|
||||
"2020-01-03" [{:id 2, :notes "note", :state :pending, :day "2020-01-03",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})))))
|
||||
|
||||
(deftest test-replace!
|
||||
@ -99,10 +103,10 @@
|
||||
(raw-order-row :id 4)))]
|
||||
(is (= (sut/replace! :user-id order)
|
||||
{"2020-01-01" [{:id 1, :notes "note", :state :waiting, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}
|
||||
{:id 4, :notes "note", :state :pending, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})))))
|
||||
|
||||
(testing "replace order from different day"
|
||||
@ -124,10 +128,10 @@
|
||||
(raw-order-row :id 4)))]
|
||||
(is (= (sut/replace! :user-id order)
|
||||
{"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]
|
||||
"2020-01-02" [{:id 1, :notes "note", :state :waiting, :day "2020-01-02",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})))))
|
||||
|
||||
(testing "unknown products are ignored"
|
||||
@ -149,10 +153,10 @@
|
||||
(raw-order-row :id 4)))]
|
||||
(is (= (sut/replace! :user-id order)
|
||||
{"2020-01-01" [{:id 1, :notes "note", :state :waiting, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}
|
||||
{:id 4, :notes "note", :state :pending, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))))))
|
||||
|
||||
(deftest test-delete!
|
||||
@ -163,9 +167,9 @@
|
||||
(is (= table :orders))
|
||||
(is (= by {:id 1 :user_id :user-id})))
|
||||
sql/query (constantly (raw-order-row :id 4))]
|
||||
(is (= (sut/delete! :user-id 1)
|
||||
(is (= (sut/delete! :user-id nil 1)
|
||||
{"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))))
|
||||
|
||||
(testing "nothing returned if no date set for the given order"
|
||||
@ -175,32 +179,102 @@
|
||||
(is (= table :orders))
|
||||
(is (= by {:id 1 :user_id :user-id})))
|
||||
sql/query (constantly (raw-order-row :id 4))]
|
||||
(is (nil? (sut/delete! :user-id 1))))))
|
||||
(is (nil? (sut/delete! :user-id nil 1)))))
|
||||
|
||||
(let [invocations (atom [])]
|
||||
(with-redefs [jdbc.types/as-other identity
|
||||
jdbc/transact (fn [_ f & args] (apply f args))
|
||||
jdbc/execute-one! (constantly {:orders/order_date #inst "2020-01-01"})
|
||||
sql/delete! (fn [_ table by]
|
||||
(swap! invocations conj ["deleting" table by]))
|
||||
sql/query (constantly (raw-order-row :id 4))
|
||||
sql/update! (fn [_ table status key] (swap! invocations conj ["updating" table status key]))
|
||||
sql/insert! (fn [_ table values] (swap! invocations conj ["inserting" table values]))]
|
||||
(testing "deleting without provided a date will remove the whole order"
|
||||
(reset! invocations [])
|
||||
(is (= (sut/delete! :user-id nil 1)
|
||||
{"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))
|
||||
(is (= [["deleting" :orders {:id 1 :user_id :user-id}]]
|
||||
@invocations)))
|
||||
|
||||
(testing "deleting with a provided date will soft remove a single order by updating it if it exists"
|
||||
(reset! invocations [])
|
||||
(is (= (sut/delete! :user-id "2020-01-01" 1)
|
||||
{"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))
|
||||
(is (= [["updating" :recurrence_exceptions {:status "canceled"}
|
||||
{:order_id 1, :order_date (t/to-db-date "2020-01-01")}]]
|
||||
@invocations)))
|
||||
|
||||
(testing "deleting with a provided date will soft remove a single order by adding an exception if none provided"
|
||||
(with-redefs [jdbc/execute-one! (fn [_ [q]]
|
||||
(when-not (str/includes? q "recurrence_exceptions")
|
||||
{:orders/order_date #inst "2020-01-01"}))]
|
||||
|
||||
(reset! invocations [])
|
||||
(is (= (sut/delete! :user-id "2020-01-01" 1)
|
||||
{"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))
|
||||
(is (= [["inserting" :recurrence_exceptions {:order_id 1, :order_date (t/to-db-date "2020-01-01") :status "canceled"}]]
|
||||
@invocations)))))))
|
||||
|
||||
(deftest test-change-state!
|
||||
(testing "states get changed"
|
||||
(let [updates (atom [])]
|
||||
(with-redefs [jdbc/transact (fn [_ f & args] (apply f args))
|
||||
jdbc/execute-one! #(swap! updates conj %2)
|
||||
sql/update! (fn [_ table _ val]
|
||||
(is (= table :orders))
|
||||
(is (= val {:id 1})))
|
||||
sql/query (constantly (raw-order-row :id 1 :status "waiting"))]
|
||||
(is (= (sut/change-state! :user-id 1 "fulfilled")
|
||||
(let [updates (atom [])]
|
||||
(with-redefs [jdbc.types/as-other identity
|
||||
jdbc/transact (fn [_ f & args] (apply f args))
|
||||
jdbc/execute-one! #(swap! updates conj %2)
|
||||
sql/update! (fn [_ table _ val]
|
||||
(swap! updates conj ["updating" table val]))
|
||||
sql/query (constantly (raw-order-row :id 1 :status "waiting"))
|
||||
sql/insert! (fn [_ table values] (swap! updates conj ["inserting" table values]))]
|
||||
(testing "states get changed - update when prexisiting exception"
|
||||
(reset! updates [])
|
||||
(is (= (sut/change-state! :user-id 1 "2020-01-01" "fulfilled")
|
||||
{"2020-01-01" [{:id 1, :notes "note", :state :waiting, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))
|
||||
(is (= @updates [["UPDATE products SET amount = amount - ? WHERE name = ?" 12 "eggs"]
|
||||
["UPDATE products SET amount = amount - ? WHERE name = ?" 3 "milk"]])))))
|
||||
(is (= [;; product updates
|
||||
["UPDATE products SET amount = amount - ? WHERE name = ?" 12 "eggs"]
|
||||
["UPDATE products SET amount = amount - ? WHERE name = ?" 3 "milk"]
|
||||
|
||||
;; check whether to insert or update
|
||||
["SELECT * from recurrence_exceptions WHERE order_id = ? AND order_date = ?" 1 (t/to-db-date "2020-01-01")]
|
||||
;; update
|
||||
["updating" :recurrence_exceptions {:order_id 1, :order_date (t/to-db-date "2020-01-01")}]]
|
||||
@updates)))
|
||||
|
||||
(testing "states get changed - insert when no such exception"
|
||||
(with-redefs [jdbc/execute-one! (fn [_ q]
|
||||
(swap! updates conj q)
|
||||
(when-not (str/includes? (first q) "recurrence_exceptions")
|
||||
(raw-order-row :id 1 :status "waiting")))]
|
||||
(reset! updates [])
|
||||
(is (= (sut/change-state! :user-id 1 "2020-01-01" "fulfilled")
|
||||
{"2020-01-01" [{:id 1, :notes "note", :state :waiting, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))
|
||||
(is (= [;; product updates
|
||||
["UPDATE products SET amount = amount - ? WHERE name = ?" 12 "eggs"]
|
||||
["UPDATE products SET amount = amount - ? WHERE name = ?" 3 "milk"]
|
||||
|
||||
;; check whether to insert or update
|
||||
["SELECT * from recurrence_exceptions WHERE order_id = ? AND order_date = ?" 1 (t/to-db-date "2020-01-01")]
|
||||
;; update
|
||||
["inserting" :recurrence_exceptions {:order_id 1, :order_date (t/to-db-date "2020-01-01"), :status "fulfilled"}]]
|
||||
@updates))))))
|
||||
|
||||
(testing "nothing happens if the state is already set"
|
||||
(let [updates (atom [])]
|
||||
(with-redefs [jdbc/transact (fn [_ f & args] (apply f args))
|
||||
jdbc/execute-one! #(swap! updates conj %2)
|
||||
sql/query (constantly (raw-order-row :id 1 :status "waiting"))]
|
||||
(is (= (sut/change-state! :user-id 1 "waiting")
|
||||
(is (= (sut/change-state! :user-id 1 "2020-01-01" "waiting")
|
||||
{"2020-01-01" [{:id 1, :notes "note", :state :waiting, :day "2020-01-01",
|
||||
:who {:id 2, :name "mr blobby"},
|
||||
:who {:id 2, :name "mr blobby"}, :recurrence nil
|
||||
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))
|
||||
(is (= @updates [])))))
|
||||
|
||||
|
@ -89,8 +89,8 @@
|
||||
:on-drag-start #(-> % .-dataTransfer (.setData "text" id))}
|
||||
[:div {:class :actions}
|
||||
(condp = state
|
||||
:waiting [:button {:on-click #(re-frame/dispatch [::event/fulfill-order id])} "✓"]
|
||||
:fulfilled [:button {:on-click #(re-frame/dispatch [::event/reset-order id])} "X"]
|
||||
:waiting [:button {:on-click #(re-frame/dispatch [::event/fulfill-order id day])} "✓"]
|
||||
:fulfilled [:button {:on-click #(re-frame/dispatch [::event/reset-order id day])} "X"]
|
||||
:pending nil
|
||||
nil nil)
|
||||
[:button {:on-click #(re-frame/dispatch [::event/edit-order day id])} "E"]
|
||||
@ -120,6 +120,7 @@
|
||||
(->> (if (settings :hide-fulfilled-orders)
|
||||
(remove (comp #{:fulfilled} :state) orders)
|
||||
orders)
|
||||
(remove (comp #{:canceled} :state))
|
||||
(map (partial format-order settings))
|
||||
doall)
|
||||
(when (settings :show-day-add-order)
|
||||
|
@ -46,7 +46,6 @@
|
||||
(re-frame/reg-event-fx
|
||||
::load-db
|
||||
(fn [_ _]
|
||||
(prn "loading")
|
||||
(time/update-settings config/default-settings)
|
||||
{:fx [[:dispatch [::show-from-date (time/iso-date (time/today))]]
|
||||
[:dispatch [::start-loading]]
|
||||
@ -98,15 +97,17 @@
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::fulfill-order
|
||||
(fn [{db :db} [_ id]]
|
||||
(fn [{db :db} [_ id day]]
|
||||
{:db (assoc-in db [:orders id :state] :pending)
|
||||
:http-xhrio (http-request :post (str "orders/" id "/fulfilled"))}))
|
||||
:http-xhrio (http-request :post (str "orders/" id "/fulfilled")
|
||||
:body {:day day})}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::reset-order
|
||||
(fn [{db :db} [_ id]]
|
||||
(fn [{db :db} [_ id day]]
|
||||
{:db (assoc-in db [:orders id :state] :waiting)
|
||||
:http-xhrio (http-request :post (str "orders/" id "/waiting"))}))
|
||||
:http-xhrio (http-request :post (str "orders/" id "/waiting")
|
||||
:body {:day day})}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::save-order
|
||||
|
@ -47,4 +47,4 @@
|
||||
[:h2 "Magazyn"]
|
||||
[stock-form @(re-frame/subscribe [::subs/available-products])]]
|
||||
;; On success
|
||||
:on-submit (fn [form] (prn form) (prn (process-form form)) (re-frame/dispatch [::event/save-stock (process-form form)]))))
|
||||
:on-submit (fn [form] (re-frame/dispatch [::event/save-stock (process-form form)]))))
|
||||
|
Loading…
x
Reference in New Issue
Block a user