From d0a44ff5cfad5d063929426e2420f6f0d55b1dbe Mon Sep 17 00:00:00 2001 From: Vidhu Kant Sharma Date: Tue, 9 Jul 2024 07:58:34 +0530 Subject: added custom fields support --- invoice/controller.go | 124 +++++++++++++++++++++++++++++++++++++++++++++++++- invoice/hooks.go | 32 +++++++++++++ invoice/invoice.go | 16 +++++-- invoice/router.go | 4 ++ invoice/service.go | 66 ++++++++++++++++++++++++++- main.go | 2 +- 6 files changed, 235 insertions(+), 9 deletions(-) diff --git a/invoice/controller.go b/invoice/controller.go index d3dd51c..ad6df3e 100644 --- a/invoice/controller.go +++ b/invoice/controller.go @@ -210,7 +210,6 @@ func handleGetInvoiceItems (ctx *gin.Context) { userId := uId.(uint) - err = checkInvoiceOwnership(uint(id), userId) if err != nil { ctx.Error(err) @@ -315,3 +314,126 @@ func removeItem (ctx *gin.Context) { "data": item, }) } + +// get custom fields belonging to a certain invoice +func handleGetInvoiceCustomFields (ctx *gin.Context) { + id, err := strconv.ParseUint(ctx.Param("id"), 10, 64) + if err != nil { + ctx.Error(e.ErrInvalidID) + ctx.Abort() + return + } + + uId, ok := ctx.Get("UserID") + if !ok { + ctx.Error(e.ErrUnauthorized) + ctx.Abort() + return + } + + userId := uId.(uint) + + err = checkInvoiceOwnership(uint(id), userId) + if err != nil { + ctx.Error(err) + ctx.Abort() + return + } + + var cf []CustomField + err = getInvoiceCustomFields(&cf, uint(id)) + if err != nil { + ctx.Error(err) + ctx.Abort() + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "message": "success", + "data": cf, + }) +} + +func addCustomField (ctx *gin.Context) { + id, err := strconv.ParseUint(ctx.Param("id"), 10, 64) + if err != nil { + ctx.Error(e.ErrInvalidID) + ctx.Abort() + return + } + + uId, ok := ctx.Get("UserID") + if !ok { + ctx.Error(e.ErrUnauthorized) + ctx.Abort() + return + } + + userId := uId.(uint) + + var cf CustomField + ctx.Bind(&cf) + + cf.InvoiceID = uint(id) + + err = checkInvoiceOwnership(cf.InvoiceID, userId) + if err != nil { + ctx.Error(err) + ctx.Abort() + return + } + + err = cf.upsert() + if err != nil { + ctx.Error(err) + ctx.Abort() + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "message": "success", + "data": cf, + }) +} + +func removeCustomField (ctx *gin.Context) { + id, err := strconv.ParseUint(ctx.Param("id"), 10, 64) + if err != nil { + ctx.Error(e.ErrInvalidID) + ctx.Abort() + return + } + + uId, ok := ctx.Get("UserID") + if !ok { + ctx.Error(e.ErrUnauthorized) + ctx.Abort() + return + } + + userId := uId.(uint) + + var cf CustomField + cf.ID = uint(id) + + invoiceId, err := getCustomFieldInvoice(cf.ID, userId) + if err != nil { + ctx.Error(err) + ctx.Abort() + return + } + + cf.InvoiceID = invoiceId + + err = cf.del() + if err != nil { + ctx.Error(err) + ctx.Abort() + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "message": "success", + "data": cf, + }) +} diff --git a/invoice/hooks.go b/invoice/hooks.go index 7a59352..b0ec877 100644 --- a/invoice/hooks.go +++ b/invoice/hooks.go @@ -85,3 +85,35 @@ func (i *InvoiceItem) BeforeDelete(tx *gorm.DB) error { return nil } + +func (cf *CustomField) BeforeSave(tx *gorm.DB) error { + var err error + + isDraft, err := isDraft(cf.InvoiceID) + if err != nil { + return err + } + + if !isDraft { + return errors.ErrCannotEditInvoice + } + + // TODO: check if field is duplicate + + return nil +} + +func (cf *CustomField) BeforeDelete(tx *gorm.DB) error { + var err error + + isDraft, err := isDraft(cf.InvoiceID) + if err != nil { + return err + } + + if !isDraft { + return errors.ErrCannotEditInvoice + } + + return nil +} diff --git a/invoice/invoice.go b/invoice/invoice.go index 4b93ee4..4397710 100644 --- a/invoice/invoice.go +++ b/invoice/invoice.go @@ -30,7 +30,7 @@ var db *gorm.DB func init() { db = d.DB - db.AutoMigrate(&Invoice{}, &InvoiceItem{}, &InvoiceBillingAddress{}, &InvoiceShippingAddress{}) + db.AutoMigrate(&Invoice{}, &InvoiceItem{}, &InvoiceBillingAddress{}, &InvoiceShippingAddress{}, &CustomField{}) } type InvoiceBillingAddress struct { @@ -46,13 +46,21 @@ type InvoiceShippingAddress struct { } type InvoiceItem struct { - gorm.Model i.Item + ID uint InvoiceID uint BrandName string Quantity string // float } +// user can add as many custom fields as they like +type CustomField struct { + ID uint + InvoiceID uint + Key string + Value string +} + type Invoice struct { gorm.Model UserID uint `json:"-"` @@ -64,6 +72,7 @@ type Invoice struct { IsDraft bool Note string Items []InvoiceItem + CustomFields []CustomField // issuer and customer details are stored here // because they are NOT intended to ever change @@ -80,7 +89,4 @@ type Invoice struct { CustomerPhone string CustomerEmail string CustomerWebsite string - - // Transporter Transporter - // TransactionID string } diff --git a/invoice/router.go b/invoice/router.go index 290eacb..febd8fd 100644 --- a/invoice/router.go +++ b/invoice/router.go @@ -32,5 +32,9 @@ func Routes(route *gin.RouterGroup) { g.GET("/:id/item", handleGetInvoiceItems) g.POST("/:id/item", addItem) g.DELETE("/item/:id", removeItem) + g.GET("/:id/custom", handleGetInvoiceCustomFields) + g.POST("/:id/custom", addCustomField) + g.DELETE("/custom/:id", removeCustomField) + //g.PUT("/custom/:id", editCustomField) } } diff --git a/invoice/service.go b/invoice/service.go index 91579c6..163b21e 100644 --- a/invoice/service.go +++ b/invoice/service.go @@ -52,7 +52,7 @@ func getNewInvoiceNumber(userId uint) (uint, error) { } func getInvoice(invoice *Invoice, id uint) error { - res := db.Preload("BillingAddress").Preload("ShippingAddress").Preload("Items").Find(&invoice, id) + res := db.Preload("BillingAddress").Preload("ShippingAddress").Preload("Items").Preload("CustomFields").Find(&invoice, id) // TODO: handle potential errors if res.Error != nil { @@ -96,7 +96,20 @@ func getInvoiceItems(items *[]InvoiceItem, invoiceId uint) error { return nil } -// TODO: route to only get the invouce's items +func getInvoiceCustomFields(customFields *[]CustomField, invoiceId uint) error { + res := db.Where("invoice_id = ?", invoiceId).Find(&customFields) + + // TODO: handle potential errors + if res.Error != nil { + return res.Error + } + + if res.RowsAffected == 0 { + return e.ErrEmptyResponse + } + + return nil +} func (i *Invoice) upsert() error { res := db.Save(i) @@ -147,6 +160,33 @@ func getItemInvoice(itemId, userId uint) (uint, error) { return invoiceId, nil } +// also checks for ownership +func getCustomFieldInvoice(fieldId, userId uint) (uint, error) { + var invoiceId uint + res := db. + Model(&CustomField{}). + Select("invoice_id"). + Where("id = ?", fieldId). + Find(&invoiceId) + + // TODO: handle potential errors + if res.Error != nil { + return invoiceId, res.Error + } + + if res.RowsAffected == 0 { + return invoiceId, e.ErrNotFound + } + + err := checkInvoiceOwnership(invoiceId, userId) + + if err != nil { + return invoiceId, err + } + + return invoiceId, nil +} + func (i *InvoiceItem) del() error { res := db.Delete(i) @@ -163,8 +203,30 @@ func (i *InvoiceItem) del() error { return nil } +func (c *CustomField) del() error { + res := db.Delete(c) + + // TODO: handle potential errors + if res.Error != nil { + return res.Error + } + + // returns 404 if either row doesn't exist or if the user doesn't own it + if res.RowsAffected == 0 { + return e.ErrNotFound + } + + return nil +} + func (i *InvoiceItem) upsert() error { res := db.Save(i) // TODO: handle potential errors return res.Error } + +func (c *CustomField) upsert() error { + res := db.Save(c) + // TODO: handle potential errors + return res.Error +} diff --git a/main.go b/main.go index 1e5c90f..b1150d4 100644 --- a/main.go +++ b/main.go @@ -38,7 +38,7 @@ import ( "log" ) -const OPENBILLS_VERSION = "v0.8.0" +const OPENBILLS_VERSION = "v0.9.0" func init() { if !viper.GetBool("debug_mode") { -- cgit v1.2.3