Przeglądaj źródła

dashboard: 所有待办总数

Yi 2 miesięcy temu
rodzic
commit
3aa1c6d833

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20250326061245-9a4fbec39fb6
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250331022421-15889f6c46c3
 	gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/eclipse/paho.mqtt.golang v1.4.3

+ 2 - 0
go.sum

@@ -96,6 +96,8 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250326060130-a292235a6f1a h1:GMMirOkH
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250326060130-a292235a6f1a/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250326061245-9a4fbec39fb6 h1:mfOzWgdB2NIgdunlgKAQGuZWGYidEJizQRHu2T4HXR4=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250326061245-9a4fbec39fb6/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250331022421-15889f6c46c3 h1:4bIc13fiux6y03FOqptxj8VhHtvHMCltkF4CNuROMKg=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250331022421-15889f6c46c3/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b h1:w05MxH7yqveRlaRbxHhbif5YjPrJFodRPfOjYhXn7Zk=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b/go.mod h1:8tF25X6pE9WkFCczlNAC0K2mrjwKvhhp02I7o0HtDxY=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

+ 16 - 0
http/handler/dashboard/dashboard.go

@@ -57,6 +57,22 @@ func FocusIndicatorsSet(c *gin.Context) {
 	})
 }
 
+func TodoCount(c *gin.Context) {
+	var req pasturePb.IndexDataWarningSetRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.CalendarToDoCount(c)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, res)
+}
+
 func DataWarningSet(c *gin.Context) {
 	var req pasturePb.IndexDataWarningSetRequest
 	if err := ginutil.BindProto(c, &req); err != nil {

+ 1 - 0
http/route/dashboard_api.go

@@ -18,5 +18,6 @@ func DashboardApi(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		dashboardRoute.POST("/focus/indicators/set", dashboard.FocusIndicatorsSet)
 		dashboardRoute.GET("/data/warning/list", dashboard.DataWarningList)
 		dashboardRoute.POST("/data/warning/set", dashboard.DataWarningSet)
+		dashboardRoute.GET("/todo/count", dashboard.TodoCount)
 	}
 }

+ 45 - 16
module/backend/calendar.go

@@ -12,17 +12,30 @@ import (
 	"gitee.com/xuyiping_admin/pkg/xerr"
 )
 
-// CalendarToDoList 获取日历待办列表
-func (s *StoreEntry) CalendarToDoList(ctx context.Context, req *pasturePb.CalendarToDoRequest, pagination *pasturePb.PaginationModel) (*pasturePb.CalendarToDoResponse, error) {
+func (s *StoreEntry) CalendarToDoCount(ctx context.Context) (*pasturePb.TodoCountResponse, error) {
 	userModel, err := s.GetUserModel(ctx)
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
+
+	todoList, err := s.CalendarToDoHistoryList(ctx, userModel.AppPasture.Id, "")
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &pasturePb.TodoCountResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &pasturePb.TodoCountData{Count: int32(len(todoList))},
+	}, nil
+}
+
+func (s *StoreEntry) CalendarToDoHistoryList(ctx context.Context, pastureId int64, earNumber string) ([]*pasturePb.CalendarToDoList, error) {
 	whereSql := ""
-	if req.EarNumber != "" {
-		whereSql += fmt.Sprintf(` AND ear_number = '%s' `, req.EarNumber)
+	if earNumber != "" {
+		whereSql += fmt.Sprintf(` AND ear_number = '%s' `, earNumber)
 	}
-	whereSql += fmt.Sprintf(" AND pasture_id = %d AND end_day <= %d ", userModel.AppPasture.Id, util.TimeParseLocalEndUnix(time.Now().Format(model.LayoutDate2)))
+	whereSql += fmt.Sprintf(" AND pasture_id = %d AND end_day <= %d ", pastureId, util.TimeParseLocalEndUnix(time.Now().Format(model.LayoutDate2)))
 
 	calendarToDoList := make([]*pasturePb.CalendarToDoList, 0)
 	sql := `SELECT a.cow_id,b.pen_name,a.calendar_type_name,a.calendar_type_kind as calendar_type,DATE_FORMAT(FROM_UNIXTIME(a.plan_day), '%Y-%m-%d') AS plan_day,a.remaining_days,b.lact,b.ear_number FROM (
@@ -40,35 +53,51 @@ func (s *StoreEntry) CalendarToDoList(ctx context.Context, req *pasturePb.Calend
 		UNION ALL
 		SELECT cow_id,plan_day,'干奶' as calendar_type_name,9 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days FROM event_dry_milk WHERE status = 2` + whereSql + `
 		UNION ALL
-		SELECT cow_id,disease_at as plan_day,'疾病' as calendar_type_name,7 as calendar_type_kind,0 AS remaining_days FROM event_cow_disease WHERE diagnosed_result IN (2,3) AND pasture_id = ` + fmt.Sprintf("%d", userModel.AppPasture.Id) + `
+		SELECT cow_id,disease_at as plan_day,'疾病' as calendar_type_name,7 as calendar_type_kind,0 AS remaining_days FROM event_cow_disease WHERE diagnosed_result IN (2,3) AND pasture_id = ` + fmt.Sprintf("%d", pastureId) + `
 	) as a JOIN cow b ON a.cow_id = b.id WHERE 1 = 1 `
 
 	completeSql := fmt.Sprintf("%s ORDER BY a.plan_day DESC", sql)
-	if err = s.DB.Raw(completeSql).Find(&calendarToDoList).Error; err != nil {
+	if err := s.DB.Raw(completeSql).Find(&calendarToDoList).Error; err != nil {
 		return nil, err
 	}
+	return calendarToDoList, nil
+
+}
+
+// CalendarToDoList 获取日历待办列表
+func (s *StoreEntry) CalendarToDoList(ctx context.Context, req *pasturePb.CalendarToDoRequest, pagination *pasturePb.PaginationModel) (*pasturePb.CalendarToDoResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	pastureId := userModel.AppPasture.Id
+	calendarToDoList, err := s.CalendarToDoHistoryList(ctx, pastureId, req.EarNumber)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
 
 	nowTime := time.Now().Format(model.LayoutDate2)
 	todayCompletedSql := `SELECT a.count as count,a.calendar_type_name as calendar_type_name,a.calendar_type_kind as calendar_type_kind FROM (
-		SELECT count('cow_id') as count,'免疫' as calendar_type_name,1 as calendar_type_kind FROM event_immunization_plan WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ?
+		SELECT count('cow_id') as count,'免疫' as calendar_type_name,1 as calendar_type_kind FROM event_immunization_plan WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ? AND pasture_id = ?
 		UNION ALL
-		SELECT count('cow_id') as count,'同期' as calendar_type_name,2 as calendar_type_kind FROM event_cow_same_time WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ?
+		SELECT count('cow_id') as count,'同期' as calendar_type_name,2 as calendar_type_kind FROM event_cow_same_time WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ? AND pasture_id = ?
 		UNION ALL
-		SELECT count('cow_id') as count,'孕检' as calendar_type_name,4 as calendar_type_kind FROM event_pregnant_check WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ?
+		SELECT count('cow_id') as count,'孕检' as calendar_type_name,4 as calendar_type_kind FROM event_pregnant_check WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ? AND pasture_id = ?
 		UNION ALL
-		SELECT count('cow_id') as count,'断奶' as calendar_type_name,6 as calendar_type_kind FROM event_weaning WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ?
+		SELECT count('cow_id') as count,'断奶' as calendar_type_name,6 as calendar_type_kind FROM event_weaning WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ? AND pasture_id = ?
 		UNION ALL
-		SELECT count('cow_id') as count,'配种' as calendar_type_name,8 as calendar_type_kind FROM event_mating WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ?
+		SELECT count('cow_id') as count,'配种' as calendar_type_name,8 as calendar_type_kind FROM event_mating WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ? AND pasture_id = ?
 		UNION ALL
-		SELECT count('cow_id') as count,'产犊' as calendar_type_name,9 as calendar_type_kind FROM event_calving WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ?
+		SELECT count('cow_id') as count,'产犊' as calendar_type_name,9 as calendar_type_kind FROM event_calving WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ? AND pasture_id = ?
 		UNION ALL
-		SELECT count('cow_id') as count,'干奶' as calendar_type_name,10 as calendar_type_kind FROM event_dry_milk WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ?		
+		SELECT count('cow_id') as count,'干奶' as calendar_type_name,10 as calendar_type_kind FROM event_dry_milk WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') = ? AND pasture_id = ?
 		UNION ALL
-		SELECT count('cow_id') as count,'疾病' as calendar_type_name,7 as calendar_type_kind FROM event_cow_disease WHERE diagnosed_result = 4 AND DATE_FORMAT(FROM_UNIXTIME(curable_at), '%Y-%m-%d') = ?
+		SELECT count('cow_id') as count,'疾病' as calendar_type_name,7 as calendar_type_kind FROM event_cow_disease WHERE diagnosed_result = 4 AND DATE_FORMAT(FROM_UNIXTIME(curable_at), '%Y-%m-%d') = ? AND pasture_id = ?
 	) as a `
 
 	toDayCompletedList := make([]*model.TodayCompletedData, 0)
-	if err = s.DB.Raw(todayCompletedSql, nowTime, nowTime, nowTime, nowTime, nowTime, nowTime, nowTime, nowTime).
+	if err = s.DB.Raw(todayCompletedSql, nowTime, pastureId, nowTime, pastureId, nowTime, pastureId, nowTime, pastureId, nowTime, pastureId, nowTime, pastureId, nowTime, pastureId, nowTime, pastureId).
 		Find(&toDayCompletedList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}

+ 1 - 0
module/backend/interface.go

@@ -297,6 +297,7 @@ type DashboardService interface {
 	FocusIndicatorsSet(ctx context.Context, req *pasturePb.IndexFocusIndicatorsSetRequest) error
 	DataWarningSet(ctx context.Context, req *pasturePb.IndexDataWarningSetRequest) error
 	DataWarningList(ctx context.Context) (*pasturePb.IndexDataWarningResponse, error)
+	CalendarToDoCount(ctx context.Context) (*pasturePb.TodoCountResponse, error)
 }
 
 //go:generate mockgen -destination mock/WorkService.go -package kptservicemock kpt-pasture/module/backend WorkService

+ 103 - 0
module/crontab/milk_original.go

@@ -206,6 +206,9 @@ func (e *Entry) DeleteRepeatMilkData(pastureId int64, deleteModel *DeleteMilkOri
 
 	for _, v := range milkOriginalList {
 		e.Delete1(v, deleteModel.XMind, cfg)
+		e.Delete2(v, deleteModel.XMind, cfg)
+		e.Delete3(v, deleteModel.XMind, cfg)
+		e.Delete4(v, deleteModel.XMind, cfg)
 	}
 
 	/*// 2. 删除重复记录(除第一条外)
@@ -300,6 +303,106 @@ func (e *Entry) Delete1(data *model.MilkOriginal, xMinD string, cfg *MilkClassCo
 	}
 }
 
+func (e *Entry) Delete2(data *model.MilkOriginal, xMinD string, cfg *MilkClassConfig) {
+	// 1. 检查记录是否在时间范围内
+	if data.MilkDate < xMinD {
+		return
+	}
+
+	// 2. 检查是否存在重复记录(除第一条外)
+	var count int64
+	if err := e.DB.Model(new(model.MilkOriginal)).
+		Where("id BETWEEN ? AND ?", cfg.OldUpdateMaxId+1, cfg.CurrentMaxId).
+		Where("milk_date = ?", data.MilkDate).
+		Where("shifts = ?", data.Shifts).
+		Where("detacher_address = ?", data.DetacherAddress).
+		Where("attach_time = ?", data.AttachTime).
+		Where("milk_weight = ?", data.MilkWeight).
+		Where("pasture_id = ?", data.PastureId).
+		Where("id < ?", data.Id). // 只查找比当前记录更早的记录
+		Count(&count).Error; err != nil {
+		zaplog.Error("Delete2", zap.Any("err", err))
+		return
+	}
+
+	// 3. 如果存在重复记录,则删除当前记录
+	if count > 0 {
+		if err := e.DB.Model(new(model.MilkOriginal)).
+			Where("id = ?", data.Id).
+			Delete(data).Error; err != nil {
+			zaplog.Error("Delete2", zap.Any("err", err), zap.Any("data", data))
+		}
+	}
+}
+
+func (e *Entry) Delete3(data *model.MilkOriginal, xMinD string, cfg *MilkClassConfig) {
+	// 1. 检查记录是否在时间范围内
+	if data.MilkDate < xMinD {
+		return
+	}
+
+	// 2. 检查是否为班次开始且无奶量记录
+	var isFirstInShift bool
+	if err := e.DB.Raw(`
+        SELECT 1 FROM (
+            SELECT m.id, m.milk_date, m.shifts, m.milk_weight,
+                @tot := IF(@tot + m.milk_weight > 100, 100, 
+                    IF(m.shifts = @shifts, @tot, 0) + m.milk_weight) m_tot,
+                @shifts := m.shifts
+            FROM milk_original m, (SELECT @tot := 0, @shifts := 0) vars
+            WHERE m.id BETWEEN ? AND ?
+            AND m.milk_date >= ?
+            AND m.pasture_id = ?
+            ORDER BY m.milk_date, m.shifts, m.attach_time
+        ) t 
+        WHERE t.id = ? AND t.m_tot = 0`,
+		cfg.OldUpdateMaxId+1, cfg.CurrentMaxId, xMinD, data.PastureId, data.Id).
+		Scan(&isFirstInShift).Error; err != nil {
+		zaplog.Error("Delete3", zap.Any("err", err))
+		return
+	}
+
+	// 3. 如果是班次开始且无奶量记录,则删除
+	if isFirstInShift {
+		if err := e.DB.Model(new(model.MilkOriginal)).
+			Where("id = ?", data.Id).
+			Delete(data).Error; err != nil {
+			zaplog.Error("Delete3", zap.Any("err", err), zap.Any("data", data))
+		}
+	}
+}
+
+func (e *Entry) Delete4(data *model.MilkOriginal, xMinD string, cfg *MilkClassConfig) {
+	// 1. 检查记录是否在时间范围内
+	if data.MilkDate < "2020-10-01" {
+		return
+	}
+
+	// 2. 检查是否为时间异常记录
+	var isAbnormal bool
+	if err := e.DB.Model(new(model.MilkOriginal)).
+		Where("id = ?", data.Id).
+		Where("milk_date >= ?", "2020-10-01").
+		Where("recognition_time > detacher_time").
+		Where("attach_time > detacher_time").
+		Where("SUBSTRING(attach_time, 12, 2) = ?", "23").
+		Where("pasture_id = ?", data.PastureId).
+		Select("1").
+		Scan(&isAbnormal).Error; err != nil {
+		zaplog.Error("Delete4", zap.Any("err", err))
+		return
+	}
+
+	// 3. 如果是时间异常记录,则删除
+	if isAbnormal {
+		if err := e.DB.Model(new(model.MilkOriginal)).
+			Where("id = ?", data.Id).
+			Delete(data).Error; err != nil {
+			zaplog.Error("Delete4", zap.Any("err", err), zap.Any("data", data))
+		}
+	}
+}
+
 func parseXBeg(cfg *MilkClassConfig) (xBeg, xBeg1, xBeg2, xBeg3, xBeg4 int) {
 	xBeg1Parts := strings.Split(cfg.FirstClassMilkTime, ":")
 	if len(xBeg1Parts) < 2 {