106 lines
3.8 KiB
Go
106 lines
3.8 KiB
Go
// 功能:向当前用户返回本人设备的可追溯用气统计;版本:1.0.0。
|
||
package user
|
||
|
||
import (
|
||
"math"
|
||
"strings"
|
||
"time"
|
||
|
||
"git.apinb.com/bsm-sdk/core/errcode"
|
||
"git.apinb.com/bsm-sdk/core/infra"
|
||
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
|
||
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
|
||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// GetUsageStatistics 严格限定当前归属设备,没有计量数据时返回明确不可用状态。
|
||
func GetUsageStatistics(ctx *gin.Context) {
|
||
account, ok := common.UserAccount(ctx)
|
||
if !ok {
|
||
return
|
||
}
|
||
identity := strings.TrimSpace(ctx.Query("device_identity"))
|
||
if identity == "" {
|
||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||
return
|
||
}
|
||
var device models.ProductInfo
|
||
if err := ownedProductQuery(account.ID).Where("identity = ? AND device_kind IN ?", identity, []string{"valve", "alarm"}).First(&device).Error; err != nil {
|
||
common.RespondRecordError(ctx, err)
|
||
return
|
||
}
|
||
period := strings.TrimSpace(ctx.DefaultQuery("period", "month"))
|
||
anchor := time.Now()
|
||
if raw := strings.TrimSpace(ctx.Query("anchor")); raw != "" {
|
||
parsed, err := time.Parse("2006-01-02", raw)
|
||
if err != nil {
|
||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||
return
|
||
}
|
||
anchor = parsed
|
||
}
|
||
start, end, valid := usagePeriod(period, anchor)
|
||
if !valid {
|
||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||
return
|
||
}
|
||
var rows []models.DevUsageStat
|
||
if err := impl.DBService.Where("product_info_id = ? AND stat_date >= ? AND stat_date < ? AND status = ?", device.ID, start, end, common.StatusEnable).
|
||
Order("stat_date").Find(&rows).Error; err != nil {
|
||
infra.Response.Error(ctx, err)
|
||
return
|
||
}
|
||
details := gin.H{"available": len(rows) > 0, "device": gin.H{"identity": device.Identity, "name": device.Name, "code": device.Code},
|
||
"period": period, "period_start": start.Format("2006-01-02"), "period_end": end.AddDate(0, 0, -1).Format("2006-01-02"), "unit": "kg", "points": []gin.H{}}
|
||
if len(rows) == 0 {
|
||
details["unavailable_reason"] = "该设备尚无可用计量数据"
|
||
infra.Response.Success(ctx, details)
|
||
return
|
||
}
|
||
points := make([]gin.H, 0, len(rows))
|
||
total, breakfast, lunch, dinner := 0.0, 0.0, 0.0, 0.0
|
||
latest := rows[0]
|
||
for _, row := range rows {
|
||
total += row.UsageKg
|
||
breakfast += row.BreakfastKg
|
||
lunch += row.LunchKg
|
||
dinner += row.DinnerKg
|
||
points = append(points, gin.H{"date": row.StatDate.Format("2006-01-02"), "usage": roundUsage(row.UsageKg)})
|
||
if row.CalculatedAt.After(latest.CalculatedAt) {
|
||
latest = row
|
||
}
|
||
}
|
||
details["total_usage"] = roundUsage(total)
|
||
details["average_usage"] = roundUsage(total / float64(len(rows)))
|
||
details["composition"] = gin.H{"breakfast": roundUsage(breakfast), "lunch": roundUsage(lunch), "dinner": roundUsage(dinner)}
|
||
details["points"] = points
|
||
details["source"] = latest.Source
|
||
details["calc_version"] = latest.CalcVersion
|
||
details["updated_at"] = latest.CalculatedAt
|
||
infra.Response.Success(ctx, details)
|
||
}
|
||
|
||
// usagePeriod 返回左闭右开的统计时间范围。
|
||
func usagePeriod(period string, anchor time.Time) (time.Time, time.Time, bool) {
|
||
day := time.Date(anchor.Year(), anchor.Month(), anchor.Day(), 0, 0, 0, 0, anchor.Location())
|
||
switch period {
|
||
case "day":
|
||
return day, day.AddDate(0, 0, 1), true
|
||
case "week":
|
||
offset := (int(day.Weekday()) + 6) % 7
|
||
start := day.AddDate(0, 0, -offset)
|
||
return start, start.AddDate(0, 0, 7), true
|
||
case "month":
|
||
start := time.Date(day.Year(), day.Month(), 1, 0, 0, 0, 0, day.Location())
|
||
return start, start.AddDate(0, 1, 0), true
|
||
case "year":
|
||
start := time.Date(day.Year(), 1, 1, 0, 0, 0, 0, day.Location())
|
||
return start, start.AddDate(1, 0, 0), true
|
||
default:
|
||
return time.Time{}, time.Time{}, false
|
||
}
|
||
}
|
||
|
||
func roundUsage(value float64) float64 { return math.Round(value*1000) / 1000 }
|