From 48499f9536d9e72a1436737d6886d1e59fd6e06d Mon Sep 17 00:00:00 2001 From: Daniel O'Connell Date: Thu, 19 Sep 2019 22:23:37 +0200 Subject: [PATCH] Generate invoice pdfs --- LICENSE | 277 ++++++++++++++++++++++++++++++++++++++ project.clj | 13 ++ resources/config.edn | 15 +++ src/invoices/core.clj | 75 +++++++++++ src/invoices/jira.clj | 29 ++++ src/invoices/pdf.clj | 88 ++++++++++++ src/invoices/settings.clj | 9 ++ 7 files changed, 506 insertions(+) create mode 100644 LICENSE create mode 100644 project.clj create mode 100644 resources/config.edn create mode 100644 src/invoices/core.clj create mode 100644 src/invoices/jira.clj create mode 100644 src/invoices/pdf.clj create mode 100644 src/invoices/settings.clj diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d3087e4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..40b0158 --- /dev/null +++ b/project.clj @@ -0,0 +1,13 @@ +(defproject invoices "0.1.0-SNAPSHOT" + :description "Generate invoices on the basis of a config file" + :url "https://github.com/mruwnik/invoices" + :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" + :url "https://www.eclipse.org/legal/epl-2.0/"} + :dependencies [[org.clojure/clojure "1.10.0"] + [org.clojure/tools.cli "0.4.2"] + [clj-http "3.10.0"] + [cheshire "5.9.0"] + [clj-pdf "2.4.0"]] + :main ^:skip-aot invoices.core + :target-path "target/%s" + :profiles {:uberjar {:aot :all}}) diff --git a/resources/config.edn b/resources/config.edn new file mode 100644 index 0000000..6d28372 --- /dev/null +++ b/resources/config.edn @@ -0,0 +1,15 @@ +[{: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"} + {:vat 21 :hourly 43.12 :title "Usługa szewska"} + {:vat 23 :base 4300.00 :per-day 4 :title "Praca za ladą"}] + :credentials {:tempo-token "5zq7zF9LADefEGAs12eDDas3FDttiM" + :jira-token "qypaAsdFwASasEddDDddASdC" + :jira-user "mr.blobby@boots.rs"}}] diff --git a/src/invoices/core.clj b/src/invoices/core.clj new file mode 100644 index 0000000..9ee23aa --- /dev/null +++ b/src/invoices/core.clj @@ -0,0 +1,75 @@ +(ns invoices.core + (:require [invoices.pdf :as pdf] + [invoices.settings :refer [invoices]] + [invoices.jira :refer [prev-timesheet]] + [clojure.tools.cli :refer [parse-opts]] + [clojure.string :as str]) + (:gen-class)) + +(defn invoice-number [when number] + (->> [(or number 1) (-> when .getMonthValue) (-> when .getYear)] (map str) (str/join "/"))) + +(defn calc-part-time [when creds {base :base per-day :per-day}] + (let [{worked :worked total :required} (prev-timesheet when creds) + hourly (/ (* base 8) (* total per-day))] + (float (* hourly worked)))) + +(defn calc-hourly [when creds {hourly :hourly}] + (-> (prev-timesheet when creds) :worked (* hourly))) + + +(defn set-price [when creds item] + (cond + (contains? item :hourly) (assoc item :netto (calc-hourly when creds item)) + (contains? item :base) (assoc item :netto (calc-part-time when creds item)) + (not (contains? item :netto)) (assoc item :netto 0) + :else item)) + +(defn for-month [when {seller :seller buyer :buyer items :items creds :credentials} & [number]] + (pdf/render seller buyer + (map (partial set-price when creds) items) + (pdf/last-working-day when) + (invoice-number when number))) + +(defn get-invoices [nips config] + (if (seq nips) + (filter #(some #{(-> % :buyer :nip str)} nips) (invoices config)) + (invoices config))) + + +(def cli-options + [["-n" "--number NUMBER" "Invoice number. In the case of multiple invoices, they will have subsequent numbers" + :default 1 + :parse-fn #(Integer/parseInt %)] + ["-w" "--when DATE" "The month for which to generate the invoice" + :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" + :default [] + :assoc-fn (fn [m k v] (update m k conj v))] + ["-h" "--help"]]) + +(defn exit [status msg] + (println msg) + (System/exit status)) + +(defn help [summary] + (->> ["Generate invoices for preconfigured companies, provided via a config file" "" + "Usage: faktury [options] " "" + summary] + (str/join "\n") + (exit 0))) + +(defn -main + "I don't do a whole lot ... yet." + [& args] + (let [{:keys [options arguments errors summary]} (parse-opts args cli-options)] + (cond + (:help options) (help summary) + errors (exit -1 (str/join "\n" errors)) + (not= 1 (count arguments)) (exit -1 "No config file provided")) + (println "Generating invoices") + (doseq [[i invoice] (map-indexed vector (get-invoices (:company options) (first arguments)))] + (for-month (:when options) invoice (+ i (:number options)))) + )) diff --git a/src/invoices/jira.clj b/src/invoices/jira.clj new file mode 100644 index 0000000..7d65881 --- /dev/null +++ b/src/invoices/jira.clj @@ -0,0 +1,29 @@ +(ns invoices.jira + (:require [clj-http.client :as client])) + +(defn tempo [{tempo-token :tempo-token} endpoint params] + (-> (str "https://api.tempo.io/core/3" endpoint) + (client/get {:oauth-token tempo-token :as :json :query-params params}) + :body)) + +(defn jira [{jira-user :jira-user jira-token :jira-token} endpoint] + (-> (str "https://clearcodehq.atlassian.net/rest/api/3/" endpoint) + (client/get {:basic-auth [jira-user jira-token] :as :json}) + :body)) + +(defn me [credentials] (:accountId (jira credentials "/myself"))) + +(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)) + (timesheet))) + +(defn prev-timesheet + "Get the timesheet for the previous month" + [when credentials] (get-timesheet (me credentials) (prev-month when) credentials)) diff --git a/src/invoices/pdf.clj b/src/invoices/pdf.clj new file mode 100644 index 0000000..b9a6011 --- /dev/null +++ b/src/invoices/pdf.clj @@ -0,0 +1,88 @@ +(ns invoices.pdf + (:require [clj-pdf.core :refer [pdf]] + [clojure.string :as str])) + + +(defn round [val] + (float (/ (Math/round (* val 100.0)) 100))) + +(defn vat [{netto :netto vat-level :vat}] + (round (* 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)]) + +(defn format-param [param] [:cell.param {:align :right} (str param ": ")]) +(defn format-value [value] [:cell {:colspan 2} (str value)]) + +(defn format-product [{netto :netto vat-level :vat title :title :as item}] + (concat + [[:cell {:colspan 4} title]] + (map str [1 (-> netto round str) (str vat-level "%") (vat item) (brutto item)]))) + + +(defn get-title [team who which] + (let [[nr month year] (-> which (str/split #"/")) + months ["" "styczen" "luty" "marzec" "kwiecien" "maj" "czerwiec" "lipiec" + "sierpien" "wrzesien" "pazdziernik" "listopad" "grudzien"]] + (-> (str/join "_" (remove nil? [team who (nth months (Integer/parseInt month)) which])) + (str/replace #"[ -/]" "_")))) + + +(defn render [seller buyer items when number] + (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 {:encoding :unicode :ttf-name "/usr/share/fonts/gsfonts/NimbusSans-Regular.otf"} + :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))] + [(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")))) + + +(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)) diff --git a/src/invoices/settings.clj b/src/invoices/settings.clj new file mode 100644 index 0000000..55f82cb --- /dev/null +++ b/src/invoices/settings.clj @@ -0,0 +1,9 @@ +(ns invoices.settings + (:require [clojure.edn :as edn])) + +(defn load-config + "Given a filename, load & return a config file" + [filename] + (edn/read-string (slurp filename))) + +(defn invoices [config] (load-config config))