mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-12 13:46:51 +00:00
Compare commits
2 commits
0f327c814a
...
e8bfcf26f3
| Author | SHA1 | Date | |
|---|---|---|---|
| e8bfcf26f3 | |||
| 24f289b767 |
3 changed files with 421 additions and 86 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"
|
||||
|
||||
|
|
@ -15,76 +16,52 @@ import (
|
|||
|
||||
type HTMLPdfClient struct{}
|
||||
|
||||
const (
|
||||
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 {
|
||||
r int
|
||||
g int
|
||||
b int
|
||||
}
|
||||
|
||||
var (
|
||||
invoiceTeal = rgbColor{0, 123, 143}
|
||||
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) {
|
||||
return &HTMLPdfClient{}, nil
|
||||
}
|
||||
|
||||
func (pc *HTMLPdfClient) BuildInvoice(data booking.Invoice) (*booking.GeneratedFile, error) {
|
||||
pdf := gofpdf.New("P", "mm", "A4", "")
|
||||
pdf.SetMargins(16, 16, 16)
|
||||
pdf.SetAutoPageBreak(true, 16)
|
||||
pdf.SetMargins(invoiceMarginX, 10, invoiceMarginX)
|
||||
pdf.SetAutoPageBreak(true, 14)
|
||||
pdf.AddPage()
|
||||
|
||||
tr := pdf.UnicodeTranslatorFromDescriptor("")
|
||||
writeLine := func(fontStyle string, fontSize float64, text string) {
|
||||
pdf.SetFont("Helvetica", fontStyle, fontSize)
|
||||
pdf.CellFormat(0, 7, tr(text), "", 1, "", false, 0, "")
|
||||
}
|
||||
|
||||
writeLine("B", 20, fmt.Sprintf("Invoice %s", data.ID))
|
||||
pdf.Ln(2)
|
||||
writeLine("", 11, data.Host.Name)
|
||||
writeLine("", 11, data.Host.Address)
|
||||
writeLine("", 11, fmt.Sprintf("%s %s", data.Host.ZipCode, data.Host.City))
|
||||
writeLine("", 11, data.Host.PhoneNumber)
|
||||
writeLine("", 11, data.Host.Email)
|
||||
|
||||
pdf.Ln(4)
|
||||
writeLine("B", 14, "Guest")
|
||||
writeLine("", 11, data.Name)
|
||||
if data.PhoneNumber != "" {
|
||||
writeLine("", 11, data.PhoneNumber)
|
||||
}
|
||||
writeLine("", 11, fmt.Sprintf("Stay: %s - %s", data.From, data.To))
|
||||
writeLine("", 11, fmt.Sprintf("Platform: %s", data.Platform))
|
||||
writeLine("", 11, fmt.Sprintf("Guests: %d", data.CustomersNumber))
|
||||
|
||||
pdf.Ln(4)
|
||||
writeLine("B", 14, "Charges")
|
||||
pdf.SetFont("Helvetica", "B", 11)
|
||||
pdf.CellFormat(90, 8, tr("Item"), "1", 0, "", false, 0, "")
|
||||
pdf.CellFormat(25, 8, tr("Qty"), "1", 0, "C", false, 0, "")
|
||||
pdf.CellFormat(35, 8, tr("Unit"), "1", 0, "R", false, 0, "")
|
||||
pdf.CellFormat(25, 8, tr("Total"), "1", 1, "R", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 11)
|
||||
for _, line := range data.Lines {
|
||||
pdf.CellFormat(90, 8, tr(line.Name), "1", 0, "", false, 0, "")
|
||||
pdf.CellFormat(25, 8, fmt.Sprintf("%d", line.Quantity), "1", 0, "C", false, 0, "")
|
||||
pdf.CellFormat(35, 8, fmt.Sprintf("%s EUR", line.Price), "1", 0, "R", false, 0, "")
|
||||
pdf.CellFormat(25, 8, fmt.Sprintf("%s EUR", line.Total), "1", 1, "R", false, 0, "")
|
||||
}
|
||||
|
||||
pdf.Ln(4)
|
||||
writeLine("B", 14, "Payments")
|
||||
if len(data.Payments) == 0 {
|
||||
writeLine("", 11, "No payment recorded")
|
||||
} else {
|
||||
pdf.SetFont("Helvetica", "B", 11)
|
||||
pdf.CellFormat(45, 8, tr("Date"), "1", 0, "", false, 0, "")
|
||||
pdf.CellFormat(80, 8, tr("Method"), "1", 0, "", false, 0, "")
|
||||
pdf.CellFormat(50, 8, tr("Amount"), "1", 1, "R", false, 0, "")
|
||||
pdf.SetFont("Helvetica", "", 11)
|
||||
for _, payment := range data.Payments {
|
||||
pdf.CellFormat(45, 8, tr(payment.Date), "1", 0, "", false, 0, "")
|
||||
pdf.CellFormat(80, 8, tr(payment.Method), "1", 0, "", false, 0, "")
|
||||
pdf.CellFormat(50, 8, fmt.Sprintf("%s EUR", payment.Amount), "1", 1, "R", false, 0, "")
|
||||
}
|
||||
}
|
||||
|
||||
pdf.Ln(4)
|
||||
writeLine("B", 12, fmt.Sprintf("Total: %s EUR", data.Total))
|
||||
writeLine("", 12, fmt.Sprintf("Paid: %s EUR", data.AmountPaid))
|
||||
writeLine("B", 12, fmt.Sprintf("Balance due: %s EUR", data.BalanceDue))
|
||||
drawInvoiceHeader(pdf, tr, data)
|
||||
drawInvoiceDetails(pdf, tr, data)
|
||||
drawInvoiceItemsTable(pdf, tr, data)
|
||||
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 {
|
||||
|
|
@ -118,3 +95,353 @@ func (pc *HTMLPdfClient) BuildReport(report booking.ReportData, period string, m
|
|||
|
||||
return outputPath, nil
|
||||
}
|
||||
|
||||
func drawInvoiceHeader(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
||||
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",
|
||||
gofpdf.ImageOptions{ImageType: "PNG"},
|
||||
bytes.NewReader(logo),
|
||||
)
|
||||
if info != nil {
|
||||
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)
|
||||
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),
|
||||
fmt.Sprintf("Tel : %s", data.Host.PhoneNumber),
|
||||
fmt.Sprintf("Mail : %s", data.Host.Email),
|
||||
}
|
||||
for _, line := range hostLines {
|
||||
pdf.SetX(rightBlockX)
|
||||
pdf.CellFormat(rightBlockW, 4.2, tr(line), "", 1, "R", false, 0, "")
|
||||
}
|
||||
|
||||
// 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) {
|
||||
startY := pdf.GetY()
|
||||
leftX := invoiceMarginX
|
||||
rightX := invoiceMarginX + invoiceColWidth + invoiceColGap
|
||||
cardPad := 5.0
|
||||
cardH := 36.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.CellFormat(invoiceColWidth-cardPad*2, 7, tr(data.Name), "", 1, "L", false, 0, "")
|
||||
|
||||
// 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, "")
|
||||
}
|
||||
|
||||
writeLeftDetail("Tel", emptyFallback(data.PhoneNumber))
|
||||
writeLeftDetail("Clients", fmt.Sprintf("%d", data.CustomersNumber))
|
||||
writeLeftDetail("Plateforme", data.Platform)
|
||||
|
||||
// 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.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) {
|
||||
// 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)
|
||||
for i, line := range data.Lines {
|
||||
fill := i%2 == 0
|
||||
if fill {
|
||||
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
||||
}
|
||||
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(8)
|
||||
}
|
||||
|
||||
func drawInvoicePayments(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
||||
pdf.SetFont("Helvetica", "B", 11)
|
||||
pdf.SetTextColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
||||
pdf.CellFormat(0, 7, tr("Historique des Paiements"), "", 1, "L", false, 0, "")
|
||||
pdf.Ln(1)
|
||||
|
||||
widths := []float64{54, 88, 48}
|
||||
headers := []string{"Date", "Mode de Paiement", "Montant (€)"}
|
||||
writeTableHeader(pdf, tr, widths, headers)
|
||||
|
||||
pdf.SetFont("Helvetica", "", 10)
|
||||
if len(data.Payments) == 0 {
|
||||
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
||||
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.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(8)
|
||||
}
|
||||
|
||||
func drawInvoiceSummary(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
||||
// 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(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(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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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, _ 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)
|
||||
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(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
||||
pdf.RoundedRect(invoiceMarginX, y, invoiceContentWidth, noteBoxHeight, 2, "1234", "F")
|
||||
|
||||
// Notes title
|
||||
pdf.SetXY(invoiceMarginX+invoiceNotesPadding, y+invoiceNotesPadding)
|
||||
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.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.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", 9)
|
||||
alignments := []string{"L", "C", "R", "R"}
|
||||
if len(widths) == 3 {
|
||||
alignments = []string{"L", "L", "R"}
|
||||
}
|
||||
for i, header := range headers {
|
||||
end := 0
|
||||
if i == len(headers)-1 {
|
||||
end = 1
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func emptyFallback(value string) string {
|
||||
if value == "" {
|
||||
return "-"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue