editable recurs

This commit is contained in:
Daniel O'Connell 2022-04-20 19:00:47 +02:00
parent b4ed659718
commit f6167bf3c0
9 changed files with 283 additions and 78 deletions

View File

@ -37,11 +37,11 @@
:end_date (t/to-db-date end-date)))) :end_date (t/to-db-date end-date))))
(defn duplicate-order! [tx user-id order updates] (defn duplicate-order! [tx user-id order updates]
(->> updates (->> updates
(merge {:user_id user-id}) (merge {:user_id user-id})
(set-dates order) (set-dates order)
(sql/insert! tx :orders) (sql/insert! tx :orders)
:orders/id)) :orders/id))
(defn update-non-recurring [tx order updates] (defn update-non-recurring [tx order updates]
(let [order-id (:orders/id order) (let [order-id (:orders/id order)
@ -55,12 +55,29 @@
(upsert-exception! tx order-id order_date (:recurrence_exceptions/status exception))) (upsert-exception! tx order-id order_date (:recurrence_exceptions/status exception)))
order-id)) order-id))
(update-non-recurring db/db-uri (db/get-by-id db/db-uri 2 :orders 285) {:notes "asddqwqwd" :order_date "2020-04-19"}) (defn update-single-item [tx user-id from-date {:orders/keys [id order_date] :as order} updates]
(recurrence-exception? db/db-uri 285 "2022-04-20") (upsert-exception! tx id (t/to-db-date (or from-date order_date)) "canceled")
(duplicate-order! tx user-id (dissoc order :orders/recurrence) (dissoc updates :recurrence)))
(defn update-from-date [tx user-id from-date {:orders/keys [id order_date recurrence] :as order} updates]
(let [[recur-part-1 recur-part-2] (t/split-rule order_date recurrence from-date)]
(cond
;; Split the order into 2, from the given date
recur-part-2
(do
(sql/update! tx :orders {:end_date (t/to-db-date from-date) :recurrence recur-part-1} {:id id})
(duplicate-order! tx user-id order (assoc updates :recurrence recur-part-2)))
;; No need to split the event, as it's the first one
recur-part-1
(sql/update! tx :orders (set-dates order updates) {:id id})
:else nil ;; FIXME: What should happen if an invalid date is provided?
)))
(defn upsert-order! [tx user-id customer-id {:keys [id day notes update-type order-date recurrence]}] (defn upsert-order! [tx user-id customer-id {:keys [id day notes update-type order-date recurrence]}]
(let [updates (assoc? {:customer_id customer-id} (let [updates (assoc? {:customer_id customer-id}
:recurrence recurrence :recurrence (some-> recurrence t/make-rule)
:notes notes :notes notes
:order_date (some-> day t/to-db-date)) :order_date (some-> day t/to-db-date))
order (db/get-by-id tx user-id :orders id)] order (db/get-by-id tx user-id :orders id)]
@ -75,20 +92,26 @@
(do (sql/update! tx :orders (set-dates order updates) {:id id}) id) (do (sql/update! tx :orders (set-dates order updates) {:id id}) id)
(= :from-here update-type) (= :from-here update-type)
(do (update-from-date tx user-id order-date order updates)
;; TODO: update magic recurrence rules to handle splitting stuff here
(sql/update! tx :orders {:end_date (t/to-db-date order-date)} {:id id})
(duplicate-order! tx user-id order updates))
:else ; single item modified :else ; single item modified
(do (update-single-item tx user-id order-date order updates))))
(upsert-exception! tx id order-date "canceled")
(duplicate-order! tx user-id order updates))))) ;; (db/get-by-id db/db-uri 2 :orders 267)
;; (upsert-order! db/db-uri 2 15 {:notes "no recur1" :day "2022-04-20"})
;; (upsert-order! db/db-uri 2 15 {:notes "recur2" :day "2022-04-18" :recurrence "FREQ=DAILY;COUNT=10"})
;; (upsert-order! db/db-uri 2 15 {:update-type :all :id 279 :notes "recur2" :day "2022-04-18" :recurrence "FREQ=DAILY;COUNT=10"})
;; (upsert-order! db/db-uri 2 15 {:id 267 :notes "main-default" :day "2022-04-18" :order-date "2022-04-18"})
;; (upsert-order! db/db-uri 2 15 {:id 267 :notes "main" :day "2022-04-18"})
;; (upsert-order! db/db-uri 2 15 {:id 267 :notes "main-all" :update-type :all :day "2022-04-18" :order-date "2022-04-18"})
;; (upsert-order! db/db-uri 2 15 {:id 267 :notes "main-from" :update-type :from-here :day "2022-04-18" :order-date "2022-04-22"})
;; (upsert-order! db/db-uri 2 15 {:id 267 :notes "12323" :update-type :single :day "2022-04-18" :order-date "2022-04-18"})
;; (upsert-order! db/db-uri 2 15 {:id 260 :notes "1dsf2a" :update-type :single :day "2022-04-19"})
(defn structure-order [items] (defn structure-order [items]
{:id (-> items first :orders/id) {:id (-> items first :orders/id)
:notes (-> items first :orders/notes) :notes (-> items first :orders/notes)
:recurrence (-> items first :orders/recurrence) :recurrence (some-> items first :orders/recurrence t/parse-rule)
:who {:id (-> items first :customers/id) :who {:id (-> items first :customers/id)
:name (-> items first :customers/name)} :name (-> items first :customers/name)}
:products (->> items :products (->> items
@ -100,7 +123,8 @@
"Get all days between `from` and `to` (inclusively) for which the order applies." "Get all days between `from` and `to` (inclusively) for which the order applies."
[from to items] [from to items]
(let [{:orders/keys [recurrence order_date]} (first items)] (let [{:orders/keys [recurrence order_date]} (first items)]
(->> (t/recurrence->dates (t/latest from order_date) (or recurrence "FREQ=MONTHLY;COUNT=1")) (->> (t/recurrence->dates order_date (or recurrence "FREQ=MONTHLY;COUNT=1"))
(remove #(t/before % (t/to-inst from)))
(take-while #(not (t/after % (t/to-inst to)))) (take-while #(not (t/after % (t/to-inst to))))
(map #(vector (t/format-date %) :waiting)) (map #(vector (t/format-date %) :waiting))
(into {})))) (into {}))))
@ -140,7 +164,8 @@
vals vals
(map (partial items->orders from to)) (map (partial items->orders from to))
(apply concat) (apply concat)
(filter #(t/between from (:day %) to))))) (filter #(t/between from (:day %) to))
(remove (comp #{:canceled} :state)))))
(defn get-order [tx user-id id & [day]] (defn get-order [tx user-id id & [day]]
(first (first
@ -197,7 +222,6 @@
(defn delete! [user-id day action-type id] (defn delete! [user-id day action-type id]
(jdbc/with-transaction [tx db/db-uri] (jdbc/with-transaction [tx db/db-uri]
(prn (->> id (db/get-by-id tx user-id :orders)))
(cond (cond
;; Delete the order along with all recurrences ;; Delete the order along with all recurrences
(or (->> id (db/get-by-id tx user-id :orders) :orders/recurrence nil?) (or (->> id (db/get-by-id tx user-id :orders) :orders/recurrence nil?)
@ -207,3 +231,16 @@
;; Only delete the one day ;; Only delete the one day
:else :else
(change-state! tx user-id id day "canceled")))) (change-state! tx user-id id day "canceled"))))
;; (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)

View File

@ -2,7 +2,7 @@
(:import [java.time Instant LocalDate ZoneOffset] (:import [java.time Instant LocalDate ZoneOffset]
[java.time.format DateTimeFormatter] [java.time.format DateTimeFormatter]
[java.sql Timestamp] [java.sql Timestamp]
[org.dmfs.rfc5545.recur RecurrenceRule] [org.dmfs.rfc5545.recur RecurrenceRule Freq]
[org.dmfs.rfc5545 DateTime])) [org.dmfs.rfc5545 DateTime]))
(defn parse-date [date] (defn parse-date [date]
@ -47,21 +47,102 @@
(defn earliest [& ds] (->> ds (map to-inst) (sort before) first)) (defn earliest [& ds] (->> ds (map to-inst) (sort before) first))
(defn latest [& ds] (->> ds (map to-inst) (sort after) 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 between [d1 d2 d3] (and (not (before d2 d1)) (not (after d2 d3))))
(defn same-day [d1 d2]
(when (and d1 d2)
(= (format-date d1) (format-date d2))))
(defn now [] (Instant/now)) (defn now [] (Instant/now))
(def min-date (parse-date "2020-01-01")) (def min-date (parse-date "2020-01-01"))
(def max-date (.plusSeconds (now) (* 40 356 24 60 60))) ; 40 years from now - can't be bothered to do this properly... (def max-date (.plusSeconds (now) (* 40 356 24 60 60))) ; 40 years from now - can't be bothered to do this properly...
;; Recurrence helpers
(defn to-recur-datetime [d] (-> d to-inst (.toEpochMilli) (DateTime.)))
(defn recurrence->dates [start rule] (defn recurrence->dates [start rule]
(let [iterator (.iterator (RecurrenceRule. rule) (let [iterator (.iterator (RecurrenceRule. rule) (to-recur-datetime start))]
(-> start (.toEpochMilli) (DateTime.)))]
(take-while identity (take-while identity
(repeatedly #(when (.hasNext iterator) (repeatedly #(when (.hasNext iterator)
(-> iterator (.nextDateTime) (.getTimestamp) (Instant/ofEpochMilli))))))) (-> iterator (.nextDateTime) (.getTimestamp) (Instant/ofEpochMilli)))))))
(defn next-date
"Get the next date after `day`"
[start rule day]
(->> (recurrence->dates (to-inst start) rule)
(filter (partial before (to-inst day)))
first))
(defn last-date (defn last-date
"Get the end date for the given rule" "Get the end date for the given rule"
[start rule] [start rule]
(->> (recurrence->dates (to-inst start) rule) (->> (recurrence->dates (to-inst start) rule)
(take-while #(before % max-date)) (take-while #(before % max-date))
last)) last))
(defn recurrence-pos
"The index of the day in the sequence for `day`."
[start rule day]
(->> (recurrence->dates (to-inst start) rule)
(keep-indexed (fn [i d] (when (same-day d day) i)))
first))
(def freq-units {"day" Freq/DAILY "week" Freq/WEEKLY "month" Freq/MONTHLY "year" Freq/YEARLY})
(defn set-freq [rule freq]
(.toString
(if rule
(doto (RecurrenceRule. rule) (.setFreq (freq-units freq Freq/WEEKLY) true))
(RecurrenceRule. (freq-units freq Freq/WEEKLY)))))
(defn get-freq [rule]
(-> rule
(RecurrenceRule.)
(.getFreq)
((clojure.set/map-invert freq-units))))
(defn get-interval [rule] (.getInterval (RecurrenceRule. rule)))
(defn set-interval [rule interval]
(let [rule (RecurrenceRule. rule)]
(.setInterval rule interval)
(.toString rule)))
(defn get-count [rule] (.getCount (RecurrenceRule. rule)))
(defn set-count [rule count]
(let [rule (RecurrenceRule. rule)]
(.setCount rule count)
(.toString rule)))
(defn get-until [rule]
(some-> rule (RecurrenceRule.) (.getUntil) (.getTimestamp) (Instant/ofEpochMilli)))
(defn set-until [rule until]
(let [rule (RecurrenceRule. rule)]
(.setUntil rule (to-recur-datetime until))
(.toString rule)))
(defn split-rule [start rule day]
(let [pos (recurrence-pos start rule day)]
(cond
;; FIXME: think this through...
(nil? pos) nil
;; the first date is the one to split on - so just leave it as is
(zero? pos) [rule]
(get-count rule)
[(set-count rule pos) (set-count rule (- (get-count rule) pos))]
(get-until rule)
[(set-until rule day) rule]
:else nil)))
(defn make-rule [{:keys [times until unit every]}]
(let [rule (-> nil
(set-freq unit)
(set-interval every))]
(if times
(set-count rule times)
(set-until rule until))))
(defn parse-rule [rule]
{:times (get-count rule)
:until (get-until rule)
:unit (get-freq rule)
:every (get-interval rule)})

View File

@ -10,7 +10,7 @@
[clojure.test :refer [deftest is testing]])) [clojure.test :refer [deftest is testing]]))
(defn raw-order-row [& {:keys [id notes status date user_id user_name products recurrence] (defn raw-order-row [& {:keys [id notes status date user_id user_name products recurrence]
:or {id 1 notes "note" status "pending" date #inst "2020-01-01" :or {id 1 notes "note" status "waiting" date #inst "2020-01-01"
user_id 2 user_name "mr blobby" recurrence nil user_id 2 user_name "mr blobby" recurrence nil
products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}}] products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}}]
(if products (if products
@ -44,7 +44,7 @@
(is (= params [(t/to-db-date t/min-date) (t/to-db-date t/max-date) 123 "1"])) (is (= params [(t/to-db-date t/min-date) (t/to-db-date t/max-date) 123 "1"]))
(raw-order-row))] (raw-order-row))]
(is (= (sut/get-order :tx "1" 123) (is (= (sut/get-order :tx "1" 123)
{:id 1, :notes "note", :recurrence nil :state :pending, :day "2020-01-01", {:id 1, :notes "note", :recurrence nil :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :who {:id 2, :name "mr blobby"},
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}})))) :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}))))
@ -55,7 +55,7 @@
(concat (raw-order-row) (concat (raw-order-row)
(raw-order-row :id 21)))] (raw-order-row :id 21)))]
(is (= (sut/get-order :tx "1" 123) (is (= (sut/get-order :tx "1" 123)
{:id 1, :notes "note", :state :pending, :day "2020-01-01", {:id 1, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence nil :who {:id 2, :name "mr blobby"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}))))) :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}})))))
@ -73,13 +73,13 @@
{"2020-01-01" [{:id 1, :notes "note", :state :waiting, :day "2020-01-01", {"2020-01-01" [{:id 1, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence nil :who {:id 2, :name "mr blobby"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}} :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}
{:id 3, :notes "note", :state :pending, :day "2020-01-01", {:id 3, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 43, :name "John"}, :recurrence nil :who {:id 43, :name "John"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}} :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}
{:id 4, :notes "note", :state :pending, :day "2020-01-01", {:id 4, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence nil :who {:id 2, :name "mr blobby"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}] :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]
"2020-01-03" [{:id 2, :notes "note", :state :pending, :day "2020-01-03", "2020-01-03" [{:id 2, :notes "note", :state :waiting, :day "2020-01-03",
:who {:id 2, :name "mr blobby"}, :recurrence nil :who {:id 2, :name "mr blobby"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))))) :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})))))
@ -105,7 +105,7 @@
{"2020-01-01" [{:id 1, :notes "note", :state :waiting, :day "2020-01-01", {"2020-01-01" [{:id 1, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence nil :who {:id 2, :name "mr blobby"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}} :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}
{:id 4, :notes "note", :state :pending, :day "2020-01-01", {:id 4, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence nil :who {:id 2, :name "mr blobby"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))))) :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})))))
@ -127,10 +127,6 @@
(raw-order-row :id 1 :status "waiting" :date #inst "2020-01-02") (raw-order-row :id 1 :status "waiting" :date #inst "2020-01-02")
(raw-order-row :id 4)))] (raw-order-row :id 4)))]
(is (= {"2020-01-02" [{:id 1, :notes "note", :recurrence nil, (is (= {"2020-01-02" [{:id 1, :notes "note", :recurrence nil,
:who {:id 2, :name "mr blobby"},
:day "2020-01-02", :state :waiting
:products {:eggs {:amount 12, :price nil}, :milk {:amount 3, :price 423}},}
{:id 4, :notes "note", :recurrence nil,
:who {:id 2, :name "mr blobby"}, :who {:id 2, :name "mr blobby"},
:day "2020-01-02", :state :waiting :day "2020-01-02", :state :waiting
:products {:eggs {:amount 12, :price nil}, :milk {:amount 3, :price 423}}}]} :products {:eggs {:amount 12, :price nil}, :milk {:amount 3, :price 423}}}]}
@ -157,7 +153,7 @@
{"2020-01-01" [{:id 1, :notes "note", :state :waiting, :day "2020-01-01", {"2020-01-01" [{:id 1, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence nil :who {:id 2, :name "mr blobby"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}} :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}
{:id 4, :notes "note", :state :pending, :day "2020-01-01", {:id 4, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence nil :who {:id 2, :name "mr blobby"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})))))) :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))))))
@ -170,7 +166,7 @@
(is (= by {:id 1 :user_id :user-id}))) (is (= by {:id 1 :user_id :user-id})))
sql/query (constantly (raw-order-row :id 4))] sql/query (constantly (raw-order-row :id 4))]
(is (= (sut/delete! :user-id nil nil 1) (is (= (sut/delete! :user-id nil nil 1)
{"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01", {"2020-01-01" [{:id 4, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence nil :who {:id 2, :name "mr blobby"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})))) :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))))
@ -196,7 +192,7 @@
(testing "deleting without provided a date will remove the whole order" (testing "deleting without provided a date will remove the whole order"
(reset! invocations []) (reset! invocations [])
(is (= (sut/delete! :user-id nil nil 1) (is (= (sut/delete! :user-id nil nil 1)
{"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01", {"2020-01-01" [{:id 4, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence nil :who {:id 2, :name "mr blobby"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})) :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))
(is (= [["deleting" :orders {:id 1 :user_id :user-id}]] (is (= [["deleting" :orders {:id 1 :user_id :user-id}]]
@ -205,7 +201,7 @@
(testing "a provided date is ignored and will full delete" (testing "a provided date is ignored and will full delete"
(reset! invocations []) (reset! invocations [])
(is (= (sut/delete! :user-id "2020-01-01" nil 1) (is (= (sut/delete! :user-id "2020-01-01" nil 1)
{"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01", {"2020-01-01" [{:id 4, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence nil :who {:id 2, :name "mr blobby"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})) :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))
(is (= [["deleting" :orders {:id 1 :user_id :user-id}]] (is (= [["deleting" :orders {:id 1 :user_id :user-id}]]
@ -218,7 +214,7 @@
(reset! invocations []) (reset! invocations [])
(is (= (sut/delete! :user-id "2020-01-01" :single 1) (is (= (sut/delete! :user-id "2020-01-01" :single 1)
{"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01", {"2020-01-01" [{:id 4, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence nil :who {:id 2, :name "mr blobby"}, :recurrence nil
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})) :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))
(is (= [["deleting" :orders {:id 1 :user_id :user-id}]] (is (= [["deleting" :orders {:id 1 :user_id :user-id}]]
@ -237,8 +233,8 @@
(testing "deleting with :all remove the whole order" (testing "deleting with :all remove the whole order"
(reset! invocations []) (reset! invocations [])
(is (= (sut/delete! :user-id nil :all 1) (is (= (sut/delete! :user-id nil :all 1)
{"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01", {"2020-01-01" [{:id 4, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence "FREQ=DAILY;COUNT=1" :who {:id 2, :name "mr blobby"}, :recurrence {:times 1, :until nil, :unit "day", :every 1}
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})) :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))
(is (= [["deleting" :orders {:id 1 :user_id :user-id}]] (is (= [["deleting" :orders {:id 1 :user_id :user-id}]]
@invocations))) @invocations)))
@ -246,8 +242,8 @@
(testing "deleting with a provided date will soft remove a single order by updating it if it exists" (testing "deleting with a provided date will soft remove a single order by updating it if it exists"
(reset! invocations []) (reset! invocations [])
(is (= (sut/delete! :user-id "2020-01-01" nil 1) (is (= (sut/delete! :user-id "2020-01-01" nil 1)
{"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01", {"2020-01-01" [{:id 4, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence "FREQ=DAILY;COUNT=1" :who {:id 2, :name "mr blobby"}, :recurrence {:times 1, :until nil, :unit "day", :every 1}
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})) :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]}))
(is (= [["updating" :recurrence_exceptions {:status "canceled"} (is (= [["updating" :recurrence_exceptions {:status "canceled"}
{:order_id 1, :order_date (t/to-db-date "2020-01-01")}]] {:order_id 1, :order_date (t/to-db-date "2020-01-01")}]]
@ -260,8 +256,8 @@
(reset! invocations []) (reset! invocations [])
(is (= (sut/delete! :user-id "2020-01-01" nil 1) (is (= (sut/delete! :user-id "2020-01-01" nil 1)
{"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01", {"2020-01-01" [{:id 4, :notes "note", :state :waiting, :day "2020-01-01",
:who {:id 2, :name "mr blobby"}, :recurrence "FREQ=DAILY;COUNT=1" :who {:id 2, :name "mr blobby"}, :recurrence {:times 1, :until nil, :unit "day", :every 1}
:products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})) :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"}]] (is (= [["inserting" :recurrence_exceptions {:order_id 1, :order_date (t/to-db-date "2020-01-01") :status "canceled"}]]
@invocations)))))))) @invocations))))))))

View File

@ -75,6 +75,27 @@ html body .popup .popup-content {
width: 15%; width: 15%;
} }
html body .popup .popup-content .recurrence {
padding: 0.5em 0em 1em 1em;
}
html body .popup .popup-content .recurrence #recurrence-times {
width: 2em;
}
html body .popup .popup-content .recurrence .recurrence-freq .input-item {
display: inline-block;
}
html body .popup .popup-content .recurrence .recurrence-freq #recurrence-every {
width: 2em;
}
html body .popup .popup-content .recurrence .recurrence-freq label[for="recurrence-every"] {
min-width: 1em;
padding: 5px;
}
html body .popup .popup-content .input-item label { html body .popup .popup-content .input-item label {
min-width: 60px; min-width: 60px;
display: inline-block; display: inline-block;

View File

@ -9,6 +9,17 @@
[chicken-master.events :as event] [chicken-master.events :as event]
[chicken-master.time :as time])) [chicken-master.time :as time]))
(defn int-or-nil [val]
(let [i (js/parseInt val)]
(when-not (js/isNaN i) i)))
(defn parse-recurrence [{:strs [recurrence-till recurrence-times recurrence-unit recurrence-every]}]
(when (or (int-or-nil recurrence-times) (seq recurrence-till))
{:times (int-or-nil recurrence-times)
:until (when (seq recurrence-till) recurrence-till)
:unit recurrence-unit
:every (or (int-or-nil recurrence-every) 1)}))
(defn format-raw-order [{:strs [day who who-id notes] :as raw-values}] (defn format-raw-order [{:strs [day who who-id notes] :as raw-values}]
{:who {:name who {:who {:name who
:id (if (prod/num-or-nil who-id) :id (if (prod/num-or-nil who-id)
@ -20,6 +31,7 @@
first :id))} first :id))}
:day day :day day
:notes notes :notes notes
:recurrence (parse-recurrence raw-values)
:products (prod/collect-products (remove (comp #{"who" "notes"} first) raw-values))}) :products (prod/collect-products (remove (comp #{"who" "notes"} first) raw-values))})
(defn get-group-products [customers who] (defn get-group-products [customers who]
@ -40,7 +52,7 @@
products)) products))
(defn order-form (defn order-form
([order] (order-form order #{:who :day :notes :products :group-products})) ([order] (order-form order #{:who :day :notes :products :group-products :recurrence}))
([order fields] ([order fields]
(let [customers @(re-frame/subscribe [::subs/available-customers]) (let [customers @(re-frame/subscribe [::subs/available-customers])
available-prods @(re-frame/subscribe [::subs/available-products]) available-prods @(re-frame/subscribe [::subs/available-products])
@ -71,6 +83,18 @@
(when (:notes fields) (when (:notes fields)
(html/input :notes "notka" (html/input :notes "notka"
{:default (:notes @state)})) {:default (:notes @state)}))
(when (:recurrence fields)
[:details {:class :recurrence-details}
[:summary "powtarzanie"]
[:div {:class :recurrence}
(html/input :recurrence-times "ile razy" {:type :number :default (-> order :recurrence :times)})
(html/input :recurrence-till "do kiedy" {:type :date :default (-> order :recurrence :until)})
[:div {:class :recurrence-freq}
(html/input :recurrence-every "co" {:type :number :default (-> order :recurrence :every)})
[:select {:name :recurrence-unit :id :recurrence-unit :defaultValue (-> order :recurrence :unit)}
[:option {:key :day :value "day"} "dni"]
[:option {:key :week :value "week"} "tygodni"]
[:option {:key :month :value "month"} "miesięcy"]]]]])
(when (:products fields) (when (:products fields)
[prod/products-edit (:products @state) [prod/products-edit (:products @state)
:available-prods available-prods :available-prods available-prods
@ -123,7 +147,6 @@
(->> (if (settings :hide-fulfilled-orders) (->> (if (settings :hide-fulfilled-orders)
(remove (comp #{:fulfilled} :state) orders) (remove (comp #{:fulfilled} :state) orders)
orders) orders)
(remove (comp #{:canceled} :state))
(map (partial format-order settings)) (map (partial format-order settings))
doall) doall)
(when (settings :show-day-add-order) (when (settings :show-day-add-order)

View File

@ -49,8 +49,13 @@
:margin "15% auto" :margin "15% auto"
:padding "20px" :padding "20px"
:border "1px solid #888" :border "1px solid #888"
:width "15%" :width "15%"}
} [:.recurrence {:padding "0.5em 0em 1em 1em"}
[:#recurrence-times {:width "2em"}]
[:.recurrence-freq
[:.input-item {:display :inline-block}]
[:#recurrence-every {:width "2em"}]
["label[for=\"recurrence-every\"]" {:min-width "1em" :padding "5px"}]]]
[:.input-item [:.input-item
[:label {:min-width "60px" [:label {:min-width "60px"
:display :inline-block}]] :display :inline-block}]]

View File

@ -119,7 +119,7 @@
:http-xhrio (http-post "orders" :http-xhrio (http-post "orders"
(merge (merge
(select-keys order [:id :day :hour :state :order-date]) (select-keys order [:id :day :hour :state :order-date])
(select-keys form [:id :day :hour :state :who :notes :products])))})) (select-keys form [:id :day :hour :state :who :notes :products :recurrence])))}))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::process-fetched-days ::process-fetched-days
@ -128,7 +128,14 @@
(update :current-days (fn [current-days] (update :current-days (fn [current-days]
(for [[day orders] current-days] (for [[day orders] current-days]
[day (if (contains? days day) (days day) orders)]))) [day (if (contains? days day) (days day) orders)])))
(update :orders #(reduce (fn [m order] (assoc m (:id order) order)) % (mapcat second days)))) (update :orders (fn [orders] (->> days
(mapcat second)
(group-by :id)
vals
(map (fn [items] (->> items
(reduce #(assoc %1 (:day %2) (:state %2)) {})
(assoc (first items) :days))))
(reduce #(assoc %1 (:id %2) %2) orders)))))
:dispatch [::stop-loading]})) :dispatch [::stop-loading]}))
(re-frame/reg-event-fx (re-frame/reg-event-fx
@ -146,7 +153,11 @@
(fn [{{:keys [start-date orders] :as db} :db} [_ day]] (fn [{{:keys [start-date orders] :as db} :db} [_ day]]
(let [day (or day start-date) (let [day (or day start-date)
days (into #{} (time/get-weeks day 2)) days (into #{} (time/get-weeks day 2))
filtered-orders (->> orders vals (filter (comp days :day)) (group-by :day))] filtered-orders (->> orders vals
(mapcat (fn [order]
(map #(assoc order :day (first %) :state (second %))
(:days order))))
(filter (comp days :day)) (group-by :day))]
{:db (assoc db {:db (assoc db
:start-date day :start-date day
:current-days (map #(vector % (get filtered-orders %)) (sort days))) :current-days (map #(vector % (get filtered-orders %)) (sort days)))

View File

@ -83,11 +83,11 @@
(deftest format-raw-order-test (deftest format-raw-order-test
(testing "no products" (testing "no products"
(is (= (sut/format-raw-order {}) {:who {:name nil :id nil} :day nil :notes nil :products {}})) (is (= (sut/format-raw-order {}) {:who {:name nil :id nil} :day nil :notes nil :products {} :recurrence nil}))
(is (= (sut/format-raw-order {"who" "bla" "notes" "ble"}) (is (= (sut/format-raw-order {"who" "bla" "notes" "ble"})
{:who {:name "bla" :id nil} :day nil :notes "ble" :products {}})) {:who {:name "bla" :id nil} :day nil :notes "ble" :products {} :recurrence nil}))
(is (= (sut/format-raw-order {"who" "bla" "who-id" "123" "notes" "ble" "day" "2020-10-10"}) (is (= (sut/format-raw-order {"who" "bla" "who-id" "123" "notes" "ble" "day" "2020-10-10"})
{:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" :products {}}))) {:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" :products {} :recurrence nil})))
(testing "decent products" (testing "decent products"
(is (= (sut/format-raw-order {"who" "bla" "who-id" "123" "notes" "ble" (is (= (sut/format-raw-order {"who" "bla" "who-id" "123" "notes" "ble"
@ -95,7 +95,7 @@
"product-eggs" "eggs" "amount-eggs" "12" "product-eggs" "eggs" "amount-eggs" "12"
"product-cows" "cows" "amount-cows" "22" "price-cows" "2.32" "product-cows" "cows" "amount-cows" "22" "price-cows" "2.32"
"product-milk" "milk" "amount-milk" "3.2"}) "product-milk" "milk" "amount-milk" "3.2"})
{:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" {:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" :recurrence nil
:products {:eggs {:amount 12} :cows {:amount 22 :price 232} :milk {:amount 3.2}}}))) :products {:eggs {:amount 12} :cows {:amount 22 :price 232} :milk {:amount 3.2}}})))
(testing "duplicate products" (testing "duplicate products"
@ -105,7 +105,7 @@
"product-cows1" "cows" "amount-cows1" "1" "product-cows1" "cows" "amount-cows1" "1"
"product-cows2" "cows" "amount-cows2" "2" "product-cows2" "cows" "amount-cows2" "2"
"product-milk" "milk" "amount-milk" "3.2"}) "product-milk" "milk" "amount-milk" "3.2"})
{:who {:name "bla" :id 123} :day nil :notes "ble" {:who {:name "bla" :id 123} :day nil :notes "ble" :recurrence nil
:products {:eggs {:amount 24} :cows {:amount 3} :milk {:amount 3.2}}}))) :products {:eggs {:amount 24} :cows {:amount 3} :milk {:amount 3.2}}})))
(testing "unselected are ignored" (testing "unselected are ignored"
@ -115,7 +115,7 @@
"product-bad2" "" "amount-bad2" "1" "product-bad2" "" "amount-bad2" "1"
"product-milk" "milk" "amount-milk" "3.2" "product-milk" "milk" "amount-milk" "3.2"
"product-bad3" "" "amount-bad3" "2"}) "product-bad3" "" "amount-bad3" "2"})
{:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" {:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" :recurrence nil
:products {:eggs {:amount 12} :milk {:amount 3.2}}}))) :products {:eggs {:amount 12} :milk {:amount 3.2}}})))
(testing "prices are handled" (testing "prices are handled"
@ -124,7 +124,7 @@
"product-eggs1" "eggs" "amount-eggs1" "0" "price-eggs1" "1.0" "product-eggs1" "eggs" "amount-eggs1" "0" "price-eggs1" "1.0"
"product-cow" "cow" "amount-cow" "0" "product-cow" "cow" "amount-cow" "0"
"product-milk" "milk" "amount-milk" "3.2"}) "product-milk" "milk" "amount-milk" "3.2"})
{:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" {:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" :recurrence nil
:products {:eggs {:amount 12 :price 431} :milk {:amount 3.2}}}))) :products {:eggs {:amount 12 :price 431} :milk {:amount 3.2}}})))
(testing "items with 0 are removed" (testing "items with 0 are removed"
@ -133,8 +133,30 @@
"product-eggs1" "eggs" "amount-eggs1" "0" "product-eggs1" "eggs" "amount-eggs1" "0"
"product-cow" "cow" "amount-cow" "0" "product-cow" "cow" "amount-cow" "0"
"product-milk" "milk" "amount-milk" "3.2"}) "product-milk" "milk" "amount-milk" "3.2"})
{:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" {:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" :recurrence nil
:products {:eggs {:amount 12} :milk {:amount 3.2}}})))) :products {:eggs {:amount 12} :milk {:amount 3.2}}})))
(testing "recurrence object is not created when empty vals provided"
(is (= (sut/format-raw-order {"recurrence-till" ""
"recurrence-times" ""
"recurrence-unit" ""
"recurrence-every" ""})
{:who {:name nil :id nil} :day nil :notes nil :products {} :recurrence nil})))
(testing "recurrence object is created when times provided"
(is (= (sut/format-raw-order {"recurrence-till" ""
"recurrence-times" "6"
"recurrence-unit" "week"
"recurrence-every" "3"})
{:who {:name nil :id nil} :day nil :notes nil :products {} :recurrence {:times 6, :until nil, :unit "week", :every 3}})))
(testing "recurrence object is created when till provided"
(is (= (sut/format-raw-order {"recurrence-till" "2020-01-01"
"recurrence-times" ""
"recurrence-unit" "week"
"recurrence-every" "3"})
{:who {:name nil :id nil} :day nil :notes nil :products {}
:recurrence {:times nil, :until "2020-01-01", :unit "week", :every 3}}))))
(def customers (def customers
[{:id 1 :name "mr blobby" :product-groups {"group 1" {:products {:eggs 1 :carrots 2}} [{:id 1 :name "mr blobby" :product-groups {"group 1" {:products {:eggs 1 :carrots 2}}

View File

@ -20,12 +20,12 @@
(rf/reg-event-fx event (fn [_ [_ & params]] (validator params) nil))) (rf/reg-event-fx event (fn [_ [_ & params]] (validator params) nil)))
(def sample-orders (def sample-orders
[{:id 1 :day "2020-01-02"} {:id 2 :day "2020-01-02"} {:id 3 :day "2020-01-02"} [{:id 1 :day "2020-01-02" :state :waiting} {:id 2 :day "2020-01-02" :state :waiting} {:id 3 :day "2020-01-02" :state :waiting}
{:id 4 :day "2020-01-04"} {:id 4 :day "2020-01-04" :state :waiting}
{:id 5 :day "2020-01-06"} {:id 6 :day "2020-01-06"}]) {:id 5 :day "2020-01-06" :state :waiting} {:id 6 :day "2020-01-06" :state :waiting}])
(def sample-orders-by-day (group-by :day sample-orders)) (def sample-orders-by-day (group-by :day sample-orders))
(def sample-orders-by-id (reduce #(assoc %1 (:id %2) %2) {} sample-orders)) (def sample-orders-by-id (reduce #(assoc %1 (:id %2) (assoc %2 :days {(:day %2) (:state %2)})) {} sample-orders))
(deftest hide-modal (deftest hide-modal
(testing "models can be hidden" (testing "models can be hidden"
@ -143,7 +143,7 @@
(rf/dispatch [::sut/edit-order "2020-01-01" 1]) (rf/dispatch [::sut/edit-order "2020-01-01" 1])
(is (= @(rf/subscribe [::subs/editted-order]) (is (= @(rf/subscribe [::subs/editted-order])
{:show true :day "2020-01-01" :id 1})))) {:show true :day "2020-01-01" :id 1 :order-date "2020-01-01"}))))
(testing "new orders can be edited" (testing "new orders can be edited"
(rf-test/run-test-sync (rf-test/run-test-sync
@ -152,7 +152,7 @@
(rf/dispatch [::sut/edit-order "2020-01-01" :new-order]) (rf/dispatch [::sut/edit-order "2020-01-01" :new-order])
(is (= @(rf/subscribe [::subs/editted-order]) (is (= @(rf/subscribe [::subs/editted-order])
{:show true :day "2020-01-01" :state :waiting})))) {:show true :day "2020-01-01" :state :waiting :order-date "2020-01-01"}))))
;; FIXME: the request handler is not being overloaded ;; FIXME: the request handler is not being overloaded
(testing "orders are fulfilled" (testing "orders are fulfilled"
@ -273,11 +273,11 @@
(is (= @(rf/subscribe [::subs/current-days]) (is (= @(rf/subscribe [::subs/current-days])
[["2020-01-01" [{:id :left-as-is :day "2020-01-01"}]] [["2020-01-01" [{:id :left-as-is :day "2020-01-01"}]]
["2020-01-02" [{:id 1 :day "2020-01-02"} {:id 2 :day "2020-01-02"} {:id 3 :day "2020-01-02"}]] ["2020-01-02" [{:id 1 :day "2020-01-02" :state :waiting} {:id 2 :day "2020-01-02" :state :waiting} {:id 3 :day "2020-01-02" :state :waiting}]]
["2020-01-03" nil] ["2020-01-03" nil]
["2020-01-04" [{:id 4 :day "2020-01-04"}]] ["2020-01-04" [{:id 4 :day "2020-01-04" :state :waiting}]]
["2020-01-05" nil] ["2020-01-05" nil]
["2020-01-06" [{:id 5 :day "2020-01-06"} {:id 6 :day "2020-01-06"}]] ["2020-01-06" [{:id 5 :day "2020-01-06" :state :waiting} {:id 6 :day "2020-01-06" :state :waiting}]]
["2020-01-07" nil]]))))) ["2020-01-07" nil]])))))
(deftest test-show-from-date (deftest test-show-from-date
@ -297,11 +297,14 @@
[["2019-12-30" nil] [["2019-12-30" nil]
["2019-12-31" nil] ["2019-12-31" nil]
["2020-01-01" nil] ["2020-01-01" nil]
["2020-01-02" [{:id 1, :day "2020-01-02"} {:id 2, :day "2020-01-02"} {:id 3, :day "2020-01-02"}]] ["2020-01-02" [{:id 1, :day "2020-01-02" :state :waiting, :days {"2020-01-02" :waiting}}
{:id 2, :day "2020-01-02" :state :waiting, :days {"2020-01-02" :waiting}}
{:id 3, :day "2020-01-02" :state :waiting, :days {"2020-01-02" :waiting}}]]
["2020-01-03" nil] ["2020-01-03" nil]
["2020-01-04" [{:id 4, :day "2020-01-04"}]] ["2020-01-04" [{:id 4, :day "2020-01-04" :state :waiting, :days {"2020-01-04" :waiting}}]]
["2020-01-05" nil] ["2020-01-05" nil]
["2020-01-06" [{:id 5, :day "2020-01-06"} {:id 6, :day "2020-01-06"}]] ["2020-01-06" [{:id 5, :day "2020-01-06" :state :waiting, :days {"2020-01-06" :waiting}}
{:id 6, :day "2020-01-06" :state :waiting, :days {"2020-01-06" :waiting}}]]
["2020-01-07" nil] ["2020-01-08" nil] ["2020-01-09" nil] ["2020-01-07" nil] ["2020-01-08" nil] ["2020-01-09" nil]
["2020-01-10" nil] ["2020-01-11" nil] ["2020-01-12" nil]])))) ["2020-01-10" nil] ["2020-01-11" nil] ["2020-01-12" nil]]))))
@ -314,11 +317,14 @@
[["2019-12-30" nil] [["2019-12-30" nil]
["2019-12-31" nil] ["2019-12-31" nil]
["2020-01-01" nil] ["2020-01-01" nil]
["2020-01-02" [{:id 1, :day "2020-01-02"} {:id 2, :day "2020-01-02"} {:id 3, :day "2020-01-02"}]] ["2020-01-02" [{:id 1, :day "2020-01-02" :state :waiting, :days {"2020-01-02" :waiting}}
{:id 2, :day "2020-01-02" :state :waiting, :days {"2020-01-02" :waiting}}
{:id 3, :day "2020-01-02" :state :waiting, :days {"2020-01-02" :waiting}}]]
["2020-01-03" nil] ["2020-01-03" nil]
["2020-01-04" [{:id 4, :day "2020-01-04"}]] ["2020-01-04" [{:id 4, :day "2020-01-04" :state :waiting, :days {"2020-01-04" :waiting}}]]
["2020-01-05" nil] ["2020-01-05" nil]
["2020-01-06" [{:id 5, :day "2020-01-06"} {:id 6, :day "2020-01-06"}]] ["2020-01-06" [{:id 5, :day "2020-01-06" :state :waiting, :days {"2020-01-06" :waiting}}
{:id 6, :day "2020-01-06" :state :waiting, :days {"2020-01-06" :waiting}}]]
["2020-01-07" nil] ["2020-01-08" nil] ["2020-01-09" nil] ["2020-01-07" nil] ["2020-01-08" nil] ["2020-01-09" nil]
["2020-01-10" nil] ["2020-01-11" nil] ["2020-01-12" nil]])))) ["2020-01-10" nil] ["2020-01-11" nil] ["2020-01-12" nil]]))))
@ -331,11 +337,14 @@
[["2019-12-30" nil] [["2019-12-30" nil]
["2019-12-31" nil] ["2019-12-31" nil]
["2020-01-01" nil] ["2020-01-01" nil]
["2020-01-02" [{:id 1, :day "2020-01-02"} {:id 2, :day "2020-01-02"} {:id 3, :day "2020-01-02"}]] ["2020-01-02" [{:id 1, :day "2020-01-02" :state :waiting, :days {"2020-01-02" :waiting}}
{:id 2, :day "2020-01-02" :state :waiting, :days {"2020-01-02" :waiting}}
{:id 3, :day "2020-01-02" :state :waiting, :days {"2020-01-02" :waiting}}]]
["2020-01-03" nil] ["2020-01-03" nil]
["2020-01-04" [{:id 4, :day "2020-01-04"}]] ["2020-01-04" [{:id 4, :day "2020-01-04" :state :waiting, :days {"2020-01-04" :waiting}}]]
["2020-01-05" nil] ["2020-01-05" nil]
["2020-01-06" [{:id 5, :day "2020-01-06"} {:id 6, :day "2020-01-06"}]] ["2020-01-06" [{:id 5, :day "2020-01-06" :state :waiting, :days {"2020-01-06" :waiting}}
{:id 6, :day "2020-01-06" :state :waiting, :days {"2020-01-06" :waiting}}]]
["2020-01-07" nil] ["2020-01-08" nil] ["2020-01-09" nil] ["2020-01-07" nil] ["2020-01-08" nil] ["2020-01-09" nil]
["2020-01-10" nil] ["2020-01-11" nil] ["2020-01-12" nil]]))))) ["2020-01-10" nil] ["2020-01-11" nil] ["2020-01-12" nil]])))))