diff --git a/internal/api/invoice.go b/internal/api/invoice.go index 408f6ed..4b5d409 100644 --- a/internal/api/invoice.go +++ b/internal/api/invoice.go @@ -73,15 +73,19 @@ func (api *API) CreateInvoiceHandler(context echo.Context) error { return types.Error{Code: http.StatusBadRequest, Cause: err, Messages: []string{"Client does not belong to this User!"}} } - invoice, err := api.db.CreateInvoice(userId, body) + 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!"}} + } + + invoice, err := api.db.CreateInvoice(userId, body, user.IssuedInvoicesThisYear) if err != nil { return types.Error{Code: http.StatusInternalServerError, Cause: err, Messages: []string{"Unexpected error creating Invoice!"}} } - var user types.User - - if err := api.db.GetUserById(userId, &user); err != nil { + if err := api.db.UpdateUserIssuesInvoicesThisYear(userId, user.IssuedInvoicesThisYear + 1, &user); err != nil { return types.Error{Code: http.StatusInternalServerError, Cause: errors.New("Unexpected error getting User."), Messages: []string{"Unexpected error getting User!"}} } diff --git a/internal/lib/utils.go b/internal/lib/utils.go index cdc2e1a..c30e637 100644 --- a/internal/lib/utils.go +++ b/internal/lib/utils.go @@ -5,6 +5,7 @@ import ( "net/http" "os" "path/filepath" + "time" "github.com/google/uuid" "github.com/gorilla/sessions" @@ -267,3 +268,9 @@ func GenerateInvoice(invoice types.Invoice, user types.User, client types.Client return invoicePath, nil } + +func GenerateInvoiceReference(invoiceNumber uint32) string { + year, _, _ := time.Now().Date() + + return fmt.Sprintf("INV/%d/%05d", year, invoiceNumber) +} diff --git a/internal/models/invoice.go b/internal/models/invoice.go index 03ae4f8..00c3bdd 100644 --- a/internal/models/invoice.go +++ b/internal/models/invoice.go @@ -6,6 +6,7 @@ import ( "github.com/google/uuid" + "github.com/hazemKrimi/crimson-vault/internal/lib" "github.com/hazemKrimi/crimson-vault/internal/types" ) @@ -30,7 +31,7 @@ func (db *DB) CreateItem(userId, invoiceId uuid.UUID, body types.CreateItemReque return item, nil } -func (db *DB) CreateInvoice(userId uuid.UUID, body types.CreateInvoiceRequestBody) (types.Invoice, error) { +func (db *DB) CreateInvoice(userId uuid.UUID, body types.CreateInvoiceRequestBody, issuedInvoicesThisYear uint32) (types.Invoice, error) { dueAt, err := time.Parse("2006-01-02T15:04:05Z", body.DueAt) if err != nil { @@ -45,6 +46,7 @@ func (db *DB) CreateInvoice(userId uuid.UUID, body types.CreateInvoiceRequestBod Currency: body.Currency, VAT: body.VAT, Status: types.Draft.String(), + Reference: lib.GenerateInvoiceReference(issuedInvoicesThisYear + 1), } result := db.instance.Create(&invoice) diff --git a/internal/models/user.go b/internal/models/user.go index 7155298..6676cf5 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -21,6 +21,7 @@ func (db *DB) CreateUser(body types.CreateUserRequestBody) (types.User, error) { Country: body.Country, Phone: body.Phone, Email: body.Email, + IssuedInvoicesThisYear: 0, } result := db.instance.Create(&user) @@ -99,6 +100,24 @@ func (db *DB) UpdateUser(id uuid.UUID, body types.UpdateUserRequestBody, user *t return nil } +func (db *DB) UpdateUserIssuesInvoicesThisYear(id uuid.UUID, issuedInvoicesThisYear uint32, user *types.User) error { + result := db.instance.Where("id = ?", id).First(user, id) + + if result.Error != nil { + return result.Error + } + + result = db.instance.Model(user).Updates(types.User{ + IssuedInvoicesThisYear: issuedInvoicesThisYear, + }) + + if result.Error != nil { + return result.Error + } + + return nil +} + func (db *DB) UpdateUserSecurityCredentials(id uuid.UUID, body types.UpdateUserSecurityCredentialsBody, user *types.User) error { result := db.instance.Where("id = ?", id).First(user, id) diff --git a/internal/types/user.go b/internal/types/user.go index 3862a1e..e8f857f 100644 --- a/internal/types/user.go +++ b/internal/types/user.go @@ -7,22 +7,23 @@ import ( ) type User struct { - ID string `json:"id" gorm:"type:varchar(255);primaryKey"` - SessionID string `json:"-"` - CreatedAt time.Time `json:"createAt"` - UpdatedAt time.Time `json:"updatedAt"` - DeletedAt gorm.DeletedAt `json:"deletedAt" gorm:"index"` - Logo string `json:"logo"` - Name string `json:"name"` - FiscalCode string `json:"fiscalCode"` - Address string `json:"address"` - Zip string `json:"zip"` - Country string `json:"country"` - Phone string `json:"phone"` - Email string `json:"email"` - Username string `json:"username" gorm:"unique"` - Password string `json:"-"` - Clients []Client `json:"clients" gorm:"constraint:onDelete:CASCADE"` + ID string `json:"id" gorm:"type:varchar(255);primaryKey"` + SessionID string `json:"-"` + CreatedAt time.Time `json:"createAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt gorm.DeletedAt `json:"deletedAt" gorm:"index"` + Logo string `json:"logo"` + Name string `json:"name"` + FiscalCode string `json:"fiscalCode"` + Address string `json:"address"` + Zip string `json:"zip"` + Country string `json:"country"` + Phone string `json:"phone"` + Email string `json:"email"` + Username string `json:"username" gorm:"unique"` + Password string `json:"-"` + Clients []Client `json:"clients" gorm:"constraint:onDelete:CASCADE"` + IssuedInvoicesThisYear uint32 `json:"-"` } type CreateUserRequestBody struct { diff --git a/migrations/20250815155501.sql b/migrations/20250815155501.sql new file mode 100644 index 0000000..ae62130 --- /dev/null +++ b/migrations/20250815155501.sql @@ -0,0 +1,2 @@ +-- Add column "issued_invoices_this_year" to table: "users" +ALTER TABLE `users` ADD COLUMN `issued_invoices_this_year` integer NULL; diff --git a/migrations/atlas.sum b/migrations/atlas.sum index 566796b..dfaf5b9 100644 --- a/migrations/atlas.sum +++ b/migrations/atlas.sum @@ -1,6 +1,7 @@ -h1:wDAImuaIK9lhzbnQ3++MNijLAxUnoaLdlysqqYjOfoU= +h1:dqaaDBvYJ2x0z3YaOnce0wN1vyyK5zQOFvDyqXMLMHQ= 20250611102124.sql h1:HkDgtWXUxfAR9gIObTMzP98pVEIakerASGGSufW695k= 20250616102420.sql h1:7C1kEskaDwdHmQOu3t48oZcky0WaNiFeIugJcJFkjKM= 20250616150439.sql h1:nx8CvH5om7lbeEezo7roNcTV+f0agch0gBjYxKtTDn8= 20250616151658.sql h1:O4hbYFj4bwMDML0mR9PtSn7GUbXQVCbh+lGkwTNBwnU= 20250814173803.sql h1:xRBhN15wPcJJOfzzPuMoUJsUqEYNCdLmEBiYUJBF1FA= +20250815155501.sql h1:bl3fvXR5HOkToyDak6DamRUw09gf8+tLehW3zXPH6JI=