|
1 | | -(ns otus-06.homework) |
| 1 | +(ns otus-06.homework |
| 2 | + (:require [clojure.string :as str] |
| 3 | + [clojure.java.io :as io])) |
2 | 4 |
|
3 | 5 | ;; Загрузить данные из трех файлов на диске. |
4 | 6 | ;; Эти данные сформируют вашу базу данных о продажах. |
|
94 | 96 |
|
95 | 97 |
|
96 | 98 | ;; Файлы находятся в папке otus-06/resources/homework |
| 99 | + |
| 100 | +(def cust-file "resources/homework/cust.txt") |
| 101 | +(def prod-file "resources/homework/prod.txt") |
| 102 | +(def sales-file "resources/homework/sales.txt") |
| 103 | + |
| 104 | +(def table-schema |
| 105 | + {:cust {:file cust-file :id 0 :name 1 :address 2 :phone 3} |
| 106 | + :prod {:file prod-file :id 0 :name 1 :cost 2} |
| 107 | + :sales {:file sales-file :id 0 :cust-id 1 :prod-id 2 :count 3}}) |
| 108 | + |
| 109 | +(defn map-line [line schema] |
| 110 | + (into {} (for [[key value] (dissoc schema :file)] |
| 111 | + [key (nth line value)]))) |
| 112 | + |
| 113 | +(defn clear-terminal [] |
| 114 | + (print "\033c")) |
| 115 | + |
| 116 | +(defn parse-line [line] |
| 117 | + (str/split line #"\|")) |
| 118 | + |
| 119 | +(defn for-each-line [file cb] |
| 120 | + (with-open [rdr (io/reader file)] |
| 121 | + (doseq [[index line] (map-indexed vector (line-seq rdr))] |
| 122 | + (let [line (parse-line line)] |
| 123 | + (cb line index))))) |
| 124 | + |
| 125 | +(defn format-row [row index] |
| 126 | + (->> row |
| 127 | + (mapv #(format "\"%s\"" %)) |
| 128 | + (str/join ", ") |
| 129 | + (format "%d: [%s]" (inc index)))) |
| 130 | + |
| 131 | +(defn print-table [file] |
| 132 | + (for-each-line file (fn [line index] |
| 133 | + (-> (format-row line index) |
| 134 | + (println))))) |
| 135 | + |
| 136 | +(defn load-table [table] |
| 137 | + (let [{:keys [file] :as schema} (table table-schema) |
| 138 | + all-ids (atom []) |
| 139 | + by-id (atom {})] |
| 140 | + (for-each-line file (fn [line _] ;; do we have a way to pass callback with only one argument? |
| 141 | + (let [[id] line |
| 142 | + data (map-line line schema)] |
| 143 | + (swap! all-ids conj id) |
| 144 | + (swap! by-id assoc id data)))) |
| 145 | + {:all-ids @all-ids |
| 146 | + :by-id @by-id})) |
| 147 | + |
| 148 | +(defn load-tables [] |
| 149 | + {:cust (load-table :cust) |
| 150 | + :prod (load-table :prod) |
| 151 | + :sales (load-table :sales)}) |
| 152 | + |
| 153 | +(defn find-by-id [db table id] |
| 154 | + (get-in db [table :by-id id])) |
| 155 | + |
| 156 | +(defn find-by-name [db table name] |
| 157 | + (let [table (get-in db [table :by-id]) |
| 158 | + [[id data]] (filter (fn [[_key value]] (= name (:name value))) table)] |
| 159 | + {:id id :data data})) |
| 160 | + |
| 161 | +(defn calc-total-sales-for-customer [db cust-id] |
| 162 | + (let [sales (get-in db [:sales :by-id]) |
| 163 | + sales-to-customer (filter (fn [[_key value]] (= cust-id (:cust-id value))) sales)] |
| 164 | + (reduce (fn [acc [_key value]] |
| 165 | + (let [count (Integer/parseInt (get value :count "0")) |
| 166 | + product (get-in db [:prod :by-id (:prod-id value)]) |
| 167 | + cost (Float/parseFloat (get product :cost "0.00"))] |
| 168 | + (+ acc (* count cost)))) |
| 169 | + 0 sales-to-customer))) |
| 170 | + |
| 171 | +(defn calc-total-count-for-product [db prod-id] |
| 172 | + (let [sales (get-in db [:sales :by-id]) |
| 173 | + product-sales (filter (fn [[_key value]] (= prod-id (:prod-id value))) sales)] |
| 174 | + (reduce (fn [acc [_key value]] |
| 175 | + (let [count (Integer/parseInt (get value :count "0"))] |
| 176 | + (+ acc count))) |
| 177 | + 0 product-sales))) |
| 178 | + |
| 179 | +(defn on-exit [running?] |
| 180 | + (println "Goodbye") |
| 181 | + (dosync (ref-set running? false))) |
| 182 | + |
| 183 | +(defn on-display-customer-table [] |
| 184 | + (print-table cust-file)) |
| 185 | + |
| 186 | +(defn on-display-product-table [] |
| 187 | + (print-table prod-file)) |
| 188 | + |
| 189 | +(defn on-display-sales-table [] |
| 190 | + (let [db (load-tables)] |
| 191 | + (doseq [[index id] (map-indexed vector (get-in db [:sales :all-ids])) |
| 192 | + :let [{:keys [cust-id prod-id count]} (find-by-id db :sales id) |
| 193 | + {cust-name :name} (find-by-id db :cust cust-id) |
| 194 | + {prod-name :name} (find-by-id db :prod prod-id)]] |
| 195 | + (println (format-row [cust-name prod-name count] index))))) |
| 196 | + |
| 197 | +(defn on-display-total-sales-for-customer [] |
| 198 | + (let [db (load-tables)] |
| 199 | + (println "Enter customer name:") |
| 200 | + (let [customer-name (read-line) |
| 201 | + {customer-id :id customer :data} (find-by-name db :cust customer-name)] |
| 202 | + (if customer |
| 203 | + (let [total (calc-total-sales-for-customer db customer-id)] |
| 204 | + (println (format "Total Sales for Customer %s: $%.2f" customer-name total))) |
| 205 | + (println (format "Customer %s not found" customer-name)))))) |
| 206 | + |
| 207 | +(defn on-display-total-count-for-product [] |
| 208 | + (let [db (load-tables)] |
| 209 | + (println "Enter product name:") |
| 210 | + (let [product-name (read-line) |
| 211 | + {product-id :id product :data} (find-by-name db :prod product-name)] |
| 212 | + (if product |
| 213 | + (let [count (calc-total-count-for-product db product-id)] |
| 214 | + (println (format "Total Count of Product %s: %d" product-name count))) |
| 215 | + (println (format "Product %s not found" product-name)))))) |
| 216 | + |
| 217 | +(defn render-menu [menu] |
| 218 | + (clear-terminal) |
| 219 | + (println "*** Sales Menu ***") |
| 220 | + (println "==================") |
| 221 | + (doseq [[index item] (map-indexed vector menu)] |
| 222 | + (println (str (inc index) ". " (:title item)))) |
| 223 | + (println "Enter an option? ")) |
| 224 | + |
| 225 | +(defn get-user-input [] |
| 226 | + (let [input (read-line)] |
| 227 | + (try |
| 228 | + (Integer/parseInt input) |
| 229 | + (catch Exception e |
| 230 | + (println "Invalid input") |
| 231 | + -1)))) |
| 232 | + |
| 233 | +(defn start-app |
| 234 | + "Displaying main menu and processing user choices." |
| 235 | + [] |
| 236 | + (let [running? (ref true) |
| 237 | + menu [{:title "Display Customer Table" :action on-display-customer-table} |
| 238 | + {:title "Display Product Table" :action on-display-product-table} |
| 239 | + {:title "Display Sales Table" :action on-display-sales-table} |
| 240 | + {:title "Total Sales for Customer" :action on-display-total-sales-for-customer} |
| 241 | + {:title "Total Count for Product" :action on-display-total-count-for-product} |
| 242 | + {:title "Exit" :action (partial on-exit running?)}]] |
| 243 | + (render-menu menu) |
| 244 | + (while @running? |
| 245 | + (let [choice (get-user-input) |
| 246 | + item (get menu (dec choice)) |
| 247 | + action (get item :action (fn []))] |
| 248 | + (action))))) |
| 249 | + |
| 250 | +(defn -main |
| 251 | + "Main function calling app." |
| 252 | + [& _args] |
| 253 | + (start-app)) |
0 commit comments