From d40b225e4e9fd4b80f6edb8b1efa13d90349fbe1 Mon Sep 17 00:00:00 2001 From: Nedim Date: Fri, 20 Oct 2023 12:03:59 +0200 Subject: [PATCH] Added measurements, and devices statsh Added stats endpoints --- NOVA.postman_collection.json | 589 +++++++++++++++++++++++++++- controllers/contracts_controller.go | 87 ++++ controllers/devices_controller.go | 121 ++++++ controllers/invoices_controller.go | 36 ++ database/contract/contract.go | 58 +++ database/device/device.go | 156 +++++++- database/invoice/invoice.go | 46 +++ models/contract.go | 6 + models/device_info.go | 6 + models/invoice.go | 6 + routes/public_routes.go | 9 +- shared/database.go | 5 + 12 files changed, 1120 insertions(+), 5 deletions(-) diff --git a/NOVA.postman_collection.json b/NOVA.postman_collection.json index 3136506..51a35c7 100644 --- a/NOVA.postman_collection.json +++ b/NOVA.postman_collection.json @@ -11,7 +11,7 @@ "method": "GET", "header": [], "url": { - "raw": "{{URL}}/dashboard/map/contract/devices?contract_id=1", + "raw": "{{URL}}/dashboard/map/contract/devices?contract_id=2", "host": [ "{{URL}}" ], @@ -24,7 +24,7 @@ "query": [ { "key": "contract_id", - "value": "1" + "value": "2" } ] } @@ -611,7 +611,7 @@ ] }, { - "name": "Get Notifications", + "name": "Get notifications", "request": { "method": "GET", "header": [], @@ -699,6 +699,589 @@ } ] }, + { + "name": "Stats measurements", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{URL}}/notifications?user_id=1&time=1656192796", + "host": [ + "{{URL}}" + ], + "path": [ + "notifications" + ], + "query": [ + { + "key": "user_id", + "value": "1" + }, + { + "key": "time", + "value": "1656192796" + } + ] + } + }, + "response": [ + { + "name": "Stats devices", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{URL}}/stats/measurements?company_id=1", + "host": [ + "{{URL}}" + ], + "path": [ + "stats", + "measurements" + ], + "query": [ + { + "key": "company_id", + "value": "1" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "POST, OPTIONS, GET, PUT" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Date", + "value": "Mon, 23 Oct 2023 16:19:08 GMT" + }, + { + "key": "Content-Length", + "value": "11" + } + ], + "cookie": [], + "body": "{\n \"data\": 19\n}" + } + ] + }, + { + "name": "Stats Devices", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{URL}}/stats/devices?company_id=1&start_time=1648155072000&end_time=1698155072000", + "host": [ + "{{URL}}" + ], + "path": [ + "stats", + "devices" + ], + "query": [ + { + "key": "company_id", + "value": "1" + }, + { + "key": "start_time", + "value": "1648155072000" + }, + { + "key": "end_time", + "value": "1698155072000" + } + ] + } + }, + "response": [ + { + "name": "Stats Devices", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{URL}}/stats/devices?company_id=1&start_time=1689955072&end_time=1699155072", + "host": [ + "{{URL}}" + ], + "path": [ + "stats", + "devices" + ], + "query": [ + { + "key": "company_id", + "value": "1" + }, + { + "key": "start_time", + "value": "1689955072" + }, + { + "key": "end_time", + "value": "1699155072" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "POST, OPTIONS, GET, PUT" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Date", + "value": "Tue, 24 Oct 2023 16:03:51 GMT" + }, + { + "key": "Content-Length", + "value": "209" + } + ], + "cookie": [], + "body": "{\n \"data\": {\n \"breached\": 5,\n \"normal\": 1,\n \"monthly_counts\": {\n \"2023-07\": {\n \"inRange\": 0,\n \"outOfRange\": 0\n },\n \"2023-08\": {\n \"inRange\": 1,\n \"outOfRange\": 5\n },\n \"2023-09\": {\n \"inRange\": 0,\n \"outOfRange\": 0\n },\n \"2023-10\": {\n \"inRange\": 0,\n \"outOfRange\": 0\n }\n }\n }\n}" + } + ] + }, + { + "name": "Stats contracts", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{URL}}/stats/contracts?company_id=1&start_time=1&end_time=9999999999", + "host": [ + "{{URL}}" + ], + "path": [ + "stats", + "contracts" + ], + "query": [ + { + "key": "company_id", + "value": "1" + }, + { + "key": "start_time", + "value": "1" + }, + { + "key": "end_time", + "value": "9999999999" + } + ] + } + }, + "response": [ + { + "name": "Stats contracts", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{URL}}/stats/contracts?company_id=1&start_time=1689955072&end_time=1699155072", + "host": [ + "{{URL}}" + ], + "path": [ + "stats", + "contracts" + ], + "query": [ + { + "key": "company_id", + "value": "1" + }, + { + "key": "start_time", + "value": "1689955072" + }, + { + "key": "end_time", + "value": "1699155072" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "POST, OPTIONS, GET, PUT" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Date", + "value": "Tue, 24 Oct 2023 16:03:34 GMT" + }, + { + "key": "Content-Length", + "value": "230" + } + ], + "cookie": [], + "body": "{\n \"data\": {\n \"active\": 1,\n \"executed\": 1,\n \"monthly\": {\n \"2023-07\": {\n \"active\": 0,\n \"executed\": 0,\n \"total\": 0\n },\n \"2023-08\": {\n \"active\": 1,\n \"executed\": 1,\n \"total\": 2\n },\n \"2023-09\": {\n \"active\": 0,\n \"executed\": 0,\n \"total\": 0\n },\n \"2023-10\": {\n \"active\": 0,\n \"executed\": 0,\n \"total\": 0\n }\n }\n }\n}" + } + ] + }, + { + "name": "Milestones", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{URL}}/stats/milestones?company_id=1&start_time=1689955072&end_time=1699155072", + "host": [ + "{{URL}}" + ], + "path": [ + "stats", + "milestones" + ], + "query": [ + { + "key": "company_id", + "value": "1" + }, + { + "key": "start_time", + "value": "1689955072" + }, + { + "key": "end_time", + "value": "1699155072" + } + ] + } + }, + "response": [ + { + "name": "Milestones", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{URL}}/stats/milestones?company_id=1&start_time=1&end_time=9999999999", + "host": [ + "{{URL}}" + ], + "path": [ + "stats", + "milestones" + ], + "query": [ + { + "key": "company_id", + "value": "1" + }, + { + "key": "start_time", + "value": "1" + }, + { + "key": "end_time", + "value": "9999999999" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "POST, OPTIONS, GET, PUT" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Date", + "value": "Mon, 23 Oct 2023 16:13:13 GMT" + }, + { + "key": "Content-Length", + "value": "10" + } + ], + "cookie": [], + "body": "{\n \"data\": 5\n}" + } + ] + }, + { + "name": "Stats contracts total", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{URL}}/stats/contracts/total?company_id=1&start_time=0&end_time=999999999999", + "host": [ + "{{URL}}" + ], + "path": [ + "stats", + "contracts", + "total" + ], + "query": [ + { + "key": "company_id", + "value": "1" + }, + { + "key": "start_time", + "value": "0" + }, + { + "key": "end_time", + "value": "999999999999" + } + ] + } + }, + "response": [ + { + "name": "Stats contracts total", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{URL}}/stats/contracts/total?company_id=1&start_time=0&end_time=999999999999", + "host": [ + "{{URL}}" + ], + "path": [ + "stats", + "contracts", + "total" + ], + "query": [ + { + "key": "company_id", + "value": "1" + }, + { + "key": "start_time", + "value": "0" + }, + { + "key": "end_time", + "value": "999999999999" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "POST, OPTIONS, GET, PUT" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Date", + "value": "Mon, 23 Oct 2023 16:19:15 GMT" + }, + { + "key": "Content-Length", + "value": "10" + } + ], + "cookie": [], + "body": "{\n \"data\": 2\n}" + } + ] + }, + { + "name": "Invoices", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{URL}}/stats/invoices?company_id=1&start_time=1689955072&end_time=1699155072", + "host": [ + "{{URL}}" + ], + "path": [ + "stats", + "invoices" + ], + "query": [ + { + "key": "company_id", + "value": "1" + }, + { + "key": "start_time", + "value": "1689955072" + }, + { + "key": "end_time", + "value": "1699155072" + } + ] + } + }, + "response": [ + { + "name": "Invoices", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{URL}}/stats/invoices?company_id=1&start_time=1689955072&end_time=1699155072", + "host": [ + "{{URL}}" + ], + "path": [ + "stats", + "invoices" + ], + "query": [ + { + "key": "company_id", + "value": "1" + }, + { + "key": "start_time", + "value": "1689955072" + }, + { + "key": "end_time", + "value": "1699155072" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "POST, OPTIONS, GET, PUT" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Date", + "value": "Tue, 24 Oct 2023 16:02:48 GMT" + }, + { + "key": "Content-Length", + "value": "257" + } + ], + "cookie": [], + "body": "{\n \"data\": {\n \"issued\": 1,\n \"claimed\": 1,\n \"monthly\": {\n \"2023-07\": {\n \"insurance_claimed\": 0,\n \"invoice_issued\": 0\n },\n \"2023-08\": {\n \"insurance_claimed\": 1,\n \"invoice_issued\": 1\n },\n \"2023-09\": {\n \"insurance_claimed\": 0,\n \"invoice_issued\": 0\n },\n \"2023-10\": {\n \"insurance_claimed\": 0,\n \"invoice_issued\": 0\n }\n }\n }\n}" + } + ] + }, { "name": "Save device info", "request": { diff --git a/controllers/contracts_controller.go b/controllers/contracts_controller.go index 86a8dba..23c395f 100644 --- a/controllers/contracts_controller.go +++ b/controllers/contracts_controller.go @@ -303,3 +303,90 @@ func UpdateContract(c *gin.Context) { // Respond with the contracts and the total count c.JSON(http.StatusOK, gin.H{"data": models.ConvertContractToContractResponse(contractModel)}) } + +func GetContractCountByStatus(c *gin.Context) { + companyID := c.DefaultQuery("company_id", "") + startTimeStr := c.DefaultQuery("start_time", "") + endTimeStr := c.DefaultQuery("end_time", "") + + if companyID == "" || startTimeStr == "" || endTimeStr == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Missing required query parameters", + }) + return + } + + // Convert to uint and time.Time + companyIDUint, err := strconv.ParseUint(companyID, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid companyID", + }) + return + } + + // Convert string to Unix timestamp + startUnix, err := strconv.ParseInt(startTimeStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid start time", + }) + return + } + + endUnix, err := strconv.ParseInt(endTimeStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid end time", + }) + return + } + + // Convert Unix timestamps to time.Time + startTime := time.Unix(startUnix, 0) + endTime := time.Unix(endUnix, 0) + + activeCount, executedCount, _, monthly, err := contract.CountContractsByMultipleStatusesAndTotal(uint(companyIDUint), startTime, endTime) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Internal Server Error", + }) + return + } + + c.JSON(http.StatusOK, gin.H{"data": models.ActiveContractsResponse{ActiveCount: activeCount, ExecutedCount : executedCount, MonthlyContracts: monthly}}) + +} + +func GetTotalContractCount(c *gin.Context) { + companyID := c.DefaultQuery("company_id", "0") + startTimeStr := c.DefaultQuery("start_time", "") + endTimeStr := c.DefaultQuery("end_time", "") + + // Convert to uint and time.Time + companyIDUint, err := strconv.ParseUint(companyID, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid companyID", + }) + return + } + + // Convert string to Unix timestamp + startUnix, _ := strconv.ParseInt(startTimeStr, 10, 64) + endUnix, _ := strconv.ParseInt(endTimeStr, 10, 64) + + // Convert Unix timestamps to time.Time + startTime := time.Unix(startUnix, 0) + endTime := time.Unix(endUnix, 0) + + _, _, totalCount, _, err := contract.CountContractsByMultipleStatusesAndTotal(uint(companyIDUint), startTime, endTime) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Internal Server Error", + }) + return + } + + c.JSON(http.StatusOK, gin.H{"data" : totalCount}) +} \ No newline at end of file diff --git a/controllers/devices_controller.go b/controllers/devices_controller.go index 302caf8..d23fbbf 100644 --- a/controllers/devices_controller.go +++ b/controllers/devices_controller.go @@ -158,3 +158,124 @@ func GetDevicesByContract(c *gin.Context) { // Respond with the devices c.JSON(http.StatusOK, gin.H{"data": models.ConvertDeviceToResponse(devices)}) } + +func GetCompanyRelatedDeviceInfoCount(c *gin.Context) { + // Get the Company ID from query parameters + companyIDStr := c.DefaultQuery("company_id", "") + if companyIDStr == "" { + log.Printf("GetCompanyRelatedDeviceInfoCount Error: Company ID is required") + c.JSON(http.StatusBadRequest, gin.H{"error": "Company ID is required"}) + return + } + + // Convert string to uint for CompanyID + companyID, err := strconv.ParseUint(companyIDStr, 10, 32) + if err != nil { + log.Printf("GetCompanyRelatedDeviceInfoCount Error: Invalid Company ID: %v", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid Company ID"}) + return + } + + // Perform the counting + count, err := device.CountDeviceInfoByCompany(uint(companyID)) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // Respond with the count + c.JSON(http.StatusOK, gin.H{"data": count}) +} + +func GetCompanyRelatedDeviceInfoCountWithTempRange(c *gin.Context) { + // Get the Company ID from query parameters + companyIDStr := c.DefaultQuery("company_id", "") + startTimeStr := c.DefaultQuery("start_time", "") + endTimeStr := c.DefaultQuery("end_time", "") + if companyIDStr == "" { + log.Printf("GetCompanyRelatedDeviceInfoCount Error: Company ID is required") + c.JSON(http.StatusBadRequest, gin.H{"error": "Company ID is required"}) + return + } + if startTimeStr == "" { + log.Printf("GetCompanyRelatedDeviceInfoCount Error: startTime ID is required") + c.JSON(http.StatusBadRequest, gin.H{"error": "Start time is required"}) + return + } + + if endTimeStr == "" { + log.Printf("GetCompanyRelatedDeviceInfoCount Error: endTime is required") + c.JSON(http.StatusBadRequest, gin.H{"error": "End time is required"}) + return + } + + // Convert string to Unix timestamp + startUnix, _ := strconv.ParseInt(startTimeStr, 10, 64) + endUnix, _ := strconv.ParseInt(endTimeStr, 10, 64) + + // Convert Unix timestamps to time.Time + startTime := time.Unix(startUnix, 0) + endTime := time.Unix(endUnix, 0) + + // Convert string to uint for CompanyID + companyID, err := strconv.ParseUint(companyIDStr, 10, 32) + if err != nil { + log.Printf("GetCompanyRelatedDeviceInfoCount Error: Invalid Company ID: %v", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid Company ID"}) + return + } + + + // Get the counts + inRangeCount, outOfRangeCount, monthlyCount, err := device.CountDeviceBreachedAndNormalDevicesByCompany(uint(companyID), startTime, endTime) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + data := models.NormalAndBreachedDevicesResponse{Breached: outOfRangeCount, Normal: inRangeCount, MonthlyCounts: monthlyCount} + + // Respond with both counts + c.JSON(http.StatusOK, gin.H{"data": data}) +} + + +func GetContractsMatchingDeviceLocation(c *gin.Context) { + startTimeStr := c.DefaultQuery("start_time", "") + endTimeStr := c.DefaultQuery("end_time", "") + companyIDStr := c.DefaultQuery("company_id", "") + + if companyIDStr == "" { + log.Printf("GetCompanyRelatedDeviceInfoCount Error: Company ID is required") + c.JSON(http.StatusBadRequest, gin.H{"error": "Company ID is required"}) + return + } + + // Convert string to uint for CompanyID + companyID, err := strconv.ParseUint(companyIDStr, 10, 32) + if err != nil { + log.Printf("GetCompanyRelatedDeviceInfoCount Error: Invalid Company ID: %v", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid Company ID"}) + return + } + + // Convert string to Unix timestamp + startUnix, _ := strconv.ParseInt(startTimeStr, 10, 64) + endUnix, _ := strconv.ParseInt(endTimeStr, 10, 64) + + // Convert Unix timestamps to time.Time + startTime := time.Unix(startUnix, 0) + endTime := time.Unix(endUnix, 0) + + matches, err := device.FetchMatchingContractsAndDeviceInfo(companyID, startTime, endTime) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Internal Server Error", + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "data": len(matches), + }) +} diff --git a/controllers/invoices_controller.go b/controllers/invoices_controller.go index 4fc8c33..9ef1e7e 100644 --- a/controllers/invoices_controller.go +++ b/controllers/invoices_controller.go @@ -3,6 +3,7 @@ package controllers import ( "net/http" "strconv" + "time" "github.com/gin-gonic/gin" "gitlab.com/pactual1/backend/database/invoice" @@ -104,3 +105,38 @@ func convertToResponseModel(invoices []models.Invoice) []models.ListInvoiceRespo } return listInvoiceResponses } + + +func GetInvoiceCountByStatus(c *gin.Context) { + companyID := c.DefaultQuery("company_id", "0") + startTimeStr := c.DefaultQuery("start_time", "") + endTimeStr := c.DefaultQuery("end_time", "") + + // Convert to uint and time.Time + companyIDUint, err := strconv.ParseUint(companyID, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid companyID", + }) + return + } + + // Convert string to Unix timestamp + startUnix, _ := strconv.ParseInt(startTimeStr, 10, 64) + endUnix, _ := strconv.ParseInt(endTimeStr, 10, 64) + + // Convert Unix timestamps to time.Time + startTime := time.Unix(startUnix, 0) + endTime := time.Unix(endUnix, 0) + + activeCount, executedCount, monthly, err := invoice.CountInvoicesByMultipleStatuses(uint(companyIDUint), startTime, endTime) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Internal Server Error", + }) + return + } + + c.JSON(http.StatusOK, gin.H{"data": models.ActiveInvoiceResponse{Claimed: activeCount, Issued : executedCount, MonthlyInvoices: monthly}}) + +} diff --git a/database/contract/contract.go b/database/contract/contract.go index 0076788..f145501 100644 --- a/database/contract/contract.go +++ b/database/contract/contract.go @@ -241,3 +241,61 @@ func GetContractByID(contractID uint) (models.Contract, int, error) { return contract, http.StatusOK, nil } + +func CountContractsByMultipleStatusesAndTotal(companyID uint, startTime, endTime time.Time) (int64, int64, int64, map[string]map[string]int64, error) { + var activeCount, executedCount, totalCount int64 + monthlyCounts := make(map[string]map[string]int64) + + var contract models.Contract + + // Iterate through each month within the specified date range + for dt := startTime; dt.Before(endTime); dt = dt.AddDate(0, 1, 0) { + monthEnd := dt.AddDate(0, 1, 0) + if monthEnd.After(endTime) { + monthEnd = endTime + } + + // Initialize monthlyCounts for the current month + monthKey := dt.Format("2006-01") + monthlyCounts[monthKey] = map[string]int64{"active": 0, "executed": 0, "total": 0} + + // Query for 'active' contracts for the current month + var active int64 + err := shared.GetDb().Model(&contract). + Where("status = ? AND (buyer_id = ? OR seller_id = ?) AND start_time >= ? AND end_time <= ?", "active", companyID, companyID, dt, monthEnd). + Count(&active).Error + if err != nil { + return 0, 0, 0, nil, err + } + + // Query for 'executed' contracts for the current month + var executed int64 + err = shared.GetDb().Model(&contract). + Where("status = ? AND (buyer_id = ? OR seller_id = ?) AND start_time >= ? AND end_time <= ?", "executed", companyID, companyID, dt, monthEnd). + Count(&executed).Error + if err != nil { + return 0, 0, 0, nil, err + } + + // Query for total contracts for the current month + var total int64 + err = shared.GetDb().Model(&contract). + Where("(buyer_id = ? OR seller_id = ?) AND start_time >= ? AND end_time <= ?", companyID, companyID, dt, monthEnd). + Count(&total).Error + if err != nil { + return 0, 0, 0, nil, err + } + + // Update the counts for the current month + monthlyCounts[monthKey]["active"] = active + monthlyCounts[monthKey]["executed"] = executed + monthlyCounts[monthKey]["total"] = total + + // Update the total counts + activeCount += active + executedCount += executed + totalCount += total + } + + return activeCount, executedCount, totalCount, monthlyCounts, nil +} diff --git a/database/device/device.go b/database/device/device.go index f1afc81..85267b3 100644 --- a/database/device/device.go +++ b/database/device/device.go @@ -4,9 +4,11 @@ import ( "errors" "fmt" "log" + "math" "net/http" "strconv" "strings" + "time" "github.com/jinzhu/gorm" "gitlab.com/pactual1/backend/models" @@ -170,4 +172,156 @@ func SaveDeviceInfoToDB(deviceInfo models.DeviceInfo, rawData []byte) (models.De return deviceInfo, device, nil -} \ No newline at end of file +} + +func CountDeviceInfoByCompany(companyID uint) (int64, error) { + var contracts []models.Contract + var allDeviceIDs []int64 + var count int64 + + // Find all contracts where the company is either a buyer or a seller + if err := shared.GetDb().Where("buyer_id = ? OR seller_id = ?", companyID, companyID).Find(&contracts).Error; err != nil { + log.Printf("CountDeviceInfoByCompany Error: Database error: %v", err) + return 0, err + } + + // Aggregate all DeviceIDs from these contracts + for _, contract := range contracts { + allDeviceIDs = append(allDeviceIDs, contract.DeviceIDs...) + } + + // Count DeviceInfo entries related to these DeviceIDs + if err := shared.GetDb().Model(&models.DeviceInfo{}).Where("device_id IN (?)", allDeviceIDs).Count(&count).Error; err != nil { + log.Printf("CountDeviceInfoByCompany Error: Database error: %v", err) + return 0, err + } + + return count, nil +} + + + +func CountDeviceBreachedAndNormalDevicesByCompany(companyID uint, startTime, endTime time.Time) (int64, int64, map[string]map[string]int64, error) { + var contracts []models.Contract + var allDeviceIDs []int64 + var inRangeCount, outOfRangeCount int64 + monthlyCounts := make(map[string]map[string]int64) + + // Fetch all contracts related to the company + if err := shared.GetDb().Where("buyer_id = ? OR seller_id = ?", companyID, companyID).Find(&contracts).Error; err != nil { + log.Printf("CountDeviceInfoByCompany Error: Database error: %v", err) + return 0, 0, nil, err + } + + // Aggregate all DeviceIDs + for _, contract := range contracts { + allDeviceIDs = append(allDeviceIDs, contract.DeviceIDs...) + } + + // Iterate through each month within the specified date range + for dt := startTime; dt.Before(endTime); dt = dt.AddDate(0, 1, 0) { + monthEnd := dt.AddDate(0, 1, 0) + if monthEnd.After(endTime) { + monthEnd = endTime + } + + startUnix := dt.Unix() + endUnix := monthEnd.Unix() + + // Initialize monthlyCounts for the current month + monthKey := dt.Format("2006-01") + monthlyCounts[monthKey] = map[string]int64{"inRange": 0, "outOfRange": 0} + + // Count DeviceInfo entries with temperatures in range and out of range for the current month + for _, contract := range contracts { + var inRange, outOfRange int64 + err := shared.GetDb().Model(&models.DeviceInfo{}). + Where("device_id IN (?) AND temperature >= ? AND temperature <= ? AND timestamp >= ? AND timestamp <= ?", + allDeviceIDs, contract.MinTemp, contract.MaxTemp, startUnix, endUnix). + Count(&inRange).Error + if err != nil { + return 0, 0, nil, err + } + + err = shared.GetDb().Model(&models.DeviceInfo{}). + Where("device_id IN (?) AND (temperature < ? OR temperature > ?) AND timestamp >= ? AND timestamp <= ?", + allDeviceIDs, contract.MinTemp, contract.MaxTemp, startUnix, endUnix). + Count(&outOfRange).Error + if err != nil { + return 0, 0, nil, err + } + + // Update the counts for the current month + monthlyCounts[monthKey]["inRange"] += inRange + monthlyCounts[monthKey]["outOfRange"] += outOfRange + + // Update the total counts + inRangeCount += inRange + outOfRangeCount += outOfRange + } + } + + return inRangeCount, outOfRangeCount, monthlyCounts, nil +} + + +type ContractLocationMatch struct { + ContractID uint + DeviceInfoID uint +} +const oneKmInDegrees = 0.009 // Approximately 1 km in degrees for lat/lon + +func isWithinOneKm(lat1, lon1, lat2, lon2 float64) bool { + return math.Abs(lat1-lat2) <= oneKmInDegrees && math.Abs(lon1-lon2) <= oneKmInDegrees +} + +func FetchMatchingContractsAndDeviceInfo(companyID uint64, startTime, endTime time.Time) ([]ContractLocationMatch, error) { + var contracts []models.Contract + var results []ContractLocationMatch + registeredDevices := make(map[uint]bool) // Map to keep track of registered devices + + //Fetch contracts + err := shared.GetDb(). + Where("start_time >= ? AND end_time <= ? AND (buyer_id = ? OR seller_id = ?) ", startTime, endTime, companyID, companyID). + Find(&contracts).Error + + if err != nil { + return nil, err + } + //Loop through each contract to find matching DeviceInfo + for _, contract := range contracts { + var deviceInfos []models.DeviceInfo + + deviceIDStr := strings.Trim(strings.Join(strings.Fields(fmt.Sprint(contract.DeviceIDs)), ","), "[]") + queryString := fmt.Sprintf("device_id IN (%s) AND created_at >= ? AND created_at <= ?", deviceIDStr) + + err := shared.GetDb(). + Where(queryString, contract.StartTime, contract.EndTime). + Find(&deviceInfos).Error + + if err != nil { + return nil, err + } + + // Compare locations and created_at + for _, deviceInfo := range deviceInfos { + // Continue to the next iteration if this device has already been registered + if registeredDevices[deviceInfo.DeviceID] { + continue + } + + if isWithinOneKm(contract.StartLat, contract.StartLon, deviceInfo.Lat, deviceInfo.Lon) || + isWithinOneKm(contract.EndLat, contract.EndLon, deviceInfo.Lat, deviceInfo.Lon) { + + results = append(results, ContractLocationMatch{ + ContractID: contract.ID, + DeviceInfoID: deviceInfo.ID, + }) + registeredDevices[deviceInfo.DeviceID] = true // Mark this device as registered + } + } + } + + return results, nil +} + diff --git a/database/invoice/invoice.go b/database/invoice/invoice.go index f9066fb..ad43473 100644 --- a/database/invoice/invoice.go +++ b/database/invoice/invoice.go @@ -3,6 +3,7 @@ package invoice import ( "fmt" "strings" + "time" "gitlab.com/pactual1/backend/models" "gitlab.com/pactual1/backend/shared" @@ -66,3 +67,48 @@ func GetInvoices(buyerName string, sortBy string, limit int, offset int, ids []i return invoices, total, nil } + +func CountInvoicesByMultipleStatuses(companyID uint, startTime, endTime time.Time) (int64, int64, map[string]map[string]int64, error) { + var claimed, issued int64 + monthlyCounts := make(map[string]map[string]int64) + + // Iterate through each month within the specified date range + for dt := startTime; dt.Before(endTime); dt = dt.AddDate(0, 1, 0) { + monthEnd := dt.AddDate(0, 1, 0) + if monthEnd.After(endTime) { + monthEnd = endTime + } + + // Initialize monthlyCounts for the current month + monthKey := dt.Format("2006-01") + monthlyCounts[monthKey] = map[string]int64{"insurance_claimed": 0, "invoice_issued": 0} + + // Query for 'insurance_claimed' invoices for the current month + var monthlyClaimed int64 + err := shared.GetDb().Model(&models.Invoice{}). + Where("status = ? AND (buyer_id = ? OR seller_id = ?) AND invoice_date >= ? AND invoice_due_date <= ?", "insurance_claimed", companyID, companyID, dt, monthEnd). + Count(&monthlyClaimed).Error + if err != nil { + return 0, 0, nil, err + } + + // Query for 'invoice_issued' invoices for the current month + var monthlyIssued int64 + err = shared.GetDb().Model(&models.Invoice{}). + Where("status = ? AND (buyer_id = ? OR seller_id = ?) AND invoice_date >= ? AND invoice_due_date <= ?", "invoice_issued", companyID, companyID, dt, monthEnd). + Count(&monthlyIssued).Error + if err != nil { + return 0, 0, nil, err + } + + // Update the counts for the current month + monthlyCounts[monthKey]["insurance_claimed"] = monthlyClaimed + monthlyCounts[monthKey]["invoice_issued"] = monthlyIssued + + // Update the total counts + claimed += monthlyClaimed + issued += monthlyIssued + } + + return claimed, issued, monthlyCounts, nil +} diff --git a/models/contract.go b/models/contract.go index 4ba3d7b..7055cfa 100644 --- a/models/contract.go +++ b/models/contract.go @@ -265,6 +265,12 @@ type Status struct { Value string `json:"value"` } +type ActiveContractsResponse struct { + ActiveCount int64 `json:"active"` + ExecutedCount int64 `json:"executed"` + MonthlyContracts map[string]map[string]int64 `json:"monthly"` +} + func GetContractStatuses() []Status { return []Status{ {Value: "Active", Key: "active"}, diff --git a/models/device_info.go b/models/device_info.go index 1b82677..3ece86c 100644 --- a/models/device_info.go +++ b/models/device_info.go @@ -36,6 +36,12 @@ type DeviceInfoResponse struct { ExternalDeviceID string `json:"externalDeviceId"` } +type NormalAndBreachedDevicesResponse struct { + Breached int64 `json:"breached"` + Normal int64 `json:"normal"` + MonthlyCounts map[string]map[string]int64 `json:"monthly_counts"` +} + func ConvertDeviceInfoToResponse(deviceInfos []DeviceInfo) []DeviceInfoResponse { var deviceInfoResponses []DeviceInfoResponse for _, deviceInfo := range deviceInfos { diff --git a/models/invoice.go b/models/invoice.go index 94f2db9..b904ef6 100644 --- a/models/invoice.go +++ b/models/invoice.go @@ -110,6 +110,12 @@ type KeyValue struct { Value string `json:"value"` } +type ActiveInvoiceResponse struct { + Claimed int64 `json:"issued"` + Issued int64 `json:"claimed"` + MonthlyInvoices map[string]map[string]int64 `json:"monthly"` +} + func GetInvoiceStatuses() []Status { return []Status{ {Key: "Insurance claimed", Value: "insurance_claimed"}, diff --git a/routes/public_routes.go b/routes/public_routes.go index a961425..36eda70 100644 --- a/routes/public_routes.go +++ b/routes/public_routes.go @@ -38,7 +38,14 @@ func RegisterPublicRoutes(r *gin.Engine) { // Locations r.GET("/locations", controllers.SearchPlace) - // Notifications r.GET("/notifications", controllers.GetNotifications) + + // Stats + r.GET("/stats/measurements", controllers.GetCompanyRelatedDeviceInfoCount) + r.GET("/stats/devices", controllers.GetCompanyRelatedDeviceInfoCountWithTempRange) + r.GET("/stats/contracts", controllers.GetContractCountByStatus) + r.GET("/stats/contracts/total", controllers.GetTotalContractCount) + r.GET("/stats/invoices", controllers.GetInvoiceCountByStatus) + r.GET("/stats/milestones", controllers.GetContractsMatchingDeviceLocation) } diff --git a/shared/database.go b/shared/database.go index 06c4318..c869b99 100644 --- a/shared/database.go +++ b/shared/database.go @@ -18,13 +18,18 @@ func Init() error { // port := config.AppConfig.Database.Port dbName := config.AppConfig.Database.DatabaseName password := config.AppConfig.Database.Password + dbString := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable password=%s", host, user, dbName, password) // db, err = gorm.Open("postgres", "host=localhost user=postgres dbname=postgres sslmode=disable password=root") var err error + // //PostgreSQL db, err = gorm.Open("postgres", dbString) + + db.LogMode(true) + if err != nil { log.Println("Error initializing the database: ", err) return err