#4 v1.0

Нээсэн
xuyiping 34-ийг xuyiping/feature/event-оос xuyiping/develop руу нэгтгэхийг хүсэлт
57 өөрчлөгдсөн 2614 нэмэгдсэн , 425 устгасан
  1. 1 1
      Makefile
  2. 1 1
      README.md
  3. 1 1
      config/app.go
  4. 2 0
      dep/dep.go
  5. 21 20
      go.mod
  6. 1321 138
      go.sum
  7. 59 0
      http/handler/cow/cow.go
  8. 10 0
      http/handler/dashboard/dashboard.go
  9. 0 1
      http/handler/event/event_base.go
  10. 42 0
      http/handler/feeding/feeding.go
  11. 2 0
      http/route/cow_api.go
  12. 1 0
      http/route/dashboard_api.go
  13. 19 0
      http/route/feeding_api.go
  14. 1 0
      http/route/route.go
  15. 7 1
      migrator/v0001_demo.sql
  16. 2 0
      model/app_pasture_list.go
  17. 39 2
      model/cow.go
  18. 25 5
      model/event_cow_treatment.go
  19. 11 11
      model/event_enter.go
  20. 18 0
      model/event_sale.go
  21. 79 1
      model/event_sale_cow.go
  22. 2 0
      model/event_weight.go
  23. 1 0
      model/neck_active_habit.go
  24. 0 33
      model/neck_ring_bar_change.go
  25. 4 0
      model/neck_ring_bind_log.go
  26. 3 0
      model/neck_ring_health_warning.go
  27. 7 7
      model/neck_ring_original.go
  28. 30 0
      model/neck_ring_pen_change.go
  29. 1 1
      model/outbound.go
  30. 6 1
      model/outbound_detail.go
  31. 4 4
      module/backend/analysis_other.go
  32. 15 8
      module/backend/calendar.go
  33. 4 4
      module/backend/config_data.go
  34. 38 0
      module/backend/dashboard_more.go
  35. 22 1
      module/backend/enum_options.go
  36. 31 15
      module/backend/event_base.go
  37. 2 3
      module/backend/event_base_more.go
  38. 42 35
      module/backend/event_health.go
  39. 23 25
      module/backend/event_health_more.go
  40. 75 0
      module/backend/feeding.go
  41. 10 4
      module/backend/goods.go
  42. 142 0
      module/backend/indicators.go
  43. 20 7
      module/backend/interface.go
  44. 37 13
      module/backend/neck_ring_warning.go
  45. 1 1
      module/crontab/health_waning.go
  46. 1 0
      module/crontab/model.go
  47. 96 48
      module/crontab/neck_ring_calculate.go
  48. 20 17
      module/crontab/neck_ring_estrus.go
  49. 10 0
      module/crontab/neck_ring_health.go
  50. 65 9
      module/crontab/neck_ring_merge.go
  51. 71 0
      module/crontab/neck_ring_merge_test.go
  52. 5 2
      module/crontab/sql.go
  53. 131 0
      service/httpclient/http.go
  54. 20 0
      service/httpclient/interface.go
  55. 4 3
      service/wechat/http.go
  56. 7 0
      util/util.go
  57. 2 2
      util/util_test.go

+ 1 - 1
Makefile

@@ -18,4 +18,4 @@ lint:
 build:
 	rm -rf bin
 	mkdir -p bin
-	GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -o bin/kptTmrGroup -ldflags "-X kpt.kptyun.cn:3000/kpt-event/kpt-pasture/pod.appVersion=${version}" main.go
+	GOARCH=amd64 GOOS=windows CGO_ENABLED=0 go build -o bin/kptTmrGroup -ldflags "-X kpt.kptyun.cn:3000/kpt-event/kpt-pasture/pod.appVersion=${version}" main.go

+ 1 - 1
README.md

@@ -4,7 +4,7 @@ kpt-pasture- 科湃腾牧场管理系统
 
 ## Requirements
 
-- Go >= 1.17
+- Go >= 1.19
 - MySQL >= 5.7
 - Docker CE >= 19.03
 - Docker compose

+ 1 - 1
config/app.go

@@ -241,7 +241,7 @@ func init() {
 		switch appEnv {
 		case "test":
 			err = Initialize("app.test.yaml", cfg)
-		case "development":
+		case "development", "develop":
 			err = Initialize("app.develop.yaml", cfg)
 		case "production":
 			err = Initialize("app.production.yaml", cfg)

+ 2 - 0
dep/dep.go

@@ -7,6 +7,7 @@ import (
 	"kpt-pasture/module/crontab"
 	moduleMqtt "kpt-pasture/module/mqtt"
 	"kpt-pasture/service/asynqsvc"
+	"kpt-pasture/service/httpclient"
 	"kpt-pasture/service/redis"
 	"kpt-pasture/service/sso"
 	"kpt-pasture/service/wechat"
@@ -40,5 +41,6 @@ func Options() []di.HubOption {
 		crontab.Module,
 		moduleMqtt.Module,
 		mqttstore.Module,
+		httpclient.Module,
 	}
 }

+ 21 - 20
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20250616080546-3ebf4d3f0874
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250708055755-eca7bde9521e
 	gitee.com/xuyiping_admin/pkg v0.0.0-20250613101634-36c36a2d27d0
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/eclipse/paho.mqtt.golang v1.4.3
@@ -34,16 +34,8 @@ require (
 
 require (
 	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
-	github.com/gorilla/websocket v1.5.3 // indirect
-	github.com/nyaruka/phonenumbers v1.1.7 // indirect
-	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
-	golang.org/x/arch v0.3.0 // indirect
-)
-
-require (
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
-	github.com/bytedance/sonic v1.10.1 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
 	github.com/chenzhuoyu/iasm v0.9.0 // indirect
@@ -54,17 +46,16 @@ require (
 	github.com/gin-contrib/sse v0.1.0 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
-	github.com/go-playground/validator/v10 v10.15.5 // indirect
-	github.com/go-redis/redis/v8 v8.11.2 // indirect
+	github.com/go-redis/redis/v8 v8.11.5 // indirect
 	github.com/go-sql-driver/mysql v1.7.0 // indirect
 	github.com/goccy/go-json v0.10.2 // indirect
 	github.com/google/uuid v1.6.0 // indirect
+	github.com/gorilla/websocket v1.5.3 // indirect
 	github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/huandu/xstrings v1.4.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
-	github.com/jinzhu/copier v0.3.5
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
@@ -78,18 +69,20 @@ require (
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
-	github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+	github.com/nyaruka/phonenumbers v1.1.7 // indirect
+	github.com/onsi/gomega v1.27.1 // indirect
+	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/prometheus/client_model v0.3.0 // indirect
-	github.com/prometheus/common v0.42.0 // indirect; indirec
+	github.com/prometheus/client_model v0.5.0 // indirect
+	github.com/prometheus/common v0.42.0 // indirect
 	github.com/prometheus/procfs v0.10.1 // indirect
 	github.com/richardlehane/mscfb v1.0.4 // indirect
 	github.com/richardlehane/msoleps v1.0.3 // indirect
 	github.com/robfig/cron v1.2.0 // indirect
 	github.com/robfig/cron/v3 v3.0.1 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
-	github.com/spf13/afero v1.9.5 // indirect
+	github.com/spf13/afero v1.10.0 // indirect
 	github.com/spf13/cast v1.5.1 // indirect
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
@@ -97,16 +90,13 @@ require (
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.11 // indirect
 	github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
-	github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect
-	github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect
 	github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // indirect
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.8.0 // indirect
+	golang.org/x/arch v0.3.0 // indirect
 	golang.org/x/crypto v0.23.0 // indirect
-	golang.org/x/net v0.25.0 // indirect
 	golang.org/x/sys v0.20.0 // indirect
 	golang.org/x/text v0.15.0 // indirect
-	golang.org/x/time v0.3.0
 	google.golang.org/appengine v1.6.8 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
 	google.golang.org/grpc v1.64.0 // indirect
@@ -114,3 +104,14 @@ require (
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
+
+require (
+	github.com/bytedance/sonic v1.10.1 // indirect
+	github.com/go-playground/validator/v10 v10.15.5 // indirect
+	github.com/jinzhu/copier v0.3.5
+	github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+	github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect
+	github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect
+	golang.org/x/net v0.25.0 // indirect
+	golang.org/x/time v0.5.0
+)

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 1321 - 138
go.sum


+ 59 - 0
http/handler/cow/cow.go

@@ -217,3 +217,62 @@ func LongTermInfertility(c *gin.Context) {
 	}
 	ginutil.JSONResp(c, res)
 }
+
+func AlreadySale(c *gin.Context) {
+	var req pasturePb.AlreadySalesReportRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.StartAt, valid.Required),
+		valid.Field(&req.EndAt, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	pagination := &pasturePb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.AlreadySale(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+func CanSale(c *gin.Context) {
+	var req pasturePb.CanSalesReportRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.WeightStart, valid.Required),
+		valid.Field(&req.WeightEnd, valid.Required),
+		valid.Field(&req.CurrentPrice, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	pagination := &pasturePb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.CanSale(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}

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

@@ -124,3 +124,13 @@ func Equipment(c *gin.Context) {
 
 	ginutil.JSONResp(c, res)
 }
+
+func OutNumber(c *gin.Context) {
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.OutNumber(c)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, res)
+}

+ 0 - 1
http/handler/event/event_base.go

@@ -53,7 +53,6 @@ func EnterEventCreate(c *gin.Context) {
 		valid.Field(&req.PenId, valid.Required),
 		valid.Field(&req.OperationId, valid.Required),
 		valid.Field(&req.Weight, valid.Required),
-		valid.Field(&req.BatchNumber, valid.Required),
 	); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return

+ 42 - 0
http/handler/feeding/feeding.go

@@ -0,0 +1,42 @@
+package feeding
+
+import (
+	"kpt-pasture/http/middleware"
+	"net/http"
+
+	feedingPb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+	"gitee.com/xuyiping_admin/pkg/apierr"
+	"gitee.com/xuyiping_admin/pkg/ginutil"
+	"github.com/gin-gonic/gin"
+)
+
+func GetFeedingHomepage(c *gin.Context) {
+	var req feedingPb.FeedingHomepageRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+	res, err := middleware.BackendOperation(c).OpsService.GetFeedingHomepage(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+// feeding/management
+
+func GetFeedingManagement(c *gin.Context) {
+	var req feedingPb.FeedingManagementRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.GetFeedingManagement(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}

+ 2 - 0
http/route/cow_api.go

@@ -24,5 +24,7 @@ func CowAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		searchRoute := authRouteGroup(s, "/api/v1/search/")
 		searchRoute.POST("/indicators/comparison", cow.IndicatorsComparison)
 		searchRoute.POST("/cow/long/term/infertility", cow.LongTermInfertility)
+		searchRoute.POST("/already/sale", cow.AlreadySale)
+		searchRoute.POST("/can/sale", cow.CanSale)
 	}
 }

+ 1 - 0
http/route/dashboard_api.go

@@ -21,5 +21,6 @@ func DashboardApi(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		dashboardRoute.POST("/data/warning/set", dashboard.DataWarningSet)
 		dashboardRoute.GET("/todo/count", dashboard.TodoCount)
 		dashboardRoute.GET("/equipment/list", dashboard.Equipment)
+		dashboardRoute.GET("/out/number", dashboard.OutNumber)
 	}
 }

+ 19 - 0
http/route/feeding_api.go

@@ -0,0 +1,19 @@
+package route
+
+import (
+	"kpt-pasture/http/handler/feeding"
+
+	"github.com/gin-gonic/gin"
+)
+
+func FeedingAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
+	return func(s *gin.Engine) {
+		for _, opt := range opts {
+			opt(s)
+		}
+		feedingRoute := authRouteGroup(s, "/api/v1/feeding/")
+
+		feedingRoute.GET("tmr/data", feeding.GetFeedingHomepage)
+		feedingRoute.GET("management", feeding.GetFeedingManagement)
+	}
+}

+ 1 - 0
http/route/route.go

@@ -19,6 +19,7 @@ func HTTPServerRoute(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		MilkManageAPI(opts...),
 		WarningAPI(opts...),
 		TestAPI(opts...),
+		FeedingAPI(opts...),
 	}
 
 	return func(s *gin.Engine) {

+ 7 - 1
migrator/v0001_demo.sql

@@ -3,4 +3,10 @@ CREATE TABLE IF NOT EXISTS `demo` (
    `created_at` bigint(20) unsigned NOT NULL COMMENT '创建时间',
    `updated_at` bigint(20) unsigned NOT NULL COMMENT '更新时间',
    PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='demo';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='demo';
+
+
+
+ALTER TABLE `kpt_pasture`.`app_pasture_list` 
+ADD COLUMN `pasture_id` int(11) NULL COMMENT '饲喂系统牧场id' AFTER `updated_at`,
+ADD COLUMN `pasture_url` varchar(80) NULL COMMENT '饲喂系统url' AFTER `pasture_id`;

+ 2 - 0
model/app_pasture_list.go

@@ -21,6 +21,8 @@ type AppPastureList struct {
 	PlanScale            string                         `json:"planScale"`
 	AppId                string                         `json:"appId"`
 	Status               int32                          `json:"status"`
+	FeedPastureId        int64                          `json:"feedPastureId"`
+	FeedPastureUrl       string                         `json:"feedPastureUrl"`
 	IsShow               pasturePb.IsShow_Kind          `json:"isShow"`
 	ProductionModel      int32                          `json:"productionModel"`
 	Remarks              string                         `json:"remarks"`

+ 39 - 2
model/cow.go

@@ -711,6 +711,7 @@ func NewEnterCow(pastureId int64, req *pasturePb.EventEnterRequest, penMap map[i
 		LastAbortionAt:      int64(req.AbortionAt),
 		AdmissionPrice:      req.Price,
 		BatchNumber:         req.BatchNumber,
+		NeckRingNumber:      req.NeckRingNumber,
 	}
 	cow.AdmissionAge = cow.GetAdmissionAge()
 	cow.DayAge = cow.GetDayAge()
@@ -824,6 +825,42 @@ func (c CowSlice) LongTermInfertilityToPB(breedStatusMap map[pasturePb.BreedStat
 	return res
 }
 
+func (c CowSlice) CanSaleToPB(cowKindMap map[pasturePb.CowKind_Kind]string) []*pasturePb.CanSalesReport {
+	res := make([]*pasturePb.CanSalesReport, len(c))
+	for i, v := range c {
+		cowKindName, lastWeightAtFormat, admissionAtFormat := "", "", ""
+		if name, ok := cowKindMap[v.CowKind]; ok {
+			cowKindName = name
+		}
+		if v.LastWeightAt > 0 {
+			lastWeightAtFormat = time.Unix(v.LastWeightAt, 0).Local().Format(LayoutDate2)
+		}
+		if v.AdmissionAt > 0 {
+			admissionAtFormat = time.Unix(v.AdmissionAt, 0).Local().Format(LayoutDate2)
+		}
+		res[i] = &pasturePb.CanSalesReport{
+			CowId:              int32(v.Id),
+			EarNumber:          v.EarNumber,
+			BatchNumber:        v.BatchNumber,
+			CowKindName:        cowKindName,
+			PenName:            v.PenName,
+			Weight:             float32(v.CurrentWeight) / 1000,
+			AdmissionAge:       v.AdmissionAge,
+			EnterWeight:        0,
+			EnterPrice:         0,
+			LastWeightAtFormat: lastWeightAtFormat,
+			DayAvgFeedCost:     0,
+			AllFeedCost:        0,
+			AllMedicalCharge:   0,
+			OtherCost:          0,
+			ProfitAndLoss:      0,
+			AdmissionAtFormat:  admissionAtFormat,
+			DayAvgWeight:       0,
+		}
+	}
+	return res
+}
+
 // CowBehaviorCurveResponse 脖环行为数据
 type CowBehaviorCurveResponse struct {
 	Code int32                 `json:"code"`
@@ -843,6 +880,6 @@ type CowBehaviorCurveData struct {
 	RuminaChange     []int32                                 `json:"ruminaChange"`     // 反刍变化
 	LowActivity      int32                                   `json:"lowActivity"`      // 低活动量参数
 	MiddleActivity   int32                                   `json:"middleActivity"`   // 中活动量行数
-	IQR1             []int32                                 `json:"IQR1"`
-	IQR3             []int32                                 `json:"IQR3"`
+	IQR1             []int32                                 `json:"IQR1"`             // 1IQR
+	IQR3             []int32                                 `json:"IQR3"`             // 3IQR
 }

+ 25 - 5
model/event_cow_treatment.go

@@ -2,6 +2,7 @@ package model
 
 import (
 	"encoding/json"
+	"time"
 
 	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
 	"go.uber.org/zap"
@@ -56,6 +57,7 @@ func NewEventCowTreatment(
 		PrescriptionName:   prescription.Name,
 		PrescriptionDetail: string(b),
 		TreatmentResult:    req.TreatmentResult,
+		TreatmentAt:        int64(req.TreatmentAt),
 		OperationId:        operation.Id,
 		OperationName:      operation.Name,
 		MessageId:          currentUser.Id,
@@ -74,6 +76,8 @@ func NewEventCowCurableTreatment(pastureId int64, currUser, operationUser *Syste
 		TreatmentResult: pasturePb.TreatmentResult_Curable,
 		OperationId:     operationUser.Id,
 		OperationName:   operationUser.Name,
+		MessageId:       currUser.Id,
+		MessageName:     currUser.Name,
 		Remarks:         remarks,
 		TreatmentAt:     curableAt,
 	}
@@ -92,22 +96,38 @@ func (e EventCowTreatmentSlice) ToPB(eventCowDisease *EventCowDisease) []*pastur
 			}
 		}
 
+		treatmentResult := pasturePb.IsShow_No
+		if v.TreatmentResult == pasturePb.TreatmentResult_Curable {
+			treatmentResult = pasturePb.IsShow_Ok
+		}
+
+		diseaseAtFormat, treatmentAtFormat := "", ""
+		if eventCowDisease.DiseaseAt > 0 {
+			diseaseAtFormat = time.Unix(eventCowDisease.DiseaseAt, 0).Format(LayoutDate2)
+		}
+
+		if v.TreatmentAt > 0 {
+			treatmentAtFormat = time.Unix(v.TreatmentAt, 0).Format(LayoutDate2)
+		}
+
 		res[i] = &pasturePb.EventCowTreatment{
+			Id:                 int32(v.Id),
 			CowId:              int32(v.CowId),
 			CowDiseaseId:       int32(v.CowDiseaseId),
 			PrescriptionId:     v.PrescriptionId,
 			PrescriptionName:   v.PrescriptionName,
-			PrescriptionDetail: prescriptionDetail,
-			TreatmentResult:    0,
+			TreatmentResult:    treatmentResult,
 			OperationId:        int32(v.OperationId),
 			OperationName:      v.OperationName,
 			Remarks:            v.Remarks,
-			CreatedAt:          int32(v.CreatedAt),
-			UpdatedAt:          int32(v.UpdatedAt),
 			DiseaseId:          int32(v.DiseaseId),
-			Id:                 int32(v.Id),
 			DiseaseName:        eventCowDisease.DiseaseName,
 			DiseaseAt:          int32(eventCowDisease.DiseaseAt),
+			PrescriptionDetail: prescriptionDetail,
+			DiseaseAtFormat:    diseaseAtFormat,
+			TreatmentAtFormat:  treatmentAtFormat,
+			CreatedAt:          int32(v.CreatedAt),
+			UpdatedAt:          int32(v.UpdatedAt),
 		}
 	}
 	return res

+ 11 - 11
model/event_enter.go

@@ -45,20 +45,20 @@ type EventEnter struct {
 func (e *EventEnter) TableName() string {
 	return "event_enter"
 }
-func NewEventEnter(pastureId, cowId int64, req *pasturePb.EventEnterRequest) *EventEnter {
+func NewEventEnter(pastureId int64, cowInfo *Cow, req *pasturePb.EventEnterRequest) *EventEnter {
 	return &EventEnter{
 		PastureId:        pastureId,
-		EarNumber:        req.EarNumber,
-		CowId:            cowId,
-		Sex:              req.Sex,
-		BirthAt:          int64(req.BirthAt),
-		CowSource:        req.CowSource,
-		CowType:          req.CowType,
-		BreedStatus:      req.BreedStatus,
-		Lact:             req.Lact,
+		EarNumber:        cowInfo.EarNumber,
+		CowId:            cowInfo.Id,
+		Sex:              cowInfo.Sex,
+		BirthAt:          cowInfo.BirthAt,
+		CowSource:        cowInfo.SourceKind,
+		CowType:          cowInfo.CowType,
+		BreedStatus:      cowInfo.BreedStatus,
+		Lact:             cowInfo.Lact,
 		DayAge:           int32(math.Floor(float64(int32(time.Now().Local().Unix())-req.BirthAt) / 86400)),
-		PenId:            req.PenId,
-		CowKind:          req.CowKind,
+		PenId:            cowInfo.PenId,
+		CowKind:          cowInfo.CowKind,
 		FatherNumber:     req.FatherNumber,
 		MotherNumber:     req.MotherNumber,
 		MatingAt:         int64(req.MatingAt),

+ 18 - 0
model/event_sale.go

@@ -2,6 +2,7 @@ package model
 
 import (
 	"strings"
+	"time"
 
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 )
@@ -120,6 +121,23 @@ func (e EventSaleSlice) ToPB(eventSaleCarMap map[int64][]*EventSaleCar, eventSal
 	return res
 }
 
+func (e EventSaleSlice) ToPB2(monthRang []string) *pasturePb.OutNumber {
+	res := &pasturePb.OutNumber{
+		Month:  monthRang,
+		Number: make([]int32, 0),
+		Name:   "近6个月的出栏量 ",
+	}
+	for i, m := range monthRang {
+		for _, v := range e {
+			month := time.Unix(v.SaleAt, 0).Format(LayoutMonth)
+			if month == m {
+				res.Number[i] += v.SaleCowCount
+			}
+		}
+	}
+	return res
+}
+
 type EventSaleModel struct {
 	CowList          []*Cow
 	SalesType        pasturePb.SalesType_Kind

+ 79 - 1
model/event_sale_cow.go

@@ -1,14 +1,23 @@
 package model
 
-import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+import (
+	"fmt"
+	"time"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+)
 
 type EventSaleCow struct {
 	Id           int64                      `json:"id"`
+	BatchNumber  string                     `json:"batchNumber"`
 	PastureId    int64                      `json:"pastureId"`
 	SaleId       int64                      `json:"saleId"`
 	CowId        int64                      `json:"cowId"`
 	CowType      pasturePb.CowType_Kind     `json:"cowType"`
+	CowKind      pasturePb.CowKind_Kind     `json:"cowKind"`
 	EarNumber    string                     `json:"earNumber"`
+	PenId        int32                      `json:"penId"`
+	PenName      string                     `json:"penName"`
 	DayAge       int32                      `json:"dayAge"`
 	Lact         int32                      `json:"lact"`
 	PregnancyAge int32                      `json:"pregnancyAge"`
@@ -27,16 +36,20 @@ func (e *EventSaleCow) TableName() string {
 func NewEventSaleCow(pastureId int64, saleId, saleAt int64, cowInfo *Cow) *EventSaleCow {
 	return &EventSaleCow{
 		PastureId:    pastureId,
+		BatchNumber:  cowInfo.BatchNumber,
 		SaleId:       saleId,
 		CowId:        cowInfo.Id,
 		Lact:         cowInfo.Lact,
 		EarNumber:    cowInfo.EarNumber,
+		PenId:        cowInfo.PenId,
+		PenName:      cowInfo.PenName,
 		PregnancyAge: cowInfo.PregnancyAge,
 		BreedStatus:  cowInfo.BreedStatus,
 		LactationAge: cowInfo.LactationAge,
 		AdmissionAge: cowInfo.AdmissionAge,
 		DayAge:       cowInfo.GetEventDayAge(saleAt),
 		CowType:      cowInfo.CowType,
+		CowKind:      cowInfo.CowKind,
 	}
 }
 
@@ -47,3 +60,68 @@ func NewEventSaleCowList(pastureId int64, eventSale *EventSale, cowList []*Cow)
 	}
 	return res
 }
+
+type EventSaleCowSlice []*EventSaleCow
+
+func (e EventSaleCowSlice) ToPB(
+	eventSaleMap map[int64]*EventSale,
+	cowKindMap map[pasturePb.CowKind_Kind]string,
+	cowMap map[int64]*Cow,
+	sourceMap map[pasturePb.CowSource_Kind]string,
+) []*pasturePb.AlreadySalesReport {
+	res := make([]*pasturePb.AlreadySalesReport, 0)
+	for _, v := range e {
+		cowKindName, saleAtFormat, dealerName := "", "", ""
+		if name, ok := cowKindMap[v.CowKind]; ok {
+			cowKindName = name
+		}
+
+		salePrice, saleTotalAmount, saleAllWeight, saleAvgWeight := float32(0), float32(0), float32(0), float32(0)
+		if eventSale, ok := eventSaleMap[v.SaleId]; ok {
+			if eventSale.SaleAt > 0 {
+				saleAtFormat = time.Unix(eventSale.SaleAt, 0).Local().Format(LayoutDate2)
+			}
+			saleTotalAmount = float32(eventSale.SaleAllAmount)
+			dealerName = eventSale.DealerName
+		}
+
+		feedCycle, cowSourceName := "", ""
+		if cowInfo, ok := cowMap[v.CowId]; ok {
+			admissionAtFormat := ""
+			if cowInfo.AdmissionAt > 0 {
+				feedCycle = time.Unix(cowInfo.AdmissionAt, 0).Local().Format(LayoutDate2)
+			}
+			feedCycle = fmt.Sprintf("%s-%s", admissionAtFormat, saleAtFormat)
+
+			if name, ok := sourceMap[cowInfo.SourceKind]; ok {
+				cowSourceName = name
+			}
+		}
+
+		res = append(res, &pasturePb.AlreadySalesReport{
+			CowId:            int32(v.CowId),
+			EarNumber:        v.EarNumber,
+			BatchNumber:      v.BatchNumber,
+			CowKindName:      cowKindName,
+			PenName:          v.PenName,
+			SaleAtFormat:     saleAtFormat,
+			SalePrice:        salePrice,
+			SaleTotalAmount:  saleTotalAmount,
+			SaleAvgWeight:    saleAvgWeight,
+			SaleAllWeight:    saleAllWeight,
+			AdmissionAge:     v.AdmissionAge,
+			CowSourceName:    cowSourceName,
+			EnterWeight:      0,
+			EnterPrice:       0,
+			GrowthWeight:     0,
+			AllFeedCost:      0,
+			DayAvgFeedCost:   0,
+			AllMedicalCharge: 0,
+			OtherCost:        0,
+			DealerName:       dealerName,
+			ProfitAndLoss:    0,
+			FeedCycle:        feedCycle,
+		})
+	}
+	return res
+}

+ 2 - 0
model/event_weight.go

@@ -8,6 +8,8 @@ import (
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 )
 
+const EnterWeigh = "入场体重"
+
 type EventWeight struct {
 	ID            int64  `json:"id"`
 	PastureId     int64  `json:"pastureId"`

+ 1 - 0
model/neck_active_habit.go

@@ -30,6 +30,7 @@ type NeckActiveHabit struct {
 	EarNumber            string                `json:"earNumber"`
 	Lact                 int32                 `json:"lact"`
 	CalvingAge           int32                 `json:"calvingAge"`
+	PenId                int32                 `json:"penId"`
 	ActiveTime           string                `json:"activeTime"`
 	Frameid              int32                 `json:"frameid"`
 	HeatDate             string                `json:"heatDate"`

+ 0 - 33
model/neck_ring_bar_change.go

@@ -1,33 +0,0 @@
-package model
-
-type NeckRingBarChange struct {
-	Id             int64  `json:"id"`
-	PastureId      int64  `json:"pastureId"`
-	NeckRingNumber string `json:"neckRingNumber"`
-	HeatDate       string `json:"heatDate"`
-	FrameId        int32  `json:"frameId"`
-	PenId          int32  `json:"penId"`
-	PenName        string `json:"penName"`
-	Nb             int32  `json:"nb"`
-	ChangeHigh     int32  `json:"changeHigh"`
-	ChangeFilter   int32  `json:"changeFilter"`
-	CreatedAt      int64  `json:"createdAt"`
-	UpdatedAt      int64  `json:"updatedAt"`
-}
-
-func (n *NeckRingBarChange) TableName() string {
-	return "neck_ring_bar_change"
-}
-
-func NewNeckRingBarChange(neckRingNumber, heatDate string, frameId, nb, changeHigh, changeFilter int32, pen *Pen) *NeckRingBarChange {
-	return &NeckRingBarChange{
-		PastureId:    pen.PastureId,
-		HeatDate:     heatDate,
-		FrameId:      frameId,
-		PenId:        pen.Id,
-		PenName:      pen.Name,
-		ChangeHigh:   changeHigh,
-		ChangeFilter: changeFilter,
-		Nb:           nb,
-	}
-}

+ 4 - 0
model/neck_ring_bind_log.go

@@ -4,6 +4,10 @@ const (
 	OperationNameBind   = "绑定"
 	OperationNameUnbind = "解绑"
 	OperationNameUpdate = "编辑"
+	EventEnterBind      = "入场绑定"
+	EventDieBind        = "死亡解绑"
+	DefaultBind         = "正常绑定"
+	DefaultUnBind       = "正常解绑"
 )
 
 type NeckRingBindLog struct {

+ 3 - 0
model/neck_ring_health_warning.go

@@ -62,6 +62,7 @@ func (n NeckRingHealthWarningSlice) ToPB(
 	warningHealthLevelMap map[pasturePb.WarningHealthLevel_Kind]string,
 	cowMap map[int64]*Cow,
 	eventLogMap map[int64]string,
+	healthStatusMap map[pasturePb.HealthStatus_Kind]string,
 ) []*pasturePb.HealthWarningItem {
 	res := make([]*pasturePb.HealthWarningItem, len(n))
 	for i, v := range n {
@@ -93,6 +94,8 @@ func (n NeckRingHealthWarningSlice) ToPB(
 			data.PenId = cow.PenId
 			data.PenName = cow.PenName
 			data.Lact = cow.Lact
+			data.HealthStatus = cow.HealthStatus
+			data.HealthStatusName = healthStatusMap[cow.HealthStatus]
 		}
 
 		if desc, ok := eventLogMap[v.CowId]; ok {

+ 7 - 7
model/neck_ring_original.go

@@ -126,13 +126,13 @@ func (n *NeckRingOriginalMerge) IsMageData(data *NeckRingOriginal, xframeId int3
 }
 
 func (n *NeckRingOriginalMerge) SumAvg() {
-	n.Rumina = int32(float32(n.Rumina) / float32(n.RecordCount) * float32(n.RecordCount))
-	n.Inactive = int32(float32(n.Inactive) / float32(n.RecordCount) * float32(n.RecordCount))
-	n.Active = int32(float32(n.Active) / float32(n.RecordCount) * float32(n.RecordCount))
-	n.Intake = int32(float32(n.Intake) / float32(n.RecordCount) * float32(n.RecordCount))
-	n.Other = int32(float32(n.Other) / float32(n.RecordCount) * float32(n.RecordCount))
-	n.Gasp = int32(float32(n.Gasp) / float32(n.RecordCount) * float32(n.RecordCount))
-	n.High = int32(float32(n.High) / float32(n.RecordCount) * float32(n.RecordCount))
+	n.Rumina = int32(float32(n.Rumina) / float32(n.RecordCount) * DefaultRecordCount)
+	n.Inactive = int32(float32(n.Inactive) / float32(n.RecordCount) * DefaultRecordCount)
+	n.Active = int32(float32(n.Active) / float32(n.RecordCount) * DefaultRecordCount)
+	n.Intake = int32(float32(n.Intake) / float32(n.RecordCount) * DefaultRecordCount)
+	n.Other = int32(float32(n.Other) / float32(n.RecordCount) * DefaultRecordCount)
+	n.Gasp = int32(float32(n.Gasp) / float32(n.RecordCount) * DefaultRecordCount)
+	n.High = int32(float32(n.High) / float32(n.RecordCount) * DefaultRecordCount)
 	n.Voltage = int32(float32(n.Voltage) / float32(n.RecordCount))
 }
 

+ 30 - 0
model/neck_ring_pen_change.go

@@ -0,0 +1,30 @@
+package model
+
+type NeckRingPenChange struct {
+	Id           int64  `json:"id"`
+	PastureId    int64  `json:"pastureId"`
+	HeatDate     string `json:"heatDate"`
+	Frameid      int32  `json:"frameid"`
+	PenId        int32  `json:"penId"`
+	CowCount     int32  `json:"cowCount"`
+	ChangeHigh   int32  `json:"changeHigh"`
+	ChangeFilter int32  `json:"changeFilter"`
+	CreatedAt    int64  `json:"createdAt"`
+	UpdatedAt    int64  `json:"updatedAt"`
+}
+
+func (n *NeckRingPenChange) TableName() string {
+	return "neck_ring_pen_change"
+}
+
+func NewNeckRingPenChange(pastureId int64, heatDate string, cowCount, frameId, penId, changeHigh, changeFilter int32) *NeckRingPenChange {
+	return &NeckRingPenChange{
+		PastureId:    pastureId,
+		HeatDate:     heatDate,
+		Frameid:      frameId,
+		PenId:        penId,
+		CowCount:     cowCount,
+		ChangeHigh:   changeHigh,
+		ChangeFilter: changeFilter,
+	}
+}

+ 1 - 1
model/outbound.go

@@ -37,7 +37,7 @@ func (o *Outbound) Delete() {
 func NewOutbound(pastureId int64, req *pasturePb.OutboundApplyItem, currentUser *SystemUser) *Outbound {
 	return &Outbound{
 		PastureId:        pastureId,
-		Number:           fmt.Sprintf("%s%s", util.GenerateRandomNumberString(8), time.Now().Local().Format(LayoutDate)),
+		Number:           fmt.Sprintf("%s%s", time.Now().Local().Format(LayoutDate), util.GenerateRandomNumber(12)),
 		OutType:          req.OutType,
 		AuditStatus:      pasturePb.AuditStatus_Pending,
 		ApplicantId:      int32(currentUser.Id),

+ 6 - 1
model/outbound_detail.go

@@ -49,9 +49,13 @@ func NewOutboundDetailList(outboundId int64, req []*pasturePb.OutboundApplyGoods
 
 type OutboundDetailSlice []*OutboundDetail
 
-func (o OutboundDetailSlice) ToPB() []*pasturePb.OutboundApplyGoodsItem {
+func (o OutboundDetailSlice) ToPB(unitMap map[pasturePb.Unit_Kind]string) []*pasturePb.OutboundApplyGoodsItem {
 	res := make([]*pasturePb.OutboundApplyGoodsItem, len(o))
 	for i, v := range o {
+		unitName := ""
+		if unit, ok := unitMap[v.Unit]; ok {
+			unitName = unit
+		}
 		res[i] = &pasturePb.OutboundApplyGoodsItem{
 			GoodsId:     int32(v.GoodsId),
 			GoodsName:   v.GoodsName,
@@ -60,6 +64,7 @@ func (o OutboundDetailSlice) ToPB() []*pasturePb.OutboundApplyGoodsItem {
 			BatchNumber: v.BatchNumber,
 			Price:       float32(v.Price) / 100,
 			Unit:        v.Unit,
+			UnitName:    unitName,
 			Quantity:    uint32(v.Quantity),
 		}
 	}

+ 4 - 4
module/backend/analysis_other.go

@@ -306,13 +306,13 @@ func (s *StoreEntry) SaleCowReport(ctx context.Context, req *pasturePb.SaleCowRe
 		Where("sale_at BETWEEN ? AND ?", startDayTimeUnix, endDayTimeUnix).
 		Where("pasture_id = ?", userModel.AppPasture.Id)
 	if req.AnalysisMethod == pasturePb.SaleCowAnalysisMethod_Months {
-		pref.Select(`ROUND(SUM(sale_all_amount) /100,2) AS sale_all_amount,SUM(sale_cow_count) AS sale_all_count,
-		SUM(sale_all_weight) AS sale_all_weight,DATE_FORMAT(FROM_UNIXTIME(sale_at), '%Y-%m') AS statistic_method`)
+		pref.Select(`ROUND(SUM(sale_all_amount)/100,2) AS sale_all_amount,SUM(sale_cow_count) AS sale_all_count,
+		ROUND(SUM(sale_all_weight)/1000,2 ) AS sale_all_weight,DATE_FORMAT(FROM_UNIXTIME(sale_at), '%Y-%m') AS statistic_method`)
 	}
 
 	if req.AnalysisMethod == pasturePb.SaleCowAnalysisMethod_Dealer {
-		pref.Select(`ROUND(SUM(sale_all_amount) /100,2) AS sale_all_amount,SUM(sale_cow_count) AS sale_all_count,
-		SUM(sale_all_weight) AS sale_all_weight,dealer_name as statistic_method`)
+		pref.Select(`ROUND(SUM(sale_all_amount)/100,2) AS sale_all_amount,SUM(sale_cow_count) AS sale_all_count,
+		ROUND(SUM(sale_all_weight)/1000,2) AS sale_all_weight,dealer_name as statistic_method`)
 	}
 
 	if err = pref.Group("statistic_method").

+ 15 - 8
module/backend/calendar.go

@@ -45,27 +45,34 @@ func (s *StoreEntry) CalendarToDoHistoryList(ctx context.Context, pastureId int6
 		DATE_FORMAT(FROM_UNIXTIME(a.plan_day), '%Y-%m-%d') AS plan_day,
 		IF(a.end_day <= 0, '', DATE_FORMAT(FROM_UNIXTIME(a.end_day), '%Y-%m-%d')) AS end_day,
 		IF(a.reality_day <= 0, '', DATE_FORMAT(FROM_UNIXTIME(a.reality_day), '%Y-%m-%d')) AS reality_day,
-		a.remaining_days,b.lact,b.ear_number,a.status as is_finish,a.remarks
+		a.remaining_days,b.lact,b.ear_number,a.status as is_finish,a.remarks,a.operation_name AS operator_name
 		FROM (
-			SELECT cow_id,plan_day,end_day,reality_day,status,remarks,'免疫' as calendar_type_name,1 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days 
+			SELECT cow_id,plan_day,end_day,reality_day,status,remarks,operation_name,'免疫' as calendar_type_name,
+				1 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days 
 			FROM event_immunization_plan WHERE ` + whereSql1 + `
 			  UNION ALL
-			SELECT cow_id,plan_day,end_day,reality_day,status,remarks,'同期' as calendar_type_name,2 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days 
+			SELECT cow_id,plan_day,end_day,reality_day,status,remarks,operation_name,'同期' as calendar_type_name,
+				2 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days 
 			FROM event_cow_same_time WHERE ` + whereSql1 + `
 			  UNION ALL
-			SELECT cow_id,plan_day,end_day,reality_day,status,remarks,'孕检' as calendar_type_name,4 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days 
+			SELECT cow_id,plan_day,end_day,reality_day,status,remarks,operation_name,'孕检' as calendar_type_name,
+				4 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days 
 			FROM event_pregnant_check WHERE ` + whereSql1 + `
 			  UNION ALL
-			SELECT cow_id,plan_day,end_day,reality_day,status,remarks,'断奶' as calendar_type_name,6 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days 
+			SELECT cow_id,plan_day,end_day,reality_day,status,remarks,operation_name,'断奶' as calendar_type_name,
+				6 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days 
 			FROM event_weaning WHERE ` + whereSql1 + `
 			  UNION ALL
-			SELECT cow_id,plan_day,end_day,reality_day,status,remarks,'配种' as calendar_type_name,8 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days 
+			SELECT cow_id,plan_day,end_day,reality_day,status,remarks,operation_name,'配种' as calendar_type_name,
+				8 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days 
 			FROM event_mating WHERE ` + whereSql1 + `
 			  UNION ALL
-			SELECT cow_id,plan_day,end_day,reality_day,status,remarks,'产犊' as calendar_type_name,9 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days 
+			SELECT cow_id,plan_day,end_day,reality_day,status,remarks,operation_name,'产犊' as calendar_type_name,
+				9 as calendar_type_kind,TIMESTAMPDIFF(DAY, NOW(), FROM_UNIXTIME(end_day)) AS remaining_days 
 			FROM event_calving WHERE ` + whereSql1 + `
 			  UNION ALL
-			SELECT cow_id,disease_at as plan_day,curable_at as end_day,curable_at as reality_day,health_status as status,remarks,'疾病' as calendar_type_name,7 as calendar_type_kind,0 AS remaining_days 
+			SELECT cow_id,disease_at as plan_day,curable_at as end_day,curable_at as reality_day,health_status as status,
+				remarks,'' as operation_name,'疾病' as calendar_type_name,7 as calendar_type_kind,0 AS remaining_days
 			FROM event_cow_disease WHERE health_status IN (2,3) AND ` + whereSql + `
 		) as a 
 	JOIN cow b ON a.cow_id = b.id `

+ 4 - 4
module/backend/config_data.go

@@ -126,7 +126,7 @@ func (s *StoreEntry) CowTypeEnumList(optionName, isAll string) []*pasturePb.Conf
 			Disabled: true,
 		}, &pasturePb.ConfigOptionsList{
 			Value:    int32(pasturePb.CowType_Breeding_Calf),
-			Label:    "母牛",
+			Label:    "母牛",
 			Disabled: true,
 		})
 		return cowTypeList
@@ -154,7 +154,7 @@ func (s *StoreEntry) CowTypeEnumList(optionName, isAll string) []*pasturePb.Conf
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.CowType_Breeding_Calf),
-		Label:    "母牛",
+		Label:    "母牛",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.CowType_Breeding_Bull),
@@ -179,7 +179,7 @@ func (s *StoreEntry) SameTimeCowTypeEnumList(isAll string) []*pasturePb.ConfigOp
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.SameTimeCowType_Breeding_Calf),
-		Label:    "母牛",
+		Label:    "母牛",
 		Disabled: true,
 	})
 	return cowTypeList
@@ -238,7 +238,7 @@ func (s *StoreEntry) ImmunizationCowTypeEnumList(isAll string) []*pasturePb.Conf
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.CowType_Breeding_Calf),
-		Label:    "母牛",
+		Label:    "母牛",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.CowType_Breeding_Bull),

+ 38 - 0
module/backend/dashboard_more.go

@@ -299,3 +299,41 @@ func (s *StoreEntry) Equipment(ctx context.Context) (*pasturePb.EquipmentRespons
 		Data: &pasturePb.EquipmentData{EquipmentList: equipmentList},
 	}, nil
 }
+
+func (s *StoreEntry) OutNumber(ctx context.Context) (*pasturePb.OutNumberResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	nowTime := time.Now().Local()
+	currentMonth := nowTime.Format(model.LayoutMonth)
+	startMonth := nowTime.AddDate(0, -5, 0).Format(model.LayoutMonth)
+
+	monthRang, err := util.GetMonthsBetween(startMonth, currentMonth)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if len(monthRang) != 6 {
+		return nil, xerr.Customf("错误的日期范围")
+	}
+
+	startAt := util.TimeParseLocalUnix(fmt.Sprintf("%s-01", monthRang[0]))
+	endDate, _ := util.GetLastDayOfMonth(monthRang[len(monthRang)-1])
+	endAt := util.TimeParseLocalEndUnix(endDate)
+
+	eventSaleList := make([]*model.EventSale, 0)
+	if err = s.DB.Model(new(model.EventSale)).
+		Select("sale_at, sale_cow_count").
+		Where("pasture_id = ?", userModel.AppPasture.Id).
+		Where("sale_at BETWEEN ? AND ?", startAt, endAt).
+		Find(&eventSaleList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &pasturePb.OutNumberResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: model.EventSaleSlice(eventSaleList).ToPB2(monthRang),
+	}, nil
+}

+ 22 - 1
module/backend/enum_options.go

@@ -190,9 +190,30 @@ func (s *StoreEntry) SystemUserOptions(ctx context.Context, depName string) (*pa
 		return nil, xerr.Custom("部门已经禁用")
 	}
 
+	systemUserDepthRoleList := make([]*model.SystemUserDepthRole, 0)
+	if err = s.DB.Model(new(model.SystemUserDepthRole)).
+		Where("FIND_IN_SET(?,depth_ids) > 0", systemDepth.Id).
+		Where("pasture_id = ?", userModel.AppPasture.Id).
+		Find(&systemUserDepthRoleList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	if len(systemUserDepthRoleList) <= 0 {
+		return &pasturePb.ConfigOptionsListResponse{
+			Code: http.StatusOK,
+			Msg:  "ok",
+			Data: make([]*pasturePb.ConfigOptionsList, 0),
+		}, nil
+	}
+
+	userIds := make([]int64, 0)
+	for _, v := range systemUserDepthRoleList {
+		userIds = append(userIds, v.UserId)
+	}
+
 	systemUserList := make([]*model.SystemUser, 0)
 	if err = s.DB.Table(new(model.SystemUser).TableName()).
-		Where("FIND_IN_SET(?,dept_ids) > 0", systemDepth.Id).
+		Where("id IN ?", userIds).
 		Where("is_delete = ?", pasturePb.IsShow_Ok).
 		Where("is_show = ? ", pasturePb.IsShow_Ok).
 		Find(&systemUserList).Error; err != nil {

+ 31 - 15
module/backend/event_base.go

@@ -93,19 +93,22 @@ func (s *StoreEntry) CreateEnter(ctx context.Context, req *pasturePb.EventEnterR
 		return xerr.WithStack(err)
 	}
 
+	pastureId := userModel.AppPasture.Id
 	req.MessengerId = int32(userModel.SystemUser.Id)
 	req.MessengerName = userModel.SystemUser.Name
-	if req.OperationId > 0 {
-		systemUser, _ := s.GetSystemUserById(ctx, int64(req.OperationId))
-		req.OperationName = systemUser.Name
+
+	operationSystemUser, err := s.GetSystemUserById(ctx, int64(req.OperationId))
+	if err != nil {
+		return xerr.WithStack(err)
 	}
+	req.OperationName = operationSystemUser.Name
 
-	penMap := s.PenMap(ctx, userModel.AppPasture.Id)
+	penMap := s.PenMap(ctx, pastureId)
 	if len(penMap) <= 0 {
 		return xerr.Customf("请先设置牛舍信息")
 	}
 
-	newCow := model.NewEnterCow(userModel.AppPasture.Id, req, penMap)
+	newCow := model.NewEnterCow(pastureId, req, penMap)
 	if err = s.DB.Transaction(func(tx *gorm.DB) error {
 		// 新增牛只信息
 		if err = tx.Model(new(model.Cow)).Create(newCow).Error; err != nil {
@@ -113,34 +116,47 @@ func (s *StoreEntry) CreateEnter(ctx context.Context, req *pasturePb.EventEnterR
 		}
 
 		// 新增入场事件
-		newEventEnter := model.NewEventEnter(userModel.AppPasture.Id, newCow.Id, req)
+		newEventEnter := model.NewEventEnter(pastureId, newCow, req)
 		if err = tx.Model(new(model.EventEnter)).Create(newEventEnter).Error; err != nil {
 			return xerr.WithStack(err)
 		}
 
 		// 新增胎次数据
-		newCowLact := model.NewCowLact(userModel.AppPasture.Id, newCow)
+		newCowLact := model.NewCowLact(pastureId, newCow)
 		if err = tx.Model(new(model.CowLact)).Create(newCowLact).Error; err != nil {
 			return xerr.WithStack(err)
 		}
 
-		eventWeight := model.NewEventWeight(
-			userModel.AppPasture.Id,
-			newCow,
-			userModel.SystemUser,
+		eventWeight := model.NewEventWeight(pastureId, newCow, userModel.SystemUser,
 			&pasturePb.EventWeight{
 				WeightAt:      req.EnterAt,
-				Remarks:       "入场体重",
+				Remarks:       model.EnterWeigh,
 				OperationId:   req.OperationId,
 				OperationName: req.OperationName,
 				Weight:        req.Weight,
 			})
-		if err = tx.Model(new(model.EventWeight)).Create(eventWeight).Error; err != nil {
+		if err = tx.Model(new(model.EventWeight)).
+			Create(eventWeight).Error; err != nil {
 			return xerr.WithStack(err)
 		}
 
+		// 脖环绑定
+		if newCow.NeckRingNumber != "" {
+			newCowNeckRing := model.NewNeckRing(pastureId, newCow.NeckRingNumber, newCow, operationSystemUser)
+			if err = tx.Model(new(model.NeckRing)).
+				Create(newCowNeckRing).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+
+			newNeckRingBindLog := model.NewNeckRingBindLog(pastureId, newCow.NeckRingNumber, newCow, userModel.SystemUser, model.EventEnterBind)
+			if err = tx.Model(new(model.NeckRingBindLog)).
+				Create(newNeckRingBindLog).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+		}
+
 		// 记录事件日志
-		cowLogs := s.SubmitEventLog(ctx, userModel.AppPasture.Id, newCow, pasturePb.EventType_Enter, req)
+		cowLogs := s.SubmitEventLog(ctx, pastureId, newCow, pasturePb.EventType_Enter, req)
 		if err = tx.Table(cowLogs.TableName()).Create(cowLogs).Error; err != nil {
 			return xerr.WithStack(err)
 		}
@@ -169,7 +185,7 @@ func (s *StoreEntry) GroupTransferList(ctx context.Context, req *pasturePb.Searc
 		Where("a.pasture_id = ?", userModel.AppPasture.Id)
 
 	if req.EarNumber != "" {
-		pref.Where("a.ear_number = ?", req.EarNumber)
+		pref.Where("f.ear_number = ?", req.EarNumber)
 	}
 
 	if req.TransferReasonId > 0 {

+ 2 - 3
module/backend/event_base_more.go

@@ -4,7 +4,6 @@ import (
 	"context"
 	"kpt-pasture/model"
 	"net/http"
-	"strings"
 
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
@@ -297,7 +296,7 @@ func (s *StoreEntry) CowSaleCreate(ctx context.Context, req *pasturePb.EventCowS
 					Updates(neckRing).Error; err != nil {
 					return xerr.WithStack(err)
 				}
-				newNeckRingBindLog := model.NewNeckRingBindLog(userModel.AppPasture.Id, neckRing.NeckRingNumber, cowInfo, userModel.SystemUser, "死亡解绑")
+				newNeckRingBindLog := model.NewNeckRingBindLog(userModel.AppPasture.Id, neckRing.NeckRingNumber, cowInfo, userModel.SystemUser, model.EventDieBind)
 				if err = tx.Model(new(model.NeckRingBindLog)).Create(newNeckRingBindLog).Error; err != nil {
 					return xerr.WithStack(err)
 				}
@@ -447,7 +446,7 @@ func (s *StoreEntry) ImmunizationBatch(ctx context.Context, req *pasturePb.Immun
 	if err = s.DB.Model(new(model.EventImmunizationPlan)).
 		Where("pasture_id = ?", userModel.AppPasture.Id).
 		Where("plan_id = ?", req.PlanId).
-		Where("ear_number IN (?)", strings.Join(req.EarNumbers, ",")).
+		Where("ear_number IN (?)", req.EarNumbers).
 		Find(&eventImmunizationList).Error; err != nil {
 		return xerr.WithStack(err)
 	}

+ 42 - 35
module/backend/event_health.go

@@ -29,8 +29,7 @@ func (s *StoreEntry) CowDiseaseList(ctx context.Context, req *pasturePb.SearchEv
 		Joins(fmt.Sprintf("JOIN %s AS b on a.cow_id = b.id", new(model.Cow).TableName())).
 		Select("a.*,b.pen_name").
 		Where("a.pasture_id = ?", userModel.AppPasture.Id).
-		Where("a.health_status != ?", pasturePb.HealthStatus_Curable).
-		Where("b.admission_status != ?", pasturePb.AdmissionStatus_Admission)
+		Where("b.admission_status = ?", pasturePb.AdmissionStatus_Admission)
 
 	if len(req.CowIds) > 0 {
 		pref.Where("a.cow_id IN ?", req.CowIds)
@@ -56,8 +55,9 @@ func (s *StoreEntry) CowDiseaseList(ctx context.Context, req *pasturePb.SearchEv
 		pref.Where("a.disease_at BETWEEN ? AND ?", req.DiseasedStartAt, req.DiseaseEndAt)
 	}
 
-	if err = pref.Order("a.id DESC").
-		Count(&count).Limit(int(pagination.PageSize)).
+	if err = pref.Order("b.health_status").
+		Count(&count).
+		Limit(int(pagination.PageSize)).
 		Offset(int(pagination.PageOffset)).
 		Find(&cowDiseaseList).Error; err != nil {
 		return nil, xerr.WithStack(err)
@@ -100,37 +100,21 @@ func (s *StoreEntry) CowDiseaseCreate(ctx context.Context, req *pasturePb.EventC
 		return xerr.WithStack(err)
 	}
 
-	// 牛只疾病信息
-	newEventCowDisease := model.NewEventCowDisease(pastureId, cow, disease, req, operationUser, userModel.SystemUser)
+	var alreadyCount int64
+	if err = s.DB.Model(new(model.EventCowDisease)).
+		Where("cow_id = ?", cow.Id).
+		Where("disease_id = ?", disease.Id).
+		Where(s.DB.Where("health_status = ?", pasturePb.HealthStatus_Disease).Or("health_status = ?", pasturePb.HealthStatus_Treatment)).
+		Count(&alreadyCount).Error; err != nil {
+		return xerr.Customf("该牛只已存在该疾病")
+	}
 
-	defer func() {
-		// 更新牛只健康状态
-		if newEventCowDisease.HealthStatus == pasturePb.HealthStatus_Disease || newEventCowDisease.HealthStatus == pasturePb.HealthStatus_Treatment {
-			cow.EventHealthStatusUpdate(pasturePb.HealthStatus_Disease)
-			if err = s.DB.Model(new(model.Cow)).
-				Select("health_status").
-				Where("id = ?", req.CowId).
-				Updates(cow).Error; err != nil {
-				zaplog.Error("CowDiseaseCreate", zap.Any("EventHealthStatusUpdate", err))
-			}
-		}
+	if alreadyCount > 0 {
+		return xerr.Customf("该牛只已存在该疾病")
+	}
 
-		// 更新栏舍信息
-		if req.PenId > 0 {
-			penMap := s.PenMap(ctx, userModel.AppPasture.Id)
-			penData, ok := penMap[req.PenId]
-			if !ok {
-				return
-			}
-			cow.EventPenUpdate(penData)
-			if err = s.DB.Model(new(model.Cow)).
-				Select("pen_id", "pen_name").
-				Where("id = ?", cow.Id).
-				Updates(cow).Error; err != nil {
-				zaplog.Error("CowDiseaseCreate", zap.Any("EventPenUpdate", err))
-			}
-		}
-	}()
+	// 牛只疾病信息
+	newEventCowDisease := model.NewEventCowDisease(pastureId, cow, disease, req, operationUser, userModel.SystemUser)
 	// PC端h和脖环揭发直接跳过诊断过程
 	if source == model.SourcePC || req.ExposeDiseaseType == pasturePb.ExposeDiseaseType_Neck_Ring {
 		newEventCowDisease.DiagnosedResult = pasturePb.IsShow_Ok
@@ -154,7 +138,7 @@ func (s *StoreEntry) CowDiseaseCreate(ctx context.Context, req *pasturePb.EventC
 	prescription := &model.Prescription{}
 	prescriptionDetail := make([]*pasturePb.PrescriptionDrugsList, 0)
 	// 获取处方信息
-	if req.PrescriptionId > 0 && len(req.PrescriptionDetail) <= 0 {
+	if req.PrescriptionId > 0 {
 		prescription, err = s.GetPrescriptionById(ctx, pastureId, req.PrescriptionId)
 		if err != nil {
 			return xerr.WithStack(err)
@@ -238,6 +222,30 @@ func (s *StoreEntry) CowDiseaseCreate(ctx context.Context, req *pasturePb.EventC
 				return xerr.WithStack(err)
 			}
 		}
+		// 更新牛只健康状态
+		if newEventCowDisease.HealthStatus == pasturePb.HealthStatus_Disease || newEventCowDisease.HealthStatus == pasturePb.HealthStatus_Treatment {
+			cow.EventHealthStatusUpdate(newEventCowDisease.HealthStatus)
+			if err = s.DB.Model(new(model.Cow)).
+				Select("health_status").
+				Where("id = ?", req.CowId).
+				Updates(cow).Error; err != nil {
+				zaplog.Error("CowDiseaseCreate", zap.Any("EventHealthStatusUpdate", err))
+			}
+		}
+
+		// 更新栏舍信息
+		if req.PenId > 0 {
+			penMap := s.PenMap(ctx, userModel.AppPasture.Id)
+			if penData, ok := penMap[req.PenId]; ok {
+				cow.EventPenUpdate(penData)
+				if err = s.DB.Model(new(model.Cow)).
+					Select("pen_id", "pen_name").
+					Where("id = ?", cow.Id).
+					Updates(cow).Error; err != nil {
+					zaplog.Error("CowDiseaseCreate", zap.Any("EventPenUpdate", err))
+				}
+			}
+		}
 
 		// 4. 如果是脖环揭发
 		if req.ExposeDiseaseType == pasturePb.ExposeDiseaseType_Neck_Ring {
@@ -340,7 +348,6 @@ func (s *StoreEntry) CowDiseaseTreatment(ctx context.Context, req *pasturePb.Cow
 	}
 
 	pastureId := userModel.AppPasture.Id
-
 	cow, err := s.GetCowInfoByCowId(ctx, pastureId, int64(req.CowId))
 	if err != nil {
 		return xerr.WithStack(err)

+ 23 - 25
module/backend/event_health_more.go

@@ -35,8 +35,15 @@ func (s *StoreEntry) CowDiseaseTreatmentDetail(ctx context.Context, req *pasture
 		pref.Where("disease_id = ?", req.DiseaseId)
 	}
 
-	if req.DiseaseStartAt > 0 && req.DiseaseEndAt > 0 && req.DiseaseStartAt <= req.DiseaseEndAt {
-		pref.Where("disease_at BETWEEN ? AND ?", req.DiseaseStartAt, req.DiseaseEndAt)
+	result := &pasturePb.EventCowTreatmentDetailResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &pasturePb.EventCowTreatmentDetail{
+			List:     make([]*pasturePb.EventCowTreatment, 0),
+			Total:    0,
+			PageSize: pagination.PageSize,
+			Page:     pagination.Page,
+		},
 	}
 
 	if err = pref.Count(&count).
@@ -45,40 +52,32 @@ func (s *StoreEntry) CowDiseaseTreatmentDetail(ctx context.Context, req *pasture
 		Order("id desc").
 		First(&eventCowDisease).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
-			return &pasturePb.EventCowTreatmentDetailResponse{
-				Code: http.StatusOK,
-				Msg:  "ok",
-				Data: &pasturePb.EventCowTreatmentDetail{
-					List:     make([]*pasturePb.EventCowTreatment, 0),
-					Total:    0,
-					PageSize: pagination.PageSize,
-					Page:     pagination.Page,
-				},
-			}, nil
+			return result, nil
 		} else {
 			return nil, xerr.WithStack(err)
 		}
 	}
 
 	eventCowTreatmentList := make([]*model.EventCowTreatment, 0)
-	if err = s.DB.Model(new(model.EventCowTreatment)).
+	pref2 := s.DB.Model(new(model.EventCowTreatment)).
 		Where("cow_disease_id = ?", req.Id).
-		Where("cow_id = ?", req.CowId).
+		Where("cow_id = ?", req.CowId)
+
+	if req.TreatmentStartAt > 0 && req.TreatmentEndAt > 0 && req.TreatmentStartAt <= req.TreatmentEndAt {
+		pref2.Where("treatment_at BETWEEN ? AND ?", req.TreatmentStartAt, req.TreatmentEndAt+86400)
+	}
+
+	if err = pref2.
 		Order("id desc").
 		Find(&eventCowTreatmentList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
-	return &pasturePb.EventCowTreatmentDetailResponse{
-		Code: http.StatusOK,
-		Msg:  "ok",
-		Data: &pasturePb.EventCowTreatmentDetail{
-			List:     model.EventCowTreatmentSlice(eventCowTreatmentList).ToPB(eventCowDisease),
-			Total:    int32(count),
-			PageSize: pagination.Page,
-			Page:     pagination.PageSize,
-		},
-	}, nil
+	result.Data.List = model.EventCowTreatmentSlice(eventCowTreatmentList).ToPB(eventCowDisease)
+	result.Data.Total = int32(count)
+	result.Data.PageSize = pagination.Page
+	result.Data.Page = pagination.PageSize
+	return result, nil
 }
 
 func (s *StoreEntry) DiseaseSuggestPrescription(ctx context.Context, diseaseId int64) (*pasturePb.ConfigOptionsListResponse, error) {
@@ -219,7 +218,6 @@ func (s *StoreEntry) NeckRingUpdateHealth(ctx context.Context, pastureId, cowId
 	neckRingHealth := &model.NeckRingHealth{}
 	if err := s.DB.Model(new(model.NeckRingHealth)).
 		Where("pasture_id = ?", pastureId).
-		Where("is_show = ?", pasturePb.IsShow_Ok).
 		Where("id = ?", neckRingHealthWarning.NeckRingHealthId).
 		First(neckRingHealth).Error; err != nil {
 		return xerr.WithStack(err)

+ 75 - 0
module/backend/feeding.go

@@ -0,0 +1,75 @@
+package backend
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"kpt-pasture/service/httpclient"
+
+	feedingPb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+	"gitee.com/xuyiping_admin/pkg/xerr"
+)
+
+var FeedingHeaders = []*httpclient.Header{
+	{
+		Key:   "Content-Type",
+		Value: "application/json",
+	}, {
+		Key:   "Accept",
+		Value: "application/json",
+	},
+}
+
+func (s *StoreEntry) GetFeedingHomepage(ctx context.Context, req *feedingPb.FeedingHomepageRequest) (*feedingPb.FeedingHomepageResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	pasture := userModel.AppPasture
+	if pasture.FeedPastureId == 0 {
+		return nil, xerr.Customf("饲喂数据未配置")
+	}
+
+	url := fmt.Sprintf("%d/feeding/tmrdata", pasture.FeedPastureId)
+	res := &feedingPb.FeedingHomepageResponse{
+		Code: 0,
+		Msg:  "",
+		Data: make([]*feedingPb.FeedingHomepageData, 0),
+	}
+	result, err := s.HttpClient.DoGet(url, FeedingHeaders)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	if err = json.Unmarshal(result, res); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return res, nil
+}
+
+func (s *StoreEntry) GetFeedingManagement(ctx context.Context, req *feedingPb.FeedingManagementRequest) (*feedingPb.FeedingManagementResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	pasture := userModel.AppPasture
+	if pasture.FeedPastureId == 0 {
+		return nil, xerr.Customf("饲喂数据未配置")
+	}
+
+	url := fmt.Sprintf("%d/feeding/management?typea=%s&startdate=%s&enddate=%s", pasture.FeedPastureId, req.Typea, req.Startdate, req.Enddate)
+	res := &feedingPb.FeedingManagementResponse{
+		Code: 0,
+		Msg:  "",
+		Data: []*feedingPb.FeedingManagementData{},
+	}
+	result, err := s.HttpClient.DoGet(url, FeedingHeaders)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	if err = json.Unmarshal(result, res); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return res, nil
+}

+ 10 - 4
module/backend/goods.go

@@ -165,6 +165,7 @@ func (s *StoreEntry) NeckRingCreateOrUpdate(ctx context.Context, req *pasturePb.
 				}
 				number = item.Number
 				newNeckRingLog.OperationName = model.OperationNameBind
+				newNeckRingLog.Remarks = model.DefaultBind
 				// 解绑
 			case pasturePb.NeckRingOperationStatus_UnBind:
 				if ok && neckRing.IsBind != pasturePb.NeckRingIsBind_Bind {
@@ -181,6 +182,7 @@ func (s *StoreEntry) NeckRingCreateOrUpdate(ctx context.Context, req *pasturePb.
 					return xerr.WithStack(err)
 				}
 				newNeckRingLog.OperationName = model.OperationNameUnbind
+				newNeckRingLog.Remarks = model.DefaultUnBind
 				// 编辑
 			case pasturePb.NeckRingOperationStatus_Edit:
 				if cowInfo.NeckRingNumber != "" && item.Number != cowInfo.NeckRingNumber {
@@ -404,13 +406,14 @@ func (s *StoreEntry) OutboundList(ctx context.Context, req *pasturePb.SearchOutb
 	var count int64 = 0
 	outboundList := make([]*model.Outbound, 0)
 	pref := s.DB.Model(new(model.Outbound)).
-		Where("pasture_id = ?", userModel.AppPasture.Id)
+		Where("pasture_id = ?", userModel.AppPasture.Id).
+		Where("audit_status < ?", pasturePb.AuditStatus_Delete)
 	if req.OutType > 0 {
 		pref.Where("out_type = ?", req.OutType)
 	}
 
 	if req.StartDayTime > 0 && req.EndDayTime > 0 && req.EndDayTime >= req.StartDayTime {
-		pref.Where("applicant_at BETWEEN ? AND ?", req.StartDayTime, req.EndDayTime)
+		pref.Where("applicant_at BETWEEN ? AND ?", req.StartDayTime, req.EndDayTime+86400)
 	}
 
 	if req.Number != "" {
@@ -551,6 +554,8 @@ func (s *StoreEntry) OutboundDetail(ctx context.Context, id int64) (*pasturePb.O
 	if outbound.ExamineAt > 0 {
 		examineAtFormat = time.Unix(outbound.ExamineAt, 0).Local().Format(model.LayoutTime)
 	}
+
+	unitMap := s.UnitMap()
 	return &pasturePb.OutboundDetailResponse{
 		Code: http.StatusOK,
 		Msg:  "ok",
@@ -569,7 +574,7 @@ func (s *StoreEntry) OutboundDetail(ctx context.Context, id int64) (*pasturePb.O
 			ExamineAtFormat:   examineAtFormat,
 			GoodsItem: &pasturePb.OutboundApplyItem{
 				OutType:          outbound.OutType,
-				Goods:            model.OutboundDetailSlice(outboundLogs).ToPB(),
+				Goods:            model.OutboundDetailSlice(outboundLogs).ToPB(unitMap),
 				ApplicantRemarks: outbound.ApplicantRemarks,
 			},
 		},
@@ -592,7 +597,7 @@ func (s *StoreEntry) OutboundDelete(ctx context.Context, id int64) error {
 	}
 
 	if !(outbound.AuditStatus == pasturePb.AuditStatus_Pending || outbound.AuditStatus == pasturePb.AuditStatus_Cancel) {
-		return xerr.Custom("出库单无法删除")
+		return xerr.Custom("出库单无法删除")
 	}
 
 	if userModel.SystemUser.Id != int64(outbound.ApplicantId) {
@@ -602,6 +607,7 @@ func (s *StoreEntry) OutboundDelete(ctx context.Context, id int64) error {
 	outbound.Delete()
 	if err = s.DB.Model(new(model.Outbound)).
 		Select("audit_status").
+		Where("id = ?", outbound.Id).
 		Updates(outbound).Error; err != nil {
 		return xerr.WithStack(err)
 	}

+ 142 - 0
module/backend/indicators.go

@@ -2,6 +2,7 @@ package backend
 
 import (
 	"context"
+	"fmt"
 	"kpt-pasture/model"
 	"kpt-pasture/util"
 	"net/http"
@@ -135,3 +136,144 @@ func (s *StoreEntry) LongTermInfertility(ctx context.Context, req *pasturePb.Lon
 	}, nil
 
 }
+
+func (s *StoreEntry) AlreadySale(ctx context.Context, req *pasturePb.AlreadySalesReportRequest, pagination *pasturePb.PaginationModel) (*pasturePb.AlreadySalesReportResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	pastureId := userModel.AppPasture.Id
+	eventSaleList := make([]*model.EventSale, 0)
+	pref := s.DB.Table(fmt.Sprintf("%s AS a", new(model.EventSale).TableName())).
+		Select("a.id,a.dealer_name,a.sale_kind,a.sale_price,a.sale_all_weight,a.sale_all_amount,sale_cow_count,a.sale_at,a.remarks").
+		Joins(fmt.Sprintf("JOIN %s AS b on b.sale_id = a.id", new(model.EventSaleCow).TableName())).
+		Where("a.pasture_id = ?", pastureId).
+		Where("a.sale_at BETWEEN ? AND ?", req.StartAt, req.EndAt)
+
+	if req.BatchNumber != "" {
+		pref.Where("b.batch_number = ?", req.BatchNumber)
+	}
+
+	if len(req.PenId) > 0 {
+		pref.Where("b.pen_id IN ?", req.PenId)
+	}
+
+	if req.CowKind > pasturePb.CowKind_Invalid {
+		pref.Where("b.cow_kind = ?", req.CowKind)
+	}
+
+	if err = pref.
+		Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Group("a.id").
+		Find(&eventSaleList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	result := &pasturePb.AlreadySalesReportResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &pasturePb.AlreadySalesReportData{
+			List:     make([]*pasturePb.AlreadySalesReport, 0),
+			Total:    0,
+			PageSize: pagination.PageSize,
+			Page:     pagination.Page,
+		},
+	}
+
+	if len(eventSaleList) <= 0 {
+		return result, nil
+	}
+
+	saleIds := make([]int64, 0)
+	eventSaleMap := make(map[int64]*model.EventSale)
+	for _, v := range eventSaleList {
+		saleIds = append(saleIds, v.Id)
+		eventSaleMap[v.Id] = v
+	}
+
+	eventSaleCowList := make([]*model.EventSaleCow, 0)
+	if err = s.DB.Model(new(model.EventSaleCow)).
+		Where("sale_id IN ?", saleIds).
+		Where("pasture_id = ?", pastureId).
+		Find(&eventSaleCowList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	cowIds := make([]int64, 0)
+	for _, v := range eventSaleCowList {
+		cowIds = append(cowIds, v.CowId)
+	}
+
+	cowList := make([]*model.Cow, 0)
+	if err = s.DB.Model(new(model.Cow)).
+		Where("id IN ?", cowIds).
+		Where("pasture_id = ?", pastureId).
+		Find(&cowList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	cowMap := make(map[int64]*model.Cow)
+	for _, v := range cowList {
+		cowMap[v.Id] = v
+	}
+
+	sourceMap := s.CowSourceMap()
+	cowKindMap := s.CowKindMap()
+	result.Data.Total = int32(len(eventSaleCowList))
+	result.Data.List = model.EventSaleCowSlice(eventSaleCowList).ToPB(eventSaleMap, cowKindMap, cowMap, sourceMap)
+	return result, nil
+}
+
+func (s *StoreEntry) CanSale(ctx context.Context, req *pasturePb.CanSalesReportRequest, pagination *pasturePb.PaginationModel) (*pasturePb.CanSalesReportResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	pastureId := userModel.AppPasture.Id
+	cowList := make([]*model.Cow, 0)
+	var count int64
+	pref := s.DB.Model(new(model.Cow)).
+		Where("pasture_id = ?", pastureId).
+		Where("current_weight BETWEEN ? AND ?", req.WeightStart*1000, req.WeightEnd*1000)
+
+	if req.BatchNumber != "" {
+		pref.Where("batch_number = ?", req.BatchNumber)
+	}
+
+	if req.CowKind > pasturePb.CowKind_Invalid {
+		pref.Where("cow_kind = ?", req.CowKind)
+	}
+
+	if len(req.PenId) > 0 {
+		pref.Where("pen_id IN ?", req.PenId)
+	}
+
+	if err = pref.Count(&count).
+		Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Find(&cowList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	result := &pasturePb.CanSalesReportResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &pasturePb.CanSalesReportData{
+			List:     make([]*pasturePb.CanSalesReport, 0),
+			Total:    0,
+			PageSize: pagination.PageSize,
+			Page:     pagination.Page,
+		},
+	}
+
+	if len(cowList) <= 0 {
+		return result, nil
+	}
+	cowKindMap := s.CowKindMap()
+	result.Data.Total = int32(count)
+	result.Data.List = model.CowSlice(cowList).CanSaleToPB(cowKindMap)
+	return result, nil
+}

+ 20 - 7
module/backend/interface.go

@@ -5,6 +5,7 @@ import (
 	"kpt-pasture/config"
 	"kpt-pasture/model"
 	"kpt-pasture/service/asynqsvc"
+	"kpt-pasture/service/httpclient"
 	"kpt-pasture/service/wechat"
 	"kpt-pasture/store/kptstore"
 	"mime/multipart"
@@ -24,10 +25,11 @@ type Hub struct {
 type StoreEntry struct {
 	dig.In
 
-	Cfg         *config.AppConfig
-	DB          *kptstore.DB
-	HttpClient  *wechat.ClientService
-	AsynqClient asynqsvc.Client
+	Cfg          *config.AppConfig
+	DB           *kptstore.DB
+	WeChatClient *wechat.ClientService
+	AsynqClient  asynqsvc.Client
+	HttpClient   *httpclient.Service
 }
 
 func NewStore(store StoreEntry) KptService {
@@ -36,9 +38,11 @@ func NewStore(store StoreEntry) KptService {
 
 func NewStoreEntry(cfg *config.AppConfig, Db *kptstore.DB) *StoreEntry {
 	return &StoreEntry{
-		Cfg:        cfg,
-		DB:         Db,
-		HttpClient: nil,
+		Cfg:          cfg,
+		DB:           Db,
+		WeChatClient: nil,
+		AsynqClient:  nil,
+		HttpClient:   nil,
 	}
 }
 
@@ -56,6 +60,7 @@ type KptService interface {
 	MilkHallService      // 奶厅数据相关
 	UploadService        // 上传文件相关
 	WarningService       // 预警相关
+	FeedingService       // 饲喂相关
 	TestService          // 测试相关
 }
 
@@ -258,6 +263,8 @@ type CowService interface {
 
 	IndicatorsComparison(ctx context.Context, req *pasturePb.IndicatorsComparisonRequest) (*model.IndicatorsComparisonResponse, error)
 	LongTermInfertility(ctx context.Context, req *pasturePb.LongTermInfertilityRequest, pagination *pasturePb.PaginationModel) (*pasturePb.LongTermInfertilityResponse, error)
+	AlreadySale(ctx context.Context, req *pasturePb.AlreadySalesReportRequest, pagination *pasturePb.PaginationModel) (*pasturePb.AlreadySalesReportResponse, error)
+	CanSale(ctx context.Context, req *pasturePb.CanSalesReportRequest, pagination *pasturePb.PaginationModel) (*pasturePb.CanSalesReportResponse, error)
 }
 
 //go:generate mockgen -destination mock/GoodsService.go -package kptservicemock kpt-pasture/module/backend GoodsService
@@ -313,6 +320,7 @@ type DashboardService interface {
 	DataWarningPop(ctx context.Context, req *pasturePb.WarningDataListRequest, pagination *pasturePb.PaginationModel) (*model.WarningDataPopResponse, error)
 	CalendarToDoCount(ctx context.Context) (*pasturePb.TodoCountResponse, error)
 	Equipment(ctx context.Context) (*pasturePb.EquipmentResponse, error)
+	OutNumber(ctx context.Context) (*pasturePb.OutNumberResponse, error)
 }
 
 //go:generate mockgen -destination mock/WorkService.go -package kptservicemock kpt-pasture/module/backend WorkService
@@ -359,6 +367,11 @@ type UploadService interface {
 	ImportExcel2(ctx context.Context, data [][]string, excelHeader []string) error
 }
 
+type FeedingService interface {
+	GetFeedingHomepage(ctx context.Context, req *pasturePb.FeedingHomepageRequest) (*pasturePb.FeedingHomepageResponse, error)
+	GetFeedingManagement(ctx context.Context, req *pasturePb.FeedingManagementRequest) (*pasturePb.FeedingManagementResponse, error)
+}
+
 type TestService interface {
 	CowNeckRingNumberBound(ctx context.Context, pagination *pasturePb.PaginationModel) error
 	CowNeckRingNumberBound2(ctx context.Context, pagination *pasturePb.PaginationModel) error

+ 37 - 13
module/backend/neck_ring_warning.go

@@ -6,6 +6,7 @@ import (
 	"kpt-pasture/model"
 	"kpt-pasture/util"
 	"net/http"
+	"sort"
 	"time"
 
 	"go.uber.org/zap"
@@ -22,7 +23,7 @@ func (s *StoreEntry) NeckRingWarningEstrusOrAbortionCowList(ctx context.Context,
 		return nil, xerr.WithStack(err)
 	}
 
-	var count int64
+	var count int32
 	neckRingEstrusList := make([]*model.NeckRingEstrusWarning, 0)
 	var pref *gorm.DB
 	switch req.Kind {
@@ -50,13 +51,17 @@ func (s *StoreEntry) NeckRingWarningEstrusOrAbortionCowList(ctx context.Context,
 		pref.Where("b.pen_id IN ?", req.PenIds)
 	}
 
-	if err = pref.Group("a.cow_id").
-		Order("a.level DESC").
-		Count(&count).
-		Limit(int(pagination.PageSize)).
-		Offset(int(pagination.PageOffset)).
-		Find(&neckRingEstrusList).Error; err != nil {
-		return nil, xerr.WithStack(err)
+	// 按照发情时间和发情等级倒叙排序
+	if req.MatingWindowPeriod > 0 {
+		if err = pref.Group("a.cow_id").
+			Find(&neckRingEstrusList).Error; err != nil {
+			return nil, xerr.WithStack(err)
+		}
+	} else {
+		if err = pref.Group("a.cow_id").
+			Find(&neckRingEstrusList).Error; err != nil {
+			return nil, xerr.WithStack(err)
+		}
 	}
 
 	cowMap := make(map[int64]*model.Cow)
@@ -71,21 +76,39 @@ func (s *StoreEntry) NeckRingWarningEstrusOrAbortionCowList(ctx context.Context,
 		for _, log := range lastEventLogList {
 			eventLogMap[log.CowId] = log.EventDescription
 		}
-	}
 
-	if len(cowIds) > 0 {
 		cowList, _ := s.GetCowInfoByCowIds(ctx, userModel.AppPasture.Id, cowIds)
 		for _, cow := range cowList {
 			cowMap[cow.Id] = cow
 		}
 	}
+	list := model.NeckRingEstrusWarningSlice(neckRingEstrusList).ToPB(cowMap, eventLogMap, req.MatingWindowPeriod)
+	// 排序 先按照高峰时间正序排,然后再按照发情等级倒叙排
+	sort.Slice(list, func(i, j int) bool {
+		if list[i].PostPeakTimeForHours != list[j].PostPeakTimeForHours {
+			return list[i].PostPeakTimeForHours < list[j].PostPeakTimeForHours
+		}
+		return list[i].Level > list[j].Level
+	})
+	// 分页
+	count = int32(len(list))
+	if count > pagination.PageOffset {
+		end := pagination.PageOffset + pagination.PageSize
+		if end > count {
+			end = count
+		}
+		list = list[pagination.PageOffset:end]
+	} else {
+		// 如果偏移量已超过列表长度,返回空列表
+		list = list[:0]
+	}
 
 	return &pasturePb.EstrusResponse{
 		Code: http.StatusOK,
 		Msg:  "ok",
 		Data: &pasturePb.EstrusData{
-			List:     model.NeckRingEstrusWarningSlice(neckRingEstrusList).ToPB(cowMap, eventLogMap, req.MatingWindowPeriod),
-			Total:    int32(count),
+			List:     list,
+			Total:    count,
 			PageSize: pagination.PageSize,
 			Page:     pagination.Page,
 		},
@@ -129,6 +152,7 @@ func (s *StoreEntry) NeckRingWarningHealthCowList(ctx context.Context, req *past
 	}
 
 	warningHealthLevelMap := s.WarningHealthLevelMap()
+	healthStatusMap := s.HealthStatusMap()
 	cowMap := make(map[int64]*model.Cow)
 	eventLogMap := make(map[int64]string)
 	cowIds := make([]int64, 0)
@@ -153,7 +177,7 @@ func (s *StoreEntry) NeckRingWarningHealthCowList(ctx context.Context, req *past
 			Total:    int32(count),
 			Page:     pagination.Page,
 			PageSize: pagination.PageSize,
-			List:     model.NeckRingHealthWarningSlice(neckWaringHealthList).ToPB(warningHealthLevelMap, cowMap, eventLogMap),
+			List:     model.NeckRingHealthWarningSlice(neckWaringHealthList).ToPB(warningHealthLevelMap, cowMap, eventLogMap, healthStatusMap),
 		},
 	}, nil
 

+ 1 - 1
module/crontab/health_waning.go

@@ -104,7 +104,7 @@ func (e *Entry) FindNewNeckRingHealthWarning(pastureId int64, healthValue int32)
 			zap.Any("healthValue", healthValue),
 			zap.Any("cowInfo", cowInfo),
 		)
-		if newScore > healthValue {
+		if newScore >= healthValue {
 			continue
 		}
 

+ 1 - 0
module/crontab/model.go

@@ -65,6 +65,7 @@ type FilterData struct {
 	ChangeFilter   int32
 	RuminaFilter   int32
 	ChewFilter     int32
+	ChangeHigh     int32
 }
 
 type SumHabit struct {

+ 96 - 48
module/crontab/neck_ring_calculate.go

@@ -20,7 +20,7 @@ func (e *Entry) NeckRingCalculate() error {
 		return nil
 	}
 	for _, pasture := range pastureList {
-		if err := e.EntryUpdateActiveHabit(pasture.Id); err != nil {
+		if err := e.EntryUpdateActiveHabit(pasture); err != nil {
 			zaplog.Error("NeckRingCalculate", zap.Any("err", err), zap.Any("pasture", pasture))
 		}
 		zaplog.Info(fmt.Sprintf("NeckRingCalculate Success %d", pasture.Id))
@@ -28,7 +28,8 @@ func (e *Entry) NeckRingCalculate() error {
 	return nil
 }
 
-func (e *Entry) EntryUpdateActiveHabit(pastureId int64) (err error) {
+func (e *Entry) EntryUpdateActiveHabit(appPasture *model.AppPastureList) (err error) {
+	pastureId := appPasture.Id
 	// 获取这段执行数据内最大日期和最小日期
 	xToday, err := e.XToday(pastureId)
 	if err != nil {
@@ -47,7 +48,6 @@ func (e *Entry) EntryUpdateActiveHabit(pastureId int64) (err error) {
 		zaplog.Error("NeckRingCalculate", zap.Any("pastureId", pastureId), zap.Any("FirstFilterUpdate", err), zap.Any("xToday", xToday))
 	}
 
-	zaplog.Info("NeckRingCalculate", zap.Any("pastureId", pastureId), zap.Any("xToday", xToday), zap.Any("processIds", processIds))
 	if len(processIds) <= 0 {
 		return nil
 	}
@@ -58,7 +58,7 @@ func (e *Entry) EntryUpdateActiveHabit(pastureId int64) (err error) {
 	e.SecondUpdateChangeFilter(pastureId, processIds, xToday)
 
 	// 活动量校正系数和健康评分
-	e.FilterCorrectAndScoreUpdate(pastureId, processIds, xToday)
+	e.FilterCorrectAndScoreUpdate(appPasture, processIds, xToday)
 
 	// 更新 ChangeFilter
 	e.UpdateChangeFilter(pastureId, processIds)
@@ -67,7 +67,7 @@ func (e *Entry) EntryUpdateActiveHabit(pastureId int64) (err error) {
 	e.UpdateFilterCorrect(pastureId, processIds)
 
 	// 插入群体校正表
-	e.UpdateChangeAdJust(pastureId, xToday)
+	e.UpdateChangeAdJust(pastureId, processIds)
 
 	// 更新 Cft
 	e.UpdateCft(pastureId, processIds)
@@ -171,7 +171,7 @@ func (e *Entry) FirstFilterUpdate(pastureId int64, xToDay *XToday) (processIds [
 		processIds = append(processIds, v.Id)
 		// 更新过滤值
 		if err = e.DB.Model(new(model.NeckActiveHabit)).
-			Select("filter_high", "filter_rumina", "filter_chew", "cow_id", "lact", "calving_age", "ear_number", "week_high").
+			Select("filter_high", "filter_rumina", "filter_chew", "cow_id", "lact", "calving_age", "ear_number", "pen_id", "week_high").
 			Where("id = ?", v.Id).
 			Updates(map[string]interface{}{
 				"filter_high":   firstFilterData.FilterHigh,
@@ -181,6 +181,7 @@ func (e *Entry) FirstFilterUpdate(pastureId int64, xToDay *XToday) (processIds [
 				"lact":          cowInfo.Lact,
 				"calving_age":   cowInfo.CalvingAge,
 				"ear_number":    cowInfo.EarNumber,
+				"pen_id":        cowInfo.PenId,
 				"week_high":     cowWeeklyActive,
 			}).Error; err != nil {
 			zaplog.Error("FirstFilterUpdate",
@@ -265,18 +266,6 @@ func (e *Entry) SecondUpdateChangeFilter(pastureId int64, processIds []int64, xT
 			chewFilter = 50
 		}
 
-		zaplog.Info("SecondUpdateChangeFilter",
-			zap.Any("NeckActiveHabit", v),
-			zap.Any("discount", discount),
-			zap.Any("xChangeDiscount", xChangeDiscount),
-			zap.Any("xRuminaDisc", xRuminaDisc),
-			zap.Any("chewFilterDiscount", chewFilterDiscount),
-			zap.Any("secondFilterData", secondFilterData),
-			zap.Any("changeFilter", changeFilter),
-			zap.Any("ruminaFilter", ruminaFilter),
-			zap.Any("chewFilter", chewFilter),
-		)
-
 		if err := e.DB.Model(new(model.NeckActiveHabit)).
 			Select("change_filter", "rumina_filter", "chew_filter").
 			Where("id = ?", v.Id).
@@ -291,7 +280,7 @@ func (e *Entry) SecondUpdateChangeFilter(pastureId int64, processIds []int64, xT
 }
 
 // FilterCorrectAndScoreUpdate 计算活动量变化趋势校正值(活跃度校正)和健康评分
-func (e *Entry) FilterCorrectAndScoreUpdate(pastureId int64, processIds []int64, xToday *XToday) {
+func (e *Entry) FilterCorrectAndScoreUpdate(appPasture *model.AppPastureList, processIds []int64, xToday *XToday) {
 	beginDayDate := time.Now().Local()
 	before7DayDate := beginDayDate.AddDate(0, 0, -7).Format(model.LayoutDate2)
 	before1DayDate := beginDayDate.AddDate(0, 0, -1).Format(model.LayoutDate2)
@@ -299,14 +288,14 @@ func (e *Entry) FilterCorrectAndScoreUpdate(pastureId int64, processIds []int64,
 	neckActiveHabitList := make([]*model.NeckActiveHabit, 0)
 	if err := e.DB.Model(new(model.NeckActiveHabit)).
 		Where("id IN (?)", processIds).
-		Where("pasture_id = ?", pastureId).
+		Where("pasture_id = ?", appPasture.Id).
 		Find(&neckActiveHabitList).Error; err != nil {
 		zaplog.Error("ActivityVolumeChanges-1", zap.Any("error", err), zap.Any("xToday", xToday))
 		return
 	}
 
 	for _, v := range neckActiveHabitList {
-		cowScore := calculateScore(v)
+		cowScore := CalculateScore(appPasture, v)
 		if err := e.DB.Model(new(model.NeckActiveHabit)).
 			Where("id = ?", v.Id).
 			Update("score", cowScore).Error; err != nil {
@@ -318,7 +307,7 @@ func (e *Entry) FilterCorrectAndScoreUpdate(pastureId int64, processIds []int64,
 			Select("neck_ring_number", "AVG(IF(change_filter>=60, 60, change_filter)) as avg_filter",
 				"ROUND(STD(IF(change_filter>=60, 60, change_filter))) as std_filter", "COUNT(1) as nb").
 			Where("heat_date BETWEEN ? AND ?", before7DayDate, before1DayDate).
-			Where("pasture_id = ?", pastureId).
+			Where("pasture_id = ?", appPasture.Id).
 			Where(e.DB.Where("high > ?", 12).Or("rumina >= ?", xToday.Rumina)).
 			Where("active_time <= ?", beginDayDate.Add(-12*time.Hour).Format(model.LayoutTime)).
 			Where("change_filter > ?", MinChangeFilter).
@@ -330,12 +319,10 @@ func (e *Entry) FilterCorrectAndScoreUpdate(pastureId int64, processIds []int64,
 		}
 
 		if activityVolume != nil && activityVolume.NeckRingNumber != "" {
-			//filterCorrect := model.DefaultFilterCorrect - int(math.Floor(activityVolume.AvgFilter/3+float64(activityVolume.StdFilter)/2))
 			filterCorrect := model.DefaultFilterCorrect - int(math.Round(activityVolume.AvgFilter/3+float64(int(math.Round(activityVolume.StdFilter))/2)))
 			// 活动量校正系数
 			if err := e.DB.Model(new(model.NeckActiveHabit)).
 				Where("id = ?", v.Id).
-				//Where("neck_ring_number = ?", v.NeckRingNumber).
 				Update("filter_correct", filterCorrect).Error; err != nil {
 				zaplog.Error("ActivityVolumeChanges-2", zap.Any("error", err), zap.Any("xToday", xToday))
 				continue
@@ -373,38 +360,99 @@ func (e *Entry) UpdateFilterCorrect(pastureId int64, processIds []int64) {
 }
 
 // UpdateChangeAdJust 更新群体校正数据
-func (e *Entry) UpdateChangeAdJust(pastureId int64, xToday *XToday) {
-	res := make([]*model.NeckRingBarChange, 0)
+func (e *Entry) UpdateChangeAdJust(pastureId int64, processIds []int64) {
+	neckRingPenChangeList := make([]*model.NeckRingPenChange, 0)
 	yesterday := time.Now().Local().AddDate(0, 0, -1).Format(model.LayoutDate2)
-	if err := e.DB.Table(fmt.Sprintf("%s as h", new(model.NeckActiveHabit).TableName())).
-		Select(`h.neck_ring_number,h.heat_date, h.frameid, c.pen_id, c.pen_name, COUNT(*) as nb,
-		ROUND(AVG(h.change_high)) as change_high, ROUND(AVG(h.change_filter)) as change_filter`).
-		Joins("JOIN cow as c ON h.cow_id = c.id").
-		Where("h.pasture_id = ?", pastureId).
-		Where("h.heat_date >= ?", yesterday).
-		Where("h.cow_id > ?", 0).
-		Where("c.pen_id > ?", 0).
-		Group("h.heat_date, h.frameid, c.pen_id").
-		Order("h.heat_date, h.frameid, c.pen_id").
-		Find(&res).Error; err != nil {
-		zaplog.Error("UpdateChangeAdJust", zap.Any("error", err), zap.Any("xToday", xToday))
-	}
-
-	for _, v := range res {
-		if math.Abs(float64(v.ChangeFilter)) < 10 {
-			continue
+	if err := e.DB.Model(new(model.NeckActiveHabit)).
+		Select(`heat_date,frameid,pen_id,COUNT(*) AS cow_count,ROUND(AVG(change_high)) AS change_high,ROUND(AVG(change_filter)) AS change_filter`).
+		Where("pasture_id = ?", pastureId).
+		Where("heat_date >= ?", yesterday).
+		Where("cow_id > ?", 0).
+		Where("pen_id > ?", 0).
+		Group("heat_date,frameid,pen_id").
+		Order("heat_date,frameid,pen_id").
+		Find(&neckRingPenChangeList).Error; err != nil {
+		zaplog.Error("UpdateChangeAdJust", zap.Any("error", err), zap.Any("pastureId", pastureId))
+	}
+
+	for _, v := range neckRingPenChangeList {
+		var count int64
+		if err := e.DB.Model(new(model.NeckRingPenChange)).
+			Where("pasture_id = ?", pastureId).
+			Where("heat_date = ?", v.HeatDate).
+			Where("frameid = ?", v.Frameid).
+			Where("pen_id = ?", v.PenId).
+			Count(&count).Error; err != nil {
+			zaplog.Error("UpdateChangeAdJust", zap.Any("error", err), zap.Any("v", v), zap.Any("pastureId", pastureId))
+		}
+		// 有就更新,没有就新增
+		if count > 0 {
+			if err := e.DB.Model(new(model.NeckRingPenChange)).
+				Where("pasture_id = ?", pastureId).
+				Where("heat_date = ?", v.HeatDate).
+				Where("frameid = ?", v.Frameid).
+				Where("pen_id = ?", v.PenId).
+				Updates(map[string]interface{}{
+					"cow_count":     v.CowCount,
+					"change_high":   v.ChangeHigh,
+					"change_filter": v.ChangeFilter,
+				}).Error; err != nil {
+				zaplog.Error("UpdateChangeAdJust", zap.Any("error", err), zap.Any("v", v), zap.Any("pastureId", pastureId))
+			}
+		} else {
+			neckRingPenChange := model.NewNeckRingPenChange(pastureId, v.HeatDate, v.CowCount, v.Frameid, v.PenId, v.ChangeHigh, v.ChangeFilter)
+			if err := e.DB.Model(new(model.NeckRingPenChange)).
+				Create(neckRingPenChange).Error; err != nil {
+				zaplog.Error("UpdateChangeAdJust",
+					zap.Any("error", err),
+					zap.Any("neckRingPenChange", neckRingPenChange),
+					zap.Any("pastureId", pastureId),
+				)
+			}
 		}
-		if err := e.DB.Model(new(model.NeckActiveHabit)).
+	}
+
+	neckActiveHabitList := make([]*model.NeckActiveHabit, 0)
+	if err := e.DB.Model(new(model.NeckActiveHabit)).
+		Where("id IN (?)", processIds).
+		Where("pasture_id = ?", pastureId).
+		Find(&neckActiveHabitList).Error; err != nil {
+		zaplog.Error("UpdateChangeAdJust", zap.Any("error", err))
+	}
+
+	if len(neckActiveHabitList) <= 0 {
+		return
+	}
+
+	for _, v := range neckActiveHabitList {
+		neckRingPenChange := &model.NeckRingPenChange{}
+		if err := e.DB.Model(new(model.NeckRingPenChange)).
 			Where("pasture_id = ?", pastureId).
-			Where("neck_ring_number = ?", v.NeckRingNumber).
 			Where("heat_date = ?", v.HeatDate).
-			Where("frameid = ?", v.FrameId).
-			Update("change_adjust", v.ChangeFilter).Error; err != nil {
-			zaplog.Error("UpdateChangeAdJust-1", zap.Any("error", err), zap.Any("xToday", xToday))
+			Where("frameid = ?", v.Frameid).
+			Where("pen_id = ?", v.PenId).
+			First(&neckRingPenChange).Error; err != nil {
+			zaplog.Error("UpdateChangeAdJust", zap.Any("error", err), zap.Any("v", v), zap.Any("pastureId", pastureId))
+			continue
+		}
+
+		if neckRingPenChange == nil || neckRingPenChange.Id <= 0 {
+			continue
+		}
+
+		if neckRingPenChange.ChangeFilter < 10 {
+			continue
+		}
+
+		if err := e.DB.Model(new(model.NeckActiveHabit)).
+			Where("id = ?", v.Id).
+			Update("change_adjust", neckRingPenChange.ChangeFilter).Error; err != nil {
+			zaplog.Error("UpdateChangeAdJust", zap.Any("error", err), zap.Any("v", v), zap.Any("neckRingPenChange", neckRingPenChange))
 		}
 	}
 }
 
+// UpdateCft 更新群体校正修正
 func (e *Entry) UpdateCft(pastureId int64, processIds []int64) {
 	neckActiveHabitList := make([]*model.NeckActiveHabit, 0)
 	if err := e.DB.Model(new(model.NeckActiveHabit)).

+ 20 - 17
module/crontab/neck_ring_estrus.go

@@ -95,7 +95,7 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.T
 	neckRingEstrusList := make([]*model.NeckRingEstrus, 0)
 	for cowId, cowHabitList := range neckActiveHabitMap {
 		// 最近3天最大发情记录,小于该变化趋势的不再插入
-		before3Data := e.GetBeforeThreeDaysCowEstrus(cowId, nowTime.AddDate(0, 0, -2).Format(model.LayoutTime))
+		before3Data := e.GetBeforeThreeDaysCowEstrus(pastureId, cowId, nowTime.AddDate(0, 0, -2).Format(model.LayoutTime))
 
 		// 判断最近50天内是否存在发情记录(发情等级>=2),如果18~25天@xadjust21,如果36~50天@xadjust42
 		cow21Estrus := e.GetTwoEstrus(pastureId, cowId, nowTime.AddDate(0, 0, -100).Format(model.LayoutTime), nowTime.AddDate(0, 0, -2).Format(model.LayoutTime))
@@ -146,7 +146,6 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.T
 			dayHigh := int32(maxCft) + cow21Estrus.HadJust
 			lastEstrusDate := cow21Estrus.ActiveDate
 			checkResult := getResult(before3Data, maxCft, cow21Estrus)
-			isPeak := pasturePb.IsShow_Ok
 			activeTime := lastActiveDate.Format(model.LayoutTime)
 
 			if e.HistoryNeckRingEstrus(pastureId, cowInfo.NeckRingNumber, activeTime) {
@@ -158,7 +157,6 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.T
 				zap.Any("b48", b48),
 				zap.Any("checkResult", checkResult),
 				zap.Any("isShow", isShow),
-				zap.Any("isPeak", isPeak),
 				zap.Any("lastEstrusDate", lastEstrusDate),
 				zap.Any("activeDate", lastActiveDate),
 				zap.Any("dayHigh", dayHigh),
@@ -174,7 +172,7 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.T
 			newNeckRingEstrus.ActiveTime = activeTime
 			newNeckRingEstrus.DayHigh = dayHigh
 			newNeckRingEstrus.MaxHigh = maxHigh
-			newNeckRingEstrus.IsPeak = isPeak
+			newNeckRingEstrus.IsPeak = pasturePb.IsShow_No
 			neckRingEstrusList = append(neckRingEstrusList, newNeckRingEstrus)
 		}
 	}
@@ -191,17 +189,20 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.T
 // UpdateNewNeckRingEstrus 更新牛只首次发情时间和是否是高峰期
 func (e *Entry) UpdateNewNeckRingEstrus(pastureId int64, xToday *XToday, nowTime time.Time) {
 	e.UpdateEstrusFirstTime1(pastureId)
-	e.UpdateEstrusIsPeak(pastureId)
-	e.UpdateEstrusFirstTime2(pastureId, xToday, nowTime)
+	e.UpdateEstrusFirstTime2(pastureId, xToday)
 	e.UpdateEstrusFirstTime3(pastureId, nowTime)
+	e.UpdateEstrusIsPeak(pastureId)
 }
 
 // UpdateEstrusFirstTime1 更新牛只首次发情时间
 func (e *Entry) UpdateEstrusFirstTime1(pastureId int64) {
 	// 获取牛只首次发情时间为空的记录
 	neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
+	zaplog.Info("UpdateEstrusFirstTime1", zap.Any("neckRingEstrusList", neckRingEstrusList))
+
 	for _, v := range neckRingEstrusList {
-		cowEstrusStartData := e.FindCowEstrusFirstTime1(pastureId, v.CowId)
+		cowEstrusStartData := e.FindCowEstrusFirstTime1(pastureId, v)
+		zaplog.Info("UpdateEstrusFirstTime1", zap.Any("cowEstrusStartData", cowEstrusStartData))
 		if cowEstrusStartData != nil && cowEstrusStartData.FirstTime != "" {
 			if err := e.DB.Model(new(model.NeckRingEstrus)).
 				Where("id = ?", v.Id).
@@ -216,20 +217,22 @@ func (e *Entry) UpdateEstrusFirstTime1(pastureId int64) {
 	}
 }
 
-func (e *Entry) UpdateEstrusFirstTime2(pastureId int64, xToday *XToday, nowTime time.Time) {
+func (e *Entry) UpdateEstrusFirstTime2(pastureId int64, xToday *XToday) {
 	neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
 	for _, v := range neckRingEstrusList {
+		if v.FirstTime != "" {
+			continue
+		}
+
 		// 获取牛只最近12小时内的活动记录
 		activeTime, _ := util.TimeParseLocal(model.LayoutTime, v.ActiveTime)
 		startTime := activeTime.Add(-12 * time.Hour)
-
 		// 查询符合条件的活动记录
 		var firstTime string
 		if err := e.DB.Model(new(model.NeckActiveHabit)).
 			Select("MIN(active_time) as first_time").
 			Where("pasture_id = ?", pastureId).
 			Where("cow_id = ?", v.CowId).
-			Where("heat_date = ?", activeTime.Format(model.LayoutDate2)).
 			Where("active_time BETWEEN ? AND ?", startTime.Format(model.LayoutTime), v.ActiveTime).
 			Where("cft >= ?", xToday.ActiveLow).
 			Scan(&firstTime).Error; err != nil {
@@ -336,17 +339,17 @@ func (e *Entry) UpdateEstrusIsPeak(pastureId int64) {
 }
 
 // FindCowEstrusFirstTime1 查找牛只昨天是否有发情数据
-func (e *Entry) FindCowEstrusFirstTime1(pastureId, cowId int64) *EstrusStartData {
+func (e *Entry) FindCowEstrusFirstTime1(pastureId int64, neckRingEstrus *model.NeckRingEstrus) *EstrusStartData {
 	firstTimeEventEstrus := &EstrusStartData{}
-	nowTime := time.Now().Local()
-	startDate := fmt.Sprintf("%s 00:00:00", nowTime.AddDate(0, 0, -1).Format(model.LayoutDate2))
-	endDate := fmt.Sprintf("%s 23:59:59", nowTime.Format(model.LayoutDate2))
+	activeAt := util.DateTimeParseLocalUnix2(neckRingEstrus.ActiveTime)
+	startDate := fmt.Sprintf("%s 00:00:00", activeAt.AddDate(0, 0, -1).Format(model.LayoutDate2))
 	if err := e.DB.Model(new(model.NeckRingEstrus)).
 		Select("cow_id,first_time").
-		Where("active_time BETWEEN ? AND ?", startDate, endDate).
+		Where("active_time BETWEEN ? AND ?", startDate, neckRingEstrus.ActiveTime).
 		Where("pasture_id = ?", pastureId).
-		Where("cow_id = ?", cowId).
-		Order("first_time ASC").
+		Where("cow_id = ?", neckRingEstrus.CowId).
+		Where("id != ?", neckRingEstrus.Id).
+		Order("first_time,id ASC").
 		First(&firstTimeEventEstrus).Error; err != nil {
 		return nil
 	}

+ 10 - 0
module/crontab/neck_ring_health.go

@@ -51,15 +51,25 @@ func (e *Entry) updateNeckRingHealth(pastureId int64, healthWarningList []*model
 		isMove := e.isEventCowLog(pastureId, v.CowId, startAt, endAt, pasturePb.EventType_Transfer_Ben)
 		if isMove {
 			v.IsTransferGroup = pasturePb.IsShow_Ok
+		} else {
+			v.IsTransferGroup = pasturePb.IsShow_No
 		}
+
 		isDryMilk := e.isEventCowLog(pastureId, v.CowId, startAt, endAt, pasturePb.EventType_Dry_Milk)
 		if isDryMilk {
 			v.IsDryMilk = pasturePb.IsShow_Ok
+		} else {
+			v.IsDryMilk = pasturePb.IsShow_No
 		}
 		isImmunization := e.isEventCowLog(pastureId, v.CowId, startAt, endAt, pasturePb.EventType_Immunication)
 		if isImmunization {
 			v.IsImmunization = pasturePb.IsShow_Ok
+		} else {
+			v.IsImmunization = pasturePb.IsShow_No
 		}
+		v.IsShow = pasturePb.IsShow_Ok
+		v.IsWorse = pasturePb.IsShow_Invalid
+		v.CheckResult = pasturePb.CheckResult_Pending
 	}
 	zaplog.Info("HealthWarning", zap.Any("healthWarningList", healthWarningList))
 	if err := e.DB.Model(new(model.NeckRingHealth)).

+ 65 - 9
module/crontab/neck_ring_merge.go

@@ -219,8 +219,8 @@ func computeIfPositiveElse(newValue, prevFilterValue float64, weightPrev, weight
 	return math.Ceil((prevFilterValue * weightPrev) + (weightNew * newValue))
 }
 
-// 计算 score 的逻辑
-func calculateScore(habit *model.NeckActiveHabit) int {
+// CalculateScore 计算 score 的逻辑
+func CalculateScore(appPasture *model.AppPastureList, habit *model.NeckActiveHabit) int {
 	// 第一部分逻辑
 	var part1 float64
 	switch {
@@ -231,13 +231,27 @@ func calculateScore(habit *model.NeckActiveHabit) int {
 	case habit.CalvingAge >= 2 && habit.CalvingAge <= 13:
 		part1 = math.Min((float64(habit.SumRumina+habit.SumIntake)-(100+math.Min(7, float64(habit.CalvingAge))*60))/10*2, 0)
 	case habit.ChangeFilter > -99:
-		part1 = math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter), 0), getValueOrDefault(float64(habit.SumMinHigh), 0)))*0.2 +
-			math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter), 0), getValueOrDefault(float64(habit.SumMinChew), 0)))*0.2 +
-			getRuminaSumIntakeSumScore(float64(habit.SumRumina+habit.SumIntake)) + getAdditionalScore(habit)
+		if appPasture.Category == pasturePb.PastureCategory_Beef {
+			part1 = getBeefSoreV1(habit) +
+				getBeefSportsRuminaScore(habit) +
+				getBeefRuminaSumIntakeSumScore(habit) +
+				getBeefAdditionalScore(habit)
+		} else {
+			part1 = getCowSoreV1(habit) +
+				getCowSportsRuminaScore(habit) +
+				getCowRuminaSumIntakeSumScore(habit) +
+				getCowAdditionalScore(habit)
+		}
 	default:
 		part1 = -299
 	}
 
+	fmt.Println("part1", part1)
+	fmt.Println("v1", getBeefSoreV1(habit))
+	fmt.Println("v2", getBeefSportsRuminaScore(habit))
+	fmt.Println("v3", getBeefRuminaSumIntakeSumScore(habit))
+	fmt.Println("v4", getBeefAdditionalScore(habit))
+
 	// 第二部分逻辑
 	var part2 float64
 	versionMod := habit.FirmwareVersion % 100
@@ -260,8 +274,28 @@ func getValueOrDefault(value, defaultValue float64) float64 {
 	return defaultValue
 }
 
-// 计算累计反刍得分
-func getRuminaSumIntakeSumScore(sum float64) float64 {
+func getCowSoreV1(habit *model.NeckActiveHabit) float64 {
+	return math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter), 0), getValueOrDefault(float64(habit.SumMinHigh), 0))) * 0.2
+}
+
+func getBeefSoreV1(habit *model.NeckActiveHabit) float64 {
+	return math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter)-math.Min(float64(habit.ChangeAdjust), 0), 0), getValueOrDefault(float64(habit.SumMinHigh), 0))) * 0.2
+}
+
+// 奶牛运动得反刍分
+// LEAST(0, IF(h.chew_filter>-99, h.chew_filter, 0), IF(h.sum_min_chew>-99, h.sum_min_chew, 0))*0.2
+func getCowSportsRuminaScore(habit *model.NeckActiveHabit) float64 {
+	return math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter), 0), getValueOrDefault(float64(habit.SumMinChew), 0))) * 0.2
+}
+
+// 肉牛运动得反刍分
+func getBeefSportsRuminaScore(habit *model.NeckActiveHabit) float64 {
+	return math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter), 0), getValueOrDefault(float64(habit.SumMinChew), 0))) * 0.2
+}
+
+// 奶牛累计反刍得分
+func getCowRuminaSumIntakeSumScore(habit *model.NeckActiveHabit) float64 {
+	sum := float64(habit.SumRumina + habit.SumIntake)
 	switch {
 	case sum < 80:
 		return -30
@@ -274,8 +308,14 @@ func getRuminaSumIntakeSumScore(sum float64) float64 {
 	}
 }
 
-// 计算额外得分
-func getAdditionalScore(habit *model.NeckActiveHabit) float64 {
+// 肉牛累计反刍得分
+func getBeefRuminaSumIntakeSumScore(habit *model.NeckActiveHabit) float64 {
+	sum := float64(habit.SumRumina + habit.SumIntake)
+	return math.Min(0, (sum-380)/10)
+}
+
+// 奶牛当前变化趋势是否是峰值
+func getCowAdditionalScore(habit *model.NeckActiveHabit) float64 {
 	var score float64
 	if (habit.SumRumina+habit.SumIntake < 280 || habit.SumMinHigh+habit.SumMinChew < -50) && habit.SumMaxHigh > 50 {
 		score += 10
@@ -285,3 +325,19 @@ func getAdditionalScore(habit *model.NeckActiveHabit) float64 {
 	}
 	return score
 }
+
+// 肉牛当前变化趋势是否是峰值
+// + IF(((h.sum_rumina + h.sum_intake )<280 OR (h.sum_min_high +h.sum_min_chew)<-50) AND h.sum_max_high>50, 10, 0)
+// + IF((h.change_filter -LEAST(h.change_adjust,0))<-30 AND (h.change_filter -LEAST(h.change_adjust,0))<=h.sum_min_high AND h.chew_filter<-30 AND h.chew_filter<=h.sum_min_chew, -5, 0)
+func getBeefAdditionalScore(habit *model.NeckActiveHabit) float64 {
+	var score float64
+	if (habit.SumRumina+habit.SumIntake < 280 || habit.SumMinHigh+habit.SumMinChew < -50) && habit.SumMaxHigh > 50 {
+		score += 10
+	}
+	if float64(habit.ChangeFilter)-math.Min(float64(habit.ChangeAdjust), 0) < -30 &&
+		float64(habit.ChangeFilter)-math.Min(float64(habit.ChangeAdjust), 0) <= float64(habit.SumMinHigh) &&
+		habit.ChewFilter < -30 && habit.ChewFilter <= habit.SumMinChew {
+		score -= 5
+	}
+	return score
+}

+ 71 - 0
module/crontab/neck_ring_merge_test.go

@@ -0,0 +1,71 @@
+package crontab
+
+import (
+	"fmt"
+	"kpt-pasture/model"
+	"testing"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+)
+
+func TestEntry_CalculateScore(t *testing.T) {
+	app := &model.AppPastureList{
+		Id:       4,
+		Category: pasturePb.PastureCategory_Beef,
+	}
+
+	habit := &model.NeckActiveHabit{
+		Id:                   5287253,
+		PastureId:            4,
+		NeckRingNumber:       "324",
+		CowId:                11246,
+		EarNumber:            "210868",
+		Lact:                 3,
+		CalvingAge:           312,
+		PenId:                16,
+		ActiveTime:           "2025-07-09",
+		Frameid:              7,
+		HeatDate:             "2025-07-09 11:00:00",
+		Rumina:               9,
+		Intake:               21,
+		Inactive:             74,
+		Gasp:                 0,
+		Other:                0,
+		High:                 642,
+		Active:               36,
+		FilterHigh:           974,
+		FilterRumina:         10,
+		FilterChew:           29,
+		WeekHigh:             1112,
+		HighHabit:            1755,
+		RuminaHabit:          23,
+		IntakeHabit:          38,
+		ChewHabit:            49,
+		InactiveHabit:        36,
+		OtherHabit:           0,
+		ChangeHigh:           -45,
+		ChangeRumina:         -57,
+		ChangeChew:           -41,
+		ChangeAdjust:         0,
+		ChangeFilter:         53,
+		RuminaFilter:         -57,
+		ChewFilter:           -39,
+		FilterCorrect:        95,
+		SumRumina:            129,
+		SumIntake:            106,
+		SumInactive:          456,
+		SumActive:            668,
+		SumMinHigh:           -55,
+		SumMaxHigh:           96,
+		SumMinChew:           -68,
+		BeforeThreeSumRumina: 508,
+		BeforeThreeSumIntake: 159,
+		Score:                0,
+		IsShow:               1,
+		Cft:                  60.35,
+		Voltage:              301,
+		RecordCount:          6,
+		FirmwareVersion:      57,
+	}
+	fmt.Println(CalculateScore(app, habit))
+}

+ 5 - 2
module/crontab/sql.go

@@ -101,11 +101,12 @@ func (e *Entry) GetPenMapList(pastureId int64) (map[int32]*model.Pen, error) {
 }
 
 // GetBeforeThreeDaysCowEstrus 获取值得时间之前三天内最大发情记录
-func (e *Entry) GetBeforeThreeDaysCowEstrus(cowId int64, activeTime string) *model.NeckRingEstrus {
+func (e *Entry) GetBeforeThreeDaysCowEstrus(pastureId, cowId int64, activeTime string) *model.NeckRingEstrus {
 	neckRingEstrus := &model.NeckRingEstrus{}
 	if err := e.DB.Model(new(model.NeckRingEstrus)).
 		Select("MAX(max_high) as max_high, cow_id, MAX(day_high) as day_high, MAX(IF(check_result=1,3,check_result)) AS check_result,active_time").
 		Where("cow_id = ?", cowId).
+		Where("pasture_id = ?", pastureId).
 		Where("active_time >= ?", activeTime).
 		First(neckRingEstrus).Error; err != nil {
 		return neckRingEstrus
@@ -247,7 +248,8 @@ func (e *Entry) GetMinIdByHeatDate(heatDate string, defaultId int64) (int64, err
 func (e *Entry) FindFilterData(pastureId int64, neckRingNumber, heatDate string, frameId int32) *FilterData {
 	firstFilterData := &FilterData{}
 	if err := e.DB.Model(new(model.NeckActiveHabit)).
-		Select("neck_ring_number", "filter_high", "filter_rumina", "filter_chew", "change_filter", "rumina_filter", "chew_filter").
+		Select("neck_ring_number", "filter_high", "filter_rumina", "filter_chew",
+			"change_filter", "rumina_filter", "chew_filter", "change_high").
 		Where("neck_ring_number = ?", neckRingNumber).
 		Where("heat_date = ?", heatDate).
 		Where("frameid = ?", frameId).
@@ -357,6 +359,7 @@ func (e *Entry) FindNeckRingEstrusByFirstTimeEmpty(pastureId int64) []*model.Nec
 		Where("pasture_id = ?", pastureId).
 		Find(&neckRingEstrusList).Error; err != nil {
 		zaplog.Error("FindNeckRingEstrusFirstTime", zap.Any("err", err))
+		return neckRingEstrusList
 	}
 	return neckRingEstrusList
 }

+ 131 - 0
service/httpclient/http.go

@@ -0,0 +1,131 @@
+package httpclient
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"time"
+
+	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
+	"gitee.com/xuyiping_admin/pkg/xerr"
+	"go.uber.org/zap"
+)
+
+type Service struct {
+	authClient *http.Client
+}
+
+func NewClientService() *Service {
+	return &Service{
+		authClient: &http.Client{
+			Timeout: time.Duration(60) * time.Second,
+		},
+	}
+}
+
+type Header struct {
+	Key   string `json:"key"`
+	Value string `json:"value"`
+}
+
+func (c *Service) doRequest(req *http.Request) ([]byte, error) {
+	resp, err := c.authClient.Do(req)
+	if err != nil {
+		var nErr net.Error
+		if errors.As(err, &nErr) && nErr.Timeout() {
+			for i := 0; i < 3; i++ {
+				time.Sleep(15 * time.Second)
+				resp, err = c.authClient.Do(req)
+				if err != nil {
+					zaplog.Error("ClientService", zap.Any("for", i), zap.Any("authClient.Do", err))
+					if i == 3 {
+						return nil, xerr.WithStack(err)
+					}
+				} else {
+					break
+				}
+			}
+		} else {
+			zaplog.Error("ClientService", zap.Any("authClient.Do", err))
+			return nil, xerr.WithStack(err)
+		}
+	}
+
+	defer resp.Body.Close()
+	b, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		zaplog.Error("ClientService", zap.Any("ioutil.ReadAll", err))
+		return nil, xerr.WithStack(err)
+	}
+	if resp.StatusCode != http.StatusOK {
+		if len(b) > 0 {
+			return nil, xerr.Customf("err:%v,body:%s", err, string(b))
+		} else {
+			return nil, xerr.Customf("err:%v", err)
+		}
+	}
+	return b, nil
+}
+
+func (c *Service) DoGet(url string, headers []*Header) ([]byte, error) {
+	req, err := http.NewRequest(http.MethodGet, url, nil)
+	if err != nil {
+		zaplog.Error("ClientService", zap.Any("DoGet", err))
+		return nil, err
+	}
+	req.Header.Add("Accept", "application/json")
+	req.Header.Add("Content-Type", "application/json")
+	if headers != nil {
+		for _, v := range headers {
+			req.Header.Add(v.Key, v.Value)
+		}
+	}
+	return c.doRequest(req)
+}
+
+func (c *Service) DoPut(url string, body interface{}, headers []*Header) ([]byte, error) {
+	b, err := json.Marshal(body)
+	if err != nil {
+		zaplog.Error("ClientService", zap.Any("DoPost-Marshal", err))
+		return nil, xerr.WithStack(err)
+	}
+
+	req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(b))
+	if err != nil {
+		zaplog.Error("ClientService", zap.Any("DoGet", err))
+		return nil, err
+	}
+	req.Header.Add("Accept", "application/json")
+	req.Header.Add("Content-Type", "application/json")
+	if headers != nil {
+		for _, v := range headers {
+			req.Header.Add(v.Key, v.Value)
+		}
+	}
+	return c.doRequest(req)
+}
+
+func (c *Service) DoPost(url string, body interface{}, headers []*Header) ([]byte, error) {
+	b, err := json.Marshal(body)
+	if err != nil {
+		zaplog.Error("ClientService", zap.Any("DoPost-Marshal", err))
+		return nil, xerr.WithStack(err)
+	}
+
+	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(b))
+	if err != nil {
+		zaplog.Error("ClientService", zap.Any("NewRequest", err))
+		return nil, xerr.WithStack(err)
+	}
+	req.Header.Add("Accept", "application/json")
+	req.Header.Add("Content-Type", "application/json")
+	if headers != nil {
+		for _, v := range headers {
+			req.Header.Add(v.Key, v.Value)
+		}
+	}
+	return c.doRequest(req)
+}

+ 20 - 0
service/httpclient/interface.go

@@ -0,0 +1,20 @@
+package httpclient
+
+import (
+	"kpt-pasture/config"
+	"net/http"
+
+	"gitee.com/xuyiping_admin/pkg/di"
+)
+
+type ClientService interface {
+	doRequest(req *http.Request) ([]byte, error)
+	DoGet(url string) ([]byte, error)
+	DoPost(url string, body interface{}) ([]byte, error)
+}
+
+var Module = di.Provide(NewService)
+
+func NewService(cfg *config.AppConfig) *Service {
+	return NewClientService()
+}

+ 4 - 3
service/wechat/http.go

@@ -3,12 +3,13 @@ package wechat
 import (
 	"bytes"
 	"encoding/json"
-	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
-	"gitee.com/xuyiping_admin/pkg/xerr"
 	"io/ioutil"
 	"net/http"
 	"time"
 
+	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
+	"gitee.com/xuyiping_admin/pkg/xerr"
+
 	"go.uber.org/zap"
 )
 
@@ -23,7 +24,7 @@ func NewClientService(appid, secret string) *ClientService {
 		AppID:  appid,
 		Secret: secret,
 		authClient: &http.Client{
-			Timeout: time.Duration(5) * time.Second,
+			Timeout: time.Duration(60) * time.Second,
 		},
 	}
 }

+ 7 - 0
util/util.go

@@ -159,6 +159,13 @@ func DateTimeParseLocalUnix(DayTime string) int64 {
 	return theTime.Unix()
 }
 
+// DateTimeParseLocalUnix2
+// eg 2025-10-13 15:04:05 => 1676998245
+func DateTimeParseLocalUnix2(DayTime string) time.Time {
+	theTime, _ := TimeParseLocal(LayoutTime, DayTime)
+	return theTime
+}
+
 // GetMonthRemainDay 获取当前月还剩几天
 func GetMonthRemainDay() int {
 	now := time.Now().Local()

+ 2 - 2
util/util_test.go

@@ -557,6 +557,6 @@ func Test_demo(t *testing.T) {
 		fmt.Println("2")
 	}*/
 
-	num := 0.0334345466563453467868989087765434213344544345678
-	fmt.Println(RoundToTwoDecimals(num))
+	a := float64(3) / 10
+	fmt.Println(a)
 }

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно