From e7959a5e17486ec357cabea922a855c9a1b94b46 Mon Sep 17 00:00:00 2001 From: jenish-jain Date: Mon, 25 Mar 2024 10:14:14 +0530 Subject: [PATCH] #19| added pdf report generation capability draft --- cmd/bean_counter/main.go | 10 +-- docs/assets/pdf/billingv2.pdf | Bin 0 -> 28897 bytes go.mod | 27 ++++++-- go.sum | 49 +++++++++++-- internal/reporter/reporter.go | 85 +++++++++++++++++++++++ pkg/utils/pdf/builder.go | 125 ++++++++++++++++++++++++++++++++++ pkg/utils/pdf/pdf.go | 39 +++++++++++ pkg/utils/pdf/styles.go | 96 ++++++++++++++++++++++++++ 8 files changed, 415 insertions(+), 16 deletions(-) create mode 100755 docs/assets/pdf/billingv2.pdf create mode 100644 pkg/utils/pdf/builder.go create mode 100644 pkg/utils/pdf/pdf.go create mode 100644 pkg/utils/pdf/styles.go diff --git a/cmd/bean_counter/main.go b/cmd/bean_counter/main.go index 36a57a2..c955591 100644 --- a/cmd/bean_counter/main.go +++ b/cmd/bean_counter/main.go @@ -9,15 +9,16 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/joho/godotenv" - "golang.org/x/oauth2/google" - "google.golang.org/api/option" - "google.golang.org/api/sheets/v4" "io/ioutil" "log" "net/http" "os" "time" + + "github.com/joho/godotenv" + "golang.org/x/oauth2/google" + "google.golang.org/api/option" + "google.golang.org/api/sheets/v4" ) func main() { @@ -100,6 +101,7 @@ func generateMonthlyGstReport(w http.ResponseWriter, r *http.Request) { spreadsheetID := os.Getenv("REPORTER_SPREADSHEET_ID") sheetsRepo.AddNewWorksheet(spreadsheetID, sheetName) values := reporter.GetSheetValuesToPublishReport(taxReport, month, year) + reporter.GeneratePDFReport(taxReport, month, year) sheetsRepo.WriteToSheet(spreadsheetID, sheetName, values) w.WriteHeader(http.StatusCreated) diff --git a/docs/assets/pdf/billingv2.pdf b/docs/assets/pdf/billingv2.pdf new file mode 100755 index 0000000000000000000000000000000000000000..ee54f437623934be71eb8c2f6c4bffaf0c22bf19 GIT binary patch literal 28897 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 WhNy&jS>c7Os9FV2p1gj49Q+r+Ky4}j literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod index 3ed7ded..b33d5c7 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,12 @@ module bean_counter -go 1.19 +go 1.21.1 + +toolchain go1.22.0 require ( github.com/joho/godotenv v1.5.1 + github.com/stretchr/testify v1.8.4 golang.org/x/oauth2 v0.5.0 google.golang.org/api v0.111.0 ) @@ -11,21 +14,33 @@ require ( require ( cloud.google.com/go/compute v1.18.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect + github.com/boombuler/barcode v1.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/f-amaral/go-async v0.3.0 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect + github.com/hhrutter/lzw v1.0.0 // indirect + github.com/hhrutter/tiff v1.0.1 // indirect + github.com/johnfercher/go-tree v1.0.5 // indirect + github.com/johnfercher/maroto/v2 v2.0.0-beta.16 // indirect + github.com/jung-kurt/gofpdf v1.16.2 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/pdfcpu/pdfcpu v0.6.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect + github.com/rivo/uniseg v0.4.4 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/image v0.15.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 // indirect google.golang.org/grpc v1.53.0 // indirect google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8ad3d3b..44046de 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,9 @@ cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGB cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -16,6 +19,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/f-amaral/go-async v0.3.0 h1:h4kLsX7aKfdWaHvV0lf+/EE3OIeCzyeDYJDb/vDZUyg= +github.com/f-amaral/go-async v0.3.0/go.mod h1:Hz5Qr6DAWpbTTUjytnrg1WIsDgS7NtOei5y8SipYS7U= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -44,18 +49,44 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0= +github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo= +github.com/hhrutter/tiff v1.0.1 h1:MIus8caHU5U6823gx7C6jrfoEvfSTGtEFRiM8/LOzC0= +github.com/hhrutter/tiff v1.0.1/go.mod h1:zU/dNgDm0cMIa8y8YwcYBeuEEveI4B0owqHyiPpJPHc= +github.com/johnfercher/go-tree v1.0.5 h1:zpgVhJsChavzhKdxhQiCJJzcSY3VCT9oal2JoA2ZevY= +github.com/johnfercher/go-tree v1.0.5/go.mod h1:DUO6QkXIFh1K7jeGBIkLCZaeUgnkdQAsB64FDSoHswg= +github.com/johnfercher/maroto/v2 v2.0.0-beta.16 h1:ghz1+OZJiJ8UZP5aiGljiaoiK93XbFETeNq8CGf8B1Q= +github.com/johnfercher/maroto/v2 v2.0.0-beta.16/go.mod h1:u0v7GbyiwpNYL04nvsQyMfcoQr0td5eqhe9mzRwVXWw= 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 v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc= +github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/pdfcpu/pdfcpu v0.6.0 h1:z4kARP5bcWa39TTYMcN/kjBnm7MvhTWjXgeYmkdAGMI= +github.com/pdfcpu/pdfcpu v0.6.0/go.mod h1:kmpD0rk8YnZj0l3qSeGBlAB+XszHUgNv//ORH/E7EYo= +github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -66,6 +97,9 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= +golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -76,8 +110,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= @@ -88,13 +122,13 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -132,7 +166,10 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= diff --git a/internal/reporter/reporter.go b/internal/reporter/reporter.go index a040132..3afb2fd 100644 --- a/internal/reporter/reporter.go +++ b/internal/reporter/reporter.go @@ -5,6 +5,7 @@ import ( "bean_counter/internal/types/tax" "bean_counter/internal/types/transaction" "bean_counter/pkg/files" + "bean_counter/pkg/utils/pdf" "context" "embed" "encoding/json" @@ -17,6 +18,7 @@ import ( type Reporter interface { GetTaxReportOfMonth(month time.Month, year int) taxReport GetSheetValuesToPublishReport(taxReport taxReport, month time.Month, year int) [][]interface{} + GeneratePDFReport(taxReport taxReport, month time.Month, year int) } type reporterImpl struct { @@ -200,6 +202,89 @@ func (r reporterImpl) GetSheetValuesToPublishReport(taxReport taxReport, month t return values } +func (r reporterImpl) GeneratePDFReport(taxReport taxReport, month time.Month, year int) { + pdfBuilder := pdf. + NewPDFGenerator(). + WithHeader(). + WithFooter(). + AddRow("GST Report"). + AddRow("GSTIN 24ABWPJ2263R1ZW"). + AddRow("Name Padamchand Jain c/o Jainco Textile"). + AddRow(fmt.Sprintf("Month: %s %d", month, year)). + AddRow("Purchases"). + AddRow(""). + AddTable([]string{}, [][]interface{}{ + {"Total Taxable Amount", taxReport.TotalPurchases.TaxableAmount}, + {"Total C.G.S.T", taxReport.TotalPurchases.Tax.GetCGST()}, + {"Total S.G.S.T", taxReport.TotalPurchases.Tax.GetSGST()}, + {"Total I.G.S.T", taxReport.TotalPurchases.Tax.GetIGST()}, + {"Total Purchases Amount with tax", taxReport.TotalPurchases.TotalAmountWithTax}, + }, false). + AddRow("Input Tax amount of Purchases"). + AddRow(""). + AddTable([]string{}, [][]interface{}{ + {"Total C.G.S.T", taxReport.TotalPurchases.Tax.GetCGST()}, + {"Total S.G.S.T", taxReport.TotalPurchases.Tax.GetSGST()}, + {"Total I.G.S.T", taxReport.TotalPurchases.Tax.GetIGST()}, + {"Total Input Tax amount", taxReport.TotalPurchases.Tax.GetTotalTax()}, + }, false). + AddRow("Sales"). + AddRow(""). + AddTable([]string{}, [][]interface{}{ + {"Total Sales", taxReport.TotalSales.TotalAmountWithTax}, + {"Total Sales with Tax amount", taxReport.TotalSales.TotalAmountWithTax}, + }, false). + AddRow("Total Sales as per Following"). + AddRow(""). + AddTable([]string{}, [][]interface{}{ + {"Total Taxable Amount", taxReport.TotalSales.TaxableAmount}, + {"Total C.G.S.T", taxReport.TotalSales.Tax.GetCGST()}, + {"Total S.G.S.T", taxReport.TotalSales.Tax.GetSGST()}, + {"Total I.G.S.T", taxReport.TotalSales.Tax.GetIGST()}, + {"Total Sales with tax amount", taxReport.TotalSales.TotalAmountWithTax}, + }, false). + AddRow("Total payable tax amount of sales"). + AddRow(""). + AddTable([]string{}, [][]interface{}{ + {"Total payable C.G.S.T", taxReport.TotalSales.Tax.GetCGST()}, + {"Total payable S.G.S.T", taxReport.TotalSales.Tax.GetSGST()}, + {"Total payable I.G.S.T", taxReport.TotalSales.Tax.GetIGST()}, + {"Total payable tax amount", taxReport.TotalSales.Tax.GetTotalTax()}, + }, false). + AddRow("Total payable tax liability"). + AddRow(""). + AddTable([]string{}, [][]interface{}{ + {"Total input tax amount", taxReport.TotalPurchases.Tax.GetTotalTax()}, + {"Total payable tax amount", taxReport.TotalSales.Tax.GetTotalTax()}, + {"Total Tax Credit/ Payable", taxReport.NetTax}, + }, false). + AddRow("Total payable tax as per following states"). + AddRow(""). + AddTable([]string{"Name of state", "I.G.S.T", "C.G.S.T", "S.G.S.T", "Total Tax"}, [][]interface{}{}, false) + + tinToStateDetailsMap := getTinToStateDetailsMap() + for stateCode, taxBreakup := range taxReport.StateTaxBreakup { + payableTax := taxBreakup.PayableTax + if stateCode != "**" && payableTax.GetTotalTax() != 0 { + pdfBuilder. + AddTable([]string{}, [][]interface{}{ + {tinToStateDetailsMap[stateCode].StateName, payableTax.GetIGST(), payableTax.GetCGST(), payableTax.GetSGST(), payableTax.GetTotalTax()}, + }, false) + } + } + totalSalesTax := taxReport.TotalSales.Tax + pdfBuilder. + AddTable([]string{}, [][]interface{}{ + {"TOTAL", totalSalesTax.GetIGST(), totalSalesTax.GetCGST(), totalSalesTax.GetSGST(), totalSalesTax.GetTotalTax()}, + }, false) + + err := pdfBuilder.Build() + if err != nil { + fmt.Printf("Error generating pdf %+v \n", err) + } + +} + func NewReporter(invoiceService invoice.Service) Reporter { return &reporterImpl{ invoiceService: invoiceService, diff --git a/pkg/utils/pdf/builder.go b/pkg/utils/pdf/builder.go new file mode 100644 index 0000000..233b88d --- /dev/null +++ b/pkg/utils/pdf/builder.go @@ -0,0 +1,125 @@ +package pdf + +import ( + "fmt" + "log/slog" + + "github.com/johnfercher/maroto/v2/pkg/components/col" + "github.com/johnfercher/maroto/v2/pkg/components/image" + "github.com/johnfercher/maroto/v2/pkg/components/row" + "github.com/johnfercher/maroto/v2/pkg/components/text" + "github.com/johnfercher/maroto/v2/pkg/consts/align" + "github.com/johnfercher/maroto/v2/pkg/consts/fontstyle" + "github.com/johnfercher/maroto/v2/pkg/core" + "github.com/johnfercher/maroto/v2/pkg/props" +) + +func (p *pdfBuilderImpl) Build() error { + document, err := p.builder.Generate() + if err != nil { + slog.Error("error generating pdf document: %+v", err) + } + err = document.Save("docs/assets/pdf/billingv2.pdf") + if err != nil { + slog.Error("error saving pdf document: %+v", err) + } + + // err = document.GetReport().Save("docs/assets/text/billingv2.txt") + // if err != nil { + // log.Fatal(err.Error()) + // } + return err + +} + +func (p *pdfBuilderImpl) WithHeader() PDFBuilder { + p.builder.RegisterHeader(getPageHeader()) + return p +} + +func (p *pdfBuilderImpl) WithFooter() PDFBuilder { + p.builder.RegisterFooter(getPageFooter()) + return p +} + +func (p *pdfBuilderImpl) AddRow(value string) PDFBuilder { + // TODO: take these styling param as a config in builder input later + p.builder.AddRows(text.NewRow(ROW_HEIGHT, value, BOLD_GREY_TEXT, CENTER_ALIGN)) + return p +} + +func (p *pdfBuilderImpl) AddTable(headers []string, rows [][]interface{}, indexed bool) PDFBuilder { + tableRows := []core.Row{} + columns := []core.Col{} + + if len(headers) > 0 { + if indexed { + headers = append([]string{"S.No."}, headers...) + } + for header := range headers { + column := text.NewCol(COLUMN_SIZE, headers[header], BOLD_WHITE_CENTERED_TEXT). + WithStyle(TABLE_HEADER_STYLE) + columns = append(columns, column) + } + + p.builder.AddRows(row.New(TABLE_ROW_HEIGHT).Add(columns...)) + } + + for rowNo, rowElement := range rows { + rowColumns := []core.Col{} + if indexed { + rowElement = append([]interface{}{rowNo + 1}, rowElement...) + } + if len(headers) != 0 && (len(rowElement) != len(headers)) { + slog.Error("length of row %d does not match headers %d", len(rowElement), len(headers)) + panic("length of row does not match headers") + } + for _, rowContent := range rowElement { + rowElement := text.NewCol(COLUMN_SIZE, fmt.Sprint(rowContent), NORMAL_GREY_CENTERED_TEXT). + WithStyle(TABLE_ROW_STYLE) + rowColumns = append(rowColumns, rowElement) + } + tableRows = append(tableRows, row.New(TABLE_ROW_HEIGHT).Add(rowColumns...)) + } + p.builder.AddRows(tableRows...) + + return p +} + +func getPageFooter() core.Row { + return row.New(15).Add( + col.New(12).Add( + text.New("provided to you by munshi ji", props.Text{ + Top: 20, + Style: fontstyle.BoldItalic, + Size: 6, + Align: align.Center, + Color: DARK_GREY_COLOR, + }), + ), + ) +} + +func getPageHeader() core.Row { + return row.New(20).Add( + image.NewFromFileCol(3, "docs/assets/images/biplane.jpg", props.Rect{ + Center: true, + Percent: 80, + }), + col.New(6), + col.New(3).Add( + text.New("Jainco Textiles, 101 sarvodaya textile market, ring road. gujarat. surat", props.Text{ + Size: 8, + Align: align.Right, + Color: RED_COLOR, + }), + text.New("Tel: 9825120344", props.Text{ + Top: 10, + Style: fontstyle.BoldItalic, + Size: 8, + Align: align.Right, + Color: BLUE_COLOR, + }), + ), + ) +} diff --git a/pkg/utils/pdf/pdf.go b/pkg/utils/pdf/pdf.go new file mode 100644 index 0000000..889bfb7 --- /dev/null +++ b/pkg/utils/pdf/pdf.go @@ -0,0 +1,39 @@ +package pdf + +import ( + "github.com/johnfercher/maroto/v2" + "github.com/johnfercher/maroto/v2/pkg/config" + "github.com/johnfercher/maroto/v2/pkg/core" + "github.com/johnfercher/maroto/v2/pkg/props" +) + +type pdfBuilderImpl struct { + builder core.Maroto +} + +type PDFBuilder interface { + Build() error + WithHeader() PDFBuilder + WithFooter() PDFBuilder + AddRow(value string) PDFBuilder + AddTable(headers []string, rows [][]interface{}, indexed bool) PDFBuilder +} + +func newBuilder() core.Maroto { + cnf := config.NewBuilder(). + WithPageNumber("Page {current} of {total}", props.RightBottom). + WithMargins(12, 15, 10). + WithAuthor("bean counter", false). + Build() + + mrt := maroto.New(cnf) + m := maroto.NewMetricsDecorator(mrt) + + return m +} + +func NewPDFGenerator() PDFBuilder { + return &pdfBuilderImpl{ + builder: newBuilder(), + } +} diff --git a/pkg/utils/pdf/styles.go b/pkg/utils/pdf/styles.go new file mode 100644 index 0000000..09549cd --- /dev/null +++ b/pkg/utils/pdf/styles.go @@ -0,0 +1,96 @@ +package pdf + +import ( + "github.com/johnfercher/maroto/v2/pkg/consts/align" + "github.com/johnfercher/maroto/v2/pkg/consts/border" + "github.com/johnfercher/maroto/v2/pkg/consts/fontstyle" + "github.com/johnfercher/maroto/v2/pkg/props" +) + +const ( + ROW_HEIGHT = 4 + COLUMN_SIZE = 2 + TABLE_ROW_HEIGHT = 7 +) + +// Colors +var ( + DARK_GREY_COLOR = &props.Color{ + Red: 55, + Green: 55, + Blue: 55, + } + GREY_COLOR = &props.Color{ + Red: 200, + Green: 200, + Blue: 200, + } + WHITE_COLOR = &props.Color{ + Red: 255, + Green: 255, + Blue: 255, + } + RED_COLOR = &props.Color{ + Red: 255, + Green: 0, + Blue: 0, + } + BLUE_COLOR = &props.Color{ + Red: 0, + Green: 0, + Blue: 255, + } +) + +// Text styles +var ( + BOLD_WHITE_CENTERED_TEXT = props.Text{ + Top: 3, + Style: fontstyle.Bold, + Align: align.Center, + Color: WHITE_COLOR, + Size: 7, + } + + NORMAL_GREY_CENTERED_TEXT = props.Text{ + Top: 3, + Style: fontstyle.Normal, + Align: align.Center, + Color: DARK_GREY_COLOR, + Size: 7, + } + + BOLD_GREY_CENTERED_TEXT = props.Text{ + Top: 3, + Style: fontstyle.Bold, + Align: align.Center, + Color: DARK_GREY_COLOR, + Size: 7, + } + + BOLD_GREY_TEXT = props.Text{ + Top: 3, + Style: fontstyle.Bold, + Color: DARK_GREY_COLOR, + Size: 7, + } + + CENTER_ALIGN = props.Text{ + Align: align.Center, + } +) + +// Table Formattings +var ( + TABLE_HEADER_STYLE = &props.Cell{ + BackgroundColor: GREY_COLOR, + BorderType: border.Full, + BorderThickness: 0.3, + } + + TABLE_ROW_STYLE = &props.Cell{ + BackgroundColor: WHITE_COLOR, + BorderType: border.Full, + BorderThickness: 0.1, + } +)