diff --git a/backend/src/chicken_master/api.clj b/backend/src/chicken_master/api.clj index 4a72b34..f15abbc 100644 --- a/backend/src/chicken_master/api.clj +++ b/backend/src/chicken_master/api.clj @@ -20,7 +20,11 @@ (defn get-customers [user-id] (get-values user-id [:customers])) (defn add-customer [{:keys [body basic-authentication]}] - (as-edn (some->> body :name (customers/create! basic-authentication)))) + (some->> body :name (customers/create! basic-authentication) as-edn)) +(defn save-product-group [user-id customer-id body] + (customers/save-product-group user-id (Integer/parseInt customer-id) body) + (get-customers user-id)) + (defn delete-customer [user-id id] (->> id edn/read-string (customers/delete! user-id)) (get-values user-id [:orders :customers])) @@ -46,6 +50,8 @@ (GET "/customers" [:as {user-id :basic-authentication}] (get-customers user-id)) (POST "/customers" request (add-customer request)) (DELETE "/customers/:id" [id :as {user-id :basic-authentication}] (delete-customer user-id id)) + (POST "/customers/:id/product-group" [id :as {user-id :basic-authentication body :body}] + (save-product-group user-id id body)) (GET "/products" request (get-products request)) (POST "/products" request (save-products request)) diff --git a/backend/src/chicken_master/customers.clj b/backend/src/chicken_master/customers.clj index 1e2825e..28be278 100644 --- a/backend/src/chicken_master/customers.clj +++ b/backend/src/chicken_master/customers.clj @@ -1,13 +1,37 @@ (ns chicken-master.customers (:require [next.jdbc :as jdbc] [next.jdbc.sql :as sql] + [chicken-master.products :as products] [chicken-master.db :as db])) + + +(defn insert-products [coll {:keys [id name]} products] + (->> products + (reduce #(assoc %1 (-> %2 :products/name keyword) (:customer_group_products/amount %2)) {}) + (assoc {:id id} :products) + (assoc coll name))) + +(defn extract-product-groups [client products] + (if-not products + client + (->> products + (filter :customer_groups/name) + (group-by (fn [{:customer_groups/keys [id name]}] {:id id :name name})) + (reduce-kv insert-products {}) + (assoc client :product-groups)))) + +(def users-select-query + "SELECT * FROM customers c + LEFT OUTER JOIN customer_groups cg on c.id = cg.customer_id + LEFT OUTER JOIN customer_group_products cgp on cg.id = cgp.customer_group_id + LEFT OUTER JOIN products p ON p.id = cgp.product_id + WHERE c.deleted IS NULL aND c.user_id = ?") + (defn get-all [user-id] - (->> (sql/query db/db-uri ["select * from customers where deleted is null AND user_id = ?" user-id]) - (map (fn [{:customers/keys [id name]}] {:id id :name name - :product-groups {"bla" {:eggs 2 :carrots 13} - "ble" {:eggs 12 :milk 3}}})))) + (->> (sql/query db/db-uri [users-select-query user-id]) + (group-by (fn [{:customers/keys [id name]}] {:id id :name name})) + (map (partial apply extract-product-groups)))) (defn create! [user-id name] (jdbc/execute! db/db-uri @@ -27,3 +51,19 @@ id (do (create! user-id name) (get-by-name tx user-id name)))) + + +(defn upsert-customer-group! [tx user-id customer-id {:keys [id name]}] + (if (jdbc/execute-one! tx ["SELECT * FROM customer_groups WHERE user_id = ? AND customer_id = ? AND id =?" + user-id customer-id id]) + (do (sql/update! tx :customer_groups {:name name} {:id id}) id) + (->> {:user_id user-id :name name :customer_id customer-id} + (sql/insert! tx :customer_groups) + :customer_groups/id))) + +(defn save-product-group [user-id customer-id group] + (jdbc/with-transaction [tx db/db-uri] + (products/update-products-mapping! + tx user-id :customer_group + (upsert-customer-group! tx user-id customer-id group) + (:products group)))) diff --git a/backend/src/chicken_master/db.clj b/backend/src/chicken_master/db.clj index 2a687f4..6c0cd55 100644 --- a/backend/src/chicken_master/db.clj +++ b/backend/src/chicken_master/db.clj @@ -26,5 +26,6 @@ id user-id]))) (comment + (create-user "bla" "bla") (create-user "siloa" "krach") (valid-user? "siloa" "krach")) diff --git a/backend/src/chicken_master/orders.clj b/backend/src/chicken_master/orders.clj index e7219c3..2821c53 100644 --- a/backend/src/chicken_master/orders.clj +++ b/backend/src/chicken_master/orders.clj @@ -60,16 +60,10 @@ (jdbc/with-transaction [tx db/db-uri] (let [customer-id (or (:id who) (customers/get-or-create-by-name tx user-id (:name who))) - products-map (products/products-map tx user-id products) - previous-day (some->> order :id (db/get-by-id tx user-id :orders) :orders/order_date (.toInstant)) - order-id (upsert-order! tx user-id customer-id order)] - (sql/delete! tx :order_products {:order_id order-id}) - (sql/insert-multi! tx :order_products - [:order_id :product_id :amount] - (for [[n amount] products - :let [product-id (-> n name products-map)] - :when product-id] - [order-id product-id amount])) + previous-day (some->> order :id (db/get-by-id tx user-id :orders) :orders/order_date (.toInstant))] + (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] diff --git a/backend/src/chicken_master/products.clj b/backend/src/chicken_master/products.clj index f82dd5a..dd50b46 100644 --- a/backend/src/chicken_master/products.clj +++ b/backend/src/chicken_master/products.clj @@ -28,3 +28,15 @@ (into [(str "name NOT IN " (db/psql-list (keys new-products)))] (->> new-products keys (map name))))) (get-all user-id)) + +(defn update-products-mapping! [tx user-id table id products] + (let [id-key (-> table name (str "_id") keyword) + table (-> table name (str "_products") keyword) + products-map (products-map tx user-id products)] + (sql/delete! tx table {id-key id}) + (sql/insert-multi! tx table + [id-key :product_id :amount] + (for [[n amount] products + :let [product-id (-> n name products-map)] + :when product-id] + [id product-id amount])))) diff --git a/backend/test/chicken_master/customers_test.clj b/backend/test/chicken_master/customers_test.clj index c558002..4eb5816 100644 --- a/backend/test/chicken_master/customers_test.clj +++ b/backend/test/chicken_master/customers_test.clj @@ -3,23 +3,130 @@ [next.jdbc :as jdbc] [next.jdbc.sql :as sql] [chicken-master.customers :as sut] + [chicken-master.products :as products] [clojure.test :refer [deftest is testing]])) +(def sample-customers + [{:customers/name "klient 1", :customers/id 1 + :customer_groups/name "group1", :customer_groups/id 1, + :customer_group_products/amount 2, :products/name "eggs"} + {:customers/name "klient 1", :customers/id 1 + :customer_groups/name "group1", :customer_groups/id 1, + :customer_group_products/amount 32, :products/name "milk"} + + {:customers/name "klient 1", :customers/id 1 + :customer_groups/name "group 2", :customer_groups/id 2, + :customer_group_products/amount 1, :products/name "milk"} + {:customers/name "klient 1", :customers/id 1 + :customer_groups/name "group 2", :customer_groups/id 2, + :customer_group_products/amount 6, :products/name "eggs"} + {:customers/name "klient 1", :customers/id 1 + :customer_groups/name "group 2", :customer_groups/id 2, + :customer_group_products/amount 89, :products/name "carrots"} + + {:customers/name "klient 2", :customers/id 2 + :customer_groups/name "group 3", :customer_groups/id 3, + :customer_group_products/amount 41, :products/name "milk"} + {:customers/name "klient 2", :customers/id 2 + :customer_groups/name "group 3", :customer_groups/id 3, + :customer_group_products/amount 6, :products/name "eggs"}]) + +(deftest format-products-tests + (testing "products amounts get formatted properly" + (is (= (sut/insert-products {} {:id 1 :name "bla"} {}) + {"bla" {:id 1 :products {}}})) + + (is (= (sut/insert-products {"ble" {:id 32}} {:id 1 :name "bla"} {}) + {"ble" {:id 32} "bla" {:id 1 :products {}}})) + + (is (= (sut/insert-products {} {:id 1 :name "bla"} + [{:products/name "milk" :customer_group_products/amount 1} + {:products/name "carrots" :customer_group_products/amount 12} + {:products/name "eggs" :customer_group_products/amount 3}]) + {"bla" {:id 1 :products {:eggs 3 :carrots 12 :milk 1}}}))) + + (testing "extracting product groups works" + (is (= (sut/extract-product-groups {:ble "ble"} sample-customers) + {:ble "ble" + :product-groups {"group1" {:id 1, :products {:eggs 2, :milk 32}}, + "group 2" {:id 2, :products {:milk 1, :eggs 6, :carrots 89}}, + "group 3" {:id 3, :products {:milk 41, :eggs 6}}}}))) + + (testing "extracting product groups stops if no values" + (is (= (sut/extract-product-groups {:ble "ble"} nil) + {:ble "ble"})) + + (is (= (sut/extract-product-groups {:ble "ble"} []) + {:ble "ble" :product-groups {}})))) + (deftest test-get-all (testing "query is correct" (with-redefs [sql/query (fn [_ query] - (is (= query ["select * from customers where deleted is null AND user_id = ?" "1"])) + (is (= query [sut/users-select-query "1"])) [])] (sut/get-all "1"))) (testing "results are mapped correctly" (with-redefs [sql/query (constantly [{:customers/id 1 :customers/name "mr blobby" :bla 123}])] + (is (= (sut/get-all 2) + [{:id 1 :name "mr blobby" :product-groups {}}])))) + + (testing "customer groups are mapped correctly" + (with-redefs [sql/query (constantly sample-customers)] (is (= (sut/get-all "1") - [{:id 1 :name "mr blobby"}]))))) + [{:id 1 :name "klient 1" :product-groups {"group1" {:id 1 :products {:eggs 2 :milk 32}} + "group 2" {:id 2 :products {:milk 1 :eggs 6 :carrots 89}}}} + {:id 2 :name "klient 2" :product-groups {"group 3" {:id 3 :products {:milk 41 :eggs 6}}}}]))))) (deftest test-create! (testing "correct format is returned" (with-redefs [jdbc/execute! (constantly []) sql/query (constantly [{:customers/id 1 :customers/name "mr blobby" :bla 123}])] (is (= (sut/create! "1" "mr blobby") - {:customers [{:id 1 :name "mr blobby"}]}))))) + {:customers [{:id 1 :name "mr blobby" :product-groups {}}]}))))) + + +(deftest save-product-group-test + (let [user-id 1 + customer-id 2 + group-id 123] + (with-redefs [jdbc/transact (fn [_ f & args] (apply f args)) + sql/insert! (constantly {:customer_groups/id group-id}) + sql/delete! (fn [_tx table id] + (is (= table :customer_group_products)) + (is (= id {:customer_group_id group-id}))) + + products/products-map (constantly {"eggs" 1 "milk" 2 "carrots" 3}) + + sql/insert-multi! (fn [_tx table cols products] + (is (= table :customer_group_products)) + (is (= cols [:customer_group_id :product_id :amount])) + (is (= products [[group-id 1 34] + [group-id 2 25] + [group-id 3 13]])) + :ok)] + (testing "the correct query is used to check if group exists" + (with-redefs [jdbc/execute-one! + (fn [_ query] + (is (= query ["SELECT * FROM customer_groups WHERE user_id = ? AND customer_id = ? AND id =?" + user-id customer-id nil])) + nil)] + (sut/save-product-group user-id customer-id {:name "bla" :products {:eggs 34 :milk 25 :carrots 13}}))) + + (testing "product groups get created" + (with-redefs [jdbc/execute-one! (constantly nil) ; the group doesn't yet exist + sql/insert! (fn [_ _ group] + (is (= group {:name "bla" :customer_id customer-id :user_id user-id})) + {:customer_groups/id group-id}) ; create a new group + sql/update! (fn [&args] (is nil "The group shouldn't be updated"))] + (sut/save-product-group user-id customer-id {:name "bla" :products {:eggs 34 :milk 25 :carrots 13}}))) + + (testing "existing product groups get updated" + (with-redefs [jdbc/execute-one! (constantly true) ; the group should exist + sql/update! (fn [_tx table item query] + (is (= table :customer_groups)) + (is (= item {:name "bla"})) + (is (= query {:id group-id}))) + sql/insert! (fn [&args] (is nil "The group shouldn't be created"))] + (sut/save-product-group user-id customer-id + {:id group-id :name "bla" :products {:eggs 34 :milk 25 :carrots 13}})))))) diff --git a/backend/test/chicken_master/products_test.clj b/backend/test/chicken_master/products_test.clj index ae8665a..79bf722 100644 --- a/backend/test/chicken_master/products_test.clj +++ b/backend/test/chicken_master/products_test.clj @@ -72,3 +72,27 @@ sql/query (constantly [{:products/name "eggs" :products/amount 12} {:products/name "milk" :products/amount 3}])] (is (= (sut/update! :user-id {:eggs 2 :milk 3 :cows 2}) {:eggs 12 :milk 3}))))) + +(deftest update-products-mapping-test + (testing "items get removed" + (let [item-id 123] + (with-redefs [sut/products-map (constantly {"eggs" 1 "milk" 2 "carrots" 3}) + sql/insert-multi! (constantly :ok) + + sql/delete! (fn [_tx table id] + (is (= table :bla_products)) + (is (= id {:bla_id item-id})))] + (sut/update-products-mapping! :tx 123 :bla item-id {:eggs 34 :milk 25 :carrots 13})))) + + (testing "items get removed" + (let [item-id 123] + (with-redefs [sut/products-map (constantly {"eggs" 1 "milk" 2 "carrots" 3}) + sql/delete! (constantly :ok) + + sql/insert-multi! (fn [_tx table cols products] + (is (= table :bla_products)) + (is (= cols [:bla_id :product_id :amount])) + (is (= products [[item-id 1 34] + [item-id 2 25] + [item-id 3 13]])))] + (sut/update-products-mapping! :tx 123 :bla item-id {:eggs 34 :milk 25 :carrots 13}))))) diff --git a/deploy.sh b/deploy.sh index 0b7d9c6..ac8513a 100755 --- a/deploy.sh +++ b/deploy.sh @@ -3,7 +3,7 @@ clojure -X:depstar uberjar cd ../frontend npx shadow-cljs release frontend -clojure -A:garden -m chicken-master.css +clojure -A:garden -m chicken-master.css compile cd .. scp backend/chicken-master.jar chickens:/srv/chickens/chicken-master.jar diff --git a/frontend/src/chicken_master/calendar.cljs b/frontend/src/chicken_master/calendar.cljs index 872a536..cc0cced 100644 --- a/frontend/src/chicken_master/calendar.cljs +++ b/frontend/src/chicken_master/calendar.cljs @@ -22,7 +22,9 @@ :products (prod/collect-products (remove (comp #{"who" "notes"} first) raw-values))}) (defn get-group-products [customers who] - (some->> customers (filter (comp #{who} :name)) first :product-groups)) + (some->> customers (filter (comp #{who} :name)) + first :product-groups + (reduce-kv #(assoc %1 %2 (:products %3)) {}))) (defn order-form ([order] (order-form order #{:who :day :notes :products :group-products})) diff --git a/frontend/src/chicken_master/customers.cljs b/frontend/src/chicken_master/customers.cljs index 1d378ab..197db12 100644 --- a/frontend/src/chicken_master/customers.cljs +++ b/frontend/src/chicken_master/customers.cljs @@ -27,8 +27,8 @@ [prod/products-edit (:products @state) :getter-fn #(re-frame/dispatch [::event/save-order (assoc @state :products %)])]])]))) -(defn product-group-adder [who product-group] - (let [state (reagent/atom product-group)] +(defn product-group-adder [who [name {:keys [id products]}]] + (let [state (reagent/atom {:name name :id id :products (or products {})})] (fn [] [:div {:class :customer-block} (if-not (:edit @state) @@ -68,7 +68,7 @@ (for [group (:product-groups who)] [:div {:key (gensym)} [product-group-adder who group]]) - [product-group-adder {}]] + [product-group-adder who []]] [:details {:class :client-orders} [:summary "Zamówienia"] diff --git a/frontend/src/chicken_master/events.cljs b/frontend/src/chicken_master/events.cljs index f0601f7..e2a985e 100644 --- a/frontend/src/chicken_master/events.cljs +++ b/frontend/src/chicken_master/events.cljs @@ -63,7 +63,8 @@ ::log-error (fn [_ [_ error]] (.error js/console error) - (js/alert "Wystąpił błąd"))) + (js/alert "Wystąpił błąd") + )) (re-frame/reg-event-fx ::failed-request diff --git a/frontend/src/chicken_master/products.cljs b/frontend/src/chicken_master/products.cljs index b27ea6a..e18ee39 100644 --- a/frontend/src/chicken_master/products.cljs +++ b/frontend/src/chicken_master/products.cljs @@ -81,7 +81,7 @@ (let [state (reagent/atom value)] (fn [] [:div {:class class :on-click #(.stopPropagation %)} - [:input {:type type :name :user-name :default-value value + [:input {:type type :name :user-name :default-value value :value @state :on-change #(let [val (-> % .-target .-value)] (reset! state val) (if-not button (callback val)))}] @@ -89,7 +89,7 @@ [:button {:class :add-product :type :button :disabled (= @state "") - :on-click (if callback #(-> state (reset-vals! value) first callback))} button])]))) + :on-click (if callback #(-> state (reset-vals! "") first callback))} button])]))) (defn group-products [state] [:div {:class :input-item} diff --git a/frontend/test/chicken_master/calendar_test.cljs b/frontend/test/chicken_master/calendar_test.cljs index 8e2f83c..506d646 100644 --- a/frontend/test/chicken_master/calendar_test.cljs +++ b/frontend/test/chicken_master/calendar_test.cljs @@ -47,12 +47,12 @@ {:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" :products {:eggs 12 :milk 3.2}})))) (def customers - [{:id 1 :name "mr blobby" :product-groups [{:name "group 1" :products {:eggs 1 :carrots 2}} - {:name "group 2" :products {:eggs 11 :carrots 2}} - {:name "group 3" :products {:milk 2 :eggs 12}}]} - {:id 2 :name "johnny D" :product-groups [{:name "group 4" :products {:eggs 2}} - {:name "group 5" :products {:milk 2}}]} - {:id 3 :name "joe" :product-groups []} + [{:id 1 :name "mr blobby" :product-groups {"group 1" {:products {:eggs 1 :carrots 2}} + "group 2" {:products {:eggs 11 :carrots 2}} + "group 3" {:products {:milk 2 :eggs 12}}}} + {:id 2 :name "johnny D" :product-groups {"group 4" {:products {:eggs 2}} + "group 5" {:products {:milk 2}}}} + {:id 3 :name "joe" :product-groups {}} {:id 4 :name "mark"}]) (deftest get-group-products-test