mirror of
https://github.com/mruwnik/invoices.git
synced 2025-06-28 15:14:50 +02:00
Tidy up, add tests
This commit is contained in:
parent
5bb673038d
commit
664098705b
@ -74,8 +74,7 @@ and a key providing the cost of the item. The price can be provided in one of th
|
||||
to working out what the hourly rate should be in a given month and multiplying it by the number
|
||||
of hours worked in that month
|
||||
* :function - an S-expression describing how to calculate the net value. Only numbers, basic mathematical
|
||||
operations (+, -, /, *) and timesheet specific variables are supported (:worked, :required,
|
||||
:to, :from).
|
||||
operations (+, -, /, *) and timesheet specific variables are supported (:worked, :required).
|
||||
|
||||
|
||||
Examples:
|
||||
|
40
src/invoices/calc.clj
Normal file
40
src/invoices/calc.clj
Normal file
@ -0,0 +1,40 @@
|
||||
(ns invoices.calc)
|
||||
|
||||
(defn round [val]
|
||||
(double (/ (Math/round (* val 100.0)) 100)))
|
||||
|
||||
(defn vat [{netto :netto vat-level :vat}]
|
||||
(if-not vat-level 0 (* netto (/ vat-level 100))))
|
||||
|
||||
(defn brutto [{netto :netto :as item}] (round (+ netto (vat item))))
|
||||
|
||||
(defn parse-custom
|
||||
"Parse the given function definition and execute it with the given `worklog`."
|
||||
[work-log func]
|
||||
(cond
|
||||
(and (list? func) (some #{(first func)} '(+ - * /))) (apply (resolve (first func))
|
||||
(map (partial parse-custom work-log) (rest func)))
|
||||
(some #{func} '(:worked :required)) (func work-log)
|
||||
(or (list? func) (not (number? func))) (throw (IllegalArgumentException. (str "Invalid functor provided: " (first func))))
|
||||
:else func))
|
||||
|
||||
(defn calc-part-time [{worked :worked total :required} {base :base per-day :per-day}]
|
||||
(let [hourly (/ (* base 8) (* total per-day))]
|
||||
(float (* hourly worked))))
|
||||
|
||||
(defn calc-hourly [{worked :worked} {hourly :hourly}]
|
||||
(* worked hourly))
|
||||
|
||||
(defn calc-custom [worked {function :function}]
|
||||
(parse-custom worked function))
|
||||
|
||||
|
||||
(defn set-price
|
||||
"Set the net price for the given item, calculating it from a worklog if applicable."
|
||||
[worked item]
|
||||
(cond
|
||||
(contains? item :function) (assoc item :netto (calc-custom worked item))
|
||||
(contains? item :hourly) (assoc item :netto (calc-hourly worked item))
|
||||
(contains? item :base) (assoc item :netto (calc-part-time worked item))
|
||||
(not (contains? item :netto)) (assoc item :netto 0)
|
||||
:else item))
|
@ -1,7 +1,9 @@
|
||||
(ns invoices.core
|
||||
(:require [invoices.pdf :as pdf]
|
||||
[invoices.settings :refer [invoices]]
|
||||
[invoices.jira :refer [prev-timesheet prev-month]]
|
||||
[invoices.jira :refer [prev-timesheet]]
|
||||
[invoices.time :refer [prev-month last-working-day date-applies?]]
|
||||
[invoices.calc :refer [set-price]]
|
||||
[clojure.tools.cli :refer [parse-opts]]
|
||||
[clojure.string :as str]
|
||||
[clojure.java.shell :refer [sh]]
|
||||
@ -11,33 +13,6 @@
|
||||
(defn invoice-number [when number]
|
||||
(->> [(or number 1) (-> when .getMonthValue) (-> when .getYear)] (map str) (str/join "/")))
|
||||
|
||||
(defn parse-custom [work-log func]
|
||||
(cond
|
||||
(and (list? func) (some #{(first func)} '(+ - * /))) (apply (resolve (first func))
|
||||
(map (partial parse-custom work-log) (rest func)))
|
||||
(list? func) (throw (IllegalArgumentException. (str "Invalid functor provided: " (first func))))
|
||||
(some #{func} '(:worked :required :to :from)) (func work-log)
|
||||
:else func))
|
||||
|
||||
(defn calc-part-time [{worked :worked total :required} {base :base per-day :per-day}]
|
||||
(let [hourly (/ (* base 8) (* total per-day))]
|
||||
(float (* hourly worked))))
|
||||
|
||||
(defn calc-hourly [{worked :worked} {hourly :hourly}]
|
||||
(* worked hourly))
|
||||
|
||||
(defn calc-custom [worked {function :function}]
|
||||
(parse-custom worked function))
|
||||
|
||||
|
||||
(defn set-price [worked item]
|
||||
(cond
|
||||
(contains? item :function) (assoc item :netto (calc-custom worked item))
|
||||
(contains? item :hourly) (assoc item :netto (calc-hourly worked item))
|
||||
(contains? item :base) (assoc item :netto (calc-part-time worked item))
|
||||
(not (contains? item :netto)) (assoc item :netto 0)
|
||||
:else item))
|
||||
|
||||
(defn send-email [to from {smtp :smtp} invoice]
|
||||
(when (not-any? nil? [to from smtp invoice])
|
||||
(->>
|
||||
@ -62,17 +37,13 @@
|
||||
(defn run-callbacks [invoice callbacks]
|
||||
(doall (map (partial run-callback invoice) callbacks)))
|
||||
|
||||
(defn date-applies? [when {to :to from :from}]
|
||||
(and (or (nil? to) (-> when .toString (compare to) (< 0)))
|
||||
(or (nil? from) (-> when .toString (compare from) (>= 0)))))
|
||||
|
||||
|
||||
(defn render-month [when {seller :seller buyer :buyer items :items creds :credentials font-path :font-path} number]
|
||||
(pdf/render seller buyer
|
||||
(->> items
|
||||
(filter (partial date-applies? when))
|
||||
(map (partial set-price (prev-timesheet when creds))))
|
||||
(pdf/last-working-day when)
|
||||
(last-working-day when)
|
||||
(invoice-number when number)
|
||||
font-path))
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
(ns invoices.jira
|
||||
(:require [clj-http.client :as client]))
|
||||
(:require [clj-http.client :as client]
|
||||
[invoices.time :refer [last-day prev-month]]))
|
||||
|
||||
(defn tempo [{tempo-token :tempo-token} endpoint params]
|
||||
(-> (str "https://api.tempo.io/core/3" endpoint)
|
||||
@ -16,9 +17,6 @@
|
||||
(defn timesheet [{spent :timeSpentSeconds required :requiredSeconds period :period}]
|
||||
(merge {:worked (/ spent 3600) :required (/ required 3600)} period))
|
||||
|
||||
(defn last-day [when] (-> when (.withDayOfMonth 1) (.plusMonths 1) (.minusDays 1)))
|
||||
(defn prev-month [when] (-> when (.withDayOfMonth 1) (.minusMonths 1)))
|
||||
|
||||
(defn get-timesheet [who when credentials]
|
||||
(->> {"from" (-> when (.withDayOfMonth 1) .toString) "to" (-> when last-day .toString)}
|
||||
(tempo credentials (str "/timesheet-approvals/user/" who))
|
||||
|
@ -1,17 +1,10 @@
|
||||
(ns invoices.pdf
|
||||
(:require [clj-pdf.core :refer [pdf]]
|
||||
[invoices.time :refer [skip-days-off last-working-day]]
|
||||
[invoices.calc :refer [brutto vat round]]
|
||||
[clojure.string :as str])
|
||||
(:import [java.awt Font]))
|
||||
|
||||
|
||||
(defn round [val]
|
||||
(float (/ (Math/round (* val 100.0)) 100)))
|
||||
|
||||
(defn vat [{netto :netto vat-level :vat}]
|
||||
(if-not vat-level 0 (* netto (/ vat-level 100))))
|
||||
|
||||
(defn brutto [{netto :netto :as item}] (round (+ netto (vat item))))
|
||||
|
||||
(defn format-total [items getter]
|
||||
[:cell {:background-color [216 247 249]}
|
||||
(->> items (map getter) (reduce +) round str)])
|
||||
@ -37,58 +30,55 @@
|
||||
(str/replace #"[ -/]" "_"))))
|
||||
|
||||
|
||||
(defn pdf-body
|
||||
"Generate the actual pdf body"
|
||||
[title seller buyer items when number font]
|
||||
[{:title title
|
||||
:right-margin 50
|
||||
:author (:name seller)
|
||||
:bottom-margin 10
|
||||
:left-margin 10
|
||||
:top-margin 20
|
||||
:font font
|
||||
:size "a4"
|
||||
:footer "page"}
|
||||
|
||||
[:heading "Faktura"]
|
||||
[:spacer]
|
||||
[:paragraph (str "Nr " number)]
|
||||
[:spacer 2]
|
||||
|
||||
[:table {:border false :padding 0 :spacing 0 :num-cols 6}
|
||||
[(format-param "sprzedawca") (format-value (:name seller)) (format-param "nabywca") (format-value (:name buyer))]
|
||||
[(format-param "adres") (format-value (:address seller)) (format-param "adres") (format-value (:address buyer))]
|
||||
[(format-param "nip") (format-value (:nip seller)) (format-param "nip") (format-value (:nip buyer))]
|
||||
(clojure.core/when (:phone seller) [(format-param "numer telefonu") (format-value (:phone seller))])]
|
||||
|
||||
[:spacer]
|
||||
[:line]
|
||||
[:table {:border false :padding 0 :spacing 0 :num-cols 6}
|
||||
[(format-param "data wystawienia") (-> when .toString format-value) (format-param "sposób płatności") (format-value "Przelew")]
|
||||
[(format-param "data sprzedaży") (-> when .toString format-value) (format-param "bank") (format-value (:bank seller))]
|
||||
[(format-param "termin płatności") (-> when (.plusDays 14) .toString format-value) (format-param "numer konta") (format-value (:account seller))]]
|
||||
|
||||
(concat
|
||||
[:table
|
||||
{:header [{:background-color [216 247 249]} "Lp." [:cell {:colspan 4} "Nazwa"] "Ilość" "Cena netto" "Stawka VAT" "Kwota VAT" "Wartość brutto"]
|
||||
:num-cols 10}]
|
||||
(->> items
|
||||
(map format-product)
|
||||
(map-indexed #(concat [(inc %1)] %2)))
|
||||
[[[:cell {:background-color [84 219 229] :colspan 5 :align :center} "Razem"]
|
||||
(format-total items (constantly 1))
|
||||
(format-total items :netto)
|
||||
""
|
||||
(format-total items vat)
|
||||
(format-total items brutto)]])])
|
||||
|
||||
|
||||
(defn render [seller buyer items when number & [font-path]]
|
||||
(let [title (get-title (:team seller) (:name seller) number)]
|
||||
(println " -" title)
|
||||
(pdf
|
||||
[{:title title
|
||||
:right-margin 50
|
||||
:author (:name seller)
|
||||
:bottom-margin 10
|
||||
:left-margin 10
|
||||
:top-margin 20
|
||||
:font (clojure.core/when font-path{:encoding :unicode :ttf-name font-path})
|
||||
:size "a4"
|
||||
:footer "page"}
|
||||
|
||||
[:heading "Faktura"]
|
||||
[:spacer]
|
||||
[:paragraph (str "Nr " number)]
|
||||
[:spacer 2]
|
||||
|
||||
[:table {:border false :padding 0 :spacing 0 :num-cols 6}
|
||||
[(format-param "sprzedawca") (format-value (:name seller)) (format-param "nabywca") (format-value (:name buyer))]
|
||||
[(format-param "adres") (format-value (:address seller)) (format-param "adres") (format-value (:address buyer))]
|
||||
[(format-param "nip") (format-value (:nip seller)) (format-param "nip") (format-value (:nip buyer))]
|
||||
(clojure.core/when (:phone seller) [(format-param "numer telefonu") (format-value (:phone seller))])]
|
||||
|
||||
[:spacer]
|
||||
[:line]
|
||||
[:table {:border false :padding 0 :spacing 0 :num-cols 6}
|
||||
[(format-param "data wystawienia") (-> when .toString format-value) (format-param "sposób płatności") (format-value "Przelew")]
|
||||
[(format-param "data sprzedaży") (-> when .toString format-value) (format-param "bank") (format-value (:bank seller))]
|
||||
[(format-param "termin płatności") (-> when (.plusDays 14) .toString format-value) (format-param "numer konta") (format-value (:account seller))]]
|
||||
|
||||
(concat
|
||||
[:table
|
||||
{:header [{:background-color [216 247 249]} "Lp." [:cell {:colspan 4} "Nazwa"] "Ilość" "Cena netto" "Stawka VAT" "Kwota VAT" "Wartość brutto"]
|
||||
:num-cols 10}]
|
||||
(->> items
|
||||
(map format-product)
|
||||
(map-indexed #(concat [(inc %1)] %2)))
|
||||
[[[:cell {:background-color [84 219 229] :colspan 5 :align :center} "Razem"]
|
||||
(format-total items (constantly 1))
|
||||
(format-total items :netto)
|
||||
""
|
||||
(format-total items vat)
|
||||
(format-total items brutto)]])]
|
||||
(str title ".pdf"))
|
||||
(pdf (pdf-body title seller buyer items when number (clojure.core/when font-path{:encoding :unicode :ttf-name font-path}))
|
||||
(str title ".pdf"))
|
||||
title))
|
||||
|
||||
|
||||
(defn skip-days-off [when]
|
||||
(if (some #{(.getDayOfWeek when)} [java.time.DayOfWeek/SATURDAY java.time.DayOfWeek/SUNDAY])
|
||||
(skip-days-off (.minusDays when 1)) when))
|
||||
|
||||
(defn last-working-day [when]
|
||||
(-> when (.withDayOfMonth 1) (.plusMonths 1) (.minusDays 1) skip-days-off))
|
||||
|
27
src/invoices/time.clj
Normal file
27
src/invoices/time.clj
Normal file
@ -0,0 +1,27 @@
|
||||
(ns invoices.time)
|
||||
|
||||
|
||||
(defn last-day
|
||||
"Get the last day of the month of the given `date`"
|
||||
[date] (-> date (.withDayOfMonth 1) (.plusMonths 1) (.minusDays 1)))
|
||||
|
||||
(defn prev-month
|
||||
"Get the first day of the month preceeding the given `date`"
|
||||
[date] (-> date (.withDayOfMonth 1) (.minusMonths 1)))
|
||||
|
||||
(defn skip-days-off
|
||||
"Return the first day before (and including) `date` that isn't a day off."
|
||||
[date]
|
||||
(if (some #{(.getDayOfWeek date)} [java.time.DayOfWeek/SATURDAY java.time.DayOfWeek/SUNDAY])
|
||||
(skip-days-off (.minusDays date 1)) date))
|
||||
|
||||
(defn last-working-day
|
||||
"Get the last working day of `date`'s month."
|
||||
[date]
|
||||
(-> date (.withDayOfMonth 1) (.plusMonths 1) (.minusDays 1) skip-days-off))
|
||||
|
||||
(defn date-applies?
|
||||
"Return whether the provided `date` is between the provided :to and :from dates."
|
||||
[date {to :to from :from}]
|
||||
(and (or (nil? to) (-> date .toString (compare to) (< 0)))
|
||||
(or (nil? from) (-> date .toString (compare from) (>= 0)))))
|
110
test/invoices/calc_test.clj
Normal file
110
test/invoices/calc_test.clj
Normal file
@ -0,0 +1,110 @@
|
||||
(ns invoices.calc-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[invoices.calc :refer :all]))
|
||||
|
||||
|
||||
(deftest test-round
|
||||
(testing "Rounding to 2 decimal points"
|
||||
(is (== (round 10) 10.0))
|
||||
(is (== (round 10.1) 10.1))
|
||||
(is (== (round 10.12) 10.12))
|
||||
(is (== (round 10.123) 10.12))
|
||||
(is (== (round 10.1234) 10.12)))
|
||||
|
||||
(testing "Rounding is correct"
|
||||
(is (== (round 10.5) 10.5))
|
||||
(is (== (round 10.119) 10.12))
|
||||
(is (== (round 10.155) 10.15))
|
||||
(is (== (round 10.1251) 10.13))))
|
||||
|
||||
|
||||
(deftest test-vat
|
||||
(testing "Check vat calculations"
|
||||
(is (= (vat {:netto 1000 :vat 23}) 230))
|
||||
(is (= (vat {:netto 1000 :vat 8}) 80))
|
||||
(is (= (vat {:netto 1000 :vat 0}) 0))
|
||||
(is (= (vat {:netto 0 :vat 23}) 0))
|
||||
(is (= (vat {:netto 1000 :vat -8}) -80))))
|
||||
|
||||
(deftest test-brutto
|
||||
(testing "Check whether calculating brutto works"
|
||||
(is (= (brutto {:netto 1000 :vat 23}) 1230.0))
|
||||
(is (= (brutto {:netto 1000 :vat 8}) 1080.0))
|
||||
(is (= (brutto {:netto 1000 :vat 0}) 1000.0))
|
||||
(is (= (brutto {:netto 0 :vat 23}) 0.0))
|
||||
|
||||
; negative VAT, coz why not?
|
||||
(is (= (brutto {:netto 1000 :vat -23}) 770.0))))
|
||||
|
||||
|
||||
(deftest test-parse-custom
|
||||
(testing "Check that the specified operators are allowed"
|
||||
(is (= (parse-custom {} '(+ 1 2)) 3))
|
||||
(is (= (parse-custom {} '(- 1 2)) -1))
|
||||
(is (= (parse-custom {} '(* 4 2)) 8))
|
||||
(is (= (parse-custom {} '(/ 13 2)) 13/2)))
|
||||
|
||||
(testing "Check that non specified operators cause errors"
|
||||
(is (thrown? java.lang.IllegalArgumentException (parse-custom {} '(> 1 2))))
|
||||
(is (thrown? java.lang.IllegalArgumentException (parse-custom {} '(< 1 2))))
|
||||
(is (thrown? java.lang.IllegalArgumentException (parse-custom {} '(map 1 2)))))
|
||||
|
||||
(testing "Check that worklog values get used"
|
||||
(is (= (parse-custom {:worked 12} '(+ :worked 2)) 14))
|
||||
(is (= (parse-custom {:required 32} '(- :required 2)) 30)))
|
||||
|
||||
(testing "Check error raised if non worklog keys provided"
|
||||
(is (thrown? java.lang.IllegalArgumentException (parse-custom {} '(> :bla 2))))
|
||||
(is (thrown? java.lang.IllegalArgumentException (parse-custom {} '(> :non-worked 2))))))
|
||||
|
||||
(deftest test-calc-part-time
|
||||
(testing "Check whether calculating part time costs works"
|
||||
; 4h per day, 10 per month if all hours done, the person did all of thier required hours
|
||||
(is (= (calc-part-time {:worked 12 :required 24} {:base 10 :per-day 4}) 10.0))
|
||||
|
||||
; 4h per day, 10 per month if all hours done, the person is 2 hours low
|
||||
(is (= (round (calc-part-time {:worked 10 :required 24} {:base 10 :per-day 4})) 8.33))
|
||||
|
||||
; 4h per day, 10 per month if all hours done, the person did 10h extra hours
|
||||
(is (= (round (calc-part-time {:worked 22 :required 24} {:base 10 :per-day 4})) 18.33))
|
||||
|
||||
; 8h per day, 10 per month if all hours done, the person did all required hours
|
||||
(is (= (round (calc-part-time {:worked 24 :required 24} {:base 10 :per-day 8})) 10.0))))
|
||||
|
||||
|
||||
(deftest test-calc-hourly
|
||||
(testing "Check whether calculating hourly rates works"
|
||||
(is (= (calc-hourly {:worked 12} {:hourly 10}) 120))
|
||||
(is (= (calc-hourly {:worked 12.5} {:hourly 10}) 125.0))
|
||||
(is (= (calc-hourly {:worked 12} {:hourly 10.99}) 131.88))))
|
||||
|
||||
|
||||
(deftest test-calc-custom
|
||||
(testing "Check whether custom formulas work"
|
||||
; base per hour
|
||||
(is (= (calc-custom {:worked 12} {:function '(* :worked 10)}) 120))
|
||||
; Sylwia's formula
|
||||
(is (= (round (calc-custom {:worked 100 :required 168}
|
||||
{:function '(+ 1000 (* (- :worked (/ :required 2)) (/ 2000 167)))}))
|
||||
1191.62))))
|
||||
|
||||
|
||||
(deftest test-set-price
|
||||
(let [worked {:worked 100 :required 168}]
|
||||
(testing "Check the default is to set 0"
|
||||
(is (= (:netto (set-price worked {})) 0)))
|
||||
|
||||
(testing "Check that :netto is returned if no calc func provided"
|
||||
(is (= (:netto (set-price worked {:netto 123})) 123)))
|
||||
|
||||
(testing "Check that :per-day is ignored if :base not provided"
|
||||
(is (= (:netto (set-price worked {:per-day 123})) 0)))
|
||||
|
||||
(testing "Check that part time is calculated if :base provided"
|
||||
(is (= (round (:netto (set-price worked {:base 100 :per-day 4}))) 119.05)))
|
||||
|
||||
(testing "Check that per hour calculated if :hourly provided"
|
||||
(is (= (round (:netto (set-price worked {:hourly 10}))) 1000.0)))
|
||||
|
||||
(testing "Check that custom func used if provided"
|
||||
(is (= (round (:netto (set-price worked {:function '(* :worked 12)}))) 1200.0)))))
|
108
test/invoices/pdf_test.clj
Normal file
108
test/invoices/pdf_test.clj
Normal file
@ -0,0 +1,108 @@
|
||||
(ns invoices.pdf-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[invoices.pdf :refer :all]))
|
||||
|
||||
|
||||
(deftest test-format-total
|
||||
(testing "Check whether the `total` cell gets correctly formatted"
|
||||
(is (= (format-total [1 2 3 4] identity) [:cell {:background-color [216 247 249]} "10.0"])))
|
||||
|
||||
(testing "Check whether the `total` cell gets correctly formatted with accessor"
|
||||
(is (= (format-total [] :netto) [:cell {:background-color [216 247 249]} "0.0"]))
|
||||
(is (= (format-total [{:netto 12} {:netto 32}] :netto)
|
||||
[:cell {:background-color [216 247 249]} "44.0"]))))
|
||||
|
||||
(deftest test-format-param
|
||||
(testing "Check whether parameters get correctly formatted"
|
||||
(is (= (format-param 123) [:cell.param {:align :right} "123: "]))
|
||||
(is (= (format-param "bla") [:cell.param {:align :right} "bla: "]))))
|
||||
|
||||
(deftest test-format-value
|
||||
(testing "Check whether values get correctly formatted"
|
||||
(is (= (format-value 123) [:cell {:colspan 2} "123"]))
|
||||
(is (= (format-value "bla") [:cell {:colspan 2} "bla"]))))
|
||||
|
||||
(deftest test-format-product
|
||||
(testing "Check whether whole products get correctly formatted"
|
||||
(is (= (format-product {:netto 1000 :vat 23 :title "bla bla"})
|
||||
[[:cell {:colspan 4} "bla bla"] "1" "1000.0" "23%" "230.0" "1230.0"]))
|
||||
(is (= (format-product {:netto 1000 :vat 0 :title "bla bla"})
|
||||
[[:cell {:colspan 4} "bla bla"] "1" "1000.0" "0%" "0.0" "1000.0"])))
|
||||
|
||||
(testing "Check whether no vat is handled correctly"
|
||||
(is (= (format-product {:netto 1000 :title "bla bla"})
|
||||
[[:cell {:colspan 4} "bla bla"] "1" "1000.0" "zw." "0.0" "1000.0"]))))
|
||||
|
||||
(deftest test-get-title
|
||||
(testing "Check whether getting titles works"
|
||||
(is (= (get-title nil "mr blobby" "2019/02/11") "mr_blobby_luty_2019_02_11"))
|
||||
(is (= (get-title "asd" "mr blobby" "2019/02/11") "asd_mr_blobby_luty_2019_02_11"))))
|
||||
|
||||
(deftest test-get-pdf
|
||||
(testing "Check whether generating pdf bodies works"
|
||||
(let [seller {:name "Mr. Blobby"
|
||||
:address "ul. podwodna, 12-345, Mierzów"
|
||||
:nip 1234567890
|
||||
:phone 876543216
|
||||
:account "65 2345 1233 1233 4322 3211 4567"
|
||||
:bank "Skok hop"}
|
||||
buyer {:name "Buty S.A."
|
||||
:address "ul. Szewska 32, 76-543, Bąków"
|
||||
:nip 9875645342}
|
||||
items [{:vat 8 :netto 123.21 :title "Buty kowbojskie"}
|
||||
{:netto 321.45 :title "Usługa szewska bez VAT"}]
|
||||
date (java.time.LocalDate/parse "2018-03-02")
|
||||
font "/usr/share/fonts/truetype/freefont/FreeSans.ttf"]
|
||||
(is (= (pdf-body "pdf title" seller buyer items date 12 font)
|
||||
[{:bottom-margin 10
|
||||
:right-margin 50
|
||||
:left-margin 10
|
||||
:footer "page"
|
||||
:font "/usr/share/fonts/truetype/freefont/FreeSans.ttf"
|
||||
:size "a4"
|
||||
:title "pdf title"
|
||||
:author "Mr. Blobby"
|
||||
:top-margin 20}
|
||||
[:heading "Faktura"]
|
||||
[:spacer]
|
||||
[:paragraph "Nr 12"]
|
||||
[:spacer 2]
|
||||
[:table {:border false, :padding 0, :spacing 0, :num-cols 6}
|
||||
[[:cell.param {:align :right} "sprzedawca: "]
|
||||
[:cell {:colspan 2} "Mr. Blobby"]
|
||||
[:cell.param {:align :right} "nabywca: "]
|
||||
[:cell {:colspan 2} "Buty S.A."]]
|
||||
[[:cell.param {:align :right} "adres: "]
|
||||
[:cell {:colspan 2} "ul. podwodna, 12-345, Mierzów"]
|
||||
[:cell.param {:align :right} "adres: "]
|
||||
[:cell {:colspan 2} "ul. Szewska 32, 76-543, Bąków"]]
|
||||
[[:cell.param {:align :right} "nip: "]
|
||||
[:cell {:colspan 2} "1234567890"]
|
||||
[:cell.param {:align :right} "nip: "]
|
||||
[:cell {:colspan 2} "9875645342"]]
|
||||
[[:cell.param {:align :right} "numer telefonu: "]
|
||||
[:cell {:colspan 2} "876543216"]]]
|
||||
[:spacer]
|
||||
[:line]
|
||||
[:table {:border false, :padding 0, :spacing 0, :num-cols 6}
|
||||
[[:cell.param {:align :right} "data wystawienia: "]
|
||||
[:cell {:colspan 2} "2018-03-02"]
|
||||
[:cell.param {:align :right} "sposób płatności: "]
|
||||
[:cell {:colspan 2} "Przelew"]]
|
||||
[[:cell.param {:align :right} "data sprzedaży: "]
|
||||
[:cell {:colspan 2} "2018-03-02"]
|
||||
[:cell.param {:align :right} "bank: "]
|
||||
[:cell {:colspan 2} "Skok hop"]]
|
||||
[[:cell.param {:align :right} "termin płatności: "]
|
||||
[:cell {:colspan 2} "2018-03-16"]
|
||||
[:cell.param {:align :right} "numer konta: "]
|
||||
[:cell {:colspan 2} "65 2345 1233 1233 4322 3211 4567"]]]
|
||||
(list :table {:header [{:background-color [216 247 249]} "Lp." [:cell {:colspan 4} "Nazwa"] "Ilość" "Cena netto" "Stawka VAT" "Kwota VAT" "Wartość brutto"], :num-cols 10}
|
||||
(list 1 [:cell {:colspan 4} "Buty kowbojskie"] "1" "123.21" "8%" "9.86" "133.07")
|
||||
(list 2 [:cell {:colspan 4} "Usługa szewska bez VAT"] "1" "321.45" "zw." "0.0" "321.45")
|
||||
[[:cell {:background-color [84 219 229], :colspan 5, :align :center} "Razem"]
|
||||
[:cell {:background-color [216 247 249]} "2.0"]
|
||||
[:cell {:background-color [216 247 249]} "444.66"]
|
||||
""
|
||||
[:cell {:background-color [216 247 249]} "9.86"]
|
||||
[:cell {:background-color [216 247 249]} "454.52"]])])))))
|
76
test/invoices/time_test.clj
Normal file
76
test/invoices/time_test.clj
Normal file
@ -0,0 +1,76 @@
|
||||
(ns invoices.time-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[invoices.time :refer :all]))
|
||||
|
||||
|
||||
(deftest test-last-day
|
||||
(testing "getting last day of month"
|
||||
(doseq [[day last] [["2019-10-01" "2019-10-31"]
|
||||
["2019-06-28" "2019-06-30"]
|
||||
["2019-02-03" "2019-02-28"]
|
||||
["2020-02-13" "2020-02-29"]]]
|
||||
(is (= (last-day (java.time.LocalDate/parse day)) (java.time.LocalDate/parse last))))))
|
||||
|
||||
|
||||
(deftest test-prev-month
|
||||
(testing "getting previous month"
|
||||
(doseq [[day previous] [["2019-11-01" "2019-10-01"]
|
||||
["2019-07-28" "2019-06-01"]
|
||||
["2019-03-03" "2019-02-01"]
|
||||
["2020-01-13" "2019-12-01"]]]
|
||||
(is (= (prev-month (java.time.LocalDate/parse day)) (java.time.LocalDate/parse previous))))))
|
||||
|
||||
|
||||
(deftest test-skip-days-off
|
||||
(testing "Check that work days are returned as is"
|
||||
(doseq [day ["2019-10-04" ; friday
|
||||
"2019-10-03" ; thursday
|
||||
"2019-10-02"
|
||||
"2019-10-01"
|
||||
"2019-10-07"]] ; monday
|
||||
(is (= (skip-days-off (java.time.LocalDate/parse day)) (java.time.LocalDate/parse day)))))
|
||||
(testing "Check that weekends are skipped"
|
||||
(doseq [[day friday] [["2019-10-06" "2019-10-04"] ; a sunday
|
||||
["2019-10-05" "2019-10-04"]
|
||||
["2019-09-01" "2019-08-30"]]] ; month ends are correctly handled
|
||||
(is (= (skip-days-off (java.time.LocalDate/parse day)) (java.time.LocalDate/parse friday))))))
|
||||
|
||||
|
||||
(deftest test-last-working-day
|
||||
(testing "Check that getting the last working day works"
|
||||
(doseq [[day last] [["2019-10-06" "2019-10-31"] ; skip forward to the end of the month
|
||||
["2019-08-31" "2019-08-30"]]] ; go back to the last working day
|
||||
(is (= (last-working-day (java.time.LocalDate/parse day)) (java.time.LocalDate/parse last))))))
|
||||
|
||||
|
||||
(deftest test-date-applies
|
||||
(testing "Check that all dates apply when no to or from provided"
|
||||
(doseq [day ["1066-10-06" "2019-10-31" "2219-08-30"]]
|
||||
(is (date-applies? (java.time.LocalDate/parse day) {:to nil :from nil}))))
|
||||
|
||||
(let [day (java.time.LocalDate/parse "2019-10-10")]
|
||||
(testing "Check that only dates before :to are valid"
|
||||
(is (date-applies? day {:to "2020-10-10" :from nil}))
|
||||
(is (date-applies? day {:to "2019-10-11" :from nil}))
|
||||
|
||||
(is (not (date-applies? day {:to "2019-10-10" :from nil}))) ; the same day is deemed invalid
|
||||
(is (not (date-applies? day {:to "2010-10-10" :from nil}))))
|
||||
|
||||
(testing "Check that only dates after :from are valid"
|
||||
(is (date-applies? day {:to nil :from "2000-10-10"}))
|
||||
(is (date-applies? day {:to nil :from "2019-10-09"}))
|
||||
(is (date-applies? day {:to nil :from "2019-10-10"})) ; the same day is deemed valid
|
||||
|
||||
(is (not (date-applies? day {:to nil :from "2019-10-11"})))
|
||||
(is (not (date-applies? day {:to nil :from "2219-10-11"}))))
|
||||
|
||||
(testing "Check that only dates between :to :from are valid"
|
||||
(is (date-applies? day {:to "2020-10-10" :from "2000-10-10"}))
|
||||
(is (date-applies? day {:to "2019-10-11" :from "2019-10-10"}))
|
||||
|
||||
(is (not (date-applies? day {:to "2019-10-10" :from "2019-10-10"})))
|
||||
(is (not (date-applies? day {:to "2019-10-11" :from "2019-10-11"}))))
|
||||
|
||||
(testing ":from must be before :to"
|
||||
(is (not (date-applies? day {:to "2019-10-12" :from "2020-10-10"})))
|
||||
(is (not (date-applies? day {:to "2018-10-10" :from "2020-10-10"}))))))
|
Loading…
x
Reference in New Issue
Block a user