Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,5 @@ dev.token

.cache/
gmail.password
resources/invoice.template.html

.DS_Store
5 changes: 2 additions & 3 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@
metosin/jsonista {:mvn/version "0.3.13"}
com.draines/postal {:mvn/version "2.0.5"}
hiccup/hiccup {:mvn/version "2.0.0-RC5"}
;; seancorfield/next.jdbc {:mvn/version "1.2.659"}
;; org.xerial/sqlite-jdbc {:mv/version "3.49.1.0"}
}}
clj-htmltopdf/clj-htmltopdf {:mvn/version "0.2.2"}
etaoin/etaoin {:mvn/version "1.1.43"}}}
159 changes: 159 additions & 0 deletions resources/invoice.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/* invoice.css */
body {
font-family: Helvetica, Arial, sans-serif;
color: #000;
background: #fff;
margin: 0;
padding: 0;
}

.invoice-container {
max-width: 800px;
margin: 0 auto;
padding: 40px;
box-sizing: border-box;
}

.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
}

.invoice-number {
font-size: 36px;
margin: 0;
line-height: 1;
}

.banking-details {
text-align: right;
font-size: 12px;
line-height: 1.4;
}

.banking-details h2 {
margin: 0 0 8px;
font-size: 20px;
}

.banking-details p {
margin: 4px 0;
}

.banking-details .contact {
margin-top: 8px;
}

.vat-note {
font-size: 10px;
margin: 8px 0 24px 0;
}

.invoice-meta {
font-size: 12px;
margin-bottom: 24px;
}

/* .info-grid using floats */
.info-grid {
overflow: auto;
margin-bottom: 24px;
}
.info-grid::after {
content: "";
display: table;
clear: both;
}
.info-grid > div {
float: left;
width: 30%;
margin-right: 5%;
font-size: 12px;
line-height: 1.4;
}
.info-grid > div:nth-child(3n) {
margin-right: 0;
}
.info-grid > div:nth-child(n+4) {
margin-top: 16px;
}
.info-grid strong {
display: block;
margin-bottom: 4px;
}

/* Ensure table clears any floats above it */
.items {
clear: both;
width: 100%;
border-collapse: collapse;
font-size: 12px;
margin-bottom: 24px;
}
.items th,
.items td {
padding: 8px 4px;
}
.items th {
border-bottom: 2px solid #000;
font-weight: bold;
}
/* Align numeric columns’ headers to the right */
.items th:nth-child(2),
.items th:nth-child(3),
.items th:nth-child(4) {
text-align: right;
}
.items td {
border-bottom: 1px solid #ddd;
}
.items td.right {
text-align: right;
}

/* summary-notes with floats */
.summary-notes {
overflow: auto;
margin-bottom: 40px;
font-size: 12px;
}
.summary-notes::after {
content: "";
display: table;
clear: both;
}
.notes {
float: left;
width: 70%;
line-height: 1.4;
}

/* totals as table */
.totals {
display: table;
margin-left: auto;
}
.total-row {
display: table-row;
}
.total-label,
.total-value {
display: table-cell;
padding: 2px 8px;
}
.total-value {
text-align: right;
}
/* bold for the total row */
.total-row.total {
font-weight: bold;
}

footer {
text-align: center;
font-size: 10px;
margin-top: 40px;
}

80 changes: 80 additions & 0 deletions src/coggl/invoice/components.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
(ns coggl.invoice.components
(:require [coggl.invoice.util :as util]))

(defn invoice
[& {:keys [company-name
invoice-for
company-address
company-contact
company-banking
entries ;; [{:description \"...\" :quantity n :unit-price p :total-price t} …]
adjustments
due-date
invoice-number]}]
(let [subtotal (reduce + (map :total-price entries))
total (- subtotal adjustments)]
[:html {:lang "en"}
[:head
[:meta {:charset "UTF-8"}]
[:title (str "Invoice " invoice-number)]
;; inline CSS placeholder — invoice-css must be bound to your stylesheet string
[:style {:type "text/css"} (util/invoice-css)]]
[:body
[:div.invoice-container

;; Header
[:header.header
[:h1.invoice-number invoice-number]
[:div.banking-details
[:h2 company-name]
[:p company-address]
[:p company-banking]
[:p.contact company-contact]]]

;; VAT note
[:p.vat-note "not a registered VAT vendor"]

;; Info grid: three columns
[:section.info-grid
[:div
[:strong "Invoice for"] [:br]
invoice-for]
[:div
[:strong "Invoice #"] [:br]
invoice-number]
[:div
[:strong "Due date"] [:br]
due-date]]

;; Line items
[:table.items
[:thead
[:tr
[:th "Description"]
[:th "Qty (h)"]
[:th "Unit Price"]
[:th "Total Price"]]]
[:tbody
(for [{:keys [description quantity unit-price total-price]} entries]
[:tr
[:td description]
[:td.right (str quantity)]
[:td.right (str unit-price)]
[:td.right (str total-price)]])]]

;; Summary totals (table layout)
[:div.summary-notes
[:div.totals
[:div.total-row
[:span.total-label "Subtotal"]
[:span.total-value (str subtotal)]]
[:div.total-row
[:span.total-label "Adjustments"]
[:span.total-value (str adjustments)]]
[:div.total-row.total
[:span.total-label "Total"]
[:span.total-value (str total)]]]]

;; Footer
[:footer "not a registered VAT Vendor"]]]]))

14 changes: 14 additions & 0 deletions src/coggl/invoice/core.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
(ns coggl.invoice.core
(:require [clj-htmltopdf.core :refer [->pdf]]
[hiccup.page :as h]
[coggl.invoice.components :as invoice]
[coggl.invoice.util :as util]))

(defn render [app]
(h/html5 app))

(defn generate-invoice [{:keys [invoice-number] :as invoice-data}]
(-> (invoice/invoice invoice-data)
(render)
(->pdf (util/destination-path (str invoice-number ".pdf")))))

25 changes: 25 additions & 0 deletions src/coggl/invoice/pdf.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
(ns coggl.invoice.pdf
(:require [etaoin.api :as e]
[coggl.invoice.components :as inv]))

(def mock-data {:total 19000
:subtotal 19000
:adjustments 0
:due-date "immediate"
:company-name "ModulR (Pty) Ltd"
:invoice-for "Appsquare (Pty) Ltd"
:invoice-number "APSQ001"
:company-address "302 waterford place, camargue boulevard, macassar, cape town 7130"
:company-banking "Account Type: Checking\nBank: Capitec Business\nAccount Nr: 1234567890\nAccount holder: ModulR (Pty) Ltd"
:submission-date "05/05/2025"
:company-contact "+26 84 254 8270"
:entries [{:description "zohm react rebuild"
:quantity 80
:unit-price 250
:total-price 20000}]})

(def driver (e/chrome-headless))

(e/with-chrome-headless driver
(e/go driver "https://www.youtube.com")
(e/print-page driver "target/etaoin-play/printed.pdf"))
8 changes: 8 additions & 0 deletions src/coggl/invoice/util.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
(ns coggl.invoice.util
(:require [coggl.config :as conf]))

(defn invoice-css []
(slurp (:invoice-css-path conf/env)))

(defn destination-path [f]
(str (:cache-dir conf/env) "/" f))