wip: generating and downloading invoices

This commit is contained in:
2025-08-05 11:46:58 +01:00
parent f2c474daf0
commit a85e13e17d
6 changed files with 281 additions and 2 deletions
+27 -1
View File
@@ -2,11 +2,13 @@ package api
import (
"errors"
"fmt"
"net/http"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/hazemKrimi/crimson-vault/internal/lib"
"github.com/hazemKrimi/crimson-vault/internal/types"
)
@@ -55,13 +57,37 @@ func (api *API) CreateInvoiceHandler(context echo.Context) error {
return err
}
clientId, err := uuid.Parse(body.ClientID)
if err != nil {
return types.Error{Code: http.StatusInternalServerError, Cause: errors.New("Invalid Client ID."), Messages: []string{"Unexpected error getting User!"}}
}
var client types.Client
if err := api.db.GetClientById(userId, clientId, &client); err != nil {
return types.Error{Code: http.StatusInternalServerError, Cause: errors.New("Unexpected error getting Client."), Messages: []string{"Unexpected error getting User!"}}
}
if client.UserID != context.Get("id") {
return types.Error{Code: http.StatusBadRequest, Cause: err, Messages: []string{"Client does not belong to this User!"}}
}
invoice, err := api.db.CreateInvoice(userId, body)
if err != nil {
return types.Error{Code: http.StatusInternalServerError, Cause: err, Messages: []string{"Unexpected error creating Invoice!"}}
}
return context.JSON(http.StatusOK, invoice)
var user types.User
if err := api.db.GetUserById(userId, &user); err != nil {
return types.Error{Code: http.StatusInternalServerError, Cause: errors.New("Unexpected error getting User."), Messages: []string{"Unexpected error getting User!"}}
}
lib.GenerateInvoice(invoice, user, client)
return context.Attachment(fmt.Sprintf("invoices/%s.pdf", invoice.ID), fmt.Sprintf("invoices/%s.pdf", invoice.ID))
}
func (api *API) GetAllItemsHandler(context echo.Context) error {
+206
View File
@@ -1,12 +1,24 @@
package lib
import (
"fmt"
"net/http"
"os"
"path/filepath"
"github.com/google/uuid"
"github.com/gorilla/sessions"
"github.com/johnfercher/maroto/v2"
"github.com/johnfercher/maroto/v2/pkg/components/code"
"github.com/johnfercher/maroto/v2/pkg/components/col"
"github.com/johnfercher/maroto/v2/pkg/components/image"
"github.com/johnfercher/maroto/v2/pkg/components/row"
"github.com/johnfercher/maroto/v2/pkg/components/text"
"github.com/johnfercher/maroto/v2/pkg/config"
"github.com/johnfercher/maroto/v2/pkg/consts/align"
"github.com/johnfercher/maroto/v2/pkg/consts/fontstyle"
"github.com/johnfercher/maroto/v2/pkg/core"
"github.com/johnfercher/maroto/v2/pkg/props"
"github.com/labstack/echo/v4"
"golang.org/x/crypto/bcrypt"
@@ -73,3 +85,197 @@ func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
func getGrayColor() *props.Color {
return &props.Color{
Red: 200,
Green: 200,
Blue: 200,
}
}
func getDarkGrayColor() *props.Color {
return &props.Color{
Red: 200,
Green: 200,
Blue: 200,
}
}
func getBlueColor() *props.Color {
return &props.Color{
Red: 10,
Green: 10,
Blue: 150,
}
}
func getRedColor() *props.Color {
return &props.Color{
Red: 150,
Green: 10,
Blue: 10,
}
}
func getPageHeader(client types.Client, logo string) core.Row {
return row.New(20).Add(
image.NewFromFileCol(3, logo, props.Rect{
Center: true,
Percent: 80,
}),
col.New(6),
col.New(3).Add(
text.New(fmt.Sprintf("%s, %s, %s", client.Address, client.Zip, client.Country), props.Text{
Size: 8,
Align: align.Right,
Color: getRedColor(),
}),
text.New(client.Phone, props.Text{
Top: 12,
Style: fontstyle.BoldItalic,
Size: 8,
Align: align.Right,
Color: getBlueColor(),
}),
),
)
}
func getPageFooter(user types.User) core.Row {
return row.New(20).Add(
col.New(12).Add(
text.New(user.Phone, props.Text{
Top: 13,
Style: fontstyle.BoldItalic,
Size: 8,
Align: align.Left,
Color: getBlueColor(),
}),
),
)
}
func getTransactions(items []types.Item) []core.Row {
rows := []core.Row{
row.New(5).Add(
col.New(3),
text.NewCol(4, "Service", props.Text{Size: 9, Align: align.Center, Style: fontstyle.Bold}),
text.NewCol(2, "Quantity", props.Text{Size: 9, Align: align.Center, Style: fontstyle.Bold}),
text.NewCol(3, "Price", props.Text{Size: 9, Align: align.Center, Style: fontstyle.Bold}),
),
}
var contentsRow []core.Row
for i, item := range items {
r := row.New(4).Add(
col.New(3),
text.NewCol(4, item.Name, props.Text{Size: 8, Align: align.Center}),
text.NewCol(2, fmt.Sprintf("%d", item.Quantity), props.Text{Size: 8, Align: align.Center}),
text.NewCol(3, fmt.Sprintf("%d", item.Price), props.Text{Size: 8, Align: align.Center}),
)
if i%2 == 0 {
gray := getGrayColor()
r.WithStyle(&props.Cell{BackgroundColor: gray})
}
contentsRow = append(contentsRow, r)
}
rows = append(rows, contentsRow...)
rows = append(rows, row.New(20).Add(
col.New(7),
text.NewCol(2, "Total:", props.Text{
Top: 5,
Style: fontstyle.Bold,
Size: 8,
Align: align.Right,
}),
text.NewCol(3, "$2.567,00", props.Text{
Top: 5,
Style: fontstyle.Bold,
Size: 8,
Align: align.Center,
}),
))
return rows
}
func GenerateInvoice(invoice types.Invoice, user types.User, client types.Client) error {
cfg := config.NewBuilder().WithPageNumber().WithLeftMargin(10).WithRightMargin(10).WithTopMargin(15).Build()
darkGray := getDarkGrayColor()
mrt := maroto.New(cfg)
m := maroto.NewMetricsDecorator(mrt)
err := m.RegisterHeader(getPageHeader(client, user.Logo))
if err != nil {
return err
}
err = m.RegisterFooter(getPageFooter(user))
if err != nil {
return err
}
m.AddRows(text.NewRow(10, "Invoice ABC123456789", props.Text{
Top: 3,
Style: fontstyle.Bold,
Align: align.Center,
}))
m.AddRow(7,
text.NewCol(3, "Transactions", props.Text{
Top: 1.5,
Size: 9,
Style: fontstyle.Bold,
Align: align.Center,
Color: &props.WhiteColor,
}),
).WithStyle(&props.Cell{BackgroundColor: darkGray})
m.AddRows(getTransactions(invoice.Items)...)
m.AddRow(15,
col.New(6).Add(
code.NewBar("5123.151231.512314.1251251.123215", props.Barcode{
Percent: 0,
Proportion: props.Proportion{
Width: 20,
Height: 2,
},
}),
text.New("5123.151231.512314.1251251.123215", props.Text{
Top: 12,
Family: "",
Style: fontstyle.Bold,
Size: 9,
Align: align.Center,
}),
),
col.New(6),
)
document, err := m.Generate()
if err != nil {
return err
}
err = document.Save(fmt.Sprintf("invoices/%s.pdf", invoice.ID))
if err != nil {
return err
}
err = document.GetReport().Save(fmt.Sprintf("invoices/%s.txt", invoice.ID))
if err != nil {
return err
}
return nil
}
+1 -1
View File
@@ -95,7 +95,7 @@ type CreateInvoiceRequestBody struct {
ClientID string `json:"clientId" validate:"uuid4,required"`
DueAt string `json:"dueAt" validate:"datetime=2006-01-02T15:04:05Z,required"`
Currency string `json:"currency" validate:"iso4217,required"`
VAT uint32 `json:"vat" validate:"number,required"`
VAT uint32 `json:"vat" validate:"number,omitempty"`
Items []CreateItemRequestBody `json:"items"`
}