Merge branch 'stats' into 'main'

Stats

See merge request ukacorp/mesari/backend!19
This commit was merged in pull request #19.
This commit is contained in:
2023-10-25 02:51:06 +00:00
12 changed files with 1120 additions and 5 deletions

View File

@@ -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": {

View File

@@ -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})
}

View File

@@ -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),
})
}

View File

@@ -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}})
}

View File

@@ -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
}

View File

@@ -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
}
}
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
}

View File

@@ -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
}

View File

@@ -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"},

View File

@@ -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 {

View File

@@ -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"},

View File

@@ -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)
}

View File

@@ -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