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:
|
||||
|
||||
* :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
|
||||
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
|
||||
@ -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
|
||||
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
|
||||
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
|
||||
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
|
||||
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}
|
||||
{: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"]}
|
||||
{:vat 23 :function (* :worked (+ 1 2 3 (- 23 13))) :title "Pucowania obuwia"}
|
||||
{:vat 23 :base 4300.00 :per-day 4 :title "Praca za ladą"}]
|
||||
:smtp {:host "smtp.gmail.com" :user "mr.blobby@buty.sa" :pass "asd;l;kjsdfkljld" :ssl true}}]
|
||||
:worklogs [{:type :jira
|
||||
:ids [:from-jira]
|
||||
:tempo-token "5zq7zF9LADefEGAs12eDDas3FDttiM"
|
||||
:jira-token "qypaAsdFwASasEddDDddASdC"
|
||||
:jira-user "mr.blobby@boots.rs"}
|
||||
{:type :imap
|
||||
:ids [:item1 :item2]
|
||||
:folder "inbox"
|
||||
:host "imap.gmail.com"
|
||||
:user "mr.blobby@boots.rs"
|
||||
:pass "lksjdfklsjdflkjw"
|
||||
:from "hr@boots.rs"
|
||||
:subject "'Hours 'YYYY-MM"
|
||||
:headers [:id :worked]}]}
|
||||
{:vat 23 :function (* 23 (+ 1 2 3 (- 23 13))) :title "Pucowania obuwia - stały koszt"}
|
||||
;; {: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
|
||||
{:vat 23 :base 4300.00 :per-day 4 :title "Praca za ladą" :worklog :item2}]
|
||||
;; :smtp {:host "smtp.gmail.com" :user "mr.blobby@buty.sa" :pass "asd;l;kjsdfkljld" :ssl true}
|
||||
}]
|
||||
:worklogs [
|
||||
;; {:type :jira
|
||||
;; :ids [:from-jira]
|
||||
;; :tempo-token "5zq7zF9LADefEGAs12eDDas3FDttiM"
|
||||
;; :jira-token "qypaAsdFwASasEddDDddASdC"
|
||||
;; :jira-user "mr.blobby@boots.rs"}
|
||||
;; {:type :imap
|
||||
;; :ids [:item1 :item2]
|
||||
;; :folder "inbox"
|
||||
;; :host "imap.gmail.com"
|
||||
;; :user "mr.blobby@boots.rs"
|
||||
;; :pass "lksjdfklsjdflkjw"
|
||||
;; :from "hr@boots.rs"
|
||||
;; :subject "'Hours 'YYYY-MM"
|
||||
;; :headers [:id :worked]}
|
||||
]}
|
||||
|
@ -19,16 +19,16 @@
|
||||
:else func))
|
||||
|
||||
(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))]
|
||||
(float (* hourly worked))))
|
||||
(float (* hourly worked)))))
|
||||
|
||||
(defn calc-hourly [{worked :worked} {hourly :hourly}]
|
||||
(* worked hourly))
|
||||
(when worked (* 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]
|
||||
|
@ -27,14 +27,17 @@
|
||||
|
||||
(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))]
|
||||
(email/send-invoice file (:email buyer) smtp)
|
||||
;; (email/send-invoice file (:email buyer) smtp)
|
||||
(run-callbacks file callbacks)))
|
||||
|
||||
(defn item-price [worklogs item]
|
||||
(-> item :worklog (worklogs) (set-price item)))
|
||||
|
||||
(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]
|
||||
(-> invoice
|
||||
@ -54,7 +57,7 @@
|
||||
:default 1
|
||||
:parse-fn #(Integer/parseInt %)]
|
||||
["-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 %)]
|
||||
;; A non-idempotent option (:default is applied first)
|
||||
["-c" "--company NIP" "companies for which to generate invoices. All, if not provided"
|
||||
@ -83,8 +86,8 @@
|
||||
(not= 1 (count arguments)) (exit -1 "No config file provided"))
|
||||
|
||||
(println "Generating invoices" )
|
||||
(let [month (java.time.LocalDate/now)
|
||||
config (-> "config.edn" (invoices month))
|
||||
(let [month (:when options)
|
||||
config (-> (first arguments) (invoices month))
|
||||
worklogs (timesheets month (:worklogs config))]
|
||||
(process-invoices config month worklogs)))
|
||||
(shutdown-agents))
|
||||
|
@ -69,14 +69,27 @@
|
||||
(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))))
|
||||
(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
|
||||
(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))))
|
||||
(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
|
||||
|
@ -42,10 +42,25 @@
|
||||
(is (nil? (format-notes [])))
|
||||
(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
|
||||
(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"))))
|
||||
(is (= (get-title {:title "mr blobby"} "2019/02/11") "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
|
||||
(testing "Check whether generating pdf bodies works"
|
||||
|
Loading…
x
Reference in New Issue
Block a user