// 功能:向当前用户返回本人设备的可追溯用气统计;版本: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 }