1 {-# LANGUAGE OverloadedLists #-}
2 {-# LANGUAGE PolyKinds #-}
4 {-# LANGUAGE UndecidableInstances #-}
5 {-# OPTIONS_GHC -Wno-orphans #-}
7 module Literate.Invoice.HTML where
9 import Data.Time.Format.ISO8601 qualified as Time
10 import Data.Time.LocalTime qualified as Time
11 import Literate.Accounting.Math
12 import Literate.Document qualified as Doc
13 import Literate.Document.HTML
14 import Literate.Document.Type (Block (BlockPara))
15 import Literate.Invoice.Invoice
16 import Literate.Prelude
17 import Paths_literate_invoice qualified as Self
18 import System.FilePath.Posix ((</>))
19 import System.FilePath.Posix qualified as File
20 import Text.Blaze.Html5.Attributes qualified as HA
21 import Text.Printf qualified as Printf
22 import Prelude qualified
24 -- import Text.Blaze.Html5 qualified as H
25 class HTMLIOable a where
26 htmlIO :: a -> IO Html
28 instance ToMarkup Address where
29 toMarkup Address{..} = do
30 div ! classes ["address"] $ do
31 forM_ addressText \t ->
33 div ! classes ["address-bottom"] $ do
34 div $ addressZipCode & toHtml
35 div $ addressCity & toHtml
36 div $ addressCountry & toHtml
37 instance ToMarkup (String, Entity) where
38 toMarkup (pos, Entity{..}) = do
39 div ! classes ["entity"] $ do
40 div ! classes ["key-value", "entity-name"] $ do
41 div ! classes ["key"] $ do
43 ("\x202F:" :: String) & toHtml
44 div ! classes ["value"] $ do
46 div ! classes ["entity-address"] $ do
47 entityAddress & toHtml
51 div ! classes ["key-value", "entity-siren"] $ do
52 div ! classes ["key"] $ do
53 ("SIREN\x202F:" :: String) & toHtml
54 div ! classes ["value"] $ do
59 div ! classes ["key-value", "entity-email"] $ do
60 div ! classes ["key"] $ do
61 ("Email\x202F:" :: String) & toHtml
62 div ! classes ["value"] $ do
63 a ! HA.href ("mailto:" <> toValue email) $ do
66 instance (ToMarkup (Quantity qf), UnitShowS unit) => ToMarkup (Amount qf unit) where
68 (amountQuantity & toMarkup)
69 <> (if null unit then "" else "\x202F" <> (unit & toHtml))
72 instance (Doc.ToInlines (Quantity qf), UnitShowS unit) => Doc.ToInlines (Amount qf unit) where
73 toInlines Amount{..} =
74 (amountQuantity & Doc.toInlines)
75 <> (if null unit then "" else "\x202F" <> (unit & Doc.toInlines))
78 instance (Doc.ToBlock (Quantity qf), QuantFact qf, UnitShowS unit) => Doc.ToBlock (Amount qf unit) where
79 toBlock x = Doc.BlockPara $ x & Doc.toInlines
81 instance QuantFact qf => ToMarkup (Quantity qf) where
86 & (Prelude.toRational >>> Prelude.fromRational :: Ratio Natural -> Double)
87 & ( `Printf.formatArg`
90 , fmtAlternate = False
95 quantisationFactor @qf
96 & (Prelude.fromIntegral :: _ -> Double)
104 instance QuantFact qf => Doc.ToInlines (Quantity qf) where
108 & quantityToRatio @qf
109 & (Prelude.toRational >>> Prelude.fromRational :: Ratio Natural -> Double)
110 & ( `Printf.formatArg`
112 { fmtAdjust = Nothing
113 , fmtAlternate = False
118 quantisationFactor @qf
119 & (Prelude.fromIntegral :: _ -> Double)
127 instance QuantFact qf => Doc.ToBlock (Quantity qf) where
128 toBlock x = Doc.BlockPara $ x & Doc.toInlines
130 instance HTMLIOable (InvoiceId, Invoice) where
131 htmlIO (invoiceId, invoice@Invoice{..}) = do
132 -- FIXME: this absolute path is not portable out of my system
133 dataPath <- Self.getDataDir <&> File.normalise
134 -- paperCSS <- dataPath </> "styles" </> "Paper.css" & BS.readFile <&> Text.decodeUtf8
135 -- invoiceCSS <- dataPath </> "styles" </> "Invoice.css" & BS.readFile <&> Text.decodeUtf8
141 ( [ "styles/Document.css"
144 , "styles/Invoice.css"
150 ! HA.rel "stylesheet"
151 ! HA.type_ "text/css"
152 ! HA.href (dataPath </> cssFile & toValue)
153 -- styleCSS $ cssPrintPage pageOrientation pageSize
154 -- styleCSS $ pagesDifficulties & difficultyCSS
157 ! classes ["A4", "portrait", "sheet"]
158 ! styles ["size" := "A4 portrait"]
160 div ! classes ["invoice"] $ do
161 div ! classes ["key-value", "invoice-id"] $ do
162 div ! classes ["key"] $ do
163 ("Invoice#\x202F:" :: String) & toHtml
164 div ! classes ["value"] $ do
166 div ! classes ["key-value", "invoice-creation"] $ do
167 div ! classes ["key"] $ do
168 ("Date\x202F:" :: String) & toHtml
169 div ! classes ["value"] $ do
170 invoiceCreation & Time.localDay & Time.iso8601Show & toHtml
171 div ! classes ["invoice-from-to"] $ do
172 div ! classes ["invoice-issuer"] $ do
173 toHtml $ ("Seller" :: String) := invoiceIssuer
174 div ! classes ["invoice-recipient"] $ do
175 toHtml $ ("Buyer" :: String) := invoiceCustomer
179 { Doc.flexDirection = Doc.FlexDirectionColumn
180 , Doc.flexGap = 0.5 & Doc.cm
183 { Doc.flexItemContent =
186 { Doc.tableTemplate =
187 [ Doc.LengthRelative $ Doc.LengthRelativeMaxContent
188 , Doc.LengthRelative $ 5 & Doc.fr
189 , Doc.LengthRelative $ 1 & Doc.fr
190 , Doc.LengthRelative $ 1 & Doc.fr
191 , Doc.LengthRelative $ 1 & Doc.fr
193 , Doc.tableRowsEvenOdd = True
196 { Doc.tableHeadColumns =
197 [ Doc.tableCell{Doc.tableCellContent = "#"}
198 , Doc.tableCell{Doc.tableCellContent = "Description"}
199 , Doc.tableCell{Doc.tableCellContent = "Rate (excl.\xA0taxes.)"}
200 , Doc.tableCell{Doc.tableCellContent = "Quantity"}
201 , Doc.tableCell{Doc.tableCellContent = "Total (excl.\xA0taxes.)"}
209 { Doc.tableCellContent = itemCount & Doc.toBlock
210 , Doc.tableCellJustify = Doc.JustificationEnd
213 { Doc.tableCellContent = invoiceItem & invoiceItemDescription & Doc.toBlock
214 , Doc.tableCellJustify = Doc.JustificationLeft
217 { Doc.tableCellContent = invoiceItem & invoiceItemRate & Doc.toBlock
218 , Doc.tableCellJustify = Doc.JustificationEnd
221 { Doc.tableCellContent = invoiceItem & invoiceItemQuantity & Doc.toBlock
222 , Doc.tableCellJustify = Doc.JustificationEnd
225 { Doc.tableCellContent = invoiceItem & invoiceItemTotal & Doc.toBlock
226 , Doc.tableCellJustify = Doc.JustificationEnd
230 | (itemCount, invoiceItem) <- invoiceItems & ol1
236 { Doc.flexItemContent =
243 [ Doc.tableCell{Doc.tableCellContent = "Total quantity"}
244 , Doc.tableCell{Doc.tableCellContent = "Total (excl. taxes)"}
249 { Doc.tableRowColumns =
251 { Doc.tableCellJustify = Doc.JustificationCenter
252 , Doc.tableCellContent =
254 [ itm & invoiceItemQuantity
255 | itm <- invoiceItems
261 { Doc.tableCellJustify = Doc.JustificationCenter
262 , Doc.tableCellContent =
264 [ itm & invoiceItemTotal
265 | itm <- invoiceItems