mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-06 02:36:49 +00:00
feat(pdf): improve invoice PDF layout and style
Some checks failed
CI / checks (push) Has been cancelled
Some checks failed
CI / checks (push) Has been cancelled
Redesigned the invoice PDF with a more modern, modular layout. Updated colors, spacing, and section organization for better readability and visual appeal. Added a summary block, improved table formatting, and refined header/footer presentation. Also updated dependencies in go.mod and go.sum to support new features.
This commit is contained in:
parent
24f289b767
commit
e8bfcf26f3
3 changed files with 287 additions and 137 deletions
19
go.mod
19
go.mod
|
|
@ -4,11 +4,13 @@ go 1.26
|
|||
|
||||
require (
|
||||
github.com/a-h/templ v0.3.1001
|
||||
github.com/getsentry/sentry-go v0.43.0
|
||||
github.com/getsentry/sentry-go v0.44.1
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/gorilla/sessions v1.4.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/jung-kurt/gofpdf/v2 v2.17.3
|
||||
github.com/minio/minio-go/v7 v7.0.99
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
||||
github.com/rjNemo/underscore v0.10.0
|
||||
github.com/stripe/stripe-go/v83 v83.2.1
|
||||
|
|
@ -21,30 +23,29 @@ require (
|
|||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jung-kurt/gofpdf/v2 v2.17.3 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/minio/crc64nvme v1.1.1 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.99 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/tinylib/msgp v1.6.1 // indirect
|
||||
github.com/tinylib/msgp v1.6.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/crypto v0.49.0 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.8.0 // indirect
|
||||
github.com/jackc/pgx/v5 v5.9.1 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
|
|
|
|||
39
go.sum
39
go.sum
|
|
@ -2,13 +2,14 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk
|
|||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/a-h/templ v0.3.1001 h1:yHDTgexACdJttyiyamcTHXr2QkIeVF1MukLy44EAhMY=
|
||||
github.com/a-h/templ v0.3.1001/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/getsentry/sentry-go v0.43.0 h1:XbXLpFicpo8HmBDaInk7dum18G9KSLcjZiyUKS+hLW4=
|
||||
github.com/getsentry/sentry-go v0.43.0/go.mod h1:XDotiNZbgf5U8bPDUAfvcFmOnMQQceESxyKaObSssW0=
|
||||
github.com/getsentry/sentry-go v0.44.1 h1:/cPtrA5qB7uMRrhgSn9TYtcEF36auGP3Y6+ThvD/yaI=
|
||||
github.com/getsentry/sentry-go v0.44.1/go.mod h1:XDotiNZbgf5U8bPDUAfvcFmOnMQQceESxyKaObSssW0=
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||
|
|
@ -31,8 +32,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
|||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
|
||||
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
|
||||
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
|
||||
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
|
|
@ -43,13 +44,17 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jung-kurt/gofpdf/v2 v2.17.3 h1:otZXZby2gXJ7uU6pzprXHq/R57lsHLi0WtH79VabWxY=
|
||||
github.com/jung-kurt/gofpdf/v2 v2.17.3/go.mod h1:Qx8ZNg4cNsO5i6uLDiBngnm+ii/FjtAqjRNO6drsoYU=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
|
||||
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
|
||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
|
|
@ -70,6 +75,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rjNemo/underscore v0.10.0 h1:f0WTiHXujG9mgbEt51VH06TqLMS5n4EUKtp5wzhBqQM=
|
||||
github.com/rjNemo/underscore v0.10.0/go.mod h1:g2nURJw5INpBuh8ie0AjK1KLnsHSA+Tzc+oXiooq7ms=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/sethvargo/go-envconfig v1.3.0 h1:gJs+Fuv8+f05omTpwWIu6KmuseFAXKrIaOZSh8RMt0U=
|
||||
|
|
@ -92,18 +99,16 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
|||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
|
||||
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
|
||||
github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
|
|
@ -111,6 +116,8 @@ golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/jung-kurt/gofpdf/v2"
|
||||
|
||||
|
|
@ -16,12 +17,14 @@ import (
|
|||
type HTMLPdfClient struct{}
|
||||
|
||||
const (
|
||||
invoiceMarginX = 10.0
|
||||
invoiceContentWidth = 190.0
|
||||
invoiceHeaderHeight = 28.0
|
||||
invoiceTableRowH = 8.5
|
||||
invoiceSummaryLineH = 10.0
|
||||
invoiceNotesPadding = 3.0
|
||||
invoiceMarginX = 10.0
|
||||
invoiceContentWidth = 190.0
|
||||
invoiceHeaderHeight = 32.0
|
||||
invoiceTableRowH = 9.0
|
||||
invoiceSummaryLineH = 9.0
|
||||
invoiceNotesPadding = 4.0
|
||||
invoiceColGap = 6.0
|
||||
invoiceColWidth = (invoiceContentWidth - invoiceColGap) / 2.0
|
||||
)
|
||||
|
||||
type rgbColor struct {
|
||||
|
|
@ -32,10 +35,13 @@ type rgbColor struct {
|
|||
|
||||
var (
|
||||
invoiceTeal = rgbColor{0, 123, 143}
|
||||
invoiceLightGray = rgbColor{244, 244, 244}
|
||||
invoiceMidGray = rgbColor{221, 221, 221}
|
||||
invoiceTextGray = rgbColor{85, 85, 85}
|
||||
invoiceTealDark = rgbColor{0, 98, 114}
|
||||
invoiceLightGray = rgbColor{248, 249, 250}
|
||||
invoiceMidGray = rgbColor{222, 226, 230}
|
||||
invoiceTextGray = rgbColor{73, 80, 87}
|
||||
invoiceTextMuted = rgbColor{108, 117, 125}
|
||||
invoiceNoteBg = rgbColor{237, 247, 249}
|
||||
invoiceWhite = rgbColor{255, 255, 255}
|
||||
)
|
||||
|
||||
func NewPdfClient() (*HTMLPdfClient, error) {
|
||||
|
|
@ -44,7 +50,7 @@ func NewPdfClient() (*HTMLPdfClient, error) {
|
|||
|
||||
func (pc *HTMLPdfClient) BuildInvoice(data booking.Invoice) (*booking.GeneratedFile, error) {
|
||||
pdf := gofpdf.New("P", "mm", "A4", "")
|
||||
pdf.SetMargins(invoiceMarginX, 12, invoiceMarginX)
|
||||
pdf.SetMargins(invoiceMarginX, 10, invoiceMarginX)
|
||||
pdf.SetAutoPageBreak(true, 14)
|
||||
pdf.AddPage()
|
||||
|
||||
|
|
@ -55,6 +61,7 @@ func (pc *HTMLPdfClient) BuildInvoice(data booking.Invoice) (*booking.GeneratedF
|
|||
drawInvoicePayments(pdf, tr, data)
|
||||
drawInvoiceSummary(pdf, tr, data)
|
||||
drawInvoiceNotes(pdf, tr, data)
|
||||
drawInvoiceFooter(pdf, tr, data)
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := pdf.Output(&buf); err != nil {
|
||||
|
|
@ -90,9 +97,17 @@ func (pc *HTMLPdfClient) BuildReport(report booking.ReportData, period string, m
|
|||
}
|
||||
|
||||
func drawInvoiceHeader(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
||||
pdf.SetFillColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
||||
pdf.Rect(invoiceMarginX, 12, invoiceContentWidth, invoiceHeaderHeight, "F")
|
||||
topY := 10.0
|
||||
|
||||
// Full-width teal banner
|
||||
pdf.SetFillColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
||||
pdf.Rect(invoiceMarginX, topY, invoiceContentWidth, invoiceHeaderHeight, "F")
|
||||
|
||||
// Subtle darker teal accent strip at very top
|
||||
pdf.SetFillColor(invoiceTealDark.r, invoiceTealDark.g, invoiceTealDark.b)
|
||||
pdf.Rect(invoiceMarginX, topY, invoiceContentWidth, 1.2, "F")
|
||||
|
||||
// Logo in a white rounded rectangle
|
||||
if logo, err := assets.Static.ReadFile("assets/img/logo.png"); err == nil {
|
||||
info := pdf.RegisterImageOptionsReader(
|
||||
"invoice-logo",
|
||||
|
|
@ -100,15 +115,28 @@ func drawInvoiceHeader(pdf *gofpdf.Fpdf, tr func(string) string, data booking.In
|
|||
bytes.NewReader(logo),
|
||||
)
|
||||
if info != nil {
|
||||
pdf.ImageOptions("invoice-logo", 14, 15.5, 17, 17, false, gofpdf.ImageOptions{ImageType: "PNG"}, 0, "")
|
||||
logoX := invoiceMarginX + 5
|
||||
logoY := topY + 4
|
||||
logoSize := 24.0
|
||||
// white background circle behind logo
|
||||
pdf.SetFillColor(255, 255, 255)
|
||||
pdf.SetDrawColor(255, 255, 255)
|
||||
radius := logoSize/2 + 1.5
|
||||
pdf.Circle(logoX+logoSize/2, logoY+logoSize/2, radius, "F")
|
||||
pdf.ImageOptions("invoice-logo", logoX+1.5, logoY+1.5, logoSize-3, logoSize-3, false, gofpdf.ImageOptions{ImageType: "PNG"}, 0, "")
|
||||
}
|
||||
}
|
||||
|
||||
// Host info on the right side of banner
|
||||
pdf.SetTextColor(255, 255, 255)
|
||||
pdf.SetXY(110, 14.5)
|
||||
pdf.SetFont("Helvetica", "B", 11)
|
||||
pdf.CellFormat(86, 4.8, tr(data.Host.Name), "", 1, "R", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 8.8)
|
||||
rightBlockX := 100.0
|
||||
rightBlockW := invoiceMarginX + invoiceContentWidth - rightBlockX - 4
|
||||
|
||||
pdf.SetXY(rightBlockX, topY+5)
|
||||
pdf.SetFont("Helvetica", "B", 13)
|
||||
pdf.CellFormat(rightBlockW, 5.5, tr(data.Host.Name), "", 1, "R", false, 0, "")
|
||||
|
||||
pdf.SetFont("Helvetica", "", 8.5)
|
||||
hostLines := []string{
|
||||
data.Host.Address,
|
||||
fmt.Sprintf("%s %s", data.Host.ZipCode, data.Host.City),
|
||||
|
|
@ -116,170 +144,267 @@ func drawInvoiceHeader(pdf *gofpdf.Fpdf, tr func(string) string, data booking.In
|
|||
fmt.Sprintf("Mail : %s", data.Host.Email),
|
||||
}
|
||||
for _, line := range hostLines {
|
||||
pdf.SetX(110)
|
||||
pdf.CellFormat(86, 4.0, tr(line), "", 1, "R", false, 0, "")
|
||||
pdf.SetX(rightBlockX)
|
||||
pdf.CellFormat(rightBlockW, 4.2, tr(line), "", 1, "R", false, 0, "")
|
||||
}
|
||||
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pdf.SetDrawColor(invoiceMidGray.r, invoiceMidGray.g, invoiceMidGray.b)
|
||||
pdf.Line(invoiceMarginX, 45, invoiceMarginX+invoiceContentWidth, 45)
|
||||
pdf.SetY(49)
|
||||
// Invoice number badge below banner
|
||||
pdf.Ln(5)
|
||||
badgeY := pdf.GetY()
|
||||
pdf.SetFont("Helvetica", "", 9)
|
||||
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
||||
pdf.SetX(invoiceMarginX)
|
||||
pdf.CellFormat(80, 6, tr("FACTURE"), "", 0, "L", false, 0, "")
|
||||
|
||||
pdf.SetFont("Helvetica", "B", 14)
|
||||
pdf.SetTextColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
||||
pdf.CellFormat(invoiceContentWidth-80, 6, tr(data.ID), "", 1, "R", false, 0, "")
|
||||
|
||||
pdf.SetDrawColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
||||
pdf.SetLineWidth(0.4)
|
||||
pdf.Line(invoiceMarginX, badgeY+8, invoiceMarginX+invoiceContentWidth, badgeY+8)
|
||||
pdf.SetLineWidth(0.2)
|
||||
|
||||
pdf.SetY(badgeY + 12)
|
||||
}
|
||||
|
||||
func drawInvoiceDetails(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
||||
leftX := invoiceMarginX + 4
|
||||
rightX := 103.0
|
||||
labelColor := invoiceTextGray
|
||||
startY := pdf.GetY()
|
||||
leftX := invoiceMarginX
|
||||
rightX := invoiceMarginX + invoiceColWidth + invoiceColGap
|
||||
cardPad := 5.0
|
||||
cardH := 36.0
|
||||
|
||||
writeDetail := func(x float64, label, value string, boldValue bool) {
|
||||
pdf.SetX(x)
|
||||
pdf.SetFont("Helvetica", "B", 10.5)
|
||||
pdf.SetTextColor(labelColor.r, labelColor.g, labelColor.b)
|
||||
pdf.CellFormat(38, 7, tr(label), "", 0, "", false, 0, "")
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
style := ""
|
||||
if boldValue {
|
||||
style = "B"
|
||||
}
|
||||
pdf.SetFont("Helvetica", style, 10.5)
|
||||
pdf.CellFormat(48, 7, tr(value), "", 1, "", false, 0, "")
|
||||
}
|
||||
// Left card: Customer info - light gray background
|
||||
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
||||
pdf.RoundedRect(leftX, startY, invoiceColWidth, cardH, 2, "1234", "F")
|
||||
|
||||
// Section label
|
||||
pdf.SetXY(leftX+cardPad, startY+cardPad)
|
||||
pdf.SetFont("Helvetica", "", 7.5)
|
||||
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
||||
pdf.CellFormat(invoiceColWidth-cardPad*2, 4, tr("CLIENT"), "", 1, "L", false, 0, "")
|
||||
|
||||
// Customer name
|
||||
pdf.SetX(leftX + cardPad)
|
||||
pdf.SetFont("Helvetica", "B", 12)
|
||||
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
||||
pdf.SetX(leftX)
|
||||
pdf.CellFormat(86, 7, tr(data.Name), "", 0, "", false, 0, "")
|
||||
pdf.SetX(rightX)
|
||||
pdf.CellFormat(86, 7, tr("N° de facture :"), "", 0, "", false, 0, "")
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pdf.SetFont("Helvetica", "", 11)
|
||||
pdf.CellFormat(20, 7, tr(data.ID), "", 1, "R", false, 0, "")
|
||||
pdf.CellFormat(invoiceColWidth-cardPad*2, 7, tr(data.Name), "", 1, "L", false, 0, "")
|
||||
|
||||
writeDetail(leftX, "Tel :", emptyFallback(data.PhoneNumber), false)
|
||||
writeDetail(rightX, "Du :", data.From, false)
|
||||
// Customer details
|
||||
detailLineH := 5.5
|
||||
writeLeftDetail := func(label, value string) {
|
||||
pdf.SetX(leftX + cardPad)
|
||||
pdf.SetFont("Helvetica", "", 8.5)
|
||||
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
||||
pdf.CellFormat(28, detailLineH, tr(label), "", 0, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 9)
|
||||
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
||||
pdf.CellFormat(invoiceColWidth-cardPad*2-28, detailLineH, tr(value), "", 1, "L", false, 0, "")
|
||||
}
|
||||
|
||||
writeDetail(leftX, "Nombre de clients :", fmt.Sprintf("%d", data.CustomersNumber), false)
|
||||
writeDetail(rightX, "Au :", data.To, false)
|
||||
writeLeftDetail("Tel", emptyFallback(data.PhoneNumber))
|
||||
writeLeftDetail("Clients", fmt.Sprintf("%d", data.CustomersNumber))
|
||||
writeLeftDetail("Plateforme", data.Platform)
|
||||
|
||||
writeDetail(leftX, "Plateforme :", data.Platform, false)
|
||||
writeDetail(rightX, "Montant Total :", euroAmount(data.Total), false)
|
||||
// Right card: Booking info - light gray background
|
||||
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
||||
pdf.RoundedRect(rightX, startY, invoiceColWidth, cardH, 2, "1234", "F")
|
||||
|
||||
pdf.Ln(4)
|
||||
pdf.SetDrawColor(invoiceMidGray.r, invoiceMidGray.g, invoiceMidGray.b)
|
||||
pdf.Line(invoiceMarginX, pdf.GetY(), invoiceMarginX+invoiceContentWidth, pdf.GetY())
|
||||
pdf.Ln(5)
|
||||
pdf.SetXY(rightX+cardPad, startY+cardPad)
|
||||
pdf.SetFont("Helvetica", "", 7.5)
|
||||
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
||||
pdf.CellFormat(invoiceColWidth-cardPad*2, 4, tr("SÉJOUR"), "", 1, "L", false, 0, "")
|
||||
|
||||
writeRightDetail := func(label, value string, bold bool) {
|
||||
pdf.SetX(rightX + cardPad)
|
||||
pdf.SetFont("Helvetica", "", 8.5)
|
||||
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
||||
pdf.CellFormat(28, detailLineH, tr(label), "", 0, "L", false, 0, "")
|
||||
style := ""
|
||||
if bold {
|
||||
style = "B"
|
||||
}
|
||||
pdf.SetFont("Helvetica", style, 9.5)
|
||||
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
||||
pdf.CellFormat(invoiceColWidth-cardPad*2-28, detailLineH, tr(value), "", 1, "L", false, 0, "")
|
||||
}
|
||||
|
||||
// Date range as main heading
|
||||
pdf.SetX(rightX + cardPad)
|
||||
pdf.SetFont("Helvetica", "B", 12)
|
||||
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
||||
pdf.CellFormat(invoiceColWidth-cardPad*2, 7, tr(fmt.Sprintf("%s — %s", data.From, data.To)), "", 1, "L", false, 0, "")
|
||||
|
||||
writeRightDetail("Nuits", nightsCount(data.From, data.To), false)
|
||||
writeRightDetail("Montant", euroAmount(data.Total), true)
|
||||
|
||||
pdf.SetY(startY + cardH + 8)
|
||||
}
|
||||
|
||||
func drawInvoiceItemsTable(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
||||
widths := []float64{70, 28, 39, 53}
|
||||
// Section title
|
||||
pdf.SetFont("Helvetica", "B", 11)
|
||||
pdf.SetTextColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
||||
pdf.CellFormat(0, 7, tr("Prestations"), "", 1, "L", false, 0, "")
|
||||
pdf.Ln(1)
|
||||
|
||||
widths := []float64{72, 30, 40, 48}
|
||||
headers := []string{"Objet", "Quantité", "Prix (€)", "Total (€)"}
|
||||
|
||||
writeTableHeader(pdf, tr, widths, headers)
|
||||
pdf.SetFont("Helvetica", "", 10.5)
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
|
||||
pdf.SetFont("Helvetica", "", 10)
|
||||
for i, line := range data.Lines {
|
||||
fill := i%2 == 0
|
||||
if fill {
|
||||
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
||||
}
|
||||
pdf.CellFormat(widths[0], invoiceTableRowH, tr(line.Name), "1", 0, "", fill, 0, "")
|
||||
pdf.CellFormat(widths[1], invoiceTableRowH, fmt.Sprintf("%d", line.Quantity), "1", 0, "C", fill, 0, "")
|
||||
pdf.CellFormat(widths[2], invoiceTableRowH, line.Price, "1", 0, "R", fill, 0, "")
|
||||
pdf.CellFormat(widths[3], invoiceTableRowH, line.Total, "1", 1, "R", fill, 0, "")
|
||||
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
||||
pdf.CellFormat(widths[0], invoiceTableRowH, " "+tr(line.Name), "", 0, "", fill, 0, "")
|
||||
pdf.CellFormat(widths[1], invoiceTableRowH, fmt.Sprintf("%d", line.Quantity), "", 0, "C", fill, 0, "")
|
||||
pdf.CellFormat(widths[2], invoiceTableRowH, line.Price, "", 0, "R", fill, 0, "")
|
||||
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
||||
pdf.SetFont("Helvetica", "B", 10)
|
||||
pdf.CellFormat(widths[3], invoiceTableRowH, line.Total+" ", "", 1, "R", fill, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 10)
|
||||
|
||||
// Subtle separator line
|
||||
lineY := pdf.GetY()
|
||||
pdf.SetDrawColor(invoiceMidGray.r, invoiceMidGray.g, invoiceMidGray.b)
|
||||
pdf.Line(invoiceMarginX, lineY, invoiceMarginX+invoiceContentWidth, lineY)
|
||||
}
|
||||
|
||||
pdf.Ln(6)
|
||||
pdf.Ln(8)
|
||||
}
|
||||
|
||||
func drawInvoicePayments(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
||||
pdf.SetFont("Helvetica", "B", 13)
|
||||
pdf.SetFont("Helvetica", "B", 11)
|
||||
pdf.SetTextColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
||||
pdf.CellFormat(0, 7, tr("Historique des Paiements"), "", 1, "", false, 0, "")
|
||||
pdf.CellFormat(0, 7, tr("Historique des Paiements"), "", 1, "L", false, 0, "")
|
||||
pdf.Ln(1)
|
||||
|
||||
widths := []float64{54, 84, 52}
|
||||
widths := []float64{54, 88, 48}
|
||||
headers := []string{"Date", "Mode de Paiement", "Montant (€)"}
|
||||
writeTableHeader(pdf, tr, widths, headers)
|
||||
|
||||
pdf.SetFont("Helvetica", "", 10.5)
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pdf.SetFont("Helvetica", "", 10)
|
||||
if len(data.Payments) == 0 {
|
||||
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
||||
pdf.CellFormat(widths[0], invoiceTableRowH, tr("-"), "1", 0, "", true, 0, "")
|
||||
pdf.CellFormat(widths[1], invoiceTableRowH, tr("Aucun paiement enregistré"), "1", 0, "", true, 0, "")
|
||||
pdf.CellFormat(widths[2], invoiceTableRowH, tr("0.00"), "1", 1, "R", true, 0, "")
|
||||
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
||||
pdf.CellFormat(widths[0], invoiceTableRowH, " "+tr("-"), "", 0, "", true, 0, "")
|
||||
pdf.CellFormat(widths[1], invoiceTableRowH, tr("Aucun paiement enregistré"), "", 0, "", true, 0, "")
|
||||
pdf.CellFormat(widths[2], invoiceTableRowH, tr("0.00")+" ", "", 1, "R", true, 0, "")
|
||||
} else {
|
||||
for i, payment := range data.Payments {
|
||||
fill := i%2 == 0
|
||||
if fill {
|
||||
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
||||
}
|
||||
pdf.CellFormat(widths[0], invoiceTableRowH, tr(payment.Date), "1", 0, "", fill, 0, "")
|
||||
pdf.CellFormat(widths[1], invoiceTableRowH, tr(payment.Method), "1", 0, "", fill, 0, "")
|
||||
pdf.CellFormat(widths[2], invoiceTableRowH, payment.Amount, "1", 1, "R", fill, 0, "")
|
||||
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
||||
pdf.CellFormat(widths[0], invoiceTableRowH, " "+tr(payment.Date), "", 0, "", fill, 0, "")
|
||||
pdf.CellFormat(widths[1], invoiceTableRowH, tr(payment.Method), "", 0, "", fill, 0, "")
|
||||
pdf.SetFont("Helvetica", "B", 10)
|
||||
pdf.CellFormat(widths[2], invoiceTableRowH, payment.Amount+" ", "", 1, "R", fill, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 10)
|
||||
|
||||
lineY := pdf.GetY()
|
||||
pdf.SetDrawColor(invoiceMidGray.r, invoiceMidGray.g, invoiceMidGray.b)
|
||||
pdf.Line(invoiceMarginX, lineY, invoiceMarginX+invoiceContentWidth, lineY)
|
||||
}
|
||||
}
|
||||
|
||||
pdf.Ln(6)
|
||||
pdf.Ln(8)
|
||||
}
|
||||
|
||||
func drawInvoiceSummary(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
||||
labels := []string{"Montant Total :", "Montant Payé :", "Solde Restant :"}
|
||||
values := []string{data.Total, data.AmountPaid, data.BalanceDue}
|
||||
// Right-aligned summary block
|
||||
summaryX := invoiceMarginX + invoiceContentWidth - 80
|
||||
summaryW := 80.0
|
||||
|
||||
labels := []string{"Montant Total", "Montant Payé", "Solde Restant"}
|
||||
values := []string{euroAmount(data.Total), euroAmount(data.AmountPaid), euroAmount(data.BalanceDue)}
|
||||
|
||||
for i := range labels {
|
||||
pdf.SetX(invoiceMarginX + 3)
|
||||
pdf.SetFont("Helvetica", "B", 11)
|
||||
pdf.SetX(summaryX)
|
||||
pdf.SetFont("Helvetica", "", 9.5)
|
||||
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
||||
pdf.CellFormat(summaryW/2, invoiceSummaryLineH, tr(labels[i]), "", 0, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "B", 10)
|
||||
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
||||
pdf.CellFormat(60, invoiceSummaryLineH, tr(labels[i]), "", 0, "", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 11)
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pdf.CellFormat(95, invoiceSummaryLineH, "", "", 0, "", false, 0, "")
|
||||
pdf.CellFormat(32, invoiceSummaryLineH, values[i], "", 1, "R", false, 0, "")
|
||||
pdf.CellFormat(summaryW/2, invoiceSummaryLineH, tr(values[i]), "", 1, "R", false, 0, "")
|
||||
|
||||
if i < len(labels)-1 {
|
||||
lineY := pdf.GetY()
|
||||
pdf.SetDrawColor(invoiceMidGray.r, invoiceMidGray.g, invoiceMidGray.b)
|
||||
pdf.Line(summaryX, lineY, summaryX+summaryW, lineY)
|
||||
}
|
||||
}
|
||||
|
||||
pdf.Ln(2)
|
||||
pdf.SetDrawColor(invoiceMidGray.r, invoiceMidGray.g, invoiceMidGray.b)
|
||||
pdf.Line(invoiceMarginX, pdf.GetY(), invoiceMarginX+invoiceContentWidth, pdf.GetY())
|
||||
pdf.Ln(6)
|
||||
// Highlight the balance due
|
||||
balanceY := pdf.GetY() - invoiceSummaryLineH
|
||||
pdf.SetFillColor(invoiceNoteBg.r, invoiceNoteBg.g, invoiceNoteBg.b)
|
||||
pdf.RoundedRect(summaryX-2, balanceY-0.5, summaryW+4, invoiceSummaryLineH+1, 1.5, "1234", "F")
|
||||
pdf.SetXY(summaryX, balanceY)
|
||||
pdf.SetFont("Helvetica", "B", 9.5)
|
||||
pdf.SetTextColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
||||
pdf.CellFormat(summaryW/2, invoiceSummaryLineH, tr("Solde Restant"), "", 0, "L", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "B", 11)
|
||||
pdf.CellFormat(summaryW/2, invoiceSummaryLineH, tr(euroAmount(data.BalanceDue)), "", 1, "R", false, 0, "")
|
||||
|
||||
pdf.Ln(8)
|
||||
}
|
||||
|
||||
func drawInvoiceNotes(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
||||
notes := "Notes\nTVA non applicable, art. 293 B du CGI\nDispensé d'immatriculation au registre du commerce et des sociétés (RCS) et au répertoire des métiers.\nConditions de paiement : paiement à réception de facture. Aucun escompte consenti pour règlement anticipé ou désistement. Tout incident de paiement est passible d'intérêts de retard. Le montant des pénalités résulte de l'application aux sommes restant dues d'un taux d'intérêt légal en vigueur au moment de l'incident.\nIl sera demandé 15€ par jour par personne supplémentaire."
|
||||
func drawInvoiceNotes(pdf *gofpdf.Fpdf, tr func(string) string, _ booking.Invoice) {
|
||||
notes := "TVA non applicable, art. 293 B du CGI\nDispensé d'immatriculation au registre du commerce et des sociétés (RCS) et au répertoire des métiers.\nConditions de paiement : paiement à réception de facture. Aucun escompte consenti pour règlement anticipé ou désistement. Tout incident de paiement est passible d'intérêts de retard. Le montant des pénalités résulte de l'application aux sommes restant dues d'un taux d'intérêt légal en vigueur au moment de l'incident.\nIl sera demandé 15€ par jour par personne supplémentaire."
|
||||
|
||||
pdf.SetFont("Helvetica", "", 7.5)
|
||||
lines := pdf.SplitLines([]byte(tr(notes)), invoiceContentWidth-(invoiceNotesPadding*2))
|
||||
noteBoxHeight := float64(len(lines))*3.2 + invoiceNotesPadding*2
|
||||
totalBlockHeight := noteBoxHeight + 18
|
||||
if pdf.GetY()+totalBlockHeight > 282 {
|
||||
pdf.SetFont("Helvetica", "", 7)
|
||||
lines := pdf.SplitLines([]byte(tr(notes)), invoiceContentWidth-(invoiceNotesPadding*2)-8)
|
||||
noteBoxHeight := float64(len(lines))*3.0 + invoiceNotesPadding*2 + 6
|
||||
|
||||
if pdf.GetY()+noteBoxHeight+30 > 282 {
|
||||
pdf.AddPage()
|
||||
}
|
||||
|
||||
y := pdf.GetY()
|
||||
pdf.SetFillColor(invoiceNoteBg.r, invoiceNoteBg.g, invoiceNoteBg.b)
|
||||
pdf.Rect(invoiceMarginX, y, invoiceContentWidth, noteBoxHeight, "F")
|
||||
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
||||
pdf.RoundedRect(invoiceMarginX, y, invoiceContentWidth, noteBoxHeight, 2, "1234", "F")
|
||||
|
||||
// Notes title
|
||||
pdf.SetXY(invoiceMarginX+invoiceNotesPadding, y+invoiceNotesPadding)
|
||||
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
||||
pdf.MultiCell(invoiceContentWidth-(invoiceNotesPadding*2), 3.2, tr(notes), "", "L", false)
|
||||
pdf.SetFont("Helvetica", "B", 7.5)
|
||||
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
||||
pdf.CellFormat(invoiceContentWidth-(invoiceNotesPadding*2), 4, tr("MENTIONS LÉGALES"), "", 1, "L", false, 0, "")
|
||||
|
||||
pdf.Ln(4)
|
||||
pdf.SetDrawColor(invoiceMidGray.r, invoiceMidGray.g, invoiceMidGray.b)
|
||||
pdf.Line(invoiceMarginX, pdf.GetY(), invoiceMarginX+invoiceContentWidth, pdf.GetY())
|
||||
pdf.Ln(4)
|
||||
pdf.SetX(invoiceMarginX + invoiceNotesPadding)
|
||||
pdf.SetFont("Helvetica", "", 7)
|
||||
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
||||
pdf.MultiCell(invoiceContentWidth-(invoiceNotesPadding*2), 3.0, tr(notes), "", "L", false)
|
||||
|
||||
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
||||
pdf.SetFont("Helvetica", "B", 11)
|
||||
pdf.CellFormat(0, 5, tr("Total"), "", 1, "R", false, 0, "")
|
||||
pdf.SetTextColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
||||
pdf.SetFont("Helvetica", "B", 15)
|
||||
pdf.CellFormat(0, 7, tr(euroAmount(data.Total)), "", 0, "R", false, 0, "")
|
||||
pdf.SetY(y + noteBoxHeight + 6)
|
||||
}
|
||||
|
||||
func drawInvoiceFooter(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
||||
// Total banner at the bottom
|
||||
footerH := 14.0
|
||||
y := pdf.GetY()
|
||||
|
||||
pdf.SetFillColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
||||
pdf.RoundedRect(invoiceMarginX, y, invoiceContentWidth, footerH, 2, "1234", "F")
|
||||
|
||||
pdf.SetXY(invoiceMarginX+6, y+2)
|
||||
pdf.SetFont("Helvetica", "", 9)
|
||||
pdf.SetTextColor(255, 255, 255)
|
||||
pdf.CellFormat(60, 10, tr("TOTAL À PAYER"), "", 0, "L", false, 0, "")
|
||||
|
||||
pdf.SetFont("Helvetica", "B", 16)
|
||||
pdf.CellFormat(invoiceContentWidth-72, 10, tr(euroAmount(data.Total)), "", 0, "R", false, 0, "")
|
||||
}
|
||||
|
||||
func writeTableHeader(pdf *gofpdf.Fpdf, tr func(string) string, widths []float64, headers []string) {
|
||||
pdf.SetFillColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
||||
pdf.SetTextColor(255, 255, 255)
|
||||
pdf.SetFont("Helvetica", "B", 10)
|
||||
pdf.SetFont("Helvetica", "B", 9)
|
||||
alignments := []string{"L", "C", "R", "R"}
|
||||
if len(widths) == 3 {
|
||||
alignments = []string{"L", "L", "R"}
|
||||
|
|
@ -289,10 +414,27 @@ func writeTableHeader(pdf *gofpdf.Fpdf, tr func(string) string, widths []float64
|
|||
if i == len(headers)-1 {
|
||||
end = 1
|
||||
}
|
||||
pdf.CellFormat(widths[i], 8, tr(header), "1", end, alignments[i], true, 0, "")
|
||||
pad := ""
|
||||
if i == 0 {
|
||||
pad = " "
|
||||
}
|
||||
pdf.CellFormat(widths[i], 8, pad+tr(header), "", end, alignments[i], true, 0, "")
|
||||
}
|
||||
}
|
||||
|
||||
func nightsCount(from, to string) string {
|
||||
f, err1 := time.Parse("02/01/2006", from)
|
||||
t, err2 := time.Parse("02/01/2006", to)
|
||||
if err1 != nil || err2 != nil {
|
||||
return "-"
|
||||
}
|
||||
days := int(t.Sub(f).Hours() / 24)
|
||||
if days < 0 {
|
||||
days = 0
|
||||
}
|
||||
return fmt.Sprintf("%d", days)
|
||||
}
|
||||
|
||||
func euroAmount(value string) string {
|
||||
return fmt.Sprintf("%s €", value)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue