Skip to content

Instantly share code, notes, and snippets.

@maacl
Last active January 29, 2023 07:07
Show Gist options
  • Save maacl/b0795e5f3d4ab72ca8add1e2d091e0e4 to your computer and use it in GitHub Desktop.
Save maacl/b0795e5f3d4ab72ca8add1e2d091e0e4 to your computer and use it in GitHub Desktop.
Domain Modelling using Clojure
(ns pms.core
(:require [clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as gen]
[clojure.spec.test.alpha :as stest]))
(defrecord Sale [amount])
(defrecord Purchase [amount])
(defn non-blank-string? [x] (and (string? x) (not (clojure.string/blank? x))))
(s/def :project/id pos-int?)
(s/def :project/name (s/and string? seq))
(s/def :project/prj-list (s/and (s/coll-of ::project :gen-max 5) seq))
(s/def ::project
(s/or :prj (s/keys :req-un [:project/id :project/name])
:prj-group (s/keys :req-un [:project/name :project/prj-list])))
(s/def ::money double?)
(s/def :budget/income ::money)
(s/def :budget/expenditure ::money)
(s/def ::budget (s/keys :req-un [:budget/income :budget/expenditure]))
(s/def ::transaction (s/or :sale #(instance? % Sale)
:purchase #(instance? % Purchase)))
(s/def :report/budget-profit ::money)
(s/def :report/net-profit ::money)
(s/def :report/difference ::money)
(s/def ::report (s/keys :req-un [:report/budget-profit :report/net-profit :report/budget-profit]))
nil [{:keys [name prj-list] {:keys [budget-profit net-profit difference]}} & [indent]]
(let [indent (or indent "")]
(str name " - " "Budg.p.: " budget-profit " Net.p.: " net-profit " Diff.: " difference
(defmulti pp-project (fn [p & [indent]] (:id p)))
(defmethod pp-project nil [{:keys [name prj-list]
{:keys [budget-profit net-profit difference] :as report} :report}
& [indent]]
(let [indent (or indent "")]
(str name " - " "Budg.p.: " budget-profit " Net.p.: " net-profit " Diff.: " difference "\n"
(apply str
(for [p (butlast prj-list)]
(str indent "|\n" indent "+-"
(pp-project p (str indent "| "))
"\n")))
indent "|\n" indent "`-"
(pp-project (last prj-list) (str indent " ")))))
(defmethod pp-project :default [{:keys [id name]
{:keys [budget-profit net-profit difference] :as report} :report}
& [_]]
(str " " name " [" id "] " "Budg.p.: " budget-profit " Net.p.: " net-profit " Diff.: " difference))
(defn get-budget [_]
{:income (bigdec (/ (rand-int 1000000) 100)) :expenditure (bigdec (/ (rand-int 1000000) 100))})
(defn get-transactions [_]
[(->Sale (bigdec (/ (rand-int 400000) 100))) (->Purchase (bigdec (/ (rand-int 400000) 100)))])
(defprotocol Transactable
(transact [t]))
(extend-protocol Transactable
Sale
(transact [t]
(:amount t))
Purchase
(transact [t]
(-' (:amount t))))
(defn calculate-report [{:keys [income expenditure]} transactions]
(let [budget-profit (- income expenditure)
net-profit (reduce + (map transact transactions))]
{:budget-profit budget-profit
:net-profit net-profit
:difference (- net-profit budget-profit)}))
(defmulti calculate-project-report :prj-list)
(defmethod calculate-project-report nil [p]
(assoc p :report
(calculate-report (get-budget p) (get-transactions p))))
(defmethod calculate-project-report :default [p]
(let [reported-prj-list (map calculate-project-report (:prj-list p))]
(assoc p :report
(reduce (partial merge-with +) (map :report reported-prj-list))
:prj-list reported-prj-list)))
(def some-project
{:name "Sweden"
:prj-list [{:name "Stockholm"
:prj-list [{:id 1 :name "Djurgaarden"}
{:id 2 :name "Skaergaarden"}]}
{:id 3
:name "Gothenborg"}
{:name "Malmo"
:prj-list [{:name "Malmo City"
:prj-list [{:id 41 :name "Fosie1"}
{:id 42 :name "Fosie2"}
{:name "Fosie3"
:prj-list [{:id 31 :name "Djurgaarden"}
{:id 32 :name "Skaergaarden"}]}
{:id 5 :name "Rosengaard"}]}
{:name "Limhamn"
:prj-list [{:id 6 :name "Kalkbrottet"}
{:id 7 :name "Sibbarp"}]}]}
{:id 4
:name "Eskilstuna"}
]})
(print (pp-project (calculate-project-report some-project)))
(print (pp-project (calculate-project-report (first (gen/sample (s/gen ::project) 1)))))
(defproject pms "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.9.0"]
[org.clojure/spec.alpha "0.1.143"]]
:main ^:skip-aot pms.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all}
:dev {:dependencies [[org.clojure/test.check "0.9.0"]]}})
@owickstrom
Copy link

Looking back at this, I love how you added more projects with good names. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment