已完成用户APP首期功能开发
交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
105
backend/api/internal/logic/client/user/usage_statistics.go
Normal file
105
backend/api/internal/logic/client/user/usage_statistics.go
Normal file
@@ -0,0 +1,105 @@
|
||||
// 功能:向当前用户返回本人设备的可追溯用气统计;版本: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 }
|
||||
Reference in New Issue
Block a user