From 7025f526c10bbf813c33db33d0b6b25f7333c325 Mon Sep 17 00:00:00 2001 From: kalipso Date: Tue, 1 Jul 2025 15:30:06 +0200 Subject: [PATCH] add invoice view --- controllers/configController.go | 32 ++++++ controllers/printController.go | 10 +- go.mod | 1 + go.sum | 2 + main.go | 3 + models/printer.go | 6 + repositories/InvoiceRepository.go | 4 +- static/output.css | 185 +++++++++++++++++++----------- utils/utils.go | 18 ++- views/header.html | 2 + views/invoiceview.html | 77 +++++++++++++ 11 files changed, 257 insertions(+), 83 deletions(-) create mode 100644 views/invoiceview.html diff --git a/controllers/configController.go b/controllers/configController.go index e9b8556..6af35b9 100644 --- a/controllers/configController.go +++ b/controllers/configController.go @@ -21,6 +21,9 @@ type ConfigController interface { PaperHandler(*gin.Context) AddPaperHandler(*gin.Context) + InvoiceView(*gin.Context) + InvoiceHandler(*gin.Context) + CreateTag(*gin.Context) GetAllTags(*gin.Context) TagView(*gin.Context) @@ -208,6 +211,35 @@ func (rc *configController) GetAllPaper(c *gin.Context) { ////////////////////////////////////////////////////////////////////// +func (rc *configController) InvoiceView(c *gin.Context) { + invoices, err := repositories.Invoices.GetAll() + + if err != nil { + c.HTML(http.StatusBadRequest, "invoiceview.html", gin.H{"data": gin.H{"error": err}}) + } + + data := CreateSessionData(c, gin.H{ + "invoices": invoices, + }) + + if err != nil { + c.HTML(http.StatusBadRequest, "invoiceview.html", data) + } + + c.HTML(http.StatusOK, "invoiceview.html", data) +} + +func (rc *configController) InvoiceHandler(ctx *gin.Context) { + action := ctx.PostForm("action") + if action == "delete" { + repositories.Invoices.DeleteById(ctx.Param("id")) + } + + rc.InvoiceView(ctx) +} + +////////////////////////////////////////////////////////////////////// + func (rc *configController) TagHandler(ctx *gin.Context) { name := ctx.PostForm("name") color := ctx.PostForm("color") diff --git a/controllers/printController.go b/controllers/printController.go index 5d850b4..a7bf78f 100644 --- a/controllers/printController.go +++ b/controllers/printController.go @@ -113,6 +113,7 @@ func (rc *printController) PrintHandler(c *gin.Context) { } var printJobs []models.PrintJob + priceTotal := 0.0 for idx := range variantIds { variant, err := repositories.ShopItems.GetVariantById(variantIds[idx]) @@ -160,13 +161,7 @@ func (rc *printController) PrintHandler(c *gin.Context) { return } printJob.CalculatePrintCosts() - printJob, err = repositories.PrintJobs.Create(printJob) - - if err != nil { - c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}}) - return - } - + priceTotal += printJob.PriceTotal printJobs = append(printJobs, printJob) } @@ -174,6 +169,7 @@ func (rc *printController) PrintHandler(c *gin.Context) { PrintJobs: printJobs, PricePerClick: 0.002604, PartCosts: 0.0067, + PriceTotal: priceTotal, } invoice, err := repositories.Invoices.Create(invoice) diff --git a/go.mod b/go.mod index 5dd1a6c..c4aae08 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module git.dynamicdiscord.de/kalipso/zineshop go 1.23.3 require ( + github.com/dslipak/pdf v0.0.2 github.com/gin-gonic/gin v1.10.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/joho/godotenv v1.5.1 diff --git a/go.sum b/go.sum index 030a4a2..2d98493 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ 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/dslipak/pdf v0.0.2 h1:djAvcM5neg9Ush+zR6QXB+VMJzR6TdnX766HPIg1JmI= +github.com/dslipak/pdf v0.0.2/go.mod h1:2L3SnkI9cQwnAS9gfPz2iUoLC0rUZwbucpbKi5R1mUo= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= diff --git a/main.go b/main.go index 09d1b7c..d5f7734 100644 --- a/main.go +++ b/main.go @@ -82,6 +82,9 @@ func main() { viewRoutes.GET("/paper/:id", userController.TagView) viewRoutes.POST("/paper", authValidator.RequireAdmin, configController.AddPaperHandler) + viewRoutes.GET("/invoice", authValidator.RequireAdmin, configController.InvoiceView) + viewRoutes.POST("/invoice/:id", authValidator.RequireAdmin, configController.InvoiceHandler) + viewRoutes.GET("/cart", authValidator.RequireAuth, cartItemController.CartItemView) viewRoutes.POST("/cart", authValidator.RequireAuth, cartItemController.AddItemHandler) viewRoutes.POST("/cart/delete", authValidator.RequireAuth, cartItemController.DeleteItemHandler) diff --git a/models/printer.go b/models/printer.go index e537c4a..9de3af4 100644 --- a/models/printer.go +++ b/models/printer.go @@ -32,6 +32,7 @@ type Invoice struct { PrintJobs []PrintJob PricePerClick float64 PartCosts float64 + PriceTotal float64 } type PrintJob struct { @@ -45,6 +46,8 @@ type PrintJob struct { CoverPaperTypeId *uint CoverPaperType *Paper `gorm:"foreignKey:CoverPaperTypeId"` Amount uint + PricePerPiece float64 + PriceTotal float64 InvoiceID uint } @@ -134,6 +137,7 @@ func (p *PrintJob) CalculatePrintCosts() (float64, error) { //Get actual pagecount depending on printmode actualPageCount := pageCount + fmt.Println("PagCount: ", actualPageCount) if printMode == CreateBooklet { dividedCount := float64(pageCount) / 4.0 @@ -156,5 +160,7 @@ func (p *PrintJob) CalculatePrintCosts() (float64, error) { fmt.Printf("Printing Costs per Zine: %v\n", printingCosts) fmt.Printf("Printing Costs Total: %v\n", printingCosts*float64(p.Amount)) + p.PricePerPiece = printingCosts + p.PriceTotal = printingCosts * float64(p.Amount) return printingCosts, nil } diff --git a/repositories/InvoiceRepository.go b/repositories/InvoiceRepository.go index 53f9130..d073c93 100644 --- a/repositories/InvoiceRepository.go +++ b/repositories/InvoiceRepository.go @@ -28,7 +28,7 @@ func NewGORMInvoiceRepository(db *gorm.DB) InvoiceRepository { } func (t *GORMInvoiceRepository) Create(invoice models.Invoice) (models.Invoice, error) { - result := t.DB.Omit("PrintJobs").Create(&invoice) + result := t.DB.Create(&invoice) if result.Error != nil { return models.Invoice{}, result.Error @@ -39,7 +39,7 @@ func (t *GORMInvoiceRepository) Create(invoice models.Invoice) (models.Invoice, func (t *GORMInvoiceRepository) GetAll() ([]models.Invoice, error) { var invoice []models.Invoice - result := t.DB.Preload("PrintJobs").Find(&invoice) + result := t.DB.Preload("PrintJobs.ShopItem").Preload("PrintJobs.Variant").Preload("PrintJobs.PaperType").Preload("PrintJobs.CoverPaperType").Preload("PrintJobs").Find(&invoice) return invoice, result.Error } diff --git a/static/output.css b/static/output.css index 689dee7..667f7ca 100644 --- a/static/output.css +++ b/static/output.css @@ -599,6 +599,10 @@ video { margin: 1rem; } +.-m-1\.5 { + margin: -0.375rem; +} + .-mx-2 { margin-left: -0.5rem; margin-right: -0.5rem; @@ -674,18 +678,22 @@ video { margin-top: 1.5rem; } -.mb-3 { - margin-bottom: 0.75rem; -} - .block { display: block; } +.inline-block { + display: inline-block; +} + .flex { display: flex; } +.inline-flex { + display: inline-flex; +} + .table { display: table; } @@ -762,6 +770,10 @@ video { width: 100%; } +.min-w-full { + min-width: 100%; +} + .max-w-2xl { max-width: 42rem; } @@ -826,10 +838,6 @@ video { grid-template-columns: repeat(6, minmax(0, 1fr)); } -.grid-cols-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); -} - .flex-col { flex-direction: column; } @@ -838,10 +846,6 @@ video { flex-wrap: wrap; } -.content-stretch { - align-content: stretch; -} - .items-center { align-items: center; } @@ -879,6 +883,11 @@ video { row-gap: 1rem; } +.gap-x-2 { + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; +} + .space-x-4 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(1rem * var(--tw-space-x-reverse)); @@ -920,6 +929,10 @@ video { border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1)); } +.overflow-hidden { + overflow: hidden; +} + .overflow-x-auto { overflow-x: auto; } @@ -987,6 +1000,10 @@ video { border-color: rgb(209 213 219 / var(--tw-border-opacity, 1)); } +.border-transparent { + border-color: transparent; +} + .bg-amber-100 { --tw-bg-opacity: 1; background-color: rgb(254 243 199 / var(--tw-bg-opacity, 1)); @@ -1272,16 +1289,6 @@ video { background-color: rgb(24 24 27 / var(--tw-bg-opacity, 1)); } -.bg-gray-700 { - --tw-bg-opacity: 1; - background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1)); -} - -.bg-gray-500 { - --tw-bg-opacity: 1; - background-color: rgb(107 114 128 / var(--tw-bg-opacity, 1)); -} - .fill-red-50 { fill: #fef2f2; } @@ -1307,6 +1314,10 @@ video { padding: 1rem; } +.p-1\.5 { + padding: 0.375rem; +} + .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; @@ -1382,14 +1393,9 @@ video { padding-bottom: 2rem; } -.py-20 { - padding-top: 5rem; - padding-bottom: 5rem; -} - -.px-8 { - padding-left: 2rem; - padding-right: 2rem; +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; } .pb-3 { @@ -1416,10 +1422,6 @@ video { padding-top: 1.25rem; } -.pb-4 { - padding-bottom: 1rem; -} - .text-left { text-align: left; } @@ -1432,6 +1434,18 @@ video { text-align: right; } +.text-start { + text-align: start; +} + +.text-end { + text-align: end; +} + +.align-middle { + vertical-align: middle; +} + .text-2xl { font-size: 1.5rem; line-height: 2rem; @@ -1503,6 +1517,10 @@ video { font-weight: 600; } +.uppercase { + text-transform: uppercase; +} + .leading-10 { line-height: 2.5rem; } @@ -1784,8 +1802,9 @@ video { color: rgb(39 39 42 / var(--tw-text-opacity, 1)); } -.underline { - text-decoration-line: underline; +.text-blue-600 { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity, 1)); } .antialiased { @@ -1799,24 +1818,18 @@ video { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -.shadow-sm { - --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - -.shadow-xl { - --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - .shadow-md { --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.shadow-sm { + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + .outline { outline-style: solid; } @@ -1941,6 +1954,11 @@ video { color: rgb(255 255 255 / var(--tw-text-opacity, 1)); } +.hover\:text-blue-800:hover { + --tw-text-opacity: 1; + color: rgb(30 64 175 / var(--tw-text-opacity, 1)); +} + .hover\:underline:hover { text-decoration-line: underline; } @@ -1954,6 +1972,11 @@ video { border-color: rgb(59 130 246 / var(--tw-border-opacity, 1)); } +.focus\:text-blue-800:focus { + --tw-text-opacity: 1; + color: rgb(30 64 175 / var(--tw-text-opacity, 1)); +} + .focus\:outline-none:focus { outline: 2px solid transparent; outline-offset: 2px; @@ -2013,6 +2036,14 @@ video { outline-color: #4f46e5; } +.disabled\:pointer-events-none:disabled { + pointer-events: none; +} + +.disabled\:opacity-50:disabled { + opacity: 0.5; +} + .group:hover .group-hover\:fill-red-400 { fill: #f87171; } @@ -2092,10 +2123,6 @@ video { max-width: 24rem; } - .sm\:grid-cols-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - .sm\:items-center { align-items: center; } @@ -2139,10 +2166,6 @@ video { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .md\:grid-cols-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - .md\:flex-row { flex-direction: row; } @@ -2187,18 +2210,14 @@ video { max-width: 80rem; } - .lg\:grid-cols-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - .lg\:grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } - .lg\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .lg\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .lg\:p-8 { padding: 2rem; } @@ -2219,13 +2238,13 @@ video { } @media (min-width: 1280px) { - .xl\:grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } - .xl\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } + + .xl\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } } @media (min-width: 1536px) { @@ -2249,6 +2268,11 @@ video { border-color: rgb(31 41 55 / var(--tw-divide-opacity, 1)); } + .dark\:divide-neutral-700 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(64 64 64 / var(--tw-divide-opacity, 1)); + } + .dark\:border-gray-600 { --tw-border-opacity: 1; border-color: rgb(75 85 99 / var(--tw-border-opacity, 1)); @@ -2309,6 +2333,21 @@ video { color: rgb(255 255 255 / var(--tw-text-opacity, 1)); } + .dark\:text-blue-500 { + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity, 1)); + } + + .dark\:text-neutral-200 { + --tw-text-opacity: 1; + color: rgb(229 229 229 / var(--tw-text-opacity, 1)); + } + + .dark\:text-neutral-500 { + --tw-text-opacity: 1; + color: rgb(115 115 115 / var(--tw-text-opacity, 1)); + } + .dark\:placeholder-gray-400::-moz-placeholder { --tw-placeholder-opacity: 1; color: rgb(156 163 175 / var(--tw-placeholder-opacity, 1)); @@ -2334,11 +2373,21 @@ video { color: rgb(255 255 255 / var(--tw-text-opacity, 1)); } + .dark\:hover\:text-blue-400:hover { + --tw-text-opacity: 1; + color: rgb(96 165 250 / var(--tw-text-opacity, 1)); + } + .dark\:focus\:border-blue-500:focus { --tw-border-opacity: 1; border-color: rgb(59 130 246 / var(--tw-border-opacity, 1)); } + .dark\:focus\:text-blue-400:focus { + --tw-text-opacity: 1; + color: rgb(96 165 250 / var(--tw-text-opacity, 1)); + } + .dark\:focus\:ring-blue-500:focus { --tw-ring-opacity: 1; --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1)); diff --git a/utils/utils.go b/utils/utils.go index 107aaac..b9a693a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,11 +1,11 @@ package utils import ( - "bufio" "crypto/rand" "encoding/hex" + "fmt" + "github.com/dslipak/pdf" "io" - "os" ) func GenerateSessionId(length int) string { @@ -30,6 +30,7 @@ func Pages(reader io.ByteReader) (pages int) { for { b, err := reader.ReadByte() if err != nil { + fmt.Println(err) return } check: @@ -51,10 +52,15 @@ func Pages(reader io.ByteReader) (pages int) { // PagesAtPath opens a PDF file at the given file path, returning the number // of pages found. func CountPagesAtPath(path string) (pages int) { - if reader, err := os.Open(path); err == nil { - reader.Chdir() - pages = Pages(bufio.NewReader(reader)) - reader.Close() + r, err := pdf.Open(path) + + if err != nil { + fmt.Println("LOL") + fmt.Println(err) + return 0 } + + pages = r.NumPage() + return } diff --git a/views/header.html b/views/header.html index 9556eed..4792bcd 100644 --- a/views/header.html +++ b/views/header.html @@ -42,6 +42,8 @@ hover:text-white">Config Paper + Invoices Logout {{ end }} diff --git a/views/invoiceview.html b/views/invoiceview.html new file mode 100644 index 0000000..226f8f1 --- /dev/null +++ b/views/invoiceview.html @@ -0,0 +1,77 @@ +{{ template "header.html" . }} + + +
+
+ Your Company +

Invoices

+
+ + {{ range .data.invoices }} +
+
+
+ Invoice #{{ .ID }} +
+
+
+
+
+
+ + + + + + + + + + + + + + {{ range .PrintJobs }} + + + + + + + + + + + {{ end }} + + + + + + + + + + + +
ImageNameVariantPaperCoverPaperAmountPrice
+ + imac image + + {{ .ShopItem.Name }}{{ .Variant.Name}}{{ .PaperType.Brand }} - {{.PaperType.Name }}: {{ .PaperType.Size }} {{ .PaperType.Weight }}g/m2{{ .PaperType.Brand }} - {{.PaperType.Name }}: {{ .PaperType.Size }} {{ .PaperType.Weight }}g/m2{{ .Amount }}{{ .PriceTotal }}
+ TOTAL + {{ .PriceTotal }}
+
+
+
+
+
+
+ +
+
+
+ {{ end }} +
+ +{{ template "footer.html" . }}