From 60042ebd30efa97b54c06eae8caf59745b4ae807 Mon Sep 17 00:00:00 2001 From: Nedim Date: Wed, 27 Sep 2023 19:20:44 +0200 Subject: [PATCH] Added invoices endpoint --- controllers/contracts_controller.go | 117 +++++++++++++++------------- controllers/invoices_controller.go | 58 ++++++++++++++ database/contract/contract.go | 113 ++++++++++++++++++--------- database/invoice/invoice.go | 61 +++++++++++++++ models/company.go | 3 + models/invoice.go | 42 ++++++++++ models/invoice_item.go | 27 +++++++ routes/public_routes.go | 5 ++ shared/database.go | 4 +- 9 files changed, 342 insertions(+), 88 deletions(-) create mode 100644 controllers/invoices_controller.go create mode 100644 database/invoice/invoice.go create mode 100644 models/invoice.go create mode 100644 models/invoice_item.go diff --git a/controllers/contracts_controller.go b/controllers/contracts_controller.go index 9faddf0..39a5b49 100644 --- a/controllers/contracts_controller.go +++ b/controllers/contracts_controller.go @@ -1,7 +1,6 @@ package controllers import ( - "log" "net/http" "strconv" "time" @@ -12,62 +11,76 @@ import ( ) func GetLatestContracts(c *gin.Context) { - // Get limit, offset, and status from query parameters with defaults - limitStr := c.DefaultQuery("limit", "99999999999999999999999999999999") - offsetStr := c.DefaultQuery("offset", "0") - status := c.DefaultQuery("status", models.ContractStatusActive) + // Existing parameters + limitStr := c.DefaultQuery("limit", "50") + offsetStr := c.DefaultQuery("offset", "0") + status := c.DefaultQuery("status", models.ContractStatusActive) - // Optional search parameters - companyName := c.Query("company_name") - startTimeStr := c.Query("start_time") - deviceIDsStr := c.QueryArray("deviceIDs[]") // Assuming deviceIDs are passed as an array query parameter + // New/Updated optional parameters + companyName := c.Query("company_name") + companyAddress := c.Query("company_address") + companyEmail := c.Query("company_email") + companyPhone := c.Query("company_phone") + contractName := c.Query("contract_name") + startTimeStr := c.Query("start_time") + endTimeStr := c.Query("end_time") + deviceIDsStr := c.QueryArray("deviceIDs[]") - // Convert limit and offset to int - limit, err := strconv.Atoi(limitStr) - if err != nil { - log.Printf("GetLatestContracts Error: Invalid limit value: %v", err) - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid limit value"}) - return - } + // Convert limit and offset to int + limit, err := strconv.Atoi(limitStr) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid limit value"}) + return + } - offset, err := strconv.Atoi(offsetStr) - if err != nil { - log.Printf("GetLatestContracts Error: Invalid offset value: %v", err) - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid offset value"}) - return - } + offset, err := strconv.Atoi(offsetStr) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid offset value"}) + return + } - // Convert startTime to time.Time from Unix time in seconds - var startTime time.Time - if startTimeStr != "" { - startTimeUnix, err := strconv.ParseInt(startTimeStr, 10, 64) - if err != nil { - log.Printf("GetLatestContracts Error: Invalid startTime value: %v", err) - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid startTime value"}) - return - } - startTime = time.Unix(startTimeUnix, 0) - } + // Convert startTime to time.Time + var startTime time.Time + if startTimeStr != "" { + startTimeUnix, err := strconv.ParseInt(startTimeStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid start_time value"}) + return + } + startTime = time.Unix(startTimeUnix, 0) + } - // Convert deviceIDs to []int64 - var deviceIDs []int64 - for _, idStr := range deviceIDsStr { - id, err := strconv.ParseInt(idStr, 10, 64) - if err != nil { - log.Printf("GetLatestContracts Error: Invalid deviceID value: %v", err) - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid deviceID value"}) - return - } - deviceIDs = append(deviceIDs, id) - } + // Convert endTime to time.Time + var endTime time.Time + if endTimeStr != "" { + endTimeUnix, err := strconv.ParseInt(endTimeStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid end_time value"}) + return + } + endTime = time.Unix(endTimeUnix, 0) + } - // Fetch contracts - contracts, total, st, err := contract.GetContracts(status, companyName, startTime, deviceIDs,limit, offset) + // Convert deviceIDs to []int64 + var deviceIDs []int64 + for _, idStr := range deviceIDsStr { + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid deviceID value"}) + return + } + deviceIDs = append(deviceIDs, id) + } - if err != nil { - c.JSON(st, gin.H{"error": err.Error()}) - return - } - // Respond with the contracts and the total count - c.JSON(http.StatusOK, gin.H{"total": total, "contracts": contracts}) + // Fetch contracts + contracts, total, st, err := contract.GetContracts(status, companyName, companyAddress, companyEmail, companyPhone, startTime, endTime, contractName, deviceIDs, limit, offset) + + if err != nil { + c.JSON(st, gin.H{"error": err.Error()}) + return + } + + // Respond with the contracts and the total count + c.JSON(http.StatusOK, gin.H{"total": total, "contracts": contracts}) } + diff --git a/controllers/invoices_controller.go b/controllers/invoices_controller.go new file mode 100644 index 0000000..ca3431c --- /dev/null +++ b/controllers/invoices_controller.go @@ -0,0 +1,58 @@ +package controllers + +import ( + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "gitlab.com/pactual1/backend/database/invoice" +) + +func GetInvoices(c *gin.Context) { + limitStr := c.DefaultQuery("limit", "10") + offsetStr := c.DefaultQuery("offset", "0") + buyerName := c.Query("buyer_name") + sortBy := c.Query("sort_by") + + limit, err := strconv.Atoi(limitStr) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid limit value"}) + return + } + + offset, err := strconv.Atoi(offsetStr) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid offset value"}) + return + } + + invoices, total, err := invoice.GetInvoices(buyerName, sortBy, limit, offset, 0) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"total": total, "invoices": invoices}) +} + +func GetInvoiceByID(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.Atoi(idStr) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) + return + } + + invoices, _, err := invoice.GetInvoices("", "", 1, 0, uint(id)) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + if len(invoices) > 0 { + c.JSON(http.StatusOK, gin.H{"invoice": invoices[0]}) + } else { + c.JSON(http.StatusNotFound, gin.H{"error": "Invoice not found"}) + } +} + diff --git a/database/contract/contract.go b/database/contract/contract.go index d936c13..03d8075 100644 --- a/database/contract/contract.go +++ b/database/contract/contract.go @@ -7,54 +7,97 @@ import ( "time" "github.com/jinzhu/gorm" + "github.com/lib/pq" "gitlab.com/pactual1/backend/models" "gitlab.com/pactual1/backend/shared" ) -func GetContracts(status string, companyName string, startTime time.Time, deviceIDs []int64,limit, offset int) ([]models.Contract, int64, int, error) { - // Create a slice to hold the contracts - var contracts []models.Contract +func GetContracts(status string, companyName string, companyAddress string, + companyEmail string, companyPhone string, startTime time.Time, endTime time.Time, + contractName string, deviceIDs []int64, limit, offset int) ([]models.Contract, int64, int, error) { + var contracts []models.Contract + db := shared.GetDb() - // Initialize the query - db := shared.GetDb().Where("status = ?", status) - countDb := db + countDb := db - if companyName != "" { - db = db.Joins("left join companies on companies.id = contracts.buyer_id").Where("companies.name LIKE ?", "%"+companyName+"%") - countDb = countDb.Joins("left join companies on companies.id = contracts.buyer_id").Where("companies.name LIKE ?", "%"+companyName+"%") - } - - if !startTime.IsZero() { - db = db.Where("start_time >= ?", startTime) - countDb = countDb.Where("start_time >= ?", startTime) - } - if len(deviceIDs) > 0 { - db = db.Where("device_ids && ARRAY[?]::int8[]", deviceIDs) - countDb = countDb.Where("device_ids && ARRAY[?]::int8[]", deviceIDs) + // Search by Status + if status != "" { + db = shared.GetDb().Where("contracts.status = ?", status) + countDb = countDb.Where("contracts.status = ?", status) + } + + // Search by Company Fields + if companyName != "" || companyAddress != "" || companyEmail != "" || companyPhone != "" { + db = db.Joins("left join companies on companies.id = contracts.buyer_id") + countDb = countDb.Joins("left join companies on companies.id = contracts.buyer_id") + + if companyName != "" { + db = db.Where("companies.name LIKE ?", "%"+companyName+"%") + countDb = countDb.Where("companies.name LIKE ?", "%"+companyName+"%") + } + + if companyAddress != "" { + db = db.Where("companies.address LIKE ?", "%"+companyAddress+"%") + countDb = countDb.Where("companies.address LIKE ?", "%"+companyAddress+"%") + } + + if companyEmail != "" { + db = db.Where("companies.email LIKE ?", "%"+companyEmail+"%") + countDb = countDb.Where("companies.email LIKE ?", "%"+companyEmail+"%") + } + + if companyPhone != "" { + db = db.Where("companies.phone LIKE ?", "%"+companyPhone+"%") + countDb = countDb.Where("companies.phone LIKE ?", "%"+companyPhone+"%") + } + } + + // Search by Contract Name + if contractName != "" { + db = db.Where("contracts.name LIKE ?", "%"+contractName+"%") + countDb = countDb.Where("contracts.name LIKE ?", "%"+contractName+"%") } - // Count the total number of contracts - var total int64 - if err := countDb.Model(&models.Contract{}).Count(&total).Error; err != nil { - log.Printf("GetLatestContracts Error: Database error: %v", err) - return contracts, total, http.StatusInternalServerError, err - } + // Search by Start Time + if !startTime.IsZero() { + db = db.Where("start_time >= ?", startTime) + countDb = countDb.Where("start_time >= ?", startTime) + } - // Fetch the latest contracts from the database with LIMIT and OFFSET - if err := db.Order("created_at desc").Limit(limit).Offset(offset).Find(&contracts).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - log.Printf("GetLatestContracts Error: No contracts found: %v", err) - return contracts, total, http.StatusNotFound, err - } else { - log.Printf("GetLatestContracts Error: Database error: %v", err) - return contracts, total, http.StatusInternalServerError, err - } - } - return contracts, total, http.StatusOK, nil + // Search by End Time + if !endTime.IsZero() { + db = db.Where("end_time <= ?", endTime) + countDb = countDb.Where("end_time <= ?", endTime) + } + + // Search by Device IDs + if len(deviceIDs) > 0 { + db = db.Where("device_ids && ?", pq.Array(deviceIDs)) + countDb = countDb.Where("device_ids && ?", pq.Array(deviceIDs)) + } + + var total int64 + if err := countDb.Model(&models.Contract{}).Count(&total).Error; err != nil { + log.Printf("GetContracts Error: Database error: %v", err) + return contracts, total, http.StatusInternalServerError, err + } + + if err := db.Order("created_at desc").Limit(limit).Offset(offset).Find(&contracts).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + log.Printf("GetContracts Error: No contracts found: %v", err) + return contracts, total, http.StatusNotFound, err + } else { + log.Printf("GetContracts Error: Database error: %v", err) + return contracts, total, http.StatusInternalServerError, err + } + } + + return contracts, total, http.StatusOK, nil } + func GetContractByID(contractID uint64) (models.Contract, int ,error) { // Fetch the contract creation date based on contractID diff --git a/database/invoice/invoice.go b/database/invoice/invoice.go new file mode 100644 index 0000000..c9a625c --- /dev/null +++ b/database/invoice/invoice.go @@ -0,0 +1,61 @@ +package invoice + +import ( + "fmt" + "strings" + + "gitlab.com/pactual1/backend/models" + "gitlab.com/pactual1/backend/shared" +) + +func GetInvoices(buyerName string, sortBy string, limit int, offset int, id uint) ([]models.Invoice, int64, error) { + var invoices []models.Invoice + + // Default sort by InvoiceDate DESC + sortField := "invoice_date" + sortOrder := "desc" + + if sortBy == "InvoiceDate" || sortBy == "InvoiceDueDate" { + sortField = strings.ToLower(sortBy) + } + + dbOrder := fmt.Sprintf("%s %s", sortField, sortOrder) + + db := shared.GetDb() + countDb := db + + if buyerName != "" { + db = db.Where("buyer_name LIKE ?", "%"+buyerName+"%") + countDb = countDb.Where("buyer_name LIKE ?", "%"+buyerName+"%") + } + + // Added conditional for ID search + if id != 0 { + db = db.Where("id = ?", id) + countDb = countDb.Where("id = ?", id) + } + + var total int64 + if err := countDb.Model(&models.Invoice{}).Count(&total).Error; err != nil { + return nil, 0, err + } + + if err := db.Preload("InvoiceItem"). + Order(dbOrder). + Limit(limit). + Offset(offset). + Find(&invoices).Error; err != nil { + return nil, 0, err + } + + for i := range invoices { + var sum int64 + for _, item := range invoices[i].InvoiceItem { + sum += item.PriceCents * item.Quantity + } + invoices[i].PriceCents = sum + } + + return invoices, total, nil +} + diff --git a/models/company.go b/models/company.go index 61ab57c..b30fa2e 100644 --- a/models/company.go +++ b/models/company.go @@ -5,6 +5,9 @@ import "github.com/jinzhu/gorm" type Company struct { gorm.Model Name string + Address string + Email string + Phone string Users []User Devices []Device } diff --git a/models/invoice.go b/models/invoice.go new file mode 100644 index 0000000..ff68619 --- /dev/null +++ b/models/invoice.go @@ -0,0 +1,42 @@ +package models + +import ( + "time" + + "github.com/jinzhu/gorm" +) + +type Invoice struct { + gorm.Model + BuyerID uint + BuyerName string + BuyerAddress string + BuyerEmail string + BuyerPhone string + SellerID uint + SellerName string + SellerAddress string + SellerEmail string + SellerPhone string + PriceCents int64 `gorm:"column:price_cents"` + Discount int64 + Tax int64 + TermsAndConditions string + InvoiceName string + InvoiceDate time.Time + InvoiceDueDate time.Time + ContractID uint + InvoiceItem [] InvoiceItem +} + + + +func (Invoice) Update() (bool, error) { + return false, nil +} +func (Invoice) Create() (bool, error) { + return false, nil +} +func (Invoice) Delete() (bool, error) { + return false, nil +} diff --git a/models/invoice_item.go b/models/invoice_item.go new file mode 100644 index 0000000..eaa14dc --- /dev/null +++ b/models/invoice_item.go @@ -0,0 +1,27 @@ +package models + +import ( + "github.com/jinzhu/gorm" +) + +type InvoiceItem struct { + gorm.Model + Description string + Quantity int64 + Unit string + PriceCents int64 `gorm:"column:price_cents"` + InvoiceID uint +} + + + +func (InvoiceItem) Update() (bool, error) { + return false, nil +} +func (InvoiceItem) Create() (bool, error) { + return false, nil +} +func (InvoiceItem) Delete() (bool, error) { + return false, nil +} + diff --git a/routes/public_routes.go b/routes/public_routes.go index 5d2dde9..5eb86cb 100644 --- a/routes/public_routes.go +++ b/routes/public_routes.go @@ -8,10 +8,15 @@ import ( func RegisterPublicRoutes(r *gin.Engine) { + // Map dashboard r.GET("/dashboard/map/contract/devices", controllers.GetDevicesByContract) r.GET("/dashboard/map/contracts", controllers.GetLatestContracts) r.GET("/dashboard/map/device_data", controllers.GetDeviceData) + // Invoices + r.GET("/invoices", controllers.GetInvoices) + r.GET("/invoice/:id", controllers.GetInvoiceByID) + r.POST("/device_data/save", controllers.SaveDeviceInfo) r.GET("/buyers/", controllers.ListBuyers) r.GET("/product_templates/", controllers.ListProductTemplates) diff --git a/shared/database.go b/shared/database.go index 9495f78..fbe907b 100644 --- a/shared/database.go +++ b/shared/database.go @@ -31,7 +31,9 @@ func Init() error { return err } //TODO AUTOMIGRATE models once we have them - db.AutoMigrate(&models.User{}, &models.Company{}, &models.Device{}, &models.DeviceInfo{}, &models.Contract{}, &models.ContractInfo{}, &models.Buyer{}, &models.ProductTemplate{}, &models.TextTemplate{}) + db.AutoMigrate(&models.User{}, &models.Company{}, &models.Device{}, &models.DeviceInfo{}, + &models.Contract{}, &models.ContractInfo{}, &models.Buyer{}, + &models.ProductTemplate{}, &models.TextTemplate{}, &models.Invoice{}, &models.InvoiceItem{} ) return nil