From f6167bf3c0d64c45b33235c5bf67cab63ef24d35 Mon Sep 17 00:00:00 2001 From: Daniel O'Connell Date: Wed, 20 Apr 2022 19:00:47 +0200 Subject: [PATCH] editable recurs --- backend/src/chicken_master/orders.clj | 75 ++++++++++++---- backend/src/chicken_master/time.clj | 87 ++++++++++++++++++- backend/test/chicken_master/orders_test.clj | 40 ++++----- frontend/resources/public/css/screen.css | 21 +++++ frontend/src/chicken_master/calendar.cljs | 27 +++++- frontend/src/chicken_master/css.clj | 9 +- frontend/src/chicken_master/events.cljs | 17 +++- .../test/chicken_master/calendar_test.cljs | 40 +++++++-- frontend/test/chicken_master/events_test.cljs | 45 ++++++---- 9 files changed, 283 insertions(+), 78 deletions(-) diff --git a/backend/src/chicken_master/orders.clj b/backend/src/chicken_master/orders.clj index 8ca0efc..01fdb23 100644 --- a/backend/src/chicken_master/orders.clj +++ b/backend/src/chicken_master/orders.clj @@ -37,11 +37,11 @@ :end_date (t/to-db-date end-date)))) (defn duplicate-order! [tx user-id order updates] - (->> updates - (merge {:user_id user-id}) - (set-dates order) - (sql/insert! tx :orders) - :orders/id)) + (->> updates + (merge {:user_id user-id}) + (set-dates order) + (sql/insert! tx :orders) + :orders/id)) (defn update-non-recurring [tx order updates] (let [order-id (:orders/id order) @@ -55,12 +55,29 @@ (upsert-exception! tx order-id order_date (:recurrence_exceptions/status exception))) 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"}) -(recurrence-exception? db/db-uri 285 "2022-04-20") +(defn update-single-item [tx user-id from-date {:orders/keys [id order_date] :as order} updates] + (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]}] (let [updates (assoc? {:customer_id customer-id} - :recurrence recurrence + :recurrence (some-> recurrence t/make-rule) :notes notes :order_date (some-> day t/to-db-date)) 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) (= :from-here update-type) - (do - ;; 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)) + (update-from-date tx user-id order-date order updates) :else ; single item modified - (do - (upsert-exception! tx id order-date "canceled") - (duplicate-order! tx user-id order updates))))) + (update-single-item tx user-id order-date 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] {:id (-> items first :orders/id) :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) :name (-> items first :customers/name)} :products (->> items @@ -100,7 +123,8 @@ "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")) + (->> (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)))) (map #(vector (t/format-date %) :waiting)) (into {})))) @@ -140,7 +164,8 @@ vals (map (partial items->orders from to)) (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]] (first @@ -197,7 +222,6 @@ (defn delete! [user-id day action-type id] (jdbc/with-transaction [tx db/db-uri] - (prn (->> id (db/get-by-id tx user-id :orders))) (cond ;; Delete the order along with all recurrences (or (->> id (db/get-by-id tx user-id :orders) :orders/recurrence nil?) @@ -207,3 +231,16 @@ ;; Only delete the one day :else (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) diff --git a/backend/src/chicken_master/time.clj b/backend/src/chicken_master/time.clj index 548d16f..201d808 100644 --- a/backend/src/chicken_master/time.clj +++ b/backend/src/chicken_master/time.clj @@ -2,7 +2,7 @@ (:import [java.time Instant LocalDate ZoneOffset] [java.time.format DateTimeFormatter] [java.sql Timestamp] - [org.dmfs.rfc5545.recur RecurrenceRule] + [org.dmfs.rfc5545.recur RecurrenceRule Freq] [org.dmfs.rfc5545 DateTime])) (defn parse-date [date] @@ -47,21 +47,102 @@ (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 same-day [d1 d2] + (when (and d1 d2) + (= (format-date d1) (format-date d2)))) (defn now [] (Instant/now)) (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... +;; Recurrence helpers + +(defn to-recur-datetime [d] (-> d to-inst (.toEpochMilli) (DateTime.))) (defn recurrence->dates [start rule] - (let [iterator (.iterator (RecurrenceRule. rule) - (-> start (.toEpochMilli) (DateTime.)))] + (let [iterator (.iterator (RecurrenceRule. rule) (to-recur-datetime start))] (take-while identity (repeatedly #(when (.hasNext iterator) (-> 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 "Get the end date for the given rule" [start rule] (->> (recurrence->dates (to-inst start) rule) (take-while #(before % max-date)) 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)}) diff --git a/backend/test/chicken_master/orders_test.clj b/backend/test/chicken_master/orders_test.clj index 10596ea..d9abb49 100644 --- a/backend/test/chicken_master/orders_test.clj +++ b/backend/test/chicken_master/orders_test.clj @@ -10,7 +10,7 @@ [clojure.test :refer [deftest is testing]])) (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 products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}}] (if products @@ -44,7 +44,7 @@ (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", :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"}, :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}})))) @@ -55,7 +55,7 @@ (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", + {: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}}}))))) @@ -73,13 +73,13 @@ {"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}}} - {: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 :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 :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 :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", :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", + {:id 4, :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}}}]}))))) @@ -127,10 +127,6 @@ (raw-order-row :id 1 :status "waiting" :date #inst "2020-01-02") (raw-order-row :id 4)))] (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"}, :day "2020-01-02", :state :waiting :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", :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", + {:id 4, :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}}}]})))))) @@ -170,7 +166,7 @@ (is (= by {:id 1 :user_id :user-id}))) sql/query (constantly (raw-order-row :id 4))] (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 :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" (reset! invocations []) (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 :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})) (is (= [["deleting" :orders {:id 1 :user_id :user-id}]] @@ -205,7 +201,7 @@ (testing "a provided date is ignored and will full delete" (reset! invocations []) (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 :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})) (is (= [["deleting" :orders {:id 1 :user_id :user-id}]] @@ -218,7 +214,7 @@ (reset! invocations []) (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 :products {:eggs {:amount 12 :price nil} :milk {:amount 3 :price 423}}}]})) (is (= [["deleting" :orders {:id 1 :user_id :user-id}]] @@ -237,8 +233,8 @@ (testing "deleting with :all remove the whole order" (reset! invocations []) (is (= (sut/delete! :user-id nil :all 1) - {"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01", - :who {:id 2, :name "mr blobby"}, :recurrence "FREQ=DAILY;COUNT=1" + {"2020-01-01" [{:id 4, :notes "note", :state :waiting, :day "2020-01-01", + :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}}}]})) (is (= [["deleting" :orders {:id 1 :user_id :user-id}]] @invocations))) @@ -246,8 +242,8 @@ (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" nil 1) - {"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01", - :who {:id 2, :name "mr blobby"}, :recurrence "FREQ=DAILY;COUNT=1" + {"2020-01-01" [{:id 4, :notes "note", :state :waiting, :day "2020-01-01", + :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}}}]})) (is (= [["updating" :recurrence_exceptions {:status "canceled"} {:order_id 1, :order_date (t/to-db-date "2020-01-01")}]] @@ -260,8 +256,8 @@ (reset! invocations []) (is (= (sut/delete! :user-id "2020-01-01" nil 1) - {"2020-01-01" [{:id 4, :notes "note", :state :pending, :day "2020-01-01", - :who {:id 2, :name "mr blobby"}, :recurrence "FREQ=DAILY;COUNT=1" + {"2020-01-01" [{:id 4, :notes "note", :state :waiting, :day "2020-01-01", + :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}}}]})) (is (= [["inserting" :recurrence_exceptions {:order_id 1, :order_date (t/to-db-date "2020-01-01") :status "canceled"}]] @invocations)))))))) diff --git a/frontend/resources/public/css/screen.css b/frontend/resources/public/css/screen.css index 601c2b0..b45d999 100644 --- a/frontend/resources/public/css/screen.css +++ b/frontend/resources/public/css/screen.css @@ -75,6 +75,27 @@ html body .popup .popup-content { 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 { min-width: 60px; display: inline-block; diff --git a/frontend/src/chicken_master/calendar.cljs b/frontend/src/chicken_master/calendar.cljs index eb9db53..09d9f4d 100644 --- a/frontend/src/chicken_master/calendar.cljs +++ b/frontend/src/chicken_master/calendar.cljs @@ -9,6 +9,17 @@ [chicken-master.events :as event] [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}] {:who {:name who :id (if (prod/num-or-nil who-id) @@ -20,6 +31,7 @@ first :id))} :day day :notes notes + :recurrence (parse-recurrence raw-values) :products (prod/collect-products (remove (comp #{"who" "notes"} first) raw-values))}) (defn get-group-products [customers who] @@ -40,7 +52,7 @@ products)) (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] (let [customers @(re-frame/subscribe [::subs/available-customers]) available-prods @(re-frame/subscribe [::subs/available-products]) @@ -71,6 +83,18 @@ (when (:notes fields) (html/input :notes "notka" {: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) [prod/products-edit (:products @state) :available-prods available-prods @@ -123,7 +147,6 @@ (->> (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) diff --git a/frontend/src/chicken_master/css.clj b/frontend/src/chicken_master/css.clj index dc255c5..d9cbad5 100644 --- a/frontend/src/chicken_master/css.clj +++ b/frontend/src/chicken_master/css.clj @@ -49,8 +49,13 @@ :margin "15% auto" :padding "20px" :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 [:label {:min-width "60px" :display :inline-block}]] diff --git a/frontend/src/chicken_master/events.cljs b/frontend/src/chicken_master/events.cljs index ddfc012..0131ef7 100644 --- a/frontend/src/chicken_master/events.cljs +++ b/frontend/src/chicken_master/events.cljs @@ -119,7 +119,7 @@ :http-xhrio (http-post "orders" (merge (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 ::process-fetched-days @@ -128,7 +128,14 @@ (update :current-days (fn [current-days] (for [[day orders] current-days] [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]})) (re-frame/reg-event-fx @@ -146,7 +153,11 @@ (fn [{{:keys [start-date orders] :as db} :db} [_ day]] (let [day (or day start-date) 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 :start-date day :current-days (map #(vector % (get filtered-orders %)) (sort days))) diff --git a/frontend/test/chicken_master/calendar_test.cljs b/frontend/test/chicken_master/calendar_test.cljs index 9b515db..4fbcca7 100644 --- a/frontend/test/chicken_master/calendar_test.cljs +++ b/frontend/test/chicken_master/calendar_test.cljs @@ -83,11 +83,11 @@ (deftest format-raw-order-test (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"}) - {: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"}) - {: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" (is (= (sut/format-raw-order {"who" "bla" "who-id" "123" "notes" "ble" @@ -95,7 +95,7 @@ "product-eggs" "eggs" "amount-eggs" "12" "product-cows" "cows" "amount-cows" "22" "price-cows" "2.32" "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}}}))) (testing "duplicate products" @@ -105,7 +105,7 @@ "product-cows1" "cows" "amount-cows1" "1" "product-cows2" "cows" "amount-cows2" "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}}}))) (testing "unselected are ignored" @@ -115,7 +115,7 @@ "product-bad2" "" "amount-bad2" "1" "product-milk" "milk" "amount-milk" "3.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}}}))) (testing "prices are handled" @@ -124,7 +124,7 @@ "product-eggs1" "eggs" "amount-eggs1" "0" "price-eggs1" "1.0" "product-cow" "cow" "amount-cow" "0" "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}}}))) (testing "items with 0 are removed" @@ -133,8 +133,30 @@ "product-eggs1" "eggs" "amount-eggs1" "0" "product-cow" "cow" "amount-cow" "0" "product-milk" "milk" "amount-milk" "3.2"}) - {:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" - :products {:eggs {:amount 12} :milk {:amount 3.2}}})))) + {:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" :recurrence nil + :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 [{:id 1 :name "mr blobby" :product-groups {"group 1" {:products {:eggs 1 :carrots 2}} diff --git a/frontend/test/chicken_master/events_test.cljs b/frontend/test/chicken_master/events_test.cljs index 195aa1b..65ec2a2 100644 --- a/frontend/test/chicken_master/events_test.cljs +++ b/frontend/test/chicken_master/events_test.cljs @@ -20,12 +20,12 @@ (rf/reg-event-fx event (fn [_ [_ & params]] (validator params) nil))) (def sample-orders - [{:id 1 :day "2020-01-02"} {:id 2 :day "2020-01-02"} {:id 3 :day "2020-01-02"} - {:id 4 :day "2020-01-04"} - {:id 5 :day "2020-01-06"} {:id 6 :day "2020-01-06"}]) + [{: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" :state :waiting} + {: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-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 (testing "models can be hidden" @@ -143,7 +143,7 @@ (rf/dispatch [::sut/edit-order "2020-01-01" 1]) (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" (rf-test/run-test-sync @@ -152,7 +152,7 @@ (rf/dispatch [::sut/edit-order "2020-01-01" :new-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 (testing "orders are fulfilled" @@ -273,11 +273,11 @@ (is (= @(rf/subscribe [::subs/current-days]) [["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-04" [{:id 4 :day "2020-01-04"}]] + ["2020-01-04" [{:id 4 :day "2020-01-04" :state :waiting}]] ["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]]))))) (deftest test-show-from-date @@ -297,11 +297,14 @@ [["2019-12-30" nil] ["2019-12-31" 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-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-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-10" nil] ["2020-01-11" nil] ["2020-01-12" nil]])))) @@ -314,11 +317,14 @@ [["2019-12-30" nil] ["2019-12-31" 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-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-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-10" nil] ["2020-01-11" nil] ["2020-01-12" nil]])))) @@ -331,11 +337,14 @@ [["2019-12-30" nil] ["2019-12-31" 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-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-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-10" nil] ["2020-01-11" nil] ["2020-01-12" nil]])))))