aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVidhu Kant Sharma <vidhukant@vidhukant.com>2024-07-09 07:58:34 +0530
committerVidhu Kant Sharma <vidhukant@vidhukant.com>2024-07-09 07:58:34 +0530
commitd0a44ff5cfad5d063929426e2420f6f0d55b1dbe (patch)
tree476ad4ff14e39bed40049b4d1cba296c6d395c1b
parent908fb4b2b0fd1c31aa8c1cc87f56d490b42aca7a (diff)
added custom fields supportv0.9.0
-rw-r--r--invoice/controller.go124
-rw-r--r--invoice/hooks.go32
-rw-r--r--invoice/invoice.go16
-rw-r--r--invoice/router.go4
-rw-r--r--invoice/service.go66
-rw-r--r--main.go2
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") {