customer products

This commit is contained in:
Daniel O'Connell 2021-03-13 13:58:12 +01:00
parent 072e0b3f4d
commit 10d9ebe046
13 changed files with 219 additions and 32 deletions

View File

@ -20,7 +20,11 @@
(defn get-customers [user-id] (get-values user-id [:customers])) (defn get-customers [user-id] (get-values user-id [:customers]))
(defn add-customer [{:keys [body basic-authentication]}] (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] (defn delete-customer [user-id id]
(->> id edn/read-string (customers/delete! user-id)) (->> id edn/read-string (customers/delete! user-id))
(get-values user-id [:orders :customers])) (get-values user-id [:orders :customers]))
@ -46,6 +50,8 @@
(GET "/customers" [:as {user-id :basic-authentication}] (get-customers user-id)) (GET "/customers" [:as {user-id :basic-authentication}] (get-customers user-id))
(POST "/customers" request (add-customer request)) (POST "/customers" request (add-customer request))
(DELETE "/customers/:id" [id :as {user-id :basic-authentication}] (delete-customer user-id id)) (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)) (GET "/products" request (get-products request))
(POST "/products" request (save-products request)) (POST "/products" request (save-products request))

View File

@ -1,13 +1,37 @@
(ns chicken-master.customers (ns chicken-master.customers
(:require [next.jdbc :as jdbc] (:require [next.jdbc :as jdbc]
[next.jdbc.sql :as sql] [next.jdbc.sql :as sql]
[chicken-master.products :as products]
[chicken-master.db :as db])) [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] (defn get-all [user-id]
(->> (sql/query db/db-uri ["select * from customers where deleted is null AND user_id = ?" user-id]) (->> (sql/query db/db-uri [users-select-query user-id])
(map (fn [{:customers/keys [id name]}] {:id id :name name (group-by (fn [{:customers/keys [id name]}] {:id id :name name}))
:product-groups {"bla" {:eggs 2 :carrots 13} (map (partial apply extract-product-groups))))
"ble" {:eggs 12 :milk 3}}}))))
(defn create! [user-id name] (defn create! [user-id name]
(jdbc/execute! db/db-uri (jdbc/execute! db/db-uri
@ -27,3 +51,19 @@
id id
(do (create! user-id name) (do (create! user-id name)
(get-by-name tx 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))))

View File

@ -26,5 +26,6 @@
id user-id]))) id user-id])))
(comment (comment
(create-user "bla" "bla")
(create-user "siloa" "krach") (create-user "siloa" "krach")
(valid-user? "siloa" "krach")) (valid-user? "siloa" "krach"))

View File

@ -60,16 +60,10 @@
(jdbc/with-transaction [tx db/db-uri] (jdbc/with-transaction [tx db/db-uri]
(let [customer-id (or (:id who) (let [customer-id (or (:id who)
(customers/get-or-create-by-name tx user-id (:name 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))]
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
order-id (upsert-order! tx user-id customer-id order)] (upsert-order! tx user-id customer-id order)
(sql/delete! tx :order_products {:order_id order-id}) products)
(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]))
(orders-for-days tx user-id previous-day (some-> order :day t/parse-date))))) (orders-for-days tx user-id previous-day (some-> order :day t/parse-date)))))
(defn delete! [user-id id] (defn delete! [user-id id]

View File

@ -28,3 +28,15 @@
(into [(str "name NOT IN " (db/psql-list (keys new-products)))] (into [(str "name NOT IN " (db/psql-list (keys new-products)))]
(->> new-products keys (map name))))) (->> new-products keys (map name)))))
(get-all user-id)) (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]))))

View File

@ -3,23 +3,130 @@
[next.jdbc :as jdbc] [next.jdbc :as jdbc]
[next.jdbc.sql :as sql] [next.jdbc.sql :as sql]
[chicken-master.customers :as sut] [chicken-master.customers :as sut]
[chicken-master.products :as products]
[clojure.test :refer [deftest is testing]])) [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 (deftest test-get-all
(testing "query is correct" (testing "query is correct"
(with-redefs [sql/query (fn [_ query] (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"))) (sut/get-all "1")))
(testing "results are mapped correctly" (testing "results are mapped correctly"
(with-redefs [sql/query (constantly [{:customers/id 1 :customers/name "mr blobby" :bla 123}])] (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") (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! (deftest test-create!
(testing "correct format is returned" (testing "correct format is returned"
(with-redefs [jdbc/execute! (constantly []) (with-redefs [jdbc/execute! (constantly [])
sql/query (constantly [{:customers/id 1 :customers/name "mr blobby" :bla 123}])] sql/query (constantly [{:customers/id 1 :customers/name "mr blobby" :bla 123}])]
(is (= (sut/create! "1" "mr blobby") (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}}))))))

View File

@ -72,3 +72,27 @@
sql/query (constantly [{:products/name "eggs" :products/amount 12} sql/query (constantly [{:products/name "eggs" :products/amount 12}
{:products/name "milk" :products/amount 3}])] {:products/name "milk" :products/amount 3}])]
(is (= (sut/update! :user-id {:eggs 2 :milk 3 :cows 2}) {:eggs 12 :milk 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})))))

View File

@ -3,7 +3,7 @@ clojure -X:depstar uberjar
cd ../frontend cd ../frontend
npx shadow-cljs release frontend npx shadow-cljs release frontend
clojure -A:garden -m chicken-master.css clojure -A:garden -m chicken-master.css compile
cd .. cd ..
scp backend/chicken-master.jar chickens:/srv/chickens/chicken-master.jar scp backend/chicken-master.jar chickens:/srv/chickens/chicken-master.jar

View File

@ -22,7 +22,9 @@
: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]
(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 (defn order-form
([order] (order-form order #{:who :day :notes :products :group-products})) ([order] (order-form order #{:who :day :notes :products :group-products}))

View File

@ -27,8 +27,8 @@
[prod/products-edit (:products @state) [prod/products-edit (:products @state)
:getter-fn #(re-frame/dispatch [::event/save-order (assoc @state :products %)])]])]))) :getter-fn #(re-frame/dispatch [::event/save-order (assoc @state :products %)])]])])))
(defn product-group-adder [who product-group] (defn product-group-adder [who [name {:keys [id products]}]]
(let [state (reagent/atom product-group)] (let [state (reagent/atom {:name name :id id :products (or products {})})]
(fn [] (fn []
[:div {:class :customer-block} [:div {:class :customer-block}
(if-not (:edit @state) (if-not (:edit @state)
@ -68,7 +68,7 @@
(for [group (:product-groups who)] (for [group (:product-groups who)]
[:div {:key (gensym)} [:div {:key (gensym)}
[product-group-adder who group]]) [product-group-adder who group]])
[product-group-adder {}]] [product-group-adder who []]]
[:details {:class :client-orders} [:details {:class :client-orders}
[:summary "Zamówienia"] [:summary "Zamówienia"]

View File

@ -63,7 +63,8 @@
::log-error ::log-error
(fn [_ [_ error]] (fn [_ [_ error]]
(.error js/console error) (.error js/console error)
(js/alert "Wystąpił błąd"))) (js/alert "Wystąpił błąd")
))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::failed-request ::failed-request

View File

@ -81,7 +81,7 @@
(let [state (reagent/atom value)] (let [state (reagent/atom value)]
(fn [] (fn []
[:div {:class class :on-click #(.stopPropagation %)} [: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)] :on-change #(let [val (-> % .-target .-value)]
(reset! state val) (reset! state val)
(if-not button (callback val)))}] (if-not button (callback val)))}]
@ -89,7 +89,7 @@
[:button {:class :add-product [:button {:class :add-product
:type :button :type :button
:disabled (= @state "") :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] (defn group-products [state]
[:div {:class :input-item} [:div {:class :input-item}

View File

@ -47,12 +47,12 @@
{:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" :products {:eggs 12 :milk 3.2}})))) {:who {:name "bla" :id 123} :day "2020-10-10" :notes "ble" :products {:eggs 12 :milk 3.2}}))))
(def customers (def customers
[{:id 1 :name "mr blobby" :product-groups [{:name "group 1" :products {:eggs 1 :carrots 2}} [{:id 1 :name "mr blobby" :product-groups {"group 1" {:products {:eggs 1 :carrots 2}}
{:name "group 2" :products {:eggs 11 :carrots 2}} "group 2" {:products {:eggs 11 :carrots 2}}
{:name "group 3" :products {:milk 2 :eggs 12}}]} "group 3" {:products {:milk 2 :eggs 12}}}}
{:id 2 :name "johnny D" :product-groups [{:name "group 4" :products {:eggs 2}} {:id 2 :name "johnny D" :product-groups {"group 4" {:products {:eggs 2}}
{:name "group 5" :products {:milk 2}}]} "group 5" {:products {:milk 2}}}}
{:id 3 :name "joe" :product-groups []} {:id 3 :name "joe" :product-groups {}}
{:id 4 :name "mark"}]) {:id 4 :name "mark"}])
(deftest get-group-products-test (deftest get-group-products-test