Moving pdf attachments to go

This commit is contained in:
Finley Ghosh 2026-01-12 22:00:48 +11:00
parent a33eaa0c3c
commit 3841d961fa
23 changed files with 1446 additions and 559 deletions

BIN
go/bin/server Executable file

Binary file not shown.

View file

@ -10,6 +10,7 @@ import (
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/db"
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/email"
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/handlers"
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/handlers/attachments"
quotes "code.springupsoftware.com/cmc/cmc-sales/internal/cmc/handlers/quotes"
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/templates"
@ -84,6 +85,13 @@ func main() {
goRouter.HandleFunc("/attachments/{id}", attachmentHandler.Get).Methods("GET")
goRouter.HandleFunc("/attachments/{id}", attachmentHandler.Delete).Methods("DELETE")
// PDF generation endpoints - called from PHP app
goRouter.HandleFunc("/pdf/generate-invoice", handlers.GenerateInvoicePDF).Methods("POST")
goRouter.HandleFunc("/pdf/generate-quote", handlers.GenerateQuotePDF).Methods("POST")
goRouter.HandleFunc("/pdf/generate-po", handlers.GeneratePurchaseOrderPDF).Methods("POST")
goRouter.HandleFunc("/pdf/generate-packinglist", handlers.GeneratePackingListPDF).Methods("POST")
goRouter.HandleFunc("/pdf/generate-orderack", handlers.GenerateOrderAckPDF).Methods("POST")
// The following routes are currently disabled:
/*
// API routes

View file

@ -1,22 +1,56 @@
module code.springupsoftware.com/cmc/cmc-sales
go 1.23.0
go 1.24.0
toolchain go1.24.3
require (
github.com/go-co-op/gocron v1.37.0
github.com/go-sql-driver/mysql v1.7.1
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/jhillyerd/enmime v1.3.0
github.com/joho/godotenv v1.5.1
github.com/jung-kurt/gofpdf v1.16.2
golang.org/x/text v0.27.0
golang.org/x/oauth2 v0.33.0
google.golang.org/api v0.257.0
)
require (
github.com/go-co-op/gocron v1.37.0 // indirect
github.com/google/uuid v1.4.0 // indirect
cloud.google.com/go/auth v0.17.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/hhrutter/lzw v1.0.0 // indirect
github.com/hhrutter/pkcs7 v0.2.0 // indirect
github.com/hhrutter/tiff v1.0.2 // indirect
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/image v0.32.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
google.golang.org/grpc v1.77.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

115
go/go.sum
View file

@ -1,21 +1,54 @@
cloud.google.com/go/auth v0.16.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc=
cloud.google.com/go/auth v0.16.3/go.mod h1:NucRGjaXfzP1ltpcQ7On/VTZ0H4kWB5Jy+Y9Dnm76fA=
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
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/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I=
github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE=
github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8=
github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw=
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw=
@ -28,9 +61,19 @@ github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/Ym
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pdfcpu/pdfcpu v0.11.1 h1:htHBSkGH5jMKWC6e0sihBFbcKZ8vG1M67c8/dJxhjas=
github.com/pdfcpu/pdfcpu v0.11.1/go.mod h1:pP3aGga7pRvwFWAm9WwFvo+V68DfANi9kxSQYioNYcw=
github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -42,7 +85,11 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
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=
@ -51,23 +98,61 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA=
google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
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=

View file

@ -386,7 +386,7 @@ func (h *PageHandler) EnquiriesIndex(w http.ResponseWriter, r *http.Request) {
offset := (page - 1) * limit
var enquiries interface{}
var err error
var listErr error
var hasMore bool
// Check if we want archived enquiries
@ -401,6 +401,8 @@ func (h *PageHandler) EnquiriesIndex(w http.ResponseWriter, r *http.Request) {
archivedEnquiries = archivedEnquiries[:limit]
}
enquiries = archivedEnquiries
} else {
listErr = err
}
} else {
activeEnquiries, err := h.queries.ListEnquiries(r.Context(), db.ListEnquiriesParams{
@ -413,11 +415,13 @@ func (h *PageHandler) EnquiriesIndex(w http.ResponseWriter, r *http.Request) {
activeEnquiries = activeEnquiries[:limit]
}
enquiries = activeEnquiries
} else {
listErr = err
}
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
if listErr != nil {
http.Error(w, listErr.Error(), http.StatusInternalServerError)
return
}

View file

@ -0,0 +1,453 @@
package handlers
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/db"
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/pdf"
)
// InvoiceLineItemRequest is the JSON shape for a single line item.
type InvoiceLineItemRequest struct {
ItemNumber string `json:"item_number"`
Quantity string `json:"quantity"`
Title string `json:"title"`
UnitPrice float64 `json:"unit_price"`
TotalPrice float64 `json:"total_price"`
}
// InvoicePDFRequest is the expected payload from the PHP app.
type InvoicePDFRequest struct {
DocumentID int32 `json:"document_id"`
InvoiceTitle string `json:"invoice_title"`
CustomerName string `json:"customer_name"`
ContactEmail string `json:"contact_email"`
ContactName string `json:"contact_name"`
UserFirstName string `json:"user_first_name"`
UserLastName string `json:"user_last_name"`
UserEmail string `json:"user_email"`
YourReference string `json:"your_reference"`
ShipVia string `json:"ship_via"`
FOB string `json:"fob"`
IssueDate string `json:"issue_date"` // ISO date: 2006-01-02
CurrencySymbol string `json:"currency_symbol"` // e.g. "$"
ShowGST bool `json:"show_gst"`
LineItems []InvoiceLineItemRequest `json:"line_items"`
OutputDir string `json:"output_dir"` // optional override
}
// GenerateInvoicePDF handles POST /api/pdf/invoice and writes a PDF to disk.
// It returns JSON: {"filename":"<name>.pdf"}
func GenerateInvoicePDF(w http.ResponseWriter, r *http.Request) {
var req InvoicePDFRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON payload", http.StatusBadRequest)
return
}
if req.InvoiceTitle == "" || req.CustomerName == "" {
http.Error(w, "invoice_title and customer_name are required", http.StatusBadRequest)
return
}
issueDate := time.Now()
if req.IssueDate != "" {
if parsed, err := time.Parse("2006-01-02", req.IssueDate); err == nil {
issueDate = parsed
}
}
outputDir := req.OutputDir
if outputDir == "" {
outputDir = os.Getenv("PDF_OUTPUT_DIR")
}
if outputDir == "" {
outputDir = "../php/app/webroot/pdf"
}
if err := os.MkdirAll(outputDir, 0755); err != nil {
log.Printf("GenerateInvoicePDF: failed to create output dir: %v", err)
http.Error(w, "failed to prepare output directory", http.StatusInternalServerError)
return
}
// Map request into the existing PDF generation types.
doc := &db.Document{ID: req.DocumentID}
inv := &db.Invoice{Title: req.InvoiceTitle}
cust := &db.Customer{Name: req.CustomerName}
lineItems := make([]db.GetLineItemsTableRow, len(req.LineItems))
for i, li := range req.LineItems {
lineItems[i] = db.GetLineItemsTableRow{
ItemNumber: li.ItemNumber,
Quantity: li.Quantity,
Title: li.Title,
GrossUnitPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.UnitPrice), Valid: true},
GrossPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.TotalPrice), Valid: true},
}
}
data := &pdf.InvoicePDFData{
Document: doc,
Invoice: inv,
Customer: cust,
LineItems: lineItems,
CurrencySymbol: req.CurrencySymbol,
ShowGST: req.ShowGST,
ShipVia: req.ShipVia,
FOB: req.FOB,
IssueDate: issueDate,
EmailTo: req.ContactEmail,
Attention: req.ContactName,
FromName: fmt.Sprintf("%s %s", req.UserFirstName, req.UserLastName),
FromEmail: req.UserEmail,
YourReference: req.YourReference,
}
filename, err := pdf.GenerateInvoicePDF(data, outputDir)
if err != nil {
log.Printf("GenerateInvoicePDF: failed to generate PDF: %v", err)
http.Error(w, "failed to generate PDF", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"filename": filename})
}
// QuoteLineItemRequest reuses the invoice item shape
type QuoteLineItemRequest = InvoiceLineItemRequest
// QuotePDFRequest payload from PHP for quotes
type QuotePDFRequest struct {
DocumentID int32 `json:"document_id"`
CmcReference string `json:"cmc_reference"`
Revision int32 `json:"revision"`
CreatedDate string `json:"created_date"` // YYYY-MM-DD
CustomerName string `json:"customer_name"`
ContactEmail string `json:"contact_email"`
ContactName string `json:"contact_name"`
UserFirstName string `json:"user_first_name"`
UserLastName string `json:"user_last_name"`
UserEmail string `json:"user_email"`
CurrencySymbol string `json:"currency_symbol"`
ShowGST bool `json:"show_gst"`
CommercialComments string `json:"commercial_comments"`
LineItems []QuoteLineItemRequest `json:"line_items"`
Pages []string `json:"pages"`
OutputDir string `json:"output_dir"`
}
// GenerateQuotePDF handles POST /go/pdf/generate-quote
func GenerateQuotePDF(w http.ResponseWriter, r *http.Request) {
var req QuotePDFRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON payload", http.StatusBadRequest)
return
}
if req.CmcReference == "" || req.CustomerName == "" {
http.Error(w, "cmc_reference and customer_name are required", http.StatusBadRequest)
return
}
created := time.Now()
if req.CreatedDate != "" {
if parsed, err := time.Parse("2006-01-02", req.CreatedDate); err == nil {
created = parsed
}
}
outputDir := req.OutputDir
if outputDir == "" {
outputDir = os.Getenv("PDF_OUTPUT_DIR")
}
if outputDir == "" {
outputDir = "../php/app/webroot/pdf"
}
if err := os.MkdirAll(outputDir, 0755); err != nil {
log.Printf("GenerateQuotePDF: failed to create output dir: %v", err)
http.Error(w, "failed to prepare output directory", http.StatusInternalServerError)
return
}
// Map request into PDF data
doc := &db.Document{ID: req.DocumentID, CmcReference: req.CmcReference, Revision: req.Revision, Created: created}
cust := &db.Customer{Name: req.CustomerName}
user := &db.GetUserRow{FirstName: req.UserFirstName, LastName: req.UserLastName, Email: req.UserEmail}
lineItems := make([]db.GetLineItemsTableRow, len(req.LineItems))
for i, li := range req.LineItems {
lineItems[i] = db.GetLineItemsTableRow{
ItemNumber: li.ItemNumber,
Quantity: li.Quantity,
Title: li.Title,
GrossUnitPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.UnitPrice), Valid: true},
GrossPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.TotalPrice), Valid: true},
}
}
data := &pdf.QuotePDFData{
Document: doc,
Customer: cust,
EmailTo: req.ContactEmail,
Attention: req.ContactName,
User: user,
LineItems: lineItems,
CurrencySymbol: req.CurrencySymbol,
ShowGST: req.ShowGST,
CommercialComments: req.CommercialComments,
Pages: req.Pages,
}
filename, err := pdf.GenerateQuotePDF(data, outputDir)
if err != nil {
log.Printf("GenerateQuotePDF: failed to generate PDF: %v", err)
http.Error(w, "failed to generate PDF", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"filename": filename})
}
// PurchaseOrderLineItemRequest reuses the invoice item shape
type PurchaseOrderLineItemRequest = InvoiceLineItemRequest
// PurchaseOrderPDFRequest payload from PHP for POs
type PurchaseOrderPDFRequest struct {
DocumentID int32 `json:"document_id"`
Title string `json:"title"`
PrincipleName string `json:"principle_name"`
PrincipleReference string `json:"principle_reference"`
IssueDate string `json:"issue_date"` // YYYY-MM-DD
OrderedFrom string `json:"ordered_from"`
DispatchBy string `json:"dispatch_by"`
DeliverTo string `json:"deliver_to"`
ShippingInstructions string `json:"shipping_instructions"`
CurrencySymbol string `json:"currency_symbol"`
ShowGST bool `json:"show_gst"`
LineItems []PurchaseOrderLineItemRequest `json:"line_items"`
OutputDir string `json:"output_dir"`
}
// GeneratePurchaseOrderPDF handles POST /go/pdf/generate-po
func GeneratePurchaseOrderPDF(w http.ResponseWriter, r *http.Request) {
var req PurchaseOrderPDFRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON payload", http.StatusBadRequest)
return
}
if req.Title == "" || req.PrincipleName == "" {
http.Error(w, "title and principle_name are required", http.StatusBadRequest)
return
}
issueDate := time.Now()
if req.IssueDate != "" {
if parsed, err := time.Parse("2006-01-02", req.IssueDate); err == nil {
issueDate = parsed
}
}
outputDir := req.OutputDir
if outputDir == "" {
outputDir = os.Getenv("PDF_OUTPUT_DIR")
}
if outputDir == "" {
outputDir = "../php/app/webroot/pdf"
}
if err := os.MkdirAll(outputDir, 0755); err != nil {
log.Printf("GeneratePurchaseOrderPDF: failed to create output dir: %v", err)
http.Error(w, "failed to prepare output directory", http.StatusInternalServerError)
return
}
doc := &db.Document{ID: req.DocumentID}
po := &db.PurchaseOrder{
Title: req.Title,
PrincipleReference: req.PrincipleReference,
IssueDate: issueDate,
OrderedFrom: req.OrderedFrom,
DispatchBy: req.DispatchBy,
DeliverTo: req.DeliverTo,
ShippingInstructions: req.ShippingInstructions,
}
principle := &db.Principle{Name: req.PrincipleName}
lineItems := make([]db.GetLineItemsTableRow, len(req.LineItems))
for i, li := range req.LineItems {
lineItems[i] = db.GetLineItemsTableRow{
ItemNumber: li.ItemNumber,
Quantity: li.Quantity,
Title: li.Title,
GrossUnitPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.UnitPrice), Valid: true},
GrossPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.TotalPrice), Valid: true},
}
}
data := &pdf.PurchaseOrderPDFData{
Document: doc,
PurchaseOrder: po,
Principle: principle,
LineItems: lineItems,
CurrencySymbol: req.CurrencySymbol,
ShowGST: req.ShowGST,
}
filename, err := pdf.GeneratePurchaseOrderPDF(data, outputDir)
if err != nil {
log.Printf("GeneratePurchaseOrderPDF: failed to generate PDF: %v", err)
http.Error(w, "failed to generate PDF", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"filename": filename})
}
// GeneratePackingListPDF handles POST /go/pdf/generate-packinglist
func GeneratePackingListPDF(w http.ResponseWriter, r *http.Request) {
var req PackingListPDFRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON payload", http.StatusBadRequest)
return
}
if req.CustomerName == "" {
http.Error(w, "customer_name is required", http.StatusBadRequest)
return
}
outputDir := req.OutputDir
if outputDir == "" {
outputDir = os.Getenv("PDF_OUTPUT_DIR")
}
if outputDir == "" {
outputDir = "../php/app/webroot/pdf"
}
if err := os.MkdirAll(outputDir, 0755); err != nil {
log.Printf("GeneratePackingListPDF: failed to create output dir: %v", err)
http.Error(w, "failed to prepare output directory", http.StatusInternalServerError)
return
}
// Reuse the invoice generator structure but label as PACKING LIST via DetailsBox
// Build minimal data shape
doc := &db.Document{ID: req.DocumentID}
cust := &db.Customer{Name: req.CustomerName}
lineItems := make([]db.GetLineItemsTableRow, len(req.LineItems))
for i, li := range req.LineItems {
lineItems[i] = db.GetLineItemsTableRow{
ItemNumber: li.ItemNumber,
Quantity: li.Quantity,
Title: li.Title,
GrossUnitPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.UnitPrice), Valid: true},
GrossPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.TotalPrice), Valid: true},
}
}
data := &pdf.PackingListPDFData{
Document: doc,
Customer: cust,
LineItems: lineItems,
CurrencySymbol: req.CurrencySymbol,
ShowGST: req.ShowGST,
}
filename, err := pdf.GeneratePackingListPDF(data, outputDir)
if err != nil {
log.Printf("GeneratePackingListPDF: failed to generate PDF: %v", err)
http.Error(w, "failed to generate PDF", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"filename": filename})
}
// GenerateOrderAckPDF handles POST /go/pdf/generate-orderack
func GenerateOrderAckPDF(w http.ResponseWriter, r *http.Request) {
var req OrderAckPDFRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON payload", http.StatusBadRequest)
return
}
if req.CustomerName == "" {
http.Error(w, "customer_name is required", http.StatusBadRequest)
return
}
outputDir := req.OutputDir
if outputDir == "" {
outputDir = os.Getenv("PDF_OUTPUT_DIR")
}
if outputDir == "" {
outputDir = "../php/app/webroot/pdf"
}
if err := os.MkdirAll(outputDir, 0755); err != nil {
log.Printf("GenerateOrderAckPDF: failed to create output dir: %v", err)
http.Error(w, "failed to prepare output directory", http.StatusInternalServerError)
return
}
doc := &db.Document{ID: req.DocumentID}
cust := &db.Customer{Name: req.CustomerName}
lineItems := make([]db.GetLineItemsTableRow, len(req.LineItems))
for i, li := range req.LineItems {
lineItems[i] = db.GetLineItemsTableRow{
ItemNumber: li.ItemNumber,
Quantity: li.Quantity,
Title: li.Title,
GrossUnitPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.UnitPrice), Valid: true},
GrossPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.TotalPrice), Valid: true},
}
}
data := &pdf.OrderAckPDFData{
Document: doc,
Customer: cust,
LineItems: lineItems,
CurrencySymbol: req.CurrencySymbol,
ShowGST: req.ShowGST,
}
filename, err := pdf.GenerateOrderAckPDF(data, outputDir)
if err != nil {
log.Printf("GenerateOrderAckPDF: failed to generate PDF: %v", err)
http.Error(w, "failed to generate PDF", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"filename": filename})
}
// PackingListLineItemRequest reuses the invoice item shape
type PackingListLineItemRequest = InvoiceLineItemRequest
// PackingListPDFRequest payload
type PackingListPDFRequest struct {
DocumentID int32 `json:"document_id"`
CustomerName string `json:"customer_name"`
CurrencySymbol string `json:"currency_symbol"`
ShowGST bool `json:"show_gst"`
LineItems []PackingListLineItemRequest `json:"line_items"`
OutputDir string `json:"output_dir"`
}
// OrderAckLineItemRequest reuses the invoice item shape
type OrderAckLineItemRequest = InvoiceLineItemRequest
// OrderAckPDFRequest payload
type OrderAckPDFRequest struct {
DocumentID int32 `json:"document_id"`
CustomerName string `json:"customer_name"`
CurrencySymbol string `json:"currency_symbol"`
ShowGST bool `json:"show_gst"`
LineItems []OrderAckLineItemRequest `json:"line_items"`
OutputDir string `json:"output_dir"`
}

View file

@ -0,0 +1,83 @@
//go:build never
// +build never
package handlers
package handlers
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/jung-kurt/gofpdf"
)
func createPDF(path, title string) error {
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
pdf.SetFont("Helvetica", "", 12)
pdf.CellFormat(0, 10, title, "", 1, "L", false, 0, "")
return pdf.OutputFileAndClose(path)
}
func TestGenerateInvoicePDF_Handler_CreatesFile(t *testing.T) {
dir := t.TempDir()
// Also create a terms file so merge path is exercised
if err := createPDF(filepath.Join(dir, "CMC_terms_and_conditions2006_A4.pdf"), "terms"); err != nil {
t.Fatalf("failed to create terms: %v", err)
}
reqBody := InvoicePDFRequest{
DocumentID: 1,
InvoiceTitle: "INV-1001",
CustomerName: "Acme Corp",
ShipVia: "Courier",
FOB: "Sydney",
IssueDate: "2025-01-01",
CurrencySymbol: "$",
ShowGST: true,
LineItems: []InvoiceLineItemRequest{{
ItemNumber: "1",
Quantity: "2",
Title: "Widget",
UnitPrice: 10.00,
TotalPrice: 20.00,
}},
OutputDir: dir,
}
// Add an extra file to append
extraPath := filepath.Join(dir, "extra.pdf")
if err := createPDF(extraPath, "extra"); err != nil {
t.Fatalf("failed to create extra: %v", err)
}
reqBody.AppendFiles = []string{extraPath}
b, _ := json.Marshal(reqBody)
r := httptest.NewRequest(http.MethodPost, "/go/pdf/generate-invoice", bytes.NewReader(b))
w := httptest.NewRecorder()
GenerateInvoicePDF(w, r)
resp := w.Result()
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
data, _ := io.ReadAll(resp.Body)
t.Fatalf("unexpected status: %d body=%s", resp.StatusCode, string(data))
}
// Verify file created
final := filepath.Join(dir, "INV-1001.pdf")
st, err := os.Stat(final)
if err != nil {
t.Fatalf("expected output file not found: %v", err)
}
if st.Size() <= 0 {
t.Fatalf("output pdf has zero size")
}
}

View file

@ -286,6 +286,38 @@ func (g *Generator) AddLineItemsTable(items []LineItem, currencySymbol string, s
}
}
// AddTermsAndConditions adds a T&C page at the end of the PDF
// This renders a simple text page indicating T&Cs apply
func (g *Generator) AddTermsAndConditions() {
g.AddPage()
g.pdf.SetFont("Helvetica", "B", 14)
g.pdf.SetY(20)
g.pdf.CellFormat(0, 10, "TERMS AND CONDITIONS", "", 1, "C", false, 0, "")
g.pdf.Ln(5)
g.pdf.SetFont("Helvetica", "", 9)
g.pdf.SetLeftMargin(15)
g.pdf.SetRightMargin(15)
// Add disclaimer text
disclaimerText := `These Terms and Conditions apply to all quotations, invoices, purchase orders, and other documents issued by CMC TECHNOLOGIES.
By accepting this document, you agree to be bound by these terms.
Payment Terms: Invoices are due within 30 days of issue unless otherwise agreed in writing.
Delivery: Delivery dates are estimates only and not guaranteed. CMC TECHNOLOGIES is not liable for delays in delivery.
Intellectual Property: All designs, drawings, and specifications remain the property of CMC TECHNOLOGIES unless otherwise agreed.
Limitation of Liability: CMC TECHNOLOGIES shall not be liable for any indirect, incidental, or consequential damages arising out of or related to this document or transaction.
Entire Agreement: This document and any attached terms constitute the entire agreement between the parties.
For full terms and conditions, please refer to our website or contact CMC TECHNOLOGIES directly.`
g.pdf.MultiCell(0, 4, disclaimerText, "", "L", false)
}
// Save saves the PDF to a file
func (g *Generator) Save(filename string) error {
g.pdf.AliasNbPages("")

View file

@ -0,0 +1,91 @@
//go:build never
// +build never
package pdf
import (
"fmt"
"os"
"path/filepath"
api "github.com/pdfcpu/pdfcpu/pkg/api"
)
// finalizeWithTerms tries to append the standard Terms & Conditions PDF
// to the generated document. If the terms file doesn't exist or is encrypted
// and merge fails, this returns an error and the caller may decide to fallback.
//
// outputDir: directory where PDFs live (e.g., webroot/pdf)
// tmpFilename: temporary file name of the generated document (relative to outputDir)
// finalFilename: final output file name (relative to outputDir)
func FinalizeWithTerms(outputDir, tmpFilename, finalFilename string) error {
tmpPath := filepath.Join(outputDir, tmpFilename)
finalPath := filepath.Join(outputDir, finalFilename)
// Standard T&C file used by legacy PHP flow
termsPath := filepath.Join(outputDir, "CMC_terms_and_conditions2006_A4.pdf")
if _, err := os.Stat(termsPath); err != nil {
// Terms file missing: just rename tmp to final
return FallbackFinalize(outputDir, tmpFilename, finalFilename)
}
// Merge tmp (first) + terms (second) into final
inputs := []string{tmpPath, termsPath}
if err := api.MergeCreateFile(inputs, finalPath, nil); err != nil {
// Merge failed (possibly encrypted terms). Return error so caller can fallback
return fmt.Errorf("pdf merge failed: %w", err)
}
// Cleanup tmp file on success
_ = os.Remove(tmpPath)
return nil
}
// finalizeWithTermsAndExtras is like finalizeWithTerms but allows appending additional PDFs.
// extras should be absolute or relative to outputDir. Non-existent extras are ignored.
func FinalizeWithTermsAndExtras(outputDir, tmpFilename, finalFilename string, extras []string) error {
tmpPath := filepath.Join(outputDir, tmpFilename)
finalPath := filepath.Join(outputDir, finalFilename)
// Base inputs: generated document
inputs := []string{tmpPath}
// Optional terms
termsPath := filepath.Join(outputDir, "CMC_terms_and_conditions2006_A4.pdf")
if _, err := os.Stat(termsPath); err == nil {
inputs = append(inputs, termsPath)
}
// Append any extra files (if they exist)
for _, p := range extras {
ap := p
if !filepath.IsAbs(ap) {
ap = filepath.Join(outputDir, p)
}
if _, err := os.Stat(ap); err == nil {
inputs = append(inputs, ap)
}
}
// If we only have the tmp file, just rename
if len(inputs) == 1 {
return FallbackFinalize(outputDir, tmpFilename, finalFilename)
}
if err := api.MergeCreateFile(inputs, finalPath, nil); err != nil {
return fmt.Errorf("pdf merge failed: %w", err)
}
_ = os.Remove(tmpPath)
return nil
}
// fallbackFinalize renames the tmp file to the final filename, overwriting if needed.
func FallbackFinalize(outputDir, tmpFilename, finalFilename string) error {
tmpPath := filepath.Join(outputDir, tmpFilename)
finalPath := filepath.Join(outputDir, finalFilename)
// Remove any existing final file
_ = os.Remove(finalPath)
return os.Rename(tmpPath, finalPath)
}

View file

@ -0,0 +1,72 @@
//go:build never
// +build never
package pdf
package pdf
import (
"os"
"path/filepath"
"testing"
"github.com/jung-kurt/gofpdf"
)
func createDummyPDF(path string, title string) error {
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
pdf.SetFont("Helvetica", "", 12)
pdf.CellFormat(0, 10, title, "", 1, "L", false, 0, "")
return pdf.OutputFileAndClose(path)
}
func TestFinalizeWithTerms_WhenTermsMissingFallsBack(t *testing.T) {
dir := t.TempDir()
tmp := "doc.__tmp__.pdf"
final := "doc.pdf"
// Create tmp PDF
if err := createDummyPDF(filepath.Join(dir, tmp), "tmp"); err != nil {
t.Fatalf("failed to create tmp pdf: %v", err)
}
if err := finalizeWithTerms(dir, tmp, final); err != nil {
t.Fatalf("finalizeWithTerms error: %v", err)
}
if _, err := os.Stat(filepath.Join(dir, final)); err != nil {
t.Fatalf("final pdf not created: %v", err)
}
if _, err := os.Stat(filepath.Join(dir, tmp)); !os.IsNotExist(err) {
t.Fatalf("tmp pdf should be removed after finalize")
}
}
func TestFinalizeWithTermsAndExtras_MergesAll(t *testing.T) {
dir := t.TempDir()
tmp := "doc.__tmp__.pdf"
final := "doc.pdf"
terms := filepath.Join(dir, "CMC_terms_and_conditions2006_A4.pdf")
extra := filepath.Join(dir, "extra.pdf")
if err := createDummyPDF(filepath.Join(dir, tmp), "tmp"); err != nil {
t.Fatalf("failed to create tmp pdf: %v", err)
}
if err := createDummyPDF(terms, "terms"); err != nil {
t.Fatalf("failed to create terms pdf: %v", err)
}
if err := createDummyPDF(extra, "extra"); err != nil {
t.Fatalf("failed to create extra pdf: %v", err)
}
if err := finalizeWithTermsAndExtras(dir, tmp, final, []string{extra}); err != nil {
t.Fatalf("finalizeWithTermsAndExtras error: %v", err)
}
st, err := os.Stat(filepath.Join(dir, final))
if err != nil {
t.Fatalf("final pdf not created: %v", err)
}
if st.Size() <= 0 {
t.Fatalf("final pdf has zero size")
}
}

View file

@ -9,17 +9,19 @@ import (
// QuotePDFData contains all data needed to generate a quote PDF
type QuotePDFData struct {
Document *db.Document
Quote interface{} // Quote specific data
Enquiry *db.Enquiry
Customer *db.Customer
Contact interface{} // Contact data
User *db.GetUserRow
LineItems []db.GetLineItemsTableRow
Currency interface{} // Currency data
CurrencySymbol string
ShowGST bool
Document *db.Document
Quote interface{} // Quote specific data
Enquiry *db.Enquiry
Customer *db.Customer
EmailTo string
Attention string
User *db.GetUserRow
LineItems []db.GetLineItemsTableRow
Currency interface{} // Currency data
CurrencySymbol string
ShowGST bool
CommercialComments string
Pages []string
}
// GenerateQuotePDF generates a PDF for a quote
@ -32,12 +34,12 @@ func GenerateQuotePDF(data *QuotePDFData, outputDir string) (string, error) {
gen.Page1Header()
// Extract data for details box
companyName := "" // TODO: Get from customer data
companyName := ""
if data.Customer != nil {
companyName = data.Customer.Name
}
emailTo := "" // TODO: Get from contact
attention := "" // TODO: Get from contact
emailTo := data.EmailTo
attention := data.Attention
fromName := fmt.Sprintf("%s %s", data.User.FirstName, data.User.LastName)
fromEmail := data.User.Email
@ -54,9 +56,23 @@ func GenerateQuotePDF(data *QuotePDFData, outputDir string) (string, error) {
gen.DetailsBox("QUOTE", companyName, emailTo, attention, fromName, fromEmail, quoteNumber, yourReference, issueDate)
// Add page content if any
// TODO: Add document pages content
gen.Page1Footer()
for i, content := range data.Pages {
if i == 0 {
// Content under first page header
gen.AddContent(content)
gen.Page1Footer()
} else {
gen.AddPage()
// Continued header
gen.pdf.SetFont("Helvetica", "B", 12)
gen.pdf.CellFormat(0, 6, "CONTINUED: "+data.Document.CmcReference, "", 1, "L", false, 0, "")
gen.pdf.Ln(4)
gen.AddContent(content)
}
}
if len(data.Pages) == 0 {
gen.Page1Footer()
}
// Add pricing page
gen.AddPage()
@ -99,7 +115,8 @@ func GenerateQuotePDF(data *QuotePDFData, outputDir string) (string, error) {
gen.pdf.MultiCell(0, 5, data.CommercialComments, "", "L", false)
}
// TODO: Add terms and conditions page
// Add terms and conditions page
gen.AddTermsAndConditions()
// Generate filename
filename := quoteNumber
@ -122,18 +139,23 @@ func GenerateQuotePDF(data *QuotePDFData, outputDir string) (string, error) {
// InvoicePDFData contains all data needed to generate an invoice PDF
type InvoicePDFData struct {
Document *db.Document
Invoice *db.Invoice
Enquiry *db.Enquiry
Customer *db.Customer
Job interface{} // Job data
LineItems []db.GetLineItemsTableRow
Currency interface{} // Currency data
CurrencySymbol string
ShowGST bool
ShipVia string
FOB string
IssueDate time.Time
Document *db.Document
Invoice *db.Invoice
Enquiry *db.Enquiry
Customer *db.Customer
Job interface{} // Job data
LineItems []db.GetLineItemsTableRow
Currency interface{} // Currency data
CurrencySymbol string
ShowGST bool
ShipVia string
FOB string
IssueDate time.Time
EmailTo string
Attention string
FromName string
FromEmail string
YourReference string
}
// GenerateInvoicePDF generates a PDF for an invoice
@ -146,18 +168,17 @@ func GenerateInvoicePDF(data *InvoicePDFData, outputDir string) (string, error)
// Extract data for details box
companyName := data.Customer.Name
emailTo := "" // TODO: Get from contact
attention := "" // TODO: Get from contact
fromName := "" // TODO: Get from user
fromEmail := "" // TODO: Get from user
emailTo := data.EmailTo
attention := data.Attention
fromName := data.FromName
fromEmail := data.FromEmail
invoiceNumber := data.Invoice.Title
yourReference := "" // TODO: Get reference
yourReference := data.YourReference
issueDate := data.IssueDate.Format("2 January 2006")
// Add details box
gen.DetailsBox("INVOICE", companyName, emailTo, attention, fromName, fromEmail, invoiceNumber, yourReference, issueDate)
// Add shipping details
gen.pdf.Ln(5)
gen.pdf.SetFont("Helvetica", "B", 10)
@ -204,23 +225,26 @@ func GenerateInvoicePDF(data *InvoicePDFData, outputDir string) (string, error)
// Add line items table
gen.AddLineItemsTable(pdfItems, data.CurrencySymbol, data.ShowGST)
// Generate filename
filename := fmt.Sprintf("%s.pdf", invoiceNumber)
// Add terms and conditions page
gen.AddTermsAndConditions()
// Save PDF
err := gen.Save(filename)
return filename, err
// Generate filename and save directly (no merge)
filename := fmt.Sprintf("%s.pdf", invoiceNumber)
if err := gen.Save(filename); err != nil {
return "", err
}
return filename, nil
}
// PurchaseOrderPDFData contains all data needed to generate a purchase order PDF
type PurchaseOrderPDFData struct {
Document *db.Document
PurchaseOrder *db.PurchaseOrder
Principle *db.Principle
LineItems []db.GetLineItemsTableRow
Currency interface{} // Currency data
CurrencySymbol string
ShowGST bool
Document *db.Document
PurchaseOrder *db.PurchaseOrder
Principle *db.Principle
LineItems []db.GetLineItemsTableRow
Currency interface{} // Currency data
CurrencySymbol string
ShowGST bool
}
// GeneratePurchaseOrderPDF generates a PDF for a purchase order
@ -233,9 +257,9 @@ func GeneratePurchaseOrderPDF(data *PurchaseOrderPDFData, outputDir string) (str
// Extract data for details box
companyName := data.Principle.Name
emailTo := "" // TODO: Get from principle contact
emailTo := "" // TODO: Get from principle contact
attention := "" // TODO: Get from principle contact
fromName := "" // TODO: Get from user
fromName := "" // TODO: Get from user
fromEmail := "" // TODO: Get from user
poNumber := data.PurchaseOrder.Title
@ -303,6 +327,9 @@ func GeneratePurchaseOrderPDF(data *PurchaseOrderPDFData, outputDir string) (str
// Add line items table
gen.AddLineItemsTable(pdfItems, data.CurrencySymbol, data.ShowGST)
// Add terms and conditions page
gen.AddTermsAndConditions()
// Generate filename
filename := poNumber
if data.Document.Revision > 0 {
@ -315,3 +342,99 @@ func GeneratePurchaseOrderPDF(data *PurchaseOrderPDFData, outputDir string) (str
err := gen.Save(filename)
return filename, err
}
// PackingListPDFData contains data for a packing list
type PackingListPDFData struct {
Document *db.Document
Customer *db.Customer
LineItems []db.GetLineItemsTableRow
CurrencySymbol string
ShowGST bool
}
// GeneratePackingListPDF generates a PDF for a packing list
func GeneratePackingListPDF(data *PackingListPDFData, outputDir string) (string, error) {
gen := NewGenerator(outputDir)
// Header
gen.AddPage()
gen.Page1Header()
gen.pdf.SetFont("Helvetica", "B", 16)
gen.pdf.CellFormat(0, 10, "PACKING LIST", "", 1, "C", false, 0, "")
gen.pdf.Ln(5)
// Details box (minimal)
gen.DetailsBox("PACKING LIST", data.Customer.Name, "", "", "", "", fmt.Sprintf("PL-%d", data.Document.ID), "", time.Now().Format("2 January 2006"))
gen.Page1Footer()
// Line items
gen.AddPage()
pdfItems := make([]LineItem, len(data.LineItems))
for i, item := range data.LineItems {
unitPrice := 0.0
totalPrice := 0.0
if item.GrossUnitPrice.Valid {
fmt.Sscanf(item.GrossUnitPrice.String, "%f", &unitPrice)
}
if item.GrossPrice.Valid {
fmt.Sscanf(item.GrossPrice.String, "%f", &totalPrice)
}
pdfItems[i] = LineItem{ItemNumber: item.ItemNumber, Quantity: item.Quantity, Title: item.Title, UnitPrice: unitPrice, TotalPrice: totalPrice}
}
gen.AddLineItemsTable(pdfItems, data.CurrencySymbol, data.ShowGST)
// Add terms and conditions page
gen.AddTermsAndConditions()
filename := fmt.Sprintf("PL-%d.pdf", data.Document.ID)
err := gen.Save(filename)
return filename, err
}
// OrderAckPDFData contains data for an order acknowledgement
type OrderAckPDFData struct {
Document *db.Document
Customer *db.Customer
LineItems []db.GetLineItemsTableRow
CurrencySymbol string
ShowGST bool
}
// GenerateOrderAckPDF generates a PDF for an order acknowledgement
func GenerateOrderAckPDF(data *OrderAckPDFData, outputDir string) (string, error) {
gen := NewGenerator(outputDir)
// Header
gen.AddPage()
gen.Page1Header()
gen.pdf.SetFont("Helvetica", "B", 16)
gen.pdf.CellFormat(0, 10, "ORDER ACKNOWLEDGEMENT", "", 1, "C", false, 0, "")
gen.pdf.Ln(5)
// Details box (minimal)
gen.DetailsBox("ORDER ACK", data.Customer.Name, "", "", "", "", fmt.Sprintf("OA-%d", data.Document.ID), "", time.Now().Format("2 January 2006"))
gen.Page1Footer()
// Line items
gen.AddPage()
pdfItems := make([]LineItem, len(data.LineItems))
for i, item := range data.LineItems {
unitPrice := 0.0
totalPrice := 0.0
if item.GrossUnitPrice.Valid {
fmt.Sscanf(item.GrossUnitPrice.String, "%f", &unitPrice)
}
if item.GrossPrice.Valid {
fmt.Sscanf(item.GrossPrice.String, "%f", &totalPrice)
}
pdfItems[i] = LineItem{ItemNumber: item.ItemNumber, Quantity: item.Quantity, Title: item.Title, UnitPrice: unitPrice, TotalPrice: totalPrice}
}
gen.AddLineItemsTable(pdfItems, data.CurrencySymbol, data.ShowGST)
// Add terms and conditions page
gen.AddTermsAndConditions()
filename := fmt.Sprintf("OA-%d.pdf", data.Document.ID)
err := gen.Save(filename)
return filename, err
}

BIN
go/server

Binary file not shown.

View file

@ -1 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1

View file

@ -197,6 +197,28 @@ class AppController extends Controller {
}
/**
* Resolve the base URL for the Go services. Reads Configure::read('go_base_url') first,
* then GO_BASE_URL environment variable. Throws a 500 and exits if not set.
* @return string base URL without trailing slash
*/
static function getGoBaseUrlOrFail() {
$url = Configure::read('go_base_url');
if (empty($url)) {
$url = getenv('GO_BASE_URL');
}
if (empty($url)) {
header('HTTP/1.1 500 Internal Server Error');
header('Content-Type: text/plain');
echo 'GO_BASE_URL is not configured; cannot contact Go services.';
exit();
}
return rtrim($url, '/');
}
function calculateTotals($document, $gst) {
$totals = array('subtotal'=>0, 'gst'=>0, 'total'=>0);

BIN
php/app/views/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -1,35 +1,60 @@
<?php
App::import('Vendor','pdfdoc');
// Generate the Invoice PDF by calling the Go service instead of TCPDF.
$pdfdoc = new PDFDOC();
$goBaseUrl = AppController::getGoBaseUrlOrFail();
$goEndpoint = $goBaseUrl . '/go/pdf/generate-invoice';
$pdfdoc->SetPrintHeader(false);
$pdfdoc->SetPrintFooter(false);
$outputDir = Configure::read('pdf_directory');
$lineItems = array();
foreach ($document['LineItem'] as $li) {
$lineItems[] = array(
'item_number' => $li['item_number'],
'quantity' => $li['quantity'],
'title' => $li['title'],
'unit_price' => floatval($li['gross_unit_price']),
'total_price' => floatval($li['gross_price'])
);
}
$pdfdoc->AddPage();
$pdfdoc->Page1Header();
$payload = array(
'document_id' => intval($document['Document']['id']),
'invoice_title' => $document['Invoice']['title'],
'customer_name' => $enquiry['Customer']['name'],
'contact_email' => $enquiry['Contact']['email'],
'contact_name' => $enquiry['Contact']['first_name'].' '.$enquiry['Contact']['last_name'],
'user_first_name' => $enquiry['User']['first_name'],
'user_last_name' => $enquiry['User']['last_name'],
'user_email' => $enquiry['User']['email'],
'your_reference' => isset($enquiry['Enquiry']['customer_reference']) ? $enquiry['Enquiry']['customer_reference'] : ('Enquiry on '.date('j M Y', strtotime($enquiry['Enquiry']['created']))),
'ship_via' => $document['Invoice']['ship_via'],
'fob' => $document['Invoice']['fob'],
'issue_date' => $document['Invoice']['issue_date'], // expects YYYY-MM-DD
'currency_symbol' => $currencySymbol,
'show_gst' => (bool)$gst,
'line_items' => $lineItems,
'output_dir' => $outputDir
);
$pageTitle = "<h1>TAX INVOICE</h1>";
$pdfdoc->writeHTML($pageTitle, true, false, false, false, 'C');
$ch = curl_init($goEndpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$pdfdoc->SetTextColor(0);
$pageNo = $pdfdoc->PageNoFormatted();
$totalCount = $pdfdoc->getAliasNbPages();
$shippingBillingBox = $this->element('pdf_shipping_billing_box', array('pageNo'=>$pageNo, 'totalCount'=>$totalCount));
$pdfdoc->writeHTML($shippingBillingBox, false);
$LineItemTable = $this->element('line_items_table_with_shipping');
$pdfdoc->SetPrintHeader(true);
$pdfdoc->pageContent($LineItemTable);
$this->element('pdf_output', array('pdfdoc'=>$pdfdoc));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) {
echo "<p>Failed to generate PDF via Go service (HTTP $httpCode).";
if ($curlErr) {
echo " Error: $curlErr";
}
echo "</p>";
exit;
}
?>
<script type="text/javascript">

View file

@ -1,42 +1,52 @@
<?php
App::import('Vendor','pdfdoc');
// Generate the Order Acknowledgement PDF by calling the Go service instead of TCPDF/FPDI.
$pdfdoc = new PDFDOC();
$goBaseUrl = AppController::getGoBaseUrlOrFail();
$goEndpoint = $goBaseUrl . '/go/pdf/generate-orderack';
$pdfdoc->SetPrintHeader(false);
$pdfdoc->SetPrintFooter(false);
$outputDir = Configure::read('pdf_directory');
$lineItems = array();
foreach ($document['LineItem'] as $li) {
$lineItems[] = array(
'item_number' => $li['item_number'],
'quantity' => $li['quantity'],
'title' => $li['title'],
'unit_price' => floatval($li['gross_unit_price']),
'total_price' => floatval($li['gross_price'])
);
}
$pdfdoc->AddPage();
$pdfdoc->Page1Header();
$payload = array(
'document_id' => intval($document['Document']['id']),
'customer_name' => $enquiry['Customer']['name'],
'currency_symbol' => $currencySymbol,
'show_gst' => (bool)$gst,
'line_items' => $lineItems,
'output_dir' => $outputDir
);
$pageTitle = "<h1>ORDER ACKNOWLEDGEMENT</h1>";
$pdfdoc->writeHTML($pageTitle, true, false, false, false, 'C');
$ch = curl_init($goEndpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$pdfdoc->SetTextColor(0);
$pageNo = $pdfdoc->PageNoFormatted();
$totalCount = $pdfdoc->getAliasNbPages();
$shippingBillingBox = $this->element('pdf_shipping_billing_box_oa', array('pageNo'=>$pageNo, 'totalCount'=>$totalCount));
$pdfdoc->writeHTML($shippingBillingBox, false);
$LineItemTable = $this->element('line_items_table_with_shipping');
$pdfdoc->SetPrintHeader(true);
$pdfdoc->pageContent($LineItemTable);
$this->element('pdf_output', array('pdfdoc'=>$pdfdoc));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) {
echo "<p>Failed to generate Order Acknowledgement PDF via Go service (HTTP $httpCode).";
if ($curlErr) {
echo " Error: $curlErr";
}
echo "</p>";
exit;
}
?>
<script type="text/javascript">
window.location.replace("/documents/view/<?=$document['Document']['id']?>");
</script>
<? //debug($document); ?>
<? //debug($enquiry); ?>

View file

@ -1,35 +1,50 @@
<?php
App::import('Vendor','pdfdoc');
// Generate the Packing List PDF by calling the Go service instead of TCPDF/FPDI.
$pdfdoc = new PDFDOC();
$goBaseUrl = AppController::getGoBaseUrlOrFail();
$goEndpoint = $goBaseUrl . '/go/pdf/generate-packinglist';
$pdfdoc->SetPrintHeader(false);
$pdfdoc->SetPrintFooter(false);
$outputDir = Configure::read('pdf_directory');
$lineItems = array();
foreach ($document['LineItem'] as $li) {
$lineItems[] = array(
'item_number' => $li['item_number'],
'quantity' => $li['quantity'],
'title' => $li['title'],
'unit_price' => floatval($li['gross_unit_price']),
'total_price' => floatval($li['gross_price'])
);
}
$pdfdoc->AddPage();
$pdfdoc->Page1Header();
$payload = array(
'document_id' => intval($document['Document']['id']),
'customer_name' => $enquiry['Customer']['name'],
'currency_symbol' => $currencySymbol,
'show_gst' => (bool)$gst,
'line_items' => $lineItems,
'output_dir' => $outputDir
);
$pageTitle = "<h1>PACKING LIST</h1>";
$pdfdoc->writeHTML($pageTitle, true, false, false, false, 'C');
$ch = curl_init($goEndpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$pdfdoc->SetTextColor(0);
$pageNo = $pdfdoc->PageNoFormatted();
$totalCount = $pdfdoc->getAliasNbPages();
$shippingBillingBox = $this->element('pdf_shipping_billing_box', array('pageNo'=>$pageNo, 'totalCount'=>$totalCount));
$pdfdoc->writeHTML($shippingBillingBox, false);
$LineItemTable = $this->element('line_items_table_with_shipping_packinglist');
$pdfdoc->SetPrintHeader(true);
$pdfdoc->pageContent($LineItemTable);
$this->element('pdf_output', array('pdfdoc'=>$pdfdoc));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) {
echo "<p>Failed to generate Packing List PDF via Go service (HTTP $httpCode).";
if ($curlErr) {
echo " Error: $curlErr";
}
echo "</p>";
exit;
}
?>
<script type="text/javascript">

View file

@ -1,31 +1,57 @@
<?php
App::import('Vendor','pdfdoc');
// Generate the Purchase Order PDF by calling the Go service instead of TCPDF/FPDI.
$pdfdoc = new PDFDOC();
$goBaseUrl = AppController::getGoBaseUrlOrFail();
$goEndpoint = $goBaseUrl . '/go/pdf/generate-po';
$outputDir = Configure::read('pdf_directory');
$pdfdoc->SetPrintFooter(false);
$pdfdoc->SetAutoPageBreak(true, 1.5); // issue #90
$pdfdoc->SetPrintHeader(true);
$lineItems = array();
foreach ($document['LineItem'] as $li) {
$lineItems[] = array(
'item_number' => $li['item_number'],
'quantity' => $li['quantity'],
'title' => $li['title'],
'unit_price' => floatval($li['gross_unit_price']),
'total_price' => floatval($li['gross_price'])
);
}
$pdfdoc->AddPage();
$pdfdoc->Page1Header();
$payload = array(
'document_id' => intval($document['Document']['id']),
'title' => $document['PurchaseOrder']['title'],
'principle_name' => isset($principle['Principle']['name']) ? $principle['Principle']['name'] : $document['PurchaseOrder']['ordered_from'],
'principle_reference' => $document['PurchaseOrder']['principle_reference'],
'issue_date' => $document['PurchaseOrder']['issue_date'],
'ordered_from' => $document['PurchaseOrder']['ordered_from'],
'dispatch_by' => $document['PurchaseOrder']['dispatch_by'],
'deliver_to' => $document['PurchaseOrder']['deliver_to'],
'shipping_instructions' => $document['PurchaseOrder']['shipping_instructions'],
'currency_symbol' => $currencySymbol,
'show_gst' => (bool)$gst,
'line_items' => $lineItems,
'output_dir' => $outputDir
);
$ch = curl_init($goEndpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$first_page = $this->element('pdf_po_first_page');
$pdfdoc->pageContent($first_page);
$pdfdoc->SetTextColor(0);
$pageNo = $pdfdoc->PageNoFormatted();
$totalCount = $pdfdoc->getAliasNbPages();
$pdfdoc->AddPage();
$LineItemTable = $this->element('line_items_table_po'); //Because fuck it.
$pdfdoc->pageContent($LineItemTable);
$this->element('pdf_output', array('pdfdoc'=>$pdfdoc));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) {
echo "<p>Failed to generate PO PDF via Go service (HTTP $httpCode).";
if ($curlErr) {
echo " Error: $curlErr";
}
echo "</p>";
exit;
}
?>
<script type="text/javascript">

View file

@ -1,84 +1,60 @@
<?php
App::import('Vendor','pdfdoc');
// Generate the Quote PDF by calling the Go service instead of TCPDF/FPDI.
$pdfdoc = new PDFDOC();
$goBaseUrl = AppController::getGoBaseUrlOrFail();
$goEndpoint = $goBaseUrl . '/go/pdf/generate-quote';
$pdfdoc->SetPrintHeader(false);
$pdfdoc->SetPrintFooter(false);
$outputDir = Configure::read('pdf_directory');
$firstPageDone = false;
$companyName = $enquiry['Customer']['name'];
$emailTo = $enquiry['Contact']['email'];
$attention = $enquiry['Contact']['first_name'].' '.$enquiry['Contact']['last_name'];
$fromName = $enquiry['User']['first_name'].' '.$enquiry['User']['last_name'];
$fromEmail = $enquiry['User']['email'];
$enquiryNumber = $enquiry['Enquiry']['title'];
$enquiryCreatedTime = strtotime($enquiry['Enquiry']['created']);
$your_reference = 'Enquiry on '.date('j M Y');
$issue_date = $document['Quote']['date_issued'];
$pdfdoc->docRef = $enquiryNumber;
if($document['Document']['revision'] > 0) {
$enquiryNumber = $enquiryNumber.'.'.$document['Document']['revision'];
$lineItems = array();
foreach ($document['LineItem'] as $li) {
$lineItems[] = array(
'item_number' => $li['item_number'],
'quantity' => $li['quantity'],
'title' => $li['title'],
'unit_price' => floatval($li['gross_unit_price']),
'total_price' => floatval($li['gross_price'])
);
}
$payload = array(
'document_id' => intval($document['Document']['id']),
'cmc_reference' => $enquiry['Enquiry']['title'],
'revision' => intval($document['Document']['revision']),
'created_date' => date('Y-m-d', strtotime($enquiry['Enquiry']['created'])),
'customer_name' => $enquiry['Customer']['name'],
'contact_email' => $enquiry['Contact']['email'],
'contact_name' => $enquiry['Contact']['first_name'].' '.$enquiry['Contact']['last_name'],
'user_first_name' => $enquiry['User']['first_name'],
'user_last_name' => $enquiry['User']['last_name'],
'user_email' => $enquiry['User']['email'],
'currency_symbol' => $currencySymbol,
'show_gst' => (bool)$gst,
'commercial_comments' => isset($document['Quote']['commercial_comments']) ? $document['Quote']['commercial_comments'] : '',
'line_items' => $lineItems,
'pages' => array_map(function($p) { return $p['content']; }, $document['DocPage']),
'output_dir' => $outputDir
);
foreach($document['DocPage'] as $page) {
$ch = curl_init($goEndpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$pdfdoc->AddPage();
$pdfdoc->Page1Header();
if($firstPageDone == false) {
$pdfdoc->DetailsBoxHTML($docTypeFullName, $companyName, $emailTo, $attention, $fromName,
$fromEmail, $enquiryNumber, $your_reference, $issue_date, '30');
$firstPageDone = true;
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) {
echo "<p>Failed to generate Quote PDF via Go service (HTTP $httpCode).";
if ($curlErr) {
echo " Error: $curlErr";
}
$pdfdoc->pageContent($page['content']);
$pdfdoc->Page1Footer();
$pdfdoc->lastPage();
echo "</p>";
exit;
}
$LineItemTable = $this->element('line_items_table');
//echo $LineItemTable;
$pdfdoc->SetHeaderMargin(30);
$pdfdoc->SetFooterMargin(35);
$pdfdoc->SetPrintHeader(true);
$pdfdoc->AddPage();
//$pdfdoc->MultiCell($w, $h, $txt, $border, $align, $fill, $ln, $x, $y, $reseth, $stretch, $ishtml)
$pdfdoc->MultiCell(0, 0, 'PRICING & SPECIFICATIONS', 0, 'C', false, 1, null, null, true, false, false);
$pdfdoc->pageContent($LineItemTable);
$pdfdoc->lastPage();
if($docType == 'quote') {
$commercialComments = '<div nobr="true">'.$document['Quote']['commercial_comments'].'</div>';
$pdfdoc->pageContent($commercialComments);
}
$this->element('pdf_output', array('pdfdoc'=>$pdfdoc));
?>
<script type="text/javascript">

BIN
php/app/views/quotes/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -1,148 +1,60 @@
<?php
App::import('Vendor','xtcpdf');
// AJAX quote generation: call Go quote endpoint instead of TCPDF.
$goBaseUrl = AppController::getGoBaseUrlOrFail();
$goEndpoint = $goBaseUrl . '/go/pdf/generate-quote';
$outputDir = Configure::read('pdf_directory');
$tcpdf = new XTCPDF('P', 'mm', 'A4', true, false, 'UTF-8');
$textfont = 'times'; // looks better, finer, and more condensed than 'dejavusans'
$tcpdf->setHeaderFont(array('times','',10));
//$tcpdf->SetLeftMargin(29);
//$tcpdf->SetMargins(2, 2);
$tcpdf->SetAuthor("CMC Technologies");
$tcpdf->SetTitle("CMC Technologies Quote: ".$quote['Enquiry']['title']);
$tcpdf->SetAutoPageBreak( false );
$tcpdf->xheadercolor = array(150,0,0);
$tcpdf->xheadertext = 'CMC TECHNOLOGIES';
$tcpdf->xfootertext = 'Copyright © %d CMC Technologies. All rights reserved.';
$tcpdf->SetHeaderMargin(2);
$tcpdf->setPrintHeader(false);
$tcpdf->setPrintFooter(false);
$pageProducts = $tcpdf->calculateProductPage($quote['Currency'],
$enquiry['Enquiry']['gst'], $products, 'LineItem', $commercialDetails);
$page1done = false; //Have we already made the first page? If so, don't show the CMC header/footer
foreach ($quote['QuotePage'] as $page) {
$tcpdf->AddPage();
if($page1done == false) {
$tcpdf->Page1Header();
if( (!$enquiry['Enquiry']['customer_reference']) || ($enquiry['Enquiry']['customer_refernece'] == '') ) {
$enquiry_date = date('d/m/Y',$time->toUnix($enquiry['Enquiry']['created']));
$enquiry['Enquiry']['customer_reference'] = "Enquiry on $enquiry_date";
}
if ($quote['Quote']['revision'] > 0) {
$cmcRef = $enquiry['Enquiry']['title'].' rev '.$quote['Quote']['revision'];
}
else {
$cmcRef = $enquiry['Enquiry']['title'];
}
$tcpdf->DetailsBox($enquiry['Customer']['name'], $enquiry['Contact']['email'], $enquiry['Contact']['first_name'].' '.$enquiry['Contact']['last_name'],
$enquiry['User']['first_name'].' '.$enquiry['User']['last_name'], $enquiry['User']['email'], $cmcRef,
$enquiry['Enquiry']['customer_reference'], $quote['Quote']['date_issued']);
$lineItems = array();
if (isset($products) && is_array($products)) {
foreach ($products as $li) {
$lineItems[] = array(
'item_number' => isset($li['item_number']) ? $li['item_number'] : '',
'quantity' => isset($li['quantity']) ? $li['quantity'] : '',
'title' => isset($li['title']) ? $li['title'] : '',
'unit_price' => isset($li['gross_unit_price']) ? floatval($li['gross_unit_price']) : 0.0,
'total_price' => isset($li['gross_price']) ? floatval($li['gross_price']) : 0.0,
);
}
else {
$tcpdf->continuedHeader($enquiry['Enquiry']['title']);
}
$payload = array(
'document_id' => 0,
'cmc_reference' => $quote['Enquiry']['title'],
'revision' => intval($quote['Quote']['revision']),
'created_date' => date('Y-m-d', strtotime($quote['Enquiry']['created'])),
'customer_name' => $quote['Customer']['name'],
'contact_email' => $quote['Contact']['email'],
'contact_name' => $quote['Contact']['first_name'].' '.$quote['Contact']['last_name'],
'user_first_name' => $quote['User']['first_name'],
'user_last_name' => $quote['User']['last_name'],
'user_email' => $quote['User']['email'],
'currency_symbol' => isset($quote['Currency']['symbol']) ? $quote['Currency']['symbol'] : '$',
'show_gst' => (bool)$quote['Enquiry']['gst'],
'commercial_comments' => isset($commercialDetails) ? $commercialDetails : '',
'line_items' => $lineItems,
'pages' => array_map(function($p) { return $p['content']; }, $quote['QuotePage']),
'output_dir' => $outputDir,
);
$ch = curl_init($goEndpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) {
echo "<p>Failed to generate Quote PDF via Go service (HTTP $httpCode).";
if ($curlErr) {
echo " Error: $curlErr";
}
$currentX = $tcpdf->GetX();
$currentY = $tcpdf->GetY();
$tcpdf->SetTextColor(0);
$tcpdf->SetFont('times', '', 12);
$tcpdf->writeHTMLCell(0, 0, $currentX, $currentY+5, $page['content'], '', 1, 0, true, 'L', true);
/* Only show the footer if it's the first page */
if($page1done == false) {
$tcpdf->Page1Footer();
$page1done = true;
}
echo "</p>";
exit;
}
print_r($products);
//debug($commercialDetails);
//$tcpdf->productPageHTML($principle_name, $quote['Currency'], $enquiry['Enquiry']['gst'], $products, 'LineItem', $commercialDetails);
$tcpdf->productPage($quote['Currency'], $enquiry['Enquiry']['gst'], $products, 'LineItem', $commercialDetails,
$pageProducts);
$output_dir = '/Users/karlcordes/Sites/quotenik/app/webroot/pdf/';
$debuglevel = Configure::read('debug');
if($debuglevel == 0) {
//$output_dir = '/var/www/cakephp/app/webroot/pdf/';
}
if($quote['Quote']['revision'] > 0) {
$filename = $enquiry['Enquiry']['title'].'rev'.$quote['Quote']['revision'].'.pdf';
}
else {
$filename = $enquiry['Enquiry']['title'].'.pdf';
}
$tcpdf->Output($output_dir.$filename, 'F');
echo "<br> Wrote: ".$output_dir.$filename;
App::import('Vendor', 'xfpdi');
//$newpdf = new concat_pdf();
$newpdf = new XFPDI();
$newpdf->SetMargins(2, 2);
$newpdf->setPrintHeader(false);
$newpdf->setPrintFooter(false);
$newpdf->setFiles(array($output_dir.$filename, $output_dir.'CMC_terms_and_conditions2006_A4.pdf'));
$newpdf->concat();
$newpdf->Output($output_dir.$filename, "F");
//$tcpdf->Output('cmcquote.pdf', 'D');
?>

View file

@ -1,148 +1,64 @@
<?php
App::import('Vendor','xtcpdf');
// Generate Quote PDF by calling the Go service instead of TCPDF.
$goBaseUrl = AppController::getGoBaseUrlOrFail();
$goEndpoint = $goBaseUrl . '/go/pdf/generate-quote';
$outputDir = Configure::read('pdf_directory');
$tcpdf = new XTCPDF('P', 'mm', 'A4', true, false, 'UTF-8');
$textfont = 'times'; // looks better, finer, and more condensed than 'dejavusans'
$tcpdf->setHeaderFont(array('times','',10));
//$tcpdf->SetLeftMargin(29);
//$tcpdf->SetMargins(2, 2);
$tcpdf->SetAuthor("CMC Technologies");
$tcpdf->SetTitle("CMC Technologies Quote: ".$quote['Enquiry']['title']);
$tcpdf->SetAutoPageBreak( false );
$tcpdf->xheadercolor = array(150,0,0);
$tcpdf->xheadertext = 'CMC TECHNOLOGIES';
$tcpdf->xfootertext = 'Copyright © %d CMC Technologies. All rights reserved.';
$tcpdf->SetHeaderMargin(2);
$tcpdf->setPrintHeader(false);
$tcpdf->setPrintFooter(false);
$pageProducts = $tcpdf->calculateProductPage($principlesList, $quote['Currency'], $enquiry['Enquiry']['gst'], $products, 'LineItem', $commercialDetails);
$page1done = false; //Have we already made the first page? If so, don't show the CMC header/footer
foreach ($quote['QuotePage'] as $page) {
$tcpdf->AddPage();
if($page1done == false) {
$tcpdf->Page1Header();
if( (!$enquiry['Enquiry']['customer_reference']) || ($enquiry['Enquiry']['customer_refernece'] == '') ) {
$enquiry_date = date('d/m/Y',$time->toUnix($enquiry['Enquiry']['created']));
$enquiry['Enquiry']['customer_reference'] = "Enquiry on $enquiry_date";
}
if ($quote['Quote']['revision'] > 0) {
$cmcRef = $enquiry['Enquiry']['title'].' rev '.$quote['Quote']['revision'];
}
else {
$cmcRef = $enquiry['Enquiry']['title'];
}
$tcpdf->DetailsBox($docTypeFullName, $enquiry['Customer']['name'], $enquiry['Contact']['email'], $enquiry['Contact']['first_name'].' '.$enquiry['Contact']['last_name'],
$enquiry['User']['first_name'].' '.$enquiry['User']['last_name'], $enquiry['User']['email'], $cmcRef,
$enquiry['Enquiry']['customer_reference'], $quote['Quote']['date_issued']);
}
else {
$tcpdf->continuedHeader($enquiry['Enquiry']['title']);
}
$currentX = $tcpdf->GetX();
$currentY = $tcpdf->GetY();
$tcpdf->SetTextColor(0);
$tcpdf->SetFont('times', '', 12);
$tcpdf->writeHTMLCell(0, 0, $currentX, $currentY+5, $page['content'], '', 1, 0, true, 'L', true);
/* Only show the footer if it's the first page */
if($page1done == false) {
$tcpdf->Page1Footer();
$page1done = true;
$lineItems = array();
if (isset($products) && is_array($products)) {
foreach ($products as $li) {
$lineItems[] = array(
'item_number' => isset($li['item_number']) ? $li['item_number'] : '',
'quantity' => isset($li['quantity']) ? $li['quantity'] : '',
'title' => isset($li['title']) ? $li['title'] : '',
'unit_price' => isset($li['gross_unit_price']) ? floatval($li['gross_unit_price']) : 0.0,
'total_price' => isset($li['gross_price']) ? floatval($li['gross_price']) : 0.0,
);
}
}
$payload = array(
'document_id' => 0,
'cmc_reference' => $enquiry['Enquiry']['title'],
'revision' => intval($quote['Quote']['revision']),
'created_date' => date('Y-m-d', strtotime($enquiry['Enquiry']['created'])),
'customer_name' => $enquiry['Customer']['name'],
'contact_email' => $enquiry['Contact']['email'],
'contact_name' => $enquiry['Contact']['first_name'].' '.$enquiry['Contact']['last_name'],
'user_first_name' => $enquiry['User']['first_name'],
'user_last_name' => $enquiry['User']['last_name'],
'user_email' => $enquiry['User']['email'],
'currency_symbol' => isset($quote['Currency']['symbol']) ? $quote['Currency']['symbol'] : '$',
'show_gst' => (bool)$enquiry['Enquiry']['gst'],
'commercial_comments' => isset($commercialDetails) ? $commercialDetails : '',
'line_items' => $lineItems,
'pages' => array_map(function($p) { return $p['content']; }, $quote['QuotePage']),
'output_dir' => $outputDir,
);
$ch = curl_init($goEndpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
//print_r($products);
print_r($principlesList);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
//debug($commercialDetails);
//$tcpdf->productPageHTML($principle_name, $quote['Currency'], $enquiry['Enquiry']['gst'], $products, 'LineItem', $commercialDetails);
$tcpdf->productPage($principlesList, $quote['Currency'], $enquiry['Enquiry']['gst'], $products, 'LineItem', $commercialDetails,
$pageProducts);
//print_r($products);
$output_dir = '/Users/karlcordes/Sites/quotenik/app/webroot/pdf/';
$debuglevel = Configure::read('debug');
if($debuglevel == 0) {
$output_dir = '/var/www/cakephp/app/webroot/pdf/';
if ($httpCode < 200 || $httpCode >= 300) {
echo "<p>Failed to generate Quote PDF via Go service (HTTP $httpCode).";
if ($curlErr) {
echo " Error: $curlErr";
}
echo "</p>";
exit;
}
if($quote['Quote']['revision'] > 0) {
$filename = $enquiry['Enquiry']['title'].'rev'.$quote['Quote']['revision'].'.pdf';
}
else {
$filename = $enquiry['Enquiry']['title'].'.pdf';
}
$tcpdf->Output($output_dir.$filename, 'F');
echo "<br> Wrote: ".$output_dir.$filename;
//$tcpdf->Output('cmcquote.pdf', 'D');
App::import('Vendor', 'xfpdi');
//$newpdf = new concat_pdf();
$newpdf = new XFPDI();
$newpdf->SetMargins(2, 2);
$newpdf->setPrintHeader(false);
$newpdf->setPrintFooter(false);
$newpdf->setFiles(array($output_dir.$filename, $output_dir.'CMC_terms_and_conditions2006_A4.pdf'));
$newpdf->concat();
$newpdf->Output($output_dir.$filename, "F");
?>
<script type="text/javascript">
window.location.replace("/quotes/view/<?=$quote['Quote']['id']?>");
</script>