From 861b18651bd77371711aef0727e3a472fc90a4a0 Mon Sep 17 00:00:00 2001 From: kalipso Date: Fri, 27 Jun 2025 16:31:37 +0200 Subject: [PATCH 01/17] add ui configurable config options --- controllers/configController.go | 218 ++++++++++++++++++++++++++++++ controllers/shopItemController.go | 114 ---------------- main.go | 11 +- models/config.go | 11 ++ repositories/configRepository.go | 81 +++++++++++ repositories/repository.go | 16 ++- static/output.css | 19 +-- views/configview.html | 33 +++++ 8 files changed, 365 insertions(+), 138 deletions(-) create mode 100644 controllers/configController.go create mode 100644 models/config.go create mode 100644 repositories/configRepository.go create mode 100644 views/configview.html diff --git a/controllers/configController.go b/controllers/configController.go new file mode 100644 index 0000000..4c46251 --- /dev/null +++ b/controllers/configController.go @@ -0,0 +1,218 @@ +package controllers + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + + "git.dynamicdiscord.de/kalipso/zineshop/models" + //"git.dynamicdiscord.de/kalipso/zineshop/services" + "git.dynamicdiscord.de/kalipso/zineshop/repositories" +) + +type ConfigController interface { + AddConfigHandler(*gin.Context) + ConfigHandler(*gin.Context) + ConfigView(*gin.Context) + CreateTag(*gin.Context) + GetAllTags(*gin.Context) + TagView(*gin.Context) + TagHandler(*gin.Context) + AddTagHandler(*gin.Context) +} + +type configController struct{} + +func NewConfigController() ConfigController { + return &configController{} +} + +func (rc *configController) AddConfigHandler(c *gin.Context) { + key := c.PostForm("key") + value := c.PostForm("value") + + if key == "" || value == "" { + err := "Key or Value empty during config creation" + fmt.Println(err) + c.HTML(http.StatusBadRequest, "configview.html", gin.H{"error": err}) + return + } + + config := models.Config{ + Key: key, + Value: value, + } + + _, err := repositories.ConfigOptions.Create(config) + if err != nil { + data := CreateSessionData(c, gin.H{ + "error": err, + "success": "", + }) + + c.HTML(http.StatusOK, "configview.html", data) + return + } + + rc.ConfigView(c) +} + +func (rc *configController) ConfigView(c *gin.Context) { + configOptions, err := repositories.ConfigOptions.GetAll() + + if err != nil { + c.HTML(http.StatusBadRequest, "configview.html", gin.H{"data": gin.H{"error": err}}) + } + + data := CreateSessionData(c, gin.H{ + "configOptions": configOptions, + }) + + if err != nil { + c.HTML(http.StatusBadRequest, "configview.html", data) + } + + c.HTML(http.StatusOK, "configview.html", data) +} + +func (rc *configController) ConfigHandler(ctx *gin.Context) { + key := ctx.PostForm("key") + value := ctx.PostForm("value") + action := ctx.PostForm("action") + + config, err := repositories.ConfigOptions.GetById(ctx.Param("id")) + + if err != nil { + fmt.Println(err) + ctx.HTML(http.StatusBadRequest, "configview.html", gin.H{"error": err}) + return + } + + if action == "update" { + config.Key = key + config.Value = value + config, err = repositories.ConfigOptions.Update(config) + + if err != nil { + fmt.Println(err) + ctx.HTML(http.StatusBadRequest, "configview.html", gin.H{"error": err}) + return + } + } + + if action == "delete" { + repositories.ConfigOptions.DeleteById(ctx.Param("id")) + } + + rc.ConfigView(ctx) +} + +func (rc *configController) TagHandler(ctx *gin.Context) { + name := ctx.PostForm("name") + color := ctx.PostForm("color") + action := ctx.PostForm("action") + + tag, err := repositories.Tags.GetById(ctx.Param("id")) + + if err != nil { + fmt.Println(err) + ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err}) + return + } + + if action == "update" { + tag.Name = name + tag.Color = color + tag, err = repositories.Tags.Update(tag) + + if err != nil { + fmt.Println(err) + ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err}) + return + } + } + + if action == "delete" { + repositories.Tags.DeleteById(ctx.Param("id")) + } + + rc.TagView(ctx) +} + +func (rc *configController) AddTagHandler(c *gin.Context) { + tag, err := models.NewTag(c) + + if err != nil { + fmt.Println(err) + c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err}) + return + } + + _, err = repositories.Tags.Create(tag) + if err != nil { + data := CreateSessionData(c, gin.H{ + "error": err, + "success": "", + }) + + c.HTML(http.StatusOK, "tagview.html", data) + return + } + + rc.TagView(c) +} + +func (rc *configController) TagView(c *gin.Context) { + tags, err := repositories.Tags.GetAll() + + if err != nil { + c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"data": gin.H{"error": err}}) + } + + data := CreateSessionData(c, gin.H{ + "tags": tags, + }) + + if err != nil { + c.HTML(http.StatusBadRequest, "tagview.html", data) + } + + c.HTML(http.StatusOK, "tagview.html", data) +} + +func (rc *configController) CreateTag(c *gin.Context) { + tag, err := models.NewTagByJson(c) + + if err != nil { + ReplyError(c, err) + } + + _, err = repositories.Tags.Create(tag) + if err != nil { + ReplyError(c, fmt.Errorf("tag creation failed: %s", err)) + return + } + + //userID := user.(models.User).ID + //rc.DB.Model(&models.shopItem{}).Where("id = ?"), room.ID).Association("Admins").Append(&models.User{ID: userID}) + + //if result.Error != nil { + // ReplyError(c, fmt.Errorf("shopItem creation failed: %s", result.Error)) + // return + //} + + ReplyOK(c, fmt.Sprintf("tag '%s' was created", tag.Name)) + +} + +func (rc *configController) GetAllTags(c *gin.Context) { + tags, err := repositories.Tags.GetAll() + + if err != nil { + ReplyError(c, fmt.Errorf("Could not query Tags")) + return + } + + c.JSON(http.StatusOK, tags) +} diff --git a/controllers/shopItemController.go b/controllers/shopItemController.go index a0da554..d662d18 100644 --- a/controllers/shopItemController.go +++ b/controllers/shopItemController.go @@ -29,15 +29,10 @@ type ShopItemController interface { AddItemHandler(*gin.Context) AddItemsView(*gin.Context) AddItemsHandler(*gin.Context) - CreateTag(*gin.Context) - GetAllTags(*gin.Context) EditItemView(*gin.Context) EditItemHandler(*gin.Context) DeleteItemView(*gin.Context) DeleteItemHandler(*gin.Context) - TagView(*gin.Context) - TagHandler(*gin.Context) - AddTagHandler(*gin.Context) } type shopItemController struct{} @@ -621,115 +616,6 @@ func (rc *shopItemController) DeleteItemHandler(c *gin.Context) { c.HTML(http.StatusOK, "index.html", data) } -func (rc *shopItemController) TagHandler(ctx *gin.Context) { - name := ctx.PostForm("name") - color := ctx.PostForm("color") - action := ctx.PostForm("action") - - tag, err := repositories.Tags.GetById(ctx.Param("id")) - - if err != nil { - fmt.Println(err) - ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err}) - return - } - - if action == "update" { - tag.Name = name - tag.Color = color - tag, err = repositories.Tags.Update(tag) - - if err != nil { - fmt.Println(err) - ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err}) - return - } - } - - if action == "delete" { - repositories.Tags.DeleteById(ctx.Param("id")) - } - - rc.TagView(ctx) -} - -func (rc *shopItemController) AddTagHandler(c *gin.Context) { - tag, err := models.NewTag(c) - - if err != nil { - fmt.Println(err) - c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err}) - return - } - - _, err = repositories.Tags.Create(tag) - if err != nil { - data := CreateSessionData(c, gin.H{ - "error": err, - "success": "", - }) - - c.HTML(http.StatusOK, "tagview.html", data) - return - } - - rc.TagView(c) -} - -func (rc *shopItemController) TagView(c *gin.Context) { - tags, err := repositories.Tags.GetAll() - - if err != nil { - c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"data": gin.H{"error": err}}) - } - - data := CreateSessionData(c, gin.H{ - "tags": tags, - }) - - if err != nil { - c.HTML(http.StatusBadRequest, "tagview.html", data) - } - - c.HTML(http.StatusOK, "tagview.html", data) -} - -func (rc *shopItemController) CreateTag(c *gin.Context) { - tag, err := models.NewTagByJson(c) - - if err != nil { - ReplyError(c, err) - } - - _, err = repositories.Tags.Create(tag) - if err != nil { - ReplyError(c, fmt.Errorf("shopItem creation failed: %s", err)) - return - } - - //userID := user.(models.User).ID - //rc.DB.Model(&models.shopItem{}).Where("id = ?"), room.ID).Association("Admins").Append(&models.User{ID: userID}) - - //if result.Error != nil { - // ReplyError(c, fmt.Errorf("shopItem creation failed: %s", result.Error)) - // return - //} - - ReplyOK(c, fmt.Sprintf("tag '%s' was created", tag.Name)) - -} - -func (rc *shopItemController) GetAllTags(c *gin.Context) { - tags, err := repositories.Tags.GetAll() - - if err != nil { - ReplyError(c, fmt.Errorf("Could not query Tags")) - return - } - - c.JSON(http.StatusOK, tags) -} - func ReplyError(ctx *gin.Context, err error) { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } diff --git a/main.go b/main.go index d603f48..acf0549 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ var ( userController controllers.UserController = controllers.UserController{} cartItemController controllers.CartItemController = controllers.NewCartItemController() printController controllers.PrintController = controllers.NewPrintController() + configController controllers.ConfigController = controllers.NewConfigController() authValidator middlewares.AuthValidator = middlewares.AuthValidator{} ) @@ -67,10 +68,14 @@ func main() { viewRoutes.GET("/cart/print", authValidator.RequireAdmin, printController.PrintCartView) viewRoutes.POST("/print", authValidator.RequireAdmin, printController.PrintHandler) - viewRoutes.GET("/tags", authValidator.RequireAdmin, shopItemController.TagView) - viewRoutes.POST("/tags/:id", authValidator.RequireAdmin, shopItemController.TagHandler) + viewRoutes.GET("/config", authValidator.RequireAdmin, configController.ConfigView) + viewRoutes.POST("/config/:id", authValidator.RequireAdmin, configController.ConfigHandler) + viewRoutes.POST("/config", authValidator.RequireAdmin, configController.AddConfigHandler) + + viewRoutes.GET("/tags", authValidator.RequireAdmin, configController.TagView) + viewRoutes.POST("/tags/:id", authValidator.RequireAdmin, configController.TagHandler) viewRoutes.GET("/tags/:id", userController.TagView) - viewRoutes.POST("/tags", authValidator.RequireAdmin, shopItemController.AddTagHandler) + viewRoutes.POST("/tags", authValidator.RequireAdmin, configController.AddTagHandler) 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/config.go b/models/config.go new file mode 100644 index 0000000..59c8a1d --- /dev/null +++ b/models/config.go @@ -0,0 +1,11 @@ +package models + +import ( + "gorm.io/gorm" +) + +type Config struct { + gorm.Model + Key string `json:"key" binding:"required" gorm:"unique;not null"` + Value string `json:"value" binding:"required"` +} diff --git a/repositories/configRepository.go b/repositories/configRepository.go new file mode 100644 index 0000000..443a272 --- /dev/null +++ b/repositories/configRepository.go @@ -0,0 +1,81 @@ +package repositories + +import ( + "strconv" + + "gorm.io/gorm" + + "git.dynamicdiscord.de/kalipso/zineshop/models" +) + +type ConfigRepository interface { + Create(models.Config) (models.Config, error) + GetAll() ([]models.Config, error) + GetById(string) (models.Config, error) + Update(models.Config) (models.Config, error) + DeleteById(string) error +} + +type GORMConfigRepository struct { + DB *gorm.DB +} + +func NewGORMConfigRepository(db *gorm.DB) ConfigRepository { + return &GORMConfigRepository{ + DB: db, + } +} + +func (t *GORMConfigRepository) Create(config models.Config) (models.Config, error) { + result := t.DB.Create(&config) + + if result.Error != nil { + return models.Config{}, result.Error + } + + return config, nil +} + +func (t *GORMConfigRepository) GetAll() ([]models.Config, error) { + var configs []models.Config + result := t.DB.Find(&configs) + + return configs, result.Error +} + +func (t *GORMConfigRepository) GetById(id string) (models.Config, error) { + configId, err := strconv.Atoi(id) + + if err != nil { + return models.Config{}, err + } + + var config models.Config + result := t.DB.First(&config, uint(configId)) + + if result.Error != nil { + return models.Config{}, result.Error + } + + return config, nil +} + +func (t *GORMConfigRepository) Update(config models.Config) (models.Config, error) { + result := t.DB.Save(&config) + if result.Error != nil { + return models.Config{}, result.Error + } + + return config, nil +} + +func (t *GORMConfigRepository) DeleteById(id string) error { + configId, err := strconv.Atoi(id) + + if err != nil { + return err + } + + result := t.DB.Delete(&models.Config{}, configId) + return result.Error +} diff --git a/repositories/repository.go b/repositories/repository.go index e3e81ff..dad8aa5 100644 --- a/repositories/repository.go +++ b/repositories/repository.go @@ -9,12 +9,13 @@ import ( ) var ( - ShopItems ShopItemRepository - Users UserRepository - Tags TagRepository - CartItems CartItemRepository - Orders OrderRepository - Tokens RegisterTokenRepository + ShopItems ShopItemRepository + Users UserRepository + Tags TagRepository + CartItems CartItemRepository + Orders OrderRepository + Tokens RegisterTokenRepository + ConfigOptions ConfigRepository ) func InitRepositories() { @@ -29,6 +30,8 @@ func InitRepositories() { &models.Tag{}, &models.CartItem{}, &models.Order{}, + &models.Config{}, + &models.Paper{}, &models.RegisterToken{}) if err != nil { @@ -41,4 +44,5 @@ func InitRepositories() { CartItems = NewGORMCartItemRepository(db) Orders = NewGORMOrderRepository(db) Tokens = NewGORMRegisterTokenRepository(db) + ConfigOptions = NewGORMConfigRepository(db) } diff --git a/static/output.css b/static/output.css index ce1c154..7856e3d 100644 --- a/static/output.css +++ b/static/output.css @@ -1722,27 +1722,16 @@ video { -moz-osx-font-smoothing: grayscale; } -.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-2xl { --tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25); --tw-shadow-colored: 0 25px 50px -12px 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-red-900 { - --tw-shadow-color: #7f1d1d; - --tw-shadow: var(--tw-shadow-colored); +.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 { diff --git a/views/configview.html b/views/configview.html new file mode 100644 index 0000000..a29ee6a --- /dev/null +++ b/views/configview.html @@ -0,0 +1,33 @@ +{{ template "header.html" . }} + + +
+
+ Your Company +

Edit config options

+
+ +
+ {{ range .data.configOptions }} +
+
+
+ + + + + +
+ + {{ end }} +
+
+
+ + + +
+
+
+
+{{ template "footer.html" . }} From f4faeb351d6e702cfacb9eb2fb31ddb9124fef72 Mon Sep 17 00:00:00 2001 From: kalipso Date: Fri, 27 Jun 2025 17:02:57 +0200 Subject: [PATCH 02/17] add basic paper model/view/controller paper weight is missing --- controllers/configController.go | 99 +++++++++++++++++++++++++++++++++ main.go | 6 ++ models/paper.go | 74 ++++++++++++++++++++++++ repositories/paperRepository.go | 82 +++++++++++++++++++++++++++ repositories/repository.go | 2 + views/paperview.html | 37 ++++++++++++ 6 files changed, 300 insertions(+) create mode 100644 models/paper.go create mode 100644 repositories/paperRepository.go create mode 100644 views/paperview.html diff --git a/controllers/configController.go b/controllers/configController.go index 4c46251..5dfba1f 100644 --- a/controllers/configController.go +++ b/controllers/configController.go @@ -15,6 +15,12 @@ type ConfigController interface { AddConfigHandler(*gin.Context) ConfigHandler(*gin.Context) ConfigView(*gin.Context) + + GetAllPaper(*gin.Context) + PaperView(*gin.Context) + PaperHandler(*gin.Context) + AddPaperHandler(*gin.Context) + CreateTag(*gin.Context) GetAllTags(*gin.Context) TagView(*gin.Context) @@ -108,6 +114,99 @@ func (rc *configController) ConfigHandler(ctx *gin.Context) { rc.ConfigView(ctx) } +func (rc *configController) PaperHandler(ctx *gin.Context) { + newPaper, err := models.NewPaper(ctx) + action := ctx.PostForm("action") + + if err != nil { + fmt.Println(err) + ctx.HTML(http.StatusBadRequest, "paperview.html", gin.H{"error": err}) + return + } + + paper, err := repositories.Papers.GetById(ctx.Param("id")) + + if err != nil { + fmt.Println(err) + ctx.HTML(http.StatusBadRequest, "paperview.html", gin.H{"error": err}) + return + } + + if action == "update" { + paper.Name = newPaper.Name + paper.Brand = newPaper.Brand + paper.Size = newPaper.Size + paper.Price = newPaper.Price + paper, err = repositories.Papers.Update(paper) + + if err != nil { + fmt.Println(err) + ctx.HTML(http.StatusBadRequest, "paperview.html", gin.H{"error": err}) + return + } + } + + if action == "delete" { + repositories.Papers.DeleteById(ctx.Param("id")) + } + + rc.PaperView(ctx) +} + +func (rc *configController) AddPaperHandler(c *gin.Context) { + paper, err := models.NewPaper(c) + + if err != nil { + fmt.Println(err) + c.HTML(http.StatusBadRequest, "paperview.html", gin.H{"error": err}) + return + } + + _, err = repositories.Papers.Create(paper) + if err != nil { + data := CreateSessionData(c, gin.H{ + "error": err, + "success": "", + }) + + c.HTML(http.StatusOK, "paperview.html", data) + return + } + + rc.PaperView(c) +} + +func (rc *configController) PaperView(c *gin.Context) { + papers, err := repositories.Papers.GetAll() + + if err != nil { + c.HTML(http.StatusBadRequest, "paperview.html", gin.H{"data": gin.H{"error": err}}) + } + + data := CreateSessionData(c, gin.H{ + "paper": papers, + }) + + if err != nil { + c.HTML(http.StatusBadRequest, "paperview.html", data) + } + + c.HTML(http.StatusOK, "paperview.html", data) +} + +func (rc *configController) GetAllPaper(c *gin.Context) { + papers, err := repositories.Papers.GetAll() + + if err != nil { + ReplyError(c, fmt.Errorf("Could not query Papers")) + return + } + + c.JSON(http.StatusOK, papers) +} + +////////////////////////////////////////////////////////////////////// + func (rc *configController) TagHandler(ctx *gin.Context) { name := ctx.PostForm("name") color := ctx.PostForm("color") diff --git a/main.go b/main.go index acf0549..09d1b7c 100644 --- a/main.go +++ b/main.go @@ -76,6 +76,12 @@ func main() { viewRoutes.POST("/tags/:id", authValidator.RequireAdmin, configController.TagHandler) viewRoutes.GET("/tags/:id", userController.TagView) viewRoutes.POST("/tags", authValidator.RequireAdmin, configController.AddTagHandler) + + viewRoutes.GET("/paper", authValidator.RequireAdmin, configController.PaperView) + viewRoutes.POST("/paper/:id", authValidator.RequireAdmin, configController.PaperHandler) + viewRoutes.GET("/paper/:id", userController.TagView) + viewRoutes.POST("/paper", authValidator.RequireAdmin, configController.AddPaperHandler) + 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/paper.go b/models/paper.go new file mode 100644 index 0000000..5dc3b37 --- /dev/null +++ b/models/paper.go @@ -0,0 +1,74 @@ +package models + +import ( + "fmt" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "strconv" + "strings" +) + +type PaperSize string + +const ( + A3 PaperSize = "A3" + A4 PaperSize = "A4" + A5 PaperSize = "A5" + SRA3 PaperSize = "SRA3" +) + +func ParseSize(s string) (c PaperSize, err error) { + s = strings.ToUpper(s) + if s == "A3" { + return A3, nil + } else if s == "A4" { + return A4, nil + } else if s == "A5" { + return A5, nil + } else if s == "SRA3" { + return SRA3, nil + } + + return c, fmt.Errorf("Cannot parse category %s", s) +} + +type Paper struct { + gorm.Model + Name string `json:"name" binding:"required" gorm:"not null"` + Brand string `json:"brand" binding:"required"` + Size PaperSize `json:"size" binding:"required"` + Price float64 `json:"price" binding:"required"` +} + +func NewPaper(ctx *gin.Context) (Paper, error) { + name := ctx.PostForm("name") + brand := ctx.PostForm("brand") + sizeTmp := ctx.PostForm("size") + priceTmp := ctx.PostForm("price") + + price, err := strconv.ParseFloat(priceTmp, 64) + + if err != nil { + return Paper{}, fmt.Errorf("Couldnt parse Price") + } + + size, err := ParseSize(sizeTmp) + + if err != nil { + return Paper{}, fmt.Errorf("Couldnt parse Size") + } + + if name == "" || brand == "" { + return Paper{}, fmt.Errorf("Name or brand empty") + } + + // Convert the price string to float64 + tag := Paper{ + Name: name, + Brand: brand, + Size: size, + Price: price, + } + + return tag, nil +} diff --git a/repositories/paperRepository.go b/repositories/paperRepository.go new file mode 100644 index 0000000..a0a2c93 --- /dev/null +++ b/repositories/paperRepository.go @@ -0,0 +1,82 @@ +package repositories + +import ( + "strconv" + + "gorm.io/gorm" + + "git.dynamicdiscord.de/kalipso/zineshop/models" +) + +type PaperRepository interface { + Create(models.Paper) (models.Paper, error) + GetAll() ([]models.Paper, error) + GetById(string) (models.Paper, error) + //GetByShopItemId(string) (models.Paper, error) + Update(models.Paper) (models.Paper, error) + DeleteById(string) error +} + +type GORMPaperRepository struct { + DB *gorm.DB +} + +func NewGORMPaperRepository(db *gorm.DB) PaperRepository { + return &GORMPaperRepository{ + DB: db, + } +} + +func (t *GORMPaperRepository) Create(tag models.Paper) (models.Paper, error) { + result := t.DB.Create(&tag) + + if result.Error != nil { + return models.Paper{}, result.Error + } + + return tag, nil +} + +func (t *GORMPaperRepository) GetAll() ([]models.Paper, error) { + var tags []models.Paper + result := t.DB.Find(&tags) + + return tags, result.Error +} + +func (t *GORMPaperRepository) GetById(id string) (models.Paper, error) { + tagId, err := strconv.Atoi(id) + + if err != nil { + return models.Paper{}, err + } + + var tag models.Paper + result := t.DB.First(&tag, uint(tagId)) + + if result.Error != nil { + return models.Paper{}, result.Error + } + + return tag, nil +} + +func (t *GORMPaperRepository) Update(tag models.Paper) (models.Paper, error) { + result := t.DB.Save(&tag) + if result.Error != nil { + return models.Paper{}, result.Error + } + + return tag, nil +} + +func (t *GORMPaperRepository) DeleteById(id string) error { + tagId, err := strconv.Atoi(id) + + if err != nil { + return err + } + + result := t.DB.Delete(&models.Paper{}, tagId) + return result.Error +} diff --git a/repositories/repository.go b/repositories/repository.go index dad8aa5..3e6251d 100644 --- a/repositories/repository.go +++ b/repositories/repository.go @@ -16,6 +16,7 @@ var ( Orders OrderRepository Tokens RegisterTokenRepository ConfigOptions ConfigRepository + Papers PaperRepository ) func InitRepositories() { @@ -45,4 +46,5 @@ func InitRepositories() { Orders = NewGORMOrderRepository(db) Tokens = NewGORMRegisterTokenRepository(db) ConfigOptions = NewGORMConfigRepository(db) + Papers = NewGORMPaperRepository(db) } diff --git a/views/paperview.html b/views/paperview.html new file mode 100644 index 0000000..581fee3 --- /dev/null +++ b/views/paperview.html @@ -0,0 +1,37 @@ +{{ template "header.html" . }} + + +
+
+ Your Company +

Edit Paper

+
+ +
+ {{ range .data.paper }} +
+
+
+ + + + + + + +
+ + {{ end }} +
+
+
+ + + + + +
+
+
+
+{{ template "footer.html" . }} From 6330a990f5ed0871c837308f0cc891974c500c74 Mon Sep 17 00:00:00 2001 From: kalipso Date: Sun, 29 Jun 2025 15:37:02 +0200 Subject: [PATCH 03/17] add paper weight --- controllers/configController.go | 1 + models/paper.go | 37 ++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/controllers/configController.go b/controllers/configController.go index 5dfba1f..e9b8556 100644 --- a/controllers/configController.go +++ b/controllers/configController.go @@ -136,6 +136,7 @@ func (rc *configController) PaperHandler(ctx *gin.Context) { paper.Name = newPaper.Name paper.Brand = newPaper.Brand paper.Size = newPaper.Size + paper.Weight = newPaper.Weight paper.Price = newPaper.Price paper, err = repositories.Papers.Update(paper) diff --git a/models/paper.go b/models/paper.go index 5dc3b37..ab374c5 100644 --- a/models/paper.go +++ b/models/paper.go @@ -34,40 +34,49 @@ func ParseSize(s string) (c PaperSize, err error) { type Paper struct { gorm.Model - Name string `json:"name" binding:"required" gorm:"not null"` - Brand string `json:"brand" binding:"required"` - Size PaperSize `json:"size" binding:"required"` - Price float64 `json:"price" binding:"required"` + Name string `json:"name" binding:"required" gorm:"not null"` + Brand string `json:"brand" binding:"required"` + Size PaperSize `json:"size" binding:"required"` + Weight int `json:"weight" binding:"required"` + Price float64 `json:"price" binding:"required"` } func NewPaper(ctx *gin.Context) (Paper, error) { name := ctx.PostForm("name") brand := ctx.PostForm("brand") sizeTmp := ctx.PostForm("size") + weightTmp := ctx.PostForm("weight") priceTmp := ctx.PostForm("price") - price, err := strconv.ParseFloat(priceTmp, 64) - - if err != nil { - return Paper{}, fmt.Errorf("Couldnt parse Price") - } - size, err := ParseSize(sizeTmp) if err != nil { return Paper{}, fmt.Errorf("Couldnt parse Size") } + weight, err := strconv.Atoi(weightTmp) + + if err != nil { + return Paper{}, fmt.Errorf("Couldnt parse Weight") + } + + price, err := strconv.ParseFloat(priceTmp, 64) + + if err != nil { + return Paper{}, fmt.Errorf("Couldnt parse Price") + } + if name == "" || brand == "" { return Paper{}, fmt.Errorf("Name or brand empty") } // Convert the price string to float64 tag := Paper{ - Name: name, - Brand: brand, - Size: size, - Price: price, + Name: name, + Brand: brand, + Size: size, + Weight: weight, + Price: price, } return tag, nil From 17a1ef0123145785bfe3fd14fca64b7bbbcb42a4 Mon Sep 17 00:00:00 2001 From: kalipso Date: Sun, 29 Jun 2025 15:37:10 +0200 Subject: [PATCH 04/17] fix paperview --- static/output.css | 112 +++++++++++++++++++++++++++++++++++++++++++ views/paperview.html | 81 ++++++++++++++++++++++++------- 2 files changed, 175 insertions(+), 18 deletions(-) diff --git a/static/output.css b/static/output.css index 7856e3d..689dee7 100644 --- a/static/output.css +++ b/static/output.css @@ -674,6 +674,10 @@ video { margin-top: 1.5rem; } +.mb-3 { + margin-bottom: 0.75rem; +} + .block { display: block; } @@ -810,14 +814,34 @@ video { grid-template-columns: repeat(12, minmax(0, 1fr)); } +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + .grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); } +.grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + .flex-col { flex-direction: column; } +.flex-wrap { + flex-wrap: wrap; +} + +.content-stretch { + align-content: stretch; +} + .items-center { align-items: center; } @@ -830,6 +854,10 @@ video { justify-content: space-between; } +.gap-2 { + gap: 0.5rem; +} + .gap-4 { gap: 1rem; } @@ -974,6 +1002,11 @@ video { background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1)); } +.bg-blue-500 { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1)); +} + .bg-blue-600 { --tw-bg-opacity: 1; background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1)); @@ -1049,6 +1082,11 @@ video { background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1)); } +.bg-green-500 { + --tw-bg-opacity: 1; + background-color: rgb(34 197 94 / var(--tw-bg-opacity, 1)); +} + .bg-green-600 { --tw-bg-opacity: 1; background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1)); @@ -1134,6 +1172,11 @@ video { background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1)); } +.bg-red-500 { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1)); +} + .bg-red-800 { --tw-bg-opacity: 1; background-color: rgb(153 27 27 / var(--tw-bg-opacity, 1)); @@ -1229,6 +1272,16 @@ 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; } @@ -1329,6 +1382,16 @@ video { padding-bottom: 2rem; } +.py-20 { + padding-top: 5rem; + padding-bottom: 5rem; +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + .pb-3 { padding-bottom: 0.75rem; } @@ -1353,6 +1416,10 @@ video { padding-top: 1.25rem; } +.pb-4 { + padding-bottom: 1rem; +} + .text-left { text-align: left; } @@ -1717,6 +1784,10 @@ video { color: rgb(39 39 42 / var(--tw-text-opacity, 1)); } +.underline { + text-decoration-line: underline; +} + .antialiased { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -1734,6 +1805,18 @@ video { 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); +} + .outline { outline-style: solid; } @@ -1838,6 +1921,11 @@ video { background-color: rgb(67 56 202 / var(--tw-bg-opacity, 1)); } +.hover\:bg-red-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(185 28 28 / var(--tw-bg-opacity, 1)); +} + .hover\:bg-red-900:hover { --tw-bg-opacity: 1; background-color: rgb(127 29 29 / var(--tw-bg-opacity, 1)); @@ -2004,6 +2092,10 @@ video { max-width: 24rem; } + .sm\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .sm\:items-center { align-items: center; } @@ -2047,6 +2139,10 @@ 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; } @@ -2095,6 +2191,14 @@ video { 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\:p-8 { padding: 2rem; } @@ -2118,9 +2222,17 @@ video { .xl\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } + + .xl\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } } @media (min-width: 1536px) { + .\32xl\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + .\32xl\:px-0 { padding-left: 0px; padding-right: 0px; diff --git a/views/paperview.html b/views/paperview.html index 581fee3..2553c22 100644 --- a/views/paperview.html +++ b/views/paperview.html @@ -7,31 +7,76 @@

Edit Paper

-
+
+
{{ range .data.paper }}
-
-
- - - - - - - +
+
+ {{ .Brand }} - {{ .Name }}: {{ .Size }} {{ .Weight }}g/m2
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
{{ end }} +
-
-
- - - - - +
+
+ Add new Paper
-
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
{{ template "footer.html" . }} From 8e1df934b3f69074020e1667da1c24b99dad5aaa Mon Sep 17 00:00:00 2001 From: kalipso Date: Sun, 29 Jun 2025 16:57:51 +0200 Subject: [PATCH 05/17] WIP cost calculation --- controllers/printController.go | 74 ++++++++++++++++++++++++++++++++-- utils/utils.go | 41 +++++++++++++++++++ 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/controllers/printController.go b/controllers/printController.go index f917cd2..9fa5bda 100644 --- a/controllers/printController.go +++ b/controllers/printController.go @@ -2,11 +2,13 @@ package controllers import ( "fmt" + "math" "net/http" "strconv" "git.dynamicdiscord.de/kalipso/zineshop/models" "git.dynamicdiscord.de/kalipso/zineshop/repositories" + "git.dynamicdiscord.de/kalipso/zineshop/utils" "github.com/gin-gonic/gin" ) @@ -63,7 +65,14 @@ func (rc *printController) PrintCartView(c *gin.Context) { return } + paper, err := repositories.Papers.GetAll() + if err != nil { + c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}}) + return + } + data := CreateSessionData(c, gin.H{ + "paper": paper, "cartItems": cartItems, }) @@ -78,9 +87,16 @@ func (rc *printController) PrintOrderView(c *gin.Context) { return } + paper, err := repositories.Papers.GetAll() + if err != nil { + c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}}) + return + } + cartItems := order.CartItems data := CreateSessionData(c, gin.H{ + "paper": paper, "cartItems": cartItems, }) @@ -90,6 +106,7 @@ func (rc *printController) PrintOrderView(c *gin.Context) { func (rc *printController) PrintHandler(c *gin.Context) { variantIds := c.PostFormArray("variant-id[]") variantAmounts := c.PostFormArray("variant-amount[]") + variantPapertypes := c.PostFormArray("variant-papertype[]") variantCoverPages := c.PostFormArray("variant-coverpage[]") if len(variantIds) != len(variantAmounts) || len(variantIds) != len(variantCoverPages) { @@ -113,9 +130,53 @@ func (rc *printController) PrintHandler(c *gin.Context) { return } - coverPage := false - if variantCoverPages[idx] == "1" { - coverPage = true + paperType, err := repositories.Papers.GetById(fmt.Sprintf("%v", variantPapertypes[idx])) + if err != nil { + c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}}) + return + } + + var coverPage models.Paper + doPrintCoverpage := false + if variantCoverPages[idx] != "0" { + coverPage, err = repositories.Papers.GetById(fmt.Sprintf("%v", variantCoverPages[idx])) + if err != nil { + c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}}) + return + } + + doPrintCoverpage = true + } + + calculatePrintCosts := func(shopItem models.ShopItem, paperType models.Paper, coverPagePaperType models.Paper, colored bool, amount int) (float64, error) { + pageCount := utils.CountPagesAtPath(shopItem.Pdf) + printMode := models.GetPrintMode(shopItem.PrintMode) + + //Get actual pagecount depending on printmode + actualPageCount := pageCount + + if printMode == models.CreateBooklet { + dividedCount := float64(pageCount) / 4.0 + actualPageCount = int(math.Ceil(dividedCount)) + } + + if printMode == models.LongEdge || printMode == models.ShortEdge { + dividedCount := float64(pageCount) / 2.0 + actualPageCount = int(math.Ceil(dividedCount)) + } + + PPC := 0.002604 + partCost := 0.0067 + if colored { + partCost = 0.0478 + } + printingCosts := float64(actualPageCount) * paperType.Price + printingCosts += float64(actualPageCount/2) * PPC + printingCosts += partCost * float64(actualPageCount) + + fmt.Printf("Printing Costs per Zine: %v\n", printingCosts) + fmt.Printf("Printing Costs Total: %v\n", printingCosts*float64(amount)) + return printingCosts, nil } variantAmount, err := strconv.Atoi(variantAmounts[idx]) @@ -124,7 +185,12 @@ func (rc *printController) PrintHandler(c *gin.Context) { return } - printJob, err := models.NewPrintJob(shopItem, variant, coverPage, uint(variantAmount)) + fmt.Println("Printing Costs:") + + colored := variant.Name == "Colored" + calculatePrintCosts(shopItem, paperType, coverPage, colored, int(variantAmount)) + + printJob, err := models.NewPrintJob(shopItem, variant, doPrintCoverpage, uint(variantAmount)) if err != nil { c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}}) diff --git a/utils/utils.go b/utils/utils.go index 66c4d20..107aaac 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,8 +1,11 @@ package utils import ( + "bufio" "crypto/rand" "encoding/hex" + "io" + "os" ) func GenerateSessionId(length int) string { @@ -17,3 +20,41 @@ func GenerateSessionId(length int) string { func GenerateToken() string { return GenerateSessionId(16) } + +const match = "/Page\x00" + +// Pages reads the given io.ByteReader until EOF is reached, returning the +// number of pages encountered. +func Pages(reader io.ByteReader) (pages int) { + i := 0 + for { + b, err := reader.ReadByte() + if err != nil { + return + } + check: + switch match[i] { + case 0: + if !(b >= 'A' && b <= 'Z' || b >= 'a' && b <= 'z') { + pages++ + } + i = 0 + goto check + case b: + i++ + default: + i = 0 + } + } +} + +// 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() + } + return +} From 8ce01417e7a1dba7de9fa5f9da14cb6e5d232983 Mon Sep 17 00:00:00 2001 From: kalipso Date: Sun, 29 Jun 2025 16:58:07 +0200 Subject: [PATCH 06/17] select papertype on print --- views/header.html | 4 ++++ views/printvariant.html | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/views/header.html b/views/header.html index c25d85d..9556eed 100644 --- a/views/header.html +++ b/views/header.html @@ -38,6 +38,10 @@ hover:text-white">Print Cart + Config + Paper Logout
{{ end }} diff --git a/views/printvariant.html b/views/printvariant.html index 7a5d48d..44f85f9 100644 --- a/views/printvariant.html +++ b/views/printvariant.html @@ -37,10 +37,20 @@
+ + + + From 4b0649439c788767fc76b19c8bf17c1d911899f5 Mon Sep 17 00:00:00 2001 From: kalipso Date: Tue, 1 Jul 2025 13:15:02 +0200 Subject: [PATCH 07/17] update PrintJob model and add Invoice --- controllers/printController.go | 46 ++------------ models/printer.go | 99 +++++++++++++++++++++++++----- models/shopItem.go | 2 +- repositories/printJobRepository.go | 82 +++++++++++++++++++++++++ repositories/repository.go | 4 ++ 5 files changed, 175 insertions(+), 58 deletions(-) create mode 100644 repositories/printJobRepository.go diff --git a/controllers/printController.go b/controllers/printController.go index 9fa5bda..ba1e485 100644 --- a/controllers/printController.go +++ b/controllers/printController.go @@ -2,13 +2,11 @@ package controllers import ( "fmt" - "math" "net/http" "strconv" "git.dynamicdiscord.de/kalipso/zineshop/models" "git.dynamicdiscord.de/kalipso/zineshop/repositories" - "git.dynamicdiscord.de/kalipso/zineshop/utils" "github.com/gin-gonic/gin" ) @@ -136,47 +134,15 @@ func (rc *printController) PrintHandler(c *gin.Context) { return } - var coverPage models.Paper - doPrintCoverpage := false + var coverPage *models.Paper if variantCoverPages[idx] != "0" { - coverPage, err = repositories.Papers.GetById(fmt.Sprintf("%v", variantCoverPages[idx])) + coverPageTmp, err := repositories.Papers.GetById(fmt.Sprintf("%v", variantCoverPages[idx])) if err != nil { c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}}) return } - doPrintCoverpage = true - } - - calculatePrintCosts := func(shopItem models.ShopItem, paperType models.Paper, coverPagePaperType models.Paper, colored bool, amount int) (float64, error) { - pageCount := utils.CountPagesAtPath(shopItem.Pdf) - printMode := models.GetPrintMode(shopItem.PrintMode) - - //Get actual pagecount depending on printmode - actualPageCount := pageCount - - if printMode == models.CreateBooklet { - dividedCount := float64(pageCount) / 4.0 - actualPageCount = int(math.Ceil(dividedCount)) - } - - if printMode == models.LongEdge || printMode == models.ShortEdge { - dividedCount := float64(pageCount) / 2.0 - actualPageCount = int(math.Ceil(dividedCount)) - } - - PPC := 0.002604 - partCost := 0.0067 - if colored { - partCost = 0.0478 - } - printingCosts := float64(actualPageCount) * paperType.Price - printingCosts += float64(actualPageCount/2) * PPC - printingCosts += partCost * float64(actualPageCount) - - fmt.Printf("Printing Costs per Zine: %v\n", printingCosts) - fmt.Printf("Printing Costs Total: %v\n", printingCosts*float64(amount)) - return printingCosts, nil + coverPage = &coverPageTmp } variantAmount, err := strconv.Atoi(variantAmounts[idx]) @@ -187,15 +153,13 @@ func (rc *printController) PrintHandler(c *gin.Context) { fmt.Println("Printing Costs:") - colored := variant.Name == "Colored" - calculatePrintCosts(shopItem, paperType, coverPage, colored, int(variantAmount)) - - printJob, err := models.NewPrintJob(shopItem, variant, doPrintCoverpage, uint(variantAmount)) + printJob, err := models.NewPrintJob(shopItem, variant, paperType, coverPage, uint(variantAmount)) if err != nil { c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}}) return } + printJob.CalculatePrintCosts() printJobs = append(printJobs, printJob) } diff --git a/models/printer.go b/models/printer.go index 994f08e..e537c4a 100644 --- a/models/printer.go +++ b/models/printer.go @@ -2,6 +2,9 @@ package models import ( "fmt" + "git.dynamicdiscord.de/kalipso/zineshop/utils" + "gorm.io/gorm" + "math" "os/exec" "strings" ) @@ -18,12 +21,33 @@ const ( TriFold PrintOption = "-o Fold=TriFold -o Binding=TopBinding" ) -type PrintJob struct { +type OldPrintJob struct { Pdf string Amount uint Options []PrintOption } +type Invoice struct { + gorm.Model + PrintJobs []PrintJob + PricePerClick float64 + PartCosts float64 +} + +type PrintJob struct { + gorm.Model + ShopItemID uint + ShopItem ShopItem + VariantID uint + Variant ItemVariant + PaperTypeId uint + PaperType Paper `gorm:"foreignKey:PaperTypeId"` + CoverPaperTypeId *uint + CoverPaperType *Paper `gorm:"foreignKey:CoverPaperTypeId"` + Amount uint + InvoiceID uint +} + func GetPrintMode(mode string) PrintOption { if mode == "LongEdge" { return LongEdge @@ -40,7 +64,7 @@ func GetPrintMode(mode string) PrintOption { return CreateBooklet } -func NewPrintJob(shopItem ShopItem, variant ItemVariant, coverPage bool, amount uint) (PrintJob, error) { +func NewPrintJob(shopItem ShopItem, variant ItemVariant, paperType Paper, coverPaperType *Paper, amount uint) (PrintJob, error) { if shopItem.Pdf == "" { return PrintJob{}, fmt.Errorf("ShopItem has no PDF assigned") } @@ -49,32 +73,44 @@ func NewPrintJob(shopItem ShopItem, variant ItemVariant, coverPage bool, amount return PrintJob{}, fmt.Errorf("Amount to big. This is denied for security reasons") } - var result PrintJob - result.Pdf = shopItem.Pdf - result.Amount = amount - - if variant.Name == "Colored" { - result.Options = append(result.Options, Colored) + result := PrintJob{ + ShopItem: shopItem, + Variant: variant, + PaperType: paperType, + CoverPaperType: coverPaperType, + Amount: amount, } - if coverPage { - result.Options = append(result.Options, CoverPage) - } - - result.Options = append(result.Options, GetPrintMode(shopItem.PrintMode)) - return result, nil } +func (p *PrintJob) IsColored() bool { + return p.Variant.Name == "Colored" +} + +func (p *PrintJob) GeneratePrintOptions() []PrintOption { + var result []PrintOption + if p.Variant.Name == "Colored" { + result = append(result, Colored) + } + + if p.CoverPaperType != nil { + result = append(result, CoverPage) + } + + result = append(result, GetPrintMode(p.ShopItem.PrintMode)) + return result +} + func (p *PrintJob) Execute() error { baseCommand := "lp -d KonicaBooklet" baseCommand += fmt.Sprintf(" -n %v ", p.Amount) - for _, option := range p.Options { + for _, option := range p.GeneratePrintOptions() { baseCommand += fmt.Sprintf(" %v ", option) } - baseCommand += fmt.Sprintf(" -- %s", p.Pdf) + baseCommand += fmt.Sprintf(" -- %s", p.ShopItem.Pdf) parts := strings.Fields(baseCommand) @@ -91,3 +127,34 @@ func (p *PrintJob) Execute() error { fmt.Printf("Output:\n%s\n", output) return nil } + +func (p *PrintJob) CalculatePrintCosts() (float64, error) { + pageCount := utils.CountPagesAtPath(p.ShopItem.Pdf) + printMode := GetPrintMode(p.ShopItem.PrintMode) + + //Get actual pagecount depending on printmode + actualPageCount := pageCount + + if printMode == CreateBooklet { + dividedCount := float64(pageCount) / 4.0 + actualPageCount = int(math.Ceil(dividedCount)) + } + + if printMode == LongEdge || printMode == ShortEdge { + dividedCount := float64(pageCount) / 2.0 + actualPageCount = int(math.Ceil(dividedCount)) + } + + PPC := 0.002604 + partCost := 0.0067 + if p.IsColored() { + partCost = 0.0478 + } + printingCosts := float64(actualPageCount) * p.PaperType.Price + printingCosts += float64(actualPageCount/2) * PPC + printingCosts += partCost * float64(actualPageCount) + + fmt.Printf("Printing Costs per Zine: %v\n", printingCosts) + fmt.Printf("Printing Costs Total: %v\n", printingCosts*float64(p.Amount)) + return printingCosts, nil +} diff --git a/models/shopItem.go b/models/shopItem.go index 55e49de..30a018b 100644 --- a/models/shopItem.go +++ b/models/shopItem.go @@ -43,7 +43,7 @@ type ItemVariant struct { type ShopItem struct { gorm.Model Name string `json:"name" binding:"required" gorm:"unique;not null"` - Abstract string `json:"Abstract" binding:"required"` + Abstract string `json:"abstract" binding:"required"` Description string `json:"description" binding:"required"` Category Category `json:"category"` Variants []ItemVariant `json:"variant"` diff --git a/repositories/printJobRepository.go b/repositories/printJobRepository.go new file mode 100644 index 0000000..f3d91e0 --- /dev/null +++ b/repositories/printJobRepository.go @@ -0,0 +1,82 @@ +package repositories + +import ( + "strconv" + + "gorm.io/gorm" + + "git.dynamicdiscord.de/kalipso/zineshop/models" +) + +type PrintJobRepository interface { + Create(models.PrintJob) (models.PrintJob, error) + GetAll() ([]models.PrintJob, error) + GetById(string) (models.PrintJob, error) + //GetByShopItemId(string) (models.PrintJob, error) + Update(models.PrintJob) (models.PrintJob, error) + DeleteById(string) error +} + +type GORMPrintJobRepository struct { + DB *gorm.DB +} + +func NewGORMPrintJobRepository(db *gorm.DB) PrintJobRepository { + return &GORMPrintJobRepository{ + DB: db, + } +} + +func (t *GORMPrintJobRepository) Create(tag models.PrintJob) (models.PrintJob, error) { + result := t.DB.Create(&tag) + + if result.Error != nil { + return models.PrintJob{}, result.Error + } + + return tag, nil +} + +func (t *GORMPrintJobRepository) GetAll() ([]models.PrintJob, error) { + var tags []models.PrintJob + result := t.DB.Find(&tags) + + return tags, result.Error +} + +func (t *GORMPrintJobRepository) GetById(id string) (models.PrintJob, error) { + tagId, err := strconv.Atoi(id) + + if err != nil { + return models.PrintJob{}, err + } + + var tag models.PrintJob + result := t.DB.First(&tag, uint(tagId)) + + if result.Error != nil { + return models.PrintJob{}, result.Error + } + + return tag, nil +} + +func (t *GORMPrintJobRepository) Update(tag models.PrintJob) (models.PrintJob, error) { + result := t.DB.Save(&tag) + if result.Error != nil { + return models.PrintJob{}, result.Error + } + + return tag, nil +} + +func (t *GORMPrintJobRepository) DeleteById(id string) error { + tagId, err := strconv.Atoi(id) + + if err != nil { + return err + } + + result := t.DB.Delete(&models.PrintJob{}, tagId) + return result.Error +} diff --git a/repositories/repository.go b/repositories/repository.go index 3e6251d..8061fac 100644 --- a/repositories/repository.go +++ b/repositories/repository.go @@ -17,6 +17,7 @@ var ( Tokens RegisterTokenRepository ConfigOptions ConfigRepository Papers PaperRepository + PrintJobs PrintJobRepository ) func InitRepositories() { @@ -33,6 +34,8 @@ func InitRepositories() { &models.Order{}, &models.Config{}, &models.Paper{}, + &models.PrintJob{}, + &models.Invoice{}, &models.RegisterToken{}) if err != nil { @@ -47,4 +50,5 @@ func InitRepositories() { Tokens = NewGORMRegisterTokenRepository(db) ConfigOptions = NewGORMConfigRepository(db) Papers = NewGORMPaperRepository(db) + PrintJobs = NewGORMPrintJobRepository(db) } From 992b9c17c32e7ba9edf2506d1ba2ea7ab8f4ff47 Mon Sep 17 00:00:00 2001 From: kalipso Date: Tue, 1 Jul 2025 13:38:28 +0200 Subject: [PATCH 08/17] store invoices and printjobs --- controllers/printController.go | 18 +++++++ repositories/InvoiceRepository.go | 82 ++++++++++++++++++++++++++++++ repositories/printJobRepository.go | 30 +++++------ repositories/repository.go | 2 + 4 files changed, 117 insertions(+), 15 deletions(-) create mode 100644 repositories/InvoiceRepository.go diff --git a/controllers/printController.go b/controllers/printController.go index ba1e485..5d850b4 100644 --- a/controllers/printController.go +++ b/controllers/printController.go @@ -160,10 +160,28 @@ 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 + } printJobs = append(printJobs, printJob) } + invoice := models.Invoice{ + PrintJobs: printJobs, + PricePerClick: 0.002604, + PartCosts: 0.0067, + } + invoice, err := repositories.Invoices.Create(invoice) + + if err != nil { + c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}}) + return + } + executeJobs := func() { for _, printJob := range printJobs { printJob.Execute() diff --git a/repositories/InvoiceRepository.go b/repositories/InvoiceRepository.go new file mode 100644 index 0000000..53f9130 --- /dev/null +++ b/repositories/InvoiceRepository.go @@ -0,0 +1,82 @@ +package repositories + +import ( + "strconv" + + "gorm.io/gorm" + + "git.dynamicdiscord.de/kalipso/zineshop/models" +) + +type InvoiceRepository interface { + Create(models.Invoice) (models.Invoice, error) + GetAll() ([]models.Invoice, error) + GetById(string) (models.Invoice, error) + //GetByShopItemId(string) (models.Invoice, error) + Update(models.Invoice) (models.Invoice, error) + DeleteById(string) error +} + +type GORMInvoiceRepository struct { + DB *gorm.DB +} + +func NewGORMInvoiceRepository(db *gorm.DB) InvoiceRepository { + return &GORMInvoiceRepository{ + DB: db, + } +} + +func (t *GORMInvoiceRepository) Create(invoice models.Invoice) (models.Invoice, error) { + result := t.DB.Omit("PrintJobs").Create(&invoice) + + if result.Error != nil { + return models.Invoice{}, result.Error + } + + return invoice, nil +} + +func (t *GORMInvoiceRepository) GetAll() ([]models.Invoice, error) { + var invoice []models.Invoice + result := t.DB.Preload("PrintJobs").Find(&invoice) + + return invoice, result.Error +} + +func (t *GORMInvoiceRepository) GetById(id string) (models.Invoice, error) { + invoiceId, err := strconv.Atoi(id) + + if err != nil { + return models.Invoice{}, err + } + + var invoice models.Invoice + result := t.DB.Preload("PrintJobs").First(&invoice, uint(invoiceId)) + + if result.Error != nil { + return models.Invoice{}, result.Error + } + + return invoice, nil +} + +func (t *GORMInvoiceRepository) Update(invoice models.Invoice) (models.Invoice, error) { + result := t.DB.Save(&invoice) + if result.Error != nil { + return models.Invoice{}, result.Error + } + + return invoice, nil +} + +func (t *GORMInvoiceRepository) DeleteById(id string) error { + invoiceId, err := strconv.Atoi(id) + + if err != nil { + return err + } + + result := t.DB.Delete(&models.Invoice{}, invoiceId) + return result.Error +} diff --git a/repositories/printJobRepository.go b/repositories/printJobRepository.go index f3d91e0..028472e 100644 --- a/repositories/printJobRepository.go +++ b/repositories/printJobRepository.go @@ -27,56 +27,56 @@ func NewGORMPrintJobRepository(db *gorm.DB) PrintJobRepository { } } -func (t *GORMPrintJobRepository) Create(tag models.PrintJob) (models.PrintJob, error) { - result := t.DB.Create(&tag) +func (t *GORMPrintJobRepository) Create(printJob models.PrintJob) (models.PrintJob, error) { + result := t.DB.Create(&printJob) if result.Error != nil { return models.PrintJob{}, result.Error } - return tag, nil + return printJob, nil } func (t *GORMPrintJobRepository) GetAll() ([]models.PrintJob, error) { - var tags []models.PrintJob - result := t.DB.Find(&tags) + var printJobs []models.PrintJob + result := t.DB.Preload("ShopItem").Preload("Variant").Preload("PaperType").Preload("CoverPaperType").Find(&printJobs) - return tags, result.Error + return printJobs, result.Error } func (t *GORMPrintJobRepository) GetById(id string) (models.PrintJob, error) { - tagId, err := strconv.Atoi(id) + printJobId, err := strconv.Atoi(id) if err != nil { return models.PrintJob{}, err } - var tag models.PrintJob - result := t.DB.First(&tag, uint(tagId)) + var printJob models.PrintJob + result := t.DB.Preload("ShopItem").Preload("Variant").Preload("PaperType").Preload("CoverPaperType").First(&printJob, uint(printJobId)) if result.Error != nil { return models.PrintJob{}, result.Error } - return tag, nil + return printJob, nil } -func (t *GORMPrintJobRepository) Update(tag models.PrintJob) (models.PrintJob, error) { - result := t.DB.Save(&tag) +func (t *GORMPrintJobRepository) Update(printJob models.PrintJob) (models.PrintJob, error) { + result := t.DB.Save(&printJob) if result.Error != nil { return models.PrintJob{}, result.Error } - return tag, nil + return printJob, nil } func (t *GORMPrintJobRepository) DeleteById(id string) error { - tagId, err := strconv.Atoi(id) + printJobId, err := strconv.Atoi(id) if err != nil { return err } - result := t.DB.Delete(&models.PrintJob{}, tagId) + result := t.DB.Delete(&models.PrintJob{}, printJobId) return result.Error } diff --git a/repositories/repository.go b/repositories/repository.go index 8061fac..9fe7591 100644 --- a/repositories/repository.go +++ b/repositories/repository.go @@ -18,6 +18,7 @@ var ( ConfigOptions ConfigRepository Papers PaperRepository PrintJobs PrintJobRepository + Invoices InvoiceRepository ) func InitRepositories() { @@ -51,4 +52,5 @@ func InitRepositories() { ConfigOptions = NewGORMConfigRepository(db) Papers = NewGORMPaperRepository(db) PrintJobs = NewGORMPrintJobRepository(db) + Invoices = NewGORMInvoiceRepository(db) } From 7025f526c10bbf813c33db33d0b6b25f7333c325 Mon Sep 17 00:00:00 2001 From: kalipso Date: Tue, 1 Jul 2025 15:30:06 +0200 Subject: [PATCH 09/17] 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" . }} From 6c59d1233fb57c321e27d1593302efd744b752b8 Mon Sep 17 00:00:00 2001 From: kalipso Date: Tue, 1 Jul 2025 23:45:27 +0200 Subject: [PATCH 10/17] show invoice after print --- controllers/printController.go | 7 ++- static/output.css | 96 ++++++++++++---------------------- views/invoice.html | 49 +++++++++++++++++ views/invoiceview.html | 52 +----------------- views/printstarted.html | 22 ++++++++ views/printvariant.html | 2 +- 6 files changed, 113 insertions(+), 115 deletions(-) create mode 100644 views/invoice.html create mode 100644 views/printstarted.html diff --git a/controllers/printController.go b/controllers/printController.go index a7bf78f..988d388 100644 --- a/controllers/printController.go +++ b/controllers/printController.go @@ -186,5 +186,10 @@ func (rc *printController) PrintHandler(c *gin.Context) { go executeJobs() - c.HTML(http.StatusOK, "index.html", nil) + fmt.Println(invoice) + data := CreateSessionData(c, gin.H{ + "invoice": invoice, + }) + + c.HTML(http.StatusOK, "printstarted.html", data) } diff --git a/static/output.css b/static/output.css index 667f7ca..f3583d6 100644 --- a/static/output.css +++ b/static/output.css @@ -591,6 +591,10 @@ video { grid-column: span 3 / span 3; } +.-m-1\.5 { + margin: -0.375rem; +} + .m-2 { margin: 0.5rem; } @@ -599,8 +603,8 @@ video { margin: 1rem; } -.-m-1\.5 { - margin: -0.375rem; +.m-8 { + margin: 2rem; } .-mx-2 { @@ -690,10 +694,6 @@ video { display: flex; } -.inline-flex { - display: inline-flex; -} - .table { display: table; } @@ -798,6 +798,10 @@ video { max-width: 1280px; } +.max-w-4xl { + max-width: 56rem; +} + .flex-1 { flex: 1 1 0%; } @@ -838,6 +842,10 @@ video { grid-template-columns: repeat(6, minmax(0, 1fr)); } +.grid-cols-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); +} + .flex-col { flex-direction: column; } @@ -883,11 +891,6 @@ 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)); @@ -1000,10 +1003,6 @@ 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)); @@ -1302,6 +1301,10 @@ video { object-fit: cover; } +.p-1\.5 { + padding: 0.375rem; +} + .p-2 { padding: 0.5rem; } @@ -1314,8 +1317,8 @@ video { padding: 1rem; } -.p-1\.5 { - padding: 0.375rem; +.p-8 { + padding: 2rem; } .px-2 { @@ -1383,6 +1386,11 @@ video { padding-bottom: 6rem; } +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + .py-4 { padding-top: 1rem; padding-bottom: 1rem; @@ -1393,9 +1401,9 @@ video { padding-bottom: 2rem; } -.py-3 { - padding-top: 0.75rem; - padding-bottom: 0.75rem; +.px-8 { + padding-left: 2rem; + padding-right: 2rem; } .pb-3 { @@ -1802,11 +1810,6 @@ video { color: rgb(39 39 42 / var(--tw-text-opacity, 1)); } -.text-blue-600 { - --tw-text-opacity: 1; - color: rgb(37 99 235 / var(--tw-text-opacity, 1)); -} - .antialiased { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -1954,11 +1957,6 @@ 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; } @@ -1972,11 +1970,6 @@ 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; @@ -2036,14 +2029,6 @@ 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; } @@ -2328,16 +2313,6 @@ video { color: rgb(17 24 39 / var(--tw-text-opacity, 1)); } - .dark\:text-white { - --tw-text-opacity: 1; - 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)); @@ -2348,6 +2323,11 @@ video { color: rgb(115 115 115 / var(--tw-text-opacity, 1)); } + .dark\:text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / 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)); @@ -2373,21 +2353,11 @@ 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/views/invoice.html b/views/invoice.html new file mode 100644 index 0000000..85c2a4c --- /dev/null +++ b/views/invoice.html @@ -0,0 +1,49 @@ +
+
+
+ + + + + + + + + + + + + + {{ 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 }}
+
+
+
diff --git a/views/invoiceview.html b/views/invoiceview.html index 226f8f1..adaddb8 100644 --- a/views/invoiceview.html +++ b/views/invoiceview.html @@ -15,56 +15,8 @@
-
-
-
- - - - - - - - - - - - - - {{ 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 }}
-
-
-
-
+ {{ template "invoice.html" . }} +
diff --git a/views/printstarted.html b/views/printstarted.html new file mode 100644 index 0000000..d470e46 --- /dev/null +++ b/views/printstarted.html @@ -0,0 +1,22 @@ +{{ template "header.html" . }} + +
+
+
+
+

Print Started +

+
+
+ {{ template "invoice.html" .data.invoice }} +
+
+
+
+
+
+
+
+ + +{{ template "footer.html" . }} diff --git a/views/printvariant.html b/views/printvariant.html index 44f85f9..28c72ac 100644 --- a/views/printvariant.html +++ b/views/printvariant.html @@ -15,7 +15,7 @@

CoverPage: If selected, the Printer will take Paper from the BypassTray for the first page. For example you can put colored paper there to have a nice looking front page, and the rest will be normal paper. - Makue sure you put paper in that tray when selecting this option.

+ Make sure you put paper in that tray when selecting this option.

Print Order: The Zines will be printed from top to bottom as seen in this list. From 9e638dcfc2376c1181fb5905a8122ed760b86bb2 Mon Sep 17 00:00:00 2001 From: kalipso Date: Wed, 2 Jul 2025 00:10:37 +0200 Subject: [PATCH 11/17] add tested/not tested indicator on print view --- controllers/printController.go | 14 ++++++++- controllers/shopItemController.go | 3 ++ models/shopItem.go | 1 + static/output.css | 47 +++++++++++++++++++++++++++++++ views/printvariant.html | 8 +++++- 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/controllers/printController.go b/controllers/printController.go index 988d388..6c8d695 100644 --- a/controllers/printController.go +++ b/controllers/printController.go @@ -180,7 +180,19 @@ func (rc *printController) PrintHandler(c *gin.Context) { executeJobs := func() { for _, printJob := range printJobs { - printJob.Execute() + printJob.ShopItem.WasPrinted = true + _, err = repositories.ShopItems.Update(printJob.ShopItem) + + if err != nil { + fmt.Printf("Error: %s\n", err) + } + + err := printJob.Execute() + + if err == nil { + return + } + } } diff --git a/controllers/shopItemController.go b/controllers/shopItemController.go index d662d18..7649709 100644 --- a/controllers/shopItemController.go +++ b/controllers/shopItemController.go @@ -161,6 +161,7 @@ func (rc *shopItemController) NewShopItemFromForm(ctx *gin.Context) (models.Shop Pdf: dstPdf, Variants: variants, PrintMode: printMode, + WasPrinted: false, } fmt.Println("Creating Shopitem: ", shopItem) @@ -400,6 +401,7 @@ func (rc *shopItemController) AddItemsHandler(c *gin.Context) { Pdf: dstPdf, Variants: variants, PrintMode: "CreateBooklet", + WasPrinted: false, } _, err = repositories.ShopItems.Create(shopItem) @@ -537,6 +539,7 @@ func (rc *shopItemController) EditItemHandler(c *gin.Context) { newShopItem.Variants = shopItem.Variants newShopItem.BasePrice = shopItem.BasePrice newShopItem.IsPublic = shopItem.IsPublic + newShopItem.WasPrinted = false if len(shopItem.Tags) != 0 { newShopItem.Tags = shopItem.Tags diff --git a/models/shopItem.go b/models/shopItem.go index 30a018b..8146878 100644 --- a/models/shopItem.go +++ b/models/shopItem.go @@ -53,4 +53,5 @@ type ShopItem struct { Image string Pdf string PrintMode string `json:"printMode" gorm:"default:CreateBooklet"` + WasPrinted bool `gorm:"default:false"` } diff --git a/static/output.css b/static/output.css index f3583d6..941769a 100644 --- a/static/output.css +++ b/static/output.css @@ -694,6 +694,10 @@ video { display: flex; } +.inline-flex { + display: inline-flex; +} + .table { display: table; } @@ -1288,6 +1292,16 @@ video { background-color: rgb(24 24 27 / var(--tw-bg-opacity, 1)); } +.bg-red-50 { + --tw-bg-opacity: 1; + background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1)); +} + +.bg-green-50 { + --tw-bg-opacity: 1; + background-color: rgb(240 253 244 / var(--tw-bg-opacity, 1)); +} + .fill-red-50 { fill: #fef2f2; } @@ -1406,6 +1420,11 @@ video { padding-right: 2rem; } +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + .pb-3 { padding-bottom: 0.75rem; } @@ -1810,6 +1829,16 @@ video { color: rgb(39 39 42 / var(--tw-text-opacity, 1)); } +.text-red-700 { + --tw-text-opacity: 1; + color: rgb(185 28 28 / var(--tw-text-opacity, 1)); +} + +.text-green-700 { + --tw-text-opacity: 1; + color: rgb(21 128 61 / var(--tw-text-opacity, 1)); +} + .antialiased { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -1849,6 +1878,24 @@ video { outline-color: #d1d5db; } +.ring-1 { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.ring-inset { + --tw-ring-inset: inset; +} + +.ring-red-600\/10 { + --tw-ring-color: rgb(220 38 38 / 0.1); +} + +.ring-green-600\/20 { + --tw-ring-color: rgb(22 163 74 / 0.2); +} + .filter { filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } diff --git a/views/printvariant.html b/views/printvariant.html index 28c72ac..aa30b5d 100644 --- a/views/printvariant.html +++ b/views/printvariant.html @@ -1,7 +1,7 @@ {{ template "header.html" . }}

-
+

Zineshop Print @@ -33,6 +33,12 @@ imac image + + {{ if .ShopItem.WasPrinted }} + Tested + {{ else }} + NOT TESTED + {{ end }} {{ .ShopItem.Name }} - {{ .ItemVariant.Name }}

From 6c0440f06d1af2d7b53523e9ed52c17decf99b87 Mon Sep 17 00:00:00 2001 From: kalipso Date: Wed, 2 Jul 2025 00:28:13 +0200 Subject: [PATCH 12/17] add coverpage to price calculation --- models/printer.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/models/printer.go b/models/printer.go index 9de3af4..d8cd2ee 100644 --- a/models/printer.go +++ b/models/printer.go @@ -133,6 +133,11 @@ func (p *PrintJob) Execute() error { func (p *PrintJob) CalculatePrintCosts() (float64, error) { pageCount := utils.CountPagesAtPath(p.ShopItem.Pdf) + + if pageCount == 0 { + return 0, fmt.Errorf("Cant calculate price, pdf seems to be empty") + } + printMode := GetPrintMode(p.ShopItem.PrintMode) //Get actual pagecount depending on printmode @@ -154,7 +159,15 @@ func (p *PrintJob) CalculatePrintCosts() (float64, error) { if p.IsColored() { partCost = 0.0478 } - printingCosts := float64(actualPageCount) * p.PaperType.Price + + printingCosts := float64(actualPageCount-1) * p.PaperType.Price + + if p.CoverPaperType != nil { + printingCosts += p.CoverPaperType.Price + } else { + printingCosts += p.PaperType.Price + } + printingCosts += float64(actualPageCount/2) * PPC printingCosts += partCost * float64(actualPageCount) From ad5573ee2c47662c32a6fd4d996663f69d012ba1 Mon Sep 17 00:00:00 2001 From: kalipso Date: Wed, 2 Jul 2025 00:28:27 +0200 Subject: [PATCH 13/17] add CoverPage hint to - + {{ range $.data.paper }} {{ end}} From 16c68cd0f83b24429a24d50c093e89d8e5f8804e Mon Sep 17 00:00:00 2001 From: kalipso Date: Wed, 2 Jul 2025 00:33:11 +0200 Subject: [PATCH 14/17] set WasPrinted after printjob execution --- controllers/printController.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/controllers/printController.go b/controllers/printController.go index 6c8d695..d8c6bb5 100644 --- a/controllers/printController.go +++ b/controllers/printController.go @@ -180,6 +180,12 @@ func (rc *printController) PrintHandler(c *gin.Context) { executeJobs := func() { for _, printJob := range printJobs { + err := printJob.Execute() + + if err == nil { + return + } + printJob.ShopItem.WasPrinted = true _, err = repositories.ShopItems.Update(printJob.ShopItem) @@ -187,12 +193,6 @@ func (rc *printController) PrintHandler(c *gin.Context) { fmt.Printf("Error: %s\n", err) } - err := printJob.Execute() - - if err == nil { - return - } - } } From b75c46ec2f0aafdfda9f124c9644a85d352596aa Mon Sep 17 00:00:00 2001 From: kalipso Date: Wed, 2 Jul 2025 00:50:04 +0200 Subject: [PATCH 15/17] use pdfcpu for pagecount --- go.mod | 16 ++++++++++++---- go.sum | 33 +++++++++++++++++++++++++-------- models/printer.go | 1 + utils/utils.go | 44 +++++--------------------------------------- 4 files changed, 43 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index c4aae08..3345190 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ 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 - golang.org/x/crypto v0.23.0 + github.com/pdfcpu/pdfcpu v0.11.0 + golang.org/x/crypto v0.38.0 gorm.io/driver/sqlite v1.5.7 gorm.io/gorm v1.25.12 ) @@ -23,22 +23,30 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // 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/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.8.0 // indirect + golang.org/x/image v0.27.0 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2d98493..69c7fbd 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,6 @@ 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= @@ -32,6 +30,12 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -48,6 +52,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -55,10 +61,17 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pdfcpu/pdfcpu v0.11.0 h1:mL18Y3hSHzSezmnrzA21TqlayBOXuAx7BUzzZyroLGM= +github.com/pdfcpu/pdfcpu v0.11.0/go.mod h1:F1ca4GIVFdPtmgvIdvXAycAm88noyNxZwzr9CpTy+Mw= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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= @@ -78,22 +91,26 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/models/printer.go b/models/printer.go index d8cd2ee..98885d1 100644 --- a/models/printer.go +++ b/models/printer.go @@ -135,6 +135,7 @@ func (p *PrintJob) CalculatePrintCosts() (float64, error) { pageCount := utils.CountPagesAtPath(p.ShopItem.Pdf) if pageCount == 0 { + fmt.Println("Pagecount of 0 - something is wrong here.") return 0, fmt.Errorf("Cant calculate price, pdf seems to be empty") } diff --git a/utils/utils.go b/utils/utils.go index b9a693a..2299e37 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -4,8 +4,7 @@ import ( "crypto/rand" "encoding/hex" "fmt" - "github.com/dslipak/pdf" - "io" + "github.com/pdfcpu/pdfcpu/pkg/api" ) func GenerateSessionId(length int) string { @@ -21,46 +20,13 @@ func GenerateToken() string { return GenerateSessionId(16) } -const match = "/Page\x00" - -// Pages reads the given io.ByteReader until EOF is reached, returning the -// number of pages encountered. -func Pages(reader io.ByteReader) (pages int) { - i := 0 - for { - b, err := reader.ReadByte() - if err != nil { - fmt.Println(err) - return - } - check: - switch match[i] { - case 0: - if !(b >= 'A' && b <= 'Z' || b >= 'a' && b <= 'z') { - pages++ - } - i = 0 - goto check - case b: - i++ - default: - i = 0 - } - } -} - -// PagesAtPath opens a PDF file at the given file path, returning the number -// of pages found. func CountPagesAtPath(path string) (pages int) { - r, err := pdf.Open(path) - + ctx, err := api.ReadContextFile(path) if err != nil { - fmt.Println("LOL") - fmt.Println(err) - return 0 + fmt.Println("Error reading PDF:", err) + return } - pages = r.NumPage() - + pages = ctx.PageCount return } From db3dc9ecd157a2e86e18d9298f436b605fdcdb90 Mon Sep 17 00:00:00 2001 From: kalipso Date: Wed, 2 Jul 2025 11:09:10 +0200 Subject: [PATCH 16/17] better invoice view --- static/output.css | 96 ++++++++++++++++++++++------------------------ views/invoice.html | 24 +++++++----- 2 files changed, 61 insertions(+), 59 deletions(-) diff --git a/static/output.css b/static/output.css index 941769a..4294069 100644 --- a/static/output.css +++ b/static/output.css @@ -603,10 +603,6 @@ video { margin: 1rem; } -.m-8 { - margin: 2rem; -} - .-mx-2 { margin-left: -0.5rem; margin-right: -0.5rem; @@ -786,6 +782,10 @@ video { max-width: 48rem; } +.max-w-4xl { + max-width: 56rem; +} + .max-w-6xl { max-width: 72rem; } @@ -802,10 +802,6 @@ video { max-width: 1280px; } -.max-w-4xl { - max-width: 56rem; -} - .flex-1 { flex: 1 1 0%; } @@ -846,10 +842,6 @@ video { grid-template-columns: repeat(6, minmax(0, 1fr)); } -.grid-cols-7 { - grid-template-columns: repeat(7, minmax(0, 1fr)); -} - .flex-col { flex-direction: column; } @@ -948,6 +940,19 @@ video { white-space: nowrap; } +.break-normal { + overflow-wrap: normal; + word-break: normal; +} + +.break-words { + overflow-wrap: break-word; +} + +.break-all { + word-break: break-all; +} + .rounded { border-radius: 0.25rem; } @@ -1102,6 +1107,11 @@ video { background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1)); } +.bg-green-50 { + --tw-bg-opacity: 1; + background-color: rgb(240 253 244 / var(--tw-bg-opacity, 1)); +} + .bg-green-500 { --tw-bg-opacity: 1; background-color: rgb(34 197 94 / var(--tw-bg-opacity, 1)); @@ -1192,6 +1202,11 @@ video { background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1)); } +.bg-red-50 { + --tw-bg-opacity: 1; + background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1)); +} + .bg-red-500 { --tw-bg-opacity: 1; background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1)); @@ -1292,16 +1307,6 @@ video { background-color: rgb(24 24 27 / var(--tw-bg-opacity, 1)); } -.bg-red-50 { - --tw-bg-opacity: 1; - background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1)); -} - -.bg-green-50 { - --tw-bg-opacity: 1; - background-color: rgb(240 253 244 / var(--tw-bg-opacity, 1)); -} - .fill-red-50 { fill: #fef2f2; } @@ -1331,10 +1336,6 @@ video { padding: 1rem; } -.p-8 { - padding: 2rem; -} - .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; @@ -1370,6 +1371,11 @@ video { padding-bottom: 0.125rem; } +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + .py-1\.5 { padding-top: 0.375rem; padding-bottom: 0.375rem; @@ -1415,16 +1421,6 @@ video { padding-bottom: 2rem; } -.px-8 { - padding-left: 2rem; - padding-right: 2rem; -} - -.py-1 { - padding-top: 0.25rem; - padding-bottom: 0.25rem; -} - .pb-3 { padding-bottom: 0.75rem; } @@ -1659,6 +1655,11 @@ video { color: rgb(34 197 94 / var(--tw-text-opacity, 1)); } +.text-green-700 { + --tw-text-opacity: 1; + color: rgb(21 128 61 / var(--tw-text-opacity, 1)); +} + .text-green-800 { --tw-text-opacity: 1; color: rgb(22 101 52 / var(--tw-text-opacity, 1)); @@ -1739,6 +1740,11 @@ video { color: rgb(239 68 68 / var(--tw-text-opacity, 1)); } +.text-red-700 { + --tw-text-opacity: 1; + color: rgb(185 28 28 / var(--tw-text-opacity, 1)); +} + .text-red-800 { --tw-text-opacity: 1; color: rgb(153 27 27 / var(--tw-text-opacity, 1)); @@ -1829,16 +1835,6 @@ video { color: rgb(39 39 42 / var(--tw-text-opacity, 1)); } -.text-red-700 { - --tw-text-opacity: 1; - color: rgb(185 28 28 / var(--tw-text-opacity, 1)); -} - -.text-green-700 { - --tw-text-opacity: 1; - color: rgb(21 128 61 / var(--tw-text-opacity, 1)); -} - .antialiased { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -1888,14 +1884,14 @@ video { --tw-ring-inset: inset; } -.ring-red-600\/10 { - --tw-ring-color: rgb(220 38 38 / 0.1); -} - .ring-green-600\/20 { --tw-ring-color: rgb(22 163 74 / 0.2); } +.ring-red-600\/10 { + --tw-ring-color: rgb(220 38 38 / 0.1); +} + .filter { filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } diff --git a/views/invoice.html b/views/invoice.html index 85c2a4c..7a057b3 100644 --- a/views/invoice.html +++ b/views/invoice.html @@ -4,11 +4,9 @@ - + - - @@ -21,10 +19,20 @@ imac image - - - - + + @@ -38,8 +46,6 @@ - - From 249cccd24068437d25ef620f5fce9077ae7cd282 Mon Sep 17 00:00:00 2001 From: kalipso Date: Wed, 2 Jul 2025 11:09:33 +0200 Subject: [PATCH 17/17] show newest invoice first, show datetime --- controllers/configController.go | 2 +- repositories/InvoiceRepository.go | 20 +++++++++++++++++++- views/invoiceview.html | 4 ++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/controllers/configController.go b/controllers/configController.go index 6af35b9..2b3e7fa 100644 --- a/controllers/configController.go +++ b/controllers/configController.go @@ -212,7 +212,7 @@ func (rc *configController) GetAllPaper(c *gin.Context) { ////////////////////////////////////////////////////////////////////// func (rc *configController) InvoiceView(c *gin.Context) { - invoices, err := repositories.Invoices.GetAll() + invoices, err := repositories.Invoices.GetAllNewestFirst() if err != nil { c.HTML(http.StatusBadRequest, "invoiceview.html", gin.H{"data": gin.H{"error": err}}) diff --git a/repositories/InvoiceRepository.go b/repositories/InvoiceRepository.go index d073c93..843882c 100644 --- a/repositories/InvoiceRepository.go +++ b/repositories/InvoiceRepository.go @@ -11,8 +11,11 @@ import ( type InvoiceRepository interface { Create(models.Invoice) (models.Invoice, error) GetAll() ([]models.Invoice, error) + GetAllSorted(string) ([]models.Invoice, error) + GetAllNewestFirst() ([]models.Invoice, error) + GetAllNewestLast() ([]models.Invoice, error) GetById(string) (models.Invoice, error) - //GetByShopItemId(string) (models.Invoice, error) + //GetByInvoiceId(string) (models.Invoice, error) Update(models.Invoice) (models.Invoice, error) DeleteById(string) error } @@ -44,6 +47,21 @@ func (t *GORMInvoiceRepository) GetAll() ([]models.Invoice, error) { return invoice, result.Error } +func (t *GORMInvoiceRepository) GetAllSorted(sortString string) ([]models.Invoice, error) { + var invoices []models.Invoice + result := t.DB.Preload("PrintJobs.ShopItem").Preload("PrintJobs.Variant").Preload("PrintJobs.PaperType").Preload("PrintJobs.CoverPaperType").Preload("PrintJobs").Order(sortString).Find(&invoices) + + return invoices, result.Error +} + +func (r *GORMInvoiceRepository) GetAllNewestFirst() ([]models.Invoice, error) { + return r.GetAllSorted("created_at desc") +} + +func (r *GORMInvoiceRepository) GetAllNewestLast() ([]models.Invoice, error) { + return r.GetAllSorted("created_at asc") +} + func (t *GORMInvoiceRepository) GetById(id string) (models.Invoice, error) { invoiceId, err := strconv.Atoi(id) diff --git a/views/invoiceview.html b/views/invoiceview.html index adaddb8..acc5a25 100644 --- a/views/invoiceview.html +++ b/views/invoiceview.html @@ -8,10 +8,10 @@ {{ range .data.invoices }} -
+
- Invoice #{{ .ID }} + Invoice #{{ .ID }} - {{ .CreatedAt }}
Image NameVariant PaperCoverPaper Amount Price
{{ .ShopItem.Name }}{{ .Variant.Name}}{{ .PaperType.Brand }} - {{.PaperType.Name }}: {{ .PaperType.Size }} {{ .PaperType.Weight }}g/m2{{ .PaperType.Brand }} - {{.PaperType.Name }}: {{ .PaperType.Size }} {{ .PaperType.Weight }}g/m2 +
+

{{ .ShopItem.Name }}

+

{{ .Variant.Name }}

+
+
+
+

{{ .PaperType.Brand }} - {{.PaperType.Name }}: {{ .PaperType.Size }} {{ .PaperType.Weight }}g

+ {{ if .CoverPaperType }} +

{{ .CoverPaperType.Brand }} - {{.CoverPaperType.Name }}: {{ .CoverPaperType.Size }} {{ .CoverPaperType.Weight }}g

+ {{ end }} +
+
{{ .Amount }} {{ .PriceTotal }}
{{ .PriceTotal }}