mirror of
https://github.com/mruwnik/invoices.git
synced 2025-06-28 15:14:50 +02:00
Skip items with no :netto
This commit is contained in:
parent
19c371cfef
commit
aea45a0609
@ -72,7 +72,8 @@ item is VAT free), :to (the date from which this item is valid), :from (the date
|
|||||||
The price can be provided in one of the following ways:
|
The price can be provided in one of the following ways:
|
||||||
|
|
||||||
* :netto - is a set price and will be displayed as provided
|
* :netto - is a set price and will be displayed as provided
|
||||||
* :hourly - is an hourly price - JIRA will be queried in order to work out how many hours should be billed
|
* :hourly - is an hourly price - worklogs will be queried in order to work out how many hours should be billed.
|
||||||
|
If no worklog could be found (or its :worked is nil), this item will be skipped.
|
||||||
* :base + :per-day - in the case of a variable number of hours worked. :base provides the amount that would be paid
|
* :base + :per-day - in the case of a variable number of hours worked. :base provides the amount that would be paid
|
||||||
if `<number of hours worked> == <number of hours in the current month if full time> / per-day`.
|
if `<number of hours worked> == <number of hours in the current month if full time> / per-day`.
|
||||||
In the case of someone working full time, :per-day would be 8, and if the number of hours worked
|
In the case of someone working full time, :per-day would be 8, and if the number of hours worked
|
||||||
@ -81,9 +82,12 @@ The price can be provided in one of the following ways:
|
|||||||
had worked exactly half the number of working hours in a given month, then the price will also
|
had worked exactly half the number of working hours in a given month, then the price will also
|
||||||
be :base. Otherwise the final price will be scaled accordingly. This is pretty much equivalent
|
be :base. Otherwise the final price will be scaled accordingly. This is pretty much equivalent
|
||||||
to working out what the hourly rate should be in a given month and multiplying it by the number
|
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
|
of hours worked in that month. If no `:worked` value can be found (or if it's nil), this item
|
||||||
|
will be skipped.
|
||||||
* :function - an S-expression describing how to calculate the net value. Only numbers, basic mathematical
|
* :function - an S-expression describing how to calculate the net value. Only numbers, basic mathematical
|
||||||
operations (+, -, /, *) and timesheet specific variables are supported (:worked, :required).
|
operations (+, -, /, *) and timesheet specific variables are supported (:worked, :required).
|
||||||
|
If a timesheet variable is used, but no such value can be found in the timesheet, an exception
|
||||||
|
will be raised.
|
||||||
|
|
||||||
If the price is to be calculated on the basis of a worklog, add a `:worklog` key
|
If the price is to be calculated on the basis of a worklog, add a `:worklog` key
|
||||||
and make sure the `:worklogs` section has an item that can be used to access the worklog.
|
and make sure the `:worklogs` section has an item that can be used to access the worklog.
|
||||||
|
@ -14,20 +14,24 @@
|
|||||||
{:vat 21 :hourly 43.12 :title "Usługa szewska" :worklog :from-jira}
|
{:vat 21 :hourly 43.12 :title "Usługa szewska" :worklog :from-jira}
|
||||||
{:netto 321.45 :title "Usługa szewska bez VAT"}
|
{:netto 321.45 :title "Usługa szewska bez VAT"}
|
||||||
{:netto 321.45 :title "Usługa szewska zwolniona z VAT" :notes ["Podstawa zwolnienia z VAT: art. 113 ust. 1 i 9 Ustawa o VAT"]}
|
{:netto 321.45 :title "Usługa szewska zwolniona z VAT" :notes ["Podstawa zwolnienia z VAT: art. 113 ust. 1 i 9 Ustawa o VAT"]}
|
||||||
{:vat 23 :function (* :worked (+ 1 2 3 (- 23 13))) :title "Pucowania obuwia"}
|
{:vat 23 :function (* 23 (+ 1 2 3 (- 23 13))) :title "Pucowania obuwia - stały koszt"}
|
||||||
{:vat 23 :base 4300.00 :per-day 4 :title "Praca za ladą"}]
|
;; {:vat 23 :function (* :worked (+ 1 2 3 (- 23 13))) :title "Pucowania obuwia" :worklog :item1} ; this will cause an error if no :item1 worklog can be found
|
||||||
:smtp {:host "smtp.gmail.com" :user "mr.blobby@buty.sa" :pass "asd;l;kjsdfkljld" :ssl true}}]
|
{:vat 23 :base 4300.00 :per-day 4 :title "Praca za ladą" :worklog :item2}]
|
||||||
:worklogs [{:type :jira
|
;; :smtp {:host "smtp.gmail.com" :user "mr.blobby@buty.sa" :pass "asd;l;kjsdfkljld" :ssl true}
|
||||||
:ids [:from-jira]
|
}]
|
||||||
:tempo-token "5zq7zF9LADefEGAs12eDDas3FDttiM"
|
:worklogs [
|
||||||
:jira-token "qypaAsdFwASasEddDDddASdC"
|
;; {:type :jira
|
||||||
:jira-user "mr.blobby@boots.rs"}
|
;; :ids [:from-jira]
|
||||||
{:type :imap
|
;; :tempo-token "5zq7zF9LADefEGAs12eDDas3FDttiM"
|
||||||
:ids [:item1 :item2]
|
;; :jira-token "qypaAsdFwASasEddDDddASdC"
|
||||||
:folder "inbox"
|
;; :jira-user "mr.blobby@boots.rs"}
|
||||||
:host "imap.gmail.com"
|
;; {:type :imap
|
||||||
:user "mr.blobby@boots.rs"
|
;; :ids [:item1 :item2]
|
||||||
:pass "lksjdfklsjdflkjw"
|
;; :folder "inbox"
|
||||||
:from "hr@boots.rs"
|
;; :host "imap.gmail.com"
|
||||||
:subject "'Hours 'YYYY-MM"
|
;; :user "mr.blobby@boots.rs"
|
||||||
:headers [:id :worked]}]}
|
;; :pass "lksjdfklsjdflkjw"
|
||||||
|
;; :from "hr@boots.rs"
|
||||||
|
;; :subject "'Hours 'YYYY-MM"
|
||||||
|
;; :headers [:id :worked]}
|
||||||
|
]}
|
||||||
|
@ -19,16 +19,16 @@
|
|||||||
:else func))
|
:else func))
|
||||||
|
|
||||||
(defn calc-part-time [{worked :worked total :required} {base :base per-day :per-day}]
|
(defn calc-part-time [{worked :worked total :required} {base :base per-day :per-day}]
|
||||||
|
(when (and worked total)
|
||||||
(let [hourly (/ (* base 8) (* total per-day))]
|
(let [hourly (/ (* base 8) (* total per-day))]
|
||||||
(float (* hourly worked))))
|
(float (* hourly worked)))))
|
||||||
|
|
||||||
(defn calc-hourly [{worked :worked} {hourly :hourly}]
|
(defn calc-hourly [{worked :worked} {hourly :hourly}]
|
||||||
(* worked hourly))
|
(when worked (* worked hourly)))
|
||||||
|
|
||||||
(defn calc-custom [worked {function :function}]
|
(defn calc-custom [worked {function :function}]
|
||||||
(parse-custom worked function))
|
(parse-custom worked function))
|
||||||
|
|
||||||
|
|
||||||
(defn set-price
|
(defn set-price
|
||||||
"Set the net price for the given item, calculating it from a worklog if applicable."
|
"Set the net price for the given item, calculating it from a worklog if applicable."
|
||||||
[worked item]
|
[worked item]
|
||||||
|
@ -27,14 +27,17 @@
|
|||||||
|
|
||||||
(defn for-month [{seller :seller buyer :buyer smtp :smtp callbacks :callbacks :as invoice} when & [number font]]
|
(defn for-month [{seller :seller buyer :buyer smtp :smtp callbacks :callbacks :as invoice} when & [number font]]
|
||||||
(let [file (pdf/render invoice (last-working-day when) (invoice-number when number))]
|
(let [file (pdf/render invoice (last-working-day when) (invoice-number when number))]
|
||||||
(email/send-invoice file (:email buyer) smtp)
|
;; (email/send-invoice file (:email buyer) smtp)
|
||||||
(run-callbacks file callbacks)))
|
(run-callbacks file callbacks)))
|
||||||
|
|
||||||
(defn item-price [worklogs item]
|
(defn item-price [worklogs item]
|
||||||
(-> item :worklog (worklogs) (set-price item)))
|
(-> item :worklog (worklogs) (set-price item)))
|
||||||
|
|
||||||
(defn calc-prices [invoice worklogs]
|
(defn calc-prices [invoice worklogs]
|
||||||
(update invoice :items (partial map (partial item-price worklogs))))
|
(->> invoice :items
|
||||||
|
(map (partial item-price worklogs))
|
||||||
|
(remove (comp nil? :netto))
|
||||||
|
(assoc invoice :items)))
|
||||||
|
|
||||||
(defn prepare-invoice [{seller :seller font :font-path} month worklogs invoice]
|
(defn prepare-invoice [{seller :seller font :font-path} month worklogs invoice]
|
||||||
(-> invoice
|
(-> invoice
|
||||||
@ -54,7 +57,7 @@
|
|||||||
:default 1
|
:default 1
|
||||||
:parse-fn #(Integer/parseInt %)]
|
:parse-fn #(Integer/parseInt %)]
|
||||||
["-w" "--when DATE" "The month for which to generate the invoice"
|
["-w" "--when DATE" "The month for which to generate the invoice"
|
||||||
:default (-> (java.time.LocalDate/now) prev-month)
|
:default (java.time.LocalDate/now)
|
||||||
:parse-fn #(java.time.LocalDate/parse %)]
|
:parse-fn #(java.time.LocalDate/parse %)]
|
||||||
;; A non-idempotent option (:default is applied first)
|
;; A non-idempotent option (:default is applied first)
|
||||||
["-c" "--company NIP" "companies for which to generate invoices. All, if not provided"
|
["-c" "--company NIP" "companies for which to generate invoices. All, if not provided"
|
||||||
@ -82,9 +85,9 @@
|
|||||||
errors (exit -1 (str/join "\n" errors))
|
errors (exit -1 (str/join "\n" errors))
|
||||||
(not= 1 (count arguments)) (exit -1 "No config file provided"))
|
(not= 1 (count arguments)) (exit -1 "No config file provided"))
|
||||||
|
|
||||||
(println "Generating invoices")
|
(println "Generating invoices" )
|
||||||
(let [month (java.time.LocalDate/now)
|
(let [month (:when options)
|
||||||
config (-> "config.edn" (invoices month))
|
config (-> (first arguments) (invoices month))
|
||||||
worklogs (timesheets month (:worklogs config))]
|
worklogs (timesheets month (:worklogs config))]
|
||||||
(process-invoices config month worklogs)))
|
(process-invoices config month worklogs)))
|
||||||
(shutdown-agents))
|
(shutdown-agents))
|
||||||
|
@ -69,14 +69,27 @@
|
|||||||
(is (= (round (calc-part-time {:worked 22 :required 24} {:base 10 :per-day 4})) 18.33))
|
(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
|
; 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))))
|
(is (= (round (calc-part-time {:worked 24 :required 24} {:base 10 :per-day 8})) 10.0)))
|
||||||
|
|
||||||
|
(testing "Check whether nil is returned if :worked or :required missing are nil"
|
||||||
|
(is (nil? (calc-part-time {:worked nil :required 24} {:base 10 :per-day 8})))
|
||||||
|
(is (nil? (calc-part-time {:worked 24 :required nil} {:base 10 :per-day 8})))
|
||||||
|
(is (nil? (calc-part-time {:worked nil :required nil} {:base 10 :per-day 8}))))
|
||||||
|
|
||||||
|
(testing "Check whether nil is returned if :worked or :required missing"
|
||||||
|
(is (nil? (calc-part-time {:required 24} {:base 10 :per-day 8})))
|
||||||
|
(is (nil? (calc-part-time {:worked 24} {:base 10 :per-day 8})))
|
||||||
|
(is (nil? (calc-part-time {} {:base 10 :per-day 8})))))
|
||||||
|
|
||||||
(deftest test-calc-hourly
|
(deftest test-calc-hourly
|
||||||
(testing "Check whether calculating hourly rates works"
|
(testing "Check whether calculating hourly rates works"
|
||||||
(is (= (calc-hourly {:worked 12} {:hourly 10}) 120))
|
(is (= (calc-hourly {:worked 12} {:hourly 10}) 120))
|
||||||
(is (= (calc-hourly {:worked 12.5} {:hourly 10}) 125.0))
|
(is (= (calc-hourly {:worked 12.5} {:hourly 10}) 125.0))
|
||||||
(is (= (calc-hourly {:worked 12} {:hourly 10.99}) 131.88))))
|
(is (= (calc-hourly {:worked 12} {:hourly 10.99}) 131.88)))
|
||||||
|
|
||||||
|
(testing "nil is returned when no :worked provided"
|
||||||
|
(is (nil? (calc-hourly {:worked nil} {:hourly 10})))
|
||||||
|
(is (nil? (calc-hourly {} {:hourly 10})))))
|
||||||
|
|
||||||
|
|
||||||
(deftest test-calc-custom
|
(deftest test-calc-custom
|
||||||
|
@ -42,10 +42,25 @@
|
|||||||
(is (nil? (format-notes [])))
|
(is (nil? (format-notes [])))
|
||||||
(is (nil? (format-notes nil)))))
|
(is (nil? (format-notes nil)))))
|
||||||
|
|
||||||
|
(deftest test-month-name
|
||||||
|
(testing "Check whether the month names are correctly returned"
|
||||||
|
(doseq [[i name] (rest (map-indexed vector month-names))]
|
||||||
|
(is (= (month-name (str "2012/" i "/02")) name)))))
|
||||||
|
|
||||||
|
(deftest test-title-base
|
||||||
|
(testing "Check that :title is used if available"
|
||||||
|
(is (= (title-base {:seller {:team "A" :name "bla"} :title "title"}) "title"))
|
||||||
|
(is (= (title-base {:title "title"}) "title")))
|
||||||
|
(testing "Check that the title is made from the team and name"
|
||||||
|
(is (= (title-base {:seller {:team "A" :name "bla"}}) "A_bla"))
|
||||||
|
(is (= (title-base {:seller {:name "bla"}}) "bla"))
|
||||||
|
(is (= (title-base {:seller {:team "A"}}) "A"))
|
||||||
|
(is (= (title-base {:seller {}}) ""))))
|
||||||
|
|
||||||
(deftest test-get-title
|
(deftest test-get-title
|
||||||
(testing "Check whether getting titles works"
|
(testing "Check whether getting titles works"
|
||||||
(is (= (get-title nil "mr blobby" "2019/02/11") "mr_blobby_luty_2019_02_11"))
|
(is (= (get-title {:title "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"))))
|
(is (= (get-title {:seller {:team "asd" :name "mr blobby"}} "2019/02/11") "asd_mr_blobby_luty_2019_02_11"))))
|
||||||
|
|
||||||
(deftest test-get-pdf
|
(deftest test-get-pdf
|
||||||
(testing "Check whether generating pdf bodies works"
|
(testing "Check whether generating pdf bodies works"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user